@agentplat/inference-control 0.3.0-alpha.3

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/LICENSE +202 -0
  2. package/README.md +101 -0
  3. package/dist/assessments.d.ts +5 -0
  4. package/dist/assessments.d.ts.map +1 -0
  5. package/dist/assessments.js +216 -0
  6. package/dist/assessments.js.map +1 -0
  7. package/dist/boundary.d.ts +24 -0
  8. package/dist/boundary.d.ts.map +1 -0
  9. package/dist/boundary.js +46 -0
  10. package/dist/boundary.js.map +1 -0
  11. package/dist/canonical.d.ts +6 -0
  12. package/dist/canonical.d.ts.map +1 -0
  13. package/dist/canonical.js +74 -0
  14. package/dist/canonical.js.map +1 -0
  15. package/dist/capabilities.d.ts +43 -0
  16. package/dist/capabilities.d.ts.map +1 -0
  17. package/dist/capabilities.js +304 -0
  18. package/dist/capabilities.js.map +1 -0
  19. package/dist/context.d.ts +7 -0
  20. package/dist/context.d.ts.map +1 -0
  21. package/dist/context.js +191 -0
  22. package/dist/context.js.map +1 -0
  23. package/dist/index.d.ts +14 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +14 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/messages.d.ts +124 -0
  28. package/dist/messages.d.ts.map +1 -0
  29. package/dist/messages.js +475 -0
  30. package/dist/messages.js.map +1 -0
  31. package/dist/model.d.ts +104 -0
  32. package/dist/model.d.ts.map +1 -0
  33. package/dist/model.js +437 -0
  34. package/dist/model.js.map +1 -0
  35. package/dist/policy.d.ts +11 -0
  36. package/dist/policy.d.ts.map +1 -0
  37. package/dist/policy.js +338 -0
  38. package/dist/policy.js.map +1 -0
  39. package/dist/reducer.d.ts +193 -0
  40. package/dist/reducer.d.ts.map +1 -0
  41. package/dist/reducer.js +1341 -0
  42. package/dist/reducer.js.map +1 -0
  43. package/dist/runtime.d.ts +132 -0
  44. package/dist/runtime.d.ts.map +1 -0
  45. package/dist/runtime.js +587 -0
  46. package/dist/runtime.js.map +1 -0
  47. package/dist/scopes.d.ts +3 -0
  48. package/dist/scopes.d.ts.map +1 -0
  49. package/dist/scopes.js +83 -0
  50. package/dist/scopes.js.map +1 -0
  51. package/dist/sha256.d.ts +2 -0
  52. package/dist/sha256.d.ts.map +1 -0
  53. package/dist/sha256.js +72 -0
  54. package/dist/sha256.js.map +1 -0
  55. package/dist/state-records.d.ts +8 -0
  56. package/dist/state-records.d.ts.map +1 -0
  57. package/dist/state-records.js +204 -0
  58. package/dist/state-records.js.map +1 -0
  59. package/dist/state.d.ts +19 -0
  60. package/dist/state.d.ts.map +1 -0
  61. package/dist/state.js +689 -0
  62. package/dist/state.js.map +1 -0
  63. package/dist/streams.d.ts +5 -0
  64. package/dist/streams.d.ts.map +1 -0
  65. package/dist/streams.js +93 -0
  66. package/dist/streams.js.map +1 -0
  67. package/dist/telemetry.d.ts +12 -0
  68. package/dist/telemetry.d.ts.map +1 -0
  69. package/dist/telemetry.js +20 -0
  70. package/dist/telemetry.js.map +1 -0
  71. package/dist/tools.d.ts +221 -0
  72. package/dist/tools.d.ts.map +1 -0
  73. package/dist/tools.js +755 -0
  74. package/dist/tools.js.map +1 -0
  75. package/dist/types.d.ts +474 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +43 -0
  78. package/dist/types.js.map +1 -0
  79. package/dist/validation.d.ts +23 -0
  80. package/dist/validation.d.ts.map +1 -0
  81. package/dist/validation.js +154 -0
  82. package/dist/validation.js.map +1 -0
  83. package/package.json +56 -0
@@ -0,0 +1,1341 @@
1
+ import { assessmentMatchesRequestV1, validateAssessmentRequestV1, validateInferenceAssessmentV1, } from './assessments.js';
2
+ import { negotiateCapabilitiesV1, validateCapabilityNegotiationResultV1, } from './capabilities.js';
3
+ import { canonicalizeControlJsonV1, digestControlJsonV1, utf8ByteLength, } from './canonical.js';
4
+ import { canAdmitContextEntryV1, validateContextEntryV1 } from './context.js';
5
+ import { assertPolicyNarrowingV1, createPolicyRecordV1 } from './policy.js';
6
+ import { validateActionGrantStateV1, validateOutboundMessageAttemptStateV1, } from './state-records.js';
7
+ import { finalizeInferenceControlStateV1 } from './state.js';
8
+ import { createStreamWindowV1, validateControlStreamChunkV1, validateControlStreamV1, } from './streams.js';
9
+ import { assertDigest, assertExactKeys, assertOneOf, assertSafeInteger, assertStrictJsonValue, assertString, compareCodeUnits, deepFreeze, isInferenceControlReasonCodeV1, } from './validation.js';
10
+ const terminalPhases = new Set([
11
+ 'completed',
12
+ 'denied',
13
+ 'abstained',
14
+ 'escalated',
15
+ 'cancelled',
16
+ 'failed',
17
+ ]);
18
+ export function reduceInferenceControlStateV1(state, input, dependencies = {}) {
19
+ validateInputHead(input);
20
+ if (input.logicalTimeMs < state.logicalTimeHighWaterMs)
21
+ return unchanged(state, 'logical_time_rollback');
22
+ if (input.expectedStateGeneration !== state.stateGeneration)
23
+ return unchanged(state, 'state_conflict');
24
+ try {
25
+ switch (input.type) {
26
+ case 'policy_registered':
27
+ return registerPolicy(state, input);
28
+ case 'run_created':
29
+ return createRun(state, input);
30
+ case 'context_admitted':
31
+ case 'context_promoted':
32
+ return admitContext(state, input);
33
+ case 'capability_negotiated':
34
+ return capabilityNegotiated(state, input, dependencies);
35
+ case 'assessment_requested':
36
+ return assessmentRequested(state, input);
37
+ case 'assessment_received':
38
+ return assessmentReceived(state, input);
39
+ case 'provider_started':
40
+ return providerStarted(state, input);
41
+ case 'provider_chunk_received':
42
+ return providerChunkReceived(state, input);
43
+ case 'provider_completed':
44
+ return providerCompleted(state, input);
45
+ case 'continuation_selected':
46
+ return continuationSelected(state, input);
47
+ case 'output_release_ack':
48
+ return outputReleaseAck(state, input);
49
+ case 'grant_issued':
50
+ return grantIssued(state, input);
51
+ case 'grant_reserved':
52
+ return grantReserved(state, input);
53
+ case 'action_dispatch_outcome':
54
+ return actionDispatchOutcome(state, input);
55
+ case 'message_prepared':
56
+ return messagePrepared(state, input);
57
+ case 'message_reserved':
58
+ return messageReserved(state, input);
59
+ case 'message_dispatch_outcome':
60
+ return messageDispatchOutcome(state, input);
61
+ case 'deadline_fired':
62
+ return deadlineFired(state, input);
63
+ case 'run_cancelled':
64
+ case 'run_failed':
65
+ return terminateRun(state, input);
66
+ }
67
+ }
68
+ catch (error) {
69
+ return reject(state, reasonFromError(error), input.logicalTimeMs, 'unavailable', runIdOf(input));
70
+ }
71
+ }
72
+ function registerPolicy(state, input) {
73
+ const record = createPolicyRecordV1(input.policy);
74
+ if (record.policyDigest !== input.policyDigest)
75
+ return reject(state, 'state_conflict', input.logicalTimeMs);
76
+ const exact = state.policies.find((item) => item.policyDigest === record.policyDigest);
77
+ if (exact)
78
+ return accept(state);
79
+ if (state.policies.some((item) => item.policy.policyId === record.policy.policyId &&
80
+ item.policy.policyVersion === record.policy.policyVersion))
81
+ return reject(state, 'state_conflict', input.logicalTimeMs);
82
+ const currentHead = state.policyHeads.find((head) => head.policyId === record.policy.policyId);
83
+ if (currentHead && currentHead.policyVersion >= record.policy.policyVersion)
84
+ return reject(state, 'state_conflict', input.logicalTimeMs);
85
+ if (record.policy.parentPolicyDigest !== null) {
86
+ const parent = state.policies.find((item) => item.policyDigest === record.policy.parentPolicyDigest);
87
+ if (!parent)
88
+ return reject(state, 'state_conflict', input.logicalTimeMs);
89
+ assertPolicyNarrowingV1(parent.policy, record.policy);
90
+ }
91
+ return accept(withState(state, {
92
+ policies: sorted([...state.policies, record], (item) => item.policyDigest),
93
+ policyHeads: sorted([
94
+ ...state.policyHeads.filter((head) => head.policyId !== record.policy.policyId),
95
+ {
96
+ schemaVersion: 1,
97
+ policyId: record.policy.policyId,
98
+ policyVersion: record.policy.policyVersion,
99
+ policyDigest: record.policyDigest,
100
+ },
101
+ ], (item) => item.policyId),
102
+ }, input.logicalTimeMs));
103
+ }
104
+ function createRun(state, input) {
105
+ const run = validateCreatedRunV1(input.run);
106
+ const policy = state.policies.find((item) => item.policyDigest === run.policyDigest)?.policy;
107
+ if (run.schemaVersion !== 1 ||
108
+ run.tenantId !== state.tenantId ||
109
+ run.generation !== 1 ||
110
+ run.phase !== 'created' ||
111
+ run.capabilityHandleId !== null ||
112
+ run.createdAtLogicalMs !== input.logicalTimeMs ||
113
+ run.deadlineAtLogicalMs <= input.logicalTimeMs ||
114
+ !policy ||
115
+ run.deadlineAtLogicalMs - run.createdAtLogicalMs >
116
+ Math.min(state.limits.maxRunDurationMs, policy.maximumRunDurationMs) ||
117
+ state.runs.some((item) => item.runId === run.runId))
118
+ return reject(state, 'state_conflict', input.logicalTimeMs, 'denied', run.runId);
119
+ return accept(withState(state, { runs: sorted([...state.runs, run], (item) => item.runId) }, input.logicalTimeMs));
120
+ }
121
+ function validateCreatedRunV1(value) {
122
+ assertExactKeys(value, [
123
+ 'schemaVersion',
124
+ 'runId',
125
+ 'tenantId',
126
+ 'policyDigest',
127
+ 'capabilityDescriptorDigest',
128
+ 'capabilityHandleId',
129
+ 'scope',
130
+ 'generation',
131
+ 'phase',
132
+ 'createdAtLogicalMs',
133
+ 'deadlineAtLogicalMs',
134
+ 'dispositionCounts',
135
+ 'contextEntryIds',
136
+ 'assessmentRequestIds',
137
+ 'assessmentIds',
138
+ 'streamIds',
139
+ 'grantIds',
140
+ 'messageAttemptIds',
141
+ 'outputDigest',
142
+ 'releasedBytes',
143
+ 'terminalReasonCode',
144
+ ], 'run');
145
+ const run = value;
146
+ if (run.schemaVersion !== 1)
147
+ throw new TypeError('state_conflict');
148
+ assertString(run.runId, 'runId');
149
+ assertString(run.tenantId, 'tenantId');
150
+ assertDigest(run.policyDigest, 'policyDigest');
151
+ assertDigest(run.capabilityDescriptorDigest, 'capabilityDescriptorDigest');
152
+ if (run.capabilityHandleId !== null)
153
+ assertString(run.capabilityHandleId, 'capabilityHandleId');
154
+ if (run.scope !== null)
155
+ assertStrictJsonValue(run.scope);
156
+ assertSafeInteger(run.generation, 'generation', 1);
157
+ assertOneOf(run.phase, [
158
+ 'created',
159
+ 'input_assessed',
160
+ 'executing',
161
+ 'buffering',
162
+ 'streaming',
163
+ 'output_assessed',
164
+ 'completed',
165
+ 'denied',
166
+ 'abstained',
167
+ 'escalated',
168
+ 'cancelled',
169
+ 'failed',
170
+ ], 'run phase');
171
+ assertSafeInteger(run.createdAtLogicalMs, 'createdAtLogicalMs');
172
+ assertSafeInteger(run.deadlineAtLogicalMs, 'deadlineAtLogicalMs');
173
+ if (run.deadlineAtLogicalMs <= run.createdAtLogicalMs)
174
+ throw new TypeError('state_conflict');
175
+ assertExactKeys(run.dispositionCounts, ['revisions', 'retries', 'challenges'], 'dispositionCounts');
176
+ for (const count of Object.values(run.dispositionCounts))
177
+ assertSafeInteger(count, 'disposition count');
178
+ assertSafeInteger(run.releasedBytes, 'releasedBytes');
179
+ if (run.outputDigest !== null)
180
+ assertDigest(run.outputDigest, 'outputDigest');
181
+ if (run.terminalReasonCode !== null &&
182
+ !isInferenceControlReasonCodeV1(run.terminalReasonCode))
183
+ throw new TypeError('state_conflict');
184
+ for (const ids of [
185
+ run.contextEntryIds,
186
+ run.assessmentRequestIds,
187
+ run.assessmentIds,
188
+ run.streamIds,
189
+ run.grantIds,
190
+ run.messageAttemptIds,
191
+ ]) {
192
+ if (!Array.isArray(ids) || new Set(ids).size !== ids.length)
193
+ throw new TypeError('state_conflict');
194
+ for (const id of ids)
195
+ assertString(id, 'run relation id');
196
+ }
197
+ return deepFreeze(structuredClone(run));
198
+ }
199
+ function admitContext(state, input) {
200
+ const entry = validateContextEntryV1(input.entry);
201
+ const run = state.runs.find((item) => item.runId === entry.runId);
202
+ if (!run || terminalPhases.has(run.phase))
203
+ return reject(state, 'state_conflict', input.logicalTimeMs);
204
+ const prior = state.contextEntries.find((item) => item.contextEntryId === entry.contextEntryId);
205
+ if (prior)
206
+ return canonicalizeControlJsonV1(prior) ===
207
+ canonicalizeControlJsonV1(entry)
208
+ ? accept(state)
209
+ : reject(state, 'state_conflict', input.logicalTimeMs);
210
+ if (input.type === 'context_promoted') {
211
+ const source = state.contextEntries.find((item) => item.contextEntryId === input.sourceContextEntryId);
212
+ const derivation = entry.derivation;
213
+ if (!source ||
214
+ derivation === null ||
215
+ !('transformerId' in derivation) ||
216
+ derivation.sourceContextEntryId !== source.contextEntryId ||
217
+ derivation.sourceContentDigest !== source.contentDigest ||
218
+ !state.dependencyBindings.some((binding) => binding.kind === 'transformer' &&
219
+ binding.bindingId === derivation.transformerId &&
220
+ binding.bindingVersion === derivation.transformerVersion &&
221
+ binding.bindingDigest === input.transformerBindingDigest))
222
+ return reject(state, 'context_promotion_denied', input.logicalTimeMs);
223
+ }
224
+ const policy = state.policies.find((item) => item.policyDigest === run.policyDigest)?.policy;
225
+ if (!policy ||
226
+ !canAdmitContextEntryV1(policy, state.contextEntries.filter((item) => item.runId === run.runId), entry))
227
+ return reject(state, 'context_limit_exceeded', input.logicalTimeMs);
228
+ return accept(withState(state, {
229
+ contextEntries: sorted([...state.contextEntries, entry], (item) => item.contextEntryId),
230
+ runs: replaceRun(state, run.runId, {
231
+ contextEntryIds: [...run.contextEntryIds, entry.contextEntryId],
232
+ }),
233
+ }, input.logicalTimeMs));
234
+ }
235
+ function capabilityNegotiated(state, input, dependencies) {
236
+ const run = currentRun(state, input.runId);
237
+ const policy = run
238
+ ? state.policies.find((item) => item.policyDigest === run.policyDigest)
239
+ ?.policy
240
+ : undefined;
241
+ const handle = dependencies.capabilityRegistry?.resolve(input.capabilityHandleId);
242
+ const descriptor = dependencies.capabilityRegistry?.descriptor(input.capabilityHandleId);
243
+ if (!run ||
244
+ !policy ||
245
+ run.phase !== 'created' ||
246
+ run.capabilityHandleId !== null ||
247
+ !handle ||
248
+ !descriptor ||
249
+ handle.descriptorDigest !== input.descriptorDigest ||
250
+ run.capabilityDescriptorDigest !== handle.descriptorDigest)
251
+ return reject(state, 'policy_capability_missing', input.logicalTimeMs, 'denied', input.runId);
252
+ const result = validateCapabilityNegotiationResultV1(input.result);
253
+ const expected = negotiateCapabilitiesV1(descriptor, {
254
+ policyDigest: run.policyDigest,
255
+ descriptorDigest: handle.descriptorDigest,
256
+ mode: policy.mode,
257
+ checkpoints: policy.checkpoints,
258
+ requiredCapabilities: policy.requiredCapabilities,
259
+ minimumCapabilityAssurance: policy.minimumCapabilityAssurance,
260
+ allowedCapabilityBindings: policy.allowedCapabilityBindings,
261
+ });
262
+ if (canonicalizeControlJsonV1(result) !==
263
+ canonicalizeControlJsonV1(expected) ||
264
+ !result.accepted)
265
+ return reject(state, result.accepted ? 'policy_capability_missing' : result.reasonCode, input.logicalTimeMs, 'denied', input.runId);
266
+ const binding = {
267
+ schemaVersion: 1,
268
+ kind: 'capability',
269
+ bindingId: handle.capabilityHandleId,
270
+ bindingVersion: handle.descriptorVersion,
271
+ bindingDigest: handle.descriptorDigest,
272
+ };
273
+ const existing = state.dependencyBindings.find((item) => item.kind === binding.kind &&
274
+ item.bindingId === binding.bindingId &&
275
+ item.bindingVersion === binding.bindingVersion);
276
+ if (existing &&
277
+ canonicalizeControlJsonV1(existing) !==
278
+ canonicalizeControlJsonV1(binding))
279
+ return reject(state, 'state_conflict', input.logicalTimeMs);
280
+ return accept(withState(state, {
281
+ dependencyBindings: existing
282
+ ? state.dependencyBindings
283
+ : sorted([...state.dependencyBindings, binding], (item) => `${item.kind}\u0000${item.bindingId}\u0000${String(item.bindingVersion).padStart(16, '0')}`),
284
+ runs: replaceRun(state, run.runId, {
285
+ phase: 'input_assessed',
286
+ capabilityHandleId: handle.capabilityHandleId,
287
+ }),
288
+ }, input.logicalTimeMs));
289
+ }
290
+ function assessmentRequested(state, input) {
291
+ const preliminary = validateAssessmentRequestV1(input.request, state.limits);
292
+ const run = currentRun(state, preliminary.runId);
293
+ const limits = run ? effectiveLimitsForRun(state, run) : state.limits;
294
+ const request = validateAssessmentRequestV1(input.request, limits);
295
+ const policy = run
296
+ ? state.policies.find((record) => record.policyDigest === run.policyDigest)
297
+ ?.policy
298
+ : undefined;
299
+ const binding = policy?.assessmentBindings.find((candidate) => candidate.checkpoint === request.checkpoint);
300
+ if (!run ||
301
+ !policy ||
302
+ !binding ||
303
+ request.tenantId !== state.tenantId ||
304
+ request.policyId !== policy.policyId ||
305
+ request.policyVersion !== policy.policyVersion ||
306
+ request.assessorId !== binding.assessorId ||
307
+ request.assessorVersion !== binding.assessorVersion ||
308
+ request.requestGeneration !== run.generation ||
309
+ request.status !== 'pending' ||
310
+ request.createdAtLogicalMs !== input.logicalTimeMs ||
311
+ request.expiresAtLogicalMs > run.deadlineAtLogicalMs ||
312
+ request.contextEntryIds.some((id) => !run.contextEntryIds.includes(id)))
313
+ return reject(state, 'assessment_invalid', input.logicalTimeMs);
314
+ const prior = state.assessmentRequests.find((item) => item.assessmentRequestId === request.assessmentRequestId);
315
+ if (prior)
316
+ return canonicalizeControlJsonV1(prior) ===
317
+ canonicalizeControlJsonV1(request)
318
+ ? accept(state)
319
+ : reject(state, 'state_conflict', input.logicalTimeMs);
320
+ if (state.assessmentRequests.filter((item) => item.runId === run.runId)
321
+ .length >= limits.maxAssessmentsPerRun)
322
+ return reject(state, 'state_capacity_exceeded', input.logicalTimeMs);
323
+ const next = withState(state, {
324
+ assessmentRequests: sorted([...state.assessmentRequests, request], (item) => item.assessmentRequestId),
325
+ runs: replaceRun(state, run.runId, {
326
+ assessmentRequestIds: [
327
+ ...run.assessmentRequestIds,
328
+ request.assessmentRequestId,
329
+ ],
330
+ }),
331
+ }, input.logicalTimeMs);
332
+ return accept(next, [
333
+ effect(next, run.runId, 'request_assessment', { request }),
334
+ ]);
335
+ }
336
+ function assessmentReceived(state, input) {
337
+ const preliminary = validateInferenceAssessmentV1(input.assessment, state.limits);
338
+ const preliminaryRun = currentRun(state, preliminary.runId);
339
+ const assessment = validateInferenceAssessmentV1(input.assessment, preliminaryRun
340
+ ? effectiveLimitsForRun(state, preliminaryRun)
341
+ : state.limits);
342
+ const prior = state.assessments.find((item) => item.assessmentId === assessment.assessmentId);
343
+ if (prior)
344
+ return canonicalizeControlJsonV1(prior) ===
345
+ canonicalizeControlJsonV1(assessment)
346
+ ? accept(state)
347
+ : reject(state, 'state_conflict', input.logicalTimeMs);
348
+ const request = state.assessmentRequests.find((item) => item.assessmentRequestId === assessment.assessmentRequestId);
349
+ if (!request)
350
+ return reject(state, 'assessment_unsolicited', input.logicalTimeMs);
351
+ if (request.assessorId !== assessment.assessorId ||
352
+ request.assessorVersion !== assessment.assessorVersion)
353
+ return reject(state, 'assessment_assessor_mismatch', input.logicalTimeMs);
354
+ if (request.requestGeneration !== assessment.requestGeneration)
355
+ return reject(state, 'assessment_generation_stale', input.logicalTimeMs);
356
+ const run = currentRun(state, assessment.runId);
357
+ if (!run || run.generation !== assessment.requestGeneration)
358
+ return reject(state, 'assessment_generation_stale', input.logicalTimeMs);
359
+ const policy = state.policies.find((record) => record.policyDigest === run.policyDigest)?.policy;
360
+ const binding = policy?.assessmentBindings.find((candidate) => candidate.checkpoint === assessment.checkpoint);
361
+ if (!binding ||
362
+ assessment.evidenceReferences.length > binding.maximumEvidenceReferences ||
363
+ utf8ByteLength(canonicalizeControlJsonV1(assessment)) > binding.maximumResponseBytes)
364
+ return reject(state, 'assessment_invalid', input.logicalTimeMs);
365
+ if (!assessmentMatchesRequestV1(request, assessment, input.logicalTimeMs))
366
+ return reject(state, input.logicalTimeMs >= request.expiresAtLogicalMs ||
367
+ input.logicalTimeMs >= assessment.expiresAtLogicalMs
368
+ ? 'assessment_expired'
369
+ : 'assessment_content_mismatch', input.logicalTimeMs);
370
+ const requests = state.assessmentRequests.map((item) => item.assessmentRequestId === request.assessmentRequestId
371
+ ? { ...item, status: 'accepted' }
372
+ : item);
373
+ const phase = assessmentPhase(run, assessment);
374
+ const next = withState(state, {
375
+ assessmentRequests: requests,
376
+ assessments: sorted([...state.assessments, assessment], (item) => item.assessmentId),
377
+ runs: replaceRun(state, run.runId, {
378
+ assessmentIds: [...run.assessmentIds, assessment.assessmentId],
379
+ phase,
380
+ terminalReasonCode: phase === 'denied' || phase === 'abstained' || phase === 'escalated'
381
+ ? assessment.reasonCodes[0]
382
+ : run.terminalReasonCode,
383
+ }),
384
+ }, input.logicalTimeMs);
385
+ const release = releaseWindowForAssessment(next, request, assessment);
386
+ return accept(next, [
387
+ effect(next, run.runId, 'emit_receipt', {
388
+ outcome: assessment.disposition === 'allow' ? 'accepted' : 'withheld',
389
+ }),
390
+ ...(release
391
+ ? [
392
+ effect(next, run.runId, 'release_output', {
393
+ streamId: release.stream.streamId,
394
+ fromSequence: release.window.fromSequence,
395
+ throughSequence: release.window.throughSequence,
396
+ fromByte: release.window.fromByte,
397
+ throughByteExclusive: release.window.throughByteExclusive,
398
+ windowDigest: release.window.windowDigest,
399
+ }),
400
+ ]
401
+ : []),
402
+ ]);
403
+ }
404
+ function providerStarted(state, input) {
405
+ const run = currentRun(state, input.runId);
406
+ const policy = run
407
+ ? state.policies.find((item) => item.policyDigest === run.policyDigest)
408
+ ?.policy
409
+ : undefined;
410
+ if (!run ||
411
+ !policy ||
412
+ run.phase !== 'input_assessed' ||
413
+ input.generation !== run.generation ||
414
+ state.streams.some((stream) => stream.streamId === input.streamId))
415
+ return reject(state, 'stream_sequence_invalid', input.logicalTimeMs);
416
+ assertDigest(input.providerRequestDigest, 'providerRequestDigest');
417
+ if (input.logicalTimeMs >= run.deadlineAtLogicalMs)
418
+ return reject(state, 'assessment_expired', input.logicalTimeMs);
419
+ if (!state.dependencyBindings.some((binding) => binding.kind === 'capability' &&
420
+ binding.bindingId === run.capabilityHandleId &&
421
+ binding.bindingDigest === run.capabilityDescriptorDigest))
422
+ return reject(state, 'policy_capability_missing', input.logicalTimeMs);
423
+ if (policy.mode !== 'observe' &&
424
+ policy.checkpoints.includes('pre_run') &&
425
+ !state.assessmentRequests.some((request) => request.runId === run.runId &&
426
+ request.checkpoint === 'pre_run' &&
427
+ request.targetDigest === input.providerRequestDigest &&
428
+ request.requestGeneration === run.generation &&
429
+ assessmentAllowsRequest(state, request)))
430
+ return reject(state, 'assessment_required', input.logicalTimeMs);
431
+ const stream = validateControlStreamV1({
432
+ schemaVersion: 1,
433
+ streamId: input.streamId,
434
+ runId: input.runId,
435
+ generation: input.generation,
436
+ nextSequence: 0,
437
+ releasedThroughSequence: -1,
438
+ receivedBytes: 0,
439
+ releasedBytes: 0,
440
+ finalDigest: null,
441
+ status: 'open',
442
+ });
443
+ return accept(withState(state, {
444
+ streams: sorted([...state.streams, stream], (item) => item.streamId),
445
+ runs: replaceRun(state, run.runId, {
446
+ phase: policy.mode === 'buffered' ? 'buffering' : 'streaming',
447
+ streamIds: [...run.streamIds, stream.streamId],
448
+ }),
449
+ }, input.logicalTimeMs));
450
+ }
451
+ function providerChunkReceived(state, input) {
452
+ const run = currentRun(state, input.runId);
453
+ const stream = state.streams.find((item) => item.streamId === input.chunk.streamId);
454
+ const limits = run ? effectiveLimitsForRun(state, run) : state.limits;
455
+ const policy = run
456
+ ? state.policies.find((record) => record.policyDigest === run.policyDigest)
457
+ ?.policy
458
+ : undefined;
459
+ const chunk = validateControlStreamChunkV1(input.chunk, limits);
460
+ if (!run ||
461
+ !stream ||
462
+ stream.status !== 'open' ||
463
+ stream.runId !== run.runId ||
464
+ chunk.generation !== stream.generation ||
465
+ chunk.sequence !== stream.nextSequence ||
466
+ chunk.fromByte !== stream.receivedBytes)
467
+ return reject(state, 'stream_sequence_invalid', input.logicalTimeMs);
468
+ const existing = state.streamChunks.find((item) => item.chunkId === chunk.chunkId ||
469
+ (item.streamId === chunk.streamId &&
470
+ item.generation === chunk.generation &&
471
+ item.sequence === chunk.sequence));
472
+ if (existing)
473
+ return canonicalizeControlJsonV1(existing) ===
474
+ canonicalizeControlJsonV1(chunk)
475
+ ? accept(state)
476
+ : reject(state, 'stream_sequence_invalid', input.logicalTimeMs);
477
+ if (state.streamChunks.filter((item) => run.streamIds.includes(item.streamId))
478
+ .length >= limits.maxOutputChunksPerRun ||
479
+ chunk.throughByteExclusive > limits.maxBufferedOutputBytes ||
480
+ (policy?.mode === 'incremental' &&
481
+ chunk.throughByteExclusive - stream.releasedBytes >
482
+ limits.maxPendingWindowBytes))
483
+ return reject(state, 'state_capacity_exceeded', input.logicalTimeMs);
484
+ return accept(withState(state, {
485
+ streamChunks: sorted([...state.streamChunks, chunk], (item) => `${item.streamId}\u0000${String(item.generation).padStart(16, '0')}\u0000${String(item.sequence).padStart(16, '0')}`),
486
+ streams: state.streams.map((item) => item.streamId === stream.streamId
487
+ ? {
488
+ ...item,
489
+ nextSequence: item.nextSequence + 1,
490
+ receivedBytes: chunk.throughByteExclusive,
491
+ }
492
+ : item),
493
+ }, input.logicalTimeMs));
494
+ }
495
+ function providerCompleted(state, input) {
496
+ const run = currentRun(state, input.runId);
497
+ const stream = state.streams.find((item) => item.streamId === input.streamId);
498
+ if (!run ||
499
+ !stream ||
500
+ stream.status !== 'open' ||
501
+ stream.generation !== input.generation)
502
+ return reject(state, 'stream_sequence_invalid', input.logicalTimeMs);
503
+ assertDigest(input.completionDigest, 'completionDigest');
504
+ const observed = state.streamChunks
505
+ .filter((chunk) => chunk.streamId === stream.streamId &&
506
+ chunk.generation === stream.generation)
507
+ .map((chunk) => chunk.content)
508
+ .join('');
509
+ if ((observed.length > 0 && observed !== input.completionContent) ||
510
+ digestControlJsonV1('assessment-target', input.completionContent) !==
511
+ input.completionDigest)
512
+ return reject(state, 'stream_content_mismatch', input.logicalTimeMs, 'denied', run.runId);
513
+ return accept(withState(state, {
514
+ streams: state.streams.map((item) => item.streamId === stream.streamId
515
+ ? {
516
+ ...item,
517
+ status: 'completed',
518
+ finalDigest: input.completionDigest,
519
+ }
520
+ : item),
521
+ runs: replaceRun(state, run.runId, {
522
+ outputDigest: input.completionDigest,
523
+ }),
524
+ }, input.logicalTimeMs));
525
+ }
526
+ function continuationSelected(state, input) {
527
+ const run = currentRun(state, input.runId);
528
+ const assessment = state.assessments.find((item) => item.assessmentId === input.assessmentId);
529
+ const policy = run
530
+ ? state.policies.find((item) => item.policyDigest === run.policyDigest)
531
+ ?.policy
532
+ : undefined;
533
+ const countKey = input.kind === 'revise'
534
+ ? 'revisions'
535
+ : input.kind === 'retry'
536
+ ? 'retries'
537
+ : 'challenges';
538
+ if (!run ||
539
+ !assessment ||
540
+ !policy ||
541
+ assessment.runId !== run.runId ||
542
+ assessment.requestGeneration !== run.generation ||
543
+ assessment.disposition !== input.kind)
544
+ return reject(state, 'assessment_invalid', input.logicalTimeMs);
545
+ if (run.dispositionCounts[countKey] >= policy.budgets[countKey])
546
+ return terminateForExhaustion(state, run, policy, input.logicalTimeMs);
547
+ const next = withState(state, {
548
+ ...fenceRunPermissions(state, run.runId),
549
+ runs: replaceRun(state, run.runId, {
550
+ generation: run.generation + 1,
551
+ capabilityHandleId: null,
552
+ dispositionCounts: {
553
+ ...run.dispositionCounts,
554
+ [countKey]: run.dispositionCounts[countKey] + 1,
555
+ },
556
+ phase: 'created',
557
+ }),
558
+ streams: state.streams.map((stream) => stream.runId === run.runId && stream.status === 'open'
559
+ ? { ...stream, status: 'cancelled' }
560
+ : stream),
561
+ assessmentRequests: state.assessmentRequests.map((request) => request.runId === run.runId && request.status === 'pending'
562
+ ? { ...request, status: 'cancelled' }
563
+ : request),
564
+ }, input.logicalTimeMs);
565
+ return accept(next, [
566
+ effect(next, run.runId, 'invoke_continuation', {
567
+ kind: input.kind,
568
+ sourceAssessmentId: assessment.assessmentId,
569
+ revisedContextEntryId: null,
570
+ }),
571
+ ]);
572
+ }
573
+ function outputReleaseAck(state, input) {
574
+ const run = currentRun(state, input.runId);
575
+ const stream = state.streams.find((item) => item.streamId === input.streamId);
576
+ const policy = run
577
+ ? state.policies.find((item) => item.policyDigest === run.policyDigest)
578
+ ?.policy
579
+ : undefined;
580
+ if (!run ||
581
+ !stream ||
582
+ !policy ||
583
+ stream.generation !== input.generation ||
584
+ input.throughSequence <= stream.releasedThroughSequence ||
585
+ input.throughSequence >= stream.nextSequence ||
586
+ input.throughByteExclusive <= stream.releasedBytes ||
587
+ input.throughByteExclusive > stream.receivedBytes)
588
+ return reject(state, 'stream_sequence_invalid', input.logicalTimeMs);
589
+ assertDigest(input.windowDigest, 'windowDigest');
590
+ const chunks = state.streamChunks.filter((chunk) => chunk.streamId === stream.streamId &&
591
+ chunk.generation === stream.generation &&
592
+ chunk.sequence > stream.releasedThroughSequence &&
593
+ chunk.sequence <= input.throughSequence);
594
+ const window = createStreamWindowV1(stream, chunks);
595
+ if (window.fromSequence !== stream.releasedThroughSequence + 1 ||
596
+ window.fromByte !== stream.releasedBytes ||
597
+ window.throughSequence !== input.throughSequence ||
598
+ window.throughByteExclusive !== input.throughByteExclusive ||
599
+ window.windowDigest !== input.windowDigest)
600
+ return reject(state, 'stream_content_mismatch', input.logicalTimeMs);
601
+ if (window.utf8Bytes > effectiveLimitsForRun(state, run).maxPendingWindowBytes)
602
+ return reject(state, 'state_capacity_exceeded', input.logicalTimeMs);
603
+ const assessmentMatches = policy.mode === 'incremental'
604
+ ? state.assessmentRequests.some((request) => request.runId === run.runId &&
605
+ request.checkpoint === 'stream' &&
606
+ request.targetKind === 'stream_window' &&
607
+ request.targetDigest === window.windowDigest &&
608
+ request.requestGeneration === run.generation &&
609
+ assessmentAllowsRequest(state, request))
610
+ : state.assessmentRequests.some((request) => request.runId === run.runId &&
611
+ request.checkpoint === 'post_run' &&
612
+ request.targetDigest === run.outputDigest &&
613
+ request.requestGeneration === run.generation &&
614
+ assessmentAllowsRequest(state, request));
615
+ // An acknowledgement confirms a previously emitted, assessment-authorised
616
+ // release. It never authorises or emits a second release itself.
617
+ if (!assessmentMatches)
618
+ return reject(state, 'assessment_required', input.logicalTimeMs);
619
+ const next = withState(state, {
620
+ streams: state.streams.map((item) => item.streamId === stream.streamId
621
+ ? {
622
+ ...item,
623
+ releasedThroughSequence: input.throughSequence,
624
+ releasedBytes: input.throughByteExclusive,
625
+ }
626
+ : item),
627
+ runs: replaceRun(state, run.runId, {
628
+ releasedBytes: input.throughByteExclusive,
629
+ }),
630
+ }, input.logicalTimeMs);
631
+ return accept(next);
632
+ }
633
+ function grantIssued(state, input) {
634
+ const run = currentRun(state, input.grant.runId);
635
+ const limits = run ? effectiveLimitsForRun(state, run) : state.limits;
636
+ const policy = run
637
+ ? state.policies.find((record) => record.policyDigest === run.policyDigest)
638
+ ?.policy
639
+ : undefined;
640
+ validateActionGrantStateV1(input.grant, limits);
641
+ const request = state.assessmentRequests.find((item) => item.assessmentRequestId === input.grant.assessmentRequestId);
642
+ const assessment = state.assessments.find((item) => item.assessmentId === input.grant.assessmentId);
643
+ if (!run ||
644
+ !policy ||
645
+ run.capabilityHandleId === null ||
646
+ !request ||
647
+ !assessment ||
648
+ request.checkpoint !== 'pre_tool' ||
649
+ request.status !== 'accepted' ||
650
+ request.requestGeneration !== run.generation ||
651
+ assessment.disposition !== 'allow' ||
652
+ assessment.assessmentRequestId !== request.assessmentRequestId ||
653
+ !assessmentAllowsRequest(state, request) ||
654
+ request.targetDigest !== input.grant.assessmentTargetDigest ||
655
+ request.runId !== run.runId ||
656
+ assessment.requestGeneration !== run.generation ||
657
+ canonicalizeControlJsonV1(request.scope) !==
658
+ canonicalizeControlJsonV1(input.grant.scope) ||
659
+ input.grant.status !== 'issued' ||
660
+ input.grant.reservation !== null ||
661
+ input.grant.issuedAtLogicalMs !== input.logicalTimeMs ||
662
+ input.grant.expiresAtLogicalMs > run.deadlineAtLogicalMs ||
663
+ input.grant.expiresAtLogicalMs > assessment.expiresAtLogicalMs ||
664
+ !policy.allowedActions.some((action) => action.namespace === input.grant.namespace &&
665
+ action.toolId === input.grant.toolId &&
666
+ action.operation === input.grant.operation &&
667
+ action.actionBindingId === input.grant.actionBindingId &&
668
+ input.grant.actionBindingVersion >= action.minimumActionBindingVersion) ||
669
+ (policy.coordinatedActionsRequired &&
670
+ input.grant.scope.kind !== 'coordinated') ||
671
+ (input.grant.scope.kind === 'coordinated' &&
672
+ input.grant.expiresAtLogicalMs >
673
+ input.grant.scope.leaseExpiresAtLogicalMs))
674
+ return reject(state, 'grant_assessment_mismatch', input.logicalTimeMs);
675
+ const prior = state.grants.find((item) => item.grantId === input.grant.grantId);
676
+ if (prior)
677
+ return canonicalizeControlJsonV1(prior) ===
678
+ canonicalizeControlJsonV1(input.grant)
679
+ ? accept(state)
680
+ : reject(state, 'state_conflict', input.logicalTimeMs);
681
+ if (state.grants.filter((item) => ['issued', 'reserved'].includes(item.status))
682
+ .length >= state.limits.maxActiveGrants ||
683
+ state.grants.length >= state.limits.maxRetainedGrantRecords)
684
+ return reject(state, 'state_capacity_exceeded', input.logicalTimeMs);
685
+ if (state.grants.filter((item) => item.runId === run.runId &&
686
+ ['issued', 'reserved'].includes(item.status)).length >= limits.maxActiveGrants ||
687
+ state.grants.filter((item) => item.runId === run.runId).length >=
688
+ limits.maxRetainedGrantRecords ||
689
+ input.grant.expiresAtLogicalMs - input.grant.issuedAtLogicalMs >
690
+ limits.maxGrantTtlMs)
691
+ return reject(state, 'state_capacity_exceeded', input.logicalTimeMs);
692
+ const idempotency = state.actionIdempotency.find((item) => item.scopeDigest === input.grant.scopeDigest &&
693
+ item.idempotencyKey === input.grant.idempotencyKey);
694
+ if (idempotency)
695
+ return reject(state, idempotency.actionDigest === input.grant.actionDigest
696
+ ? 'grant_consumed'
697
+ : 'grant_idempotency_conflict', input.logicalTimeMs);
698
+ const next = withState(state, {
699
+ grants: sorted([...state.grants, input.grant], (item) => item.grantId),
700
+ actionIdempotency: sorted([
701
+ ...state.actionIdempotency,
702
+ {
703
+ schemaVersion: 1,
704
+ scopeDigest: input.grant.scopeDigest,
705
+ idempotencyKey: input.grant.idempotencyKey,
706
+ actionDigest: input.grant.actionDigest,
707
+ grantId: input.grant.grantId,
708
+ retainedOutcome: input.grant.status,
709
+ },
710
+ ], (item) => `${item.scopeDigest}\u0000${item.idempotencyKey}`),
711
+ runs: replaceRun(state, run.runId, {
712
+ grantIds: [...run.grantIds, input.grant.grantId],
713
+ }),
714
+ }, input.logicalTimeMs);
715
+ return accept(next, [
716
+ effect(next, run.runId, 'persist_grant', {
717
+ grantId: input.grant.grantId,
718
+ stateGeneration: next.stateGeneration,
719
+ }),
720
+ ]);
721
+ }
722
+ function grantReserved(state, input) {
723
+ const grant = state.grants.find((item) => item.grantId === input.grantId);
724
+ const run = grant ? currentRun(state, grant.runId) : undefined;
725
+ const request = grant
726
+ ? state.assessmentRequests.find((item) => item.assessmentRequestId === grant.assessmentRequestId)
727
+ : undefined;
728
+ if (!grant ||
729
+ !run ||
730
+ run.capabilityHandleId === null ||
731
+ !request ||
732
+ request.requestGeneration !== run.generation ||
733
+ request.status !== 'accepted' ||
734
+ grant.status !== 'issued' ||
735
+ input.logicalTimeMs >= grant.expiresAtLogicalMs)
736
+ return reject(state, grant ? 'grant_expired' : 'grant_missing', input.logicalTimeMs);
737
+ const grants = state.grants.map((item) => item.grantId === grant.grantId
738
+ ? {
739
+ ...item,
740
+ stateGeneration: item.stateGeneration + 1,
741
+ status: 'reserved',
742
+ reservation: input.reservation,
743
+ }
744
+ : item);
745
+ return accept(withState(state, {
746
+ grants,
747
+ actionIdempotency: updateActionOutcome(state, grant.grantId, 'reserved'),
748
+ }, input.logicalTimeMs));
749
+ }
750
+ function actionDispatchOutcome(state, input) {
751
+ const grant = state.grants.find((item) => item.grantId === input.grantId);
752
+ if (!grant || grant.status !== 'reserved' || grant.reservation === null)
753
+ return reject(state, 'state_conflict', input.logicalTimeMs);
754
+ const reservation = grant.reservation;
755
+ if (reservation.reservationId !== input.reservationId ||
756
+ reservation.dispatchAttemptId !== input.dispatchAttemptId)
757
+ return reject(state, 'state_conflict', input.logicalTimeMs);
758
+ const grants = state.grants.map((item) => item.grantId === grant.grantId
759
+ ? {
760
+ ...item,
761
+ stateGeneration: item.stateGeneration + 1,
762
+ status: input.outcome,
763
+ }
764
+ : item);
765
+ return accept(withState(state, {
766
+ grants,
767
+ actionIdempotency: updateActionOutcome(state, grant.grantId, input.outcome),
768
+ }, input.logicalTimeMs));
769
+ }
770
+ function messagePrepared(state, input) {
771
+ validateOutboundMessageAttemptStateV1(input.attempt);
772
+ const run = currentRun(state, input.attempt.runId);
773
+ const limits = run ? effectiveLimitsForRun(state, run) : state.limits;
774
+ const request = state.assessmentRequests.find((item) => item.assessmentRequestId === input.attempt.assessmentRequestId);
775
+ const assessment = state.assessments.find((item) => item.assessmentId === input.attempt.assessmentId);
776
+ if (!run ||
777
+ run.capabilityHandleId === null ||
778
+ !request ||
779
+ !assessment ||
780
+ input.attempt.preparedAtLogicalMs !== input.logicalTimeMs ||
781
+ request.checkpoint !== 'pre_message' ||
782
+ request.status !== 'accepted' ||
783
+ request.requestGeneration !== run.generation ||
784
+ assessment.disposition !== 'allow' ||
785
+ assessment.assessmentRequestId !== request.assessmentRequestId ||
786
+ !assessmentAllowsRequest(state, request) ||
787
+ request.targetDigest !== input.attempt.messageDigest ||
788
+ request.scope === null ||
789
+ digestControlJsonV1('scope', request.scope) !== input.attempt.scopeDigest ||
790
+ request.runId !== run.runId ||
791
+ assessment.requestGeneration !== run.generation)
792
+ return reject(state, 'assessment_required', input.logicalTimeMs);
793
+ if (state.messageAttempts.length >= state.limits.maxRetainedMessageAttempts ||
794
+ state.messageAttempts.filter((item) => item.runId === run.runId).length >=
795
+ limits.maxRetainedMessageAttempts ||
796
+ input.attempt.expiresAtLogicalMs <= input.logicalTimeMs ||
797
+ input.attempt.expiresAtLogicalMs > run.deadlineAtLogicalMs ||
798
+ input.attempt.expiresAtLogicalMs > assessment.expiresAtLogicalMs ||
799
+ (request.scope.kind === 'coordinated' &&
800
+ input.attempt.expiresAtLogicalMs >
801
+ request.scope.leaseExpiresAtLogicalMs) ||
802
+ input.attempt.expiresAtLogicalMs - input.attempt.preparedAtLogicalMs >
803
+ limits.maxMessagePermitTtlMs ||
804
+ state.messageAttempts.filter((item) => ['prepared', 'reserved'].includes(item.status)).length >= state.limits.maxActiveMessageAttempts ||
805
+ state.messageAttempts.filter((item) => item.runId === run.runId &&
806
+ ['prepared', 'reserved'].includes(item.status)).length >= limits.maxActiveMessageAttempts)
807
+ return reject(state, 'state_capacity_exceeded', input.logicalTimeMs);
808
+ const prior = state.messageIdempotency.find((item) => item.scopeDigest === input.attempt.scopeDigest &&
809
+ item.idempotencyKey === input.attempt.idempotencyKey);
810
+ if (prior)
811
+ return reject(state, prior.messageDigest === input.attempt.messageDigest
812
+ ? 'message_indeterminate'
813
+ : 'message_idempotency_conflict', input.logicalTimeMs);
814
+ return accept(withState(state, {
815
+ messageAttempts: sorted([...state.messageAttempts, input.attempt], (item) => item.messageAttemptId),
816
+ messageIdempotency: sorted([
817
+ ...state.messageIdempotency,
818
+ {
819
+ schemaVersion: 1,
820
+ scopeDigest: input.attempt.scopeDigest,
821
+ idempotencyKey: input.attempt.idempotencyKey,
822
+ messageDigest: input.attempt.messageDigest,
823
+ messageAttemptId: input.attempt.messageAttemptId,
824
+ retainedOutcome: input.attempt.status,
825
+ },
826
+ ], (item) => `${item.scopeDigest}\u0000${item.idempotencyKey}`),
827
+ runs: replaceRun(state, run.runId, {
828
+ messageAttemptIds: [
829
+ ...run.messageAttemptIds,
830
+ input.attempt.messageAttemptId,
831
+ ],
832
+ }),
833
+ }, input.logicalTimeMs));
834
+ }
835
+ function messageReserved(state, input) {
836
+ const attempt = state.messageAttempts.find((item) => item.messageAttemptId === input.messageAttemptId);
837
+ const run = attempt ? currentRun(state, attempt.runId) : undefined;
838
+ const request = attempt
839
+ ? state.assessmentRequests.find((item) => item.assessmentRequestId === attempt.assessmentRequestId)
840
+ : undefined;
841
+ if (!attempt ||
842
+ !run ||
843
+ run.capabilityHandleId === null ||
844
+ !request ||
845
+ request.requestGeneration !== run.generation ||
846
+ request.status !== 'accepted' ||
847
+ attempt.status !== 'prepared' ||
848
+ input.logicalTimeMs >= attempt.expiresAtLogicalMs)
849
+ return reject(state, attempt ? 'message_indeterminate' : 'message_not_permitted', input.logicalTimeMs);
850
+ const attempts = state.messageAttempts.map((item) => item.messageAttemptId === attempt.messageAttemptId
851
+ ? {
852
+ ...item,
853
+ generation: item.generation + 1,
854
+ status: 'reserved',
855
+ reservation: input.reservation,
856
+ reservedAtLogicalMs: input.logicalTimeMs,
857
+ }
858
+ : item);
859
+ return accept(withState(state, {
860
+ messageAttempts: attempts,
861
+ messageIdempotency: updateMessageOutcome(state, attempt.messageAttemptId, 'reserved'),
862
+ }, input.logicalTimeMs));
863
+ }
864
+ function messageDispatchOutcome(state, input) {
865
+ const attempt = state.messageAttempts.find((item) => item.messageAttemptId === input.messageAttemptId);
866
+ if (!attempt || attempt.status !== 'reserved' || attempt.reservation === null)
867
+ return reject(state, 'state_conflict', input.logicalTimeMs);
868
+ const reservation = attempt.reservation;
869
+ if (reservation.reservationId !== input.reservationId ||
870
+ reservation.messageDispatchAttemptId !== input.messageDispatchAttemptId)
871
+ return reject(state, 'state_conflict', input.logicalTimeMs);
872
+ const attempts = state.messageAttempts.map((item) => item.messageAttemptId === attempt.messageAttemptId
873
+ ? {
874
+ ...item,
875
+ generation: item.generation + 1,
876
+ status: input.outcome,
877
+ }
878
+ : item);
879
+ return accept(withState(state, {
880
+ messageAttempts: attempts,
881
+ messageIdempotency: updateMessageOutcome(state, attempt.messageAttemptId, input.outcome),
882
+ }, input.logicalTimeMs));
883
+ }
884
+ function deadlineFired(state, input) {
885
+ if (input.timerKind === 'run') {
886
+ const run = currentRun(state, input.timerId);
887
+ if (!run ||
888
+ run.generation !== input.timerGeneration ||
889
+ input.logicalTimeMs < run.deadlineAtLogicalMs)
890
+ return reject(state, 'state_conflict', input.logicalTimeMs);
891
+ return terminate(state, run, 'failed', 'assessment_expired', input.logicalTimeMs);
892
+ }
893
+ if (input.timerKind === 'assessment') {
894
+ const request = state.assessmentRequests.find((item) => item.assessmentRequestId === input.timerId);
895
+ if (!request ||
896
+ request.requestGeneration !== input.timerGeneration ||
897
+ request.status !== 'pending' ||
898
+ input.logicalTimeMs < request.expiresAtLogicalMs)
899
+ return reject(state, 'state_conflict', input.logicalTimeMs);
900
+ return accept(withState(state, {
901
+ assessmentRequests: state.assessmentRequests.map((item) => item.assessmentRequestId === request.assessmentRequestId
902
+ ? { ...item, status: 'expired' }
903
+ : item),
904
+ }, input.logicalTimeMs));
905
+ }
906
+ if (input.timerKind === 'grant') {
907
+ const grant = state.grants.find((item) => item.grantId === input.timerId);
908
+ if (!grant ||
909
+ grant.stateGeneration !== input.timerGeneration ||
910
+ grant.status !== 'issued' ||
911
+ input.logicalTimeMs < grant.expiresAtLogicalMs)
912
+ return reject(state, 'state_conflict', input.logicalTimeMs);
913
+ const grants = state.grants.map((item) => item.grantId === grant.grantId
914
+ ? {
915
+ ...item,
916
+ stateGeneration: item.stateGeneration + 1,
917
+ status: 'expired',
918
+ }
919
+ : item);
920
+ return accept(withState(state, {
921
+ grants,
922
+ actionIdempotency: updateActionOutcome(state, grant.grantId, 'expired'),
923
+ }, input.logicalTimeMs));
924
+ }
925
+ const attempt = state.messageAttempts.find((item) => item.messageAttemptId === input.timerId);
926
+ if (!attempt ||
927
+ attempt.generation !== input.timerGeneration ||
928
+ attempt.status !== 'prepared' ||
929
+ input.logicalTimeMs < attempt.expiresAtLogicalMs)
930
+ return reject(state, 'state_conflict', input.logicalTimeMs);
931
+ const attempts = state.messageAttempts.map((item) => item.messageAttemptId === attempt.messageAttemptId
932
+ ? { ...item, generation: item.generation + 1, status: 'expired' }
933
+ : item);
934
+ return accept(withState(state, {
935
+ messageAttempts: attempts,
936
+ messageIdempotency: updateMessageOutcome(state, attempt.messageAttemptId, 'expired'),
937
+ }, input.logicalTimeMs));
938
+ }
939
+ function terminateRun(state, input) {
940
+ const run = currentRun(state, input.runId);
941
+ if (!run || run.generation !== input.generation)
942
+ return reject(state, 'assessment_generation_stale', input.logicalTimeMs);
943
+ return terminate(state, run, input.type === 'run_cancelled' ? 'cancelled' : 'failed', input.reasonCode, input.logicalTimeMs);
944
+ }
945
+ function terminate(state, run, phase, reasonCode, logicalTimeMs) {
946
+ const next = withState(state, {
947
+ ...fenceRunPermissions(state, run.runId),
948
+ runs: replaceRun(state, run.runId, {
949
+ phase,
950
+ generation: run.generation + 1,
951
+ terminalReasonCode: reasonCode,
952
+ }),
953
+ streams: state.streams.map((stream) => stream.runId === run.runId && stream.status === 'open'
954
+ ? { ...stream, status: 'cancelled' }
955
+ : stream),
956
+ assessmentRequests: state.assessmentRequests.map((request) => request.runId === run.runId && request.status === 'pending'
957
+ ? { ...request, status: 'cancelled' }
958
+ : request),
959
+ }, logicalTimeMs);
960
+ return accept(next);
961
+ }
962
+ function terminateForExhaustion(state, run, policy, logicalTimeMs) {
963
+ return terminate(state, run, policy.exhaustedDisposition === 'deny'
964
+ ? 'denied'
965
+ : policy.exhaustedDisposition === 'abstain'
966
+ ? 'abstained'
967
+ : 'escalated', 'assessment_budget_exhausted', logicalTimeMs);
968
+ }
969
+ function assessmentPhase(run, assessment) {
970
+ if (assessment.disposition === 'deny')
971
+ return 'denied';
972
+ if (assessment.disposition === 'abstain')
973
+ return 'abstained';
974
+ if (assessment.disposition === 'escalate')
975
+ return 'escalated';
976
+ if (assessment.disposition !== 'allow')
977
+ return run.phase;
978
+ if (assessment.checkpoint === 'pre_run')
979
+ return 'input_assessed';
980
+ if (assessment.checkpoint === 'post_run')
981
+ return 'output_assessed';
982
+ return run.phase;
983
+ }
984
+ function validateInputHead(input) {
985
+ const common = [
986
+ 'schemaVersion',
987
+ 'inputId',
988
+ 'type',
989
+ 'expectedStateGeneration',
990
+ 'logicalTimeMs',
991
+ ];
992
+ const variants = {
993
+ policy_registered: ['policy', 'policyDigest'],
994
+ run_created: ['run'],
995
+ context_admitted: ['entry'],
996
+ context_promoted: [
997
+ 'sourceContextEntryId',
998
+ 'entry',
999
+ 'transformerBindingDigest',
1000
+ ],
1001
+ capability_negotiated: [
1002
+ 'runId',
1003
+ 'capabilityHandleId',
1004
+ 'descriptorDigest',
1005
+ 'result',
1006
+ ],
1007
+ assessment_requested: ['request'],
1008
+ assessment_received: ['assessment'],
1009
+ provider_started: [
1010
+ 'runId',
1011
+ 'generation',
1012
+ 'providerRequestDigest',
1013
+ 'streamId',
1014
+ ],
1015
+ provider_chunk_received: ['runId', 'chunk'],
1016
+ provider_completed: [
1017
+ 'runId',
1018
+ 'generation',
1019
+ 'streamId',
1020
+ 'completionContent',
1021
+ 'completionDigest',
1022
+ ],
1023
+ continuation_selected: ['runId', 'assessmentId', 'kind'],
1024
+ output_release_ack: [
1025
+ 'runId',
1026
+ 'streamId',
1027
+ 'generation',
1028
+ 'throughSequence',
1029
+ 'throughByteExclusive',
1030
+ 'windowDigest',
1031
+ ],
1032
+ grant_issued: ['grant'],
1033
+ grant_reserved: ['grantId', 'reservation'],
1034
+ action_dispatch_outcome: [
1035
+ 'grantId',
1036
+ 'reservationId',
1037
+ 'dispatchAttemptId',
1038
+ 'outcome',
1039
+ ],
1040
+ message_prepared: ['message', 'attempt'],
1041
+ message_reserved: ['messageAttemptId', 'reservation'],
1042
+ message_dispatch_outcome: [
1043
+ 'messageAttemptId',
1044
+ 'reservationId',
1045
+ 'messageDispatchAttemptId',
1046
+ 'outcome',
1047
+ ],
1048
+ deadline_fired: ['timerKind', 'timerId', 'timerGeneration'],
1049
+ run_cancelled: ['runId', 'generation', 'reasonCode'],
1050
+ run_failed: ['runId', 'generation', 'reasonCode'],
1051
+ };
1052
+ const variant = variants[input.type];
1053
+ if (!variant)
1054
+ throw new TypeError('state_conflict');
1055
+ assertExactKeys(input, [...common, ...variant], 'reducer input');
1056
+ if (input.schemaVersion !== 1)
1057
+ throw new TypeError('state_conflict');
1058
+ assertString(input.inputId, 'inputId');
1059
+ assertSafeInteger(input.expectedStateGeneration, 'expectedStateGeneration', 1);
1060
+ assertSafeInteger(input.logicalTimeMs, 'logicalTimeMs');
1061
+ }
1062
+ function currentRun(state, runId) {
1063
+ const run = state.runs.find((item) => item.runId === runId);
1064
+ return run && !terminalPhases.has(run.phase) ? run : undefined;
1065
+ }
1066
+ /** The serialised state can only tighten a policy, never dilute it. */
1067
+ function effectiveLimitsForRun(state, run) {
1068
+ const policy = state.policies.find((record) => record.policyDigest === run.policyDigest)?.policy;
1069
+ if (!policy)
1070
+ throw new TypeError('state_conflict');
1071
+ const limits = {};
1072
+ for (const key of Object.keys(state.limits))
1073
+ limits[key] = Math.min(state.limits[key], policy.limits[key]);
1074
+ limits.maxRunDurationMs = Math.min(limits.maxRunDurationMs, policy.maximumRunDurationMs);
1075
+ limits.maxAssessmentTtlMs = Math.min(limits.maxAssessmentTtlMs, policy.maximumAssessmentTtlMs);
1076
+ limits.maxGrantTtlMs = Math.min(limits.maxGrantTtlMs, policy.maximumGrantTtlMs);
1077
+ limits.maxMessagePermitTtlMs = Math.min(limits.maxMessagePermitTtlMs, policy.maximumMessagePermitTtlMs);
1078
+ return limits;
1079
+ }
1080
+ /** An accepted assessment is usable only when it exactly closes its request. */
1081
+ function assessmentAllowsRequest(state, request) {
1082
+ return state.assessments.some((assessment) => assessment.assessmentRequestId === request.assessmentRequestId &&
1083
+ assessment.disposition === 'allow' &&
1084
+ assessment.requestGeneration === request.requestGeneration &&
1085
+ assessment.runId === request.runId &&
1086
+ assessment.tenantId === request.tenantId &&
1087
+ assessment.policyId === request.policyId &&
1088
+ assessment.policyVersion === request.policyVersion &&
1089
+ assessment.checkpoint === request.checkpoint &&
1090
+ assessment.assessorId === request.assessorId &&
1091
+ assessment.assessorVersion === request.assessorVersion &&
1092
+ assessment.targetKind === request.targetKind &&
1093
+ assessment.targetDigest === request.targetDigest &&
1094
+ assessment.zoneDigest === request.zoneDigest &&
1095
+ assessment.provenanceDigest === request.provenanceDigest &&
1096
+ canonicalizeControlJsonV1(assessment.scope) ===
1097
+ canonicalizeControlJsonV1(request.scope));
1098
+ }
1099
+ /**
1100
+ * Derives the only currently unreleased window an accepted assessment may
1101
+ * authorise. There is no caller-controlled byte or sequence range here.
1102
+ */
1103
+ function releaseWindowForAssessment(state, request, assessment) {
1104
+ if (assessment.disposition !== 'allow')
1105
+ return null;
1106
+ const run = currentRun(state, request.runId);
1107
+ const policy = run
1108
+ ? state.policies.find((record) => record.policyDigest === run.policyDigest)
1109
+ ?.policy
1110
+ : undefined;
1111
+ if (!run || !policy || policy.mode === 'observe')
1112
+ return null;
1113
+ const streams = state.streams.filter((stream) => stream.runId === run.runId && stream.generation === run.generation);
1114
+ if (policy.mode === 'incremental' &&
1115
+ request.checkpoint === 'stream' &&
1116
+ request.targetKind === 'stream_window' &&
1117
+ policy.checkpoints.includes('stream')) {
1118
+ for (const stream of streams) {
1119
+ const chunks = state.streamChunks.filter((chunk) => chunk.streamId === stream.streamId &&
1120
+ chunk.generation === stream.generation &&
1121
+ chunk.sequence > stream.releasedThroughSequence);
1122
+ for (let end = 1; end <= chunks.length; end += 1) {
1123
+ const window = createStreamWindowV1(stream, chunks.slice(0, end));
1124
+ if (window.windowDigest === assessment.targetDigest &&
1125
+ window.utf8Bytes <=
1126
+ effectiveLimitsForRun(state, run).maxPendingWindowBytes)
1127
+ return { stream, window };
1128
+ }
1129
+ }
1130
+ return null;
1131
+ }
1132
+ if (policy.mode === 'buffered' &&
1133
+ request.checkpoint === 'post_run' &&
1134
+ request.targetKind === 'final_output' &&
1135
+ policy.checkpoints.includes('post_run') &&
1136
+ run.outputDigest === assessment.targetDigest) {
1137
+ for (const stream of streams) {
1138
+ if (stream.status !== 'completed')
1139
+ continue;
1140
+ const chunks = state.streamChunks.filter((chunk) => chunk.streamId === stream.streamId &&
1141
+ chunk.generation === stream.generation &&
1142
+ chunk.sequence > stream.releasedThroughSequence);
1143
+ if (!chunks.length)
1144
+ continue;
1145
+ const window = createStreamWindowV1(stream, chunks);
1146
+ if (window.utf8Bytes <=
1147
+ effectiveLimitsForRun(state, run).maxPendingWindowBytes)
1148
+ return { stream, window };
1149
+ }
1150
+ }
1151
+ return null;
1152
+ }
1153
+ function replaceRun(state, runId, patch) {
1154
+ return state.runs.map((run) => run.runId === runId ? { ...run, ...patch } : run);
1155
+ }
1156
+ function withState(state, patch, logicalTimeMs) {
1157
+ const { traceDigest: _traceDigest, encodedBytes: _encodedBytes, ...material } = {
1158
+ ...state,
1159
+ ...patch,
1160
+ stateGeneration: state.stateGeneration + 1,
1161
+ logicalTimeHighWaterMs: Math.max(state.logicalTimeHighWaterMs, logicalTimeMs),
1162
+ };
1163
+ return finalizeInferenceControlStateV1(material);
1164
+ }
1165
+ function accept(state, effects = []) {
1166
+ return deepFreeze({ accepted: true, state, effects: [...effects] });
1167
+ }
1168
+ function unchanged(state, reasonCode) {
1169
+ return deepFreeze({ accepted: false, reasonCode, state, effects: [] });
1170
+ }
1171
+ function reject(state, reasonCode, logicalTimeMs, outcome = 'withheld', runId = null) {
1172
+ const diagnostic = {
1173
+ schemaVersion: 1,
1174
+ diagnosticId: `diagnostic:${state.stateGeneration + 1}:${state.diagnostics.length + 1}`,
1175
+ logicalTimeMs,
1176
+ runId,
1177
+ checkpoint: null,
1178
+ reasonCode,
1179
+ outcome,
1180
+ sizeBucket: 0,
1181
+ correlationId: null,
1182
+ };
1183
+ // A rejection must not evict prior audit evidence. If the state cannot
1184
+ // accommodate another diagnostic, preserve it verbatim and fail closed.
1185
+ if (state.diagnostics.length >= state.limits.maxDiagnostics)
1186
+ return unchanged(state, reasonCode);
1187
+ let next;
1188
+ try {
1189
+ next = withState(state, { diagnostics: [...state.diagnostics, diagnostic] }, logicalTimeMs);
1190
+ }
1191
+ catch {
1192
+ return unchanged(state, reasonCode);
1193
+ }
1194
+ return deepFreeze({
1195
+ accepted: false,
1196
+ reasonCode,
1197
+ state: next,
1198
+ effects: [
1199
+ effect(next, runId ?? 'control', 'emit_diagnostic', { diagnostic }),
1200
+ ],
1201
+ });
1202
+ }
1203
+ function effect(state, runId, type, payload) {
1204
+ return deepFreeze({
1205
+ schemaVersion: 1,
1206
+ effectId: `effect:${state.stateGeneration}:${type}:${runId}`,
1207
+ runId,
1208
+ generation: state.runs.find((run) => run.runId === runId)?.generation ??
1209
+ state.stateGeneration,
1210
+ type,
1211
+ ...payload,
1212
+ });
1213
+ }
1214
+ function updateActionOutcome(state, grantId, outcome) {
1215
+ return state.actionIdempotency.map((record) => record.grantId === grantId
1216
+ ? { ...record, retainedOutcome: outcome }
1217
+ : record);
1218
+ }
1219
+ function updateMessageOutcome(state, messageAttemptId, outcome) {
1220
+ return state.messageIdempotency.map((record) => record.messageAttemptId === messageAttemptId
1221
+ ? { ...record, retainedOutcome: outcome }
1222
+ : record);
1223
+ }
1224
+ function fenceRunPermissions(state, runId) {
1225
+ const grants = state.grants.map((grant) => {
1226
+ if (grant.runId !== runId)
1227
+ return grant;
1228
+ if (grant.status === 'issued')
1229
+ return {
1230
+ ...grant,
1231
+ stateGeneration: grant.stateGeneration + 1,
1232
+ status: 'expired',
1233
+ };
1234
+ if (grant.status === 'reserved')
1235
+ return {
1236
+ ...grant,
1237
+ stateGeneration: grant.stateGeneration + 1,
1238
+ status: 'indeterminate',
1239
+ };
1240
+ return grant;
1241
+ });
1242
+ const messageAttempts = state.messageAttempts.map((attempt) => {
1243
+ if (attempt.runId !== runId)
1244
+ return attempt;
1245
+ if (attempt.status === 'prepared')
1246
+ return {
1247
+ ...attempt,
1248
+ generation: attempt.generation + 1,
1249
+ status: 'expired',
1250
+ };
1251
+ if (attempt.status === 'reserved')
1252
+ return {
1253
+ ...attempt,
1254
+ generation: attempt.generation + 1,
1255
+ status: 'indeterminate',
1256
+ };
1257
+ return attempt;
1258
+ });
1259
+ return {
1260
+ grants,
1261
+ actionIdempotency: state.actionIdempotency.map((record) => ({
1262
+ ...record,
1263
+ retainedOutcome: grants.find((grant) => grant.grantId === record.grantId)?.status ??
1264
+ record.retainedOutcome,
1265
+ })),
1266
+ messageAttempts,
1267
+ messageIdempotency: state.messageIdempotency.map((record) => ({
1268
+ ...record,
1269
+ retainedOutcome: messageAttempts.find((attempt) => attempt.messageAttemptId === record.messageAttemptId)?.status ?? record.retainedOutcome,
1270
+ })),
1271
+ };
1272
+ }
1273
+ function sorted(items, key) {
1274
+ return [...items].sort((left, right) => compareCodeUnits(key(left), key(right)));
1275
+ }
1276
+ function reasonFromError(error) {
1277
+ const message = error instanceof Error ? error.message : '';
1278
+ const values = [
1279
+ 'context_zone_invalid',
1280
+ 'context_promotion_denied',
1281
+ 'context_limit_exceeded',
1282
+ 'policy_capability_missing',
1283
+ 'assessment_required',
1284
+ 'assessment_invalid',
1285
+ 'assessment_indeterminate',
1286
+ 'assessment_expired',
1287
+ 'assessment_scope_mismatch',
1288
+ 'assessment_content_mismatch',
1289
+ 'assessment_budget_exhausted',
1290
+ 'release_mode_incompatible',
1291
+ 'release_buffer_exceeded',
1292
+ 'stream_abort_unavailable',
1293
+ 'action_not_permitted',
1294
+ 'grant_missing',
1295
+ 'grant_expired',
1296
+ 'grant_consumed',
1297
+ 'grant_scope_mismatch',
1298
+ 'grant_action_mismatch',
1299
+ 'grant_input_mismatch',
1300
+ 'grant_assessment_mismatch',
1301
+ 'grant_epoch_stale',
1302
+ 'grant_fence_stale',
1303
+ 'grant_idempotency_conflict',
1304
+ 'gateway_unavailable',
1305
+ 'downstream_fence_rejected',
1306
+ 'downstream_indeterminate',
1307
+ 'state_capacity_exceeded',
1308
+ 'state_conflict',
1309
+ 'logical_time_rollback',
1310
+ 'assessment_unsolicited',
1311
+ 'assessment_assessor_mismatch',
1312
+ 'assessment_generation_stale',
1313
+ 'stream_sequence_invalid',
1314
+ 'stream_content_mismatch',
1315
+ 'message_not_permitted',
1316
+ 'message_indeterminate',
1317
+ 'message_idempotency_conflict',
1318
+ 'dependency_rebind_failed',
1319
+ ];
1320
+ return values.includes(message)
1321
+ ? message
1322
+ : 'state_conflict';
1323
+ }
1324
+ function runIdOf(input) {
1325
+ if ('runId' in input && typeof input.runId === 'string')
1326
+ return input.runId;
1327
+ if ('run' in input)
1328
+ return input.run.runId;
1329
+ if ('entry' in input)
1330
+ return input.entry.runId;
1331
+ if ('request' in input)
1332
+ return input.request.runId;
1333
+ if ('assessment' in input)
1334
+ return input.assessment.runId;
1335
+ if ('grant' in input)
1336
+ return input.grant.runId;
1337
+ if ('attempt' in input)
1338
+ return input.attempt.runId;
1339
+ return null;
1340
+ }
1341
+ //# sourceMappingURL=reducer.js.map