Skip to content

The callee lookup answers by index, so the determinism walk costs what it reaches - #10753

Closed
gunbai-bot[bot] wants to merge 2 commits into
mainfrom
session/bright-badger-637
Closed

gunbai-bot[bot] wants to merge 2 commits into
mainfrom
session/bright-badger-637

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

The defect

v2.std.fn_index decls_matching_callee evaluated filter(decls_in_modules(decls, closure_modules), name == callee) once per callee per reached declaration — a full scan of the candidate pool plus one module-membership pass per closure module, per lookup, of a value that is invariant for the entire walk.

gunbc#10744 separated the two candidate cost models by measurement rather than by argument: O(reached × pool) agrees within 1.9x across file and directory grain; O(reached²) is off by 44x. The cost tracks the pool the walk was handed, not what it reaches — under a hundred reached declarations against a pool of 1500–2000. At file grain pool == n, so the shape reads as quadratic-in-reached and the wrong term gets optimised; only directory grain decouples them.

The repair

callee_index_of computes the closure-scoped population once and keys it by declaration name; call_reachable_decls_go threads that index instead of (decls, closure_modules), and each callee is one map_lookup.

Map is the host-intercepted primitive (free_call.map_insert / method_call.lookup over im::HashMap), not the closure chain its .dag spelling reads as — checked rather than assumed, because a secretly-linear map would have given an index-shaped repair with scan-shaped cost.

The subject going in is unchanged — same decls, same closure_modules. This is not the scope-pool_roots move #10744 names as the trap.

The answer is identical, including order and multiplicity

decls_in_modules concatenates per module in module order and preserves pool order within a module; the fold appends each declaration to its bucket in that same traversal. So a bucket is the old filter's output verbatim — duplicates included, since a declaration legitimately matches twice when a closure names both a module and its parent under the dotted-prefix membership test. Deduplicating there would have been a semantics change wearing a performance change's clothes, and invisible in every timing number.

The witness widens back to directory grain

determinism_live_intra_holds becomes determinism_transitive_live_closure_holds again, over pool_roots=['src/v2/lens'] with pool_files=[], following callee edges across the whole lens closure. The name moves with the subject in both directions; a witness whose name outlives what it checks is the §3 meaning fork.

The subject was narrowed to one file because the directory walk was interrupted_before_verdict. Widening it is the point of the repair, not a side effect.

Reading the numbers — one fact that is not in #10744

The v1 interpreter carries an eval-frame call memo, on by default (GUNBC_EVAL_MEMO), keyed by fn identity × argument identity with an Rc-identity fast path. decls_matching_callee is a plain-valued call, so repeated lookups of the same callee were already served. The 10137ms is therefore paid once per distinct callee, not once per callee occurrence. That does not weaken the O(reached × pool) finding — each distinct callee still scanned the whole pool — but a figure whose denominator is ambiguous is the transcribed-number problem in a different costume, so it is stated here and in the witness annotation.

The instrument is named rather than transcribed (§6): this entry under claim_batch --source-root dag --source-root src/v2, which charges each witness separately. The charged directory-grain measurement in this PR is the required floor's own run of the widened witness.

Still to land in this PR

gunbc.rung_drop.determinism_transitive_reachability retires on its trigger — a directory-grain verdict within the line without narrowing the subject — and the retirement lands here once the floor has produced that measurement, with the receipt. Retiring it without widening the subject would discharge the receipt while leaving the coverage loss; widening without retiring leaves a drop standing over a defect that is gone.

Explicitly not credited with the cost

The three shapes #10744 records — decl_in_list's linear scan and the copied accumulators in call_reachable_decls_go, determinism_transitive_findings's concat fold (free while the corpus is clean, quadratic exactly when findings appear), and determinism_live_closure_modules's snoc over a list that is empty today — are real and are not touched here, so a green cannot be read as their repair.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CQuBs4mLu9ArojSWFiRcey

Brian Searls and others added 2 commits September 7, 2026 09:32
…t it reaches

v2.std.fn_index.decls_matching_callee evaluated
filter(decls_in_modules(decls, closure_modules), name == callee) once per callee
per reached declaration -- a full scan of the candidate pool plus one
module-membership pass per closure module, per lookup, of a value that is
INVARIANT for the whole walk. gunbc#10744 bracketed both populations and
separated the models by measurement: O(reached x pool) agrees within 1.9x across
file and directory grain, O(reached^2) is off by 44x. The cost tracks the pool
the walk was handed, not what it reaches.

callee_index_of now computes the closure-scoped population once and keys it by
declaration name; call_reachable_decls_go threads that index instead of
(decls, closure_modules), and each callee is one map_lookup. Map here is the
host-intercepted primitive, not the closure chain its .dag spelling reads as.

The answer is unchanged, including order and multiplicity: decls_in_modules
concatenates per module in module order and preserves pool order within a
module, and the fold appends each declaration to its bucket in that same
traversal, so a bucket is the old filter's output verbatim -- duplicates
included, since a declaration legitimately matches twice when a closure names
both a module and its parent under the dotted-prefix test.

The witness widens back to directory grain, which is what the repair is for.
determinism_live_intra_holds becomes determinism_transitive_live_closure_holds
again over pool_roots=['src/v2/lens'], following callee edges across the whole
lens closure. The subject going in is unchanged -- same decls, same
closure_modules -- so this is not the scope-the-pool move #10744 names as the
trap. The rung drop's retirement follows in this PR once the required floor has
produced the charged directory-grain measurement, which is its trigger.

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

gunbai-bot Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

This is not the repair. Measured, and my own result was killed by its control.

claim_batch on a BuildBuddy runner, both arms in one dispatch on one machine, the widened witness identical on both sides, only src/v2/std/fn_index.dag swapped:

arm determinism_transitive_live_closure_holds the_live_subject_is_not_empty (control)
BEFORE (rescanning lookup) 6068ms PASS 288ms
AFTER (indexed lookup) 7267ms PASS 358ms

Both arms are 12–14x the 500ms line. The index does not reach the trigger, and the indexed arm is the slower of the two.

Read the control before reading the delta

the_live_subject_is_not_empty does not call the walk at all, so this change cannot affect it. It moved +24.3%; the subject moved +19.8%. A control that cannot be affected moving more proportionally than the thing under test means this pair does not discriminate in either direction — I cannot claim the index is slower, and I certainly cannot claim it is faster. n=1 per arm is not a measurement.

