Skip to content

Derive the self converge-receipt verdict from the knob⊓revision meet (close the empty-observation narrow) - #8101

Merged
briansrls merged 2 commits into
mainfrom
converge/receipt-joint-verdict
Aug 10, 2026
Merged

briansrls merged 2 commits into
mainfrom
converge/receipt-joint-verdict

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Focused §5 fix. Found while gathering CONVERGE-0 acceptance evidence: the srv1 gunbc-roadmap-fleet-converge.service persisted a receipt whose first line said verdict=converged for a run that never observed the desired revision, and the collector reads that first line — so the fold would have inherited a false converged. That is a fabricated 1/1 waiting to happen, and it is exactly DESIGNs empty-observation narrow (applied=0/drifted=0 is 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_gate demoted the converged bool (honest exit=1) but left out.line still saying verdict=converged. That is validation conceding the contradictory state is writable.

Construction, not validation

  • converge_verdict_meet (in host_converge) — 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 verdict=converged on line 1 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

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: 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.

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 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 collectors own parse_converge_receipt_line and asserts it reads not-converged. Reproduces the exact srv1 case. PASS locally.

Out of scope

Does not touch the srv1 environment defect (a User=root unit over a briansrls-owned tree with no credential for the private HTTPS remote → cannot observe refs/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 → Absent conflates a sentinel. converge_verdict already mints Absent for "subject not present on the host", so the isolated verdict=absent token 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 on parse_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 the converge-revision evidence line beside line 1, so verdict=absent + unobserved cause= is the revision case and verdict=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 distinct Unobserved verdict) with the same reasoning as item 2 — it widens the shared ConvergeVerdict enum knob-only convergence uses and would never emit. Declared in-code at converge_cli_revision_verdict.

2. VerdictConverged does 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 converged that 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.)

Brian Searls and others added 2 commits August 10, 2026 13:33
…(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
@gunbai-bot

gunbai-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown
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 3f8b0d6ea). Freshness proven by the cast-identity control flipping FAIL→PASS on the rebuild, and witness_self_receipt_verdict_is_joint_never_false_converged PASSes on that fresh binary. — sent from wise-moth-430

@briansrls
briansrls merged commit fbba81e into main Aug 10, 2026
4 of 5 checks passed
@briansrls
briansrls deleted the converge/receipt-joint-verdict branch August 10, 2026 15:45
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>
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