feat(config): T-1285 — the atlas authoring verbs, and a verify nobody checked

reach atlas db / names / systems-done / check / verify / commit-and-sync /
update-field / flatness. Eight scripts retired, five of them bash. verify
reproduces the original exactly: 2 errors across 301 proposals, exit 1.

The binary has more verbs than its wrapper documented. The bash usage text
listed four; atlas-commit-and-sync calls four more it never mentioned. All
eight are declared so reach atlas --help is a complete index, and unknown verbs
are still forwarded — a hand-maintained list falls behind the binary it
describes, so rejecting on it would break the day someone adds a subcommand.

commit-and-sync now stages by default and commits only with --commit. Nothing
else in reach writes to git history, and committing as a side effect of "sync"
is a different risk class from writing a file; the default prints the message
it would use, leaving the decision where it was.

Two real bugs found in that script while porting it. It ran atlas-verify and
never checked the exit code, so a proposal that FAILED verification was still
wiped, committed and synced — bad data in systems.db is far harder to undo than
a failed command, and it now refuses. And it hardcoded a pinned
"Co-Authored-By: Claude Opus 4.6" into every atlas commit, which the git-commit
skill names as the root cause of attribution drift.

update-field gains two guards the original lacked. Its field→table map lived
inside a bash heredoc string where nothing could check it, and an unknown field
produced an UPDATE against a table of None; it now names the nine accepted
fields. And it checks rowcount, so a system_id that does not exist is a failure
rather than a silent no-op reported as success.

