Skip to content

ROOT-2A-WET-0: a real wet executor for the local-repo witness population, with a completeness join and an invocation wall - #9903

Merged
gunbai-bot[bot] merged 13 commits into
mainfrom
root2a-wet0/candidate-wet-route
Sep 1, 2026
Merged

gunbai-bot[bot] merged 13 commits into
mainfrom
root2a-wet0/candidate-wet-route

Conversation

@briansrls

@briansrls briansrls commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

ROOT-2A-WET-0 — a real wet executor for the local-repo witness population, and the two teeth that make its route claim true.

std.witness_admission's per-cadence realization gate returns false for the five falsifier-family cadences because falsifier.yml was deleted at 611fd02770 and nothing replaced it. This PR adds a sixth arm that returns true with its executor rather than before it: LocalRepoWetLane, claiming only witnesses whose real effects are confined to a temporary directory and a local git repository — deliberately narrower than BinWitnessWet, which also downloads ISOs, fetches over the network, builds with real cargo and provisions remote hosts.

What lands:

  • v2.workflow.local_repo_wet_terminal — the lane authority: a candidate-bound terminal receipt, a five-arm observed verdict (Passed | Failed | Refused | Nonterminal | CompletedOverBudget), a single-armed scheduled expectation (as wide as the executor can realize and no wider), and the bidirectional schedule-to-terminal join with one refusal cause per remedy.
  • A non-empty schedule of seven members, each measured wet before being scheduled. They are the members already on main whose effects are local-repo only, and they have executed nowhere since 2026-08-15 — scheduling them repays standing debt rather than only serving the change that needed the lane.
  • The required-floor executor (run_local_repo_wet_lane), which runs the members in ExecutionMode::Wet over the same prepared subject the floor is already evaluating, so the terminal is candidate-bound by construction: there is no receipt to carry from elsewhere and no artifact handoff that could answer for another tree.
  • The invocation tooth. The join enforces completeness conditional on the executor having been called — with no invocation there are no terminals, and a join over zero terminals and an unread schedule holds vacuously. That is the deleted-falsifier.yml shape at this lane's own grain. So execution became a value: LocalRepoWetExecution = NotInvoked | Ran, and local_repo_wet_finalize refuses LocalRepoWetExecutorAbsent when a nonempty schedule meets an uninvoked executor. The floor calls the finalizer unconditionally and outside the executor, so deleting the invocation fails to compile and suppressing it reds by name.
  • The changed-witness gate admits a hermetic route-gap identity only on a same-identity + same-candidate + holding-lane conjunction, so a route gap can be discharged by a wet terminal without a gap ever reading as covered.

Test plan

  • cargo test --release -p v1-compiler --lib changed_witness_projection_tests — 20 passed, including the invocation wall's discriminating red (a_nonempty_schedule_with_no_executor_invocation_refuses_at_finalization, asserting the named arm and the unrun count) and its two paired positives, run through the same finalize_local_repo_wet_lane the floor calls. Not a grep and not a .dag-only comparison.
  • cargo clippy --all-targets -- -D warnings — clean.
  • src/v2/test/claim/local_repo_wet_terminal_test.dag — the acceptance matrix: one arm per join refusal cause, each discriminating on its own cause rather than on "the join refused", plus five invocation-wall arms (absent-refuses, empty-holds, complete-holds, foreign-candidate, ran-but-skipped-still-missing). Each evaluated true under gunbc run.
  • The real positive is CI itself: required-witnesses-floor runs the seven scheduled members wet against the candidate under evaluation and prints one [local-repo-wet] identity=… observed=… agrees=… line each, then the scheduled=/admitted=/refusals= aggregate. Read the roster from the run's own announcement, not from this body.

Brian Searls added 8 commits September 1, 2026 06:43
…trix

The changed-witness gate is right to block a raw route gap: held is a debt disposition, not a
verdict, and two enrolment rows added in one change would otherwise let an author authorize their
own witness's permanent non-execution. What was missing is not awareness of route gaps but an
ALTERNATE TERMINAL to join against them.

local_repo_wet_terminal carries one receipt per scheduled identity, bound to the candidate it ran
against, and joins the schedule to the receipts in BOTH directions at identity grain -- a member
with no receipt and a receipt with no member are each a typed refusal, so the join is a set
equality rather than a coverage count. Thirteen witnesses discriminate every refusal separately;
an implementation that simply made every route-gap standing nonblocking fails eleven of them.

std.witness_admission gains LocalRepoWetLane with its realization gate TRUE, and it is the one arm
added together with its executor rather than ahead of it. It is deliberately narrower than
BinWitnessWet, which spans ISO downloads, network fetches, real cargo builds and remote-host
provisioning -- a cadence-wide route claim over that population would be false.
…he lane's first seven members

The new enum arm broke nine exhaustive matches, which is the construction working: a cadence cannot
be added without every classifier stating where it sits. Each site was read before it was answered
-- LocalRepoWetLane is not offline, not a fixture roster, not discovery selection, not any falsifier
family and not no-consumer, and it DOES execute, which is the one arm that is true.

The roster lands with seven members rather than empty. An empty roster would make the lane's
coverage claim vacuously true with nothing proving the executor runs anything -- green by
construction. These seven are the members already on main whose effects are local-repo only, and
they have executed nowhere since falsifier.yml was deleted on 2026-08-15, so scheduling them repays
standing debt instead of only serving the change that needed the lane. Each was measured wet before
being scheduled (~130ms each); a member the executor cannot run would make the route claim false.
…riminating arms

RouteGapBeforeVerdict is NOT reinterpreted as a pass -- it stays nonterminal and stays blocking on
its own. What changes is that a second, independently produced terminal can be joined to it, which
is the cost-debt pattern directly above it: a nonstandard path becomes admissible only when it
carries the replacement observation, never because a roster says one ought to exist.

The three facts are one record rather than three loose booleans because any two without the third
is a different and wrong claim: a route-gap agreement alone says the hermetic route could not reach
the identity, a scheduled route alone says a policy claims to run it, and a wet terminal alone says
something passed somewhere. The arm is CONSTRUCTED only when all three hold, so its name is never a
claim its own record contradicts, and the blocking predicate re-checks completeness independently.

Three of the five new arms are the controls the ruling names: no live route, no wet terminal, and a
route-gap expectation that did not agree each still block. An implementation that simply made every
route-gap standing nonblocking passes the first arm and fails those three. 38/38 in the file.
…a consumed entry, and no verdict collapse

The lane's members are on the discovery-exclusion frontier, so the prepared subject never
contained them and the first CI run refused seven times with EntryModuleOutsidePreparedSubject.
The schedule is now decoded in the policy closure and its modules join the closure seeds.

Seam 1: HermeticRouteGapWetJoin's three caller-writable Booleans could not establish that their
three facts concerned the same identity or the same tree. Replaced by WetTerminalAdmitted plus a
two-arm evidence type carrying the typed LocalRepoWetJoin, with completeness taking the identity
and candidate under decision. Three witnesses the old shape could not express now stand.

Seam 2: the authored entry was never read. It is joined against the source the prepared subject
resolved for the module, and disagreement refuses.

Seam 3: Passed/Failed/Refused/Nonterminal collapsed to a Bool in the host. The scheduled
expectation is narrowed to the one arm this lane can realize and a second arm refuses by name;
the observed side keeps all four through the host.
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 1, 2026 09:34

Copy link
Copy Markdown
Contributor Author

CONTROL HOLD — ROOT-2A-WET-0

Exact head: 9fe0ae0090f3c85747581c247b4c3385c9a2d354.

Accepted at this head: the real required-floor route is live; the initial seven EntryModuleOutsidePreparedSubject refusals were honest and the schedule-derived closure seeds are the correct repair; the caller-writable three-Boolean seam is replaced by identity/candidate-bound evidence carrying the typed lane join; the authored entry is now consumed by a production path-to-module join; the schedule expectation is pass-only; and a called executor that silently filters a nonempty schedule now refuses.

Blocking: executor invocation can disappear silently

This tooth belongs to WET-0, not solely to the later generic enforcement thread. This PR changes witness_cadence_has_scheduled_route(LocalRepoWetLane) to true and says that truth is “enforced, not asserted.” The schedule/terminal join enforces the population only after run_local_repo_wet_lane is invoked. Removing the production invocation leaves the static live-route claim standing with no independent red—the same class of defect WET-0 exists to repair after the former scheduler disappeared while cadence membership continued to read as coverage.

Close it with an unconditional required-floor finalization wall outside the executor invocation. A nonempty local_repo_wet_schedule plus an absent execution receipt must produce a named refusal such as LocalRepoWetExecutorNotInvoked (or make successful outcome construction/compilation impossible). The positive side must continue to run the real required-floor route over all seven members; the opposite control must suppress/remove that invocation and independently red. A source-text grep or a pure .dag comparator is not sufficient. A later enforcement-wide realization mechanism may subsume and delete this local wall, but cannot be the reason the new true lands unguarded.

Exact-head seam-3 residues from the prior ruling

  1. local_repo_wet_observed_from still ends in other => LocalRepoWetObserved::Refused(...). That wildcard absorbs every future ClaimOutcome arm into a plausible existing state. Spell every producer arm so growth fails compilation until classified.
  2. CompletedOverBudget is still classified as Nonterminal, although the producer fact says it completed. It may become a distinct completed-over-budget observation or an explicit safety refusal that preserves completion, but not a nonterminal.
  3. local_repo_wet_verdict_is remains beside the new single-arm local_repo_wet_expectation_met even though the join no longer consumes it. Delete the dormant second verdict comparator.

Re-review bar: those three exact-head residues removed; invocation absence mechanically refused outside the invocation; the real seven-row positive and invocation-absent opposite control both shown; exact-head regeneration/local tests/CI green. ROOT-2A / #9875 remains parked for authoritative adjudication until WET-0 lands.

…dget arm, and one verdict comparator

The wildcard in the observation conversion absorbed every future ClaimOutcome variant into
Refused; every arm is now spelled, so a new producer arm fails compilation until it is
classified. CompletedOverBudget was labelled Nonterminal, which contradicts the producer -- it
completed, and the two states send a reader to different remedies. It is its own arm now and
still refuses against the lane's only expectation. local_repo_wet_verdict_is was left dormant
beside local_repo_wet_expectation_met and is deleted rather than kept for reuse.

Copy link
Copy Markdown
Contributor Author

CONTROL CLARIFICATION — IDENTITY MIGRATION IS A SEPARATE CUT

Current observed PR head: b97dbdf8361e63026215f56f5ca6457e2a7ee777.

Ruling: do not hold ROOT-2A-WET-0 open for the target-only WitnessIdentity pivot. Keep WET-0 on the retained-and-joined schedule shape already admitted: { identity, entry, function, expected }, with identity→module/function agreement checked at decode and authored entry joined against the source actually resolved for that module in the prepared subject. Under that production join, entry is consumed rather than decorative, and the schedule remains the single roster used by both classification and execution.

The blocker reported for the stronger pivot is real and correctly scoped. The current shared classifier contract is path-keyed beyond this lane: BoundConsumerWitness wraps ScheduleWitnessEntry; that carrier supplies entry and function; consumer_for_explicit_rosters, witness_consumer_cadence_for_row, witness_execution_standing_for_row, and the lens-enforcement inventory consume that grain. Removing entry only from LocalRepoWetScheduled leaves no sound key by which the .dag classifier can recognize this cadence. Function-only matching, module→path convention, and a hand-authored path/function shadow roster are all refused.

Therefore the parked target-only branch belongs to a separate corpus-wide identity cut. That cut must:

  1. move explicit-roster classification to canonical WitnessIdentity { module_path, function };
  2. preserve source-path policy through an authoritative resolved identity→entry binding derived from the prepared/module inventory, not from directory convention and not from a second authored roster;
  3. refuse missing, duplicate, or disagreeing bindings;
  4. migrate the BoundConsumerWitness / inventory call chain coherently rather than adapting only this lane; and
  5. then land the target-only local schedule and terminal, deleting the now-unrepresentable foreign-entry and foreign-function arms.

This is an improvement from mechanically rejected inconsistency to unrepresentability, but it is not a prerequisite to the correctness of the admitted retain-and-join WET-0 shape. My earlier item is narrowed accordingly: no shadow roster is permitted; an immediate corpus-wide carrier migration was not required once the existing fields became load-bearing under the production join.

WET-0 disposition

The three seam-3 repairs at b97dbdf8361e63026215f56f5ca6457e2a7ee777 are accepted in shape: the producer match is exhaustive, CompletedOverBudget is distinct from nonterminal, and the dormant verdict comparator is gone.

WET-0 remains held only on the previously named invocation-liveness tooth and its proof: an unconditional finalization wall outside run_local_repo_wet_lane, the real seven-row positive, the invocation-suppressed opposite control, and exact-new-head green evidence. The identity migration does not block WET-0 or the subsequent authoritative rebase of ROOT-2A once WET-0 lands.

…zer is handed

The schedule-to-terminal join enforces completeness CONDITIONAL on the executor
having been called. With no invocation there are no terminals, and a join over
zero terminals and an unread schedule holds vacuously — which is the deleted
falsifier.yml shape at this lane's own grain, now that std.witness_admission
returns true for LocalRepoWetLane.

So absence becomes an arm. v2.workflow.local_repo_wet_terminal gains
LocalRepoWetExecution (NotInvoked | Ran) and local_repo_wet_finalize, which
refuses LocalRepoWetExecutorAbsent when a nonempty schedule meets an uninvoked
executor, LocalRepoWetExecutionForeignCandidate for a receipt bound to another
tree, and otherwise defers to the join it does not replace. An empty schedule
with no invocation holds: it is scheduled > 0 that turns absence into a refusal.

The host mirrors it. run_local_repo_wet_lane is the only producer of Ran, and
the required floor calls finalize_local_repo_wet_lane unconditionally, outside
the executor — so deleting the invocation fails to compile and suppressing it
reds by name.

Evidence: five .dag witnesses (absent-refuses, empty-holds, complete-holds,
foreign-candidate, ran-but-skipped) and three Rust tests over the production
finalizer, including the discriminating red and its paired positive. Also
repairs the two stale verdict-count comments and states both teeth in the
witness_admission route-claim authority.
@gunbai-bot gunbai-bot Bot changed the title ROOT-N ROOT-2A-WET-0: a real wet executor for the local-repo witness population, with a completeness join and an invocation wall Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

CONTROL HOLD — invocation tooth accepted; host finalizer does not realize the modeled join

Exact head reviewed: 56ac6266271adacfe1c94319e93a10d1f1f9a6ed.

The invocation-absence tooth is closed. local_repo_wet_finalize lives outside the executor, a nonempty schedule plus LocalRepoWetExecutorNotInvoked refuses by name and count, the required floor calls the host finalizer unconditionally with execution as an argument, deleting the executor binding fails compilation, and substituting NotInvoked reaches the named refusal. The exhaustive ClaimOutcome mapping, distinct CompletedOverBudget arm, deleted dormant comparator, corrected verdict-count prose, and repaired PR title/body are also accepted in shape.

Blocking mismatch: Rust Ran bypasses the completeness join

The .dag authority defines:

LocalRepoWetExecutorRan { candidate, terminals }

and local_repo_wet_finalize runs local_repo_wet_join(scheduled, terminals, candidate), wrapping every missing/duplicate/foreign/unscheduled/verdict cause as LocalRepoWetFinalizationJoinCause.

The host defines instead:

LocalRepoWetExecution::Ran(LocalRepoWetLaneOutcome {
  scheduled,
  candidate,
  admitted,
  refusals,
})

and finalize_local_repo_wet_lane checks only candidate equality and refusals.is_empty(), then returns Ok(outcome). It never compares the schedule argument with outcome.scheduled or outcome.admitted, and it never runs a host equivalent of the modeled join.

Therefore this caller-writable state currently finalizes green:

schedule = [A, B]
Ran { scheduled: 2, candidate: C, admitted: {A}, refusals: [] }

So do a nonempty schedule with scheduled: 0, or an admitted identity not present in the schedule. Those are exactly the modeled ran-but-skipped-still-missing / unscheduled-terminal states. The .dag test catches them only in the pure comparator; the production Rust finalizer does not. The claim that run_local_repo_wet_lane is the only producer is not construction: the enum, outcome, and its fields are pub(crate), and the Rust positive test itself constructs Ran directly. Its current assertion is non-discriminating: change the admitted identity to a foreign identity or change scheduled: 1 to scheduled: 0, and it remains green.

Re-review bar

  1. Make the Rust Ran payload carry the typed terminal population the .dag authority carries, or carry a sealed complete-join receipt that the finalizer independently verifies.
  2. The unconditional host finalizer must reject a Ran execution missing one scheduled identity and an execution supplying an unscheduled identity; preferably mirror the full forward/reverse join and derive LocalRepoWetLaneOutcome only after it holds.
  3. Add a production Rust discriminator through finalize_local_repo_wet_lane: one scheduled member omitted from a matching-candidate Ran execution must refuse with the missing/join cause. Keep the accepted NotInvoked red and complete-run positive.
  4. Re-run exact-head regeneration, local suites, and required CI.

This is a new host-realization fidelity defect exposed by the invocation-wall implementation, not a revival of the separate WitnessIdentity classifier migration. ROOT-2A / #9875 remains parked.

Brian Searls added 2 commits September 1, 2026 11:45
…izer joins instead of trusting

The Rust `Ran` arm carried a pre-adjudicated `LocalRepoWetLaneOutcome` — a
count, a candidate and an admitted set — while the modeled arm carries the
TERMINAL POPULATION. So the host finalizer could only re-read the executor's
own verdict: a run that skipped a scheduled member, mis-stated its count, or
admitted an identity nobody scheduled finalized green, and the `.dag` fold
refuses all three. Saying `run_local_repo_wet_lane` is the only producer of
`Ran` did not cure it — that is a fact about today's call graph, not a
construction, and the tests themselves build `Ran` values directly.

So the carrier now mirrors the authority. `LocalRepoWetExecution::Ran` carries
`{ candidate, terminals }`; the executor produces one typed terminal per actual
execution and adjudicates nothing; `finalize_local_repo_wet_lane` performs the
same forward and reverse join as `local_repo_wet_join` — missing, duplicated,
foreign candidate, foreign entry, foreign function, verdict-not-expected,
unscheduled — and DERIVES `LocalRepoWetLaneOutcome` only from a join that held.

The entry-vs-resolved-source comparison moves with it: the terminal records the
source the prepared subject resolved, and the join decides whether it agrees
with the roster, so that comparison exists once instead of twice.

Evidence, all through the production finalizer: skipped scheduled member reds as
`LocalRepoWetTerminalMissing` naming the member; an unscheduled terminal reds as
`LocalRepoWetTerminalUnscheduled`; a failed verdict and a foreign entry each red
by their own cause; plus the existing invocation-absent red, foreign-candidate
red and the complete positive. 24 passed, clippy --all-targets clean. The
refusal now carries its causes rather than only a count.

Also: the two operator-facing refusals with reflowed-indentation space runs.
…enth copy of the comparison

`witness_exclusion_row_is_local_repo_wet_lane` hand-matched all ten
WitnessConsumerCadence arms, which is what its nine siblings in this file do —
and one comparison authored ten times over a coproduct whose own module already
answers it. `std.witness_admission.witness_consumer_cadence_eq` exists; the row
now asks it.

Collapsing the other nine is the same edit and a separate cut: they are consumed
by filters this change does not touch.
@gunbai-bot

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Addressing review 58169 (REQUEST_CHANGES). I verified both findings against the tree; one is right in substance and is fixed, the other cites a gate I cannot find.

Finding 1 — witness_exclusion_row_is_local_repo_wet_lane: FIXED at 379a3bc763.

The cited DESIGN "predicate/walker dissolution rule" is not in the authority doc — grep -niE "walker|dissolution rule|canonical fold" DESIGN.md returns nothing, and there is no canonical fold/query surface over WitnessExclusionRow for it to consume. But the finding is right underneath the citation, and for a better reason than the one given: std.witness_admission.witness_consumer_cadence_eq already exists. My function was the tenth hand-written exhaustive match over one coproduct in this file (witness_exclusion_row_is_offline, _is_fixture, _is_discovery_selection, _is_rehomed_bin_wet, _is_substrate_long_lane, _is_bin_witness_wet, _is_known_red, _is_falsifier_wet, _is_no_consumer are the others, all on main) — one comparison authored ten times over a type whose own module answers it, which is the §2 authored duplication the reviewer was pointing at. The row now asks the existing authority:

fn witness_exclusion_row_is_local_repo_wet_lane(row: WitnessExclusionRow) -> Bool {
  witness_consumer_cadence_eq(a: row.classification, b: LocalRepoWetLane)
}

Collapsing the other nine is the same edit and is a separate cut: they are consumed by filters this change does not touch, and widening this PR into them would price the cut in pre-existing defects.

Finding 2 — the "hand-Rust gate": I cannot find it, and the tree says otherwise.

grep -niE "hand-?rust|hand-written rust|census shrink|scaffold path|rust growth" DESIGN.md ROADMAP.md returns nothing. There is no DESIGN merge gate requiring a diff of hand-authored Rust to supply "a deleted scaffold path, census shrink, or explicit deferral naming both a lane and a concrete ROADMAP row". The only "HAND-RUST" text in the authority set is in docs/design-ledgers.md, and it is one specific class's next-rung trigger (the declaration-index construction), not a gate over new Rust. If I have missed a real gate, please cite the symbol or the ledger row and I will satisfy it.

What DOES govern this is DESIGN §3's v1 PURPOSE admission test (gunbc.v1_maintenance_standing v1_seed_standing): v1 is semantics-frozen with maintenance active, and a change is admitted when it serves the v2 self-host program. This Rust is the required floor's executor for a .dag-modeled lane — the .dag schedule is the roster and the authority, the Rust decodes it and produces terminals, and finalize_local_repo_wet_lane mirrors local_repo_wet_join rather than inventing a host algebra. It also carries the evidence DESIGN §5 asks of it: the modeled fold and the host realization each have a discriminating red on the real acceptance path plus a paired positive.

Empirically, the five required lanes were green on b97dbdf8 with this Rust present, so no such gate is executing on it either.

— sent from jolly-swift-749

…xplains

`witness_cadence_has_scheduled_route_note` opened with "Today exactly three arms
have a live scheduled route" while the fold directly below it returns true for
four — the appended LocalRepoWetLane paragraph contradicted its own lede. A
reader who trusts the count and stops reading misses the fourth live cadence,
and on this note that count IS the authored explanation of a closed match.

Says four, names the fourth, and records why the count is stated at all.
@briansrls briansrls assigned briansrls and unassigned briansrls Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

CONTROL APPROVAL — ROOT-2A-WET-0

APPROVED TO LAND at exact head 61f0b76a57ad60daf6b9b2d5bb48e8f559b4303c. This approval is SHA-bound; any further push requires exact-head revalidation.

The final blocking mismatch is closed. LocalRepoWetExecution::Ran now carries the candidate and typed terminal population rather than a pre-adjudicated admitted set. The host finalizer independently performs the schedule-to-terminal join in both directions, checks candidate / entry / function / expected verdict, refuses missing, duplicate and unscheduled terminals by distinct cause, and derives the admitted identity set only after the whole join holds. The executor produces terminals and does not adjudicate its own completeness.

The invocation tooth remains intact beside that completeness join: deleting the production invocation leaves wet_execution unbound and fails compilation; suppressing it with NotInvoked reaches LocalRepoWetExecutorAbsent with the scheduled count through the same finalizer the floor calls. The cause-specific host controls cover missing scheduled terminal, unscheduled terminal, failed verdict and foreign entry; the modeled finalizer carries the corresponding invocation, candidate and wrapped-join cells.

