Skip to content

Stop merging the failure-mode ledger on every append - #10822

Merged
briansrls merged 23 commits into
mainfrom
session/nimble-wolf-96
Sep 8, 2026
Merged

briansrls merged 23 commits into
mainfrom
session/nimble-wolf-96

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Summary

  • Independent failure-mode filings no longer share a committed merge region: docs/design-failure-modes.md is not committed, and gunbc.recurring_failure_mode.roster is derived from the row directory instead of hand-appended.
  • No committed combined file. No identity index. No hand-appended roster.
  • DESIGN.md points authors at dag/gunbc/recurring_failure_mode/. Heal still regenerates the rung-drop ledger only.

Absent is loud (the four writers are convenience)

Three modules import gunbc.recurring_failure_mode.roster: gunbc.design_ledgers, gunbc.ledger_row_coherence, and test.claim.generated_artifact_merge_driver_real_execution_witness. A reader that never hits the four collect writers still fails at resolve: missing module, not an empty fold. The four ensure_ sites exist so a clone compiles without a committed roster; they are not load-bearing for the absent case.

Short roster is refused (not silent)

(a) A non-UTF-8 .dag stem is a typed refusal in the writer, not continue. read_dir / dirent errors already propagate.

(b) ledger_row_coherence is roster-to-projection, so a short roster still projects a short document and stays green. The required-CI parse join now identity-joins sibling .dag files (except roster.dag) to roster members, independent of the writer: RowFileNotRostered and RosterMemberNotARowFile. That is the 175/175 stem measurement as an executing check.

length(rows) == 0 => NotALedgerArtifact still classifies an empty roster as "not a ledger" rather than an empty ledger (predicate vacuously true on an empty domain). It does not bite while absent-is-loud holds; it is not a second line of defence.

File-join wall vs discriminating evidence (§4b(1))

run_rostered_row_join runs in the required-CI parse phase (claim_executor). The declared-versus-rostered arm has an on-path control: test.fixture.rostered_row_join.omitted_control.

The two new arms (RowFileNotRostered, RosterMemberNotARowFile) do not have an equivalent on-path fixture. Their discriminating REDs are #[test] functions in rostered_row_join.rs (file_roster_join_tests), which run only under cargo test -p v1-compiler --lib — the declared drop gunbc.rung_drop.rust_unit_tests_off_the_merge_path. Compile-and-CI enforce the production join on every required run; the evidence one would point at to show those two arms discriminate is off the merge path. "The join executes" is not "the join is proven to discriminate" for those two arms.

Q2 — write during read (DESIGN §7)

Stated §3b divergence: those four collects write a gitignored file into the source tree.

  • Two compiles racing: both derive the same identity-sorted bytes; write is *.dag.deriving then rename; last rename wins with identical content.
  • Write fails (read-only checkout, sandbox): collect refuses. It does not proceed with a missing file. If on-disk already byte-matches, the write is skipped and a read-only checkout can compile.
  • Torn live file: no. The destination is replaced by rename.

A compile that cannot write source is a new failure mode for those four collect consumers (local sessions, remote CI, BuildBuddy).

Seed growth and filename-as-name

The substrate cannot enumerate a directory into typed value bindings. Directory-as-membership is a §4b wall-after-grounding realized in the host. Retired by declaration-value binding over the declared population (gunbc.guarantee_stall.roster_re_enumerates_its_own_rows_stall).

Filename stem = declaration name is a spelling join, not typed inhabitance. Same retirement trigger.

Gate 3 is not a §4b(3) drop

A committed-bytes wall over a file that no longer exists had its subject removed. Content is still checked by required_lane_claim_agreement_witness against expected_design_failure_modes_md() as generated text.

Drop population (rust_unit_tests_off_the_merge_path)

This change enrols v1_compiler.cli_run.rostered_row_join file_roster_join_tests into that declared drop's --lib population. The restoration trigger is unchanged and covers them: they are --lib tests.

Test plan

  • Required CI parse join still reports a complete RecurringFailureMode population after adding only a new row file (no roster edit).
  • Two concurrent branches each adding a distinct row file merge without touching a shared ledger path.

@gunbai-bot

gunbai-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Verified against current head from review 62305 (dashboard-only, reviewed 4163d30).

  1. Hand-Rust .dag strings / generated_artifact registry — not registering. roster.dag is a gitignored compile input, not a docs projection. Putting it on gunbc.generated_artifact would put a derived membership list on the regen/heal/drift surface this PR exists to take the ledger off of. Footprint is already modeled at gunbc.repo_workspace. Dissolution trigger is named in the writer and the PR body: declaration-value binding (gunbc.guarantee_stall.roster_re_enumerates_its_own_rows_stall). Seed growth is a §4b wall-after-grounding, not an unreceipted scaffold.

  2. Four copied call sites — fixed in 51aa920. One ensure_if_row_dir / ensure_if_row_dir_or_panic. A fifth walker that never writes still refuses at resolve: three modules import gunbc.recurring_failure_mode.roster. Write-during-read remains a stated §7 divergence (temp+rename; write fail refuses).

  3. Hardcoded "recurring_failure_mode" / "gunbc" pair — fixed in the same commit. The match is now the modeled directory gunbc/recurring_failure_mode.

@gunbai-bot

gunbai-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Verified against current head from review 62325.

  1. Fixed temp path — real. Concurrent writers could rename a truncated roster.dag.deriving into place. write_atomically now uses roster.dag.{pid}.{nanos}.deriving and deletes the tmp if rename fails. Landed in fda7942.

  2. Converge actuator instead of read-path write — not doing that in this PR. Absent roster is already a missing-module refusal for any reader that is not one of the four seed walks (including emitted collect_dag_files in 05_emit_rust.dag). The four writes are convenience so a clone compiles; parent accepted the §7 write-during-read as a stated divergence (temp+rename, skip when bytes match, refuse on write fail). An explicit converge actuator is a follow-up, not a silent empty fold we would ship.

  3. Three spellings of the row home — the join now walks dag + ROW_DIR_REL, and ROW_DIR_REL is asserted equal to ROW_MODULE with / for .. ensure_if_row_dir returning Ok on a non-matching path is the other directories in the walk, not a silent skip of the modeled home. If the directory is moved, imports of gunbc.recurring_failure_mode.roster still refuse at resolve.

@gunbai-bot

gunbai-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Verified against current head from review 62332.

nfr_reason_concurrent_divergence_repair_policy still said both design-ledger artifacts answer HealRegeneratesAfterProvisionalMerge. The match is DesignRungDropsArtifact only. Updated that reason, and the // note at gunbc.ledger_row_coherence that named the same false pair.

@gunbai-bot
gunbai-bot Bot force-pushed the session/nimble-wolf-96 branch from 528bea1 to 2959772 Compare September 8, 2026 03:59
@gunbai-bot

gunbai-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Verified against current head from review 62348.

  1. Required CI parse sweep never derived roster.dag — real. run_dag_parse_sweep is the first required-CI phase and it was not one of the four collect sites, so a clean checkout had no gunbc.recurring_failure_mode.roster in the index and the join reported RosterModuleAbsent on the intended state. The sweep now calls ensure_if_row_dir before listing each directory. 56ca471.

  2. Sprinkling at call sites — the gate is this sweep, not the four convenience collectors. Those still write so other compilers compile; the merge path writer is now the parse walk. Unifying every future walker onto one corpus-acquisition type is a follow-up; leaving the file committed would put the membership list back on the shared merge surface this PR deletes.

  3. Seed-growth as a //! rather than a seed-retained row — not adding a second tracking census here. Dissolution is already the named capability gunbc.guarantee_stall.roster_re_enumerates_its_own_rows_stall (declaration-value binding), recorded on the writer and in the PR body. A v1-zero-hand-maintained-rust lane row would be a second name for that same trigger.

@gunbai-bot

gunbai-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Verified against current head from review 62367.

  1. Stall row stale for RecurringFailureMode — real. roster_re_enumerates_its_own_rows_stall still said every author writes the roster entry and named gunbc.recurring_failure_mode.roster as heading the measured population. That member is a directory fold as of this PR. The row now records that it left the hand-append set, that the host writer is a second path beside the declared value-binding climb (not a climb of this stall), and that the stall still holds for every carrier that hand-appends, including this one. 2885484.

  2. Register the producer in generated_artifact — not doing that. Putting roster.dag on that registry would put a derived membership list on heal/drift, which is the shared rewrite this PR deletes. The footprint is already in gunbc.repo_workspace. Dissolution remains declaration-value binding on this stall row, now stated there rather than only in a Rust //!.

@gunbai-bot

gunbai-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

review 62378 (Opus REQUEST_CHANGES on 2885484):

  1. Fixed on dbf5222. agree_if_committed is gone. tools.docs_projection_gate main now agrees only DesignRungDropsArtifact (committed bytes). Regen still writes a local failure-mode copy. Regen-correctness for that projection stays on expected_design_failure_modes_md / required_lane_claim_agreement_witness and on docs_projection_bytes_agree fixtures that can still go red — not a skip-success committed-bytes compare.

  2. Not reverted. Parent/operator already admitted the seed writer as the wall-after-grounding host fold (directory → .dag bytes) until declaration-value binding retires gunbc.guarantee_stall.roster_re_enumerates_its_own_rows_stall. The stall row already says this is a second path beside that climb, not the climb. Registering roster.dag on gunbc.generated_artifact would restore heal/drift rewrite of a gitignored file. Completing the surviving construction is that stall's trigger, not deleting the host that makes a clean checkout parse.

  3. Fixed on the same commit. collect_dag_files_tolerant calls ensure_if_row_dir and ignores write errors; run_dag_parse_sweep remains the loud required-CI writer.

@gunbai-bot

gunbai-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

review 62400 (cursor/auto REQUEST_CHANGES): landed. gunbc.derived_row_roster_seed_growth derived_row_roster_seed_growth_justification enumerates the added hand items (derived_row_roster.rs plus the new join arms and file_roster_join_tests members) and is on seed_growth_justification_roster(). Dissolution stays gunbc.guarantee_stall.roster_re_enumerates_its_own_rows_stall (declaration-value binding), not a sentence in the Rust module. cli_run.rs / claim_executor.rs / JoinFindingKind variant adds are ExistingSeedItemModified, not netted as additions.

gunbc-ci-auto-heal and others added 21 commits September 8, 2026 06:55
The committed markdown projection and the hand-appended roster were two
shared merge regions for independent class filings. Membership is now the
row directory (derived before compile), and the markdown is generated on
demand rather than committed.

Co-authored-by: Cursor <cursoragent@cursor.com>
The failure-mode ledger has no checkout bytes to agree; requiring them would put the 1.19MB file back on the merge path. Regen can still write a local copy. Rung-drops stay on the agreement path.

Co-authored-by: Cursor <cursoragent@cursor.com>
The derived roster is a missing module when unread, not an empty fold; write it through a rename so a concurrent reader cannot see a torn file.

Co-authored-by: Cursor <cursoragent@cursor.com>
A non-utf8 .dag stem is a shortened membership, not a skip; read_dir errors already stop the collect.

Co-authored-by: Cursor <cursoragent@cursor.com>
…path.

A short derived list stayed well-formed; the projection join is roster-to-markdown, so it could not see an omitted file. The required-CI join now identity-compares sibling .dag stems to roster members, independent of the writer.

Co-authored-by: Cursor <cursoragent@cursor.com>
…nly declared-versus-rostered.

Co-authored-by: Cursor <cursoragent@cursor.com>
The four collect walks now share one call; the directory match is gunbc/recurring_failure_mode, not two uncorrelated folder names.

Co-authored-by: Cursor <cursoragent@cursor.com>
…e file.

A maintainer should not hunt a missing list entry in gitignored roster.dag.

Co-authored-by: Cursor <cursoragent@cursor.com>
Required CI runs the production join; these REDs are the rust_unit_tests_off_the_merge_path drop.

Co-authored-by: Cursor <cursoragent@cursor.com>
The restoration trigger is unchanged: they are --lib tests, so the same runner-supply capability restores them with the rest of that population.

Co-authored-by: Cursor <cursoragent@cursor.com>
A shared roster.dag.deriving let concurrent writers rename a truncated body into place. Unique pid+nanos temps close that. The join now walks the same ROW_DIR_REL the writer matches.

Co-authored-by: Cursor <cursoragent@cursor.com>
…edgers.

The NFR reason and the coherence note still said DesignFailureModesArtifact was a heal subject after it left the committed population.

Co-authored-by: Cursor <cursoragent@cursor.com>
The instrument-name paragraph is in design_document.dag; the rebase left the generated file on the earlier wording.

Co-authored-by: Cursor <cursoragent@cursor.com>
That walk is the first merge-path reader and it was not one of the four collect sites, so a clean checkout had no roster module and the join refused the intended state.

Co-authored-by: Cursor <cursoragent@cursor.com>
Ledger-Repair-Judged: docs/design-rung-drops.md
…s roster.

That member is a directory fold beside the declared value-binding climb; the stall still holds for every carrier that hand-appends, including this one.

Co-authored-by: Cursor <cursoragent@cursor.com>
The gate compares committed bytes; DesignFailureModesArtifact is NotCommitted,
so that arm could only succeed. collect_dag_files_tolerant now ignores roster
write errors instead of panicking, matching its swallow of unreadable dirs.

Co-authored-by: Cursor <cursoragent@cursor.com>
The committed mirror already called ensure_if_row_dir_or_panic; the .dag
source did not, so required generated-artifact reported compiler_tests.rs
drift. Heal does not install stage0 mirrors.

Co-authored-by: Cursor <cursoragent@cursor.com>
The inserted ensure call added a concat without a matching paren, so
v1-stage0-v1-infer failed to compile on the required build.

Co-authored-by: Cursor <cursoragent@cursor.com>
Hand-wrapping concat() around two string args made concat ternary and
broke v1-stage0-v1-infer. Rebuild the mirror from the last green emit
with the same nesting as every other fragment.

Co-authored-by: Cursor <cursoragent@cursor.com>
Unenumerated hand Rust in src/v1 is a stop-line. The stall names the
dissolution capability; it does not enroll the added items. Wire the
row into seed_growth_justification_roster.

Co-authored-by: Cursor <cursoragent@cursor.com>
@gunbai-bot
gunbai-bot Bot force-pushed the session/nimble-wolf-96 branch from 92bf1a8 to 8bce1ac Compare September 8, 2026 06:56
@briansrls
briansrls merged commit aac9545 into main Sep 8, 2026
4 checks passed
@briansrls
briansrls deleted the session/nimble-wolf-96 branch September 8, 2026 15:04
@briansrls
briansrls restored the session/nimble-wolf-96 branch September 8, 2026 15:06
briansrls pushed a commit that referenced this pull request Sep 8, 2026
main #10822 dissolved both conflicting files: the failure-mode roster is now
DERIVED from the row directory (gitignored) and the markdown projection is
generated on demand rather than committed. Both deletions accepted; my class
row survives as its row FILE, which is the membership under the new join.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011D6tYivYRP6YoGk8FJo3M7
gunbai-bot Bot pushed a commit that referenced this pull request Sep 8, 2026
…rojection

#10822 made recurring-failure-mode membership the row DIRECTORY and stopped
committing docs/design-failure-modes.md, which is exactly the shared append
region this branch kept conflicting on. Resolution accepts the deletion: this
branch's whole contribution is 8 added lines in the row file itself, which is
now the only place the class is filed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
gunbai-bot Bot pushed a commit that referenced this pull request Sep 8, 2026
…ctory

#10822 stopped committing docs/design-failure-modes.md, which was the shared
append region this branch conflicted on. Resolution accepts the deletion. The
two append rosters merged cleanly and were verified as strict supersets in both
directions: the floor_expected_red chunk chain keeps every chunk main carries,
with this branch's disjoint_refinement_chains spliced in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
gunbai-bot Bot pushed a commit that referenced this pull request Sep 8, 2026
…ctory

#10822 stopped committing docs/design-failure-modes.md. Resolution accepts the
deletion; this branch's whole contribution is the specimen on its row file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
briansrls pushed a commit that referenced this pull request Sep 8, 2026
Same resolution as the regen-digest branch: main #10822 dissolved the roster
and the committed markdown, so both deletions are accepted and membership is
the row FILE. Additionally v1_std_core.rs conflicted; taking the ours side
dropped main's new AlgebraApplicationEvidenceUnavailable variant and failed to
compile, so the mirror was regenerated from the merged .dag authority via
--required-regen and the candidate installed (18 deletions, the two prose-blob
fns this migration removes).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011D6tYivYRP6YoGk8FJo3M7
gunbai-bot Bot pushed a commit that referenced this pull request Sep 8, 2026
… its roster

#10822 deleted dag/gunbc/recurring_failure_mode/roster.dag and
docs/design-failure-modes.md outright. Both arrived as modify/delete against this
branch, and both deletions are taken: membership is the row DIRECTORY now, derived
before compile, and the markdown projection is generated on demand rather than
committed. This branch's six failure-mode rows are members by existing as files; no
roster edit remains.

That dissolves at the root the merge class this PR spent the day absorbing -- the
append-vs-append roster conflict that silently deleted #10801's row, the duplicate that
took out two floor witnesses, and every --ours resolution of the 1.19MB projection. The
both-direction roster check this branch built has no subject any more, which is the
correct outcome: DESIGN section 5 prefers making a state unwritable over checking for it.

VERIFIED, because this is the merge where content goes missing quietly: all 178 of
main's row files present (mine is 184 = 178 + this branch's 6); exactly one common row
body differs, check_subject_narrower_than_its_declared_claim, which is this branch's own
edit. roster.dag is deleted from the index and regenerated locally as an ignored derived
artifact, as gunbc.repo_workspace declares it (RegenerableFromSource).

Regenerated BEFORE this commit rather than after, because every commit in this worktree
is auto-pushed by the CI account -- the commit is the publish, so a follow-up regen
commit guarantees one red head. Every conflicted path in this merge and the four before
it was a DERIVED artifact; the .dag sources have never conflicted once.

