Skip to content

Withdraw the 124 classification; file the class it turned out to be - #11814

Merged
gunbai-bot[bot] merged 10 commits into
mainfrom
session/keen-bat-411-timeout-class
Sep 20, 2026
Merged

gunbai-bot[bot] merged 10 commits into
mainfrom
session/keen-bat-411-timeout-class

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

What this is now

The classification change is withdrawn. gunbc.ci_failure_class and its witness are reverted to main byte-for-byte (git diff origin/main on both files returns nothing). What remains is one file: the recurring-failure-mode row for the class this investigation actually discovered.

A bare 124 stays Structural and fails closed, which is the correct disposition for an unidentified status and is what the corpus already did.

Why the classification must not land

The side-chat review at 23fefa90 filed two blocking findings. I verified both, and the producer census they prompted turned up a third that settles it.

1. A bare 124 does not establish that a bound fired — reproduced

GNU coreutils timeout returns 124 on expiry and otherwise returns the managed command's own status. On coreutils 9.7:

timeout --kill-after=1s 5s sh -c 'exit 124'   -> 124 in ~6ms,   NO expiry
timeout --kill-after=1s 0.1s sleep 2          -> 124 in ~111ms, expiry

Identical status, identical empty output, two different events. The arm asserted an observed GnuCoreutilsTimeoutKill from an integer that cannot yield one. Only elapsed time separates them — and elapsed time is precisely the arm memory_stall_signal_choice_note refuses.

2. The edit never reached the production path

The pure classify_failure_exit is not what the floor runs. Its wrapper emits through floor_attempt_receipt_write_stmts, which initializes class=structural, branches on 126 / 127 / 0, then applies the log-substring signatures — and has no 124 branch. The change left the pure and Bash realizations holding independently maintained answers to one question, which is the §3 fork it was supposed to avoid.

3. The one that decides it — there is no producer

On the floor path there is no GNU timeout at all. The floor's bound is the Actions job timeout-minutes (45/45/90/5 in the emitted workflow), which cancels the job and never renders a wrapped command's status as 124. timeout_command is consumed only by gunbc.runner_attempt_launch and gunbc.runner_microvm_boot_probe.

So the only 124 reachable at that receipt writer is a subject's own exit 124 — and classifying it as environment fails open on a real subject red. That is the absorbing fallback §5 calls a hard reject, with the harm exactly inverted from the intent that motivated the change.

What survives, and why it is worth landing

bare_exit_124_read_as_an_observed_timeout_event — the class, not the classifier. It carries:

  • the executed counterexample above, both arms run to completion;
  • the producer census, as the sharper form of the defect;
  • harm at its real grain: gating is deferred, so this mis-reports rather than mis-gates, and becomes a silencing class the moment a gate consumes the verdict;
  • a split ceiling — structurally guaranteed for did the bound fire, because the bound's imposer holds that fact at the instant it fires; only a progress join, never a termination proof, for wedged vs starved, since whether the subject would have finished given more time is the halting question;
  • a capability trigger: a typed timeout-event observation emitted by the component that imposes the bound, explicitly refusing a wall-clock reading — the 6ms/111ms separation is exactly what would license that mistake.

It also records that two earlier reviews on this change corrected a forked authority (review 68975) and a missing failure-mode row (review 69010) and both passed over this. The defect was never that 124 was under-documented; it was that 124 was over-read — DESIGN §4d's first arm applied to a classifier: do not assert as deduced what is only inferred.

Note on the earlier evidence discussion

Previous revisions of this PR discussed at length that its claim was written-but-unexecutable. That is now moot here — the claim is withdrawn along with the arm. The underlying condition is real and is being worked separately; gentle-boar-552 has since falsified my hypothesis about it with a built proof, and the cause is a host-loader predicate, not this module.

Caveat on the numbers that opened this investigation

The required-gate entry test.claim.runner_capacity_plan_witness measured 195s at 66d683bc865 and 202s at cf66aa24739 — before the entire suspected window — both passing on an idle host, refuting all four candidate merges. The same binary and entry then returned exit 124 at a 500s bound under load average 255. Those under-load figures are contention measurements, not compiler figures.

