agentic-workflow-manager 9.11.12 → 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 -9
- package/dist/src/commands/preflight/checks.js +18 -0
- package/dist/src/commands/watch/apply.js +62 -4
- 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 +113 -18
- 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 +96 -6
- 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 +4 -3
- 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/preflight/preflight.test.js +65 -0
- package/dist/tests/commands/watch/apply.test.js +84 -0
- 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 +112 -1
- 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/setup-readiness.test.js +83 -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
|
@@ -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
|
+
});
|
|
@@ -4,6 +4,8 @@ const types_1 = require("../../../src/core/journal/types");
|
|
|
4
4
|
const journal_1 = require("../../../src/core/model-policy/journal");
|
|
5
5
|
const capabilities_1 = require("../../../src/core/model-policy/capabilities");
|
|
6
6
|
const routing_approval_1 = require("../../helpers/routing-approval");
|
|
7
|
+
const selection_policy_digest_1 = require("../../../src/core/model-policy/selection-policy-digest");
|
|
8
|
+
const resolve_1 = require("../../../src/core/model-policy/resolve");
|
|
7
9
|
describe('routing journal helpers', () => {
|
|
8
10
|
it('reserves one idempotent attempt and records native observation', () => {
|
|
9
11
|
const state = (0, types_1.emptyState)('main');
|
|
@@ -13,6 +15,73 @@ describe('routing journal helpers', () => {
|
|
|
13
15
|
const observed = (0, journal_1.observeRoutingAttempt)(replay.state, { attemptId: reserved.attemptId, nativeAgentId: 'native-1' }, '2026-09-17T00:02:00.000Z');
|
|
14
16
|
expect(observed.routingAttempts?.[0]).toMatchObject({ state: 'active', nativeAgentId: 'native-1' });
|
|
15
17
|
});
|
|
18
|
+
it('persists a mismatch as one alertable blocked incident without renewing capability', () => {
|
|
19
|
+
const state = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope: routing_approval_1.routingEnvelope,
|
|
20
|
+
fingerprint: 'e'.repeat(64), approval: routing_approval_1.routingApproval }, '2026-09-17T00:00:00.000Z').state;
|
|
21
|
+
const attemptId = state.routingAttempts[0].id;
|
|
22
|
+
const mismatched = (0, journal_1.observeRoutingAttempt)(state, { attemptId, nativeAgentId: 'native-1', observed: { ...(0, routing_approval_1.routingSelection)('high'), selector: { kind: 'model', id: 'different' } } }, '2026-09-17T00:02:00.000Z');
|
|
23
|
+
expect(mismatched.routingAttempts?.[0]).toMatchObject({ state: 'blocked', verdict: 'inconclusive', reasonCode: 'SELECTION_MISMATCH' });
|
|
24
|
+
expect(mismatched.routingIncidents).toMatchObject([{ reasonCode: 'SELECTION_MISMATCH', blockedCount: 1, fallbackCount: 0, alertState: 'pending' }]);
|
|
25
|
+
expect((0, journal_1.observeRoutingAttempt)(mismatched, { attemptId, nativeAgentId: 'native-1', observed: { ...(0, routing_approval_1.routingSelection)('high'), selector: { kind: 'model', id: 'different' } } }, '2026-09-17T00:03:00.000Z')).toEqual(mismatched);
|
|
26
|
+
expect(state.routingIncidents).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
it('persists missing native provenance as a blocked incident, not an active optimized attempt', () => {
|
|
29
|
+
const state = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope: routing_approval_1.routingEnvelope,
|
|
30
|
+
fingerprint: 'e'.repeat(64), approval: routing_approval_1.routingApproval }, '2026-09-17T00:00:00.000Z').state;
|
|
31
|
+
const attemptId = state.routingAttempts[0].id;
|
|
32
|
+
const missing = (0, journal_1.observeRoutingAttempt)(state, { attemptId, nativeAgentId: 'native-1', unavailableReason: 'PROVENANCE_MISSING' }, '2026-09-17T00:02:00.000Z');
|
|
33
|
+
expect(missing.routingAttempts?.[0]).toMatchObject({ state: 'blocked', verdict: 'inconclusive', reasonCode: 'PROVENANCE_MISSING' });
|
|
34
|
+
expect(missing.routingIncidents).toMatchObject([{ reasonCode: 'PROVENANCE_MISSING', blockedCount: 1, alertState: 'pending' }]);
|
|
35
|
+
});
|
|
36
|
+
it('reserves a separately proved full fallback and records why optimization was skipped', () => {
|
|
37
|
+
const approved = (0, routing_approval_1.routingApproval)();
|
|
38
|
+
approved.policy.content.mappings[0].degradation.allowMissingObservedIdentity = true;
|
|
39
|
+
const eventScope = { runtime: routing_approval_1.routingEnvelope.runtime, binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64) };
|
|
40
|
+
const full = approved.policy.content.mappings[0].fullCapability;
|
|
41
|
+
const eventReceipt = { schema: 'routing-capabilities/v2', ...eventScope, recordedAt: '2026-09-17T00:00:00.000Z', claims: [{ selection: full,
|
|
42
|
+
mappingDigest: (0, selection_policy_digest_1.selectionPolicyDigest)(approved.policy.content.mappings[0], full), eventDigest: 'e'.repeat(64), source: 'codex-turn-context',
|
|
43
|
+
observedAt: '2026-09-17T00:00:00.000Z', actualModel: 'unverified', tokenUsage: 'unknown' }] };
|
|
44
|
+
const result = (0, resolve_1.resolveEventWithFallback)({ role: 'implementer', requestedProfile: 'mechanical', policy: approved.policy, receipt: eventReceipt, scope: eventScope, now: approved.checkedAt });
|
|
45
|
+
if (result.state !== 'resolved')
|
|
46
|
+
throw new Error('full fallback must resolve');
|
|
47
|
+
const routed = { ...routing_approval_1.routingEnvelope, effectiveProfile: 'full', resolved: full, outcome: result.outcome, unavailableEvidence: result.unavailableEvidence,
|
|
48
|
+
capabilityDigest: result.capabilityDigest };
|
|
49
|
+
const reserved = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope: routed,
|
|
50
|
+
fingerprint: 'f'.repeat(64), approval: () => ({ policy: approved.policy, eventReceipt, eventScope, checkedAt: approved.checkedAt }) }, '2026-09-17T00:00:00.000Z');
|
|
51
|
+
expect(reserved.state.routingAttempts?.[0]).toMatchObject({ state: 'reserved', reasonCode: 'ROUTING_SELECTION_UNENROLLED', envelope: { effectiveProfile: 'full' } });
|
|
52
|
+
expect((0, journal_1.routingReport)(reserved.state)).toMatchObject({ fallbacks: 1, unavailable: { observedModelEvidence: 1, ROUTING_SELECTION_UNENROLLED: 1 } });
|
|
53
|
+
expect(reserved.state.routingIncidents).toMatchObject([{ reasonCode: 'ROUTING_SELECTION_UNENROLLED', affectedObligations: 1, fallbackCount: 1, blockedCount: 0, alertState: 'pending' }]);
|
|
54
|
+
expect((0, journal_1.routingReport)(reserved.state)).toMatchObject({ incidents: [expect.objectContaining({ reasonCode: 'ROUTING_SELECTION_UNENROLLED', fallbackCount: 1, alertState: 'pending' })] });
|
|
55
|
+
});
|
|
56
|
+
it('reserves verified full capability after an optimized provider rejection in the same lineage', () => {
|
|
57
|
+
const approved = (0, routing_approval_1.routingApproval)();
|
|
58
|
+
approved.policy.content.mappings[0].degradation.allowMissingObservedIdentity = true;
|
|
59
|
+
const mapping = approved.policy.content.mappings[0];
|
|
60
|
+
const eventScope = { runtime: routing_approval_1.routingEnvelope.runtime, binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64) };
|
|
61
|
+
const eventReceipt = { schema: 'routing-capabilities/v2', ...eventScope, recordedAt: '2026-09-17T00:00:00.000Z', claims: [mapping.profiles.mechanical, mapping.fullCapability].map(selected => ({ selection: selected,
|
|
62
|
+
mappingDigest: (0, selection_policy_digest_1.selectionPolicyDigest)(mapping, selected), eventDigest: 'e'.repeat(64), source: 'codex-turn-context',
|
|
63
|
+
observedAt: '2026-09-17T00:00:00.000Z', actualModel: 'unverified', tokenUsage: 'unknown' })) };
|
|
64
|
+
const approvalSnapshot = () => ({ policy: approved.policy, eventReceipt, eventScope, checkedAt: approved.checkedAt });
|
|
65
|
+
const preferred = (0, resolve_1.resolveEventWithFallback)({ role: 'implementer', requestedProfile: 'mechanical', policy: approved.policy,
|
|
66
|
+
receipt: eventReceipt, scope: eventScope, now: approved.checkedAt });
|
|
67
|
+
if (preferred.state !== 'resolved')
|
|
68
|
+
throw new Error('preferred selection should be enrolled');
|
|
69
|
+
const firstEnvelope = { ...routing_approval_1.routingEnvelope, outcome: preferred.outcome, unavailableEvidence: preferred.unavailableEvidence, capabilityDigest: preferred.capabilityDigest };
|
|
70
|
+
const first = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope: firstEnvelope,
|
|
71
|
+
fingerprint: 'e'.repeat(64), approval: approvalSnapshot }, '2026-09-17T12:00:00.000Z');
|
|
72
|
+
expect(first.state.routingAttempts?.[0]).toMatchObject({ nativeEvidenceRequired: true, reservedAt: '2026-09-17T12:00:00.000Z' });
|
|
73
|
+
const rejected = (0, journal_1.observeRoutingAttempt)(first.state, { attemptId: first.attemptId, nativeAgentId: 'native-1', unavailableReason: 'PROVIDER_REJECTED' }, '2026-09-17T12:01:00.000Z');
|
|
74
|
+
const fallback = (0, resolve_1.resolveEventWithFallback)({ role: 'implementer', requestedProfile: 'mechanical', policy: approved.policy,
|
|
75
|
+
receipt: eventReceipt, scope: eventScope, now: approved.checkedAt, circuitReason: 'PROVIDER_REJECTED' });
|
|
76
|
+
if (fallback.state !== 'resolved')
|
|
77
|
+
throw new Error('full capability should be enrolled');
|
|
78
|
+
const fullEnvelope = { ...firstEnvelope, effectiveProfile: 'full', resolved: fallback.selection, outcome: fallback.outcome,
|
|
79
|
+
unavailableEvidence: fallback.unavailableEvidence, capabilityDigest: fallback.capabilityDigest };
|
|
80
|
+
const second = (0, journal_1.reserveRoutingAttempt)(rejected, { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope: fullEnvelope,
|
|
81
|
+
fingerprint: 'f'.repeat(64), approval: approvalSnapshot }, '2026-09-17T12:02:00.000Z');
|
|
82
|
+
expect(second.state.routingAttempts?.map(item => item.state)).toEqual(['blocked', 'reserved']);
|
|
83
|
+
expect((0, journal_1.routingReport)(second.state)).toMatchObject({ fallbacks: 1, incidents: [expect.objectContaining({ reasonCode: 'PROVIDER_REJECTED', affectedObligations: 1, fallbackCount: 1, blockedCount: 1 })] });
|
|
84
|
+
});
|
|
16
85
|
it('reserves an initial mechanical attempt at its approved explicit high effort', () => {
|
|
17
86
|
const high = { ...routing_approval_1.routingEnvelope, resolved: { ...routing_approval_1.routingEnvelope.resolved, effort: { kind: 'explicit', value: 'high' } } };
|
|
18
87
|
expect(() => (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope: high, fingerprint: 'e'.repeat(64) }, '2026-09-17T00:00:00.000Z')).toThrow(/approval/i);
|
|
@@ -65,7 +134,30 @@ describe('routing journal helpers', () => {
|
|
|
65
134
|
});
|
|
66
135
|
it('reports routing attempts without envelope bodies or native identities', () => {
|
|
67
136
|
const state = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope: routing_approval_1.routingEnvelope, fingerprint: 'e'.repeat(64), approval: routing_approval_1.routingApproval }, '2026-09-17T00:00:00.000Z').state;
|
|
68
|
-
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
|
|
137
|
+
expect((0, journal_1.routingReport)(state)).toEqual({ schema: 'routing-report/v1', attempts: 1, plannedByRole: { implementer: 1 }, actualByRole: {}, selections: [{ role: 'implementer', configured: routing_approval_1.routingEnvelope.resolved, accepted: 'unknown', backendModel: 'unknown' }], byState: { reserved: 1 }, retries: 0, fallbacks: 0, unavailable: {}, verdicts: {}, administrativeRepairs: 0,
|
|
138
|
+
measurement: { configuredSelection: 'envelope-recorded', acceptedSelection: 'not-natively-reconciled', backendModel: 'unknown', tokenUsage: 'unknown', savings: 'unverified' } });
|
|
139
|
+
});
|
|
140
|
+
it('reports a sealed native reconciliation separately from backend identity and savings', () => {
|
|
141
|
+
const state = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope: routing_approval_1.routingEnvelope,
|
|
142
|
+
fingerprint: 'e'.repeat(64), approval: routing_approval_1.routingApproval }, '2026-09-17T00:00:00.000Z').state;
|
|
143
|
+
state.routingAttempts[0].nativeEventDigest = 'f'.repeat(64);
|
|
144
|
+
state.routingAttempts[0].nativeAgentId = 'child-1';
|
|
145
|
+
state.routingAttempts[0].observed = routing_approval_1.routingEnvelope.resolved;
|
|
146
|
+
state.routingAttempts[0].state = 'active';
|
|
147
|
+
expect((0, journal_1.routingReport)(state)).toMatchObject({ nativeReconciledByRole: { implementer: 1 },
|
|
148
|
+
selections: [{ role: 'implementer', configured: routing_approval_1.routingEnvelope.resolved, accepted: routing_approval_1.routingEnvelope.resolved, backendModel: 'unknown' }],
|
|
149
|
+
measurement: { acceptedSelection: 'natively-reconciled', backendModel: 'unknown', tokenUsage: 'unknown', savings: 'unverified' } });
|
|
150
|
+
});
|
|
151
|
+
it('reports a provider-reported Claude model only after native reconciliation', () => {
|
|
152
|
+
const state = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'lineage:S1', envelope: routing_approval_1.routingEnvelope,
|
|
153
|
+
fingerprint: 'e'.repeat(64), approval: routing_approval_1.routingApproval }, '2026-09-17T00:00:00.000Z').state;
|
|
154
|
+
state.routingAttempts[0].nativeEventDigest = 'f'.repeat(64);
|
|
155
|
+
state.routingAttempts[0].nativeAgentId = 'child-1';
|
|
156
|
+
state.routingAttempts[0].observed = routing_approval_1.routingEnvelope.resolved;
|
|
157
|
+
state.routingAttempts[0].observedBackendModel = 'claude-example-model';
|
|
158
|
+
state.routingAttempts[0].state = 'active';
|
|
159
|
+
expect((0, journal_1.routingReport)(state)).toMatchObject({ selections: [{ backendModel: 'claude-example-model' }],
|
|
160
|
+
measurement: { backendModel: 'provider-reported' } });
|
|
69
161
|
});
|
|
70
162
|
it('uses the exact deterministic implementation escalation sequence', () => {
|
|
71
163
|
let state = (0, types_1.emptyState)('main');
|
|
@@ -81,6 +173,25 @@ describe('routing journal helpers', () => {
|
|
|
81
173
|
state.routingAttempts[2].envelope = { ...state.routingAttempts[2].envelope, resolved: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'high' } } };
|
|
82
174
|
expect(() => (0, journal_1.resolveLineageEscalation)(state, 'l1', 'mechanical')).toThrow(/exhausted/i);
|
|
83
175
|
});
|
|
176
|
+
it('returns the initial route for a native rejection so the full fallback can resolve', () => {
|
|
177
|
+
const state = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'l1', envelope: routing_approval_1.routingEnvelope,
|
|
178
|
+
fingerprint: '1'.repeat(64), approval: routing_approval_1.routingApproval }, '2026-09-17T00:00:00.000Z').state;
|
|
179
|
+
state.routingAttempts[0].state = 'blocked';
|
|
180
|
+
state.routingAttempts[0].verdict = 'inconclusive';
|
|
181
|
+
state.routingAttempts[0].reasonCode = 'PROVIDER_REJECTED';
|
|
182
|
+
expect((0, journal_1.resolveLineageEscalation)(state, 'l1', 'mechanical')).toEqual({ profile: 'mechanical', effort: 'medium' });
|
|
183
|
+
state.routingAttempts[0].envelope.effectiveProfile = 'full';
|
|
184
|
+
expect(() => (0, journal_1.resolveLineageEscalation)(state, 'l1', 'mechanical')).toThrow(/fallback|exhausted/i);
|
|
185
|
+
});
|
|
186
|
+
it('preserves Claude runtime-default effort across profile escalation', () => {
|
|
187
|
+
const state = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'impl:S1', lineageId: 'l1', envelope: routing_approval_1.routingEnvelope,
|
|
188
|
+
fingerprint: '1'.repeat(64), approval: routing_approval_1.routingApproval }, '2026-09-17T00:00:00.000Z').state;
|
|
189
|
+
state.implementationLineages[0].initialEffort = 'runtime-default';
|
|
190
|
+
state.routingAttempts[0].envelope.resolved.effort = { kind: 'runtime-default' };
|
|
191
|
+
state.routingAttempts[0].state = 'blocked';
|
|
192
|
+
state.routingAttempts[0].verdict = 'fail';
|
|
193
|
+
expect((0, journal_1.resolveLineageEscalation)(state, 'l1', 'mechanical')).toEqual({ profile: 'integration', effort: 'runtime-default' });
|
|
194
|
+
});
|
|
84
195
|
it('rejects hostile roles and unavailable provenance before it can reach a report', () => {
|
|
85
196
|
expect((0, types_1.isRoutingEnvelope)({ ...routing_approval_1.routingEnvelope, role: 'attacker-key' })).toBe(false);
|
|
86
197
|
expect((0, types_1.isRoutingEnvelope)({ ...routing_approval_1.routingEnvelope, unavailableEvidence: ['secret=leak'] })).toBe(false);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const local_claude_scope_1 = require("../../../src/core/model-policy/local-claude-scope");
|
|
4
|
+
describe('Claude local scope', () => {
|
|
5
|
+
const key = Buffer.alloc(32, 2);
|
|
6
|
+
const deps = {
|
|
7
|
+
readRoutingHookStatus: () => ({ state: 'absent' }),
|
|
8
|
+
readMachineKey: () => key,
|
|
9
|
+
queryClaudeAuthStatus: async () => ({ provenance: 'claude-auth-status', accountState: 'identified',
|
|
10
|
+
accountScopeDigest: 'a'.repeat(64), inferenceDispatched: false }),
|
|
11
|
+
queryRuntimeExecutable: async () => ({ version: '2.1.0', binaryDigest: 'b'.repeat(64), inferenceDispatched: false }),
|
|
12
|
+
readClaudeModelConfigDigest: () => 'c'.repeat(64),
|
|
13
|
+
};
|
|
14
|
+
it('assembles native account/runtime and local model config without inference', async () => {
|
|
15
|
+
await expect((0, local_claude_scope_1.queryLocalClaudeScope)('/tmp/project', 'native', deps)).resolves.toEqual({ state: 'current', scope: {
|
|
16
|
+
runtime: { target: 'claude-code', kind: 'native', version: '2.1.0', accountScopeDigest: 'a'.repeat(64) },
|
|
17
|
+
binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64),
|
|
18
|
+
} });
|
|
19
|
+
});
|
|
20
|
+
it('fails closed without machine key or stable account identity', async () => {
|
|
21
|
+
await expect((0, local_claude_scope_1.queryLocalClaudeScope)('/tmp/project', 'native', { ...deps, readMachineKey: () => null })).resolves.toEqual({ state: 'unverified', reason: 'MACHINE_KEY_MISSING' });
|
|
22
|
+
await expect((0, local_claude_scope_1.queryLocalClaudeScope)('/tmp/project', 'native', { ...deps, queryClaudeAuthStatus: async () => ({
|
|
23
|
+
provenance: 'claude-auth-status', accountState: 'unverified', accountScopeDigest: null, inferenceDispatched: false,
|
|
24
|
+
}) })).resolves.toEqual({ state: 'unverified', reason: 'ACCOUNT_UNVERIFIED' });
|
|
25
|
+
});
|
|
26
|
+
it('blocks dispatch after a hook failure while allowing a real native stop to recover', async () => {
|
|
27
|
+
const failed = { ...deps, readRoutingHookStatus: () => ({ state: 'failed', failures: [{ phase: 'stop', reasonCode: 'HOOK_TRANSCRIPT_UNVERIFIED', count: 1 }] }) };
|
|
28
|
+
await expect((0, local_claude_scope_1.queryLocalClaudeScope)('/tmp/project', 'native', failed)).resolves.toEqual({ state: 'unverified', reason: 'HOOK_CAPTURE_FAILED' });
|
|
29
|
+
await expect((0, local_claude_scope_1.queryLocalClaudeScope)('/tmp/project', 'native', { ...failed, requireHookHealthy: false })).resolves.toMatchObject({ state: 'current' });
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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 machine_key_1 = require("../../../src/core/model-policy/machine-key");
|
|
10
|
+
describe('routing machine key', () => {
|
|
11
|
+
it('creates a stable private key only during explicit setup', () => {
|
|
12
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-machine-key-'));
|
|
13
|
+
try {
|
|
14
|
+
expect((0, machine_key_1.readMachineKey)(root)).toBeNull();
|
|
15
|
+
const first = (0, machine_key_1.ensureMachineKey)(root);
|
|
16
|
+
expect(first).toHaveLength(32);
|
|
17
|
+
expect((0, machine_key_1.ensureMachineKey)(root)).toEqual(first);
|
|
18
|
+
expect((0, machine_key_1.readMachineKey)(root)).toEqual(first);
|
|
19
|
+
if (process.platform !== 'win32')
|
|
20
|
+
expect(fs_1.default.statSync(path_1.default.join(root, 'routing-machine.key')).mode & 0o777).toBe(0o600);
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
it('rejects a symlinked key without reading its target', () => {
|
|
27
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-machine-key-'));
|
|
28
|
+
const outside = path_1.default.join(root, 'outside');
|
|
29
|
+
fs_1.default.writeFileSync(outside, Buffer.alloc(32, 9));
|
|
30
|
+
fs_1.default.symlinkSync(outside, path_1.default.join(root, 'routing-machine.key'));
|
|
31
|
+
try {
|
|
32
|
+
expect(() => (0, machine_key_1.ensureMachineKey)(root)).toThrow(/symlink|unsafe/i);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
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 native_claude_agent_definition_1 = require("../../../src/core/model-policy/native-claude-agent-definition");
|
|
10
|
+
describe('Claude custom-agent declared model', () => {
|
|
11
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-claude-agent-'));
|
|
12
|
+
const cwd = path_1.default.join(root, 'project');
|
|
13
|
+
const configDir = path_1.default.join(root, 'config');
|
|
14
|
+
const directory = path_1.default.join(configDir, 'agents');
|
|
15
|
+
beforeAll(() => { fs_1.default.mkdirSync(cwd); fs_1.default.mkdirSync(directory, { recursive: true }); });
|
|
16
|
+
afterAll(() => fs_1.default.rmSync(root, { recursive: true, force: true }));
|
|
17
|
+
afterEach(() => { for (const name of fs_1.default.readdirSync(directory))
|
|
18
|
+
fs_1.default.unlinkSync(path_1.default.join(directory, name)); });
|
|
19
|
+
it('requires an explicit full ID on the matching native agent type', () => {
|
|
20
|
+
fs_1.default.writeFileSync(path_1.default.join(directory, 'integration.md'), '---\nname: awm-integration\ndescription: work\nmodel: claude-sonnet-4-6\n---\n');
|
|
21
|
+
expect((0, native_claude_agent_definition_1.readClaudeAgentDeclaredModel)({ cwd, configDir, agentType: 'awm-integration' })).toEqual({ agentType: 'awm-integration', modelId: 'claude-sonnet-4-6' });
|
|
22
|
+
expect(() => (0, native_claude_agent_definition_1.readClaudeAgentDeclaredModel)({ cwd, configDir, agentType: 'awm-missing' })).toThrow(/no explicit custom definition/);
|
|
23
|
+
});
|
|
24
|
+
it('rejects aliases, inherit, duplicate definitions and unsafe paths', () => {
|
|
25
|
+
fs_1.default.writeFileSync(path_1.default.join(directory, 'integration.md'), '---\nname: awm-integration\ndescription: work\nmodel: sonnet\n---\n');
|
|
26
|
+
expect(() => (0, native_claude_agent_definition_1.readClaudeAgentDeclaredModel)({ cwd, configDir, agentType: 'awm-integration' })).toThrow(/full model ID/);
|
|
27
|
+
fs_1.default.writeFileSync(path_1.default.join(directory, 'integration.md'), '---\nname: awm-integration\ndescription: work\nmodel: claude-sonnet-4-6\n---\n');
|
|
28
|
+
fs_1.default.writeFileSync(path_1.default.join(directory, 'duplicate.md'), '---\nname: awm-integration\ndescription: work\nmodel: claude-sonnet-4-6\n---\n');
|
|
29
|
+
expect(() => (0, native_claude_agent_definition_1.readClaudeAgentDeclaredModel)({ cwd, configDir, agentType: 'awm-integration' })).toThrow(/ambiguous/);
|
|
30
|
+
});
|
|
31
|
+
});
|