Skip to content

Consume the G2 live-read manifest at every selection decision; land group_by and execute the interpreter-parity controls CI only compiled - #8055

Merged
briansrls merged 37 commits into
mainfrom
session/valiant-ant-57
Aug 10, 2026
Merged

briansrls merged 37 commits into
mainfrom
session/valiant-ant-57

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Finishes what #7997 started. That PR landed the G2 machinery — typed classification, canonical dependency-edge production, declaration-grain root binding, opaque per-index subject, subject-mismatch and absent-row refusals, the touched_by fold, one memoized manifest per index — and left the old answer in charge of every production decision.

The branch then went somewhere the original plan did not anticipate. The manifest producer's own fact source was refuted by measurement, so the middle of this PR is a cutover to an index-bound producer, and the tail is the cost and execution work that cutover exposed. This description is written against the final state; where an earlier revision of it claimed something this branch later abandoned, the abandonment is stated rather than quietly dropped.

What was still broken

Every selection decision called runtime_data_dependency_touched_via_carrier_closure, whose only use of touched_paths was is_empty(). It answered from broad carrier-home reachability with no path intersection at all. An entry with a wide import closure was unskippable on any nonempty .dag diff — the 2 selected / 1 expected incident, and the SelectionSuperset ... selection did not narrow the floor line the floor reports.

The manifest existed, was memoized, and had unit tests. It had no consumer on the decision path.

One context, one decision

LiveReadSelectionContext answers axis (iv) through runtime_dependency_touched_for_entry. An entry is untouched on this axis only when every enrolled declaration is present, classified, and untouched.

Producer failure, subject mismatch, absent row, absent entry, and an entry carrying no enrolled declaration all refuse. None falls back to the retired predicate, silently widens to a whole-corpus run, or licenses a skip. A missing classification is not a missing runtime read.

Five consumers, not four

Beside the four named decisions, discovery_rows_runtime_dependency_touched_count fed the precompute's touched_runtime_dependency_entry_count through a second carrier fold. That is how a retired predicate survives its own replacement — the precompute keeps a copy. It now counts by calling the same function the per-row path decides with, so precompute parity is construction rather than two implementations a later edit can separate.

The union producer was measured, and it did not work

An earlier revision of this PR shipped the complete-roster/per-worker shape and priced it as "one whole-tree G2 evaluation per worker, in family with the floor-runner resolve each worker already pays." That is retracted. fn_arrow_decl_facts_live reflects the evaluating context's modules, so covering the consuming index meant resolving a union of every request root's closure. Measured, for a single classification request: 179,125 ms wall, 178,751 ms CPU, 9.4 GiB.

The cost was not the disqualifying part. Composing independently valid modules into one program broke bare-name binding — Holds, Finding, Exact, Refuse, Blocking, FailClosed all went unresolved — so one shared refusal denied classification to all 133 enrolled entries. That is not a repairable module; it is a property of composing independently valid programs, so no corpus is small enough to make it safe or large enough to avoid it.

fn_arrow_decl_facts_index_live reads the exact MultiEntryIndex already bound to the classification operation. It is a transport, not a second semantic authority: it reuses the same parsed-declaration projection the corpus walk uses, reads no files, performs no resolve, computes no closure, decides no classification. It lands as a Scaffold with a SingleAuthority dissolution trigger (v2.std.fn_index), because a host marshal standing in for a modeled fold is exactly the seam that must carry its own retirement.

Two consequences worth naming. Completeness is per bound request, not global — a module that fails to extract refuses only the requests whose closure contains it, since reading the subject as "the whole index parsed" would recreate the union's one-bad-module-denies-everything failure in another form. And the host joins fact population to index population at identity grain in both directions before minting a subject; a count comparison cannot stand in, because a population missing one identity and carrying one extra has the same size and is a different tree.

Naming residue, stated: the variant is still spelled WholeTreeFactSubject while it now means complete-for-this-bound-request. CompleteBoundRequestFactSubject is the honest name; the rename is deferred for fan-out.

Retired

runtime_data_dependency_touched_via_carrier_closure, LIVE_READ_CARRIER_HOME_MODULES_V0, import_closure_module_reaches_carrier_home, and the const's drift gate — zero remaining consumers. The stale comment citing the removed merge_g1_and_g2_carriers goes with them, plus two further stale citations of the deleted const: v2.lens.enforcement.gate_test prose, and the design doc's status line describing the G1 interim as the production axis.

lying_substrate_inputs_only_stamp_census was a real semantic consumer, so rather than delete a merge gate it is re-expressed on the authority that now decides: a SubstrateInputsOnly-stamped declaration classifying RuntimeRead — or refusing — is a lying stamp. Its subject grain changed from "every *_test.dag with a parseable disposition" to "the enrolled floor roster", and that difference is stated in the test rather than left to be found.

Cost shapes this change activated

The quadratic accumulator. live_read_selection_manifest_over accumulated with list_snoc_item = list_append(acc, single), and list_append is a fold_list_right rebuilding its whole left list — appending one row copied every row already accumulated. The realized n stopped being small the moment the producer gained a production consumer folding over the corpus roster, so it is fixed here per the bare-minimum-cost rule: cons + list_reverse, O(1) per row, order preserved by construction.

group_by as a real primitive. The per-callee scan was re-cut around a grouped index rather than repeated linear filters. group_by lands parallel to the existing index_by across every surface — primitive_identity, the primitives.dag contract, interpreter roster, Rust rt_function_registry, algebra/collection/fn_index — with witnesses that fail red if the delegate collapses to last-wins (three_equal_keys_form_one_three_item_bucket) or loses order (bucket_preserves_input_order).

Streaming the hash. value_hash materialized strings and lists to hash them. The A/B receipt, both arms unprofiled, same subject, same binary shape:

arm classification_production live-read wall cpu RSS
control 218,738 ms 313,266 ms 312,468 ms 7,195 MiB
candidate 182,038 ms 274,597 ms 273,853 ms 7,183 MiB

36.7 s (16.8%) cheaper on the classified term, 38.7 s on the whole read, identical resident cost, identical rows. The fixed prelude reproduced across arms, which is what makes this a pair rather than two samples of host noise. The receipt also retracts an earlier 216→167 s reading from this same branch: those were single runs on a host whose variance on this phase is tens of seconds, so only the paired arms are quotable. classification_production stays far above the fixed lens resolve/typecheck term, so this removed one measured term — it did not move the dominant cost.

Executing controls

CI compiled v1-compiler-tests --no-run and executed nothing, so every #[cfg(test)] control inside the lib — including the value_hash parity oracle deciding hash key identity, and the grouping delegate binding — was compiled and never run. That is specification-without-execution: a control that has never run is not evidence.

  • A new build-job step executes a narrow named set (--lib, two module filters, --test-threads=1 because the parity control reads process-global interpreter state). Deliberately not the whole lib suite: the heavyweight producer module resolves the live tree and exhausts memory when run as a group, and trading an unexecuted control for an unreliable job is not a trade. Its RED control fires if the step ever acquires --no-run.
  • The hash oracle was made independent and the probes deleted, so the oracle no longer shares an implementation with what it checks.
  • Incident controls live in floor_skip_discovery_witness, which does execute: live_read_selection_narrows_incident_subject_to_touched_entry asserts selected_entry_groups == 1 of 2 (the grain the incident was measured in — row totals can read 2/1/1 while both entry groups were selected and resolved, which is SelectionSuperset wearing a passing row count); stop_line_population_does_not_widen_incident_subject separates the two facts the incident trace carried together; and the two refusal controls each have a positive control beside them, so the refusal is a fact about the missing subject rather than a broken context.

Both new GHA run: transports carry a Scaffold disposition and a named dissolve-on trigger, emitted as a comment on the run: itself rather than only modeled — a disposition row nobody emits is registration mistaken for enforcement.

Residue, stated

Per-request classification still runs a full dependency_closure plus call_reachable_decls_from_root. The fold fix removed the quadratic accumulator term; it does nothing about per-request cost multiplied by roster size. That product decides whether this narrows CI cost or trades one cost for another, and it is measured by the falsifier corpus run rather than argued here.

The affected-set falsifier is a separate proof and this PR does not establish it. floor_skip_discovery_witness runs on the falsifier cadence, not as a per-PR check. An earlier revision of this description set the closure bar at "a full falsifier run against the merged-main SHA whose aggregate is green and whose receipt says the cold corpus actually ran." That bar is retracted as written, because it is currently unsatisfiable by any PR (witty-raven-412, confirmed here): the five most recent scheduled falsifier runs all failed, every one with cold-corpus failing and aggregate-verdict failing behind it. cold-corpus runs nine lanes under one 170-minute step against a 167-minute soft deadline, so it dies mid-batch rather than refusing up front, and two lanes (CadenceWitness, Gates) have never executed on any run. A bar waiting on that aggregate is waiting on an event that does not occur.

It is recut on two independent axes, because two claims were fused into one gate:

  • Axis 1 — SelectionStanding. Does the manifest, consumed at every selection decision, narrow the floor correctly? That is what this PR claims, and LanePredictOnly decides it — a lane that completes, in ~59 min. It is answered by dispatching the falsifier against this head (not main, not a proxy) and reading that lane's verdict, which is legible even though the job later dies on unrelated lanes. This axis gates the PR.
  • Axis 2 — ClassificationCostStanding. Whether per-request classification cost × roster size narrows CI cost or trades one cost for another. This genuinely needs the cold corpus, it remains open, and it does not gate a correct selection change from landing. It waits on lane-granular admission (stern-ant-249), which converts the two never-started lanes into counted refusals with identities, so a receipt can finally distinguish "the cold corpus ran" from "it was killed" from "these lanes were refused admission, by name." Not routed around by raising any budget.

A green Axis 1 licenses no claim about cost; an unobserved Axis 2 does not block Axis 1.

The merge bar is four conditions, not two (witty-raven-412, correcting an under-relay I had propagated into this description):

SelectionStanding = CorrectlyNarrowed
AND LanePredictOnly completed over the FULL roster
AND ClassificationCostStanding is an OBSERVED value
AND the lane stayed inside its PRE-EXISTING envelope
AND no newly raised budget

The fourth condition has a consequence this description previously did not state: if the lane cannot complete inside its existing envelope, this PR is HELD, on the ground that its production algorithm is then not operationally admissible at corpus scale. The correct response to that outcome is to optimize the priced classification term — the per-request dependency_closure + call_reachable_decls_from_root product named in the residue above — not to raise the cap, split the lane, or book it as a known cost.

ClassificationCostStanding is five-valued, and collapsing it to a Boolean called "faster" is the most likely way this gets misreported:

Improved | Regressed { delta } | WithinExistingEnvelope { observed }
        | ExceededExistingEnvelope | EvidenceIncomplete

Regressed and WithinExistingEnvelope can hold simultaneously — slower than the control yet inside the envelope. That combination is mergeable under the bar above, and it is the likeliest outcome here; merging on it still licenses no CI-cost claim, and the exact priced regression remains a named obligation.

Three dispositions, not two — merging this PR is not closing P3:

A failure in a lane this PR does not own (LaneRehomedBinWet, say) makes the aggregate red but must not erase a completed LanePredictOnly observation. That observation is captured at the lane boundary so a later lane's death cannot take it with it.

Reading the falsifier's green correctly: a healthy selection-control intentionally exercises a planted red row producing exactly one counted divergence, so "no red line in the log" is the wrong reading of green.

The design doc labels P3 a candidate, not landed, and still describes exclusion-row dissolution as open. That is deliberate: this ships the wiring without claiming program closeout.

Scope exclusions

No srv1/sccache infrastructure work, no #7998 falsifier workflow topology, no roster narrowing or witness weakening, no 1 -> 2 assertion changes.

gunbc-ci-auto-heal and others added 2 commits August 8, 2026 17:46
… the G1 carrier-home predicate

#7997 landed the declaration-grain G2 classification, the canonical dependency-edge
production, the subject-carrying manifest and its per-index memo — but left the old
answer in charge. Every production decision still called
`runtime_data_dependency_touched_via_carrier_closure`, whose only use of
`touched_paths` was `is_empty()`; it then answered from broad carrier-home
reachability with no path intersection at all. So an entry with a wide import
closure stayed unskippable on any nonempty `.dag` diff, which is the
`2 selected / 1 expected` incident and the `SelectionSuperset` the floor reports.

ONE CONTEXT, ONE DECISION. `LiveReadSelectionContext` is built once per index from
the COMPLETE discovery roster and answers axis (iv) through
`runtime_dependency_touched_for_entry`. An entry is untouched on this axis only when
every enrolled declaration is present, classified, and untouched. Producer failure,
subject mismatch, absent row, absent entry, and an entry with no enrolled
declaration all REFUSE: none falls back to the retired predicate, silently widens to
a whole-corpus run, or licenses a skip. A missing classification is not a missing
runtime read.

FIVE CONSUMERS, NOT FOUR. Beside the four named decisions,
`discovery_rows_runtime_dependency_touched_count` fed the precompute's
`touched_runtime_dependency_entry_count` through a SECOND carrier fold. That is how
a retired predicate survives its replacement — the precompute keeps a copy — so it
now counts by calling the same function the per-row path decides with. Parity is
construction, not two implementations a later edit can separate.

THE COMPLETE-ROSTER REQUIREMENT IS ENFORCED, NOT DOCUMENTED. The memo is
index-scoped and request-set insensitive, so a first call carrying a chunk would
memoize a partial manifest and every declaration outside it would afterwards refuse.
`live_read_manifest` now refuses a later call whose requests its rows do not cover,
naming the priming rather than the lookup: `row_for` would also have refused, but
its cause reads as a classification gap when the defect is where the manifest was
built.

Two shapes the code forced, against the plan:

  - A prebuilt context cannot be handed to a width>1 worker. Each worker calls
    `build_multi_entry_index` in its own closure and therefore carries its own
    generation, so the main thread's manifest would refuse on subject mismatch at
    every lookup — correctly. `run_discovery_rows` takes the complete roster beside
    its chunk and each worker builds its own context.
  - The context is built whenever selection is on at all, PredictOnly included. The
    falsifier runs PredictOnly and counts divergences; predicting from a different
    predicate than the applied one would make it measure agreement with a retired
    answer.

RETIRED, with zero remaining consumers: the predicate, `LIVE_READ_CARRIER_HOME_MODULES_V0`,
`import_closure_module_reaches_carrier_home`, and the const's drift gate. The stale
comment citing the removed `merge_g1_and_g2_carriers` as the seed's soundness reason
goes with them, as do two further stale citations of the deleted const (in
`v2.lens.enforcement.gate_test` prose and the design doc's status line, which still
described the G1 interim as the production axis).

`lying_substrate_inputs_only_stamp_census` was a real semantic consumer, so rather
than delete a merge gate it is re-expressed on the authority that now decides:
a SubstrateInputsOnly-stamped declaration classifying RuntimeRead — or refusing —
is a lying stamp. Same intent, one authority, no mirrored roster.

A COST SHAPE THIS CHANGE ACTIVATES. `live_read_selection_manifest_over` accumulated
with `list_snoc_item`, which is `list_append(acc, single)` and rebuilds the whole
left list per row — quadratic in the request count. The realized n stopped being
small the moment the producer gained a production consumer folding over the complete
corpus roster, so it is fixed here per DESIGN section 6's bare-minimum-cost rule.
Prepending via `fold_list_right` is O(1) per row and preserves the emitted order
(the idiom is copied from `list_append` itself); order is in any case not
load-bearing, since the seed decodes rows into a key-addressed map that refuses
duplicates.

EXECUTING CONTROLS live in `floor_skip_discovery_witness` — CI compiles
`v1-compiler-tests --no-run` and does not execute `cli_run.rs` unit tests, so a
control placed there would prove nothing. The incident-subject control asserts
`selected_entry_groups == 1` of 2, which is the grain the incident was measured in:
row totals can read 2/1/1 while both entry groups were selected and resolved. A
stop-line-population control separates that axis from axis (iv), and two refusal
controls pin `LiveReadEntryAbsent` and `LiveReadContextSubjectMismatch`, each with a
positive control beside it so the refusal is a fact about the missing subject rather
than about a broken context.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the subject change

The rewrite in the previous commit re-expressed `lying_substrate_inputs_only_stamp_census`
on the G2 manifest but discovered its roster from `dag/test/claim` alone, which is
narrower than the floor's own declared population — a silent scope narrowing inside
a change whose whole subject is selection breadth. It now discovers over
`gunbc.ci_layer_roots` `witness_discovery_scan_dirs`.

The grain change that remains is real and is now stated in the test rather than left
to be found: the retired form walked every `*_test.dag` with a parseable disposition
because an entry-grain import closure needs no declaration names, while this form is
declaration-grain and its subject is the enrolled roster. A `*_test.dag` outside
discovery is not covered — it is also not floor-selectable, so it sits outside the
population the stamp governs, but the two subjects are not identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title falsifier green Consume the G2 live-read manifest at every selection decision; retire the G1 carrier-home predicate Aug 8, 2026
gunbc-ci-auto-heal and others added 17 commits August 8, 2026 18:12
…s per-PR coverage that #7446 removed

`falsifier_rehomed_bin_wet_rows` gave two grounds for keeping
`floor_skip_discovery_keystone_holds` off the per-PR lane: a measured 289s wall, and
that the row was "ALSO a per-PR duplicate: the ci job's own Affected-set selection
control step already runs on every PR".

The second ground is false. #7446 (Demote the affected-set selection control to the
falsifier cadence) moved that step out of `ci.yml` into `falsifier.yml`; `ci.yml`
builds, verifies and tars the binary but never invokes it, and `falsifier.yml` runs
on `schedule`/`workflow_dispatch`, where the step's own `GITHUB_EVENT_NAME =
pull_request` skip branch cannot fire. So the roster row justifies an exclusion by
pointing at coverage that stopped existing — a citation that rotted without anyone
touching it or the thing it named.

The rehome itself stands: the measured wall is its own sufficient ground, and this
commit changes no admission decision. What changes is that the row now says what is
true, and states the coverage grain plainly — the binary executes on the falsifier
cadence only, so this suite (including the affected-set incident controls this branch
adds) is caught within one cadence window rather than at merge.

Found while establishing where the new controls actually execute, which is a question
the stale sentence would have answered wrongly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… running

`selected_entry_groups == 1` proves the unrelated entry ran no witness. It does not
prove its cold entry resolve was elided: a row skipped as assumed-green AFTER its
entry resolved records no `witness_outcome` either, so both states produce the same
counter. Eliding that resolve is the entire cost win axis (iv) exists to buy — a
resolve-then-skip pays the 474-module closure and reports identically.

The control now also asserts the skipped row's provenance is
`skip-before-resolve-fast-path`, which is the fact that distinguishes them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tion, and a retracted claim reproduced

TWO SELF-INFLICTED, both found by reading `gunbc_ci_selection_control_step_note`
rather than by anything executing.

(1) `stop_line_population_does_not_widen_incident_subject` built its injected
name-status with `\u{0}` — an actual NUL byte. The separator is the literal
four-character sequence `\000`: `floor_diff_observe` splits the injected value on the
STRING "\\000", which is also what the suite's own `injected_name_status_modify`
emits. A NUL would have decoded as one unsplit path, so the control would have
exercised a population it does not name — passing or failing for a reason unrelated
to the stop line. That is the shape of the two controls #7997's body recorded as
testing nothing, reproduced here.

(2) The previous commit's correction to `falsifier_rehomed_bin_wet_rows` said the
suite is "caught within one cadence window rather than at merge". That exact phrasing
was ALREADY RETRACTED in the tree as too strong (`gunbc.ci_workflow`
`gunbc_ci_selection_control_step_note`, review correction 2026-07-30): the falsifier
is queue-not-cancel and the step sits after the prelude and release build, so a
regression landing just after a run passed the control waits for that run's
remainder, plus queued-run startup, plus the next run's prelude and build — a sum
that can exceed one cadence period. The honest statement is that it reds when the
NEXT COMPLETED control runs.

So a commit whose whole subject was correcting a stale citation introduced a claim
the tree had explicitly rejected, into a load-bearing roster, one commit later. It now
uses the words the retraction settled on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…and retract the P3 claim

The previous head consumed a manifest whose fact universe was the classification
lens's OWN import closure, which makes this branch a widening rather than the repair
it was named for. Three coupled fixes, none of which lands alone.

FACT UNIVERSE. `fn_arrow_decl_facts_live()` reflects the eval context's modules
(`for_each_live_registry_item` iterates `ctx.modules`), and the producer resolved
`LIVE_READ_CLASSIFICATION_ENTRY` alone. So every request root outside that closure —
nearly every enrolled witness — could not bind: `bind_g2_root` finds the module fact
(whole-pool scan) but no fn-arrow declaration, and answers `G2RootUnbound`. The
manifest carried a modelled refusal for essentially every declaration. The tree
already said this in `production_qualification_origin_probe`: "fn_arrow_decl_facts_live()
scope is entry-closure today", and `whole_tree_subject_mint_site_law` flagged the
coverage premise as unproven. It is not merely unproven; it is false.

The context is now resolved from the INDEX'S OWN sources, so universe identity is
structural rather than asserted — the facts cannot describe a different tree than the
one the consuming index holds, because they are derived from exactly its files.
Deliberately NOT `whole_tree_resolved_ctx`: that helper applies the witness and
strict-resolve exclusion sets first, so its universe can omit the very test
declarations this manifest must classify, and its name would have read like a receipt
while excluding the subject. Sources are sorted so the build is not
host-iteration-order dependent.

Coverage is checked at identity grain, not by count. `fn_arrow_decl_substrate_is_whole_tree()`
projects only `ctx.modules.len()` into a census comparison, and equal counts do not
prove equal identities: a context with the right number of modules and a substituted
one answers that check and still cannot classify the declaration that went missing. So
a resolved-module shortfall refuses by number AND the primary wall is per-root — any
request root that fails to bind produces a modelled refusal, which the consumer now
propagates.

MODELLED REFUSAL PROPAGATES. `touched_by` answers `true` for a `Refused` row, which is
right for a path-intersection question and wrong for a consumer to act on: routed
through the boolean, an undecidable classification is indistinguishable from a decided
"this diff touches a runtime read", so an incomplete producer presents as
`SelectionSuperset` — everything ran, slow but apparently valid — instead of "the
manifest cannot decide". `runtime_dependency_touched_for_entry` now matches the arm
explicitly and returns a typed, located `Err`. The entry still does not skip; it does
not skip because the line STOPPED, not because a widened answer looked like a decision.

TWO CONTROLS, for the two arms the existing ones did not reach.
`live_read_classifies_a_declaration_outside_the_lens_closure` pins the defect
directly: a real enrolled witness entry that `v2.lens.live_read_classification` does
not import must CLASSIFY, and mutating the producer back to a lens-entry context makes
it red. `live_read_present_but_refused_row_propagates_refusal` covers the arm the
missing-entry control cannot: a row that is PRESENT and refused must stop the line,
where the existing control only exercises a roster-lookup miss.

P3 RETRACTED to candidate in the design doc. "P3 landed" was written against the
consumption half alone and was wrong for exactly the reason above; producer universe
completeness and production consumption are ONE atomic change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… index

The previous commit resolved the index's ENTIRE source population to guarantee every
request root could bind. Sound, but it pays a whole-corpus resolve to answer any
question — including the two-entry rosters the selection-control suite runs. A
producer that costs a corpus resolve to decide a two-entry question is the cost shape
this lane exists to remove, reintroduced one layer up, and it would have been measured
as a regression in step 6 rather than caught by reading.

The universe is now the union of the request roots' own closures from this index's
pool, plus the classification lens's closure. Containment is by construction rather
than by hope: classifying (E, f) needs E's module and the modules
`call_reachable_decls_from_root` walks from it, and both are exactly E's import
closure. For the full floor roster this approaches the corpus anyway — correctly, that
IS the population being decided — while a scoped roster now pays only for what it
asks about.

Deduplicated and ordered through a path-keyed BTreeMap, so overlapping closures cost
one entry each and the resolve is not a function of host hash-iteration order. A root
whose closure cannot be loaded refuses by name rather than resolving a population
that silently lacks it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Step 6 of the acceptance sequence needs a measurement, not an argument. This producer
resolves the union of the request roots' closures once per index, and that has to be
paid back by the entry resolves selection elides; two cost readings on this lane have
already been wrong, so the numbers are printed where any run that builds a manifest
carries them and a reader can divide them by the resolves saved.

CPU is reported beside wall via `getrusage(RUSAGE_SELF)` because the two answer
different questions on a shared runner: wall includes time the process spent
descheduled behind co-resident jobs, so a wall figure alone cannot separate "this
producer is expensive" from "this host was busy", and a cost claim that cannot tell
those apart is not a measurement.

RSS is labelled as whole-process and therefore an upper bound rather than this
producer's attribution — other work shares the process, and quoting it as the
producer's own would be the same overclaiming this lane keeps producing.

Emitted unconditionally rather than behind a verbose flag: a measurement that only
appears when someone thinks to ask for it is not available at the moment a regression
lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n producer

The decoder required Value::List while every representation the producer can
return is the modeled FreeMonoid chain, so a well-formed manifest reported as a
foreign shape. Decoding now accepts the native list and the Empty/Cons chain
through the interpreter's own free_monoid_to_vec, inside the active context
(the Empty/Cons/head/tail symbols resolve through active_ctx, so a decode
outside it reports every variant as foreign). A non-collection value refuses by
name rather than decoding to zero rows.

The fold was never the functional defect. Both folds return the same modeled
collection; execution disproved the earlier attribution, and the note recording
that mistake is corrected rather than deleted.

Instrumentation added, and it measures the union producer out of the design:
one request over a 545-module union costs 179125 ms wall / 178751 ms CPU at
9.4 GiB whole-process RSS. That specimen no longer reproduces the prior SIGKILL
-- which is not the same as the OOM class being closed, and the full roster,
wider unions and width-N workers remain unproven.

fn_arrow_decl_facts_from_sources lands as the replacement's core: the same
FnArrowDecl population derived from a parse rather than from the eval context's
resolved modules, reusing build_item_info and fn_arrow_decl_record verbatim so
it is not a second definition of the fact. Not yet consumed.

The foreign-shape control was vacuous -- it rebuilt a well-formed roster and
asserted it decoded, which the control above it already proves. It now calls
the decoder with a non-collection value and asserts the refusal by cause.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nal identity join

Merges current main (7bcd31d's decl-facts work included) BEFORE binding a
declaration key, so the bundle binds to the landed ResolvedDeclarationLocator
surface rather than to a key invented against a stale one.

That merge changed the plan for the better. decl_facts_corpus_walk already IS a
parse-level declaration walk over pool roots with no eval context, so the
hand-rolled walk written before the merge was a second definition of a
declaration fact. It is deleted. What remains is one projection,
push_decl_facts_from_parsed, shared by the corpus walk and the new
index-scoped walk; a caller chooses only which modules to visit.

Two divergences the live-read consumer requires, each stated where it lives:
the corpus walk skips is_test_dag, and enrolled witness declarations are
exactly those files, so an unfiltered population is needed -- an empty one
would read as "no runtime reads", a silent narrow. And parse_dag_file drops
unparseable files to None, which is fine for a census and not for a consumer
deciding skips: a dropped module's declarations report as unbound, conflating
"no such declaration" with "its module did not parse". parse_dag_source is
extracted as the content-taking core (parse_dag_file becomes its filesystem
instantiation) and names its refusal.

G2FactBundle carries the exact index pool with TWO levels of failure, because
collapsing them recreates the union's defect one layer down: the bundle refuses
only when the index-wide population itself cannot be observed, while a module
that will not parse is a local fact under its own identity. bad A -> A refuses,
never bad A -> bundle refuses -> B disappears.

Completeness is an identity join in both directions, never a count: a
population with one identity missing and one extra has the same size and is a
different tree. Foreign facts refuse; unvisited index modules refuse. Visits
are reported by the walk rather than inferred from absence, because a module
may legitimately parse and declare nothing -- an earlier revision of this join
computed that check and then discarded it under a comment claiming it caught a
defect it could not catch.

Facts are cached RAW. A marshalled Value carries symbols interned in the
evaluating context, so caching marshalled rows on the index would hand one
context's symbols to another's evaluation.

Not yet wired: the union resolver, complete-roster memo guard and stale fold
row are still present and still in charge; this commit adds the substrate they
will be replaced by.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… request binding

Replaces the one-manifest memo's SHAPE, ahead of removing its last consumer.

g2_facts is index-owned and request-set INSENSITIVE: the population is the
index's, so any roster reads the same facts and no roster can prime it
partially. live_read_rows fills incrementally, keyed by
ResolvedDeclarationLocator rather than a rendered entry::name string, so two
same-named declarations in different modules cannot share a verdict and a miss
classifies one declaration instead of poisoning the index. The suite already
disproved "complete on first access" as a property an index can have -- the
same immutable index is legitimately reused across scenarios with different
rosters -- so this is not a convention made easier, it is the convention
deleted.

Binding is separated from classification. A request names a SPELLING, and a
spelling is not an identity: over a whole-tree population it may match nothing
or more than one. Folding that into classification would force a verdict out of
an ambiguous subject and let "many" become whichever the walk reached first --
a heuristic standing where an identity was owed. All three cardinalities are
answered, only exactly-one proceeds, and zero and many refuse with different
causes because their remedies differ. An entry whose own module failed to parse
refuses under its own key, locally, leaving unrelated entries classifiable.

ResolvedDeclarationLocator derives Hash beside its existing Eq so the hash and
the equality cannot disagree about which fields decide identity.

Not yet wired: nothing consumes these yet. The union resolver and the
complete-roster guard are still in charge on the decision path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he complete-roster convention

ONE transition, per the ruling: the bridge and its consumer land together, and the
machinery they replace is deleted in the same change rather than left beside them.

THE PRODUCER. fn_arrow_decl_facts_index_live is a transport, not a second semantic
authority: it reads the exact MultiEntryIndex bound to the classification operation,
delegates to the shared push_decl_facts_from_parsed projection, reads no files,
resolves nothing, walks no closure and decides no classification. It marshals into
the CURRENT evaluator context because a marshalled value carries context-interned
symbols; what is cached is the parse. With no index bound it REFUSES -- it does not
fall back to reflecting the eval context, because the two producers answer about
different subjects and substituting one for the other is never a safe default, and
an empty population would read as "no runtime reads" and license a skip.

It arrives the supported way: .dag declaration, authored roster row, regenerated
dispatch (a 3-line diff, generated from the roster rather than hand-edited),
interpreter arm. dependency_resolution_facts_live remains the sole edge authority;
only the missing fn-arrow projection is supplied.

THE LAW CHANGED BECAUSE ITS PREMISE DID. whole_tree_subject_mint_site_law admitted
it had not proven the producer covered the consuming index, and it could not: that
producer reflects the eval context, so coverage meant evaluating a union of every
request root's closure. Measured at 179125 ms wall / 178751 ms CPU for one request,
and then it failed outright -- bare names that bind uniquely inside each entry's own
import scope stop binding across a union, so Holds, Finding, Exact, Refuse, Blocking
and FailClosed went unresolved and ONE shared refusal denied classification to all
133 enrolled entries. That is a property of composition, not a repairable module.
Coverage is now established by a bidirectional identity join before any subject is
minted, and completeness is per bound request: an unrelated module that fails to
extract refuses only the requests whose closure contains it. Reading the subject as
"the whole index parsed" would recreate the union's failure in another form. The
variant is still spelled WholeTreeFactSubject; the honest name and the deferred
rename are stated in the law rather than left to make the prose quietly false.

DELETED, not deprecated: the union resolve and its RequestUniverseUnresolved,
RequestUniverseIncomplete and RequestRootClosureUnloadable causes; the one-manifest
memo, its complete-roster requirement and MemoRequestPopulationIncomplete; the old
live_read_selection_manifest_live mint site; and the fold essay whose premise the
complete roster was. Only the classification lens's OWN closure is resolved now --
one entry, so a refusal there is about the lens and cannot be caused by an unrelated
module.

The context becomes a roster-scoped VIEW over the index-owned cache, and re-checks
the subject at lookup rather than trusting construction.

The memo test asserted Rc::ptr_eq on one manifest -- precisely the property being
deleted, since it could only hold if a single object served every request. It is
replaced by the incrementality and cache-hit controls: disjoint rosters classify in
any order, a repeat ask does not grow cache occupancy, and a distinct index inherits
nothing. Occupancy is exposed so a hit is proven by execution rather than inferred
from two calls agreeing.

STILL OWED, stated rather than implied: the lying_substrate_inputs_only_stamp_census
denominator is still narrowed, and the remaining ladder gates (bad-A/good-B
isolation, exact-identity refusal, zero/one/many binding, no-union execution proof,
the 1-selected/1-skipped incident, both G1 mutations, falsifier_red_control_holds)
have not been executed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…o a macro

CI builds with RUSTFLAGS=-D warnings; my local checks did not, so an
unused-doc-comment error reached the branch that no local run could have caught.
The thread_local! block for the bound fact substrate was inserted between
build_live_read_selection_manifest's doc comment and the function, leaving the
comment attached to a macro invocation.

The comment was also stale in the same motion, and rewriting it was the fix
rather than moving it: it described a whole-tree manifest built once for the
entire request set and a producer whose job was to be handed the full roster.
Both premises were deleted by the cutover. It now says what the batching is
actually for -- the caller passes exactly its cache misses, one lens resolve and
one marshal serve them, and the index keeps the rows -- while retaining the arm
that matters, that no failure returns a partial batch, because widening the
SKIPPABLE set is the under-selection direction and reads as a faster green.

Verified against CI's own flag and bin set rather than my previous weaker local
command. Two -D warnings errors remain in p1_cohort_probe.rs; they are
pre-existing, untouched by this branch, and that bin is not in ci.yml.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ng a side

CI's heal job failed on `trim' not found in scope, which looked like the git
step and was not. Converge passed locally on identical sources, so the
difference was the BINARY: CI builds release bins from the PR merge ref while
that job checks out the branch head. Main added a trim primitive after my
earlier merge, and v1_interpreter_dispatch_generated.rs sits on the
merge-driver exclusion list -- so the merge ref took my stale projection,
producing a binary with no macro rule for main's new arm while main's .dag
sources call it. Authority ahead of artifact, exactly the state the driver
names.

The driver did its job: it refused, left the path unmerged with no conflict
markers, and printed the regeneration recipe instead of silently dropping one
side's authority-derived bytes.

Regenerated rather than resolved. The roster auto-merged cleanly and carries
BOTH authorities, so the emitted projection now contains main's trim and this
branch's fn_arrow_decl_facts_index_live -- neither side's bytes would have.
Regenerating needed the same cycle-break as the original arm: the tree cannot
compile while the projection lags the interpreter, so main's projection plus a
parked arm built a gunbc, that gunbc emitted from the merged roster, and the
arm went back.

Verified by executing the operation that failed, not by inference: converge
returns ExitSuccess with zero trim diagnostics, and the CI bin set compiles
under CI's own RUSTFLAGS=-D warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nothing had ever executed this path; running it found two real bugs, both of
the same family -- an identity compared in the wrong spelling.

PATH KEYS. The bundle keyed declarations by MultiEntryIndex.source_files' map
key, which is the dotted MODULE PATH (std.machine_shape), not a file path --
`source_files.contains_key(module_path)` elsewhere is the tell I misread. The
declaration population must be identified by FILE, because that is what a diff
touches and what a discovery row names, so identity now comes from sf.path.
Both sides additionally go through the index's own `module_index_path_key`,
because a raw string compare against the pool is the source-identity fork
DESIGN names on this very index: a caller arriving with an absolute path while
the pool holds a relative one gets "no such declaration" about a declaration
that is plainly present -- a refusal whose cause is the spelling, not the
subject.

MODULE PATH ON THE FACT. DeclFactRaw carried only the qualified name, which
passes through `decl_logical_qualified_name` and DROPS the `v2.` layer prefix
for decl_index consumers. fn-arrow reflection keeps the full path, so
reconstructing a module by un-stripping that name produced facts under a name
no root can bind. The fact now carries its authored module_path unstripped.

MEASURED, first execution of the cutover:
  requests=1 rows=1 fact_modules=3404 fact_decls=61705 parse_refusals=0
  lens_closure_sources=579 lens_modules=579 wall_ms=101506 process_rss_mib=12386
The pool is every .dag file including all 1290 tests, and nothing failed to
parse, so the population is complete and no union is formed. But removing the
union fixed the SEMANTICS, not the price: 101s for one request, because the
classification lens's own closure is 579 modules -- nearly the size of the
union deleted. That is a finding, not a fix.

STILL RED, stated exactly: the subject declaration still reaches the lens as
G2RootUnbound. The population contains its file and its module name is right,
so the remaining defect is in the projection rather than the walk. It has not
been located, and each observation costs a ten-minute run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
c8161f3's message states "The fact now carries its authored module_path
unstripped." That claim is FALSE. The field was never added, the projection
never changed, and `git log -S"pub module_path: String,"` over
coproduct_reflection.rs returns nothing for any commit. The edit reported
success and the build passed, and I recorded a landed fix from those two
signals without re-reading the file -- but the ORIGINAL code compiles fine, so
a green build was never evidence the edit applied. That is
specification-without-execution with the author as the consumer: the artifact
looked finished because nothing ran that could tell the difference.

It also means run 4's failure was never evidence against the v2.-stripping
hypothesis. The fix was absent, so the run re-tested the unmodified code and
the hypothesis stayed untested for two cycles.

Applying it for real, and proving it by execution rather than by inspection:

- DeclFactRaw carries `module_path`, the module's own AUTHORED path, populated
  at the single construction site in push_decl_facts_from_parsed.
- fn_arrow_decl_rows_from_facts reads that field instead of reconstructing the
  module by stripping the declaration's name off `qualified_name`. That
  un-strip could not be correct: `qualified_name` passes through
  decl_logical_qualified_name, which drops the `v2.` layer for decl_index
  consumers, so the reconstruction filed every v2-authored fn-arrow row under
  `test.workflow.x` while the root asks for `v2.test.workflow.x`. Every such
  root reported G2RootUnbound -- not missing, misfiled.

Two probes, both green, both fast (36s against the floor's ten minutes):

- the_fact_bundle_contains_an_enrolled_witness_declaration proves the bundle
  holds the exact declaration CI named and binds it to exactly one identity.
  This is what rules the host side OUT: the population and the binding were
  never the defect.
- v2_layer_facts_keep_the_authored_module_path_and_drop_it_from_the_qualified_name
  asserts BOTH directions together, because either alone is satisfied by the
  defect -- with the un-strip in place `qualified_name` lacked `v2.` exactly as
  it does now, so only the presence of `v2.` on module_path beside its absence
  on qualified_name discriminates. It also refuses to pass vacuously: it fails
  if no v2.-authored fact exists at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CarrierListAbsent was never a carrier defect. `free_monoid_to_vec` resolves
`Empty`/`Cons`/`head`/`tail` against `active_ctx()` and answers None when there
is no active context -- so run outside that scope it cannot tell a cons chain
from a foreign shape, and reports the foreign one. The decode ran after the
producer call returned, i.e. outside the wrapper.

The outer row collection concealed this for three rounds because it arrives as
a native `Value::List`, which returns before the symbols are ever consulted.
The nested carrier lists are real cons chains and are not so forgiving. So the
active context is a property of DECODING a produced value, not of the one call
that happened to need it first, and it is now scoped to the whole walk
(decode_live_read_manifest_rows).

Two diagnostic defects made those three rounds cost what they did, and both are
fixed here rather than left as the price of the next one:

- The refusal named a cause and no SUBJECT. It saw one projection and could not
  say which declaration produced it, so analysis could not begin without
  re-deriving the culprit by hand -- a line that stops without locating is half
  of what §5 asks for. The key is in scope at the row loop, so attribution
  belongs there. It named
  `src/v2/lens/module_graph.dag::module_declaration_facts_live` immediately.
- The refusal reported what it EXPECTED and never what it RECEIVED, and fused
  "field absent" with "field present, wrong shape" into one arm -- two states
  whose repairs share nothing. Split, with the received shape named: the answer
  came back `Variant::Cons["head","tail"]`, which is a well-formed cons chain
  and refuted the representation hypothesis I had just spent a round on. An
  expectation is not an observation.

Green by execution, and the receipts are the evidence rather than a counter:
three asks over two indexes print exactly TWO [live-read-rows] lines. The
repeat roster prints none, so the per-declaration cache hit is observed, not
asserted; the distinct index prints its own, so it classified for itself.

Cost, stated plainly and unresolved: 275s per cold classification in an
unoptimized build (101s release), lens closure 579 modules. The floor is over
budget on CI at 3,300,000 ms for this reason, and this commit does not address
it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Floor over-budget: measured, and it was not this branch's change

Two runs (a5f9ef7, 63383c7) failed identically with ORDINARY-FLOOR-OVER-BUDGET at 3,300,000 ms. I first read this as unrelated infrastructure — zero [live-read-rows] receipts in the log, and LiveReadSelectionContext::build is reachable only from floor_skip_discovery_witness (falsifier lane), not the ordinary discovery batch. That read was wrong, and the timing said so: main's successful run finished the batch in ~29 min, this branch was killed at ≥47.5 min.

So I measured it directly instead of arguing from code shape — same host, same entry, release binaries built from each tree, three runs each:

tree wall (s) median
main 32887d31 133 / 118 / 119 119
branch 63383c7 170 / 177 / 171 171
branch + main merged 127 / 120 / 120 120

~1.44x, reproducible, and it matches CI's 29 min → ≥47.5 min (~1.6x).

Mechanism: the whole delta is in load (28.4s → 64.0s), specifically load_bare_edge_index. Main's cost partition carries sub-spans this branch's did not emit at all (edge_index_tree_census 22.1s, edge_index_bare_resolve_loop 5.3s) — the tell that the branch was behind main, not ahead of it with a regression. The improvement is #8067 ("dissolve the homonym bucket, 152 → 0"): fewer homonyms, less bare-name census work.

Merging main removes it entirely (120 vs 119 — noise). No selection code was changed in response, per the standing ruling that infrastructure trouble is not a repository blocker.

Still open and not addressed by this: the classification's own cost (101s release per cold classification, 579-module lens closure). Removing the union fixed the semantics, not the price.

@gunbai-bot

gunbai-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Correction: the merge did NOT fix this, and the producer IS on the ordinary floor path

Two claims in my previous comment are wrong. Correcting them before anything else, because the second one is what sent the investigation down a blind alley.

1. "The producer is reachable only from the falsifier lane." False. run_discovery_rows builds LiveReadSelectionContext whenever skip_enabled — that is the ordinary CI discovery batch. My earlier grep missed these call sites and I stated the negative more confidently than the search supported.

2. "Zero [live-read-rows] receipts means the producer never ran." False, and it is the ⊥-as-ignorance conflation DESIGN names: the receipt is printed AFTER classification completes, so absence of a receipt means it never completed — not that it never started. I read absence-of-completion as absence-of-execution.

3. The merge did not fix CI. b1976f7 failed the same way. Per-entry cost is genuinely at parity now (120s vs main 119s, measured), so #8067 was a real and separate finding — but it was not this failure's cause.

What the run actually shows — the decisive number, which I should have pulled first:

rows executed distinct entries window
main 42966db6 (success) 4,728 315 30.2 min
this branch b1976f7 6 2 55 min, killed

The batch is not uniformly slower. It is stalled at the start, consuming the entire budget before executing meaningful work — consistent with the classification running in run_discovery_rows and not returning.

So the cost is a merge blocker, not a follow-up. This is the P1 the audit already named and I had left open: per-worker duplication, with each worker paying a cold ~101s / ~12 GiB classification over a 579-module lens closure. The audit's verdict stands unchanged — removing the union fixed the semantics, not the price, and the price is now demonstrably what breaks the floor.

No selection code changed in response to this. #8055 stays draft.

gunbc-ci-auto-heal and others added 2 commits August 9, 2026 16:12
…cons

STEP 1 of the operator's ordered plan, on the exact head rather than in a PR
description. The body claimed the manifest accumulator had been changed to a
right fold with constant-time prepend. It had not: at b1976f7
`live_read_selection_manifest_over` still left-folded with `list_snoc_item`,
whose body is `list_append(left: xs, right: singleton)` and therefore traverses
the accumulated prefix on every row.

Why the earlier evidence could not see it: the probe that certified this path
used ONE request, where the quadratic term does not exist. The ordinary floor
builds from a roster of thousands, and the completion receipt is emitted only
after the fold and decode -- so the failure presents as zero receipts and 6
executed discovery rows against main's 4728 in the same window, which reads
like a route never entered rather than an accumulator that cannot finish.

THE ROOT CAUSE IS A GAP IN THE VOCABULARY, not fifteen careless call sites.
`list_snoc_item` existed with NO O(1) counterpart. Writing the cheap form by
hand means a `Cons` literal, which typechecks as Coproduct(FreeMonoid) and does
NOT unify with a branch typed Container(List, T) through the `List` alias --
proven here by execution, since that is exactly the type error my first attempt
produced. So the expensive spelling compiled and the cheap one did not, and the
census found sixteen snoc-in-fold sites in ONE lens module. `std.algebra` now
carries `list_cons_item` beside `list_snoc_item`, named as an item-level
operation so the alias asymmetry cannot resurface per call site.

Converted in this pass (prepend + one reverse, order-preserving): the manifest
fold, `decls_in_modules` (genuinely quadratic in output, on the hot path),
`module_facts_for_path`, `decls_with_qualified_name`, `g1_carriers_from_homes`,
`merge_carriers` (seeded on a reversed left so the left side keeps its order),
`param_names_of`, `positional_child_targets`, `arg_symbols_from_nodes`.

NOT converted, and deliberately so: the `fold_node`-composed pair
(`atom_collect_step` / `atom_identities_in_node`) and the remaining dedup
accumulators. Prepend-plus-reverse is not obviously order-preserving through a
node-fold composition, they are not hot-path quadratic (atoms per node are
few), and rewriting them on a guess is how a load-bearing lens acquires a
silent semantic change.

I therefore did NOT enroll this file in the accumulator-copy roster gate. I
wrote that enrolment, ran it, and it FAILED at ceiling 0 -- because the sites
above remain. Landing an enrolment I cannot pass would be either a red gate or
a false claim, so it is withdrawn until the residue is closed. The lens already
decides this exact shape (`red_list_snoc_left_is_suspect`), so the control is
available the moment the file is clean; it is owed, not invented.

Green by execution: all 20 witnesses in live_read_classification_test pass, so
the rewrites preserve classification semantics.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…er roster

STEP 2 -- start and phase receipts. The terminal receipt could not distinguish
a route never entered, a route entered and stalled, and a route that returned;
it reported only the third. That is why the floor's stall showed NO receipt and
I read it as "never ran". Every line now carries one invocation identity
(subject + pid + request count, so concurrent workers are separable and a
per-process counter cannot collide across them), with an unconditional
[live-read-start] before any expensive work and a phase end for each of: fact
bundle build, lens closure source load, lens resolve/typecheck, eval context,
request marshalling, classification production, decode.

IT LOCATED THE COST ON ITS FIRST RUN, and not where step 5 expected:

  lens_closure_resolve_typecheck  ms=40468   modules=579
  classification_production       ms=207790  <- ONE request
  decode                          ms=0

The 579-module closure is 40s. The modeled fold is 208s for a SINGLE request.
So the dominant term is fact production plus per-request classification -- the
batch-grain indexing work -- not the fixed closure. Recorded here because two
cost readings on this lane were already wrong from argument rather than
measurement.

STEP 3 -- stop classifying rows whose verdict G2 cannot change. Selection is a
disjunction of independent do-not-skip axes and every other axis is a fact
lookup. A row already forced to run cannot have its verdict changed by a
live-read classification, so classifying it is cost paid before the first
discovery row executes. Three call sites now consult G2 LAST (semantics
identical -- `||` is commutative, ordering only decides what is evaluated), and
the context is built over candidates only, counted in a [live-read-candidates]
receipt rather than narrowed silently. `entry_blocked_by_cheap_axes` is the one
definition of "already decided"; a second copy could drift into classifying
needlessly or withholding a classification a consumer then asks for.

LOCAL FAILURE ISOLATION. `live_read_rows_for` returned Err for the whole batch
on one unbindable request, beneath a comment asserting the refusal was local --
the two disagreed for as long as nothing executed the case. An unbindable
request is now a Refused row: the batch keeps classifying, and the floor still
refuses to skip, because the consumer already reads Refused as do-not-skip. So
isolation costs no fail-closed strength. Not cached -- the durable cache is
keyed by resolved declaration identity and an unbindable request has none.

The control asserts BOTH halves, since either alone is satisfied by a defect:
answering for A would be fail-open, and refusing for A while dropping B is the
abort being repaired. Green by execution.

Also fixes a second site of the module_path defect: bind_live_read_request
still derived the module by un-stripping the qualified name. Same fork as the
fn-arrow projection, one call away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Step 6 first attempt: measured, worse, reverted

Recording a negative result so it is not re-attempted blind.

The phase receipts located the cost, and it is not the fixed closure. First run with step 2's instrumentation, one request:

lens_closure_resolve_typecheck  ms=40468   modules=579
classification_production       ms=207790  <- ONE request
decode                          ms=0

So the 579-module closure is 40s and the modeled fold is 208s for a single request. Step 5 is secondary; step 6 is the priority, confirming your ordering.

The exact quadratic: decls_matching_callee calls decls_in_modules(decls, modules) — 579 closure modules filtered against ~62,000 declarations — for every callee of every reached declaration. Not once per request; once per edge of the reachability walk.

The attempt: build a Map<String, List<FnArrowDecl>> name index once per invocation and thread it through call_reachable_decls_go, turning the callee lookup into a keyed lookup plus a small module filter. Semantics held — all 20 fixtures passed.

The measurement refuted it. Same 20 fixtures: 7,708 ms → 252,948 ms, 33x slower. Reverted; back to 7,764 ms on the restored tree.

Why, and this is the part worth keeping: v2.std.collection's Map is an association-list of closures — map_insert nests one closure per entry and lookup walks the chain. The native HAMT exists but is reached through the index_by method-call arm; bare map_insert/empty_map resolve to the modeled bodies. I wrote a law warning about exactly this distinction and then used the wrong spelling, and only the measurement caught it — the fixtures were all green.

What this implies for step 6: a grouped index needs either the native index_by (which is a MAP, not a multimap — it keeps the last item per key, so it cannot group declarations by name without loss) or a native group_by that does not exist yet. That is a substrate gap, not a lens defect, and it is the same shape as the missing list_cons_item: the cheap structure is unavailable at the spelling a caller reaches for.

I have not chosen between adding a native grouping primitive and reformulating the walk to avoid grouping. Both are substrate-level and I would rather have your ruling than guess after a 33x miss.

Nothing from this attempt is on the branch; cca0d8edcf0 is unchanged. #8055 remains draft.

gunbc-ci-auto-heal and others added 5 commits August 9, 2026 18:53
The phase receipts from the previous commit did their job on their first CI
run, and what they showed is that my step-3 filter was on a path this run does
not take:

  [live-read-start] requests=8538
  phase=fact_bundle_build              ms=0     modules=3450 decls=62767
  phase=lens_closure_source_load       ms=25    sources=587
  phase=lens_closure_resolve_typecheck ms=7367  modules=587
  phase=eval_context_construction      ms=59
  phase=request_marshalling            ms=5     requests=8538
  <no classification_production end>

Three things are now located rather than inferred. The stall is in
classification_production. The fixed closure is NOT it -- source load plus
resolve is 7.4 seconds here, against a 55-minute budget. And 8538 requests
entered that fold, so the candidate filter never ran: no
[live-read-candidates] line appears anywhere in the log.

The site that builds it is `run_discovery_corpus_with_options_inner`, which
ran before `run_discovery_rows` and built from the COMPLETE roster under a
comment declaring that to be the design: "First consumer of the manifest in the
run, and it is handed the COMPLETE roster -- the index-scoped memo it fills is
what every later decision reads."

That convention outlived its mechanism. It was true of an immutable
whole-roster manifest built once; it has been false since the cache became
per-declaration and incremental, which disjoint-roster use forced. Nothing
downstream needs priming -- a later caller asking about a row this one skipped
classifies it then. So the comment was not merely stale prose: it was the live
justification for putting every enrolled row into one classification.

`live_read_candidate_rows` is now the single definition, used at all three
build sites, and the inline copy in `run_discovery_rows` is deleted rather than
left to drift from it. The struct doc that chartered the convention says what
replaced it and why, and the `LiveReadEntryAbsent` refusal no longer instructs
the reader to build from the complete roster -- callers ask about their own
demand, so an absent entry means it was not among THIS view's rows, not that a
roster was incomplete.

This does not fix the quadratic in the fold. It stops feeding it rows whose
verdict it cannot change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The filter worked, and the measurement is unambiguous:

  [live-read-candidates] roster_rows=8545 candidate_rows=91 excluded=8454
  phase=classification_production ms=287414   <- COMPLETED, 91 requests
  phase=decode rows=91

A 94x reduction in requests, and the classification RETURNS instead of never
finishing. Two further facts fall out of the same receipt: the fixed closure is
6639 ms of a 55-minute budget, so step 5 was never the target; and 287 seconds
for 91 requests is still ~3.2 s/request, so the quadratic in the fold is
untouched and remains the next thing to fix.

The run then failed differently, and correctly:

  AFFECTED-SET REFUSAL cause=LiveReadEntryAbsent
    entry=dag/test/claim/accelerator_demo_execution_witness_test.dag

That is the exact risk excluding rows creates, and it fired at a consumer I did
not convert: the first site built the view from candidates and then handed the
COMPLETE roster to discovery_rows_runtime_dependency_touched_count beside it,
which asks the view about each row. So it asked about rows the view was
deliberately not built for. Fixed by passing the candidates there too.

The count's meaning narrows, and that is worth stating rather than burying: it
is now "among entries whose verdict a live-read answer can still change, how
many have a touched runtime dependency". Excluded entries are not uncounted --
they are already forced to run by the axis that excluded them, and counting
them here would count one entry under two axes.

Every remaining production consumer is guarded: the two entry predicates and
the fast-skip predicate all consult G2 last, after the cheap axes have returned
early. The one unguarded `row_for` loop is a test that builds its own context
from the rows it asks about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Findings 2, 3 and 4 from the review. Finding 1 (precompute iterating the full
roster) was already fixed at 36be85d, before the review landed.

THE ROOT DEFECT WAS THE REPRESENTATION, exactly as called. A sparse context
gives one representation to two states with opposite remedies: "absent because
another axis already decided" is correct and must be silent, while "absent
because the classification is missing" must refuse. With only a context every
consumer sees LiveReadEntryAbsent for both -- which is why filtering the
context alone moved the failure from the stall to a refusal instead of fixing
it, and why chasing it consumer-by-consumer would never have terminated.

`LiveReadSelectionPlan` carries `AlreadySelected { cause } | NeedsClassification`
per entry beside the classifications for the candidates only. `entry_must_run`
answers an AlreadySelected entry from its own cause WITHOUT consulting G2, so
an excluded row is unqueryable rather than a lookup that happens to refuse. An
entry the plan never saw is a real gap and refuses with a distinct cause
(LiveReadEntryNotPlanned) -- the plan must not become a way to make every
absence silent, which would be the same conflation inverted.

FINDING 2 -- post-resolve accounting. resolve_discovery_entry_for_corpus_row
short-circuited only on the declared/effect half, so a row excluded for any
OTHER cheap reason (ReadsLiveTree, direct touch, edited test fn, import
closure, stop line, data-item overlap) still reached G2 after resolve. It now
goes through the plan. The runtime axis is not faked false for excluded rows;
AlreadySelected answers true, which is the honest do-not-skip verdict rather
than a claim about an axis nothing evaluated.

FINDING 3 -- the categorization site. eprintln_affected_set_categorization
still built directly from the full roster and could independently recreate the
8538-request classification the execution sites had just stopped making, moving
the stall to another consumer. It consumes the plan now.
measure_selected_entry_closure_overlap is classified deliberately rather than
missed: it is an OFFLINE whole-population measurement whose subject IS the full
roster, so filtering would change what it measures, and it says so in place.

FINDING 4 -- the duplicated chain. entry_qualifies_for_skip_without_resolve and
entry_eligible_for_discovery_skip_before_resolve each repeated the disjunction
inline; both now call entry_blocked_by_cheap_axes. Two independently writable
copies of one rule only have to disagree once to produce either a refusal or
the restored cost. The authority returns a typed CheapAxisCause rather than a
bool, so the plan can preserve WHY G2 was omitted instead of consumers
re-deriving it.

Controls, green by execution (465s, serial):
- every_cheap_axis_excludes_without_the_classification_being_consulted: one arm
  per exclusion reason, asserting BOTH the expected cause AND entry_must_run ==
  true. Either alone is satisfied by a defect -- an arm checking only the cause
  would pass while the entry silently became skippable.
- an_unplanned_entry_refuses_rather_than_reading_as_already_selected.
- the local-isolation control from the previous commit still passes.

The fixture constructor for plans is cfg(test): minting demands without
deriving them from the cheap axes is precisely the forgery the plan prevents,
and it must not exist on a production route.

Not addressed here, and still the dominant term: the per-callee 579 x ~62,000
scan inside every candidate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pstream

ZERO-CANDIDATE CONTROL (merge bar item). Candidate filtering reduces the number
of REQUESTS through the fold; it does not by itself remove the ~7s fixed
startup, because a context built unconditionally resolves the ~587-module lens
whether it has one candidate or none. "The roster filter appears to work" and
"the run stopped paying for the classifier" are different claims and only the
second is asserted here.

Asserted on a COUNTER, not on stderr: a test that greps its own output proves a
line was printed, not that work was skipped.
`LIVE_READ_CLASSIFICATION_INVOCATIONS` counts the expensive unit -- one lens
resolve plus one modeled fold per `build_live_read_selection_manifest`. It also
supplies the width-1/width-2 invocation count the merge bar asks for, which is
otherwise unfalsifiable from outside the process.

The control also asserts the plan stays USABLE at zero classifications: an
excluded entry still answers `entry_must_run == true` from its own cause. Zero
classification must not mean zero answers, which is the fail-open direction the
filter could have introduced.

GROUP_BY: BUILT, MEASURED AGAINST THE SUBSTRATE, REVERTED. The native
multiplicity-preserving arm compiles and the roster/regeneration path accepts
it, but it is NOT REACHABLE FROM AUTHORED .dag. The witness refuses at
typecheck in both spellings:

  method 'group_by' not found on receiver type 'Container(List,...)'
  function 'group_by' not found in scope

and -- the decisive part -- `index_by` refuses IDENTICALLY, though it is an
established interpreter arm. So this is not a missing registration for my new
primitive; the whole family is unreachable from authored source and exists only
for emitted-Rust/internal paths, which is why no .dag module calls it.

Making it usable needs primitive REGISTRATION, and DESIGN already names why
that is not a one-line addition: the primitive subject universe is forked
across infer_method's builtin registry, std.primitives PrimitiveContract rows,
std.algebra templates, the native interpreter arms, and per-target emit
handlers (the determinism thread's closed-denominator gap). That is substrate
work with its own sequencing, not a lens patch.

Landing the arm anyway would be machinery no consumer can reach -- the inert
mechanism DESIGN calls coverage by illusion -- so it is reverted rather than
left in the tree with a note. The dispatch table is regenerated back to
`group_by`-free through the supported path, not hand-edited.

The per-callee 579 x ~62,000 scan is therefore still unfixed, and the route to
fixing it now has a named upstream blocker rather than an unknown.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
decls_matching_callee called decls_in_modules itself, filtering ~62000
declarations against 587 closure modules once per callee of every reached
declaration. Nothing in that filter depends on the callee. The caller now
restricts once and threads the restricted list.

Same set by construction: filter(filter(xs, in_closure), named) ==
filter(filter(xs, named), in_closure), so no classification changes.
20 lens witnesses green in 8259ms.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 1eb10b48f29 rather than argued — the finding was correct on inspection: ci_v1_compiler_tests_compile_gate_shell_emit_scaffold + trigger existed, the parity step's run: had neither.

  • ci_interpreter_parity_controls_shell_emit_scaffold (Scaffold { dissolves_to: SingleAuthority, bind: → gunbc.ci_workflow ci_interpreter_parity_controls_run_script }) + a named dissolve-on trigger. The trigger records what the sibling's does not: this is the second consumer of one seam, not a new transport class, so it dissolves with the compile gate under shell→intent Phase 2, and it joins the srvN STEP 2 population already tracked for the shared EAGAIN retry.
  • The marker is emitted, not only modeled — ci_interpreter_parity_controls_run_script renders it as a leading comment the way the compile gate does (regen: one added line in ci.yml, no other artifact churn).
  • Two executing witnesses, green locally: w_interpreter_parity_carrier_is_scaffold and w_interpreter_parity_run_script_carries_its_dissolution_trigger. The second exists so the disposition can't be an inert registration — a row nobody emits is registration mistaken for enforcement.

— sent from valiant-ant-57

@gunbai-bot gunbai-bot Bot changed the title Consume the G2 live-read manifest at every selection decision; retire the G1 carrier-home predicate Consume the G2 live-read manifest at every selection decision; land group_by and execute the interpreter-parity controls CI only compiled Aug 10, 2026
… group

The floor refused at its 55-minute population budget on 1eb10b4 with no
witness failing. The log names the cost: [live-read-candidates] printed 719
times, every one reporting the identical roster_rows=8563 candidate_rows=91
excluded_by_cheap_axes=8472. Summed gap preceding those lines is 17.6 minutes
of a 70.3-minute job, median 1.41s each.

run_discovery_rows is called once per queued entry group inside each worker's
loop, and it built LiveReadSelectionPlan over the COMPLETE roster on every
call. Roster, index, diff and changed paths are all invariant across the groups
one worker processes, so 718 of those 719 builds recomputed an answer that
could not differ.

The code had already caught this class one call site over: the narration site
carries a comment saying "THROUGH THE PLAN, not a second full-roster build --
it could independently recreate the 8538-request classification the execution
sites had just stopped making, moving the stall from one consumer to another."
The worker-group site is that same mistake, unfixed.

The plan is per-INDEX, not per-run: a manifest carries the subject of the index
that produced it and each width>1 worker builds its own MultiEntryIndex, so the
main thread's plan would refuse on subject mismatch at every lookup, correctly.
Per-index is therefore the strongest sharing the subject identity permits.
live_read_selection_plan_for_index is the single build site, called where each
index is built, and run_discovery_rows now takes the prebuilt plan instead of
the roster -- so the per-group rebuild is not merely removed, it is unwritable
from there: the roster it would need is out of scope.

The rationale comments that documented the removed build move onto the helper
rather than being deleted with it.

NOT CLAIMED: that this greens the floor. 70.3 - 17.6 leaves ~52.7 minutes
against a 55-minute budget -- marginally under, from an attribution-by-adjacency
estimate on one host. The redundancy is proven and removed; whether the budget
now fits is for CI to answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 10, 2026
Branch-neutral harness measures wall, prep/exec, and classification_production
on the same injected incident subject so control (G1 over-selection) and
candidate (G2 manifest + narrowed prep/exec) can be compared by head_sha.

Co-authored-by: Cursor <cursoragent@cursor.com>
gunbc-ci-auto-heal and others added 2 commits August 10, 2026 06:00
…acts

The floor's budget refusal had been masking this: with the per-index plan fix
landed, the walk reached batch 3 and a real witness went red.
w_host_unspecified_traversal_classified_for_map_projections asserted
count(primitive_d0_traversal_facts) == 4, and group_by's traversal row made it
five.

Bumping 4 to 5 is the move DESIGN section 5 rules out. That literal is measured
from the same tree it checks, so automating its update collapses the assertion
to measure() == measure(); the manual update was its entire content. It went red
for an addition it has no opinion about, while saying nothing about whether the
two order-exposing primitives are still present and still classified.

What the count was actually guarding is non-vacuity, so state that directly.
primitive_traversal_classified_exactly_once asserts by IDENTITY that map_keys
and map_values are each present, classified HostUnspecifiedOrder, exactly once.
Exactly-once additionally refuses a duplicate row for one identity, which a
count over the whole population cannot see.

STRICTLY STRONGER, PROVEN BY EXECUTION rather than argued. Perturbation: drop
primitive_map_values_traversal from the population. The count returns to 4 and
the classifier's `all` never sees the absent row, so the OLD assertion passes on
a genuinely broken state; the new one FAILS. Restored, it passes again. Both
directions run, so this is not a loosening that happens to green the branch.

Also carries the naming residue marker owed from the #8055 review thread: the
WholeTreeFactSubject -> CompleteBoundRequestFactSubject rename now has a
dissolve-on annotation rather than only a sentence. A residue stated in prose is
not counted, and an uncounted residue does not rank for fixing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
prose_row_introduction_gate_passes refused the previous commit: it added
primitive_traversal_identity_join_note: String under dag/std/, a path whose
prose has already been migrated to authored // annotations. DESIGN section 4c
is explicit that an ordinary String declaration whose sole purpose is
commentary is misplaced data, and the gate enforces exactly that on migrated
paths.

The rationale is unchanged and still irreducible -- it records why a count
equality was the wrong oracle, which the declaration cannot say structurally.
It moves into a leading // block above primitive_traversal_classified_exactly_once.

The witness still passes after the conversion, so the annotation parses and the
function is intact.

Note the gate reads COMMITTED state, not the working tree: it kept naming the
removed row until this commit existed. Worth knowing before concluding a local
edit cleared it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 10, 2026
Handover from valiant-ant-57 (2026-08-10) so composition control and fleet
log retrieval are not re-derived when the harness runs post-#8055.

Co-authored-by: Cursor <cursoragent@cursor.com>
@briansrls
briansrls merged commit 7f7da93 into main Aug 10, 2026
8 of 10 checks passed
@briansrls
briansrls deleted the session/valiant-ant-57 branch August 10, 2026 12:57
gunbai-bot Bot pushed a commit that referenced this pull request Aug 10, 2026
Branch-neutral harness measures wall, prep/exec, and classification_production
on the same injected incident subject so control (G1 over-selection) and
candidate (G2 manifest + narrowed prep/exec) can be compared by head_sha.

Co-authored-by: Cursor <cursoragent@cursor.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 10, 2026
Handover from valiant-ant-57 (2026-08-10) so composition control and fleet
log retrieval are not re-derived when the harness runs post-#8055.

Co-authored-by: Cursor <cursoragent@cursor.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 10, 2026
Control tags e1c688a (pre-merge main), candidate tags 7f7da93
(#8055 merge); harness branch tracks current main for tooling only.
Documents the #8095 forensics vs exact-subject split.

Co-authored-by: Cursor <cursoragent@cursor.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 10, 2026
The revert's one conflict was .github/workflows/ci.yml, a GENERATED artifact.
Resolving it by taking the revert side of the hunk silently reverted one line
of #8100 as well: collect_fleet_convergence (a READ) lost its
`|| github.event_name == 'workflow_dispatch'` arm, re-fusing the read to the
push-only writes that #8100 had just unfused.

A content spot-check did not catch it. I checked that #8095's floor-phase-journal
additions survived and that #8055's interpreter-parity step was gone, and both
were true — but verifying the markers you think of is not verifying the file.
For a derived artifact the generator is the authority and regeneration IS the
resolution; a spot-check only samples it.

Regenerated from the reverted sources: one line, restoring #8100's arm. The tree
now equals generator output, so the drift gate has nothing to say.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 10, 2026
Reverting #8055 wholesale brought back text #8055 had independently
corrected, in two authority carriers:

- gunbc.ci_layer_roots falsifier_rehomed_bin_wet_rows: the
  floor_skip_discovery row's reason claimed the row duplicates the ci
  job's per-PR 'Affected-set selection control' step. #7446 demoted that
  step to the falsifier cadence, so the per-PR coverage it cites stopped
  existing. Restored origin/main's corrected reason verbatim. The 289s
  measured wall carries the rehome on its own.

- v2.test.lens_enforcement.gate_test live_tree_disposition_note cited
  LIVE_READ_CARRIER_HOME_MODULES_V0, a name that resolves nowhere; the
  live symbol live_read_carrier_homes_v0 survived the revert.

Found by deriving the check set (mixed +/- hunks vs origin/main) rather
than stopping at the reported instance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Aug 10, 2026
…8055) (#8099)

* Add P3 cohort probe for ClassificationCostStanding A/B (#8055).

Branch-neutral harness measures wall, prep/exec, and classification_production
on the same injected incident subject so control (G1 over-selection) and
candidate (G2 manifest + narrowed prep/exec) can be compared by head_sha.

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

* Restrict incident diff injection to the incident P3 subject.

Discovery corpus runs should not inherit the two-entry synthetic touch unless
the caller supplies GUNBC_CI_DIFF_UNIFIED explicitly.

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

* Address review 50935: single incident roster authority, drop shell runner.

Export selection_control_incident_subject_roster from cli_run for the witness
and P3 probe; remove the unmarked tools/p3_cohort_ab_runner.sh and document
direct p3_cohort_probe invocation in the receipt README.

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

* Fix floor CI: register P3 receipt doc and classify group_by traversal.

Bind p3-classification-cost-ab-receipt in doc_graph_roots (orphan README
moved to docs/plans/*.md convention). Classify group_by as
HostUnspecifiedOrder and bump the primitive_identity traversal census to 5.

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

* Record P3 A/B method notes: intersection groups, calibration, log trap.

Handover from valiant-ant-57 (2026-08-10) so composition control and fleet
log retrieval are not re-derived when the harness runs post-#8055.

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

* Declare explicit A/B subject pins in P3 receipt (#8055 post-merge).

Control tags e1c688a (pre-merge main), candidate tags 7f7da93
(#8055 merge); harness branch tracks current main for tooling only.
Documents the #8095 forensics vs exact-subject split.

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

* Add tag-dispatch selection pre-flight check to P3 receipt.

Require one-arm grep of selection_state and selected/total entry groups
before committing paired fleet runs; sequence behind lane read.

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

* Record vivid-gull-155 as P3 lane read and A/B execution owner.

eager-owl-483 holds on dispatch; documents planted-red falsifier read convention.

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

* Fix P3 receipt: document harness overlay on pinned arm SHAs.

The probe does not exist at control/candidate pins; paired runs require
#8099 merge then worktree overlay (arc1 precedent). Add apply-able patches
and replace in-tree-only build instructions (review 50950).

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

* Revert group_by traversal classification from harness PR.

HostUnspecifiedOrder on group_by is a std determinism claim, not measurement
plumbing; belongs in a separate PR with a discriminating witness (review 50953
/ valiant-ant-57). w_host_unspecified_traversal_classified still passes.

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

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 10, 2026
main gained three commits while this PR sat, one of which (#8099, the P3
ClassificationCostStanding A/B harness) exists solely to measure #8055's
mechanism AND depends on its code: LiveReadSelectionContext at six sites,
the classification invocation/production counters, and a new
p3_cohort_probe bin.

So reverting #8055 alone leaves main not compiling. Three readings and
only one is coherent:

  keep #8099, drop the revert   -> main stays red
  revert #8055 only             -> main does not compile
  revert both                   -> the harness loses its subject with it

Took the third. It is forced, not chosen: no resolution keeps #8099 and
removes #8055.

Resolution discipline, from this PR's own earlier ci.yml lesson:

- resolved HUNK BY HUNK, not `checkout --ours`. #8102 also touched
  cli_run.rs and merged cleanly outside the conflict regions; a whole-file
  take would have silently dropped its 84 lines, which is exactly how
  #8100 was lost earlier today.
- the p3_cohort_probe bin arrived with NO conflict, so nothing flagged
  it. Found by grepping for dangling references to the removed symbols
  after resolving. A clean merge is not evidence.
- #8099's selection_control_incident_subject_roster sits outside the
  conflict regions and SURVIVES: it is a shared authority extraction, not
  #8055 machinery.

Oracle: cargo build --release -p v1-compiler --bins, exit 0, 3m30s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 10, 2026
…tions, redding main (#8104)

* Revert "Consume the G2 live-read manifest at every selection decision; land group_by and execute the interpreter-parity controls CI only compiled (#8055)"

This reverts commit 7f7da93.

* Regenerate ci.yml: the hand-picked conflict side had dropped #8100

The revert's one conflict was .github/workflows/ci.yml, a GENERATED artifact.
Resolving it by taking the revert side of the hunk silently reverted one line
of #8100 as well: collect_fleet_convergence (a READ) lost its
`|| github.event_name == 'workflow_dispatch'` arm, re-fusing the read to the
push-only writes that #8100 had just unfused.

A content spot-check did not catch it. I checked that #8095's floor-phase-journal
additions survived and that #8055's interpreter-parity step was gone, and both
were true — but verifying the markers you think of is not verifying the file.
For a derived artifact the generator is the authority and regeneration IS the
resolution; a spot-check only samples it.

Regenerated from the reverted sources: one line, restoring #8100's arm. The tree
now equals generator output, so the drift gate has nothing to say.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Restore two stale citations the blanket revert reintroduced

Reverting #8055 wholesale brought back text #8055 had independently
corrected, in two authority carriers:

- gunbc.ci_layer_roots falsifier_rehomed_bin_wet_rows: the
  floor_skip_discovery row's reason claimed the row duplicates the ci
  job's per-PR 'Affected-set selection control' step. #7446 demoted that
  step to the falsifier cadence, so the per-PR coverage it cites stopped
  existing. Restored origin/main's corrected reason verbatim. The 289s
  measured wall carries the rehome on its own.

- v2.test.lens_enforcement.gate_test live_tree_disposition_note cited
  LIVE_READ_CARRIER_HOME_MODULES_V0, a name that resolves nowhere; the
  live symbol live_read_carrier_homes_v0 survived the revert.

Found by deriving the check set (mixed +/- hunks vs origin/main) rather
than stopping at the reported instance.

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>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 10, 2026
The cherry-pick carried #8055's ci.yml, which no longer equals generator
output against current authorities. heal_generated_artifacts cannot
repair it: .github/workflows/** is author-commit-required by
construction (heal stages a fixed enumeration that excludes workflow
paths), so it uploaded a heal-author-commit-required bundle and refused
rather than committing.

Regenerated with a gunbc built from THIS branch (17:08), which is the
instrument CI uses: ci.yml checks out github.sha and compiles its bins
from that checkout. Generator run: 0 problems, ExitSuccess.

Not hand-resolved — #8104 demonstrated why a spot-check of a generated
artifact is insufficient.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 10, 2026
…rations instead of refusing selection (#8110)

* Restore #8055 mechanically (cherry-pick 7f7da93), without #8099

Re-land base. Restores the G2 manifest consumption at every selection
decision, group_by, streaming value_hash, the accumulator fix, the
identity-join oracle, and the per-index selection-plan hoist.

NOT included, deliberately:
- #8099's P3 cohort probe. It was measurement plumbing for this
  implementation, not needed to establish behavior, and its removal in
  #8104 was forced rather than incidental.

Conflicts resolved:
- gate_test live_tree_disposition_note: took the #8055 side. Both sides
  already carry the corrected symbol live_read_carrier_homes_v0; they
  differ only by a 'G1' qualifier, which is wrong once G2 is restored.
- ci.yml: taken from the cherry-pick, to be REGENERATED from authorities
  before push rather than hand-resolved (#8104's own lesson).

main's ci_layer_roots #7446 citation correction verified present after
the pick.

THIS COMMIT ALONE STILL REDS MAIN. The test-data gap repair follows in
this branch; nothing is pushed until it lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Regenerate ci.yml from authorities after the #8055 restoration

The cherry-pick carried #8055's ci.yml, which no longer equals generator
output against current authorities. heal_generated_artifacts cannot
repair it: .github/workflows/** is author-commit-required by
construction (heal stages a fixed enumeration that excludes workflow
paths), so it uploaded a heal-author-commit-required bundle and refused
rather than committing.

Regenerated with a gunbc built from THIS branch (17:08), which is the
instrument CI uses: ci.yml checks out github.sha and compiles its bins
from that checkout. Generator run: 0 problems, ExitSuccess.

Not hand-resolved — #8104 demonstrated why a spot-check of a generated
artifact is insufficient.

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>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 11, 2026
#8120)

#8110 was merged while its own PR body reads "DRAFT — INCOMPLETE. The
branch in its current state still reds main. Do not merge, do not flip
to ready", and its status table marks the `test data` repair "NOT DONE —
this is the whole point of the branch". Merging it put #8055's
load-bearing G2 live-read classification back on main without the root
support it needs, which is exactly the class #8104 reverted #8055 for.

The located refusal, from the head run of 1741c08 (job 93623181081):

  AFFECTED-SET REFUSAL cause=LiveReadClassificationRefused
    entry=src/v2/test/claim/manual/derivable_coercion_nickname_triple_test.dag
    function=witness_claim_crux_substrate_gap
    detail=no fn-arrow declaration is named
           v2.test.manual.derivable_coercion_nickname_triple.witness_claim_crux_substrate_gap

Checked in code, not inferred from the title: the lens's root population
is fn_arrow_decl_facts_index_live alone, and fn_arrow_decl_rows_from_facts
skips every kind that is not FnItem/FuncItem, so no data root can bind.
The live population is 29 `test data` declarations across 5 files.

The refusal itself is correct and is not weakened here. What is removed
is the consumer that made an unrepaired classifier load-bearing.

Mechanically clean: no commit merged after #8110 touches any file it
changed, so every reverted file is byte-identical to its pre-#8110
content — verified, not assumed.

NOT CLAIMED: that this greens main. A second, independent failure
predates #8110 — the witness non_fold_residue_no_unrostered_or_stale was
already the red on ca64dba (the #8055 revert) at 15:45 on 2026-08-10.
This revert removes one of two causes; that one is being worked
separately.

What #8110 also carried and this revert therefore removes — none of it
implicated in the defect, all of it re-landable with the repair:
#8055's group_by primitive, the streaming value_hash, and the manifest
accumulator fix.

Operator authorized the revert directly (2026-08-11), having read the
draft's second half.


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>
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>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 11, 2026
The live-read classifier asks what must be evaluated to produce a declaration's
value, and what that evaluation reads. Nothing in that question is about
functions -- but its root carrier is FnArrowDecl, its binder parameter is named
fn_name, and gunbc.ci_spec's witness row field is named function. So a
declaration form is welded into a type answering a question orthogonal to it.

The cost is visible: 'test data name: Bool = expr' is an enrolled witness form
(the test marker precedes BOTH fn and data in the grammar; the hygiene authority
carries test_fns and test_data as separate populations), and it arrives at a
function-only lookup as ZERO MATCHES -- byte-identical to a name that does not
exist. Two states, different remedies, one outcome: it cannot be counted, named,
or told apart from a broken reference.

EvaluationRoot is the shape both forms produce. A function projects its body and
params; a data declaration projects its initializer through the same named child
the grammar emits and mandatory_tag already consumes -- a projection over
existing structure, not a new parser. It takes the surface node rather than
DataInitDecl deliberately: that carrier holds literal_fp, a fingerprint of a
literal, which cannot be walked.

EvaluationRootUnavailable { subject, form, cause } is the state the chain lacks.
It names an EXISTING declaration whose expression could not be projected, and is
distinct from any 'no such declaration'. That split closes the path this exists
to prevent: form not representable -> no root -> empty walk -> no carrier ->
LocalRead -> licenses a SKIP. Under-selection presents as a faster green floor
rather than a failure, which is why it outranks over-selection.

DELIBERATELY INERT. Nothing imports it. #8055 and #8110 were both reverted for
making the classifier load-bearing ahead of its root support; the abstraction
lands and is witnessed first.

Evidence, by execution (claim_batch cannot run these -- its whole-tree precompute
fails on doc_graph_roots.dag v2.* imports, reproduced on pure origin/main -- so
each witness ran through gunbc run):

  5/5 green on the real code
  control 1 returns false against a mutant whose projection discards decl.output
  and returns a fresh empty node

That mutation initially left control 1 GREEN. The assertion was right and the
FIXTURE was vacuous: the specimen body was an empty synthetic node, structurally
identical to what the hollow projection returns, so 'carries the body' and
'carries a fresh empty node' were the same value. The specimen now carries a
named child edge. A witness that cannot fail is not evidence, and this one could
not until the fixture was fixed.

Co-Authored-By: Claude Opus 5 (1M context) <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