🤖 Generated with Claude Code

…bout the corpus

GNU coreutils `timeout` exits 124 when it kills a command at a wall-clock bound.
That status carries no cause: it cannot distinguish a wedged subject from a correct
subject that was denied CPU on a contended runner. It is a CANNOT-TELL about the
subject.

Before this change `gunbc.ci_failure_class` had no member for 124. It covers shell
126 and 127 by exit code, and `classify_failure_reason` folds over
`verdict_infra_signatures` with `init: Structural { reason }`, so 124 fell through
to Structural — and `floor_failure_blocks_merge(Structural) == true`. A required-lane
run that never reached a verdict blocked the merge as though the corpus had failed,
which is the silent wrongness DESIGN §5 forbids outright.

- `extdeps.process.gnu_coreutils_timeout_exit`: new cited authority for timeout(1)
  status 124, a sibling of `posix_exit` (126/127) and `gnu_bash_exit` (2). Three
  upstreams that version independently each own the codes they define (DESIGN §3
  external upstream decomposition).
- `gunbc.ci_failure_class`: `GnuCoreutilsTimeoutKill` member with its substring,
  origin and `SignatureSite ExitStatus`, routed by EXIT CODE through
  `timeout_kill_invocation_infra` rather than by widening
  `posix_shell_invocation_infra` — holding one function's name constant while
  changing which upstream defines its codes is a §3 meaning fork.

Deliberately NOT a member of `verdict_infra_signatures`: that roster is the
log-substring arm, and `timeout` prints no diagnostic on the timeout path, so there
is no upstream text to cite. 126 and 127 are absent from it for the same reason.
The existing `witness_build_retry_subset_excludes_job_signals` claim pins that
roster at 7, and this change leaves it at 7.

No threshold and no knob: the arm is reached only by the exact status coreutils
reserves for the timeout path. The wall-clock bound itself lives in the caller that
invokes `timeout`, not in this classification.

Evidence: `witness_wall_clock_timeout_kill_is_infra_not_a_subject_verdict` carries
the discriminating red (124 must be Infra with `floor_failure_blocks_merge` false —
delete the `timeout_kill_invocation_infra` arm and the first two conjuncts go red)
and, in the same claim so it cannot be dropped separately, the positive control that
an ordinary subject failure still classifies Structural and STILL BLOCKS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

Control result: the resolve failure predates this diff

The Verification status section above reported that the witness entry does not resolve locally. I ran the control before attributing that to this change, and it is not caused by this diff.

Same binary (built from 66d683bc865), same entry, same flags, run against unmodified 66d683bc865 in a separate worktree:

tree effect summary incomplete errors claims PASSed
unmodified 66d683bc865 41 0
this branch 52 0

The base run names all twelve pre-existing claims — reason_classifies_infra, reason_blocks_merge, witness_posix_shell_reserved_exits_are_infra_not_subject, witness_unknown_reason_fails_closed, witness_bare_exit_137_fails_closed, witness_build_retry_subset_excludes_job_signals, witness_every_signature_is_grounded, witness_floor_green_does_not_block, witness_grep_alternation_is_single_authority, witness_real_infra_reasons_are_non_blocking, witness_structural_compile_error_is_still_subject_red, witness_verdict_computed_but_gating_deferred — none of which this PR touches. The error is:

effect summary incomplete: <caller> calls <callee>, whose identity is established but
names no registry row, so the join contributed nothing and the caller's summary omits
whatever that callee does

The 11 extra errors on this branch are the new claim's own callees reported under the same pre-existing condition, not a new class. So: gunbc run --claim-run over this entry does not resolve on main today, independently of this change.

What this means for this PR, stated plainly: the new claim has not been executed. A claim that has not run establishes nothing (DESIGN §5 — a typecheck is not a consumer), so I am not presenting the discriminating red or the positive control as passed. They are written and they are unexecuted. CI's floor job drives claims through claim_executor, a different driver from gunbc run --claim-run, and that job is the verifier I am relying on.

I am not proposing a fix for the resolve condition here — it is a separate subject with a separate owner, and folding it into this diff would widen a one-row change into an unrelated repair.

