Skip to content

Bound the microVM guest by containment, and refuse at the signed Firecracker boundary - #10021

Merged
gunbai-bot[bot] merged 17 commits into
mainfrom
session/smart-crane-230
Sep 3, 2026
Merged

gunbai-bot[bot] merged 17 commits into
mainfrom
session/smart-crane-230

Conversation

@briansrls

@briansrls briansrls commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

The boundary is signed, and it was fail-open

FirecrackerMachineConfig carries vcpu_count: Int and mem_size_mib: Int — upstream's own shape, faithfully modeled, and signed. Until this revision the admission took mem_size_mib straight through to a magnitude with nothing in between, and Nat downstream does not rescue it because the realization is i64. So:

one valid root device, mem_size_mib = -1, reserve 256 MiB, cell 16 GiB
  ->  -1 MiB + 256 MiB <= 16 GiB  ->  GuestFitsWithinCell  ->  ADMITTED

A fail-open arm inside the very relation this change exists to establish. Non-positive vcpu_count was never validated at all.

guest_resources_of_machine_config now produces validated magnitudes or a typed refusal before fit and before admission. Two decisions worth naming:

  • The upper bound is the representable one, (2^63−1)/2^20, not a policy guess. Refusing where the arithmetic cannot be performed is honest; a smaller "reasonable" ceiling would be unstated policy wearing a representability argument's clothes.
  • The addition is gone, not guarded. guest + reserve <= envelope is the law, but evaluating the left side can wrap on a hostile value and answer the wrong question silently. The comparison is by subtraction — guest <= envelope − reserve, with reserve-exceeds-envelope split out so it never runs below zero — which cannot overflow for non-negative inputs.

Five controls, and the fifth is not optional: negative memory refuses, zero refuses, non-positive vCPU refuses, unrepresentable memory refuses, and a small positive guest is still admitted. Without that last one the four refusals are all satisfied by an admission that refuses everything.


The one execution that carries the change

the_same_guest_is_refused_under_the_production_reserve. The same guest, judged by the same admission that three other witnesses see admit it — refused, because one coordinate changed: the realization reserve.

guest 4 GiB, declared 256 MiB reserve, 16 GiB cell   -> ADMITTED
guest 4 GiB, production (unmodeled) reserve          -> REFUSED, naming where the quantity is owed

Boundary cases can all be green under a predicate that is subtly wrong in one direction. A single input where one varied coordinate flips the answer rules out both degenerate readings — admits-everything and refuses-everything — in one execution. Everything below is why that flip is the right behaviour.


Revives PR #9578 (runner microVM in .dag), reconciles it with FCI-1's bounded execution context, and — after the reconciliation turned out to be wrong — replaces the sizing derivation with a containment judgment and two typed refusals.

What the guest's resources are, and what they are not

The FCI-1 reconciliation asked the right question ("what bounded owned cell realizes this Work?") and answered it with one number doing the work of three. The guest's RAM was derived as the cell's whole MemoryMax. But machine_config.mem_size_mib is guest RAM, and the Firecracker VMM process that owns the guest lives inside that same cell — so the realization's own cost was zero and unnamed, and the slice would have OOM-killed the VM it exists to bound.

Three quantities now, as three carriers:

carrier what it answers
CellMemoryEnvelope what the bounded cell is granted — read from gunbc_runner_slot_desired, the single authority
GuestMemoryRequest what the guest is handed — Firecracker's mem_size_mib
RealizationReserve what the VMM process itself costs, inside that cell

The safety property is containment, not equality:

guest + reserve <= envelope

A guest smaller than the envelope fits. The withdrawn controls asserted the opposite in both directions at once — the accept arm admitted a guest sized at the whole ceiling, and the refuse arm rejected a 4 GiB guest against a 16 GiB cell. Both were wrong, in opposite directions, while the pair looked discriminating. What it discriminated was equality-to-the-envelope, which is neither the safety property nor a property the cell can enforce.

The reserve is a parameter, and that is what makes the walls authorable

Production cannot supply a reserve. extdeps.virtualization.firecracker cites no VMM footprint, and the quantity may not exist as a constant at all — it scales with guest size through page tables, with the device model, and with host page size, so what upstream documents is a measured overhead for one stated configuration, an observation about one boot rather than a property of the realization. Inventing a subtrahend would be a fabricated plausible output (§5) wearing a derivation's clothes.

So gunbc_runner_microvm_realization_reserve() refuses, and every production path refuses with it.

Had the reserve been read from a global, that refusal would be the only reachable outcome — neither fit arm could ever execute, and the containment check would be a permanently-green decoration that gets cited as coverage (§4b). Taking it as a parameter lets a witness declare one from a controlled fixture and drive both polarities, while production stays honest. That is also what restores the admission's accept arm, which the previous revision had left unreachable and therefore unexercised.

The CPU axis refuses, and it is not repairable in this module

vcpu_count was derived from an absolute cell CPU entitlement that does not exist. gunbc.fabric.fabric_cell_effect fabric_cell_slice_desired_directives emits five directives whose CPU one is CPUWeight — a relative share that decides who yields first under contention and guarantees no amount of anything. The absolute ceiling this fleet does declare (gunbc.host.host_converge runner_cpu_boundary_knobs, writing CapacityQuota as a CPUQuota drop-in) lands on the runner unit, a different systemd object from the cell slice.

One bounded execution context, realized twice, with the two realizations enforcing different axes. Deriving a guest vCPU count from the cell would equate it to a quantity the cell never enforces — the failure gunbc.ci.ci_runner_placement already records once: "the arithmetic said six threads per slot and the host was never told."

