Skip to content

fix(runtime): retire authored starts refused before journal reservation - #355

Merged
roodboi merged 1 commit into
nextfrom
wf/g4-authored-receipt-b17
Oct 20, 2026
Merged

roodboi merged 1 commit into
nextfrom
wf/g4-authored-receipt-b17

Conversation

@roodboi

@roodboi roodboi commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Refs HACK-1272

Root cause

The M3 authored fixture on published v5.0.0-next.5 declares
docker.io/library/busybox@sha256:<manifest digest>. That is a repository
digest, not the sha256: image content ID that the bounded native consumer
requires. config validate and graph native plan accept it. After startup has
reserved the frontend start intent and source envelope, graph native serve
lowers the configuration and refuses native_graph_admission
(configuration_with_source → image_id). This happens before
journal::reserve, so no runtime journal or receipt exists. Because the journal
directory is absent, the Rust foreground owner retires its own publication.

The TypeScript startup then runs Removed retirement: it calls
graph native inspect --run-id. With no journal, journal::load refuses with
the generic native_graph_receipt. On next.5 that inspection code overwrote the
startup code. #331 (54cc4c9, already on next) fixed that masking, so current
next reports (native_graph_admission).

The remaining defect is still on next. A refusal before reservation leaves the
start intent and source retained forever:

  • ps reports pending.
  • down has no Ready mapping to stop.
  • down --recover requires a complete stored generation.

The same thing happens for any refusal before reservation: image identity,
network intent, capacity, review change, cancellation, or engine connect. The
receipt path could not tell "this run never reserved anything" apart from
damaged or foreign receipt evidence, so nothing could safely retire the attempt.

The 6/6 refusal is consistent with this. All six variations use the same image.
Pool, branch, port, HACK_SERVICE_NAME and readiness are not part of the refusing
check. This analysis is read-only, from the retained records and
s4-authored-start.sh on the M3. Nothing was re-run there.

Fix

  • Rust: graph native inspect (macOS only). Inspect now refuses with a
    distinct native_graph_unreserved before guest access. It does so only when
    all of these hold:

    • the run's journal directory is exactly absent;
    • every existing ancestor is a private state directory;
    • foreground::require_unpublished finds no owner publication;
    • the journal is still absent when checked again after the owner check.

    A partial directory, a public ancestor, a published or abandoned owner, or a
    present journal keeps the existing refusals and validation. No receipt or
    ownership check is relaxed.

  • Startup. After the consumer process settles, Removed retirement may fail
    with exactly native_graph_unreserved. In that case startup retires the exact
    start intent and the validated source, and the startup failure reports outcome
    unreserved with the original native code. This applies only when all of these
    hold:

    • no receipt was observed;
    • there is no Ready mapping;
    • there are no lifecycle hooks or host processes;
    • there is no live-stop owner.

    ps then reports not_started. A canceled unreserved startup exits 130, like
    confirmed removal. Every other inspection result retains everything, as before.

  • down --recover for a pending start. This is the supported recovery for
    attempts already retained, for example by next.5. It applies when a start
    intent exists with no Ready mapping and no recovery intent. The command:

    1. Takes a fresh startup admission. A live or crashed CLI holds that admission,
      so the command refuses as busy.
    2. Requires no Ready mapping, hook owner or live-stop owner.
    3. Inspects the exact run.
    4. Retires the source, then the start intent, only for
      native_graph_unreserved.

    It does not compile input, acquire values, serve, or touch the guest.
    Stored-generation recovery is unchanged.

  • Status note and docs. The pending text note now points to
    down --recover. docs/reference/native-authored-commands.md and the
    runtime-core README are updated.

Tests

Each new test was checked by mutation: with the fix reverted the test fails, and
with the fix restored it passes.

  • Rust: inspect_names_only_an_exactly_absent_unpublished_journal_as_unreserved
    covers an absent journal, a published owner, a finished owner, a partial
    directory, a public ancestor, and a reserved journal. With the inspect check
    reverted, the test reports runtime_not_running and fails.
  • tests/native-authored-project-start.test.ts:
    • HACK-1272 repro: serve refuses native_graph_admission, then inspect refuses
      native_graph_unreserved. The test expects outcome unreserved, the native
      code kept, no artifacts left, and ps not_started. With the fix reverted,
      the outcome is retained.
    • down --recover on the next.5-style retained state: native_graph_receipt,
      native_graph_foreground, runtime_not_running, and a successful inspection
      each keep both files and pending. Only native_graph_unreserved retires
      them. With the refusal-code check reverted, the test fails.
  • tests/native-authored-command.test.ts: a pending unreserved start goes to
    the new down --recover owner, and its failures map to E_LIFECYCLE_FAILED. A
    canceled unreserved startup exits 130. With either change reverted, these tests
    fail.

Commands run locally on macOS (pinned Rust 1.97.1 and Bun 1.4.2 via mise):

  • bun run --cwd packages/cli typecheck: 0 errors. This includes the tests.
  • bun run --cwd packages/cli check: 0 errors. The warnings already existed; the
    changed files are clean.
  • bun run check:local: passed.
  • All-features cargo clippy -D warnings: passed.
  • All-features provider::graph::native tests: 181 passed.
  • tests/cli-spawn-env-isolation.test.ts plus the native-authored command,
    dispatch, observe, run, and mutation-lease suites: passed.
  • bun run test:local: 980 passed and 7 failed. The 7 failures are
    provider::lifecycle::{host_filesystem,short_home,interrupted} tests failing
    with provider_timeout_uncertain. The same 7 fail with unmodified
    origin/next runtime-core on this host. The default-feature build does not
    include the changed module, which is gated by native-config-plan.

Remaining gaps

  • No live run. Nothing was run on the M3 or M5. Proof is local, using stub
    runners and the Rust filesystem fixture.
  • Timing-sensitive startup suite. This host was heavily loaded (load average
    30–65). Under that load, the ready-path fixture in
    native-authored-project-start.test.ts misses its ready window. The first
    failure is "holds admission through exact ready publication", which fails the
    same way on unmodified origin/next. The fixture's settlement guard then skips
    the rest of the file. The three no-ready cases touched here pass when run on
    their own. Some native-runtime-client descendant-pipe timing cases were also
    flaky; this change only adds a constant to that module. A clean exact-head CI
    run is required.
  • Hooks, host processes, live-stop. An unreserved attempt that uses any of
    these is still retained, and no recovery is added for it.
  • Failed self-cleanup after reservation. If the Rust consumer refuses after
    reserving its journal and its own cleanup also fails, the journal-backed
    attempt is still retained with no supported recovery.
  • Repository digest accepted early. config validate and
    graph native plan still accept a repository@sha256: digest that execution
    then refuses. This PR does not change that; it is reported as a separate
    finding.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

A native consumer that refuses before reserving its runtime journal (for
example native_graph_admission for a repository@sha256 image, the M3 next.5
repro) leaves no receipt, so Removed retirement could never succeed and the
start intent stayed pending with no supported recovery.

graph native inspect now refuses the distinct native_graph_unreserved on macOS
only when the run's journal directory is exactly absent under private state
directories and no foreground owner is published. Startup retires the exact
start intent and source for that refusal after its consumer settled, reporting
outcome `unreserved` with the original native code. `down --recover` retires a
pending start the same way under fresh startup admission. Every other
inspection result keeps all evidence; receipt and ownership checks are
unchanged.

Refs HACK-1272

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 19, 2026

Copy link
Copy Markdown

HACK-1272

@blacksmith-sh

This comment has been minimized.

@roodboi
roodboi marked this pull request as ready for review October 20, 2026 17:58
@roodboi
roodboi merged commit d1a9f49 into next Oct 20, 2026
32 of 33 checks passed
@roodboi
roodboi deleted the wf/g4-authored-receipt-b17 branch October 20, 2026 17:58
roodboi added a commit that referenced this pull request Oct 21, 2026
Brings #355 (G4, HACK-1272: retire authored starts refused before journal
reservation), #352, #341, #342 and #346 into the G1 closed-refusal branch.
Both sides' behavior is kept.

Conflict resolutions:

- docs/reference/native-authored-commands.md: keep G1's explanation of
  `pending` (in-progress start or failed start with retained evidence; only
  the owner's cleanup proof may retire it) and append G4's pointer that
  `down --recover` retires an attempt that never reserved runtime state when
  no startup is running.

- src/lib/native-authored-command.ts: the `pending` status note keeps G1's
  wording and adds G4's `hack down --recover` hint. Auto-merged code keeps
  G4's canceled `unreserved` exit 130 ahead of G1's stopped-runtime
  classification. Semantic fix: G1's stoppedRuntimeStartup message map had
  no entry for G4's new `unreserved` outcome, so a runtime_not_running start
  that was retired as unreserved would have produced an undefined message.
  It now reports E_DOCKER_UNAVAILABLE with an `unreserved` message (intent
  retired, `hack ps` reports not_started). G4's `down --recover` pending-start
  path already refuses through commandFailure as a closed coded HackCliError
  (E_LIFECYCLE_FAILED, values omitted), and unreserved startup failures map
  to E_STARTUP_INCOMPLETE or E_DOCKER_UNAVAILABLE, so no stack or help is
  printed.

- tests/native-authored-project-start.test.ts: the fixture keeps G1's
  `stoppedDuringServe` and G4's `failBeforeReceiptCode` and
  `unreservedInspection` options. The stub serve emits G4's configurable
  failBeforeReceipt code and G1's wrapped stopped-runtime refusal. The stub
  inspect answers both failure modes, with `native_graph_unreserved` only
  when requested. The duplicate native-runtime-client import is merged into
  one. Semantic fix: G4's HACK-1272 call-log expectation now includes G1's
  read-only `runtime status --json` preflight between plan and serve.
  Both sides' tests are kept.

- tests/native-authored-command.test.ts (auto-merged): add an `unreserved`
  case to the stopped-runtime startup table.

Verification (Bun 1.4.2, macOS):
- packages/cli typecheck and check: 0 errors.
- native-authored-command, native-refusal-cli,
  native-authored-command-dispatch and cli-spawn-env-isolation: 124 pass.
- Other native-authored-* and native-runtime-* suites: 409 pass, 5 fail.
  2 are load-timing failures that pass when rerun alone. 3 are host
  live-session census failures in the recovery and incarnation suites,
  which fail identically on origin/next and ead8383 on this host.
- native-authored-project-start (--timeout 60000): 48 pass. The 3
  host-metadata cases are intermittent (HACK-1299). They also fail
  intermittently on origin/next and ead8383 and pass when run alone.
- Six scripts/check-native-*-plan-cli.ts: all pass after build and
  build:config-compiler.
- docs:cli-reference and generate:agent-plugins: no drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant