refactor(layering): one parametrized runtime-command-cutover gate (ADR 0019 §8) (#1745)

* refactor(layering): fold the four cutover policies into one parametrized gate

ADR 0019 §8: the per-command cutover gates consolidate into one parametrized
runtime-command-cutover gate driven by a table of migrated commands. Adding a
migrated command adds a row; the mechanism carries one planted-red proof instead
of one per command.

Part of #1739 (wave 0)

* fix(layering): scope cutover calls to lexical owners

* chore: format cutover ownership model
This commit is contained in:
Michał Pierzchała
2026-08-12 11:55:08 +02:00
committed by GitHub
parent 8f98d23f14
commit ba48146520
20 changed files with 2215 additions and 1846 deletions
+19 -68
View File
@@ -33,10 +33,10 @@
// owner" shape as R7's SessionState ownership, applied to bin.ts's `--help` fast path.
// - Over PLATFORM PACKAGE COMPOSITION: six private metadata façades meet at the exact root
// composition file; premature implementation loading and forbidden cross-boundary edges fail (R13).
// - Over COMMAND-ATOMIC RUNTIME CUTOVERS: retired logs, network, and record routes/admission cannot
// coexist with their operation-fact-derived descriptor and handler paths (R14-R16).
// - Over the DEVICES COMMAND CUTOVER: the handler calls the neutral inventory gateway and no
// superseded inventory module, import, or identifier remains in production (R17).
// - Over COMMAND-ATOMIC RUNTIME CUTOVERS: one parametrized gate reads the migrated-command
// table (devices R17, logs R14, network R15, record R16) and proves each command keeps
// exactly one platform-execution path — retired routes, admission, modules, and widened
// runtime access cannot coexist with its operation-fact-derived descriptor and handler.
// - Over CONTRACTS PRODUCTION SOURCE: contracts owns vocabulary only — host, process, and timer
// mechanics belong in capture-kit or an adapter (R18).
// Only `(root)` is unranked among src/ zones (see `UNRANKED_ZONES` in model.ts):
@@ -89,33 +89,16 @@ import {
platformPackagePolicySummary,
} from './platform-package-policy.ts';
import {
checkDeviceInventoryCutover,
deviceInventoryCutoverSummary,
} from './device-inventory-cutover-policy.ts';
checkRuntimeCommandCutover,
runtimeCommandCutoverSummary,
} from './runtime-command-cutover-policy.ts';
import {
listUntrackedProductionTypeScriptFiles,
readTrackedPlatformPackageDeclarations,
} from './platform-package-repository.ts';
import { policyLead, policyViolation, ZONE_POLICIES } from './zone-policy.ts';
import {
logsLegacyRouteViolations,
logsRuntimeNarrowingViolations,
logsSessionStateOwnershipViolations,
sourceExecutedUsingDeclarationViolations,
} from './logs-runtime-cutover-policy.ts';
import { contractsImplementationAuthorityViolations } from './contracts-implementation-policy.ts';
import { selectorPipelineOwnershipViolations } from './selector-pipeline-ownership.ts';
import {
networkLegacyRouteViolations,
networkRuntimeNarrowingViolations,
networkRuntimeRouteViolations,
} from './network-runtime-cutover-policy.ts';
import {
recordLegacyRouteViolations,
recordRuntimeNarrowingViolations,
recordRuntimeRouteViolations,
} from './record-runtime-cutover-policy.ts';
import { recordRuntimeDaemonMechanicsViolations } from './record-runtime-mechanics-policy.ts';
import { recordRuntimeRegistryJoinViolations } from './record-runtime-registry-policy.ts';
const repoRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {
@@ -194,16 +177,6 @@ function checkCycles(edges: readonly ResolvedImportEdge[]): LayeringViolation[]
}));
}
function checkLogsRuntimeCutover(sources: ReadonlyMap<string, string>): LayeringViolation[] {
const production = [...sources].map(([file, source]) => ({ path: file, source }));
return [
...logsLegacyRouteViolations(production),
...logsRuntimeNarrowingViolations(production),
...logsSessionStateOwnershipViolations(production),
...sourceExecutedUsingDeclarationViolations(production),
];
}
function checkContractsImplementationAuthority(
sources: ReadonlyMap<string, string>,
): LayeringViolation[] {
@@ -212,32 +185,14 @@ function checkContractsImplementationAuthority(
);
}
function checkNetworkRuntimeCutover(sources: ReadonlyMap<string, string>): LayeringViolation[] {
/**
* The record registry-join scan is a descriptor-shape check, not a cutover claim, so it
* stays its own module. Record's daemon-mechanics scan is the row's lifecycle proof and
* runs from the cutover table. Both report under R16.
*/
function checkRecordRuntimeRegistryJoin(sources: ReadonlyMap<string, string>): LayeringViolation[] {
const production = [...sources].map(([file, source]) => ({ path: file, source }));
return [
...networkLegacyRouteViolations(production),
...networkRuntimeNarrowingViolations(production),
...networkRuntimeRouteViolations(production),
].map((violation) => {
const separator = violation.indexOf(': ');
return {
rule: 'R15 network-runtime-cutover',
file: separator < 0 ? '(network runtime)' : violation.slice(0, separator),
line: 1,
message: separator < 0 ? violation : violation.slice(separator + 2),
};
});
}
function checkRecordRuntimeCutover(sources: ReadonlyMap<string, string>): LayeringViolation[] {
const production = [...sources].map(([file, source]) => ({ path: file, source }));
return [
...recordLegacyRouteViolations(production),
...recordRuntimeDaemonMechanicsViolations(production),
...recordRuntimeNarrowingViolations(production),
...recordRuntimeRegistryJoinViolations(production),
...recordRuntimeRouteViolations(production),
].map((violation) => {
return recordRuntimeRegistryJoinViolations(production).map((violation) => {
const separator = violation.indexOf(': ');
return {
rule: 'R16 record-runtime-cutover',
@@ -621,7 +576,7 @@ function report(
`inside its declared owner (R7); every zero-dep CI job resolves without ` +
`node_modules (R8); ${typeCycleNote(typeCycle)}; ${daemonModularitySummary()}; ` +
`${packageBoundariesSummary(repoRoot)}; ${platformPackagePolicySummary()}; ` +
`${deviceInventoryCutoverSummary()}; and bin.ts imports normalizeCliCommandAlias, ` +
`${runtimeCommandCutoverSummary()}; and bin.ts imports normalizeCliCommandAlias, ` +
`actually passes it into buildCommandUsageText, and holds no local alias literals ` +
`(R12).\n`,
);
@@ -671,11 +626,10 @@ export type LayeringRule = (context: LayeringContext) => LayeringViolation[];
export const LAYERING_RULE_IDS = [
'zone-policies',
'value-import-cycles',
'logs-runtime-cutover',
'runtime-command-cutover',
'record-runtime-registry-join',
'contracts-implementation-authority',
'selector-pipeline-ownership',
'network-runtime-cutover',
'record-runtime-cutover',
'back-edges',
'type-spine-inversions',
'session-state-ownership',
@@ -684,7 +638,6 @@ export const LAYERING_RULE_IDS = [
'bin-alias-fast-path',
'package-boundaries',
'platform-package-policy',
'device-inventory-cutover',
] as const;
export type LayeringRuleId = (typeof LAYERING_RULE_IDS)[number];
@@ -692,13 +645,12 @@ export type LayeringRuleId = (typeof LAYERING_RULE_IDS)[number];
export const LAYERING_RULES: Readonly<Record<LayeringRuleId, LayeringRule>> = {
'zone-policies': (context) => checkLayeringRules(context.edges),
'value-import-cycles': (context) => checkCycles(context.edges),
'logs-runtime-cutover': (context) => checkLogsRuntimeCutover(context.sources),
'runtime-command-cutover': (context) => checkRuntimeCommandCutover(context.sources),
'record-runtime-registry-join': (context) => checkRecordRuntimeRegistryJoin(context.sources),
'contracts-implementation-authority': (context) =>
checkContractsImplementationAuthority(context.sources),
'selector-pipeline-ownership': (context) =>
selectorPipelineOwnershipViolations(context.edges, workspaceSpecifierTargets(repoRoot)),
'network-runtime-cutover': (context) => checkNetworkRuntimeCutover(context.sources),
'record-runtime-cutover': (context) => checkRecordRuntimeCutover(context.sources),
'back-edges': (context) => checkBackEdges(context.edges),
'type-spine-inversions': (context) => checkTypeInversions(context.edges),
'session-state-ownership': (context) => checkSessionStateOwnership(context.sources),
@@ -717,7 +669,6 @@ export const LAYERING_RULES: Readonly<Record<LayeringRuleId, LayeringRule>> = {
readTrackedPlatformPackageDeclarations(repoRoot),
{ untrackedProductionFiles: listUntrackedProductionTypeScriptFiles(repoRoot) },
),
'device-inventory-cutover': (context) => checkDeviceInventoryCutover(context.sources),
};
export function main(): number {
@@ -1,4 +1,5 @@
export type RecordRuntimeProductionSource = Readonly<{ path: string; source: string }>;
/** A production file the cutover policies scan: repo-relative path plus its source. */
export type ProductionSource = Readonly<{ path: string; source: string }>;
export function memberName(node: Record<string, unknown>): string | undefined {
const property = node.property as Record<string, unknown> | undefined;
@@ -1,100 +0,0 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { checkDeviceInventoryCutover } from './device-inventory-cutover-policy.ts';
const HANDLER_FILE = 'src/daemon/handlers/session-inventory.ts';
function handlerSource(localName = 'listDeviceInventory'): string {
return [
`import { listDeviceInventory as ${localName} } from '../../core/dispatch-resolve.ts';`,
`export async function handleDevices(request: Request) { return await ${localName}(request); }`,
].join('\n');
}
function sources(
handler = handlerSource(),
extra: readonly (readonly [string, string])[] = [],
): ReadonlyMap<string, string> {
return new Map([[HANDLER_FILE, handler], ...extra]);
}
function messages(input: ReadonlyMap<string, string>): string {
return checkDeviceInventoryCutover(input)
.map(({ message }) => message)
.join('\n');
}
test('devices keeps one gateway-owned handler route through an aliased import', () => {
assert.deepEqual(checkDeviceInventoryCutover(sources(handlerSource('discoverInventory'))), []);
});
test('devices rejects a handler that imports but never calls the inventory gateway', () => {
const handler = [
"import { listDeviceInventory } from '../../core/dispatch-resolve.ts';",
'export async function handleDevices() { return []; }',
].join('\n');
assert.match(messages(sources(handler)), /must call its imported listDeviceInventory binding/);
});
test('devices does not accept a call through a binding that shadows the imported alias', () => {
const handler = [
"import { listDeviceInventory as discoverInventory } from '../../core/dispatch-resolve.ts';",
'export async function decoy(discoverInventory: (request: Request) => Promise<unknown>) {',
' return await discoverInventory({});',
'}',
].join('\n');
assert.match(messages(sources(handler)), /shadows its imported listDeviceInventory binding/);
});
test('devices rejects an exact legacy inventory module path', () => {
assert.match(
messages(
sources(handlerSource(), [
['src/platforms/apple/devices.ts', 'export const staleInventoryRoute = true;'],
]),
),
/legacy device inventory module path remains/,
);
});
test('devices rejects executable legacy imports and identifiers', () => {
const legacy = [
"import { listAppleDevices } from './platforms/apple/devices.ts';",
'export async function discover() { return await listAppleDevices(); }',
].join('\n');
const found = messages(sources(handlerSource(), [['src/legacy-inventory.ts', legacy]]));
assert.match(found, /imports legacy device inventory module/);
assert.match(found, /legacy device inventory identifier 'listAppleDevices' remains/);
});
test('devices rejects dynamic imports and re-exports of legacy inventory modules', () => {
for (const route of [
"export const legacy = import('./platforms/apple/devices.ts');",
"export { staleInventory } from './core/platform-inventory.ts';",
]) {
assert.match(
messages(sources(handlerSource(), [['src/legacy-inventory.ts', route]])),
/imports legacy device inventory module/,
);
}
});
test('devices ignores comments, strings, and similarly named non-route modules', () => {
const prose = [
"const note = 'listAppleDevices from core/platform-inventory';",
'// discoverDevices used to live in platforms/apple/devices.ts',
'export const cacheKey = note;',
].join('\n');
assert.deepEqual(
checkDeviceInventoryCutover(
sources(handlerSource('discoverInventory'), [
['src/platforms/apple/devices-cache.ts', prose],
]),
),
[],
);
});
@@ -1,257 +0,0 @@
import { parseSync } from 'oxc-parser';
import type { LayeringViolation } from './model.ts';
const RULE = 'R17 device-inventory-cutover';
const HANDLER_FILE = 'src/daemon/handlers/session-inventory.ts';
const INVENTORY_IMPORT_SOURCES = new Set([
'../../core/device-inventory-context.ts',
'../../core/dispatch-resolve.ts',
]);
const LEGACY_INVENTORY_IDENTIFIERS = new Set([
'discoverDevices',
'listAndroidDevices',
'listAppleDevices',
'listHarmonyDevices',
'listHarmonyOsDevices',
'listLinuxDevices',
'listMacosDevices',
'listVegaDevices',
'listWebDevices',
]);
type AstNode = Record<string, unknown>;
function violation(file: string, line: number, message: string): LayeringViolation {
return { rule: RULE, file, line, message };
}
function lineOf(source: string, node: AstNode): number {
return source.slice(0, (node['start'] as number | undefined) ?? 0).split('\n').length;
}
function identifierName(node: unknown): string | undefined {
if (node === null || typeof node !== 'object') return undefined;
const record = node as AstNode;
return record['type'] === 'Identifier' && typeof record['name'] === 'string'
? record['name']
: undefined;
}
function literalString(node: unknown): string | undefined {
if (node === null || typeof node !== 'object') return undefined;
const value = (node as AstNode)['value'];
return typeof value === 'string' ? value : undefined;
}
function visitAst(node: unknown, visitor: (node: AstNode) => void): void {
if (node === null || typeof node !== 'object') return;
if (Array.isArray(node)) {
for (const child of node) visitAst(child, visitor);
return;
}
const record = node as AstNode;
visitor(record);
for (const value of Object.values(record)) visitAst(value, visitor);
}
function isLegacyInventoryModulePath(file: string): boolean {
return (
file === 'src/core/platform-inventory.ts' ||
/^src\/platforms\/(?:[^/]+\/)*devices\.ts$/.test(file)
);
}
function isLegacyInventoryImport(specifier: string): boolean {
return (
/(?:^|\/)core\/platform-inventory(?:\.[cm]?[jt]s)?$/.test(specifier) ||
/(?:^|\/)platforms\/(?:[^/]+\/)*devices(?:\.[cm]?[jt]s)?$/.test(specifier)
);
}
function importedModuleSpecifier(node: AstNode): string | undefined {
if (
node['type'] === 'ImportDeclaration' ||
node['type'] === 'ImportExpression' ||
node['type'] === 'ExportNamedDeclaration' ||
node['type'] === 'ExportAllDeclaration'
) {
return literalString(node['source']);
}
return undefined;
}
function importedInventoryBinding(program: AstNode): string | undefined {
const body = program['body'];
if (!Array.isArray(body)) return undefined;
for (const statement of body as AstNode[]) {
if (statement['type'] !== 'ImportDeclaration') continue;
const specifier = literalString(statement['source']);
if (!specifier || !INVENTORY_IMPORT_SOURCES.has(specifier)) continue;
const importSpecifiers = statement['specifiers'];
if (!Array.isArray(importSpecifiers)) continue;
for (const imported of importSpecifiers as AstNode[]) {
if (
imported['type'] === 'ImportSpecifier' &&
identifierName(imported['imported']) === 'listDeviceInventory'
) {
return identifierName(imported['local']);
}
}
}
return undefined;
}
function callsBinding(program: AstNode, binding: string): boolean {
let called = false;
visitAst(program, (node) => {
if (node['type'] === 'CallExpression' && identifierName(node['callee']) === binding) {
called = true;
}
});
return called;
}
function patternBinds(node: unknown, binding: string): boolean {
if (node === null || typeof node !== 'object') return false;
const record = node as AstNode;
if (record['type'] === 'Identifier') return record['name'] === binding;
if (record['type'] === 'RestElement') return patternBinds(record['argument'], binding);
if (record['type'] === 'AssignmentPattern') return patternBinds(record['left'], binding);
if (record['type'] === 'TSParameterProperty') return patternBinds(record['parameter'], binding);
if (record['type'] === 'ArrayPattern') {
const elements = record['elements'];
return Array.isArray(elements) && elements.some((element) => patternBinds(element, binding));
}
if (record['type'] === 'ObjectPattern') {
const properties = record['properties'];
return (
Array.isArray(properties) &&
properties.some((property) => {
if (property === null || typeof property !== 'object') return false;
const entry = property as AstNode;
return entry['type'] === 'Property'
? patternBinds(entry['value'], binding)
: patternBinds(entry['argument'], binding);
})
);
}
return false;
}
function shadowingBindingSite(program: AstNode, binding: string): AstNode | undefined {
let found: AstNode | undefined;
visitAst(program, (node) => {
if (found) return;
if (
node['type'] === 'VariableDeclarator' ||
node['type'] === 'FunctionDeclaration' ||
node['type'] === 'FunctionExpression' ||
node['type'] === 'ClassDeclaration' ||
node['type'] === 'ClassExpression'
) {
if (patternBinds(node['id'], binding)) found = node;
if (
!found &&
Array.isArray(node['params']) &&
node['params'].some((param) => patternBinds(param, binding))
) {
found = node;
}
return;
}
if (node['type'] === 'ArrowFunctionExpression') {
if (
Array.isArray(node['params']) &&
node['params'].some((param) => patternBinds(param, binding))
) {
found = node;
}
return;
}
if (node['type'] === 'CatchClause' && patternBinds(node['param'], binding)) found = node;
});
return found;
}
function checkHandlerRoute(source: string | undefined): LayeringViolation[] {
if (source === undefined) {
return [violation(HANDLER_FILE, 1, 'devices gateway-owned handler module is missing')];
}
const program = parseSync(HANDLER_FILE, source).program as AstNode;
const binding = importedInventoryBinding(program);
if (!binding) {
return [
violation(
HANDLER_FILE,
1,
'devices handler must import listDeviceInventory from the neutral inventory owner',
),
];
}
const shadow = shadowingBindingSite(program, binding);
if (shadow) {
return [
violation(
HANDLER_FILE,
lineOf(source, shadow),
'devices handler shadows its imported listDeviceInventory binding',
),
];
}
if (!callsBinding(program, binding)) {
return [
violation(
HANDLER_FILE,
1,
'devices handler must call its imported listDeviceInventory binding',
),
];
}
return [];
}
function checkLegacyRoutes(file: string, source: string): LayeringViolation[] {
const violations: LayeringViolation[] = [];
if (isLegacyInventoryModulePath(file)) {
violations.push(violation(file, 1, 'legacy device inventory module path remains'));
}
const program = parseSync(file, source).program as AstNode;
const seenIdentifiers = new Set<string>();
visitAst(program, (node) => {
const specifier = importedModuleSpecifier(node);
if (specifier && isLegacyInventoryImport(specifier)) {
violations.push(
violation(
file,
lineOf(source, node),
`production source imports legacy device inventory module '${specifier}'`,
),
);
}
if (node['type'] !== 'Identifier') return;
const name = identifierName(node);
if (!name || !LEGACY_INVENTORY_IDENTIFIERS.has(name) || seenIdentifiers.has(name)) return;
seenIdentifiers.add(name);
violations.push(
violation(
file,
lineOf(source, node),
`legacy device inventory identifier '${name}' remains in production code`,
),
);
});
return violations;
}
export function checkDeviceInventoryCutover(
sources: ReadonlyMap<string, string>,
): LayeringViolation[] {
return [
...checkHandlerRoute(sources.get(HANDLER_FILE)),
...[...sources].flatMap(([file, source]) => checkLegacyRoutes(file, source)),
];
}
export function deviceInventoryCutoverSummary(): string {
return 'R17 holds the devices command to one gateway-owned inventory route with no legacy route';
}
@@ -1,263 +0,0 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import {
logsLegacyRouteViolations,
logsRuntimeNarrowingViolations,
logsSessionStateOwnershipViolations,
sourceExecutedUsingDeclarationViolations,
} from './logs-runtime-cutover-policy.ts';
function summaries(violations: readonly Readonly<{ file: string; message: string }>[]): string[] {
return violations.map(({ file, message }) => `${file}: ${message}`);
}
test('legacy logs scan catches a planted provider route and plugin facet', () => {
assert.deepEqual(
summaries(
logsLegacyRouteViolations([
{
path: 'src/daemon/planted.ts',
source: 'await withAppLogProvider(provider, task); await startAppLog(request);',
},
{
path: 'src/platforms/planted/plugin.ts',
source:
'export const plugin = { appLog: { resolveBackend() {} } } satisfies PlatformPlugin;',
},
]),
),
[
'src/daemon/planted.ts: legacy logs route withAppLogProvider',
'src/daemon/planted.ts: legacy logs route startAppLog',
'src/platforms/planted/plugin.ts: legacy PlatformPlugin appLog facet',
],
);
});
test('legacy logs scan ignores retired names in comments and string data', () => {
assert.deepEqual(
summaries(
logsLegacyRouteViolations([
{
path: 'src/daemon/planted.ts',
source: `
// AppLogProvider and startAppLog were removed.
const migrationNote = 'withAppLogProvider and requireCommandSupported("logs")';
const metadata = { note: 'appLogProvider' };
`,
},
{
path: 'src/platforms/apple/plugin.ts',
source: `const note = 'PUBLIC_COMMANDS.logs';`,
},
]),
),
[],
);
});
test('legacy logs scan retains type-import and computed executable route coverage', () => {
assert.deepEqual(
summaries(
logsLegacyRouteViolations([
{
path: 'src/daemon/planted.ts',
source: `
import type { AppLogProvider as LegacyProvider } from './old-app-log.ts';
const route = handlers['startAppLog'];
`,
},
]),
),
[
'src/daemon/planted.ts: legacy logs route AppLogProvider',
'src/daemon/planted.ts: legacy logs route startAppLog',
],
);
});
test('legacy logs scan catches planted capability and dual-admission routes', () => {
const violations = logsLegacyRouteViolations([
{
path: 'src/core/command-descriptor/registry.ts',
source: `
const registry = [
{ name: 'logs', capability: { apple: {} }, platformExecution: runtime },
{ name: 'events', capability: { apple: {} } },
];
`,
},
{
path: 'src/daemon/handlers/planted.ts',
source: `
const literal = requireCommandSupported('logs', device);
const symbolic = requireCommandSupported(PUBLIC_COMMANDS.logs, device);
`,
},
{
path: 'src/platforms/apple/plugin.ts',
source: `const supports = { [PUBLIC_COMMANDS.logs]: supportsCoreDevice };`,
},
{
path: 'src/core/capabilities.ts',
source: `const HARMONYOS_SUPPORTED_COMMANDS = new Set(['open', 'logs']);`,
},
]);
assert.deepEqual(summaries(violations), [
'src/core/command-descriptor/registry.ts: logs descriptor retains legacy capability admission',
'src/daemon/handlers/planted.ts: legacy logs capability admission requireCommandSupported',
'src/daemon/handlers/planted.ts: legacy logs capability admission requireCommandSupported',
'src/platforms/apple/plugin.ts: Apple plugin retains legacy logs support or hint closure',
'src/core/capabilities.ts: HarmonyOS static command set retains logs admission',
]);
});
test('legacy logs scan follows declaration roles after files move', () => {
const violations = logsLegacyRouteViolations([
{
path: 'src/moved/command-declarations.ts',
source: `const descriptor = { name: 'logs', capability: { apple: {} } };`,
},
{
path: 'src/moved/platform-contract.ts',
source: `export type PlatformPlugin = { readonly appLog?: { inspect(): void } };`,
},
{
path: 'src/moved/apple-family.ts',
source: `const plugin = { appLog: { inspect() {} } } as const satisfies PlatformPlugin;`,
},
{
path: 'src/moved/typed-plugin.ts',
source: `const plugin: PlatformPlugin = { appLog: { inspect() {} } };`,
},
{
path: 'src/moved/capability-data.ts',
source: `const HARMONYOS_SUPPORTED_COMMANDS = new Set(['logs']);`,
},
]);
assert.deepEqual(
violations.map(({ file, message }) => ({ file, message })),
[
{
file: 'src/moved/command-declarations.ts',
message: 'logs descriptor retains legacy capability admission',
},
{
file: 'src/moved/platform-contract.ts',
message: 'legacy PlatformPlugin appLog facet',
},
{
file: 'src/moved/apple-family.ts',
message: 'legacy PlatformPlugin appLog facet',
},
{
file: 'src/moved/typed-plugin.ts',
message: 'legacy PlatformPlugin appLog facet',
},
{
file: 'src/moved/capability-data.ts',
message: 'HarmonyOS static command set retains logs admission',
},
],
);
});
test('R14 violations retain their exact source line and punctuation', () => {
const [violation] = logsRuntimeNarrowingViolations([
{
path: 'src/daemon/moved-handler.ts',
source: `
const note = 'reason: retained';
const widened = runtime as BoundDeviceRuntime<typeof use>;
`,
},
]);
assert.deepEqual(violation, {
rule: 'R14 logs-runtime-cutover',
file: 'src/daemon/moved-handler.ts',
line: 3,
message: 'widened logs runtime access as BoundDeviceRuntime',
});
});
test('narrowing scan catches planted assertions, non-null repair, and bracket access', () => {
const violations = logsRuntimeNarrowingViolations([
{
path: 'src/daemon/handlers/planted.ts',
source: `
const widened = runtime as BoundDeviceRuntime<typeof use>;
widened.operations.appLogStart!({});
widened.operations['appLogDoctor']({});
`,
},
]);
assert.equal(violations.length, 3);
const messages = violations.map(({ message }) => message).join('\n');
assert.match(messages, /BoundDeviceRuntime/);
assert.match(messages, /appLogStart!/);
assert.match(messages, /appLogDoctor/);
});
test('session state scan catches planted app-log record construction outside its owner', () => {
assert.deepEqual(
summaries(
logsSessionStateOwnershipViolations([
{
path: 'src/daemon/handlers/planted.ts',
source: `sessionStore.set(name, { ...session, appLog: resource, appLogFailure: undefined });`,
},
{
path: 'src/daemon/request-platform-providers.ts',
source: `
type Scope = { appLog: { provider?: AppLogProvider } };
const scope = { appLog: { provider } };
`,
},
{
path: 'src/daemon/app-log-session-resource.ts',
source: `sessionStore.set(name, { ...session, appLog: resource });`,
},
{
path: 'src/daemon/session-teardown.ts',
source: `teardownSessionResources({ appLog: 'run' }); teardownSessionResources({ appLog: 'already-settled' });`,
},
{
path: 'src/daemon/handlers/invalid-teardown.ts',
source: `teardownSessionResources({ appLog: 'skip' });`,
},
]),
),
[
'src/daemon/handlers/planted.ts: session appLog record constructed outside its owner',
'src/daemon/handlers/planted.ts: session appLogFailure record constructed outside its owner',
'src/daemon/request-platform-providers.ts: session appLog record constructed outside its owner',
'src/daemon/handlers/invalid-teardown.ts: session appLog record constructed outside its owner',
],
);
});
test('source-executed syntax scan rejects using declarations but ignores prose', () => {
assert.deepEqual(
summaries(
sourceExecutedUsingDeclarationViolations([
{
path: 'packages/platform-apple/src/logs/planted.ts',
source: `
// await using oldHandle = acquire();
const migrationNote = 'using replacement = acquire()';
async function run() { await using handle = acquire(); }
function runSync() { using cleanup = acquireSync(); }
`,
},
]),
),
[
'packages/platform-apple/src/logs/planted.ts: source-executed TypeScript uses unsupported await using declaration',
'packages/platform-apple/src/logs/planted.ts: source-executed TypeScript uses unsupported using declaration',
],
);
});
@@ -1,312 +0,0 @@
import { parseSync } from 'oxc-parser';
import type { LayeringViolation } from './model.ts';
export type LogsRuntimeProductionSource = Readonly<{ path: string; source: string }>;
const LOGS_RUNTIME_CUTOVER_RULE = 'R14 logs-runtime-cutover';
const LEGACY_LOG_ROUTE_NAMES = new Set([
'startAppLog',
'stopAppLog',
'runAppLogDoctor',
'resolveLogBackend',
'withAppLogProvider',
'appLogProvider',
'AppLogProviderResolver',
'AppLogProvider',
]);
const RUNTIME_TYPE_ASSERTION =
/\bas\s+(?:BoundDeviceRuntime|AppLogRuntimeOperations|AppLogLiveHandle)\b/g;
const NON_NULL_RUNTIME_OPERATION = /\.operations(?:\.[A-Za-z_$][\w$]*|\[[^\]]+\])!/g;
const BRACKETED_RUNTIME_OPERATION = /\.operations\[['"]appLog[A-Za-z]+['"]\]/g;
/** Legacy provider/tag execution is forbidden once the logs descriptor is runtime-backed. */
export function logsLegacyRouteViolations(
sources: readonly LogsRuntimeProductionSource[],
): LayeringViolation[] {
const violations: LayeringViolation[] = [];
for (const file of sources) {
const parsed = parseSync(file.path, file.source);
const seenRoutes = new Set<string>();
visitAst(parsed.program, (node) => {
const route = legacyRouteName(node);
if (route) {
const identity = `${String(node.start ?? '')}:${route}`;
if (!seenRoutes.has(identity)) {
seenRoutes.add(identity);
violations.push(astViolation(file, node, `legacy logs route ${route}`));
}
}
if (isLegacyLogsAdmission(node)) {
violations.push(
astViolation(file, node, 'legacy logs capability admission requireCommandSupported'),
);
}
if (isLogsDescriptorWithCapability(node)) {
violations.push(
astViolation(file, node, 'logs descriptor retains legacy capability admission'),
);
}
if (isAppleLogsCommandMember(node)) {
violations.push(
astViolation(file, node, 'Apple plugin retains legacy logs support or hint closure'),
);
}
if (isHarmonyLogsCommandSetDeclaration(node)) {
violations.push(
astViolation(file, node, 'HarmonyOS static command set retains logs admission'),
);
}
if (isPlatformPluginAppLogDeclaration(node)) {
violations.push(astViolation(file, node, 'legacy PlatformPlugin appLog facet'));
}
});
}
return violations;
}
function legacyRouteName(node: Record<string, unknown>): string | undefined {
if (node.type === 'Identifier' && LEGACY_LOG_ROUTE_NAMES.has(String(node.name))) {
return String(node.name);
}
if (node.type === 'Property' || node.type === 'TSPropertySignature') {
const key = propertyName(node.key);
return key && LEGACY_LOG_ROUTE_NAMES.has(key) ? key : undefined;
}
if (node.type === 'MemberExpression' && node.computed === true) {
const key = propertyName(node.property);
return key && LEGACY_LOG_ROUTE_NAMES.has(key) ? key : undefined;
}
return undefined;
}
function isLegacyLogsAdmission(node: Record<string, unknown>): boolean {
if (node.type !== 'CallExpression') return false;
const callee = node.callee as Record<string, unknown> | undefined;
const args = node.arguments as readonly Record<string, unknown>[] | undefined;
return (
callee?.type === 'Identifier' &&
callee.name === 'requireCommandSupported' &&
isLogsCommandExpression(args?.[0])
);
}
function isLogsCommandExpression(node: Record<string, unknown> | undefined): boolean {
if (node?.type === 'Literal') return node.value === 'logs';
if (node?.type !== 'MemberExpression') return false;
const object = node.object as Record<string, unknown> | undefined;
return (
object?.type === 'Identifier' &&
object.name === 'PUBLIC_COMMANDS' &&
memberName(node) === 'logs'
);
}
function isLogsDescriptorWithCapability(node: Record<string, unknown>): boolean {
if (node.type !== 'ObjectExpression' || !Array.isArray(node.properties)) return false;
let nameIsLogs = false;
let hasCapability = false;
for (const property of node.properties as Record<string, unknown>[]) {
if (property.type !== 'Property' || property.computed === true) continue;
const key = propertyName(property.key);
const value = property.value as Record<string, unknown> | undefined;
if (key === 'name' && value?.type === 'Literal' && value.value === 'logs') {
nameIsLogs = true;
}
if (key === 'capability') hasCapability = true;
}
return nameIsLogs && hasCapability;
}
function isAppleLogsCommandMember(node: Record<string, unknown>): boolean {
if (node.type !== 'Property' || node.computed !== true) return false;
return isLogsCommandExpression(node.key as Record<string, unknown> | undefined);
}
function isHarmonyLogsCommandSetDeclaration(node: Record<string, unknown>): boolean {
if (node.type !== 'VariableDeclarator') return false;
const id = node.id as Record<string, unknown> | undefined;
return (
id?.type === 'Identifier' &&
id.name === 'HARMONYOS_SUPPORTED_COMMANDS' &&
astContainsString(node.init, 'logs')
);
}
function isPlatformPluginAppLogDeclaration(node: Record<string, unknown>): boolean {
if (node.type === 'TSTypeAliasDeclaration' || node.type === 'TSInterfaceDeclaration') {
const id = node.id as Record<string, unknown> | undefined;
return id?.type === 'Identifier' && id.name === 'PlatformPlugin' && astContainsAppLog(node);
}
if (node.type === 'TSSatisfiesExpression' || node.type === 'TSAsExpression') {
return isNamedType(node.typeAnnotation, 'PlatformPlugin') && astContainsAppLog(node.expression);
}
if (node.type !== 'VariableDeclarator') return false;
const id = node.id as Record<string, unknown> | undefined;
return isNamedType(id?.typeAnnotation, 'PlatformPlugin') && astContainsAppLog(node.init);
}
function isNamedType(node: unknown, expected: string): boolean {
if (node === null || typeof node !== 'object') return false;
const record = node as Record<string, unknown>;
if (record.type === 'TSTypeAnnotation') return isNamedType(record.typeAnnotation, expected);
if (record.type !== 'TSTypeReference') return false;
const name = record.typeName as Record<string, unknown> | undefined;
return name?.type === 'Identifier' && name.name === expected;
}
function astContainsAppLog(node: unknown): boolean {
let found = false;
visitAst(node, (candidate) => {
if (
(candidate.type === 'Property' || candidate.type === 'TSPropertySignature') &&
candidate.computed !== true &&
propertyName(candidate.key) === 'appLog'
) {
found = true;
}
});
return found;
}
function memberName(node: Record<string, unknown>): string | undefined {
const property = node.property as Record<string, unknown> | undefined;
if (!property) return undefined;
if (node.computed === true) return propertyName(property);
return property.type === 'Identifier' ? String(property.name) : undefined;
}
function astContainsString(node: unknown, expected: string): boolean {
let found = false;
visitAst(node, (candidate) => {
if (candidate.type === 'Literal' && candidate.value === expected) found = true;
});
return found;
}
function visitAst(node: unknown, visitor: (node: Record<string, unknown>) => void): void {
if (node === null || typeof node !== 'object') return;
if (Array.isArray(node)) {
for (const child of node) visitAst(child, visitor);
return;
}
const record = node as Record<string, unknown>;
visitor(record);
for (const child of Object.values(record)) visitAst(child, visitor);
}
/** Migrated daemon owners consume narrowed operations without manufacturing facet proof. */
export function logsRuntimeNarrowingViolations(
sources: readonly LogsRuntimeProductionSource[],
): LayeringViolation[] {
const violations: LayeringViolation[] = [];
for (const file of sources.filter(({ path }) => path.startsWith('src/daemon/'))) {
for (const pattern of [
RUNTIME_TYPE_ASSERTION,
NON_NULL_RUNTIME_OPERATION,
BRACKETED_RUNTIME_OPERATION,
]) {
for (const match of file.source.matchAll(pattern)) {
violations.push(
offsetViolation(file, match.index, `widened logs runtime access ${match[0]}`),
);
}
}
}
return violations;
}
/** Session app-log state transitions have one whole-record replacement owner. */
export function logsSessionStateOwnershipViolations(
sources: readonly LogsRuntimeProductionSource[],
): LayeringViolation[] {
const violations: LayeringViolation[] = [];
for (const file of sources) {
if (
!file.path.startsWith('src/daemon/') ||
file.path === 'src/daemon/app-log-session-resource.ts' ||
file.path === 'src/daemon/types.ts'
) {
continue;
}
const parsed = parseSync(file.path, file.source);
visitAst(parsed.program, (node) => {
if (node.type === 'Property' && node.kind === 'init' && node.computed !== true) {
const field = propertyName(node.key);
if (
(field === 'appLog' || field === 'appLogFailure') &&
!isAppLogTeardownDiscriminant(field, node.value)
) {
violations.push(
astViolation(file, node, `session ${field} record constructed outside its owner`),
);
}
}
});
}
return violations;
}
function isAppLogTeardownDiscriminant(field: string, valueNode: unknown): boolean {
if (field !== 'appLog' || !valueNode || typeof valueNode !== 'object') return false;
const value = valueNode as Record<string, unknown>;
return value.type === 'Literal' && (value.value === 'run' || value.value === 'already-settled');
}
/**
* Source-executed TypeScript must remain parseable by the supported Node 22
* type-stripping runtime. OXC identifies both declaration forms without
* mistaking comments or string data for executable syntax.
*/
export function sourceExecutedUsingDeclarationViolations(
sources: readonly LogsRuntimeProductionSource[],
): LayeringViolation[] {
const violations: LayeringViolation[] = [];
for (const file of sources) {
const parsed = parseSync(file.path, file.source);
visitAst(parsed.program, (node) => {
if (
node.type === 'VariableDeclaration' &&
(node.kind === 'using' || node.kind === 'await using')
) {
violations.push(
astViolation(
file,
node,
`source-executed TypeScript uses unsupported ${String(node.kind)} declaration`,
),
);
}
});
}
return violations;
}
function astViolation(
file: LogsRuntimeProductionSource,
node: Record<string, unknown>,
message: string,
): LayeringViolation {
return offsetViolation(file, typeof node.start === 'number' ? node.start : 0, message);
}
function offsetViolation(
file: LogsRuntimeProductionSource,
offset: number,
message: string,
): LayeringViolation {
return {
rule: LOGS_RUNTIME_CUTOVER_RULE,
file: file.path,
line: file.source.slice(0, offset).split('\n').length,
message,
};
}
function propertyName(node: unknown): string | undefined {
if (node === null || typeof node !== 'object') return undefined;
const record = node as Record<string, unknown>;
return record.type === 'Identifier' || record.type === 'Literal'
? ((record.name as string | undefined) ?? (record.value as string | undefined))
: undefined;
}
@@ -1,107 +0,0 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import {
networkLegacyRouteViolations,
networkRuntimeNarrowingViolations,
networkRuntimeRouteViolations,
} from './network-runtime-cutover-policy.ts';
test('network cutover scan catches planted legacy execution and dual admission', () => {
assert.deepEqual(
networkLegacyRouteViolations([
{
path: 'src/daemon/handlers/planted.ts',
source: `
await readSessionNetworkCapture(input);
await provider.dumpNetwork({ include: 'all' });
requireCommandSupported('network', device);
`,
},
{
path: 'src/core/command-descriptor/registry.ts',
source: `const commands = [{ name: 'network', capability: { apple: {} } }];`,
},
{
path: 'src/core/capabilities.ts',
source: `const WEB_QUERY_COMMANDS = ['find', 'network'];`,
},
{
path: 'src/platforms/apple/plugin.ts',
source: `const supports = { [PUBLIC_COMMANDS.network]: supportsDevice };`,
},
]),
[
'src/daemon/handlers/planted.ts: legacy network route readSessionNetworkCapture',
'src/daemon/handlers/planted.ts: daemon invokes legacy WebProvider.dumpNetwork',
'src/daemon/handlers/planted.ts: legacy network capability admission requireCommandSupported',
'src/core/command-descriptor/registry.ts: network descriptor retains legacy capability admission',
'src/core/capabilities.ts: static platform command set retains network admission',
'src/platforms/apple/plugin.ts: Apple plugin retains legacy network support or hint closure',
],
);
});
test('network cutover scan rejects a retired implementation by committed path', () => {
assert.deepEqual(
networkLegacyRouteViolations([
{ path: 'src/daemon/network-log.ts', source: `export const parser = () => [];` },
]),
['src/daemon/network-log.ts: retired daemon network implementation remains'],
);
});
test('network cutover scan ignores retired names in comments and string data', () => {
assert.deepEqual(
networkLegacyRouteViolations([
{
path: 'src/daemon/planted.ts',
source: `
// readSessionNetworkCapture and requireCommandSupported('network') were removed.
const note = 'provider.dumpNetwork and PUBLIC_COMMANDS.network';
`,
},
]),
[],
);
});
test('network narrowing scan catches planted casts, non-null repair, and bracket access', () => {
const violations = networkRuntimeNarrowingViolations([
{
path: 'src/daemon/handlers/planted.ts',
source: `
const widened = runtime as BoundDeviceRuntime<typeof use>;
widened.operations.networkDump!({});
widened.operations['networkDump']({});
`,
},
]);
assert.equal(violations.length, 3);
assert.match(violations.join('\n'), /type assertion/);
assert.match(violations.join('\n'), /non-null/);
assert.match(violations.join('\n'), /bracketed/);
});
test('network route scan rejects planted neither and duplicate routes', () => {
assert.deepEqual(networkRuntimeRouteViolations([]), [
'(network runtime): expected one handleNetworkCommand route, found 0',
'(network runtime): expected one narrowed networkDump call, found 0',
]);
assert.deepEqual(
networkRuntimeRouteViolations([
{
path: 'src/daemon/planted.ts',
source: `
handleNetworkCommand(first);
handleNetworkCommand(second);
runtime.operations.networkDump(input);
fallback.operations.networkDump(input);
`,
},
]),
[
'(network runtime): expected one handleNetworkCommand route, found 2',
'(network runtime): expected one narrowed networkDump call, found 2',
],
);
});
@@ -1,257 +0,0 @@
import { parseSync } from 'oxc-parser';
export type NetworkRuntimeProductionSource = Readonly<{ path: string; source: string }>;
const RETIRED_DAEMON_MODULES = new Set([
'src/daemon/network-log.ts',
'src/daemon/app-log-network-recovery.ts',
'src/daemon/network-log-android-recovery.ts',
'src/daemon/network-log-ios-simulator-recovery.ts',
]);
const LEGACY_NETWORK_ROUTE_NAMES = new Set([
'handleWebNetworkCommand',
'readSessionNetworkCapture',
'readRecentNetworkTraffic',
'readRecentAndroidLogcatForPackage',
'readRecentIosSimulatorLogShowForBundle',
]);
/** Network command admission and execution have one operation-fact-derived route. */
export function networkLegacyRouteViolations(
sources: readonly NetworkRuntimeProductionSource[],
): string[] {
const violations: string[] = [];
for (const file of sources) {
if (RETIRED_DAEMON_MODULES.has(file.path)) {
violations.push(`${file.path}: retired daemon network implementation remains`);
}
const parsed = parseSync(file.path, file.source);
const seen = new Set<string>();
visitAst(parsed.program, (node) => {
const route = legacyRouteName(node);
if (route && file.path.startsWith('src/daemon/')) {
const identity = `${String(node.start ?? '')}:${route}`;
if (!seen.has(identity)) {
seen.add(identity);
violations.push(`${file.path}: legacy network route ${route}`);
}
}
if (isLegacyNetworkAdmission(node)) {
violations.push(
`${file.path}: legacy network capability admission requireCommandSupported`,
);
}
if (
file.path === 'src/core/command-descriptor/registry.ts' &&
isNetworkDescriptorWithCapability(node)
) {
violations.push(`${file.path}: network descriptor retains legacy capability admission`);
}
if (file.path === 'src/core/capabilities.ts' && isStaticNetworkAdmission(node)) {
violations.push(`${file.path}: static platform command set retains network admission`);
}
if (file.path === 'src/platforms/apple/plugin.ts' && isAppleNetworkCommandMember(node)) {
violations.push(
`${file.path}: Apple plugin retains legacy network support or hint closure`,
);
}
if (file.path.startsWith('src/daemon/') && isLegacyWebDumpCall(node)) {
violations.push(`${file.path}: daemon invokes legacy WebProvider.dumpNetwork`);
}
});
}
return violations;
}
/** Daemon consumers must use the use-narrowed network operation without repairing proof. */
export function networkRuntimeNarrowingViolations(
sources: readonly NetworkRuntimeProductionSource[],
): string[] {
const violations: string[] = [];
for (const file of sources.filter(({ path }) => path.startsWith('src/daemon/'))) {
const parsed = parseSync(file.path, file.source);
visitAst(parsed.program, (node) => {
if (node.type === 'TSAsExpression' && containsRuntimeTypeRepair(node.typeAnnotation)) {
violations.push(`${file.path}: widened network runtime type assertion`);
}
if (node.type === 'TSNonNullExpression' && isNetworkOperationAccess(node.expression)) {
violations.push(`${file.path}: non-null repair of networkDump operation`);
}
if (isBracketedNetworkOperationAccess(node)) {
violations.push(`${file.path}: bracketed networkDump operation access`);
}
});
}
return violations;
}
/** The command has one daemon orchestration route and one operation projection call. */
export function networkRuntimeRouteViolations(
sources: readonly NetworkRuntimeProductionSource[],
): string[] {
let handlerCalls = 0;
let operationCalls = 0;
for (const file of sources.filter(({ path }) => path.startsWith('src/daemon/'))) {
const parsed = parseSync(file.path, file.source);
visitAst(parsed.program, (node) => {
if (node.type !== 'CallExpression') return;
const callee = node.callee as Record<string, unknown> | undefined;
if (callee?.type === 'Identifier' && callee.name === 'handleNetworkCommand') {
handlerCalls += 1;
}
if (callee?.type === 'MemberExpression' && isRuntimeNetworkOperation(callee)) {
operationCalls += 1;
}
});
}
const violations: string[] = [];
if (handlerCalls !== 1) {
violations.push(
`(network runtime): expected one handleNetworkCommand route, found ${handlerCalls}`,
);
}
if (operationCalls !== 1) {
violations.push(
`(network runtime): expected one narrowed networkDump call, found ${operationCalls}`,
);
}
return violations;
}
function legacyRouteName(node: Record<string, unknown>): string | undefined {
if (node.type === 'Identifier' && LEGACY_NETWORK_ROUTE_NAMES.has(String(node.name))) {
return String(node.name);
}
if (node.type === 'Property' || node.type === 'TSPropertySignature') {
const name = propertyName(node.key);
return name && LEGACY_NETWORK_ROUTE_NAMES.has(name) ? name : undefined;
}
return undefined;
}
function isLegacyNetworkAdmission(node: Record<string, unknown>): boolean {
if (node.type !== 'CallExpression') return false;
const callee = node.callee as Record<string, unknown> | undefined;
const args = node.arguments as readonly Record<string, unknown>[] | undefined;
return (
callee?.type === 'Identifier' &&
callee.name === 'requireCommandSupported' &&
args?.[0]?.type === 'Literal' &&
args[0].value === 'network'
);
}
function isNetworkDescriptorWithCapability(node: Record<string, unknown>): boolean {
if (node.type !== 'ObjectExpression' || !Array.isArray(node.properties)) return false;
let network = false;
let capability = false;
for (const property of node.properties as Record<string, unknown>[]) {
if (property.type !== 'Property' || property.computed === true) continue;
const key = propertyName(property.key);
const value = property.value as Record<string, unknown> | undefined;
if (key === 'name' && value?.type === 'Literal' && value.value === 'network') network = true;
if (key === 'capability') capability = true;
}
return network && capability;
}
function isStaticNetworkAdmission(node: Record<string, unknown>): boolean {
if (node.type !== 'VariableDeclarator') return false;
const id = node.id as Record<string, unknown> | undefined;
return (
id?.type === 'Identifier' &&
/(?:WEB|HARMONY).*COMMANDS/.test(String(id.name)) &&
astContainsString(node.init, 'network')
);
}
function isAppleNetworkCommandMember(node: Record<string, unknown>): boolean {
if (node.type !== 'MemberExpression') return false;
const object = node.object as Record<string, unknown> | undefined;
return (
object?.type === 'Identifier' &&
object.name === 'PUBLIC_COMMANDS' &&
memberName(node) === 'network'
);
}
function isLegacyWebDumpCall(node: Record<string, unknown>): boolean {
if (node.type !== 'CallExpression') return false;
const callee = node.callee as Record<string, unknown> | undefined;
return callee?.type === 'MemberExpression' && memberName(callee) === 'dumpNetwork';
}
function containsRuntimeTypeRepair(node: unknown): boolean {
let found = false;
visitAst(node, (candidate) => {
if (
candidate.type === 'Identifier' &&
(candidate.name === 'BoundDeviceRuntime' || candidate.name === 'NetworkRuntimeOperations')
) {
found = true;
}
});
return found;
}
function isNetworkOperationAccess(node: unknown): boolean {
if (node === null || typeof node !== 'object') return false;
const member = node as Record<string, unknown>;
return member.type === 'MemberExpression' && memberName(member) === 'networkDump';
}
function isBracketedNetworkOperationAccess(node: Record<string, unknown>): boolean {
if (node.type !== 'MemberExpression' || node.computed !== true) return false;
const object = node.object as Record<string, unknown> | undefined;
return (
memberName(node) === 'networkDump' &&
object?.type === 'MemberExpression' &&
memberName(object) === 'operations'
);
}
function isRuntimeNetworkOperation(node: Record<string, unknown>): boolean {
const object = node.object as Record<string, unknown> | undefined;
return (
memberName(node) === 'networkDump' &&
object?.type === 'MemberExpression' &&
memberName(object) === 'operations'
);
}
function memberName(node: Record<string, unknown>): string | undefined {
const property = node.property as Record<string, unknown> | undefined;
if (!property) return undefined;
return node.computed === true
? propertyName(property)
: property.type === 'Identifier'
? String(property.name)
: undefined;
}
function astContainsString(node: unknown, expected: string): boolean {
let found = false;
visitAst(node, (candidate) => {
if (candidate.type === 'Literal' && candidate.value === expected) found = true;
});
return found;
}
function propertyName(node: unknown): string | undefined {
if (node === null || typeof node !== 'object') return undefined;
const value = node as Record<string, unknown>;
return value.type === 'Identifier' || value.type === 'Literal'
? ((value.name as string | undefined) ?? (value.value as string | undefined))
: undefined;
}
function visitAst(node: unknown, visitor: (node: Record<string, unknown>) => void): void {
if (node === null || typeof node !== 'object') return;
if (Array.isArray(node)) {
for (const child of node) visitAst(child, visitor);
return;
}
const record = node as Record<string, unknown>;
visitor(record);
for (const child of Object.values(record)) visitAst(child, visitor);
}
@@ -1,134 +0,0 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import {
recordLegacyRouteViolations,
recordRuntimeNarrowingViolations,
recordRuntimeRouteViolations,
} from './record-runtime-cutover-policy.ts';
test('R16 catches a planted legacy recording backend, admission, and plugin facet', () => {
assert.deepEqual(
recordLegacyRouteViolations([
{
path: 'src/daemon/handlers/planted.ts',
source: `
await resolveRecordingBackendForDevice(device).start(input);
requireCommandSupported('record', device);
requireCommandSupported(PUBLIC_COMMANDS.record, device);
const backend = RECORDING_BACKENDS_BY_TAG.android;
type LegacyTag = RecordingBackendTag;
`,
},
{
path: 'src/core/command-descriptor/registry.ts',
source: `const commands = [{ name: 'record', capability: { apple: {} } }];`,
},
{
path: 'src/platforms/apple/plugin.ts',
source: `const plugin = { recording: { resolveBackendTag() {} } } satisfies PlatformPlugin;`,
},
]),
[
'src/daemon/handlers/planted.ts: legacy recording route resolveRecordingBackendForDevice',
'src/daemon/handlers/planted.ts: legacy record capability admission requireCommandSupported',
'src/daemon/handlers/planted.ts: legacy record capability admission requireCommandSupported',
'src/daemon/handlers/planted.ts: legacy recording backend map RECORDING_BACKENDS_BY_TAG',
'src/daemon/handlers/planted.ts: legacy recording backend tag RecordingBackendTag',
'src/core/command-descriptor/registry.ts: record descriptor retains legacy capability admission',
'src/platforms/apple/plugin.ts: legacy PlatformPlugin recording facet',
],
);
});
test('R16 excludes trace-only mechanics from the record cutover scan', () => {
assert.deepEqual(
recordLegacyRouteViolations([
{
path: 'src/daemon/handlers/record-trace.ts',
source: `
const traceRecording = { startTrace() {}, stopTrace() {} };
traceRecording.startTrace();
`,
},
]),
[],
);
});
test('R16 preserves the one record-trace gateway while requiring exact recording operation routes', () => {
assert.deepEqual(
recordRuntimeRouteViolations([
{
path: 'src/daemon/handlers/session.ts',
source: `
handleRecordTraceCommands(input);
runtime.operations.screenRecordingStart(input);
runtime.operations.screenRecordingReattach(input);
runtime.operations.screenRecordingCleanup(input);
`,
},
]),
[],
);
assert.deepEqual(recordRuntimeRouteViolations([]), [
'(record runtime): expected one handleRecordTraceCommands route, found 0',
'(record runtime): expected one narrowed screenRecordingStart call, found 0',
'(record runtime): expected one narrowed screenRecordingReattach call, found 0',
'(record runtime): expected one narrowed screenRecordingCleanup call, found 0',
]);
});
test('R16 rejects the retired recording-provider scope while allowing the focused transport', () => {
const violations = recordLegacyRouteViolations([
{
path: 'src/daemon/request-platform-providers.ts',
source: `
import type { RecordingProvider } from './recording-provider.ts';
const recordingProvider = resolveRecordingProvider();
await withRecordingProvider(undefined, task);
`,
},
{
path: 'src/platform-runtime-screen-recording-apple-transport.ts',
source: `
// recordingProvider and withRecordingProvider are retired prose.
const prose = 'src/daemon/recording-provider.ts';
withAppleSimulatorScreenRecordingTransport(transport, task);
`,
},
{
path: 'src/daemon/recording-provider.ts',
source: `export const retired = true;`,
},
{
path: 'src/daemon/dynamic-provider.ts',
source: `
const providers = { ['recordingProvider']: resolver };
await import('./recording-provider.ts');
`,
},
]);
assert.equal(violations.length, 8);
assert.match(violations.join('\n'), /retired recording provider module/);
assert.match(violations.join('\n'), /RecordingProvider/);
assert.match(violations.join('\n'), /recordingProvider/);
assert.match(violations.join('\n'), /resolveRecordingProvider/);
assert.match(violations.join('\n'), /withRecordingProvider/);
});
test('R16 rejects proof repair for a narrowed screen-recording operation', () => {
const violations = recordRuntimeNarrowingViolations([
{
path: 'src/daemon/handlers/planted.ts',
source: `
const widened = runtime as BoundDeviceRuntime<typeof use>;
widened.operations.screenRecordingStart!({});
widened.operations['screenRecordingReattach']({});
`,
},
]);
assert.equal(violations.length, 3);
assert.match(violations.join('\n'), /type assertion/);
assert.match(violations.join('\n'), /non-null/);
assert.match(violations.join('\n'), /bracketed/);
});
@@ -1,335 +0,0 @@
import { parseSync } from 'oxc-parser';
import {
memberName,
propertyName,
type RecordRuntimeProductionSource,
visitAst,
} from './record-runtime-policy-ast.ts';
const LEGACY_RECORD_ROUTE_NAMES = new Set([
'resolveRecordingBackendForDevice',
'stopActiveRecording',
'RecordingBackend',
'RecordingStartBackend',
]);
const RETIRED_RECORDING_PROVIDER_NAMES = new Set([
'RecordingProvider',
'RecordingProviderResolver',
'recordingProvider',
'resolveRecordingProvider',
'withRecordingProvider',
'createLocalRecordingProvider',
]);
const SCREEN_RECORDING_OPERATION_NAMES = new Set([
'screenRecordingStart',
'screenRecordingReattach',
'screenRecordingCleanup',
]);
/** Record owns one runtime-backed route: legacy tag/backend selection may not survive it. */
export function recordLegacyRouteViolations(
sources: readonly RecordRuntimeProductionSource[],
): string[] {
const violations: string[] = [];
for (const file of sources) {
if (file.path === 'src/daemon/recording-provider.ts') {
violations.push(`${file.path}: retired recording provider module`);
}
const parsed = parseSync(file.path, file.source);
const seen = new Set<string>();
visitAst(parsed.program, (node) => {
if (isRetiredRecordingProviderImport(node)) {
pushOnce(violations, seen, node, file, 'retired recording provider module');
}
if (node.type === 'Identifier' && RETIRED_RECORDING_PROVIDER_NAMES.has(String(node.name))) {
pushOnce(
violations,
seen,
node,
file,
`retired recording provider name ${String(node.name)}`,
);
}
if (isRetiredRecordingProviderComputedProperty(node)) {
pushOnce(
violations,
seen,
node,
file,
`retired recording provider name ${String(propertyName(node.key))}`,
);
}
if (isNamedIdentifier(node, 'RECORDING_BACKENDS_BY_TAG')) {
pushOnce(
violations,
seen,
node,
file,
'legacy recording backend map RECORDING_BACKENDS_BY_TAG',
);
}
if (isNamedIdentifier(node, 'RecordingBackendTag')) {
pushOnce(violations, seen, node, file, 'legacy recording backend tag RecordingBackendTag');
}
const route = legacyRecordRouteName(node);
if (route) pushOnce(violations, seen, node, file, `legacy recording route ${route}`);
if (isLegacyRecordAdmission(node)) {
pushOnce(
violations,
seen,
node,
file,
'legacy record capability admission requireCommandSupported',
);
}
if (isRecordDescriptorWithCapability(node)) {
pushOnce(
violations,
seen,
node,
file,
'record descriptor retains legacy capability admission',
);
}
if (isPlatformPluginRecordingDeclaration(node)) {
pushOnce(violations, seen, node, file, 'legacy PlatformPlugin recording facet');
}
});
}
return violations;
}
function isNamedIdentifier(node: Record<string, unknown>, name: string): boolean {
return node.type === 'Identifier' && node.name === name;
}
/** Daemon consumers must not manufacture proof for screen-recording runtime operations. */
export function recordRuntimeNarrowingViolations(
sources: readonly RecordRuntimeProductionSource[],
): string[] {
const violations: string[] = [];
for (const file of sources.filter(({ path }) => path.startsWith('src/daemon/'))) {
const parsed = parseSync(file.path, file.source);
visitAst(parsed.program, (node) => {
if (node.type === 'TSAsExpression' && containsRuntimeTypeRepair(node.typeAnnotation)) {
violations.push(`${file.path}: widened screen-recording runtime type assertion`);
}
if (
node.type === 'TSNonNullExpression' &&
isScreenRecordingOperationAccess(node.expression)
) {
violations.push(`${file.path}: non-null repair of screen-recording operation`);
}
if (isBracketedScreenRecordingOperationAccess(node)) {
violations.push(`${file.path}: bracketed screen-recording operation access`);
}
});
}
return violations;
}
/** Record keeps the one existing record-trace gateway; trace mechanics are otherwise out of scope. */
export function recordRuntimeRouteViolations(
sources: readonly RecordRuntimeProductionSource[],
): string[] {
let recordTraceGatewayCalls = 0;
let startCalls = 0;
let reattachCalls = 0;
let cleanupCalls = 0;
for (const file of sources.filter(({ path }) => path.startsWith('src/daemon/'))) {
const parsed = parseSync(file.path, file.source);
visitAst(parsed.program, (node) => {
if (node.type !== 'CallExpression') return;
const callee = node.callee as Record<string, unknown> | undefined;
if (callee?.type === 'Identifier' && callee.name === 'handleRecordTraceCommands') {
recordTraceGatewayCalls += 1;
}
if (callee?.type !== 'MemberExpression' || !isRuntimeScreenRecordingOperation(callee)) return;
switch (memberName(callee)) {
case 'screenRecordingStart':
startCalls += 1;
break;
case 'screenRecordingReattach':
reattachCalls += 1;
break;
case 'screenRecordingCleanup':
cleanupCalls += 1;
break;
}
});
}
return [
exactRouteViolation('handleRecordTraceCommands', recordTraceGatewayCalls),
exactOperationViolation('screenRecordingStart', startCalls),
exactOperationViolation('screenRecordingReattach', reattachCalls),
exactOperationViolation('screenRecordingCleanup', cleanupCalls),
].filter((violation): violation is string => violation !== null);
}
function isRetiredRecordingProviderImport(node: Record<string, unknown>): boolean {
if (node.type !== 'ImportDeclaration' && node.type !== 'ImportExpression') return false;
const source = node.source as Record<string, unknown> | undefined;
return (
source?.type === 'Literal' &&
/(?:^|\/)recording-provider(?:\.[cm]?[jt]s)?$/.test(String(source.value))
);
}
function isRetiredRecordingProviderComputedProperty(node: Record<string, unknown>): boolean {
return (
(node.type === 'Property' || node.type === 'TSPropertySignature') &&
node.computed === true &&
RETIRED_RECORDING_PROVIDER_NAMES.has(propertyName(node.key) ?? '')
);
}
function legacyRecordRouteName(node: Record<string, unknown>): string | undefined {
if (node.type === 'Identifier' && LEGACY_RECORD_ROUTE_NAMES.has(String(node.name))) {
return String(node.name);
}
if (node.type === 'Property' || node.type === 'TSPropertySignature') {
const name = propertyName(node.key);
return name && LEGACY_RECORD_ROUTE_NAMES.has(name) ? name : undefined;
}
return undefined;
}
function isLegacyRecordAdmission(node: Record<string, unknown>): boolean {
if (node.type !== 'CallExpression') return false;
const callee = node.callee as Record<string, unknown> | undefined;
const args = node.arguments as readonly Record<string, unknown>[] | undefined;
return (
callee?.type === 'Identifier' &&
callee.name === 'requireCommandSupported' &&
isRecordCommandExpression(args?.[0])
);
}
function isRecordCommandExpression(node: Record<string, unknown> | undefined): boolean {
if (node?.type === 'Literal') return node.value === 'record';
if (node?.type !== 'MemberExpression') return false;
const object = node.object as Record<string, unknown> | undefined;
return (
object?.type === 'Identifier' &&
object.name === 'PUBLIC_COMMANDS' &&
memberName(node) === 'record'
);
}
function isRecordDescriptorWithCapability(node: Record<string, unknown>): boolean {
if (node.type !== 'ObjectExpression' || !Array.isArray(node.properties)) return false;
let record = false;
let capability = false;
for (const property of node.properties as Record<string, unknown>[]) {
if (property.type !== 'Property' || property.computed === true) continue;
const key = propertyName(property.key);
const value = property.value as Record<string, unknown> | undefined;
if (key === 'name' && value?.type === 'Literal' && value.value === 'record') record = true;
if (key === 'capability') capability = true;
}
return record && capability;
}
function isPlatformPluginRecordingDeclaration(node: Record<string, unknown>): boolean {
if (node.type === 'TSTypeAliasDeclaration' || node.type === 'TSInterfaceDeclaration') {
const id = node.id as Record<string, unknown> | undefined;
return id?.type === 'Identifier' && id.name === 'PlatformPlugin' && astContainsRecording(node);
}
if (node.type === 'TSSatisfiesExpression' || node.type === 'TSAsExpression') {
return (
isNamedType(node.typeAnnotation, 'PlatformPlugin') && astContainsRecording(node.expression)
);
}
if (node.type !== 'VariableDeclarator') return false;
const id = node.id as Record<string, unknown> | undefined;
return isNamedType(id?.typeAnnotation, 'PlatformPlugin') && astContainsRecording(node.init);
}
function astContainsRecording(node: unknown): boolean {
let found = false;
visitAst(node, (candidate) => {
if (
(candidate.type === 'Property' || candidate.type === 'TSPropertySignature') &&
candidate.computed !== true &&
propertyName(candidate.key) === 'recording'
) {
found = true;
}
});
return found;
}
function containsRuntimeTypeRepair(node: unknown): boolean {
let found = false;
visitAst(node, (candidate) => {
if (
candidate.type === 'Identifier' &&
(candidate.name === 'BoundDeviceRuntime' ||
candidate.name === 'ScreenRecordingRuntimeOperations' ||
candidate.name === 'ScreenRecordingLiveHandle')
) {
found = true;
}
});
return found;
}
function isScreenRecordingOperationAccess(node: unknown): boolean {
if (node === null || typeof node !== 'object') return false;
const member = node as Record<string, unknown>;
return (
member.type === 'MemberExpression' &&
SCREEN_RECORDING_OPERATION_NAMES.has(memberName(member) ?? '')
);
}
function isBracketedScreenRecordingOperationAccess(node: Record<string, unknown>): boolean {
if (node.type !== 'MemberExpression' || node.computed !== true) return false;
const object = node.object as Record<string, unknown> | undefined;
return (
SCREEN_RECORDING_OPERATION_NAMES.has(memberName(node) ?? '') &&
object?.type === 'MemberExpression' &&
memberName(object) === 'operations'
);
}
function isRuntimeScreenRecordingOperation(node: Record<string, unknown>): boolean {
const object = node.object as Record<string, unknown> | undefined;
return (
SCREEN_RECORDING_OPERATION_NAMES.has(memberName(node) ?? '') &&
object?.type === 'MemberExpression' &&
memberName(object) === 'operations'
);
}
function exactRouteViolation(name: string, calls: number): string | null {
return calls === 1 ? null : `(record runtime): expected one ${name} route, found ${calls}`;
}
function exactOperationViolation(name: string, calls: number): string | null {
return calls === 1
? null
: `(record runtime): expected one narrowed ${name} call, found ${calls}`;
}
function pushOnce(
violations: string[],
seen: Set<string>,
node: Record<string, unknown>,
file: RecordRuntimeProductionSource,
message: string,
): void {
const identity = `${String(node.start ?? '')}:${message}`;
if (seen.has(identity)) return;
seen.add(identity);
violations.push(`${file.path}: ${message}`);
}
function isNamedType(node: unknown, expected: string): boolean {
if (node === null || typeof node !== 'object') return false;
const record = node as Record<string, unknown>;
if (record.type === 'TSTypeAnnotation') return isNamedType(record.typeAnnotation, expected);
if (record.type !== 'TSTypeReference') return false;
const name = record.typeName as Record<string, unknown> | undefined;
return name?.type === 'Identifier' && name.name === expected;
}
@@ -1,9 +1,5 @@
import { parseSync } from 'oxc-parser';
import {
memberName,
type RecordRuntimeProductionSource,
visitAst,
} from './record-runtime-policy-ast.ts';
import { memberName, type ProductionSource, visitAst } from './cutover-policy-ast.ts';
const DAEMON_RECORD_MECHANIC_CALLS = new Set([
'runCmd',
@@ -19,7 +15,7 @@ const DAEMON_RECORD_MECHANIC_CALLS = new Set([
/** The daemon record owner coordinates runtime operations; native mechanics stay behind owners. */
export function recordRuntimeDaemonMechanicsViolations(
sources: readonly RecordRuntimeProductionSource[],
sources: readonly ProductionSource[],
): string[] {
const violations: string[] = [];
for (const file of sources.filter(({ path }) => isDaemonRecordOwner(path))) {
@@ -1,12 +1,8 @@
import { parseSync } from 'oxc-parser';
import {
memberName,
type RecordRuntimeProductionSource,
visitAst,
} from './record-runtime-policy-ast.ts';
import { memberName, type ProductionSource, visitAst } from './cutover-policy-ast.ts';
export function recordRuntimeRegistryJoinViolations(
sources: readonly RecordRuntimeProductionSource[],
sources: readonly ProductionSource[],
): string[] {
const file = sources.find(({ path }) => path === 'src/core/command-descriptor/registry.ts');
if (!file) return [missingRecordRuntimeRegistryJoin()];
@@ -0,0 +1,139 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import {
appLogSessionStateOwnershipViolations,
devicesGatewayBindingViolations,
sourceExecutedUsingDeclarationViolations,
} from './runtime-command-cutover-extensions.ts';
const HANDLER_FILE = 'src/daemon/handlers/session-inventory.ts';
function handlerSource(localName = 'listDeviceInventory'): string {
return [
`import { listDeviceInventory as ${localName} } from '../../core/dispatch-resolve.ts';`,
`export async function handleDevices(request: Request) { return await ${localName}(request); }`,
].join('\n');
}
function sources(entries: readonly (readonly [string, string])[]): ReadonlyMap<string, string> {
return new Map(entries);
}
function summaries(violations: readonly Readonly<{ file: string; message: string }>[]): string[] {
return violations.map(({ file, message }) => `${file}: ${message}`);
}
test('devices keeps one gateway-owned handler route through an aliased import', () => {
assert.deepEqual(
devicesGatewayBindingViolations(sources([[HANDLER_FILE, handlerSource('discoverInventory')]])),
[],
);
});
test('devices rejects a missing handler module', () => {
assert.match(
summaries(devicesGatewayBindingViolations(sources([]))).join('\n'),
/gateway-owned handler module is missing/,
);
});
test('devices rejects a handler that imports but never calls the inventory gateway', () => {
const handler = [
"import { listDeviceInventory } from '../../core/dispatch-resolve.ts';",
'export async function handleDevices() { return []; }',
].join('\n');
assert.match(
summaries(devicesGatewayBindingViolations(sources([[HANDLER_FILE, handler]]))).join('\n'),
/must call its imported listDeviceInventory binding/,
);
});
test('devices rejects a handler that never imports the inventory gateway', () => {
assert.match(
summaries(
devicesGatewayBindingViolations(
sources([[HANDLER_FILE, 'export const handleDevices = () => [];']]),
),
).join('\n'),
/must import listDeviceInventory from the neutral inventory owner/,
);
});
test('devices does not accept a call through a binding that shadows the imported alias', () => {
const handler = [
"import { listDeviceInventory as discoverInventory } from '../../core/dispatch-resolve.ts';",
'export async function decoy(discoverInventory: (request: Request) => Promise<unknown>) {',
' return await discoverInventory({});',
'}',
].join('\n');
assert.match(
summaries(devicesGatewayBindingViolations(sources([[HANDLER_FILE, handler]]))).join('\n'),
/shadows its imported listDeviceInventory binding/,
);
});
test('session state scan catches planted app-log record construction outside its owner', () => {
assert.deepEqual(
summaries(
appLogSessionStateOwnershipViolations(
sources([
[
'src/daemon/handlers/planted.ts',
`sessionStore.set(name, { ...session, appLog: resource, appLogFailure: undefined });`,
],
[
'src/daemon/request-platform-providers.ts',
`
type Scope = { appLog: { provider?: AppLogProvider } };
const scope = { appLog: { provider } };
`,
],
[
'src/daemon/app-log-session-resource.ts',
`sessionStore.set(name, { ...session, appLog: resource });`,
],
[
'src/daemon/session-teardown.ts',
`teardownSessionResources({ appLog: 'run' }); teardownSessionResources({ appLog: 'already-settled' });`,
],
[
'src/daemon/handlers/invalid-teardown.ts',
`teardownSessionResources({ appLog: 'skip' });`,
],
]),
),
),
[
'src/daemon/handlers/planted.ts: session appLog record constructed outside its owner',
'src/daemon/handlers/planted.ts: session appLogFailure record constructed outside its owner',
'src/daemon/request-platform-providers.ts: session appLog record constructed outside its owner',
'src/daemon/handlers/invalid-teardown.ts: session appLog record constructed outside its owner',
],
);
});
test('source-executed syntax scan rejects using declarations but ignores prose', () => {
assert.deepEqual(
summaries(
sourceExecutedUsingDeclarationViolations(
sources([
[
'packages/platform-apple/src/logs/planted.ts',
`
// await using oldHandle = acquire();
const migrationNote = 'using replacement = acquire()';
async function run() { await using handle = acquire(); }
function runSync() { using cleanup = acquireSync(); }
`,
],
]),
),
),
[
'packages/platform-apple/src/logs/planted.ts: source-executed TypeScript uses unsupported await using declaration',
'packages/platform-apple/src/logs/planted.ts: source-executed TypeScript uses unsupported using declaration',
],
);
});
@@ -0,0 +1,242 @@
import { parseSync } from 'oxc-parser';
import { propertyName, visitAst, type ProductionSource } from './cutover-policy-ast.ts';
import type { UnruledViolation } from './runtime-command-cutover-model.ts';
type AstNode = Record<string, unknown>;
const DEVICES_HANDLER_FILE = 'src/daemon/handlers/session-inventory.ts';
const DEVICES_INVENTORY_IMPORT_SOURCES = new Set([
'../../core/device-inventory-context.ts',
'../../core/dispatch-resolve.ts',
]);
const DEVICES_GATEWAY_BINDING = 'listDeviceInventory';
const APP_LOG_SESSION_STATE_OWNERS = new Set([
'src/daemon/app-log-session-resource.ts',
'src/daemon/types.ts',
]);
/**
* `devices` proves its single inventory route by binding identity, not by name: the
* handler must import the neutral gateway, must not shadow the binding it imported, and
* must actually call it. No other migrated command routes through a named gateway
* binding, so this stays a row extension rather than a table column.
*/
export function devicesGatewayBindingViolations(
sources: ReadonlyMap<string, string>,
): UnruledViolation[] {
const source = sources.get(DEVICES_HANDLER_FILE);
if (source === undefined) {
return [
{
file: DEVICES_HANDLER_FILE,
line: 1,
message: 'devices gateway-owned handler module is missing',
},
];
}
const program = parseSync(DEVICES_HANDLER_FILE, source).program as AstNode;
const binding = importedGatewayBinding(program);
if (binding === undefined) {
return [
{
file: DEVICES_HANDLER_FILE,
line: 1,
message: `devices handler must import ${DEVICES_GATEWAY_BINDING} from the neutral inventory owner`,
},
];
}
const shadow = shadowingBindingSite(program, binding);
if (shadow !== undefined) {
return [
{
file: DEVICES_HANDLER_FILE,
line: lineOf(source, shadow),
message: `devices handler shadows its imported ${DEVICES_GATEWAY_BINDING} binding`,
},
];
}
if (!callsBinding(program, binding)) {
return [
{
file: DEVICES_HANDLER_FILE,
line: 1,
message: `devices handler must call its imported ${DEVICES_GATEWAY_BINDING} binding`,
},
];
}
return [];
}
/**
* The app-log session record has one whole-record replacement owner (ADR 0019 §4-5).
* `logs` is the durable-resource pilot; request-scoped rows own no session record, so
* this stays a row extension.
*/
export function appLogSessionStateOwnershipViolations(
sources: ReadonlyMap<string, string>,
): UnruledViolation[] {
const violations: UnruledViolation[] = [];
for (const file of productionSources(sources)) {
if (!file.path.startsWith('src/daemon/') || APP_LOG_SESSION_STATE_OWNERS.has(file.path)) {
continue;
}
const program = parseSync(file.path, file.source).program as AstNode;
visitAst(program, (node) => {
if (node['type'] !== 'Property' || node['kind'] !== 'init' || node['computed'] === true) {
return;
}
const field = propertyName(node['key']);
if (
(field === 'appLog' || field === 'appLogFailure') &&
!isAppLogTeardownDiscriminant(field, node['value'])
) {
violations.push({
file: file.path,
line: lineOf(file.source, node),
message: `session ${field} record constructed outside its owner`,
});
}
});
}
return violations;
}
/**
* Source-executed TypeScript must stay parseable by the Node 22 type-stripping runtime.
* Not command-specific: it is declared on the logs row so its violations carry R14, the
* id the layering report groups them under.
*/
export function sourceExecutedUsingDeclarationViolations(
sources: ReadonlyMap<string, string>,
): UnruledViolation[] {
const violations: UnruledViolation[] = [];
for (const file of productionSources(sources)) {
const program = parseSync(file.path, file.source).program as AstNode;
visitAst(program, (node) => {
if (
node['type'] === 'VariableDeclaration' &&
(node['kind'] === 'using' || node['kind'] === 'await using')
) {
violations.push({
file: file.path,
line: lineOf(file.source, node),
message: `source-executed TypeScript uses unsupported ${String(node['kind'])} declaration`,
});
}
});
}
return violations;
}
function productionSources(sources: ReadonlyMap<string, string>): ProductionSource[] {
return [...sources].map(([path, source]) => ({ path, source }));
}
function isAppLogTeardownDiscriminant(field: string, valueNode: unknown): boolean {
if (field !== 'appLog' || valueNode === null || typeof valueNode !== 'object') return false;
const value = valueNode as AstNode;
return (
value['type'] === 'Literal' &&
(value['value'] === 'run' || value['value'] === 'already-settled')
);
}
function importedGatewayBinding(program: AstNode): string | undefined {
const body = program['body'];
if (!Array.isArray(body)) return undefined;
for (const statement of body as AstNode[]) {
if (statement['type'] !== 'ImportDeclaration') continue;
const source = statement['source'] as AstNode | undefined;
const specifier = source === undefined ? undefined : source['value'];
if (typeof specifier !== 'string' || !DEVICES_INVENTORY_IMPORT_SOURCES.has(specifier)) continue;
const specifiers = statement['specifiers'];
if (!Array.isArray(specifiers)) continue;
for (const imported of specifiers as AstNode[]) {
if (
imported['type'] === 'ImportSpecifier' &&
identifierName(imported['imported']) === DEVICES_GATEWAY_BINDING
) {
return identifierName(imported['local']);
}
}
}
return undefined;
}
function callsBinding(program: AstNode, binding: string): boolean {
let called = false;
visitAst(program, (node) => {
if (node['type'] === 'CallExpression' && identifierName(node['callee']) === binding) {
called = true;
}
});
return called;
}
function shadowingBindingSite(program: AstNode, binding: string): AstNode | undefined {
let found: AstNode | undefined;
visitAst(program, (node) => {
if (found !== undefined) return;
if (
node['type'] === 'VariableDeclarator' ||
node['type'] === 'FunctionDeclaration' ||
node['type'] === 'FunctionExpression' ||
node['type'] === 'ClassDeclaration' ||
node['type'] === 'ClassExpression'
) {
if (patternBinds(node['id'], binding)) found = node;
if (found === undefined && bindsInParams(node, binding)) found = node;
return;
}
if (node['type'] === 'ArrowFunctionExpression' && bindsInParams(node, binding)) {
found = node;
return;
}
if (node['type'] === 'CatchClause' && patternBinds(node['param'], binding)) found = node;
});
return found;
}
function bindsInParams(node: AstNode, binding: string): boolean {
const params = node['params'];
return Array.isArray(params) && params.some((param) => patternBinds(param, binding));
}
function patternBinds(node: unknown, binding: string): boolean {
if (node === null || typeof node !== 'object') return false;
const record = node as AstNode;
if (record['type'] === 'Identifier') return record['name'] === binding;
if (record['type'] === 'RestElement') return patternBinds(record['argument'], binding);
if (record['type'] === 'AssignmentPattern') return patternBinds(record['left'], binding);
if (record['type'] === 'TSParameterProperty') return patternBinds(record['parameter'], binding);
if (record['type'] === 'ArrayPattern') {
const elements = record['elements'];
return Array.isArray(elements) && elements.some((element) => patternBinds(element, binding));
}
if (record['type'] === 'ObjectPattern') {
const properties = record['properties'];
return (
Array.isArray(properties) &&
properties.some((property) => {
if (property === null || typeof property !== 'object') return false;
const entry = property as AstNode;
return entry['type'] === 'Property'
? patternBinds(entry['value'], binding)
: patternBinds(entry['argument'], binding);
})
);
}
return false;
}
function identifierName(node: unknown): string | undefined {
if (node === null || typeof node !== 'object') return undefined;
const record = node as AstNode;
return record['type'] === 'Identifier' && typeof record['name'] === 'string'
? record['name']
: undefined;
}
function lineOf(source: string, node: AstNode): number {
return source.slice(0, (node['start'] as number | undefined) ?? 0).split('\n').length;
}
@@ -0,0 +1,55 @@
import { checkRuntimeCommandCutover } from './runtime-command-cutover-policy.ts';
import { MIGRATED_COMMAND_CUTOVERS } from './runtime-command-cutover-table.ts';
import type { MigratedCommandCutover } from './runtime-command-cutover-model.ts';
export type SourceEntry = readonly [path: string, source: string];
export function sources(entries: readonly SourceEntry[]): ReadonlyMap<string, string> {
return new Map(entries);
}
/** The four rows share one scan, so per-row assertions read their own rule's output. */
export function summariesFor(
rule: string,
entries: readonly SourceEntry[],
table: readonly MigratedCommandCutover[] = MIGRATED_COMMAND_CUTOVERS,
): string[] {
return checkRuntimeCommandCutover(sources(entries), table)
.filter((violation) => violation.rule === rule)
.map(({ file, message }) => `${file}: ${message}`);
}
export function rowFor(command: string): MigratedCommandCutover {
const row = MIGRATED_COMMAND_CUTOVERS.find((candidate) => candidate.command === command);
if (row === undefined) throw new Error(`no cutover row for ${command}`);
return row;
}
/**
* A row for a command that does not exist, so the mechanism can be exercised without
* the real table's rows reacting to the planted sources.
*/
export const PLANTED_ROW: MigratedCommandCutover = {
rule: 'RPlanted planted-cutover',
command: 'planted',
subject: 'planted',
tier: 'request-scoped',
execution: 'device-runtime',
legacyRetirement: {
modulePaths: ['src/daemon/planted-legacy.ts'],
importPatterns: [/(?:^|\/)planted-legacy(?:\.[cm]?[jt]s)?$/],
routeNames: ['resolvePlantedBackend'],
pluginFacetKeys: ['planted'],
},
admissionMember: {
forms: ['computed-property'],
message: 'Apple plugin retains legacy planted support or hint closure',
},
runtimeTypeNames: ['PlantedRuntimeOperations'],
operations: { names: ['plantedDump'] },
singularExecution: {
routes: ['handlePlantedCommand'],
operations: ['plantedDump'],
operationOwners: { plantedDump: ['handlePlantedCommand'] },
},
};
@@ -0,0 +1,271 @@
import type { LayeringViolation } from './model.ts';
/** A violation before the parametrized gate stamps its row's rule id. */
export type UnruledViolation = Omit<LayeringViolation, 'rule'>;
export type CutoverCheck = (sources: ReadonlyMap<string, string>) => UnruledViolation[];
/** At least one element, so "declared but empty" cannot satisfy a mandatory claim. */
export type NonEmpty<T> = readonly [T, ...T[]];
/**
* What a migrated command must retire. A row declares every form that applies to it;
* `cutoverRowDefects` requires at least one, because a cutover that retires nothing is
* not a cutover.
*/
export type LegacyRetirementClaim = Readonly<{
/** Retired module paths that must not exist in production. */
modulePaths?: readonly string[];
/** Retired module path shapes (matched against the repo-relative path). */
modulePathPatterns?: readonly RegExp[];
/** Import/re-export specifiers of retired modules, static or dynamic. */
importPatterns?: readonly RegExp[];
/** Retired executable names, scanned across all production sources. */
routeNames?: readonly string[];
/** Retired executable names scanned in `src/daemon/` production only. */
daemonOnlyRouteNames?: readonly string[];
/** Legacy provider methods the daemon may not call (`provider.dumpNetwork`). */
daemonOnlyProviderMethods?: readonly string[];
/** `PlatformPlugin` facet keys retired with the legacy adapter. */
pluginFacetKeys?: readonly string[];
}>;
/**
* Command-keyed plugin admission members. Form and file scope differ per row because
* the same syntax means admission for one command and opaque identifier data for
* another — `PUBLIC_COMMANDS.record` is live session-event data.
*/
export type AdmissionMemberClaim = Readonly<{
forms: NonEmpty<'computed-property' | 'public-commands-member'>;
/** Undefined scans every production source. */
files?: readonly string[];
message: string;
}>;
/** Which operations belong to this command: an exact set, or an input-dependent family. */
export type OperationClaim =
| Readonly<{ names: NonEmpty<string>; pattern?: RegExp }>
| Readonly<{ names?: undefined; pattern: RegExp }>;
export type CutoverIdentity = Readonly<{
/** Layering rule id. Per row, so the report keeps each cutover's identity. */
rule: string;
/** Canonical descriptor name: drives admission, descriptor, and static-set checks. */
command: string;
/** Noun used in prose for this command's mechanics ('recording', 'device inventory'). */
subject: string;
/**
* ADR 0019 §8 evidence tier. A `durable-resource` row must carry a lifecycle proof;
* a request-scoped row must not need one.
*/
tier: 'request-scoped' | 'durable-resource';
legacyRetirement: LegacyRetirementClaim;
admissionMember?: AdmissionMemberClaim;
/** Assertions that do not generalize. Stamped with this row's rule id. */
extensions?: readonly CutoverCheck[];
/** Required iff `tier` is `durable-resource`. */
lifecycleProof?: CutoverCheck;
}>;
/**
* An inventory command binds no device runtime, so its singular-execution proof is the
* identity of the one gateway binding its handler must import and call.
*/
export type InventoryCutover = CutoverIdentity &
Readonly<{
execution: 'inventory';
singularExecution: Readonly<{ gatewayProof: CutoverCheck }>;
}>;
type DeviceRuntimeBase = CutoverIdentity &
Readonly<{
execution: 'device-runtime';
runtimeTypeNames: NonEmpty<string>;
/**
* `declared` checks this row's operations. `any-operation` covers every daemon
* runtime operation, which a row whose operation set is input-dependent needs.
*/
nonNullRepairScope?: 'declared' | 'any-operation';
}>;
/**
* A command that names its operations exactly must also enforce each of them exactly
* once. Knowing the whole operation set and checking only the route would leave the
* operations themselves free to be called twice or not at all.
*/
type NamedOperationCutover = DeviceRuntimeBase &
Readonly<{
operations: Readonly<{ names: NonEmpty<string>; pattern?: RegExp }>;
singularExecution: Readonly<{
/** Daemon route functions that must be called exactly once. */
routes: NonEmpty<string>;
/** Narrowed operations that must be called exactly once — the named set. */
operations: NonEmpty<string>;
/**
* Lexical function owner for each narrowed operation call. An unrelated daemon
* caller must neither satisfy a missing command call nor create a false duplicate.
*/
operationOwners: Readonly<Record<string, NonEmpty<string>>>;
}>;
}>;
/**
* A command whose operation set is input-dependent (the app-log plan picks its
* operations from the request) cannot name a fixed set to enforce, so route
* singularity is the whole claim. Route-only singularity is permitted here alone.
*/
type PatternOperationCutover = DeviceRuntimeBase &
Readonly<{
operations: Readonly<{ names?: undefined; pattern: RegExp }>;
singularExecution: Readonly<{ routes: NonEmpty<string>; operations?: undefined }>;
}>;
/**
* A device-runtime command must prove its narrowing (which types re-widen a bound
* runtime, which operations are its own) and that its execution is singular.
*/
export type DeviceRuntimeCutover = NamedOperationCutover | PatternOperationCutover;
/**
* One migrated command's cutover row (ADR 0019 §6/§8). The gate that reads this table
* proves the command has exactly one platform-execution path.
*
* The union is discriminated by `execution` and its claims are mandatory, so a row is
* not a place to opt out of enforcement: adding a command means stating what it
* retired, how its admission is derived, and what makes its execution singular.
* `cutoverRowDefects` rejects the claims the type system cannot require.
*/
export type MigratedCommandCutover = InventoryCutover | DeviceRuntimeCutover;
const RETIREMENT_FORMS = [
'modulePaths',
'modulePathPatterns',
'importPatterns',
'routeNames',
'daemonOnlyRouteNames',
'daemonOnlyProviderMethods',
'pluginFacetKeys',
] as const satisfies readonly (keyof LegacyRetirementClaim)[];
/**
* Cross-field claims the type system cannot express. Returns one message per defect so
* an incomplete row fails the gate instead of passing with nothing to enforce.
*
* Reads defensively: a row that omits whole claim objects is exactly the input this
* rejects, so it must not depend on them being present.
*/
export function cutoverRowDefects(row: MigratedCommandCutover): string[] {
const defects: string[] = [];
if (!hasAnyRetirementForm(row.legacyRetirement)) {
defects.push('declares no legacy retirement form');
}
if (row.tier !== 'request-scoped' && row.tier !== 'durable-resource') {
defects.push('declares no evidence tier');
}
if (row.tier === 'durable-resource' && row.lifecycleProof === undefined) {
defects.push('is durable-resource tier but declares no lifecycle proof');
}
if (row.tier === 'request-scoped' && row.lifecycleProof !== undefined) {
defects.push('is request-scoped tier but declares a durable lifecycle proof');
}
defects.push(...executionDefects(row));
return defects;
}
function executionDefects(row: MigratedCommandCutover): string[] {
if (row.execution === 'inventory') {
return row.singularExecution?.gatewayProof === undefined
? ['declares no singular gateway proof']
: [];
}
if (row.execution !== 'device-runtime') return ['declares no execution kind'];
const defects: string[] = [];
if ((row.runtimeTypeNames?.length ?? 0) === 0) defects.push('declares no runtime type names');
if (!hasOperationClaim(row.operations)) defects.push('declares no operations');
if ((row.singularExecution?.routes?.length ?? 0) === 0) {
defects.push('declares no singular daemon route');
}
defects.push(...namedOperationDefects(row));
return defects;
}
/**
* The named set and the enforced set must be the same set, each listed once. Equality
* has to hold in both directions: an unenforced name leaves an operation free to be
* called twice or never, and an enforced name the row does not claim asserts singularity
* over an operation this command does not own. Route-only singularity stays available to
* pattern-only rows, whose operations are not knowable statically.
*/
function namedOperationDefects(row: DeviceRuntimeCutover): string[] {
const named = row.operations?.names ?? [];
if (named.length === 0) return [];
const enforced = row.singularExecution?.operations ?? [];
if (enforced.length === 0) {
return ['names its operations but enforces none of them exactly once'];
}
const defects: string[] = [];
defects.push(...duplicateDefects('names', named));
defects.push(...duplicateDefects('enforces', enforced));
const enforcedSet = new Set(enforced);
const namedSet = new Set(named);
const unenforced = named.filter((name) => !enforcedSet.has(name));
const unnamed = enforced.filter((name) => !namedSet.has(name));
if (unenforced.length > 0) {
defects.push(
`names operations it does not enforce exactly once: ${sortedUnique(unenforced).join(', ')}`,
);
}
if (unnamed.length > 0) {
defects.push(`enforces operations it does not name: ${sortedUnique(unnamed).join(', ')}`);
}
const owners = row.singularExecution?.operationOwners ?? {};
const owned = Object.keys(owners);
const ownerless = named.filter((name) => (owners[name]?.length ?? 0) === 0);
const unclaimedOwners = owned.filter((name) => !namedSet.has(name));
if (ownerless.length > 0) {
defects.push(`names operations without lexical owners: ${sortedUnique(ownerless).join(', ')}`);
}
if (unclaimedOwners.length > 0) {
defects.push(
`declares lexical owners for unnamed operations: ${sortedUnique(unclaimedOwners).join(', ')}`,
);
}
for (const [operation, lexicalOwners] of Object.entries(owners)) {
const duplicates = duplicateValues(lexicalOwners);
if (duplicates.length > 0) {
defects.push(`declares duplicate lexical owners for ${operation}: ${duplicates.join(', ')}`);
}
}
return defects;
}
function duplicateDefects(verb: 'names' | 'enforces', values: readonly string[]): string[] {
const duplicates = duplicateValues(values);
return duplicates.length === 0 ? [] : [`${verb} duplicate operations: ${duplicates.join(', ')}`];
}
function duplicateValues(values: readonly string[]): string[] {
const seen = new Set<string>();
const duplicates = values.filter((value) => {
if (seen.has(value)) return true;
seen.add(value);
return false;
});
return sortedUnique(duplicates);
}
function sortedUnique(values: readonly string[]): string[] {
return [...new Set(values)].sort();
}
function hasAnyRetirementForm(claim: LegacyRetirementClaim | undefined): boolean {
if (claim === undefined) return false;
return RETIREMENT_FORMS.some((form) => (claim[form]?.length ?? 0) > 0);
}
function hasOperationClaim(claim: OperationClaim | undefined): boolean {
if (claim === undefined) return false;
return (claim.names?.length ?? 0) > 0 || claim.pattern !== undefined;
}
@@ -0,0 +1,283 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { checkRuntimeCommandCutover } from './runtime-command-cutover-policy.ts';
import { MIGRATED_COMMAND_CUTOVERS } from './runtime-command-cutover-table.ts';
import { cutoverRowDefects, type MigratedCommandCutover } from './runtime-command-cutover-model.ts';
import { PLANTED_ROW, rowFor, sources, summariesFor } from './runtime-command-cutover-fixtures.ts';
// The mechanism itself (ADR 0019 §8): one planted row, one planted red. Per-row
// acceptance for the four migrated commands lives in the table test.
const PLANTED_RULE = 'RPlanted planted-cutover';
test('the parametrized gate goes red on a planted row across every generalized column', () => {
assert.deepEqual(
summariesFor(
PLANTED_RULE,
[
['src/daemon/planted-legacy.ts', 'export const retired = true;'],
[
'src/daemon/planted-handler.ts',
[
"import { resolvePlantedBackend } from './planted-legacy.ts';",
"requireCommandSupported('planted', device);",
'const widened = runtime as PlantedRuntimeOperations;',
"function handlePlantedCommand() { widened.operations['plantedDump']({}); }",
].join('\n'),
],
[
'src/core/command-descriptor/registry.ts',
`const commands = [{ name: 'planted', capability: { apple: {} } }];`,
],
['src/core/capabilities.ts', `const WEB_QUERY_COMMANDS = ['find', 'planted'];`],
[
'src/platforms/apple/plugin.ts',
`const supports = { [PUBLIC_COMMANDS.planted]: supportsDevice };
const plugin = { planted: { resolveBackend() {} } } satisfies PlatformPlugin;`,
],
],
[PLANTED_ROW],
),
[
'src/daemon/planted-legacy.ts: retired planted module remains',
"src/daemon/planted-handler.ts: production source imports retired planted module './planted-legacy.ts'",
'src/daemon/planted-handler.ts: legacy planted route resolvePlantedBackend',
'src/daemon/planted-handler.ts: legacy planted capability admission requireCommandSupported',
'src/daemon/planted-handler.ts: widened planted runtime type assertion',
'src/daemon/planted-handler.ts: bracketed planted operation access',
'src/core/command-descriptor/registry.ts: planted descriptor retains legacy capability admission',
'src/core/capabilities.ts: static platform command set retains planted admission',
'src/platforms/apple/plugin.ts: legacy PlatformPlugin planted facet',
'src/platforms/apple/plugin.ts: Apple plugin retains legacy planted support or hint closure',
// The bracketed access is still a call, so the operation count is satisfied while the
// route is missing: the narrowing column and the singular-route column are independent.
'(planted runtime): expected one handlePlantedCommand route, found 0',
],
);
});
test('the planted row is green once the command has exactly one execution path', () => {
assert.deepEqual(
summariesFor(
PLANTED_RULE,
[
[
'src/daemon/planted-handler.ts',
`
// resolvePlantedBackend and requireCommandSupported('planted') were removed.
const note = 'planted-legacy.ts';
function handlePlantedCommand() {
runtime.operations.plantedDump(input);
}
handlePlantedCommand(request);
`,
],
],
[PLANTED_ROW],
),
[],
);
});
test('rows are independent: one command going red leaves the others alone', () => {
const violations = checkRuntimeCommandCutover(
sources([['src/daemon/network-log.ts', 'export const parser = () => [];']]),
MIGRATED_COMMAND_CUTOVERS.filter(
({ command }) => command === 'network' || command === 'record',
),
);
assert.deepEqual(
violations
.filter(({ rule }) => rule === 'R15 network-runtime-cutover')
.map(({ message }) => message),
[
'retired network module remains',
'expected one handleNetworkCommand route, found 0',
'expected one narrowed networkDump call, found 0',
],
);
assert.deepEqual(
violations
.filter(({ rule }) => rule === 'R16 record-runtime-cutover')
.map(({ message }) => message),
[
'expected one handleRecordTraceCommands route, found 0',
'expected one narrowed screenRecordingStart call, found 0',
'expected one narrowed screenRecordingReattach call, found 0',
'expected one narrowed screenRecordingCleanup call, found 0',
],
);
});
// ---------------------------------------------------------------------------
// A row cannot opt out of enforcement by leaving its claims unstated.
// ---------------------------------------------------------------------------
test('planted red: an incomplete row fails instead of enforcing nothing', () => {
const incomplete = {
rule: PLANTED_RULE,
command: 'planted',
subject: 'planted',
} as unknown as MigratedCommandCutover;
assert.deepEqual(
summariesFor(PLANTED_RULE, [['src/daemon/anything.ts', 'export const x = 1;']], [incomplete]),
[
'(planted cutover row): cutover row declares no legacy retirement form',
'(planted cutover row): cutover row declares no evidence tier',
'(planted cutover row): cutover row declares no execution kind',
],
);
});
test('planted red: a row that retires nothing is rejected', () => {
assert.deepEqual(cutoverRowDefects({ ...PLANTED_ROW, legacyRetirement: {} }), [
'declares no legacy retirement form',
]);
});
test('planted red: an empty singular-execution claim is rejected', () => {
assert.deepEqual(
cutoverRowDefects({
...PLANTED_ROW,
singularExecution: { routes: [] as unknown as [string, ...string[]] },
}),
[
'declares no singular daemon route',
'names its operations but enforces none of them exactly once',
],
);
});
test('planted red: a named-operation row that enforces no operation is rejected', () => {
assert.deepEqual(
cutoverRowDefects({
...PLANTED_ROW,
singularExecution: {
routes: ['handlePlantedCommand'],
} as unknown as (typeof PLANTED_ROW)['singularExecution'],
}),
['names its operations but enforces none of them exactly once'],
);
});
test('planted red: a named-operation row that enforces only some operations is rejected', () => {
assert.deepEqual(
cutoverRowDefects({
...PLANTED_ROW,
operations: { names: ['plantedDump', 'plantedReattach'] },
singularExecution: {
routes: ['handlePlantedCommand'],
operations: ['plantedDump'],
operationOwners: {
plantedDump: ['handlePlantedCommand'],
plantedReattach: ['handlePlantedCommand'],
},
},
}),
['names operations it does not enforce exactly once: plantedReattach'],
);
});
test('planted red: a row that enforces an operation it does not name is rejected', () => {
assert.deepEqual(
cutoverRowDefects({
...PLANTED_ROW,
operations: { names: ['plantedDump'] },
singularExecution: {
routes: ['handlePlantedCommand'],
operations: ['plantedDump', 'somebodyElsesOperation'],
operationOwners: { plantedDump: ['handlePlantedCommand'] },
},
}),
['enforces operations it does not name: somebodyElsesOperation'],
);
});
test('planted red: duplicate operations on either side are rejected', () => {
assert.deepEqual(
cutoverRowDefects({
...PLANTED_ROW,
operations: { names: ['plantedDump', 'plantedDump'] },
singularExecution: {
routes: ['handlePlantedCommand'],
operations: ['plantedDump', 'plantedDump'],
operationOwners: { plantedDump: ['handlePlantedCommand'] },
},
}),
['names duplicate operations: plantedDump', 'enforces duplicate operations: plantedDump'],
);
});
test('planted red: every named operation declares its lexical owner', () => {
assert.deepEqual(
cutoverRowDefects({
...PLANTED_ROW,
singularExecution: {
routes: ['handlePlantedCommand'],
operations: ['plantedDump'],
operationOwners: {},
},
}),
['names operations without lexical owners: plantedDump'],
);
});
test('unrelated daemon calls neither satisfy nor duplicate an owner-scoped operation', () => {
const missing = summariesFor(
PLANTED_RULE,
[
[
'src/daemon/planted-handler.ts',
`
function handlePlantedCommand() {}
function handleUnrelatedCommand() { runtime.operations.plantedDump(input); }
handlePlantedCommand(request);
`,
],
],
[PLANTED_ROW],
);
assert.deepEqual(missing, ['(planted runtime): expected one narrowed plantedDump call, found 0']);
const present = summariesFor(
PLANTED_RULE,
[
[
'src/daemon/planted-handler.ts',
`
function handlePlantedCommand() { runtime.operations.plantedDump(input); }
function handleUnrelatedCommand() { runtime.operations.plantedDump(input); }
handlePlantedCommand(request);
`,
],
],
[PLANTED_ROW],
);
assert.deepEqual(present, []);
});
test('a pattern-only row may prove singularity through its route alone', () => {
assert.deepEqual(cutoverRowDefects(rowFor('logs')), []);
});
test('planted red: a durable row without a lifecycle proof is rejected', () => {
assert.deepEqual(cutoverRowDefects({ ...PLANTED_ROW, tier: 'durable-resource' }), [
'is durable-resource tier but declares no lifecycle proof',
]);
});
test('planted red: a request-scoped row claiming durable machinery is rejected', () => {
assert.deepEqual(cutoverRowDefects({ ...PLANTED_ROW, lifecycleProof: () => [] }), [
'is request-scoped tier but declares a durable lifecycle proof',
]);
});
test('every shipped row states its claims', () => {
assert.deepEqual(
MIGRATED_COMMAND_CUTOVERS.flatMap((row) =>
cutoverRowDefects(row).map((defect) => `${row.command}: ${defect}`),
),
[],
);
});
@@ -0,0 +1,539 @@
import { parseSync } from 'oxc-parser';
import type { LayeringViolation } from './model.ts';
import {
cutoverRowDefects,
type CutoverCheck,
type DeviceRuntimeCutover,
type MigratedCommandCutover,
type UnruledViolation,
} from './runtime-command-cutover-model.ts';
import { MIGRATED_COMMAND_CUTOVERS } from './runtime-command-cutover-table.ts';
import { memberName, propertyName, visitAst, type ProductionSource } from './cutover-policy-ast.ts';
type AstNode = Record<string, unknown>;
const SHARED_RUNTIME_TYPE_NAME = 'BoundDeviceRuntime';
/**
* The one parametrized runtime-command-cutover gate (ADR 0019 §8). Every migrated
* command is a row in {@link MIGRATED_COMMAND_CUTOVERS}; the mechanism below is proven
* once by a planted row rather than once per command.
*/
export function checkRuntimeCommandCutover(
sources: ReadonlyMap<string, string>,
table: readonly MigratedCommandCutover[] = MIGRATED_COMMAND_CUTOVERS,
): LayeringViolation[] {
const files: ProductionSource[] = [...sources].map(([path, source]) => ({ path, source }));
const programs = new Map<string, AstNode>(
files.map(({ path, source }) => [path, parseSync(path, source).program as AstNode]),
);
return table.flatMap((row) =>
rowViolations(row, files, programs).map((violation) => ({ rule: row.rule, ...violation })),
);
}
export function runtimeCommandCutoverSummary(
table: readonly MigratedCommandCutover[] = MIGRATED_COMMAND_CUTOVERS,
): string {
const commands = table.map(({ command }) => command).join(', ');
return `each migrated command (${commands}) keeps exactly one platform-execution path`;
}
function rowViolations(
row: MigratedCommandCutover,
files: readonly ProductionSource[],
programs: ReadonlyMap<string, AstNode>,
): UnruledViolation[] {
// An under-declared row would enforce nothing and read green, so the row itself is
// checked before the repo is.
const defects = cutoverRowDefects(row);
if (defects.length > 0) {
return defects.map((defect) => ({
file: `(${row.command} cutover row)`,
line: 1,
message: `cutover row ${defect}`,
}));
}
const violations: UnruledViolation[] = [];
for (const file of files) {
const program = programs.get(file.path);
if (!program) continue;
violations.push(...retiredModuleViolations(row, file));
violations.push(...legacyRouteViolations(row, file, program));
violations.push(...admissionViolations(row, file, program));
violations.push(...narrowingViolations(row, file, program));
}
violations.push(...exactCallViolations(row, files, programs));
const sources = new Map(files.map(({ path, source }) => [path, source]));
for (const check of rowChecks(row)) violations.push(...check(sources));
return violations;
}
function rowChecks(row: MigratedCommandCutover): CutoverCheck[] {
return [
...(row.execution === 'inventory' ? [row.singularExecution.gatewayProof] : []),
...(row.lifecycleProof === undefined ? [] : [row.lifecycleProof]),
...(row.extensions ?? []),
];
}
// ---------------------------------------------------------------------------
// The legacy adapter is gone: no retired module, import, or executable name.
// ---------------------------------------------------------------------------
function retiredModuleViolations(
row: MigratedCommandCutover,
file: ProductionSource,
): UnruledViolation[] {
const retired =
(row.legacyRetirement.modulePaths ?? []).includes(file.path) ||
(row.legacyRetirement.modulePathPatterns ?? []).some((pattern) => pattern.test(file.path));
return retired
? [{ file: file.path, line: 1, message: `retired ${row.subject} module remains` }]
: [];
}
function legacyRouteViolations(
row: MigratedCommandCutover,
file: ProductionSource,
program: AstNode,
): UnruledViolation[] {
const violations: UnruledViolation[] = [];
const seen = new Set<string>();
const isDaemon = file.path.startsWith('src/daemon/');
const scoped = new Set([
...(row.legacyRetirement.routeNames ?? []),
...(isDaemon ? (row.legacyRetirement.daemonOnlyRouteNames ?? []) : []),
]);
visitAst(program, (node) => {
const specifier = retiredImportSpecifier(row, node);
if (specifier !== undefined) {
push(
violations,
seen,
file,
node,
`production source imports retired ${row.subject} module '${specifier}'`,
);
}
const route = executableName(node, scoped);
if (route !== undefined) {
push(violations, seen, file, node, `legacy ${row.subject} route ${route}`);
}
if (
isDaemon &&
isProviderMethodCall(node, row.legacyRetirement.daemonOnlyProviderMethods ?? [])
) {
push(
violations,
seen,
file,
node,
`daemon invokes legacy provider method ${memberName(node['callee'] as AstNode) ?? ''}`,
);
}
if (isPlatformPluginFacet(node, row.legacyRetirement.pluginFacetKeys ?? [])) {
push(violations, seen, file, node, `legacy PlatformPlugin ${facetKeyList(row)} facet`);
}
});
return violations;
}
function facetKeyList(row: MigratedCommandCutover): string {
return (row.legacyRetirement.pluginFacetKeys ?? []).join('/');
}
/** Identifier, property key (computed or not), and computed member access all count. */
function executableName(node: AstNode, names: ReadonlySet<string>): string | undefined {
if (names.size === 0) return undefined;
if (node['type'] === 'Identifier' && names.has(String(node['name']))) return String(node['name']);
if (node['type'] === 'Property' || node['type'] === 'TSPropertySignature') {
const key = propertyName(node['key']);
return key !== undefined && names.has(key) ? key : undefined;
}
if (node['type'] === 'MemberExpression' && node['computed'] === true) {
const key = propertyName(node['property']);
return key !== undefined && names.has(key) ? key : undefined;
}
return undefined;
}
function retiredImportSpecifier(row: MigratedCommandCutover, node: AstNode): string | undefined {
const patterns = row.legacyRetirement.importPatterns ?? [];
if (patterns.length === 0) return undefined;
if (
node['type'] !== 'ImportDeclaration' &&
node['type'] !== 'ImportExpression' &&
node['type'] !== 'ExportNamedDeclaration' &&
node['type'] !== 'ExportAllDeclaration'
) {
return undefined;
}
// `export const x = 1` is an ExportNamedDeclaration with a null source.
const source = node['source'] as AstNode | null | undefined;
const specifier = source === null || source === undefined ? undefined : source['value'];
if (typeof specifier !== 'string') return undefined;
return patterns.some((pattern) => pattern.test(specifier)) ? specifier : undefined;
}
function isProviderMethodCall(node: AstNode, methods: readonly string[]): boolean {
if (methods.length === 0 || node['type'] !== 'CallExpression') return false;
const callee = node['callee'] as AstNode | undefined;
if (callee === undefined || callee['type'] !== 'MemberExpression') return false;
const name = memberName(callee);
return name !== undefined && methods.includes(name);
}
function isPlatformPluginFacet(node: AstNode, facetKeys: readonly string[]): boolean {
if (facetKeys.length === 0) return false;
if (node['type'] === 'TSTypeAliasDeclaration' || node['type'] === 'TSInterfaceDeclaration') {
const id = node['id'] as AstNode | undefined;
return (
id?.['type'] === 'Identifier' &&
id['name'] === 'PlatformPlugin' &&
declaresFacetKey(node, facetKeys)
);
}
if (node['type'] === 'TSSatisfiesExpression' || node['type'] === 'TSAsExpression') {
return (
isNamedType(node['typeAnnotation'], 'PlatformPlugin') &&
declaresFacetKey(node['expression'], facetKeys)
);
}
if (node['type'] !== 'VariableDeclarator') return false;
const id = node['id'] as AstNode | undefined;
return (
isNamedType(id?.['typeAnnotation'], 'PlatformPlugin') &&
declaresFacetKey(node['init'], facetKeys)
);
}
function declaresFacetKey(node: unknown, facetKeys: readonly string[]): boolean {
let found = false;
visitAst(node, (candidate) => {
if (
(candidate['type'] === 'Property' || candidate['type'] === 'TSPropertySignature') &&
candidate['computed'] !== true &&
facetKeys.includes(propertyName(candidate['key']) ?? '')
) {
found = true;
}
});
return found;
}
function isNamedType(node: unknown, expected: string): boolean {
if (node === null || typeof node !== 'object') return false;
const record = node as AstNode;
if (record['type'] === 'TSTypeAnnotation') return isNamedType(record['typeAnnotation'], expected);
if (record['type'] !== 'TSTypeReference') return false;
const name = record['typeName'] as AstNode | undefined;
return name?.['type'] === 'Identifier' && name['name'] === expected;
}
// ---------------------------------------------------------------------------
// Admission comes from facts: no capability bucket, no static command set.
// ---------------------------------------------------------------------------
function admissionViolations(
row: MigratedCommandCutover,
file: ProductionSource,
program: AstNode,
): UnruledViolation[] {
const violations: UnruledViolation[] = [];
const member = row.admissionMember;
const memberInScope = member !== undefined && (member.files?.includes(file.path) ?? true);
visitAst(program, (node) => {
if (isLegacyAdmissionCall(node, row.command)) {
violations.push(
at(file, node, `legacy ${row.command} capability admission requireCommandSupported`),
);
}
if (isDescriptorWithCapability(node, row.command)) {
violations.push(
at(file, node, `${row.command} descriptor retains legacy capability admission`),
);
}
if (isStaticCommandSet(node, row.command)) {
violations.push(
at(file, node, `static platform command set retains ${row.command} admission`),
);
}
if (memberInScope && isAdmissionMember(node, row.command, member.forms)) {
violations.push(at(file, node, member.message));
}
});
return violations;
}
function isLegacyAdmissionCall(node: AstNode, command: string): boolean {
if (node['type'] !== 'CallExpression') return false;
const callee = node['callee'] as AstNode | undefined;
const args = node['arguments'] as readonly AstNode[] | undefined;
return (
callee?.['type'] === 'Identifier' &&
callee['name'] === 'requireCommandSupported' &&
isCommandExpression(args?.[0], command)
);
}
/** `'logs'` and `PUBLIC_COMMANDS.logs` name the same command. */
function isCommandExpression(node: AstNode | undefined, command: string): boolean {
if (node?.['type'] === 'Literal') return node['value'] === command;
if (node?.['type'] !== 'MemberExpression') return false;
const object = node['object'] as AstNode | undefined;
return (
object?.['type'] === 'Identifier' &&
object['name'] === 'PUBLIC_COMMANDS' &&
memberName(node) === camelCommandKey(command)
);
}
/** `PUBLIC_COMMANDS` keys are camelCase (`react-native` -> `reactNative`). */
function camelCommandKey(command: string): string {
return command.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
}
function isDescriptorWithCapability(node: AstNode, command: string): boolean {
if (node['type'] !== 'ObjectExpression' || !Array.isArray(node['properties'])) return false;
let named = false;
let capability = false;
for (const property of node['properties'] as AstNode[]) {
if (property['type'] !== 'Property' || property['computed'] === true) continue;
const key = propertyName(property['key']);
const value = property['value'] as AstNode | undefined;
if (key === 'name' && value?.['type'] === 'Literal' && value['value'] === command) named = true;
if (key === 'capability') capability = true;
}
return named && capability;
}
function isStaticCommandSet(node: AstNode, command: string): boolean {
if (node['type'] !== 'VariableDeclarator') return false;
const id = node['id'] as AstNode | undefined;
return (
id?.['type'] === 'Identifier' &&
/(?:WEB|HARMONY).*COMMANDS/.test(String(id['name'])) &&
containsStringLiteral(node['init'], command)
);
}
function isAdmissionMember(
node: AstNode,
command: string,
forms: readonly ('computed-property' | 'public-commands-member')[],
): boolean {
if (
forms.includes('computed-property') &&
node['type'] === 'Property' &&
node['computed'] === true &&
isCommandExpression(node['key'] as AstNode | undefined, command)
) {
return true;
}
return (
forms.includes('public-commands-member') &&
node['type'] === 'MemberExpression' &&
isCommandExpression(node, command)
);
}
function containsStringLiteral(node: unknown, expected: string): boolean {
let found = false;
visitAst(node, (candidate) => {
if (candidate['type'] === 'Literal' && candidate['value'] === expected) found = true;
});
return found;
}
// ---------------------------------------------------------------------------
// The runtime route stays narrowed: no manufactured proof, no widened access.
// ---------------------------------------------------------------------------
function narrowingViolations(
row: MigratedCommandCutover,
file: ProductionSource,
program: AstNode,
): UnruledViolation[] {
// An inventory row binds no device runtime, so it has nothing to re-widen.
if (!file.path.startsWith('src/daemon/') || row.execution !== 'device-runtime') return [];
const violations: UnruledViolation[] = [];
const runtimeTypes = new Set([SHARED_RUNTIME_TYPE_NAME, ...row.runtimeTypeNames]);
visitAst(program, (node) => {
if (
(node['type'] === 'TSAsExpression' || node['type'] === 'TSTypeAssertion') &&
containsTypeName(node['typeAnnotation'], runtimeTypes)
) {
violations.push(at(file, node, `widened ${row.subject} runtime type assertion`));
}
if (node['type'] === 'TSNonNullExpression' && isNonNullRepair(row, node['expression'])) {
violations.push(at(file, node, `non-null repair of a narrowed ${row.subject} operation`));
}
if (isBracketedOperationAccess(row, node)) {
violations.push(at(file, node, `bracketed ${row.subject} operation access`));
}
});
return violations;
}
function isNonNullRepair(row: DeviceRuntimeCutover, node: unknown): boolean {
if (node === null || typeof node !== 'object') return false;
const member = node as AstNode;
if (member['type'] !== 'MemberExpression') return false;
if (row.nonNullRepairScope === 'any-operation') {
const object = member['object'] as AstNode | undefined;
return object?.['type'] === 'MemberExpression' && memberName(object) === 'operations';
}
return isRowOperation(row, memberName(member));
}
function isBracketedOperationAccess(row: DeviceRuntimeCutover, node: AstNode): boolean {
if (node['type'] !== 'MemberExpression' || node['computed'] !== true) return false;
const object = node['object'] as AstNode | undefined;
return (
isRowOperation(row, memberName(node)) &&
object?.['type'] === 'MemberExpression' &&
memberName(object) === 'operations'
);
}
function isRowOperation(row: DeviceRuntimeCutover, name: string | undefined): boolean {
if (name === undefined) return false;
return (
(row.operations.names ?? []).includes(name) || (row.operations.pattern?.test(name) ?? false)
);
}
function containsTypeName(node: unknown, names: ReadonlySet<string>): boolean {
let found = false;
visitAst(node, (candidate) => {
if (candidate['type'] === 'Identifier' && names.has(String(candidate['name']))) found = true;
});
return found;
}
// ---------------------------------------------------------------------------
// The route is singular: one daemon orchestration call, one operation call.
// ---------------------------------------------------------------------------
function exactCallViolations(
row: MigratedCommandCutover,
files: readonly ProductionSource[],
programs: ReadonlyMap<string, AstNode>,
): UnruledViolation[] {
if (row.execution !== 'device-runtime') return [];
const routes: readonly string[] = row.singularExecution.routes;
const operations: readonly string[] = row.singularExecution.operations ?? [];
const routeCounts = new Map<string, number>(routes.map((name) => [name, 0]));
const ownerNodes = new Map<string, AstNode[]>();
for (const file of files.filter(({ path }) => path.startsWith('src/daemon/'))) {
const program = programs.get(file.path);
if (!program) continue;
visitAst(program, (node) => {
const owner = lexicalFunctionOwnerName(node);
if (owner !== undefined) {
const nodes = ownerNodes.get(owner) ?? [];
nodes.push(node);
ownerNodes.set(owner, nodes);
}
if (node['type'] !== 'CallExpression') return;
const callee = node['callee'] as AstNode | undefined;
if (callee?.['type'] === 'Identifier' && routeCounts.has(String(callee['name']))) {
bump(routeCounts, String(callee['name']));
}
});
}
const operationCounts = new Map(
operations.map((operation) => [
operation,
countOwnedOperationCalls(
operation,
row.singularExecution.operationOwners[operation] ?? [],
ownerNodes,
),
]),
);
const pseudoFile = `(${row.command} runtime)`;
return [
...routes.map((name) => ({ found: routeCounts.get(name) ?? 0, what: `${name} route` })),
...operations.map((name) => ({
found: operationCounts.get(name) ?? 0,
what: `narrowed ${name} call`,
})),
]
.filter(({ found }) => found !== 1)
.map(({ what, found }) => ({
file: pseudoFile,
line: 1,
message: `expected one ${what}, found ${found}`,
}));
}
function lexicalFunctionOwnerName(node: AstNode): string | undefined {
if (node['type'] === 'FunctionDeclaration') {
const id = node['id'] as AstNode | null | undefined;
return id?.['type'] === 'Identifier' ? String(id['name']) : undefined;
}
if (node['type'] !== 'VariableDeclarator') return undefined;
const id = node['id'] as AstNode | undefined;
const init = node['init'] as AstNode | null | undefined;
if (id?.['type'] !== 'Identifier') return undefined;
if (init?.['type'] !== 'ArrowFunctionExpression' && init?.['type'] !== 'FunctionExpression') {
return undefined;
}
return String(id['name']);
}
function countOwnedOperationCalls(
operation: string,
owners: readonly string[],
ownerNodes: ReadonlyMap<string, readonly AstNode[]>,
): number {
let count = 0;
for (const owner of owners) {
for (const node of ownerNodes.get(owner) ?? []) {
visitAst(node, (candidate) => {
if (candidate['type'] !== 'CallExpression') return;
const callee = candidate['callee'] as AstNode | undefined;
if (
callee?.['type'] === 'MemberExpression' &&
isRuntimeOperationCall(callee) &&
memberName(callee) === operation
) {
count += 1;
}
});
}
}
return count;
}
function isRuntimeOperationCall(callee: AstNode): boolean {
const object = callee['object'] as AstNode | undefined;
return object?.['type'] === 'MemberExpression' && memberName(object) === 'operations';
}
function bump(counts: Map<string, number>, name: string): void {
counts.set(name, (counts.get(name) ?? 0) + 1);
}
// ---------------------------------------------------------------------------
function push(
violations: UnruledViolation[],
seen: Set<string>,
file: ProductionSource,
node: AstNode,
message: string,
): void {
const identity = `${String(node['start'] ?? '')}:${message}`;
if (seen.has(identity)) return;
seen.add(identity);
violations.push(at(file, node, message));
}
function at(file: ProductionSource, node: AstNode, message: string): UnruledViolation {
const offset = typeof node['start'] === 'number' ? node['start'] : 0;
return { file: file.path, line: file.source.slice(0, offset).split('\n').length, message };
}
@@ -0,0 +1,490 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { checkRuntimeCommandCutover } from './runtime-command-cutover-policy.ts';
import { rowFor, sources, summariesFor } from './runtime-command-cutover-fixtures.ts';
// Per-row acceptance for the four migrated commands. Every case here was carried over
// from the per-command policy tests these rows replaced; the mechanism itself is proven
// in runtime-command-cutover-policy.test.ts.
const DEVICES_HANDLER = [
"import { listDeviceInventory as discoverInventory } from '../../core/dispatch-resolve.ts';",
'export async function handleDevices(request: Request) { return await discoverInventory(request); }',
].join('\n');
const DEVICES_HANDLER_FILE = 'src/daemon/handlers/session-inventory.ts';
// ---------------------------------------------------------------------------
// devices (R13)
// ---------------------------------------------------------------------------
test('devices rejects an exact legacy inventory module path', () => {
assert.deepEqual(
summariesFor('R17 device-inventory-cutover', [
[DEVICES_HANDLER_FILE, DEVICES_HANDLER],
['src/platforms/apple/devices.ts', 'export const staleInventoryRoute = true;'],
]),
['src/platforms/apple/devices.ts: retired device inventory module remains'],
);
});
test('devices rejects executable legacy imports and identifiers', () => {
const legacy = [
"import { listAppleDevices } from './platforms/apple/devices.ts';",
'export async function discover() { return await listAppleDevices(); }',
].join('\n');
const found = summariesFor('R17 device-inventory-cutover', [
[DEVICES_HANDLER_FILE, DEVICES_HANDLER],
['src/legacy-inventory.ts', legacy],
]).join('\n');
assert.match(found, /imports retired device inventory module/);
assert.match(found, /legacy device inventory route listAppleDevices/);
});
test('devices rejects dynamic imports and re-exports of legacy inventory modules', () => {
for (const route of [
"export const legacy = import('./platforms/apple/devices.ts');",
"export { staleInventory } from './core/platform-inventory.ts';",
]) {
assert.match(
summariesFor('R17 device-inventory-cutover', [
[DEVICES_HANDLER_FILE, DEVICES_HANDLER],
['src/legacy-inventory.ts', route],
]).join('\n'),
/imports retired device inventory module/,
);
}
});
test('devices ignores comments, strings, and similarly named non-route modules', () => {
const prose = [
"const note = 'listAppleDevices from core/platform-inventory';",
'// discoverDevices used to live in platforms/apple/devices.ts',
'export const cacheKey = note;',
].join('\n');
assert.deepEqual(
summariesFor('R17 device-inventory-cutover', [
[DEVICES_HANDLER_FILE, DEVICES_HANDLER],
['src/platforms/apple/devices-cache.ts', prose],
]),
[],
);
});
// ---------------------------------------------------------------------------
// logs (R14)
// ---------------------------------------------------------------------------
test('legacy logs scan catches a planted provider route and plugin facet', () => {
assert.deepEqual(
summariesFor('R14 logs-runtime-cutover', [
[
'src/daemon/planted.ts',
'await withAppLogProvider(provider, task); await startAppLog(request); handleLogsCommand(params);',
],
[
'src/platforms/planted/plugin.ts',
'export const plugin = { appLog: { resolveBackend() {} } } satisfies PlatformPlugin;',
],
]),
[
'src/daemon/planted.ts: legacy logs route withAppLogProvider',
'src/daemon/planted.ts: legacy logs route startAppLog',
'src/platforms/planted/plugin.ts: legacy PlatformPlugin appLog facet',
],
);
});
test('legacy logs scan ignores retired names in comments and string data', () => {
assert.deepEqual(
summariesFor('R14 logs-runtime-cutover', [
[
'src/daemon/planted.ts',
`
// AppLogProvider and startAppLog were removed.
const migrationNote = 'withAppLogProvider and requireCommandSupported("logs")';
const metadata = { note: 'appLogProvider' };
handleLogsCommand(params);
`,
],
['src/platforms/apple/plugin.ts', `const note = 'PUBLIC_COMMANDS.logs';`],
]),
[],
);
});
test('legacy logs scan retains type-import and computed executable route coverage', () => {
assert.deepEqual(
summariesFor('R14 logs-runtime-cutover', [
[
'src/daemon/planted.ts',
`
import type { AppLogProvider as LegacyProvider } from './old-app-log.ts';
const route = handlers['startAppLog'];
handleLogsCommand(params);
`,
],
]),
[
'src/daemon/planted.ts: legacy logs route AppLogProvider',
'src/daemon/planted.ts: legacy logs route startAppLog',
],
);
});
test('legacy logs scan catches planted capability and dual-admission routes', () => {
assert.deepEqual(
summariesFor('R14 logs-runtime-cutover', [
[
'src/core/command-descriptor/registry.ts',
`
const registry = [
{ name: 'logs', capability: { apple: {} }, platformExecution: runtime },
{ name: 'events', capability: { apple: {} } },
];
`,
],
[
'src/daemon/handlers/planted.ts',
`
const literal = requireCommandSupported('logs', device);
const symbolic = requireCommandSupported(PUBLIC_COMMANDS.logs, device);
handleLogsCommand(params);
`,
],
[
'src/platforms/apple/plugin.ts',
`const supports = { [PUBLIC_COMMANDS.logs]: supportsCoreDevice };`,
],
[
'src/core/capabilities.ts',
`const HARMONYOS_SUPPORTED_COMMANDS = new Set(['open', 'logs']);`,
],
]),
[
'src/core/command-descriptor/registry.ts: logs descriptor retains legacy capability admission',
'src/daemon/handlers/planted.ts: legacy logs capability admission requireCommandSupported',
'src/daemon/handlers/planted.ts: legacy logs capability admission requireCommandSupported',
'src/platforms/apple/plugin.ts: Apple plugin retains legacy logs support or hint closure',
'src/core/capabilities.ts: static platform command set retains logs admission',
],
);
});
test('legacy logs scan follows declaration roles after files move', () => {
assert.deepEqual(
summariesFor('R14 logs-runtime-cutover', [
[
'src/moved/command-declarations.ts',
`const descriptor = { name: 'logs', capability: { apple: {} } };`,
],
[
'src/moved/platform-contract.ts',
`export type PlatformPlugin = { readonly appLog?: { inspect(): void } };`,
],
[
'src/moved/apple-family.ts',
`const plugin = { appLog: { inspect() {} } } as const satisfies PlatformPlugin;`,
],
['src/moved/typed-plugin.ts', `const plugin: PlatformPlugin = { appLog: { inspect() {} } };`],
['src/moved/capability-data.ts', `const HARMONYOS_SUPPORTED_COMMANDS = new Set(['logs']);`],
['src/daemon/logs-route.ts', 'handleLogsCommand(params);'],
]),
[
'src/moved/command-declarations.ts: logs descriptor retains legacy capability admission',
'src/moved/platform-contract.ts: legacy PlatformPlugin appLog facet',
'src/moved/apple-family.ts: legacy PlatformPlugin appLog facet',
'src/moved/typed-plugin.ts: legacy PlatformPlugin appLog facet',
'src/moved/capability-data.ts: static platform command set retains logs admission',
],
);
});
test('R14 violations retain their exact source line', () => {
const [violation] = checkRuntimeCommandCutover(
sources([
[
'src/daemon/moved-handler.ts',
`
const note = 'reason: retained';
const widened = runtime as BoundDeviceRuntime<typeof use>;
`,
],
]),
[rowFor('logs')],
);
assert.deepEqual(violation, {
rule: 'R14 logs-runtime-cutover',
file: 'src/daemon/moved-handler.ts',
line: 3,
message: 'widened logs runtime type assertion',
});
});
test('logs narrowing scan catches planted assertions, non-null repair, and bracket access', () => {
const found = summariesFor('R14 logs-runtime-cutover', [
[
'src/daemon/handlers/planted.ts',
`
const widened = runtime as BoundDeviceRuntime<typeof use>;
widened.operations.appLogStart!({});
widened.operations['appLogDoctor']({});
handleLogsCommand(params);
`,
],
]);
assert.equal(found.length, 3);
assert.match(found.join('\n'), /widened logs runtime type assertion/);
assert.match(found.join('\n'), /non-null repair of a narrowed logs operation/);
assert.match(found.join('\n'), /bracketed logs operation access/);
});
test('logs keeps the broader non-null scan over every daemon runtime operation', () => {
assert.deepEqual(
summariesFor('R14 logs-runtime-cutover', [
[
'src/daemon/handlers/planted.ts',
'runtime.operations.somethingElse!({}); handleLogsCommand(params);',
],
]),
['src/daemon/handlers/planted.ts: non-null repair of a narrowed logs operation'],
);
});
// ---------------------------------------------------------------------------
// network (R15)
// ---------------------------------------------------------------------------
test('network cutover scan catches planted legacy execution and dual admission', () => {
assert.deepEqual(
summariesFor('R15 network-runtime-cutover', [
[
'src/daemon/handlers/planted.ts',
`
await readSessionNetworkCapture(input);
await provider.dumpNetwork({ include: 'all' });
requireCommandSupported('network', device);
function handleNetworkCommand() { runtime.operations.networkDump(input); }
handleNetworkCommand(request);
`,
],
[
'src/core/command-descriptor/registry.ts',
`const commands = [{ name: 'network', capability: { apple: {} } }];`,
],
['src/core/capabilities.ts', `const WEB_QUERY_COMMANDS = ['find', 'network'];`],
[
'src/platforms/apple/plugin.ts',
`const supports = { [PUBLIC_COMMANDS.network]: supportsDevice };`,
],
]),
[
'src/daemon/handlers/planted.ts: legacy network route readSessionNetworkCapture',
'src/daemon/handlers/planted.ts: daemon invokes legacy provider method dumpNetwork',
'src/daemon/handlers/planted.ts: legacy network capability admission requireCommandSupported',
'src/core/command-descriptor/registry.ts: network descriptor retains legacy capability admission',
'src/core/capabilities.ts: static platform command set retains network admission',
'src/platforms/apple/plugin.ts: Apple plugin retains legacy network support or hint closure',
],
);
});
test('network cutover scan rejects a retired implementation by committed path', () => {
assert.deepEqual(
summariesFor('R15 network-runtime-cutover', [
['src/daemon/network-log.ts', `export const parser = () => [];`],
[
'src/daemon/handlers/ok.ts',
'function handleNetworkCommand() { runtime.operations.networkDump(i); } handleNetworkCommand(r);',
],
]),
['src/daemon/network-log.ts: retired network module remains'],
);
});
test('network cutover scan ignores retired names in comments and string data', () => {
assert.deepEqual(
summariesFor('R15 network-runtime-cutover', [
[
'src/daemon/planted.ts',
`
// readSessionNetworkCapture and requireCommandSupported('network') were removed.
const note = 'provider.dumpNetwork and PUBLIC_COMMANDS.network';
function handleNetworkCommand() { runtime.operations.networkDump(input); }
handleNetworkCommand(request);
`,
],
]),
[],
);
});
test('network narrowing scan catches planted casts, non-null repair, and bracket access', () => {
const found = summariesFor('R15 network-runtime-cutover', [
[
'src/daemon/handlers/planted.ts',
`
const widened = runtime as BoundDeviceRuntime<typeof use>;
widened.operations.networkDump!({});
widened.operations['networkDump']({});
`,
],
]);
const messages = found.join('\n');
assert.match(messages, /widened network runtime type assertion/);
assert.match(messages, /non-null repair of a narrowed network operation/);
assert.match(messages, /bracketed network operation access/);
});
test('network route scan rejects planted neither and duplicate routes', () => {
assert.deepEqual(summariesFor('R15 network-runtime-cutover', []), [
'(network runtime): expected one handleNetworkCommand route, found 0',
'(network runtime): expected one narrowed networkDump call, found 0',
]);
assert.deepEqual(
summariesFor('R15 network-runtime-cutover', [
[
'src/daemon/planted.ts',
`
function handleNetworkCommand() {
runtime.operations.networkDump(input);
fallback.operations.networkDump(input);
}
handleNetworkCommand(first);
handleNetworkCommand(second);
`,
],
]),
[
'(network runtime): expected one handleNetworkCommand route, found 2',
'(network runtime): expected one narrowed networkDump call, found 2',
],
);
});
// ---------------------------------------------------------------------------
// record (R16)
// ---------------------------------------------------------------------------
test('R16 catches a planted legacy recording backend, admission, and plugin facet', () => {
assert.deepEqual(
summariesFor('R16 record-runtime-cutover', [
[
'src/daemon/handlers/planted.ts',
`
await resolveRecordingBackendForDevice(device).start(input);
requireCommandSupported('record', device);
requireCommandSupported(PUBLIC_COMMANDS.record, device);
const backend = RECORDING_BACKENDS_BY_TAG.android;
type LegacyTag = RecordingBackendTag;
function startRecording() { runtime.operations.screenRecordingStart(input); }
function createScreenRecordingRecoveryControl() {
runtime.operations.screenRecordingReattach(input);
runtime.operations.screenRecordingCleanup(input);
}
handleRecordTraceCommands(input);
`,
],
[
'src/core/command-descriptor/registry.ts',
`const commands = [{ name: 'record', capability: { apple: {} } }];`,
],
[
'src/platforms/apple/plugin.ts',
`const plugin = { recording: { resolveBackendTag() {} } } satisfies PlatformPlugin;`,
],
]),
[
'src/daemon/handlers/planted.ts: legacy recording route resolveRecordingBackendForDevice',
'src/daemon/handlers/planted.ts: legacy recording route RECORDING_BACKENDS_BY_TAG',
'src/daemon/handlers/planted.ts: legacy recording route RecordingBackendTag',
'src/daemon/handlers/planted.ts: legacy record capability admission requireCommandSupported',
'src/daemon/handlers/planted.ts: legacy record capability admission requireCommandSupported',
'src/core/command-descriptor/registry.ts: record descriptor retains legacy capability admission',
'src/platforms/apple/plugin.ts: legacy PlatformPlugin recording facet',
],
);
});
test('R16 excludes trace-only mechanics from the record cutover scan', () => {
assert.deepEqual(
summariesFor('R16 record-runtime-cutover', [
[
'src/daemon/handlers/record-trace.ts',
`
const traceRecording = { startTrace() {}, stopTrace() {} };
traceRecording.startTrace();
function startRecording() { runtime.operations.screenRecordingStart(input); }
function createScreenRecordingRecoveryControl() {
runtime.operations.screenRecordingReattach(input);
runtime.operations.screenRecordingCleanup(input);
}
handleRecordTraceCommands(input);
`,
],
]),
[],
);
});
test('R16 rejects the retired recording-provider scope while allowing the focused transport', () => {
const found = summariesFor('R16 record-runtime-cutover', [
[
'src/daemon/request-platform-providers.ts',
`
import type { RecordingProvider } from './recording-provider.ts';
const recordingProvider = resolveRecordingProvider();
await withRecordingProvider(undefined, task);
`,
],
[
'src/platform-runtime-screen-recording-apple-transport.ts',
`
// recordingProvider and withRecordingProvider are retired prose.
const prose = 'src/daemon/recording-provider.ts';
withAppleSimulatorScreenRecordingTransport(transport, task);
`,
],
['src/daemon/recording-provider.ts', `export const retired = true;`],
[
'src/daemon/dynamic-provider.ts',
`
const providers = { ['recordingProvider']: resolver };
await import('./recording-provider.ts');
`,
],
]);
const messages = found.join('\n');
assert.match(messages, /retired recording module remains/);
assert.match(messages, /imports retired recording module/);
assert.match(messages, /legacy recording route RecordingProvider/);
assert.match(messages, /legacy recording route recordingProvider/);
assert.match(messages, /legacy recording route resolveRecordingProvider/);
assert.match(messages, /legacy recording route withRecordingProvider/);
assert.doesNotMatch(
messages,
/src\/platform-runtime-screen-recording-apple-transport\.ts/,
'prose in the focused transport must stay allowed',
);
});
test('R16 rejects proof repair for a narrowed screen-recording operation', () => {
const found = summariesFor('R16 record-runtime-cutover', [
[
'src/daemon/handlers/planted.ts',
`
const widened = runtime as BoundDeviceRuntime<typeof use>;
widened.operations.screenRecordingStart!({});
widened.operations['screenRecordingReattach']({});
`,
],
]);
const messages = found.join('\n');
assert.match(messages, /widened recording runtime type assertion/);
assert.match(messages, /non-null repair of a narrowed recording operation/);
assert.match(messages, /bracketed recording operation access/);
});
@@ -0,0 +1,171 @@
import type { MigratedCommandCutover, UnruledViolation } from './runtime-command-cutover-model.ts';
import {
appLogSessionStateOwnershipViolations,
devicesGatewayBindingViolations,
sourceExecutedUsingDeclarationViolations,
} from './runtime-command-cutover-extensions.ts';
import { recordRuntimeDaemonMechanicsViolations } from './record-runtime-mechanics-policy.ts';
/**
* One row per migrated command (ADR 0019 §8). A new command unit adds a row here; the
* mechanism in `runtime-command-cutover-policy.ts` carries one planted-red proof for
* every row, and `cutoverRowDefects` rejects a row that leaves its claims unstated.
*
* Rule ids are per row: the layering report groups violations under R17 devices,
* R14 logs, R15 network, R16 record.
*/
export const MIGRATED_COMMAND_CUTOVERS: readonly MigratedCommandCutover[] = [
{
rule: 'R17 device-inventory-cutover',
command: 'devices',
subject: 'device inventory',
tier: 'request-scoped',
execution: 'inventory',
legacyRetirement: {
modulePaths: ['src/core/platform-inventory.ts'],
modulePathPatterns: [/^src\/platforms\/(?:[^/]+\/)*devices\.ts$/],
importPatterns: [
/(?:^|\/)core\/platform-inventory(?:\.[cm]?[jt]s)?$/,
/(?:^|\/)platforms\/(?:[^/]+\/)*devices(?:\.[cm]?[jt]s)?$/,
],
routeNames: [
'discoverDevices',
'listAndroidDevices',
'listAppleDevices',
'listHarmonyDevices',
'listHarmonyOsDevices',
'listLinuxDevices',
'listMacosDevices',
'listVegaDevices',
'listWebDevices',
],
},
singularExecution: { gatewayProof: devicesGatewayBindingViolations },
},
{
rule: 'R14 logs-runtime-cutover',
command: 'logs',
subject: 'logs',
tier: 'durable-resource',
execution: 'device-runtime',
legacyRetirement: {
routeNames: [
'startAppLog',
'stopAppLog',
'runAppLogDoctor',
'resolveLogBackend',
'withAppLogProvider',
'appLogProvider',
'AppLogProviderResolver',
'AppLogProvider',
],
pluginFacetKeys: ['appLog'],
},
// Apple logs admission is a computed property key; `PUBLIC_COMMANDS.logs` as a plain
// member is not an admission signal for this command.
admissionMember: {
forms: ['computed-property'],
message: 'Apple plugin retains legacy logs support or hint closure',
},
runtimeTypeNames: ['AppLogRuntimeOperations', 'AppLogLiveHandle'],
// Input-dependent plan: the logs use narrows to a family, not a fixed operation list.
operations: { pattern: /^appLog[A-Za-z]+$/ },
nonNullRepairScope: 'any-operation',
// The app-log operation set is input-dependent, so only the daemon route is singular.
singularExecution: { routes: ['handleLogsCommand'] },
lifecycleProof: appLogSessionStateOwnershipViolations,
extensions: [sourceExecutedUsingDeclarationViolations],
},
{
rule: 'R15 network-runtime-cutover',
command: 'network',
subject: 'network',
tier: 'request-scoped',
execution: 'device-runtime',
legacyRetirement: {
modulePaths: [
'src/daemon/network-log.ts',
'src/daemon/app-log-network-recovery.ts',
'src/daemon/network-log-android-recovery.ts',
'src/daemon/network-log-ios-simulator-recovery.ts',
],
daemonOnlyRouteNames: [
'handleWebNetworkCommand',
'readSessionNetworkCapture',
'readRecentNetworkTraffic',
'readRecentAndroidLogcatForPackage',
'readRecentIosSimulatorLogShowForBundle',
],
daemonOnlyProviderMethods: ['dumpNetwork'],
},
// Scoped to the Apple plugin: elsewhere `PUBLIC_COMMANDS.network` is an identifier,
// not an admission decision.
admissionMember: {
forms: ['public-commands-member'],
files: ['src/platforms/apple/plugin.ts'],
message: 'Apple plugin retains legacy network support or hint closure',
},
runtimeTypeNames: ['NetworkRuntimeOperations'],
operations: { names: ['networkDump'] },
singularExecution: {
routes: ['handleNetworkCommand'],
operations: ['networkDump'],
operationOwners: { networkDump: ['handleNetworkCommand'] },
},
},
{
rule: 'R16 record-runtime-cutover',
command: 'record',
subject: 'recording',
tier: 'durable-resource',
execution: 'device-runtime',
legacyRetirement: {
modulePaths: ['src/daemon/recording-provider.ts'],
importPatterns: [/(?:^|\/)recording-provider(?:\.[cm]?[jt]s)?$/],
routeNames: [
'resolveRecordingBackendForDevice',
'stopActiveRecording',
'RecordingBackend',
'RecordingStartBackend',
'RECORDING_BACKENDS_BY_TAG',
'RecordingBackendTag',
'RecordingProvider',
'RecordingProviderResolver',
'recordingProvider',
'resolveRecordingProvider',
'withRecordingProvider',
'createLocalRecordingProvider',
],
pluginFacetKeys: ['recording'],
},
// No admission-member claim: `PUBLIC_COMMANDS.record` is live identifier-only data in
// the daemon session-event tables, so the member form cannot discriminate here.
runtimeTypeNames: ['ScreenRecordingRuntimeOperations', 'ScreenRecordingLiveHandle'],
operations: {
names: ['screenRecordingStart', 'screenRecordingReattach', 'screenRecordingCleanup'],
},
singularExecution: {
routes: ['handleRecordTraceCommands'],
operations: ['screenRecordingStart', 'screenRecordingReattach', 'screenRecordingCleanup'],
operationOwners: {
screenRecordingStart: ['startRecording'],
screenRecordingReattach: ['createScreenRecordingRecoveryControl'],
screenRecordingCleanup: ['createScreenRecordingRecoveryControl'],
},
},
lifecycleProof: recordDaemonMechanicsProof,
},
];
/** The record mechanics policy predates the row model and reports `path: message`. */
function recordDaemonMechanicsProof(sources: ReadonlyMap<string, string>): UnruledViolation[] {
const production = [...sources].map(([path, source]) => ({ path, source }));
return recordRuntimeDaemonMechanicsViolations(production).map((violation) => {
const separator = violation.indexOf(': ');
return {
file: separator < 0 ? '(record runtime)' : violation.slice(0, separator),
line: 1,
message: separator < 0 ? violation : violation.slice(separator + 2),
};
});
}