Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 79 additions & 5 deletions scripts/layering/daemon-modularity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { test } from 'node:test';
import {
checkDaemonModularityRatchets,
DAEMON_MODULARITY_BASELINE,
LOGICAL_MODULE_POLICIES,
TYPE_CYCLE_BASELINE,
} from './daemon-modularity.ts';
import { SESSION_STATE_FIELD_OWNERS } from './session-state.ts';
Expand All @@ -27,6 +28,20 @@ function baselineDaemonTypesEdges(): ResolvedImportEdge[] {
);
}

function recordedMigrationEdges(): ResolvedImportEdge[] {
return LOGICAL_MODULE_POLICIES.flatMap((module) =>
(module.recordedMigrationImports ?? []).map((recorded) => {
const [file, target] = recorded.split(' -> ');
return importEdge(file!, target!);
}),
);
}

/** Every recorded import present and nothing else forbidden: the quiet state of the ratchets. */
function baselineEdges(): ResolvedImportEdge[] {
return [...baselineDaemonTypesEdges(), ...recordedMigrationEdges()];
}

test('daemon modularity baseline records the measured R7 ownership pressure', () => {
assert.equal(
Object.keys(SESSION_STATE_FIELD_OWNERS).length,
Expand All @@ -49,11 +64,14 @@ test('external daemon/types.ts importer membership changes require the baseline
]),
);

const violations = checkDaemonModularityRatchets([...baselineDaemonTypesEdges(), ...edges], []);
const violations = checkDaemonModularityRatchets([...baselineEdges(), ...edges], []);
assert.equal(violations.length, 1);
assert.match(violations[0]!.message, /may only shrink from the recorded 4/);

const removed = checkDaemonModularityRatchets(baselineDaemonTypesEdges().slice(1), []);
const removed = checkDaemonModularityRatchets(
[...baselineDaemonTypesEdges().slice(1), ...recordedMigrationEdges()],
[],
);
assert.equal(removed.length, 1);
assert.match(removed[0]!.message, /delete it from externalDaemonTypesImporters/);
});
Expand All @@ -66,11 +84,67 @@ test('planned logical modules start with zero forbidden imports', () => {
]),
);

const violations = checkDaemonModularityRatchets([...baselineDaemonTypesEdges(), ...edges], []);
const violations = checkDaemonModularityRatchets([...baselineEdges(), ...edges], []);
assert.equal(violations.length, 1);
assert.match(violations[0]!.message, /replay-test must not import/);
});

test('replay-test rejects request-global and engine-internal imports', () => {
const edges = resolveImportEdges(
new Map([
[
'src/replay/test/scheduler.ts',
[
"import { emitRequestProgress } from '../../request/progress.ts';",
"import { readReplayScriptMetadata } from '../script.ts';",
"import { parseMaestroProgram } from '../../compat/maestro/program-ir-parser.ts';",
].join('\n'),
],
['src/request/progress.ts', 'export function emitRequestProgress() {}'],
['src/replay/script.ts', 'export function readReplayScriptMetadata() {}'],
['src/compat/maestro/program-ir-parser.ts', 'export function parseMaestroProgram() {}'],
]),
);

const violations = checkDaemonModularityRatchets([...baselineEdges(), ...edges], []);
assert.deepEqual(
violations.map(({ message }) => message.replace(/;.*/, '')),
[
'replay-test must not import src/request/progress.ts',
'replay-test must not import src/replay/script.ts',
'replay-test must not import src/compat/maestro/program-ir-parser.ts',
],
);
});

test('replay-test may still import its own files inside the wider replay engine root', () => {
const edges = resolveImportEdges(
new Map([
['src/replay/test/reporting.ts', "import { spec } from './reporters/spec.ts';"],
['src/replay/test/reporters/spec.ts', 'export const spec = 1;'],
]),
);

assert.deepEqual(checkDaemonModularityRatchets([...baselineEdges(), ...edges], []), []);
});