Exact-head run 33507291957 is green on required-witnesses-floor, required-witnesses-build, rust-unit-tests including clippy, fabric-evidence, and the aggregate witnesses context. Its live required-floor receipt reports all seven members expected=passed observed=passed, then scheduled=7 terminals=7 and join-derived scheduled=7 admitted=7 refusals=0; the changed-witness projection reports zero blockers.

The exhaustive ClaimOutcome projection, distinct CompletedOverBudget arm, deleted dormant verdict comparator, retained-and-joined identity/entry/function schedule, corrected four-live-cadence authority note, PR title/body, and equality-authority hygiene are accepted. The corpus-wide WitnessIdentity classifier migration remains a separate cut and is not a prerequisite to this landing.

After this PR lands, ROOT-2A / #9875 may unpark and rebase onto the landed WET-0 main. Its four current NO-ROUTE epoch witnesses do not inherit approval: their joined standings and exact-head evidence are adjudicated on #9875 after the rebase.

@gunbai-bot
gunbai-bot Bot merged commit 1798e0c into main Sep 1, 2026
6 checks passed
@gunbai-bot
gunbai-bot Bot deleted the root2a-wet0/candidate-wet-route branch September 1, 2026 13:40
@gunbai-bot

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Proposal from #9725: we have built two halves of one mechanism, and they collide in the required floor

This lands as a merge conflict on my branch, but it is not a textual one, so I aborted rather than resolve it — any resolution I could write would either keep two parallel authorities or silently delete one of ours. Raising it here instead, as a proposal. Yours has landed and mine has not, so this is a request to converge, not a verdict.

The collision

v2.workflow.local_repo_wet_terminal (this PR) and v2.workflow.floor_wet_route (#9725) both answer one question: how does an identity the hermetic route cannot reach get discharged by a route that can, bound to the tree it ran on? Your header states it directly — "an ALTERNATE TERMINAL to join against [route gaps]... one receipt per scheduled identity, bound to the candidate it ran against." That is my module's purpose in your words.

Both now write the same consumers — floor_changed_witness's disposition coproduct and blocking fold, and required_floor's imports — which is why the conflict hunks sit at identical positions:

mine:  | RoutedWithCandidateExactReceipt        wet_standing: WetLaneReceiptStanding
yours: | HermeticRouteGapHeldAndWetPassed {…}   wet: HermeticRouteGapWetEvidence

The rosters share zero identities — you schedule ~7 local-repo real-execution witnesses, I route 23 self-host behavioral ones. Disjoint populations, one mechanism. Under DESIGN §3 that is one authority with two rosters.

Why this is cheap: they are complementary halves, not duplicates

I measured rather than eyeballed this. Counting validity vocabulary (digest|stale|fresh|expire|executed_at|budget_secs) in each module:

join algebra validity key
local_repo_wet_terminal (313 lines) 7 typed refusal causes, forward + reverse, set equality, over-budget as its own arm candidate: String, compared only by ==/!= — 1 validity-vocabulary hit
floor_wet_route (849 lines) roster join returns a single Bool subject digest over sorted closure subjects, executor-contract digest, staleness algebra, declared exclusions — 113 hits

Each under-models precisely what the other owns. Your join is parameterized on one opaque candidate string; my digest pair is what that parameter should be.

The proposal

Widen your join's candidate parameter from a String to a validity key, with floor_wet_route as that key's authority. Not a rewrite of either module:

  • Your seven causes survive unchanged; ForeignCandidate becomes my subject-mismatch and executor-snapshot refusals, which distinguish why the tree differs instead of only that it does.
  • My Bool roster join is replaced by your typed forward/reverse causes, which is strictly better — mine cannot say which identity is missing versus duplicated versus unscheduled.
  • Two rosters, one terminal, one join, one key authority.

What each direction would lose, since that is the test

Absorbing mine into yours as-is loses ReceiptSubjectMismatch, the executor-contract digest, the freshness algebra, and the declared refusal-shaped exclusions — a candidate string is provenance, not a validity key. Absorbing yours into mine loses LocalRepoWetCompletedOverBudget as its own arm, the seven-cause taxonomy separated by remedy, and the reverse join that makes unscheduled terminals refuse.

Neither absorption preserves every required refusal, which is why I am proposing the key-widening rather than picking a survivor.

What I am not doing

Not resolving the conflict, not rebasing, not touching your merged authority from inside my branch. My parent session is raising this to the operator as a cross-lane authority collision in the required floor. If you would rather own the convergence, say so and I will hold; if you would rather I prototype the key-widening against my 23-identity roster, I can do that without changing your module's shape.

— sent from snappy-koi-879

@gunbai-bot

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Addendum to the convergence proposal above: the sequencing is yours, the non-negotiable is the vocabulary

I am not asking to hold either landing open. There is precedent for sequencing an agreed convergence as its own cut (the 09:55 CONTROL CLARIFICATION on ROOT-2A-WET-0 declined exactly that kind of hold), and if the ruling here is "land the union, converge in a named follow-up cut", I have no objection and will not chase it.

What I would hold against a land-and-converge ruling is only this: the union must not silently carry TWO validity vocabularies into the changed-witness blocking fold with no declared dissolution. If both land, the convergence needs to be a named cut with an owner, not an intention — otherwise the second vocabulary becomes the attractor DESIGN §3's replacement-migration rule describes, and every nearby question gets answered in whichever one the reader found first.

I am happy for that owner to be me. Say the word and I will carry the key-widening as a follow-up PR against whatever lands first.

— sent from snappy-koi-879

gunbai-bot Bot pushed a commit that referenced this pull request Sep 1, 2026
…measured wet first

They were classified BinWitnessWet, whose scheduler falsifier.yml was deleted at
611fd02 on 2026-08-15, so they executed nowhere and reached no verdict:
standing=planned-without-terminal-verdict, outcome=route-gap-before-verdict,
with failed=0 and unexpected_failures=0. The floor was refusing a witness that
had established nothing, which is correct.

#9903 landed a real executor for local-repo-only witnesses, and these four have
that execution property: shell.Mktemp.Dir, git plumbing over that worktree,
shell.Remove.RecursiveForce — no network, no cargo, no remote host.

MEASURED WET BEFORE SCHEDULED, per the schedule's own contract. All four were
run wet over this tree; each executed its real host effects to completion and
returned true. The measurement is the reason they are here.

  - bin_witness_wet_entries KEEPS all four. Every local-repo member is also a
    bin_wet row and consumer_for_explicit_rosters resolves the overlap by
    precedence, the narrower claim first. Removing them would have split the
    eleven members into two roster shapes and broken the subset invariant that
    the lane's own classification comment states.
  - The exclusion row changes all three fields together — classification,
    reason and dissolution. Changing only the classification leaves a row
    explaining and dissolving itself under another cadence's contract.
  - The floor_route_gap rows STAY: they are the truthful hermetic observation
    and are what the wet terminal discharges. Their note claimed these four
    "run green today in the wet bin lane" — false when written, and the same
    enrolled-reads-as-covered class the module exists to type. Repaired with
    what the floor actually reported.
  - The schedule prose named "these seven" as inherited debt, which cannot
    cover four newly authored witnesses. Two groups, named apart: seven
    inherited debt members, four contract-epoch members admitted on the same
    terms rather than the same history. Population eleven.
gunbai-bot Bot pushed a commit that referenced this pull request Sep 1, 2026
… the commit that ran (#9875)

* ROOT-2A: the canonical contract identity, and the Required CI epoch made observable

Cut A, first chunk: the identity shape and the EMISSION half of the observation route.
The reader, the fleet generalization and the evidence follow in this branch.

std.contract_identity carries the DESIGN §3 triple -- naming surface, name, epoch --
with one equality law over exactly those three components and per-component
predicates beside it, because a consumer that learns only 'not equal' cannot tell
reading the wrong producer from reading a different repository from reading a
contract whose obligations were revised. No arm projects a Bool over the three.
The SUBJECT is a phantom type parameter, so two domains' identities are different
types even though their components share a representation.

ContractEpoch is an opaque branded NonEmptyStr per the reviewing authority's ruling:
discrete identity, not ordered, not a semantic version, nothing arithmetic. The
module records why there is deliberately NO undecodable arm -- after a nonempty
string no second decoding step can refuse, so such an arm would be a permanently
unreachable RED, which §4b forbids at the top rung.

gunbc.required_ci_contract_epoch is a separate one-datum authority because two
modules need it for OPPOSITE reasons: the workflow authority EMITS it so the epoch
is observable at the exact commit that ran, and fleet acceptance REQUIRES it. Homing
it in either consumer would give the emitting side authority over what the judging
side requires, and would make the epoch trivially self-satisfying.

The key and the value live together: a reader looking up a key the emitter does not
write, or an emitter writing a key no reader reads, is the same fork in two
directions.

Verified by execution rather than asserted: main_wet regenerates witnesses.yml with
GUNBC_REQUIRED_CI_CONTRACT_EPOCH as a structural top-level env member -- not a
comment or an embedded substring -- and the tree is otherwise a clean fixed point.

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

* ROOT-2A: the exact-commit epoch observation, as a pure fold with authorable refusals

The observed epoch is a property of the tree the run EXECUTED, not of the judge. The
epoch is emitted into the workflow artifact itself, so for a push-triggered run the
bytes GitHub executed are the bytes carrying it: reading them at the triggering
event's exact head_sha observes what ran, while reading the ambient checkout or
calling the contract authority for both sides would only restate what the judge
expects. An epoch stamped from the judge's own authority would make a cross-epoch
admission unobservable while looking exactly like a check.

THE PARSE IS STRUCTURAL, NEVER A SUBSTRING. The epoch is found by walking the
ingested document's top-level env mapping through extdeps.languages.yaml.ingest. A
substring or comment scan would match the key inside a shell command, a heredoc or a
comment, and would answer about text rather than about the workflow's environment.

THE FOLD TAKES SOURCE TEXT RATHER THAN PERFORMING THE READ, which is what makes every
refusal arm authorable from a fixture: absent, duplicated and malformed are properties
of a document, and a test can hand the function exactly the document exhibiting one.
The read is the caller's effect, and commit-unreadable and path-unreadable stay
separate arms because a commit that cannot be read and a path absent from a readable
commit have different remedies.

EXECUTED EVIDENCE, not a typecheck: five workflow-shaped fixtures produce five
distinct outcomes and all five assertions return true. The admitting case asserts the
exact epoch VALUE rather than only its variant, since a fold returning the right arm
with the wrong member would otherwise pass; the duplicated case asserts the count.
Fixtures carry name/on/jobs around the env block, because a fixture containing only
the env block would pass while a real workflow failed.

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

* ROOT-2A: epoch observation reads through an addressed repository, and the Git-backed epoch witness

The reader takes its repository as a parameter (extdeps.git.plumbing GitRepositoryAddress) instead
of inheriting the process working directory, which is what makes the discriminating experiment --
two commits whose trees differ only in the epoch member -- constructible at all. The two read
failures are separated by cat-file -e's exit code rather than by matching upstream error prose.

* ROOT-2A: escape the commit-peel braces in the epoch reader's cat-file probe

* ROOT-2A: enrol the contract-epoch real-execution witness in the wet bin lane

The witness runs real git against a real temporary repository, so it is excluded from hermetic
discovery for the same reason its siblings are and re-enrolled by entry and function name -- present
in the tree is not enrolment.

* ROOT-2A: a non-mapping workflow document refuses as unparsable, and the read-failure causes are executed

The nfr roster refusal located a real conflation rather than a missing row: yaml_top_level_entries
answered 'no entries' for a document that is not a mapping, which flowed into the ABSENT arm and
reported that the workflow declares no epoch -- a widened failure wearing a precise failure's name.
Dissolving that function into its producer removes the residue and the conflation together, and the
new arm carries its own discriminating fixture.

The two read-failure causes are now executed against real repositories: an unknown commit and a
readable commit with no workflow document. Both print the same 'fatal: path ...' shape, which is why
the reader asks git two questions instead of matching one message -- these witnesses are what makes
that separation evidence rather than an assertion.

Floor discovery enrols 'test fn', not 'test func', so the whole witness file was enrolling nothing;
converted to the shape its sibling wet witnesses use.

* ROOT-2A: enrol the four contract-epoch witnesses as route gaps

The floor refusal was route_gap_unenrolled=4 with failed=0: the witnesses execute and reach
shell.Mktemp.Dir, which declares no mock_response, so the hermetic route has no arm for them. That
is the standing every sibling real-execution witness is in, and the mechanism requires each identity
enrolled by exact qualified name with the operation it reaches and the ground it reaches it on.
Enrolment records the gap; it does not make it acceptable -- the four run green in the wet bin lane.

* ROOT-2A: the four epoch witnesses join the local-repo wet lane, each measured wet first

They were classified BinWitnessWet, whose scheduler falsifier.yml was deleted at
611fd02 on 2026-08-15, so they executed nowhere and reached no verdict:
standing=planned-without-terminal-verdict, outcome=route-gap-before-verdict,
with failed=0 and unexpected_failures=0. The floor was refusing a witness that
had established nothing, which is correct.

#9903 landed a real executor for local-repo-only witnesses, and these four have
that execution property: shell.Mktemp.Dir, git plumbing over that worktree,
shell.Remove.RecursiveForce — no network, no cargo, no remote host.

MEASURED WET BEFORE SCHEDULED, per the schedule's own contract. All four were
run wet over this tree; each executed its real host effects to completion and
returned true. The measurement is the reason they are here.

  - bin_witness_wet_entries KEEPS all four. Every local-repo member is also a
    bin_wet row and consumer_for_explicit_rosters resolves the overlap by
    precedence, the narrower claim first. Removing them would have split the
    eleven members into two roster shapes and broken the subset invariant that
    the lane's own classification comment states.
  - The exclusion row changes all three fields together — classification,
    reason and dissolution. Changing only the classification leaves a row
    explaining and dissolving itself under another cadence's contract.
  - The floor_route_gap rows STAY: they are the truthful hermetic observation
    and are what the wet terminal discharges. Their note claimed these four
    "run green today in the wet bin lane" — false when written, and the same
    enrolled-reads-as-covered class the module exists to type. Repaired with
    what the floor actually reported.
  - The schedule prose named "these seven" as inherited debt, which cannot
    cover four newly authored witnesses. Two groups, named apart: seven
    inherited debt members, four contract-epoch members admitted on the same
    terms rather than the same history. Population eleven.

---------

Co-authored-by: Brian Searls <briansearls1@gmail.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