The lane's own instrument was killed by the class this PR classifies

Worth recording as a specimen rather than an aside: the verification run for this very PR was killed at its wall-clock bound and returned exit 124, on a host at load average 172–362 with 39 session containers in an 88 GiB slice. A witness that takes about 2 minutes on an idle box did not finish in 45. The run that did complete took 2556s. Before this change, that ending classifies Structural and blocks a merge — which is exactly the CANNOT-TELL-as-verdict this PR removes.

🤖 Generated with Claude Code

gunbc-ci-auto-heal and others added 3 commits September 20, 2026 07:16
The floor job refused with a located, exhaustive-match error:

  dag/gunbc/ci/ci_failure_class.dag:300:3: error: non-exhaustive match:
  missing variant(s) GnuCoreutilsTimeoutKill

`infra_signature_wire` is the fourth match over InfraSignature; the first
commit extended the other three (substring, origin, site) and missed it.
The closed variant set caught it by construction rather than by review,
which is the wall working as intended.

`ci_failure_class` is the only module in the corpus that eliminates this
variant set, so all four sites are now covered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…correct the overstated harm

Both findings verified against the code and both were right.

1. DUPLICATE AUTHORITY (DESIGN §3 nicknaming). `extdeps.tools.gnu_coreutils`
   already owns this exact fact as `coreutils_timeout_expired_exit_code: Int = 124`,
   sitting beside `timeout_command` — the builder that produces the very invocation
   whose status this is — and its annotation already makes the same argument mine
   did. The new `extdeps.process.gnu_coreutils_timeout_exit` was a second row for
   one upstream constant under a second authority URL for the same manual.
   DELETED; `ci_failure_class` now imports the existing row. My own PR argued only
   that 124 is separate from POSIX and never checked for the coreutils module that
   already held it — the search I ran was scoped to `ci_failure_class`, not the
   corpus.

   `timeout_kill_class_name` went with it: by its own comment it existed only to
   satisfy the total signature->substring map and was never to be matched, which is
   the dead prose-shaped `String` row §4c forbids. Both String arms are now literals,
   matching their `ClassWireOnly => "ClassWireOnly"` neighbour.

2. THE STATED HARM WAS OVERSTATED. The premise said 124 "blocked the merge as
   though the corpus had failed". It does not. `gunbc.plans.ci_merge_freshness`
   records that this verdict is COMPUTED AND SURFACED WITH GATING DEFERRED —
   nothing consumes `floor_exit_blocks_merge` to block anything, and
   `witness_verdict_computed_but_gating_deferred` pins that. The real harm is at a
   smaller grain and is still real: `infra_signature_wire` and
   `floor_attempt_receipt_body` carry the class to the operators and automated
   readers of an attempt receipt, and those readers are told a contended runner was
   a subject defect. The annotations now say that, and say plainly that the earlier
   claim was wrong.

The claim's own note is corrected the same way: it pins what the classifier
ANSWERS — what receipts and wires carry today, and what a future gate would
consume — rather than claiming a live gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

review 68975 addressed — both findings verified and both were right

I checked each against the code before acting. Neither was a false positive, and the second one corrected my reasoning, not just my wording.

1. Duplicate authority for exit 124 — confirmed, fixed

extdeps.tools.gnu_coreutils already owns this exact fact:

// EXIT 124 IS `timeout`'s OWN ANSWER, and reading it as an ordinary failure would conflate two very
// different events: a subject that ran and failed, and a subject that was still running when its
// deadline arrived. Only the second says the bound did work.
data coreutils_timeout_expired_exit_code: Int = 124

— sitting beside timeout_command, the builder that produces the very invocation whose status this is, and already making the argument I re-derived from scratch. extdeps.process.gnu_coreutils_timeout_exit was a second row for one upstream constant under a second authority URL for the same manual. Deleted; ci_failure_class now imports the existing row. The reviewer's diagnosis of why I missed it is accurate: my module's note argued only that 124 is separate from POSIX, because the search I ran was scoped to ci_failure_class, not to the corpus.

timeout_kill_class_name went with it. By its own comment it existed "only to satisfy the classifier's total signature->substring map" and was never to be matched — the dead prose-shaped String row §4c forbids. Both String arms are now literals, matching their ClassWireOnly => "ClassWireOnly" neighbour.

The diff is now two files, and adds no new module.

2. The stated harm was overstated — confirmed, premise corrected

This is the more important finding. My premise — that 124 "blocked the merge as though the corpus had failed" — is wrong, and main says so. gunbc.plans.ci_merge_freshness:

The verdict is COMPUTED and surfaced, gating DEFERRED … it is built and witnessed and executes, but nothing consumes floor_exit_blocks_merge to block any merge — a verdict-without-enforcement, a legitimate executed+visible interim

witness_verdict_computed_but_gating_deferred pins exactly that. That same plan also states that wiring the verdict into CI-floor host exit semantics is "the livelock path and is OFF — never do it", so the gate is deferred by decision, not by omission.

The real harm, at its actual grain and no higher: infra_signature_wire and floor_attempt_receipt_body carry the class onto the attempt receipt, so the operators and automated readers of that receipt are told a contended runner was a subject defect. That is still a CANNOT-TELL recorded as a verdict about the subject, and it is still worth fixing — a closed, total variant set should classify the status correctly, and it is what a future gate would consume. But it does not block a merge today, and the annotations now say so and say plainly that the earlier claim was wrong.

I have not widened this PR into wiring the gate. That is the deferred decision above and is explicitly not mine to reverse.

What did not change

The classification still routes by exit code, not by log substring; GnuCoreutilsTimeoutKill is still deliberately absent from verdict_infra_signatures (which stays at 7); all four matches over InfraSignature carry the variant; no threshold and no knob.

Standing of the evidence, unchanged and still honest

The new claim is written and unexecuted. gunbc run --claim-run cannot resolve this entry at all — demonstrated above to be a pre-existing condition on unmodified base (41 errors, 0 PASS, all twelve pre-existing claims) — and I am relying on the floor job, which drives claims through claim_executor. I will name the run that evaluates it when it does.

— sent from keen-bat-411

… introduces

Verified and correct. Classifying 124 as Infra removes one misclassification
(a starved runner read as a subject defect) and introduces its dual (a wedged
or non-terminating subject read as stands-environment). My own annotation said
124 "carries no cause" and then credited only one direction of that — a §4d
over-assertion in the other direction.

The residual was carried in a `//` annotation, which §4c rules is the right
place for the rationale and the wrong place for the class: no Accepted program
can read one. §4b requires a row under `dag/gunbc/recurring_failure_mode/` with
invalid state, harm, rung found at, ceiling with reason, and next trigger; meta
obligation 2 forbids an untracked stall.

The corpus already fixes the shape, one arm above in the same file: 127 is an
honest-wide Infra arm whose class lives on
`command_not_found_exit_cannot_distinguish_eviction_from_misnaming`, is cited
from `infra_signature_origin`, and is pinned by the witness. This follows it
exactly:

- New row `timeout_kill_exit_cannot_distinguish_a_wedged_subject_from_a_starved_one`.
  Names the dual as the reason it exists; carries review 69010 as the specimen
  of the annotation-only first draft, and the 195s/202s-vs-124-at-load
  measurement as the observed STARVED arm — stating plainly that the WEDGED arm
  has no specimen yet rather than leaving that to be inferred, and that the
  under-load figures are contention measurements, not compiler figures.
- HARM is bounded honestly: gating is deferred, so this mis-REPORTS rather than
  mis-gates today, and becomes a silencing class the moment a gate consumes the
  verdict.
- CEILING is a join against observed progress, never a termination proof —
  whether the subject would have finished given more time is the halting
  question.
- NEXT-RUNG TRIGGER names a CAPABILITY: join a 124 to the killed process's own
  user-CPU share, major-fault rate and a monotone progress signal. It explicitly
  REFUSES a wall-clock-only reading as sufficient, because that is the
  elapsed-time arm `memory_stall_signal_choice_note` rules out and would resolve
  the honest slow case into the wedged one — the same conflation read backwards.
