Skip to content

NO-FALLBACK-1: mount is a bounded establishment, and a green after a terminal refusal is a defect - #9782

Merged
gunbai-bot[bot] merged 12 commits into
mainfrom
session/silent-crab-842-nofallback-1
Aug 31, 2026
Merged

gunbai-bot[bot] merged 12 commits into
mainfrom
session/silent-crab-842-nofallback-1

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Second of the two no-fallback follow-ups ruled during #9690's review. Stacked on #9779 (base is that branch, so this diff is only the NO-FALLBACK-1 change).

The defect

Read as prose, "wait for the listing to change" and "keep asking until it agrees" are the same sentence. The executed MegaRAC sequence contains a genuine asynchronous pending phase — media config accepted, the daemon's own retry interval elapses, the images listing goes from refusing to listing the image — and that phase existed only in a comment. A documented pending state is legitimate; repeating an operation until an observation finally agrees is not; and nothing in the model could tell them apart.

The repair

gunbc.machine_intake_mount_establishment makes the difference structural:

MountRequested → MountPending → MountEstablished
                              | MountTimedOut
                              | MountObservationContradictory
  • Pending is representable only inside a declared bounded window. The deadline is a function of the request, so "wait a bit longer" has nowhere to live.
  • Every exit is terminal, and the fold has no arm that replaces a terminal standing with a newer observation — last-wins is unwritable rather than discouraged.
  • A listing arriving after the deadline is TimedOutThenListed, carrying the deadline it missed and the bytes it was read from, because discarding an earlier terminal refusal because a later poll agreed is the instability this refuses.
  • An absence after establishment is EstablishedThenAbsent, not a return to pending.
  • Out-of-order and before-the-request observations are defects, not newer readings — the last-wins hazard at its root.
  • An empty observation list is MountRequested and not established: a mount nobody looked at is not a mount that worked.

The carrier is workflow-layer, not extdeps, per DESIGN §3's external-upstream rule: AMI owns the operation contract in extdeps.bmc.megarac; what our polling saw is a receipt of our attempt.

Also repaired

My #9690 merge resolution left the boot-from-ISO recipe annotation duplicated in megarac.dag — main's #9752 conversion and my own copy both landed. Two prose blocks for one fact is exactly the §3 violation that conversion existed to remove. One survives, and it now points at the carrier above instead of describing the wait in prose that reads as permission to poll.

Evidence

All twenty-three witnesses evaluate true. The mutation is the point: making a listing after the timeout an accepted late success — precisely retry-until-green — turns a_listing_after_the_timeout_is_a_contradiction_not_a_late_success false while a_listing_inside_the_window_establishes_the_mount stays true. The pair discriminates the unsafe behaviour specifically rather than any change at all: the legitimate asynchronous path stays green and only the retry-past-terminal path reds. Corpus parse-clean at 4408 files.

🤖 Generated with Claude Code

https://claude.ai/code/session_019fKvi5dnJ8TcE514qbmWTk

Corrections from review (this branch)

Equal timestamps are unordered. The out-of-order check refused only a strict decrease, so two observations sharing one millisecond were resolved by the order the caller passed them — absent-then-listed established the mount, listed-then-absent contradicted it. One clock, two verdicts, decided by an argument's index. ObservationTimestampCollision refuses the pair in either order, byte-identical included: the defect is that the ordering is unknown, not that the observations disagree.

The window is half-open. requested_at <= observed_at < deadline, so an observation exactly at the deadline is outside it and a budget cannot be spent at its own boundary. A zero budget is therefore an immediately-closed window, which is the honest meaning of Milliseconds' declared range rather than a claim the carrier does not support.

Time closes the window. The fold answered only over observations actually taken, so a pending attempt nobody re-polled stayed pending indefinitely past its own deadline — the declared bound was enforced only against callers that kept looking. MountEvaluationBoundary carries its own clock evidence rather than reading a clock, so the same observations plus the same boundary always give the same standing.

The boundary refusal is total across every standing. This was a real wrong answer, caught in review. The frontier was reconstructed from the standing, but only MountPending carries one: MountEstablished records when it was established, not the last thing seen after it, and the other terminal arms carry no frontier at all. So a boundary earlier than the evidence behind any terminal standing was silently accepted — a verdict read as of a moment before the observations that produced it. A per-arm timestamp projection cannot fix this, because "established at 2000" and "last observed at 4000" are different facts and only the fold holds the second. The fold is now the answer and the standing its projection; the two wrong-construction helpers are deleted rather than left beside the fix. EvaluationBoundaryBeforeRequest covers the no-observations case, where the coherent lower bound is the request itself.

Terminal means no revival, not immutability. The prose claimed no later observation can overwrite a terminal standing, which is false and was hiding correct behaviour: Established and TimedOut may be refined into Contradictory, because evidence that an answer was wrong must not be discarded to protect the answer. What no observation may do is revive a terminal standing into pending or success.

Eleven further controls, four of which reach a terminal standing and then observe again so the frontier is later than anything the standing carries — shapes a per-standing patch cannot satisfy.

Round 4 — two facts, and a timeout that invents nothing

MountFold carried one timestamp answering two questions. previous_at answers "is this observation out of order or colliding", which is about the immediately preceding element; the boundary check asked it "what is the latest moment this fold has evidence about", which is about the maximum. Those coincide only while timestamps strictly increase, and this module exists to represent populations where they do not — listed@2000 / absent@3000 / absent@2500 retracted the frontier to 2500 and then accepted a boundary at 2600 over evidence timestamped 3000. frontier_at is now a separate monotone maximum, advanced on every supplied observation including refused ones: it is the high-water mark of what the fold has seen, not of what it accepted, because evidence that provoked a refusal is still evidence in hand.

That same population also lost its contradiction. The temporal prechecks run before advance_mount_standing, so a malformed observation arriving after a contradiction replaced it with its own cause — the declared law that Contradictory maps to the identical Contradictory standing held only for well-formed successors. mount_fold_step now opens with a contradiction guard.

boundary_incoherence checks the two lower bounds independently. The request bound previously lived inside the no-observations arm, so an observation at 500 against a request at 1000, read as of 750, returned an as-of verdict about an attempt that had not yet begun.

And the unobserved timeout fabricated its own evidence: it recorded last_observed_at = window.requested_at, which is when the request was made and not a moment anything was seen. MountTimedOut is now { window, basis } over MountTimeoutBasis, so a clock-only close carries the boundary that closed it with an honestly absent frontier, and a close established by an observed absence carries that observation and its bytes — distinguishable by a consumer, which they were not. The witness covering the unobserved timeout ignored the field and so greened over the fabrication; it now reads it.

Five further controls: contradiction preservation under a malformed successor, 3000 then 2000 read at 2600, a before-request boundary with observations, and the observed-absence basis naming its observation.

Brian Searls and others added 10 commits August 30, 2026 23:11
THE HAZARD WAS NOT THE RANKING WALK, IT WAS WHAT THE PLAN CARRIED. Eligibility
is settled from evidence before any ranking happens, so walking the policy order
attempts nothing -- but the plan then carried the raw solving verdicts, and when
two transports were eligible it handed an executor a second CandidateEligible
row beside the selected one. That row IS the next thing to try, and an executor
holding it can turn a terminal refusal into an ordered search.

A plan now carries BootDeliveryPlanCandidateDisposition:

  PlanCandidateSelected
  PlanCandidateOutrankedBy { selected: BootDeliveryCandidate }
  PlanCandidateIneligible { standing: BootDeliveryCandidateStanding }

There is deliberately no arm meaning "eligible and not selected", so an
available alternative has NO REPRESENTATION in a plan -- construction, not a
check that could be satisfied by editing a declaration. A candidate the policy
outranked names the transport that beat it, so the row states a completed
decision rather than an offer. The raw standing survives on the refusal path,
where nothing was selected and the whole considered set is the evidence.

The candidate identity travels on BuiltBootArtifactDelivery, so a disposition is
decided by IDENTITY against the selection rather than by position in the
preference list, and first_eligible_boot_artifact_delivery is renamed
selected_boot_artifact_delivery: it ranks a set already decided, and the old
name described a search.

THE CREDENTIAL HALF. megarac_factory_login said the published password is "the
first credential to TRY", which is an ordered search with an implied next. There
is no next: a refusal is a terminal typed fact about the unit (rotated, or a
differently-shipped ODM build) carried by a workflow-layer intake receipt.
Guessing again is both the fallback this model refuses and how a fleet locks
itself out.

