Skip to content

fix(native): keep policy refusal reasons past the storage deadline and exclude only carrier proofs - #421

Merged
roodboi merged 1 commit into
nextfrom
wf/g50-storage-budget-diagnostics
Oct 24, 2026
Merged

roodboi merged 1 commit into
nextfrom
wf/g50-storage-budget-diagnostics

Conversation

@roodboi

@roodboi roodboi commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fix unit G50: storage-budget diagnostics follow-ups after #375 (HACK-1361).

(1) A policy refusal caught after the carrier deadline was reported as storage-budget

Root cause, in src/lib/native-compose-storage-witness-docker.ts:

  • nativeComposeStorageCarrierBudgetReason returned storage-budget for any
    refusal once the carrier deadline had passed, without looking at the error's
    own observed reason.
  • Guard proofs run with original command records, so every helper command goes
    through runNativeComposeStorageDockerCommand with originalCommand. When its
    admission check (assertAdmitted, which re-inspects the helper through
    checkNativeComposeStorageDockerCarrier) refuses with a helper policy reason,
    the runner replaces that error with its fixed command refusal
    (refuseCommand). The replacement dropped the issued
    storage-helper-policy / helper-* boundary. The outer invoke catch then
    labelled it storage-helper-start with storage-budget if the deadline had
    passed (and unclassified otherwise). The human summary then told the
    operator to raise HACK_NATIVE_STORAGE_TIMEOUT_MS for a policy failure.

Fix:

  • nativeComposeStorageCarrierBudgetReason now takes the error's issued reason
    (registry boundary, or the classified command cause) first. It reports
    storage-budget only for a deadline-shaped reason (probe-timeout,
    probe-budget, storage-budget, or no observed cause) when the deadline has
    expired and the carrier was not cancelled. Any other observed reason, such as
    helper-host-policy, is kept.
  • refuseCommand takes an optional source and copies its issued boundary onto
    the replacement refusal (first attach still wins). The runner keeps the first
    publication-stopping error (publicationSource) for the bounded-await and
    child-publication paths, and the outer originalCommand catch passes the
    caught error.

Diagnostics only: no check, deadline, retention or recovery rule changes.

(2) start.excludeStorage wrapped all of storage.verify

The exclusion covered assertFresh (twice) and both store.loadCurrent reads
as well as the carrier proofs. That non-carrier time also extended the start
deadline, and the per-carrier bound does not limit it. I narrowed it:
NativeComposeCommandStorageVerifyOptions.excludeProof now wraps only each
enrolled volume's carrier proof (fresh carrier creation plus
verifyNativeComposeStorageXattrWitness). executePreparedGeneration passes
start.excludeStorage through it. The cap entry and exit checks now run per
carrier proof, which matches the documented "a proof that would start at or past
the cap is refused before it runs".

(3) Comment precision

The NATIVE_STORAGE_EXCLUSION_MULTIPLE comment no longer calls the uncapped sum
"unbounded in practice". It now says the sum is finite, at most (5B+3) x V x S,
and can reach hours. For example, 4 builds and 3 volumes allow 69 carriers:
about 1.7 h at the 90 s default and 11.5 h at the 600 s maximum. The
createNativeComposeStartDeadline doc and the reference docs say which span is
excluded.

Tests (each proven by mutation)

  • tests/native-compose-storage-witness-docker.test.ts: "a helper policy
    refusal caught after its carrier deadline keeps its policy reason". It uses a
    real checkNativeComposeStorageDockerCarrier host-policy refusal and a fake
    clock past the deadline. Mutation: drop the deadline-shaped precondition
    (the old behavior) -> storage-budget received, test fails.
  • tests/native-compose-storage-command-record.test.ts: "a helper policy refusal
    {before arming, after arming, after the stopped child is published} keeps its
    own reason after the carrier deadline". It uses the real recorded-command
    runner with a temp private directory, a child that is never resumed (marker
    absent), and a fake clock for the reason. Mutations:
    • outer catch without source -> all 3 fail;
    • settlement refuseCommand without publicationSource -> the
      child-published case fails;
    • refuseCommand without the copy -> all 3 fail.
  • Narrowed exclusion: the existing wiring test
    tests/native-compose-start-storage-exclusion.test.ts passes (about 103 s on
    this loaded Mac). Mutations, each killed (up exits 1, workload not
    admitted):
    • excludeProof: start.excludeStorage removed from the guard;
    • verify ignoring options.excludeProof.

Evidence

  • Bun 1.4.2 (mise x bun@1.4.2), bun install --frozen-lockfile; no
    bun.lock change.
  • bun run --cwd packages/cli typecheck, root bun run typecheck, root
    bun run check (privacy-check ok): 0 errors. Biome on the changed files
    reports only 3 existing complexity warnings with unchanged scores (checked
    against the origin/next copy).
  • gitleaks 8.30.1 (gitleaks dir over the git log -p origin/next..HEAD
    export): no leaks.
  • Focused local run: 7 files, 282 pass and 1 timeout-only failure under load.
    The files are: storage command record, docker witness, docker IO, xattr owner,
    startup budget, effect diagnostics and build. The failure was runEffect captures the original fresh carrier factory ..., which hit the 5 s default
    timeout. It passes serially in 3.7 s with --timeout 60000.
    tests/agent-instruction-source.test.ts (reads the changed docs) passes.
  • M3 full suite at 9301b86: pending: queued behind the shared M3 CI lock (several units ahead); this section will be updated with the result.

Gaps

  • The narrowed exclusion is proven by the command-level wiring test and two
    mutations. I did not add a unit test that pins assertFresh or the
    saved-state reads outside the excluded span. A full proof through
    prepareNativeComposeCommandStorage needs a real witness.
  • An untagged policy-shaped mismatch (for example a plain refuse() when a
    re-inspected carrier's createdAt or policy string changed) still has no
    observed reason. Past the deadline it is still reported as storage-budget.
    Tagging those checks is a separate change (see findings).
  • Hosted CI and native e2e scenarios are not run by this report. M3 is not a
    qualifying host for them.

Refs HACK-1361

🤖 Generated with Claude Code


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

…d exclude only carrier proofs

- A carrier boundary now keeps a refusal's own observed reason (an issued
  helper policy boundary or a classified command cause) even when it is caught
  after the carrier deadline. `storage-budget` is reported only for a
  deadline-shaped refusal (timeout, budget, lifetime mark or no observed cause)
  whose carrier deadline expired and was not cancelled.
- A recorded storage command that replaces an admission refusal with its fixed
  command refusal now carries the source's issued boundary, so a helper policy
  refusal observed during admission is no longer relabeled `storage-budget` at
  `storage-helper-start`.
- The start deadline's storage exclusion now wraps only each carrier proof
  (carrier creation and witness verification). The guard's freshness checks
  and saved-state reads consume the start budget.
- The exclusion-cap comment states that the uncapped sum is finite,
  (5B+3) x V x S, but can reach hours.

Refs HACK-1361

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi
roodboi marked this pull request as ready for review October 24, 2026 02:42
@roodboi
roodboi merged commit 535579a into next Oct 24, 2026
32 of 33 checks passed
@roodboi
roodboi deleted the wf/g50-storage-budget-diagnostics branch October 24, 2026 02:43
roodboi added a commit that referenced this pull request Oct 24, 2026
Combine HACK-1365's commandCause classification with #421's (HACK-1361)
publication source: refuseCommand now takes both the classified cause of a
rewrapped admission rejection and the original refusal as source, so a helper
policy refusal keeps its issued boundary and a cancelled probe or exhausted
budget keeps its reason. Both sides' command-record tests are kept.

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