fix(kernel): adopt only a journal that resume can actually use (#185) - #186
Conversation
#185, a regression I introduced in #177. `Engine::start` creates the journal, appends RunSpawned, then registers, so a crash leaves TWO different residues and #177 only recognised one: * killed AFTER RunSpawned -- a real run missing its index entry. Adopt it; that is what #177 fixed. * killed BEFORE RunSpawned -- an empty journal that never became a run. #177's gate was `journal.run_id() == params.run_id`. An empty journal still carries a meta row with the run id, so it passed: the file was adopted, registered, and resume then died on `read run spec: Query returned no rows` -- an internal failure where the honest answer is that the run does not exist. Before #177 that case returned a clean `run_not_found`, so the change traded a legible refusal for a confusing error in the one sub-case it did not anticipate. Adoption now also requires `run_spec()` to succeed. That is the honest predicate because it is exactly what resume calls next: adopt only what resume can actually use. Found by the diagnostics from #175 and #176 on run 33982088411, where the crash-resume test failed in 63 seconds instead of hanging, and the dump printed the cause: stderr (114 bytes): Error: journal_write_failed: read run spec: SQLite journal failed: Query returned no rows --- journal (0 entries) --- Evidence. Every command below is literally runnable from the repository root, and the output is complete: $ shasum -a 256 kernel/relayflowd/src/server.rs f5e10e4b3d00a9e5c16c24624f9253d578e4eeefabb6483e5d1ebda26fefc88f kernel/relayflowd/src/server.rs $ (cd kernel && PATH="$HOME/.cargo/bin:$PATH" RUSTUP_TOOLCHAIN=stable cargo test -p relayflowd --lib server::tests::run_resume) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.18s Running unittests src/lib.rs (target/debug/deps/relayflowd-01b0ec98cb5c81e7) running 4 tests test server::tests::run_resume_asks_the_registry_instead_of_treating_an_orphan_file_as_a_run ... ok test server::tests::run_resume_refuses_a_journal_that_never_recorded_its_run ... ok test server::tests::run_resume_refuses_a_valid_journal_that_belongs_to_another_run ... ok test server::tests::run_resume_adopts_a_real_journal_whose_registry_row_is_missing ... ok test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 26 filtered out; finished in 0.02s # MUTATION: drop the run_spec() requirement -- back to #177 behaviour $ shasum -a 256 kernel/relayflowd/src/server.rs 47e5ae9042c1b02ea8a3d1eea3ce96c32408b484b483a8fe8ea1e78862662432 kernel/relayflowd/src/server.rs $ (cd kernel && PATH="$HOME/.cargo/bin:$PATH" RUSTUP_TOOLCHAIN=stable cargo test -p relayflowd --lib server::tests::run_resume) Compiling relayflowd v0.1.0 (/Users/khaliqgant/AgentWorkforce/flows-185/kernel/relayflowd) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.87s Running unittests src/lib.rs (target/debug/deps/relayflowd-01b0ec98cb5c81e7) running 4 tests test server::tests::run_resume_asks_the_registry_instead_of_treating_an_orphan_file_as_a_run ... ok test server::tests::run_resume_refuses_a_journal_that_never_recorded_its_run ... FAILED test server::tests::run_resume_refuses_a_valid_journal_that_belongs_to_another_run ... ok test server::tests::run_resume_adopts_a_real_journal_whose_registry_row_is_missing ... ok failures: ---- server::tests::run_resume_refuses_a_journal_that_never_recorded_its_run stdout ---- thread 'server::tests::run_resume_refuses_a_journal_that_never_recorded_its_run' (18692000) panicked at relayflowd/src/server/tests.rs:295:5: assertion `left == right` failed: refusing it as not-found is the honest answer; an internal spec-read failure is not left: "journal_write_failed" right: "run_not_found" note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: server::tests::run_resume_refuses_a_journal_that_never_recorded_its_run test result: FAILED. 3 passed; 1 failed; 0 ignored; 0 measured; 26 filtered out; finished in 0.03s error: test failed, to rerun pass `-p relayflowd --lib` # RESTORED $ shasum -a 256 kernel/relayflowd/src/server.rs f5e10e4b3d00a9e5c16c24624f9253d578e4eeefabb6483e5d1ebda26fefc88f kernel/relayflowd/src/server.rs The mutation drops the `run_spec()` requirement, restoring #177's gate exactly. It fails only the new test, while `run_resume_adopts_a_real_journal_whose_registry_row_is_missing` keeps passing -- so this narrows adoption without undoing what #177 fixed. Kernel workspace at this head: 157 passed, 0 failed, no warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR Session-Id: c228933d-4f94-4d83-9a9a-daf3c83b94f1
|
Warning Review limit reachedNext included review available in 31 minutes. View limit detailsLimit details: You’ve used the included review currently available. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (2)
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing. Comment |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR Session-Id: c228933d-4f94-4d83-9a9a-daf3c83b94f1
Closes #167. The row claimed gate 2 was missing "a test proving a duplicate event does not double-execute". That was already false when #167 was filed, and tonight's work went further, so the row understated progress and the gate at once. Done, each claim checked against main rather than remembered: sequential duplicates (#14); concurrent racing deliveries under the production topology of one `Engine` per protocol request (#171); claims surviving the process that made them (#171 boot id, #182 panic unwind); resume adopting only a journal it can actually use (#177, #186). Still missing, narrowly: the RFC-0001 Appendix A wake-time context contract — nothing specifies what `wake_context` guarantees, or that a resumed run observes the same context rather than a recomputed one. And the correction #167 cared about most: the row was understating the gate. RFC-0001 §3's bar is `hn-monitor` running as a relayflow in production on its real events with zero bespoke persistence, not a passing test suite. Evidence at the merged head 8e57b17: - signoff: local 3-lens preswarm, maintainability / history / structure all REVIEW_PASSED - CI: none applies. `cloud-runtime-artifact.yml` filters on `kernel/**`, `sdk/**`, `testdata/**` and `scripts/cloud-artifact*`; this touches only `ops/SCOREBOARD.md`, so no artifact run was triggered — verified by reading the workflow's `paths:` rather than waiting on a run that was never going to start. The `review` check is red for the reason common to every flows PR: the gate invokes `agent-relay` and no step installs it. No counts in the row, deliberately — counts drift with the base, PR numbers and test names do not.
Nothing verified main. `cloud-runtime-artifact.yml` triggered only on `pull_request` and `workflow_dispatch`, and `review-swarm.yml` only on `pull_request` — so every PR is checked at its own head and never as merged, and since we squash-merge onto a main that has moved since that CI ran, the composed result went unverified. Not theoretical: twelve PRs merged on 2026-09-05 across the exactly-once claim path (#171, #182), resume adoption (#177, #186), the authored-flow executor (#184, #187) and the CLI run loop (#180) — each green on its own branch, the composed tree never run until dispatched by hand: ``` run 33987924703 workflow_dispatch main completed/success ed917bf ``` Main is fine. But nobody knew that, and finding out required knowing to ask. Without this, a bad compose surfaces as an unrelated PR going red — the most expensive way to find it, since the author debugs their own change first. Tonight already produced three failures on PRs that belonged to something else (#179, #185, the #174 chain), and each cost a tick to attribute. Paths are deliberately not filtered on the push trigger: on a PR the question is "does this change affect the runtime", on main it is "is the tree good", and a docs-only merge can land on a tree someone else broke. Evidence at the merged head d1cb32a: - signoff: local 3-lens preswarm, maintainability / history / structure all REVIEW_PASSED - CI: run 33988314276 success on d1cb32a, verified by headSha and event=pull_request - YAML parsed and asserted: triggers are `pull_request`, `push`, `workflow_dispatch`; push is branch-scoped to main; the pull_request path filter is unchanged
Fixes #185 — a regression I introduced in #177.
Two residues, not one
Engine::startcreates the journal, appendsRunSpawned, then registers. So a crash leaves two different things behind, and #177 only recognised one:RunSpawned— real run, no index entryRunSpawned— empty journal, never a run#177's gate was
journal.run_id() == params.run_id. An empty journal still carries a meta row with the run id, so it passed: the file was adopted, registered, and resume then died onBefore #177 that case returned a clean
run_not_found. The change traded a legible refusal for a confusing internal error in the one sub-case it didn't anticipate.The fix
Adoption now also requires
run_spec()to succeed — the honest predicate, because it is exactly what resume calls next. Adopt only what resume can actually use.How it was found
By the diagnostics from #175 and #176, on run 33982088411. The crash-resume test failed in 63 seconds instead of hanging for 30 minutes, and the dump printed the cause outright:
Zero entries. That is the whole diagnosis, and it came free.
Evidence
A fourth test —
run_resume_refuses_a_journal_that_never_recorded_its_run— builds exactly that residue (valid journal, correct run id, no entries) and asserts both therun_not_foundrefusal and that no registry row is left behind.The mutation drops the
run_spec()requirement, restoring #177's gate exactly. It fails only the new test, whilerun_resume_adopts_a_real_journal_whose_registry_row_is_missingkeeps passing — so this narrows adoption without undoing what #177 fixed. Full transcript with runnable commands and sha256 before/mutated/restored is in the commit message.The four tests now state the rule together: refuse a file that is not a journal, refuse a journal that is not this run's, refuse a journal that never recorded its run, adopt the one that did.
Kernel workspace: 157 passed, 0 failed, no warnings.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR