Skip to content

Commit ddc103e

Browse files
committed
refactor(daemon): admit-before-bind as an identity-keyed admitted-plan token; retire the R32 syntax policy
admitRuntimePlan (was inspectRequiredRuntimeUse) takes the plan and, on success, mints an AdmittedRuntimePlan: a nominal class instance with nothing readable on it. Its payload — a frozen copy of the device the facts were read for, and the plan — lives in a module-private WeakMap keyed by the token's exact identity, and the only way to read it is unwrapAdmittedRuntimePlan, which refuses anything not minted here. The snapshot owning interface (resolveBoundSnapshotCaptureRuntime, #1847) admits through it and its private binder takes only the token: no bare plan, no separate device, and no look-alike — a spread lacks the #private member (not assignable), a Proxy around a real token types as the token but is a different identity (refused at unwrap), Object.assign/defineProperty throw on the frozen instance, and the class value is not exported so its constructor is not nameable. That retires scripts/layering/runtime-command-cutover-snapshot.ts — R32's per-command AST policy (call-shape recognition of the admission and a text sniff for a local admission) — and the source-regex test beside the descriptor tests. The generic row keeps retirement, narrowing, and singular execution; the manufactured-proof column now also rejects casts to AdmittedRuntimePlan. Planted reds: token degraded to a plain public shape → 2 unused @ts-expect-error directives; unwrap reading the token surface via getters → the Proxy regression fails; getter-based branded literal → the runtime retarget test fails.
1 parent b12a3e3 commit ddc103e

11 files changed

Lines changed: 256 additions & 298 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 & 80 deletions
This file was deleted.

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

Lines changed: 0 additions & 134 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
@@ -91,11 +91,7 @@ test('R32 snapshot rejects legacy admission and dispatcher projection', () => {
9191
}
9292
function resolveBoundSnapshotCaptureRuntime(params) {
9393
const plan = resolveSnapshotRuntimePlan(normalizedIntent);
94-
inspectRequiredRuntimeUse({
95-
device,
96-
use: plan.use,
97-
inspectFacts: params.inspectFacts,
98-
});
94+
admitRuntimePlan({ device, plan, inspectFacts: params.inspectFacts });
9995
return bindSnapshotCaptureRuntime(params.bindDevice, device, plan);
10096
}
10197
`,

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

@@ -471,7 +470,7 @@ export const MIGRATED_COMMAND_CUTOVERS: readonly MigratedCommandCutover[] = [
471470
captureSnapshotWithoutActiveApp: ['selectSnapshotWithoutActiveApp'],
472471
},
473472
},
474-
extensions: [snapshotRetiredDispatchProjectionProof, snapshotPlatformPolicyBranchViolations],
473+
extensions: [snapshotRetiredDispatchProjectionProof],
475474
},
476475
{
477476
rule: 'R33 diff-runtime-cutover',
Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +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-
const snapshotRuntimeCommandSource = readFileSync(
15-
new URL('../../../daemon/snapshot-command-runtime.ts', import.meta.url),
16-
'utf8',
17-
);
18-
195
test('snapshot descriptor declares its complete planned capture uses with no legacy projection', () => {
206
const snapshot = commandDescriptors.find(({ name }) => name === 'snapshot');
217

@@ -43,39 +29,3 @@ test('diff descriptor reuses the complete snapshot plan uses with no legacy proj
4329
uses: snapshotRuntimePlanUses,
4430
});
4531
});
46-
47-
test('shared snapshot owning interface inspects facts and binds the declared use exactly once', () => {
48-
const publicRoute = snapshotRuntimeSource.slice(
49-
snapshotRuntimeSource.indexOf('export async function dispatchSnapshotViaRuntime'),
50-
snapshotRuntimeSource.indexOf('function publishedSnapshotGeneration'),
51-
);
52-
const owningInterface = snapshotRuntimeBindingSource.slice(
53-
snapshotRuntimeBindingSource.indexOf(
54-
'export async function resolveBoundSnapshotCaptureRuntime',
55-
),
56-
snapshotRuntimeBindingSource.indexOf('async function bindSnapshotCaptureRuntime'),
57-
);
58-
59-
expect(publicRoute).toContain('dispatchSnapshotRuntimeCommand({');
60-
expect(
61-
snapshotRuntimeCommandSource.match(
62-
/resolveBoundSnapshotCaptureRuntime\(params, params\.command\)/g,
63-
),
64-
).toHaveLength(1);
65-
expect(owningInterface.match(/resolveSnapshotRuntimePlan\(\{/g)).toHaveLength(1);
66-
expect(owningInterface.match(/inspectRequiredRuntimeUse\(\{/g)).toHaveLength(1);
67-
expect(
68-
owningInterface.match(/bindSnapshotCaptureRuntime\(params\.bindDevice, device, plan\)/g),
69-
).toHaveLength(1);
70-
expect(
71-
owningInterface.match(/use: plan\.use,[\s\S]*inspectFacts: params\.inspectFacts/g),
72-
).toHaveLength(1);
73-
expect(
74-
snapshotRuntimeBindingSource.match(/const bind = requireRuntimeBinding\(bindDevice\)/g),
75-
).toHaveLength(1);
76-
expect(owningInterface.match(/runtime\.captureSnapshot\(/g)).toHaveLength(1);
77-
expect(publicRoute).not.toContain("requireCommandSupported('snapshot'");
78-
for (const policyName of ['isIosFamily', 'isIosSimulator', 'providerOwned']) {
79-
expect(snapshotRuntimeBindingSource).not.toContain(policyName);
80-
}
81-
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { DeviceInfo } from '@agent-device/kernel/device';
2+
import { expect, test } from 'vitest';
3+
import {
4+
IOS_SIMULATOR,
5+
makeIosSession,
6+
makeSessionStore,
7+
} from '../../__tests__/test-utils/index.ts';
8+
import type { BindDeviceRuntime } from '../request-runtime-binding.ts';
9+
import { resolveBoundSnapshotCaptureRuntime } from '../snapshot-runtime-binding.ts';
10+
import type { DaemonRequest } from '../types.ts';
11+
import { snapshotRuntimeFixture } from './snapshot-runtime-fixture.ts';
12+
13+
// The owning interface (ADR 0019 §9): one plan, one facts-first admission, one bind — and the
14+
// bind target is whatever the admission was minted for, read back by token identity. The binder
15+
// itself is module-private; this is its only entry.
16+
test('the owning interface binds exactly the session device the facts were admitted for', async () => {
17+
const { inspectFacts, bindDevice } = snapshotRuntimeFixture();
18+
const boundDevices: DeviceInfo[] = [];
19+
const recordingBind: BindDeviceRuntime = async (device, use) => {
20+
boundDevices.push(device);
21+
return await bindDevice(device, use);
22+
};
23+
const sessionStore = makeSessionStore();
24+
// An active app: the fixture admits captureSnapshot for a local iOS simulator only with one.
25+
sessionStore.set(
26+
'bind-test',
27+
makeIosSession('bind-test', { device: IOS_SIMULATOR, appBundleId: 'com.example.app' }),
28+
);
29+
const req: DaemonRequest = {
30+
command: 'snapshot',
31+
positionals: [],
32+
session: 'bind-test',
33+
token: 'test-token',
34+
};
35+
36+
const resolved = await resolveBoundSnapshotCaptureRuntime(
37+
{
38+
req,
39+
sessionName: 'bind-test',
40+
logPath: '/tmp/bind-test.log',
41+
sessionStore,
42+
inspectFacts,
43+
bindDevice: recordingBind,
44+
},
45+
'snapshot',
46+
);
47+
48+
expect(resolved.ok).toBe(true);
49+
expect(boundDevices).toEqual([IOS_SIMULATOR]);
50+
});

0 commit comments

Comments
 (0)