Skip to content

Commit 3d4afad

Browse files
committed
refactor(daemon): make admit-before-bind structural with an admitted-plan token; retire the R32 syntax policy
admitRuntimePlan (was inspectRequiredRuntimeUse) now takes the plan and, on success, returns an AdmittedRuntimePlan<Plan>: the plan plus a module-private symbol key no other module can name. bindSnapshotCaptureRuntime requires that token instead of a bare plan, so the snapshot route cannot reach the capture operations without facts-first admission having run for the very plan whose use is bound. Forging the token needs a type assertion, and the cutover gate's manufactured-proof column now rejects a cast to AdmittedRuntimePlan in src/daemon/ alongside BoundDeviceRuntime. That retires scripts/layering/runtime-command-cutover-snapshot.ts — R32's per-command AST policy (exact-call-shape recognition of the admission call and a text sniff for a locally reimplemented admission) — and the source-regex test in snapshot-runtime-execution.test.ts. The generic row keeps retirement, narrowing, and singular-execution claims; nothing snapshot-specific remains in scripts/layering/. Two @ts-expect-error proofs pin the seam: a literal is not an admission, and the binder refuses a bare plan.
1 parent 6a8beb6 commit 3d4afad

12 files changed

Lines changed: 128 additions & 218 deletions

scripts/layering/runtime-command-cutover-policy.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ test('the parametrized gate goes red on a planted row across every generalized c
2222
"import { resolvePlantedBackend } from './planted-legacy.ts';",
2323
"requireCommandSupported('planted', device);",
2424
'const widened = runtime as PlantedRuntimeOperations;',
25+
'const forged = { admitted: true, plan } as AdmittedRuntimePlan<PlantedPlan>;',
2526
"function handlePlantedCommand() { widened.operations['plantedDump']({}); }",
2627
].join('\n'),
2728
],
@@ -44,6 +45,9 @@ test('the parametrized gate goes red on a planted row across every generalized c
4445
'src/daemon/planted-handler.ts: legacy planted route resolvePlantedBackend',
4546
'src/daemon/planted-handler.ts: legacy planted capability admission requireCommandSupported',
4647
'src/daemon/planted-handler.ts: widened planted runtime type assertion',
48+
// The admission proof is shared across rows: a route that casts its way to an
49+
// AdmittedRuntimePlan has manufactured the facts-first admission the binder requires.
50+
'src/daemon/planted-handler.ts: widened planted runtime type assertion',
4751
'src/daemon/planted-handler.ts: bracketed planted operation access',
4852
'src/core/command-descriptor/registry.ts: planted descriptor retains legacy capability admission',
4953
'src/core/capabilities.ts: static platform command set retains planted admission',

scripts/layering/runtime-command-cutover-policy.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import { memberName, propertyName, visitAst, type ProductionSource } from './cut
1313

1414
type AstNode = Record<string, unknown>;
1515

16-
const SHARED_RUNTIME_TYPE_NAME = 'BoundDeviceRuntime';
16+
// Types no daemon route may manufacture with an assertion: the bound runtime itself, and the
17+
// admission proof a facts-first binder requires (`AdmittedRuntimePlan`, minted only by
18+
// `admitRuntimePlan`). Together with the row's own runtime type names they are the
19+
// manufactured-proof column — a cast to any of them is a route repairing missing proof.
20+
const SHARED_RUNTIME_TYPE_NAMES = ['BoundDeviceRuntime', 'AdmittedRuntimePlan'] as const;
1721

1822
/**
1923
* The one parametrized runtime-command-cutover gate (ADR 0019 §8). Every migrated
@@ -372,7 +376,7 @@ function narrowingViolations(
372376
// An inventory row binds no device runtime, so it has nothing to re-widen.
373377
if (!file.path.startsWith('src/daemon/') || row.execution !== 'device-runtime') return [];
374378
const violations: UnruledViolation[] = [];
375-
const runtimeTypes = new Set([SHARED_RUNTIME_TYPE_NAME, ...row.runtimeTypeNames]);
379+
const runtimeTypes = new Set([...SHARED_RUNTIME_TYPE_NAMES, ...row.runtimeTypeNames]);
376380
visitAst(program, (node) => {
377381
if (
378382
(node['type'] === 'TSAsExpression' || node['type'] === 'TSTypeAssertion') &&

scripts/layering/runtime-command-cutover-snapshot.test.ts

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

scripts/layering/runtime-command-cutover-snapshot.ts

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

scripts/layering/runtime-command-cutover-table.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,7 @@ test('R32 snapshot rejects legacy admission and dispatcher projection', () => {
7070
}
7171
function dispatchSnapshotViaRuntime() {
7272
const plan = resolveSnapshotRuntimePlan(normalizedIntent);
73-
inspectRequiredRuntimeUse({
74-
device,
75-
use: plan.use,
76-
inspectFacts: params.inspectFacts,
77-
});
73+
admitRuntimePlan({ device, plan, inspectFacts: params.inspectFacts });
7874
requireCommandSupported('snapshot', device);
7975
}
8076
function handleSnapshotCommands() {

scripts/layering/runtime-command-cutover-table.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
runtimeLifecycleRouteBindingViolations,
1111
sourceExecutedUsingDeclarationViolations,
1212
} from './runtime-command-cutover-extensions.ts';
13-
import { snapshotPlatformPolicyBranchViolations } from './runtime-command-cutover-snapshot.ts';
1413
import { recordRuntimeDaemonMechanicsViolations } from './record-runtime-mechanics-policy.ts';
1514
import { retiredDispatchProjectionViolations } from './runtime-command-cutover-descriptor.ts';
1615

@@ -470,7 +469,7 @@ export const MIGRATED_COMMAND_CUTOVERS: readonly MigratedCommandCutover[] = [
470469
captureSnapshotWithoutActiveApp: ['selectSnapshotWithoutActiveApp'],
471470
},
472471
},
473-
extensions: [snapshotRetiredDispatchProjectionProof, snapshotPlatformPolicyBranchViolations],
472+
extensions: [snapshotRetiredDispatchProjectionProof],
474473
},
475474
];
476475

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
1-
import { readFileSync } from 'node:fs';
21
import { snapshotRuntimePlanUses } from '@agent-device/contracts/platform';
32
import { expect, test } from 'vitest';
43
import { commandDescriptors } from '../registry.ts';
54

6-
const snapshotRuntimeSource = readFileSync(
7-
new URL('../../../daemon/snapshot-runtime.ts', import.meta.url),
8-
'utf8',
9-
);
10-
const snapshotRuntimeBindingSource = readFileSync(
11-
new URL('../../../daemon/snapshot-runtime-binding.ts', import.meta.url),
12-
'utf8',
13-
);
14-
155
test('snapshot descriptor declares its complete planned capture uses with no legacy projection', () => {
166
const snapshot = commandDescriptors.find(({ name }) => name === 'snapshot');
177

@@ -28,27 +18,3 @@ test('snapshot descriptor declares its complete planned capture uses with no leg
2818
['captureSnapshot', 'captureSnapshotWithCustomActions', 'captureSnapshotWithoutActiveApp'],
2919
]);
3020
});
31-
32-
test('snapshot public route inspects facts and binds the declared use exactly once', () => {
33-
const publicRoute = snapshotRuntimeSource.slice(
34-
snapshotRuntimeSource.indexOf('export async function dispatchSnapshotViaRuntime'),
35-
snapshotRuntimeSource.indexOf('function publishedSnapshotGeneration'),
36-
);
37-
38-
expect(publicRoute.match(/resolveSnapshotRuntimePlan\(\{/g)).toHaveLength(1);
39-
expect(publicRoute.match(/inspectRequiredRuntimeUse\(\{/g)).toHaveLength(1);
40-
expect(
41-
publicRoute.match(/bindSnapshotCaptureRuntime\(params\.bindDevice, device, plan\)/g),
42-
).toHaveLength(1);
43-
expect(
44-
publicRoute.match(/use: plan\.use,[\s\S]*inspectFacts: params\.inspectFacts/g),
45-
).toHaveLength(1);
46-
expect(
47-
snapshotRuntimeBindingSource.match(/const bind = requireRuntimeBinding\(bindDevice\)/g),
48-
).toHaveLength(1);
49-
expect(publicRoute.match(/runtime\.captureSnapshot\(/g)).toHaveLength(1);
50-
expect(publicRoute).not.toContain("requireCommandSupported('snapshot'");
51-
for (const policyName of ['isIosFamily', 'isIosSimulator', 'providerOwned']) {
52-
expect(snapshotRuntimeBindingSource).not.toContain(policyName);
53-
}
54-
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { resolveSnapshotRuntimePlan } from '@agent-device/contracts/platform';
2+
import { expect, test } from 'vitest';
3+
import { IOS_SIMULATOR } from '../../__tests__/test-utils/index.ts';
4+
import { admitRuntimePlan } from '../handlers/session-runtime-admission.ts';
5+
import { bindSnapshotCaptureRuntime } from '../snapshot-runtime-binding.ts';
6+
import { snapshotRuntimeFixture } from './snapshot-runtime-fixture.ts';
7+
8+
test('the snapshot binder takes an admission, never a bare plan', () => {
9+
const plan = resolveSnapshotRuntimePlan({ customActions: false, hasActiveApp: true });
10+
// Type-level proof that admit-before-bind is enforced at the seam: the binder's parameter is
11+
// AdmittedRuntimePlan<SnapshotRuntimePlan>, which only admitRuntimePlan produces. Widening it
12+
// back to SnapshotRuntimePlan makes this directive unused and tsc fails.
13+
// @ts-expect-error the facts-first binder requires the admission token, not the plan
14+
const bindBarePlan = () => bindSnapshotCaptureRuntime(undefined, IOS_SIMULATOR, plan);
15+
expect(bindBarePlan).toBeTypeOf('function');
16+
});
17+
18+
test('binds the operation the admitted plan selected and nothing wider', async () => {
19+
const { inspectFacts, bindDevice } = snapshotRuntimeFixture();
20+
const plan = resolveSnapshotRuntimePlan({ customActions: false, hasActiveApp: true });
21+
const admission = await admitRuntimePlan({ device: IOS_SIMULATOR, plan, inspectFacts });
22+
if (!admission.admitted) throw new Error('fixture admits captureSnapshot on an iOS simulator');
23+
const runtime = await bindSnapshotCaptureRuntime(bindDevice, IOS_SIMULATOR, admission);
24+
expect(Object.keys(runtime)).toEqual(['captureSnapshot']);
25+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { resolveSnapshotRuntimePlan } from '@agent-device/contracts/platform';
2+
import { expect, test } from 'vitest';
3+
import { ANDROID_EMULATOR, IOS_SIMULATOR } from '../../../__tests__/test-utils/index.ts';
4+
import { snapshotRuntimeFixture } from '../../__tests__/snapshot-runtime-fixture.ts';
5+
import { admitRuntimePlan, type AdmittedRuntimePlan } from '../session-runtime-admission.ts';
6+
7+
const { inspectFacts } = snapshotRuntimeFixture();
8+
9+
test('admits the plan whose required operations the owner facts report available', async () => {
10+
const plan = resolveSnapshotRuntimePlan({ customActions: false, hasActiveApp: true });
11+
const admission = await admitRuntimePlan({ device: IOS_SIMULATOR, plan, inspectFacts });
12+
expect(admission.admitted).toBe(true);
13+
if (!admission.admitted) throw new Error('unreachable');
14+
// The token carries the very plan that was admitted, so a binder that takes the token
15+
// binds exactly the use the facts were checked against.
16+
expect(admission.plan).toBe(plan);
17+
});
18+
19+
test('refuses on the first required operation the owner facts report unavailable', async () => {
20+
const plan = resolveSnapshotRuntimePlan({ customActions: true, hasActiveApp: true });
21+
const admission = await admitRuntimePlan({ device: ANDROID_EMULATOR, plan, inspectFacts });
22+
expect(admission).toMatchObject({
23+
admitted: false,
24+
operation: 'captureSnapshotWithCustomActions',
25+
fact: { available: false, reason: 'unsupported-platform-leaf' },
26+
});
27+
});
28+
29+
test('throws when no facts inspection seam was supplied', async () => {
30+
const plan = resolveSnapshotRuntimePlan({ customActions: false, hasActiveApp: true });
31+
await expect(admitRuntimePlan({ device: IOS_SIMULATOR, plan })).rejects.toThrow(
32+
'Device runtime facts inspection is unavailable.',
33+
);
34+
});
35+
36+
test('the admission proof cannot be written down: only admitRuntimePlan mints it', () => {
37+
const plan = resolveSnapshotRuntimePlan({ customActions: false, hasActiveApp: true });
38+
// A planted red for the seam itself: delete the proof key from AdmittedRuntimePlan and this
39+
// directive becomes unused, which tsc reports. The key is a module-private symbol, so no
40+
// literal in any other module can satisfy the type — a route holds a token only by having
41+
// called admitRuntimePlan (or by a type assertion, which the cutover gate rejects in src/daemon/).
42+
// @ts-expect-error a literal without the module-private proof key is not an admission
43+
const forged: AdmittedRuntimePlan<typeof plan> = { admitted: true, plan };
44+
expect(forged.plan).toBe(plan);
45+
});

0 commit comments

Comments
 (0)