import_economics.py 2,620 → 309 lines — a thin orchestrator keeping the exact CLI, single-transaction/rollback contract, and exit codes. The 16 import steps, MIGRATION_SQL, brands shell-out, validators, and stamp write now live in tooling/economy-db/economy_import/ (db, migration, economy, corporations, brands, bodies, atlas, specialization, traits, validators, stamp, paths, errors). Full type hints throughout. tooling/generator_sources.py replaces the triplicated source registry (importer / stamp checker / pr-process watch list — the skill now derives its list via --list). The registry stamps itself, and economy_import/ modules are globbed fail-closed, so a future module is stamped the moment it exists — closing the silently-weakened-stamp failure mode. Rider: connector config helpers centralized in tooling/db/common.py. Byte-identical behavior proven: full-import table dump diff EMPTY over 107,843 lines / 37 tables (volatile timestamp fields excluded); dry-run output parity; generated_brands.toml sha unchanged. make test-tooling PASS; ruff clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
161 lines
5.6 KiB
Python
161 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
check-systems-db-stamp — verify that server/data/systems.db is up to date.
|
|
|
|
Reads the meta table from systems.db and checks that the stored SHA-1 of each
|
|
generator's source file(s) matches the current file content on disk.
|
|
|
|
Exit codes:
|
|
0 — DB is stamped and all generator SHAs match current sources
|
|
1 — DB is stale, has an unknown generator, or references a missing source file
|
|
2 — DB does not have a meta table (treat as unstamped — run make regen-db)
|
|
|
|
Usage (called by .config/hooks/pre-push):
|
|
tooling/check-systems-db-stamp
|
|
|
|
Usage (interactive):
|
|
tooling/check-systems-db-stamp --verbose
|
|
|
|
Decision refs: #855 (generator versioning), #857 (pre-push hook)
|
|
"""
|
|
|
|
import re
|
|
import sqlite3
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# semver pattern: MAJOR.MINOR.PATCH (no pre-release or build metadata)
|
|
_SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$")
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
DB_PATH = REPO_ROOT / "server" / "data" / "systems.db"
|
|
|
|
# The generator-source registry and the SHA helper live in the shared module
|
|
# tooling/generator_sources.py (T-1067) — the single source of truth, also
|
|
# imported by the importer's stamp writer and consumed by /pr-process via
|
|
# `python3 tooling/generator_sources.py --list`.
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from generator_sources import GENERATOR_SOURCES, file_sha1 # noqa: E402
|
|
|
|
|
|
def check(verbose: bool = False) -> int:
|
|
"""Return exit code: 0 = fresh, 1 = stale, 2 = no meta table."""
|
|
if not DB_PATH.exists():
|
|
if verbose:
|
|
print(f"check-systems-db-stamp: {DB_PATH} not found — skipping check")
|
|
return 0
|
|
|
|
try:
|
|
conn = sqlite3.connect(str(DB_PATH))
|
|
rows = conn.execute(
|
|
"SELECT generator_name, schema_version, generator_sha FROM meta"
|
|
).fetchall()
|
|
conn.close()
|
|
except sqlite3.OperationalError:
|
|
# meta table does not exist
|
|
if verbose:
|
|
print("check-systems-db-stamp: no meta table — systems.db has not been stamped")
|
|
print(" Run: make regen-db")
|
|
return 2
|
|
|
|
if not rows:
|
|
if verbose:
|
|
print("check-systems-db-stamp: meta table is empty — systems.db has not been stamped")
|
|
print(" Run: make regen-db")
|
|
return 2
|
|
|
|
stale: list[str] = []
|
|
unknown: list[str] = []
|
|
bad_version: list[str] = []
|
|
seen_versions: dict[str, str] = {} # generator_name -> schema_version
|
|
for generator_name, schema_version, stored_sha in rows:
|
|
seen_versions[generator_name] = schema_version
|
|
# Validate schema_version is a semver string (#888).
|
|
# Old DBs may still carry a SHA-1 hex (40-char) — flag them as stale
|
|
# so the user knows to run make regen-db rather than getting a silent pass.
|
|
if not _SEMVER_RE.match(schema_version or ""):
|
|
bad_version.append(
|
|
f"{generator_name}: schema_version='{schema_version}' "
|
|
f"(expected semver like '1.0.0' — run make regen-db)"
|
|
)
|
|
|
|
sources = GENERATOR_SOURCES.get(generator_name)
|
|
if sources is None:
|
|
# Unknown generator — fail closed (T6). A future branch adding a
|
|
# new generator without registering it here must update this map
|
|
# before the check will pass, preventing the "silent no-op" trap.
|
|
unknown.append(generator_name)
|
|
continue
|
|
try:
|
|
current_sha = file_sha1(*sources)
|
|
except FileNotFoundError as exc:
|
|
# Source file moved/deleted — explicit failure instead of
|
|
# silent empty-hash (H2).
|
|
print(
|
|
f"check-systems-db-stamp: BROKEN — {generator_name}: {exc}",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
if current_sha != stored_sha:
|
|
stale.append(generator_name)
|
|
if verbose:
|
|
print(
|
|
f"check-systems-db-stamp: STALE — {generator_name}"
|
|
f"\n stored: {stored_sha}"
|
|
f"\n current: {current_sha}"
|
|
)
|
|
|
|
if bad_version:
|
|
for msg in bad_version:
|
|
print(f"check-systems-db-stamp: BAD schema_version — {msg}", file=sys.stderr)
|
|
return 1
|
|
|
|
# All generators must agree on the same schema_version (#888 defense-in-depth).
|
|
# If they differ, the DB was partially regenerated with different source trees.
|
|
unique_versions = set(seen_versions.values())
|
|
if len(unique_versions) > 1:
|
|
print(
|
|
"check-systems-db-stamp: CONFLICT — generators disagree on schema_version:",
|
|
file=sys.stderr,
|
|
)
|
|
for gen, ver in sorted(seen_versions.items()):
|
|
print(f" {gen}: {ver}", file=sys.stderr)
|
|
print(" Run: make regen-db", file=sys.stderr)
|
|
return 1
|
|
|
|
if unknown:
|
|
print(
|
|
"check-systems-db-stamp: UNKNOWN generator(s) in meta table: "
|
|
f"{unknown}",
|
|
file=sys.stderr,
|
|
)
|
|
print(
|
|
" Update GENERATOR_SOURCES in tooling/generator_sources.py to "
|
|
"register them before pushing.",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
if stale:
|
|
if not verbose:
|
|
print(
|
|
"systems.db is stale — run `make regen-db` before pushing.",
|
|
file=sys.stderr,
|
|
)
|
|
print(f" Stale generators: {stale}", file=sys.stderr)
|
|
return 1
|
|
|
|
if verbose:
|
|
print(f"check-systems-db-stamp: OK — {len(rows)} generator(s) up to date")
|
|
return 0
|
|
|
|
|
|
def main() -> None:
|
|
verbose = "--verbose" in sys.argv or "-v" in sys.argv
|
|
sys.exit(check(verbose=verbose))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|