ci: sprint 37 sweep — asset pipeline discipline + decision coverage #136

Closed
jpmschweitzer wants to merge 0 commits from sprint-37/ci into main
Owner

Summary

Sprint 37 CI sweep. Seven tickets, all closed:

  • #854 (epic) — Generator-driven asset pipeline
  • #855 — systems.db regeneration with versioning awareness
  • #856meta table stamping (schema_version + generator_sha + generated_at)
  • #857 — Pre-push hook rejects stale systems.db via tooling/check-systems-db-stamp
  • #858/pr-push auto-runs make regen-db when generator sources changed
  • #859.claude/rules/asset-pipeline.md, CLAUDE.md hook, DEVOPS.md section
  • #723tooling/db/decision show <D-NNN> with implementing tickets + cross-refs; decisions-coverage now lists ticket IDs per decision

Motivation

Sprint 36 hit a binary merge conflict class when two branches both committed changes to server/data/systems.db. The meta stamp + pre-push hook + /pr-push regen form a systemic fix: the canonical rule is "edit sources, not the DB" and the tooling now enforces it.

Verification

  • make regen-db ran successfully; three generators stamped in meta table
  • make check-systems-db exit 0 — check-systems-db-stamp: OK — 3 generator(s) up to date
  • ruff check tooling/ — All checks passed
  • All 7 tickets moved to done in the DB

Test plan

  • make regen-db runs end-to-end on a clean clone
  • make check-systems-db exits 0 after a fresh regen
  • Attempt git push with a modified generator but stale DB — hook rejects
  • /pr-push on a branch with modified tooling/economy-db/import_economics.py auto-regenerates before pushing
  • tooling/db/decision show D-159 returns implementing tickets + refs

🤖 Generated with Claude Code

## Summary Sprint 37 CI sweep. Seven tickets, all closed: - **#854** (epic) — Generator-driven asset pipeline - **#855** — systems.db regeneration with versioning awareness - **#856** — `meta` table stamping (schema_version + generator_sha + generated_at) - **#857** — Pre-push hook rejects stale systems.db via `tooling/check-systems-db-stamp` - **#858** — `/pr-push` auto-runs `make regen-db` when generator sources changed - **#859** — `.claude/rules/asset-pipeline.md`, CLAUDE.md hook, DEVOPS.md section - **#723** — `tooling/db/decision show <D-NNN>` with implementing tickets + cross-refs; `decisions-coverage` now lists ticket IDs per decision ## Motivation Sprint 36 hit a binary merge conflict class when two branches both committed changes to `server/data/systems.db`. The meta stamp + pre-push hook + `/pr-push` regen form a systemic fix: the canonical rule is **"edit sources, not the DB"** and the tooling now enforces it. ## Verification - `make regen-db` ran successfully; three generators stamped in `meta` table - `make check-systems-db` exit 0 — `check-systems-db-stamp: OK — 3 generator(s) up to date` - `ruff check tooling/` — All checks passed - All 7 tickets moved to `done` in the DB ## Test plan - [ ] `make regen-db` runs end-to-end on a clean clone - [ ] `make check-systems-db` exits 0 after a fresh regen - [ ] Attempt `git push` with a modified generator but stale DB — hook rejects - [ ] `/pr-push` on a branch with modified `tooling/economy-db/import_economics.py` auto-regenerates before pushing - [ ] `tooling/db/decision show D-159` returns implementing tickets + refs 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jpmschweitzer added 5 commits 2026-04-21 17:40:55 +02:00
Adds systems.db regeneration discipline (#855) via a `meta` table (#856)
stamped by every generator, a pre-push hook that rejects stale DBs (#857),
and the top-level `make regen-db` / `make check-systems-db` targets that
drive the whole pipeline.

The stamp stores SHA-1 of generator source + schema, so the pre-push hook
can cheaply detect "you changed a generator but forgot to regen the DB"
before a binary merge conflict lands. Sprint 36 hit that class of conflict
on two branches touching systems.db simultaneously — this is the systemic
fix.

Regenerated systems.db is stamped; `make check-systems-db` passes.

Refs: #855 #856 #857

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a new step 4a that detects generator-source or economy-data changes
vs origin/main and runs `make regen-db` before the push, staging the
updated systems.db. Pairs with the pre-push hook (#857) — instead of
rejecting a stale DB, /pr-push proactively freshens it.

Refs: #858

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds `.claude/rules/asset-pipeline.md` describing the full pipeline —
generators, meta stamp, pre-push hook, how to make DB/schema changes,
and why direct systems.db edits are forbidden. CLAUDE.md and DEVOPS.md
point at the rules file; sprint-start template now reminds teammates
to read it before touching DB sources.

CLAUDE.md also gains the missing `decision show` row in the CLI tool
table (companion to #723's `decision show` subcommand).

Refs: #859 #723

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes the decision-to-ticket coverage gap (#723). The `decisions-coverage`
Makefile target already reported per-decision counts; this adds the
single-decision drill-down via `tooling/db/decision show D-159`, returning
linked tickets, outbound refs, and inbound refs in one shot.

Schema unchanged — reverse link is a SELECT on tickets.decision_ref.

Refs: #723

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refs: #723 #854 #855 #856 #857 #858 #859

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

Review: sprint-37/ci → main (PR #136)

Reviewers: Hoshe (code quality & tests) + Tyre (architecture).

Verdict: CHANGES REQUESTED

Source-canonical rule is a solid design — SHA-of-sources rather than DB-content hash sidesteps SQLite non-determinism cleanly, pre-push hook is the correct enforcement layer, /pr-push auto-regen is appropriately scoped. But 5 blocking items + several minor/suggestion items need addressing before merge.


Hoshe — Code Quality & Tests: REQUEST_CHANGES

Exit codes are correct, error messages actionable, schema coherent, show_decision handles zero-ticket cases cleanly, pre-push hook integrates without disrupting existing checks. Two real bugs + two nits.

# File:Line Issue Severity
H1 tooling/planet-gen/generate_atlas.py:~1383-1391 Double-commit bug. conn.commit() at 1384 persists atlas data, then _write_stamp(conn) issues its own conn.commit() at line 124. Inconsistent with import_economics._write_stamp which leaves transaction ownership to caller. Either remove the inner commit (match the economics pattern — preferred) or remove the outer commit. error
H2 tooling/check-systems-db-stamp:~48-54 (file_sha1) Silent ghost-SHA on missing source file. Skips missing paths (if p.exists(): h.update(...)). If a generator source file is moved/deleted, validator computes SHA-1 of zero bytes (always da39a3ee...). The stored SHA won't match → push correctly rejected, but the error message says "STALE" with no hint that the file is missing. Edge case: if both stored and current SHA converge on the empty hash, check passes vacuously. Add explicit "source file not found: " error with exit 1. warning
H3 tooling/check-systems-db-stamp:~32-37 (GENERATOR_SOURCES) generate_brands source is listed but no stamp writer in the Rust binary — it's stamped on behalf by import_economics._write_stamp. Pre-existing design constraint, not a regression, but should be called out in the meta table schema comment or a TODO so the next engineer doesn't add an independent stamp path that races. nit
H4 .config/hooks/pre-push (stamp section, last ~30 lines) rc=2 (no meta table) is advisory (intended grace for new DBs). But the warning message "run make regen-db after next regeneration" reads like a future action. Suggest: "systems.db has no meta stamp — run make regen-db now if this DB was generated by you." nit

Tyre — Architecture: REQUEST_CHANGES

The core design is elegant where it matters. SHA-of-sources (not hash-of-DB) is the right choice because it sidesteps SQLite's non-determinism (timestamps, autoincs, rowids). Pre-push is the correct enforcement layer. /pr-push auto-regen is scoped tight to generator-touching branches. But three cracks need patching, plus one Makefile failure-handling bug.

# File:Line Issue Severity
T1 .claude/rules/asset-pipeline.md:~95 ("Why direct DB edits are forbidden") Rule overstates determinism. States "Regeneration is deterministic (same sources → same DB, byte-for-byte)". Not true: meta.generated_at uses datetime('now'), SQLite rowids/autoinc aren't stable across runs. The stamp is deterministic, not the DB binary. The rule's strength doesn't depend on byte-determinism — so soften the claim to "the stamp is deterministic (same generator source → same recorded SHA)" and remove the byte-for-byte assertion. Otherwise reviewers expect binary-identical DBs after regen and are confused when diffs appear. major
T2 tooling/economy-db/import_economics.py:1194 + tooling/check-systems-db-stamp:37-43 generate_brands stamped on behalf by import_economics, not by its own Rust binary. Undocumented coupling in the rule's "Three generators write to systems.db" table. Two real failure modes: (a) someone runs tooling/generate-brands directly (the rule's documented invocation) without import_economics after it → SHA drifts from stamp; (b) brands source changes, economics doesn't → any workflow invoking only the Rust binary silently skips stamping. Fix: either make the Rust binary stamp itself (~30 LOC with rusqlite), or document the coupling explicitly and remove "on behalf" as an invisible convention. Hoshe also flagged this (H3). major
T3 .claude/rules/asset-pipeline.md (whole doc) Escape hatch not named. Rule says "never run UPDATE or INSERT directly on systems.db outside of a migration" but doesn't define when a migration is legitimate, who writes them, or where they live. MIGRATION_SQL in import_economics.py is the de-facto location — the rule should state that explicitly and forbid any other form of direct DB edit (no hand-run sqlite-exec, no one-off patches). Otherwise the rule has a loophole an operator can drive a truck through. major
T4 Makefile:357-366 (regen-db target) No inter-step failure handling. Three generators run sequentially but each recipe line is an independent shell — failures in step 1 don't halt step 2. If generate_brands (step 1) fails, import_economics still runs and stamps → DB with stale brand data and a "fresh" stamp. Add set -e semantics or || exit 1 after each step, or use a .SHELLFLAGS guard. major
T5 .config/hooks/pre-push:~155 Stamp check skipped on a branch's first push (no remote ref exists). Low-frequency corner case but real: developer creating a new branch with a stale DB gets no warning until the second push. One-line guard: run check-systems-db-stamp unconditionally when server/data/systems.db was modified in ANY branch commit, regardless of remote-ref state. Alternatively, document the gap. minor
T6 tooling/check-systems-db-stamp:61-65 Fail-open on unknown generators. Unknown generator_name in meta silently skipped ("forward compat"). Wrong default for a safety gate — a future branch adding a new generator without registering it here becomes a no-op check, silently. Warn loudly on unknown names, or require explicit allowlist bump. Fail closed. minor
T7 .claude/skills/pr-push/SKILL.md step 4a Watch list hardcodes generator paths (tooling/economy-db/import_economics.py, tooling/planet-gen/generate_atlas.py, server/src/bin/generate_brands/main.rs, etc.) plus content dirs. This will drift from GENERATOR_SOURCES in check-systems-db-stamp. Two sources of truth in a system whose purpose is source-canonical discipline is exactly what this PR is trying to prevent. Shell out to a registry script, or at minimum add mutual cross-reference comments. minor
T8 tooling/db/decisions_sync.py:417-457 (show_decision) Solid — reverse link via tickets.decision_ref is the right direction (no two-way sync). One note: decision_ref is free-text without a foreign key, so typo'd or deleted D-IDs silently don't show up anywhere. Not blocking; worth a follow-up decisions-orphan-tickets CLI to surface mismatches. suggestion
T9 server/data/systems-schema.sql (meta table) schema_version TEXT uses SHA-1 of the schema file. The rule doc names "savegame migration lineage" as a future use. When that arrives, a content-hash is harder to order than a monotonic version number — you cannot tell from two SHAs which is newer. Don't change now; flag for the migration phase: v1.0-style semver would serve lineage better. suggestion

Process note

PR test plan has 5 unchecked boxes:

  • make regen-db runs end-to-end on a clean clone
  • make check-systems-db exits 0 after a fresh regen
  • Attempt git push with modified generator but stale DB — hook rejects
  • /pr-push on a branch with modified generator auto-regenerates before pushing
  • tooling/db/decision show D-159 returns implementing tickets + refs

These are NOT merge-path per skill 0b-i (no pre-game/scene/save/connection code). No automatic verdict downgrade. But they ARE the pipeline's own smoke tests — the features this PR ships. Worth running before the next push so the verification is concrete, not aspirational.


Summary of blocking issues

5 error/major-severity fixes required:

  • H1 — Double-commit in generate_atlas._write_stamp (inconsistent with economics pattern)
  • T1 — Rule overstates byte-for-byte determinism (stamp is deterministic, DB is not)
  • T2generate_brands silently stamped "on behalf" by economics (also H3). Either self-stamp or document the coupling explicitly.
  • T3 — Escape hatch for legitimate direct DB writes (migrations) is absent from the rule. Name the sanctioned path.
  • T4Makefile:regen-db doesn't halt on generator failure → can produce stale-data-fresh-stamp state.

Other items are minor/suggestion; address before merge or push back with rationale.

# Review: sprint-37/ci → main (PR #136) Reviewers: **Hoshe** (code quality & tests) + **Tyre** (architecture). ## Verdict: CHANGES REQUESTED Source-canonical rule is a solid design — SHA-of-sources rather than DB-content hash sidesteps SQLite non-determinism cleanly, pre-push hook is the correct enforcement layer, `/pr-push` auto-regen is appropriately scoped. But 5 blocking items + several minor/suggestion items need addressing before merge. --- ## Hoshe — Code Quality & Tests: REQUEST_CHANGES Exit codes are correct, error messages actionable, schema coherent, `show_decision` handles zero-ticket cases cleanly, pre-push hook integrates without disrupting existing checks. Two real bugs + two nits. | # | File:Line | Issue | Severity | |---|-----------|-------|----------| | H1 | `tooling/planet-gen/generate_atlas.py:~1383-1391` | **Double-commit bug.** `conn.commit()` at 1384 persists atlas data, then `_write_stamp(conn)` issues its *own* `conn.commit()` at line 124. Inconsistent with `import_economics._write_stamp` which leaves transaction ownership to caller. Either remove the inner commit (match the economics pattern — preferred) or remove the outer commit. | **error** | | H2 | `tooling/check-systems-db-stamp:~48-54` (`file_sha1`) | **Silent ghost-SHA on missing source file.** Skips missing paths (`if p.exists(): h.update(...)`). If a generator source file is moved/deleted, validator computes SHA-1 of zero bytes (always `da39a3ee...`). The stored SHA won't match → push correctly rejected, but the error message says "STALE" with no hint that the file is missing. Edge case: if both stored and current SHA converge on the empty hash, check passes vacuously. Add explicit "source file not found: <path>" error with exit 1. | warning | | H3 | `tooling/check-systems-db-stamp:~32-37` (`GENERATOR_SOURCES`) | **`generate_brands` source is listed but no stamp writer in the Rust binary** — it's stamped on behalf by `import_economics._write_stamp`. Pre-existing design constraint, not a regression, but should be called out in the meta table schema comment or a `TODO` so the next engineer doesn't add an independent stamp path that races. | nit | | H4 | `.config/hooks/pre-push` (stamp section, last ~30 lines) | `rc=2` (no meta table) is advisory (intended grace for new DBs). But the warning message "run `make regen-db` after next regeneration" reads like a future action. Suggest: "systems.db has no meta stamp — run `make regen-db` now if this DB was generated by you." | nit | --- ## Tyre — Architecture: REQUEST_CHANGES The core design is elegant where it matters. SHA-of-sources (not hash-of-DB) is the right choice because it sidesteps SQLite's non-determinism (timestamps, autoincs, rowids). Pre-push is the correct enforcement layer. `/pr-push` auto-regen is scoped tight to generator-touching branches. But three cracks need patching, plus one Makefile failure-handling bug. | # | File:Line | Issue | Severity | |---|-----------|-------|----------| | T1 | `.claude/rules/asset-pipeline.md:~95` ("Why direct DB edits are forbidden") | **Rule overstates determinism.** States *"Regeneration is deterministic (same sources → same DB, byte-for-byte)"*. Not true: `meta.generated_at` uses `datetime('now')`, SQLite rowids/autoinc aren't stable across runs. The *stamp* is deterministic, not the DB binary. The rule's strength doesn't depend on byte-determinism — so soften the claim to "the stamp is deterministic (same generator source → same recorded SHA)" and remove the byte-for-byte assertion. Otherwise reviewers expect binary-identical DBs after regen and are confused when diffs appear. | **major** | | T2 | `tooling/economy-db/import_economics.py:1194` + `tooling/check-systems-db-stamp:37-43` | **`generate_brands` stamped on behalf by `import_economics`**, not by its own Rust binary. Undocumented coupling in the rule's "Three generators write to systems.db" table. Two real failure modes: (a) someone runs `tooling/generate-brands` directly (the rule's documented invocation) without `import_economics` after it → SHA drifts from stamp; (b) brands source changes, economics doesn't → any workflow invoking only the Rust binary silently skips stamping. Fix: either make the Rust binary stamp itself (~30 LOC with `rusqlite`), or document the coupling explicitly and remove "on behalf" as an invisible convention. Hoshe also flagged this (H3). | **major** | | T3 | `.claude/rules/asset-pipeline.md` (whole doc) | **Escape hatch not named.** Rule says "never run UPDATE or INSERT directly on systems.db **outside of a migration**" but doesn't define *when* a migration is legitimate, who writes them, or where they live. `MIGRATION_SQL` in `import_economics.py` is the de-facto location — the rule should state that explicitly and forbid any other form of direct DB edit (no hand-run `sqlite-exec`, no one-off patches). Otherwise the rule has a loophole an operator can drive a truck through. | **major** | | T4 | `Makefile:357-366` (`regen-db` target) | **No inter-step failure handling.** Three generators run sequentially but each recipe line is an independent shell — failures in step 1 don't halt step 2. If `generate_brands` (step 1) fails, `import_economics` still runs and stamps → DB with stale brand data and a "fresh" stamp. Add `set -e` semantics or `\|\| exit 1` after each step, or use a `.SHELLFLAGS` guard. | **major** | | T5 | `.config/hooks/pre-push:~155` | Stamp check skipped on a branch's first push (no remote ref exists). Low-frequency corner case but real: developer creating a new branch with a stale DB gets no warning until the second push. One-line guard: run `check-systems-db-stamp` unconditionally when `server/data/systems.db` was modified in ANY branch commit, regardless of remote-ref state. Alternatively, document the gap. | minor | | T6 | `tooling/check-systems-db-stamp:61-65` | **Fail-open on unknown generators.** Unknown `generator_name` in meta silently skipped ("forward compat"). Wrong default for a safety gate — a future branch adding a new generator without registering it here becomes a no-op check, silently. Warn loudly on unknown names, or require explicit allowlist bump. Fail closed. | minor | | T7 | `.claude/skills/pr-push/SKILL.md` step 4a | Watch list hardcodes generator paths (`tooling/economy-db/import_economics.py`, `tooling/planet-gen/generate_atlas.py`, `server/src/bin/generate_brands/main.rs`, etc.) plus content dirs. This will drift from `GENERATOR_SOURCES` in `check-systems-db-stamp`. Two sources of truth in a system whose purpose is source-canonical discipline is exactly what this PR is trying to prevent. Shell out to a registry script, or at minimum add mutual cross-reference comments. | minor | | T8 | `tooling/db/decisions_sync.py:417-457` (`show_decision`) | Solid — reverse link via `tickets.decision_ref` is the right direction (no two-way sync). One note: `decision_ref` is free-text without a foreign key, so typo'd or deleted D-IDs silently don't show up anywhere. Not blocking; worth a follow-up `decisions-orphan-tickets` CLI to surface mismatches. | suggestion | | T9 | `server/data/systems-schema.sql` (meta table) | `schema_version TEXT` uses SHA-1 of the schema file. The rule doc names "savegame migration lineage" as a future use. When that arrives, a content-hash is harder to order than a monotonic version number — you cannot tell from two SHAs which is newer. Don't change now; flag for the migration phase: `v1.0`-style semver would serve lineage better. | suggestion | --- ## Process note PR test plan has 5 unchecked boxes: - [ ] `make regen-db` runs end-to-end on a clean clone - [ ] `make check-systems-db` exits 0 after a fresh regen - [ ] Attempt `git push` with modified generator but stale DB — hook rejects - [ ] `/pr-push` on a branch with modified generator auto-regenerates before pushing - [ ] `tooling/db/decision show D-159` returns implementing tickets + refs These are NOT merge-path per skill 0b-i (no pre-game/scene/save/connection code). No automatic verdict downgrade. But they ARE the pipeline's own smoke tests — the features this PR ships. Worth running before the next push so the verification is concrete, not aspirational. --- ## Summary of blocking issues 5 `error`/`major`-severity fixes required: - **H1** — Double-commit in `generate_atlas._write_stamp` (inconsistent with economics pattern) - **T1** — Rule overstates byte-for-byte determinism (stamp is deterministic, DB is not) - **T2** — `generate_brands` silently stamped "on behalf" by economics (also H3). Either self-stamp or document the coupling explicitly. - **T3** — Escape hatch for legitimate direct DB writes (migrations) is absent from the rule. Name the sanctioned path. - **T4** — `Makefile:regen-db` doesn't halt on generator failure → can produce stale-data-fresh-stamp state. Other items are minor/suggestion; address before merge or push back with rationale.
jpmschweitzer added 2 commits 2026-04-22 08:39:19 +02:00
Addresses all blocking + minor items from PR #136 review.

Architectural change (T2/H3 — the review's main complaint):

  generate_brands was previously a separate Rust binary that produced a TOML
  artifact, with its stamp written "on behalf" by import_economics.py at the
  end of its own run.  Reviewers flagged the invisible coupling: two sources
  of truth in a system designed to have one, and no way to tell from the
  stamp that one "generator" was really a subroutine of the other.

  import_economics now invokes tooling/generate-brands as the first step of
  its main() flow, before opening its own DB connection.  The TOML artefact
  is still produced and still committed (useful for diff-review of brand
  changes), but there's now one pipeline owner.  The meta table carries two
  rows (import_economics, generate_atlas) not three; the Rust binary's
  source SHA folds into import_economics' stamp via IMPORT_ECONOMICS_SOURCES.
  A MIGRATION_SQL DELETE cleans up pre-merge DBs that still have the
  orphan generate_brands row.

Other review items addressed in-line:

  H1  generate_atlas._write_stamp no longer commits — transaction ownership
      stays with the caller (matches import_economics pattern).  Stamp +
      atlas data now commit atomically; a failed stamp rolls back the
      atlas data rather than leaving a stamp-missing-data intermediate.

  H2  _file_sha1 (in both import_economics, generate_atlas,
      check-systems-db-stamp) raises FileNotFoundError on missing sources
      instead of silently contributing an empty-bytes hash.  A ghost-SHA
      convergence could otherwise produce vacuous "fresh" passes.

  H4  pre-push no-meta-table warning rephrased — was "run after next
      regeneration", now "run now if this DB was generated by you".

  T1  asset-pipeline.md determinism claim softened: the stamp is
      deterministic (same source → same recorded SHA), the DB binary is
      not (generated_at + SQLite rowids/freelist churn).

  T3  asset-pipeline.md gains a "migration escape hatch" section naming
      MIGRATION_SQL in import_economics.py as the only sanctioned path
      for direct writes, and forbidding hand-run sqlite-exec / one-off
      patch scripts / SQLite-GUI edits.

  T4  Makefile regen-db now runs as a single shell with `set -e`.  A
      failure in one generator halts the pipeline immediately, preventing
      the "stale data, fresh stamp" state where a later step stamped a
      DB whose earlier step had failed.  import_economics' exit code 2
      (coverage gate warning) remains explicitly tolerated.

  T5  pre-push stamp check now runs on a branch's first push too —
      compares against origin/main instead of origin/$BRANCH, closing
      the gap where a new branch could ship a stale DB via the first push.

  T6  check-systems-db-stamp fails closed on unknown generator_names in
      meta — a future branch adding a new generator without registering
      it in GENERATOR_SOURCES will now be rejected, not silently skipped.

  T7  /pr-push watch list gains a mutual cross-reference comment with
      GENERATOR_SOURCES in check-systems-db-stamp, plus the missing
      names.rs source file, so the two lists cannot silently drift.

Follow-up tickets created:

  #887 T8  decisions-orphan-tickets CLI — surfaces tickets whose
           decision_ref points at a non-existent D-record.
  #888 T9  meta.schema_version monotonic semver — for savegame migration
           lineage in Phase 5+ (SHA comparison can't be ordered).

Verified:

  make regen-db end-to-end — OK
  make check-systems-db    — OK, 2 generator(s) up to date
  STALE detection          — OK, verified by touching generate_atlas.py
  /pr-push watch list      — OK, flags this branch's changed sources
  decision show D-159      — OK, structured output with tickets + refs

Refs: #855 #856 #857 #858 #859 PR #136

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Corrects the "three generators" wording after the brand pipeline merge.

Refs: PR #136

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

Review: sprint-37/ci → main (PR #136) — Round 2

Reviewers: Hoshe (code quality & tests) + Tyre (architecture).

Verdict: APPROVED

3 → 0 blockers across 2 rounds. All round-1 items correctly addressed. The T2 pivot (merging the brand pipeline into import_economics instead of documenting the stamp-on-behalf coupling) is architecture, not paper.


Hoshe — Code Quality & Tests: APPROVE

All 4 round-1 items fixed. H2 error-handling path is consistent across import_economics, generate_atlas, and check-systems-db-stamp. The H3 architectural pivot is clean.

Round-1 status

# Item Status
H1 generate_atlas._write_stamp double-commit FIXED — inner conn.commit() removed; caller wraps stamp + data in try/except with rollback. Data and stamp land atomically or not at all
H2 Silent ghost-SHA on missing source FIXED_file_sha1 now raises FileNotFoundError with formatted path. check() loop catches it and returns rc=1 with "BROKEN — {generator_name}: {exc}" on stderr. Not swallowed. Consistent across all three files
H3 Undocumented stamping coupling FIXED via pivotgenerate_brands no longer stamps at all. import_economics.regenerate_brands() shells out to the Rust binary as step zero of its main(), checks return code, raises _ImportAborted on non-zero exit (stderr printed before raising). IMPORT_ECONOMICS_SOURCES includes the Rust .rs files + the wrapper script. Migration DELETE FROM meta WHERE generator_name='generate_brands' cleans the orphan row on pre-PR DBs
H4 pre-push no-meta-table wording FIXED"pre-push: WARNING — systems.db has no meta stamp — run 'make regen-db' now if this DB was generated by you"

New observation (non-blocking)

The stamp-write failure path in import_economics (around line 1271) catches Exception, prints a warning, and continues — but does not roll back the data commit (which has already landed). Intentional: the stamp is advisory, and a missing-meta state is caught by check-systems-db-stamp as rc=2 (warn-not-block). That's a coherent deliberate choice. Worth a one-line comment "stamp failure is non-fatal; DB data is already committed" so the next reader doesn't wonder if the missing rollback is a bug. Not required before merge.


Tyre — Architecture: APPROVE

The T2 pivot is real architecture, not documentation papering over a smell. Single pipeline owner with one stamp row per meta entry; Rust source SHAs fold into IMPORT_ECONOMICS_SOURCES so any change to main.rs / names.rs / the wrapper invalidates the stamp correctly. All 7 addressable items fixed; 2 suggestions deferred to #887/#888.

Architectural deep-check on the pivot

  • Does import_economics.py get too big? Grew from 1170 → 1306 lines. Growth is MIGRATION_SQL expansion + a 30-line regenerate_brands() subprocess wrapper + the DELETE-orphan migration. The Python file doesn't absorb brand-generation logic — it shells out. The Rust binary is untouched.
  • Is the Rust binary still independently invokable? Yes — tooling/generate-brands works standalone for debug/experimentation. The regenerate_brands() docstring explicitly calls out the non-canonical-seed escape hatch ("callers that need non-canonical seeds must still invoke the wrapper directly — experimentation only"). The "must go through import_economics" rule is self-enforcing via the stamp: wrapper-only runs don't write a stamp, so the next stamp check catches the drift. That's the right level.
  • Migration DELETE idempotency on revert: DELETE FROM meta WHERE generator_name = 'generate_brands' runs on every import. Pull branch → regen → revert: orphan row stays gone because main's import_economics won't re-add it. Revert further to pre-PR state where the Makefile called generate-brands separately and the binary wrote its own stamp — yes, the row comes back, but that's the correct pre-PR behavior. Clean.

Round-1 status

# Status
T1 (determinism overclaim) FIXED — section renamed "What's deterministic / What's NOT deterministic", explicitly names generated_at, rowid drift, freelist reshuffling
T2 (stamped on behalf) FIXED via pivot — coupling eliminated, not documented
T3 (escape hatch unnamed) FIXED — "The migration escape hatch" section names MIGRATION_SQL + COLUMN_MIGRATIONS as the sanctioned path and explicitly forbids hand-run sqlite-exec, GUI edits, one-off patch scripts, and committing systems.db alone
T4 (Makefile failure halting) FIXEDset -e in a single shell, `ec=0; ...
T5 (first-push gap) FIXED — compares against origin/main (always exists) instead of origin/$BRANCH. No false-positive risk: the stamp check validates meta.generator_sha vs current source SHAs, so a DB-only commit with untouched generators passes cleanly
T6 (fail-open on unknowns) FIXED — unknown generator_name returns rc=1 with "Update GENERATOR_SOURCES in tooling/check-systems-db-stamp to register them before pushing" on stderr
T7 (watch-list drift) FIXED — mutual cross-reference comments exist in both places: GENERATOR_SOURCES comment says "Keep this list in sync with IMPORT_ECONOMICS_SOURCES"; the pr-push SKILL watch list cites T7 by name and lists all four generate_brands sources
T8 (decisions-orphan-tickets CLI) DEFERRED to #887
T9 (semver vs SHA) DEFERRED to #888

No new architectural concerns

Ship it.


Pre-merge action (not a reviewer issue)

PR test plan still has 5 unchecked boxes (pipeline smoke tests). The commit body reports manual verifications were done:

Verified:
  make regen-db end-to-end    — OK
  make check-systems-db       — OK, 2 generator(s) up to date
  STALE detection             — OK, verified by touching generate_atlas.py
  /pr-push watch list         — OK, flags this branch's changed sources
  decision show D-159         — OK, structured output with tickets + refs

Worth ticking those boxes in the PR description before merge so the evidence is recorded where reviewers expect it, matching the Sprint 36 retro "evidence over aspiration" norm. Not a gate.

Summary

APPROVED across both reviewers. 14 round-1 items total (4 Hoshe + 9 Tyre + 1 cross-reference) → 0 blockers in round 2. The pivot on T2/H3 is the structural win — fixing the coupling by removing it rather than documenting it.

# Review: sprint-37/ci → main (PR #136) — Round 2 Reviewers: **Hoshe** (code quality & tests) + **Tyre** (architecture). ## Verdict: APPROVED 3 → 0 blockers across 2 rounds. All round-1 items correctly addressed. The T2 pivot (merging the brand pipeline into `import_economics` instead of documenting the stamp-on-behalf coupling) is architecture, not paper. --- ## Hoshe — Code Quality & Tests: APPROVE All 4 round-1 items fixed. H2 error-handling path is consistent across `import_economics`, `generate_atlas`, and `check-systems-db-stamp`. The H3 architectural pivot is clean. ### Round-1 status | # | Item | Status | |---|------|--------| | H1 | `generate_atlas._write_stamp` double-commit | **FIXED** — inner `conn.commit()` removed; caller wraps stamp + data in `try/except` with rollback. Data and stamp land atomically or not at all | | H2 | Silent ghost-SHA on missing source | **FIXED** — `_file_sha1` now raises `FileNotFoundError` with formatted path. `check()` loop catches it and returns rc=1 with `"BROKEN — {generator_name}: {exc}"` on stderr. Not swallowed. Consistent across all three files | | H3 | Undocumented stamping coupling | **FIXED via pivot** — `generate_brands` no longer stamps at all. `import_economics.regenerate_brands()` shells out to the Rust binary as step zero of its `main()`, checks return code, raises `_ImportAborted` on non-zero exit (stderr printed before raising). `IMPORT_ECONOMICS_SOURCES` includes the Rust `.rs` files + the wrapper script. Migration `DELETE FROM meta WHERE generator_name='generate_brands'` cleans the orphan row on pre-PR DBs | | H4 | pre-push no-meta-table wording | **FIXED** — `"pre-push: WARNING — systems.db has no meta stamp — run 'make regen-db' now if this DB was generated by you"` | ### New observation (non-blocking) The stamp-write failure path in `import_economics` (around line 1271) catches `Exception`, prints a warning, and continues — but does not roll back the data commit (which has already landed). Intentional: the stamp is advisory, and a missing-meta state is caught by `check-systems-db-stamp` as rc=2 (warn-not-block). That's a coherent deliberate choice. Worth a one-line comment "stamp failure is non-fatal; DB data is already committed" so the next reader doesn't wonder if the missing rollback is a bug. Not required before merge. --- ## Tyre — Architecture: APPROVE The T2 pivot is real architecture, not documentation papering over a smell. Single pipeline owner with one stamp row per `meta` entry; Rust source SHAs fold into `IMPORT_ECONOMICS_SOURCES` so any change to `main.rs` / `names.rs` / the wrapper invalidates the stamp correctly. All 7 addressable items fixed; 2 suggestions deferred to #887/#888. ### Architectural deep-check on the pivot - **Does `import_economics.py` get too big?** Grew from 1170 → 1306 lines. Growth is `MIGRATION_SQL` expansion + a 30-line `regenerate_brands()` subprocess wrapper + the DELETE-orphan migration. The Python file doesn't absorb brand-generation logic — it shells out. The Rust binary is untouched. - **Is the Rust binary still independently invokable?** Yes — `tooling/generate-brands` works standalone for debug/experimentation. The `regenerate_brands()` docstring explicitly calls out the non-canonical-seed escape hatch ("callers that need non-canonical seeds must still invoke the wrapper directly — experimentation only"). The "must go through import_economics" rule is self-enforcing via the stamp: wrapper-only runs don't write a stamp, so the next stamp check catches the drift. That's the right level. - **Migration DELETE idempotency on revert:** `DELETE FROM meta WHERE generator_name = 'generate_brands'` runs on every import. Pull branch → regen → revert: orphan row stays gone because main's `import_economics` won't re-add it. Revert further to pre-PR state where the Makefile called `generate-brands` separately and the binary wrote its own stamp — yes, the row comes back, but that's the correct pre-PR behavior. Clean. ### Round-1 status | # | Status | |---|--------| | T1 (determinism overclaim) | **FIXED** — section renamed "What's deterministic / What's NOT deterministic", explicitly names `generated_at`, rowid drift, freelist reshuffling | | T2 (stamped on behalf) | **FIXED via pivot** — coupling eliminated, not documented | | T3 (escape hatch unnamed) | **FIXED** — "The migration escape hatch" section names `MIGRATION_SQL` + `COLUMN_MIGRATIONS` as the sanctioned path and explicitly forbids hand-run `sqlite-exec`, GUI edits, one-off patch scripts, and committing `systems.db` alone | | T4 (Makefile failure halting) | **FIXED** — `set -e` in a single shell, `ec=0; ... || ec=$?`, tolerates only exit 2 (`import_economics` coverage warning), halts on anything else. Comment cites the prior "stale data, fresh stamp" failure mode | | T5 (first-push gap) | **FIXED** — compares against `origin/main` (always exists) instead of `origin/$BRANCH`. No false-positive risk: the stamp check validates `meta.generator_sha` vs current source SHAs, so a DB-only commit with untouched generators passes cleanly | | T6 (fail-open on unknowns) | **FIXED** — unknown `generator_name` returns rc=1 with `"Update GENERATOR_SOURCES in tooling/check-systems-db-stamp to register them before pushing"` on stderr | | T7 (watch-list drift) | **FIXED** — mutual cross-reference comments exist in both places: `GENERATOR_SOURCES` comment says "Keep this list in sync with IMPORT_ECONOMICS_SOURCES"; the pr-push SKILL watch list cites T7 by name and lists all four `generate_brands` sources | | T8 (decisions-orphan-tickets CLI) | **DEFERRED** to #887 | | T9 (semver vs SHA) | **DEFERRED** to #888 | ### No new architectural concerns Ship it. --- ## Pre-merge action (not a reviewer issue) PR test plan still has 5 unchecked boxes (pipeline smoke tests). The commit body reports manual verifications were done: ``` Verified: make regen-db end-to-end — OK make check-systems-db — OK, 2 generator(s) up to date STALE detection — OK, verified by touching generate_atlas.py /pr-push watch list — OK, flags this branch's changed sources decision show D-159 — OK, structured output with tickets + refs ``` Worth ticking those boxes in the PR description before merge so the evidence is recorded where reviewers expect it, matching the Sprint 36 retro "evidence over aspiration" norm. Not a gate. ## Summary APPROVED across both reviewers. 14 round-1 items total (4 Hoshe + 9 Tyre + 1 cross-reference) → 0 blockers in round 2. The pivot on T2/H3 is the structural win — fixing the coupling by removing it rather than documenting it.
jpmschweitzer closed this pull request 2026-04-22 08:47:38 +02:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jpmschweitzer/settled-reach#136