refactor(tooling): T-1289 — economy-db becomes reach ledger

The sole generator of systems.db moves to tooling/domains/ledger/ and is
now `reach ledger import`. economy_import/ keeps its name (Rust comments in
server/src cite it); the entrypoint becomes service.py; schema_version.py
moves with the importer, which is where the version is defined.

The stamp survived the move, which is the thing that had to hold:

- generated_brands.toml is byte-identical (sha256 e748531…) before and after
- `reach check systems-db-stamp` reported STALE after the move (the registry
  saw it) and OK after the regen
- the dry-run carries every count and warning of the baseline transcript,
  and exit 2 — imported and stamped, coverage gate unmet — still reaches the
  caller through @command

`make regen-db` survives as a one-line delegate, per D-263's muscle-memory
clause: about fifty files name it, including the headers of generated wiki
TOMLs and the remedies the push gate prints. `make economy-db` is retired;
it ran `reach generate brands` before the import, which the import already
does as its first step.

economy_import.errors is reconciled as DOMAINS.md asked. ImportAborted stays
as internal rollback control flow and never reaches a caller; the service
converts it to a ReachError carrying the remedy.

regenerate_brands caught cargo_binary's ReachError, printed it and raised
ImportAborted, dropping the remedy. It runs before the import transaction
opens, so there is nothing to roll back — it now propagates.

Both sys.path bootstraps are gone; they existed only because the directory
was hyphenated. The step labels ran [1/10]…[10/13]…[17/19]; one 24-step
counter now drives the event phase and progress.

Stale pointers fixed on the way: MIGRATION_SQL has lived in
economy_import/migration.py since T-1067, but the asset-pipeline rule,
DEVOPS and the schema comments still sent readers to import_economics.py;
the rule and DEVOPS also still named the check scripts T-1281 retired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 16:18:59 +02:00
co-authored by Claude Opus 5.5
parent 668772075c
commit 23538d640f
35 changed files with 720 additions and 572 deletions
+58
View File
@@ -345,3 +345,61 @@ nothing prints but `console`, no `subprocess` outside `core/process`.
DONE 2026-09-23. reach atlas planet: ten verbs over the moved package; tooling/test_planet_router.py (in make test-tooling) fails if a router option drifts from its module''s argparse. Finishing the 2026-09-02 half-move found: (1) lazy in-function imports and all of sol_data/ still used bare sibling names that only resolved via sys.path hacks — generate/batch/sol-import would have failed on first globe render under reach; qualified, hacks removed. (2) 247 print() + a per-8KB-block stdout progress writer routed through console (report verbs -> out, progress -> event, throttled download progress). (3) Every error exit now raises ReachError with fix=. (4) TWO CHECKS THAT COULD NOT FAIL: batch --verify-determinism and import-provinces both exited 0 on detected failures; both now raise. (5) sol-import --body is action=append but the router took one value — now repeatable. (6) test_conformance only walked one level, so the nested group''s ten verbs were never checked; now recursive, proven against a mutant. Stray PNGs from the 2026-09-03 runaway test run (8 modified reliefmaps, 6 new heightmaps) parked in .cache/t1288-stray-pngs/, reliefmaps restored from HEAD. NOTE: the reliefmaps differed from the committed bake, i.e. the current renderer no longer reproduces committed reliefmap bytes — not investigated here.', NULL, '2026-09-23 14:07:42', '2026-09-23 14:07:42.675', '2026-09-23 14:07:42.675', NULL, 'b6c6d1e211eada353630e3946da96310', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65NSJ4RG6SJ12TQE78S6TKR', 'status', 'backlog', 'done', NULL, '2026-09-23 14:07:43', '2026-09-23 14:07:43.036', '2026-09-23 14:07:43.036', NULL, 'e3329ba1f64d0a63c635d4a9049d56b6', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65T2N1WAT491WY8SV736J3G', 'description', '`tooling/economy-db/` (17 files) + `schema_version.py` become `reach ledger`.
The name is deliberate and is the user''s call (D-263 amendment): reach verbs
mirror the game''s implant-app presentation patterns. `ledger` is the player-facing
component that will aggregate economics — browse markets, wealth, transactions —
the way `atlas` aggregates topography. The tooling verb takes the same name so the
CLI and the UI do not diverge into two vocabularies for one subject.
THIS IS THE HIGHEST-RISK PORT IN THE EPIC and the reason E5 (T-1252) exists as a
separate epic: `import_economics` is the SOLE generator of `server/data/systems.db`,
and its meta stamp is computed over a source-file registry. Two things bite:
1. `tooling/generator_sources.py` lists every stamped path. Moving a file without
updating the registry either breaks the import (missing path) or silently
changes the SHA. T-1286 already had to swap the retired `generate-brands`
wrapper for `core/process.py` there and regen. Expect the same.
2. `economy-db` is hyphenated, so it is NOT importable as a package. `brands.py`
currently bootstraps `sys.path` with REPO_ROOT to reach `tooling.core` — see
the comment there. That bootstrap should DISAPPEAR in this port, not be
copied; it exists only because the directory could not be imported. Overlaps
T-1272 (hyphen sweep) — do them together or in that order.
Every port of this domain ends with `make regen-db` + `reach check systems-db-stamp`
+ a commit of `server/data/systems.db`. `generated_brands.toml` must come back
byte-identical; if it does not, the port changed generator behaviour and that is
a bug, not a regen.
Standard port acceptance as on T-1281/T-1286.', '`tooling/economy-db/` (17 files) + `schema_version.py` become `reach ledger`.
The name is deliberate and is the user''s call (D-263 amendment): reach verbs
mirror the game''s implant-app presentation patterns. `ledger` is the player-facing
component that will aggregate economics — browse markets, wealth, transactions —
the way `atlas` aggregates topography. The tooling verb takes the same name so the
CLI and the UI do not diverge into two vocabularies for one subject.
THIS IS THE HIGHEST-RISK PORT IN THE EPIC and the reason E5 (T-1252) exists as a
separate epic: `import_economics` is the SOLE generator of `server/data/systems.db`,
and its meta stamp is computed over a source-file registry. Two things bite:
1. `tooling/generator_sources.py` lists every stamped path. Moving a file without
updating the registry either breaks the import (missing path) or silently
changes the SHA. T-1286 already had to swap the retired `generate-brands`
wrapper for `core/process.py` there and regen. Expect the same.
2. `economy-db` is hyphenated, so it is NOT importable as a package. `brands.py`
currently bootstraps `sys.path` with REPO_ROOT to reach `tooling.core` — see
the comment there. That bootstrap should DISAPPEAR in this port, not be
copied; it exists only because the directory could not be imported. Overlaps
T-1272 (hyphen sweep) — do them together or in that order.
Every port of this domain ends with `make regen-db` + `reach check systems-db-stamp`
+ a commit of `server/data/systems.db`. `generated_brands.toml` must come back
byte-identical; if it does not, the port changed generator behaviour and that is
a bug, not a regen.
Standard port acceptance as on T-1281/T-1286.
DONE 2026-09-23. tooling/economy-db/ + schema_version.py -> tooling/domains/ledger/ (economy_import/ kept by name, since server/src Rust comments cite economy_import/*.py; import_economics.py became service.py). reach ledger import [--db] [--dry-run] [--strict-specialization]. Evidence: generated_brands.toml sha256 identical before/after (e748531…); stamp gate reported STALE after the move and OK after regen; dry-run transcript carries every count and warning of the baseline; exit 2 (coverage gap, data committed) proven to relay through @command with a probe. Make: regen-db kept as a one-line delegate per D-263''s muscle-memory clause (~50 files name it, incl. generated TOML headers and gate remedies); economy-db retired (it ran generate brands, which the import already does first). Reconciled economy_import.errors (DOMAINS.md ask): ImportAborted stays internal rollback control flow, converted to ReachError at the service boundary. Also: regenerate_brands used to swallow cargo_binary''s ReachError into ImportAborted, dropping its remedy — now propagates (it runs before the transaction, nothing to roll back). Both sys.path bootstraps removed. Step labels were inconsistent ([1/10]..[10/13]..[17/19]); one 24-step counter now drives phase + progress. Stale doc pointers fixed while there: MIGRATION_SQL had lived in economy_import/migration.py since T-1067 but rules/DEVOPS still said import_economics.py; asset-pipeline rule and DEVOPS still named the check-systems-db script/target T-1281 retired.', NULL, '2026-09-23 14:18:49', '2026-09-23 14:18:49.681', '2026-09-23 14:18:49.681', NULL, '13300d7ea5bd79ae4857cd59399531a6', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65T2N1WAT491WY8SV736J3G', 'status', 'backlog', 'done', NULL, '2026-09-23 14:18:50', '2026-09-23 14:18:50.093', '2026-09-23 14:18:50.093', NULL, '63d24743b229b1544d62104c4e68ca66', 2) ON CONFLICT(hash) DO NOTHING;
+60
View File
@@ -501,3 +501,63 @@ nothing prints but `console`, no `subprocess` outside `core/process`.
DONE 2026-09-23. reach atlas planet: ten verbs over the moved package; tooling/test_planet_router.py (in make test-tooling) fails if a router option drifts from its module''s argparse. Finishing the 2026-09-02 half-move found: (1) lazy in-function imports and all of sol_data/ still used bare sibling names that only resolved via sys.path hacks — generate/batch/sol-import would have failed on first globe render under reach; qualified, hacks removed. (2) 247 print() + a per-8KB-block stdout progress writer routed through console (report verbs -> out, progress -> event, throttled download progress). (3) Every error exit now raises ReachError with fix=. (4) TWO CHECKS THAT COULD NOT FAIL: batch --verify-determinism and import-provinces both exited 0 on detected failures; both now raise. (5) sol-import --body is action=append but the router took one value — now repeatable. (6) test_conformance only walked one level, so the nested group''s ten verbs were never checked; now recursive, proven against a mutant. Stray PNGs from the 2026-09-03 runaway test run (8 modified reliefmaps, 6 new heightmaps) parked in .cache/t1288-stray-pngs/, reliefmaps restored from HEAD. NOTE: the reliefmaps differed from the committed bake, i.e. the current renderer no longer reproduces committed reliefmap bytes — not investigated here.', 'done', 'high', NULL, 'tooling', 'D-263', '2026-09-02 15:57:45.382', '2026-09-23 14:07:43.036', NULL, '2a78f8f8646d915600ed55c5dc5d0993', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06GCX60QRMD7KJ1TVWTR7FWK28', 'bug', '06FBPPMZNNEV052DBYYY3A897C', 'Planet renderer no longer reproduces committed reliefmap.png bytes (8 bodies differ on re-render)', 'Found during T-1288. A 2026-09-03 accidental generate run re-rendered bodies in GJ-1002, GJ-1116B, GJ-139, GJ-147, GJ-216A, GJ-234A, GJ-273, GJ-282B: every reliefmap.png came back a few hundred bytes different from HEAD, while GJ1002b''s heightmap.png came back byte-identical (so the simulation is deterministic — make test-tooling confirms — and the drift is in the reliefmap RENDER path or its PNG encoding). Copies of the re-rendered files are in .cache/t1288-stray-pngs/. Question to answer first: is this a visible change (renderer code evolved since the April bake) or encoder-level noise (zlib/Pillow version, metadata)? Pixel-diff one pair before anything else. If visible: decide whether committed reliefmaps should be re-baked. If noise: make the encode deterministic so a re-render is a no-op in git.', 'backlog', 'low', NULL, 'tooling', NULL, '2026-09-23 14:07:49.189', '2026-09-23 14:07:49.189', NULL, '215478973901c77cdd0e6b5c446ff19e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65T2N1WAT491WY8SV736J3G', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Port ledger — the economics pipeline under its UI name', '`tooling/economy-db/` (17 files) + `schema_version.py` become `reach ledger`.
The name is deliberate and is the user''s call (D-263 amendment): reach verbs
mirror the game''s implant-app presentation patterns. `ledger` is the player-facing
component that will aggregate economics — browse markets, wealth, transactions —
the way `atlas` aggregates topography. The tooling verb takes the same name so the
CLI and the UI do not diverge into two vocabularies for one subject.
THIS IS THE HIGHEST-RISK PORT IN THE EPIC and the reason E5 (T-1252) exists as a
separate epic: `import_economics` is the SOLE generator of `server/data/systems.db`,
and its meta stamp is computed over a source-file registry. Two things bite:
1. `tooling/generator_sources.py` lists every stamped path. Moving a file without
updating the registry either breaks the import (missing path) or silently
changes the SHA. T-1286 already had to swap the retired `generate-brands`
wrapper for `core/process.py` there and regen. Expect the same.
2. `economy-db` is hyphenated, so it is NOT importable as a package. `brands.py`
currently bootstraps `sys.path` with REPO_ROOT to reach `tooling.core` — see
the comment there. That bootstrap should DISAPPEAR in this port, not be
copied; it exists only because the directory could not be imported. Overlaps
T-1272 (hyphen sweep) — do them together or in that order.
Every port of this domain ends with `make regen-db` + `reach check systems-db-stamp`
+ a commit of `server/data/systems.db`. `generated_brands.toml` must come back
byte-identical; if it does not, the port changed generator behaviour and that is
a bug, not a regen.
Standard port acceptance as on T-1281/T-1286.
DONE 2026-09-23. tooling/economy-db/ + schema_version.py -> tooling/domains/ledger/ (economy_import/ kept by name, since server/src Rust comments cite economy_import/*.py; import_economics.py became service.py). reach ledger import [--db] [--dry-run] [--strict-specialization]. Evidence: generated_brands.toml sha256 identical before/after (e748531…); stamp gate reported STALE after the move and OK after regen; dry-run transcript carries every count and warning of the baseline; exit 2 (coverage gap, data committed) proven to relay through @command with a probe. Make: regen-db kept as a one-line delegate per D-263''s muscle-memory clause (~50 files name it, incl. generated TOML headers and gate remedies); economy-db retired (it ran generate brands, which the import already does first). Reconciled economy_import.errors (DOMAINS.md ask): ImportAborted stays internal rollback control flow, converted to ReachError at the service boundary. Also: regenerate_brands used to swallow cargo_binary''s ReachError into ImportAborted, dropping its remedy — now propagates (it runs before the transaction, nothing to roll back). Both sys.path bootstraps removed. Step labels were inconsistent ([1/10]..[10/13]..[17/19]); one 24-step counter now drives phase + progress. Stale doc pointers fixed while there: MIGRATION_SQL had lived in economy_import/migration.py since T-1067 but rules/DEVOPS still said import_economics.py; asset-pipeline rule and DEVOPS still named the check-systems-db script/target T-1281 retired.', 'backlog', 'high', NULL, 'tooling', 'D-263', '2026-09-02 16:16:28.431', '2026-09-23 14:18:49.681', NULL, '4690f4d637a68bb49840c7a2de30801a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65T2N1WAT491WY8SV736J3G', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Port ledger — the economics pipeline under its UI name', '`tooling/economy-db/` (17 files) + `schema_version.py` become `reach ledger`.
The name is deliberate and is the user''s call (D-263 amendment): reach verbs
mirror the game''s implant-app presentation patterns. `ledger` is the player-facing
component that will aggregate economics — browse markets, wealth, transactions —
the way `atlas` aggregates topography. The tooling verb takes the same name so the
CLI and the UI do not diverge into two vocabularies for one subject.
THIS IS THE HIGHEST-RISK PORT IN THE EPIC and the reason E5 (T-1252) exists as a
separate epic: `import_economics` is the SOLE generator of `server/data/systems.db`,
and its meta stamp is computed over a source-file registry. Two things bite:
1. `tooling/generator_sources.py` lists every stamped path. Moving a file without
updating the registry either breaks the import (missing path) or silently
changes the SHA. T-1286 already had to swap the retired `generate-brands`
wrapper for `core/process.py` there and regen. Expect the same.
2. `economy-db` is hyphenated, so it is NOT importable as a package. `brands.py`
currently bootstraps `sys.path` with REPO_ROOT to reach `tooling.core` — see
the comment there. That bootstrap should DISAPPEAR in this port, not be
copied; it exists only because the directory could not be imported. Overlaps
T-1272 (hyphen sweep) — do them together or in that order.
Every port of this domain ends with `make regen-db` + `reach check systems-db-stamp`
+ a commit of `server/data/systems.db`. `generated_brands.toml` must come back
byte-identical; if it does not, the port changed generator behaviour and that is
a bug, not a regen.
Standard port acceptance as on T-1281/T-1286.
DONE 2026-09-23. tooling/economy-db/ + schema_version.py -> tooling/domains/ledger/ (economy_import/ kept by name, since server/src Rust comments cite economy_import/*.py; import_economics.py became service.py). reach ledger import [--db] [--dry-run] [--strict-specialization]. Evidence: generated_brands.toml sha256 identical before/after (e748531…); stamp gate reported STALE after the move and OK after regen; dry-run transcript carries every count and warning of the baseline; exit 2 (coverage gap, data committed) proven to relay through @command with a probe. Make: regen-db kept as a one-line delegate per D-263''s muscle-memory clause (~50 files name it, incl. generated TOML headers and gate remedies); economy-db retired (it ran generate brands, which the import already does first). Reconciled economy_import.errors (DOMAINS.md ask): ImportAborted stays internal rollback control flow, converted to ReachError at the service boundary. Also: regenerate_brands used to swallow cargo_binary''s ReachError into ImportAborted, dropping its remedy — now propagates (it runs before the transaction, nothing to roll back). Both sys.path bootstraps removed. Step labels were inconsistent ([1/10]..[10/13]..[17/19]); one 24-step counter now drives phase + progress. Stale doc pointers fixed while there: MIGRATION_SQL had lived in economy_import/migration.py since T-1067 but rules/DEVOPS still said import_economics.py; asset-pipeline rule and DEVOPS still named the check-systems-db script/target T-1281 retired.', 'done', 'high', NULL, 'tooling', 'D-263', '2026-09-02 16:16:28.431', '2026-09-23 14:18:50.093', NULL, '579a9be46ac9f07e3b7a687fbe4772fe', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;