22
33## Status
44
5- Accepted (2026-10-07). Decision spike for #3278 (umbrella #3276 , workstream 2 ), measured at
6- ` 7dda0c2bf ` . No rule moves. The spike's harness is temporary; see [ Deletion] ( #deletion ) .
5+ Accepted (2026-10-07). Spike for #3278 (umbrella #3276 ), measured by the harness at ` d9959f510 `
6+ (production tree of ` 7dda0c2bf ` ) . No rule moves; the harness is temporary ( [ Deletion] ( #deletion ) ).
77
88## Rules at a glance
99
1010- R2, R4, R5, R6, R77, R78, R14 and R71 stay on the ` resolveImportEdges ` edge model. Neither
1111 dependency-cruiser 18.5 nor fallow 3.32 replaces them.
1212- Every layering rule reads one edge model. Do not enforce a subset from a second import graph: both
1313 engines disagree with it (tables below), and about 20 AST and ownership rules keep it alive.
14- - Do not upgrade fallow to 3.x to get ` boundaries ` .
1514- Fix the custom gap at the shared parser: ` parseImports ` misses TypeScript ` import('x').T ` type
1615 positions (9 file pairs). The fix may move the R9 type-cycle ratchet.
17- - Revisit when a [ trigger] ( #revisit-triggers ) fires, re-running the harness first .
16+ - Do not upgrade fallow to 3.x for ` boundaries ` ; revisit on a [ trigger] ( #revisit-triggers ) .
1817
1918## Measured
2019
21- ` scripts/layering/boundary-engine-spike.ts ` produces every number below (usage in its header). It
22- generates both engine configs from ` TARGET_DAG_RANK ` and the rule tables, and refuses to run unless
23- the production roots hold exactly the tracked tree (1,837 files, 41 zones). Recorded R6 and R78
24- edges go to each engine's known-violations baseline.
20+ ` scripts/layering/boundary-engine-spike.ts ` produces the edge-set, parity, clean-tree and runtime
21+ numbers; the deletion table counts lines of the named code. The harness builds both engine configs
22+ from ` TARGET_DAG_RANK ` and the rule tables, runs only on exactly the tracked production tree (1,837
23+ files, 41 zones), and gives recorded R6 and R78 edges to each engine's known-violations baseline.
2524
2625### Edge-set diff against ` resolveImportEdges `
2726
@@ -36,19 +35,18 @@ edges go to each engine's known-violations baseline.
3635The 9 extra pairs are ` import('./x').T ` type positions custom misses; both engines are right.
3736
3837The 3.1.1 cross-check missed 88 dynamic and type-only edges; 18.5 misses none but mislabels 46 pairs
39- (33 dynamic → type, 8 dynamic+type → type, 5 dynamic+value → value, with tsc or swc). It deduplicates
40- a file's dependencies on ` (specifier, moduleSystem, type-only flag) ` ( ` getDependencyUniqueKey ` , not
41- configurable), so ` typeof import('./x').f ` and ` await import('./x') ` keep whichever comes first.
38+ (33 dynamic → type, 8 dynamic+type → type, 5 dynamic+value → value, with tsc or swc): its
39+ hardcoded ` getDependencyUniqueKey ` dedupes on ` (specifier, moduleSystem, type-only flag) ` , so
40+ ` typeof import('./x').f ` and ` await import('./x') ` keep whichever comes first.
4241
4342fallow's value-versus-type split matches custom exactly (6,136 runtime pairs each), with no dynamic
4443kind. Its boundary check follows ` export type * ` barrels to the declaring module, so
45- ` src/commands/cli-runner.ts → src/agent-device-client.ts ` counts as ` commands → client ` : one more R6
46- finding. Clean-tree findings : custom 0, depcruise 10 (3 R6 survivors, 7 recorded R78 edges), fallow 11.
44+ ` src/commands/cli-runner.ts → src/agent-device-client.ts ` counts as ` commands → client ` , one more R6
45+ finding. Clean tree : custom 0, depcruise 10 (3 R6 survivors, 7 recorded R78 edges), fallow 11.
4746
4847### Planted-violation parity
4948
50- ` flag ` rows must be reported; ` pass ` rows are the closest negative the custom rule admits. ✗ is a
51- miss on ` flag ` or a false positive on ` pass ` .
49+ ` pass ` rows are the closest negative custom admits; ✗ is a missed ` flag ` or a flagged ` pass ` .
5250
5351| Plant | Expect | custom | depcruise | fallow |
5452| --- | --- | --- | --- | --- |
@@ -84,13 +82,13 @@ fallow's R77 rule pack carries an id, message and line. On the stale row, depcru
8482
8583### Runtime
8684
87- Medians of 5 runs after a warm-up, on a shared host (load average about 9 ):
85+ Medians of 5 runs after a warm-up, on a shared host (load average 9–17 ):
8886
8987| | custom | depcruise | fallow |
9088| --- | --- | --- | --- |
91- | graph build | 0.75 s, shared and kept | 1.63 s (tsc), 1.66 s (swc) | 3.26 s standalone |
89+ | graph build | 0.74 s, shared and kept | 1.66 s (tsc), 1.56 s (swc) | 3.04 s standalone |
9290| the eight rules | 0.10 s | included | included |
93- | marginal CI cost | 0.10 s | +1.6 s, a second graph | +0.26 s in the repo's dead-code run (3.27 → 3.53 s) |
91+ | marginal CI cost | 0.10 s | +1.6 s, a second graph | none measurable in the repo's dead-code run (3.23 → 3.13 s) |
9492
9593### Lines deletable
9694
@@ -108,9 +106,9 @@ Rule plus test lines a full migration would delete. The resolver and the R4/R5/R
108106| R14, R71 | ` retired-paths-policy.ts ` | 218 | TS only (R14 gap) | TS only, generic |
109107| shared | registry, imports, success line in ` check.ts ` | ≈25 | | |
110108
111- About 1,224 lines, half of them tests; a migration adds about 110 lines of generated config. An
112- exact-edge R6 baseline also changes policy: it rejects swapping one inversion for another within a
113- pair, which the per-pair count ratchet admits.
109+ About 1,224 lines, half of them tests; a migration adds a generator like the harness's
110+ ` depcruiseConfig ` and helpers (122 lines). An exact-edge R6 baseline also changes policy: it rejects
111+ swapping one inversion for another within a pair, which the per-pair count ratchet admits.
114112
115113## Decision
116114
@@ -122,10 +120,10 @@ the custom graph:
122120 fail on stale entries, and needs ` typescript@<7 ` or ` @swc/core ` beside TypeScript 7 (+1.6 s).
123121 Adopting it for four rules would delete about 430 lines and put two disagreeing import graphs
124122 behind one gate.
125- - ** fallow** is already a dependency, adds about 0.3 s, and has the best stale gate. It has no
126- dynamic kind, so R5's and R77's lazy-seam exemption becomes violations or misses. Its boundary
127- findings carry no rule id or hint, its rule packs skip files no entry point reaches, and 3.x rejects
128- the 73 ` comment ` fields in ` .fallowrc.json ` .
123+ - ** fallow** is already a dependency, adds no measurable time (± 0.3 s between runs) , and has the
124+ best stale gate. With no dynamic kind, R5's and R77's lazy-seam exemption becomes violations or
125+ misses. Its boundary findings carry no rule id or hint, its rule packs skip files no entry point
126+ reaches, and 3.x rejects the 73 ` comment ` fields in ` .fallowrc.json ` .
129127
130128## Revisit triggers
131129
@@ -136,6 +134,6 @@ the custom graph:
136134
137135## Deletion
138136
139- ` scripts/layering/boundary-engine-spike.ts ` is not a gate, and nothing keeps it compiling. The change
140- that evaluates a revisit trigger re-runs it, updates the tables here, and deletes it together with
141- any migrated rule's code from the deletion table.
137+ ` scripts/layering/boundary-engine-spike.ts ` is not a gate, and nothing keeps it compiling. The
138+ change that evaluates a revisit trigger re-runs it, updates the tables here, and deletes it together
139+ with any migrated rule's code from the deletion table.
0 commit comments