agentic-workflow-manager 9.11.12 → 9.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) 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/index.js +39 -4
  5. package/dist/src/commands/model-policy/index.js +253 -0
  6. package/dist/src/commands/plan/index.js +53 -9
  7. package/dist/src/commands/preflight/checks.js +18 -0
  8. package/dist/src/commands/watch/apply.js +62 -4
  9. package/dist/src/commands/watch/supervisor.js +63 -3
  10. package/dist/src/core/admission/index.js +20 -6
  11. package/dist/src/core/journal/types.js +21 -5
  12. package/dist/src/core/model-policy/capabilities-v2.js +152 -0
  13. package/dist/src/core/model-policy/capabilities.js +7 -1
  14. package/dist/src/core/model-policy/claude-hook-events.js +120 -0
  15. package/dist/src/core/model-policy/event-store.js +221 -0
  16. package/dist/src/core/model-policy/hook-diagnostics.js +94 -0
  17. package/dist/src/core/model-policy/journal.js +113 -18
  18. package/dist/src/core/model-policy/local-claude-scope.js +41 -0
  19. package/dist/src/core/model-policy/local-codex-scope.js +33 -0
  20. package/dist/src/core/model-policy/local-event-scope.js +13 -0
  21. package/dist/src/core/model-policy/machine-key.js +56 -0
  22. package/dist/src/core/model-policy/native-claude-agent-definition.js +82 -0
  23. package/dist/src/core/model-policy/native-claude-config.js +107 -0
  24. package/dist/src/core/model-policy/native-claude-machine.js +58 -0
  25. package/dist/src/core/model-policy/native-claude-transcript.js +80 -0
  26. package/dist/src/core/model-policy/native-claude.js +41 -0
  27. package/dist/src/core/model-policy/native-codex-machine.js +116 -0
  28. package/dist/src/core/model-policy/native-codex-rollout.js +111 -0
  29. package/dist/src/core/model-policy/native-codex.js +302 -0
  30. package/dist/src/core/model-policy/native-routing-observe.js +106 -0
  31. package/dist/src/core/model-policy/native-routing-proof.js +52 -0
  32. package/dist/src/core/model-policy/native-runtime.js +86 -0
  33. package/dist/src/core/model-policy/resolve.js +96 -6
  34. package/dist/src/core/model-policy/selection-policy-digest.js +22 -0
  35. package/dist/src/core/model-policy/setup-readiness.js +77 -0
  36. package/dist/src/providers/index.js +3 -1
  37. package/dist/tests/commands/doctor-routing.test.js +22 -0
  38. package/dist/tests/commands/doctor.test.js +5 -0
  39. package/dist/tests/commands/hooks/install.test.js +6 -0
  40. package/dist/tests/commands/hooks/resync.test.js +3 -0
  41. package/dist/tests/commands/hooks/status.test.js +12 -1
  42. package/dist/tests/commands/job/routing-file.test.js +32 -0
  43. package/dist/tests/commands/job/routing-report-cli.test.js +4 -3
  44. package/dist/tests/commands/model-policy/claude-hook-flow.test.js +104 -0
  45. package/dist/tests/commands/model-policy/index.test.js +179 -0
  46. package/dist/tests/commands/preflight/preflight.test.js +65 -0
  47. package/dist/tests/commands/watch/apply.test.js +84 -0
  48. package/dist/tests/commands/watch/dispatch-routing-identity.test.js +23 -0
  49. package/dist/tests/core/admission/index.test.js +21 -0
  50. package/dist/tests/core/model-policy/capabilities-v2.test.js +42 -0
  51. package/dist/tests/core/model-policy/claude-hook-events.test.js +40 -0
  52. package/dist/tests/core/model-policy/event-store-claude.test.js +64 -0
  53. package/dist/tests/core/model-policy/event-store.test.js +151 -0
  54. package/dist/tests/core/model-policy/hook-diagnostics.test.js +34 -0
  55. package/dist/tests/core/model-policy/journal.test.js +112 -1
  56. package/dist/tests/core/model-policy/local-claude-scope.test.js +31 -0
  57. package/dist/tests/core/model-policy/machine-key.test.js +38 -0
  58. package/dist/tests/core/model-policy/native-claude-agent-definition.test.js +31 -0
  59. package/dist/tests/core/model-policy/native-claude-config.test.js +53 -0
  60. package/dist/tests/core/model-policy/native-claude-machine.test.js +18 -0
  61. package/dist/tests/core/model-policy/native-claude-transcript.test.js +45 -0
  62. package/dist/tests/core/model-policy/native-claude.test.js +21 -0
  63. package/dist/tests/core/model-policy/native-codex-machine.test.js +34 -0
  64. package/dist/tests/core/model-policy/native-codex-rollout.test.js +44 -0
  65. package/dist/tests/core/model-policy/native-codex.test.js +133 -0
  66. package/dist/tests/core/model-policy/native-routing-observe.test.js +60 -0
  67. package/dist/tests/core/model-policy/native-routing-proof.test.js +30 -0
  68. package/dist/tests/core/model-policy/native-runtime.test.js +14 -0
  69. package/dist/tests/core/model-policy/resolve-event.test.js +65 -0
  70. package/dist/tests/core/model-policy/setup-readiness.test.js +83 -0
  71. package/dist/tests/providers/index.test.js +1 -1
  72. package/package.json +2 -2
  73. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  74. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  75. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  76. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -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
+ }
@@ -0,0 +1,56 @@
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.readMachineKey = readMachineKey;
7
+ exports.ensureMachineKey = ensureMachineKey;
8
+ const crypto_1 = require("crypto");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const native_bridge_1 = require("../secure-fs/native-bridge");
12
+ const KEY_NAME = 'routing-machine.key';
13
+ function keyPath(root) {
14
+ if (typeof root !== 'string' || !path_1.default.isAbsolute(root) || path_1.default.normalize(root) !== root || root.includes('\0'))
15
+ throw new Error('routing machine root must be an absolute normalized path');
16
+ for (let dir = root;; dir = path_1.default.dirname(dir)) {
17
+ const stat = fs_1.default.lstatSync(dir);
18
+ if (!stat.isDirectory() || stat.isSymbolicLink())
19
+ throw new Error('routing machine root has unsafe or symlinked ancestor');
20
+ if (dir === path_1.default.dirname(dir))
21
+ break;
22
+ }
23
+ return path_1.default.join(root, KEY_NAME);
24
+ }
25
+ /** Read-only status path; never initializes machine state during diagnostics. */
26
+ function readMachineKey(root) {
27
+ const file = keyPath(root);
28
+ try {
29
+ const stat = fs_1.default.lstatSync(file);
30
+ if (!stat.isFile() || stat.isSymbolicLink())
31
+ throw new Error('routing machine key is unsafe or symlinked');
32
+ if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0)
33
+ throw new Error('routing machine key permissions are unsafe');
34
+ const bytes = native_bridge_1.secureFs.readRegularFile(file, 32).bytes;
35
+ if (bytes.length !== 32)
36
+ throw new Error('routing machine key length is invalid');
37
+ return Buffer.from(bytes);
38
+ }
39
+ catch (error) {
40
+ if (error.code === 'ENOENT')
41
+ return null;
42
+ throw error;
43
+ }
44
+ }
45
+ /** Only explicit setup may call this producer. No secret bytes are reported. */
46
+ function ensureMachineKey(root) {
47
+ keyPath(root);
48
+ return native_bridge_1.secureFs.withProjectLease(root, () => {
49
+ const existing = readMachineKey(root);
50
+ if (existing)
51
+ return existing;
52
+ const bytes = (0, crypto_1.randomBytes)(32);
53
+ native_bridge_1.secureFs.writeProjectTransaction(root, KEY_NAME, bytes, { mode: 'create', createParents: false });
54
+ return bytes;
55
+ });
56
+ }
@@ -0,0 +1,82 @@
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.isNativeClaudeAgentDeclaration = isNativeClaudeAgentDeclaration;
7
+ exports.readClaudeAgentDeclaredModel = readClaudeAgentDeclaredModel;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const js_yaml_1 = __importDefault(require("js-yaml"));
11
+ const native_bridge_1 = require("../secure-fs/native-bridge");
12
+ const observedDeclarations = new WeakMap();
13
+ function isNativeClaudeAgentDeclaration(value) {
14
+ return !!value && typeof value === 'object' && observedDeclarations.get(value) === JSON.stringify(value);
15
+ }
16
+ function safeDirectory(dir) {
17
+ for (let current = dir;; current = path_1.default.dirname(current)) {
18
+ const stat = fs_1.default.lstatSync(current);
19
+ if (!stat.isDirectory() || stat.isSymbolicLink())
20
+ throw new Error('Claude agent definition has an unsafe ancestor');
21
+ if (current === path_1.default.dirname(current))
22
+ break;
23
+ }
24
+ }
25
+ /** An explicit custom-agent model is necessary, but the effective model must
26
+ * still be independently observed in the native child transcript. Built-in
27
+ * agents, aliases and an inherited/default model cannot enroll a selection. */
28
+ function readClaudeAgentDeclaredModel(input) {
29
+ if (!input || typeof input !== 'object' || !path_1.default.isAbsolute(input.cwd) || !path_1.default.isAbsolute(input.configDir)
30
+ || path_1.default.normalize(input.cwd) !== input.cwd || path_1.default.normalize(input.configDir) !== input.configDir
31
+ || input.cwd.includes('\0') || input.configDir.includes('\0')
32
+ || typeof input.agentType !== 'string' || !/^awm-[A-Za-z0-9][A-Za-z0-9_-]{0,123}$/.test(input.agentType))
33
+ throw new Error('Claude agent definition identity is invalid');
34
+ const dirs = [path_1.default.join(input.configDir, 'agents'), path_1.default.join(input.cwd, '.claude', 'agents')];
35
+ const declared = [];
36
+ for (const dir of dirs) {
37
+ try {
38
+ safeDirectory(dir);
39
+ }
40
+ catch (error) {
41
+ if (error.code === 'ENOENT')
42
+ continue;
43
+ throw error;
44
+ }
45
+ const names = fs_1.default.readdirSync(dir).filter(name => name.endsWith('.md'));
46
+ if (names.length > 256)
47
+ throw new Error('Claude agent definitions exceed limit');
48
+ for (const name of names) {
49
+ const file = path_1.default.join(dir, name);
50
+ const stat = fs_1.default.lstatSync(file);
51
+ if (!stat.isFile() || stat.isSymbolicLink())
52
+ throw new Error('Claude agent definition is unsafe');
53
+ const bytes = native_bridge_1.secureFs.readRegularFile(file, 1024 * 1024).bytes;
54
+ const content = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
55
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
56
+ if (!match)
57
+ continue;
58
+ let frontmatter;
59
+ try {
60
+ frontmatter = js_yaml_1.default.load(match[1]);
61
+ }
62
+ catch {
63
+ throw new Error('Claude agent frontmatter is invalid');
64
+ }
65
+ if (!frontmatter || typeof frontmatter !== 'object' || Array.isArray(frontmatter))
66
+ continue;
67
+ const row = frontmatter;
68
+ if (row.name !== input.agentType)
69
+ continue;
70
+ if (typeof row.description !== 'string' || !row.description.trim())
71
+ throw new Error('Claude agent description is missing');
72
+ if (typeof row.model !== 'string' || !/^claude-[a-z0-9][a-z0-9.-]{1,126}$/.test(row.model))
73
+ throw new Error('Claude agent requires an explicit full model ID; aliases and inherit are unverified');
74
+ declared.push(row.model);
75
+ }
76
+ }
77
+ if (declared.length !== 1)
78
+ throw new Error(declared.length ? 'Claude agent definition is ambiguous' : 'Claude agent has no explicit custom definition');
79
+ const result = { agentType: input.agentType, modelId: declared[0] };
80
+ observedDeclarations.set(result, JSON.stringify(result));
81
+ return result;
82
+ }
@@ -0,0 +1,107 @@
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.readClaudeModelConfigDigest = readClaudeModelConfigDigest;
7
+ const crypto_1 = require("crypto");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const native_bridge_1 = require("../secure-fs/native-bridge");
11
+ const json_1 = require("../plan/json");
12
+ const MODEL_KEYS = ['model', 'effortLevel', 'availableModels', 'enforceAvailableModels', 'modelOverrides'];
13
+ const ENV_KEYS = ['ANTHROPIC_MODEL', 'ANTHROPIC_DEFAULT_OPUS_MODEL', 'ANTHROPIC_DEFAULT_SONNET_MODEL', 'ANTHROPIC_DEFAULT_HAIKU_MODEL', 'CLAUDE_CODE_SUBAGENT_MODEL', 'CLAUDE_CODE_SUBAGENT_MODEL_FORCE'];
14
+ function safeDirectory(dir) {
15
+ for (let current = dir;; current = path_1.default.dirname(current)) {
16
+ const stat = fs_1.default.lstatSync(current);
17
+ if (!stat.isDirectory() || stat.isSymbolicLink())
18
+ throw new Error('Claude settings have an unsafe or symlinked ancestor');
19
+ if (current === path_1.default.dirname(current))
20
+ break;
21
+ }
22
+ }
23
+ function settings(file) {
24
+ let stat;
25
+ try {
26
+ stat = fs_1.default.lstatSync(file);
27
+ }
28
+ catch (error) {
29
+ if (error.code === 'ENOENT')
30
+ return null;
31
+ throw error;
32
+ }
33
+ safeDirectory(path_1.default.dirname(file));
34
+ if (!stat.isFile() || stat.isSymbolicLink())
35
+ throw new Error('Claude settings file is unsafe or symlinked');
36
+ const bytes = native_bridge_1.secureFs.readRegularFile(file, 1024 * 1024).bytes;
37
+ const value = (0, json_1.parseJsonNoDuplicate)(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
38
+ if (!value || typeof value !== 'object' || Array.isArray(value))
39
+ throw new Error('Claude settings must be an object');
40
+ return value;
41
+ }
42
+ function agentDefinitions(dir) {
43
+ let stat;
44
+ try {
45
+ stat = fs_1.default.lstatSync(dir);
46
+ }
47
+ catch (error) {
48
+ if (error.code === 'ENOENT')
49
+ return [];
50
+ throw error;
51
+ }
52
+ safeDirectory(path_1.default.dirname(dir));
53
+ if (!stat.isDirectory() || stat.isSymbolicLink())
54
+ throw new Error('Claude agents directory is unsafe or symlinked');
55
+ const names = fs_1.default.readdirSync(dir).filter(name => name.endsWith('.md')).sort();
56
+ if (names.length > 256)
57
+ throw new Error('Claude agent definitions exceed limit');
58
+ return names.map(name => {
59
+ const file = path_1.default.join(dir, name);
60
+ const entry = fs_1.default.lstatSync(file);
61
+ if (!entry.isFile() || entry.isSymbolicLink())
62
+ throw new Error('Claude agent definition is unsafe or symlinked');
63
+ const bytes = native_bridge_1.secureFs.readRegularFile(file, 1024 * 1024).bytes;
64
+ return [name, (0, crypto_1.createHash)('sha256').update(bytes).digest('hex')];
65
+ });
66
+ }
67
+ /** Fingerprints only locally visible model controls. Managed/server-side policy
68
+ * is not represented here; a native child observation remains mandatory. */
69
+ function readClaudeModelConfigDigest(input) {
70
+ if (!input || typeof input !== 'object' || !path_1.default.isAbsolute(input.cwd) || !path_1.default.isAbsolute(input.configDir)
71
+ || path_1.default.normalize(input.cwd) !== input.cwd || path_1.default.normalize(input.configDir) !== input.configDir
72
+ || input.cwd.includes('\0') || input.configDir.includes('\0') || !Buffer.isBuffer(input.key) || input.key.length !== 32
73
+ || !input.environment || typeof input.environment !== 'object' || Array.isArray(input.environment))
74
+ throw new Error('Claude model config input is invalid');
75
+ safeDirectory(input.cwd);
76
+ safeDirectory(input.configDir);
77
+ const files = [path_1.default.join(input.configDir, 'settings.json'), path_1.default.join(input.cwd, '.claude', 'settings.json'), path_1.default.join(input.cwd, '.claude', 'settings.local.json')];
78
+ const projection = files.map(file => {
79
+ const value = settings(file);
80
+ if (!value)
81
+ return null;
82
+ const model = {};
83
+ for (const key of MODEL_KEYS)
84
+ if (Object.prototype.hasOwnProperty.call(value, key))
85
+ model[key] = value[key];
86
+ if (value.env && typeof value.env === 'object' && !Array.isArray(value.env)) {
87
+ const env = {};
88
+ for (const key of ENV_KEYS)
89
+ if (Object.prototype.hasOwnProperty.call(value.env, key))
90
+ env[key] = value.env[key];
91
+ if (Object.keys(env).length)
92
+ model.env = env;
93
+ }
94
+ return model;
95
+ });
96
+ const environment = {};
97
+ for (const key of ENV_KEYS) {
98
+ const value = input.environment[key];
99
+ if (value !== undefined) {
100
+ if (typeof value !== 'string' || value.length > 4096)
101
+ throw new Error('Claude model environment is invalid');
102
+ environment[key] = value;
103
+ }
104
+ }
105
+ const agents = [agentDefinitions(path_1.default.join(input.configDir, 'agents')), agentDefinitions(path_1.default.join(input.cwd, '.claude', 'agents'))];
106
+ return (0, crypto_1.createHmac)('sha256', input.key).update(JSON.stringify({ projection, agents, environment })).digest('hex');
107
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queryClaudeAuthStatus = queryClaudeAuthStatus;
4
+ const crypto_1 = require("crypto");
5
+ const child_process_1 = require("child_process");
6
+ /** `claude auth status` is documented as a JSON, no-inference command. The
7
+ * account shape varies by auth method; incomplete identity stays unverified. */
8
+ function queryClaudeAuthStatus(input) {
9
+ if (!input || typeof input !== 'object' || typeof input.command !== 'string' || input.command.length === 0 || input.command.length > 4096
10
+ || !Array.isArray(input.args) || input.args.length > 16 || input.args.some(value => typeof value !== 'string' || value.length > 65536)
11
+ || !Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 30000
12
+ || !Buffer.isBuffer(input.key) || input.key.length !== 32)
13
+ throw new Error('Claude auth query is invalid');
14
+ return new Promise((resolve, reject) => {
15
+ const child = (0, child_process_1.spawn)(input.command, input.args, { stdio: ['ignore', 'pipe', 'pipe'], shell: false });
16
+ let settled = false;
17
+ let buffer = '';
18
+ const finish = (error, facts) => {
19
+ if (settled)
20
+ return;
21
+ settled = true;
22
+ clearTimeout(timer);
23
+ if (error) {
24
+ child.kill();
25
+ reject(error);
26
+ }
27
+ else
28
+ resolve(facts);
29
+ };
30
+ const timer = setTimeout(() => finish(new Error('Claude auth status timed out')), input.timeoutMs);
31
+ child.on('error', () => finish(new Error('Claude CLI could not be started')));
32
+ child.stdout.setEncoding('utf8');
33
+ child.stdout.on('data', (chunk) => { buffer += chunk; if (buffer.length > 64 * 1024)
34
+ finish(new Error('Claude auth status exceeds 64 KiB')); });
35
+ child.on('exit', code => {
36
+ if (code !== 0)
37
+ return finish(new Error('Claude auth status failed or is logged out'));
38
+ try {
39
+ const parsed = JSON.parse(buffer);
40
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
41
+ throw new Error('shape');
42
+ const value = parsed;
43
+ if (value.loggedIn !== true)
44
+ throw new Error('login');
45
+ const fields = ['authMethod', 'apiProvider', 'email', 'orgId'];
46
+ const identified = fields.every(field => typeof value[field] === 'string' && value[field].length > 0 && value[field].length <= 320);
47
+ const subscriptionType = value.subscriptionType;
48
+ if (subscriptionType !== null && subscriptionType !== undefined && (typeof subscriptionType !== 'string' || subscriptionType.length > 128))
49
+ throw new Error('subscription');
50
+ const accountScopeDigest = identified ? (0, crypto_1.createHmac)('sha256', input.key).update(JSON.stringify(fields.map(field => value[field]).concat([subscriptionType ?? null]))).digest('hex') : null;
51
+ finish(undefined, { provenance: 'claude-auth-status', accountState: accountScopeDigest ? 'identified' : 'unverified', accountScopeDigest, inferenceDispatched: false });
52
+ }
53
+ catch {
54
+ finish(new Error('Claude auth status response is invalid'));
55
+ }
56
+ });
57
+ });
58
+ }