agentic-workflow-manager 9.11.12 → 9.12.1

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.
Files changed (83) hide show
  1. package/dist/src/commands/doctor.js +23 -1
  2. package/dist/src/commands/hooks/claude.js +61 -8
  3. package/dist/src/commands/hooks/resync.js +1 -1
  4. package/dist/src/commands/job/gate.js +6 -1
  5. package/dist/src/commands/job/index.js +61 -5
  6. package/dist/src/commands/model-policy/index.js +253 -0
  7. package/dist/src/commands/plan/index.js +53 -9
  8. package/dist/src/commands/preflight/checks.js +18 -0
  9. package/dist/src/commands/watch/apply.js +69 -5
  10. package/dist/src/commands/watch/archive-unused.js +2 -0
  11. package/dist/src/commands/watch/index.js +24 -0
  12. package/dist/src/commands/watch/init.js +2 -1
  13. package/dist/src/commands/watch/request-recovery.js +153 -0
  14. package/dist/src/commands/watch/supervisor.js +70 -6
  15. package/dist/src/core/admission/index.js +20 -6
  16. package/dist/src/core/journal/requests.js +1 -0
  17. package/dist/src/core/journal/types.js +50 -6
  18. package/dist/src/core/model-policy/capabilities-v2.js +152 -0
  19. package/dist/src/core/model-policy/capabilities.js +7 -1
  20. package/dist/src/core/model-policy/claude-hook-events.js +120 -0
  21. package/dist/src/core/model-policy/event-store.js +221 -0
  22. package/dist/src/core/model-policy/hook-diagnostics.js +94 -0
  23. package/dist/src/core/model-policy/journal.js +113 -18
  24. package/dist/src/core/model-policy/local-claude-scope.js +41 -0
  25. package/dist/src/core/model-policy/local-codex-scope.js +33 -0
  26. package/dist/src/core/model-policy/local-event-scope.js +13 -0
  27. package/dist/src/core/model-policy/machine-key.js +56 -0
  28. package/dist/src/core/model-policy/native-claude-agent-definition.js +82 -0
  29. package/dist/src/core/model-policy/native-claude-config.js +107 -0
  30. package/dist/src/core/model-policy/native-claude-machine.js +58 -0
  31. package/dist/src/core/model-policy/native-claude-transcript.js +80 -0
  32. package/dist/src/core/model-policy/native-claude.js +41 -0
  33. package/dist/src/core/model-policy/native-codex-machine.js +116 -0
  34. package/dist/src/core/model-policy/native-codex-rollout.js +111 -0
  35. package/dist/src/core/model-policy/native-codex.js +302 -0
  36. package/dist/src/core/model-policy/native-routing-observe.js +106 -0
  37. package/dist/src/core/model-policy/native-routing-proof.js +52 -0
  38. package/dist/src/core/model-policy/native-runtime.js +86 -0
  39. package/dist/src/core/model-policy/resolve.js +96 -6
  40. package/dist/src/core/model-policy/selection-policy-digest.js +22 -0
  41. package/dist/src/core/model-policy/setup-readiness.js +77 -0
  42. package/dist/src/providers/index.js +3 -1
  43. package/dist/tests/commands/doctor-routing.test.js +22 -0
  44. package/dist/tests/commands/doctor.test.js +5 -0
  45. package/dist/tests/commands/hooks/install.test.js +6 -0
  46. package/dist/tests/commands/hooks/resync.test.js +3 -0
  47. package/dist/tests/commands/hooks/status.test.js +12 -1
  48. package/dist/tests/commands/job/routing-file.test.js +32 -0
  49. package/dist/tests/commands/job/routing-report-cli.test.js +4 -3
  50. package/dist/tests/commands/model-policy/claude-hook-flow.test.js +104 -0
  51. package/dist/tests/commands/model-policy/index.test.js +179 -0
  52. package/dist/tests/commands/preflight/preflight.test.js +65 -0
  53. package/dist/tests/commands/watch/apply.test.js +84 -0
  54. package/dist/tests/commands/watch/dispatch-routing-identity.test.js +23 -0
  55. package/dist/tests/commands/watch/request-recovery.test.js +253 -0
  56. package/dist/tests/core/admission/index.test.js +21 -0
  57. package/dist/tests/core/model-policy/capabilities-v2.test.js +42 -0
  58. package/dist/tests/core/model-policy/claude-hook-events.test.js +40 -0
  59. package/dist/tests/core/model-policy/event-store-claude.test.js +64 -0
  60. package/dist/tests/core/model-policy/event-store.test.js +151 -0
  61. package/dist/tests/core/model-policy/hook-diagnostics.test.js +34 -0
  62. package/dist/tests/core/model-policy/journal.test.js +112 -1
  63. package/dist/tests/core/model-policy/local-claude-scope.test.js +31 -0
  64. package/dist/tests/core/model-policy/machine-key.test.js +38 -0
  65. package/dist/tests/core/model-policy/native-claude-agent-definition.test.js +31 -0
  66. package/dist/tests/core/model-policy/native-claude-config.test.js +53 -0
  67. package/dist/tests/core/model-policy/native-claude-machine.test.js +18 -0
  68. package/dist/tests/core/model-policy/native-claude-transcript.test.js +45 -0
  69. package/dist/tests/core/model-policy/native-claude.test.js +21 -0
  70. package/dist/tests/core/model-policy/native-codex-machine.test.js +34 -0
  71. package/dist/tests/core/model-policy/native-codex-rollout.test.js +44 -0
  72. package/dist/tests/core/model-policy/native-codex.test.js +133 -0
  73. package/dist/tests/core/model-policy/native-routing-observe.test.js +60 -0
  74. package/dist/tests/core/model-policy/native-routing-proof.test.js +30 -0
  75. package/dist/tests/core/model-policy/native-runtime.test.js +14 -0
  76. package/dist/tests/core/model-policy/resolve-event.test.js +65 -0
  77. package/dist/tests/core/model-policy/setup-readiness.test.js +83 -0
  78. package/dist/tests/providers/index.test.js +1 -1
  79. package/package.json +2 -2
  80. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  81. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  82. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  83. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -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
+ });
@@ -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
+ });