Skip to content

Commit 82774b2

Browse files
committed
docs(eager-closure): price the deleted-contents read and state the gate's actual check
Two review notes on PR #3298: - The gate's cost comment still promised four git processes; the deleted-contents fallback adds a fifth cat-file batch when a closure loses sources. - ADR 0027 claimed the gate proves a moved module carries no deleted source's statements. The gate checks the conservative openWrite probe instead; the comment states what is verified and why the check runs at the base path.
1 parent efb0176 commit 82774b2

3 files changed

Lines changed: 35 additions & 20 deletions

File tree

‎docs/adr/0027-descriptor-root-vs-eager-closure-budget.md‎

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,16 @@ facts computed from the two closure graphs and the merge-base tree, not by an en
9292
(renames canonicalized), so nothing was dropped or swapped.
9393
2. **Added-module novelty:** every module the head closure evaluates that the base closure did
9494
not is absent from the merge-base tree at its rename-canonicalized path AND carries no code
95-
from a source deleted since the merge-base. A module the tree already had, sitting outside
96-
the closure, becoming eager — under its own name, a renamed one, or a rewritten move that
97-
evades git's `-M` similarity pairing — is a new edge, not re-homed code; and without this
98-
fact, weight deleted elsewhere in the closure would fund it. Rename canonicalization
99-
preserves modules ALREADY IN the base closure; it is not a pass for an external module
100-
moved in, which fails the check at the base path the merge-base necessarily still has.
95+
from a source deleted since the merge-base — matched when the added module shares **two or
96+
more identical normalized top-level statements** with one deleted source; a single shared
97+
statement is treated as coincidence, because short constants (a schema version, a default
98+
timeout) repeat across the repo and branding every reuse a move would poison new files).
99+
A module the tree already had, sitting outside the closure, becoming eager — under its own
100+
name, a renamed one, or a rewritten move that evades git's `-M` similarity pairing — is a
101+
new edge, not re-homed code; and without this fact, weight deleted elsewhere in the closure
102+
would fund it. Rename canonicalization preserves modules ALREADY IN the base closure; it is
103+
not a pass for an external module moved in, which fails the check at the base path the
104+
merge-base necessarily still has.
101105
3. **Flat weight:** the closure's total top-level statement count — wiring lines excluded, so
102106
re-homing is invisible to it — does not exceed the merge-base closure's.
103107

