Skip to content

Commit fd4d0f0

Browse files
committed
refactor(layering): one retired-paths rule for src/utils and src/replay
R14 (src-utils-retirement) and R71 (replay-ownership) were the same path-prefix denylist instantiated in two modules. Fold both into scripts/layering/retired-paths-policy.ts, driven by a table keyed by rule id; ids, messages, inputs (tracked src/utils paths vs production sources) and check.ts wiring are unchanged.
1 parent e624ef9 commit fd4d0f0

6 files changed

Lines changed: 106 additions & 86 deletions

File tree

scripts/layering/check.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,10 @@ import {
112112
import { runtimeExecutionIntegrityViolations } from './runtime-execution-policy.ts';
113113
import { sourceExecutionCompatibilityViolations } from './source-execution-policy.ts';
114114
import { sessionResourceOwnershipViolations } from './session-resource-ownership.ts';
115-
import { replayOwnershipViolations } from './replay-ownership.ts';
116115
import { applicationLifecycleOwnershipViolations } from './application-lifecycle-policy.ts';
117116
import { iosSnapshotEngineOwnershipViolations } from './ios-snapshot-engine-policy.ts';
118117
import { providerSnapshotPresentationViolations } from './provider-snapshot-presentation-policy.ts';
119-
import { SRC_UTILS_RETIREMENT_RULE, srcUtilsRetirementViolations } from './src-utils-retirement.ts';
118+
import { RETIRED_PATH_RULES, retiredPathRuleViolations } from './retired-paths-policy.ts';
120119

121120
const repoRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {
122121
encoding: 'utf8',
@@ -488,7 +487,7 @@ function report(
488487
`Layering guard: OK — ${files.length} source files satisfy R2 and contain no ` +
489488
`value-import cycles (both checked globally); the ranked target spine contains no ` +
490489
`back-edges; the ranked spine's type-only inversions match the R6 ratchet (${Object.values(TYPE_INVERSION_BASELINE).reduce((sum, count) => sum + count, 0)} remaining); ` +
491-
`${SRC_UTILS_RETIREMENT_RULE} permits no tracked paths under retired src/utils; ` +
490+
`${RETIRED_PATH_RULES.R14.rule} permits no tracked paths under retired src/utils; ` +
492491
`all ${sessionStateFieldCount()} SessionState fields are classified and every write is ` +
493492
`inside its declared owner (R7); the largest type-level cycle is ${typeCycle} files ` +
494493
`(R9); ${daemonModularitySummary()}; ` +
@@ -611,8 +610,9 @@ export const LAYERING_RULES: Readonly<Record<LayeringRuleId, LayeringRule>> = {
611610
{ untrackedProductionFiles: listUntrackedProductionTypeScriptFiles(repoRoot) },
612611
),
613612
'retired-platforms-zone': () => checkRetiredPlatformsZone(listTrackedPlatformZoneFiles(repoRoot)),
614-
'src-utils-retirement': (context) => srcUtilsRetirementViolations(context.trackedSrcUtilsFiles),
615-
'replay-ownership': (context) => replayOwnershipViolations(context.sourceFiles),
613+
'src-utils-retirement': (context) =>
614+
retiredPathRuleViolations('R14', context.trackedSrcUtilsFiles),
615+
'replay-ownership': (context) => retiredPathRuleViolations('R71', context.sourceFiles),
616616
'ios-snapshot-engine-ownership': (context) =>
617617
iosSnapshotEngineOwnershipViolations(
618618
[...context.sources].map(([path, source]) => ({ path, source })),

scripts/layering/replay-ownership.test.ts

Lines changed: 0 additions & 29 deletions
This file was deleted.

scripts/layering/replay-ownership.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

scripts/layering/src-utils-retirement.test.ts renamed to scripts/layering/retired-paths-policy.test.ts

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,52 +10,95 @@ import {
1010
listTrackedSrcUtilsFiles,
1111
listTrackedTypeScriptFiles,
1212
} from './tracked-sources.ts';
13-
import { SRC_UTILS_RETIREMENT_RULE, srcUtilsRetirementViolations } from './src-utils-retirement.ts';
13+
import { RETIRED_PATH_RULES, retiredPathRuleViolations } from './retired-paths-policy.ts';
1414

1515
const repoRoot = path.resolve(import.meta.dirname, '../..');
1616

17-
function contextWithTrackedSrcUtilsFiles(trackedSrcUtilsFiles: readonly string[]): LayeringContext {
17+
function contextWithFiles(
18+
files: Partial<Pick<LayeringContext, 'sourceFiles' | 'trackedSrcUtilsFiles'>>,
19+
): LayeringContext {
1820
return {
1921
sourceFiles: [],
2022
sources: new Map(),
2123
allTypeScriptSources: new Map(),
22-
trackedSrcUtilsFiles,
24+
trackedSrcUtilsFiles: [],
2325
edges: [],
2426
typeCycleMembers: [],
27+
...files,
2528
};
2629
}
2730

2831
test('R14 rejects every tracked path under the retired src/utils zone', () => {
2932
const trackedFiles = ['src/utils', 'src/utils/regrown.ts', 'src/utils/__fixtures__/capture.json'];
30-
const violations = srcUtilsRetirementViolations(trackedFiles);
33+
const violations = retiredPathRuleViolations('R14', trackedFiles);
3134

32-
assert.equal(SRC_UTILS_RETIREMENT_RULE, 'R14 src-utils-retirement');
35+
assert.equal(RETIRED_PATH_RULES.R14.rule, 'R14 src-utils-retirement');
3336
assert.deepEqual(
34-
violations.map(({ rule, file }) => ({ rule, file })),
35-
trackedFiles.map((file) => ({ rule: SRC_UTILS_RETIREMENT_RULE, file })),
37+
violations,
38+
trackedFiles.map((file) => ({
39+
rule: 'R14 src-utils-retirement',
40+
file,
41+
line: 1,
42+
message: 'src/utils is retired; move this path to its owning package or command zone',
43+
})),
3644
);
3745
assert.deepEqual(
38-
LAYERING_RULES['src-utils-retirement'](contextWithTrackedSrcUtilsFiles(trackedFiles)),
46+
LAYERING_RULES['src-utils-retirement'](
47+
contextWithFiles({ trackedSrcUtilsFiles: trackedFiles, sourceFiles: trackedFiles }),
48+
),
3949
violations,
4050
);
41-
for (const violation of violations) {
42-
assert.match(violation.message, /src\/utils is retired; move this path/);
43-
}
4451
});
4552

4653
test('R14 ignores paths outside the retired zone', () => {
4754
assert.deepEqual(
48-
srcUtilsRetirementViolations([
55+
retiredPathRuleViolations('R14', [
4956
'src/snapshot/scroll-edge-state.ts',
5057
'src/utils-next.ts',
58+
'src/replay/planted.ts',
5159
'test/fixtures/src/utils/example.json',
5260
]),
5361
[],
5462
);
5563
});
5664

65+
test('R71 rejects a planted production file under retired src/replay by name', () => {
66+
const violations = retiredPathRuleViolations('R71', ['src/replay/planted-production-file.ts']);
67+
68+
assert.equal(RETIRED_PATH_RULES.R71.rule, 'R71 replay-ownership');
69+
assert.deepEqual(violations, [
70+
{
71+
rule: 'R71 replay-ownership',
72+
file: 'src/replay/planted-production-file.ts',
73+
line: 1,
74+
message:
75+
'src/replay/ is retired; caller source acquisition belongs under src/commands/replay/ ' +
76+
'and replay-test presentation belongs under src/cli/replay-test/.',
77+
},
78+
]);
79+
assert.deepEqual(
80+
LAYERING_RULES['replay-ownership'](
81+
contextWithFiles({ sourceFiles: ['src/replay/planted-production-file.ts'] }),
82+
),
83+
violations,
84+
);
85+
});
86+
87+
test('R71 accepts the current replay owners and ignores the src/utils zone', () => {
88+
assert.deepEqual(
89+
retiredPathRuleViolations('R71', [
90+
'src/commands/replay/script-source-bundle.ts',
91+
'src/cli/replay-test/reporting.ts',
92+
'src/daemon/replay-script-source.ts',
93+
'src/replay-next.ts',
94+
'src/utils/regrown.ts',
95+
]),
96+
[],
97+
);
98+
});
99+
57100
test('tracked-path discovery includes top-level and nested retired paths', () => {
58-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'src-utils-retirement-'));
101+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'retired-paths-policy-'));
59102
fs.mkdirSync(path.join(repo, 'src/utils/nested'), { recursive: true });
60103
fs.mkdirSync(path.join(repo, 'packages/example/src'), { recursive: true });
61104
fs.mkdirSync(path.join(repo, 'src/snapshot'), { recursive: true });
@@ -84,18 +127,18 @@ test('tracked-path discovery includes top-level and nested retired paths', () =>
84127
assert.deepEqual(listTrackedPackageManifests(repo), ['packages/example/package.json']);
85128
assert.deepEqual(
86129
LAYERING_RULES['src-utils-retirement'](
87-
contextWithTrackedSrcUtilsFiles(listTrackedSrcUtilsFiles(repo)),
130+
contextWithFiles({ trackedSrcUtilsFiles: listTrackedSrcUtilsFiles(repo) }),
88131
).map(({ rule, file }) => ({ rule, file })),
89132
[
90-
{ rule: SRC_UTILS_RETIREMENT_RULE, file: 'src/utils/café.ts' },
91-
{ rule: SRC_UTILS_RETIREMENT_RULE, file: 'src/utils/nested/capture.json' },
92-
{ rule: SRC_UTILS_RETIREMENT_RULE, file: 'src/utils/regrown.ts' },
133+
{ rule: 'R14 src-utils-retirement', file: 'src/utils/café.ts' },
134+
{ rule: 'R14 src-utils-retirement', file: 'src/utils/nested/capture.json' },
135+
{ rule: 'R14 src-utils-retirement', file: 'src/utils/regrown.ts' },
93136
],
94137
);
95138
});
96139

97140
test('tracked-path discovery includes an exact retired root entry', () => {
98-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'src-utils-retirement-root-'));
141+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'retired-paths-policy-root-'));
99142
fs.mkdirSync(path.join(repo, 'src'), { recursive: true });
100143
fs.writeFileSync(path.join(repo, 'src/utils'), 'retired root\n');
101144
execFileSync('git', ['init', '-q'], { cwd: repo });
@@ -104,10 +147,10 @@ test('tracked-path discovery includes an exact retired root entry', () => {
104147
assert.deepEqual(listTrackedSrcUtilsFiles(repo), ['src/utils']);
105148
});
106149

107-
test('the committed tree has no tracked src/utils path', () => {
150+
test('the committed tree has no tracked path under a retired directory', () => {
108151
assert.deepEqual(
109152
LAYERING_RULES['src-utils-retirement'](
110-
contextWithTrackedSrcUtilsFiles(listTrackedSrcUtilsFiles(repoRoot)),
153+
contextWithFiles({ trackedSrcUtilsFiles: listTrackedSrcUtilsFiles(repoRoot) }),
111154
),
112155
[],
113156
);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { LayeringViolation } from './model.ts';
2+
import { retiredPathViolations } from './retired-zone-policy.ts';
3+
4+
export type RetiredPathRuleId = 'R14' | 'R71';
5+
6+
type RetiredPathRule = Readonly<{ rule: string; prefix: string; guidance: string }>;
7+
8+
/**
9+
* Retired source directories, keyed by layering rule id. A path is rejected when it is the
10+
* prefix itself or lives under it; the message names the prefix and points at the owner.
11+
*/
12+
export const RETIRED_PATH_RULES: Readonly<Record<RetiredPathRuleId, RetiredPathRule>> = {
13+
R14: {
14+
rule: 'R14 src-utils-retirement',
15+
prefix: 'src/utils',
16+
guidance: 'move this path to its owning package or command zone',
17+
},
18+
R71: {
19+
rule: 'R71 replay-ownership',
20+
prefix: 'src/replay/',
21+
guidance:
22+
'caller source acquisition belongs under src/commands/replay/ and replay-test ' +
23+
'presentation belongs under src/cli/replay-test/.',
24+
},
25+
};
26+
27+
export function retiredPathRuleViolations(
28+
id: RetiredPathRuleId,
29+
files: readonly string[],
30+
): LayeringViolation[] {
31+
const { rule, prefix, guidance } = RETIRED_PATH_RULES[id];
32+
return retiredPathViolations(
33+
files,
34+
prefix.replace(/\/$/, ''),
35+
rule,
36+
`${prefix} is retired; ${guidance}`,
37+
);
38+
}

scripts/layering/src-utils-retirement.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)