- Also records why `gunbc.memory_stall_refusal` does NOT cover this arm: it
  refuses only on the fault-rate AND CPU-share conjunction, so a
  starved-but-progressing run correctly reaches no refusal and correctly
  reaches 124.

Cited from `infra_signature_origin(GnuCoreutilsTimeoutKill)` and pinned by two
new conjuncts in the claim, mirroring how CommandNotFound's row is pinned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

review 69010 addressed — verified, and it corrected my reasoning

The finding is right and it is the more important of the two reviews so far.

Classifying 124 as Infra removes one misclassification — a starved runner read as a subject defect — and introduces its dual: a wedged or non-terminating subject now reads as stands-environment. My own annotation said 124 "carries no cause", and then credited only one direction of that. That is a §4d over-assertion in the other direction, and the residual sat in a // annotation, which §4c rules is the right place for the rationale and the wrong place for the class — "an annotation is never evidence that a machine claim holds, because no Accepted program can read one."

The reviewer also named the fix already present one arm above in the same file, and I followed it exactly: 127 is an honest-wide Infra arm whose class lives on command_not_found_exit_cannot_distinguish_eviction_from_misnaming, is cited from infra_signature_origin, and is pinned by the witness.

New row: timeout_kill_exit_cannot_distinguish_a_wedged_subject_from_a_starved_one

  • Invalid state — a starved-but-correct run and a wedged subject present as the same status; timeout reports only that the bound was reached and observes nothing about why.
  • Specimens — review 69010 itself, as the specimen of the annotation-only first draft. Plus the measurement that opened this subject: the required-gate entry at 195s (66d683bc865) and 202s (cf66aa24739) on an idle host, then 124 at a 500s bound with the host at load average 255 across 39 session containers. That is the starved arm, observed. The row states plainly that the wedged arm has no specimen yet, rather than leaving it to be inferred, and repeats that the under-load figures are contention measurements and not compiler figures.
  • Harm, bounded honestly — gating is deferred, so this mis-reports rather than mis-gates today, and becomes a silencing class the moment a gate consumes the verdict.
  • Rung found at — mitigatable; the classifier has no join to any independent observation of progress and review is the wall.
  • Ceiling — a join against observed progress, never a termination proof. Whether the subject would have finished given more time is the halting question, so structural impossibility is not reachable from this status.
  • Next-rung trigger, a capability and not an artifact — join a 124 to the killed process's own user-CPU share, major-fault rate and a monotone progress signal. It explicitly refuses a wall-clock-only reading as sufficient, because that is precisely the elapsed-time arm memory_stall_signal_choice_note rules out, and it would resolve the honest slow case into the wedged one — the same conflation read backwards.

The row also records why gunbc.memory_stall_refusal does not already cover this arm: it refuses only on the fault-rate and CPU-share conjunction, so a starved-but-progressing run correctly reaches no refusal and correctly reaches 124. That is the same reasoning that made me withdraw the original CPU-share repair.

Wired the way 127's is: cited from infra_signature_origin(GnuCoreutilsTimeoutKill), and pinned by two new conjuncts in the claim.

— sent from keen-bat-411

gunbc-ci-auto-heal and others added 2 commits September 20, 2026 09:16
Side-chat review at 23fefa9 filed two blocking findings. I verified both and
then found a third that settles the disposition: the classification must not
land at all.

1. VERIFIED, and I reproduced it myself on GNU coreutils 9.7. `timeout` returns
   124 on expiry AND otherwise returns the managed command's own status, so:
     timeout --kill-after=1s 5s sh -c 'exit 124'   -> 124 in ~6ms,   NO expiry
     timeout --kill-after=1s 0.1s sleep 2          -> 124 in ~111ms, expiry
   Identical status, identical empty output, two different events. The arm
   asserted an observed `GnuCoreutilsTimeoutKill` from an integer it cannot
   derive one from.

2. VERIFIED. The pure `classify_failure_exit` is not the production path. The
   floor's wrapper emits through `floor_attempt_receipt_write_stmts`, which
   inits class=structural, branches on 126/127/0, then applies log-substring
   signatures — and has no 124 branch. The edit left the pure and Bash
   realizations holding independently maintained answers for one question.

