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,53 @@
|
|
|
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_config_1 = require("../../../src/core/model-policy/native-claude-config");
|
|
10
|
+
describe('Claude local model config fingerprint', () => {
|
|
11
|
+
it('changes only when model-relevant settings change and never returns settings content', () => {
|
|
12
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-claude-config-'));
|
|
13
|
+
const configDir = path_1.default.join(root, 'home');
|
|
14
|
+
const cwd = path_1.default.join(root, 'project');
|
|
15
|
+
fs_1.default.mkdirSync(configDir);
|
|
16
|
+
fs_1.default.mkdirSync(path_1.default.join(cwd, '.claude'), { recursive: true });
|
|
17
|
+
const settings = path_1.default.join(cwd, '.claude', 'settings.json');
|
|
18
|
+
fs_1.default.writeFileSync(settings, JSON.stringify({ model: 'claude-sonnet-4-6', permissions: { allow: ['Read'] } }));
|
|
19
|
+
const key = Buffer.alloc(32, 7);
|
|
20
|
+
try {
|
|
21
|
+
const first = (0, native_claude_config_1.readClaudeModelConfigDigest)({ cwd, configDir, key, environment: {} });
|
|
22
|
+
expect(first).toMatch(/^[a-f0-9]{64}$/);
|
|
23
|
+
fs_1.default.writeFileSync(settings, JSON.stringify({ model: 'claude-sonnet-4-6', permissions: { allow: ['Read', 'Edit'] } }));
|
|
24
|
+
expect((0, native_claude_config_1.readClaudeModelConfigDigest)({ cwd, configDir, key, environment: {} })).toBe(first);
|
|
25
|
+
fs_1.default.writeFileSync(settings, JSON.stringify({ model: 'claude-opus-4-7', permissions: { allow: ['Read', 'Edit'] } }));
|
|
26
|
+
expect((0, native_claude_config_1.readClaudeModelConfigDigest)({ cwd, configDir, key, environment: {} })).not.toBe(first);
|
|
27
|
+
const agents = path_1.default.join(cwd, '.claude', 'agents');
|
|
28
|
+
fs_1.default.mkdirSync(agents);
|
|
29
|
+
const beforeAgent = (0, native_claude_config_1.readClaudeModelConfigDigest)({ cwd, configDir, key, environment: {} });
|
|
30
|
+
fs_1.default.writeFileSync(path_1.default.join(agents, 'reviewer.md'), '---\nmodel: claude-sonnet-4-6\n---\n');
|
|
31
|
+
expect((0, native_claude_config_1.readClaudeModelConfigDigest)({ cwd, configDir, key, environment: {} })).not.toBe(beforeAgent);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
it('rejects a symlinked settings file', () => {
|
|
38
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-claude-config-'));
|
|
39
|
+
const configDir = path_1.default.join(root, 'home');
|
|
40
|
+
const cwd = path_1.default.join(root, 'project');
|
|
41
|
+
fs_1.default.mkdirSync(configDir);
|
|
42
|
+
fs_1.default.mkdirSync(path_1.default.join(cwd, '.claude'), { recursive: true });
|
|
43
|
+
const outside = path_1.default.join(root, 'outside.json');
|
|
44
|
+
fs_1.default.writeFileSync(outside, '{}');
|
|
45
|
+
fs_1.default.symlinkSync(outside, path_1.default.join(cwd, '.claude', 'settings.json'));
|
|
46
|
+
try {
|
|
47
|
+
expect(() => (0, native_claude_config_1.readClaudeModelConfigDigest)({ cwd, configDir, key: Buffer.alloc(32, 7), environment: {} })).toThrow(/symlink|unsafe/i);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const native_claude_machine_1 = require("../../../src/core/model-policy/native-claude-machine");
|
|
4
|
+
describe('Claude Code read-only account discovery', () => {
|
|
5
|
+
const key = Buffer.alloc(32, 7);
|
|
6
|
+
it('HMACs a logged-in identity without returning the email or starting a model', async () => {
|
|
7
|
+
const response = { loggedIn: true, authMethod: 'claude.ai', apiProvider: 'firstParty', email: 'owner@example.test', orgId: 'org-1', subscriptionType: 'max' };
|
|
8
|
+
const script = `process.stdout.write(${JSON.stringify(JSON.stringify(response))})`;
|
|
9
|
+
const facts = await (0, native_claude_machine_1.queryClaudeAuthStatus)({ command: process.execPath, args: ['-e', script], timeoutMs: 5000, key });
|
|
10
|
+
expect(facts).toMatchObject({ provenance: 'claude-auth-status', accountState: 'identified', inferenceDispatched: false });
|
|
11
|
+
expect(facts.accountScopeDigest).toMatch(/^[a-f0-9]{64}$/);
|
|
12
|
+
expect(JSON.stringify(facts)).not.toContain(response.email);
|
|
13
|
+
});
|
|
14
|
+
it('keeps missing account fields unverified instead of inventing a scope', async () => {
|
|
15
|
+
const script = `process.stdout.write('${JSON.stringify({ loggedIn: true, authMethod: 'apiKey', apiProvider: 'firstParty' })}')`;
|
|
16
|
+
expect(await (0, native_claude_machine_1.queryClaudeAuthStatus)({ command: process.execPath, args: ['-e', script], timeoutMs: 5000, key })).toMatchObject({ accountState: 'unverified', accountScopeDigest: null });
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
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_transcript_1 = require("../../../src/core/model-policy/native-claude-transcript");
|
|
10
|
+
describe('Claude Code native subagent transcript', () => {
|
|
11
|
+
it('extracts one actual model and timestamp without returning transcript content or inventing usage', () => {
|
|
12
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-claude-transcript-'));
|
|
13
|
+
const file = path_1.default.join(root, 'projects', 'project-1', 'session-1', 'subagents', 'agent-agent-1.jsonl');
|
|
14
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
15
|
+
fs_1.default.writeFileSync(file, [
|
|
16
|
+
{ type: 'assistant', timestamp: '2026-09-23T22:00:00.000Z', message: { model: 'claude-sonnet-4-6', content: [{ type: 'text', text: 'private prompt content' }], usage: { input_tokens: 10, output_tokens: 20 } } },
|
|
17
|
+
{ type: 'assistant', timestamp: '2026-09-23T22:00:01.000Z', message: { model: 'claude-sonnet-4-6', content: [] } },
|
|
18
|
+
].map(item => JSON.stringify(item)).join('\n'));
|
|
19
|
+
try {
|
|
20
|
+
const observed = (0, native_claude_transcript_1.readClaudeSubagentTranscript)({ configDir: root, file, agentId: 'agent-1' });
|
|
21
|
+
expect(observed).toMatchObject({ provenance: 'claude-subagent-transcript', actualModel: 'claude-sonnet-4-6', observedAt: '2026-09-23T22:00:01.000Z', tokenUsage: 'unknown' });
|
|
22
|
+
expect(JSON.stringify(observed)).not.toContain('private prompt content');
|
|
23
|
+
fs_1.default.writeFileSync(file, `${JSON.stringify({ type: 'assistant', timestamp: '2026-09-23T22:00:00.000Z', message: { model: 'claude-opus-4-7' } })}\n${JSON.stringify({ type: 'assistant', timestamp: '2026-09-23T22:00:01.000Z', message: { model: 'claude-sonnet-4-6' } })}`);
|
|
24
|
+
expect(() => (0, native_claude_transcript_1.readClaudeSubagentTranscript)({ configDir: root, file, agentId: 'agent-1' })).toThrow(/mixed|multiple/i);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
it('rejects symlinks and transcript files outside the native subagents tree', () => {
|
|
31
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-claude-transcript-'));
|
|
32
|
+
const outside = path_1.default.join(root, 'outside.jsonl');
|
|
33
|
+
const linked = path_1.default.join(root, 'projects', 'p', 's', 'subagents', 'agent-agent-1.jsonl');
|
|
34
|
+
fs_1.default.mkdirSync(path_1.default.dirname(linked), { recursive: true });
|
|
35
|
+
fs_1.default.writeFileSync(outside, '{}');
|
|
36
|
+
fs_1.default.symlinkSync(outside, linked);
|
|
37
|
+
try {
|
|
38
|
+
expect(() => (0, native_claude_transcript_1.readClaudeSubagentTranscript)({ configDir: root, file: linked, agentId: 'agent-1' })).toThrow(/symlink|unsafe/i);
|
|
39
|
+
expect(() => (0, native_claude_transcript_1.readClaudeSubagentTranscript)({ configDir: root, file: outside, agentId: 'agent-1' })).toThrow(/outside|subagents/i);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const native_claude_1 = require("../../../src/core/model-policy/native-claude");
|
|
4
|
+
describe('Claude native subagent lifecycle', () => {
|
|
5
|
+
const start = { hook_event_name: 'SubagentStart', session_id: 'session-1', agent_id: 'agent-1', agent_type: 'Explore' };
|
|
6
|
+
const stop = { hook_event_name: 'SubagentStop', session_id: 'session-1', agent_id: 'agent-1', agent_type: 'Explore', agent_transcript_path: '/tmp/agent-1.jsonl', stop_hook_active: false, last_assistant_message: 'done' };
|
|
7
|
+
it('links native hook identities without claiming a model or paid usage', () => {
|
|
8
|
+
expect((0, native_claude_1.normalizeClaudeSubagentLifecycle)(start, stop)).toEqual({
|
|
9
|
+
provenance: 'claude-code-subagent-hooks', sessionId: 'session-1', agentId: 'agent-1', agentType: 'Explore',
|
|
10
|
+
nativeDispatchVerified: true, acceptedSelectionVerified: false, actualModelVerified: false, tokenUsage: 'unknown',
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
it('rejects mismatched agent and session identities', () => {
|
|
14
|
+
expect(() => (0, native_claude_1.normalizeClaudeSubagentLifecycle)(start, { ...stop, agent_id: 'agent-2' })).toThrow(/identity/i);
|
|
15
|
+
expect(() => (0, native_claude_1.normalizeClaudeSubagentLifecycle)(start, { ...stop, session_id: 'session-2' })).toThrow(/identity/i);
|
|
16
|
+
});
|
|
17
|
+
it('rejects forged event kinds and missing transcript paths', () => {
|
|
18
|
+
expect(() => (0, native_claude_1.normalizeClaudeSubagentLifecycle)({ ...start, hook_event_name: 'Notification' }, stop)).toThrow(/hook/i);
|
|
19
|
+
expect(() => (0, native_claude_1.normalizeClaudeSubagentLifecycle)(start, { ...stop, agent_transcript_path: '' })).toThrow(/transcript/i);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const native_codex_machine_1 = require("../../../src/core/model-policy/native-codex-machine");
|
|
4
|
+
describe('Codex machine facts', () => {
|
|
5
|
+
it('hashes effective config and ChatGPT account scope without returning identifiers', async () => {
|
|
6
|
+
const server = `
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
const input = readline.createInterface({ input: process.stdin });
|
|
9
|
+
input.on('line', line => {
|
|
10
|
+
const request = JSON.parse(line);
|
|
11
|
+
if (request.method === 'initialize') process.stdout.write(JSON.stringify({ id: request.id, result: {} }) + '\\n');
|
|
12
|
+
if (request.method === 'config/read') process.stdout.write(JSON.stringify({ id: request.id, result: { config: { model: 'gpt-6-sol', model_reasoning_effort: 'medium' }, origins: {} } }) + '\\n');
|
|
13
|
+
if (request.method === 'account/read') process.stdout.write(JSON.stringify({ id: request.id, result: { account: { type: 'chatgpt', email: 'secret@example.com', planType: 'pro' }, requiresOpenaiAuth: true } }) + '\\n');
|
|
14
|
+
});`;
|
|
15
|
+
const input = { command: process.execPath, args: ['-e', server], timeoutMs: 5000, cwd: process.cwd(), key: Buffer.alloc(32, 7) };
|
|
16
|
+
const first = await (0, native_codex_machine_1.queryCodexMachineFacts)(input);
|
|
17
|
+
const second = await (0, native_codex_machine_1.queryCodexMachineFacts)(input);
|
|
18
|
+
expect(first).toEqual(second);
|
|
19
|
+
expect(first).toMatchObject({ provenance: 'codex-app-server-config-account', accountState: 'identified', configDigest: expect.stringMatching(/^[a-f0-9]{64}$/), accountScopeDigest: expect.stringMatching(/^[a-f0-9]{64}$/) });
|
|
20
|
+
expect(JSON.stringify(first)).not.toContain('secret@example.com');
|
|
21
|
+
});
|
|
22
|
+
it('keeps API-key account identity unverified instead of equating all keys', async () => {
|
|
23
|
+
const server = `
|
|
24
|
+
const readline = require('readline');
|
|
25
|
+
const input = readline.createInterface({ input: process.stdin });
|
|
26
|
+
input.on('line', line => {
|
|
27
|
+
const request = JSON.parse(line);
|
|
28
|
+
if (request.method === 'initialize') process.stdout.write(JSON.stringify({ id: request.id, result: {} }) + '\\n');
|
|
29
|
+
if (request.method === 'config/read') process.stdout.write(JSON.stringify({ id: request.id, result: { config: {}, origins: {} } }) + '\\n');
|
|
30
|
+
if (request.method === 'account/read') process.stdout.write(JSON.stringify({ id: request.id, result: { account: { type: 'apiKey' }, requiresOpenaiAuth: true } }) + '\\n');
|
|
31
|
+
});`;
|
|
32
|
+
await expect((0, native_codex_machine_1.queryCodexMachineFacts)({ command: process.execPath, args: ['-e', server], timeoutMs: 5000, cwd: process.cwd(), key: Buffer.alloc(32, 7) })).resolves.toMatchObject({ accountState: 'unverified', accountScopeDigest: null });
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
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_codex_rollout_1 = require("../../../src/core/model-policy/native-codex-rollout");
|
|
10
|
+
describe('Codex local rollout selection reader', () => {
|
|
11
|
+
const expected = { parentThreadId: 'parent-1', childThreadId: 'child-1', turnId: 'turn-1' };
|
|
12
|
+
const session = { type: 'session_meta', payload: { id: 'child-1', source: { subagent: { thread_spawn: { parent_thread_id: 'parent-1' } } }, cli_version: '0.156.1' } };
|
|
13
|
+
const turn = { timestamp: '2026-09-23T20:00:00.000Z', type: 'turn_context', payload: { turn_id: 'turn-1', model: 'gpt-6-luna', effort: 'high' } };
|
|
14
|
+
it('reads only an in-scope rollout and returns bounded per-turn metadata', () => {
|
|
15
|
+
const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-codex-home-'));
|
|
16
|
+
const file = path_1.default.join(home, 'sessions', 'rollout.jsonl');
|
|
17
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file));
|
|
18
|
+
fs_1.default.writeFileSync(file, `${JSON.stringify(session)}\n${JSON.stringify({ type: 'event_msg', payload: { private: 'do not return' } })}\n${JSON.stringify(turn)}\n`);
|
|
19
|
+
try {
|
|
20
|
+
expect((0, native_codex_rollout_1.readCodexRolloutSelection)({ codexHome: home, file, expected })).toMatchObject({
|
|
21
|
+
configuredSelection: { selector: { id: 'gpt-6-luna' }, effort: { value: 'high' } }, actualModelVerified: false,
|
|
22
|
+
});
|
|
23
|
+
expect(JSON.stringify((0, native_codex_rollout_1.readCodexRolloutSelection)({ codexHome: home, file, expected }))).not.toContain('private');
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
it('rejects a symlinked rollout and a path outside Codex sessions', () => {
|
|
30
|
+
const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-codex-home-'));
|
|
31
|
+
const outside = path_1.default.join(home, 'outside.jsonl');
|
|
32
|
+
const linked = path_1.default.join(home, 'sessions', 'linked.jsonl');
|
|
33
|
+
fs_1.default.mkdirSync(path_1.default.dirname(linked));
|
|
34
|
+
fs_1.default.writeFileSync(outside, `${JSON.stringify(session)}\n${JSON.stringify(turn)}\n`);
|
|
35
|
+
fs_1.default.symlinkSync(outside, linked);
|
|
36
|
+
try {
|
|
37
|
+
expect(() => (0, native_codex_rollout_1.readCodexRolloutSelection)({ codexHome: home, file: linked, expected })).toThrow(/symlink|unsafe/i);
|
|
38
|
+
expect(() => (0, native_codex_rollout_1.readCodexRolloutSelection)({ codexHome: home, file: outside, expected })).toThrow(/outside|sessions/i);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
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_codex_1 = require("../../../src/core/model-policy/native-codex");
|
|
10
|
+
describe('Codex native model discovery', () => {
|
|
11
|
+
const response = () => ({
|
|
12
|
+
data: [
|
|
13
|
+
{ id: 'gpt-6-sol', model: 'gpt-6-sol', hidden: false, isDefault: true,
|
|
14
|
+
defaultReasoningEffort: 'medium', supportedReasoningEfforts: [
|
|
15
|
+
{ reasoningEffort: 'medium', description: 'balanced' },
|
|
16
|
+
{ reasoningEffort: 'high', description: 'deep' },
|
|
17
|
+
] },
|
|
18
|
+
], nextCursor: null,
|
|
19
|
+
});
|
|
20
|
+
it('normalizes catalog selections without certifying dispatch or actual model identity', () => {
|
|
21
|
+
expect((0, native_codex_1.normalizeCodexModelCatalog)(response())).toEqual({
|
|
22
|
+
provenance: 'native-catalog',
|
|
23
|
+
selections: [
|
|
24
|
+
{ selector: { kind: 'model', id: 'gpt-6-sol' }, effort: { kind: 'explicit', value: 'medium' } },
|
|
25
|
+
{ selector: { kind: 'model', id: 'gpt-6-sol' }, effort: { kind: 'explicit', value: 'high' } },
|
|
26
|
+
],
|
|
27
|
+
nativeDispatchVerified: false,
|
|
28
|
+
actualModelVerified: false,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
it('rejects malformed and duplicate native catalog data instead of inventing selections', () => {
|
|
32
|
+
expect(() => (0, native_codex_1.normalizeCodexModelCatalog)({ data: [{ ...response().data[0], model: '' }] })).toThrow(/model/i);
|
|
33
|
+
expect(() => (0, native_codex_1.normalizeCodexModelCatalog)({ data: [response().data[0], response().data[0]] })).toThrow(/duplicate/i);
|
|
34
|
+
expect(() => (0, native_codex_1.normalizeCodexModelCatalog)({ data: [
|
|
35
|
+
{ ...response().data[0], supportedReasoningEfforts: [{ reasoningEffort: 'medium' }] },
|
|
36
|
+
] })).toThrow(/effort/i);
|
|
37
|
+
});
|
|
38
|
+
it('reads the native model/list protocol without starting a model turn', async () => {
|
|
39
|
+
const server = `
|
|
40
|
+
const readline = require('readline');
|
|
41
|
+
const input = readline.createInterface({ input: process.stdin });
|
|
42
|
+
input.on('line', line => {
|
|
43
|
+
const request = JSON.parse(line);
|
|
44
|
+
if (request.method === 'initialize') process.stdout.write(JSON.stringify({ id: request.id, result: { userAgent: 'fake' } }) + '\\n');
|
|
45
|
+
if (request.method === 'model/list') process.stdout.write(JSON.stringify({ id: request.id, result: ${JSON.stringify(response())} }) + '\\n');
|
|
46
|
+
});`;
|
|
47
|
+
const result = await (0, native_codex_1.queryCodexModelCatalog)({ command: process.execPath, args: ['-e', server], timeoutMs: 5000 });
|
|
48
|
+
expect(result.selections).toHaveLength(2);
|
|
49
|
+
expect(result.nativeDispatchVerified).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
it('fails closed on an invalid native response', async () => {
|
|
52
|
+
const server = `process.stdin.on('data', () => process.stdout.write('not-json\\n'));`;
|
|
53
|
+
await expect((0, native_codex_1.queryCodexModelCatalog)({ command: process.execPath, args: ['-e', server], timeoutMs: 5000 })).rejects.toThrow(/protocol|JSON/i);
|
|
54
|
+
});
|
|
55
|
+
it('separates real child dispatch from configured selection and backend identity', () => {
|
|
56
|
+
const child = { thread: { id: 'child-1', parentThreadId: 'parent-1', model: 'gpt-6-sol', reasoningEffort: 'low', turns: [{ id: 'turn-1', status: 'completed', items: [] }] } };
|
|
57
|
+
expect((0, native_codex_1.normalizeCodexChildThreadObservation)(child, 'parent-1')).toEqual({
|
|
58
|
+
provenance: 'codex-app-server-thread-read', parentThreadId: 'parent-1', childThreadId: 'child-1', turnId: 'turn-1',
|
|
59
|
+
nativeDispatchVerified: true, configuredSelection: { selector: { kind: 'model', id: 'gpt-6-sol' }, effort: { kind: 'explicit', value: 'low' } },
|
|
60
|
+
acceptedSelectionVerified: false, actualModelVerified: false, tokenUsage: 'unknown',
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
it('rejects a parent mismatch and refuses to promote a child with no turn', () => {
|
|
64
|
+
const child = { thread: { id: 'child-1', parentThreadId: 'parent-1', model: 'gpt-6-sol', reasoningEffort: 'low', turns: [{ id: 'turn-1', status: 'completed', items: [] }] } };
|
|
65
|
+
expect(() => (0, native_codex_1.normalizeCodexChildThreadObservation)(child, 'parent-2')).toThrow(/parent/i);
|
|
66
|
+
expect(() => (0, native_codex_1.normalizeCodexChildThreadObservation)({ thread: { ...child.thread, turns: [] } }, 'parent-1')).toThrow(/turn/i);
|
|
67
|
+
expect(() => (0, native_codex_1.normalizeCodexChildThreadObservation)({ thread: { ...child.thread, model: null } }, 'parent-1')).toThrow(/model/i);
|
|
68
|
+
});
|
|
69
|
+
it('reads an actual child thread through native thread/read without sending a turn', async () => {
|
|
70
|
+
const observed = { thread: { id: 'child-1', parentThreadId: 'parent-1', model: 'gpt-6-sol', reasoningEffort: 'low', turns: [{ id: 'turn-1', status: 'completed', items: [] }] } };
|
|
71
|
+
const server = `
|
|
72
|
+
const readline = require('readline');
|
|
73
|
+
const input = readline.createInterface({ input: process.stdin });
|
|
74
|
+
input.on('line', line => {
|
|
75
|
+
const request = JSON.parse(line);
|
|
76
|
+
if (request.method === 'initialize') process.stdout.write(JSON.stringify({ id: request.id, result: { userAgent: 'fake' } }) + '\\n');
|
|
77
|
+
if (request.method === 'thread/read') {
|
|
78
|
+
if (request.params.threadId !== 'child-1' || request.params.includeTurns !== true) process.exit(13);
|
|
79
|
+
process.stdout.write(JSON.stringify({ id: request.id, result: ${JSON.stringify(observed)} }) + '\\n');
|
|
80
|
+
}
|
|
81
|
+
if (request.method === 'turn/start' || request.method === 'thread/start') process.exit(14);
|
|
82
|
+
});`;
|
|
83
|
+
const result = await (0, native_codex_1.queryCodexChildThreadObservation)({ command: process.execPath, args: ['-e', server], timeoutMs: 5000, parentThreadId: 'parent-1', childThreadId: 'child-1' });
|
|
84
|
+
expect(result).toMatchObject({ nativeDispatchVerified: true, acceptedSelectionVerified: false, actualModelVerified: false, configuredSelection: { selector: { id: 'gpt-6-sol' } } });
|
|
85
|
+
});
|
|
86
|
+
it('binds an immutable turn context to the observed child, parent and turn', () => {
|
|
87
|
+
const events = [
|
|
88
|
+
{ type: 'session_meta', payload: { id: 'child-1', source: { subagent: { thread_spawn: { parent_thread_id: 'parent-1' } } }, cli_version: '0.156.1' } },
|
|
89
|
+
{ timestamp: '2026-09-23T20:00:00.000Z', type: 'turn_context', payload: { turn_id: 'turn-1', model: 'gpt-6-luna', effort: 'high' } },
|
|
90
|
+
];
|
|
91
|
+
expect((0, native_codex_1.normalizeCodexRolloutSelection)(events, { parentThreadId: 'parent-1', childThreadId: 'child-1', turnId: 'turn-1' })).toEqual({
|
|
92
|
+
provenance: 'codex-rollout-turn-context', cliVersion: '0.156.1', observedAt: '2026-09-23T20:00:00.000Z',
|
|
93
|
+
configuredSelection: { selector: { kind: 'model', id: 'gpt-6-luna' }, effort: { kind: 'explicit', value: 'high' } },
|
|
94
|
+
actualModelVerified: false,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
it('rejects unrelated parent, child and turn contexts', () => {
|
|
98
|
+
const events = [
|
|
99
|
+
{ type: 'session_meta', payload: { id: 'child-1', source: { subagent: { thread_spawn: { parent_thread_id: 'parent-1' } } }, cli_version: '0.156.1' } },
|
|
100
|
+
{ timestamp: '2026-09-23T20:00:00.000Z', type: 'turn_context', payload: { turn_id: 'turn-1', model: 'gpt-6-luna', effort: 'high' } },
|
|
101
|
+
];
|
|
102
|
+
expect(() => (0, native_codex_1.normalizeCodexRolloutSelection)(events, { parentThreadId: 'other', childThreadId: 'child-1', turnId: 'turn-1' })).toThrow(/parent/i);
|
|
103
|
+
expect(() => (0, native_codex_1.normalizeCodexRolloutSelection)(events, { parentThreadId: 'parent-1', childThreadId: 'other', turnId: 'turn-1' })).toThrow(/child/i);
|
|
104
|
+
expect(() => (0, native_codex_1.normalizeCodexRolloutSelection)(events, { parentThreadId: 'parent-1', childThreadId: 'child-1', turnId: 'other' })).toThrow(/turn/i);
|
|
105
|
+
expect(() => (0, native_codex_1.normalizeCodexRolloutSelection)([...events, events[1]], { parentThreadId: 'parent-1', childThreadId: 'child-1', turnId: 'turn-1' })).toThrow(/duplicate/i);
|
|
106
|
+
expect(() => (0, native_codex_1.normalizeCodexRolloutSelection)([{ ...events[0] }, { ...events[1], timestamp: undefined }], { parentThreadId: 'parent-1', childThreadId: 'child-1', turnId: 'turn-1' })).toThrow(/timestamp/i);
|
|
107
|
+
});
|
|
108
|
+
it('attests the configured selection at the completed native turn only with matching rollout provenance', async () => {
|
|
109
|
+
const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-codex-observe-'));
|
|
110
|
+
const file = path_1.default.join(home, 'sessions', 'rollout.jsonl');
|
|
111
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file));
|
|
112
|
+
fs_1.default.writeFileSync(file, [
|
|
113
|
+
{ type: 'session_meta', payload: { id: 'child-1', source: { subagent: { thread_spawn: { parent_thread_id: 'parent-1' } } }, cli_version: '0.156.1' } },
|
|
114
|
+
{ timestamp: '2026-09-23T20:00:00.000Z', type: 'turn_context', payload: { turn_id: 'turn-1', model: 'gpt-6-luna', effort: 'high' } },
|
|
115
|
+
].map(value => JSON.stringify(value)).join('\n'));
|
|
116
|
+
const observed = { thread: { id: 'child-1', parentThreadId: 'parent-1', model: 'gpt-6-luna', reasoningEffort: 'high', path: file, turns: [{ id: 'turn-1', status: 'completed', items: [] }] } };
|
|
117
|
+
const server = `
|
|
118
|
+
const readline = require('readline');
|
|
119
|
+
const input = readline.createInterface({ input: process.stdin });
|
|
120
|
+
input.on('line', line => {
|
|
121
|
+
const request = JSON.parse(line);
|
|
122
|
+
if (request.method === 'initialize') process.stdout.write(JSON.stringify({ id: request.id, result: {} }) + '\\n');
|
|
123
|
+
if (request.method === 'thread/read') process.stdout.write(JSON.stringify({ id: request.id, result: ${JSON.stringify(observed)} }) + '\\n');
|
|
124
|
+
});`;
|
|
125
|
+
try {
|
|
126
|
+
const result = await (0, native_codex_1.queryCodexChildThreadObservation)({ command: process.execPath, args: ['-e', server], timeoutMs: 5000, parentThreadId: 'parent-1', childThreadId: 'child-1', codexHome: home });
|
|
127
|
+
expect(result).toMatchObject({ acceptedSelectionVerified: true, childRuntimeVersion: '0.156.1', configuredSelection: { selector: { id: 'gpt-6-luna' }, effort: { value: 'high' } }, actualModelVerified: false });
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const native_routing_observe_1 = require("../../../src/core/model-policy/native-routing-observe");
|
|
4
|
+
const local_event_scope_1 = require("../../../src/core/model-policy/local-event-scope");
|
|
5
|
+
const machine_key_1 = require("../../../src/core/model-policy/machine-key");
|
|
6
|
+
const native_codex_1 = require("../../../src/core/model-policy/native-codex");
|
|
7
|
+
const event_store_1 = require("../../../src/core/model-policy/event-store");
|
|
8
|
+
const store_1 = require("../../../src/core/model-policy/store");
|
|
9
|
+
const selection_policy_digest_1 = require("../../../src/core/model-policy/selection-policy-digest");
|
|
10
|
+
const routing_approval_1 = require("../../helpers/routing-approval");
|
|
11
|
+
const native_routing_proof_1 = require("../../../src/core/model-policy/native-routing-proof");
|
|
12
|
+
jest.mock('../../../src/core/model-policy/local-event-scope');
|
|
13
|
+
jest.mock('../../../src/core/model-policy/machine-key');
|
|
14
|
+
jest.mock('../../../src/core/model-policy/native-codex');
|
|
15
|
+
jest.mock('../../../src/core/model-policy/event-store');
|
|
16
|
+
jest.mock('../../../src/core/model-policy/store');
|
|
17
|
+
const key = Buffer.alloc(32, 9);
|
|
18
|
+
const now = new Date('2026-09-23T12:00:03.000Z');
|
|
19
|
+
const reservedAt = '2026-09-23T12:00:00.000Z';
|
|
20
|
+
const observedAt = '2026-09-23T12:00:02.000Z';
|
|
21
|
+
const scope = { runtime: routing_approval_1.routingEnvelope.runtime, binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64) };
|
|
22
|
+
function attempt() {
|
|
23
|
+
return { schema: 'routing-attempt/v1', id: 'route-1', obligationId: 'o1', lineageId: 'l1', attempt: 1,
|
|
24
|
+
envelope: routing_approval_1.routingEnvelope, envelopeDigest: 'e'.repeat(64), fingerprint: 'f'.repeat(64), state: 'reserved',
|
|
25
|
+
nativeEvidenceRequired: true, reservedAt };
|
|
26
|
+
}
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
const approved = (0, routing_approval_1.routingApproval)();
|
|
30
|
+
jest.mocked(store_1.readEffectivePolicy).mockReturnValue({ state: 'approved', provenance: 'project', policy: approved.policy });
|
|
31
|
+
jest.mocked(machine_key_1.readMachineKey).mockReturnValue(key);
|
|
32
|
+
jest.mocked(local_event_scope_1.queryLocalEventScope).mockResolvedValue({ state: 'current', scope });
|
|
33
|
+
const receipt = { schema: 'routing-capabilities/v2', ...scope, recordedAt: observedAt,
|
|
34
|
+
claims: [{ selection: routing_approval_1.routingEnvelope.resolved,
|
|
35
|
+
mappingDigest: (0, selection_policy_digest_1.selectionPolicyDigest)(approved.policy.content.mappings[0], routing_approval_1.routingEnvelope.resolved),
|
|
36
|
+
eventDigest: 'a'.repeat(64), source: 'codex-turn-context', observedAt,
|
|
37
|
+
actualModel: 'unverified', tokenUsage: 'unknown' }] };
|
|
38
|
+
jest.mocked(event_store_1.readStoredEventReceipt).mockReturnValue({ state: 'present', receipt });
|
|
39
|
+
jest.mocked(native_codex_1.queryCodexChildThreadObservation).mockResolvedValue({ provenance: 'codex-app-server-thread-read',
|
|
40
|
+
parentThreadId: 'parent-1', childThreadId: 'child-1', turnId: 'turn-1', nativeDispatchVerified: true,
|
|
41
|
+
configuredSelection: routing_approval_1.routingEnvelope.resolved, acceptedSelectionVerified: true,
|
|
42
|
+
childRuntimeVersion: routing_approval_1.routingEnvelope.runtime.version, observedAt,
|
|
43
|
+
actualModelVerified: false, tokenUsage: 'unknown' });
|
|
44
|
+
});
|
|
45
|
+
describe('normal-work native routing observation', () => {
|
|
46
|
+
it('reconciles a completed Codex child and renews only its matching claim', async () => {
|
|
47
|
+
const proof = await (0, native_routing_observe_1.observeNativeRoutingChild)({ attempt: attempt(), nativeAgentId: 'child-1', parentThreadId: 'parent-1',
|
|
48
|
+
cwd: '/tmp/awm-native-observe-test', now, consumedEventDigests: [] });
|
|
49
|
+
expect((0, native_routing_proof_1.verifyNativeRoutingProof)(proof, { attemptId: 'route-1', nativeAgentId: 'child-1',
|
|
50
|
+
reservedAt, selection: routing_approval_1.routingEnvelope.resolved }, key, now)).toBe(true);
|
|
51
|
+
expect(event_store_1.publishCodexObservation).toHaveBeenCalledWith(expect.objectContaining({ expectedSelection: routing_approval_1.routingEnvelope.resolved }));
|
|
52
|
+
});
|
|
53
|
+
it('rejects a setup turn preceding reservation without renewing its claim', async () => {
|
|
54
|
+
jest.mocked(native_codex_1.queryCodexChildThreadObservation).mockResolvedValueOnce({ ...await (0, native_codex_1.queryCodexChildThreadObservation)({ command: 'codex', args: [], timeoutMs: 1, parentThreadId: 'parent-1', childThreadId: 'child-1' }),
|
|
55
|
+
observedAt: '2026-09-23T11:59:59.000Z' });
|
|
56
|
+
await expect((0, native_routing_observe_1.observeNativeRoutingChild)({ attempt: attempt(), nativeAgentId: 'child-1', parentThreadId: 'parent-1',
|
|
57
|
+
cwd: '/tmp/awm-native-observe-test', now, consumedEventDigests: [] })).rejects.toThrow(/predates reservation/);
|
|
58
|
+
expect(event_store_1.publishCodexObservation).not.toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const native_routing_proof_1 = require("../../../src/core/model-policy/native-routing-proof");
|
|
4
|
+
const routing_approval_1 = require("../../helpers/routing-approval");
|
|
5
|
+
const key = Buffer.alloc(32, 7);
|
|
6
|
+
const input = { attemptId: 'route-1', nativeAgentId: 'child-1', source: 'codex-turn-context',
|
|
7
|
+
eventDigest: 'a'.repeat(64), observedAt: '2026-09-23T12:00:02.000Z', selection: (0, routing_approval_1.routingSelection)('medium') };
|
|
8
|
+
describe('native routing proof', () => {
|
|
9
|
+
it('binds a native event to one reserved attempt and child', () => {
|
|
10
|
+
const proof = (0, native_routing_proof_1.signNativeRoutingProof)(input, key);
|
|
11
|
+
expect((0, native_routing_proof_1.verifyNativeRoutingProof)(proof, { attemptId: 'route-1', nativeAgentId: 'child-1',
|
|
12
|
+
reservedAt: '2026-09-23T12:00:00.000Z', selection: input.selection }, key, new Date('2026-09-23T12:00:03.000Z'))).toBe(true);
|
|
13
|
+
expect((0, native_routing_proof_1.verifyNativeRoutingProof)(proof, { attemptId: 'route-2', nativeAgentId: 'child-1',
|
|
14
|
+
reservedAt: '2026-09-23T12:00:00.000Z', selection: input.selection }, key, new Date('2026-09-23T12:00:03.000Z'))).toBe(false);
|
|
15
|
+
expect((0, native_routing_proof_1.verifyNativeRoutingProof)({ ...proof, selection: (0, routing_approval_1.routingSelection)('high') }, { attemptId: 'route-1', nativeAgentId: 'child-1',
|
|
16
|
+
reservedAt: '2026-09-23T12:00:00.000Z', selection: input.selection }, key, new Date('2026-09-23T12:00:03.000Z'))).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
it('rejects a setup event replayed as a daily dispatch', () => {
|
|
19
|
+
const proof = (0, native_routing_proof_1.signNativeRoutingProof)(input, key);
|
|
20
|
+
expect((0, native_routing_proof_1.verifyNativeRoutingProof)(proof, { attemptId: 'route-1', nativeAgentId: 'child-1',
|
|
21
|
+
reservedAt: '2026-09-23T12:00:03.000Z', selection: input.selection }, key, new Date('2026-09-23T12:00:04.000Z'))).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
it('rejects a proof after its bounded observation window and protects a provider model claim', () => {
|
|
24
|
+
const proof = (0, native_routing_proof_1.signNativeRoutingProof)({ ...input, source: 'claude-assistant-transcript', actualModel: 'claude-example-model' }, key);
|
|
25
|
+
const expected = { attemptId: 'route-1', nativeAgentId: 'child-1', reservedAt: '2026-09-23T12:00:00.000Z', selection: input.selection };
|
|
26
|
+
expect((0, native_routing_proof_1.verifyNativeRoutingProof)(proof, expected, key, new Date('2026-09-23T12:00:03.000Z'))).toBe(true);
|
|
27
|
+
expect((0, native_routing_proof_1.verifyNativeRoutingProof)({ ...proof, actualModel: 'different' }, expected, key, new Date('2026-09-23T12:00:03.000Z'))).toBe(false);
|
|
28
|
+
expect((0, native_routing_proof_1.verifyNativeRoutingProof)(proof, expected, key, new Date('2026-09-23T12:16:03.000Z'))).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const native_runtime_1 = require("../../../src/core/model-policy/native-runtime");
|
|
4
|
+
describe('native runtime fingerprint', () => {
|
|
5
|
+
it('binds reported version and executable content without launching inference', async () => {
|
|
6
|
+
const result = await (0, native_runtime_1.queryRuntimeExecutable)({ command: process.execPath, args: ['--version'], timeoutMs: 5000 });
|
|
7
|
+
expect(result).toMatchObject({ version: expect.stringMatching(/^\d+\.\d+\.\d+$/), binaryDigest: expect.stringMatching(/^[a-f0-9]{64}$/), inferenceDispatched: false });
|
|
8
|
+
expect(await (0, native_runtime_1.queryRuntimeExecutable)({ command: process.execPath, args: ['--version'], timeoutMs: 5000 })).toEqual(result);
|
|
9
|
+
});
|
|
10
|
+
it('rejects an invalid binary or an inference-shaped invocation', async () => {
|
|
11
|
+
await expect((0, native_runtime_1.queryRuntimeExecutable)({ command: '/definitely/missing/awm-runtime', args: ['--version'], timeoutMs: 1000 })).rejects.toThrow(/runtime|executable/i);
|
|
12
|
+
await expect((0, native_runtime_1.queryRuntimeExecutable)({ command: process.execPath, args: ['-e', 'console.log(1)'], timeoutMs: 1000 })).rejects.toThrow(/version/i);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const resolve_1 = require("../../../src/core/model-policy/resolve");
|
|
4
|
+
const selection_policy_digest_1 = require("../../../src/core/model-policy/selection-policy-digest");
|
|
5
|
+
const a = 'a'.repeat(64);
|
|
6
|
+
const b = 'b'.repeat(64);
|
|
7
|
+
const c = 'c'.repeat(64);
|
|
8
|
+
const selected = (id) => ({ selector: { kind: 'model', id }, effort: { kind: 'explicit', value: 'high' } });
|
|
9
|
+
const policy = () => ({ schema: 'approved-model-policy/v1', content: { schema: 'model-policy/v1', mappings: [{ target: 'codex', runtimeKind: 'native',
|
|
10
|
+
profiles: { mechanical: selected('luna'), integration: selected('sol'), judgment: selected('astra') }, fullCapability: selected('astra'),
|
|
11
|
+
degradation: { allowMissingModelOverride: false, allowMissingEffortOverride: false, allowMissingObservedIdentity: true } }],
|
|
12
|
+
implementationBudget: { maxAttempts: 3, escalation: ['mechanical', 'integration', 'judgment'], judgmentEfforts: ['medium', 'high'] } },
|
|
13
|
+
contentDigest: a, approval: { approvedAt: '2026-09-23T00:00:00.000Z', approvalId: 'owner' }, lineage: { previousDigest: null } });
|
|
14
|
+
const scope = () => ({ runtime: { target: 'codex', kind: 'native', version: '0.156.1', accountScopeDigest: a }, binaryDigest: b, configDigest: c });
|
|
15
|
+
const receipt = () => ({ schema: 'routing-capabilities/v2', ...scope(), recordedAt: '2026-09-23T00:00:00.000Z', claims: ['luna', 'astra'].map(id => ({ selection: selected(id),
|
|
16
|
+
mappingDigest: (0, selection_policy_digest_1.selectionPolicyDigest)(policy().content.mappings[0], selected(id)), eventDigest: b, source: 'codex-turn-context',
|
|
17
|
+
observedAt: '2026-09-22T23:59:00.000Z', actualModel: 'unverified', tokenUsage: 'unknown' })) });
|
|
18
|
+
const input = () => ({ role: 'implementer', requestedProfile: 'mechanical', policy: policy(), receipt: receipt(), scope: scope(), now: new Date('2026-09-24T01:00:00.000Z') });
|
|
19
|
+
describe('event-scoped routing resolution', () => {
|
|
20
|
+
it('routes a proved selection after 25 hours but labels missing backend identity as degraded', () => {
|
|
21
|
+
expect((0, resolve_1.resolveEventSelection)(input())).toMatchObject({ state: 'resolved', selection: selected('luna'), outcome: 'degraded', unavailableEvidence: ['observedModelEvidence'] });
|
|
22
|
+
});
|
|
23
|
+
it('requires the full selection to be separately proved', () => {
|
|
24
|
+
const value = input();
|
|
25
|
+
value.receipt.claims = value.receipt.claims.filter(item => item.selection.selector.id !== 'astra');
|
|
26
|
+
expect((0, resolve_1.resolveEventSelection)({ ...value, role: 'code-quality-reviewer' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_SELECTION_UNENROLLED' }] });
|
|
27
|
+
});
|
|
28
|
+
it('blocks on account drift and when the owner did not permit missing backend identity', () => {
|
|
29
|
+
expect((0, resolve_1.resolveEventSelection)({ ...input(), scope: { ...scope(), runtime: { ...scope().runtime, accountScopeDigest: c } } })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_ACCOUNT_DRIFT' }] });
|
|
30
|
+
const value = input();
|
|
31
|
+
value.policy.content.mappings[0].degradation.allowMissingObservedIdentity = false;
|
|
32
|
+
expect((0, resolve_1.resolveEventSelection)(value)).toMatchObject({ state: 'blocked' });
|
|
33
|
+
});
|
|
34
|
+
it('uses independently enrolled full capability when only the optimized selection is absent', () => {
|
|
35
|
+
const value = input();
|
|
36
|
+
value.receipt.claims = value.receipt.claims.filter(item => item.selection.selector.id === 'astra');
|
|
37
|
+
expect((0, resolve_1.resolveEventWithFallback)(value)).toMatchObject({ state: 'resolved', selection: selected('astra'), effectiveProfile: 'full', outcome: 'degraded', fallbackReason: 'ROUTING_SELECTION_UNENROLLED' });
|
|
38
|
+
const drift = { ...value, scope: { ...scope(), runtime: { ...scope().runtime, accountScopeDigest: c } } };
|
|
39
|
+
expect((0, resolve_1.resolveEventWithFallback)(drift)).toMatchObject({ state: 'blocked', diagnostics: [expect.objectContaining({ code: 'ROUTING_FALLBACK_UNAVAILABLE' })] });
|
|
40
|
+
});
|
|
41
|
+
it('trips a same-run circuit to full when a provider rejects an otherwise current optimized selection', () => {
|
|
42
|
+
const value = input();
|
|
43
|
+
expect((0, resolve_1.resolveEventSelection)(value)).toMatchObject({ state: 'resolved', selection: selected('luna') });
|
|
44
|
+
expect((0, resolve_1.resolveEventWithFallback)({ ...value, circuitReason: 'PROVIDER_REJECTED' })).toMatchObject({ state: 'resolved', selection: selected('astra'), effectiveProfile: 'full', fallbackReason: 'PROVIDER_REJECTED' });
|
|
45
|
+
value.receipt.claims = value.receipt.claims.filter(item => item.selection.selector.id !== 'astra');
|
|
46
|
+
expect((0, resolve_1.resolveEventWithFallback)({ ...value, circuitReason: 'PROVIDER_REJECTED' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_FALLBACK_UNAVAILABLE' }] });
|
|
47
|
+
});
|
|
48
|
+
it('returns the certified Claude agent type and preserves it on full fallback', () => {
|
|
49
|
+
const value = input();
|
|
50
|
+
value.policy.content.mappings[0].target = 'claude-code';
|
|
51
|
+
value.policy.content.mappings[0].profiles.mechanical = { selector: { kind: 'model', id: 'claude-haiku-4-5' }, effort: { kind: 'runtime-default' } };
|
|
52
|
+
value.policy.content.mappings[0].fullCapability = { selector: { kind: 'model', id: 'claude-opus-4-7' }, effort: { kind: 'runtime-default' } };
|
|
53
|
+
const claudeScope = { ...scope(), runtime: { ...scope().runtime, target: 'claude-code' } };
|
|
54
|
+
const mapping = value.policy.content.mappings[0];
|
|
55
|
+
const claims = [
|
|
56
|
+
{ selection: mapping.profiles.mechanical, nativeAgentType: 'awm-mechanical' },
|
|
57
|
+
{ selection: mapping.fullCapability, nativeAgentType: 'awm-full' },
|
|
58
|
+
].map(row => ({ ...row, mappingDigest: (0, selection_policy_digest_1.selectionPolicyDigest)(mapping, row.selection), eventDigest: b,
|
|
59
|
+
source: 'claude-assistant-transcript', observedAt: '2026-09-22T23:59:00.000Z',
|
|
60
|
+
actualModel: { id: row.selection.selector.id, evidenceDigest: c }, tokenUsage: 'unknown' }));
|
|
61
|
+
const routed = { ...value, receipt: { schema: 'routing-capabilities/v2', ...claudeScope, recordedAt: '2026-09-23T00:00:00.000Z', claims }, scope: claudeScope };
|
|
62
|
+
expect((0, resolve_1.resolveEventSelection)(routed)).toMatchObject({ state: 'resolved', nativeAgentType: 'awm-mechanical' });
|
|
63
|
+
expect((0, resolve_1.resolveEventWithFallback)({ ...routed, circuitReason: 'PROVIDER_REJECTED' })).toMatchObject({ state: 'resolved', nativeAgentType: 'awm-full', effectiveProfile: 'full' });
|
|
64
|
+
});
|
|
65
|
+
});
|