THE WITNESS IS THE TWO-ELIGIBLE CASE, which is the only shape that separates a
plan from a fallback list -- every other plan control asserts which transport was
selected, and a model that also ships a spare satisfies those identically. The
fixture's profile and live observation admit BOTH Redfish VirtualMedia and the
MegaRAC REST surface, so the policy has a real loser to record: exactly one
PlanCandidateSelected, it is Redfish, and at least one row is
PlanCandidateOutrankedBy naming Redfish. That last clause is what keeps the test
non-vacuous.

DISCRIMINATING, PROVEN BY MUTATION: with the outranked arm replaced so an
eligible unselected candidate stays selected-shaped, the witness returns false;
true with the repair. Corpus parse-clean at 4406 files; the boot-delivery
witnesses evaluate true.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fKvi5dnJ8TcE514qbmWTk
…terminal refusal is a defect

READ AS PROSE, "wait for the listing to change" AND "keep asking until it agrees"
ARE THE SAME SENTENCE. The executed MegaRAC sequence contains a real
asynchronous pending phase -- the media config is accepted, the daemon's own
retry interval elapses, and the images listing goes from refusing to listing the
image -- and that phase was described only in a comment. A documented pending
state is legitimate; repeating an operation until an observation finally agrees
is not; and nothing in the model could tell them apart.

gunbc.machine_intake_mount_establishment makes the difference structural.
Pending is representable ONLY inside a declared bounded window, so "wait a bit
longer" has nowhere to live: the deadline is a function of the request, not of
how patient the caller feels. Every exit from that window is TERMINAL, and the
fold has no arm that replaces a terminal standing with a newer observation, so
last-wins is unwritable rather than discouraged:

  MountRequested -> MountPending -> MountEstablished
                                 |  MountTimedOut
                                 |  MountObservationContradictory

A listing that arrives after the deadline is TimedOutThenListed -- a
contradiction carrying the deadline it missed and the bytes it was read from --
because discarding an earlier terminal refusal because a later poll agreed is
the instability this refuses. An absence after establishment is
EstablishedThenAbsent rather than a return to pending. An out-of-order or
before-the-request observation is a defect, not a newer reading: that is the
last-wins hazard at its root. An empty observation list is MountRequested and
NOT established, because a mount nobody looked at is not a mount that worked.

THE CARRIER IS WORKFLOW-LAYER, NOT extdeps, per DESIGN section 3's external
upstream rule: AMI owns the operation contract in extdeps.bmc.megarac; what OUR
polling saw is a receipt of OUR attempt and is ours to model.

ALSO REPAIRED HERE: my #9690 merge resolution left the boot-from-ISO recipe
annotation DUPLICATED in megarac.dag -- main's #9752 conversion and my own copy
both landed. Two prose blocks for one fact is the §3 violation the conversion
existed to remove. One survives, and it now points at the carrier above rather
than describing the wait in prose that reads as permission to poll.

EVIDENCE, and the mutation is the point. All seven witnesses evaluate true.
Mutating the model so a listing after the timeout is accepted as a late success
-- exactly retry-until-green -- turns
a_listing_after_the_timeout_is_a_contradiction_not_a_late_success FALSE while
a_listing_inside_the_window_establishes_the_mount stays TRUE. The pair
discriminates the unsafe behaviour specifically rather than any change at all:
the legitimate asynchronous pending path is still green, and only the
retry-past-terminal path reds. Corpus parse-clean at 4408 files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fKvi5dnJ8TcE514qbmWTk
Reviewing authority's hold on NO-FALLBACK-0. PlanCandidateIneligible carried
the whole BootDeliveryCandidateStanding, and that coproduct contains
CandidateEligible, so this was authorable:

    PlanCandidateIneligible { standing: CandidateEligible }

a candidate recorded as ineligible whose stated cause is that it was eligible
— a spare available transport sitting inside a plan, which is the exact state
NO-FALLBACK-0 exists to forbid. No producer emitted it, but the type admitted
it, so the guarantee rested on the producer's restraint. That is validation
standing where construction was available (DESIGN §5).

BootDeliveryCandidateIneligibility is now a refusal-only coproduct carrying
every named cause and no eligible inhabitant, and standing is the two-arm sum
CandidateEligible | CandidateIneligible { cause }. The plan's ineligible arm
carries the refusal-only type, so the contradictory row has no constructor
rather than merely no producer. 29 production and 24 witness construction and
pattern sites migrated to the nested cause.

THE WALL IS PROVEN WHERE ITS RED IS AUTHORABLE. The violation can no longer be
written in the accepted corpus, so a check for it inside the corpus would be
permanently green by construction — a decoration that would later be cited as
coverage (§4b). §4b names the boundary that decides this: a state
unrepresentable in the accepted corpus may still be representable as source
handed to the compiler by a fixture. no_fallback_plan_ineligibility_wall_test
does exactly that. The RED places CandidateEligible in the plan's ineligible
arm and is refused with a blocking TypeMismatch; the positive control places a
real ineligibility cause and still compiles, so a green RED cannot be
attributed to a typo or a broken import. Both execute.

The PR's placeholder title and auto-opened TODO body are replaced with the
actual NO-FALLBACK-0 summary and evidence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fKvi5dnJ8TcE514qbmWTk
Reviewing authority's four corrections to NO-FALLBACK-1.

EQUAL TIMESTAMPS. The out-of-order check refused only a strict decrease, so two
observations sharing one millisecond were interpreted in the order the caller
passed them: absent-then-listed established the mount, listed-then-absent
contradicted it. One clock, two verdicts, decided by an argument's index. The
clock proves no order between them, so ObservationTimestampCollision refuses
the pair -- even when the two observations are byte-identical, because the
defect is that the ordering is unknown, not that the observations disagree. A
producer that legitimately emits several observations inside one millisecond
needs a source-minted monotonic sequence number; until it has one, its ordering
is not a fact this model may read.

HALF-OPEN WINDOW. requested_at <= observed_at < deadline. An observation
exactly AT the deadline is outside the establishment window, so a budget cannot
be spent at its own boundary.

TIME CLOSES THE WINDOW. The fold answered over the observations actually taken,
so with no later poll a pending attempt stayed Pending and an unobserved one
stayed Requested, indefinitely, past its own deadline -- the declared bound was
only enforced against callers that kept looking. MountEvaluationBoundary
carries its own clock evidence rather than reading a clock, so the same
observations plus the same boundary always give the same standing. A boundary
preceding the last observation is refused rather than clamped: clamping would
silently reopen a window the observations had already carried past.

TERMINAL MEANS NO REVIVAL, NOT IMMUTABILITY. The annotation claimed no later
observation can overwrite a terminal standing, which is false and was hiding a
correct behaviour: Established and TimedOut may be REFINED INTO Contradictory,
because evidence that an answer was wrong must not be discarded to protect the
answer. What no observation may do is revive a terminal standing into pending
or success. The narrower law is now stated as the fold implements it.

The zero-budget claim is withdrawn rather than propped up. The annotation said
a budgetless window was unrepresentable; Milliseconds is range(min: 0) and the
compiler reports where-refinements as deferred, so the claim was rung inflation
(DESIGN 4b). Adding a second unenforced refinement would have been the same
inflation twice. Zero is now modeled explicitly as an immediately-closed
window, which half-open already handles correctly: it admits no observation, so
the first one is judged past the deadline.

Six controls added, one per corrected behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fKvi5dnJ8TcE514qbmWTk
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fKvi5dnJ8TcE514qbmWTk
…anding

Reviewing authority reopened a narrow hold, and it was a real wrong answer.

mount_standing_as_of reconstructed the observation frontier FROM the standing,
but only MountPending carries one: MountEstablished records when it was
established, not the last thing seen after it, and MountTimedOut and
MountObservationContradictory carry no frontier at all. So the
boundary-before-last-observation refusal fired only for pending attempts, and a
boundary earlier than the evidence behind any TERMINAL standing was silently
accepted -- a verdict read as of a moment before the observations that produced
it. All three of these were authorable: listed at 4000 read as of 3000,
timed out at 9000 read as of 8000, a contradiction completed at 5000 read as
of 4000.

Repairing it with a timestamp projection per terminal variant would still be
wrong, because "established at 2000" and "last observed at 4000" are different
facts and no arm of the standing holds the second. The fold already computes it
exactly, so the fold is now the answer and the standing is its projection.
mount_last_observed_at and boundary_precedes_last_observation are DELETED rather
than left beside the fix: they were the wrong construction, and a climb removes
the machinery it obsoletes.

With nothing observed there is no frontier at all, so the coherent lower bound
is the request itself: EvaluationBoundaryBeforeRequest refuses a boundary that
would describe the request as already standing at a time before it occurred.

Four controls, each reaching a TERMINAL standing and then observing again so the
frontier is later than anything the standing carries -- shapes a per-standing
timestamp patch cannot satisfy. All 18 witnesses execute and return true.

The zero-budget annotation's refinement claim was too broad and is corrected.
Saying `where range` refinements "enforce nothing" is false: decidable
predicates at literal positions are hard construction walls, and only genuinely
nonliteral values stay deferred. The accurate claim, now stated, is that a
second gt_zero would not establish positivity for nonliteral inputs, so this
module does not claim a total positive-budget wall; that residual gap belongs to
the compiler's refinement lane, not to a local duplicate validator here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fKvi5dnJ8TcE514qbmWTk
…unobserved timeout carries no observation

MountFold carried one timestamp serving two questions. previous_at answers
"is this observation out of order or colliding", which is about the
IMMEDIATELY PRECEDING element. The boundary check asked it "what is the
latest moment this fold has evidence about", which is about the MAXIMUM.
Those coincide only while timestamps strictly increase, and this module
exists to represent populations where they do not.

frontier_at is now a separate, monotone maximum advanced on every supplied
observation including refused ones -- it is the high-water mark of what the
fold has SEEN, not of what it accepted, because evidence that provoked a
refusal is still evidence in hand. listed@2000 / absent@3000 / absent@2500
previously retracted the frontier to 2500 and accepted a boundary at 2600
over evidence timestamped 3000.

The same population also lost its contradiction. The temporal prechecks run
before advance_mount_standing, so an out-of-order, colliding or
before-request observation arriving AFTER a contradiction replaced it with
its own cause -- the declared law that Contradictory maps to the identical
Contradictory standing was true only for well-formed successors.
mount_fold_step's first arm is now a contradiction guard, so the original
cause survives while the frontier still advances.

boundary_incoherence checks the two lower bounds independently. The request
bound previously lived inside the no-observations arm, so an observation at
500 against a request at 1000, read as of 750, returned an as-of verdict
about an attempt that had not yet begun. It is now checked first and
unconditionally.

And the unobserved timeout invented its own evidence: it recorded
last_observed_at = window.requested_at, which is when the REQUEST was made
and not a moment anything was seen. MountTimedOut is now { window, basis }
over MountTimeoutBasis, so a clock-only close carries the boundary that
closed it with an honestly absent frontier, and a close established by an
observed absence carries that observation and its bytes. The two are now
distinguishable by a consumer, which they were not.

The witness that covered the unobserved timeout ignored the field, so it
greened over the fabrication; it now reads it. Five further controls: the
contradiction-preservation case, 3000-then-2000 read at 2600, a
before-request boundary WITH observations, and the observed-absence basis.
23 witnesses, all green by execution.
Base automatically changed from session/silent-crab-842-nofallback to main August 31, 2026 17:30
Brian Searls added 2 commits August 31, 2026 17:30
…ere rebuilds the defect it closes

cli_run::nfr_tests::nfr_roster_receipt refused this branch: unrostered=1,
and the site was standing_is_contradictory, the round-4 guard. The check
flags a wildcard over a closed coproduct, and I had written one.

Rostering it was the available debt path and is the wrong one here. This
guard decides whether the fold PRESERVES what it already concluded, so
`_ => false` classifies every standing anyone adds later as not
contradictory and lets the temporal prechecks overwrite it -- which is
exactly the defect the guard was added to close, reintroduced by the next
person to extend RemoteMediaMountStanding, silently and at a distance. The
arms are now listed, so that change is a compile error rather than a wrong
answer.

The module now contains no wildcard match at all. 23 witnesses green by
execution (pass=23 fail=0), and the roster receipt passes.
@gunbai-bot
gunbai-bot Bot merged commit c8be9a3 into main Aug 31, 2026
5 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/silent-crab-842-nofallback-1 branch August 31, 2026 19:37
@briansrls
briansrls restored the session/silent-crab-842-nofallback-1 branch August 31, 2026 19:40
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