Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | 67x 67x 2195x 2162x 186x 1976x 480x 480x 480x 942x 942x 261x 681x 681x 480x 517x 517x | /**
* Issue #1241: records where an ADR's rule fired during transpilation.
*
* Occupancy of the scope-context matrix is derived from source POSITIONS. A
* diagnostic supplies one; a codegen decision did not, so ADRs governing
* successful resolution (ADR-016, ADR-057) had fixtures that could never occupy
* a cell -- the gate read `warn` for seventeen cells that eleven fixtures were
* already exercising. That is an observability gap, not a coverage gap, and
* writing more fixtures could not have closed it.
*
* Deliberately a plain sink, not a decision-maker: it records that a rule fired
* at a position and answers nothing about which cell that is. Classification
* stays in the matrix tooling, walking the parse tree, so provenance cannot
* drift from the fixture the way a declared cell can.
*
* The current file lives here rather than being passed at every call. A
* recording site is a one-liner inside a decision it is already making, and
* threading a path through helpers that deliberately take injected deps
* (TypeGenerationHelper) would have made those helpers know about global state
* purely to report on themselves.
*
* ## #1452, and what this module does NOT satisfy
*
* It moved out of `src/transpiler/state/` for box 1, and it does not satisfy
* box 4 -- "no module reachable from the pipeline holds mutable cross-pass
* state". It holds two mutable statics, written at 17 `record` sites across
* 2.1 Analyze and 2.3 Render plus four `beginFile` sites, and read once at the
* end of the run. That is cross-pass by the box's literal text.
*
* Neither alternative is better. Splitting the sink per pass yields three
* accumulators where there is one, and the only consumer
* (`scripts/matrix/AdrProvenanceLines.ts`) reads `adr` and `line` and never
* `sourcePath`, so the attribution a split changes is unobservable to the only
* gate watching it. Threading an instance through the call sites contradicts
* the paragraph above, which is a recorded design decision, not an oversight.
*
* The owner's call, taken 2026-09-23: box 4 governs PROGRAM state. A fact about
* the run is not one, so `src/instrumentation/` is admitted by
* `docs/architecture/README.md` as a fourth kind of root beside the layers, the
* shared contracts and the host -- and as the one root that may hold mutable
* state, because what it accumulates is an observation rather than a fact
* carried between passes. Nothing downstream branches on it.
*
* That exemption is stated, not incidental. `passes-hold-no-mutable-state.test.ts`
* scans the layers and not this root because instrumentation is not a pass, and
* the line it rests on is enforced: `instrumentation-cannot-import-a-layer`
* (`reachable: true`) stops this module reaching back into the thing it reports
* on. Mutation-checked -- an import of a 1-Analyze module produces twelve
* violations naming this file.
*
* The sibling arrives with the rest of #1452: the toolchain-requirement
* accumulator, still inside `CodeGenState` today, is the same kind of fact and
* belongs here too.
*/
import type IRecordedAdrSite from "../types/IRecordedAdrSite";
class AdrProvenance {
private static sites: IRecordedAdrSite[] = [];
private static currentSourcePath: string | null = null;
/** Attribute subsequent recordings to `sourcePath`. */
static beginFile(sourcePath: string | null): void {
this.currentSourcePath = sourcePath;
}
/**
* Note that `adr`'s rule fired at `line` of the current file.
*
* Silently ignores a call with no current file or a non-positive line: a
* position that cannot be resolved must not become occupancy for whatever
* declaration happens to start the file, which is the same reason
* FixtureContext refuses the synthetic 1:0 placeholder.
*/
static record(adr: string, line: number | null | undefined): void {
if (!this.currentSourcePath || line == null || line <= 0) {
return;
}
this.sites.push({ adr, sourcePath: this.currentSourcePath, line });
}
/** Every site recorded since the last reset, deduplicated. */
static collect(): readonly IRecordedAdrSite[] {
const seen = new Set<string>();
const unique: IRecordedAdrSite[] = [];
for (const site of this.sites) {
const key = `${site.adr} ${site.sourcePath} ${site.line}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
unique.push(site);
}
return unique;
}
static reset(): void {
this.sites = [];
this.currentSourcePath = null;
}
}
export default AdrProvenance;
|