Repository navigation
Derive the self converge-receipt verdict from the knob⊓revision meet (close the empty-observation narrow) - #8101
Merged
Merged
Conversation
…(close the empty-observation narrow) The srv1 fleet-converge unit persisted a receipt that led with `verdict=converged` for a run that never observed the desired revision (git ls-remote refused → RevisionUnobserved), and the collector reads that first line, so the fold would have inherited a false "converged". DESIGN's empty-observation narrow, verbatim: applied=0/drifted=0 is bottom-as- IGNORANCE (nothing was comparable), rendered as bottom-as-ANSWER. The root was a dual representation, not a missing gate: the receipt carried two verdicts (the knob verdict on line 1, the revision verdict on line 2) that could disagree while the collector read only one. The former `converge_cli_revision_gate` demoted the `converged` bool (honest exit=1) but left `out.line` saying `verdict=converged` — validation that conceded the contradictory state was writable. Construction, not validation: - `converge_verdict_meet` (host_converge) is the ConvergeVerdict lattice meet, Absent < Drifted < VerdictConverged. - `converge_cli_receipt_join_revision` meets the knob verdict with the revision standing BEFORE the receipt line is rendered, so line 1's `verdict=converged` is only constructable when the revision was observed and matched. RevisionUnobserved pulls the joint to Absent. - `converge_cli_revision_line` now carries the revision OBSERVATION as evidence (`observed=`/`drift`/`unobserved`), never a second `verdict=` token — the receipt holds exactly one verdict, so the two cells cannot disagree. - The bool-only `converge_cli_revision_gate` is DELETED (§5 dissolution on climb — the construction replaces the validator). - The stale comment claiming the collector "sees BOTH cells" is corrected (it reads only line 1); cited by symbol per §3. Rung, honestly: this is structurally guaranteed at the receipt-render seam — the self output derives line 1's verdict from the revision standing via a total meet at the one publish site, and no production path renders converged without RevisionConverged. It is NOT yet structurally impossible at the type level: ConvergeVerdict.VerdictConverged does not carry its grounding revision, so a hand-authored receipt could still be converged-without- observation. Making `VerdictConverged` carry the observed GitObjectId is the named next-rung trigger; not done here to avoid widening the shared verdict enum used by knob-only convergence. Witness (green by execution locally, PASS): a knob-converged receipt met with RevisionUnobserved renders `verdict=absent` not converged, met with RevisionDrifted renders not-converged, met with RevisionConverged passes the knob verdict through — and the end-to-end clause feeds the joint unobserved line through the collector's own parse_converge_receipt_line and asserts it reads not-converged. Reproduces the exact srv1 case. Does not touch the srv1 environment defect (root over a briansrls-owned tree with no credential for the private HTTPS remote — provisioning, separate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168R31q15ZsPJyik18Roz3y
…g item Review finding (2026-08-10): the meet maps RevisionUnobserved to Absent, but converge_verdict already mints Absent for "subject not present on the host" — so the isolated verdict=absent token conflates two states with opposite remedies (converge the subject vs fix the b1 git-credential defect). This is the sentinel review 50817 warned about, one level down. It is fail-closed not fail-open (both fold not-converged), and the receipt as a whole disambiguates via the converge-revision evidence line the self path always emits beside it (unobserved cause= vs observed=<hex>). Only the isolated line-1 token is ambiguous, and only to a human ignoring line 2. Declared in-code as a next-rung item rather than fixed: a distinct Unobserved verdict is deferred with the same reasoning as the GitObjectId-in-VerdictConverged trigger — it widens the shared ConvergeVerdict enum that knob-only convergence uses and would never emit. That the enum cannot carry it without burdening an unrelated consumer is the dual-representation smell one rung up, named not fixed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168R31q15ZsPJyik18Roz3y
Contributor
Author
|
Re-validation note: the local green-by-execution was re-confirmed on a freshly built claim_batch (2026-08-10 13:46; the prior prebuilt binary predated the identity-cast feature |
briansrls
added a commit
that referenced
this pull request
Aug 10, 2026
…wner; refuse a converged receipt not backed by an observed revision (#8108) The fleet-converge unit ran `gunbc converge --host self` as User=root inside WorkingDirectory=/opt/gunbc/gunbc, a service-user-owned tree whose private-remote credential exists only under briansrls on srv1. Root git therefore hit dubious-ownership and had no credential, so `git ls-remote origin refs/fleet/desired` never observed the desired ref and every cadence rendered verdict=absent. This is why a merged main revision never became observed fleet state (CONVERGE-0 leg b1). Coupled fix in emit.dag: - emit_fleet_converge_service_unit_doc: User=root -> User=/Group=svc.service_user, matching the sibling serve/belt/tree-sync units (which run as briansrls precisely because the credential lives there). - the FleetConvergeTimerUnit receipts-dir step: install -d now owns the dir for the service user, so the briansrls converge run can WRITE its receipt (a root-owned 0755 dir would have failed EACCES after the observation succeeded). Third fact (the closing fold must not be fabricatable): the collector read only line 1, so a stale receipt a pre-#8101 binary persisted (line1 verdict=converged beside line2 converge-revision verdict=unobserved) would still fold as converged. classify_receipt_stdout now admits a converged verdict to the fold ONLY when the same receipt carries an observed revision cell (converge-revision observed=<hex>); otherwise ReceiptConvergedUnbacked, refused from the fold. Structural presence test, not a freshness heuristic. The fleet_converge_timer.dag fork still renders User=root + no WorkingDirectory (srv2-srv4 route); its disposition note is upgraded to record this is now a b1 correctness fork, not only byte-drift — dissolving it to the single emit authority is a b1 requirement for those hosts. Witnesses (green on fresh authoritative claim_batch built from this branch): - fleet_converge_unit_runs_as_credentialed_owner_never_root (RED if User=root returns or the receipts owner is dropped) - witness_converged_receipt_refused_unless_backed_by_observed_revision (RED if the collector reverts to line-1-only) Claude-Session: https://claude.ai/code/session_0168R31q15ZsPJyik18Roz3y Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
briansrls
pushed a commit
that referenced
this pull request
Aug 11, 2026
…eet, partition-closure census, doc/roadmap authorities (#8122) * Total the converge-verdict meet, the last unrostered non-fold residue `non_fold_residue_no_unrostered_or_stale` has been red on main since the 15:45 run on 2026-08-10 — it is the failure on ca64dba (the #8055 revert) and on every main run since. It was the second of two independent causes of main being red; #8120 closed the first. Located by execution, not by reading the scanner. A probe over the same host census the witness consumes named exactly one site: live=167 roster=166 unrostered (1): dag/gunbc/host_converge.dag::converge_verdict_meet stale (0) Introduced by #8101 (fbba81e), which merged at 15:45 that day — the run in which the witness first went red. The site: Drifted => match b { Absent => Absent _ => Drifted } a wildcard over the closed coproduct ConvergeVerdict = VerdictConverged | Drifted | Absent. FIXED BY TOTALLING THE FOLD, NOT BY A ROSTER ROW. The roster is detection-only debt for residues that cannot yet be totalled; this one can, and the three arms are semantically identical to the wildcard (Absent -> Absent, Drifted -> Drifted, VerdictConverged -> Drifted, all correct for a meet). A row here would be counted debt standing where construction was available. Same probe after the change: live=166 roster=166, unrostered 0, stale 0. The witness that comes with it is not padding. The existing witness_self_receipt_verdict_is_joint_never_false_converged drives only the a=VerdictConverged row, so the a=Drifted row — the one carrying the wildcard, the one this change edits — had NO executing consumer at all. Enumerating a wildcard preserves behaviour only if something asserts what the behaviour is, so the full 3x3 meet table lands with it. Green by execution (claim_batch, the entrypoint CI uses): PASS witness_converge_verdict_meet_is_total_over_every_pair PASS witness_self_receipt_verdict_is_joint_never_false_converged NOT CLAIMED: that main now goes green. Two causes are closed; the floor short-circuits at its first structural failure, so anything it never reached remains unobserved. Main's last green ci run was 888321e. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XxLzhuMisV3GAPpP2mBtSG * Heal the nine floor failures behind the first stop, and decompose the witnesses that could not execute #8122 removed the floor's first structural stop, and the run then reached work it had not observed since 2026-08-10 03:12: 10 of 8610 discovery witnesses failed where the previous run reported 1 of 8562. None of the ten was caused by that change. They are three clusters of pre-existing breakage, all merged during the window when CI could not reach them. Nine are fixed here, each verified by execution. The tenth is named at the bottom and is NOT fixed. CLUSTER 1 — DOC REACHABILITY (3 rows). Two docs landed bound to nothing: docs/plans/four-lane-closeout-and-root.md (#8116) and docs/plans/v2-frontend-std-ingestion-frontier.md (#8114). #8116's own message records it FIXING an orphan while introducing one. Both now carry a HandAuthoredDocBind anchored to the work they document — the three v2 frontend stages the frontier receipt measures, and the CI-gate program note plus concurrency-key expression the closeout reports on. PASS doc_graph_is_clean, PASS doc_graph_has_no_orphan_docs CLUSTER 2 — ROADMAP (2 rows). witness_five_minute_ci_gate_program_chain_is_explicit asserted an edge five-minute-ci-gate -> native-selected-witness-bundle that the 2026-08-10 CI2-0 STOP DELETED ("THE NATIVE ROW NO LONGER PRECEDES THIS GATE"). #8116 implemented the ruling in the authority and left the witness asserting the removed edge, so the witness read main as broken while main followed the ruling. The clause is now a negative on both directions, and the 2026-08-07 note that required the old edge records its own supersession rather than silently contradicting it. PASS witness_five_minute_ci_gate_program_chain_is_explicit witness_ticket_brief_budget_holds_and_reds: two briefs exceeded the 100-word page budget — native-selected-witness-bundle at 177 and v2-lens-suite-execution at 203, both authored by #8116 under a budget witness that had never executed. Trimmed to 97 and 99 words with EVERY authored fact preserved verbatim in two cited notes in the same module, the pattern this file already uses; the brief names the symbol instead of restating it. Nothing operator-authored is deleted. PASS witness_ticket_brief_budget_holds_and_reds CLUSTER 3 — stage0_partition_closure (5 rows), from #8111, which merged on a CANCELLED check so its own new witnesses never executed once. THE BUDGET NUMBER WAS NOT THE COST. CI reported "5040ms > 5000ms", which is where the budget INTERRUPTS; measured here, one live verdict costs about 175 SECONDS of thread CPU. Five rows that each re-derived the whole corpus presented as five witnesses slightly over budget. Decomposed per the 2026-08-04 witness-cost ruling: small discriminating mechanism fixtures, plus the irreducible census as its own row. partition_closure_verdict_over is extracted as the pure core over supplied facts; the three REDs and the extent row run on a fixture of one crate, one importer and two targets. Importer and targets are REAL emitted modules (std_measure genuinely references std_types and std_nat), so the confirmation step still reads a real file and cannot be satisfied by invented content. RED 2 is now strictly more discriminating: it uses a roster where only std_nat is missing, so it cannot pass by restating RED 1, and it pins importer and owning package rather than asserting non-emptiness. 4ms / 1ms / 1ms / 1ms, all PASS — against a ~175,000ms baseline. TWO COST-SHAPE DEFECTS ARE ALSO FIXED, AND THEY DID NOT DELIVER THE WIN. emitted_reference_confirms re-read the importer's emitted .rs once per (importer, target) pair though the read depends only on the importer, and the verdict computed module_declaration_facts_live twice and dependency_resolution_facts_live twice. Both are genuine redundant work and both are removed. MEASURED: 177.6s after, against a 173.6s baseline — no improvement, within noise. The live producers dominate and are already host-memoized. Stated because the hypothesis was mine and it was wrong. NOT FIXED, AND IT WILL STILL RED CI: w_live_corpus_partition_closure_holds keeps the live roster as its subject at ~175s. It cannot be decomposed — its subject IS the corpus — and narrowing pool_roots would under-approximate the edges, the exact fail-open this module's own authority note warns against. It needs a cadence home with a declared ceiling, which is the operator-owned half of the 2026-08-04 ruling. The module comment claiming the REDs run against live facts is corrected in the same change; leaving it would describe a property the tree stopped having. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XxLzhuMisV3GAPpP2mBtSG * chore: regenerate drifted generated artifacts (ci auto-heal) * Say plainly that the read-hoist delivered no measured win The first review of #8122 described the hoist as "a real measured cost fix". It is not, and the note invited that reading by citing the 173.6s BASELINE without ever stating the after figure. Measured after: 177.6s — no improvement, inside noise. The live fact producers dominate and are host-memoized, so the redundancy removed was never the dominant term. The hoist stays because re-reading one file per outbound edge is redundant work regardless of realized n, not because it bought anything here. What made the mechanism witnesses affordable was moving them off the live verdict. Comment-only; no behavior change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XxLzhuMisV3GAPpP2mBtSG * Split the partition-closure census onto the long lane, keeping its fixtures per-PR The last of the ten floor failures. w_live_corpus_partition_closure_holds costs ~175s of thread CPU against the 5s fast-lane budget, so it redded CI on its own after the other nine were fixed. IT CANNOT BE DECOMPOSED. Its subject IS the corpus: partition_closure_verdict derives dependency_resolution_facts_live and module_declaration_facts_live over pool roots dag and src/v2. Narrowing those roots would UNDER-APPROXIMATE the edges, which is the fail-open this module's own partition_closure_authority_note exists to refuse. So it goes to the falsifier substrate long lane, per the 2026-08-04 ruling's second remedy: a lane that declares its own ceiling and names the row as an executing consumer. WHY A NEW FILE. The long lane admits by FILE-GRAIN path pattern, so enrolling the census where it sat would have dragged the four 1-4ms fixtures off per-PR discovery with it — deleting the coverage the decomposition in the previous commit exists to create. Splitting is what lifecycle_survivor_corpus_census_test already does for the same reason, and it needs no new machinery. Each file points at the other, because the halves are a pair: a mechanism that never refuses fails the REDs, one that always refuses fails the census. A WRONG ROUTE WAS TRIED FIRST AND IS RECORDED because the reason generalises. An exact function-grain ExplicitWitnessAdmission would avoid the file-grain problem, and it is the direction that carrier's own separation note names as its migration frontier. But every roster every_admission_is_projected_into_exactly_one_cadence_roster counts is a KNOWN-RED probe roster, and this witness is expected to PASS, so a green long-lane row has no projection there — two walls said so by execution before anything was pushed. Wiring one is that carrier's work, not this repair's. Reverting it also restored every_declared_long_budget_row_is_also_expected_red_today, correctly: with no green long-budget row in the tree, that claim stands and must not be retired. THE MARGIN IS THIN AND IS ON BOTH ROWS, not hidden: 177.6s sits about 1.3 percent under the lane's declared 180000ms ceiling, measured on a contended session container, so a slower runner may exceed it. That surfaces as a counted cadence failure rather than a blocked merge, which is why enrolling still beats the fast lane. Both dissolution conditions name cost reduction first and self-host second, and both record that the two cost-shape defects already removed here measurably did NOT move the total (177.6s against a 173.6s baseline) — so the next attempt starts at the live producers rather than repeating that. Green by execution: 6 fixture rows PASS, the 3 admission walls PASS, witness_exclusion_frontier_reconciliation_holds PASS with its unbacked-row RED control PASS. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XxLzhuMisV3GAPpP2mBtSG --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Focused §5 fix. Found while gathering CONVERGE-0 acceptance evidence: the srv1
gunbc-roadmap-fleet-converge.servicepersisted a receipt whose first line saidverdict=convergedfor a run that never observed the desired revision, and the collector reads that first line — so the fold would have inherited a falseconverged. That is a fabricated 1/1 waiting to happen, and it is exactly DESIGNs empty-observation narrow (applied=0/drifted=0is bottom-as-ignorance — nothing was comparable — rendered as bottom-as-answer).Root: a dual representation, not a missing gate
The receipt carried two verdicts — the knob verdict on line 1, the revision verdict on line 2 — that could disagree while the collector read only one. The former
converge_cli_revision_gatedemoted theconvergedbool (honest exit=1) but leftout.linestill sayingverdict=converged. That is validation conceding the contradictory state is writable.Construction, not validation
converge_verdict_meet(inhost_converge) — theConvergeVerdictlattice meet,Absent < Drifted < VerdictConverged.converge_cli_receipt_join_revisionmeets the knob verdict with the revision standing before the receipt line is rendered, soverdict=convergedon line 1 is only constructable when the revision was observed and matched;RevisionUnobservedpulls the joint toAbsent.converge_cli_revision_linenow carries the revision observation as evidence (observed=/drift/unobserved), never a secondverdict=token — the receipt holds exactly one verdict, so the two cells cannot disagree.converge_cli_revision_gateis deleted (§5 dissolution-on-climb — the construction replaces the validator).Rung, honestly
Structurally guaranteed at the receipt-render seam — the self output derives line 1s verdict from the revision standing via a total meet at the one publish site; no production path renders converged without
RevisionConverged. It is not yet structurally impossible at the type level:VerdictConvergeddoes not carry its grounding revision, so a hand-authored receipt could still be converged-without-observation. MakingVerdictConvergedcarry the observedGitObjectIdis the named next-rung trigger; not done here to avoid widening the shared verdict enum used by knob-only convergence.Evidence (green by execution, local — CI is currently floor-budget-red for timing reasons)
witness_self_receipt_verdict_is_joint_never_false_converged: a knob-converged receipt met withRevisionUnobservedrendersverdict=absent(not converged), met withRevisionDriftedrenders not-converged, met withRevisionConvergedpasses the knob verdict through — and the end-to-end clause feeds the joint unobserved line through the collectors ownparse_converge_receipt_lineand asserts it reads not-converged. Reproduces the exact srv1 case. PASS locally.Out of scope
Does not touch the srv1 environment defect (a
User=rootunit over abriansrls-owned tree with no credential for the private HTTPS remote → cannot observerefs/fleet/desired) — that is provisioning, its own program, being routed separately. This PR makes the receipt honest about that failure rather than papering over it.Declared next-rung items (review 2026-08-10)
1.
RevisionUnobserved → Absentconflates a sentinel.converge_verdictalready mintsAbsentfor "subject not present on the host", so the isolatedverdict=absenttoken now carries two states with opposite remedies — install/converge the subject vs fix the desired-ref observation (the srv1 b1 git-credential defect). This is the sentinel review 50817 warned about onparse_converge_verdict_label, one level down (value, not decode). It is fail-closed, not fail-open (both fold not-converged, so acceptance correctly fails and the collector is never misled), and the receipt as a whole disambiguates: the self path always emits theconverge-revisionevidence line beside line 1, soverdict=absent+unobserved cause=is the revision case andverdict=absent+observed=<hex>is the subject-absent case. Only the isolated line-1 token is ambiguous, and only to a human remedy-router who ignores line 2. Deferred (a distinctUnobservedverdict) with the same reasoning as item 2 — it widens the sharedConvergeVerdictenum knob-only convergence uses and would never emit. Declared in-code atconverge_cli_revision_verdict.2.
VerdictConvergeddoes not carry its grounding revision — the top rung (structurally impossible, not just guaranteed at the render seam). Deferred to avoid widening the shared verdict enum. That the enum cannot carry a revision because another consumer (knob-only convergence) has none is itself the dual-representation smell one level up — the enum doing two jobs — named here so it does not silently keep the top rung out of reach.The srv1 coupling, stated sharply
The fix cannot take effect on srv1 until the b1 provisioning defect is repaired. srv1''s unit fails every cadence (root dubious-ownership + no credential), so it cannot re-run the corrected binary at all. Until b1 is fixed, srv1''s persisted receipt will keep asserting a stale
convergedthat this corrected code would never produce — so a green srv1 cell read from that file is not evidence of convergence. (b1 is routed separately; not this PR.)