agentic-workflow-manager 9.11.11 → 9.12.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/doctor.js +23 -1
- package/dist/src/commands/hooks/claude.js +61 -8
- package/dist/src/commands/hooks/resync.js +1 -1
- package/dist/src/commands/job/index.js +39 -4
- package/dist/src/commands/model-policy/index.js +253 -0
- package/dist/src/commands/plan/index.js +53 -12
- package/dist/src/commands/preflight/checks.js +18 -0
- package/dist/src/commands/watch/apply.js +71 -5
- package/dist/src/commands/watch/supervisor.js +63 -3
- package/dist/src/core/admission/index.js +20 -6
- package/dist/src/core/journal/types.js +21 -5
- package/dist/src/core/model-policy/capabilities-v2.js +152 -0
- package/dist/src/core/model-policy/capabilities.js +7 -1
- package/dist/src/core/model-policy/claude-hook-events.js +120 -0
- package/dist/src/core/model-policy/event-store.js +221 -0
- package/dist/src/core/model-policy/hook-diagnostics.js +94 -0
- package/dist/src/core/model-policy/journal.js +125 -16
- package/dist/src/core/model-policy/local-claude-scope.js +41 -0
- package/dist/src/core/model-policy/local-codex-scope.js +33 -0
- package/dist/src/core/model-policy/local-event-scope.js +13 -0
- package/dist/src/core/model-policy/machine-key.js +56 -0
- package/dist/src/core/model-policy/native-claude-agent-definition.js +82 -0
- package/dist/src/core/model-policy/native-claude-config.js +107 -0
- package/dist/src/core/model-policy/native-claude-machine.js +58 -0
- package/dist/src/core/model-policy/native-claude-transcript.js +80 -0
- package/dist/src/core/model-policy/native-claude.js +41 -0
- package/dist/src/core/model-policy/native-codex-machine.js +116 -0
- package/dist/src/core/model-policy/native-codex-rollout.js +111 -0
- package/dist/src/core/model-policy/native-codex.js +302 -0
- package/dist/src/core/model-policy/native-routing-observe.js +106 -0
- package/dist/src/core/model-policy/native-routing-proof.js +52 -0
- package/dist/src/core/model-policy/native-runtime.js +86 -0
- package/dist/src/core/model-policy/resolve.js +117 -3
- package/dist/src/core/model-policy/selection-policy-digest.js +22 -0
- package/dist/src/core/model-policy/setup-readiness.js +77 -0
- package/dist/src/providers/index.js +3 -1
- package/dist/tests/commands/doctor-routing.test.js +22 -0
- package/dist/tests/commands/doctor.test.js +5 -0
- package/dist/tests/commands/hooks/install.test.js +6 -0
- package/dist/tests/commands/hooks/resync.test.js +3 -0
- package/dist/tests/commands/hooks/status.test.js +12 -1
- package/dist/tests/commands/job/routing-file.test.js +32 -0
- package/dist/tests/commands/job/routing-report-cli.test.js +12 -5
- package/dist/tests/commands/job/routing.test.js +2 -2
- package/dist/tests/commands/model-policy/claude-hook-flow.test.js +104 -0
- package/dist/tests/commands/model-policy/index.test.js +179 -0
- package/dist/tests/commands/plan/index.test.js +2 -2
- package/dist/tests/commands/preflight/preflight.test.js +65 -0
- package/dist/tests/commands/watch/apply.test.js +116 -10
- package/dist/tests/commands/watch/dispatch-routing-identity.test.js +23 -0
- package/dist/tests/core/admission/index.test.js +21 -0
- package/dist/tests/core/model-policy/capabilities-v2.test.js +42 -0
- package/dist/tests/core/model-policy/claude-hook-events.test.js +40 -0
- package/dist/tests/core/model-policy/event-store-claude.test.js +64 -0
- package/dist/tests/core/model-policy/event-store.test.js +151 -0
- package/dist/tests/core/model-policy/hook-diagnostics.test.js +34 -0
- package/dist/tests/core/model-policy/journal.test.js +165 -12
- package/dist/tests/core/model-policy/local-claude-scope.test.js +31 -0
- package/dist/tests/core/model-policy/machine-key.test.js +38 -0
- package/dist/tests/core/model-policy/native-claude-agent-definition.test.js +31 -0
- package/dist/tests/core/model-policy/native-claude-config.test.js +53 -0
- package/dist/tests/core/model-policy/native-claude-machine.test.js +18 -0
- package/dist/tests/core/model-policy/native-claude-transcript.test.js +45 -0
- package/dist/tests/core/model-policy/native-claude.test.js +21 -0
- package/dist/tests/core/model-policy/native-codex-machine.test.js +34 -0
- package/dist/tests/core/model-policy/native-codex-rollout.test.js +44 -0
- package/dist/tests/core/model-policy/native-codex.test.js +133 -0
- package/dist/tests/core/model-policy/native-routing-observe.test.js +60 -0
- package/dist/tests/core/model-policy/native-routing-proof.test.js +30 -0
- package/dist/tests/core/model-policy/native-runtime.test.js +14 -0
- package/dist/tests/core/model-policy/resolve-event.test.js +65 -0
- package/dist/tests/core/model-policy/resolve.test.js +17 -0
- package/dist/tests/core/model-policy/setup-readiness.test.js +83 -0
- package/dist/tests/helpers/routing-approval.js +14 -0
- package/dist/tests/providers/index.test.js +1 -1
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -47,6 +47,14 @@ const store_1 = require("../../../src/core/journal/store");
|
|
|
47
47
|
const paths_1 = require("../../../src/core/journal/paths");
|
|
48
48
|
const atomicFile = __importStar(require("../../../src/core/atomic-file"));
|
|
49
49
|
const gate_1 = require("../../../src/commands/job/gate");
|
|
50
|
+
const canonical_1 = require("../../../src/core/model-policy/canonical");
|
|
51
|
+
const capabilities_1 = require("../../../src/core/model-policy/capabilities");
|
|
52
|
+
const routing_approval_1 = require("../../helpers/routing-approval");
|
|
53
|
+
const machine_key_1 = require("../../../src/core/model-policy/machine-key");
|
|
54
|
+
const native_routing_proof_1 = require("../../../src/core/model-policy/native-routing-proof");
|
|
55
|
+
const eventStore = __importStar(require("../../../src/core/model-policy/event-store"));
|
|
56
|
+
const policyStore = __importStar(require("../../../src/core/model-policy/store"));
|
|
57
|
+
const selection_policy_digest_1 = require("../../../src/core/model-policy/selection-policy-digest");
|
|
50
58
|
function jobPayload(argv) {
|
|
51
59
|
return { argv, paths: [], cwd: '.', fingerprint: 'fp-1', commandDigest: 'cd-1', expandedPaths: [] };
|
|
52
60
|
}
|
|
@@ -195,16 +203,114 @@ describe('aplicacion transaccional de requests', () => {
|
|
|
195
203
|
expect(s.controllerHeartbeatAt).toBeUndefined();
|
|
196
204
|
});
|
|
197
205
|
test('routing reserve/observe es supervisado, idempotente y una generacion vieja no muta intentos', () => {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
const priorHome = process.env.AWM_HOME;
|
|
207
|
+
process.env.AWM_HOME = path_1.default.join(repo, 'operator');
|
|
208
|
+
try {
|
|
209
|
+
const approved = (0, routing_approval_1.routingApproval)();
|
|
210
|
+
approved.policy.contentDigest = (0, canonical_1.canonicalPolicyDigest)(approved.policy.content);
|
|
211
|
+
approved.capabilities.recordedAt = new Date(Date.now() - 60_000).toISOString();
|
|
212
|
+
approved.capabilities.expiresAt = new Date(Date.now() + 3_600_000).toISOString();
|
|
213
|
+
const envelope = { ...routing_approval_1.routingEnvelope, policyDigest: approved.policy.contentDigest, capabilityDigest: (0, capabilities_1.capabilityReceiptDigest)(approved.capabilities) };
|
|
214
|
+
fs_1.default.mkdirSync(path_1.default.join(repo, '.awm'), { recursive: true });
|
|
215
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.awm', 'model-policy.json'), JSON.stringify(approved.policy));
|
|
216
|
+
const receiptPath = path_1.default.join(process.env.AWM_HOME, 'routing-capabilities', 'codex', 'native.json');
|
|
217
|
+
fs_1.default.mkdirSync(path_1.default.dirname(receiptPath), { recursive: true });
|
|
218
|
+
fs_1.default.writeFileSync(receiptPath, JSON.stringify(approved.capabilities));
|
|
219
|
+
(0, requests_1.emitRequest)(repo, 'rama', { kind: 'routing-reserve', generationToken: 'g1', idempotencyKey: 'routing-1', payload: { obligationId: 'o1', lineageId: 'l1', envelope, fingerprint: 'e'.repeat(64) } });
|
|
220
|
+
expect((0, apply_1.consumePendingRequests)(repo, 'rama', 'g1').applied).toBe(1);
|
|
221
|
+
const attempt = (0, store_1.readJournal)(repo, 'rama').state.routingAttempts[0];
|
|
222
|
+
(0, requests_1.emitRequest)(repo, 'rama', { kind: 'routing-observe', generationToken: 'g1', idempotencyKey: 'routing-2', payload: { attemptId: attempt.id, nativeAgentId: 'native-1' } });
|
|
223
|
+
(0, apply_1.consumePendingRequests)(repo, 'rama', 'g1');
|
|
224
|
+
expect((0, store_1.readJournal)(repo, 'rama').state.routingAttempts[0]).toMatchObject({ state: 'active', nativeAgentId: 'native-1' });
|
|
225
|
+
(0, requests_1.emitRequest)(repo, 'rama', { kind: 'routing-reserve', generationToken: 'old', idempotencyKey: 'routing-old', payload: { obligationId: 'o1', lineageId: 'l2', envelope, fingerprint: 'f'.repeat(64) } });
|
|
226
|
+
expect((0, apply_1.consumePendingRequests)(repo, 'rama', 'g1').rejectedStale).toBe(1);
|
|
227
|
+
expect((0, store_1.readJournal)(repo, 'rama').state.routingAttempts).toHaveLength(1);
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
if (priorHome === undefined)
|
|
231
|
+
delete process.env.AWM_HOME;
|
|
232
|
+
else
|
|
233
|
+
process.env.AWM_HOME = priorHome;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
test('un recibo nativo v2 no acepta una observacion positiva redactada por el agente', () => {
|
|
237
|
+
const state = (0, store_1.readJournal)(repo, 'rama').state;
|
|
238
|
+
state.routingAttempts = [{ schema: 'routing-attempt/v1', id: 'route-native-1', obligationId: 'o1', lineageId: 'l1', attempt: 1,
|
|
239
|
+
envelope: routing_approval_1.routingEnvelope, envelopeDigest: 'a'.repeat(64), fingerprint: 'b'.repeat(64), state: 'reserved',
|
|
240
|
+
nativeEvidenceRequired: true, reservedAt: new Date(Date.now() - 1_000).toISOString() }];
|
|
241
|
+
(0, store_1.writeJournal)(repo, 'rama', state);
|
|
242
|
+
(0, requests_1.emitRequest)(repo, 'rama', { kind: 'routing-observe', generationToken: 'g1', idempotencyKey: 'forged-positive',
|
|
243
|
+
payload: { attemptId: 'route-native-1', nativeAgentId: 'fake-child', observed: routing_approval_1.routingEnvelope.resolved } });
|
|
244
|
+
const result = (0, apply_1.consumePendingRequests)(repo, 'rama', 'g1');
|
|
245
|
+
expect({ result, problems: (0, store_1.readJournal)(repo, 'rama').state?.requestProblems }).toMatchObject({ result: { applied: 1 } });
|
|
246
|
+
const after = (0, store_1.readJournal)(repo, 'rama').state;
|
|
247
|
+
expect(after.routingAttempts?.[0]).toMatchObject({ state: 'blocked', reasonCode: 'PROVENANCE_MISSING', verdict: 'inconclusive' });
|
|
248
|
+
expect(after.routingIncidents).toMatchObject([{ reasonCode: 'PROVENANCE_MISSING', alertState: 'pending' }]);
|
|
249
|
+
});
|
|
250
|
+
test('un evento nativo sellado posterior a la reserva activa solo su intento', () => {
|
|
251
|
+
const previousHome = process.env.AWM_HOME;
|
|
252
|
+
const operator = path_1.default.join(repo, 'operator');
|
|
253
|
+
fs_1.default.mkdirSync(operator);
|
|
254
|
+
process.env.AWM_HOME = operator;
|
|
255
|
+
const scope = { runtime: routing_approval_1.routingEnvelope.runtime, binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64) };
|
|
256
|
+
const approved = (0, routing_approval_1.routingApproval)().policy;
|
|
257
|
+
const enrolledAt = new Date(Date.now() - 1_000).toISOString();
|
|
258
|
+
const eventReader = jest.spyOn(eventStore, 'readStoredEventReceipt').mockReturnValue({ state: 'present', receipt: {
|
|
259
|
+
schema: 'routing-capabilities/v2', ...scope, recordedAt: enrolledAt, claims: [{
|
|
260
|
+
selection: routing_approval_1.routingEnvelope.resolved, mappingDigest: (0, selection_policy_digest_1.selectionPolicyDigest)(approved.content.mappings[0], routing_approval_1.routingEnvelope.resolved),
|
|
261
|
+
eventDigest: 'c'.repeat(64), source: 'codex-turn-context', observedAt: enrolledAt,
|
|
262
|
+
actualModel: 'unverified', tokenUsage: 'unknown'
|
|
263
|
+
}],
|
|
264
|
+
} });
|
|
265
|
+
const policyReader = jest.spyOn(policyStore, 'readEffectivePolicy').mockReturnValue({ state: 'approved', provenance: 'project', policy: approved });
|
|
266
|
+
try {
|
|
267
|
+
const reservedAt = new Date(Date.now() - 2_000).toISOString();
|
|
268
|
+
const state = (0, store_1.readJournal)(repo, 'rama').state;
|
|
269
|
+
state.routingAttempts = [{ schema: 'routing-attempt/v1', id: 'route-native-1', obligationId: 'o1', lineageId: 'l1', attempt: 1,
|
|
270
|
+
envelope: routing_approval_1.routingEnvelope, envelopeDigest: 'a'.repeat(64), fingerprint: 'b'.repeat(64), state: 'reserved',
|
|
271
|
+
nativeEvidenceRequired: true, reservedAt }];
|
|
272
|
+
(0, store_1.writeJournal)(repo, 'rama', state);
|
|
273
|
+
const proof = (0, native_routing_proof_1.signNativeRoutingProof)({ attemptId: 'route-native-1', nativeAgentId: 'child-1', source: 'codex-turn-context',
|
|
274
|
+
eventDigest: 'c'.repeat(64), observedAt: new Date(Date.now() - 1_000).toISOString(), selection: routing_approval_1.routingEnvelope.resolved }, (0, machine_key_1.ensureMachineKey)(operator));
|
|
275
|
+
(0, requests_1.emitRequest)(repo, 'rama', { kind: 'routing-observe', generationToken: 'g1', idempotencyKey: 'native-positive',
|
|
276
|
+
payload: { attemptId: 'route-native-1', nativeAgentId: 'child-1', observed: routing_approval_1.routingEnvelope.resolved, nativeProof: proof } });
|
|
277
|
+
expect((0, apply_1.consumePendingRequests)(repo, 'rama', 'g1', scope).applied).toBe(1);
|
|
278
|
+
expect((0, store_1.readJournal)(repo, 'rama').state.routingAttempts?.[0]).toMatchObject({ state: 'active', nativeEventDigest: 'c'.repeat(64) });
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
eventReader.mockRestore();
|
|
282
|
+
policyReader.mockRestore();
|
|
283
|
+
if (previousHome === undefined)
|
|
284
|
+
delete process.env.AWM_HOME;
|
|
285
|
+
else
|
|
286
|
+
process.env.AWM_HOME = previousHome;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
test('un proof en cola no activa el intento sin scope local vigente', () => {
|
|
290
|
+
const previousHome = process.env.AWM_HOME;
|
|
291
|
+
const operator = path_1.default.join(repo, 'operator');
|
|
292
|
+
fs_1.default.mkdirSync(operator);
|
|
293
|
+
process.env.AWM_HOME = operator;
|
|
294
|
+
try {
|
|
295
|
+
const state = (0, store_1.readJournal)(repo, 'rama').state;
|
|
296
|
+
const reservedAt = new Date(Date.now() - 2_000).toISOString();
|
|
297
|
+
state.routingAttempts = [{ schema: 'routing-attempt/v1', id: 'route-native-1', obligationId: 'o1', lineageId: 'l1', attempt: 1,
|
|
298
|
+
envelope: routing_approval_1.routingEnvelope, envelopeDigest: 'a'.repeat(64), fingerprint: 'b'.repeat(64), state: 'reserved',
|
|
299
|
+
nativeEvidenceRequired: true, reservedAt }];
|
|
300
|
+
(0, store_1.writeJournal)(repo, 'rama', state);
|
|
301
|
+
const proof = (0, native_routing_proof_1.signNativeRoutingProof)({ attemptId: 'route-native-1', nativeAgentId: 'child-1', source: 'codex-turn-context',
|
|
302
|
+
eventDigest: 'c'.repeat(64), observedAt: new Date(Date.now() - 1_000).toISOString(), selection: routing_approval_1.routingEnvelope.resolved }, (0, machine_key_1.ensureMachineKey)(operator));
|
|
303
|
+
(0, requests_1.emitRequest)(repo, 'rama', { kind: 'routing-observe', generationToken: 'g1', idempotencyKey: 'native-stale-scope',
|
|
304
|
+
payload: { attemptId: 'route-native-1', nativeAgentId: 'child-1', observed: routing_approval_1.routingEnvelope.resolved, nativeProof: proof } });
|
|
305
|
+
(0, apply_1.consumePendingRequests)(repo, 'rama', 'g1');
|
|
306
|
+
expect((0, store_1.readJournal)(repo, 'rama').state.routingAttempts?.[0]).toMatchObject({ state: 'blocked', reasonCode: 'PROVENANCE_MISSING' });
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
if (previousHome === undefined)
|
|
310
|
+
delete process.env.AWM_HOME;
|
|
311
|
+
else
|
|
312
|
+
process.env.AWM_HOME = previousHome;
|
|
313
|
+
}
|
|
208
314
|
});
|
|
209
315
|
test('request corrupta se aparta VISIBLE como .corrupt, jamas se descarta (R1.6)', () => {
|
|
210
316
|
fs_1.default.writeFileSync(path_1.default.join((0, paths_1.requestsDir)(repo, 'rama'), 'req-roto.json'), '{no-json');
|
|
@@ -149,6 +149,29 @@ describe('the supervisor admission actually carries the identity (#166)', () =>
|
|
|
149
149
|
fs_1.default.rmSync(repo, { recursive: true, force: true });
|
|
150
150
|
}
|
|
151
151
|
});
|
|
152
|
+
test('uses sealed event evidence and a freshly measured Codex scope instead of a 24h receipt', async () => {
|
|
153
|
+
const repo = boundRepo();
|
|
154
|
+
try {
|
|
155
|
+
const runtime = { target: 'codex', kind: 'native', version: '0.156.1', accountScopeDigest: DIGEST };
|
|
156
|
+
const eventScope = { runtime, binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64) };
|
|
157
|
+
const eventReceipt = { schema: 'routing-capabilities/v2', ...eventScope, recordedAt: '2026-09-22T00:00:00.000Z', claims: [] };
|
|
158
|
+
const seen = [];
|
|
159
|
+
const readCapabilities = jest.fn();
|
|
160
|
+
const admission = (0, supervisor_1.defaultDispatchAdmission)(repo, 'main', 'codex', { kind: 'native', version: '0.156.1', accountScopeDigest: DIGEST }, {
|
|
161
|
+
admit: (async (input) => { seen.push(input); return { state: 'admitted' }; }),
|
|
162
|
+
readEffectivePolicy: () => ({ state: 'absent' }),
|
|
163
|
+
readCapabilities,
|
|
164
|
+
readStoredEventReceipt: (() => ({ state: 'present', receipt: eventReceipt })),
|
|
165
|
+
queryLocalCodexScope: (async () => ({ state: 'current', scope: eventScope })),
|
|
166
|
+
});
|
|
167
|
+
await admission();
|
|
168
|
+
expect(readCapabilities).not.toHaveBeenCalled();
|
|
169
|
+
expect(seen[0].routing).toMatchObject({ runtime, eventScope, eventReceipt });
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
fs_1.default.rmSync(repo, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
152
175
|
test('dispatches without routing facts when no identity was asserted', async () => {
|
|
153
176
|
const repo = boundRepo();
|
|
154
177
|
try {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const admission_1 = require("../../../src/core/admission");
|
|
4
4
|
const types_1 = require("../../../src/core/journal/types");
|
|
5
|
+
const selection_policy_digest_1 = require("../../../src/core/model-policy/selection-policy-digest");
|
|
5
6
|
const valid = {
|
|
6
7
|
state: 'valid', schema: 'compact-slices/v1', planDigest: 'a'.repeat(64),
|
|
7
8
|
manifest: {
|
|
@@ -50,6 +51,26 @@ describe('admitPlan', () => {
|
|
|
50
51
|
const report = await (0, admission_1.admitPlan)({ plan: v2(profile), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], routing: routing() });
|
|
51
52
|
expect(report).toMatchObject({ state: 'admitted', routingForecast: { implementerProfiles: { [profile]: 1 }, trackB: { state: 'unavailable', lowerBound: 1 }, controller: { state: 'unavailable', lowerBound: 0 } } });
|
|
52
53
|
});
|
|
54
|
+
it('awaits read-only routing evidence only after earlier admission gates pass', async () => {
|
|
55
|
+
const reader = jest.fn(async () => routing());
|
|
56
|
+
expect(await (0, admission_1.admitPlan)({ plan: v2(), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], routingReader: reader })).toMatchObject({ state: 'admitted' });
|
|
57
|
+
expect(reader).toHaveBeenCalledTimes(1);
|
|
58
|
+
});
|
|
59
|
+
it('admits a +25h event-scoped Codex enrollment only when the full fallback is independently proved', async () => {
|
|
60
|
+
const evidence = routing();
|
|
61
|
+
evidence.policy.content.mappings[0].degradation.allowMissingObservedIdentity = true;
|
|
62
|
+
const mapping = evidence.policy.content.mappings[0];
|
|
63
|
+
const eventScope = { runtime: evidence.runtime, binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64) };
|
|
64
|
+
const claimed = [mapping.profiles.mechanical, mapping.fullCapability];
|
|
65
|
+
const eventReceipt = { schema: 'routing-capabilities/v2', ...eventScope, recordedAt: '2026-09-17T00:00:00.000Z', claims: claimed.map(selection => ({ selection,
|
|
66
|
+
mappingDigest: (0, selection_policy_digest_1.selectionPolicyDigest)(mapping, selection), eventDigest: sha, source: 'codex-turn-context', observedAt: '2026-09-17T00:00:00.000Z', actualModel: 'unverified', tokenUsage: 'unknown' })) };
|
|
67
|
+
const routingFacts = { runtime: evidence.runtime, policy: evidence.policy, eventReceipt, eventScope, now: new Date('2026-09-18T01:00:00.000Z') };
|
|
68
|
+
expect(await (0, admission_1.admitPlan)({ plan: v2(), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], routing: routingFacts })).toMatchObject({ state: 'admitted' });
|
|
69
|
+
expect(await (0, admission_1.admitPlan)({ plan: v2(), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], routing: { ...routingFacts,
|
|
70
|
+
eventReceipt: { ...eventReceipt, claims: [eventReceipt.claims[1]] } } })).toMatchObject({ state: 'admitted' });
|
|
71
|
+
expect(await (0, admission_1.admitPlan)({ plan: v2(), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], routing: { ...routingFacts,
|
|
72
|
+
eventReceipt: { ...eventReceipt, claims: [eventReceipt.claims[0]] } } })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_SELECTION_UNENROLLED' }] });
|
|
73
|
+
});
|
|
53
74
|
it('blocks a valid cross-provider policy and receipt instead of admitting codex under another runtime target', async () => {
|
|
54
75
|
const evidence = routing();
|
|
55
76
|
const foreign = structuredClone(evidence);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const capabilities_v2_1 = require("../../../src/core/model-policy/capabilities-v2");
|
|
4
|
+
const digestA = 'a'.repeat(64);
|
|
5
|
+
const digestB = 'b'.repeat(64);
|
|
6
|
+
const digestC = 'c'.repeat(64);
|
|
7
|
+
const selection = (id) => ({ selector: { kind: 'model', id }, effort: { kind: 'explicit', value: 'high' } });
|
|
8
|
+
const receipt = () => ({
|
|
9
|
+
schema: 'routing-capabilities/v2',
|
|
10
|
+
runtime: { target: 'codex', kind: 'native', version: '0.156.1', accountScopeDigest: digestA },
|
|
11
|
+
binaryDigest: digestB, configDigest: digestC, recordedAt: '2026-09-23T00:00:00.000Z',
|
|
12
|
+
claims: [
|
|
13
|
+
{ selection: selection('gpt-6-luna'), mappingDigest: digestA, eventDigest: digestB, source: 'codex-turn-context', observedAt: '2026-09-23T00:00:00.000Z', actualModel: 'unverified', tokenUsage: 'unknown' },
|
|
14
|
+
{ selection: selection('gpt-6-astra'), mappingDigest: digestB, eventDigest: digestC, source: 'codex-turn-context', observedAt: '2026-09-23T00:00:00.000Z', actualModel: 'unverified', tokenUsage: 'unknown' },
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
describe('event-scoped routing capabilities v2', () => {
|
|
18
|
+
const scope = { runtime: receipt().runtime, binaryDigest: digestB, configDigest: digestC };
|
|
19
|
+
const later = new Date('2026-09-24T01:00:00.000Z');
|
|
20
|
+
it('keeps an unchanged native claim current after 25 hours without renewal', () => {
|
|
21
|
+
expect((0, capabilities_v2_1.validateEventReceipt)(receipt())).toMatchObject({ schema: 'routing-capabilities/v2' });
|
|
22
|
+
expect((0, capabilities_v2_1.evaluateEventReceipt)(receipt(), scope, selection('gpt-6-luna'), digestA, later)).toEqual({ state: 'current' });
|
|
23
|
+
});
|
|
24
|
+
it('invalidates only a changed selection mapping, not its neighbor', () => {
|
|
25
|
+
expect((0, capabilities_v2_1.evaluateEventReceipt)(receipt(), scope, selection('gpt-6-luna'), digestC, later)).toEqual({ state: 'drift', reason: 'POLICY_DRIFT' });
|
|
26
|
+
expect((0, capabilities_v2_1.evaluateEventReceipt)(receipt(), scope, selection('gpt-6-astra'), digestB, later)).toEqual({ state: 'current' });
|
|
27
|
+
});
|
|
28
|
+
it('fails closed on runtime, account, binary and config drift', () => {
|
|
29
|
+
const value = receipt();
|
|
30
|
+
expect((0, capabilities_v2_1.evaluateEventReceipt)(value, { ...scope, runtime: { ...scope.runtime, version: '0.157.0' } }, selection('gpt-6-luna'), digestA, later)).toEqual({ state: 'drift', reason: 'RUNTIME_DRIFT' });
|
|
31
|
+
expect((0, capabilities_v2_1.evaluateEventReceipt)(value, { ...scope, runtime: { ...scope.runtime, accountScopeDigest: digestB } }, selection('gpt-6-luna'), digestA, later)).toEqual({ state: 'drift', reason: 'ACCOUNT_DRIFT' });
|
|
32
|
+
expect((0, capabilities_v2_1.evaluateEventReceipt)(value, { ...scope, binaryDigest: digestC }, selection('gpt-6-luna'), digestA, later)).toEqual({ state: 'drift', reason: 'RUNTIME_DRIFT' });
|
|
33
|
+
expect((0, capabilities_v2_1.evaluateEventReceipt)(value, { ...scope, configDigest: digestB }, selection('gpt-6-luna'), digestA, later)).toEqual({ state: 'drift', reason: 'CONFIG_DRIFT' });
|
|
34
|
+
});
|
|
35
|
+
it('rejects malformed proof, duplicate claims, future records and missing scope', () => {
|
|
36
|
+
expect(() => (0, capabilities_v2_1.validateEventReceipt)({ ...receipt(), claims: [receipt().claims[0], receipt().claims[0]] })).toThrow(/duplicate/i);
|
|
37
|
+
expect(() => (0, capabilities_v2_1.validateEventReceipt)({ ...receipt(), claims: [{ ...receipt().claims[0], eventDigest: 'pretend' }] })).toThrow(/digest/i);
|
|
38
|
+
expect((0, capabilities_v2_1.evaluateEventReceipt)({ ...receipt(), recordedAt: '2026-09-25T00:00:00.000Z' }, scope, selection('gpt-6-luna'), digestA, later)).toEqual({ state: 'drift', reason: 'FUTURE_EVIDENCE' });
|
|
39
|
+
expect(() => (0, capabilities_v2_1.evaluateEventReceipt)(receipt(), { ...scope, configDigest: '' }, selection('gpt-6-luna'), digestA, later)).toThrow(/configDigest/i);
|
|
40
|
+
expect(() => (0, capabilities_v2_1.validateEventReceipt)({ ...receipt(), runtime: { ...receipt().runtime, target: 'antigravity' }, claims: [{ ...receipt().claims[0], source: 'claude-assistant-transcript' }] })).toThrow(/provider|target/i);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
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 claude_hook_events_1 = require("../../../src/core/model-policy/claude-hook-events");
|
|
10
|
+
const machine_key_1 = require("../../../src/core/model-policy/machine-key");
|
|
11
|
+
describe('sealed Claude hook pair', () => {
|
|
12
|
+
const start = { hook_event_name: 'SubagentStart', session_id: 'session-1', agent_id: 'agent-1', agent_type: 'plugin:reviewer' };
|
|
13
|
+
const stop = { hook_event_name: 'SubagentStop', session_id: 'session-1', agent_id: 'agent-1', agent_type: 'plugin:reviewer',
|
|
14
|
+
agent_transcript_path: '/tmp/agent-1.jsonl', stop_hook_active: false };
|
|
15
|
+
it('links start/stop without storing prompt text, rejects tampering and stale pending events', () => {
|
|
16
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-claude-hook-'));
|
|
17
|
+
const previous = process.env.AWM_HOME;
|
|
18
|
+
process.env.AWM_HOME = root;
|
|
19
|
+
try {
|
|
20
|
+
(0, machine_key_1.ensureMachineKey)(root);
|
|
21
|
+
(0, claude_hook_events_1.recordClaudeHookStart)({ ...start, prompt: 'private prompt' }, new Date('2026-09-23T22:00:00.000Z'));
|
|
22
|
+
const file = path_1.default.join(root, 'claude-hook-starts', fs_1.default.readdirSync(path_1.default.join(root, 'claude-hook-starts'))[0]);
|
|
23
|
+
expect(fs_1.default.readFileSync(file, 'utf8')).not.toContain('private prompt');
|
|
24
|
+
const pair = (0, claude_hook_events_1.readClaudeHookPair)(stop, new Date('2026-09-23T22:01:00.000Z'));
|
|
25
|
+
expect(pair.lifecycle).toMatchObject({ sessionId: 'session-1', agentId: 'agent-1' });
|
|
26
|
+
expect(() => (0, claude_hook_events_1.readClaudeHookPair)(stop, new Date('2026-09-25T22:01:00.000Z'))).toThrow(/stale/i);
|
|
27
|
+
const envelope = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
28
|
+
envelope.start.agent_type = 'other';
|
|
29
|
+
fs_1.default.writeFileSync(file, JSON.stringify(envelope));
|
|
30
|
+
expect(() => (0, claude_hook_events_1.readClaudeHookPair)(stop, new Date('2026-09-23T22:01:00.000Z'))).toThrow(/seal/i);
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
if (previous === undefined)
|
|
34
|
+
delete process.env.AWM_HOME;
|
|
35
|
+
else
|
|
36
|
+
process.env.AWM_HOME = previous;
|
|
37
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
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 event_store_1 = require("../../../src/core/model-policy/event-store");
|
|
10
|
+
const machine_key_1 = require("../../../src/core/model-policy/machine-key");
|
|
11
|
+
const native_claude_1 = require("../../../src/core/model-policy/native-claude");
|
|
12
|
+
const native_claude_transcript_1 = require("../../../src/core/model-policy/native-claude-transcript");
|
|
13
|
+
const native_claude_agent_definition_1 = require("../../../src/core/model-policy/native-claude-agent-definition");
|
|
14
|
+
describe('Claude native event enrollment', () => {
|
|
15
|
+
it('binds a matching hook pair and transcript; keeps an unchanged claim beyond 24 hours', () => {
|
|
16
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-claude-event-'));
|
|
17
|
+
const previous = process.env.AWM_HOME;
|
|
18
|
+
process.env.AWM_HOME = root;
|
|
19
|
+
try {
|
|
20
|
+
(0, machine_key_1.ensureMachineKey)(root);
|
|
21
|
+
const configDir = path_1.default.join(root, 'claude');
|
|
22
|
+
const file = path_1.default.join(configDir, 'projects', 'p', 's', 'subagents', 'agent-agent-1.jsonl');
|
|
23
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
24
|
+
fs_1.default.writeFileSync(file, `${JSON.stringify({ type: 'assistant', timestamp: '2026-09-23T22:00:00.000Z', message: { model: 'claude-sonnet-4-6', content: [] } })}\n`);
|
|
25
|
+
fs_1.default.mkdirSync(path_1.default.join(configDir, 'agents'));
|
|
26
|
+
fs_1.default.writeFileSync(path_1.default.join(configDir, 'agents', 'worker.md'), '---\nname: awm-worker\ndescription: Test worker\nmodel: claude-sonnet-4-6\n---\n');
|
|
27
|
+
const start = { hook_event_name: 'SubagentStart', session_id: 'session-1', agent_id: 'agent-1', agent_type: 'awm-worker' };
|
|
28
|
+
const stop = { hook_event_name: 'SubagentStop', session_id: 'session-1', agent_id: 'agent-1', agent_type: 'awm-worker', agent_transcript_path: file, stop_hook_active: false };
|
|
29
|
+
const lifecycle = (0, native_claude_1.normalizeClaudeSubagentLifecycle)(start, stop);
|
|
30
|
+
const transcript = (0, native_claude_transcript_1.readClaudeSubagentTranscript)({ configDir, file, agentId: 'agent-1' });
|
|
31
|
+
const scope = { runtime: { target: 'claude-code', kind: 'native', version: '2.1.0', accountScopeDigest: 'a'.repeat(64) }, binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64) };
|
|
32
|
+
const expectedSelection = { selector: { kind: 'model', id: 'claude-sonnet-4-6' }, effort: { kind: 'runtime-default' } };
|
|
33
|
+
const input = { scope, lifecycle, transcript, expectedSelection, mappingDigest: 'd'.repeat(64), now: new Date('2026-09-23T22:01:00.000Z') };
|
|
34
|
+
expect(() => (0, event_store_1.publishClaudeObservation)(input)).toThrow(/declared agent/i);
|
|
35
|
+
const declaredAgent = (0, native_claude_agent_definition_1.readClaudeAgentDeclaredModel)({ cwd: root, configDir, agentType: 'awm-worker' });
|
|
36
|
+
const enrolled = { ...input, declaredAgent };
|
|
37
|
+
const first = (0, event_store_1.publishClaudeObservation)(enrolled);
|
|
38
|
+
expect(first.claims[0]).toMatchObject({ actualModel: { id: 'claude-sonnet-4-6' }, tokenUsage: 'unknown' });
|
|
39
|
+
expect((0, event_store_1.publishClaudeObservation)({ ...enrolled, now: new Date('2026-09-25T00:00:00.000Z') })).toEqual(first);
|
|
40
|
+
expect((0, event_store_1.readStoredEventReceipt)(scope.runtime)).toEqual({ state: 'present', receipt: first });
|
|
41
|
+
expect(() => (0, event_store_1.publishClaudeObservation)({ ...enrolled, expectedSelection: { ...expectedSelection, selector: { kind: 'model', id: 'claude-opus-4-7' } } })).toThrow(/selection/i);
|
|
42
|
+
expect(() => (0, event_store_1.publishClaudeObservation)({ ...enrolled, expectedSelection: { ...expectedSelection, effort: { kind: 'explicit', value: 'high' } } })).toThrow(/selection|effort/i);
|
|
43
|
+
expect(() => (0, event_store_1.publishClaudeObservation)({ ...enrolled, lifecycle: { ...lifecycle } })).toThrow(/provenance/i);
|
|
44
|
+
expect(() => (0, event_store_1.publishClaudeObservation)({ ...enrolled, declaredAgent: { ...declaredAgent } })).toThrow(/declared agent/i);
|
|
45
|
+
const secondFile = path_1.default.join(configDir, 'projects', 'p', 's', 'subagents', 'agent-agent-2.jsonl');
|
|
46
|
+
fs_1.default.writeFileSync(secondFile, `${JSON.stringify({ type: 'assistant', timestamp: '2026-09-23T22:02:00.000Z', message: { model: 'claude-sonnet-4-6', content: [] } })}\n`);
|
|
47
|
+
const secondLifecycle = (0, native_claude_1.normalizeClaudeSubagentLifecycle)({ ...start, agent_id: 'agent-2' }, { ...stop, agent_id: 'agent-2', agent_transcript_path: secondFile });
|
|
48
|
+
const secondTranscript = (0, native_claude_transcript_1.readClaudeSubagentTranscript)({ configDir, file: secondFile, agentId: 'agent-2' });
|
|
49
|
+
const second = (0, event_store_1.publishClaudeObservation)({ ...enrolled, lifecycle: secondLifecycle, transcript: secondTranscript,
|
|
50
|
+
now: new Date('2026-09-23T22:03:00.000Z') });
|
|
51
|
+
expect(second.claims[0].nativeEvents).toEqual(expect.arrayContaining([
|
|
52
|
+
expect.objectContaining({ nativeAgentIdDigest: (0, event_store_1.claudeAgentIdDigest)('agent-1', (0, machine_key_1.ensureMachineKey)(root)) }),
|
|
53
|
+
expect.objectContaining({ nativeAgentIdDigest: (0, event_store_1.claudeAgentIdDigest)('agent-2', (0, machine_key_1.ensureMachineKey)(root)) }),
|
|
54
|
+
]));
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
if (previous === undefined)
|
|
58
|
+
delete process.env.AWM_HOME;
|
|
59
|
+
else
|
|
60
|
+
process.env.AWM_HOME = previous;
|
|
61
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
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 event_store_1 = require("../../../src/core/model-policy/event-store");
|
|
10
|
+
const native_codex_1 = require("../../../src/core/model-policy/native-codex");
|
|
11
|
+
const machine_key_1 = require("../../../src/core/model-policy/machine-key");
|
|
12
|
+
const a = 'a'.repeat(64);
|
|
13
|
+
const b = 'b'.repeat(64);
|
|
14
|
+
const c = 'c'.repeat(64);
|
|
15
|
+
const runtime = { target: 'codex', kind: 'native', version: '0.156.1', accountScopeDigest: a };
|
|
16
|
+
const scope = { runtime, binaryDigest: b, configDigest: c };
|
|
17
|
+
const observation = (id) => ({ provenance: 'codex-app-server-thread-read', parentThreadId: 'parent-1', childThreadId: `child-${id}`, turnId: `turn-${id}`, nativeDispatchVerified: true,
|
|
18
|
+
configuredSelection: { selector: { kind: 'model', id }, effort: { kind: 'explicit', value: 'high' } }, acceptedSelectionVerified: true, childRuntimeVersion: '0.156.1', actualModelVerified: false, tokenUsage: 'unknown' });
|
|
19
|
+
const enrollment = (id, now, native = observation(id)) => ({ scope, observation: native, expectedSelection: observation(id).configuredSelection, mappingDigest: a, now });
|
|
20
|
+
async function nativeObservation(id) {
|
|
21
|
+
const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-native-event-'));
|
|
22
|
+
const file = path_1.default.join(home, 'sessions', 'rollout.jsonl');
|
|
23
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file));
|
|
24
|
+
fs_1.default.writeFileSync(file, [
|
|
25
|
+
{ type: 'session_meta', payload: { id: `child-${id}`, source: { subagent: { thread_spawn: { parent_thread_id: 'parent-1' } } }, cli_version: '0.156.1' } },
|
|
26
|
+
{ timestamp: '2026-09-23T00:59:00.000Z', type: 'turn_context', payload: { turn_id: `turn-${id}`, model: id, effort: 'high' } },
|
|
27
|
+
].map(value => JSON.stringify(value)).join('\n'));
|
|
28
|
+
const response = { thread: { id: `child-${id}`, parentThreadId: 'parent-1', model: id, reasoningEffort: 'high', path: file,
|
|
29
|
+
turns: [{ id: `turn-${id}`, status: 'completed', items: [] }] } };
|
|
30
|
+
const server = `
|
|
31
|
+
const readline = require('readline');
|
|
32
|
+
const input = readline.createInterface({ input: process.stdin });
|
|
33
|
+
input.on('line', line => {
|
|
34
|
+
const request = JSON.parse(line);
|
|
35
|
+
if (request.method === 'initialize') process.stdout.write(JSON.stringify({ id: request.id, result: {} }) + '\\n');
|
|
36
|
+
if (request.method === 'thread/read') process.stdout.write(JSON.stringify({ id: request.id, result: ${JSON.stringify(response)} }) + '\\n');
|
|
37
|
+
});`;
|
|
38
|
+
try {
|
|
39
|
+
return await (0, native_codex_1.queryCodexChildThreadObservation)({ command: process.execPath, args: ['-e', server], timeoutMs: 5000, parentThreadId: 'parent-1', childThreadId: `child-${id}`, codexHome: home });
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
describe('event-scoped capability store', () => {
|
|
46
|
+
it('publishes native claims once and preserves a second selection without renewal', async () => {
|
|
47
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-event-store-'));
|
|
48
|
+
const previous = process.env.AWM_HOME;
|
|
49
|
+
process.env.AWM_HOME = root;
|
|
50
|
+
try {
|
|
51
|
+
expect((0, event_store_1.readStoredEventReceipt)(runtime)).toEqual({ state: 'absent' });
|
|
52
|
+
(0, machine_key_1.ensureMachineKey)(root);
|
|
53
|
+
const luna = await nativeObservation('gpt-6-luna');
|
|
54
|
+
const first = (0, event_store_1.publishCodexObservation)(enrollment('gpt-6-luna', new Date('2026-09-23T01:00:00.000Z'), luna));
|
|
55
|
+
expect(first.claims).toHaveLength(1);
|
|
56
|
+
const file = path_1.default.join(root, 'routing-capabilities-v2', 'codex', 'native.json');
|
|
57
|
+
const before = fs_1.default.readFileSync(file);
|
|
58
|
+
(0, event_store_1.publishCodexObservation)(enrollment('gpt-6-luna', new Date('2026-09-24T03:00:00.000Z'), luna));
|
|
59
|
+
expect(fs_1.default.readFileSync(file)).toEqual(before);
|
|
60
|
+
const second = (0, event_store_1.publishCodexObservation)(enrollment('gpt-6-sol', new Date('2026-09-23T01:00:00.000Z'), await nativeObservation('gpt-6-sol')));
|
|
61
|
+
expect(second.claims).toHaveLength(2);
|
|
62
|
+
const read = (0, event_store_1.readStoredEventReceipt)(runtime);
|
|
63
|
+
expect(read.state).toBe('present');
|
|
64
|
+
if (read.state !== 'present')
|
|
65
|
+
throw new Error('expected a present receipt');
|
|
66
|
+
expect(read.receipt.claims.map(item => item.selection.selector.id)).toEqual(['gpt-6-luna', 'gpt-6-sol']);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
if (previous === undefined)
|
|
70
|
+
delete process.env.AWM_HOME;
|
|
71
|
+
else
|
|
72
|
+
process.env.AWM_HOME = previous;
|
|
73
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
it('fails closed on a symlinked destination', async () => {
|
|
77
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-event-store-'));
|
|
78
|
+
const previous = process.env.AWM_HOME;
|
|
79
|
+
process.env.AWM_HOME = root;
|
|
80
|
+
const file = path_1.default.join(root, 'routing-capabilities-v2', 'codex', 'native.json');
|
|
81
|
+
(0, machine_key_1.ensureMachineKey)(root);
|
|
82
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
83
|
+
const outside = path_1.default.join(root, 'outside.json');
|
|
84
|
+
fs_1.default.writeFileSync(outside, 'untouched');
|
|
85
|
+
fs_1.default.symlinkSync(outside, file);
|
|
86
|
+
try {
|
|
87
|
+
const native = await nativeObservation('gpt-6-luna');
|
|
88
|
+
expect(() => (0, event_store_1.publishCodexObservation)(enrollment('gpt-6-luna', new Date('2026-09-23T01:00:00.000Z'), native))).toThrow(/symlink|unsafe/i);
|
|
89
|
+
expect(fs_1.default.readFileSync(outside, 'utf8')).toBe('untouched');
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
if (previous === undefined)
|
|
93
|
+
delete process.env.AWM_HOME;
|
|
94
|
+
else
|
|
95
|
+
process.env.AWM_HOME = previous;
|
|
96
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
it('refuses an unverified selection or a child from another runtime version', async () => {
|
|
100
|
+
const native = await nativeObservation('gpt-6-luna');
|
|
101
|
+
native.acceptedSelectionVerified = false;
|
|
102
|
+
expect(() => (0, event_store_1.publishCodexObservation)(enrollment('gpt-6-luna', new Date('2026-09-23T01:00:00.000Z'), native))).toThrow(/native provenance/i);
|
|
103
|
+
native.acceptedSelectionVerified = true;
|
|
104
|
+
native.childRuntimeVersion = '0.156.0';
|
|
105
|
+
expect(() => (0, event_store_1.publishCodexObservation)(enrollment('gpt-6-luna', new Date('2026-09-23T01:00:00.000Z'), native))).toThrow(/native provenance/i);
|
|
106
|
+
});
|
|
107
|
+
it('rejects a caller-authored accepted-looking observation without native provenance', () => {
|
|
108
|
+
expect(() => (0, event_store_1.publishCodexObservation)(enrollment('gpt-6-luna', new Date('2026-09-23T01:00:00.000Z')))).toThrow(/native provenance/i);
|
|
109
|
+
});
|
|
110
|
+
it('rejects an old native turn for a new enrollment but retains an already stored claim after 25 hours', async () => {
|
|
111
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-event-store-'));
|
|
112
|
+
const previous = process.env.AWM_HOME;
|
|
113
|
+
process.env.AWM_HOME = root;
|
|
114
|
+
try {
|
|
115
|
+
(0, machine_key_1.ensureMachineKey)(root);
|
|
116
|
+
const native = await nativeObservation('gpt-6-luna');
|
|
117
|
+
expect(() => (0, event_store_1.publishCodexObservation)(enrollment('gpt-6-luna', new Date('2026-09-24T03:00:00.000Z'), native))).toThrow(/fresh/i);
|
|
118
|
+
const first = (0, event_store_1.publishCodexObservation)(enrollment('gpt-6-luna', new Date('2026-09-23T01:00:00.000Z'), native));
|
|
119
|
+
const later = (0, event_store_1.publishCodexObservation)(enrollment('gpt-6-luna', new Date('2026-09-24T03:00:00.000Z'), native));
|
|
120
|
+
expect(later).toEqual(first);
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
if (previous === undefined)
|
|
124
|
+
delete process.env.AWM_HOME;
|
|
125
|
+
else
|
|
126
|
+
process.env.AWM_HOME = previous;
|
|
127
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
it('rejects a forged on-disk claim even if its JSON shape and digests look valid', async () => {
|
|
131
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-event-store-'));
|
|
132
|
+
const previous = process.env.AWM_HOME;
|
|
133
|
+
process.env.AWM_HOME = root;
|
|
134
|
+
try {
|
|
135
|
+
(0, machine_key_1.ensureMachineKey)(root);
|
|
136
|
+
(0, event_store_1.publishCodexObservation)(enrollment('gpt-6-luna', new Date('2026-09-23T01:00:00.000Z'), await nativeObservation('gpt-6-luna')));
|
|
137
|
+
const file = path_1.default.join(root, 'routing-capabilities-v2', 'codex', 'native.json');
|
|
138
|
+
const envelope = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
139
|
+
envelope.receipt.claims[0].selection.selector.id = 'gpt-6-astra';
|
|
140
|
+
fs_1.default.writeFileSync(file, JSON.stringify(envelope));
|
|
141
|
+
expect((0, event_store_1.readStoredEventReceipt)(runtime)).toMatchObject({ state: 'invalid' });
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
if (previous === undefined)
|
|
145
|
+
delete process.env.AWM_HOME;
|
|
146
|
+
else
|
|
147
|
+
process.env.AWM_HOME = previous;
|
|
148
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
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 hook_diagnostics_1 = require("../../../src/core/model-policy/hook-diagnostics");
|
|
10
|
+
describe('durable Claude hook diagnostics', () => {
|
|
11
|
+
it('alerts once per unresolved cause and retains a named failure until native capture recovers', () => {
|
|
12
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-hook-diagnostic-'));
|
|
13
|
+
const previous = process.env.AWM_HOME;
|
|
14
|
+
process.env.AWM_HOME = root;
|
|
15
|
+
try {
|
|
16
|
+
expect((0, hook_diagnostics_1.readRoutingHookStatus)()).toEqual({ state: 'absent' });
|
|
17
|
+
expect((0, hook_diagnostics_1.recordRoutingHookStatus)('stop', 'HOOK_TRANSCRIPT_UNVERIFIED', new Date('2026-09-23T01:00:00.000Z'))).toBe(true);
|
|
18
|
+
expect((0, hook_diagnostics_1.recordRoutingHookStatus)('stop', 'HOOK_TRANSCRIPT_UNVERIFIED', new Date('2026-09-23T01:01:00.000Z'))).toBe(false);
|
|
19
|
+
expect((0, hook_diagnostics_1.readRoutingHookStatus)()).toEqual({ state: 'failed', failures: [{ phase: 'stop', reasonCode: 'HOOK_TRANSCRIPT_UNVERIFIED', count: 2 }] });
|
|
20
|
+
expect((0, hook_diagnostics_1.recordRoutingHookStatus)('stop', 'success', new Date('2026-09-23T01:02:00.000Z'))).toBe(false);
|
|
21
|
+
expect((0, hook_diagnostics_1.readRoutingHookStatus)()).toEqual({ state: 'healthy' });
|
|
22
|
+
const file = path_1.default.join(root, 'routing-hook-status.json');
|
|
23
|
+
fs_1.default.writeFileSync(file, '{"schema":"routing-hook-status/v1","start":{},"stop":{}}');
|
|
24
|
+
expect((0, hook_diagnostics_1.readRoutingHookStatus)()).toEqual({ state: 'invalid' });
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
if (previous === undefined)
|
|
28
|
+
delete process.env.AWM_HOME;
|
|
29
|
+
else
|
|
30
|
+
process.env.AWM_HOME = previous;
|
|
31
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|