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,221 @@
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
+ exports.claudeAgentIdDigest = claudeAgentIdDigest;
7
+ exports.readStoredEventReceipt = readStoredEventReceipt;
8
+ exports.publishCodexObservation = publishCodexObservation;
9
+ exports.publishClaudeObservation = publishClaudeObservation;
10
+ const crypto_1 = require("crypto");
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const paths_1 = require("../paths");
14
+ const json_1 = require("../plan/json");
15
+ const native_bridge_1 = require("../secure-fs/native-bridge");
16
+ const capabilities_1 = require("./capabilities");
17
+ const capabilities_v2_1 = require("./capabilities-v2");
18
+ const native_codex_1 = require("./native-codex");
19
+ const native_claude_1 = require("./native-claude");
20
+ const native_claude_transcript_1 = require("./native-claude-transcript");
21
+ const native_claude_agent_definition_1 = require("./native-claude-agent-definition");
22
+ const machine_key_1 = require("./machine-key");
23
+ const validate_1 = require("./validate");
24
+ const MAX_RECEIPT_BYTES = 256 * 1024;
25
+ const SEAL_DOMAIN = 'AWM routing-capabilities/v2 local receipt\0';
26
+ const CLAUDE_AGENT_DOMAIN = 'AWM Claude native agent ID/v1\0';
27
+ function claudeAgentIdDigest(agentId, key) {
28
+ if (typeof agentId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(agentId) || !Buffer.isBuffer(key) || key.length !== 32)
29
+ throw new Error('Claude native agent identity is invalid');
30
+ return (0, crypto_1.createHmac)('sha256', key).update(CLAUDE_AGENT_DOMAIN).update(agentId).digest('hex');
31
+ }
32
+ function receiptPath(runtime) {
33
+ const valid = (0, capabilities_1.validateRuntimeKey)(runtime);
34
+ return path_1.default.join((0, paths_1.awmHome)(), 'routing-capabilities-v2', valid.target, `${valid.kind}.json`);
35
+ }
36
+ function safeParents(file, create) {
37
+ const chain = [];
38
+ for (let dir = path_1.default.dirname(file);; dir = path_1.default.dirname(dir)) {
39
+ chain.unshift(dir);
40
+ if (dir === path_1.default.dirname(dir))
41
+ break;
42
+ }
43
+ for (const dir of chain) {
44
+ try {
45
+ const stat = fs_1.default.lstatSync(dir);
46
+ if (!stat.isDirectory() || stat.isSymbolicLink())
47
+ throw new Error('event receipt has unsafe or symlinked ancestor');
48
+ }
49
+ catch (error) {
50
+ if (error.code !== 'ENOENT' || !create)
51
+ throw error;
52
+ fs_1.default.mkdirSync(dir, { mode: 0o700 });
53
+ }
54
+ }
55
+ }
56
+ function readFile(file) {
57
+ try {
58
+ safeParents(file, false);
59
+ const stat = fs_1.default.lstatSync(file);
60
+ if (!stat.isFile() || stat.isSymbolicLink())
61
+ throw new Error('event receipt is unsafe or symlinked');
62
+ const bytes = native_bridge_1.secureFs.readRegularFile(file, MAX_RECEIPT_BYTES).bytes;
63
+ const parsed = (0, json_1.parseJsonNoDuplicate)(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
64
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
65
+ throw new Error('event receipt envelope is invalid');
66
+ const envelope = parsed;
67
+ if (Object.keys(envelope).length !== 2 || !Object.keys(envelope).every(key => ['receipt', 'mac'].includes(key))
68
+ || typeof envelope.mac !== 'string' || !/^[a-f0-9]{64}$/.test(envelope.mac))
69
+ throw new Error('event receipt seal is invalid');
70
+ const receipt = (0, capabilities_v2_1.validateEventReceipt)(envelope.receipt);
71
+ const key = (0, machine_key_1.readMachineKey)((0, paths_1.awmHome)());
72
+ if (!key)
73
+ throw new Error('event receipt machine key is missing');
74
+ const expected = seal(receipt, key);
75
+ if (!(0, crypto_1.timingSafeEqual)(Buffer.from(envelope.mac, 'hex'), Buffer.from(expected, 'hex')))
76
+ throw new Error('event receipt seal does not match this machine');
77
+ return receipt;
78
+ }
79
+ catch (error) {
80
+ if (error.code === 'ENOENT')
81
+ return null;
82
+ throw error;
83
+ }
84
+ }
85
+ function seal(receipt, key) {
86
+ return (0, crypto_1.createHmac)('sha256', key).update(SEAL_DOMAIN).update(JSON.stringify(receipt)).digest('hex');
87
+ }
88
+ function readStoredEventReceipt(runtime) {
89
+ try {
90
+ const value = readFile(receiptPath(runtime));
91
+ return value === null ? { state: 'absent' } : { state: 'present', receipt: value };
92
+ }
93
+ catch (error) {
94
+ return { state: 'invalid', reason: error.message.slice(0, 4096) };
95
+ }
96
+ }
97
+ function sameSelection(left, right) {
98
+ return left.selector.kind === right.selector.kind && left.selector.id === right.selector.id && left.effort.kind === right.effort.kind
99
+ && (left.effort.kind !== 'explicit' || left.effort.value === right.effort.value);
100
+ }
101
+ function sameScope(left, right) {
102
+ return left.runtime.target === right.runtime.target && left.runtime.kind === right.runtime.kind && left.runtime.version === right.runtime.version
103
+ && left.runtime.accountScopeDigest === right.runtime.accountScopeDigest && left.binaryDigest === right.binaryDigest && left.configDigest === right.configDigest;
104
+ }
105
+ /** The only Codex v2 writer takes a native observation, never a candidate JSON.
106
+ * The CLI must call the native adapter and derive scope from this machine. */
107
+ function publishCodexObservation(input) {
108
+ if (!input || typeof input !== 'object' || !(0, native_codex_1.isNativeCodexChildObservation)(input.observation))
109
+ throw new Error('Codex native provenance is required');
110
+ if (input.observation.provenance !== 'codex-app-server-thread-read'
111
+ || input.observation.nativeDispatchVerified !== true || input.observation.acceptedSelectionVerified !== true
112
+ || input.observation.actualModelVerified !== false || input.observation.tokenUsage !== 'unknown')
113
+ throw new Error('Codex native selection is unverified');
114
+ const scope = input.scope;
115
+ if (!scope || scope.runtime?.target !== 'codex' || input.observation.childRuntimeVersion !== scope.runtime.version)
116
+ throw new Error('Codex native runtime version mismatch');
117
+ if (!sameSelection(input.observation.configuredSelection, input.expectedSelection))
118
+ throw new Error('Codex observed selection does not match approved selection');
119
+ (0, validate_1.assertDigest)(input.mappingDigest, 'mappingDigest');
120
+ if (!(input.now instanceof Date) || !Number.isFinite(input.now.getTime()))
121
+ throw new Error('event receipt clock is invalid');
122
+ const recordedAt = input.now.toISOString();
123
+ if (typeof input.observation.observedAt !== 'string' || !Number.isFinite(Date.parse(input.observation.observedAt)))
124
+ throw new Error('Codex native turn timestamp is unavailable');
125
+ const eventDigest = (0, crypto_1.createHash)('sha256').update(JSON.stringify({ parent: input.observation.parentThreadId, child: input.observation.childThreadId,
126
+ turn: input.observation.turnId, selection: input.observation.configuredSelection, runtimeVersion: input.observation.childRuntimeVersion,
127
+ accountScopeDigest: scope.runtime.accountScopeDigest })).digest('hex');
128
+ const claim = { selection: input.expectedSelection, mappingDigest: input.mappingDigest, eventDigest,
129
+ source: 'codex-turn-context', observedAt: input.observation.observedAt, actualModel: 'unverified', tokenUsage: 'unknown' };
130
+ const initial = (0, capabilities_v2_1.validateEventReceipt)({ schema: 'routing-capabilities/v2', ...scope, recordedAt, claims: [claim] });
131
+ const file = receiptPath(initial.runtime);
132
+ const root = (0, paths_1.awmHome)();
133
+ return native_bridge_1.secureFs.withProjectLease(root, () => {
134
+ const key = (0, machine_key_1.readMachineKey)(root);
135
+ if (!key)
136
+ throw new Error('event receipt machine key is missing; run explicit setup');
137
+ safeParents(file, true);
138
+ const current = readFile(file);
139
+ if (current && sameScope(current, scope)) {
140
+ const prior = current.claims.find(item => sameSelection(item.selection, claim.selection));
141
+ if (prior && prior.mappingDigest === claim.mappingDigest && prior.eventDigest === claim.eventDigest)
142
+ return current;
143
+ }
144
+ const ageMs = input.now.getTime() - Date.parse(input.observation.observedAt);
145
+ if (ageMs < 0 || ageMs > 15 * 60 * 1000)
146
+ throw new Error('Codex native turn is not fresh for enrollment');
147
+ const claims = current && sameScope(current, scope) ? current.claims.filter(item => !sameSelection(item.selection, claim.selection)).concat(claim) : [claim];
148
+ const next = (0, capabilities_v2_1.validateEventReceipt)({ ...initial, claims });
149
+ const destination = path_1.default.relative(root, file).split(path_1.default.sep).join('/');
150
+ const bytes = Buffer.from(`${JSON.stringify({ receipt: next, mac: seal(next, key) }, null, 2)}\n`);
151
+ if (current) {
152
+ const observed = native_bridge_1.secureFs.readRegularFile(file, MAX_RECEIPT_BYTES);
153
+ native_bridge_1.secureFs.writeProjectTransaction(root, destination, bytes, { mode: 'replace', createParents: false, expected: observed.bytes, expectedIdentity: observed.identity });
154
+ }
155
+ else
156
+ native_bridge_1.secureFs.writeProjectTransaction(root, destination, bytes, { mode: 'create', createParents: false });
157
+ return next;
158
+ });
159
+ }
160
+ /** Bind a native hook pair to the provider's assistant transcript. The transcript
161
+ * reports a model, not effective effort or paid usage, so only runtime-default
162
+ * effort may be enrolled and usage remains unknown. */
163
+ function publishClaudeObservation(input) {
164
+ if (!input || typeof input !== 'object' || !(0, native_claude_1.isNativeClaudeSubagentLifecycle)(input.lifecycle)
165
+ || !(0, native_claude_transcript_1.isNativeClaudeTranscriptObservation)(input.transcript))
166
+ throw new Error('Claude native hook and transcript provenance are required');
167
+ if (!(0, native_claude_agent_definition_1.isNativeClaudeAgentDeclaration)(input.declaredAgent) || input.declaredAgent.agentType !== input.lifecycle.agentType
168
+ || input.declaredAgent.modelId !== input.transcript.actualModel)
169
+ throw new Error('Claude declared agent model provenance is required');
170
+ if (input.scope?.runtime?.target !== 'claude-code' || input.lifecycle.agentId !== input.transcript.agentId
171
+ || input.expectedSelection?.selector?.kind !== 'model' || input.expectedSelection.selector.id !== input.transcript.actualModel
172
+ || input.expectedSelection.effort.kind !== 'runtime-default')
173
+ throw new Error('Claude native selection does not match approved model or effort');
174
+ (0, validate_1.assertDigest)(input.mappingDigest, 'mappingDigest');
175
+ if (!(input.now instanceof Date) || !Number.isFinite(input.now.getTime()))
176
+ throw new Error('event receipt clock is invalid');
177
+ const eventDigest = (0, crypto_1.createHash)('sha256').update(JSON.stringify({ session: input.lifecycle.sessionId, agent: input.lifecycle.agentId,
178
+ transcriptDigest: input.transcript.evidenceDigest, accountScopeDigest: input.scope.runtime.accountScopeDigest })).digest('hex');
179
+ const nativeAgentIdDigest = claudeAgentIdDigest(input.lifecycle.agentId, (0, machine_key_1.readMachineKey)((0, paths_1.awmHome)()));
180
+ const nativeEvent = { nativeAgentIdDigest, eventDigest, observedAt: input.transcript.observedAt };
181
+ const claim = { selection: input.expectedSelection, mappingDigest: input.mappingDigest, eventDigest,
182
+ nativeAgentType: input.lifecycle.agentType,
183
+ nativeAgentIdDigest, nativeEvents: [nativeEvent],
184
+ source: 'claude-assistant-transcript', observedAt: input.transcript.observedAt,
185
+ actualModel: { id: input.transcript.actualModel, evidenceDigest: input.transcript.evidenceDigest }, tokenUsage: 'unknown' };
186
+ const initial = (0, capabilities_v2_1.validateEventReceipt)({ schema: 'routing-capabilities/v2', ...input.scope, recordedAt: input.now.toISOString(), claims: [claim] });
187
+ const file = receiptPath(initial.runtime);
188
+ const root = (0, paths_1.awmHome)();
189
+ return native_bridge_1.secureFs.withProjectLease(root, () => {
190
+ const key = (0, machine_key_1.readMachineKey)(root);
191
+ if (!key)
192
+ throw new Error('event receipt machine key is missing; run explicit setup');
193
+ safeParents(file, true);
194
+ const current = readFile(file);
195
+ if (current && sameScope(current, input.scope)) {
196
+ const prior = current.claims.find(item => sameSelection(item.selection, claim.selection));
197
+ if (prior && prior.mappingDigest === claim.mappingDigest && prior.eventDigest === claim.eventDigest)
198
+ return current;
199
+ }
200
+ const ageMs = input.now.getTime() - Date.parse(input.transcript.observedAt);
201
+ if (!Number.isFinite(ageMs) || ageMs < 0 || ageMs > 15 * 60 * 1000)
202
+ throw new Error('Claude native transcript is not fresh for enrollment');
203
+ const prior = current && sameScope(current, input.scope) ? current.claims.find(item => sameSelection(item.selection, claim.selection)) : undefined;
204
+ const previousEvents = prior?.nativeEvents ?? (prior?.nativeAgentIdDigest ? [{ nativeAgentIdDigest: prior.nativeAgentIdDigest,
205
+ eventDigest: prior.eventDigest, observedAt: prior.observedAt }] : []);
206
+ const recentEvents = previousEvents.filter(item => input.now.getTime() - Date.parse(item.observedAt) <= 15 * 60_000
207
+ && item.nativeAgentIdDigest !== nativeAgentIdDigest && item.eventDigest !== eventDigest);
208
+ const mergedClaim = { ...claim, nativeEvents: recentEvents.concat(nativeEvent).slice(-64) };
209
+ const claims = current && sameScope(current, input.scope) ? current.claims.filter(item => !sameSelection(item.selection, claim.selection)).concat(mergedClaim) : [mergedClaim];
210
+ const next = (0, capabilities_v2_1.validateEventReceipt)({ ...initial, claims });
211
+ const destination = path_1.default.relative(root, file).split(path_1.default.sep).join('/');
212
+ const bytes = Buffer.from(`${JSON.stringify({ receipt: next, mac: seal(next, key) }, null, 2)}\n`);
213
+ if (current) {
214
+ const observed = native_bridge_1.secureFs.readRegularFile(file, MAX_RECEIPT_BYTES);
215
+ native_bridge_1.secureFs.writeProjectTransaction(root, destination, bytes, { mode: 'replace', createParents: false, expected: observed.bytes, expectedIdentity: observed.identity });
216
+ }
217
+ else
218
+ native_bridge_1.secureFs.writeProjectTransaction(root, destination, bytes, { mode: 'create', createParents: false });
219
+ return next;
220
+ });
221
+ }
@@ -0,0 +1,94 @@
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
+ exports.recordRoutingHookStatus = recordRoutingHookStatus;
7
+ exports.readRoutingHookStatus = readRoutingHookStatus;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const paths_1 = require("../paths");
11
+ const json_1 = require("../plan/json");
12
+ const native_bridge_1 = require("../secure-fs/native-bridge");
13
+ const fresh = () => ({ schema: 'routing-hook-status/v1', start: { count: 0 }, stop: { count: 0 } });
14
+ function target() { return path_1.default.join((0, paths_1.awmHome)(), 'routing-hook-status.json'); }
15
+ function safeParents(file) {
16
+ const chain = [];
17
+ for (let dir = path_1.default.dirname(file);; dir = path_1.default.dirname(dir)) {
18
+ chain.unshift(dir);
19
+ if (dir === path_1.default.dirname(dir))
20
+ break;
21
+ }
22
+ for (const dir of chain) {
23
+ const stat = fs_1.default.lstatSync(dir);
24
+ if (!stat.isDirectory() || stat.isSymbolicLink())
25
+ throw new Error('routing hook status has an unsafe ancestor');
26
+ }
27
+ }
28
+ function read() {
29
+ const file = target();
30
+ safeParents(file);
31
+ let stat;
32
+ try {
33
+ stat = fs_1.default.lstatSync(file);
34
+ }
35
+ catch (error) {
36
+ if (error.code === 'ENOENT')
37
+ return { status: fresh(), observed: null };
38
+ throw error;
39
+ }
40
+ if (!stat.isFile() || stat.isSymbolicLink())
41
+ throw new Error('routing hook status is unsafe');
42
+ const observed = native_bridge_1.secureFs.readRegularFile(file, 8192);
43
+ const value = (0, json_1.parseJsonNoDuplicate)(new TextDecoder('utf-8', { fatal: true }).decode(observed.bytes));
44
+ if (!value || value.schema !== 'routing-hook-status/v1' || !value.start || !value.stop
45
+ || !Number.isSafeInteger(value.start.count) || !Number.isSafeInteger(value.stop.count)
46
+ || value.start.count < 0 || value.stop.count < 0)
47
+ throw new Error('routing hook status is corrupt');
48
+ return { status: value, observed };
49
+ }
50
+ /** Failure is a redacted code, never provider payload or an exception string.
51
+ * Returns true only for a newly alertable failure episode. */
52
+ function recordRoutingHookStatus(phase, result, now = new Date()) {
53
+ if (phase !== 'start' && phase !== 'stop')
54
+ throw new Error('routing hook phase is invalid');
55
+ if (result !== 'success' && !/^[A-Z0-9_]{3,64}$/.test(result))
56
+ throw new Error('routing hook reason code is invalid');
57
+ if (!(now instanceof Date) || !Number.isFinite(now.getTime()))
58
+ throw new Error('routing hook clock is invalid');
59
+ const root = (0, paths_1.awmHome)();
60
+ return native_bridge_1.secureFs.withProjectLease(root, () => {
61
+ const { status, observed } = read();
62
+ const item = status[phase];
63
+ const pending = item.lastFailureAt !== undefined && (item.lastSuccessAt === undefined || item.lastFailureAt > item.lastSuccessAt);
64
+ const alert = result !== 'success' && (!pending || item.reasonCode !== result);
65
+ if (result === 'success')
66
+ item.lastSuccessAt = now.toISOString();
67
+ else {
68
+ item.lastFailureAt = now.toISOString();
69
+ item.reasonCode = result;
70
+ item.count += 1;
71
+ }
72
+ const bytes = Buffer.from(`${JSON.stringify(status)}\n`);
73
+ native_bridge_1.secureFs.writeProjectTransaction(root, path_1.default.basename(target()), bytes, observed
74
+ ? { mode: 'replace', createParents: false, expected: observed.bytes, expectedIdentity: observed.identity }
75
+ : { mode: 'create', createParents: false });
76
+ return alert;
77
+ });
78
+ }
79
+ function readRoutingHookStatus() {
80
+ try {
81
+ const { status, observed } = read();
82
+ if (!observed)
83
+ return { state: 'absent' };
84
+ const failures = ['start', 'stop'].flatMap(phase => {
85
+ const item = status[phase];
86
+ return item.lastFailureAt && item.reasonCode && (!item.lastSuccessAt || item.lastFailureAt > item.lastSuccessAt)
87
+ ? [{ phase, reasonCode: item.reasonCode, count: item.count }] : [];
88
+ });
89
+ return failures.length ? { state: 'failed', failures } : { state: 'healthy' };
90
+ }
91
+ catch {
92
+ return { state: 'invalid' };
93
+ }
94
+ }
@@ -21,6 +21,26 @@ function canonical(value) { if (Array.isArray(value))
21
21
  function digest(value) { return crypto_1.default.createHash('sha256').update(canonical(value)).digest('hex'); }
22
22
  function clone(state) { const next = structuredClone(state); if (!Array.isArray(next.routingAttempts))
23
23
  next.routingAttempts = []; return next; }
24
+ function noteRoutingIncident(state, target, selection, reasonCode, at, fallback, sameObligationAlreadyCounted = false) {
25
+ if (!/^[A-Z0-9_]{3,64}$/.test(reasonCode))
26
+ throw new Error('routing incident reason is invalid');
27
+ state.routingIncidents ??= [];
28
+ // One incident/alert per selected route in a run even if later failures
29
+ // refine the cause; retain the first actionable reason.
30
+ const id = crypto_1.default.createHash('sha256').update(`${state.journalId}\0${state.cycle.startedAt}\0${target}\0${canonical(selection)}`).digest('hex').slice(0, 24);
31
+ const prior = state.routingIncidents.find(item => item.id === id);
32
+ if (prior) {
33
+ prior.affectedObligations += Number(!sameObligationAlreadyCounted);
34
+ prior.fallbackCount += Number(fallback);
35
+ prior.blockedCount += Number(!fallback);
36
+ prior.lastAt = at;
37
+ return;
38
+ }
39
+ if (state.routingIncidents.length >= 4096)
40
+ throw new Error('routing incident limit exhausted');
41
+ state.routingIncidents.push({ schema: 'routing-incident/v1', id, runStartedAt: state.cycle.startedAt, target, selection: structuredClone(selection), reasonCode,
42
+ firstAt: at, lastAt: at, affectedObligations: 1, fallbackCount: Number(fallback), blockedCount: Number(!fallback), alertState: 'pending' });
43
+ }
24
44
  function reserveRoutingAttempt(state, input, now) {
25
45
  assertTimestamp(now);
26
46
  if (!(0, types_1.isWellFormedState)(state) || !input || typeof input.obligationId !== 'string' || input.obligationId.length === 0 || input.obligationId.length > 128 || typeof input.lineageId !== 'string' || input.lineageId.length === 0 || input.lineageId.length > 128 || !(0, types_1.isRoutingEnvelope)(input.envelope) || !/^[a-f0-9]{64}$/.test(input.fingerprint))
@@ -35,34 +55,62 @@ function reserveRoutingAttempt(state, input, now) {
35
55
  next.implementationLineages ??= [];
36
56
  const priorLineage = next.implementationLineages.find(lineage => lineage.id === input.lineageId);
37
57
  if (input.envelope.role === 'implementer' && priorLineage === undefined)
38
- next.implementationLineages.push({ id: input.lineageId, obligationId: input.obligationId, sliceId: input.envelope.sliceId, planDigest: input.envelope.planDigest, executionDigest: input.envelope.executionDigest, initialProfile: input.envelope.requestedProfile, initialEffort: input.envelope.resolved.effort.kind === 'explicit' && input.envelope.resolved.effort.value === 'high' ? 'high' : 'medium', attempts: 0 });
58
+ next.implementationLineages.push({ id: input.lineageId, obligationId: input.obligationId, sliceId: input.envelope.sliceId, planDigest: input.envelope.planDigest, executionDigest: input.envelope.executionDigest, initialProfile: input.envelope.requestedProfile, initialEffort: input.envelope.resolved.effort.kind === 'runtime-default' ? 'runtime-default' : input.envelope.resolved.effort.value === 'high' ? 'high' : 'medium', attempts: 0 });
39
59
  if (priorLineage !== undefined && (priorLineage.obligationId !== input.obligationId || priorLineage.planDigest !== input.envelope.planDigest || priorLineage.executionDigest !== input.envelope.executionDigest || priorLineage.sliceId !== input.envelope.sliceId || priorLineage.initialProfile !== input.envelope.requestedProfile))
40
60
  throw new Error('routing lineage binding mismatch');
41
61
  const attempts = next.routingAttempts.filter(item => item.lineageId === input.lineageId && item.envelope.role === 'implementer');
42
62
  if (input.envelope.role === 'implementer' && attempts.length >= 3)
43
63
  throw new Error('routing implementation budget exhausted');
64
+ const fullFallback = input.envelope.role === 'implementer' && input.envelope.effectiveProfile === 'full';
65
+ if (fullFallback && attempts.some(item => item.envelope.effectiveProfile === 'full'))
66
+ throw new Error('routing full fallback cannot recursively retry a lineage');
67
+ if (fullFallback && attempts.length > 0) {
68
+ const previous = attempts[attempts.length - 1];
69
+ if (previous.state !== 'blocked' || previous.verdict !== 'inconclusive'
70
+ || !['PROVIDER_REJECTED', 'SELECTION_MISMATCH', 'PROVENANCE_MISSING'].includes(previous.reasonCode ?? ''))
71
+ throw new Error('routing full fallback requires a terminal native routing failure');
72
+ }
44
73
  if (input.envelope.role === 'implementer') {
45
74
  const initial = input.envelope.requestedProfile;
46
75
  if (initial === 'full')
47
76
  throw new Error('routing implementer profile is invalid');
48
- const expected = resolveLineageEscalation(next, input.lineageId, initial);
49
- const actualEffort = input.envelope.resolved.effort.kind === 'explicit' ? input.envelope.resolved.effort.value : 'runtime-default';
50
- const judgmentHighAfterIntegration = expected.profile === 'judgment' && expected.effort === 'medium' && actualEffort === 'high' && attempts.length > 0 && attempts[attempts.length - 1].envelope.effectiveProfile === 'integration';
51
- if (input.envelope.effectiveProfile !== expected.profile || (actualEffort !== expected.effort && !judgmentHighAfterIntegration))
52
- throw new Error('routing effective escalation mismatch');
77
+ if (!fullFallback) {
78
+ const expected = resolveLineageEscalation(next, input.lineageId, initial);
79
+ const actualEffort = input.envelope.resolved.effort.kind === 'explicit' ? input.envelope.resolved.effort.value : 'runtime-default';
80
+ const judgmentHighAfterIntegration = expected.profile === 'judgment' && expected.effort === 'medium' && actualEffort === 'high' && attempts.length > 0 && attempts[attempts.length - 1].envelope.effectiveProfile === 'integration';
81
+ if (input.envelope.effectiveProfile !== expected.profile || (actualEffort !== expected.effort && !judgmentHighAfterIntegration))
82
+ throw new Error('routing effective escalation mismatch');
83
+ }
53
84
  }
54
85
  const approval = input.approval?.();
55
86
  if (!approval)
56
87
  throw new Error('routing reservation requires current approval');
57
- const selectionInput = { role: input.envelope.role, requestedProfile: input.envelope.role === 'implementer' ? input.envelope.effectiveProfile : 'full', runtime: (0, capabilities_1.validateRuntimeKey)(input.envelope.runtime), policy: approval.policy, capabilities: approval.capabilities, now: approval.checkedAt };
88
+ if ((approval.eventReceipt || approval.eventScope) && (!approval.eventReceipt || !approval.eventScope || canonical(approval.eventScope.runtime) !== canonical(input.envelope.runtime)))
89
+ throw new Error('routing reservation event scope mismatch');
90
+ const selectionInput = { role: input.envelope.role, requestedProfile: input.envelope.role === 'implementer' ? input.envelope.effectiveProfile : 'full', runtime: (0, capabilities_1.validateRuntimeKey)(input.envelope.runtime), policy: approval.policy, capabilities: approval.capabilities, eventReceipt: approval.eventReceipt, eventScope: approval.eventScope, circuitIncidents: next.routingIncidents, now: approval.checkedAt };
91
+ const mapping = approval.policy?.content.mappings.find(item => item.target === input.envelope.runtime.target && item.runtimeKind === input.envelope.runtime.kind);
92
+ const optimizedSelection = mapping && input.envelope.role === 'implementer' && input.envelope.requestedProfile !== 'full' ? mapping.profiles[input.envelope.requestedProfile] : undefined;
93
+ const circuitReason = optimizedSelection && next.routingIncidents?.find(item => item.target === input.envelope.runtime.target && canonical(item.selection) === canonical(optimizedSelection)
94
+ && ['PROVIDER_REJECTED', 'SELECTION_MISMATCH', 'PROVENANCE_MISSING'].includes(item.reasonCode))?.reasonCode;
58
95
  const resolved = input.envelope.role === 'implementer' && input.envelope.effectiveProfile === 'judgment' && input.envelope.resolved.effort.kind === 'explicit' && input.envelope.resolved.effort.value === 'high'
59
96
  ? (0, resolve_1.resolveEscalatedSelection)({ ...selectionInput, role: 'implementer', requestedProfile: 'judgment', expectedEffort: 'high' })
60
- : (0, resolve_1.resolveSelection)(selectionInput);
61
- if (resolved.state !== 'resolved' || resolved.effectiveProfile !== input.envelope.effectiveProfile || canonical(resolved.selection) !== canonical(input.envelope.resolved) || resolved.policyDigest !== input.envelope.policyDigest || resolved.capabilityDigest !== input.envelope.capabilityDigest || resolved.outcome !== input.envelope.outcome || canonical(resolved.unavailableEvidence) !== canonical(input.envelope.unavailableEvidence))
97
+ : approval.eventReceipt && approval.eventScope
98
+ ? (0, resolve_1.resolveEventWithFallback)({ role: selectionInput.role, requestedProfile: input.envelope.role === 'implementer' ? fullFallback ? input.envelope.requestedProfile : input.envelope.effectiveProfile : 'full', policy: approval.policy, receipt: approval.eventReceipt, scope: approval.eventScope, now: approval.checkedAt, circuitReason })
99
+ : (0, resolve_1.resolveSelection)(selectionInput);
100
+ if (fullFallback && (resolved.state !== 'resolved' || !resolved.fallbackReason))
101
+ throw new Error('routing full fallback requires a current optimized-route failure');
102
+ if (resolved.state !== 'resolved' || resolved.effectiveProfile !== input.envelope.effectiveProfile || canonical(resolved.selection) !== canonical(input.envelope.resolved) || resolved.policyDigest !== input.envelope.policyDigest || resolved.capabilityDigest !== input.envelope.capabilityDigest || resolved.outcome !== input.envelope.outcome || canonical(resolved.unavailableEvidence) !== canonical(input.envelope.unavailableEvidence) || resolved.nativeAgentType !== input.envelope.nativeAgentType)
62
103
  throw new Error('routing reservation approval mismatch');
63
104
  const attempt = attempts.length + 1;
64
105
  const id = `route-${crypto_1.default.createHash('sha256').update(`${input.lineageId}\0${attempt}\0${envelopeDigest}\0${input.fingerprint}`).digest('hex').slice(0, 24)}`;
65
- next.routingAttempts.push({ schema: 'routing-attempt/v1', id, obligationId: input.obligationId, lineageId: input.lineageId, attempt, envelope: structuredClone(input.envelope), envelopeDigest, fingerprint: input.fingerprint, state: 'reserved' });
106
+ next.routingAttempts.push({ schema: 'routing-attempt/v1', id, obligationId: input.obligationId, lineageId: input.lineageId, attempt, envelope: structuredClone(input.envelope), envelopeDigest, fingerprint: input.fingerprint, state: 'reserved', reservedAt: now, ...(approval.eventReceipt ? { nativeEvidenceRequired: true } : {}), ...(resolved.state === 'resolved' && resolved.fallbackReason ? { reasonCode: resolved.fallbackReason } : {}) });
107
+ if (resolved.state === 'resolved' && resolved.fallbackReason) {
108
+ const mapping = approval.policy?.content.mappings.find(item => item.target === input.envelope.runtime.target && item.runtimeKind === input.envelope.runtime.kind);
109
+ if (!mapping || input.envelope.role !== 'implementer' || input.envelope.requestedProfile === 'full')
110
+ throw new Error('routing fallback lacks approved optimized selection');
111
+ const sameObligationAlreadyCounted = attempts.some(item => item.obligationId === input.obligationId && item.state === 'blocked' && item.reasonCode === resolved.fallbackReason);
112
+ noteRoutingIncident(next, input.envelope.runtime.target, mapping.profiles[input.envelope.requestedProfile], resolved.fallbackReason, now, true, sameObligationAlreadyCounted);
113
+ }
66
114
  const lineage = next.implementationLineages.find(candidate => candidate.id === input.lineageId);
67
115
  if (lineage !== undefined)
68
116
  lineage.attempts = attempt;
@@ -70,25 +118,53 @@ function reserveRoutingAttempt(state, input, now) {
70
118
  }
71
119
  function observeRoutingAttempt(state, input, now) {
72
120
  assertTimestamp(now);
73
- if (!(0, types_1.isWellFormedState)(state) || !input || typeof input.attemptId !== 'string' || input.attemptId.length === 0 || typeof input.nativeAgentId !== 'string' || input.nativeAgentId.length === 0 || input.nativeAgentId.length > 128 || (input.observed !== undefined && !(0, types_1.isRoutingSelection)(input.observed)) || (input.unavailableReason !== undefined && (typeof input.unavailableReason !== 'string' || input.unavailableReason.length === 0 || input.unavailableReason.length > 128)))
121
+ if (!(0, types_1.isWellFormedState)(state) || !input || typeof input.attemptId !== 'string' || input.attemptId.length === 0 || typeof input.nativeAgentId !== 'string' || input.nativeAgentId.length === 0 || input.nativeAgentId.length > 128 || (input.observed !== undefined && !(0, types_1.isRoutingSelection)(input.observed)) || (input.observedBackendModel !== undefined && (typeof input.observedBackendModel !== 'string' || input.observedBackendModel.length === 0 || input.observedBackendModel.length > 128 || input.nativeEventDigest === undefined)) || (input.unavailableReason !== undefined && (typeof input.unavailableReason !== 'string' || input.unavailableReason.length === 0 || input.unavailableReason.length > 128)))
74
122
  throw new Error('routing observation is invalid');
75
123
  const next = clone(state);
76
124
  const attempt = next.routingAttempts.find(item => item.id === input.attemptId);
77
125
  if (!attempt)
78
126
  throw new Error('routing attempt is unknown');
79
127
  if (attempt.state === 'active') {
80
- if (attempt.nativeAgentId === input.nativeAgentId && canonical(attempt.observed) === canonical(input.observed))
128
+ if (attempt.nativeAgentId === input.nativeAgentId && canonical(attempt.observed) === canonical(input.observed) && attempt.observedBackendModel === input.observedBackendModel)
129
+ return next;
130
+ throw new Error('routing observation is immutable');
131
+ }
132
+ if (attempt.state === 'blocked') {
133
+ const mismatch = input.observed !== undefined && canonical(input.observed) !== canonical(attempt.envelope.resolved);
134
+ const reason = mismatch ? 'SELECTION_MISMATCH' : ['PROVIDER_REJECTED', 'PROVENANCE_MISSING'].includes(input.unavailableReason ?? '') ? input.unavailableReason : undefined;
135
+ if (attempt.nativeAgentId === input.nativeAgentId && canonical(attempt.observed) === canonical(input.observed) && reason === attempt.reasonCode)
81
136
  return next;
82
137
  throw new Error('routing observation is immutable');
83
138
  }
84
139
  if (attempt.state !== 'reserved')
85
140
  throw new Error('routing attempt cannot be observed');
86
- attempt.state = 'active';
141
+ if (input.nativeEventDigest !== undefined) {
142
+ if (!/^[a-f0-9]{64}$/.test(input.nativeEventDigest) || next.routingAttempts.some(item => item.id !== attempt.id
143
+ && (item.nativeEventDigest === input.nativeEventDigest || (item.nativeEvidenceRequired && item.nativeAgentId === input.nativeAgentId))))
144
+ throw new Error('native routing event was already consumed');
145
+ attempt.nativeEventDigest = input.nativeEventDigest;
146
+ }
87
147
  attempt.nativeAgentId = input.nativeAgentId;
88
148
  if (input.observed)
89
149
  attempt.observed = structuredClone(input.observed);
90
- if (input.unavailableReason)
91
- attempt.reasonCode = input.unavailableReason;
150
+ if (input.observedBackendModel)
151
+ attempt.observedBackendModel = input.observedBackendModel;
152
+ const mismatch = input.observed !== undefined && canonical(input.observed) !== canonical(attempt.envelope.resolved);
153
+ const unavailable = input.unavailableReason === 'PROVIDER_REJECTED' || input.unavailableReason === 'PROVENANCE_MISSING';
154
+ if (mismatch || unavailable) {
155
+ const reason = mismatch ? 'SELECTION_MISMATCH' : input.unavailableReason;
156
+ attempt.state = 'blocked';
157
+ attempt.verdict = 'inconclusive';
158
+ attempt.reasonCode = reason;
159
+ // Agent-supplied negative evidence may conservatively trip a breaker,
160
+ // but never mints or renews native capability evidence.
161
+ noteRoutingIncident(next, attempt.envelope.runtime.target, attempt.envelope.resolved, reason, now, false);
162
+ }
163
+ else {
164
+ attempt.state = 'active';
165
+ if (input.unavailableReason)
166
+ attempt.reasonCode = input.unavailableReason;
167
+ }
92
168
  return next;
93
169
  }
94
170
  /** Computes, but never persists, the only permitted implementation retry.
@@ -105,12 +181,19 @@ function resolveLineageEscalation(state, lineageId, initial) {
105
181
  return { profile: initial, effort: lineage?.initialEffort ?? 'medium' };
106
182
  }
107
183
  const last = attempts[attempts.length - 1];
184
+ if (last.envelope.effectiveProfile === 'full')
185
+ throw new Error('routing full fallback is exhausted');
186
+ if (last.verdict === 'inconclusive' && ['PROVIDER_REJECTED', 'SELECTION_MISMATCH', 'PROVENANCE_MISSING'].includes(last.reasonCode ?? '')) {
187
+ const lineage = state.implementationLineages?.find(candidate => candidate.id === lineageId);
188
+ return { profile: initial, effort: lineage?.initialEffort ?? 'medium' };
189
+ }
108
190
  if (last.verdict !== 'fail')
109
191
  throw new Error('routing lineage advances only after a terminal failed verdict');
192
+ const routeEffort = last.envelope.resolved.effort.kind === 'runtime-default' ? 'runtime-default' : 'medium';
110
193
  if (last.envelope.effectiveProfile === 'mechanical')
111
- return { profile: 'integration', effort: 'medium' };
194
+ return { profile: 'integration', effort: routeEffort };
112
195
  if (last.envelope.effectiveProfile === 'integration')
113
- return { profile: 'judgment', effort: 'medium' };
196
+ return { profile: 'judgment', effort: routeEffort };
114
197
  if (last.envelope.effectiveProfile === 'judgment' && last.envelope.resolved.effort.kind === 'explicit' && last.envelope.resolved.effort.value === 'medium')
115
198
  return { profile: 'judgment', effort: 'high' };
116
199
  throw new Error('routing lineage escalation exhausted');
@@ -121,6 +204,7 @@ function routingReport(state) {
121
204
  const attempts = state.routingAttempts ?? [];
122
205
  const plannedByRole = {};
123
206
  const actualByRole = {};
207
+ const nativeReconciledByRole = {};
124
208
  const byState = {};
125
209
  const unavailable = {};
126
210
  const verdicts = {};
@@ -128,11 +212,22 @@ function routingReport(state) {
128
212
  plannedByRole[attempt.envelope.role] = (plannedByRole[attempt.envelope.role] ?? 0) + 1;
129
213
  if (attempt.nativeAgentId !== undefined)
130
214
  actualByRole[attempt.envelope.role] = (actualByRole[attempt.envelope.role] ?? 0) + 1;
215
+ if (attempt.nativeEventDigest !== undefined && ['active', 'complete'].includes(attempt.state))
216
+ nativeReconciledByRole[attempt.envelope.role] = (nativeReconciledByRole[attempt.envelope.role] ?? 0) + 1;
131
217
  byState[attempt.state] = (byState[attempt.state] ?? 0) + 1;
132
218
  for (const reason of attempt.envelope.unavailableEvidence)
133
219
  unavailable[reason] = (unavailable[reason] ?? 0) + 1;
220
+ if (attempt.envelope.role === 'implementer' && attempt.envelope.effectiveProfile === 'full' && attempt.reasonCode)
221
+ unavailable[attempt.reasonCode] = (unavailable[attempt.reasonCode] ?? 0) + 1;
134
222
  if (attempt.verdict !== undefined)
135
223
  verdicts[attempt.verdict] = (verdicts[attempt.verdict] ?? 0) + 1;
136
224
  }
137
- return { schema: 'routing-report/v1', attempts: attempts.length, plannedByRole, actualByRole, byState, retries: attempts.filter((attempt) => attempt.attempt > 1).length, fallbacks: attempts.filter((attempt) => attempt.envelope.outcome === 'degraded').length, unavailable, verdicts, administrativeRepairs: attempts.filter((attempt) => attempt.administrativeRepair === true).length };
225
+ const incidents = (state.routingIncidents ?? []).map(({ reasonCode, selection, affectedObligations, fallbackCount, blockedCount, alertState }) => ({ reasonCode, selection, affectedObligations, fallbackCount, blockedCount, alertState }));
226
+ const reconciled = Object.values(nativeReconciledByRole).reduce((total, count) => total + count, 0);
227
+ const selections = attempts.map(attempt => ({ role: attempt.envelope.role, configured: attempt.envelope.resolved,
228
+ accepted: attempt.nativeEventDigest && ['active', 'complete'].includes(attempt.state) ? attempt.observed ?? 'unknown' : 'unknown',
229
+ backendModel: attempt.nativeEventDigest && ['active', 'complete'].includes(attempt.state) ? attempt.observedBackendModel ?? 'unknown' : 'unknown' }));
230
+ return { schema: 'routing-report/v1', attempts: attempts.length, plannedByRole, actualByRole, selections,
231
+ ...(reconciled ? { nativeReconciledByRole } : {}), byState, retries: attempts.filter((attempt) => attempt.attempt > 1).length, fallbacks: attempts.filter((attempt) => attempt.envelope.role === 'implementer' && attempt.envelope.effectiveProfile === 'full' && attempt.envelope.requestedProfile !== 'full').length, unavailable, verdicts, administrativeRepairs: attempts.filter((attempt) => attempt.administrativeRepair === true).length,
232
+ measurement: { configuredSelection: 'envelope-recorded', acceptedSelection: reconciled === 0 ? 'not-natively-reconciled' : reconciled === attempts.length ? 'natively-reconciled' : 'partially-reconciled', backendModel: selections.some(item => item.backendModel !== 'unknown') ? 'provider-reported' : 'unknown', tokenUsage: 'unknown', savings: 'unverified' }, ...(incidents.length ? { incidents } : {}) };
138
233
  }
@@ -0,0 +1,41 @@
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
+ exports.queryLocalClaudeScope = queryLocalClaudeScope;
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const paths_1 = require("../paths");
10
+ const machine_key_1 = require("./machine-key");
11
+ const native_claude_machine_1 = require("./native-claude-machine");
12
+ const native_claude_config_1 = require("./native-claude-config");
13
+ const native_runtime_1 = require("./native-runtime");
14
+ const hook_diagnostics_1 = require("./hook-diagnostics");
15
+ /** Read-only local facts; no model inference or machine-key creation. */
16
+ async function queryLocalClaudeScope(cwd, runtimeKind, deps = {}) {
17
+ if (typeof cwd !== 'string' || !path_1.default.isAbsolute(cwd) || cwd.includes('\0') || typeof runtimeKind !== 'string'
18
+ || !/^[a-z0-9][a-z0-9_-]{0,127}$/.test(runtimeKind))
19
+ throw new Error('local Claude scope input is invalid');
20
+ const hooks = (deps.readRoutingHookStatus ?? hook_diagnostics_1.readRoutingHookStatus)();
21
+ if (deps.requireHookHealthy !== false && (hooks.state === 'failed' || hooks.state === 'invalid'))
22
+ return { state: 'unverified', reason: 'HOOK_CAPTURE_FAILED' };
23
+ const key = (deps.readMachineKey ?? machine_key_1.readMachineKey)((0, paths_1.awmHome)());
24
+ if (!key)
25
+ return { state: 'unverified', reason: 'MACHINE_KEY_MISSING' };
26
+ try {
27
+ const [auth, runtime] = await Promise.all([
28
+ (deps.queryClaudeAuthStatus ?? native_claude_machine_1.queryClaudeAuthStatus)({ command: 'claude', args: ['auth', 'status'], timeoutMs: 10000, key }),
29
+ (deps.queryRuntimeExecutable ?? native_runtime_1.queryRuntimeExecutable)({ command: 'claude', args: ['--version'], timeoutMs: 10000 }),
30
+ ]);
31
+ if (auth.accountState !== 'identified' || !auth.accountScopeDigest)
32
+ return { state: 'unverified', reason: 'ACCOUNT_UNVERIFIED' };
33
+ const configDigest = (deps.readClaudeModelConfigDigest ?? native_claude_config_1.readClaudeModelConfigDigest)({ cwd, configDir: path_1.default.resolve(process.env.CLAUDE_CONFIG_DIR ?? path_1.default.join(os_1.default.homedir(), '.claude')),
34
+ key, environment: process.env });
35
+ return { state: 'current', scope: { runtime: { target: 'claude-code', kind: runtimeKind, version: runtime.version,
36
+ accountScopeDigest: auth.accountScopeDigest }, binaryDigest: runtime.binaryDigest, configDigest } };
37
+ }
38
+ catch {
39
+ return { state: 'unverified', reason: 'NATIVE_QUERY_FAILED' };
40
+ }
41
+ }
@@ -0,0 +1,33 @@
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
+ exports.queryLocalCodexScope = queryLocalCodexScope;
7
+ const path_1 = __importDefault(require("path"));
8
+ const paths_1 = require("../paths");
9
+ const machine_key_1 = require("./machine-key");
10
+ const native_codex_machine_1 = require("./native-codex-machine");
11
+ const native_runtime_1 = require("./native-runtime");
12
+ /** Read-only, no-token local facts for the same cwd as the routed work. */
13
+ async function queryLocalCodexScope(cwd, runtimeKind, deps = {}) {
14
+ if (typeof cwd !== 'string' || !path_1.default.isAbsolute(cwd) || cwd.includes('\0') || typeof runtimeKind !== 'string'
15
+ || !/^[a-z0-9][a-z0-9_-]{0,127}$/.test(runtimeKind))
16
+ throw new Error('local Codex scope input is invalid');
17
+ const key = (deps.readMachineKey ?? machine_key_1.readMachineKey)((0, paths_1.awmHome)());
18
+ if (!key)
19
+ return { state: 'unverified', reason: 'MACHINE_KEY_MISSING' };
20
+ try {
21
+ const [machine, executable] = await Promise.all([
22
+ (deps.queryCodexMachineFacts ?? native_codex_machine_1.queryCodexMachineFacts)({ command: 'codex', args: ['app-server', '--stdio'], timeoutMs: 10000, cwd, key }),
23
+ (deps.queryRuntimeExecutable ?? native_runtime_1.queryRuntimeExecutable)({ command: 'codex', args: ['--version'], timeoutMs: 10000 }),
24
+ ]);
25
+ if (machine.accountState !== 'identified' || !machine.accountScopeDigest)
26
+ return { state: 'unverified', reason: 'ACCOUNT_UNVERIFIED' };
27
+ return { state: 'current', scope: { runtime: { target: 'codex', kind: runtimeKind, version: executable.version,
28
+ accountScopeDigest: machine.accountScopeDigest }, binaryDigest: executable.binaryDigest, configDigest: machine.configDigest } };
29
+ }
30
+ catch {
31
+ return { state: 'unverified', reason: 'NATIVE_QUERY_FAILED' };
32
+ }
33
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queryLocalEventScope = queryLocalEventScope;
4
+ const local_claude_scope_1 = require("./local-claude-scope");
5
+ const local_codex_scope_1 = require("./local-codex-scope");
6
+ /** Provider-specific, read-only scope discovery for dispatch-time drift checks. */
7
+ async function queryLocalEventScope(cwd, target, runtimeKind) {
8
+ if (target === 'codex')
9
+ return (0, local_codex_scope_1.queryLocalCodexScope)(cwd, runtimeKind);
10
+ if (target === 'claude-code')
11
+ return (0, local_claude_scope_1.queryLocalClaudeScope)(cwd, runtimeKind);
12
+ return { state: 'unverified', reason: 'PROVIDER_UNSUPPORTED' };
13
+ }