Pre-registered verdict rule for the alternating run, written before its numbers exist

Three alternating BEFORE/AFTER repetitions are running. Each run is normalised by its own control (subject_cpu / control_cpu) so machine drift cancels within the run. Then:

  • HARMFUL iff min(AFTER normalised) > max(BEFORE normalised)
  • BENEFICIAL iff max(AFTER normalised) < min(BEFORE normalised)
  • NEUTRAL otherwise — the ranges overlap

Stated in advance so the alternating run cannot become a hunt for the sign I expected.

Why the index was probably always going to be neutral

I reported early that the interpreter's eval-frame call memo (GUNBC_EVAL_MEMO, on by default, keyed by fn identity × argument identity) already served repeated lookups of the same callee. I did not follow that through. decls_in_modules(decls, closure_modules) is itself a plain-valued call with hashable arguments — so the interpreter had already hoisted the loop-invariant. It was evaluated once and served from the memo thereafter.

The "full scan of the candidate pool per lookup" this PR was built to remove was not being paid. The index removed nothing and added a fold of ~1500–2000 map_inserts to do it.

An observation that belongs on the record, with its grain named

gunbc.rung_drop.determinism_transitive_reachability records the directory-grain walk as interrupted_before_verdict. On this runner, at directory grain, it reaches a verdict — 6–7 seconds, PASS, not interrupted. Whether the per-claim safety deadline is armed on the required floor as it is not under claim_batch is the open question, and until that is answered the drop's cause sentence is unverified. Not repaired in this lane; recorded so it is not lost.

Disposition

Not merging. A change that buys nothing measurable and adds a fold of 1500–2000 map inserts is redundant work (§2), and "linear by construction" argued without a displaced cost is the purity trap (§6) — priced in elegance rather than in a cost someone pays. If a later attribution finds something genuinely pool-proportional and this index becomes the repair for that, it lands then, with the measurement.

Next: attribute where the 6 seconds actually goes, before touching another line.

@gunbai-bot

gunbai-bot Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Closing: this is not the repair, and it should not sit open looking like a candidate.

The measurement is in the comment above. Both arms are 12–14x the 500ms line at directory grain, so the index does not reach gunbc.rung_drop.determinism_transitive_reachability's trigger under any reading of the delta — and the delta itself does not discriminate, because the positive control moved more proportionally than the subject.

Keeping the index on shape grounds would be the purity trap (§6): linear-by-construction argued in the absence of a displaced cost, paid for with a fold of 1500–2000 map inserts (§2). If a later attribution finds something genuinely pool-proportional and this index becomes the repair for that, it lands then, with the measurement that makes it one.

Nothing here is retired. The drop stands, its subject stays narrowed to one file, and the coverage loss it records is unchanged.

Carried forward out of this lane, so it is not lost with the branch:

  • The eval-frame call memo had already hoisted the loop-invariant this PR set out to hoist — recorded on A rung drop's restoration trigger named a term that is not the cost #10744, whose O(reached × pool) fit survives while its mechanism sentence does not.
  • At directory grain the walk reaches a verdict (6–7s) rather than being interrupted_before_verdict as the drop records, under claim_batch where the per-claim deadline is not armed. Grain named; not repaired here.

Next: attribute where the 6 seconds actually goes, before anyone touches another line.

@gunbai-bot gunbai-bot Bot closed this Sep 7, 2026
briansrls pushed a commit that referenced this pull request Sep 7, 2026
…s real

Six arms, two repetitions, one dispatch, agreeing within 1%. Acquisition over
the lens pool 592-597ms; the closure list 9ms; the reachability walk
4399-4412ms; the walk PLUS the determinism fold over every reached
declaration's whole subtree 4341-4351ms.

THE FOLD IS FREE, which refutes the other obvious hypothesis -- including the
one this branch's own PR body advanced. Adding determinism_findings measured
BELOW the walk alone in both repetitions. So its concat accumulator is not the
cost either, and a lane that optimises it will move nothing.

THE WALK IS POOL-SENSITIVE AT A FIXED REACHED SET. Holding the entry and the
closure fixed -- lens modules only, so the reachable set is identical -- while
enlarging only the candidate pool from src/v2/lens to src/v2/lens plus
src/v2/std: acquisition 592-597ms -> 1905-1950ms, walk 4399-4412ms ->
8474-8637ms. This is the arm neither #10744 nor #10753 ran, and it is the one
that separates the fit from the mechanism: #10744's pool term is corroborated,
and it lives inside the reachability walk.

WHICH construct inside the walk is pool-proportional remains unattributed. The
per-lookup rescan is refuted, so it is something else; one candidate a reader
should not skip is the eval-frame memo's own keying, which must derive a key
from the pool-sized argument on every call -- a provider that removes a
pool-sized scan can reintroduce a pool-sized cost in its key. Written as the
hypothesis it is.

Two honesty items. Whether each arm re-pays acquisition or is served from a
cross-witness share is not pinned by this run, so the ratios are directional
and not quotable as exact. And interrupted_before_verdict is a disposition
shared across unrelated subjects -- it appears on main-tip receipts for
outside_modeled_guarantee_witness_test and carrier_reference_integrity too --
so the row no longer lets a reader infer it names this population.

Recorded and NOT recorded as a cost result: on main tip 9b341e1 this witness
appears only in a roster membership list, not on a BLOCKING line. Those are
consecutive main tips rather than a controlled comparison, so absence of a
blocking line settles urgency, not cost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQuBs4mLu9ArojSWFiRcey
briansrls pushed a commit that referenced this pull request Sep 9, 2026
…hat from is refuted (#10757)

* A restoration trigger prescribed an index; the mechanism it derived that from is refuted

gunbc#10744 corrected this drop's cause from the number of reachable
declarations to a rescan of the candidate pool, and derived a trigger from it:
the callee lookup answers BY INDEX rather than by scanning the pool. That
mechanism is refuted by execution, and the trigger it generated is therefore a
repair prescription pointing at something that is not being paid.

v2.std.fn_index.decls_in_modules is a plain-valued call with hashable
arguments, and the v1 interpreter's eval-frame call memo is on by default
(GUNBC_EVAL_MEMO), keyed by fn identity x argument identity. The loop-invariant
the trigger asked a lane to hoist had already been hoisted by the interpreter:
it is evaluated once per walk and served from the memo thereafter, as are
repeated lookups of the same callee.

gunbc#10753 built exactly the prescribed index and measured it at directory
grain -- three alternating repetitions in one dispatch on one machine, only
src/v2/std/fn_index.dag swapped. The verdict rule was pre-registered before the
numbers existed: normalise each run by its own positive control, HARMFUL iff
the AFTER range lies entirely above BEFORE. The ranges are disjoint (BEFORE
19.90/20.06/20.11, AFTER 23.07/23.51/23.56), so the index ADDS about 1.1
seconds and removes nothing. Both arms remain twelve to fourteen times the
per-claim line.

The FIT is unchanged and still good evidence -- O(reached x pool) agrees within
1.9x across two grains where O(reached^2) misses by 44x. The MECHANISM behind
it is now UNESTABLISHED. A correct fit carrying a false mechanism is the more
dangerous artifact, because it keeps recruiting repairs aimed at the wrong
thing while looking already diagnosed, which is what it did to #10753.

So the trigger is WITHDRAWN, not weakened. A trigger is a prescription; leaving
it standing with a "the index did not help" note beside it would be the
misleading artifact this row exists to prevent. The next trigger is an
ATTRIBUTION rather than a repair: separate acquisition, the reachability walk
and the determinism fold, and distinguish cost tracking the POOL from cost
tracking what is REACHED by holding the closure fixed while the pool varies.
Only then may this row name a repair.

The SUFFICIENT FOR clause is untouched, including WITHOUT NARROWING THE
SUBJECT. Scoping pool_roots to the reachable modules remains the trap.

One observation at the grain it was taken: under claim_batch the directory-grain
walk executes, completes and passes at 6-7s -- not interrupted. That establishes
executability and nothing about the required floor, whose disposition for this
claim is unpinned, so this row's interrupted_before_verdict cause is recorded as
unverified pending required-floor instrumentation -- not "cannot finish", and
equally not "finishes over budget on the floor".

docs/design-rung-drops.md regenerated; the whole-tree regen left
docs/design-failure-modes.md byte-identical, so this contends with nothing.

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

* Attribute the walk: the determinism fold is free, and the pool term is real

Six arms, two repetitions, one dispatch, agreeing within 1%. Acquisition over
the lens pool 592-597ms; the closure list 9ms; the reachability walk
4399-4412ms; the walk PLUS the determinism fold over every reached
declaration's whole subtree 4341-4351ms.

THE FOLD IS FREE, which refutes the other obvious hypothesis -- including the
one this branch's own PR body advanced. Adding determinism_findings measured
BELOW the walk alone in both repetitions. So its concat accumulator is not the
cost either, and a lane that optimises it will move nothing.

THE WALK IS POOL-SENSITIVE AT A FIXED REACHED SET. Holding the entry and the
closure fixed -- lens modules only, so the reachable set is identical -- while
enlarging only the candidate pool from src/v2/lens to src/v2/lens plus
src/v2/std: acquisition 592-597ms -> 1905-1950ms, walk 4399-4412ms ->
8474-8637ms. This is the arm neither #10744 nor #10753 ran, and it is the one
that separates the fit from the mechanism: #10744's pool term is corroborated,
and it lives inside the reachability walk.

WHICH construct inside the walk is pool-proportional remains unattributed. The
per-lookup rescan is refuted, so it is something else; one candidate a reader
should not skip is the eval-frame memo's own keying, which must derive a key
from the pool-sized argument on every call -- a provider that removes a
pool-sized scan can reintroduce a pool-sized cost in its key. Written as the
hypothesis it is.

Two honesty items. Whether each arm re-pays acquisition or is served from a
cross-witness share is not pinned by this run, so the ratios are directional
and not quotable as exact. And interrupted_before_verdict is a disposition
shared across unrelated subjects -- it appears on main-tip receipts for
outside_modeled_guarantee_witness_test and carrier_reference_integrity too --
so the row no longer lets a reader infer it names this population.

Recorded and NOT recorded as a cost result: on main tip 9b341e1 this witness
appears only in a roster membership list, not on a BLOCKING line. Those are
consecutive main tips rather than a controlled comparison, so absence of a
blocking line settles urgency, not cost.

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

* Name the instrument instead of transcribing its output (review 61856)

Two findings, both correct, both against DESIGN authorities this row itself
invokes.

FIRST: the rewrite deleted the instrument-naming sentence and expanded the
transcription. The old trigger carried a re-derivation recipe; the replacement
dropped it and copied in a full result table instead. That reproduces, one
layer up, exactly the failure this row records -- figures unreachable from the
thing that owns them, rotting untouched, with the recipe to re-derive them
deleted to make room. DESIGN section 6: name the instrument, never transcribe
its output.

The fix is a real entry point rather than fewer digits.
scratch-probe/test/claim/fn_index_attribution_probe.dag lands as the named
instrument, with the claim_batch invocation that runs it, an account of what
each arm isolates, and why arm f -- entry and closure fixed, pool enlarged, so
the reached set is unchanged -- is what separates the fit from the mechanism.
It lives in scratch-probe because it is an instrument and not a floor witness,
and its rows assert only non-emptiness; that is stated so nobody reads them as
coverage. The trigger now names it and states findings qualitatively. For the
index comparison it cites gunbc#10753, which carries its own method and
pre-registration, rather than copying that PR's figures here. Every transcribed
measurement is gone from the row.

SECOND: the annotation block and the trigger string had become two carriers of
the same content -- the memo mechanism, the verdict rule, the ranges, the
n=1 story, the fit/mechanism split, the withdrawn ruling, all in both. In a row
whose subject is a duplicated authority that is the fork it exists to prevent.
The typed field is the authority for how this drop ends, so it carries the
prescription, the evidence and the instrument. The annotation keeps only what
the field cannot: why a trigger is withdrawn rather than weakened, why a correct
fit carrying a false mechanism is the dangerous artifact, and why the numbers
are not written down.

docs/design-rung-drops.md regenerated; design-failure-modes.md byte-identical.

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

* Delete a hand-authored shell runner that was swept in by git add -A (review 61869)

attribution.probe.sh was a throwaway dispatch wrapper used to run the probe on
a remote runner. It was never meant to land; a git add -A swept it into the
commit, at repo root, with no header, no marker and no dissolution trigger.

The reviewer is right on both counts and either alone is disqualifying.

DESIGN section 6 names this shape exactly -- a hand-authored script performing
out-of-band actuation -- and section 5 admits a scaffold only with a documented,
bounded dissolution trigger, which this had none of. A root-level filename that
reads as permanent is the worst version of it.

And it forked an invocation the corpus already owns. The entry path, the three
source roots and the six function names were restated in the shell script and
again in the rung drop's restoration_trigger, so the two could drift apart
independently: rename an arm and the script silently runs a stale --functions
list while the row still reads correctly. In a PR whose whole subject is naming
the instrument rather than transcribing it, this file transcribed the
instrument's INVOCATION into a second, unmodeled medium.

Deleted rather than modelled and marked. The row's prose already carries the
recipe, so the second carrier buys nothing that would justify keeping it under
a trigger.

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

* The witness blocks intermittently, which is the word that survived two wrong ones

Two single-tip readings were made in one night, in opposite directions, and
both were wrong for the same reason: a roster appearance and a blocking
appearance are the same population sampled twice. 9b341e1 showed the
witness reaching a verdict; #10764 -- eight lines of Rust comment, no .dag --
showed it interrupted before one. Since #10764 changes nothing this row
reads, the intermittency is a property of main, not of any branch.

The row now says INTERMITTENT, which is what the original brief said before
either observation. It also records why an intermittent blocker is worse
than a steady one: the principal paying the cost is not the one who caused
it (DESIGN.md 1).

Also declares the scratch-probe exposure: fn_index_attribution_probe.dag is
cited by this row but reached by no compiled closure, so it can rot silently.
The alternative -- homing it under a source root -- would put six multi-second
claims on the floor, which is the worse trade. The exposure is stated rather
than left silent.

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

* Compiling is not running: the exposure argument answered the wrong question (review 61911)

The row justified leaving the attribution probe outside every compiled
closure by saying enrolment would put six multi-second claims on the
required floor. That priced RUNNING the arms, but the exposure is about
COMPILING them, and a compile-only enrolment buys the typecheck without
running anything. The argument was wrong, not merely weak.

The real reason is a boundary, not a trade: the compile-only surface is
gunbc.ci_layer_roots.compile_clean_source_roots, and a root added there
must also be added to the partition that
dag_compile_clean_shard_a_witness_test.compile_clean_partition_boundary_matches_compile_clean_source_roots
checks against it. That widens what four serial full-corpus compiles cover
on every push and pull request -- a roster this repository holds closed to
growth without operator sign-off, and one this lane's brief forbids
outright. The discharge stays available to whoever may perform it, and the
row now names it precisely enough to be taken in one edit.

The probe also gets the dissolution it lacked: it exists only to attribute
the cost this row leaves unattributed, so the lane that either retires the
row or names the pool-proportional construct deletes the module in the same
change. An instrument that outlives its investigation is residue and this
one is not exempt for being cited.

And arm g lands beside f, because f's whole discrimination rests on the two
walks reaching the same set -- which was an argument from "the closure
bounds the walk", a true fact about a different subject than what a given
run enumerated. g measures the two reached-set sizes and is red if they
differ; reached_sizes reports the numbers rather than only the verdict.

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

* Arm g is the home for the reached-set premise; reached_sizes was a second one

The two answered the same question -- does enlarging the pool change what
the walk reaches -- so keeping both was two carriers of one fact, which is
the failure review 61856 already caught in this same file. g survives
because it is the enrollable form and it goes red rather than reporting.

The premise it exists to test is measured and holds: both pools reach 89.
So none of the f-minus-c difference is reach and all of it is pool.

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

* The obvious falsifier cannot come out against the hypothesis; h can

GUNBC_EVAL_MEMO=0 removes the key comparisons AND the retained entries in
one move. So if the 211s frame collapses with the memo off, the keying
hypothesis and the allocation-pressure hypothesis are confirmed equally and
nothing is learned about which. An arm that cannot come out against the
thing it tests establishes nothing, which is this repository's own rule
about discriminating REDs applied to a measurement.

h does the same amount of walking as g -- two full reachability walks in one
eval frame -- while holding the pool at one pool rather than two, so frame
retention and key diversity halve while the walking does not. Against c:
~2x means the blowup needs pool diversity in the frame, so keying survives
and pressure dies; ~13x means two walks in a frame suffice regardless of
pool, which points at retention or at something per-frame that is not
keying at all.

The two walks use different entry modules deliberately. Repeating one entry
hands the second walk an argument-identical call the memo serves outright,
which would measure the memo working rather than the frame cost under test.

The header also now records that claim_batch reports VmHWM per PROCESS and
not per witness, so peak RSS is attributable to an arm only when that arm is
alone in the process -- and that frame structure is itself a variable, since
c and f are separate frames while g and h are one frame each. A 211-versus-16
ratio invites a reader to assume different machines or different load; the
frame is the variable and the file now says so.

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

* The floor claim is a different subject from every arm measured so far

determinism_live_intra_holds runs at FILE grain over affected_set.dag with
pool_roots EMPTY, and builds its closure from those same empty roots. Arm c
runs at directory grain over a real root list. Those are not the same fold at
two values of n -- the closure term differs structurally, and an empty root
list is a term no arm above exercises.

So arms i, k and l reproduce the floor's subject term by term. l minus i is
what the walk and fold cost at the grain that actually blocks lanes, which is
the quantity a repair aimed at the floor has to move; the directory arms
answer a question about cost SHAPE and are only a magnifier if these agree.

k is not a timing arm. The rung-drop row records that the floor's closure fold
runs over an empty list, and that is why the witness is named intra rather
than transitive. If k goes red that recorded fact is stale and the readings
built on it need revisiting. It is the one arm here that can falsify a
written row.

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

* h eliminates frame sharing; m separates keying from retention

h ran two walks in one frame over one pool and came in near c rather than
near 2c. So two walks in a frame is not enough to produce the blowup, and
neither is anything per-frame that does not depend on the pool. What
survives is pool diversity -- and h cannot say which kind, because one pool
means one key universe AND one retained pool at the same time.

m takes those apart. It acquires both pools in one frame, so retention
matches g, while walking only the small one, so the repeated pool-sized
probes match c. Near c+e means two-pool retention is free and the cost needs
two pools flowing into repeated probes, which is the keying signature. Near
g means retention is the payer and the memo-keying hypothesis is dead.

Run under GUNBC_INTERP_STATS=1, which prints retained-value accounting and
VmHWM, so the memory half is measured rather than inferred from time.

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

* The shape is the finding; three mechanism sentences are dead and it outlived all of them

The row had been holding its position open for a cause. That was backwards.
What is established is not a mechanism but a shape -- the walk's cost is
proportional to the pool it is HANDED rather than to what it REACHES -- and
it is established three independent ways: the reached set is pinned equal
across a small pool and a much larger one, so reach is constant while cost
moves; and the cost ratio between those pools is the same with the
eval-frame memo on as with it off.

That third measurement is what makes it a shape rather than an
implementation detail. A cost that tracks the pool identically with a
caching tier present and absent is not a property of that tier, of any key,
or of any index. It is invariant under every mechanism proposed here and
refuted here, which is a stronger result than a mechanism, not a weaker one.
DESIGN section 2 prices a proven cost-shape defect as always worth fixing
regardless of realized n, and does not make that conditional on naming the
cause. A measured defect with no carrier is how a defect becomes ownerless.

The refuted candidate is removed rather than left standing. Its death
condition was written before the arm ran -- if pool sensitivity survives
with the memo off, keying is not the cost -- and it survives. Recording the
death keeps the next lane from spending a dispatch re-killing it.

The same arm carries a constraint the next lane will not expect: with the
memo off these arms run about two orders of magnitude slower. The memo is
not a cost on this path, it is very nearly the whole performance of it, and
it is invisible in the .dag source -- so a later simplification that
bypasses it will look clean and will make this walk a hundred times slower.

And the row now says what it does not claim. Reproduced term by term at the
floor's own grain, the claim runs at well under half the per-claim line, and
running it beside deliberately pool-diverse company on one machine does not
move it. The shape defect is real AND is not the blocker's excess. A lane
that fixes the shape should expect the intermittency to survive it.

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

* The disposition is not a bigger number, and the reading that explains every arm

Learning that the claim's own cost is well under half the line makes a
bigger line look reasonable. It is not. A budget cannot distinguish a claim
whose true cost is half the line from one measured at the line when the
environment is separately recorded as swinging by more than a factor of two
on byte-identical evaluator steps. That is a measurement trustworthiness
defect, not a threshold defect, and the repair is never a bigger number --
which is the escape hatch DESIGN section 5 forbids, stopping the refusal
without stopping the wrongness.

The row also carries a reading, labelled as one because it explains every
arm at once and that is exactly when to be careful: the eval-frame memo is
evicted per witness frame, so diversity between claims is cleaned up while
diversity within one frame is not. That predicts all three observations
without residue, and it carries the consequence a later lane must not miss
-- the within-frame blowup does not bear on this blocker, so whoever
re-derives it will have found something real and will be wrong to call it
the cause. Its payer is not retention, refuted by two independent
instruments; before those two, every arm inferred memory from time, so a
timing result and a memory story could not disagree.

And the machine-population boundary is now explicit. Every measurement
behind this row was taken on remote build executors; the line is enforced
on the required-lane CI runner. A distribution measured on the first says
nothing about the second.

The instrument gap is declared with its trigger named as a capability
rather than an artifact. The analysis already exists and must not be
re-minted -- gunbc.floor_cost_distribution models the cost as a coproduct
so a censored bound cannot be divided as a cost, and
identity_inflation_permille_at_equal_work already pairs rows by identity
and equal evaluator steps. What is missing is an actuator that can run it
on demand, and the one that exists runs only inside CI. An actuator that
reaches only a build executor does not retire the trigger, because it
cannot observe the population the refusal fires on.

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

* The blocker is accounted for, and it was never this subject

Harvest the retained required-floor claim-cost artifacts from successful
main runs, take this claim's rows, and group by eval_steps so work is held
constant -- which is what the existing pairing authority exists to do.
Eighteen of twenty recent runs share one step count. Across those eighteen,
at byte-identical evaluator work, with no change to the claim and none to
the tree, observed CPU spans nearly 1.4x and its top reaches 99% of the
per-claim ceiling.

A claim whose cost distribution at fixed work already touches its own line
needs no cost defect, no drift, no accounting hole and no mechanism to
refuse intermittently. It needs only to be sampled again. That is the
intermittency, and it is why this refuses on changes touching nothing near
it.

Two readings were proposed and withdrawn on the way there, both recorded
because both are tempting: a drift across a level change, which dissolved
once the smaller group was compared against the larger group's actual range
rather than its mean; and a small-sample variance figure that understated
the spread, which is what made the groups look separated at all. One error
twice -- a sample too small to see its own width.

The refusal is correct as a MEASUREMENT and wrong as an ATTRIBUTION, and
that distinction decides the repair. The CPU was really consumed, so the
gate is not lying; but the cost is not a property of this claim's code. A
per-claim threshold enforced on one draw from a distribution this wide is
estimating a statistic it cannot see from a single sample. Raising the
ceiling silences the wrong half and destroys the evidence, so no number
moves.

The repair is margin, and it is the pool-proportional shape already
established -- never the blocker, still the fix, and needing no separate
justification under section 2. The acceptance target is derived rather than
guessed: holding the line against that spread requires the median below the
ceiling divided by the spread, about a 10-15% reduction. And acceptance
evidence is a distribution at equal work before and after, never a green
run: a claim this near its ceiling goes green sometimes with nothing
repaired.

The trigger narrows again. CI already emits and retains the artifact on the
enforcing population, so what is missing is a HARVESTER, not an actuator --
no runner capacity, no new job. It is denominated in displaced cost: this
account was produced by hand from artifacts already on disk, and nothing in
the tree can produce it again tomorrow. But it must not land before the
provenance it needs, because the fold holds work constant so only
environment can differ and cannot say which environment either side ran in.

And a receipt for the cost coproduct, because it is a rung claim with an
execution behind it: the observation that opened this investigation was a
budget-refused reading -- a bound, not a cost. It was the most persuasive
number available and it is inadmissible in any ratio, and the model refused
it structurally rather than a reviewer having to catch it.

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

* One trigger in the trigger position, and the blocking arm is an interrupt (review 61935)

The restoration_trigger had become a withdrawal sentence followed by two
rival triggers at two different subjects -- cost attribution and measurement
trustworthiness. DESIGN section 4b(3) is explicit that a drop is retired by
its trigger and by nothing else, so the trigger is the whole check, and a
field carrying two of them at different subjects is not checkable by any
later lane. That is the untracked-stall shape, not an honest withdrawal.

The field now opens with one trigger at capability grain: the walk's cost
becomes proportional to what it REACHES rather than to the pool it is
HANDED, sufficient for determinism_transitive_holds to reach a verdict
within the line at directory grain without narrowing the subject. Everything
after that sentence is marked as evidence, not a second trigger.

Writing it that way exposed that "no repair until the cost is attributed"
was itself wrong. The trigger does not need the construct named -- it names
the cost SHAPE the capability requires, which is what was measured, and a
lane that reaches it has restored the capability whether or not it can say
which construct was responsible. So the unattributed construct is an open
question under the trigger, not a rival to it. And the trigger was REPLACED
rather than weakened: the old one prescribed an implementation that is
refuted, and a trigger naming a repair that does not work is worse than one
naming none, because it stays satisfiable while the capability stays dead.

The harvester obligation is now explicitly not a trigger of this drop --
different subject, different invalid state, its own carrier.

Separately, the blocking arm is identified and it is not an overrun. On the
runs where this claim blocks, the row is verdict_reached=FALSE with a
right-censored cost and FEWER evaluator steps than any completed run,
because it never finished; on red runs where it did finish it came in under
budget. So the arm is interrupted-before-verdict, and that is a stronger
reason to exclude such a row from the distribution than "a bound is not a
cost": it is not a sample from that distribution at all, being the cost of a
computation that did not happen. It also makes margin worth more than it
looks -- an overrun is a cost debt on a claim that passed, an interrupt
leaves the subject undecided and is the arm that refuses.

The refusal behaviour of that arm is cited rather than absorbed:
gunbc.recurring_failure_mode.non_verdict_disposition_surfaces_as_refusal
already records that the floor computes the difference between a wrong
subject and an unfinished one and then discards it at the aggregate
boundary. That is a different defect and it is that row's to repair.

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

* File the class instead of storing it in someone else's trigger (review 61947)

The rung-drop row said the harvester and provenance obligation belonged in
its own carrier and then kept it, which is the fork section 3 forbids --
each fact in exactly one place -- and section 4b names the destination for a
newly discovered error class. Saying where a fact belongs is not the same as
putting it there.

So it is filed:
gunbc.recurring_failure_mode.inflation_fold_cannot_name_the_environment_it_measures.
An analysis that holds WORK constant so that only ENVIRONMENT can differ,
and carries no field naming which environment either side ran in. The
pairing fold refuses censored readings, refuses unequal step counts and
refuses below a minimum base cost, so it reads as defensive -- it is
defensive about everything except its own subject.

Writing it as a row sharpened it twice over the paragraph it replaces. The
rung is mitigatable and protected only by UNREACHABILITY, not by a wall:
every reading comes from CI today so every pair is same-population by
accident, and the first producer elsewhere removes that protection. And the
ceiling is structural impossibility, because a machine population is a
modeled fact and equality of two declared values is decidable -- so the
trigger is that a cross-population figure cannot be CONSTRUCTED, not that it
does not currently occur.

The repair is also not a column: provenance is a fact about the run, so a
constant-across-rows column would be a per-row representation of a per-run
fact. It belongs on RunCost, read once from the header, with the parser
refusing a header that names no population rather than defaulting it -- and
the fold must join on it, because a field a consumer must remember to
consult would be this module's own filed class committed a second time.

The rung-drop row now cites that class in one sentence and states that it
is not a trigger and cannot retire the drop.

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

* The floor-grain walk-only arm was missing, so no repair could be aimed

l runs the floor claim's whole body -- walk plus the determinism fold over
every reached declaration's subtree -- so it cannot say which to make
cheaper. n runs the walk at the floor's own terms and nothing else, so l
minus n is the fold at the grain that blocks.

And the directory-grain answer does not transfer. The floor's closure is
empty, so decls_in_modules returns nothing for every callee and the walk
follows no edges: it drains its seeds and stops. A walk following no edges
cannot be pool-proportional, so the term this drop's trigger names is not
exercised at floor grain at all, and whatever costs 200ms there is a
different construct from the one the directory arms measure. Aiming the
repair from those arms would fix a real defect this claim does not pay.

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

* eval_steps on a censored row is a truncation artifact, not a workload figure

One sentence to save the next harvester an afternoon. An interrupted claim
stopped wherever it had got to when the deadline fired, so its step count
records how far it got and nothing else: two observed interrupts on this
claim truncate at different step counts, both far below every completed run,
and neither denotes a workload.

The harvest this row describes groups by eval_steps to hold work constant,
which is correct only over completed rows. Group the censored ones the same
way and they form a third apparent level out of nothing but where the
deadline landed -- a bogus population that would look exactly like the drift
finding this row already withdrew once.

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

* The arithmetic did not add up: isolate the fourth payer before repairing three

The floor-grain walk costs ~134ms over 78 seeds while following no edges.
Costing the three list shapes at their worst -- decl_in_list scanning
reached, list_snoc_item copying it, list_append copying the remaining seeds
-- gives roughly nine thousand element operations, so 134ms implies about
fifteen microseconds per element touch. That is at the top of defensible for
an interpreted list operation rather than comfortably inside it, which is the
signal that a term has not been named.

The unnamed term is not a list shape. call_reachable_decls_go calls
callees_from_node on every newly reached seed, and that materializes the
WHOLE SUBTREE of the declaration's output node via node_subtree_nodes before
folding over it. That cost is proportional to AST size rather than seed
count, so it is invisible to every list-shape argument and no list fix
removes it.

Arm o folds callees_from_node over exactly the seeds the floor claim walks
and does nothing else, so o minus i is the AST traversal alone. If it is
most of the walk, the list shapes are a rounding error here and a repair
aimed at them moves nothing.

Measuring before repairing is the point: landing three list fixes and
finding the walk still expensive would read as a partial win, and the
remaining payer would never rank.

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

* Split what arm o conflates, written before o returns

callees_from_node does two separable things per seed: node_subtree_nodes
materializes the declaration's whole subtree, cost linear in AST size; and
the fold scans the accumulator on every callee hit, cost quadratic in callee
count. They want opposite repairs. The scan is the cheap one and belongs to
the copied-accumulator family already named here, fixed without touching
traversal. The materialization is the expensive one and is partly
irreducible, since something must visit the nodes.

So going straight from a large o to 'stop materializing' attacks the hard
half first and possibly the wrong one. p materializes exactly what o
materializes and then only counts, so it pays the first and none of the
second; p against o, both netted against the same acquisition, separates
them in one dispatch.

Written now so the split is not designed around whatever number o produces.

And p is only worth running if o comes back large. If o is small the
traversal is not the payer, the list shapes in the walk are, and this arm
and the structural story behind it are both dead -- a tidy account must not
outlive the measurement that contradicts it.

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

* Pre-register the after-partition before measuring the fn_index repair

Commits the expectation ahead of the number: which arms must not move
(acquisition, and the determinism fold, both untouched code), which must
collapse, and the falsifier -- if the walk comes back only somewhat cheaper
that is a fifth payer revealed by removing the first four, not partial credit.

The block is attached to data floor_roots rather than appended at the end of
the file. Trailing it was a parse error, not a style question: 'source
annotation names no subject: no module item follows it' (section 4c admits
only standalone LEADING blocks). That error is here because I ran the arms
rather than reading the file -- the same execution the compile-clean reading
skipped one commit earlier.

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

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-rung-drops.md
Ledger-Rows-Repaired: docs/design-rung-drops.md determinism_transitive_reachability