No vCPU number is produced. The sizing refuses and names which axis is unavailable. Either an absolute cell entitlement becomes real — a change to fabric_cell_effect, not to this module — or guest topology is declared as its own product decision rather than derived. Both are legitimate; deriving from an entitlement that does not exist is not. That is filed as gunbc.guarantee_rung_drop runner_microvm_guest_size_derivation_stall.

The nineteen witnesses, and which ones carry the claim

All nineteen executed planned-and-passed on the floor (verdict=FloorClean unexpected_failures=0). Four carry the argument:

  • a_guest_smaller_than_the_cell_envelope_fits — 4 GiB guest + 256 MiB reserve against a 16 GiB cell. Fits, and is not equal, so the predicate is not green by only ever seeing equal values. This is the configuration the withdrawn control refused.
  • a_guest_sized_at_the_whole_envelope_is_refused — the measured defect itself, now a red: a guest equal to MemoryMax leaves the VMM nothing, so any nonzero reserve puts the pair over. This is the configuration the withdrawn derivation produced and the withdrawn control admitted.
  • a_guest_exactly_filling_the_envelope_with_its_reserve_fits — the inclusive boundary, asserted because "fits" and "exactly fills" are one comparison apart and an off-by-one silently re-admits the defect.
  • the_same_guest_is_refused_under_the_production_reserve — the pair that matters most. The same guest and the same admission that are admitted under a declared reserve are refused under the production one. Only the reserve changes and the answer flips, so the admission is demonstrably neither admitting everything nor refusing everything.

a_resolved_core_count_refuses_naming_the_relative_cpu_entitlement and an_unresolved_core_count_refuses_naming_the_core_count reach different causes from the same function, which is what separates a refusal that reports which fact is missing from one that refuses uniformly.

Two corrections against my own earlier work in this PR

A witness was deleted rather than repaired. no_fleet_constructed_vm_is_admissible_while_the_footprint_is_unmodeled returned true from its unresolved arm, so it returned true without ever reaching the admission. It could not fail, and it cleared the floor and two approvals in that state while standing in this description as a replacement for five withdrawn witnesses. A witness whose refusal arm returns true is not a control; it is a restatement of the refusal. Its honest successor is the_production_reserve_is_unmodeled_and_says_where_it_is_owed, which fails if the cause stops naming where the quantity is owed.

One bad assertion left this branch by accident, not by diagnosis. the_mvp_sized_vm_is_refused_against_the_cell_envelope — which refused a guest that fits — was removed because MicroVmSized became unreachable, not because anyone noticed the containment error. Had the redesign gone another way it would have been carried forward intact, and the reviews would not have caught it: that pair had already been described as the strongest evidence in the change.


🤖 Generated with Claude Code

https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc

…olve the coproduct predicates review 57217 named

#9578 landed the microVM model with three things still open. This closes them.

THE VM SIZE IS THE CELL'S RESOURCE ENVELOPE, DERIVED. FCI-1 freezes
`cell-resource-envelope` as an output identity, and this fleet already answers it once
in gunbc.runner_slot_allocation gunbc_runner_slot_desired. runner_microvm authored a
second one beside it -- 4 vCPU / 4 GiB against a cell declaring 6 cores / 16 GiB -- which
is the section 3 failure, and not a harmless one: a guest sized under the cell's ceiling
makes the same work die inside a slice that still had room, with the cgroup ledger
showing the slice well under its limit while the job died. The size is now derived from
that one authority, so there is no second row to drift.

THE PREDICATES ARE DISSOLVED INTO THE DECIDING SITE, not rostered. Review 57217's live
finding was two Bools classifying coproducts -- jit_transport_is_attempt_scoped over
JitCredentialTransport and topology_requires_cgroup_reaching_teardown over
VmmProcessTopology. gunbc.non_fold_residue rosters matches carrying a top-level WILDCARD
arm; these are total, so the roster is not their form. commit_workflow's CheckRemoval
selection is: match the coproduct where the consequence is decided, so a new variant
fails to compile at the site that must rule on it. runner_microvm_admission is now that
cascade. The two functions I would otherwise have added for the envelope comparison are
absent for the same reason.

An unresolved envelope and a disagreeing envelope are separate refusal arms. They have
different owners and different remedies, and one Bool would have answered for both.

std.measure gains mebibyte_scale_factor_bytes and mebibyte_to_byte_size, exact peers of
the gibibyte and kibibyte conversions already there -- a fill along the scale axis, not a
new concept.

Both files move into the runner family directories, where every other runner_*.dag and
runner_*_witness_test.dag already lives; they were the only two stragglers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
@gunbai-bot
gunbai-bot Bot force-pushed the session/smart-crane-230 branch from 5f82539 to e2a3ea9 Compare September 2, 2026 07:38
@gunbai-bot gunbai-bot Bot changed the title FCI-1b structural arm: revive orphaned PR #9578 (runner microVM in .dag) — merge current main, clear the floor red, reconcile runner_microvm with FCI-1's bounded execution context Main's floor is refused by one Firecracker disposition row; and the runner microVM's size is the bounded cell's envelope, derived rather than authored beside it Sep 2, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 2, 2026 07:41
@gunbai-bot
gunbai-bot Bot force-pushed the session/smart-crane-230 branch from 7134c49 to e2a3ea9 Compare September 2, 2026 07:53
@gunbai-bot gunbai-bot Bot changed the title Main's floor is refused by one Firecracker disposition row; and the runner microVM's size is the bounded cell's envelope, derived rather than authored beside it The runner microVM's size is the bounded cell's resource envelope, derived rather than authored beside it — and review 57217's coproduct predicates are dissolved into the deciding site Sep 2, 2026
Integrating #10005, #10014, #10017, #10024 and #10025 so this branch is judged
against the base in use rather than 4605989. No overlap with the files this
branch touches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
@gunbai-bot

