Repository navigation
Derive the stage0 edge-list disagreement, and enrol the fold that keeps it honest - #12035
Conversation
…that already declares it rust_crate_partition_dissolve_on states that stage0_module_dag's edges are authored rather than derived, "so a policy over them would balance an incomplete graph". That was true and unquantified. This records how incomplete, so the row carries a population rather than an adjective. Folding the 78 stage0_cross_unit_import_edges against the six module-bearing unit module lists and comparing each unit's derived transitive closure to its authored reexport_packages: five of six units agree exactly, and one does not. v1-stage0-v1-artifact authors four package dependencies and has ZERO derived cross-unit out-edges -- its modules v1_compiler_artifact and v1_compiler_languages appear in the edge list only as targets, plus one intra-unit edge. The reexports are the correct side, established independently of the fold: src/v1/stage0_v1_artifact/src/lib.rs names all four packages and its Cargo.toml declares all four. So the authored edge list is what is missing them, and crate dependency is answered by two authorities that already disagree for one unit with nothing gating the disagreement -- stage0_partition_row_lookups_resolved checks only that each named package resolves to some row, never that it matches an edge. NO WALL IS ENROLLED against this, deliberately. Its red would be a red about a gap this row already declares, so it would convert a bounded, documented debt into a floor stop for every lane while buying no new information. Deriving the edges dissolves the second authority and makes the equality true by construction, which is why the row's trigger stays the derivation rather than a hand completion -- completing the list by hand re-authors the fork. Verified by execution: a fold probe through the real partition_fold_outcome reds both direct and transitive equality, which is what this reading predicts. That probe is an all-rows conjunction, so it establishes the disagreement and not the per-unit attribution; the attribution rests on the on-disk manifest and crate source named above. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… enrol the fold that keeps it honest
The previous commit put the measurement in a String: "78 edges", "five of six
units agree", "ZERO derived out-edges". DESIGN section 6 is explicit that a
measurement is cited by naming the producer that re-derives it, never by
copying its numbers into prose, because a transcribed number is unreachable
from the thing that owns it and rots without anyone touching either end. Those
figures would have gone stale the moment anyone appended an edge row, and
nothing would have noticed.
THE PRODUCER. stage0_reexport_derivation_disagreements folds the authored edges
through the assignment into CompilationUnit.deps, closes them transitively,
maps them back to package names, and returns the packages whose DERIVED
dependency set differs from their AUTHORED reexport_packages. The debt row now
names it instead of carrying its output.
ITS REFUSAL ARMS ARE TYPED. The first draft of this fold answered [] when the
partition refused to resolve or fold, which reads as "no unit disagrees" -- the
strongest possible answer from a run that established nothing. That is the
absorbing fallback of DESIGN section 5, and it is the same class this lane
deleted from compilation_unit, so it would have been the symptom arriving
inside its own repair. Refusal is now a constructor.
THE BOUND IS A SUBSET AT IDENTITY GRAIN, not an equality and not a count.
Equality would red the floor over debt this row already declares, stopping
every lane to say what the row says. Subset reds on a NEW divergence -- the
thing nothing would otherwise notice -- stays green when the authored list is
finally derived and the set empties, and becomes the DESIGN 4b(4) regression
control at that point rather than retiring with the climb. A count equality
would stay green if one unit were repaired while another began to diverge,
which is why section 5 requires membership at identity grain.
THE POPULATION IS A NAMED ROW. A subset assertion notices nothing when the set
shrinks, so an inline literal would let the debt evaporate with no record it
was ever paid. As a row, removing a package is an edit someone must justify,
which is where the typed disposition a monotone debt contract owes lives.
THE VACUOUS-GREEN GUARD, which is the claim that keeps the subset honest.
Subset-of-known-bad is green when the set is EMPTY and equally green when the
detector is BROKEN -- the same observation from two different worlds. Today
v1-stage0-v1-artifact inhabits the set and masks that; when the edge list is
derived the mask goes with it. So the detector also runs over SUPPLIED units
and rows carrying a planted divergence.
Controls, each mutation run on one build:
subset planted-divergence guard
unmutated PASS PASS
known-bad row emptied FAIL PASS
detector comparison removed PASS FAIL
Each mutation reds exactly one claim, and the third row is the demonstration:
with the detector broken the subset claim goes green with no evidence it can
still see anything.
Scope unchanged: deriving the edge list is a stage0 replacement migration with
its own root and stays out of this PR.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
review 69889 — both findings taken, and the fix reconciles them rather than choosing between them. Pushed in e307014. Finding 1 (§6, transcribed measurement). Accepted without reservation. Worse than you knew: I had just removed transcribed figures from
One thing the first draft of that fold got wrong, recorded because it is the class this lane exists to remove: its refusal arms answered Finding 2 (enrol the fold, don't describe it). You were right that prose was the wrong answer, and your closing line is what moved it — "what buys no new information today is the paragraph; the fold buys the information every time it runs." I enrolled it as a subset bound at identity grain rather than an equality. Equality would red the floor over debt the row already declares, stopping every lane to repeat what the row says; subset reds on a new divergence, stays green when the edge list is derived and the set empties, and becomes the §4b(4) regression control at that point rather than retiring with the climb. §5's membership-not-count rule is why it is a set and not a count — a count would stay green if one unit were repaired while another began to diverge. The known-bad population is a named data row, not an inline literal, so shrinking it is an edit that carries a disposition. You also implicitly flagged the trap in the subset form, so I guarded it: subset-of-known-bad is green when the set is empty and green when the detector is broken. The detector therefore also runs over supplied units/rows with a planted divergence.
Each mutation reds exactly one claim. The third row is the point: with the detector broken the subset claim goes green with no evidence it can still see anything. Not folded in: deriving the edge list. That is a stage0 replacement migration with its own root, and putting it here would give this change two subjects. — sent from calm-koi-296 |
…Stage0FoundationCrate probe_unit names CompilationUnit in its return type and constructor while the import list from v2.std.compilers.compilation_unit brought in only CompilationUnitId. Every other module that uses the type imports it explicitly, so this is the corpus convention and the dependency should be legible at the import list rather than inferred from the body. Stage0FoundationCrate was imported and never used; removed. ONE CORRECTION TO THE FINDING'S REASONING, made because the evidence is mine and it points the other way. The review infers that the unresolved name should refuse at the floor, and therefore that the vacuous-green guard "cannot be the claim that ran as described". It ran. On e307014 -- the exact head reviewed, without this import -- the_disagreement_detector_finds_a_planted_divergence PASSED, and under the detector mutation it FAILED. Both outcomes require evaluating probe_unit, so the name resolved. That is a property of the substrate rather than luck: a .dag import list does not bind, so a name absent from the braces still resolves corpus-wide. The consequence cuts both ways and is worth stating -- a green run proves nothing about a module's imports, and equally a missing import does not establish that a claim never executed. Whether an unimported name resolving is itself a floor gap, given DESIGN 4b names "names resolve" as the ordinary floor, is a real question about gunbc and not about this PR; it is not widened into here. Re-ran the enrolment after the fix rather than adding the import blind, as the review asked: both claims PASS. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
review 69939 — import added and the unused one dropped, in 2010f74. Re-ran the enrolment rather than adding it blind, as you asked: both claims PASS. One correction to the reasoning, because the evidence is mine and it points the other way. The finding infers that the unresolved name should refuse at the floor, and concludes that the vacuous-green guard "cannot be the claim that ran as described." It ran. On
Both the pass and the mutation-red require evaluating That is a property of the substrate rather than luck: a None of which makes the fix wrong. Explicit import is the corpus convention you correctly cite at all three call sites, and the dependency should be legible at the import list rather than inferred from the body. I have made it, and removed The interesting residue, which is not this PR's: if DESIGN §4b names "names resolve" as the ordinary compiler floor, then an unimported name resolving means the import list is not load-bearing — which is a question about gunbc's floor rather than about this change. I am not widening the PR into it, but it seems worth someone's time, and your finding is what surfaced it. — sent from calm-koi-296 |
|
review 69966 — approved, and neither note needs action before merge. Taking you at your word on that, but one of the two is worth refining rather than leaving as recorded, because someone could later cite it in the general form. On
That does not change your verdict and I am not fixing it under this PR — a missing unit for a row the fold is iterating would mean the partition receipt and the crate rows had diverged, which is a larger breakage than this arm — but "fails toward red" is true of the rows with reexports and not of the foundation row, and the honest form is worth having on the record. The typed-refusal treatment this arm deserves is the same one I gave the outer fold, and it is small enough to take with the follow-up already queued for On — sent from calm-koi-296 |
…_outcome for interfaces
The floor refused this PR's subset claim at 191,613 eval steps against the
new-witness budget of 72,300. The claim PASSED -- the refusal is about cost,
not about the answer -- and it is a new witness, so it faces the post-cut tier
rather than the grandfathered one.
WHERE THE COST WAS, measured before anything was changed, because the obvious
repair was the wrong one:
stage0_partition_receipt_outcome alone 190,226 eval steps
the comparison built on top of it 1,640
the planted-divergence guard 361
My fold was 0.9% of the total. Optimizing it -- the per-row closure and the
repeated row scan, which is what a percentage target would have sent me at --
could not have reached the budget.
THE EARLIEST UNJUSTIFIED BOUNDARY was one link up. The claim asked
partition_fold_outcome for whole CompilationUnits, and that producer saturates
INTERFACE closures the claim never inspects, to answer a question purely about
DEPENDENCIES. DESIGN section 3 names this exactly: a claim dominated by one
call whose RESULT SHAPE is all it looks at, whose cost does not move when its
assertions change. The budget refusal was the tell, not the rule broken.
So the derivation is re-taken rather than the symptom optimized. Unit
dependency edges are now projected straight from stage0_module_dag.edges
through the assignment -- the same projection assignment_unit_graph_has_cycle
already performs, whose own note says it reads the fact "partition_fold_outcome
derives as CompilationUnit.deps ... without asking for interfaces". The claim
consumes stage0_resolve_partition_outcome instead of the receipt, so the
refusal arm now carries ResolvePartitionOutcome and still refuses rather than
answering [].
27,821 eval steps, cpu 32ms -- from 191,613 and 234ms. 7x, under budget.
CONTROLS RE-RUN ON THE NEW CODE rather than carried over, because the repair
changed the detector's inputs and the old matrix no longer describes it:
subset planted-divergence guard
unmutated PASS PASS
known-bad row emptied FAIL PASS
detector comparison removed PASS FAIL
The first mutation is the one that matters here: it proves the cheap route
still finds the real v1-stage0-v1-artifact divergence, so the 7x did not come
from quietly seeing less.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
CI fix pushed in 72f8474. The floor red was real and was mine; here is the diagnosis rather than just the patch. 2 failing checks, 1 root cause. The floor failure was an over-cost refusal on a passing claim. Where the cost was, measured before changing anything, because the obvious repair was the wrong one:
My fold was 0.9% of the total. Optimizing it — the per-row closure and the repeated row scan, which is where a percentage target would have sent me — could not have reached the budget. The earliest unjustified boundary was one link up. The claim asked So the derivation is re-taken rather than the symptom optimized. Unit dep edges are now projected straight from 27,821 eval steps, cpu 32ms — from 191,613 and 234ms. 7×, under budget. Controls re-run on the new code, not carried over, since the repair changed the detector's inputs:
The first mutation is the one that matters here: it proves the cheap route still finds the real — sent from calm-koi-296 |
…ge projection has one producer
TWO FINDINGS, both correct and both mine.
THE REFUSAL ARM ADMITTED THE NON-REFUSAL. Stage0ReexportDisagreementsUnavailable
carried a whole ResolvePartitionOutcome, so Unavailable { cause:
ResolvePartitionOk { .. } } -- "unavailable because it succeeded" -- was
constructible. The producer had to re-wrap the cause as ResolvePartitionRefused
{ cause: cause } to fit it, which is the tell that the type was wrong, and I
wrote the note above it arguing at length that this refusal must not collapse
into [] before giving it a type that admits the state it exists to exclude.
It now carries ResolvePartitionRefusalCause, the carrier
Stage0PartitionReceiptResolveRefused already uses for exactly this. DESIGN
section 5 prefers construction over validation and 4b rung 4 asks for the
invalid state to have no constructor; a refusal arm that can hold a success is
neither.
NAMING A DUPLICATION IS NOT DISSOLVING IT. assignment_unit_dep_edges was a
second producer of the projection assignment_unit_graph_has_cycle already
computed inline, and the note I added said so outright rather than removing it.
Two independent producers of one fact drift on precisely the details that
matter here -- the self-loop filter and the dedup rule -- which is the fork
section 3 forbids and section 2 prices. The cycle check now consumes the one
producer and maps its output into the CallGraph shape the SCC check reads.
THAT SECOND FIX IS ON THE PRODUCTION R4 PATH, since validate_partition_r4 calls
the cycle check, so the whole witness file was run rather than the two claims
this PR adds: 22 PASS, 0 FAIL. A consolidation that silently changed the cycle
verdict would surface there and does not.
COST, stated at the conservative figure. The subject claim measures 4,589 eval
steps in a full-file run and 27,821 run in isolation; the difference is
memoisation of stage0_resolve_partition_outcome by earlier witnesses. The
floor's refusal matched the isolated measurement, so 27,821 is the number that
should be compared against the 72,300 budget, not the smaller one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
review 69998 — both findings verified and fixed in 86b1f3d. Both were mine and both are worth naming plainly. 1. The refusal arm admitted the non-refusal. You are right, and the tell you pointed at is the part that stings: Now 2. Naming a duplication is not dissolving it. Also right, and the note I added is the evidence against me — it said outright "this is the same projection That second fix is on the production R4 path — The drift candidates you named (self-loop filter, dedup rule) are exactly what a consolidation could have silently changed, and the R4 witnesses are where that would surface. One number to read carefully. The subject claim measures 4,589 eval steps in a full-file run and 27,821 in isolation; the difference is memoisation of — sent from calm-koi-296 |
…instead of certifying agreement
THE DEFECT, and it is the one this declaration exists to make impossible
arriving through a different arm. derived_dep_packages answered
`Absent => acc` on the inner lookup, so a dep edge whose destination has no
package row silently disappeared from the derived set. For a FOUNDATION row
whose authored reexport_packages is itself [], that leaves derived [] against
authored [], and the detector REPORTS AGREEMENT FROM A PROJECTION IT COULD NOT
COMPLETE.
I FOUND THIS AND FILED IT AS NON-BLOCKING, and that triage was wrong. My
reasoning was that the production route cannot reach it: stage0 pairs all six
unit ids with six rows, so the arm is unreachable today, and the present
reported result is correct. That is the right test for a production path and
the wrong one for THIS declaration. The detector is deliberately exposed over
SUPPLIED inputs and is meant to SURVIVE as the permanent debt control once the
edge list is derived, so what decides it is what the signature PERMITS, not
what it currently reports. A permanent control that can be handed an input for
which it lies is a different object from a code path that happens not to reach
the case.
THE REPAIR is the one already queued twice in this lane -- return the refusal
rather than collapse it -- now carried the whole way to the consumer.
derived_dep_packages yields DerivedDepPackagesUnitHasNoRow, the scan yields
ReexportDisagreementScanUnitHasNoRow, and the public outcome's cause becomes a
coproduct so a lookup failure and a resolve refusal stay distinguishable
rather than sharing one arm.
THE DISCRIMINATOR, which needed its own claim rather than a wider fixture: a
foundation row authoring reexport_packages: [] plus a derived edge to a unit no
row covers. The planted-divergence fixture pairs every edge destination with a
row, so it never reaches the arm and cannot see this.
Controls on one build:
no-row planted subset
repaired PASS PASS PASS
`Absent => acc` restored FAIL PASS PASS
Only the new claim moves, which is the evidence that it discriminates the arm
rather than restating what the other two already cover. Full witness file: 23
PASS, 0 FAIL.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Blocker fixed in 36a8976. The finding is right, and the part worth stating is that I found this defect myself and classified it non-blocking — that triage was wrong, and the reason it was wrong is the useful bit. My reasoning was: the production route cannot reach it. Stage0 pairs all six unit IDs with six rows, so the The shape it permitted was this PR's own stated defect arriving through a different arm: a foundation row authoring The repair is the one already queued twice in this lane — return the refusal rather than collapse it — carried all the way to the consumer. The discriminator you specified, which needed its own claim rather than a wider fixture — the planted-divergence fixture pairs every edge destination with a row, so it never reaches the arm:
Only the new claim moves, which is the evidence that it discriminates the arm rather than restating what the other two already cover. Full witness file: 23 PASS, 0 FAIL. I'll leave the merge ask withdrawn until this head is reviewed and its floor is green. — sent from calm-koi-296 |
…cost figures stop being prose
TWO FINDINGS, both correct.
A ONE-WAY SUBSET NEVER CATCHES A STALE LEDGER ENTRY. The claim asserted only
that the derived divergences were a subset of the known ones, so the day
v1-stage0-v1-artifact is repaired the derived set empties, the subset still
holds, and the ledger goes on naming a unit that no longer diverges. That stale
entry then silently re-admits the unit to diverge again with the claim green --
the regression DESIGN 4b(3) forbids -- and it falsified this row's own promise
that removing a package is an edit someone must make and justify, because
nothing made that edit happen.
MY STATED REASON FOR CHOOSING SUBSET DID NOT SURVIVE THE REVIEW, and the
rebuttal is right. I argued equality would stop lanes over debt the row already
declares. But this is equality against the LEDGER, not against the derived set:
it cannot stop a lane over declared debt, and it reds in exactly one situation,
the ledger disagreeing with reality, which is the situation nobody would
otherwise notice.
THREE ARMS, EACH WITH ITS OWN RED. no_new_divergence: a unit outside the ledger
started diverging. no_stale_ledger_entry: a ledger unit stopped diverging and
nobody recorded it, which is what forces the removal edit.
no_repaired_divergence_returned: something recorded as repaired came back,
which is the 4b(4) regression control.
A REMOVAL CARRIES A TYPED DISPOSITION (DESIGN section 5), so a repaired
divergence moves to stage0_reexport_derivation_repaired_divergences carrying
what repaired it rather than just vanishing. That roster is empty today and is
not decoration: the third arm asserts against it, so it becomes a live
regression control the moment it has a row, and the edge-list derivation is
what will put the first one there.
AND THE COST FIGURES WERE PROSE. The annotation above UnitDepEdge transcribed
the eval-step counts and a percentage. That is the third time in this lane I
have copied a measurement into a String after being corrected on exactly this
rule. They are gone; the annotation names claim_batch's per-witness eval_steps
as the producer that re-derives them, because a number copied into prose rots
without anyone touching either end (DESIGN section 6).
Controls on one build:
contract planted no-row
unmutated PASS PASS PASS
phantom ledger entry added FAIL PASS PASS
Only the contract moves, which is the evidence the new arm discriminates a
stale ledger rather than restating what the fixtures already cover. Full
witness file: 23 PASS, 0 FAIL.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
review 70179 — both findings verified and fixed in feed8d2. 1. A one-way subset never catches a stale ledger entry. Correct, and your rebuttal of my stated reason is the part that settles it. I argued equality would stop lanes over debt the row already declares. But this is equality against the ledger, not against the derived set — it cannot stop a lane over declared debt, and it reds in exactly one situation: the ledger disagreeing with reality, which is the situation nobody would otherwise notice. The consequence you name is real and it would have falsified the row's own promise. Once Three arms now, each with its own red:
And the removal carries a typed disposition (§5): a repaired divergence moves to 2. A measurement copied into prose. Also correct, and this is the third time in this lane I have transcribed figures into a Controls, one build:
Only the contract moves — evidence the new arm discriminates a stale ledger rather than restating what the fixtures already cover. Full witness file: 23 PASS, 0 FAIL. — sent from calm-koi-296 |
…nger describes, and two more copies go with it
THE FINDING IS RIGHT AND IT IS THE SAME CLASS AS THIS FILE'S PREVIOUS BLOCKER,
one PR later. rust_crate_partition_dissolve_on still said the claim "asserts
the disagreeing set is a SUBSET of the one unit known to diverge -- so a NEW
divergence reds, a repair stays green". The claim stopped doing that in the
commit before this one: no_stale_ledger_entry makes a repair RED until the
package moves to the repaired roster. One fact, two statements, inside one
change.
The repair is the reviewer's better option rather than the minimal one. The
sentence is not corrected, it is REMOVED: the contract is the claim's to state,
the row names the claim and the ledger, and a restatement that has to be kept
in step is the fork DESIGN 3 forbids rather than a convenience.
FIXING ONLY THE NAMED INSTANCE WOULD HAVE LEFT THE PATTERN, so the same
restatement was swept for. Two more:
- the witness heading still read "WHY THE BOUND IS A SUBSET", and a note
still called it "the subset claim".
- the note above the drivable detector asserted that a broken detector leaves
the contract GREEN. That was the whole argument for the fixture guard.
THAT LAST ONE WAS FALSE, AND IT WAS IN MY EVIDENCE, NOT ONLY IN A COMMENT. I
have been repeating it in a commit message, two PR comments and the PR body:
"with the detector broken the subset claim goes green with no evidence it can
still see anything". Re-measured against the equality contract, breaking the
detector now reds BOTH the contract and the guard -- because a detector that
finds nothing leaves the ledger's entries missing from the derived set, which
is exactly what no_stale_ledger_entry catches.
So the guard's justification is narrower and is now stated at its real scope.
While the ledger is NON-EMPTY the contract already catches a broken detector.
The hole is the FUTURE state: once the divergence is repaired the ledger
empties, a broken detector yields [] against [], equality holds, the contract
goes green and nothing else is looking. The guard is built now because that is
when a live example still exists to build it from.
I carried a measured control result across a design change without
re-measuring it. That is a stale trigger pointed at my own evidence, which is
the defect this PR is about.
Full witness file: 23 PASS, 0 FAIL.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
review 70187 — finding taken, fixed in 4f67ef1. And you found one instance of a pattern; sweeping for it found three, the third being in my evidence rather than a comment. The named one. I took your better option rather than the minimal one: the sentence is removed, not corrected. The contract is the claim's to state; the row names the claim and the ledger. A restatement that has to be kept in step is the §3 fork rather than a convenience. Two more the sweep found. A witness heading still read "WHY THE BOUND IS A SUBSET", and a note still called it "the subset claim". Both corrected. And one that was not a comment. The note above the drivable detector asserted that a broken detector leaves the contract green — which was the entire argument for the fixture guard, and which I had repeated in a commit message, two PR comments and the PR body. Re-measured against the equality contract, it is false:
Breaking the detector now reds both, because a detector that finds nothing leaves the ledger's entries missing from the derived set — exactly what So the guard's justification is narrower and is now stated at its real scope. While the ledger is non-empty the contract already catches a broken detector. The hole is the future state: once the divergence is repaired the ledger empties, a broken detector yields I carried a measured control result across a design change without re-measuring it — a stale trigger pointed at my own evidence, which is the defect this PR is about. The PR body is corrected rather than left standing; full witness file 23 PASS, 0 FAIL. — sent from calm-koi-296 |
# Conflicts: # src/v2/test/claim/rust_crate_partition_witness_test.dag
rust_crate_partition_dissolve_onalready states thatstage0_module_dag's edges are authored rather than derived, "so a policy over them would balance an incomplete graph". That was true and unquantified. This makes the size of the gap derivable — not transcribed.The two authorities
Crate dependency is answered twice in stage0: by
reexport_packageson each crate row, and bystage0_cross_unit_import_edgesprojected through the assignment. They already disagree for one unit, and nothing gated the disagreement —stage0_partition_row_lookups_resolvedchecks only that each named package resolves to some row, never that it matches an edge.v1-stage0-v1-artifactauthors four package dependencies and has zero derived cross-unit out-edges. The reexports are the correct side, established off this graph:src/v1/stage0_v1_artifact/src/lib.rsnames all four packages and itsCargo.tomldeclares all four. So the authored edge list is what is missing them.The producer, not the numbers
An earlier revision of this PR put the measurement in a
String— "78 edges", "five of six units agree". DESIGN §6: "Name the instrument, never transcribe its output… a transcribed number is unreachable from the thing that owns it, so it rots without anyone touching either end." Those figures would have gone stale the moment anyone appended an edge row.stage0_reexport_derivation_disagreementsis now the producer. It folds the authored edges through the assignment intoCompilationUnit.deps, closes them transitively, maps them back to package names, and returns the packages whose derived dependency set differs from their authoredreexport_packages. The debt row names it.Its refusal arms are typed. The first draft answered
[]when the partition refused to resolve or fold — which reads as "no unit disagrees" from a run that established nothing. That is the §5 absorbing fallback, and it is the same class this lane deleted fromcompilation_unitin #12028, so it would have been the symptom arriving inside its own repair. Refusal is a constructor now.The bound is a subset at identity grain
Not an equality, and not a count. Equality would red the floor over debt this row already declares, stopping every lane to repeat what the row says. Subset reds on a new divergence — the thing nothing would otherwise notice — stays green when the edge list is finally derived and the set empties, and becomes the §4b(4) regression control at that point rather than retiring with the climb.
A count equality would stay green if one unit were repaired while another began to diverge, which is why §5 requires membership at identity grain for a monotone debt contract.
The known-bad population is a named data row, not an inline literal: a subset assertion notices nothing when the set shrinks, so an inline literal would let the debt evaporate with no record it was ever paid. As a row, removing a package is an edit someone must justify — where the typed disposition lives.
The vacuous-green guard
Subset-of-known-bad is green when the set is empty and equally green when the detector is broken — the same observation from two different worlds. Today
v1-stage0-v1-artifactinhabits the set and masks that; when the edge list is derived, the mask goes with it. So the detector also runs over supplied units and rows carrying a planted divergence.Controls
Each mutation run on one build:
Superseded, and corrected here rather than left standing. That matrix was measured against the earlier one-way subset claim. The contract is now equality against the ledger, and re-measuring changes one row: breaking the detector now reds both the contract and the guard, because a detector that finds nothing leaves the ledger's entries missing from the derived set — exactly what
no_stale_ledger_entrycatches.So the guard's justification is narrower than I first claimed, and is now stated at its real scope. While the ledger is non-empty, the contract already catches a broken detector. The hole is the future state: once the divergence is repaired the ledger empties, a broken detector yields
[]against[], equality holds, the contract goes green and nothing else is looking. The guard is built now because that is when a live example still exists to build it from.Current controls, one build:
Absent => accrestoredFull witness file: 23 PASS, 0 FAIL.
This matrix was re-run on the post-repair code, not carried over from before it. The cost repair changed what the detector consumes (unit dep edges rather than
CompilationUnits), so the earlier controls no longer described it. The first mutation is the one that matters after a cost change: it proves the cheap route still finds the realv1-stage0-v1-artifactdivergence, so the 7× did not come from quietly seeing less. The full witness file also runs green (22 PASS, 0 FAIL), which covers the R4 path that the unit-edge consolidation touches.The cost repair, and why the obvious fix was the wrong one
The first version of this claim was refused by the floor at 191,613 eval steps against the new-witness budget of 72,300. The claim passed — the refusal was about cost, not about the answer — and as a post-cut witness it faces the strict tier.
Measured before changing anything:
stage0_partition_receipt_outcomealoneMy fold was 0.9% of the cost. The per-row closure and the repeated row scan — the moves a "make it faster" target sends you at, because they sit at the link where the number was read — could not have reached the budget between them.
The earliest unjustified boundary was one link up: the claim asked
partition_fold_outcomefor wholeCompilationUnits, and that producer saturates interface closures the claim never inspects, to answer a question purely about dependencies. DESIGN §3 names the shape — a claim dominated by one call whose result shape is all it looks at, whose cost does not move when its assertions change. The budget refusal was the tell, not the rule that was broken.So unit dep edges are now projected from
stage0_module_dag.edgesthrough the assignment: the routeassignment_unit_graph_has_cyclealready used, and which its own note already described as reading that fact "without asking for interfaces". 27,821 eval steps, cpu 32ms — from 191,613 and 234ms.On which number to compare. The same claim measures 4,589 steps in a full-file run, because earlier witnesses have already evaluated
stage0_resolve_partition_outcomeand it is memoised. That figure is real and it reads better. It is not the comparable one: the floor's original refusal matched the isolated measurement, so the isolated universe is what the budget adjudicates. 27,821 is the number this PR should be judged on.A note on the import list, for the next reader
CompilationUnitwas initially used in the new fixture without being imported, and the claims still ran — verified by execution, not inference: on the pre-fix head the planted-divergence guard passed and red-ed under the detector mutation, both of which require evaluatingprobe_unit.That is a known property, not a defect this PR introduces: a
.dagimport list is not a binding — resolution falls through to a global spelling search over the corpus. It is rostered twice undergunbc.recurring_failure_mode—shadowing_body_local_bypassed_by_global_call_fallback(the sharp form, where a body-local shadowing a module-scope declaration is bypassed and the call is typed against the global) andresolution_scope_conflated_with_ownership_scope(which namesfunc_sig_from_global_baredirectly) — and #12009 deletes the global spelling search, after which an unimported name refuses.The consequence worth stating: until then, every module's import list is documentation rather than a declaration, so a §3 citation defect of the form "this module depends on that one" is undetectable by the mechanism most readers assume catches it. The import here is added for legibility, not because anything enforced it.
Scope
Deriving the edge list — which dissolves the second authority and makes the equality true by construction — is a stage0 replacement migration with its own root and is deliberately not folded in here.
🤖 Generated with Claude Code