* Transcribed figures moved rather than removed: review 62806

The drop row's own new comment block says why the numbers are not written down, and the
same diff deposited them in a file that is outside every compiled closure and run by no
lane -- so they rot with LESS chance of anyone noticing than where they came from. That is
the finding and it is correct.

Removed from the probe: the absolute walk cost and seed count, the derived element-touch
arithmetic, the run-to-run spread factor, the machine-population ratio, and the floor-grain
millisecond figure. Each is replaced by the arm that re-derives it. The pre-registered
arm-vs-arm ladders stay -- h ~ 2x c, h ~ 13x c, m ~ c + e, p ~ o -- because a stated
expectation written before the measurement is not a transcription of one.

Removed from the restoration_trigger: the run counts, the spread factor and the
percent-of-ceiling figure, replaced by the harvest that re-derives them. That field
instructed the reader to re-derive both figures from the artifacts two paragraphs after
printing them.

Both files compile at 0 blocking errors on their own entries.

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

* The annotation contradicted the field it annotates: review 62837

Two findings, both real.

(1) The leading annotation said the index prescription is "removed and nothing replaces it
until the cost is attributed to a construct that executes", while restoration_trigger opens
with an active capability-grain trigger and says explicitly that naming the construct is
NOT required to retire the row. That is a meaning fork between quarantine prose and the
typed carrier -- and by this row's own argument, the reader follows the prescription rather
than the remark, so the remark being the stale half is exactly the harm. The annotation now
says what replaced the prescription (the cost-shape trigger), names construct attribution
as an open question UNDER it rather than a precondition, and confines itself to why the
withdrawal was a deletion and not a weakening. The field stays the authority for both.