gunbai-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Both findings verified against the current tree. Finding 1 is correct and is a real defect I introduced — fixed. Finding 2 I am not taking, with reasoning below.

Finding 1 — the non-terminating unresolved arm. Confirmed.

review 58648 is right, and it is worse than the review states. specimen_config's unresolved arm called two_root_config(), and two_root_config built three of its four fields from specimen_config(). That is unbounded mutual recursion with no descent — in a file whose entire subject is fail-closed refusal — reachable exactly when the cell envelope stops resolving, which is the one case the arm existed to handle.

It was also a fabricated fallback on its own terms, which I should have seen when I wrote the comment claiming the opposite. Substituting a deliberately malformed configuration makes admission refuse for a different reason, converting "the envelope did not resolve" into "the VM was malformed" — two states with different owners and different remedies. That is the absorbing arm §5 forbids, and my own module changes in this PR split those exact two states into separate refusal arms while the fixture quietly re-merged them.

The fix propagates instead of substituting. specimen_config() -> FirecrackerVmConfig becomes specimen_resolution() -> RunnerMicroVmConfigResolution, with no fallback arm at all. All nine witness call sites now match it:

match specimen_resolution() {
  MicroVmConfigUnresolved { cause: _ } => false
  MicroVmConfigResolved { config: c } => <the assertion, against c>
}

An unresolved envelope now makes each witness false by its own arm — the honest red. two_root_config is rebuilt from mvp_sized_config(), a plain constructor, so the cycle is gone: two_root_config no longer reaches the specimen at all. Verified: 15 test fns preserved, braces balanced, zero remaining references to the deleted function, zero specimen references inside two_root_config.

Finding 2 — the envelope recognizer. Not taking it.

admission_is_envelope_refusal is total over all six RunnerMicroVmAdmission arms — six explicit match arms, no wildcard. That is the opposite of the harm the dissolution rule targets. The rule exists because a Bool classifier destroys exhaustiveness at every call site, so a new variant gets silently absorbed into one side; here a seventh variant fails to compile in this helper, which is precisely the property the rule protects.

It is also not a new surface. Four sibling recognizers already exist in this file — admission_is_admitted, _orphan_refusal, _credential_refusal, _malformed_refusal — all the same shape, all pre-existing, and I extended each with the two new arms in this PR. A witness function must return Bool; that is the test harness's signature, not a modelling choice.

So inlining only mine would leave four identical helpers plus one special case. The §2 citation in the review ("model a concept once; derive every use") argues for the uniform family and against making one of five an exception — singling out the newest member forks the file's idiom rather than dissolving anything.

The predicates this PR genuinely dissolved were different in kind: jit_transport_is_attempt_scoped and topology_requires_cgroup_reaching_teardown were production classifiers in gunbc.runner_microvm, consumed by an if-chain that made the admission's decisions unfalsifiable per-variant. Those are gone, and the two Bool helpers I had written for the envelope comparison went with them. A total recognizer in a witness file is not that thing.

Not yet pushed, deliberately

This PR has never had a valid CI result — every prior run was cancelled except one failure predating every fix. Run 33621969276 is currently executing on the merge of current main and is the first real floor this branch will have had. Pushing now would cancel it, which is what produced the earlier cancellations. The fix and this reasoning are committed locally and go up the moment that run reaches terminal, so the evidence is not thrown away a fourth time. The recursion is latent — it fires only on an unresolved envelope, which does not occur on this run — so it does not affect what that floor measures.

— sent from smart-crane-230

Brian Searls and others added 2 commits September 2, 2026 11:17
…d the coverage hole gets its second cited instance

TWO CHANGES, ONE A REAL DEFECT CAUGHT BY REVIEW 58648.

THE UNRESOLVED ARM WAS UNBOUNDED MUTUAL RECURSION. specimen_config's unresolved arm
substituted two_root_config(), and two_root_config built three of its four fields FROM
specimen_config(). No descent, in a file whose subject is fail-closed refusal, reachable
exactly when the cell envelope stops resolving -- the one case the arm existed to handle.

It was a fabricated fallback on its own terms as well. Substituting a deliberately
malformed configuration makes admission refuse for a DIFFERENT reason, converting "the
envelope did not resolve" into "the VM was malformed" -- two states with different owners.
That is the absorbing arm DESIGN 5 forbids, and this PR's own module changes split those
exact two states into separate refusal arms while the fixture quietly re-merged them.

specimen_config -> specimen_resolution, returning RunnerMicroVmConfigResolution with NO
fallback arm. All nine witness call sites match it, so an unresolved envelope makes each
witness false by its own arm. two_root_config is rebuilt from mvp_sized_config, a plain
constructor, so it no longer reaches the specimen at all.

THE UNOWNED-MIRROR COVERAGE HOLE GETS ITS SECOND CITED INSTANCE, as an annotation on the
witness that already documents it rather than as a new artifact. cli_run.rs IS in
HAND_MAINTAINED_STAGE0_FILES; v1_compiler_compiler_tests_rust.rs is NOT, so it is emitted
rather than copied. One instance on a hand-authored file is a bookkeeping gap; an instance
on a GENERATED mirror says the crate-partition authority does not cover files the regen
itself produces. The two differ on exactly the axis that separates those readings, which
is why the second is evidence rather than a duplicate. Observed 2026-09-02 when
--regen-round-cost refused its partitioned rebuild naming that mirror.

Not fixed here: stage0_crate_partition_generated.dag declares itself generated from
v2.workflow.rust_crate_partition and not hand-editable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
…s branch added

required-witnesses-build refused this branch:

  required-ci: regen FAIL generated surface drift: std_measure.rs
  required-ci: FAILED PHASE regen (1 failure(s))

