agentic-workflow-manager 9.8.0 → 9.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/commands/job/gate.js +18 -0
- package/dist/src/commands/job/index.js +61 -0
- package/dist/src/commands/model-policy/index.js +94 -0
- package/dist/src/commands/plan/index.js +59 -2
- package/dist/src/commands/watch/apply.js +32 -0
- package/dist/src/core/admission/index.js +52 -2
- package/dist/src/core/admission/registry-contracts.js +1 -1
- package/dist/src/core/journal/requests.js +1 -1
- package/dist/src/core/journal/types.js +22 -1
- package/dist/src/core/model-policy/canonical.js +18 -0
- package/dist/src/core/model-policy/capabilities.js +197 -0
- package/dist/src/core/model-policy/capability-types.js +2 -0
- package/dist/src/core/model-policy/digest.js +35 -0
- package/dist/src/core/model-policy/journal.js +124 -0
- package/dist/src/core/model-policy/paths.js +12 -0
- package/dist/src/core/model-policy/resolve.js +155 -0
- package/dist/src/core/model-policy/store.js +189 -0
- package/dist/src/core/model-policy/types.js +2 -0
- package/dist/src/core/model-policy/validate.js +112 -0
- package/dist/src/core/plan/identity.js +4 -2
- package/dist/src/core/plan/validate.js +15 -16
- package/dist/src/index.js +2 -0
- package/dist/tests/commands/job/gate-reconcile.test.js +16 -0
- package/dist/tests/commands/job/gate.test.js +9 -0
- package/dist/tests/commands/job/routing-report-cli.test.js +46 -0
- package/dist/tests/commands/job/routing.test.js +11 -0
- package/dist/tests/commands/model-policy/index.test.js +117 -0
- package/dist/tests/commands/plan/index.test.js +52 -0
- package/dist/tests/commands/watch/apply.test.js +12 -0
- package/dist/tests/core/admission/index.test.js +53 -0
- package/dist/tests/core/model-policy/capabilities.test.js +365 -0
- package/dist/tests/core/model-policy/digest.test.js +117 -0
- package/dist/tests/core/model-policy/journal.test.js +46 -0
- package/dist/tests/core/model-policy/policy.test.js +47 -0
- package/dist/tests/core/model-policy/resolve.test.js +185 -0
- package/dist/tests/core/model-policy/store.test.js +177 -0
- package/dist/tests/core/plan/validate.test.js +40 -2
- package/dist/tests/core/secure-fs/native-bridge.test.js +81 -4
- package/dist/tests/structural/directory-fsync-is-single-source.test.js +97 -0
- package/dist/tests/structural/native-release-artifacts.test.js +14 -0
- package/package.json +3 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/linux-arm64/secure_fs.node +0 -0
- package/prebuilds/linux-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const capabilities_1 = require("../../../src/core/model-policy/capabilities");
|
|
11
|
+
const digest = 'a'.repeat(64);
|
|
12
|
+
const receipt = () => ({
|
|
13
|
+
schema: 'routing-capabilities/v1',
|
|
14
|
+
runtime: { target: 'codex', kind: 'native', version: '1.0.0', accountScopeDigest: digest },
|
|
15
|
+
recordedAt: '2026-09-17T00:00:00.000Z', expiresAt: '2026-09-18T00:00:00.000Z',
|
|
16
|
+
capabilities: { artifactDelivery: 'unverified', interactiveExecution: 'supported', unattendedController: 'supported', nativeSubagents: 'unverified', modelOverride: 'supported', effortOverride: 'supported', observedModelEvidence: 'supported', durableResume: 'unverified' },
|
|
17
|
+
availableSelections: [{ selector: { kind: 'model', id: 'full' }, effort: { kind: 'explicit', value: 'high' } }],
|
|
18
|
+
runtimeDefaultSelection: { selector: { kind: 'model', id: 'full' }, effort: { kind: 'explicit', value: 'high' } },
|
|
19
|
+
evidence: ['interactiveExecution', 'unattendedController', 'modelOverride', 'effortOverride', 'observedModelEvidence'].map(capability => ({ capability, kind: 'native-control', receiptDigest: digest })),
|
|
20
|
+
approval: { approvalId: 'approval-1', snapshotDigest: digest },
|
|
21
|
+
});
|
|
22
|
+
/** A child can retain its fixture script briefly after it reports an outcome on
|
|
23
|
+
* Windows. Retry only that documented transient after every child has exited. */
|
|
24
|
+
async function removeC2Fixture(target, attempts = 10, delayMs = 50, remove = (pathToRemove) => fs_1.default.rmSync(pathToRemove, { recursive: true, force: true }), isWindows = process.platform === 'win32') {
|
|
25
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
26
|
+
try {
|
|
27
|
+
remove(target);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (!isWindows || error.code !== 'EBUSY' || attempt === attempts - 1)
|
|
32
|
+
throw error;
|
|
33
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function startC2Competitor(root, candidate, expectedDigest, predecessor, awmHome) {
|
|
38
|
+
const module = path_1.default.resolve(__dirname, '../../../dist/src/core/model-policy/capabilities.js');
|
|
39
|
+
const worker = path_1.default.join(root, `c2-competitor-${path_1.default.basename(candidate)}.js`);
|
|
40
|
+
fs_1.default.writeFileSync(worker, `
|
|
41
|
+
const { approveCapabilities, capabilityReceiptDigest } = require(process.env.C2_CAPABILITIES_MODULE);
|
|
42
|
+
function report(message) {
|
|
43
|
+
if (typeof process.send !== 'function' || !process.connected) process.exit(1);
|
|
44
|
+
process.send(message, () => {
|
|
45
|
+
if (process.connected) process.disconnect();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
process.once('message', message => {
|
|
49
|
+
if (message !== 'go') process.exit(1);
|
|
50
|
+
try {
|
|
51
|
+
const receipt = approveCapabilities({ file: process.env.C2_CANDIDATE, cwd: process.env.C2_CWD, expectedDigest: process.env.C2_EXPECTED_DIGEST, replaceDigest: process.env.C2_PREDECESSOR, now: new Date(process.env.C2_NOW) });
|
|
52
|
+
report({ outcome: 'accepted', digest: capabilityReceiptDigest(receipt) });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
report({ outcome: 'rejected', reason: error instanceof Error ? error.message : String(error) });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
process.once('disconnect', () => process.exit(0));
|
|
58
|
+
process.send({ type: 'ready' });
|
|
59
|
+
`);
|
|
60
|
+
let readyResolve;
|
|
61
|
+
let readyReject;
|
|
62
|
+
let outcomeResolve;
|
|
63
|
+
let outcomeReject;
|
|
64
|
+
let readySettled = false;
|
|
65
|
+
let outcomeSettled = false;
|
|
66
|
+
let exitResolve;
|
|
67
|
+
const ready = new Promise((resolve, reject) => { readyResolve = resolve; readyReject = reject; });
|
|
68
|
+
const outcome = new Promise((resolve, reject) => { outcomeResolve = resolve; outcomeReject = reject; });
|
|
69
|
+
const exited = new Promise(resolve => { exitResolve = resolve; });
|
|
70
|
+
const child = (0, child_process_1.fork)(worker, [], { cwd: root, env: { ...process.env, AWM_HOME: awmHome, C2_CAPABILITIES_MODULE: module, C2_CANDIDATE: candidate, C2_CWD: root, C2_EXPECTED_DIGEST: expectedDigest, C2_PREDECESSOR: predecessor, C2_NOW: '2026-09-17T12:00:00.000Z' }, stdio: ['ignore', 'ignore', 'ignore', 'ipc'] });
|
|
71
|
+
const rejectPending = (error) => {
|
|
72
|
+
if (!readySettled) {
|
|
73
|
+
readySettled = true;
|
|
74
|
+
readyReject(error);
|
|
75
|
+
}
|
|
76
|
+
if (!outcomeSettled) {
|
|
77
|
+
outcomeSettled = true;
|
|
78
|
+
outcomeReject(error);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
child.once('error', rejectPending);
|
|
82
|
+
child.on('message', (message) => {
|
|
83
|
+
if (message && typeof message === 'object' && message.type === 'ready') {
|
|
84
|
+
if (!readySettled) {
|
|
85
|
+
readySettled = true;
|
|
86
|
+
readyResolve();
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const result = message;
|
|
91
|
+
if (!outcomeSettled && (result.outcome === 'accepted' || result.outcome === 'rejected')) {
|
|
92
|
+
outcomeSettled = true;
|
|
93
|
+
outcomeResolve(result);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
child.once('exit', code => { exitResolve(); rejectPending(new Error(`C2 competitor exited before reporting an outcome (${code})`)); });
|
|
97
|
+
// A failure before both outcomes are awaited must not leave a rejected
|
|
98
|
+
// promise unobserved while the test's finally block is closing the child.
|
|
99
|
+
void ready.catch(() => undefined);
|
|
100
|
+
void outcome.catch(() => undefined);
|
|
101
|
+
return {
|
|
102
|
+
ready,
|
|
103
|
+
release: () => {
|
|
104
|
+
if (!child.connected)
|
|
105
|
+
throw new Error('C2 competitor IPC closed before release');
|
|
106
|
+
child.send('go');
|
|
107
|
+
},
|
|
108
|
+
outcome,
|
|
109
|
+
close: async () => {
|
|
110
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
111
|
+
return exited;
|
|
112
|
+
if (child.connected) {
|
|
113
|
+
try {
|
|
114
|
+
child.disconnect();
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error.code !== 'ERR_IPC_DISCONNECTED')
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const timeout = setTimeout(() => {
|
|
122
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
123
|
+
child.kill();
|
|
124
|
+
}, 5000);
|
|
125
|
+
try {
|
|
126
|
+
await exited;
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
clearTimeout(timeout);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
describe('capability receipt validation', () => {
|
|
135
|
+
it('accepts an exhaustive explicit native attestation', () => {
|
|
136
|
+
expect((0, capabilities_1.validateCapabilityReceipt)(receipt())).toMatchObject({ runtime: { target: 'codex', kind: 'native' } });
|
|
137
|
+
});
|
|
138
|
+
it.each([
|
|
139
|
+
['renderer-only supported evidence', () => { const value = receipt(); value.evidence[0].kind = 'unsupported'; return value; }],
|
|
140
|
+
['expiry greater than 24 hours', () => { const value = receipt(); value.expiresAt = '2026-09-18T00:00:00.001Z'; return value; }],
|
|
141
|
+
['missing capability field', () => { const value = receipt(); delete value.capabilities.effortOverride; return value; }],
|
|
142
|
+
])('rejects %s', (_name, make) => expect(() => (0, capabilities_1.validateCapabilityReceipt)(make())).toThrow());
|
|
143
|
+
it.each(['September 17, 2026 00:00:00 UTC', '2026-09-17', '2026-09-17T00:00:00.000+00:00', '2026-09-17T00:00:00.000Zx', '2026-02-30T00:00:00.000Z', '2026-09-17T00:00:00Z'])('rejects a noncanonical receipt timestamp: %s', timestamp => {
|
|
144
|
+
const recorded = receipt();
|
|
145
|
+
recorded.recordedAt = timestamp;
|
|
146
|
+
const expires = receipt();
|
|
147
|
+
expires.expiresAt = timestamp;
|
|
148
|
+
expect(() => (0, capabilities_1.validateCapabilityReceipt)(recorded)).toThrow();
|
|
149
|
+
expect(() => (0, capabilities_1.validateCapabilityReceipt)(expires)).toThrow();
|
|
150
|
+
});
|
|
151
|
+
it.each(['renderer', 'documentation'])('does not certify supported execution from %s evidence', kind => {
|
|
152
|
+
const value = receipt();
|
|
153
|
+
value.evidence = value.evidence.map(entry => ({ ...entry, kind: kind }));
|
|
154
|
+
expect(() => (0, capabilities_1.validateCapabilityReceipt)(value)).toThrow(/evidence|native/i);
|
|
155
|
+
});
|
|
156
|
+
it('reads absent capability state without creating operator storage', () => {
|
|
157
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-receipt-'));
|
|
158
|
+
const prior = process.env.AWM_HOME;
|
|
159
|
+
process.env.AWM_HOME = path_1.default.join(root, 'operator');
|
|
160
|
+
try {
|
|
161
|
+
expect((0, capabilities_1.readCapabilities)(receipt().runtime, new Date('2026-09-17T12:00:00.000Z'))).toEqual({ state: 'absent' });
|
|
162
|
+
expect(fs_1.default.existsSync(process.env.AWM_HOME)).toBe(false);
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
if (prior === undefined)
|
|
166
|
+
delete process.env.AWM_HOME;
|
|
167
|
+
else
|
|
168
|
+
process.env.AWM_HOME = prior;
|
|
169
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
it('uses only a validated target and runtime kind in its persistence path', () => {
|
|
173
|
+
expect((0, capabilities_1.capabilityReceiptPath)(receipt().runtime)).toMatch(/routing-capabilities[\\/]codex[\\/]native\.json$/);
|
|
174
|
+
expect(() => (0, capabilities_1.capabilityReceiptPath)({ ...receipt().runtime, kind: '../native' })).toThrow();
|
|
175
|
+
});
|
|
176
|
+
it.each(['2026-09-15T23:59:59.999Z', '2026-09-18T00:00:00.001Z'])('rejects stale or future approval at the injected clock', recordedAt => {
|
|
177
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-receipt-'));
|
|
178
|
+
const candidate = path_1.default.join(root, 'receipt.json');
|
|
179
|
+
const value = receipt();
|
|
180
|
+
value.recordedAt = recordedAt;
|
|
181
|
+
value.expiresAt = recordedAt === '2026-09-15T23:59:59.999Z' ? '2026-09-16T23:59:59.999Z' : '2026-09-19T00:00:00.001Z';
|
|
182
|
+
fs_1.default.writeFileSync(candidate, JSON.stringify(value));
|
|
183
|
+
try {
|
|
184
|
+
expect(() => (0, capabilities_1.approveCapabilities)({ file: candidate, cwd: root, expectedDigest: (0, capabilities_1.capabilityReceiptDigest)(value), now: new Date('2026-09-17T00:00:00.000Z') })).toThrow(/future|stale/);
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
it('rejects an empty replace digest before reading or writing capability state', () => {
|
|
191
|
+
expect(() => (0, capabilities_1.approveCapabilities)({ file: 'missing.json', cwd: process.cwd(), expectedDigest: digest, replaceDigest: '' })).toThrow(/replaceDigest/);
|
|
192
|
+
});
|
|
193
|
+
it('rejects a symlinked candidate and a symlinked receipt destination', () => {
|
|
194
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-receipt-'));
|
|
195
|
+
const linked = path_1.default.join(root, 'linked.json');
|
|
196
|
+
const outside = path_1.default.join(root, 'outside.json');
|
|
197
|
+
fs_1.default.writeFileSync(outside, JSON.stringify(receipt()));
|
|
198
|
+
const before = fs_1.default.readFileSync(outside);
|
|
199
|
+
fs_1.default.symlinkSync(outside, linked);
|
|
200
|
+
try {
|
|
201
|
+
expect(() => (0, capabilities_1.approveCapabilities)({ file: linked, cwd: root, expectedDigest: digest, now: new Date('2026-09-17T00:00:00.000Z') })).toThrow(/symlink|unsafe/);
|
|
202
|
+
expect(fs_1.default.readFileSync(outside).equals(before)).toBe(true);
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
it.each(['destination', 'ancestor'])('rejects a symlinked %s without changing external bytes', kind => {
|
|
209
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-receipt-'));
|
|
210
|
+
const prior = process.env.AWM_HOME;
|
|
211
|
+
process.env.AWM_HOME = path_1.default.join(root, 'operator');
|
|
212
|
+
const external = path_1.default.join(root, 'external.json');
|
|
213
|
+
fs_1.default.writeFileSync(external, 'outside');
|
|
214
|
+
const runtime = receipt().runtime;
|
|
215
|
+
try {
|
|
216
|
+
const target = (0, capabilities_1.capabilityReceiptPath)(runtime);
|
|
217
|
+
const ancestor = path_1.default.dirname(path_1.default.dirname(target));
|
|
218
|
+
if (kind === 'destination') {
|
|
219
|
+
fs_1.default.mkdirSync(path_1.default.dirname(target), { recursive: true });
|
|
220
|
+
fs_1.default.symlinkSync(external, target);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
const externalTree = path_1.default.join(root, 'external-tree');
|
|
224
|
+
const sentinel = path_1.default.join(externalTree, 'codex', 'native.json');
|
|
225
|
+
fs_1.default.mkdirSync(path_1.default.dirname(sentinel), { recursive: true });
|
|
226
|
+
fs_1.default.writeFileSync(sentinel, 'sentinel');
|
|
227
|
+
fs_1.default.mkdirSync(path_1.default.dirname(ancestor), { recursive: true });
|
|
228
|
+
fs_1.default.symlinkSync(externalTree, ancestor);
|
|
229
|
+
}
|
|
230
|
+
expect((0, capabilities_1.readCapabilities)(runtime, new Date('2026-09-17T12:00:00.000Z')).state).toBe('invalid');
|
|
231
|
+
expect(fs_1.default.readFileSync(external, 'utf8')).toBe('outside');
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
if (prior === undefined)
|
|
235
|
+
delete process.env.AWM_HOME;
|
|
236
|
+
else
|
|
237
|
+
process.env.AWM_HOME = prior;
|
|
238
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
it('publishes with exact predecessor CAS and leaves the accepted C2 receipt intact', () => {
|
|
242
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-receipt-'));
|
|
243
|
+
const previous = process.env.AWM_HOME;
|
|
244
|
+
process.env.AWM_HOME = path_1.default.join(root, 'operator');
|
|
245
|
+
const file = path_1.default.join(root, 'receipt.json');
|
|
246
|
+
const now = new Date('2026-09-17T12:00:00.000Z');
|
|
247
|
+
const first = receipt();
|
|
248
|
+
fs_1.default.writeFileSync(file, JSON.stringify(first));
|
|
249
|
+
try {
|
|
250
|
+
const firstDigest = (0, capabilities_1.capabilityReceiptDigest)(first);
|
|
251
|
+
expect((0, capabilities_1.approveCapabilities)({ file, cwd: root, expectedDigest: firstDigest, now })).toEqual(first);
|
|
252
|
+
expect(() => (0, capabilities_1.approveCapabilities)({ file, cwd: root, expectedDigest: firstDigest, now })).toThrow(/predecessor|exists/i);
|
|
253
|
+
const second = receipt();
|
|
254
|
+
second.approval.approvalId = 'approval-2';
|
|
255
|
+
fs_1.default.writeFileSync(file, JSON.stringify(second));
|
|
256
|
+
const secondDigest = (0, capabilities_1.capabilityReceiptDigest)(second);
|
|
257
|
+
expect(() => (0, capabilities_1.approveCapabilities)({ file, cwd: root, expectedDigest: secondDigest, replaceDigest: 'f'.repeat(64), now })).toThrow(/predecessor/);
|
|
258
|
+
expect((0, capabilities_1.approveCapabilities)({ file, cwd: root, expectedDigest: secondDigest, replaceDigest: firstDigest, now })).toEqual(second);
|
|
259
|
+
expect((0, capabilities_1.readCapabilities)(second.runtime, now)).toMatchObject({ state: 'current', digest: secondDigest });
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
if (previous === undefined)
|
|
263
|
+
delete process.env.AWM_HOME;
|
|
264
|
+
else
|
|
265
|
+
process.env.AWM_HOME = previous;
|
|
266
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
it('retries only bounded Windows EBUSY cleanup after a C2 child exits', async () => {
|
|
270
|
+
const calls = [];
|
|
271
|
+
await removeC2Fixture('fixture', 3, 0, () => {
|
|
272
|
+
calls.push('remove');
|
|
273
|
+
if (calls.length < 3)
|
|
274
|
+
throw Object.assign(new Error('busy'), { code: 'EBUSY' });
|
|
275
|
+
}, true);
|
|
276
|
+
expect(calls).toHaveLength(3);
|
|
277
|
+
await expect(removeC2Fixture('fixture', 3, 0, () => {
|
|
278
|
+
calls.push('non-busy');
|
|
279
|
+
throw Object.assign(new Error('denied'), { code: 'EPERM' });
|
|
280
|
+
}, true)).rejects.toThrow('denied');
|
|
281
|
+
expect(calls.filter(call => call === 'non-busy')).toHaveLength(1);
|
|
282
|
+
});
|
|
283
|
+
it('accepts exactly one process-raced C2 replacement from the same observed predecessor', async () => {
|
|
284
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-receipt-'));
|
|
285
|
+
const prior = process.env.AWM_HOME;
|
|
286
|
+
const now = new Date('2026-09-17T12:00:00.000Z');
|
|
287
|
+
const file = path_1.default.join(root, 'receipt.json');
|
|
288
|
+
const base = receipt();
|
|
289
|
+
let leftProcess;
|
|
290
|
+
let rightProcess;
|
|
291
|
+
process.env.AWM_HOME = path_1.default.join(root, 'operator');
|
|
292
|
+
fs_1.default.writeFileSync(file, JSON.stringify(base));
|
|
293
|
+
try {
|
|
294
|
+
const predecessor = (0, capabilities_1.capabilityReceiptDigest)(base);
|
|
295
|
+
(0, capabilities_1.approveCapabilities)({ file, cwd: root, expectedDigest: predecessor, now });
|
|
296
|
+
const candidate = (id) => {
|
|
297
|
+
const value = receipt();
|
|
298
|
+
value.approval.approvalId = id;
|
|
299
|
+
const candidateFile = path_1.default.join(root, `${id}.json`);
|
|
300
|
+
fs_1.default.writeFileSync(candidateFile, JSON.stringify(value), { mode: 0o400 });
|
|
301
|
+
return { file: candidateFile, digest: (0, capabilities_1.capabilityReceiptDigest)(value) };
|
|
302
|
+
};
|
|
303
|
+
const left = candidate('left');
|
|
304
|
+
const right = candidate('right');
|
|
305
|
+
leftProcess = startC2Competitor(root, left.file, left.digest, predecessor, process.env.AWM_HOME);
|
|
306
|
+
rightProcess = startC2Competitor(root, right.file, right.digest, predecessor, process.env.AWM_HOME);
|
|
307
|
+
await Promise.all([leftProcess.ready, rightProcess.ready]);
|
|
308
|
+
leftProcess.release();
|
|
309
|
+
rightProcess.release();
|
|
310
|
+
const outcomes = await Promise.all([leftProcess.outcome, rightProcess.outcome]);
|
|
311
|
+
const accepted = outcomes.filter((outcome) => outcome.outcome === 'accepted');
|
|
312
|
+
const rejected = outcomes.filter((outcome) => outcome.outcome === 'rejected');
|
|
313
|
+
expect(accepted).toHaveLength(1);
|
|
314
|
+
expect(rejected).toHaveLength(1);
|
|
315
|
+
expect(rejected[0].reason).toMatch(/predecessor|lease|exist/i);
|
|
316
|
+
const persisted = (0, capabilities_1.readCapabilities)(base.runtime, now);
|
|
317
|
+
expect(persisted).toMatchObject({ state: 'current', digest: accepted[0].digest });
|
|
318
|
+
expect([left.digest, right.digest]).toContain(accepted[0].digest);
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
await Promise.all([leftProcess?.close(), rightProcess?.close()].filter((value) => value !== undefined));
|
|
322
|
+
if (prior === undefined)
|
|
323
|
+
delete process.env.AWM_HOME;
|
|
324
|
+
else
|
|
325
|
+
process.env.AWM_HOME = prior;
|
|
326
|
+
await removeC2Fixture(root);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
it.each(['destination', 'ancestor'])('writer rejects symlinked %s and preserves external bytes', kind => {
|
|
330
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-receipt-'));
|
|
331
|
+
const prior = process.env.AWM_HOME;
|
|
332
|
+
process.env.AWM_HOME = path_1.default.join(root, 'operator');
|
|
333
|
+
const now = new Date('2026-09-17T12:00:00.000Z');
|
|
334
|
+
const value = receipt();
|
|
335
|
+
const file = path_1.default.join(root, 'candidate.json');
|
|
336
|
+
fs_1.default.writeFileSync(file, JSON.stringify(value));
|
|
337
|
+
const outside = path_1.default.join(root, 'outside');
|
|
338
|
+
fs_1.default.writeFileSync(outside, 'outside');
|
|
339
|
+
const target = (0, capabilities_1.capabilityReceiptPath)(value.runtime);
|
|
340
|
+
const ancestor = path_1.default.dirname(path_1.default.dirname(target));
|
|
341
|
+
try {
|
|
342
|
+
if (kind === 'destination') {
|
|
343
|
+
fs_1.default.mkdirSync(path_1.default.dirname(target), { recursive: true });
|
|
344
|
+
fs_1.default.symlinkSync(outside, target);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
const externalTree = path_1.default.join(root, 'external-tree');
|
|
348
|
+
const sentinel = path_1.default.join(externalTree, 'codex', 'native.json');
|
|
349
|
+
fs_1.default.mkdirSync(path_1.default.dirname(sentinel), { recursive: true });
|
|
350
|
+
fs_1.default.writeFileSync(sentinel, 'sentinel');
|
|
351
|
+
fs_1.default.mkdirSync(path_1.default.dirname(ancestor), { recursive: true });
|
|
352
|
+
fs_1.default.symlinkSync(externalTree, ancestor);
|
|
353
|
+
}
|
|
354
|
+
expect(() => (0, capabilities_1.approveCapabilities)({ file, cwd: root, expectedDigest: (0, capabilities_1.capabilityReceiptDigest)(value), now })).toThrow(/unsafe|symlink|lease/);
|
|
355
|
+
expect(fs_1.default.readFileSync(outside, 'utf8')).toBe('outside');
|
|
356
|
+
}
|
|
357
|
+
finally {
|
|
358
|
+
if (prior === undefined)
|
|
359
|
+
delete process.env.AWM_HOME;
|
|
360
|
+
else
|
|
361
|
+
process.env.AWM_HOME = prior;
|
|
362
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const digest_1 = require("../../../src/core/model-policy/digest");
|
|
10
|
+
const store_1 = require("../../../src/core/model-policy/store");
|
|
11
|
+
const capabilities_1 = require("../../../src/core/model-policy/capabilities");
|
|
12
|
+
const canonical_1 = require("../../../src/core/model-policy/canonical");
|
|
13
|
+
const capabilities_2 = require("../../../src/core/model-policy/capabilities");
|
|
14
|
+
const sha = 'a'.repeat(64);
|
|
15
|
+
const policy = () => ({ schema: 'model-policy/v1', mappings: [{ target: 'codex', runtimeKind: 'native', profiles: {
|
|
16
|
+
mechanical: { selector: { kind: 'model', id: 'gpt-5.6-luna' }, effort: { kind: 'explicit', value: 'medium' } },
|
|
17
|
+
integration: { selector: { kind: 'model', id: 'gpt-5.6-terra' }, effort: { kind: 'explicit', value: 'medium' } },
|
|
18
|
+
judgment: { selector: { kind: 'model', id: 'gpt-5.6-sol' }, effort: { kind: 'explicit', value: 'medium' } },
|
|
19
|
+
}, fullCapability: { selector: { kind: 'model', id: 'gpt-5.6-sol' }, effort: { kind: 'explicit', value: 'high' } }, degradation: { allowMissingModelOverride: false, allowMissingEffortOverride: false, allowMissingObservedIdentity: false } }], implementationBudget: { maxAttempts: 3, escalation: ['mechanical', 'integration', 'judgment'], judgmentEfforts: ['medium', 'high'] } });
|
|
20
|
+
const receipt = () => ({
|
|
21
|
+
schema: 'routing-capabilities/v1',
|
|
22
|
+
runtime: { target: 'codex', kind: 'native', version: '1.0.0', accountScopeDigest: sha },
|
|
23
|
+
recordedAt: '2026-09-17T00:00:00.000Z', expiresAt: '2026-09-18T00:00:00.000Z',
|
|
24
|
+
capabilities: { artifactDelivery: 'unverified', interactiveExecution: 'supported', unattendedController: 'supported', nativeSubagents: 'unverified', modelOverride: 'supported', effortOverride: 'supported', observedModelEvidence: 'supported', durableResume: 'unverified' },
|
|
25
|
+
availableSelections: [{ selector: { kind: 'model', id: 'full' }, effort: { kind: 'explicit', value: 'high' } }],
|
|
26
|
+
runtimeDefaultSelection: { selector: { kind: 'model', id: 'full' }, effort: { kind: 'explicit', value: 'high' } },
|
|
27
|
+
evidence: ['interactiveExecution', 'unattendedController', 'modelOverride', 'effortOverride', 'observedModelEvidence'].map(capability => ({ capability, kind: 'native-control', receiptDigest: sha })),
|
|
28
|
+
approval: { approvalId: 'approval-1', snapshotDigest: sha },
|
|
29
|
+
});
|
|
30
|
+
describe('candidateDigest', () => {
|
|
31
|
+
let cwd;
|
|
32
|
+
const home = process.env.HOME;
|
|
33
|
+
const awmHome = process.env.AWM_HOME;
|
|
34
|
+
beforeEach(() => { cwd = fs_1.default.mkdtempSync(path_1.default.join(fs_1.default.realpathSync(os_1.default.tmpdir()), 'awm-digest-')); process.env.HOME = cwd; process.env.AWM_HOME = path_1.default.join(cwd, '.awm'); });
|
|
35
|
+
afterEach(() => { fs_1.default.rmSync(cwd, { recursive: true, force: true }); if (home === undefined)
|
|
36
|
+
delete process.env.HOME;
|
|
37
|
+
else
|
|
38
|
+
process.env.HOME = home; if (awmHome === undefined)
|
|
39
|
+
delete process.env.AWM_HOME;
|
|
40
|
+
else
|
|
41
|
+
process.env.AWM_HOME = awmHome; });
|
|
42
|
+
const write = (name, value) => { const file = path_1.default.join(cwd, name); fs_1.default.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`); return name; };
|
|
43
|
+
it('emits the digest the policy approval path will demand, not a second opinion', () => {
|
|
44
|
+
const file = write('policy.json', policy());
|
|
45
|
+
expect((0, digest_1.candidateDigest)({ file, cwd })).toEqual({ schema: 'model-policy/v1', digest: (0, canonical_1.canonicalPolicyDigest)(policy()) });
|
|
46
|
+
});
|
|
47
|
+
it('emits the digest the capability approval path will demand', () => {
|
|
48
|
+
const file = write('receipt.json', receipt());
|
|
49
|
+
expect((0, digest_1.candidateDigest)({ file, cwd })).toEqual({ schema: 'routing-capabilities/v1', digest: (0, capabilities_2.capabilityReceiptDigest)(receipt()) });
|
|
50
|
+
});
|
|
51
|
+
it('is insensitive to key order and whitespace, because the digest is over the canonical form', () => {
|
|
52
|
+
const content = policy();
|
|
53
|
+
const reordered = { implementationBudget: content.implementationBudget, mappings: content.mappings, schema: content.schema };
|
|
54
|
+
fs_1.default.writeFileSync(path_1.default.join(cwd, 'a.json'), JSON.stringify(content));
|
|
55
|
+
fs_1.default.writeFileSync(path_1.default.join(cwd, 'b.json'), JSON.stringify(reordered, null, 4));
|
|
56
|
+
expect((0, digest_1.candidateDigest)({ file: 'a.json', cwd }).digest).toBe((0, digest_1.candidateDigest)({ file: 'b.json', cwd }).digest);
|
|
57
|
+
});
|
|
58
|
+
it('refuses a document whose declared schema it does not own, instead of guessing', () => {
|
|
59
|
+
const file = write('other.json', { schema: 'approved-model-policy/v1' });
|
|
60
|
+
expect(() => (0, digest_1.candidateDigest)({ file, cwd })).toThrow(/schema model-policy\/v1 or routing-capabilities\/v1/);
|
|
61
|
+
});
|
|
62
|
+
it('refuses an invalid document rather than emitting a digest approval would reject', () => {
|
|
63
|
+
const broken = policy();
|
|
64
|
+
delete broken.implementationBudget;
|
|
65
|
+
const file = write('broken.json', broken);
|
|
66
|
+
expect(() => (0, digest_1.candidateDigest)({ file, cwd })).toThrow();
|
|
67
|
+
});
|
|
68
|
+
it('reports an absent candidate loudly', () => {
|
|
69
|
+
expect(() => (0, digest_1.candidateDigest)({ file: 'missing.json', cwd })).toThrow(/absent/);
|
|
70
|
+
});
|
|
71
|
+
it('refuses a symlinked candidate, matching the approval path', () => {
|
|
72
|
+
write('policy.json', policy());
|
|
73
|
+
fs_1.default.symlinkSync(path_1.default.join(cwd, 'policy.json'), path_1.default.join(cwd, 'link.json'));
|
|
74
|
+
expect(() => (0, digest_1.candidateDigest)({ file: 'link.json', cwd })).toThrow(/unsafe/);
|
|
75
|
+
});
|
|
76
|
+
it('rejects malformed input instead of returning undefined', () => {
|
|
77
|
+
expect(() => (0, digest_1.candidateDigest)(undefined)).toThrow(/digest input is required/);
|
|
78
|
+
expect(() => (0, digest_1.candidateDigest)({ file: '', cwd })).toThrow(/bounded path/);
|
|
79
|
+
expect(() => (0, digest_1.candidateDigest)({ file: 'policy.json', cwd: '' })).toThrow(/bounded path/);
|
|
80
|
+
fs_1.default.writeFileSync(path_1.default.join(cwd, 'array.json'), '[]');
|
|
81
|
+
expect(() => (0, digest_1.candidateDigest)({ file: 'array.json', cwd })).toThrow(/must be a JSON object/);
|
|
82
|
+
});
|
|
83
|
+
// The point of the command: the digest it discloses is the one approval
|
|
84
|
+
// demands. A digest that merely looked plausible would be worthless, so the
|
|
85
|
+
// proof is that approval ACCEPTS it, not that two functions agree.
|
|
86
|
+
it('produces a digest that the policy approval path accepts end to end', () => {
|
|
87
|
+
const file = write('policy.json', policy());
|
|
88
|
+
const observed = [];
|
|
89
|
+
const boundary = {
|
|
90
|
+
withProjectLease: (_root, operation) => operation(),
|
|
91
|
+
readRegularFile: (target) => { const entry = { bytes: fs_1.default.readFileSync(target), identity: Object.freeze({}) }; observed.push(entry); return entry; },
|
|
92
|
+
writeProjectTransaction: (root, destination, content) => { const target = path_1.default.join(root, destination); fs_1.default.mkdirSync(path_1.default.dirname(target), { recursive: true }); fs_1.default.writeFileSync(target, content); },
|
|
93
|
+
};
|
|
94
|
+
const approved = (0, store_1.approvePolicyWithBoundary)({ file, scope: 'project', cwd, expectedDigest: (0, digest_1.candidateDigest)({ file, cwd }).digest }, boundary);
|
|
95
|
+
expect(approved.contentDigest).toBe((0, digest_1.candidateDigest)({ file, cwd }).digest);
|
|
96
|
+
expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(cwd, '.awm', 'model-policy.json'), 'utf8')).contentDigest).toBe(approved.contentDigest);
|
|
97
|
+
});
|
|
98
|
+
it('produces a digest that the capability approval path accepts end to end', () => {
|
|
99
|
+
const value = receipt();
|
|
100
|
+
const file = write('receipt.json', value);
|
|
101
|
+
const approved = (0, capabilities_1.approveCapabilities)({ file, cwd, expectedDigest: (0, digest_1.candidateDigest)({ file, cwd }).digest, now: new Date('2026-09-17T12:00:00.000Z') });
|
|
102
|
+
expect(approved).toEqual(value);
|
|
103
|
+
});
|
|
104
|
+
// Falsifiability: the same approval must REJECT any digest but the disclosed
|
|
105
|
+
// one, or the assertion above would pass against a command that emitted noise.
|
|
106
|
+
it('is the only digest approval accepts', () => {
|
|
107
|
+
const value = receipt();
|
|
108
|
+
const file = write('receipt.json', value);
|
|
109
|
+
expect(() => (0, capabilities_1.approveCapabilities)({ file, cwd, expectedDigest: 'f'.repeat(64), now: new Date('2026-09-17T12:00:00.000Z') })).toThrow(/does not match expectedDigest/);
|
|
110
|
+
});
|
|
111
|
+
it('writes nothing: digesting is not approving', () => {
|
|
112
|
+
const file = write('policy.json', policy());
|
|
113
|
+
(0, digest_1.candidateDigest)({ file, cwd });
|
|
114
|
+
expect(fs_1.default.existsSync(path_1.default.join(cwd, '.awm'))).toBe(false);
|
|
115
|
+
expect(fs_1.default.readdirSync(cwd).sort()).toEqual(['policy.json']);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const types_1 = require("../../../src/core/journal/types");
|
|
4
|
+
const journal_1 = require("../../../src/core/model-policy/journal");
|
|
5
|
+
const envelope = { schema: 'routing-envelope/v1', runtime: { target: 'codex', kind: 'native', version: '1', accountScopeDigest: '0'.repeat(64) }, role: 'implementer', sliceId: 'S1', requestedProfile: 'mechanical', effectiveProfile: 'mechanical', resolved: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'medium' } }, outcome: 'native', unavailableEvidence: [], policyDigest: 'a'.repeat(64), capabilityDigest: 'b'.repeat(64), planDigest: 'c'.repeat(64), executionDigest: 'd'.repeat(64) };
|
|
6
|
+
describe('routing journal helpers', () => {
|
|
7
|
+
it('reserves one idempotent attempt and records native observation', () => {
|
|
8
|
+
const state = (0, types_1.emptyState)('main');
|
|
9
|
+
const reserved = (0, journal_1.reserveRoutingAttempt)(state, { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope, fingerprint: 'e'.repeat(64) }, '2026-09-17T00:00:00.000Z');
|
|
10
|
+
const replay = (0, journal_1.reserveRoutingAttempt)(reserved.state, { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope, fingerprint: 'e'.repeat(64) }, '2026-09-17T00:01:00.000Z');
|
|
11
|
+
expect(replay.attemptId).toBe(reserved.attemptId);
|
|
12
|
+
const observed = (0, journal_1.observeRoutingAttempt)(replay.state, { attemptId: reserved.attemptId, nativeAgentId: 'native-1' }, '2026-09-17T00:02:00.000Z');
|
|
13
|
+
expect(observed.routingAttempts?.[0]).toMatchObject({ state: 'active', nativeAgentId: 'native-1' });
|
|
14
|
+
});
|
|
15
|
+
it('rejects a fourth implementer attempt in one lineage', () => {
|
|
16
|
+
let state = (0, types_1.emptyState)('main');
|
|
17
|
+
for (const [index, profile] of ['mechanical', 'integration', 'judgment'].entries()) {
|
|
18
|
+
state = (0, journal_1.reserveRoutingAttempt)(state, { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope: { ...envelope, effectiveProfile: profile }, fingerprint: `${'a'.repeat(63)}${index}` }, '2026-09-17T00:00:00.000Z').state;
|
|
19
|
+
state.routingAttempts[state.routingAttempts.length - 1].state = 'blocked';
|
|
20
|
+
state.routingAttempts[state.routingAttempts.length - 1].verdict = 'fail';
|
|
21
|
+
}
|
|
22
|
+
expect(() => (0, journal_1.reserveRoutingAttempt)(state, { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope, fingerprint: 'f'.repeat(64) }, '2026-09-17T00:00:00.000Z')).toThrow(/budget/i);
|
|
23
|
+
});
|
|
24
|
+
it('reports routing attempts without envelope bodies or native identities', () => {
|
|
25
|
+
const state = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope, fingerprint: 'e'.repeat(64) }, '2026-09-17T00:00:00.000Z').state;
|
|
26
|
+
expect((0, journal_1.routingReport)(state)).toEqual({ schema: 'routing-report/v1', attempts: 1, plannedByRole: { implementer: 1 }, actualByRole: {}, byState: { reserved: 1 }, retries: 0, fallbacks: 0, unavailable: {}, verdicts: {}, administrativeRepairs: 0 });
|
|
27
|
+
});
|
|
28
|
+
it('uses the exact deterministic implementation escalation sequence', () => {
|
|
29
|
+
let state = (0, types_1.emptyState)('main');
|
|
30
|
+
expect((0, journal_1.resolveLineageEscalation)(state, 'l1', 'mechanical')).toEqual({ profile: 'mechanical', effort: 'medium' });
|
|
31
|
+
state = (0, journal_1.reserveRoutingAttempt)(state, { obligationId: 'impl:S1', lineageId: 'l1', envelope, fingerprint: '1'.repeat(64) }, '2026-09-17T00:00:00.000Z').state;
|
|
32
|
+
state.routingAttempts[0].state = 'blocked';
|
|
33
|
+
state.routingAttempts[0].verdict = 'fail';
|
|
34
|
+
expect((0, journal_1.resolveLineageEscalation)(state, 'l1', 'mechanical')).toEqual({ profile: 'integration', effort: 'medium' });
|
|
35
|
+
state.routingAttempts.push({ ...state.routingAttempts[0], id: 'route-2', attempt: 2, envelope: { ...envelope, effectiveProfile: 'integration' }, state: 'blocked', verdict: 'fail' });
|
|
36
|
+
expect((0, journal_1.resolveLineageEscalation)(state, 'l1', 'mechanical')).toEqual({ profile: 'judgment', effort: 'medium' });
|
|
37
|
+
state.routingAttempts.push({ ...state.routingAttempts[0], id: 'route-3', attempt: 3, envelope: { ...envelope, effectiveProfile: 'judgment', resolved: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'medium' } } }, state: 'blocked', verdict: 'fail' });
|
|
38
|
+
expect((0, journal_1.resolveLineageEscalation)(state, 'l1', 'mechanical')).toEqual({ profile: 'judgment', effort: 'high' });
|
|
39
|
+
state.routingAttempts[2].envelope = { ...state.routingAttempts[2].envelope, resolved: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'high' } } };
|
|
40
|
+
expect(() => (0, journal_1.resolveLineageEscalation)(state, 'l1', 'mechanical')).toThrow(/exhausted/i);
|
|
41
|
+
});
|
|
42
|
+
it('rejects hostile roles and unavailable provenance before it can reach a report', () => {
|
|
43
|
+
expect((0, types_1.isRoutingEnvelope)({ ...envelope, role: 'attacker-key' })).toBe(false);
|
|
44
|
+
expect((0, types_1.isRoutingEnvelope)({ ...envelope, unavailableEvidence: ['secret=leak'] })).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const canonical_1 = require("../../../src/core/model-policy/canonical");
|
|
4
|
+
const validate_1 = require("../../../src/core/model-policy/validate");
|
|
5
|
+
const selection = (id, effort = 'medium') => ({ selector: { kind: 'model', id }, effort: { kind: 'explicit', value: effort } });
|
|
6
|
+
const policy = () => ({
|
|
7
|
+
schema: 'model-policy/v1',
|
|
8
|
+
mappings: [{ target: 'codex', runtimeKind: 'native', profiles: {
|
|
9
|
+
mechanical: selection('gpt-5.6-luna'), integration: selection('gpt-5.6-terra'), judgment: selection('gpt-5.6-sol'),
|
|
10
|
+
}, fullCapability: selection('gpt-5.6-sol', 'high'), degradation: {
|
|
11
|
+
allowMissingModelOverride: false, allowMissingEffortOverride: false, allowMissingObservedIdentity: false,
|
|
12
|
+
} }],
|
|
13
|
+
implementationBudget: { maxAttempts: 3, escalation: ['mechanical', 'integration', 'judgment'], judgmentEfforts: ['medium', 'high'] },
|
|
14
|
+
});
|
|
15
|
+
const approvedPolicy = () => ({ schema: 'approved-model-policy/v1', content: policy(), contentDigest: 'a'.repeat(64), approval: { approvedAt: '2026-09-17T00:00:00.000Z', approvalId: 'approval-1' }, lineage: { previousDigest: null } });
|
|
16
|
+
describe('model policy validation and canonical digest', () => {
|
|
17
|
+
it.each(['September 17, 2026 00:00:00 UTC', '2026-09-17', '2026-09-17T00:00:00.000+00:00', '2026-09-17T00:00:00.000Zx', '2026-02-30T00:00:00.000Z', '2026-09-17T00:00:00Z'])('rejects a noncanonical approval timestamp: %s', approvedAt => {
|
|
18
|
+
const candidate = approvedPolicy();
|
|
19
|
+
candidate.approval.approvedAt = approvedAt;
|
|
20
|
+
expect(() => (0, validate_1.validateApprovedPolicy)(candidate)).toThrow(/approval\.approvedAt/);
|
|
21
|
+
});
|
|
22
|
+
it('validates the complete policy contract and gives reordered object keys the same digest', () => {
|
|
23
|
+
const candidate = policy();
|
|
24
|
+
const reordered = JSON.parse('{"implementationBudget":{"judgmentEfforts":["medium","high"],"escalation":["mechanical","integration","judgment"],"maxAttempts":3},"mappings":[{"fullCapability":{"effort":{"value":"high","kind":"explicit"},"selector":{"id":"gpt-5.6-sol","kind":"model"}},"degradation":{"allowMissingObservedIdentity":false,"allowMissingEffortOverride":false,"allowMissingModelOverride":false},"profiles":{"judgment":{"selector":{"kind":"model","id":"gpt-5.6-sol"},"effort":{"kind":"explicit","value":"medium"}},"integration":{"selector":{"kind":"model","id":"gpt-5.6-terra"},"effort":{"kind":"explicit","value":"medium"}},"mechanical":{"selector":{"kind":"model","id":"gpt-5.6-luna"},"effort":{"kind":"explicit","value":"medium"}}},"runtimeKind":"native","target":"codex"}],"schema":"model-policy/v1"}');
|
|
25
|
+
expect((0, validate_1.validatePolicyContent)(candidate)).toEqual(candidate);
|
|
26
|
+
expect((0, canonical_1.canonicalPolicyDigest)(candidate)).toBe((0, canonical_1.canonicalPolicyDigest)(reordered));
|
|
27
|
+
});
|
|
28
|
+
it('preserves array order in the digest', () => {
|
|
29
|
+
const candidate = policy();
|
|
30
|
+
const swapped = structuredClone(policy());
|
|
31
|
+
const second = structuredClone(swapped.mappings[0]);
|
|
32
|
+
second.target = 'claude-code';
|
|
33
|
+
swapped.mappings.push(second);
|
|
34
|
+
candidate.mappings.push(structuredClone(second));
|
|
35
|
+
swapped.mappings.reverse();
|
|
36
|
+
expect((0, canonical_1.canonicalPolicyDigest)(candidate)).not.toBe((0, canonical_1.canonicalPolicyDigest)(swapped));
|
|
37
|
+
});
|
|
38
|
+
it.each([
|
|
39
|
+
['unknown field', () => ({ ...policy(), unexpected: true })],
|
|
40
|
+
['partial profile map', () => { const value = policy(); delete value.mappings[0].profiles.judgment; return value; }],
|
|
41
|
+
['duplicate target/runtime map', () => { const value = policy(); value.mappings.push(structuredClone(value.mappings[0])); return value; }],
|
|
42
|
+
['unsafe runtime kind', () => { const value = policy(); value.mappings[0].runtimeKind = '../native'; return value; }],
|
|
43
|
+
['unsafe model id control character', () => { const value = policy(); value.mappings[0].profiles.mechanical.selector.id = 'bad\nmodel'; return value; }],
|
|
44
|
+
])('rejects %s', (_name, make) => {
|
|
45
|
+
expect(() => (0, validate_1.validatePolicyContent)(make())).toThrow();
|
|
46
|
+
});
|
|
47
|
+
});
|