@@ -109,14 +113,19 @@ unrelated shrinkage paying for a smuggled edge — and the three together admit
109113
while refusing any growth in the closure's module-scope statement weight. Statements were chosen
110114
over bytes because formatting and comment churn move bytes while statements track the thing being
111115
preserved: module-scope declarations and calls. The guarantee is exactly **newly evaluated
112-
modules are new code — new paths carrying no deleted source's statements — and total statement
113-
weight is flat over a preserved closure**, not equality of eager behavior: a split that REPLACES
114-
an existing statement with a more expensive top-level call inside an existing file moves no
115-
count and no weight, so it passes — nothing count-based, in modules or statements, can see a
116-
like-for-like replacement. That residue is bounded by what the tolerance cannot hide: every
117-
module the head newly evaluates is a path the merge-base tree did not have AND carries none of
118-
the statements of any source deleted since the merge-base (so it cannot smuggle pre-existing
119-
implementation into eager scope, rewritten or not), the hard ADR-0019 checks (façade exactness,
116+
modules are new code — new paths the gate's deleted-source probe cannot match to any deleted
117+
source's statement text — and total statement weight is flat over a preserved closure**, not
118+
equality of eager behavior: a split that REPLACES an existing statement with a more expensive
119+
top-level call inside an existing file moves no count and no weight, so it passes — nothing
120+
count-based, in modules or statements, can see a like-for-like replacement. That residue is
121+
bounded by what the tolerance cannot hide: every module the head newly evaluates is a path the
122+
merge-base tree did not have AND fails the gate's move probe — its top-level statements do not
123+
share two or more identical normalized statements with any single source deleted since the
124+
merge-base (`movedFromDeletedSource`). This probe is deliberately conservative and textual:
125+
code REWRITTEN or reformulated during a move does not match it and would pass as novel, so what
126+
is verified is "no verbatim transplant of a deleted source," not "no repurposed logic" (the
127+
weight fact is what still refuses a transplant made heavier). What keeps the residue small is
128+
everything around the probe: the hard ADR-0019 checks (façade exactness,
120129
no platform implementation before binding) stay count- and pattern-based under it, and the
121130
added-module listing every failure prints is what a reviewer reads. The planted tests in
122131
`scripts/__tests__/closure-growth-rule.test.ts` pin every failing direction.

‎scripts/__tests__/closure-growth-rule.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414
// (renames canonicalized), so nothing was dropped or swapped -- the growth is purely
1515
// addition.
1616
// 2. ADDED-MODULE NOVELTY: every module the head closure evaluates that the base closure did
17-
// not is absent from the merge-base tree AT ITS RENAME-CANONICALIZED PATH and carries no
18-
// statement text from a source deleted since the merge-base. A module the merge-base tree
17+
// not is absent from the merge-base tree AT ITS RENAME-CANONICALIZED PATH and is not a
18+
// verbatim transplant of a deleted source: two or more identical normalized top-level
19+
// statements shared with one source deleted since the merge-base mark a move
20+
// (`movedFromDeletedSource`); a rewritten transplant evades a textual probe and would pass
21+
// as novel, which the flat-weight fact then has to catch if it is heavier. A module the
22+
// merge-base tree
1923
// already had, sitting outside the closure, becoming eager -- under its own name, a new
2024
// one, or a rewritten move `-M` cannot pair -- is a NEW EDGE, not re-homed code; without
2125
// this fact, weight deleted elsewhere in the closure would fund it. Canonicalization
@@ -29,8 +33,8 @@
2933
// pre-existing module in at flat weight; novelty without containment accepts dropping a
3034
// merge-base module; weight without the other two accepts an unrelated shrinkage paying for a
3135
// smuggled edge. The guarantee the triple states is exactly "newly evaluated modules are new
32-
// CODE -- new paths carrying no deleted source's statements -- and total statement weight is
33-
// flat over a preserved closure" -- a split that REPLACES a statement with a more expensive
36+
// CODE -- new paths the deleted-source probe cannot match to a deleted source's statements --
37+
// and total statement weight is flat over a preserved closure" -- a split that REPLACES a statement with a more expensive
3438
// one inside an existing file is invisible to any count, in modules or in statements, and is
3539
// not what this tolerance was built to admit. It replaces neither the count comparison (which
3640
// still fires first: growth must exist to be tolerated) nor the hard ADR-0019 checks (façade

‎scripts/__tests__/eager-closure-budgets.test.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,10 @@ import {
6060
* ceilings, the committed-tree reader, rename following, the bounded attribution, recursive
6161
* discovery) additionally has its own failing-direction test below, because a real tree that
6262
* happens to satisfy its rules cannot distinguish a correct rule from a vacuous one.
63-
* - Cost: one unit-lane test file plus two data/reader modules; four git processes for the
64-
* merge-base side (merge-base, ls-tree, one cat-file batch, one rename diff), no device. The
63+
* - Cost: one unit-lane test file plus two data/reader modules; up to five git processes for the
64+
* merge-base side (merge-base, ls-tree, one cat-file batch for the base tree, one rename diff,
65+
* and one further cat-file batch only when the closure loses sources — the deleted-file contents
66+
* the novelty check reads), no device. The
6567
* walker memoizes per-file edges per tree and parses each file once per distinct content, so
6668
* the base tree pays only for the files the branch changed.
6769
* - Kill criterion: if two consecutive quarters show no rule ever firing, or

0 commit comments

Comments
 (0)