dag/std/measure.dag gained mebibyte_scale_factor_bytes and mebibyte_to_byte_size in this
branch; src/v1/stage0/src/std_measure.rs is its mirror and was never regenerated, so the
committed surface did not carry them.

PRODUCED, NOT HAND-WRITTEN: installed from the --required-regen candidate tree, which was
also the check that std_measure.rs is the ONLY drift (diff -rq against the candidate names
that file and no other; the run's own line reads declared_divergent=1 [main.rs], the
standing declared divergence, and generated-artifact rostered=35 adjudicated=35 matches=35
drifted=0).

ONE ROUND, NOT TWO: std_measure.rs is compiled from source rather than emitted through a
seed function, so there is no install-and-rebuild step between the authority edit and the
mirror -- unlike the compiler_tests.rs chain, where the emitted text comes from the seed's
own compiled-in producer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
@gunbai-bot

gunbai-bot Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Investigated. The one failing check is not fixable from this branch, and I am deliberately not pushing a fix.

Run 33625319080 on 0677577:

check result
rust-unit-tests ✅ success
required-witnesses-build ✅ success
fabric-evidence ✅ success
required-witnesses-floor ❌ failure

The floor refusal, in full — this is the entire output, not an excerpt:

required-ci: floor refused: subject=a6761e2279df76bb modules_resolved=2172 modules_excluded=2
dag/extdeps/virtualization/firecracker.dag:15:3: error: field 'reason' not found in type 'SingleAuthority'

This PR does not modify firecracker.dag. The row writes SingleAuthority { reason: … }, but SingleAuthority is a fieldless ConstructionMechanism variant, not a Disposition. That defect is on main right now; main's own floor runs pass over it because unchanged modules stay out of the subject. This PR edits runner_microvm.dag, which imports that module, pulling it into the changed closure — modules_resolved=2172 here against 2170 on main. It is latent on main and this PR is simply the first to reach it.

The fix is #10023 (one line: Terminal { reason: … }, the variant 64 other modules use). It was deliberately kept out of this PR so the two would not duplicate; re-adding it here would recreate that duplicate the moment #10023 lands.

The previous run's two failures are down to one. required-witnesses-build was failing on regen FAIL generated surface drift: std_measure.rs — that one was mine, from adding two conversions to dag/std/measure.dag without regenerating its stage0 mirror. Fixed in 0677577 with the mirror produced from the regen candidate, and that job is now green.

Nothing new appeared. The floor refusal is byte-identical to the prior run's — same single line, same modules_resolved=2172 — so the recursion repair (review 58648) and the regenerated mirror introduced no new refusal.

This PR is green on everything within its own control and will be judgeable the moment #10023 merges.

(Edited: three code spans in the paragraph above were lost to shell evaluation when first posted. Content restored; nothing else changed.)

— sent from smart-crane-230

The floor blocker is gone from the authority — firecracker.dag now reads
Disposition = Terminal { reason: ... } with the matching import.

This is a merge rather than a re-run for a reason this branch itself established:
the merge ref is PINNED AT PUSH TIME, and #10021 was last pushed before #10023
landed. A re-run would replay the pinned ref, refuse identically on the same one
line, and read as though the fix had failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLOCKING REVIEW COMMENT — the executed witnesses discriminate exact equality, but exact equality is not the relation between a Firecracker guest machine and its host cgroup. GitHub will not permit the connected briansrls account to submit REQUEST_CHANGES on its own PR; this comment records the same merge-blocking ruling and is not an approval.

Bound subject:

head  c77ac6b027b455f5b16ad855c42e1cc5037f3de3
tree  18da4939fd71dc04944ef683255cd175ba5ffa96

The structural direction up to this boundary is accepted. The 4-vCPU/4-GiB row is correctly demoted from fleet authority to historical boot evidence; the unresolved-envelope fixture no longer fabricates a malformed substitute; the recursion cycle is gone; and the same-run admitted/refused pair demonstrates that the equality predicate is not constant. Those are real closures.

They expose a deeper subject error: the pair is answering the wrong question.

1. Host MemoryMax and guest mem_size_mib are different quantities

fabric_cell_slice_desired_directives realizes gunbc_runner_slot_desired().memory_max as MemoryMax on the host cgroup containing the realization. runner_microvm_size_from_cell_envelope copies that same count into Firecracker mem_size_mib, which is the amount of memory presented to the guest.

Those are not two encodings of one fact. The host cgroup accounts the Firecracker VMM and the guest-memory backing, along with other charged host-side memory. Firecracker's own specification separately names nonzero VMM memory overhead and says that it depends on workload and configuration. Therefore:

guest-visible RAM == host MemoryMax

is not construction over validation. It erases the realization overhead between two domains.

At the current 16-GiB equality, a guest may be told it owns 16 GiB while the host cgroup has no independently modeled allowance for the process that realizes those 16 GiB. Swap may change the operational outcome, but it does not make the two quantities identical; if swap is part of the admitted relation, that relation must be modeled explicitly rather than inherited by coincidence.

The repair needs separate carriers, conceptually:

FabricCellHostMemoryEnvelope {
  memory_max
  memory_high
  memory_swap_max
}

GuestMachineMemoryEnvelope {
  guest_ram
}

MicroVmMemoryRealizationBudget {
  guest_ram
  vmm_reserve
  other_host_side_reserve
  admitted_relation
}

The admitted relation may be a no-swap guarantee, a resident-plus-swap policy, or another explicitly chosen contract. This review does not choose it. It must not be exact equality merely because both current fields are denominated in bytes.

Do not repair this by subtracting a copied 5 MiB literal. Firecracker's published bound is for a specific baseline and explicitly varies with workload/configuration. The reserve needs a typed measured or cited basis at the configuration this fleet will run; unavailable basis must refuse.

The existing negative control must change with the question. A guest smaller than MemoryMax is not inherently wrong; it may be the only correct realization once overhead is reserved. Required discriminators include at least:

known host envelope + admitted realization reserve + fitting guest
  -> admitted

guest plus required reserve exceeds host envelope
  -> located memory-realization refusal

reserve unavailable/unmeasured
  -> located envelope-unresolved refusal

increased reserve with unchanged host envelope
  -> admitted guest capacity decreases or the realization refuses

2. Six vCPUs are not currently an enforced cell CPU envelope

The current fabric-cell boundary realizes exactly five directives:

MemoryMax
MemoryHigh
MemorySwapMax
TasksMax
CPUWeight

It carries neither CPUQuota nor an assigned CPU set. CPUWeight is a relative scheduling weight, not an absolute entitlement to six CPUs. gunbc_runner_cores_per_slot() is presently used to derive fleet width/planning; it is not a coordinate of the cgroup boundary FCI-1 observes and converges.

Therefore the PR's second equality also conflates two facts:

guest vCPU topology
host cell CPU entitlement

The guest may still correctly have six vCPUs, but that value is not today “the cell's own resource envelope.” Choose one honest repair:

  1. add a real typed and enforced outer CPU entitlement (CPUQuota, cpuset, or another admitted mechanism), include it in desired state, observation, digest and convergence, then relate guest vCPUs to it; or
  2. keep guest vCPU count as a separately named guest-topology/product decision derived from the runner shape, and explicitly stop claiming equality with the current cell boundary.

A comment saying CPUWeight stands for six cores is not admissible; it would rename a relative share into a ceiling.

Why the green pair does not close this

The same-run pair proves:

4/4 != 6/16
6/16 == 6/16

It does not prove that == is the correct admission law. This is exactly the answerability distinction now under discussion: an instrument may execute, discriminate both polarities, and still have a subject narrower or different from the claim cited for it.

Disposition

microVM placement and recursion repairs       ACCEPTED
exact-equality memory admission               REFUSED
“same cell CPU envelope” claim                REFUSED
current matched-pair evidence                  REAL, but for the wrong predicate
merge authorization                           NOT GRANTED

Current main has advanced beyond this subject. After the host/guest envelope split is repaired, integrate then-current main, regenerate through authority, run the complete exact-head required workflow, and bring the new full head/tree. Do not merge this head merely because run 33639276195 is green.

Brian Searls and others added 4 commits September 2, 2026 17:34
…sts mirror

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
… its own VM

Deriving the guest's RAM from the cell envelope fixed the AUTHORITY question and left the
ARITHMETIC one. `machine_config.mem_size_mib` is GUEST RAM, and the Firecracker VMM process
that owns the guest lives inside the SAME cell. Setting guest RAM to the cell's entire
MemoryMax leaves nothing for the VMM's own resident footprint, so the slice OOM-kills the
VM it exists to bound.

It is the same class as the authored 4 GiB this module used to carry, inverted. Undersized,
work dies inside a guest whose slice had room. Sized at exactly the ceiling, the guest
believes it has an envelope the cell cannot honour. The safety property is not that the
guest EQUALS the envelope, it is that the guest PLUS its realization FITS INSIDE it, and
equality is the specific wrong answer that most resembles the right one -- which is why it
survived both authoring and review. The refusal reason one function away already said "one
sized above it escapes the boundary the slice exists to hold": the prose and the
construction were never checked against each other.

So the memory axis REFUSES, naming the missing fact rather than a symptom: guest RAM is the
cell envelope MINUS the Firecracker realization's own footprint, and no such quantity is
cited in extdeps.virtualization.firecracker. Inventing one would be a fabricated plausible
output wearing a derivation's clothes.

THE vCPU AXIS IS UNAFFECTED, and the witnesses keep that visible. The core resolution is now
a PARAMETER, because gunbc_runner_cores_per_slot is nullary and always resolves -- at the
fleet call site no witness input can reach its unresolved arm, so the pair would have been
unauthorable. The two arms reach DIFFERENT causes, which is what separates a refusal that
reports which fact is missing from one that refuses uniformly.

The whole-mebibyte check is DELETED rather than kept for safety: the quantity whose
wholeness mattered was the GUEST size, which no longer resolves, and asking it of the
envelope would test an operand the code no longer states to Firecracker -- a check whose red
cannot be authored.

COVERAGE LOSS, STATED AS A LOSS. The admission checks the envelope last, so
RunnerMicroVmAdmitted is now unreachable and THE SUCCESS PATH HAS NO EXECUTED COVERAGE AT
ALL. The four witnesses that asserted admission now assert they REACH the envelope-unresolved
arm -- strictly stronger for the axis each actually tests, since it proves the topology,
credential and malformed-VM gates all passed -- but nothing demonstrates that an admissible
VM is admitted. Filed as gunbc.guarantee_rung_drop runner_microvm_guest_size_derivation_stall,
whose trigger requires BOTH the grounding and the restoration of that witness, so the accept
arm cannot go live having never run.

Also corrects an annotation this branch was carrying: #10037 gave
v1_compiler_compiler_tests_rust.rs an owner, so the second MirrorHasNoOwningPackage instance
filed here is CLOSED, and the text no longer claims it is live beside a witness proving it is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
…e cell is not what enforces it

The row as filed named only the missing Firecracker footprint. That is a subset of
the real obstacle, and a trigger naming only it would be satisfied while the CPU
axis stayed unfounded -- the exact shape of a trigger naming less than the
capability it restores.