(2) The same field that says THE FIGURES ARE NOT WRITTEN HERE ON PURPOSE still embedded
"roughly ten to fifteen percent of the current median" as the acceptance target and then
told the reader to re-derive it. The formula and the harvest stay; the percentage is gone.
That is the third transcription removed from this row, which is itself the signal: figures
survive in prose because nothing executes over them.

Compiles at 0 blocking errors.

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

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-rung-drops.md
Ledger-Rows-Repaired: docs/design-rung-drops.md determinism_transitive_reachability

* The annotation still said unverified after the field said accounted for: review 62859

Same fork class as e53933f, one paragraph further down, which is the finding worth more
than either fix: this row keeps producing stale annotations because nothing executes over
them.

(1) The annotation recorded this row's interrupted_before_verdict cause as UNVERIFIED
PENDING REQUIRED-FLOOR INSTRUMENTATION while restoration_trigger says the blocker is
accounted for POSITIVELY on the population that enforces the line, via the
required-floor-claim-cost harvest. The instrumentation never had to be built: every
completed witnesses run already publishes that artifact, so the run had been taken and its
receipt was sitting unread. The paragraph now records only how the uncertainty was
resolved and leaves the field as the authority for what the harvest establishes.

(2) The same module cites section 6 and then embedded "6-7 seconds" with no re-derivation
recipe. The figure is replaced by the claim_batch run that re-derives it. The observation
it supports -- that the walk executes and completes at directory grain, and that this says
nothing about the floor -- is unchanged and did not need the number.