test('recorded replay-test migration imports are exempt until the import is deleted', () => {
const recorded = LOGICAL_MODULE_POLICIES.find(
({ name }) => name === 'replay-test',
)?.recordedMigrationImports;
assert.deepEqual(recorded, [
'src/replay/test/reporters/default.ts -> src/replay/divergence.ts',
'src/replay/test/reporters/progress.ts -> src/request/progress.ts',
'src/replay/test/reporters/registry.ts -> src/request/progress.ts',
'src/replay/test/reporting.ts -> src/request/progress.ts',
]);
assert.deepEqual(checkDaemonModularityRatchets(baselineEdges(), []), []);

const withoutOne = checkDaemonModularityRatchets(baselineEdges().slice(0, -1), []);
assert.equal(withoutOne.length, 1);
assert.match(withoutOne[0]!.message, /delete it from replay-test's recordedMigrationImports/);
});

test('internal trees reject deep imports globally, including from daemon', () => {
const edges = resolveImportEdges(
new Map([
Expand All @@ -79,7 +153,7 @@ test('internal trees reject deep imports globally, including from daemon', () =>
]),
);

const violations = checkDaemonModularityRatchets([...baselineDaemonTypesEdges(), ...edges], []);
const violations = checkDaemonModularityRatchets([...baselineEdges(), ...edges], []);
assert.equal(violations.length, 1);
assert.match(violations[0]!.message, /must not import maestro's internal tree/);
});
Expand All @@ -89,7 +163,7 @@ test('R9 records zone ceilings and keeps engine files outside the largest compon
{ length: DAEMON_MODULARITY_BASELINE.largestTypeCycle.zoneMembers.commands + 1 },
(_, index) => `src/commands/probe-${index}.ts`,
);
const violations = checkDaemonModularityRatchets(baselineDaemonTypesEdges(), [
const violations = checkDaemonModularityRatchets(baselineEdges(), [
...commandMembers,
'src/ad-replay/internal/engine.ts',
]);
Expand Down
68 changes: 61 additions & 7 deletions scripts/layering/daemon-modularity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ type LogicalModulePolicy = {
name: string;
roots: readonly string[];
forbiddenTargetRoots: readonly string[];
/**
* Imports that already violate `forbiddenTargetRoots` on the day the rule was written, recorded
* as `source -> target`. The rule enforces immediately for everything else, so a new violation
* cannot be added while the module waits for its extraction PR; each recorded edge must be
* deleted from this list by the change that removes the import, and re-adding one is a diff a
* reviewer sees.
*/
recordedMigrationImports?: readonly string[];
};

/**
Expand All @@ -61,9 +69,28 @@ export const LOGICAL_MODULE_POLICIES: readonly LogicalModulePolicy[] = [
forbiddenTargetRoots: ['src/daemon/', 'src/platforms/', 'src/providers/', 'src/ad-replay/'],
},
{
// Replay-test schedules and reports; it must stay format-neutral. `src/request/` is
// request-global daemon plumbing (progress sinks, cancellation, AsyncLocalStorage), and the
// remaining roots are engine internals — reaching into either is how a scheduler quietly
// acquires daemon authority or an engine-specific value shape.
name: 'replay-test',
roots: ['src/replay/test/'],
forbiddenTargetRoots: ['src/daemon/', 'src/platforms/', 'src/providers/'],
forbiddenTargetRoots: [
'src/daemon/',
'src/platforms/',
'src/providers/',
'src/request/',
'src/replay/',
'src/compat/',
'src/maestro/',
'src/ad-replay/',
],
recordedMigrationImports: [
'src/replay/test/reporters/default.ts -> src/replay/divergence.ts',
'src/replay/test/reporters/progress.ts -> src/request/progress.ts',
'src/replay/test/reporters/registry.ts -> src/request/progress.ts',
'src/replay/test/reporting.ts -> src/request/progress.ts',
],
},
];

Expand Down Expand Up @@ -185,6 +212,7 @@ function checkDaemonTypesImporters(edges: readonly ResolvedImportEdge[]): Layeri

function checkLogicalModuleImports(edges: readonly ResolvedImportEdge[]): LayeringViolation[] {
const violations: LayeringViolation[] = [];
const observedMigrationImports = new Set<string>();
for (const edge of edges) {
const sourceModule = moduleForFile(edge.file);
const targetModule = moduleForFile(edge.target);
Expand All @@ -203,14 +231,36 @@ function checkLogicalModuleImports(edges: readonly ResolvedImportEdge[]): Layeri
}

if (!sourceModule) continue;
if (sourceModule.forbiddenTargetRoots.some((root) => edge.target.startsWith(root))) {
// A module's own files are never a forbidden target: `replay-test` sits inside the wider
// `src/replay/` engine root it may not import from.
if (sourceModule.roots.some((root) => edge.target.startsWith(root))) continue;
if (!sourceModule.forbiddenTargetRoots.some((root) => edge.target.startsWith(root))) continue;
const migrationImport = `${edge.file} -> ${edge.target}`;
if (sourceModule.recordedMigrationImports?.includes(migrationImport)) {
observedMigrationImports.add(migrationImport);
continue;
}
violations.push({
rule: 'R10 daemon-modularity',
file: edge.file,
line: edge.line,
message: `${sourceModule.name} must not import ${edge.target}; communicate through its façade and a narrow port with two real adapters.`,
});
}
return [...violations, ...checkRecordedMigrationImports(observedMigrationImports)];
}

function checkRecordedMigrationImports(observed: ReadonlySet<string>): LayeringViolation[] {
const violations: LayeringViolation[] = [];
for (const module of LOGICAL_MODULE_POLICIES) {
for (const migrationImport of module.recordedMigrationImports ?? []) {
if (observed.has(migrationImport)) continue;
violations.push({
rule: 'R10 daemon-modularity',
file: edge.file,
line: edge.line,
message: `${sourceModule.name} must not import ${edge.target}; communicate through its façade and a narrow port with two real adapters.`,
file: 'scripts/layering/daemon-modularity.ts',
line: 1,
message: `${migrationImport} no longer exists — delete it from ${module.name}'s recordedMigrationImports in the same change so the import cannot return.`,
});
continue;
}
}
return violations;
Expand All @@ -237,10 +287,14 @@ function countBy(values: readonly string[], keyOf: (value: string) => string): M

export function daemonModularitySummary(): string {
const session = DAEMON_MODULARITY_BASELINE.sessionState;
const recordedMigrationImports = LOGICAL_MODULE_POLICIES.reduce(
(sum, module) => sum + (module.recordedMigrationImports?.length ?? 0),
0,
);
return (
`R10 pins R7 at ${session.writerOwnedFields} writer-owned fields / ` +
`${session.ownerFileClaims} owner claims, R9 at ${TYPE_CYCLE_BASELINE} files with zone ceilings, ` +
`${DAEMON_MODULARITY_BASELINE.externalDaemonTypesImporters.length} external daemon/types.ts importers, ` +
'and zero forbidden logical-module imports'
`and zero forbidden logical-module imports beyond ${recordedMigrationImports} recorded migration import(s)`
);
}
Loading
Loading