gunbc.fabric.fabric_cell_effect fabric_cell_slice_desired_directives emits five
directives, and its CPU one is CPUWeight: a RELATIVE share that guarantees no
amount of anything under contention. The absolute ceiling this fleet does declare
-- gunbc.host.host_converge runner_cpu_boundary_knobs writing CapacityQuota as a
CPUQuota drop-in -- lands on the RUNNER UNIT, a different systemd object from the
cell slice. So one bounded execution context is realized twice and the two
realizations enforce different axes; deriving a guest vCPU count from the cell
equates it to a quantity the cell never enforces.

The trigger now carries three conjoined clauses -- memory grounding, CPU
entitlement or an honest rename of guest topology as a declared product decision,
and restoration of the admissible-accept witness -- and states that the controls
must return as FIT controls where a SMALLER guest PASSES, since containment and
not equality is the safety property.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
The only conflict was additive rather than competing: this branch and main each
appended a GuaranteeStall to gunbc.guarantee_rung_drop at the same point.
runner_microvm_guest_size_derivation_stall and
deployed_repository_empty_root_bootstrap_stall answer different subjects, so both
are kept and both are in all_guarantee_stalls. Nothing was chosen over anything.

Resolved as a merge rather than the rebase the conflict notice asked for: a
force-push drops the approvals on the branch and orphans any run in flight, and
the end state is identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
@gunbai-bot

gunbai-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

This PR now carries two approvals and must still not be merged. See the superseded block at the top of the description — merge authorization was withdrawn after both approvals were formed, for two defects neither review names:

  1. the guest was sized at the whole cell MemoryMax with no reserve for the Firecracker VMM process that lives inside the same cell, and
  2. vcpu_count was derived from a cell CPU entitlement that does not exist — fabric_cell_slice_desired_directives emits CPUWeight, a relative share; the absolute ceiling lands on the runner unit, a different systemd object.

An APPROVE means "no blocking defect found". It does not mean the design is the one that should land, and here it is not.

One concrete thing both approvals got wrong, in the same place. Review 58826 credited std/measure.dag for a Mebibyte helper "by direct analogy with the existing kibibyte row"; review 58842 calls the same pair "additive within std/measure.dag with matching stage0 emission". Both are right that the row fits the scale axis and wrong that anything consumes it: mebibyte_to_byte_size has zero consumers outside measure.dag, and mebibyte_scale_factor_bytes feeds only it. I added both to serve the derivation and then deleted every caller when the derivation became a refusal — so it is residue by DESIGN §6, not a scale-axis fill. Their peers are genuinely live (gibibyte_to_byte_size 18 consumers, kibibyte_to_byte_size 3), which is exactly why the addition reads as legitimate.

They are held rather than deleted because the redesign may consume them, and that decision lands in the redesign push rather than being deferred past it.

The description below the marker still describes the superseded design and will be rewritten once, against the final shape, rather than half-corrected now.

— sent from smart-crane-230

Brian Searls and others added 4 commits September 2, 2026 19:23
no_fleet_constructed_vm_is_admissible_while_the_footprint_is_unmodeled was GREEN
BY VACANCY. Its unresolved arm returned true, so it returned true without ever
reaching runner_microvm_admission -- the fleet constructor derives the size, so an
unresolved sizing makes the config unresolved and the witness took that arm every
time. It could not fail, and it cleared the floor and two approvals in that state
while standing in the PR body as the replacement for five witnesses withdrawn for
demonstrating a false claim.

A witness whose refusal arm returns true is not a control; it is a restatement of
the refusal.

Deleted rather than repaired, and the asymmetry is what decides it: a MISSING
witness costs nothing while it waits for the redesign, while a GREEN one that
cannot fail is active misinformation for as long as it exists and gets cited as
coverage. Repairing it would be work against a block being rewritten wholesale;
deleting removes a false claim and forecloses nothing, since the redesign authors
the honest form regardless -- assert that the resolution IS unresolved AND that its
cause NAMES the missing footprint, which has an authorable red.

The seven `MicroVmConfigUnresolved => false` witnesses are deliberately left red.
Their subject is topology and credentials, so the redesign must judge them against
a plain constructor rather than the fleet-constructed config that drags unresolved
sizing into them. That is a design requirement carried forward, not a patch owed
here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
…anded at EOF

The deletion left its explanatory annotation as the LAST thing in the file, with no
module item after it. DESIGN 4c admits only standalone LEADING `//` blocks attached
to a module-scope declaration, so the parse phase refused with 17 copies of "source
annotation names no subject: no module item follows it" and took the whole floor
with it. The wall is right; deleting the last declaration in a file strands any
annotation that followed it.

The annotation now leads the surviving replacement pair, which is also where it
belongs semantically: it explains what happened to the fifth member of that set.

One over-correction reverted rather than kept. I also collapsed the blank lines
between adjacent comment runs, assuming a run had to sit immediately against its
declaration. It does not: 118 files in dag/ separate comment runs with a blank line
and parse clean. Only the end-of-file case was ever the defect, so the fix is the
relocation alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
THE DEFECT. One number answered three different questions. The withdrawn derivation
set the guest's RAM to the cell's whole MemoryMax, so the realization reserve was
zero and unnamed -- and the Firecracker VMM process lives inside that same cell, so
the slice would have OOM-killed the VM it exists to bound. The withdrawn controls
were wrong in BOTH directions while looking discriminating: the accept arm admitted
a guest sized at the whole ceiling, and the refuse arm rejected a 4 GiB guest
against a 16 GiB cell, which fits.

THE THREE QUANTITIES, now distinct: CellMemoryEnvelope (what the cell is granted),
GuestMemoryRequest (what the guest is handed), RealizationReserve (what the VMM
process itself costs, inside that cell). The property is containment --
guest + reserve <= envelope -- and a SMALLER guest passing is a requirement of the
repair, asserted as such.

THE RESERVE IS A PARAMETER, NOT A GLOBAL, and that is what makes the fit controls
authorable. Production cannot supply one: no VMM footprint is cited in
extdeps.virtualization.firecracker, and it is not a constant -- it scales with guest
size, device model and host page size, so upstream documents a measured overhead for
one configuration rather than a property of the realization. If the reserve were
read from a global, that refusal would be the only reachable outcome and neither fit
arm could ever execute -- a wall whose red cannot be authored. Passing it lets a
witness declare one from a controlled fixture and drive both polarities while
production stays honest.

THAT ALSO RESTORES THE ACCEPT ARM. The previous revision left RunnerMicroVmAdmitted
unreachable, so the admission's success path shipped unexercised. It executes again
under a declared reserve, and the pair that matters most is
the_same_guest_is_refused_under_the_production_reserve: the same guest, the same
admission, only the reserve changed, and the answer flips. The admission is
therefore neither admitting everything nor refusing everything.

THE CPU AXIS STILL REFUSES AND IS NOT REPAIRABLE HERE. fabric_cell_effect emits
CPUWeight -- a relative share guaranteeing no absolute amount -- while the fleet's
absolute CPUQuota lands on the runner UNIT, a different systemd object. Either an
absolute cell entitlement becomes real, which is a change to fabric_cell_effect, or
guest topology is declared as its own product decision. The sizing names which axis
is unavailable rather than refusing uniformly.

An earlier comment claiming "the vCPU axis is not what is blocked" was FALSE and is
corrected: a resolved core count is a fact about the host survey, not a cell
entitlement.

The stall row's third clause is DISCHARGED -- the accept arm executes -- and the row
says explicitly that PRODUCTION reaching it is not discharged and not claimed.
mebibyte_to_byte_size is now genuinely consumed, by the fit comparison; four reviews
had credited it while it had no callers at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HOLD — merge-blocking review on the current subject.

head  13ad7443a1d1a55b3ec883aa6dc34321f3beacd9
tree  0fb9318eaad34132ded13a378c1a1cdc1fffa69d

GitHub does not permit the connected author account to submit REQUEST_CHANGES on its own PR; this COMMENT records the same gate disposition.

Accepted: the withdrawn equality law is gone; memory is now judged by guest + reserve <= envelope; the same guest flips when only the reserve changes; production honestly refuses an ungrounded reserve; CPU produces CpuEntitlementRelativeOnly rather than a fabricated vCPU number. CellMemoryEnvelope and GuestMemoryRequest are legitimate distinct facts despite sharing a ByteSize field, because their constructors, sources, ports, and lifetimes differ. I do not require this design to split into separate PRs.

Three source blockers remain.

  1. The declared bounded stall population contains dead identities. It still names gunbc.runner_microvm runner_microvm_sizing_of_cores, but this head renames that declaration to runner_microvm_sizing. It also names test.claim.runner.runner_microvm_witness_test, while the moved witness declares module test.claim.runner_microvm_witness_test. A bounded population with a phantom member and a missing live member is not the full population DESIGN 4b requires. Correct the identities and recensus the row.

  2. The strongest reserve control does not hold the cause-propagation claim it narrates. the_same_guest_is_refused_under_the_production_reserve reaches the admission, but admission_is_envelope_unresolved_refusal matches RunnerMicroVmRefusedEnvelopeUnresolved { reason: _ }. The separate test of gunbc_runner_microvm_realization_reserve() proves the producer's source string, not that admission preserves it. Replacing the final admission reason with an unrelated constant leaves all those controls green. Match the final refusal reason and require the producer identity / missing-reserve cause there, while retaining the one-axis admit/refuse flip.

  3. A signed external cardinality is converted into a magnitude without admission. FirecrackerMachineConfig.mem_size_mib and vcpu_count are Int. The memory path passes mem_size_mib directly through mebibyte(...) and mebibyte_to_byte_size(...); Nat realizes as i64, and no positive/range or checked-arithmetic boundary intervenes. A one-root config with mem_size_mib = -1 and a declared 256 MiB reserve can satisfy the present fit inequality and reach RunnerMicroVmAdmitted. Non-positive or unrepresentable memory—and likewise non-positive vCPU cardinality—must refuse before the admitted arm. Overflow of conversion/addition must refuse rather than wrap or panic. Add discriminators at that boundary.

Also correct two stale evidence surfaces: the module annotation says nonexistent runner_microvm_mvp_size_is_not_the_cell_envelope is an executed witness, and the PR title still states the withdrawn proposition that the microVM size is the cell envelope and is derived from it.

The qualified synthetic merge is also stale: it composes this head into 7c277f659233aa0aced59c8786f9b716af644073, while main has advanced. Because the source blockers require a new head anyway, integrate then-current main after repair and requalify the exact replacement head. No present merge authorization.

Brian Searls and others added 2 commits September 2, 2026 23:46
… stall population

BLOCKER 3, AND IT IS THE SERIOUS ONE. FirecrackerMachineConfig carries vcpu_count
and mem_size_mib as SIGNED Int -- upstream's shape, faithfully modeled. The admission
took mem_size_mib straight through mebibyte -> mebibyte_to_byte_size ->
GuestMemoryRequest with nothing between the signed external value and the magnitude,
and `Nat` downstream does not rescue it because the realization is i64. So a config
with mem_size_mib = -1 satisfied -1 MiB + 256 MiB <= 16 GiB, reached
GuestFitsWithinCell, and was ADMITTED -- a fail-open arm inside the very relation
this change exists to establish. Non-positive vcpu_count was never validated at all.

guest_resources_of_machine_config now turns the signed pair into validated magnitudes
or a typed refusal BEFORE fit and before admission. The upper bound is the
REPRESENTABLE one, (2^63-1)/2^20, not a policy guess: refusing where the arithmetic
cannot be performed is honest, while a smaller "reasonable" ceiling would be unstated
policy wearing a representability argument's clothes.

The addition is GONE rather than guarded. `guest + reserve <= envelope` is the law,
but evaluating the left side can exceed the representable range on a hostile value
and wrap, answering the wrong question silently. The comparison is now by
subtraction -- guest <= envelope - reserve, with reserve-exceeds-envelope split out
so the subtraction never runs below zero -- which cannot overflow for any
non-negative inputs.

Five witnesses, and the fifth is not optional: negative memory refuses, zero memory
refuses, non-positive vCPU refuses, unrepresentable memory refuses, AND a small
positive guest is still ADMITTED. Without the last one the four refusals are
satisfied by an admission that refuses everything.

BLOCKER 2. admission_is_envelope_unresolved_refusal accepted
RunnerMicroVmRefusedEnvelopeUnresolved { reason: _ } -- it established the
disposition and DISCARDED the located cause, so it stayed green under a mutation
where the producer names the missing reserve and the admission returns a bare
"unresolved". A witness on the producer cannot close that: it establishes what the
producer emits, not that this path preserves it. The recognizer now matches the
reason and requires the Firecracker identity AT THE ADMISSION.

BLOCKER 1. The bounded population named runner_microvm_sizing_of_cores, which the
previous commit deleted, and gave the witness module an intermediate `.runner`
segment it does not declare. A phantom member beside an omitted live one means the
row never carried the population 4b requires, so it is RE-CENSUSED from the module's
declarations rather than spot-repaired -- the property that failed is completeness,
and two typo fixes would leave completeness unestablished. The membership rule is
now written into the row so it is checkable: a symbol is a member when, in
PRODUCTION, it cannot reach its intended answer.

Also removes an annotation citing runner_microvm_mvp_size_is_not_the_cell_envelope
as an executed statement. That declaration does not exist -- it went with the
withdrawn equality design -- and an annotation asserting a machine fact no Accepted
program can read is what 4c forbids. Removed rather than repointed, because the
claim belonged to a design that no longer stands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
@gunbai-bot gunbai-bot Bot changed the title The runner microVM's size is the bounded cell's resource envelope, derived rather than authored beside it — and review 57217's coproduct predicates are dissolved into the deciding site Bound the microVM guest by containment, and refuse at the signed Firecracker boundary Sep 2, 2026
Brian Searls and others added 2 commits September 3, 2026 00:58
…split it depends on

The floor refused #10021's parse phase at six consecutive lines: the annotation
explaining why guest_memory_fits compares by subtraction was written INSIDE the
function body, and DESIGN 4c admits only standalone leading blocks attached to
module-scope declarations. The prose is unchanged in substance and now leads the
declaration it explains, which is the nearest grain the substrate admits.

It also gains the two things a reader needs in order not to undo the design: why
the addition is absent (there is no overflow to handle because the operation that
could overflow does not exist -- rung 4, not a checked-add guard at rung 1), and
that the reserve-exceeds-envelope split is load-bearing for that claim rather than
a redundant early-out.

That split had no control, so it gets one:
a_reserve_larger_than_the_whole_envelope_admits_no_guest declares a reserve larger
than the envelope and asserts the refusal. Its RED is authorable -- delete the
split and the subtraction runs below zero -- which is what makes it evidence
rather than decoration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
@gunbai-bot
gunbai-bot Bot merged commit cb5e247 into main Sep 3, 2026
7 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/smart-crane-230 branch September 3, 2026 02:18
@briansrls
briansrls restored the session/smart-crane-230 branch September 3, 2026 02:19
@gunbai-bot
gunbai-bot Bot deleted the session/smart-crane-230 branch September 3, 2026 02:20
gunbai-bot Bot pushed a commit that referenced this pull request Sep 3, 2026
Sixth merge, and the first with no conflict in this PR's files. #10021 added
runner_microvm_guest_size_derivation_stall to what is now gunbc.guarantee_stall;
git followed the rename and placed it, including its entry in
all_guarantee_stalls.

That is a small piece of evidence for the split itself rather than just an
absence of trouble: a row authored against gunbc.guarantee_rung_drop, by a lane
that has never seen this branch, lands on gunbc.guarantee_stall and resolves
GuaranteeStall, ClimbBlocker and the ladder variants through the new
gunbc.guarantee_rung import without an edit. The stall witness re-executed
green on the merged tree.

Identity set diffed from HEAD before the merge and after: unchanged.
Projections regenerated; the drift is main's failure-mode rows, which this
branch does not touch -- its design_ledgers change is confined to
rung_drop_blocks, so the two authorities share a projector module and no
rendering declaration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdJu3Xkr9PX3gdBqen9Cdn
gunbai-bot Bot pushed a commit that referenced this pull request Sep 3, 2026
Sole conflict is the all_guarantee_stalls roster line, where #10021 appended
runner_microvm_guest_size_derivation_stall and this branch appended
authority_target_same_expression_equivalence_stall. Additive, not a
disagreement: the subjects are disjoint (a microVM guest's resource envelope
versus a same-expression authority/target comparison), so neither restates the
other and taking a side would delete a lane's row.

Verified by content rather than by the merge succeeding: both declarations
present exactly once, both roster entries present exactly once, zero conflict
markers. Projections regenerated from the MERGED authority, not the pre-merge
one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RuWuQWB6MPkY7sNM4jEqAy
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