Repository navigation
Consume the G2 live-read manifest at every selection decision; land group_by and execute the interpreter-parity controls CI only compiled - #8055
Conversation
… 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>
…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>
Floor over-budget: measured, and it was not this branch's changeTwo runs ( So I measured it directly instead of arguing from code shape — same host, same entry, release binaries built from each tree, three runs each:
~1.44x, reproducible, and it matches CI's 29 min → ≥47.5 min (~1.6x). Mechanism: the whole delta is in 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. |
Correction: the merge did NOT fix this, and the producer IS on the ordinary floor pathTwo 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. 2. "Zero 3. The merge did not fix CI. What the run actually shows — the decisive number, which I should have pulled first:
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 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. |
…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>
Step 6 first attempt: measured, worse, revertedRecording 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: 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: The attempt: build a 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: What this implies for step 6: a grouped index needs either the native 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; |
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>
|
Fixed in
— sent from valiant-ant-57 |
… 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>
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>
…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>
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>
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>
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>
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>
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>
…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>
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>
…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>
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>
…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>
#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>
…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>
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>
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_byfold, 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 oftouched_pathswasis_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.dagdiff — the2 selected / 1 expectedincident, and theSelectionSuperset ... selection did not narrow the floorline the floor reports.The manifest existed, was memoized, and had unit tests. It had no consumer on the decision path.
One context, one decision
LiveReadSelectionContextanswers axis (iv) throughruntime_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_countfed the precompute'stouched_runtime_dependency_entry_countthrough 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_livereflects 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,FailClosedall 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_livereads the exactMultiEntryIndexalready 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 aScaffoldwith aSingleAuthoritydissolution 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
WholeTreeFactSubjectwhile it now means complete-for-this-bound-request.CompleteBoundRequestFactSubjectis 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 removedmerge_g1_and_g2_carriersgoes with them, plus two further stale citations of the deleted const:v2.lens.enforcement.gate_testprose, and the design doc's status line describing the G1 interim as the production axis.lying_substrate_inputs_only_stamp_censuswas a real semantic consumer, so rather than delete a merge gate it is re-expressed on the authority that now decides: aSubstrateInputsOnly-stamped declaration classifyingRuntimeRead— or refusing — is a lying stamp. Its subject grain changed from "every*_test.dagwith 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_overaccumulated withlist_snoc_item=list_append(acc, single), andlist_appendis afold_list_rightrebuilding 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_byas a real primitive. The per-callee scan was re-cut around a grouped index rather than repeated linear filters.group_bylands parallel to the existingindex_byacross every surface —primitive_identity, theprimitives.dagcontract, interpreter roster, Rustrt_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_hashmaterialized strings and lists to hash them. The A/B receipt, both arms unprofiled, same subject, same binary shape: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_productionstays 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-runand executed nothing, so every#[cfg(test)]control inside the lib — including thevalue_hashparity 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.--lib, two module filters,--test-threads=1because 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.floor_skip_discovery_witness, which does execute:live_read_selection_narrows_incident_subject_to_touched_entryassertsselected_entry_groups == 1of 2 (the grain the incident was measured in — row totals can read 2/1/1 while both entry groups were selected and resolved, which isSelectionSupersetwearing a passing row count);stop_line_population_does_not_widen_incident_subjectseparates 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 aScaffolddisposition and a named dissolve-on trigger, emitted as a comment on therun: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_closurepluscall_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_witnessruns 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 withcold-corpusfailing andaggregate-verdictfailing behind it.cold-corpusruns 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:
LanePredictOnlydecides 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.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):
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_rootproduct 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:
RegressedandWithinExistingEnvelopecan 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 completedLanePredictOnlyobservation. 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-controlintentionally 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 -> 2assertion changes.