Skip to content

fix(kernel): adopt only a journal that resume can actually use (#185) - #186

Merged
kjgbot merged 1 commit into
mainfrom
fix/185-adopt-only-resumable-journal
Sep 5, 2026
Merged

fix(kernel): adopt only a journal that resume can actually use (#185)#186
kjgbot merged 1 commit into
mainfrom
fix/185-adopt-only-resumable-journal

Conversation

@kjgbot

@kjgbot kjgbot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #185 — a regression I introduced in #177.

Two residues, not one

Engine::start creates the journal, appends RunSpawned, then registers. So a crash leaves two different things behind, and #177 only recognised one:

state resumable? #177 this PR
killed after RunSpawned — real run, no index entry yes adopted ✓ adopted ✓
killed before RunSpawned — empty journal, never a run no adopted, then internal error ✗ refused ✓

#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: SQLite journal failed: Query returned no rows

Before #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:

stderr (114 bytes):
Error: journal_write_failed: read run spec: SQLite journal failed: Query returned no rows

--- journal (0 entries) ---

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 the run_not_found refusal 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, while run_resume_adopts_a_real_journal_whose_registry_row_is_missing keeps 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

#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
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 31 minutes.

Check out review usage here.

View limit details

Limit 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.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 48765bae-9e72-43fb-aa66-8d950b321b86

📥 Commits

Reviewing files that changed from the base of the PR and between 440a899 and 11edaa0.

📒 Files selected for processing (2)
  • kernel/relayflowd/src/server.rs
  • kernel/relayflowd/src/server/tests.rs

Note

🎁 Summarized by CodeRabbit Free

Your 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 @coderabbitai help to get the list of available commands.

@kjgbot
kjgbot merged commit 092a25f into main Sep 5, 2026
2 of 3 checks passed
@kjgbot
kjgbot deleted the fix/185-adopt-only-resumable-journal branch September 5, 2026 18:24
kjgbot pushed a commit that referenced this pull request Sep 5, 2026
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
kjgbot added a commit that referenced this pull request Sep 5, 2026
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.
kjgbot added a commit that referenced this pull request Sep 5, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

#177's resume adoption accepts an EMPTY journal, turning run_not_found into an internal error

1 participant