3. AND THE ONE THAT DECIDES IT, from the producer census the two findings
   prompted: on the floor path there is NO GNU `timeout` producer at all. The
   floor's bound is the Actions job `timeout-minutes`, which CANCELS the job and
   never renders a wrapped command's status as 124; `timeout_command` is
   consumed only by `runner_attempt_launch` and `runner_microvm_boot_probe`. So
   the only 124 reachable there is a subject's own exit 124, and classifying it
   as environment fails OPEN on a real subject red — the absorbing fallback §5
   calls a hard reject, with the harm inverted from the intent that motivated
   the change.

So `ci_failure_class` and its witness are reverted to main exactly. A bare 124
stays Structural and fails closed, which is the correct disposition for an
unidentified status and is what the corpus already did.

What survives is the class this investigation actually discovered, rewritten
around it: `bare_exit_124_read_as_an_observed_timeout_event`. It carries the
executed counterexample, the producer census, the harm bounded at its real
grain, the ceiling split (structural for "did the bound fire", since the bound's
imposer holds that fact at the moment it fires; only a progress join, never a
termination proof, for wedged-vs-starved), and a trigger naming a CAPABILITY —
a typed timeout-event observation emitted BY THE COMPONENT THAT IMPOSES THE
BOUND — which explicitly refuses a wall-clock reading, since the 6ms/111ms
separation above is exactly what would license it.

The row also records that two earlier reviews on this change corrected a forked
authority and a missing failure-mode row and both passed over this: the defect
was never that 124 was under-documented, but that it was over-read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title CI failure class: a wall-clock timeout kill is Infra, not a verdict about the corpus Withdraw the 124 classification; file the class it turned out to be Sep 20, 2026
…ound and left unstated

Verified and correct. `gunbc.runner_microvm_boot_probe` `boot_probe_verdict`
reads `observed.exit_code == coreutils_timeout_expired_exit_code` and returns
`BootExceededHostDeadline`, and `boot_probe_verdict_text` renders
"boot=host-deadline-expired after 180 seconds -- the guest did not power itself
off and the host killed the VMM". A Firecracker process that exits 124 for its
own reasons produces that sentence verbatim, with a fabricated duration and a
fabricated narrative, while the true cause — an ordinary VMM failure, which
`BootVmmFailed` exists to carry — is discarded.

That is this row's class, inhabited by executing corpus code today. My producer
census named the module and I stopped at "it is a `timeout_command` consumer",
never stating its conclusion; the row's HARM was scoped entirely to the arm I
had just withdrawn. §4d's third arm, in the direction I was not watching: a fact
declined is a wall that does not exist.

Added the HARM receipt and two DeclarationRefs (`boot_probe_verdict`,
`runner_microvm_boot_probe_bounded_command`).

I also state the discriminating fact the reviewer flagged, in BOTH directions,
because only one survives: gunbc really does impose this deadline itself
(`runner_microvm_boot_probe_bounded_command` wraps the VMM in `timeout_command`
at 180s with a 10s kill grace), so unlike the floor path there IS a real GNU
timeout producer here — which is why this is the natural site to DISCHARGE this
row's trigger, and the trigger now says so. It is not a defence of the current
read: imposing a bound establishes that 124 COULD be the deadline, never that it
WAS, because `timeout` returns the managed command's own status when the command
exits on its own. That is precisely the executed counterexample already in the
row — a caller imposing a 5s bound and still receiving 124 at 6ms. Whether a VMM
is LIKELY to exit 124 is an inference about Firecracker, not a fact this corpus
holds, so it cannot stand in for the observation.

I am not repairing `boot_probe_verdict` here: it is another module's subject,
the repair is the trigger this row declares, and folding it in would repeat the
mistake of changing a classifier before its observation exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

review 69053 addressed — verified, and it caught the one thing I had in my hands and dropped

The finding is right, and it is the sharpest kind: the fact was already in my own producer census and I stopped one sentence short of its conclusion.

gunbc.runner_microvm_boot_probe boot_probe_verdict:

if observed.exit_code == coreutils_timeout_expired_exit_code {
  BootExceededHostDeadline { deadline: …, console: …, detail: … }
}

and boot_probe_verdict_text renders it as:

boot=host-deadline-expired after 180 seconds -- the guest did not power itself off and the host killed the VMM

A Firecracker process that exits 124 for its own reasons produces that sentence verbatim — a fabricated duration and a fabricated narrative — while the true cause, an ordinary VMM failure that BootVmmFailed exists to carry, is discarded. That is this row's class inhabited by executing corpus code today, and my HARM receipt was scoped entirely to an arm I had just withdrawn. §4d's third arm, in the direction I was not watching: a fact you decline to state is a wall that does not exist.

Added: the HARM receipt naming the live site, and two DeclarationRefs (boot_probe_verdict, runner_microvm_boot_probe_bounded_command).

On the discriminating fact you offered — I took it, and it cuts one way

You noted I might judge the site defensible because gunbc imposes the deadline itself. I checked, and the row now states it in both directions because only one survives:

  • True: runner_microvm_boot_probe_bounded_command wraps the VMM in timeout_command at 180s with a 10s kill grace. Unlike the floor path, there is a real GNU timeout producer here. That is why this is the natural site to discharge this row's trigger, and the trigger now says so explicitly.
  • Not a defence: imposing a bound establishes that 124 could be the deadline, never that it was — timeout returns the managed command's own status when the command exits on its own. That is exactly the executed counterexample already in the row: a caller imposing a 5s bound and still receiving 124 at ~6ms. Whether a VMM is likely to exit 124 is an inference about Firecracker, not a fact this corpus holds, so it cannot stand in for the observation.

What I did not do

I did not repair boot_probe_verdict in this PR. It is another module's subject, the repair is the trigger this row declares, and changing a classifier before its observation exists is the exact mistake that got the original arm withdrawn. Doing it again one module over would be the same error with a different file name.

Net diff against origin/main remains this one file.

— sent from keen-bat-411

gunbc-ci-auto-heal and others added 2 commits September 20, 2026 10:05
  bare_exit_124_read_as_an_observed_timeout_event.dag:15:5:
  error: expected expression, found Unknown

Self-inflicted and entirely mine: I wrote the receipt through a Python heredoc
and let a shell-level escaped apostrophe (`command\'s`) reach the .dag source.
`\'` is not an escape this lexer admits, so the token came back Unknown and the
whole module failed to parse — which refused the floor's changed-witness
observation before any claim ran.

Plain apostrophe; no other backslash escapes remain in the file. Verified
locally that the module now parses: resolving it reports only "evaluating main",
which is the expected outcome for a data-only module and not a lexical error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings verified against the source; both were right, and both were my
prose over-reaching facts the same row already carried.

RFM-1 — the trigger attributed the missing observation to the command BUILDER.
`runner_microvm_boot_probe_bounded_command` only constructs an `ArgvCommand`
through `timeout_command`; `boot_probe_verdict` then receives a
`ProcessObservation` and infers expiry from its exit code. Nothing in this
corpus observes the enforcement decision, so "already imposes its own bound and
therefore already holds the fact at the instant it fires" was false. Requesting
a bound is not observing that it fired — which is the row's own counterexample
applied to the row's own trigger, and I missed it while writing the sentence
directly beneath it.

The trigger now asks for a typed, EXECUTION-BOUND observation emitted AT THE
DEADLINE-ENFORCEMENT DECISION, states plainly that constructing a `timeout`
invocation does not establish that its deadline fired, and demotes the
boot-probe route from "already holds the fact" to an INITIAL INTEGRATION SITE.
It also qualifies the Actions arm: the observation must IDENTIFY TIMEOUT AS THE
CANCELLATION CAUSE, since a workflow can be cancelled by hand and a generic
cancellation event readmits the same ambiguity one layer up.

RFM-2 — the harm paragraph described the withdrawn classifier in the present
tense. That arm never reached the production floor receipt writer at all
(`floor_attempt_receipt_write_stmts` branches on 126/127/0 and has no 124
branch), so no floor behaviour was ever changed and the claim was a
counterfactual dressed as a reading.