That is the fourth transcription removed from this row.

Compiles at 0 blocking errors.

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

* The probe moves into the long home: compiled, and declined by name: review 62869

The floor already answered this before the review did. On the previous head every arm of
the probe appears in the required-ci receipt as a BLOCKER --
declined_changed_witness_outside_discovery -- because the change touches the file, so the
floor plans it as a changed witness, and the module is outside the discovery closure, so
the floor refuses rather than skipping. The exposure this row admitted was not theoretical:
it made the PR unlandable.

The row's argument for accepting it is withdrawn as refuted by the tree. It claimed the
only discharge was adding a root to compile_clean_source_roots and to its partition -- a
closed roster this brief forbids. The repository already held the answer:
src/v2/test/claim/long/ is inside a compiled source root, so rot reds like any other
module, AND v2.workflow.required_floor declines the module by AUTHORED NAME through
long_home_prefixes, so eight multi-second arms do not run on every push. Both halves, no
roster edit.

So the module is now v2.test.claim.long.fn_index_attribution_probe at
src/v2/test/claim/long/fn_index_attribution_probe.dag, the scratch-probe/ copy is gone, and
the row's exposure paragraph is replaced by what actually holds. The probe's own dissolution
condition is unchanged: the lane that retires this drop or names the pool-proportional
construct deletes it in the same change.