Local on this tree: lane=build phases_run=2 phases_failed=0, mirrors at
first_generation_equal=true, docs-projections OK, registry-projections 39/39 drifted=0
(40 -> 39 is #10822's ledger leaving the adjudicated set), fixed_point_equal=true.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtfacHQfiYMAD9SiH6oZhi
gunbai-bot Bot pushed a commit that referenced this pull request Sep 8, 2026
…ng its head record

The namespace wave-admission baseline is the head index with the diff applied
in reverse at FILE grain, which assumes one thing about every file the diff did
not name: that its base text equals its head text. That holds for a tracked
file. It is false for a source generated before compile and gitignored, because
such a file never appears in `git diff` no matter how far its content moved --
so its head record was carried into the BASE index and the wall compared the
head against itself.

#10822 made the failure-mode roster derived and gitignored. This branch carries
the first row to land after it, and had its own authored addition reported as
NewPoolCoincidenceResolution -- a name resolving from a pool with nobody
authoring a reference -- when the reference was authored by this very change.
That disposition is not auto-admitted, so a required lane blocked, and it would
have blocked for every later row. The binding delta was right; the authorship
evidence under it was poisoned.

The carry now requires the base tree to contain the path, which is what makes
unchangedness proof of byte identity. A derived path is re-rendered from the
base revision's row files through the SAME renderer the writer uses and parsed
by base_records, so no hand-built record and no second derivation authority
exist. Any other untouched head-index record absent from the base tree is
NotEvaluated: a path listing supplies existence, never content, and an untracked
path may equally be local state, a scratch fixture or a dirty worktree file --
inheriting or silently dropping it fabricates a baseline either way.

binding_disposition, locally_authored_claim_added and disposition_auto_admitted
are untouched, and NAMESPACE_TRANSITION_ADMISSIONS stays empty: nothing is
admitted by permission. Measured on this head, the wall goes from 1 unadjudicated
delta to ADMITTED with 7 real deltas, binding_rows_compared 759867 -> 759866 --
the one row that should never have been compared, because with a correct base
the name does not exist there.

Evidence: the wall's own run on this base/head reports
`namespace-wave-admission ADMITTED`. Two unit tests enrolled in the deriver's
seed-growth population cover the derivation, and the one encoding the defect is
discriminating -- it fails under a mutation that renders the base as the head
while its three siblings stay green. Those tests sit under the declared drop
rust_unit_tests_off_the_merge_path and are local diligence, not enrolled evidence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PUtSkZTNvpWQv58Lk9cGzg
briansrls pushed a commit that referenced this pull request Sep 8, 2026
…e class file

#10822 (aac9545) made recurring failure modes ONE CLASS PER FILE with
membership derived from the directory, and deleted both the hand-appended
roster and the combined markdown projection, adding both to .gitignore. This
branch still carried edits to both.

Resolved by ACCEPTING THE DELETION on:
  dag/gunbc/recurring_failure_mode/roster.dag
  docs/design-failure-modes.md
Restoring either to quiet the merge would re-create the exact authority #10822
removed -- the surviving-X attractor DESIGN section 3 names in the
replacement-migration rule. Their imports still resolve because the roster is
generated before compile; it is derived, not absent.

What this branch registers is unchanged: the class file
unapplied_function_value_admitted_at_a_value_position.dag IS the registration
now, and it carries forward as-is.

dag/gunbc/guarantee_stall/roster.dag is ALIVE and was an ordinary content
conflict -- both sides appending. Resolved additively, main's rows first, mine
after, with no row from either side dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQuBs4mLu9ArojSWFiRcey
gunbai-bot Bot pushed a commit that referenced this pull request Sep 8, 2026
…list

#10822 deleted the hand-appended failure-mode roster: membership is the row
DIRECTORY now, derived before compile. This row's corollary prescribed a count
comparison against the base for append-only carriers and cited the roster at 163
against 173 as a live countable specimen. Half that citation is a superseded claim,
and it had not landed yet -- so this corrects the row before it ever becomes an
authority rather than editing a published one.

I had declined this edit earlier on the grounds that the row was a live authority and
that changing prose inside a PR waiting on an unrelated gate is how a superseded
claim survives review. The reasoning was sound and its PREMISE WAS FALSE: the row is
absent from origin/main and exists only on this branch. There is no landed authority
to preserve.

The corrected text is taken verbatim from #10853 (deep-crane-107, 9a2358a), which
added the identical file and differed only in this one receipt string; that PR closes
rather than landing a second copy for this one to later drop. The file is now
byte-identical to its version, so one row has one authority.

What the text now does: scopes the rule to append-only carriers that are STILL A
LIST, keeps extdeps.exec.command admit_callers at 67-vs-68 as the surviving specimen,
marks the roster SUPERSEDED FOR ONE CARRIER ONLY citing #10822, and keeps the closing
instruction not to invent a replacement count for the dissolved carrier -- directory
membership is a different subject.

The correction is self-applying: refreshing that citation as though the roster were
still countable would itself be a_live_authority_name_carries_a_superseded_claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN
briansrls added a commit that referenced this pull request Sep 9, 2026
…#10674)

* Compose the micro-VM launch plan, and mint the credential last

Four stages have to hold before a guest may start: the fabric cell must be
owned, the VM config must resolve, the admission must pass, and the App
credential must carry organization self-hosted-runners write. This composes them
in that order and returns one typed plan.

The order is load-bearing rather than stylistic. Every stage but the last is
pure and free to refuse; a JIT mint is not, because it creates a runner identity
at GitHub. A refusal after a mint leaves that identity registered against the
organization for a VM that will never start, nothing on the host knows to delete
it, and orphaned registrations are reconcilable only by name. So the mint runs
last, and its authorized value is reachable only from inside LaunchAdmitted --
a caller cannot hold a mint authorization for a launch that was refused.

Every admission arm is matched where the launch rules on it rather than through
a helper over RunnerMicroVmAdmission. Such a helper needs an arm for the
admitted case that cannot occur there, and the only thing to put in it is a
fabricated refusal, in the one place nothing can observe it.

plan_microvm_launch_of_binding carries the decisions and takes a binding a
witness can build; plan_microvm_launch is the five-line projection over the
sealed RequiredBuildCellSnapshot. Putting the ordering behind the seal would
have made it unwitnessable, which is how the previous PR ended up with an
annotation claiming coverage that did not exist.

Refusal arms stay separate rather than collapsing into one cause string: the
cell belongs to fabric_required_build_cell, the size to runner_slot_allocation,
the topology to runner_unit, the credential to auth.github_credential, and
collapsing them sends an operator to the wrong module.

Credential fixtures are imported from test.claim.github_app_registry rather than
rebuilt, so there is one answer to what an installation holds.

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

* temp: diagnostic arm probes for the launch fixture

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

* Take the VMM reserve as an input, and state that production admits no guest

The launch plan read gunbc_runner_microvm_realization_reserve() inside the
composition, which made the admitted arm unreachable from any caller.
Production's reserve is ReserveUnmodeled -- deliberately, because no VMM
resident footprint is a cited quantity in extdeps.virtualization.firecracker and
inventing one would be a fabricated plausible output -- so every launch refuses
at MemoryFitUnresolved and the mint stage below it was dead code no input could
reach.

The lane caught this: the positive control asserted LaunchAdmitted, a state no
input can produce. That is the same decoration class this branch documented for
members_owned, this time written into a test rather than a production path, and
it would have shipped as coverage for an ordering nothing could exercise.

The reserve is now a parameter. The fit judgment is decided by the caller that
owns the quantity, the ordering below becomes reachable and therefore
witnessable, and production keeps refusing at the entry point rather than by
accident inside a composition.

Adds the control that says what is true today: with production's own reserve,
every launch refuses, so NO CI JOB RUNS IN A MICRO-VM until that quantity is
cited or measured and declared. It goes red the day the reserve becomes
declared, which is when the launch path becomes live and the claim has to be
re-read.

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

* Observe whether a runner host can measure a VMM's footprint

The micro-VM path admits no guest because the realization reserve is
ReserveUnmodeled: no VMM resident footprint is a cited quantity, and inventing
one would be a fabricated plausible output. The quantity has to be OBSERVED on a
host before it can be declared, so which instrument exists is a host readiness
fact in the same way /dev/kvm is -- a host that boots a guest but cannot report
what the VMM cost cannot retire the reserve.

Reports GNU time, cgroup v2 memory.peak, and the systemd version line, in the
receipt the existing microvm_host_converge mode already prints. No new module
and no new workflow job: this had to have a consumer the moment it existed.

The instruments are named rather than ranked and nothing here picks one. Each
reports the same quantity exactly, and which is available is a property of the
host's systemd and kernel, so selecting among them belongs with the measurement.
The systemd version is read rather than inferred because MemoryPeak is
version-gated at v254.

Sampling /proc/<pid>/status VmHWM is deliberately not among them: it needs the
process alive, so a guest that powers itself off can be missed, and a missed
sample under-reports -- the wrong direction for a value that becomes a safety
reserve.

Readiness deliberately does not move with the instruments, with a control for
it: wiring them into firecracker_host_ready would have taken every host
not-ready the moment this field landed, to answer a measurement question.

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

* Import systemctl_version_command, which no local gate had checked

The fleet-converge dispatch died on srv2 with NoSuchFunction while the whole
witnesses lane had reported RC=0 and FloorClean over the same tree.

Two reasons stack and either alone is enough. runner_microvm_host_ready sits
declined_outside_gate_closure, so no CI check compiles it. And claims evaluate
only the functions they call, so the witnesses added beside this observer cover
its pure functions while the func body is never evaluated -- wet bodies run only
on a host. A green floor is not evidence that a wet function resolves.

gunbc compile --entry DOES catch it, confirmed by running the red arm with the
mutation verified applied first. So the gate existed and was not run; it is now
run before any dispatch that executes a .dag entry on a host.

Worth recording that this is the opposite blindness from the indented-annotation
refusal earlier on this branch, where the lane's parse phase caught what
compile --entry missed. Neither instrument dominates; a touched module needs both.

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

* Model the VMM footprint probe, sampled across guest sizes

The micro-VM path refuses every launch because the realization reserve is
ReserveUnmodeled. This is the observation that could retire it, and it produces a
RELATIONSHIP OVER SIZES rather than a constant, because the model's own reasoning
is that the overhead is not constant.

Sampling several sizes is not thoroughness, it is the whole experiment. Guest RAM
is lazily faulted, so a page joins the VMM's resident set only when the guest
touches it, and a guest that boots and powers off touches a small and roughly
size-independent amount. One reading at one size therefore measures the workload,
not the size. A peak that stays flat as declared size rises says the declared size
is not committed; a peak that tracks it says it is. Those are different worlds for
an admission rule and one sample cannot tell them apart.

Stated in the module and not only here: this does NOT establish a worst case. That
is what a guest costs when it USES its declared memory, which needs a guest
workload that touches all of it. Reporting these numbers as if they bounded that
case would be the fabricated plausible output the reserve refusal exists to
prevent, arriving by way of a measurement instead of a guess.

The instruments were chosen on evidence rather than assumption: the srv2 receipt
reports gnu_time present, systemd 255 so MemoryPeak is available, and cgroup
memory.peak absent. GNU time's %M is a kernel-maintained high-water mark, which is
why it was preferred over sampling VmHWM -- sampling needs the process alive and
under-reports a subject that exits on its own, the wrong direction for a reserve.

The span check carries its own negative arm as a fixture rather than deferring to a
mutation run: the collapses it exists to catch -- one size, two adjacent, three
clustered -- are asserted to fail on every run. The third is the one reasoning
alone would miss, since count >= 3 passes it and only the span requirement does not.

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

* Convert mebibytes to bytes explicitly; pin the sizes in absolutes

The declared sample sizes were 512..4096 BYTES, not mebibytes. Assigning
mebibyte(count: N) into a ByteSize field is accepted and does not convert -- the
magnitude rides across unchanged -- and std.measure mebibyte_to_byte_size is the
conversion that was available and unused.

The type system stopped the READ and permitted the WRITE: mebibyte_count on a
widened value is a hard error, so the obvious repair is to switch to
byte_size_count, which compiles clean and relabels the same number as bytes. That
repair is what I made earlier on this branch and reported as a units win. It was
not one.

WHY NOTHING ELSE CAUGHT IT, which is the part worth fixing rather than the typo.
The span control asserts largest >= smallest * 4 over the same list. A uniform
unit error preserves every ratio, so it passed identically on bytes and on
mebibytes; only the absolute assertion in the render test went red, and that one
covers the witness's own fixtures rather than the list production samples.

So this also pins the production sizes in absolute bytes. The literals are the
specification -- these are the sizes the probe declares it will sample -- so the
control is a controlled fixture rather than a measurement copied out of the tree.

General rule, recorded because it generalises past this file: when a suite tests a
RELATIONSHIP -- ratio, ordering, spread, delta -- at least one control must pin an
ABSOLUTE, or a scale error is invisible to the whole suite.

Also enrolls the empty-receipt control permanently rather than as a one-off, per
4b(4): footprint_receipt_is_complete folds with init: true, so an empty outcome
list is vacuously complete and only the count guard beside it refuses. That guard
is load-bearing and had no control.

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

* Convert the one mebibyte site a digits-only regex could not match

The previous commit claimed to fix the unit bug and did not. It converted with a
pattern requiring digits, so every literal call site was repaired while
mebibyte(count: mib) in the sampled() helper -- an identifier, not a literal --
was silently skipped. That helper is exactly what the failing render claim drives,
so the fix looked complete, compiled clean, and left the same claim red.

A mechanical edit that matches most of its population and misses the rest is
indistinguishable from a complete one at the diff, and the widening it failed to
remove is legal, so nothing local objected. What found it was running the lane
before pushing rather than reporting the fix from the edit.

So the check is now over the POPULATION rather than the pattern: every mebibyte(
in both files is wrapped in mebibyte_to_byte_size, verified by a grep whose
expected output is empty, not by trusting the substitution's own reach.

The absolute control added in the previous commit is what isolated this. It passed
while the render claim failed, which said conversion works and the fault is in the
render path -- without it the two hypotheses were indistinguishable.

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

* Re-read the launch control now that the reserve is declared

production_refuses_every_launch_while_the_vmm_reserve_is_unmodeled asserted that
every launch refused at MemoryFitUnresolved, and it said in its own text that it
would turn RED the day the reserve became declared -- at which point the launch
path becomes live and the control has to be re-read rather than silently starting
to pass a guest through.

#10657 was that day. It set the allowance aside and made the reserve
ReserveDeclared, so production now reaches an admitted guest and the assertion is
false. The control was not broken; it fired. This is the re-reading it demanded.

The red was only visible on the PR merge ref: my branch alone still has the
unmodeled reserve, so both local instruments greened while CI -- which builds
branch-merged-with-main -- refused. Running an instrument against a tree that
lacks the change under test measures the wrong subject no matter how correct the
instrument is, and a green from it looks exactly like a green from the right one.

What replaces the row is the same question asked of the live path, in three parts.
The admission control pins that the guest admitted is the DECLARED size rather
than any default. The refusal control keeps that from being satisfied by a fit
judgment that answers yes to everything: a reserve larger than the cell must admit
no guest of any size. And the original discriminating RED stays enrolled with an
explicitly unmodeled reserve -- 4b(4) keeps a class's red executing after its wall
lands, and an unresolved fit is exactly what the launch path must never pass a
guest through, whoever the caller is.

The arithmetic is deliberately NOT re-derived in the witness. guest_memory_fits
already carries the comparison, and recomputing it here would be a second
representation of one constraint, satisfiable by editing the test while the
production path still lied.

Both new controls were verified by mutation rather than by reading: shrinking the
oversized reserve and expecting a size production does not declare turn each of
them red, while the untouched third stays green -- without that last one a run
that failed everything would have looked like proof.

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

* Require the execution grant, so the micro-VM is a realization under the fabric

runner_microvm_launch_plan composed a launch straight from a cell binding and
started a VMM. gunbc.fabric_ci_program FCI-3 owns that question -- what causes the
target process -- and its exit predicate is that a persistent executor re-reads
the committed Grant before starting, so an absent, corrupt or expired Grant yields
ZERO target processes. FCI-1's non-goals name Work-process-start and
ExecutionGrant explicitly; FCI-2's name process-start. The module therefore did
FCI-3's job while skipping FCI-2, which is the second execution system ROADMAP
forbids when it says the fabric is four consumers of one contract.

That is also why nothing consumed the module. The correct consumer is the FCI-3
executor, and it could not consume a launch whose signature omitted the grant --
so writing any caller would have cemented the bypass rather than closing it.

The grant is now a REQUIRED PARAMETER rather than something this module looks up.
The absent case has no constructor, so it is unwritable rather than validated; the
fence check against the issuing account covers corrupt and expired, because a
grant whose lease generation no longer admits is a superseded authorization.
Authorization is the first stage: nothing starts without it whatever the cell's
state, and a control pins that ordering by presenting an unowned cell and
requiring the grant refusal to still win.

THE CONTROLS CAUGHT A BUG THAT PASSED THEM. issue_grant returns the account
carrying the recorded lease, and the first version fenced against the PRE-issuance
account -- so every launch refused, and both grant controls passed against a
mechanism that was refusing everything. Only the seven positive claims failing
revealed it. The grant and its committed account are now one IssuedAuthorization
value, because splitting them is what made the error writable.

Verified by mutation in PRODUCTION rather than in the test: neutering the fence
check turns both grant controls red and leaves the other seven green. No claim
asserts the absent-grant case, because its RED is unauthorable once the parameter
is required -- that check would be a 4b decoration, permanently green and cited as
coverage.

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

* Move the VMM footprint measurement to its own branch

The host-readiness observation and the footprint probe answer a different question
than the launch plan does, and they stand alone: no reference to the launch plan,
the execution grant or the mint. They are now PR #10806 off main.

This branch keeps one subject -- what causes a micro-VM to start, and under whose
authorization -- so its title is the one squash-merge writes into main's history.

The extdeps rows move with the code that needs them: gnu_time_max_rss_command and
systemctl_version_command are admitted for the probe, so they belong to the probe's
branch and are reverted here rather than left admitting callers that no longer exist
on this branch.

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

* Join the grant to the reservation it authorizes, and split planning from authorization

Requiring an ExecutionGrant parameter closed the absent-grant case and nothing
else. The grant and the cell binding arrived as independent arguments and the only
thing read off the grant was its money reservation, so a grant issued for
execution A composed with the reserved cell for execution B was ADMITTED whenever
the supplied account fenced A. That is worse than no grant: its presence reads as
authorization while authorizing nothing about the subject. Found in review; the
PR text claiming this bound the launch under the fabric contract overclaimed.

MicroVmExecutionBinding is sole_constructor and reachable only through
seal_execution_binding, which refuses unless Work, Demand and Offer all agree
between the grant and the reservation. The mismatched pair now has no constructor
rather than being caught by a check inside the planner.

THE RULE IS FACTORED OUT OF THE SEAL SO ITS RED IS AUTHORABLE.
RequiredBuildCellSnapshot is sole_constructor precisely so a caller cannot
manufacture a reservation, which also means no witness outside its module can
build a DISAGREEING one -- a control aimed at the seal could never construct the
input it exists to catch, so it would be permanently green and cited as coverage.
Exposing a snapshot builder for tests would punch the hole the seal exists to
close, the tradeoff runner_microvm_attempt's own witness already records. So
grant_authorizes_reservation takes the coordinates, where a disagreeing pair IS
constructible, and the seal reads them off the snapshot and delegates to it.

PLANNING NO LONGER PRETENDS TO BE AUTHORIZATION.
plan_microvm_launch_of_binding takes no grant at all, because cell ownership, VM
config, admission and the mint are not authorization questions and a signature
that accepted a grant while ignoring its subject is what created the opening.
Production reaches it only through the sealed value, so the gate cannot be routed
around; witnesses drive each level where that level's red is writable.

WHAT THIS STILL DOES NOT ESTABLISH, recorded because the type name would read as
more. ExecutionGrant is an ordinary constructible record, so this proves AGREEMENT
between a presented grant and a presented reservation, not that fabric issuance
durably committed either. The unforgeable form is a persistent executor re-reading
the committed grant from durable storage after the submitter terminates, which is
FCI-3's own exit predicate and the next slice.

Ten claims pass. The substitution control was verified by removing ONLY the Work
join in production: it reds alone while the fence and positive controls stay
green, so it is bound to this defect rather than to a rule that refuses broadly.

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

* Reduce to the authorization rule, and file what the two homes cost

The launch plan half is deleted. Not because it was unconsumed -- because it was
REDUNDANT. runner_microvm_witness_test already carries
a_reserve_larger_than_the_whole_envelope_admits_no_guest,
the_production_reserve_is_the_declared_allowance,
the_production_reserve_lets_the_declared_configuration_reach_admission_under_authored_topology,
orphanable_unit_refuses_the_microvm_migration,
shared_slot_directory_credential_is_refused and the whole guest-memory boundary
family; cell ownership is in runner_microvm_attempt_witness_test and the
credential join in the jit mint witness. Deleting the seven planning claims
removes NO coverage. I re-derived them because this branch predated #10657 and I
never read the neighbouring witness, which is the same root as a roster edit on
this branch that would have dropped ten rows other lanes had landed.

What survives is the part nothing else answers. Grant and cell binding arrived as
independent arguments with only the money reservation read, so a grant issued for
execution A authorized execution B whenever the account fenced A -- the grant's
presence read as authorization while authorizing nothing about the subject.
grant_authorizes_reservation now requires Work, Demand and Offer to agree,
MicroVmExecutionBinding is sole_constructor so the mismatched pair has no
constructor, and three controls carry it.

The module is renamed for what it now contains. A module called launch_plan
holding no launch plan is a name promising something it does not carry.

ON THE SEAL, because the question was whether it is a 4b decoration.
SoleConstructorViolation for cross-module construction is already proven by
sole_constructor_violation_outside_module over a synthetic two-module pair, so the
red IS authorable and the seal is not a decoration. A type-specific fixture would
re-prove the general wall in a second home, which is the same redundancy this
commit deletes. Stated honestly: that evidence runs under cargo test -p
v1-compiler --lib, which no CI step executes -- the declared drop
rust_unit_tests_off_the_merge_path -- so the wall is compile-enforced but the
evidence one would cite for it is not on the merge path.

This refusal and #10733's do not duplicate. jitconfig-bytes=0 is about CREDENTIAL
MATERIAL; this is about AUTHORIZATION SUBJECT, and it orders strictly earlier
because it refuses before any drive is built. They compose.

Verified after the reduction rather than before: three claims pass, and removing
ONLY the Work join in the renamed module reds the substitution control alone while
the fence and positive controls stay green.

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

* Record the count-first merge check on the two-homes row

The near-misses this branch produced belong with the class they are
nearest to: an append-only carrier losing a row renders as reordering,
so the diff is blind and only the count against the base raises it.

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

* Restore two dark roster rows, and cut the launch-plan residue

REVIEW FINDING 1, CONFIRMED AND IT IS THIS PR'S OWN CLASS. Roster was
175 on main and 174 here: added production_actuation... and silently
dropped decision_surface_truncation (#10712) and
lookup_miss_read_as_positive_classification (#10727), both landed after
the branch point and both with their module files still present. The
triple-dot diff renders it as reorder-plus-two and shows nothing. Found
by the count comparison the PR's own row prescribes, named by an
identity join, and repaired by merging main so the roster is 176 with an
empty join in both directions -- imports and list body checked
separately, since either half can drop alone.

REVIEW FINDING 2, CONFIRMED. The reduction left the launch graph's
imports (32 lines -> 8 in the module, 37 -> 18 in the witness) and an
annotation asserting mint-last/LaunchAdmitted ordering that no longer
has a mint or a launch to range over -- a 4c annotation no Accepted
program reads, so a false one never reds and it drifts to flatter the
change. Annotation replaced with what the module now does, including
why the ordering argument is NOT restated here. Orphan fixtures deleted
(launch_org, launch_net, launch_binding, witness_reserve, both lifecycle
intents, launch_test_slot); witness_reserve's own note was stale too,
still saying production refuses every launch.

Also declares the seal a 3c frontier rather than leaving it implicit:
nothing consumes it, and no witness CAN -- its input is a
sole_constructor snapshot -- so the trigger is named beside it, with the
honest alternative stated.

Three claims pass. Compile shows the 3 pre-existing extdeps/gunbc shell
transport errors and nothing in these files.

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

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md production_actuation_unmodeled_beside_a_modeled_probe
Ledger-Rows-Repaired: docs/design-failure-modes.md decision_surface_truncation
Ledger-Repair-Judged: docs/design-rung-drops.md

* Give the Demand and Offer joins their own discriminating REDs

Review 62349 is right and it is the sharpest catch on this branch. The
rule has FOUR refusal arms -- fence, Work, Demand, Offer -- and the
witness enrolled REDs for TWO. A rule that dropped the Demand and Offer
checks entirely would have greened every claim in the file, so the join
was reported at a rung its executed evidence does not establish. That is
rung inflation in the module built to stop a substitution.

Two controls added, same shape as the Work case. Arm-to-assertion count
is now 4 and 4.

MUTATION-PROVEN INDIVIDUALLY, because five passing claims do not
establish discrimination -- a control that fails alongside others is not
a discriminating RED:
  remove ONLY the Demand arm -> ONLY a_grant_for_another_demand reds
  remove ONLY the Offer arm  -> ONLY a_grant_for_another_offer reds
Both times the other four stay green.

Also records in the witness WHY these REDs are authorable when the seal's
is not, since the questions sound identical and get opposite answers: the
seal's input is a sole_constructor snapshot, so a disagreeing one has no
constructor a fixture can reach and a control there would be a 4b
decoration; FabricIdentity is an ordinary record, so a disagreeing demand
or offer is writable right here. The constructor decides it, not how
important the check feels.

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

* Delete the unconsumed seal, and restore two more dark roster rows

Review 62362, both findings correct.

THE SEAL IS DELETED, WHICH IS WHAT MY OWN ANNOTATION SAID TO DO. I had
declared it a 3c frontier, but 3c admits one only with a NAMED LATER
CHANGE, and mine was FCI-3 -- which the displacement analysis shows is
blocked behind a prerequisite nobody has started. The review's phrasing
is right: a gate id plus a missing plan and an unpriceable prerequisite
is not a named later change.

And the annotation cited docs/plans/microvm-launch-displacement-analysis.md,
WHICH DOES NOT EXIST ON THIS BRANCH -- it is on the unmerged #10823. So
the justification for keeping unconsumed code rested on an artifact that
does not resolve, which is the citation defect this session has now made
three times across unmerged branch boundaries.

This also DISCHARGES rather than overrides the earlier ruling to keep the
seal: that keep was conditional on a fixture being able to author the
forbidden state and be refused. No witness can call it at all -- its
input is a sole_constructor snapshot -- so the condition failed.

Module is 79 lines: one rule, four refusal arms, five claims. All five
still pass with the seal gone, which is the evidence that they were
driving the RULE and never the seal.

ROSTER WAS BEHIND MAIN AGAIN, 176 vs 177, missing
guarded_filter_branch_dropped_by_emitter and
match_variant_lookup_does_not_peel_type_alias. Merged: 178 imports and
178 list entries, empty join against main in both directions. Second
time on this branch -- the ledger surface moves faster than an
edit-review cycle, and only the count comparison catches it.

The projection came back UU with NO conflict markers, the merge driver
refusing as designed. That artifact is HealRegeneratesAfterProvisionalMerge,
so the provisional state is staged for heal rather than hand-written.

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

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md guarded_filter_branch_dropped_by_emitter
Ledger-Rows-Repaired: docs/design-failure-modes.md match_variant_lookup_does_not_peel_type_alias
Ledger-Repair-Judged: docs/design-rung-drops.md

* Declare the frontier for the rule this module keeps, and record a false annotation as false

Review 62386, both findings correct.

THE ANNOTATION WAS FALSE, NOT MERELY STALE. It said the rule was
"factored out of the seal above it" and that "the seal delegates to it".
The seal was deleted last commit, and bind_microvm_to_cell takes only a
snapshot and never sees an ExecutionGrant -- so the sentence asserted a
relationship between two declarations that does not exist in the corpus.
Recorded as false rather than quietly removed: a 4c annotation is read by
no Accepted program, so a false one never reds, and the next reader
deserves to know the claim was wrong rather than find a tidy comment.

THE FRONTIER WAS DECLARED FOR WHAT I DELETED AND NOT FOR WHAT I KEPT,
which is the review's actual point and it is exact. 3c's two admissible
states differ by whether the consumer and trigger are stated BESIDE the
declaration; for grant_authorizes_reservation they were not, so the rule
was dangling rather than declared. Now stated: named consumer is the
FCI-3 executor whose exit predicate this rule is the agreement half of,
trigger is that executor existing, and the route today is that nothing
in the runner path calls it.

STATED WITH ITS COST RATHER THAN AS A REASSURANCE: that trigger is not
near, because the actuation FCI-3 must displace is authored in no
repository. A reader weighing whether the module earned its place should
have that beside what it does carry -- four typed refusal arms, each
mutation-proven to red ALONE. Correctness established by execution;
only production consumption deferred.

AND A TENSION IN MY OWN REASONING, NAMED RATHER THAN LEFT FOR A REVIEWER:
I deleted the seal partly on "the trigger is not near", and I now declare
a frontier for the rule on that same trigger. 3c requires a trigger to be
STATED, not to be near, so "not near" was over-applied there. The seal
still went for the right reason -- no fixture could construct its
sole_constructor input, so it had ZERO executed evidence. The seal failed
consumption AND evidence; the rule fails only consumption.

5 claims pass.

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

* Home the grant-authorization join beside the issuer, with typed refusal variants

Review 62520 raised two findings and both were real.

The refusal type collapsed four named causes into AuthorizationDisagrees { cause: String }
while the annotation directly above it asserted "THE REFUSAL ARMS ARE NOT COLLAPSED INTO ONE
`cause` STRING". The four arms were four if-branches producing four prose strings. That is
DESIGN section 2's anemic String leaf, and because a section 4c annotation is read by no
Accepted program, the false claim could never red. The type is now a sum matching the sibling
ReceiptAdmission: GrantAuthorizes | GrantFenceRefused | GrantAuthorizesDifferentWork |
GrantAnswersDifferentDemand | GrantIssuedAgainstDifferentOffer. The five controls match a
named variant instead of string_contains, which strengthens them -- a refusal naming the
wrong authority could still contain the right words.

The home finding was also right, but its prescribed destination does not compile.
product.fabric.budget already imports product.fabric.execution, so homing a rule that needs
MoneyAccount and fence_admits into execution.dag creates an import cycle -- the one structural
law section 4 states. The rule is homed in product.fabric.grant_issue instead, which already
imports execution, budget, identity and work, and which issues the grant: the agreement join
is the direct counterpart of issue_grant, at FCI-2's grain. gunbc.runner_microvm_execution_authorization
is deleted and the witness moved beside the issuance fixture it already imported.

The section 3c frontier is re-declared at the new home: the rule still has no production
consumer, its named consumer is the FCI-3 executor, and the trigger is stated.

Executed evidence: five claims pass under claim_batch --claim-run against
dag/test/claim/fabric/fabric_grant_authorization_witness_test.dag.

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

* Scope the append-only-carrier corollary to carriers that are still a list

#10822 deleted the hand-appended failure-mode roster: membership is the row
DIRECTORY now, derived before compile. This row's corollary prescribed a count
comparison against the base for append-only carriers and cited the roster at 163
against 173 as a live countable specimen. Half that citation is a superseded claim,
and it had not landed yet -- so this corrects the row before it ever becomes an
authority rather than editing a published one.

I had declined this edit earlier on the grounds that the row was a live authority and
that changing prose inside a PR waiting on an unrelated gate is how a superseded
claim survives review. The reasoning was sound and its PREMISE WAS FALSE: the row is
absent from origin/main and exists only on this branch. There is no landed authority
to preserve.

The corrected text is taken verbatim from #10853 (deep-crane-107, 9a2358a), which
added the identical file and differed only in this one receipt string; that PR closes
rather than landing a second copy for this one to later drop. The file is now
byte-identical to its version, so one row has one authority.

What the text now does: scopes the rule to append-only carriers that are STILL A
LIST, keeps extdeps.exec.command admit_callers at 67-vs-68 as the surviving specimen,
marks the roster SUPERSEDED FOR ONE CARRIER ONLY citing #10822, and keeps the closing
instruction not to invent a replacement count for the dissolved carrier -- directory
membership is a different subject.

The correction is self-applying: refreshing that citation as though the roster were
still countable would itself be a_live_authority_name_carries_a_superseded_claim.

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

* Fence the money reservation with the money generation, not the lease's

Review 62587 found two real defects and the second explains why the first survived.

THE RULE JOINED TWO INDEPENDENTLY ADVANCING COUNTERS. fence_admits compares the
generation the ACCOUNT holds for a reference, and product.fabric.budget reserve_money
records that from the MONEY request (`generation: request.generation`). The rule
presented grant_lease_generation, which answers the RESOURCE lease's counter. So a
grant authorized whenever the two happened to coincide, and a legitimate grant whose
counters had diverged was refused as GrantFenceRefused -- a cause naming the wrong
fact. product.fabric.execution already records this class one authority over:
comparing generations alone was a real fencing hole, and it made prose describe a wall
the code did not build. This was that class re-introduced against a different
generation authority.

ExecutionGrant carries NO money generation -- money_reservation is a bare
ReservationRef -- so there was no correct value to read off the grant. It is now a
presented coordinate, which is what the rest of this rule already does and what makes
its REDs authorable. The witness presents money_request.generation, what the ISSUER
asked for, against what the account RECORDED: two independently authored facts.
Reading it back off the account would compare a value to itself and green by
construction, which is the 4b decoration this rule exists to avoid.

THE POSITIVE CONTROL WAS GREEN BY FIXTURE COINCIDENCE. money_request.generation is 9
and fixture_lease.generation is 9, so the old control passed while proving only that
two unrelated counters agreed. The fixtures are left at 9 and the discrimination is
carried by a new claim that presents the RIGHT account against a WRONG generation --
the arm the coincidence was hiding, since the existing exhausted_account claim refuses
because the account holds no lease for the reference AT ALL and would pass a rule that
ignored the generation entirely.

MUTATION-PROVEN, and the result is the review's finding as an executed measurement:
with the defect restored, EXACTLY ONE claim fails --
a_grant_fenced_at_another_money_generation_authorizes_nothing -- while the other five
pass, INCLUDING the old positive control. The pre-existing suite was structurally
blind to this defect, which is how it survived. Reverted, all six pass.

The now-unused grant_lease_generation import is dropped: leaving it would advertise a
dependency the code no longer has.

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

---------

Co-authored-by: Brian Searls <cutencool.work@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
briansrls pushed a commit that referenced this pull request Sep 9, 2026
…aths, fold the two trigger histories

#10822 deleted docs/design-failure-modes.md and dag/gunbc/recurring_failure_mode/roster.dag
(both now gitignored and generated before compile; membership is the directory). Both
deletions accepted -- restoring either would re-create the attractor DESIGN §3 names.
inflation_fold_cannot_name_the_environment_it_measures.dag is kept: that per-class file
IS the registration now.

docs/design-rung-drops.md is taken from the base side verbatim, unregenerated, per the
generated-artifact driver's declared repair route; set difference over both row-identity
shapes reports no row gone dark.

determinism_transitive_reachability.dag's restoration_trigger was edited on both sides and
neither subsumed the other. Merged rather than chosen: this branch's capability-grain
trigger (the index prescription is refuted by execution, so the trigger names the COST
SHAPE) is the head, and main's later paragraphs are folded in -- the artifact-not-a-clock
instrument correction, the partition-by-eval_steps rule, the worsening pre-repair
distribution, the three repaired shapes in the past tense, and the map-dedup obligation.
One sentence of main's was rewritten where it cited the CAPABILITY clause that no longer
says what it said.

Compiles: 0 blocking errors on the row's own entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQuBs4mLu9ArojSWFiRcey
briansrls pushed a commit that referenced this pull request Sep 9, 2026
#10814)

* Delete world_converge at the root: read-only policy was bound as an actuator that only refuses

gunbc.world_converge fused observation, comparison and actuation into one
handler with an apply stage. Three measured consequences, none of which a
refinement could have reached:

The successful actuation path had never executed anywhere in the corpus --
the generic's own fixture bound apply to FixtureApplyShouldNotRun, and the
sole production binding is read-only. There was no green to preserve.

Read-only policy was therefore modelled as an actuator that always refuses,
so three of repo_ruleset's seven refusal causes described a write that does
not exist. One of them, RepositoryRulesetUnexpectedLiveRule, was a second
representation of a fact the divergence list already carried.

And that cost a finding: because the holds were ordered above the divergence
step, an unreadable bypass roster -- which is EVERY live read under this
repository's credential -- returned the hold and DISCARDED the drift list.
verify reported that the roster could not be read and never reported that
the enforcement, required contexts or ruleset name had also drifted.

std.goal_assessment replaces it, keeping the two layers as two layers and
making the empty divergence unconstructible: GoalDiverged and
GoalIndeterminate carry std.algebra FreeSemigroup, which has no empty
inhabitant. No NonEmptyList was minted -- FreeSemigroup is the corpus's
nonempty carrier and its own grounding note withholds that alias.

repo_ruleset cuts to inspect_goal in the same motion. Two causes are deleted
rather than moved, RulesetBypassRosterUnobserved leaves the DIVERGENCE
coproduct to become an unknown, and two helpers that already recorded having
no production consumer go with the last thing that could be mistaken for one.
Every refusal that made verify exit non-zero still does; what changed is that
the drift survives a hold.

Not landed, with triggers stated in the module: reconcile_once, whose only
honest first consumer is the fleet admission spine; and a goal revision,
whose inhabitant is the plan basis.

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

* Delete goal_is_satisfied: a one-bit summary of a four-arm coproduct, consumed by nothing

Review 62257. Verified: `git grep goal_is_satisfied` returned its own
definition and the witness asserting it, and nothing else. The one real
binding this branch lands, gunbc.repo_ruleset verify, matches GoalSatisfied
directly -- correctly, because the other three arms carry payloads it has to
report. So the declaration's only consumer was a test, which DESIGN section
3c and section 5 both red: a witness is not a consumer, and its own
annotation named a caller that did not exist.

It is the same test this branch already applied when it deleted
bypass_roster_projection_refusal and merge_queue_projection_refusal, which
had recorded the identical standing. Missing it on a declaration I had just
written is the reason the reviewer's question is asked from the consumers
inward rather than from the declaration outward.

The law the deleted witness asserted -- that indeterminate must not read as
agreement -- is not weakened by losing it. It is strengthened: with no
one-bit summary in the module, the conflation has no spelling to be written
in, which is construction where there was validation. An annotation now
stands where the predicate did, saying why the convenience is refused, so
the next author does not restore it as a kindness.

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

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md read_only_policy_bound_as_an_actuator_that_only_refuses
Ledger-Repair-Judged: docs/design-rung-drops.md

* Enroll the new witnesses as test fn, and attach the is_satisfied rationale to a declaration

TWO DEFECTS, ONE OF THEM THE REASON THE OTHER WAS NOT CAUGHT.

Review 62263: every assertion in the two new witness files was declared
`fn`, not `test fn`. Verified against the modelled rule rather than by
inspection -- v2.workflow.floor_naming_hygiene enrolls a decl only when its
line starts with `test fn ` or `test data `, and
floor_discovery_barren_test_sidecar_reason_prefix refuses a `*_test.dag`
entry declaring none. So floor discovery enrolled NOTHING from either file.
The whole evidence set for the new std.goal_assessment root -- the layer
guard, the nonempty-divergence law, the unknowns-outrank-deviations
precedence -- was inert.

That is section 4b(4) inverted: a climb deletes the lower-rung PRODUCTION
machinery and its discriminating RED stays enrolled. This migration deleted
the enrolled world_converge witnesses and put unenrolled functions in their
place. All ten are now `test fn`.

The second defect is the annotation this branch put where goal_is_satisfied
had been: a trailing `//` block at end of file, which section 4c refuses
because an annotation must attach to a module-scope declaration and that one
named no subject. Ten located parse errors, and they are what refused the
floor phase in CI. The rationale now sits above `type GoalAssessment`, which
is the carrier it is actually about.

WHY MY OWN VERIFICATION MISSED BOTH, recorded because the gap is the useful
part: `claim_batch --functions <names>` runs whatever it is handed, so it
proves the assertions HOLD and says nothing about whether anything would
ever ask them; and it resolves an entry closure rather than running the
parse phase over the corpus, so the annotation refusal never reached it.
Naming functions to a runner is not evidence that a lane runs them.
`gunbc compile --source-root dag --source-root src/v2` is the instrument for
the second, and it now reports zero errors where CI reported ten.

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

* Delete the dead binding fixture and every import that outlived its consumer

Review 62280, all four findings verified against the tree and all four real:
`fixture_subject` in the binding witness had no caller anywhere; the
`Bool` import in std.goal_assessment outlived the deleted goal_is_satisfied;
and RepositoryRulesetConvergenceSubject, ObservationRefused,
ObservationEstablished and RulesetBypassRosterUnobservable were imported and
never referenced.

RATHER THAN FIX THE FOUR THAT WERE NAMED, THE SAME QUESTION WAS ASKED OF
EVERY DECLARATION AND EVERY IMPORT THESE FILES CARRY, which found three more
the review did not reach: `List` and `GoalInspection` in the generic witness,
`NoRequiredStatusChecksRule` in the binding witness -- and `gunbc_repository`,
which became unused only once the dead fixture was deleted. A reviewer
finding four instances of one class is evidence about the class, not a list
of four repairs, and the fourth of those would have survived a
finding-by-finding fix.

That is the third time this branch has landed an unconsumed declaration:
goal_is_satisfied, then this fixture, both mine, both after the branch had
already deleted two such helpers for exactly that reason. The pattern is that
section 3c is easy to apply to code being removed and easy to forget on code
being written, so it now runs as a sweep over the diff rather than a
judgement at each site.

Ten witnesses re-run after the deletions: 10 PASS, 0 FAIL.

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

* Remove the assessor's remaining short-circuit: a one-dimension goal defect suppressed every comparison

Review 62287, verified and correct. After the actuation holds were deleted,
`ruleset_assess` still answered `signed_desire_projection_refusal()` FIRST
and returned a whole-assessment refusal, never reaching the divergence fold.
So a merge-queue projection gap suppressed live enforcement, target, name and
required-context drift that have nothing to do with it -- the same ordering
harm this branch deleted the holds to fix, reproduced one layer down on the
arm that was left, and an instance of the class this same branch rosters as
read_only_policy_bound_as_an_actuator_that_only_refuses. The helper's own
prose still reads "Refusing rather than actuating", which is what it was for:
a refusal on behalf of a writer this module does not bind.

THE DISPOSITION IS NOT THE MINIMUM THE REVIEW ASKED FOR, because the honest
one is available. The gap is not merely "do not let it suppress the rest": the
goal states the merge-queue desire TWICE -- as the signed policy and as the
rules wire the projection carries -- and when those disagree, WHICH ONE IS THE
GOAL is undetermined. merge_queue_divergences can still compare the live queue
against the signed policy, so the dimension is not unassessable for want of a
comparison; it is unassessable for want of an unambiguous goal. That is an
UNKNOWN on one dimension, RulesetMergeQueueDesireSelfInconsistent, carried
beside every other dimension's proved drift.

With that arm gone, no state of this subject leaves its inputs incomparable,
and the type now says so rather than leaving an unreachable variant to be
mistaken for a live one: AssessRefusal is v2.std.cardinality Never. The arm
survives in verify's match because the coproduct is closed -- which is the
property that makes a fifth assessment arm a compile error rather than a
silent fall-through -- and it is unreachable by construction.

Two witnesses added: the desire is self-consistent today and contributes no
unknown, and a name drift is still reported with the desire self-consistent,
which a reinstated short-circuit anywhere above the comparison fails.
36 witnesses over both repo_ruleset entries: 36 PASS, 0 FAIL.

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

* Fix forward the world_converge census that fired at merge time, not at deletion time

Review 62304. Verified and correct, and the CAUSE is the part worth keeping:
when this branch deleted gunbc.world_converge the census was clean -- four
referents, all handled. Main then landed #10712 (std.decision, "selection
precedes convergence"), which added a NEW world_converge consumer while this
branch was deleting it. Merging main was textually clean, so the break
arrived silently: no conflict, no marker, an unresolved import on a claim
witness. A textual merge cannot see a symbol deleted on one side and newly
referenced on the other.

DESIGN section 3 says the deletion IS the census and what breaks is exactly
what was load-bearing. That holds -- but only against the tree at deletion
time. Against a moving main the census must be RE-RUN after every merge, and
this branch had merged four times without doing so.

Fixed forward rather than restored, per the same section:

- dag/test/claim/realization_selection_witness_test.dag migrates to
  std.goal_assessment. Two stages disappear rather than being rewritten: it
  supplied WorldApplied over a context it threaded through, asserting nothing
  about actuation and existing only because the interface demanded a stage.
  There is now nothing to supply. The section 3d claim it exists for is
  unchanged -- a SelectedWithin candidate feeds the convergence root as its
  SUBJECT and the root reaches a satisfied assessment -- and the receipt was
  returned unchanged by the old handler too, so nothing asserted was dropped.
  Its two test names named the deleted symbol and are renamed with it.

- gunbc.design_document, gunbc.design_argument and std.decision cited
  world_converge in the prose and in the section 3d reviewer question. That
  is the stale-name defect section 3's standing rule names, sitting in the
  document that GOVERNS the criterion, so the rename lands with the deletion.
  DESIGN.md regenerated: zero occurrences.

18 witnesses on the migrated entry: 18 PASS, 0 FAIL.

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

* State the trigger for GoalAssessmentRefused: a declared frontier, not an unconsumed arm

Found by re-reading DESIGN against the diff rather than by a reviewer, which
is the only reason it is worth recording separately.

GoalAssessmentRefused has no constructor anywhere in the corpus. Both
bindings reach it never: repo_ruleset instantiates AssessRefusal at
v2.std.cardinality Never because no state of that subject leaves its inputs
incomparable, and the realization-selection witness never mints it. Under
section 3c that is a DECLARED FRONTIER, admissible only with its trigger
stated beside it, and no trigger was stated.

The trigger is a NAMED CONSUMER rather than a date, because section 4b(3)'s
rule that a trigger must name the capability applies to a frontier the same
way: product.fan_tach_health already computes exactly this fact --
FanTachRefused over DuplicateExpectedHeader, DuplicateObservedHeader and
HostIdentityMismatch, each an ill-formed QUESTION SET rather than an unhealthy
fan -- and the arm discharges when that module binds to this algebra.

DELETING THE ARM WAS THE OTHER CANDIDATE AND IS THE WRONG ONE. The
distinction between "the world differs" and "the question was malformed" is
one this module exists to keep; a coproduct that cannot express the second
forces the first binding with an ill-formed input to file it as a divergence,
which is the conflation section 5 forbids. The frontier is the honest state.

This is the fifth unconsumed declaration on this branch. The first four were
found by review; this one by reading the authority. Both are cheaper than the
sixth.

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

* Move the unreachable-arm rationale to module-item grain: an in-body annotation is a parse refusal

CI on c6a2626: required-witnesses-floor refused with four parse errors, all
mine, all one class -- "source annotation sits inside a declaration body.
Only module-item grain is modeled." The note explaining why verify's
GoalAssessmentRefused arm is unreachable sat INSIDE the match. It now sits
above verify, where DESIGN section 4c says an annotation attaches, and it
records that fact so the next author does not rediscover it the same way.

WHY THIS SHIPPED, WHICH IS THE PART WORTH KEEPING. This branch already broke
CI once on section 4c, with a TRAILING annotation that named no subject. I
fixed that and then verified this round with the check I had built for it:
"does the file end on a declaration". That check is structurally incapable of
seeing a body-level comment. I tested the previous BUG instead of the RULE,
which is the same shape as a regression control that greens on everything
except the one input it was written for.

The rule is one line of DESIGN and decides both variants at once: annotations
attach at module-item grain. The check that follows from the rule rather than
from the last failure is `^[[:space:]]+//` over the changed files, and it now
returns empty across all of them.

Verified with the instrument rather than a proxy this time: `gunbc compile
--source-root dag --source-root src/v2` completes its frontend phase -- the
phase that refused -- with zero occurrences of the annotation class and zero
errors in any file this branch touches.

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

* Restore the rationale two surviving witnesses lost with their deleted neighbours

The world_converge deletion removed the tests around them, and their leading
annotation blocks went with the wrong neighbour. Both survivors were left bare:
w_RED_an_incomplete_merge_queue_rule_refuses_and_names_the_missing_fields and
w_RED_verify_reports_an_undeclared_bypass_actor.

The restored text is rewritten rather than reverted, because the original prose
described a shape this branch removed: one half cited a type-tag guard test that
no longer exists, and the other contrasted verify against a converge actuation
path that is gone. What survives is the irreducible rationale DESIGN section 4c
asks for -- absent and zero are different observations and only the optional
carries that difference; a refusal that names nothing tells the operator that
something is wrong without telling them what.

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

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md read_only_policy_bound_as_an_actuator_that_only_refuses
Ledger-Repair-Judged: docs/design-rung-drops.md

* Restore the parameter seam that makes the merge-queue unknown reachable

merge_queue_desire_unknowns read the declared rows through a nullary wrapper,
so with this repository's merge queue unsigned the fold answered Absent on
every input the corpus can produce: the Present arm and the
RulesetMergeQueueDesireSelfInconsistent renderer it feeds were unreachable, and
the only claim over them was a positive control. DESIGN section 4b asks whether
a check's RED is authorable before the check is written, and settles it on
whether the forbidden state is authorable AS A FIXTURE rather than whether it
exists in the accepted corpus. It is.

So the seam is a parameter, as signed_desire_projection_refusal_for already was
and as the deleted repo_ruleset_observed_actuation_admission carried it for this
same reason -- it was unreachability that let an inversion sit in that arm
undetected. The nullary wrapper is deleted rather than kept beside the
parameterized one.

The RED is discriminating by execution, not by inspection: with the operand set
back to what the nullary wrapper supplied, claim_batch reports FAIL on
RED_a_signed_policy_the_projection_does_not_carry_is_an_unknown_on_that_dimension.
Restored, all 7 witnesses in the file pass.

Also fixes prose the actuation deletion left stale: the refusal string still
read "converge would send a ruleset that cannot converge. Refusing rather than
actuating" in a value that is now an unknown and not a refusal, and an
annotation cited that sentence as present tense.

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

* Reconstruct a derived source at the base revision instead of inheriting its head record

The namespace wave-admission baseline is the head index with the diff applied
in reverse at FILE grain, which assumes one thing about every file the diff did
not name: that its base text equals its head text. That holds for a tracked
file. It is false for a source generated before compile and gitignored, because
such a file never appears in `git diff` no matter how far its content moved --
so its head record was carried into the BASE index and the wall compared the
head against itself.

#10822 made the failure-mode roster derived and gitignored. This branch carries
the first row to land after it, and had its own authored addition reported as
NewPoolCoincidenceResolution -- a name resolving from a pool with nobody
authoring a reference -- when the reference was authored by this very change.
That disposition is not auto-admitted, so a required lane blocked, and it would
have blocked for every later row. The binding delta was right; the authorship
evidence under it was poisoned.

The carry now requires the base tree to contain the path, which is what makes
unchangedness proof of byte identity. A derived path is re-rendered from the
base revision's row files through the SAME renderer the writer uses and parsed
by base_records, so no hand-built record and no second derivation authority
exist. Any other untouched head-index record absent from the base tree is
NotEvaluated: a path listing supplies existence, never content, and an untracked
path may equally be local state, a scratch fixture or a dirty worktree file --
inheriting or silently dropping it fabricates a baseline either way.

binding_disposition, locally_authored_claim_added and disposition_auto_admitted
are untouched, and NAMESPACE_TRANSITION_ADMISSIONS stays empty: nothing is
admitted by permission. Measured on this head, the wall goes from 1 unadjudicated
delta to ADMITTED with 7 real deltas, binding_rows_compared 759867 -> 759866 --
the one row that should never have been compared, because with a correct base
the name does not exist there.

Evidence: the wall's own run on this base/head reports
`namespace-wave-admission ADMITTED`. Two unit tests enrolled in the deriver's
seed-growth population cover the derivation, and the one encoding the defect is
discriminating -- it fails under a mutation that renders the base as the head
while its three siblings stay green. Those tests sit under the declared drop
rust_unit_tests_off_the_merge_path and are local diligence, not enrolled evidence.

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

* Check that base and head share one renderer, and pay the consumed rows this touch brings due

TWO OBLIGATIONS, ONE FILE.

Review 62579 found a promise standing where a check belongs. The base-side
reconstruction renders base inputs with the HEAD renderer, which is sound only
while both revisions carry the same one -- and the diff said so in a comment
while nothing enforced it. A later edit to render_roster would have had the wall
render a base with head logic, compare it against a head rendered the same way,
and report the renderer's own change as zero: the very class this branch repairs
one revision earlier, re-admitted as an author obligation. DESIGN section 5 asks
for the wrong state to be unwritable, not described.

The check compares the text of render_roster on both sides and refuses with
NotEvaluated when it differs. The subject is the RENDERER, not its file: whole-
file bytes were measured first and refuse too much -- they would refuse this very
change, and every later one adding a doc comment beside the renderer, for a
reason unrelated to the hazard. A line stopped where nothing is wrong teaches
authors to route around the wall. The membership rule is a stated residual, not a
covered case, because this change moves it into a declaration the base lacks so
no text comparison spans both sides.

Verified both directions against the real base blob: the texts are equal today,
so the guard passes rather than blocking this branch, and a mutation changing the
rendered import syntax makes it refuse.

THIRTY-FIFTH DISSOLUTION. Touching this file brings the two gunbc#10688 rows due,
and CI said so: `0 unadjudicated delta(s) ... 2 consumed admission(s) due for
deletion on this roster-touching change`. Those rows demanded their own
adjudication -- "not by trusting this sentence" -- so the join was run: on main
v1.std.core DECLARES call_semantics_target while v1.compiler.emit_rust references
it four times and declares it none, so both sides resolve the spelling to
v1.std.core and the TargetChanged delta is no longer producible. Rows and label
go together, as the two label deletions before them did, and the roster is empty
again. gunbc.namespace_wave_admission drops the label from its enumerated
declarations and re-tenses the prose that claimed it was listed.

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

* Restore the record my deletion left headless, and re-tense seven dead actuation claims

TWO FIXES.

The first is mine to own: deleting CALL_SEMANTICS_TARGET_REHOME_LABEL from the
enumerated declarations dropped only the `decl_name` line and left the enclosing
DeclarationRef standing, so gunbc.namespace_wave_admission carried a record with
no name. Every required lane parses the corpus, so one malformed literal failed
all four on fd0098e. The compiler named it exactly -- missing required field
'decl_name' in literal of type 'DeclarationRef' -- which is the fail-closed
behaviour working; the generated-artifact gate now resolves again.

The second is review 62605, which found the sign-off standing still asserting the
world this branch deleted: a WHOLE-RULESET PUT, a converge run that would delete
an out-of-band rule, and "the actuation refusal below" -- a positional pointer to
deleted code -- beside a dissolution string promising "a divergence converge
repairs" two sentences before saying SIGNING THIS ROW ACTUATES NOTHING. A meaning
fork inside one operator-facing obligation.

Sweeping the module found seven such sites, not one, and they are fixed as a
class because DESIGN section 3 is explicit that a dead assumption is an attractor:
while it stands, nearby questions keep being answered in its vocabulary.

The UI-flip warning gets the sharpest correction, because the hazard did not
soften -- it INVERTED. It read: a rule enabled in the web form is deleted by the
next run, a scheduled silent self-reversal. With no writer, a flip is not
reversed at all; it PERSISTS, unmodeled, reported as a divergence on every
assessment forever, with nothing in this repository able to clear it. The
operator sees a configured queue and the assessment sees drift it can never
repair, so signing the row is now the only route rather than merely the best one.

Where a hazard survives as an obligation on a future writer rather than a current
fact -- the whole-ruleset replace, the unexpected-rule refusal, the signed desire
the projection cannot build -- it is stated that way rather than deleted, because
the reason those checks exist does not go away with the writer that consumed them.

All 7 witnesses in repo_ruleset_goal_assessment_witness pass, including the
discriminating RED.

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

* Delete the dissolution record my own edit duplicated, and the stale entry it left standing

Review 62621 found ~90 lines of the module's dissolution record present twice.
It is not a merge artifact: my scripted replacement of the SIXTH TRANSITION block
computed the block's END offset with an index search from position 0, which
matched an EARLIER occurrence of the same trigger sentence in a preceding entry.
With end < start, concatenating the two slices duplicated everything between them.

Deleting the span turned up more than the review reported. The original SIXTH
TRANSITION block was still standing as well, so the file at once documented the
two gunbc#10688 rows as live and recorded their dissolution -- two sources free to
disagree about exactly the rows this roster adjudicates, which is the section 3
violation the review named, in a sharper form than it found.

Verified by counting occurrences against origin/main rather than by reading the
diff, because this is the second time a scripted text edit of mine damaged this
file: every pre-existing record now appears exactly once as it does on main, the
THIRTY-FIFTH DISSOLUTION is the only addition, and SIXTH TRANSITION is gone.
A delta scan across every changed file finds no other block duplicated relative
to main.

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

* Record why the gunbc#10813 admission row went with this merge

Kept separate from the merge commit so the reasoning is reviewable on its own.

Main's gunbc#10813 hit the defect this branch repairs. Adding one
recurring-failure-mode class made the roster's existing declaration resolve a new
name, the wall reported NewPoolCoincidenceResolution -- a name resolving from a
pool with nobody authoring a reference -- and since that disposition does not
auto-admit, the class could not land without a permission row. Its entry called
that "the honest cost of the pool being adjudicated rather than assumed".

It was not the pool's cost. It was a wrong baseline's. The roster is derived
before compile and gitignored, so it never appears in git diff, and the
reconstruction carried its head record into the BASE index -- the wall compared
the head roster against itself, so a reference authored by the change looked
authored by nobody. Every future class would have paid the same row.

With the base re-derived from the base revision's own row files, the class is
absent from the base roster, the binding row exists on one side only, and the
comparison skips it under the rule the wall already states: only rows present on
both sides, because a newly authored name has no prior denotation to change. No
delta is produced, so the row matches nothing -- and a row matching no delta is
STALE, which refuses. Deleting it is that rule firing, not a preference of mine.

Adjudicated by measurement: the class file is ABSENT at this change's merge base
and present at its head, the exact shape that produced the delta before and
produces none after.

My own THIRTY-FIFTH DISSOLUTION entry is dropped in the same resolution. Main
independently recorded the same gunbc#10688 deletion while this branch did, and
keeping both would be two sources free to disagree about which rows were
adjudicated -- the duplicated-record fork review 62621 found here one head ago.

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

* Yield the baseline-reconstruction fix to main's, which landed the same cut

Main's gunbc#10856 implemented the repair this branch carried: a derived roster
has no base side in a diff, so a baseline that drops only the paths the diff
TOUCHED inherits the head's roster as the base's, the module's own source reads
as unchanged, the authorship discriminator answers false, and an ordinary append
classifies NewPoolCoincidenceResolution. Same diagnosis, same cut, arrived at
independently from the other side.

Two implementations of one fact is the section 3 fork, so this branch takes
main's and deletes its own. All four files now diff to zero against main.

MAIN'S IS ALSO BETTER IN A PLACE MINE WAS WRONG. roster_from_path_listing
returns None when the base tree carries no row files, keeping "the roster module
was genuinely absent at this revision" distinct from "present and empty". Mine
rendered an empty roster for both, which fabricates a module the base never had
-- the empty-observation narrow, in the code written to remove one. Main also
generalizes over sweep roots rather than hardcoding the dag/ prefix, and it
retired the gunbc#10813 admission row on the same reasoning this branch reached.

WHAT IS LOST AND WHERE IT SHOULD LAND: the renderer-sameness guard from review
62579. Main's version renders base inputs with the HEAD renderer, so a change to
render_roster would have the wall compare a head-rendered base against a
head-rendered head and report that renderer's own change as zero. The finding is
moot against the code it was written about, since that code is gone; the hazard
is not, and it now sits in main's implementation rather than this PR's. It wants
its own change against main's shape, not a rushed transplant here.

This PR is now the convergence consolidation alone: 14 files, no Rust, no
seed-growth roster, no wall.

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

* Migrate the four world_converge consumers main added while this branch was open

Review 62717 is right that the root cut was unfinished. The fe50f1d merge
brought gunbc#10683's consumers of a module this branch deletes, the textual
merge was clean, and the break arrived silently -- the same gap a14a66d already
named once. The census is the only thing that finds this, which is why the
deletion IS the census.

Migrated onto std.goal_assessment rather than restoring world_converge, because
each of these is the failure class this PR rosters. Both carried an `apply` that
could only refuse, and that refusal had to inhabit the same coproduct as the
causes meaning the subject could not be OBSERVED -- so an unread leg and a
miscoded leg, an uncaptured lane and a misconfigured one, arrived as one verdict
answering two questions. PlantCodingCannotBeActuatedHere and
SwitchCannotBeActuatedHere are deleted, not ported: each restated as a REFUSAL
what the difference list already carried as a VALUE, and the weaker copy was the
arm a reader reached first.

fabric_switch_desired carried the same conflation one layer out. Its
DesiredSpeedUndecidedCause had a single PlantDidNotConverge arm holding a plant
refusal, so a caller could not tell "the plant was never read" from "the plant
was read and diverges" -- go read the plant versus go change the copper, two
remedies behind one name. It is now PlantNotObservable and
PlantDivergedFromRequirement.

EVERY DEFECT IN THE FIRST PASS WAS CAUGHT BY EXECUTION, NOT BY READING. The
corpus compile found a stale import rename and two matches where switch refusal
causes were left standing as arms of an assessment; the witness run then found
two more, where the list-and-unwrap ceremony of the old List shape was carried
into a FreeSemigroup that answers with .head directly. All 13 witnesses across
both files now pass, and the compile reports zero errors in any file this branch
touches.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant