fix(ci): address PR #139 review — 5 issues + source list sync

1. decisions_sync.py: fix refs_created inflation (check rowcount),
   remove dead IntegrityError except block
2. Extract SCHEMA_VERSION to shared tooling/schema_version.py —
   both generators import from single source of truth
3. generate_atlas.py: narrow bare except to OperationalError +
   "duplicate column" check
4. check-systems-db-stamp: add cross-generator schema_version
   agreement assertion (defense-in-depth)
5. decision wrapper: add show + orphan-tickets to usage text
6. Add schema_version.py to all three source watch lists
   (GENERATOR_SOURCES, IMPORT_ECONOMICS_SOURCES, generate_atlas
   _write_stamp) — prevents silent staleness on version bump

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-02 10:29:24 +02:00
co-authored by Claude Opus 4.6
parent 7cc1306996
commit 873746ff8b
8 changed files with 59 additions and 30 deletions
+2 -2
View File
@@ -23,8 +23,8 @@ Two generators write to `systems.db`:
| Generator | Command | Source files (all contribute to the meta stamp SHA) |
|-----------|---------|--------------|
| `import_economics` | `python3 tooling/economy-db/import_economics.py` | `tooling/economy-db/import_economics.py` + the Rust brand binary sources it invokes: `server/src/bin/generate_brands/main.rs`, `server/src/bin/generate_brands/names.rs`, `tooling/generate-brands` |
| `generate_atlas` | `python3 tooling/planet-gen/generate_atlas.py --seed 42` | `tooling/planet-gen/generate_atlas.py` |
| `import_economics` | `python3 tooling/economy-db/import_economics.py` | `tooling/economy-db/import_economics.py` + the Rust brand binary sources it invokes: `server/src/bin/generate_brands/main.rs`, `server/src/bin/generate_brands/names.rs`, `tooling/generate-brands` + shared `tooling/schema_version.py` |
| `generate_atlas` | `python3 tooling/planet-gen/generate_atlas.py --seed 42` | `tooling/planet-gen/generate_atlas.py` + shared `tooling/schema_version.py` |
`import_economics` shells out to the Rust `generate_brands` binary as its first
step to refresh `wiki/economics/corporations/generated_brands.toml`, then reads
Binary file not shown.
+17
View File
@@ -45,9 +45,11 @@ GENERATOR_SOURCES: dict[str, list[Path]] = {
REPO_ROOT / "server" / "src" / "bin" / "generate_brands" / "main.rs",
REPO_ROOT / "server" / "src" / "bin" / "generate_brands" / "names.rs",
REPO_ROOT / "tooling" / "generate-brands",
REPO_ROOT / "tooling" / "schema_version.py",
],
"generate_atlas": [
REPO_ROOT / "tooling" / "planet-gen" / "generate_atlas.py",
REPO_ROOT / "tooling" / "schema_version.py",
],
}
@@ -96,7 +98,9 @@ def check(verbose: bool = False) -> int:
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.
@@ -137,6 +141,19 @@ def check(verbose: bool = False) -> int:
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: "
+6 -4
View File
@@ -1,8 +1,10 @@
#!/usr/bin/env bash
# Decision ID management — claim, query, and validate decision IDs.
# Usage:
# decision next [D|Q|R] Show next available ID
# decision claim <D|Q|R> <domain> [title] Claim next ID (reserves in DB)
# decision check-dupes Check for duplicate IDs in markdown
# decision sync Sync markdown -> DB
# decision sync Sync decisions/*.md into SQLite
# decision show <D-NNN> Show a decision with linked tickets + refs
# decision next [D|Q|R] Show next available ID
# decision claim <D|Q|R> <domain> [title] Claim next ID (reserves in DB)
# decision check-dupes Check for duplicate IDs in markdown
# decision orphan-tickets List tickets with invalid/missing decision_ref
exec python3 "$(dirname "$0")/decisions_sync.py" "$@"
+7 -9
View File
@@ -310,16 +310,14 @@ def sync(cfg):
)
continue
try:
conn.execute(
"""INSERT OR IGNORE INTO decision_refs
(source_id, target_id, ref_type, note)
VALUES (?, ?, ?, ?)""",
(d["id"], target_id, ref_type, note),
)
cur = conn.execute(
"""INSERT OR IGNORE INTO decision_refs
(source_id, target_id, ref_type, note)
VALUES (?, ?, ?, ?)""",
(d["id"], target_id, ref_type, note),
)
if cur.rowcount > 0:
refs_created += 1
except sqlite3.IntegrityError:
pass # duplicate ref, skip
conn.commit()
+5 -7
View File
@@ -34,6 +34,10 @@ from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
# Import shared schema version constant (#888) — single source of truth in tooling/schema_version.py
sys.path.insert(0, str(REPO_ROOT / "tooling"))
from schema_version import SCHEMA_VERSION # noqa: E402
DB_PATH = REPO_ROOT / "server" / "data" / "systems.db"
STAR_MAP = REPO_ROOT / "docs" / "design" / "star-map.json"
COMMODITIES_TOML = REPO_ROOT / "wiki" / "economics" / "commodities.toml"
@@ -51,13 +55,6 @@ GENERATE_BRANDS_NAMES_RS = REPO_ROOT / "server" / "src" / "bin" / "generate_bran
GENERATE_BRANDS_WRAPPER = REPO_ROOT / "tooling" / "generate-brands"
# Monotonic schema version — bump manually on any backwards-incompatible schema change.
# Stored in meta.schema_version so future savegame migration lineage can order snapshots.
# (SHA-1 hashes cannot be ordered; semver can.) The SHA is preserved in meta.schema_sha
# for tamper detection alongside the semver (#888).
SCHEMA_VERSION = "1.0.0"
def _file_sha1(*paths: Path) -> str:
"""Return SHA-1 hex of the concatenated content of one or more files.
@@ -83,6 +80,7 @@ IMPORT_ECONOMICS_SOURCES: tuple[Path, ...] = (
GENERATE_BRANDS_RS,
GENERATE_BRANDS_NAMES_RS,
GENERATE_BRANDS_WRAPPER,
REPO_ROOT / "tooling" / "schema_version.py",
)
+8 -8
View File
@@ -49,6 +49,10 @@ import yaml
from planet_simulation import simulate
# Import shared schema version constant (#888) — single source of truth in tooling/schema_version.py
sys.path.insert(0, str(REPO_ROOT / "tooling"))
from schema_version import SCHEMA_VERSION # noqa: E402
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
@@ -92,11 +96,6 @@ _ATLAS_SCHEMA_END_MARKER = "-- END ATLAS INDEX"
# Generator metadata stamp (#855, #856)
# ---------------------------------------------------------------------------
# Monotonic schema version — shared constant with import_economics.py (#888).
# Bump manually on any backwards-incompatible schema change.
SCHEMA_VERSION = "1.0.0"
def _file_sha1(*paths: Path) -> str:
"""Return SHA-1 hex of the concatenated content of one or more files.
@@ -126,7 +125,7 @@ def _write_stamp(conn: sqlite3.Connection) -> None:
a double-commit with the atlas data write that precedes it.
"""
schema_sha = _file_sha1(SYSTEMS_SCHEMA_PATH)
generator_sha = _file_sha1(Path(__file__))
generator_sha = _file_sha1(Path(__file__), REPO_ROOT / "tooling" / "schema_version.py")
conn.execute(
"""INSERT OR REPLACE INTO meta
(generator_name, schema_version, schema_sha, generator_sha, generated_at)
@@ -186,8 +185,9 @@ def ensure_atlas_schema(conn: sqlite3.Connection) -> None:
# Add schema_sha column to existing DBs that pre-date #888 (#888 migration).
try:
conn.execute("ALTER TABLE meta ADD COLUMN schema_sha TEXT")
except Exception:
pass # column already exists
except sqlite3.OperationalError as e:
if "duplicate column" not in str(e).lower():
raise
def _first_int(values, default: int = 0) -> int:
+14
View File
@@ -0,0 +1,14 @@
"""
Canonical systems.db schema version — shared by all generators (#888).
Bump SCHEMA_VERSION manually on any backwards-incompatible schema change
(column removed, type changed, FK constraint added, table dropped).
Additive changes (new nullable columns, new tables, new indexes) do not
require a bump.
Imported by:
tooling/economy-db/import_economics.py
tooling/planet-gen/generate_atlas.py
"""
SCHEMA_VERSION = "1.0.0"