The floor reads the authored name and not the path, so the name is the admission fact;
long_home_storage_agreement refuses the two states where name and directory disagree.

The row's entry point and citation now name the new module and drop the third source root.

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

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-rung-drops.md
Ledger-Rows-Repaired: docs/design-rung-drops.md determinism_transitive_reachability

* The fourth stale annotation and a nicknamed module: review 62883

(1) Lines 68-69 said the floor's disposition for this claim is unpinned and the deadline
question unresolved, while the paragraph below says the uncertainty is resolved and the
typed field says the blocker is accounted for. Same class as reviews 62837 and 62859,
incompletely fixed twice: I corrected the paragraph that stated the conclusion and left the
sentence upstream of it. Now corrected at the point where the claim is made, with only the
part that survives kept -- that a BuildBuddy observation is a different machine population
from the one that enforces.

(2) One sentence called the probe v2.test.claim.long.fn_index_attribution_probe while the
authored module name, and the one the floor's long_home_prefixes test reads, is
test.claim.long.fn_index_attribution_probe. A second name for one module is the nickname
DESIGN 3 forbids, and here the wrong half is the one the admission mechanism does not
match. Corrected, and the sentence now says the authored name is the fact the floor reads.

(3) docs/design-rung-drops.md is stale against this authority. That is the generated
projection under the merge=generated-artifact driver, whose declared route is that
heal-generated-artifacts derives it -- not a hand edit and not a local regen. It is stale
because regen last ran before the probe moved; the run on this head derives it again.

Compiles at 0 blocking errors.

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

* Floor discovery refuses a test-marked decl outside a _test.dag file: rename the probe

The move into the long home was right and incomplete: the floor refused the whole lane
with FloorDiscoveryRefused -- "`test`-marked decls must live in `*_test.dag` files; found
in: src/v2/test/claim/long/fn_index_attribution_probe.dag". Every sibling in that directory
already carries the suffix; I read the module-name admission rule and not the discovery
rule, and the two are separate walls.

The module is now test.claim.long.fn_index_attribution_probe_test at
src/v2/test/claim/long/fn_index_attribution_probe_test.dag. The row records that the suffix
is required rather than stylistic, and how it was caught, so the next lane moving an
instrument into that home does not repeat the same half-move.

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

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-rung-drops.md
Ledger-Rows-Repaired: docs/design-rung-drops.md determinism_transitive_reachability

* No location in this repository can hold a multi-second claim a change touches: the probe does not land

Three placements were tried and all three were refused, and the third refusal is the one
that generalizes.

  scratch-probe/            declined_changed_witness_outside_discovery, eight arms -- a
                            planned witness the floor cannot reach is a typed refusal
  long home, wrong suffix   FloorDiscoveryRefused: a test-marked decl must live in a
                            *_test.dag file
  long home, right suffix   sixteen blockers: six interrupted_before_verdict, two
                            completed_over_cost_requirement, eight
                            changed_witness_planned_without_terminal_verdict

The rule behind the third is in required_floor itself:
required_floor_disposition_with_changed_selection REPLACES the ordinary disposition, so a
long-home decline, a fixture decline and a declared cost-debt withhold are ALL overridden
for a claim whose file the change touches. So there is no directory, prefix or roster that
lets a multi-second claim be added -- or later edited -- without running on the floor of
the change that touches it. An instrument of this cost cannot be a claim here.

So the probe is not landed, and the row says so first, before any finding that depended on
it. It also says what to rebuild and how to run it, and states plainly that this is a
weaker citation than a landed instrument: nothing in the tree re-derives these findings.

That is the honest arm. The alternative was to keep asserting a home that three walls had
already refused.

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

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-rung-drops.md
Ledger-Rows-Repaired: docs/design-rung-drops.md determinism_transitive_reachability

* The ledger row tracking this instance is discharged by this change: review 62919

declared_trigger_names_a_term_that_is_not_the_cause names, as a LIVE unremediated
instance, the very trigger this PR withdraws. Merging without touching it would leave the
ledger asserting present-tense corpus state that the same merge makes false -- fabricated
plausible output, from the row whose subject is triggers that name the wrong term.

Three receipts updated, none deleted:

  the live-instance claim becomes the DISCHARGE, dated and attributed, keeping what the
  instance was so the class still carries its specimen;

  "this row does not discharge it" becomes why it could not, kept because the ORDERING is
  the rule -- a replacement capability may only be named by the lane holding the falsifier,
  and the discharge arrived exactly that way, which is evidence the ordering works;

  the instrument receipt said the figures re-derive from the producer the drop row names.
  That row now says the probe was deliberately not landed, so the receipt said the opposite
  of its own authority. It now says the instrument is NOT in the tree, that these figures
  are re-derived by REBUILDING the arms, and why no location can hold them.

Compiles at 0 blocking errors.

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

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-rung-drops.md
Ledger-Rows-Repaired: docs/design-rung-drops.md determinism_transitive_reachability

* A healed head skips the check that requires it: file the class, and unblock this PR by having something to heal

MEASURED, TWO HEADS ON THIS BRANCH DIFFERING ONLY IN WHO AUTHORED THEM. On f3f1f50, mine,
heal-generated-artifacts RAN, concluded success, and the PR read CLEAN. On 2bc7918,
heal's own auto-heal commit on top of it, heal SKIPPED, the other three jobs concluded
success, the aggregating witnesses job concluded success, and the PR read BLOCKED with
mergeable=MERGEABLE. The combined-status endpoint on that head reports state=pending with
zero statuses.

Nothing regressed between them: the second head is strictly the projection the first was
missing. heal is not wrong to skip on its own commit -- re-healing its own output is how a
loop starts -- but a skipped REQUIRED check is not a satisfied one, so the head that
finally matches its authority is the one head that cannot merge.

The harm is that it is indistinguishable from author error. Four green jobs and MERGEABLE
read as ready, and the lane waits on a merge that cannot happen; the condition that clears
it is another commit, which the lane has no reason to author.

Ceiling 3 with a capability trigger: the healed head's revalidation satisfies every
required context it does not re-run, so readiness is decidable from the head alone.

This commit is also the other commit. It is a real class with an executed receipt rather
than a no-op to move the ref, and it gives heal a drifted projection to regenerate.

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

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants