Problem
The run-dir file contract has one producer but N independent consumers in the extension, each hand-reading and cherry-picking fields. Every additive field has to be remembered in every reader; review is the only thing catching the misses.
Readers today:
Evidence it drifts (not hypothetical)
This is the same species as #83, but on a different boundary: #83 guards the solver↔host stdout grammar; this guards the run-dir files ↔ N extension readers boundary — the row that's missing from #83's table.
Proposed
One readRunDir(runDir): RunRecord that reads run.toml / result.toml / formulation.toml (+ pulse where needed) and returns the complete record; make RunCompletion its return type. Route every completion path through it; hydrateFromRunDir shares the low-level reads. One fixture-based test drives the shared reader and asserts the full record — so adding a field = update one reader + one fixture, and N-path divergence becomes structurally impossible (same "impossible to land green on the shared path" residual #83 relies on).
Sequencing
Land with / right after #81 — it already starts the extraction via readFormulation; finish it before more paths hand-roll their own reads. Cheap, TS-only, no Julia. Gate under #62 alongside the other run-dir contract checks.
Relates: #81, #70, #72, #83, #62, #64, #41.
Problem
The run-dir file contract has one producer but N independent consumers in the extension, each hand-reading and cherry-picking fields. Every additive field has to be remembered in every reader; review is the only thing catching the misses.
Readers today:
run_dir_reader.ts—ingestRunDir(replay / late-join)file_watcher.ts—onFinished(live finish)completeRun/readTerminalState(third completion path, added in 1.3: Run Inspector single→multi-run (runId-keyed protocol, per-run panes) #72)catalog_card_shell.ts—hydrateFromRunDir(catalog card; different output shape, same underlying files)Evidence it drifts (not hypothetical)
readTerminal/ingestRunDirduplication.onFinishedbuilt{runId, runDir, status, fidelity}with noformulation, so live-finished runs carriedformulation: undefinedwhile the replay path got it. Fixed with a sharedreadFormulationacross two paths; the third (completeRun, landing in 1.3: Run Inspector single→multi-run (runId-keyed protocol, per-run panes) #72) still needs it (see the 1.3: Run Inspector single→multi-run (runId-keyed protocol, per-run panes) #72 seam comment).This is the same species as #83, but on a different boundary: #83 guards the solver↔host stdout grammar; this guards the run-dir files ↔ N extension readers boundary — the row that's missing from #83's table.
Proposed
One
readRunDir(runDir): RunRecordthat readsrun.toml/result.toml/formulation.toml(+ pulse where needed) and returns the complete record; makeRunCompletionits return type. Route every completion path through it;hydrateFromRunDirshares the low-level reads. One fixture-based test drives the shared reader and asserts the full record — so adding a field = update one reader + one fixture, and N-path divergence becomes structurally impossible (same "impossible to land green on the shared path" residual #83 relies on).Sequencing
Land with / right after #81 — it already starts the extraction via
readFormulation; finish it before more paths hand-roll their own reads. Cheap, TS-only, no Julia. Gate under #62 alongside the other run-dir contract checks.Relates: #81, #70, #72, #83, #62, #64, #41.