Harm is now stated per consumer, and the containment is attributed correctly.
Verified at this head:

    boot_probe_admitted(BootExceededHostDeadline) -> false
    boot_probe_admitted(BootVmmFailed)            -> false

So an ordinary VMM exit 124 gets a false deadline narrative with a rendered
duration it never observed, but BOTH verdicts refuse boot admission: today's
defect is a wrong explanation, not a silenced failure. That containment belongs
to the boot-probe consumer, NOT to the floor's deferred merge gating, which I
had wrongly credited. The row keeps the forward-looking case: a downstream
consumer that exempted a misclassified timeout from blocking would additionally
silence a subject failure.

The concluding "a bare 124 stays Structural and fails closed" is now scoped to
the FLOOR classifier, and says explicitly that it does not describe the
boot-probe consumer — which the same row records returning
`BootExceededHostDeadline` off that same bare status.

No classifier change, no new module, no timeout implementation, no gate. The
diff against origin/main remains this one file. Kept ASCII-only and verified it
lexes locally before pushing, after the `\'` failure two heads ago.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

RFM-1 and RFM-2 addressed — both verified, both were my prose over-reaching the row's own facts

Neither was a false positive, and both were corrections to sentences I wrote directly above or below the evidence that contradicts them.

RFM-1 — the trigger attributed the observation to the command builder

Confirmed. runner_microvm_boot_probe_bounded_command only constructs an ArgvCommand through timeout_command; boot_probe_verdict then receives a ProcessObservation and infers expiry from its exit code. Nothing in this corpus observes the enforcement decision. So "already imposes its own bound and therefore already holds the fact at the instant it fires" was false — requesting a bound is not observing that it fired, which is this row's own counterexample applied to this row's own trigger.

The trigger now asks for a typed, execution-bound observation emitted at the deadline-enforcement decision, says plainly that constructing a timeout invocation does not establish that its deadline fired, and demotes the boot-probe route from "already holds the fact" to an initial integration site, not an existing producer. Your Actions qualification is in too: the observation must identify timeout as the cancellation cause, since a workflow can be cancelled by hand and a generic cancellation event readmits the same ambiguity one layer up. Exit status, elapsed duration and generic cancellation are named as non-substitutes.

RFM-2 — the harm paragraph described the withdrawn classifier in the present tense

Confirmed, and worse than stated: that arm never reached the production floor receipt writer at all — floor_attempt_receipt_write_stmts branches on 126/127/0 with no 124 branch — so no floor behaviour was ever changed and the sentence was a counterfactual dressed as a reading.

Harm is now stated per consumer, with the containment attributed correctly. Verified at this head:

boot_probe_admitted(BootExceededHostDeadline) -> false
boot_probe_admitted(BootVmmFailed)            -> false

So an ordinary VMM exit 124 gets a false deadline narrative with a rendered duration it never observed, but both verdicts refuse boot admission — today's defect is a wrong explanation, not a silenced failure. That containment belongs to the boot-probe consumer, not to the floor's deferred merge gating, which I had wrongly credited. The forward-looking case is kept: a downstream consumer that exempted a misclassified timeout from blocking would additionally silence a subject failure.

The concluding "a bare 124 stays Structural and fails closed" is now scoped to the floor classifier, and says explicitly that it does not describe the boot-probe consumer — which the same row records returning BootExceededHostDeadline off that same bare status.

On your framing of what CI could and could not settle

Agreed, and worth recording: the four green checks resolve declarations and syntax. Neither of these findings was either. They were about which component possesses the observation and which consumer suffers which harm — and no amount of reference resolution establishes that prose describes behaviour accurately. That is the same failure mode this row is about, turned on the row itself.

No classifier change, no new module, no timeout implementation, no gate. Net diff against origin/main remains this one file. Kept ASCII-only and verified it lexes locally before pushing, after the \' failure two heads ago.

— sent from keen-bat-411

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 20, 2026
Merged via the queue into main with commit 655da6f Sep 20, 2026
4 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/keen-bat-411-timeout-class branch September 20, 2026 14:38
@briansrls
briansrls restored the session/keen-bat-411-timeout-class branch September 20, 2026 14:46
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.

0 participants