The three Python scripts moved with the usual treatment — prints to console
events, argparse replaced by typed functions, __file__ roots to
config.repo_root(). No root bug this time: checked before moving rather than
after, three domains running.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-02 14:36:51 +02:00
co-authored by Claude Opus 5
parent 6daaa2235f
commit b1b57d603f
18 changed files with 579 additions and 302 deletions
+27
View File
@@ -39,3 +39,30 @@ sys.exit REMOVED from diff.py and thumbnail.py — four sites. A service must no
CALLERS REWIRED, and this is the wider blast radius than the make targets: tests/run-visual invoked visual-blank-check, visual-thumbnail and visual-diff by path at four sites, and the pre-push hook invoked godot-parse-sweep. All now call reach --no-input. .claude/settings.json loses the godot-cold-parse entry; Bash(reach *) covers it.', NULL, '2026-09-02 11:59:14', '2026-09-02 11:59:14.517', '2026-09-02 11:59:14.517', NULL, '9b3c9ad13e13e0ed35f1b856e53341b6', 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 ('06G5GWEC9P1HNABW6S0BSNTZ2W', 'status', 'in_progress', 'done', NULL, '2026-09-02 11:59:14', '2026-09-02 11:59:14.536', '2026-09-02 11:59:14.536', NULL, '74c5a448237be06cfc9b1b0697f059f2', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'description', 'The eight top-level atlas-* scripts become FLAT verbs on reach atlas: the Rust binary wrapper (stats, show-system, list-bodies, populate), atlas-check, atlas-verify, atlas-names, atlas-systems-done, atlas-update-field, atlas-commit-and-sync, atlas-flatness. Flat rather than nested because these act on the atlas as a whole; the nested groups are for GENERATION per rung (reach atlas map, reach atlas planet), which are separate tickets. Five of the eight are BASH, so those are rewrites: atlas 24, atlas-names 17, atlas-systems-done 9, atlas-update-field 59, atlas-commit-and-sync 60. Standard port acceptance as on T-1281. THREE THINGS NEEDING A DECISION OR CARE, each recorded rather than assumed: (1) the Rust binary — tooling/atlas builds server/target/debug/atlas if missing then execs it with all args, so its verbs live in Rust not in the shell; declare the four known verbs in the router so reach --help stays a complete index, and keep a passthrough for anything the binary gains later. (2) atlas-commit-and-sync MAKES GIT COMMITS, which is a risk class nothing else in reach has; port it to stage and report by default with committing behind an explicit flag, and say so in the verb help rather than changing behaviour silently. (3) atlas-update-field MUTATES systems.db directly, which .claude/rules/asset-pipeline.md forbids outside a migration — establish whether it predates that rule or is a sanctioned exception BEFORE porting, because the port must not launder a violation into a first-class verb.', 'The eight top-level atlas-* scripts become FLAT verbs on reach atlas: the Rust binary wrapper (stats, show-system, list-bodies, populate), atlas-check, atlas-verify, atlas-names, atlas-systems-done, atlas-update-field, atlas-commit-and-sync, atlas-flatness. Flat rather than nested because these act on the atlas as a whole; the nested groups are for GENERATION per rung (reach atlas map, reach atlas planet), which are separate tickets. Five of the eight are BASH, so those are rewrites: atlas 24, atlas-names 17, atlas-systems-done 9, atlas-update-field 59, atlas-commit-and-sync 60. Standard port acceptance as on T-1281. THREE THINGS NEEDING A DECISION OR CARE, each recorded rather than assumed: (1) the Rust binary — tooling/atlas builds server/target/debug/atlas if missing then execs it with all args, so its verbs live in Rust not in the shell; declare the four known verbs in the router so reach --help stays a complete index, and keep a passthrough for anything the binary gains later. (2) atlas-commit-and-sync MAKES GIT COMMITS, which is a risk class nothing else in reach has; port it to stage and report by default with committing behind an explicit flag, and say so in the verb help rather than changing behaviour silently. (3) atlas-update-field MUTATES systems.db directly, which .claude/rules/asset-pipeline.md forbids outside a migration — establish whether it predates that rule or is a sanctioned exception BEFORE porting, because the port must not launder a violation into a first-class verb.
RESOLVED BEFORE PORTING, item (3): atlas-update-field is NOT the asset-pipeline violation it resembles. The rule in .claude/rules/asset-pipeline.md forbids raw SQL against systems.db because ''any changes you make are silently reverted by the next regen-db run''. That reasoning does not apply here. Checked: import_economics touches only currency_zone and gate_energy_connected ON star_systems — it does not recreate the table. The fields this script edits (star_type, spectral_class, proper_name, geographic_sector, habitable_planet_count, plus system_gates fields) originate from the ONE-TIME BAKED imports the same rule describes separately: ''the surviving planet-gen importers are one-time build imports baked into the committed DB — not part of make regen-db''. So these edits persist; there is no regen path to route them through, and update-field is the sanctioned way to amend baked atlas data rather than a bypass of a generator. PORT IT, and carry that distinction in the verb''s help so the next reader does not see raw SQL against systems.db and delete it on sight. The genuine residual concern is different and worth stating: a value amended this way lives only in a committed binary, so it cannot be regenerated and does not show in a diff. That is a property of the baked-import design, not of this verb, and belongs to whoever revisits that design.', NULL, '2026-09-02 12:15:55', '2026-09-02 12:15:55.644', '2026-09-02 12:15:55.644', NULL, '6b53b083d627fbc35377d7cc05be9fe0', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'status', 'backlog', 'in_progress', NULL, '2026-09-02 12:16:58', '2026-09-02 12:16:58.892', '2026-09-02 12:16:58.892', NULL, '460d312c78e1e3480a8b274c1ddc627e', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'status', 'in_progress', 'in_progress', NULL, '2026-09-02 12:17:04', '2026-09-02 12:17:04.590', '2026-09-02 12:17:04.590', NULL, '6696dd2b194183fe4c7c323f2f0dad87', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'description', 'The eight top-level atlas-* scripts become FLAT verbs on reach atlas: the Rust binary wrapper (stats, show-system, list-bodies, populate), atlas-check, atlas-verify, atlas-names, atlas-systems-done, atlas-update-field, atlas-commit-and-sync, atlas-flatness. Flat rather than nested because these act on the atlas as a whole; the nested groups are for GENERATION per rung (reach atlas map, reach atlas planet), which are separate tickets. Five of the eight are BASH, so those are rewrites: atlas 24, atlas-names 17, atlas-systems-done 9, atlas-update-field 59, atlas-commit-and-sync 60. Standard port acceptance as on T-1281. THREE THINGS NEEDING A DECISION OR CARE, each recorded rather than assumed: (1) the Rust binary — tooling/atlas builds server/target/debug/atlas if missing then execs it with all args, so its verbs live in Rust not in the shell; declare the four known verbs in the router so reach --help stays a complete index, and keep a passthrough for anything the binary gains later. (2) atlas-commit-and-sync MAKES GIT COMMITS, which is a risk class nothing else in reach has; port it to stage and report by default with committing behind an explicit flag, and say so in the verb help rather than changing behaviour silently. (3) atlas-update-field MUTATES systems.db directly, which .claude/rules/asset-pipeline.md forbids outside a migration — establish whether it predates that rule or is a sanctioned exception BEFORE porting, because the port must not launder a violation into a first-class verb.
RESOLVED BEFORE PORTING, item (3): atlas-update-field is NOT the asset-pipeline violation it resembles. The rule in .claude/rules/asset-pipeline.md forbids raw SQL against systems.db because ''any changes you make are silently reverted by the next regen-db run''. That reasoning does not apply here. Checked: import_economics touches only currency_zone and gate_energy_connected ON star_systems — it does not recreate the table. The fields this script edits (star_type, spectral_class, proper_name, geographic_sector, habitable_planet_count, plus system_gates fields) originate from the ONE-TIME BAKED imports the same rule describes separately: ''the surviving planet-gen importers are one-time build imports baked into the committed DB — not part of make regen-db''. So these edits persist; there is no regen path to route them through, and update-field is the sanctioned way to amend baked atlas data rather than a bypass of a generator. PORT IT, and carry that distinction in the verb''s help so the next reader does not see raw SQL against systems.db and delete it on sight. The genuine residual concern is different and worth stating: a value amended this way lives only in a committed binary, so it cannot be regenerated and does not show in a diff. That is a property of the baked-import design, not of this verb, and belongs to whoever revisits that design.', 'The eight top-level atlas-* scripts become FLAT verbs on reach atlas: the Rust binary wrapper (stats, show-system, list-bodies, populate), atlas-check, atlas-verify, atlas-names, atlas-systems-done, atlas-update-field, atlas-commit-and-sync, atlas-flatness. Flat rather than nested because these act on the atlas as a whole; the nested groups are for GENERATION per rung (reach atlas map, reach atlas planet), which are separate tickets. Five of the eight are BASH, so those are rewrites: atlas 24, atlas-names 17, atlas-systems-done 9, atlas-update-field 59, atlas-commit-and-sync 60. Standard port acceptance as on T-1281. THREE THINGS NEEDING A DECISION OR CARE, each recorded rather than assumed: (1) the Rust binary — tooling/atlas builds server/target/debug/atlas if missing then execs it with all args, so its verbs live in Rust not in the shell; declare the four known verbs in the router so reach --help stays a complete index, and keep a passthrough for anything the binary gains later. (2) atlas-commit-and-sync MAKES GIT COMMITS, which is a risk class nothing else in reach has; port it to stage and report by default with committing behind an explicit flag, and say so in the verb help rather than changing behaviour silently. (3) atlas-update-field MUTATES systems.db directly, which .claude/rules/asset-pipeline.md forbids outside a migration — establish whether it predates that rule or is a sanctioned exception BEFORE porting, because the port must not launder a violation into a first-class verb.
RESOLVED BEFORE PORTING, item (3): atlas-update-field is NOT the asset-pipeline violation it resembles. The rule in .claude/rules/asset-pipeline.md forbids raw SQL against systems.db because ''any changes you make are silently reverted by the next regen-db run''. That reasoning does not apply here. Checked: import_economics touches only currency_zone and gate_energy_connected ON star_systems — it does not recreate the table. The fields this script edits (star_type, spectral_class, proper_name, geographic_sector, habitable_planet_count, plus system_gates fields) originate from the ONE-TIME BAKED imports the same rule describes separately: ''the surviving planet-gen importers are one-time build imports baked into the committed DB — not part of make regen-db''. So these edits persist; there is no regen path to route them through, and update-field is the sanctioned way to amend baked atlas data rather than a bypass of a generator. PORT IT, and carry that distinction in the verb''s help so the next reader does not see raw SQL against systems.db and delete it on sight. The genuine residual concern is different and worth stating: a value amended this way lives only in a committed binary, so it cannot be regenerated and does not show in a diff. That is a property of the baked-import design, not of this verb, and belongs to whoever revisits that design.
DONE 2026-09-02. reach atlas db / names / systems-done / check / verify / commit-and-sync / update-field / flatness. Five bash scripts and three Python ones retired; make atlas-verify and the tooling/atlas permission entry gone.
PARITY: reach atlas verify reproduces the original exactly — 2 errors across 301 proposals, exit 1. names and systems-done both exercised against the live DB through the Rust binary.
THE BINARY HAS MORE VERBS THAN ITS WRAPPER DOCUMENTED. The bash usage text listed stats, show-system, list-bodies, populate. atlas-commit-and-sync calls four more the wrapper never mentioned: list-stations, wipe-system, commit-system, sync-wiki. All eight are declared in binary.KNOWN_VERBS so reach atlas --help is a complete index, AND unknown verbs are still forwarded — a hand-maintained list falls behind the binary it describes, so rejecting on it would break the day someone adds a Rust subcommand.
COMMIT-AND-SYNC NOW STAGES BY DEFAULT, committing only with --commit, per the ticket. The original always committed. Nothing else in reach writes to git history, and a tool that commits as a side effect of "sync" is a different risk class from one that writes a file. The default prints the message it WOULD use, so the decision stays where it was rather than being made silently.
Also fixed while there: the original hardcoded "Co-Authored-By: Claude Opus 4.6 (1M context)" into every atlas commit — a pinned model version, which .claude/skills/git-commit names as the root cause of attribution drift. The trailer is dropped; the harness appends its own.
And a real ordering improvement: the original verified the proposal but did not stop on failure — verify''s exit code was never checked, so a proposal that failed verification was still wiped, committed and synced. It now refuses, because bad data in systems.db is far harder to undo than a failed command.
UPDATE-FIELD ported with the asset-pipeline distinction in its help, per the pre-port resolution. The FIELD_TABLE map that lived inside a bash heredoc string — where nothing could check it against the schema — is a module-level dict now, and an unknown field raises unknown_choice, so it names the nine accepted fields instead of silently updating nothing. The original would have run an UPDATE against a table of None.
ALSO CAUGHT: update_field now checks rowcount and fails when no row matched. The original''s SQL would succeed silently against a system_id that does not exist — a no-op reported as success, which is the exact shape of failure this initiative keeps finding.
THE THREE PYTHON SCRIPTS moved with the usual treatment: prints to console events, argparse mains replaced by typed functions the router calls, __file__-relative roots to config.repo_root(). No root bug this time — checked before moving rather than after, which is the third domain running.', NULL, '2026-09-02 12:36:36', '2026-09-02 12:36:36.194', '2026-09-02 12:36:36.194', NULL, 'd804c766e461d4aaf6cdf4d8d1157125', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'status', 'in_progress', 'done', NULL, '2026-09-02 12:36:36', '2026-09-02 12:36:36.208', '2026-09-02 12:36:36.208', NULL, '8aa91d6e9cacaaa410b20ef6cf9703b9', 2) ON CONFLICT(hash) DO NOTHING;
+47
View File
@@ -75,3 +75,50 @@ sys.exit REMOVED from diff.py and thumbnail.py — four sites. A service must no
CALLERS REWIRED, and this is the wider blast radius than the make targets: tests/run-visual invoked visual-blank-check, visual-thumbnail and visual-diff by path at four sites, and the pre-push hook invoked godot-parse-sweep. All now call reach --no-input. .claude/settings.json loses the godot-cold-parse entry; Bash(reach *) covers it.', 'done', 'medium', NULL, NULL, 'D-263', '2026-08-31 15:30:48.013', '2026-09-02 11:59:14.536', NULL, 'a3a0b27a427faca6f2c47ae6a9a6f9a5', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Port the atlas domain — the authoring and inspection verbs', 'The eight top-level atlas-* scripts become FLAT verbs on reach atlas: the Rust binary wrapper (stats, show-system, list-bodies, populate), atlas-check, atlas-verify, atlas-names, atlas-systems-done, atlas-update-field, atlas-commit-and-sync, atlas-flatness. Flat rather than nested because these act on the atlas as a whole; the nested groups are for GENERATION per rung (reach atlas map, reach atlas planet), which are separate tickets. Five of the eight are BASH, so those are rewrites: atlas 24, atlas-names 17, atlas-systems-done 9, atlas-update-field 59, atlas-commit-and-sync 60. Standard port acceptance as on T-1281. THREE THINGS NEEDING A DECISION OR CARE, each recorded rather than assumed: (1) the Rust binary — tooling/atlas builds server/target/debug/atlas if missing then execs it with all args, so its verbs live in Rust not in the shell; declare the four known verbs in the router so reach --help stays a complete index, and keep a passthrough for anything the binary gains later. (2) atlas-commit-and-sync MAKES GIT COMMITS, which is a risk class nothing else in reach has; port it to stage and report by default with committing behind an explicit flag, and say so in the verb help rather than changing behaviour silently. (3) atlas-update-field MUTATES systems.db directly, which .claude/rules/asset-pipeline.md forbids outside a migration — establish whether it predates that rule or is a sanctioned exception BEFORE porting, because the port must not launder a violation into a first-class verb.', 'backlog', 'high', NULL, NULL, 'D-263', '2026-09-02 12:14:48.290', '2026-09-02 12:14:48.290', NULL, 'cf4a2315117474f165f1e689d6271300', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Port the atlas domain — the authoring and inspection verbs', 'The eight top-level atlas-* scripts become FLAT verbs on reach atlas: the Rust binary wrapper (stats, show-system, list-bodies, populate), atlas-check, atlas-verify, atlas-names, atlas-systems-done, atlas-update-field, atlas-commit-and-sync, atlas-flatness. Flat rather than nested because these act on the atlas as a whole; the nested groups are for GENERATION per rung (reach atlas map, reach atlas planet), which are separate tickets. Five of the eight are BASH, so those are rewrites: atlas 24, atlas-names 17, atlas-systems-done 9, atlas-update-field 59, atlas-commit-and-sync 60. Standard port acceptance as on T-1281. THREE THINGS NEEDING A DECISION OR CARE, each recorded rather than assumed: (1) the Rust binary — tooling/atlas builds server/target/debug/atlas if missing then execs it with all args, so its verbs live in Rust not in the shell; declare the four known verbs in the router so reach --help stays a complete index, and keep a passthrough for anything the binary gains later. (2) atlas-commit-and-sync MAKES GIT COMMITS, which is a risk class nothing else in reach has; port it to stage and report by default with committing behind an explicit flag, and say so in the verb help rather than changing behaviour silently. (3) atlas-update-field MUTATES systems.db directly, which .claude/rules/asset-pipeline.md forbids outside a migration — establish whether it predates that rule or is a sanctioned exception BEFORE porting, because the port must not launder a violation into a first-class verb.
RESOLVED BEFORE PORTING, item (3): atlas-update-field is NOT the asset-pipeline violation it resembles. The rule in .claude/rules/asset-pipeline.md forbids raw SQL against systems.db because ''any changes you make are silently reverted by the next regen-db run''. That reasoning does not apply here. Checked: import_economics touches only currency_zone and gate_energy_connected ON star_systems — it does not recreate the table. The fields this script edits (star_type, spectral_class, proper_name, geographic_sector, habitable_planet_count, plus system_gates fields) originate from the ONE-TIME BAKED imports the same rule describes separately: ''the surviving planet-gen importers are one-time build imports baked into the committed DB — not part of make regen-db''. So these edits persist; there is no regen path to route them through, and update-field is the sanctioned way to amend baked atlas data rather than a bypass of a generator. PORT IT, and carry that distinction in the verb''s help so the next reader does not see raw SQL against systems.db and delete it on sight. The genuine residual concern is different and worth stating: a value amended this way lives only in a committed binary, so it cannot be regenerated and does not show in a diff. That is a property of the baked-import design, not of this verb, and belongs to whoever revisits that design.', 'backlog', 'high', NULL, NULL, 'D-263', '2026-09-02 12:14:48.290', '2026-09-02 12:15:55.644', NULL, '4bdad90f7acb1be68b6db444b921c7c3', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Port the atlas domain — the authoring and inspection verbs', 'The eight top-level atlas-* scripts become FLAT verbs on reach atlas: the Rust binary wrapper (stats, show-system, list-bodies, populate), atlas-check, atlas-verify, atlas-names, atlas-systems-done, atlas-update-field, atlas-commit-and-sync, atlas-flatness. Flat rather than nested because these act on the atlas as a whole; the nested groups are for GENERATION per rung (reach atlas map, reach atlas planet), which are separate tickets. Five of the eight are BASH, so those are rewrites: atlas 24, atlas-names 17, atlas-systems-done 9, atlas-update-field 59, atlas-commit-and-sync 60. Standard port acceptance as on T-1281. THREE THINGS NEEDING A DECISION OR CARE, each recorded rather than assumed: (1) the Rust binary — tooling/atlas builds server/target/debug/atlas if missing then execs it with all args, so its verbs live in Rust not in the shell; declare the four known verbs in the router so reach --help stays a complete index, and keep a passthrough for anything the binary gains later. (2) atlas-commit-and-sync MAKES GIT COMMITS, which is a risk class nothing else in reach has; port it to stage and report by default with committing behind an explicit flag, and say so in the verb help rather than changing behaviour silently. (3) atlas-update-field MUTATES systems.db directly, which .claude/rules/asset-pipeline.md forbids outside a migration — establish whether it predates that rule or is a sanctioned exception BEFORE porting, because the port must not launder a violation into a first-class verb.
RESOLVED BEFORE PORTING, item (3): atlas-update-field is NOT the asset-pipeline violation it resembles. The rule in .claude/rules/asset-pipeline.md forbids raw SQL against systems.db because ''any changes you make are silently reverted by the next regen-db run''. That reasoning does not apply here. Checked: import_economics touches only currency_zone and gate_energy_connected ON star_systems — it does not recreate the table. The fields this script edits (star_type, spectral_class, proper_name, geographic_sector, habitable_planet_count, plus system_gates fields) originate from the ONE-TIME BAKED imports the same rule describes separately: ''the surviving planet-gen importers are one-time build imports baked into the committed DB — not part of make regen-db''. So these edits persist; there is no regen path to route them through, and update-field is the sanctioned way to amend baked atlas data rather than a bypass of a generator. PORT IT, and carry that distinction in the verb''s help so the next reader does not see raw SQL against systems.db and delete it on sight. The genuine residual concern is different and worth stating: a value amended this way lives only in a committed binary, so it cannot be regenerated and does not show in a diff. That is a property of the baked-import design, not of this verb, and belongs to whoever revisits that design.', 'in_progress', 'high', NULL, NULL, 'D-263', '2026-09-02 12:14:48.290', '2026-09-02 12:16:58.892', NULL, 'b1921817707b37ef0299b31b50309fd2', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Port the atlas domain — the authoring and inspection verbs', 'The eight top-level atlas-* scripts become FLAT verbs on reach atlas: the Rust binary wrapper (stats, show-system, list-bodies, populate), atlas-check, atlas-verify, atlas-names, atlas-systems-done, atlas-update-field, atlas-commit-and-sync, atlas-flatness. Flat rather than nested because these act on the atlas as a whole; the nested groups are for GENERATION per rung (reach atlas map, reach atlas planet), which are separate tickets. Five of the eight are BASH, so those are rewrites: atlas 24, atlas-names 17, atlas-systems-done 9, atlas-update-field 59, atlas-commit-and-sync 60. Standard port acceptance as on T-1281. THREE THINGS NEEDING A DECISION OR CARE, each recorded rather than assumed: (1) the Rust binary — tooling/atlas builds server/target/debug/atlas if missing then execs it with all args, so its verbs live in Rust not in the shell; declare the four known verbs in the router so reach --help stays a complete index, and keep a passthrough for anything the binary gains later. (2) atlas-commit-and-sync MAKES GIT COMMITS, which is a risk class nothing else in reach has; port it to stage and report by default with committing behind an explicit flag, and say so in the verb help rather than changing behaviour silently. (3) atlas-update-field MUTATES systems.db directly, which .claude/rules/asset-pipeline.md forbids outside a migration — establish whether it predates that rule or is a sanctioned exception BEFORE porting, because the port must not launder a violation into a first-class verb.
RESOLVED BEFORE PORTING, item (3): atlas-update-field is NOT the asset-pipeline violation it resembles. The rule in .claude/rules/asset-pipeline.md forbids raw SQL against systems.db because ''any changes you make are silently reverted by the next regen-db run''. That reasoning does not apply here. Checked: import_economics touches only currency_zone and gate_energy_connected ON star_systems — it does not recreate the table. The fields this script edits (star_type, spectral_class, proper_name, geographic_sector, habitable_planet_count, plus system_gates fields) originate from the ONE-TIME BAKED imports the same rule describes separately: ''the surviving planet-gen importers are one-time build imports baked into the committed DB — not part of make regen-db''. So these edits persist; there is no regen path to route them through, and update-field is the sanctioned way to amend baked atlas data rather than a bypass of a generator. PORT IT, and carry that distinction in the verb''s help so the next reader does not see raw SQL against systems.db and delete it on sight. The genuine residual concern is different and worth stating: a value amended this way lives only in a committed binary, so it cannot be regenerated and does not show in a diff. That is a property of the baked-import design, not of this verb, and belongs to whoever revisits that design.', 'in_progress', 'high', NULL, NULL, 'D-263', '2026-09-02 12:14:48.290', '2026-09-02 12:17:04.589', NULL, 'bfaf47422a711699c9adcac79abb8f6b', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Port the atlas domain — the authoring and inspection verbs', 'The eight top-level atlas-* scripts become FLAT verbs on reach atlas: the Rust binary wrapper (stats, show-system, list-bodies, populate), atlas-check, atlas-verify, atlas-names, atlas-systems-done, atlas-update-field, atlas-commit-and-sync, atlas-flatness. Flat rather than nested because these act on the atlas as a whole; the nested groups are for GENERATION per rung (reach atlas map, reach atlas planet), which are separate tickets. Five of the eight are BASH, so those are rewrites: atlas 24, atlas-names 17, atlas-systems-done 9, atlas-update-field 59, atlas-commit-and-sync 60. Standard port acceptance as on T-1281. THREE THINGS NEEDING A DECISION OR CARE, each recorded rather than assumed: (1) the Rust binary — tooling/atlas builds server/target/debug/atlas if missing then execs it with all args, so its verbs live in Rust not in the shell; declare the four known verbs in the router so reach --help stays a complete index, and keep a passthrough for anything the binary gains later. (2) atlas-commit-and-sync MAKES GIT COMMITS, which is a risk class nothing else in reach has; port it to stage and report by default with committing behind an explicit flag, and say so in the verb help rather than changing behaviour silently. (3) atlas-update-field MUTATES systems.db directly, which .claude/rules/asset-pipeline.md forbids outside a migration — establish whether it predates that rule or is a sanctioned exception BEFORE porting, because the port must not launder a violation into a first-class verb.
RESOLVED BEFORE PORTING, item (3): atlas-update-field is NOT the asset-pipeline violation it resembles. The rule in .claude/rules/asset-pipeline.md forbids raw SQL against systems.db because ''any changes you make are silently reverted by the next regen-db run''. That reasoning does not apply here. Checked: import_economics touches only currency_zone and gate_energy_connected ON star_systems — it does not recreate the table. The fields this script edits (star_type, spectral_class, proper_name, geographic_sector, habitable_planet_count, plus system_gates fields) originate from the ONE-TIME BAKED imports the same rule describes separately: ''the surviving planet-gen importers are one-time build imports baked into the committed DB — not part of make regen-db''. So these edits persist; there is no regen path to route them through, and update-field is the sanctioned way to amend baked atlas data rather than a bypass of a generator. PORT IT, and carry that distinction in the verb''s help so the next reader does not see raw SQL against systems.db and delete it on sight. The genuine residual concern is different and worth stating: a value amended this way lives only in a committed binary, so it cannot be regenerated and does not show in a diff. That is a property of the baked-import design, not of this verb, and belongs to whoever revisits that design.
DONE 2026-09-02. reach atlas db / names / systems-done / check / verify / commit-and-sync / update-field / flatness. Five bash scripts and three Python ones retired; make atlas-verify and the tooling/atlas permission entry gone.
PARITY: reach atlas verify reproduces the original exactly — 2 errors across 301 proposals, exit 1. names and systems-done both exercised against the live DB through the Rust binary.
THE BINARY HAS MORE VERBS THAN ITS WRAPPER DOCUMENTED. The bash usage text listed stats, show-system, list-bodies, populate. atlas-commit-and-sync calls four more the wrapper never mentioned: list-stations, wipe-system, commit-system, sync-wiki. All eight are declared in binary.KNOWN_VERBS so reach atlas --help is a complete index, AND unknown verbs are still forwarded — a hand-maintained list falls behind the binary it describes, so rejecting on it would break the day someone adds a Rust subcommand.
COMMIT-AND-SYNC NOW STAGES BY DEFAULT, committing only with --commit, per the ticket. The original always committed. Nothing else in reach writes to git history, and a tool that commits as a side effect of "sync" is a different risk class from one that writes a file. The default prints the message it WOULD use, so the decision stays where it was rather than being made silently.
Also fixed while there: the original hardcoded "Co-Authored-By: Claude Opus 4.6 (1M context)" into every atlas commit — a pinned model version, which .claude/skills/git-commit names as the root cause of attribution drift. The trailer is dropped; the harness appends its own.
And a real ordering improvement: the original verified the proposal but did not stop on failure — verify''s exit code was never checked, so a proposal that failed verification was still wiped, committed and synced. It now refuses, because bad data in systems.db is far harder to undo than a failed command.
UPDATE-FIELD ported with the asset-pipeline distinction in its help, per the pre-port resolution. The FIELD_TABLE map that lived inside a bash heredoc string — where nothing could check it against the schema — is a module-level dict now, and an unknown field raises unknown_choice, so it names the nine accepted fields instead of silently updating nothing. The original would have run an UPDATE against a table of None.
ALSO CAUGHT: update_field now checks rowcount and fails when no row matched. The original''s SQL would succeed silently against a system_id that does not exist — a no-op reported as success, which is the exact shape of failure this initiative keeps finding.
THE THREE PYTHON SCRIPTS moved with the usual treatment: prints to console events, argparse mains replaced by typed functions the router calls, __file__-relative roots to config.repo_root(). No root bug this time — checked before moving rather than after, which is the third domain running.', 'in_progress', 'high', NULL, NULL, 'D-263', '2026-09-02 12:14:48.290', '2026-09-02 12:36:36.194', NULL, 'a8b8b19662d8b3f6c458732b921a0746', 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 ('06G642RKW8PNW7AP9BY9203SPG', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Port the atlas domain — the authoring and inspection verbs', 'The eight top-level atlas-* scripts become FLAT verbs on reach atlas: the Rust binary wrapper (stats, show-system, list-bodies, populate), atlas-check, atlas-verify, atlas-names, atlas-systems-done, atlas-update-field, atlas-commit-and-sync, atlas-flatness. Flat rather than nested because these act on the atlas as a whole; the nested groups are for GENERATION per rung (reach atlas map, reach atlas planet), which are separate tickets. Five of the eight are BASH, so those are rewrites: atlas 24, atlas-names 17, atlas-systems-done 9, atlas-update-field 59, atlas-commit-and-sync 60. Standard port acceptance as on T-1281. THREE THINGS NEEDING A DECISION OR CARE, each recorded rather than assumed: (1) the Rust binary — tooling/atlas builds server/target/debug/atlas if missing then execs it with all args, so its verbs live in Rust not in the shell; declare the four known verbs in the router so reach --help stays a complete index, and keep a passthrough for anything the binary gains later. (2) atlas-commit-and-sync MAKES GIT COMMITS, which is a risk class nothing else in reach has; port it to stage and report by default with committing behind an explicit flag, and say so in the verb help rather than changing behaviour silently. (3) atlas-update-field MUTATES systems.db directly, which .claude/rules/asset-pipeline.md forbids outside a migration — establish whether it predates that rule or is a sanctioned exception BEFORE porting, because the port must not launder a violation into a first-class verb.
RESOLVED BEFORE PORTING, item (3): atlas-update-field is NOT the asset-pipeline violation it resembles. The rule in .claude/rules/asset-pipeline.md forbids raw SQL against systems.db because ''any changes you make are silently reverted by the next regen-db run''. That reasoning does not apply here. Checked: import_economics touches only currency_zone and gate_energy_connected ON star_systems — it does not recreate the table. The fields this script edits (star_type, spectral_class, proper_name, geographic_sector, habitable_planet_count, plus system_gates fields) originate from the ONE-TIME BAKED imports the same rule describes separately: ''the surviving planet-gen importers are one-time build imports baked into the committed DB — not part of make regen-db''. So these edits persist; there is no regen path to route them through, and update-field is the sanctioned way to amend baked atlas data rather than a bypass of a generator. PORT IT, and carry that distinction in the verb''s help so the next reader does not see raw SQL against systems.db and delete it on sight. The genuine residual concern is different and worth stating: a value amended this way lives only in a committed binary, so it cannot be regenerated and does not show in a diff. That is a property of the baked-import design, not of this verb, and belongs to whoever revisits that design.
DONE 2026-09-02. reach atlas db / names / systems-done / check / verify / commit-and-sync / update-field / flatness. Five bash scripts and three Python ones retired; make atlas-verify and the tooling/atlas permission entry gone.
PARITY: reach atlas verify reproduces the original exactly — 2 errors across 301 proposals, exit 1. names and systems-done both exercised against the live DB through the Rust binary.
THE BINARY HAS MORE VERBS THAN ITS WRAPPER DOCUMENTED. The bash usage text listed stats, show-system, list-bodies, populate. atlas-commit-and-sync calls four more the wrapper never mentioned: list-stations, wipe-system, commit-system, sync-wiki. All eight are declared in binary.KNOWN_VERBS so reach atlas --help is a complete index, AND unknown verbs are still forwarded — a hand-maintained list falls behind the binary it describes, so rejecting on it would break the day someone adds a Rust subcommand.
COMMIT-AND-SYNC NOW STAGES BY DEFAULT, committing only with --commit, per the ticket. The original always committed. Nothing else in reach writes to git history, and a tool that commits as a side effect of "sync" is a different risk class from one that writes a file. The default prints the message it WOULD use, so the decision stays where it was rather than being made silently.
Also fixed while there: the original hardcoded "Co-Authored-By: Claude Opus 4.6 (1M context)" into every atlas commit — a pinned model version, which .claude/skills/git-commit names as the root cause of attribution drift. The trailer is dropped; the harness appends its own.
And a real ordering improvement: the original verified the proposal but did not stop on failure — verify''s exit code was never checked, so a proposal that failed verification was still wiped, committed and synced. It now refuses, because bad data in systems.db is far harder to undo than a failed command.
UPDATE-FIELD ported with the asset-pipeline distinction in its help, per the pre-port resolution. The FIELD_TABLE map that lived inside a bash heredoc string — where nothing could check it against the schema — is a module-level dict now, and an unknown field raises unknown_choice, so it names the nine accepted fields instead of silently updating nothing. The original would have run an UPDATE against a table of None.
ALSO CAUGHT: update_field now checks rowcount and fails when no row matched. The original''s SQL would succeed silently against a system_id that does not exist — a no-op reported as success, which is the exact shape of failure this initiative keeps finding.
THE THREE PYTHON SCRIPTS moved with the usual treatment: prints to console events, argparse mains replaced by typed functions the router calls, __file__-relative roots to config.repo_root(). No root bug this time — checked before moving rather than after, which is the third domain running.', 'done', 'high', NULL, NULL, 'D-263', '2026-09-02 12:14:48.290', '2026-09-02 12:36:36.208', NULL, '6eff7972f7304c334f347ae8598bdf6b', 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;