@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
package/dist/state.js ADDED
@@ -0,0 +1,689 @@
1
+ import { validateAssessmentRequestV1, validateInferenceAssessmentV1, } from './assessments.js';
2
+ import { canonicalizeControlJsonV1, digestControlJsonV1, utf8ByteLength, } from './canonical.js';
3
+ import { validateContextEntryV1 } from './context.js';
4
+ import { assertPolicyNarrowingV1, createPolicyRecordV1 } from './policy.js';
5
+ import { validateControlScopeV1 } from './scopes.js';
6
+ import { validateActionGrantStateV1, validateActionIdempotencyRecordV1, validateDependencyBindingRecordV1, validateDiagnosticV1, validateMessageIdempotencyRecordV1, validateOutboundMessageAttemptStateV1, } from './state-records.js';
7
+ import { validateControlStreamChunkV1, validateControlStreamV1, } from './streams.js';
8
+ import { INFERENCE_CONTROL_LIMITS_V1, assertDigest, assertExactKeys, assertOneOf, assertSafeInteger, assertStrictJsonValue, assertString, deepFreeze, isInferenceControlReasonCodeV1, } from './validation.js';
9
+ const STATE_KEYS = [
10
+ 'schemaVersion',
11
+ 'stateId',
12
+ 'tenantId',
13
+ 'stateGeneration',
14
+ 'logicalTimeHighWaterMs',
15
+ 'limits',
16
+ 'policies',
17
+ 'policyHeads',
18
+ 'dependencyBindings',
19
+ 'runs',
20
+ 'contextEntries',
21
+ 'assessmentRequests',
22
+ 'assessments',
23
+ 'streams',
24
+ 'streamChunks',
25
+ 'grants',
26
+ 'actionIdempotency',
27
+ 'messageAttempts',
28
+ 'messageIdempotency',
29
+ 'diagnostics',
30
+ 'traceDigest',
31
+ 'encodedBytes',
32
+ ];
33
+ const STATE_ARRAY_KEYS = [
34
+ 'policies',
35
+ 'policyHeads',
36
+ 'dependencyBindings',
37
+ 'runs',
38
+ 'contextEntries',
39
+ 'assessmentRequests',
40
+ 'assessments',
41
+ 'streams',
42
+ 'streamChunks',
43
+ 'grants',
44
+ 'actionIdempotency',
45
+ 'messageAttempts',
46
+ 'messageIdempotency',
47
+ 'diagnostics',
48
+ ];
49
+ export function createInferenceControlStateV1(input) {
50
+ assertString(input.stateId, 'stateId');
51
+ assertString(input.tenantId, 'tenantId');
52
+ const logicalTimeHighWaterMs = input.logicalTimeMs ?? 0;
53
+ assertSafeInteger(logicalTimeHighWaterMs, 'logicalTimeMs');
54
+ return finalizeState({
55
+ schemaVersion: 1,
56
+ stateId: input.stateId,
57
+ tenantId: input.tenantId,
58
+ stateGeneration: 1,
59
+ logicalTimeHighWaterMs,
60
+ limits: INFERENCE_CONTROL_LIMITS_V1,
61
+ policies: [],
62
+ policyHeads: [],
63
+ dependencyBindings: [],
64
+ runs: [],
65
+ contextEntries: [],
66
+ assessmentRequests: [],
67
+ assessments: [],
68
+ streams: [],
69
+ streamChunks: [],
70
+ grants: [],
71
+ actionIdempotency: [],
72
+ messageAttempts: [],
73
+ messageIdempotency: [],
74
+ diagnostics: [],
75
+ });
76
+ }
77
+ export function restoreInferenceControlStateV1(value, rebinder) {
78
+ assertStrictJsonValue(value);
79
+ assertExactKeys(value, STATE_KEYS, 'state');
80
+ const state = value;
81
+ validateEncodedState(state, rebinder);
82
+ const restored = normalizeState(state);
83
+ let transformed = false;
84
+ const grants = restored.grants.map((grant) => {
85
+ if (grant.status !== 'reserved')
86
+ return grant;
87
+ transformed = true;
88
+ return {
89
+ ...grant,
90
+ stateGeneration: grant.stateGeneration + 1,
91
+ status: 'indeterminate',
92
+ };
93
+ });
94
+ const messageAttempts = restored.messageAttempts.map((attempt) => {
95
+ if (attempt.status !== 'reserved')
96
+ return attempt;
97
+ transformed = true;
98
+ return {
99
+ ...attempt,
100
+ generation: attempt.generation + 1,
101
+ status: 'indeterminate',
102
+ };
103
+ });
104
+ return transformed
105
+ ? finalizeState({
106
+ ...stateMaterial(restored),
107
+ grants,
108
+ messageAttempts,
109
+ actionIdempotency: restored.actionIdempotency.map((record) => ({
110
+ ...record,
111
+ retainedOutcome: grants.find((grant) => grant.grantId === record.grantId)?.status ??
112
+ record.retainedOutcome,
113
+ })),
114
+ messageIdempotency: restored.messageIdempotency.map((record) => ({
115
+ ...record,
116
+ retainedOutcome: messageAttempts.find((attempt) => attempt.messageAttemptId === record.messageAttemptId)?.status ?? record.retainedOutcome,
117
+ })),
118
+ stateGeneration: restored.stateGeneration + 1,
119
+ })
120
+ : restored;
121
+ }
122
+ export function createInferenceControlSnapshotV1(input) {
123
+ assertString(input.snapshotId, 'snapshotId');
124
+ assertSafeInteger(input.createdAtLogicalMs, 'createdAtLogicalMs');
125
+ validateEncodedState(input.state, { rebind: () => true });
126
+ const state = normalizeState(input.state);
127
+ return deepFreeze({
128
+ schemaVersion: 1,
129
+ snapshotId: input.snapshotId,
130
+ createdAtLogicalMs: input.createdAtLogicalMs,
131
+ state,
132
+ stateDigest: state.traceDigest,
133
+ });
134
+ }
135
+ export function restoreInferenceControlSnapshotV1(value, rebinder) {
136
+ assertStrictJsonValue(value);
137
+ assertExactKeys(value, [
138
+ 'schemaVersion',
139
+ 'snapshotId',
140
+ 'createdAtLogicalMs',
141
+ 'state',
142
+ 'stateDigest',
143
+ ], 'snapshot');
144
+ const snapshot = value;
145
+ if (snapshot.schemaVersion !== 1)
146
+ throw new TypeError('Unsupported snapshot schema');
147
+ assertString(snapshot.snapshotId, 'snapshotId');
148
+ assertSafeInteger(snapshot.createdAtLogicalMs, 'createdAtLogicalMs');
149
+ assertDigest(snapshot.stateDigest, 'stateDigest');
150
+ if (snapshot.stateDigest !== snapshot.state.traceDigest)
151
+ throw new TypeError('state_conflict');
152
+ const state = restoreInferenceControlStateV1(snapshot.state, rebinder);
153
+ return deepFreeze({ ...snapshot, state, stateDigest: state.traceDigest });
154
+ }
155
+ export function createRedactedInferenceControlEvidenceV1(state) {
156
+ validateEncodedState(state, { rebind: () => true });
157
+ return deepFreeze({
158
+ schemaVersion: 1,
159
+ stateId: state.stateId,
160
+ tenantId: state.tenantId,
161
+ stateGeneration: state.stateGeneration,
162
+ logicalTimeHighWaterMs: state.logicalTimeHighWaterMs,
163
+ traceDigest: state.traceDigest,
164
+ counts: {
165
+ policies: state.policies.length,
166
+ runs: state.runs.length,
167
+ contextEntries: state.contextEntries.length,
168
+ assessments: state.assessments.length,
169
+ streams: state.streams.length,
170
+ grants: state.grants.length,
171
+ messageAttempts: state.messageAttempts.length,
172
+ },
173
+ diagnostics: state.diagnostics.map((diagnostic) => ({ ...diagnostic })),
174
+ restorable: false,
175
+ });
176
+ }
177
+ export function finalizeInferenceControlStateV1(material) {
178
+ return finalizeState(material);
179
+ }
180
+ function validateEncodedState(state, rebinder) {
181
+ validateStateTopology(state, rebinder);
182
+ const material = stateMaterial(state);
183
+ const expectedDigest = digestControlJsonV1('state', material);
184
+ const expectedBytes = utf8ByteLength(canonicalizeControlJsonV1(material));
185
+ if (state.traceDigest !== expectedDigest ||
186
+ state.encodedBytes !== expectedBytes)
187
+ throw new TypeError('state_conflict');
188
+ }
189
+ function validateStateTopology(state, rebinder) {
190
+ if (state.schemaVersion !== 1)
191
+ throw new TypeError('Unsupported state schema');
192
+ assertString(state.stateId, 'stateId');
193
+ assertString(state.tenantId, 'tenantId');
194
+ assertSafeInteger(state.stateGeneration, 'stateGeneration', 1);
195
+ assertSafeInteger(state.logicalTimeHighWaterMs, 'logicalTimeHighWaterMs');
196
+ assertSafeInteger(state.encodedBytes, 'encodedBytes');
197
+ assertDigest(state.traceDigest, 'traceDigest');
198
+ assertExactKeys(state.limits, Object.keys(INFERENCE_CONTROL_LIMITS_V1), 'state limits');
199
+ for (const key of Object.keys(INFERENCE_CONTROL_LIMITS_V1)) {
200
+ assertSafeInteger(state.limits[key], `limits.${key}`, 1);
201
+ if (state.limits[key] > INFERENCE_CONTROL_LIMITS_V1[key])
202
+ throw new TypeError('state_capacity_exceeded');
203
+ }
204
+ for (const key of STATE_ARRAY_KEYS)
205
+ if (!Array.isArray(state[key]))
206
+ throw new TypeError(`${key} must be an array`);
207
+ assertSortedUnique(state.policies.map((record) => record.policyDigest), 'policy digest');
208
+ assertSortedUnique(state.policyHeads.map((head) => head.policyId), 'policy head');
209
+ assertSortedUnique(state.dependencyBindings.map((binding) => `${binding.kind}\u0000${binding.bindingId}\u0000${binding.bindingVersion}`), 'dependency binding');
210
+ for (const binding of state.dependencyBindings)
211
+ validateDependencyBindingRecordV1(binding);
212
+ assertSortedUnique(state.runs.map((run) => run.runId), 'run ID');
213
+ assertSortedUnique(state.contextEntries.map((entry) => entry.contextEntryId), 'context ID');
214
+ const policyDigests = new Set(state.policies.map((record) => record.policyDigest));
215
+ for (const record of state.policies) {
216
+ const rebuilt = createPolicyRecordV1(record.policy);
217
+ if (rebuilt.policyDigest !== record.policyDigest)
218
+ throw new TypeError('state_conflict');
219
+ if (record.policy.parentPolicyDigest !== null) {
220
+ const parent = state.policies.find((candidate) => candidate.policyDigest === record.policy.parentPolicyDigest);
221
+ if (!parent)
222
+ throw new TypeError('state_conflict');
223
+ assertPolicyNarrowingV1(parent.policy, record.policy);
224
+ }
225
+ }
226
+ for (const head of state.policyHeads) {
227
+ const policy = state.policies.find((record) => record.policyDigest === head.policyDigest);
228
+ if (head.schemaVersion !== 1 ||
229
+ !policy ||
230
+ policy.policy.policyId !== head.policyId ||
231
+ policy.policy.policyVersion !== head.policyVersion ||
232
+ state.policies.some((candidate) => candidate.policy.policyId === head.policyId &&
233
+ candidate.policy.policyVersion > head.policyVersion))
234
+ throw new TypeError('state_conflict');
235
+ }
236
+ const runIds = new Set(state.runs.map((run) => run.runId));
237
+ for (const run of state.runs) {
238
+ validateRunRecord(run);
239
+ const policy = state.policies.find((record) => record.policyDigest === run.policyDigest)?.policy;
240
+ const capabilityBinding = run.capabilityHandleId === null
241
+ ? undefined
242
+ : state.dependencyBindings.find((binding) => binding.kind === 'capability' &&
243
+ binding.bindingId === run.capabilityHandleId &&
244
+ binding.bindingDigest === run.capabilityDescriptorDigest);
245
+ const capabilityRequired = [
246
+ 'input_assessed',
247
+ 'executing',
248
+ 'buffering',
249
+ 'streaming',
250
+ 'output_assessed',
251
+ 'completed',
252
+ ].includes(run.phase);
253
+ if (run.tenantId !== state.tenantId ||
254
+ !policy ||
255
+ !policyDigests.has(run.policyDigest) ||
256
+ (run.phase === 'created' && run.capabilityHandleId !== null) ||
257
+ (run.capabilityHandleId !== null && !capabilityBinding) ||
258
+ (capabilityRequired && !capabilityBinding) ||
259
+ (capabilityRequired &&
260
+ !policy.allowedCapabilityBindings.some((binding) => binding.descriptorDigest === run.capabilityDescriptorDigest)) ||
261
+ run.deadlineAtLogicalMs - run.createdAtLogicalMs >
262
+ effectiveLimitsForRunState(state, run).maxRunDurationMs ||
263
+ run.dispositionCounts.revisions >
264
+ Math.min(policy.budgets.revisions, state.limits.maxRevisionsPerRun) ||
265
+ run.dispositionCounts.retries >
266
+ Math.min(policy.budgets.retries, state.limits.maxRetriesPerRun) ||
267
+ run.dispositionCounts.challenges >
268
+ Math.min(policy.budgets.challenges, state.limits.maxChallengesPerRun))
269
+ throw new TypeError('state_conflict');
270
+ }
271
+ for (const source of state.contextEntries) {
272
+ const entry = validateContextEntryV1(source);
273
+ if (!runIds.has(entry.runId) || entry.tenantId !== state.tenantId)
274
+ throw new TypeError('state_conflict');
275
+ }
276
+ for (const run of state.runs) {
277
+ const limits = effectiveLimitsForRunState(state, run);
278
+ const policy = state.policies.find((record) => record.policyDigest === run.policyDigest).policy;
279
+ const entries = state.contextEntries.filter((entry) => entry.runId === run.runId);
280
+ if (entries.length > limits.maxContextEntriesPerRun ||
281
+ entries.some((entry) => entry.encodedBytes > limits.maxContextEntryBytes ||
282
+ !policy.allowedContextZones.includes(entry.zone)) ||
283
+ entries.reduce((total, entry) => total + entry.encodedBytes, 0) >
284
+ limits.maxContextBytesPerRun)
285
+ throw new TypeError('state_capacity_exceeded');
286
+ }
287
+ assertSortedUnique(state.assessmentRequests.map((request) => request.assessmentRequestId), 'assessment request ID');
288
+ const requests = new Map(state.assessmentRequests.map((source) => {
289
+ const preliminary = validateAssessmentRequestV1(source, state.limits);
290
+ const run = state.runs.find((item) => item.runId === preliminary.runId);
291
+ const request = run
292
+ ? validateAssessmentRequestV1(source, effectiveLimitsForRunState(state, run))
293
+ : preliminary;
294
+ const policyRecord = run
295
+ ? state.policies.find((item) => item.policyDigest === run.policyDigest)
296
+ : undefined;
297
+ const assessorBinding = policyRecord?.policy.assessmentBindings.find((binding) => binding.checkpoint === request.checkpoint);
298
+ if (!run ||
299
+ !policyRecord ||
300
+ request.tenantId !== state.tenantId ||
301
+ request.policyId !== policyRecord.policy.policyId ||
302
+ request.policyVersion !== policyRecord.policy.policyVersion ||
303
+ request.requestGeneration > run.generation ||
304
+ (request.status === 'pending' &&
305
+ request.requestGeneration !== run.generation) ||
306
+ request.expiresAtLogicalMs > run.deadlineAtLogicalMs ||
307
+ !assessorBinding ||
308
+ request.assessorId !== assessorBinding.assessorId ||
309
+ request.assessorVersion !== assessorBinding.assessorVersion ||
310
+ request.contextEntryIds.some((id) => !state.contextEntries.some((entry) => entry.contextEntryId === id)))
311
+ throw new TypeError('state_conflict');
312
+ return [request.assessmentRequestId, request];
313
+ }));
314
+ assertSortedUnique(state.assessments.map((assessment) => assessment.assessmentId), 'assessment ID');
315
+ for (const source of state.assessments) {
316
+ const preliminary = validateInferenceAssessmentV1(source, state.limits);
317
+ const run = state.runs.find((item) => item.runId === preliminary.runId);
318
+ const assessment = run
319
+ ? validateInferenceAssessmentV1(source, effectiveLimitsForRunState(state, run))
320
+ : preliminary;
321
+ const request = requests.get(assessment.assessmentRequestId);
322
+ const policy = run
323
+ ? state.policies.find((record) => record.policyDigest === run.policyDigest)?.policy
324
+ : undefined;
325
+ const binding = policy?.assessmentBindings.find((candidate) => candidate.checkpoint === assessment.checkpoint);
326
+ if (!request ||
327
+ !binding ||
328
+ !assessmentIdentityMatches(request, assessment) ||
329
+ assessment.evidenceReferences.length >
330
+ binding.maximumEvidenceReferences ||
331
+ utf8ByteLength(canonicalizeControlJsonV1(assessment)) > binding.maximumResponseBytes)
332
+ throw new TypeError('state_conflict');
333
+ }
334
+ if (state.runs.some((run) => state.assessments.filter((assessment) => assessment.runId === run.runId)
335
+ .length >
336
+ effectiveLimitsForRunState(state, run).maxAssessmentsPerRun ||
337
+ state.assessmentRequests.filter((request) => request.runId === run.runId).length > effectiveLimitsForRunState(state, run).maxAssessmentsPerRun))
338
+ throw new TypeError('state_capacity_exceeded');
339
+ for (const request of state.assessmentRequests) {
340
+ const count = state.assessments.filter((assessment) => assessment.assessmentRequestId === request.assessmentRequestId).length;
341
+ if ((request.status === 'accepted' && count !== 1) ||
342
+ (request.status !== 'accepted' && count !== 0))
343
+ throw new TypeError('state_conflict');
344
+ }
345
+ assertSortedUnique(state.streams.map((stream) => stream.streamId), 'stream ID');
346
+ for (const source of state.streams) {
347
+ const stream = validateControlStreamV1(source);
348
+ const run = state.runs.find((item) => item.runId === stream.runId);
349
+ if (!run ||
350
+ stream.generation > run.generation ||
351
+ (stream.status === 'open' && stream.generation !== run.generation))
352
+ throw new TypeError('state_conflict');
353
+ }
354
+ let priorChunkKey;
355
+ for (const source of state.streamChunks) {
356
+ const preliminary = validateControlStreamChunkV1(source, state.limits);
357
+ const stream = state.streams.find((item) => item.streamId === preliminary.streamId);
358
+ const run = stream
359
+ ? state.runs.find((item) => item.runId === stream.runId)
360
+ : undefined;
361
+ const chunk = run
362
+ ? validateControlStreamChunkV1(source, effectiveLimitsForRunState(state, run))
363
+ : preliminary;
364
+ if (!stream || !run)
365
+ throw new TypeError('state_conflict');
366
+ const key = `${chunk.streamId}\u0000${String(chunk.generation).padStart(16, '0')}\u0000${String(chunk.sequence).padStart(16, '0')}`;
367
+ if (priorChunkKey !== undefined && priorChunkKey >= key)
368
+ throw new TypeError('Duplicate or unsorted stream chunk');
369
+ priorChunkKey = key;
370
+ }
371
+ for (const stream of state.streams) {
372
+ const run = state.runs.find((item) => item.runId === stream.runId);
373
+ const limits = effectiveLimitsForRunState(state, run);
374
+ const chunks = state.streamChunks.filter((chunk) => chunk.streamId === stream.streamId &&
375
+ chunk.generation === stream.generation);
376
+ if (chunks.length !== stream.nextSequence ||
377
+ chunks.some((chunk, index) => chunk.sequence !== index ||
378
+ chunk.fromByte !==
379
+ (index === 0 ? 0 : chunks[index - 1].throughByteExclusive)) ||
380
+ stream.receivedBytes !==
381
+ (chunks.length === 0
382
+ ? 0
383
+ : chunks[chunks.length - 1].throughByteExclusive) ||
384
+ stream.releasedThroughSequence >= stream.nextSequence ||
385
+ stream.receivedBytes > limits.maxBufferedOutputBytes ||
386
+ stream.receivedBytes - stream.releasedBytes >
387
+ (state.policies.find((record) => record.policyDigest === run.policyDigest).policy.mode === 'incremental'
388
+ ? limits.maxPendingWindowBytes
389
+ : limits.maxBufferedOutputBytes))
390
+ throw new TypeError('stream_sequence_invalid');
391
+ }
392
+ for (const run of state.runs) {
393
+ const limits = effectiveLimitsForRunState(state, run);
394
+ const runStreams = new Set(state.streams
395
+ .filter((stream) => stream.runId === run.runId)
396
+ .map((stream) => stream.streamId));
397
+ if (state.streamChunks.filter((chunk) => runStreams.has(chunk.streamId))
398
+ .length > limits.maxOutputChunksPerRun)
399
+ throw new TypeError('state_capacity_exceeded');
400
+ }
401
+ assertSortedUnique(state.grants.map((grant) => grant.grantId), 'grant ID');
402
+ for (const grant of state.grants) {
403
+ const run = state.runs.find((item) => item.runId === grant.runId);
404
+ validateActionGrantStateV1(grant, run ? effectiveLimitsForRunState(state, run) : state.limits);
405
+ const request = requests.get(grant.assessmentRequestId);
406
+ const assessment = state.assessments.find((item) => item.assessmentId === grant.assessmentId);
407
+ if (!run ||
408
+ !request ||
409
+ !assessment ||
410
+ request.checkpoint !== 'pre_tool' ||
411
+ assessment.disposition !== 'allow' ||
412
+ request.targetDigest !== grant.assessmentTargetDigest ||
413
+ canonicalizeControlJsonV1(request.scope) !==
414
+ canonicalizeControlJsonV1(grant.scope) ||
415
+ grant.scope.runId !== run.runId ||
416
+ grant.scope.tenantId !== run.tenantId ||
417
+ grant.expiresAtLogicalMs > run.deadlineAtLogicalMs ||
418
+ grant.expiresAtLogicalMs > assessment.expiresAtLogicalMs ||
419
+ (grant.scope.kind === 'coordinated' &&
420
+ grant.expiresAtLogicalMs > grant.scope.leaseExpiresAtLogicalMs))
421
+ throw new TypeError('state_conflict');
422
+ }
423
+ if (state.grants.length > state.limits.maxRetainedGrantRecords ||
424
+ state.grants.filter((grant) => ['issued', 'reserved'].includes(grant.status)).length > state.limits.maxActiveGrants)
425
+ throw new TypeError('state_capacity_exceeded');
426
+ for (const run of state.runs) {
427
+ const limits = effectiveLimitsForRunState(state, run);
428
+ const grants = state.grants.filter((grant) => grant.runId === run.runId);
429
+ if (grants.length > limits.maxRetainedGrantRecords ||
430
+ grants.filter((grant) => ['issued', 'reserved'].includes(grant.status))
431
+ .length > limits.maxActiveGrants)
432
+ throw new TypeError('state_capacity_exceeded');
433
+ }
434
+ assertSortedUnique(state.actionIdempotency.map((record) => `${record.scopeDigest}\u0000${record.idempotencyKey}`), 'action idempotency key');
435
+ for (const record of state.actionIdempotency) {
436
+ validateActionIdempotencyRecordV1(record);
437
+ const grant = state.grants.find((item) => item.grantId === record.grantId);
438
+ if (!grant ||
439
+ grant.scopeDigest !== record.scopeDigest ||
440
+ grant.actionDigest !== record.actionDigest ||
441
+ grant.status !== record.retainedOutcome)
442
+ throw new TypeError('state_conflict');
443
+ }
444
+ for (const grant of state.grants)
445
+ if (state.actionIdempotency.filter((record) => record.grantId === grant.grantId).length !== 1)
446
+ throw new TypeError('state_conflict');
447
+ assertSortedUnique(state.messageAttempts.map((attempt) => attempt.messageAttemptId), 'message attempt ID');
448
+ for (const attempt of state.messageAttempts) {
449
+ validateOutboundMessageAttemptStateV1(attempt);
450
+ const run = state.runs.find((item) => item.runId === attempt.runId);
451
+ const request = requests.get(attempt.assessmentRequestId);
452
+ const assessment = state.assessments.find((item) => item.assessmentId === attempt.assessmentId);
453
+ if (!run ||
454
+ !request ||
455
+ !assessment ||
456
+ request.checkpoint !== 'pre_message' ||
457
+ assessment.disposition !== 'allow' ||
458
+ request.targetDigest !== attempt.messageDigest ||
459
+ request.scope === null ||
460
+ digestControlJsonV1('scope', request.scope) !== attempt.scopeDigest ||
461
+ attempt.expiresAtLogicalMs > run.deadlineAtLogicalMs ||
462
+ attempt.expiresAtLogicalMs > assessment.expiresAtLogicalMs ||
463
+ (request.scope.kind === 'coordinated' &&
464
+ attempt.expiresAtLogicalMs > request.scope.leaseExpiresAtLogicalMs) ||
465
+ attempt.expiresAtLogicalMs - attempt.preparedAtLogicalMs >
466
+ effectiveLimitsForRunState(state, run).maxMessagePermitTtlMs)
467
+ throw new TypeError('state_conflict');
468
+ }
469
+ if (state.messageAttempts.length > state.limits.maxRetainedMessageAttempts ||
470
+ state.messageAttempts.filter((attempt) => ['prepared', 'reserved'].includes(attempt.status)).length > state.limits.maxActiveMessageAttempts)
471
+ throw new TypeError('state_capacity_exceeded');
472
+ for (const run of state.runs) {
473
+ const limits = effectiveLimitsForRunState(state, run);
474
+ const attempts = state.messageAttempts.filter((attempt) => attempt.runId === run.runId);
475
+ if (attempts.length > limits.maxRetainedMessageAttempts ||
476
+ attempts.filter((attempt) => ['prepared', 'reserved'].includes(attempt.status)).length > limits.maxActiveMessageAttempts)
477
+ throw new TypeError('state_capacity_exceeded');
478
+ }
479
+ assertSortedUnique(state.messageIdempotency.map((record) => `${record.scopeDigest}\u0000${record.idempotencyKey}`), 'message idempotency key');
480
+ for (const record of state.messageIdempotency) {
481
+ validateMessageIdempotencyRecordV1(record);
482
+ const attempt = state.messageAttempts.find((item) => item.messageAttemptId === record.messageAttemptId);
483
+ if (!attempt ||
484
+ attempt.scopeDigest !== record.scopeDigest ||
485
+ attempt.messageDigest !== record.messageDigest ||
486
+ attempt.status !== record.retainedOutcome)
487
+ throw new TypeError('state_conflict');
488
+ }
489
+ for (const attempt of state.messageAttempts)
490
+ if (state.messageIdempotency.filter((record) => record.messageAttemptId === attempt.messageAttemptId).length !== 1)
491
+ throw new TypeError('state_conflict');
492
+ if (state.diagnostics.length > state.limits.maxDiagnostics)
493
+ throw new TypeError('state_capacity_exceeded');
494
+ for (const diagnostic of state.diagnostics)
495
+ validateDiagnosticV1(diagnostic);
496
+ for (const binding of state.dependencyBindings) {
497
+ validateDependencyBindingRecordV1(binding);
498
+ if (!rebinder || !rebinder.rebind(binding))
499
+ throw new TypeError('dependency_rebind_failed');
500
+ }
501
+ for (const run of state.runs)
502
+ validateRunRelations(state, run);
503
+ }
504
+ function validateRunRecord(run) {
505
+ assertExactKeys(run, [
506
+ 'schemaVersion',
507
+ 'runId',
508
+ 'tenantId',
509
+ 'policyDigest',
510
+ 'capabilityDescriptorDigest',
511
+ 'capabilityHandleId',
512
+ 'scope',
513
+ 'generation',
514
+ 'phase',
515
+ 'createdAtLogicalMs',
516
+ 'deadlineAtLogicalMs',
517
+ 'dispositionCounts',
518
+ 'contextEntryIds',
519
+ 'assessmentRequestIds',
520
+ 'assessmentIds',
521
+ 'streamIds',
522
+ 'grantIds',
523
+ 'messageAttemptIds',
524
+ 'outputDigest',
525
+ 'releasedBytes',
526
+ 'terminalReasonCode',
527
+ ], 'run');
528
+ if (run.schemaVersion !== 1)
529
+ throw new TypeError('state_conflict');
530
+ assertString(run.runId, 'runId');
531
+ assertString(run.tenantId, 'tenantId');
532
+ assertDigest(run.policyDigest, 'policyDigest');
533
+ assertDigest(run.capabilityDescriptorDigest, 'capabilityDescriptorDigest');
534
+ if (run.capabilityHandleId !== null)
535
+ assertString(run.capabilityHandleId, 'capabilityHandleId');
536
+ if (run.scope !== null)
537
+ validateControlScopeV1(run.scope);
538
+ assertSafeInteger(run.generation, 'run.generation', 1);
539
+ assertOneOf(run.phase, [
540
+ 'created',
541
+ 'input_assessed',
542
+ 'executing',
543
+ 'buffering',
544
+ 'streaming',
545
+ 'output_assessed',
546
+ 'completed',
547
+ 'denied',
548
+ 'abstained',
549
+ 'escalated',
550
+ 'cancelled',
551
+ 'failed',
552
+ ], 'run phase');
553
+ assertSafeInteger(run.createdAtLogicalMs, 'createdAtLogicalMs');
554
+ assertSafeInteger(run.deadlineAtLogicalMs, 'deadlineAtLogicalMs');
555
+ if (run.deadlineAtLogicalMs <= run.createdAtLogicalMs)
556
+ throw new TypeError('state_conflict');
557
+ assertExactKeys(run.dispositionCounts, ['revisions', 'retries', 'challenges'], 'disposition counts');
558
+ for (const value of Object.values(run.dispositionCounts))
559
+ assertSafeInteger(value, 'disposition count');
560
+ assertSafeInteger(run.releasedBytes, 'releasedBytes');
561
+ if (run.outputDigest !== null)
562
+ assertDigest(run.outputDigest, 'outputDigest');
563
+ if (run.terminalReasonCode !== null &&
564
+ !isInferenceControlReasonCodeV1(run.terminalReasonCode))
565
+ throw new TypeError('state_conflict');
566
+ for (const [label, values] of [
567
+ ['contextEntryIds', run.contextEntryIds],
568
+ ['assessmentRequestIds', run.assessmentRequestIds],
569
+ ['assessmentIds', run.assessmentIds],
570
+ ['streamIds', run.streamIds],
571
+ ['grantIds', run.grantIds],
572
+ ['messageAttemptIds', run.messageAttemptIds],
573
+ ]) {
574
+ if (!Array.isArray(values) || new Set(values).size !== values.length)
575
+ throw new TypeError(`${label} must be ordered unique`);
576
+ for (const value of values)
577
+ assertString(value, label);
578
+ }
579
+ }
580
+ function validateRunRelations(state, run) {
581
+ const relations = [
582
+ [
583
+ run.contextEntryIds,
584
+ state.contextEntries
585
+ .filter((item) => item.runId === run.runId)
586
+ .map((item) => item.contextEntryId),
587
+ ],
588
+ [
589
+ run.assessmentRequestIds,
590
+ state.assessmentRequests
591
+ .filter((item) => item.runId === run.runId)
592
+ .map((item) => item.assessmentRequestId),
593
+ ],
594
+ [
595
+ run.assessmentIds,
596
+ state.assessments
597
+ .filter((item) => item.runId === run.runId)
598
+ .map((item) => item.assessmentId),
599
+ ],
600
+ [
601
+ run.streamIds,
602
+ state.streams
603
+ .filter((item) => item.runId === run.runId)
604
+ .map((item) => item.streamId),
605
+ ],
606
+ [
607
+ run.grantIds,
608
+ state.grants
609
+ .filter((item) => item.runId === run.runId)
610
+ .map((item) => item.grantId),
611
+ ],
612
+ [
613
+ run.messageAttemptIds,
614
+ state.messageAttempts
615
+ .filter((item) => item.runId === run.runId)
616
+ .map((item) => item.messageAttemptId),
617
+ ],
618
+ ];
619
+ for (const [declared, actual] of relations) {
620
+ if (declared.length !== actual.length ||
621
+ declared.some((id) => !actual.includes(id)))
622
+ throw new TypeError('state_conflict');
623
+ }
624
+ }
625
+ function effectiveLimitsForRunState(state, run) {
626
+ const policy = state.policies.find((record) => record.policyDigest === run.policyDigest)?.policy;
627
+ if (!policy)
628
+ throw new TypeError('state_conflict');
629
+ const limits = {};
630
+ for (const key of Object.keys(state.limits))
631
+ limits[key] = Math.min(state.limits[key], policy.limits[key]);
632
+ limits.maxRunDurationMs = Math.min(limits.maxRunDurationMs, policy.maximumRunDurationMs);
633
+ limits.maxAssessmentTtlMs = Math.min(limits.maxAssessmentTtlMs, policy.maximumAssessmentTtlMs);
634
+ limits.maxGrantTtlMs = Math.min(limits.maxGrantTtlMs, policy.maximumGrantTtlMs);
635
+ limits.maxMessagePermitTtlMs = Math.min(limits.maxMessagePermitTtlMs, policy.maximumMessagePermitTtlMs);
636
+ return limits;
637
+ }
638
+ function assessmentIdentityMatches(request, assessment) {
639
+ const fields = [
640
+ 'assessmentRequestId',
641
+ 'requestGeneration',
642
+ 'runId',
643
+ 'tenantId',
644
+ 'policyId',
645
+ 'policyVersion',
646
+ 'checkpoint',
647
+ 'assessorId',
648
+ 'assessorVersion',
649
+ 'targetKind',
650
+ 'targetDigest',
651
+ 'zoneDigest',
652
+ 'provenanceDigest',
653
+ ];
654
+ return (fields.every((field) => request[field] === assessment[field]) &&
655
+ canonicalizeControlJsonV1(request.scope) ===
656
+ canonicalizeControlJsonV1(assessment.scope));
657
+ }
658
+ function stateMaterial(state) {
659
+ const { traceDigest: _traceDigest, encodedBytes: _encodedBytes, ...material } = state;
660
+ return material;
661
+ }
662
+ function finalizeState(material) {
663
+ const normalized = normalizeControlJson(material);
664
+ const json = normalized;
665
+ const traceDigest = digestControlJsonV1('state', json);
666
+ const encodedBytes = utf8ByteLength(canonicalizeControlJsonV1(json));
667
+ if (encodedBytes > normalized.limits.maxStateBytes)
668
+ throw new TypeError('state_capacity_exceeded');
669
+ return deepFreeze({ ...normalized, traceDigest, encodedBytes });
670
+ }
671
+ function normalizeState(state) {
672
+ const normalized = normalizeControlJson(state);
673
+ return deepFreeze({ ...normalized });
674
+ }
675
+ function normalizeControlJson(value) {
676
+ if (value === null || typeof value !== 'object')
677
+ return value;
678
+ if (Array.isArray(value))
679
+ return value.map(normalizeControlJson);
680
+ const result = Object.create(null);
681
+ for (const key of Object.keys(value).sort())
682
+ result[key] = normalizeControlJson(value[key]);
683
+ return result;
684
+ }
685
+ function assertSortedUnique(values, label) {
686
+ if (values.some((value, index) => index > 0 && values[index - 1] >= value))
687
+ throw new TypeError(`Duplicate or unsorted ${label}`);
688
+ }
689
+ //# sourceMappingURL=state.js.map