@agentplat/trust 0.3.0-alpha.4

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 (63) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +14 -0
  3. package/dist/canonical.d.ts +20 -0
  4. package/dist/canonical.d.ts.map +1 -0
  5. package/dist/canonical.js +173 -0
  6. package/dist/canonical.js.map +1 -0
  7. package/dist/causal.d.ts +7 -0
  8. package/dist/causal.d.ts.map +1 -0
  9. package/dist/causal.js +155 -0
  10. package/dist/causal.js.map +1 -0
  11. package/dist/eligibility.d.ts +8 -0
  12. package/dist/eligibility.d.ts.map +1 -0
  13. package/dist/eligibility.js +325 -0
  14. package/dist/eligibility.js.map +1 -0
  15. package/dist/evidence.d.ts +20 -0
  16. package/dist/evidence.d.ts.map +1 -0
  17. package/dist/evidence.js +424 -0
  18. package/dist/evidence.js.map +1 -0
  19. package/dist/fusion.d.ts +8 -0
  20. package/dist/fusion.d.ts.map +1 -0
  21. package/dist/fusion.js +1278 -0
  22. package/dist/fusion.js.map +1 -0
  23. package/dist/index.d.ts +13 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +13 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lifecycle.d.ts +41 -0
  28. package/dist/lifecycle.d.ts.map +1 -0
  29. package/dist/lifecycle.js +1369 -0
  30. package/dist/lifecycle.js.map +1 -0
  31. package/dist/mesh-records.d.ts +36 -0
  32. package/dist/mesh-records.d.ts.map +1 -0
  33. package/dist/mesh-records.js +133 -0
  34. package/dist/mesh-records.js.map +1 -0
  35. package/dist/policy.d.ts +9 -0
  36. package/dist/policy.d.ts.map +1 -0
  37. package/dist/policy.js +598 -0
  38. package/dist/policy.js.map +1 -0
  39. package/dist/profile.d.ts +21 -0
  40. package/dist/profile.d.ts.map +1 -0
  41. package/dist/profile.js +253 -0
  42. package/dist/profile.js.map +1 -0
  43. package/dist/quarantine.d.ts +53 -0
  44. package/dist/quarantine.d.ts.map +1 -0
  45. package/dist/quarantine.js +742 -0
  46. package/dist/quarantine.js.map +1 -0
  47. package/dist/sha256.d.ts +2 -0
  48. package/dist/sha256.d.ts.map +1 -0
  49. package/dist/sha256.js +66 -0
  50. package/dist/sha256.js.map +1 -0
  51. package/dist/state.d.ts +18 -0
  52. package/dist/state.d.ts.map +1 -0
  53. package/dist/state.js +1015 -0
  54. package/dist/state.js.map +1 -0
  55. package/dist/types.d.ts +862 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +60 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/validation.d.ts +18 -0
  60. package/dist/validation.d.ts.map +1 -0
  61. package/dist/validation.js +223 -0
  62. package/dist/validation.js.map +1 -0
  63. package/package.json +43 -0
package/dist/state.js ADDED
@@ -0,0 +1,1015 @@
1
+ import { canonicalTrustJsonBytesV1, canonicalizeTrustJsonV1, deepFreeze, digestTrustJsonV1, TrustValidationError, } from "./canonical.js";
2
+ import { dependencyBindingHeadV1, digestEvidenceFusionPolicyV1, validateEvidenceFusionPolicyV1, validateEvidenceTrustDependencyBindingV1, } from "./policy.js";
3
+ import { validateEvidenceCausalAuthorizationV1 } from "./causal.js";
4
+ import { deriveApplicableBindingDigests, evaluateEvidenceFusionV1, validateEvidenceFusionDecisionV1, } from "./fusion.js";
5
+ import { assertExactKeys, assertIdentifier, assertSafeInteger, assertTrustDigest, validateReasonCodeV1, } from "./validation.js";
6
+ import { validateEvidenceContentResolutionInvalidationV1, validateEvidenceContentResolutionV1, validateEvidenceRecordStateV1, projectEvidenceLifecycleV1, resolveEvidenceCausalAuthorityVerifierV1, resolveVerifiedMeshAdmissionVerifierV1, } from "./lifecycle.js";
7
+ import { digestScopeV1, digestSubjectV1 } from "./evidence.js";
8
+ import { createTrustProfileV1, digestTrustProfileKeyV1, trustProfileHeadV1, validateTrustProfileHeadV1, validateTrustProfileV1, } from "./profile.js";
9
+ import { createActiveQuarantineRecordV1, createRecoveredQuarantineRecordV1, createReviewRequiredQuarantineRecordV1, deriveQuarantineActivationsV1, evaluateQuarantineRecoveryV1, quarantineHeadV1, validateQuarantineHeadV1, validateQuarantineRecordV1, validateQuarantineRecoveryDecisionV1, } from "./quarantine.js";
10
+ const stateCanonicalLimits = (maximumBytes) => ({
11
+ maximumBytes,
12
+ maximumDepth: 64,
13
+ maximumNodes: 100_000,
14
+ maximumKeysPerObject: 256,
15
+ maximumItemsPerArray: 100_000,
16
+ });
17
+ const compareUnicode = (left, right) => left < right ? -1 : left > right ? 1 : 0;
18
+ function assertCanonicalOrder(values, label) {
19
+ if (values.some((value, index) => index > 0 && compareUnicode(values[index - 1], value) >= 0))
20
+ throw new TrustValidationError(`${label} must be canonically ordered and unique`);
21
+ }
22
+ export const EVIDENCE_TRUST_LIMITS_V1 = Object.freeze({
23
+ maximumPolicies: 16,
24
+ maximumDimensionsPerPolicy: 16,
25
+ maximumCriteriaPerPolicy: 64,
26
+ maximumSourceBindingsPerPolicy: 256,
27
+ maximumDependencyGroupsPerPolicy: 64,
28
+ maximumClaims: 4096,
29
+ maximumAttestations: 16384,
30
+ maximumChallenges: 4096,
31
+ maximumChallengesPerSourceScope: 64,
32
+ maximumPendingChallengesPerSourceScope: 16,
33
+ maximumRetractions: 4096,
34
+ maximumContentResolutions: 4096,
35
+ maximumContentInvalidations: 4096,
36
+ maximumCausalAuthorizations: 4096,
37
+ maximumDependencyBindingVersions: 256,
38
+ maximumPendingRecords: 1024,
39
+ maximumPendingAgeMs: 86_400_000,
40
+ maximumBasisReferencesPerRecord: 32,
41
+ maximumRelationshipDepth: 16,
42
+ maximumConsideredRecordsPerFusion: 1024,
43
+ maximumRetainedFusionDecisions: 4096,
44
+ maximumProfileHeads: 2048,
45
+ maximumProfileRevisionsPerHead: 32,
46
+ maximumQuarantineHeads: 2048,
47
+ maximumQuarantineRevisionsPerHead: 32,
48
+ maximumRecoveryDecisions: 4096,
49
+ maximumDiagnostics: 1024,
50
+ maximumRecordCanonicalBytes: 65_536,
51
+ maximumContentReferenceBytes: 4096,
52
+ maximumInlineSummaryBytes: 4096,
53
+ maximumStateCanonicalBytes: 67_108_864,
54
+ maximumEvidenceAgeMs: 2_592_000_000,
55
+ maximumReviewIntervalMs: 604_800_000,
56
+ });
57
+ const limitKeys = Object.keys(EVIDENCE_TRUST_LIMITS_V1);
58
+ export function validateEvidenceTrustLimitsV1(value) {
59
+ assertExactKeys(value, limitKeys, "limits");
60
+ const limits = value;
61
+ for (const key of limitKeys) {
62
+ assertSafeInteger(limits[key], key, 1);
63
+ if (limits[key] > EVIDENCE_TRUST_LIMITS_V1[key])
64
+ throw new TrustValidationError(`${key} exceeds V1 ceiling`);
65
+ }
66
+ return deepFreeze(structuredClone(limits));
67
+ }
68
+ const stateKeys = [
69
+ "schemaVersion",
70
+ "stateId",
71
+ "limits",
72
+ "logicalTimeHighWaterMs",
73
+ "policies",
74
+ "policyHeads",
75
+ "sourceBindings",
76
+ "dependencyBindings",
77
+ "dependencyBindingHeads",
78
+ "causalAuthorizations",
79
+ "records",
80
+ "contentResolutions",
81
+ "contentInvalidations",
82
+ "pendingRecords",
83
+ "fusionDecisions",
84
+ "profiles",
85
+ "profileHeads",
86
+ "quarantines",
87
+ "quarantineHeads",
88
+ "recoveryDecisions",
89
+ "diagnostics",
90
+ "traceDigest",
91
+ "encodedBytes",
92
+ ];
93
+ export function createEvidenceTrustStateV1(input) {
94
+ assertIdentifier(input.stateId, "stateId");
95
+ const limits = validateEvidenceTrustLimitsV1(input.limits ?? EVIDENCE_TRUST_LIMITS_V1);
96
+ const state = {
97
+ schemaVersion: 1,
98
+ stateId: input.stateId,
99
+ limits,
100
+ logicalTimeHighWaterMs: 0,
101
+ policies: [],
102
+ policyHeads: [],
103
+ sourceBindings: [],
104
+ dependencyBindings: [],
105
+ dependencyBindingHeads: [],
106
+ causalAuthorizations: [],
107
+ records: [],
108
+ contentResolutions: [],
109
+ contentInvalidations: [],
110
+ pendingRecords: [],
111
+ fusionDecisions: [],
112
+ profiles: [],
113
+ profileHeads: [],
114
+ quarantines: [],
115
+ quarantineHeads: [],
116
+ recoveryDecisions: [],
117
+ diagnostics: [],
118
+ traceDigest: digestTrustJsonV1("trace", []),
119
+ encodedBytes: 0,
120
+ };
121
+ const encodedBytes = canonicalTrustJsonBytesV1(state, stateCanonicalLimits(limits.maximumStateCanonicalBytes)).byteLength;
122
+ return validateEvidenceTrustStateV1({ ...state, encodedBytes });
123
+ }
124
+ export function validateEvidenceTrustStateV1(value) {
125
+ assertExactKeys(value, stateKeys, "state");
126
+ const state = value;
127
+ if (state.schemaVersion !== 1)
128
+ throw new TrustValidationError("state schema is invalid");
129
+ assertIdentifier(state.stateId, "stateId");
130
+ const limits = validateEvidenceTrustLimitsV1(state.limits);
131
+ assertSafeInteger(state.logicalTimeHighWaterMs, "logicalTimeHighWaterMs");
132
+ const logicalTimeHighWaterMs = state.logicalTimeHighWaterMs;
133
+ assertTrustDigest(state.traceDigest, "traceDigest");
134
+ assertSafeInteger(state.encodedBytes, "encodedBytes");
135
+ for (const key of ["sourceBindings"]) {
136
+ if (!Array.isArray(state[key]) || state[key].length !== 0)
137
+ throw new TrustValidationError(`${key} is unavailable before its increment`);
138
+ }
139
+ if (!Array.isArray(state.fusionDecisions))
140
+ throw new TrustValidationError("fusion decisions are invalid");
141
+ const fusionDecisions = state.fusionDecisions.map(validateEvidenceFusionDecisionV1);
142
+ if (fusionDecisions.length > limits.maximumRetainedFusionDecisions)
143
+ throw new TrustValidationError("fusion decision capacity exceeded");
144
+ assertCanonicalOrder(fusionDecisions.map((decision) => decision.fusionDecisionDigest), "fusion decisions");
145
+ if (new Set(fusionDecisions.map((decision) => decision.fusionDecisionId))
146
+ .size !== fusionDecisions.length)
147
+ throw new TrustValidationError("fusion decision IDs must be unique");
148
+ if (!Array.isArray(state.policies) || !Array.isArray(state.policyHeads))
149
+ throw new TrustValidationError("policy state arrays are invalid");
150
+ const policies = state.policies.map((policy) => validateEvidenceFusionPolicyV1(policy, limits));
151
+ for (const decision of fusionDecisions) {
152
+ if (!policies.some((policy) => policy.policyId === decision.policyId &&
153
+ policy.policyVersion === decision.policyVersion &&
154
+ digestEvidenceFusionPolicyV1(policy) === decision.policyDigest))
155
+ throw new TrustValidationError("fusion decision policy is unavailable");
156
+ }
157
+ if (policies.length > limits.maximumPolicies)
158
+ throw new TrustValidationError("policy capacity exceeded");
159
+ assertCanonicalOrder(policies.map((policy) => `${policy.policyId}\u0000${String(policy.policyVersion).padStart(16, "0")}\u0000${digestEvidenceFusionPolicyV1(policy)}`), "policies");
160
+ const policyByDigest = new Map(policies.map((policy) => [digestEvidenceFusionPolicyV1(policy), policy]));
161
+ const policyVersions = new Set();
162
+ for (const policy of policies) {
163
+ const digest = digestEvidenceFusionPolicyV1(policy);
164
+ const versionKey = `${policy.policyId}\u0000${policy.policyVersion}`;
165
+ if (policyVersions.has(versionKey))
166
+ throw new TrustValidationError("policy version is duplicated");
167
+ policyVersions.add(versionKey);
168
+ if ((policy.policyVersion === 1) !== (policy.parentPolicyDigest === null))
169
+ throw new TrustValidationError("policy lineage is invalid");
170
+ if (policy.parentPolicyDigest !== null) {
171
+ const parent = policyByDigest.get(policy.parentPolicyDigest);
172
+ if (!parent ||
173
+ parent.policyId !== policy.policyId ||
174
+ parent.policyVersion + 1 !== policy.policyVersion ||
175
+ digest === policy.parentPolicyDigest)
176
+ throw new TrustValidationError("policy lineage is invalid");
177
+ }
178
+ }
179
+ const policyHeads = state.policyHeads.map((head) => {
180
+ assertExactKeys(head, ["policyId", "policyVersion", "policyDigest"], "policy head");
181
+ const value = head;
182
+ assertIdentifier(value.policyId, "policyHead.policyId");
183
+ assertSafeInteger(value.policyVersion, "policyHead.policyVersion", 1);
184
+ assertTrustDigest(value.policyDigest, "policyHead.policyDigest");
185
+ return value;
186
+ });
187
+ assertCanonicalOrder(policyHeads.map((head) => head.policyId), "policy heads");
188
+ const expectedPolicyHeads = policies
189
+ .reduce((heads, policy) => {
190
+ const prior = heads.get(policy.policyId);
191
+ if (!prior || prior.policyVersion < policy.policyVersion)
192
+ heads.set(policy.policyId, {
193
+ policyId: policy.policyId,
194
+ policyVersion: policy.policyVersion,
195
+ policyDigest: digestEvidenceFusionPolicyV1(policy),
196
+ });
197
+ return heads;
198
+ }, new Map())
199
+ .values();
200
+ const canonicalPolicyHeads = [...expectedPolicyHeads].sort((a, b) => compareUnicode(a.policyId, b.policyId));
201
+ if (policyHeads.length !== canonicalPolicyHeads.length ||
202
+ policyHeads.some((head, index) => head.policyId !== canonicalPolicyHeads[index].policyId ||
203
+ head.policyVersion !== canonicalPolicyHeads[index].policyVersion ||
204
+ head.policyDigest !== canonicalPolicyHeads[index].policyDigest))
205
+ throw new TrustValidationError("policy heads do not match policy history");
206
+ if (!Array.isArray(state.dependencyBindings) ||
207
+ !Array.isArray(state.dependencyBindingHeads))
208
+ throw new TrustValidationError("dependency binding state arrays are invalid");
209
+ const dependencyBindings = state.dependencyBindings.map(validateEvidenceTrustDependencyBindingV1);
210
+ if (dependencyBindings.length > limits.maximumDependencyBindingVersions)
211
+ throw new TrustValidationError("dependency binding capacity exceeded");
212
+ assertCanonicalOrder(dependencyBindings.map((binding) => `${binding.bindingKind}\u0000${binding.bindingName}\u0000${String(binding.bindingVersion).padStart(16, "0")}\u0000${binding.bindingDigest}`), "dependency bindings");
213
+ const policyDigests = new Set(policies.map(digestEvidenceFusionPolicyV1));
214
+ const bindingByDigest = new Map(dependencyBindings.map((binding) => [binding.bindingDigest, binding]));
215
+ const bindingVersions = new Set();
216
+ for (const binding of dependencyBindings) {
217
+ if (binding.policyDigest !== null &&
218
+ !policyDigests.has(binding.policyDigest))
219
+ throw new TrustValidationError("dependency binding policy is unregistered");
220
+ const versionKey = `${binding.bindingKind}\u0000${binding.bindingName}\u0000${binding.bindingVersion}`;
221
+ if (bindingVersions.has(versionKey))
222
+ throw new TrustValidationError("dependency binding version is duplicated");
223
+ bindingVersions.add(versionKey);
224
+ if ((binding.bindingVersion === 1) !==
225
+ (binding.parentBindingDigest === null))
226
+ throw new TrustValidationError("dependency binding lineage is invalid");
227
+ if (binding.parentBindingDigest !== null) {
228
+ const parent = bindingByDigest.get(binding.parentBindingDigest);
229
+ if (!parent ||
230
+ parent.bindingKind !== binding.bindingKind ||
231
+ parent.bindingName !== binding.bindingName ||
232
+ parent.bindingVersion + 1 !== binding.bindingVersion)
233
+ throw new TrustValidationError("dependency binding lineage is invalid");
234
+ }
235
+ if (binding.upstreamBindingDigest !== null &&
236
+ !bindingByDigest.has(binding.upstreamBindingDigest))
237
+ throw new TrustValidationError("dependency binding upstream is unregistered");
238
+ }
239
+ const dependencyBindingHeads = state.dependencyBindingHeads.map((head) => {
240
+ assertExactKeys(head, ["bindingKind", "bindingName", "bindingVersion", "bindingDigest"], "dependency binding head");
241
+ const value = head;
242
+ if (![
243
+ "content_resolver",
244
+ "causal_authority",
245
+ "mesh_ingress",
246
+ "mesh_eligibility",
247
+ "profile_resolver",
248
+ "snapshot_protector",
249
+ "verified_mesh_origin_verifier",
250
+ "model_boundary",
251
+ "action_dispatcher",
252
+ "message_dispatcher",
253
+ ].includes(value.bindingKind))
254
+ throw new TrustValidationError("dependency binding head kind is invalid");
255
+ assertIdentifier(value.bindingName, "dependencyBindingHead.bindingName");
256
+ assertSafeInteger(value.bindingVersion, "dependencyBindingHead.bindingVersion", 1);
257
+ assertTrustDigest(value.bindingDigest, "dependencyBindingHead.bindingDigest");
258
+ return value;
259
+ });
260
+ assertCanonicalOrder(dependencyBindingHeads.map((head) => `${head.bindingKind}\u0000${head.bindingName}`), "dependency binding heads");
261
+ const expectedDependencyHeads = dependencyBindings
262
+ .reduce((heads, binding) => {
263
+ const key = `${binding.bindingKind}\u0000${binding.bindingName}`;
264
+ const prior = heads.get(key);
265
+ if (!prior || prior.bindingVersion < binding.bindingVersion)
266
+ heads.set(key, dependencyBindingHeadV1(binding));
267
+ return heads;
268
+ }, new Map())
269
+ .values();
270
+ const canonicalDependencyHeads = [...expectedDependencyHeads].sort((a, b) => compareUnicode(`${a.bindingKind}\u0000${a.bindingName}`, `${b.bindingKind}\u0000${b.bindingName}`));
271
+ if (dependencyBindingHeads.length !== canonicalDependencyHeads.length ||
272
+ dependencyBindingHeads.some((head, index) => head.bindingKind !== canonicalDependencyHeads[index].bindingKind ||
273
+ head.bindingName !== canonicalDependencyHeads[index].bindingName ||
274
+ head.bindingVersion !==
275
+ canonicalDependencyHeads[index].bindingVersion ||
276
+ head.bindingDigest !== canonicalDependencyHeads[index].bindingDigest))
277
+ throw new TrustValidationError("dependency binding heads do not match binding history");
278
+ if (!Array.isArray(state.causalAuthorizations))
279
+ throw new TrustValidationError("causal authorizations are invalid");
280
+ const causalAuthorizations = state.causalAuthorizations.map(validateEvidenceCausalAuthorizationV1);
281
+ if (causalAuthorizations.length > limits.maximumCausalAuthorizations)
282
+ throw new TrustValidationError("causal authorization capacity exceeded");
283
+ assertCanonicalOrder(causalAuthorizations.map((item) => item.authorizationDigest), "causal authorizations");
284
+ if (new Set(causalAuthorizations.map((item) => item.authorizationId)).size !==
285
+ causalAuthorizations.length)
286
+ throw new TrustValidationError("causal authorization IDs must be unique");
287
+ if (!Array.isArray(state.records) ||
288
+ !Array.isArray(state.contentResolutions) ||
289
+ !Array.isArray(state.contentInvalidations) ||
290
+ !Array.isArray(state.pendingRecords) ||
291
+ !Array.isArray(state.diagnostics))
292
+ throw new TrustValidationError("lifecycle state arrays are invalid");
293
+ const records = state.records.map(validateEvidenceRecordStateV1);
294
+ assertCanonicalOrder(records.map((item) => item.recordDigest), "records");
295
+ if (new Set(records.map((item) => item.recordId)).size !== records.length)
296
+ throw new TrustValidationError("record IDs must be unique");
297
+ if (records.some((item) => item.acceptedAtLogicalMs > logicalTimeHighWaterMs))
298
+ throw new TrustValidationError("record acceptance exceeds state high-water");
299
+ for (const authorization of causalAuthorizations) {
300
+ if (authorization.authorizedAtLogicalMs > logicalTimeHighWaterMs)
301
+ throw new TrustValidationError("causal authorization exceeds state high-water");
302
+ const record = records.find((item) => item.recordId === authorization.recordId &&
303
+ item.recordDigest === authorization.recordDigest &&
304
+ item.recordKind === authorization.recordKind);
305
+ const binding = bindingByDigest.get(authorization.authorityBindingDigest);
306
+ const policy = policyByDigest.get(authorization.policyDigest);
307
+ const references = record && "basisReferences" in record.record
308
+ ? record.record.basisReferences
309
+ : [];
310
+ const referenceKey = (item) => `${item.kind}\u0000${item.referenceType}\u0000${item.referenceId}\u0000${item.referenceDigest}`;
311
+ const target = authorization.recordKind === "challenge"
312
+ ? records.find((item) => item.recordId === authorization.targetRecordId &&
313
+ item.recordDigest === authorization.targetRecordDigest)
314
+ : null;
315
+ const claim = record?.recordKind === "claim"
316
+ ? record
317
+ : target?.recordKind === "claim"
318
+ ? target
319
+ : target?.recordKind === "attestation"
320
+ ? records.find((item) => item.recordId ===
321
+ target.record.claimId &&
322
+ item.recordDigest ===
323
+ target.record.claimDigest)
324
+ : null;
325
+ const criterion = claim?.recordKind === "claim" && policy
326
+ ? policy.criteria.find((item) => item.criterionId ===
327
+ claim.record.criterionId)
328
+ : undefined;
329
+ const allowed = authorization.recordKind === "claim"
330
+ ? criterion?.claimAuthority.allowedSourceRelations
331
+ : criterion?.challengeAuthority.allowedSourceRelations;
332
+ const basisRules = authorization.recordKind === "claim"
333
+ ? criterion?.claimAuthority.allowedBasisReferences
334
+ : criterion?.challengeAuthority.allowedBasisReferences;
335
+ const counts = new Map();
336
+ for (const reference of references)
337
+ counts.set(`${reference.kind}\u0000${reference.referenceType}`, (counts.get(`${reference.kind}\u0000${reference.referenceType}`) ?? 0) +
338
+ 1);
339
+ const basesAllowed = basisRules !== undefined &&
340
+ basisRules.every((rule) => {
341
+ const count = counts.get(`${rule.kind}\u0000${rule.referenceType}`) ?? 0;
342
+ return count >= rule.minimumCount && count <= rule.maximumCount;
343
+ }) &&
344
+ [...counts.keys()].every((key) => basisRules.some((rule) => `${rule.kind}\u0000${rule.referenceType}` === key));
345
+ const resolvedEvidenceBasesValid = authorization.bases.every((basis) => {
346
+ if (basis.kind !== "evidence")
347
+ return true;
348
+ const referenced = records.find((item) => item.recordId === basis.referenceId &&
349
+ item.recordDigest === basis.referenceDigest);
350
+ return (referenced !== undefined &&
351
+ referenced.recordKind === basis.referenceType &&
352
+ digestScopeV1(referenced.record.scope) === authorization.scopeDigest &&
353
+ basis.resolvedDigest === referenced.recordDigest &&
354
+ basis.trustedEffectiveAtLogicalMs === referenced.effectiveAtLogicalMs);
355
+ });
356
+ const visibleHead = binding
357
+ ? dependencyBindings
358
+ .filter((item) => item.bindingKind === binding.bindingKind &&
359
+ item.bindingName === binding.bindingName &&
360
+ item.registeredAtLogicalMs <= authorization.authorizedAtLogicalMs)
361
+ .sort((left, right) => right.bindingVersion - left.bindingVersion)[0]
362
+ : null;
363
+ if (!record ||
364
+ !binding ||
365
+ !policy ||
366
+ binding.bindingKind !== "causal_authority" ||
367
+ binding.policyDigest !== authorization.policyDigest ||
368
+ binding.registeredAtLogicalMs > authorization.authorizedAtLogicalMs ||
369
+ binding.validFromLogicalMs > authorization.authorizedAtLogicalMs ||
370
+ (binding.validUntilLogicalMs !== null &&
371
+ authorization.authorizedAtLogicalMs >= binding.validUntilLogicalMs) ||
372
+ visibleHead?.bindingDigest !== binding.bindingDigest ||
373
+ authorization.authorizedAtLogicalMs < record.acceptedAtLogicalMs ||
374
+ !claim ||
375
+ claim.recordKind !== "claim" ||
376
+ authorization.criterionId !==
377
+ claim.record.criterionId ||
378
+ authorization.subjectDigest !==
379
+ digestSubjectV1(claim.record
380
+ .subject) ||
381
+ authorization.scopeDigest !== digestScopeV1(record.record.scope) ||
382
+ (authorization.recordKind === "claim"
383
+ ? authorization.targetRecordId !== null ||
384
+ authorization.targetRecordDigest !== null
385
+ : !target ||
386
+ authorization.targetRecordId !== target.recordId ||
387
+ authorization.targetRecordDigest !== target.recordDigest) ||
388
+ !allowed?.includes(authorization.sourceRelation) ||
389
+ !basesAllowed ||
390
+ !resolvedEvidenceBasesValid ||
391
+ references.length !== authorization.bases.length ||
392
+ references.some((reference, index) => referenceKey(reference) !== referenceKey(authorization.bases[index])))
393
+ throw new TrustValidationError("causal authorization binding is invalid");
394
+ }
395
+ if (!Array.isArray(state.profiles) || !Array.isArray(state.profileHeads))
396
+ throw new TrustValidationError("profile state arrays are invalid");
397
+ const profiles = state.profiles.map(validateTrustProfileV1);
398
+ const profileHistories = new Map();
399
+ const profileIds = new Set();
400
+ for (const profile of profiles) {
401
+ if (profile.updatedAtLogicalMs > logicalTimeHighWaterMs ||
402
+ profileIds.has(profile.profileId))
403
+ throw new TrustValidationError("profile history is invalid");
404
+ profileIds.add(profile.profileId);
405
+ const profileKey = digestTrustProfileKeyV1({
406
+ tenantId: profile.tenantId,
407
+ scopeDigest: profile.scopeDigest,
408
+ subjectDigest: profile.subjectDigest,
409
+ policyDigest: profile.policyDigest,
410
+ });
411
+ const history = profileHistories.get(profileKey) ?? [];
412
+ history.push(profile);
413
+ profileHistories.set(profileKey, history);
414
+ }
415
+ if (profileHistories.size > limits.maximumProfileHeads)
416
+ throw new TrustValidationError("profile head capacity exceeded");
417
+ assertCanonicalOrder(profiles.map((profile) => {
418
+ const profileKey = digestTrustProfileKeyV1({
419
+ tenantId: profile.tenantId,
420
+ scopeDigest: profile.scopeDigest,
421
+ subjectDigest: profile.subjectDigest,
422
+ policyDigest: profile.policyDigest,
423
+ });
424
+ return `${profileKey}\u0000${String(profile.revision).padStart(16, "0")}\u0000${profile.profileDigest}`;
425
+ }), "profiles");
426
+ const expectedProfileHeads = [];
427
+ for (const [profileKey, history] of profileHistories) {
428
+ if (history.length > limits.maximumProfileRevisionsPerHead)
429
+ throw new TrustValidationError("profile revision capacity exceeded");
430
+ history.sort((left, right) => left.revision - right.revision);
431
+ for (let index = 0; index < history.length; index += 1) {
432
+ const profile = history[index];
433
+ const previous = history[index - 1] ?? null;
434
+ if (profile.revision !== index + 1 ||
435
+ profile.previousProfileId !== (previous?.profileId ?? null) ||
436
+ profile.previousProfileDigest !== (previous?.profileDigest ?? null))
437
+ throw new TrustValidationError("profile lineage is invalid");
438
+ const decision = fusionDecisions.find((item) => item.fusionDecisionId === profile.fusionDecisionId &&
439
+ item.fusionDecisionDigest === profile.fusionDecisionDigest);
440
+ const policy = policyByDigest.get(profile.policyDigest);
441
+ if (!decision || !policy)
442
+ throw new TrustValidationError("profile Fusion decision or policy is unavailable");
443
+ const recomputed = createTrustProfileV1({
444
+ fusionDecision: decision,
445
+ policy,
446
+ revision: profile.revision,
447
+ previousProfileId: previous?.profileId ?? null,
448
+ previousProfileDigest: previous?.profileDigest ?? null,
449
+ updatedAtLogicalMs: profile.updatedAtLogicalMs,
450
+ });
451
+ if (recomputed.profileDigest !== profile.profileDigest)
452
+ throw new TrustValidationError("profile does not rederive from Fusion");
453
+ }
454
+ expectedProfileHeads.push(trustProfileHeadV1(history[history.length - 1]));
455
+ if (expectedProfileHeads.at(-1)?.profileKey !== profileKey)
456
+ throw new TrustValidationError("profile key is inconsistent");
457
+ }
458
+ const profileHeads = state.profileHeads.map(validateTrustProfileHeadV1);
459
+ assertCanonicalOrder(profileHeads.map((head) => head.profileKey), "profile heads");
460
+ expectedProfileHeads.sort((left, right) => compareUnicode(left.profileKey, right.profileKey));
461
+ if (profileHeads.length !== expectedProfileHeads.length ||
462
+ profileHeads.some((head, index) => head.profileKey !== expectedProfileHeads[index].profileKey ||
463
+ head.profileId !== expectedProfileHeads[index].profileId ||
464
+ head.profileDigest !== expectedProfileHeads[index].profileDigest ||
465
+ head.revision !== expectedProfileHeads[index].revision))
466
+ throw new TrustValidationError("profile heads do not match profile history");
467
+ if (!Array.isArray(state.quarantines) ||
468
+ !Array.isArray(state.quarantineHeads) ||
469
+ !Array.isArray(state.recoveryDecisions))
470
+ throw new TrustValidationError("quarantine state arrays are invalid");
471
+ const recoveryDecisions = state.recoveryDecisions.map(validateQuarantineRecoveryDecisionV1);
472
+ if (recoveryDecisions.length > limits.maximumRecoveryDecisions ||
473
+ recoveryDecisions.some((decision) => decision.evaluatedAtLogicalMs > logicalTimeHighWaterMs))
474
+ throw new TrustValidationError("recovery decision state is invalid");
475
+ assertCanonicalOrder(recoveryDecisions.map((decision) => decision.recoveryDecisionDigest), "recovery decisions");
476
+ if (new Set(recoveryDecisions.map((decision) => decision.recoveryDecisionId))
477
+ .size !== recoveryDecisions.length)
478
+ throw new TrustValidationError("recovery decision IDs must be unique");
479
+ const quarantines = state.quarantines.map(validateQuarantineRecordV1);
480
+ const quarantineIds = new Set();
481
+ const quarantineHistories = new Map();
482
+ for (const quarantine of quarantines) {
483
+ if (quarantineIds.has(quarantine.quarantineId) ||
484
+ quarantine.activatedAtLogicalMs > logicalTimeHighWaterMs ||
485
+ (quarantine.recoveredAtLogicalMs !== null &&
486
+ quarantine.recoveredAtLogicalMs > logicalTimeHighWaterMs))
487
+ throw new TrustValidationError("quarantine history is invalid");
488
+ quarantineIds.add(quarantine.quarantineId);
489
+ const history = quarantineHistories.get(quarantine.quarantineKey) ?? [];
490
+ history.push(quarantine);
491
+ quarantineHistories.set(quarantine.quarantineKey, history);
492
+ }
493
+ if (quarantineHistories.size > limits.maximumQuarantineHeads)
494
+ throw new TrustValidationError("quarantine head capacity exceeded");
495
+ assertCanonicalOrder(quarantines.map((quarantine) => `${quarantine.quarantineKey}\u0000${String(quarantine.revision).padStart(16, "0")}\u0000${quarantine.quarantineId}`), "quarantines");
496
+ const expectedQuarantineHeads = [];
497
+ for (const [quarantineKey, history] of quarantineHistories) {
498
+ if (history.length > limits.maximumQuarantineRevisionsPerHead)
499
+ throw new TrustValidationError("quarantine revision capacity exceeded");
500
+ history.sort((left, right) => left.revision - right.revision);
501
+ for (let index = 0; index < history.length; index += 1) {
502
+ const quarantine = history[index];
503
+ const previous = history[index - 1] ?? null;
504
+ if (quarantine.revision !== index + 1 ||
505
+ quarantine.previousRecordId !== (previous?.quarantineId ?? null))
506
+ throw new TrustValidationError("quarantine lineage is invalid");
507
+ if (quarantine.status === "active") {
508
+ if (previous !== null && previous.status !== "recovered")
509
+ throw new TrustValidationError("quarantine activation transition is invalid");
510
+ const decision = fusionDecisions.find((candidate) => candidate.fusionDecisionId === quarantine.fusionDecisionId);
511
+ const profile = decision
512
+ ? profiles.find((candidate) => candidate.fusionDecisionId === decision.fusionDecisionId &&
513
+ candidate.fusionDecisionDigest ===
514
+ decision.fusionDecisionDigest)
515
+ : null;
516
+ const policy = policyByDigest.get(quarantine.policyDigest);
517
+ if (!decision || !profile || !policy)
518
+ throw new TrustValidationError("quarantine activation inputs are unavailable");
519
+ const projection = deriveQuarantineActivationsV1(value, profile, decision, policy, previous?.recoveredAtLogicalMs ?? null).find((candidate) => candidate.dimensionId === quarantine.dimensionId);
520
+ if (!projection)
521
+ throw new TrustValidationError("quarantine activation does not rederive");
522
+ const expected = createActiveQuarantineRecordV1({
523
+ revision: quarantine.revision,
524
+ previousRecordId: previous?.quarantineId ?? null,
525
+ tenantId: decision.tenantId,
526
+ subjectDigest: decision.subjectDigest,
527
+ scopeDigest: decision.scopeDigest,
528
+ dimensionId: projection.dimensionId,
529
+ policyDigest: decision.policyDigest,
530
+ fusionDecisionId: decision.fusionDecisionId,
531
+ activationEvidence: projection.activationEvidence,
532
+ activationDependencyGroupIds: projection.activationDependencyGroupIds,
533
+ activatedAtLogicalMs: decision.evaluatedAtLogicalMs,
534
+ reviewIntervalMs: projection.reviewIntervalMs,
535
+ });
536
+ if (expected.quarantineId !== quarantine.quarantineId)
537
+ throw new TrustValidationError("quarantine activation record does not rederive");
538
+ }
539
+ else if (quarantine.status === "review_required") {
540
+ if (!previous || previous.status !== "active")
541
+ throw new TrustValidationError("quarantine review transition is invalid");
542
+ const expected = createReviewRequiredQuarantineRecordV1(previous, previous.reviewAfterLogicalMs);
543
+ if (expected.quarantineId !== quarantine.quarantineId)
544
+ throw new TrustValidationError("quarantine review record does not rederive");
545
+ }
546
+ else {
547
+ if (!previous || previous.status !== "review_required")
548
+ throw new TrustValidationError("quarantine recovery transition is invalid");
549
+ const recovery = recoveryDecisions.find((candidate) => candidate.recoveryDecisionId === quarantine.recoveryDecisionId);
550
+ if (!recovery)
551
+ throw new TrustValidationError("quarantine recovery decision is unavailable");
552
+ const expected = createRecoveredQuarantineRecordV1(previous, recovery);
553
+ if (expected.quarantineId !== quarantine.quarantineId)
554
+ throw new TrustValidationError("quarantine recovery record does not rederive");
555
+ }
556
+ }
557
+ const head = quarantineHeadV1(history[history.length - 1]);
558
+ if (head.quarantineKey !== quarantineKey)
559
+ throw new TrustValidationError("quarantine key is inconsistent");
560
+ expectedQuarantineHeads.push(head);
561
+ }
562
+ expectedQuarantineHeads.sort((left, right) => compareUnicode(left.quarantineKey, right.quarantineKey));
563
+ const quarantineHeads = state.quarantineHeads.map(validateQuarantineHeadV1);
564
+ assertCanonicalOrder(quarantineHeads.map((head) => head.quarantineKey), "quarantine heads");
565
+ if (quarantineHeads.length !== expectedQuarantineHeads.length ||
566
+ quarantineHeads.some((head, index) => head.quarantineKey !== expectedQuarantineHeads[index].quarantineKey ||
567
+ head.quarantineId !== expectedQuarantineHeads[index].quarantineId ||
568
+ head.revision !== expectedQuarantineHeads[index].revision ||
569
+ head.status !== expectedQuarantineHeads[index].status))
570
+ throw new TrustValidationError("quarantine heads do not match quarantine history");
571
+ for (const policy of policies) {
572
+ const policyDigest = digestEvidenceFusionPolicyV1(policy);
573
+ const activeCount = quarantineHeads.filter((head) => {
574
+ const record = quarantines.find((candidate) => candidate.quarantineId === head.quarantineId);
575
+ return (record?.policyDigest === policyDigest &&
576
+ (head.status === "active" || head.status === "review_required"));
577
+ }).length;
578
+ if (activeCount > policy.quarantinePolicy.maximumActiveRecords)
579
+ throw new TrustValidationError("active quarantine capacity exceeded");
580
+ }
581
+ if (quarantineHeads.some((head) => {
582
+ const record = quarantines.find((candidate) => candidate.quarantineId === head.quarantineId);
583
+ const historyLength = quarantines.filter((candidate) => candidate.quarantineKey === head.quarantineKey).length;
584
+ return (head.status === "active" &&
585
+ record !== undefined &&
586
+ record.reviewAfterLogicalMs <= logicalTimeHighWaterMs &&
587
+ historyLength < limits.maximumQuarantineRevisionsPerHead);
588
+ }))
589
+ throw new TrustValidationError("quarantine review transition is overdue");
590
+ for (const recovery of recoveryDecisions) {
591
+ const quarantine = quarantines.find((candidate) => candidate.quarantineId === recovery.quarantineId);
592
+ const decision = fusionDecisions.find((candidate) => candidate.fusionDecisionId === recovery.fusionDecisionId);
593
+ const profile = decision
594
+ ? profiles.find((candidate) => candidate.fusionDecisionId === decision.fusionDecisionId &&
595
+ candidate.fusionDecisionDigest === decision.fusionDecisionDigest)
596
+ : null;
597
+ const policy = policyByDigest.get(recovery.policyDigest);
598
+ if (!quarantine ||
599
+ (quarantine.status !== "review_required" &&
600
+ !(quarantine.status === "active" &&
601
+ quarantine.reviewAfterLogicalMs <= recovery.evaluatedAtLogicalMs &&
602
+ quarantine.revision >= limits.maximumQuarantineRevisionsPerHead)) ||
603
+ !decision ||
604
+ !profile ||
605
+ !policy)
606
+ throw new TrustValidationError("recovery decision inputs are unavailable");
607
+ const expected = evaluateQuarantineRecoveryV1(value, quarantine, profile, decision, policy, recovery.evaluatedAtLogicalMs);
608
+ if (expected.recoveryDecisionDigest !== recovery.recoveryDecisionDigest)
609
+ throw new TrustValidationError("recovery decision does not rederive");
610
+ }
611
+ const counts = { claim: 0, attestation: 0, challenge: 0, retraction: 0 };
612
+ for (const record of records)
613
+ counts[record.recordKind] += 1;
614
+ if (counts.claim > limits.maximumClaims ||
615
+ counts.attestation > limits.maximumAttestations ||
616
+ counts.challenge > limits.maximumChallenges ||
617
+ counts.retraction > limits.maximumRetractions)
618
+ throw new TrustValidationError("record family capacity exceeded");
619
+ if (records.some((item) => "basisReferences" in item.record &&
620
+ item.record.basisReferences.length >
621
+ limits.maximumBasisReferencesPerRecord))
622
+ throw new TrustValidationError("basis reference capacity exceeded");
623
+ if (records.some((item) => item.recordKind === "claim" &&
624
+ "content" in item.record &&
625
+ item.record.content !== null &&
626
+ item.record.content.encodedBytes >
627
+ (item.record.content.kind === "reference"
628
+ ? limits.maximumContentReferenceBytes
629
+ : limits.maximumInlineSummaryBytes)))
630
+ throw new TrustValidationError("evidence content size limit exceeded");
631
+ const challengeCounts = new Map();
632
+ for (const record of records) {
633
+ if (record.recordKind !== "challenge")
634
+ continue;
635
+ const key = `${record.record.sourceKind}\u0000${record.record.sourceId}\u0000${digestScopeV1(record.record.scope)}`;
636
+ const count = challengeCounts.get(key) ?? { total: 0, pending: 0 };
637
+ count.total += 1;
638
+ if (record.status === "pending")
639
+ count.pending += 1;
640
+ challengeCounts.set(key, count);
641
+ }
642
+ if ([...challengeCounts.values()].some((count) => count.total > limits.maximumChallengesPerSourceScope ||
643
+ count.pending > limits.maximumPendingChallengesPerSourceScope))
644
+ throw new TrustValidationError("challenge source scope capacity exceeded");
645
+ const resolutions = state.contentResolutions.map(validateEvidenceContentResolutionV1);
646
+ assertCanonicalOrder(resolutions.map((item) => item.resolutionDigest), "content resolutions");
647
+ if (new Set(resolutions.map((item) => item.resolutionId)).size !==
648
+ resolutions.length)
649
+ throw new TrustValidationError("content resolution IDs must be unique");
650
+ if (resolutions.length > limits.maximumContentResolutions ||
651
+ resolutions.some((item) => item.resolvedAtLogicalMs > logicalTimeHighWaterMs))
652
+ throw new TrustValidationError("content resolution state is invalid");
653
+ for (const resolution of resolutions) {
654
+ const claim = records.find((item) => item.recordKind === "claim" &&
655
+ item.recordId === resolution.claimId &&
656
+ item.recordDigest === resolution.claimDigest);
657
+ const content = claim && "content" in claim.record ? claim.record.content : null;
658
+ if (!claim ||
659
+ digestScopeV1(claim.record.scope) !== resolution.scopeDigest ||
660
+ !content ||
661
+ content.kind !== "reference" ||
662
+ content.reference.referenceId !== resolution.referenceId ||
663
+ content.reference.referenceDigest !== resolution.referenceDigest ||
664
+ content.contentDigest !== resolution.contentDigest ||
665
+ content.mediaType !== resolution.mediaType ||
666
+ content.encodedBytes !== resolution.encodedBytes)
667
+ throw new TrustValidationError("content resolution does not bind claim content");
668
+ }
669
+ const invalidations = state.contentInvalidations.map(validateEvidenceContentResolutionInvalidationV1);
670
+ assertCanonicalOrder(invalidations.map((item) => item.invalidationId), "content invalidations");
671
+ if (new Set(invalidations.map((item) => item.invalidationId)).size !==
672
+ invalidations.length)
673
+ throw new TrustValidationError("content invalidation IDs must be unique");
674
+ if (invalidations.length > limits.maximumContentInvalidations ||
675
+ invalidations.some((item) => item.invalidatedAtLogicalMs > logicalTimeHighWaterMs))
676
+ throw new TrustValidationError("content invalidation state is invalid");
677
+ if (invalidations.some((item) => !resolutions.some((resolution) => resolution.resolutionId === item.resolutionId &&
678
+ resolution.resolutionDigest === item.resolutionDigest &&
679
+ resolution.resolverBindingDigest === item.resolverBindingDigest &&
680
+ item.invalidatedAtLogicalMs >= resolution.resolvedAtLogicalMs)))
681
+ throw new TrustValidationError("content invalidation does not bind a resolution");
682
+ const pendingRecords = state.pendingRecords.map((item) => {
683
+ assertIdentifier(item, "pendingRecord");
684
+ return item;
685
+ });
686
+ assertCanonicalOrder(pendingRecords, "pending records");
687
+ const expectedPending = records
688
+ .filter((item) => item.status === "pending")
689
+ .map((item) => item.recordId)
690
+ .sort(compareUnicode);
691
+ if (pendingRecords.length !== expectedPending.length ||
692
+ pendingRecords.some((item, index) => item !== expectedPending[index]))
693
+ throw new TrustValidationError("pending index does not match records");
694
+ if (pendingRecords.length > limits.maximumPendingRecords)
695
+ throw new TrustValidationError("pending record capacity exceeded");
696
+ const diagnostics = state.diagnostics.map((item) => {
697
+ assertExactKeys(item, ["schemaVersion", "recordId", "recordDigest", "reasonCode"], "diagnostic");
698
+ const value = item;
699
+ if (value.schemaVersion !== 1)
700
+ throw new TrustValidationError("diagnostic schema is invalid");
701
+ assertIdentifier(value.recordId, "diagnostic.recordId");
702
+ assertTrustDigest(value.recordDigest, "diagnostic.recordDigest");
703
+ validateReasonCodeV1(value.reasonCode);
704
+ return value;
705
+ });
706
+ assertCanonicalOrder(diagnostics.map((item) => item.recordDigest), "diagnostics");
707
+ if (diagnostics.length > limits.maximumDiagnostics)
708
+ throw new TrustValidationError("diagnostic capacity exceeded");
709
+ if (diagnostics.some((diagnostic) => !records.some((record) => record.recordId === diagnostic.recordId &&
710
+ record.recordDigest === diagnostic.recordDigest)))
711
+ throw new TrustValidationError("diagnostic does not bind a record");
712
+ const cloned = structuredClone({
713
+ ...state,
714
+ limits,
715
+ policies,
716
+ policyHeads,
717
+ dependencyBindings,
718
+ dependencyBindingHeads,
719
+ causalAuthorizations,
720
+ records,
721
+ contentResolutions: resolutions,
722
+ contentInvalidations: invalidations,
723
+ pendingRecords,
724
+ diagnostics,
725
+ fusionDecisions,
726
+ profiles,
727
+ profileHeads,
728
+ quarantines,
729
+ quarantineHeads,
730
+ recoveryDecisions,
731
+ });
732
+ for (const decision of fusionDecisions) {
733
+ const decisionProfileKey = digestTrustProfileKeyV1({
734
+ tenantId: decision.tenantId,
735
+ scopeDigest: decision.scopeDigest,
736
+ subjectDigest: decision.subjectDigest,
737
+ policyDigest: decision.policyDigest,
738
+ });
739
+ const priorProfile = decision.previousProfileDigest === null
740
+ ? null
741
+ : (profiles.find((profile) => profile.profileDigest === decision.previousProfileDigest &&
742
+ digestTrustProfileKeyV1({
743
+ tenantId: profile.tenantId,
744
+ scopeDigest: profile.scopeDigest,
745
+ subjectDigest: profile.subjectDigest,
746
+ policyDigest: profile.policyDigest,
747
+ }) === decisionProfileKey &&
748
+ profile.updatedAtLogicalMs <= decision.evaluatedAtLogicalMs) ?? null);
749
+ if (decision.previousProfileDigest !== null && priorProfile === null)
750
+ throw new TrustValidationError("fusion decision previous profile is unavailable");
751
+ const historicalResolutions = resolutions.filter((item) => item.resolvedAtLogicalMs <= decision.evaluatedAtLogicalMs);
752
+ const historicalInvalidations = invalidations.filter((item) => item.invalidatedAtLogicalMs <= decision.evaluatedAtLogicalMs);
753
+ const historicalProjection = projectEvidenceLifecycleV1({
754
+ records: records.filter((item) => item.acceptedAtLogicalMs <= decision.evaluatedAtLogicalMs),
755
+ logicalTimeMs: decision.evaluatedAtLogicalMs,
756
+ limits,
757
+ contentResolutions: historicalResolutions,
758
+ contentInvalidations: historicalInvalidations,
759
+ });
760
+ const historicalState = {
761
+ ...cloned,
762
+ logicalTimeHighWaterMs: decision.evaluatedAtLogicalMs,
763
+ policyHeads: [
764
+ ...policyHeads.filter((head) => head.policyId !== decision.policyId),
765
+ {
766
+ policyId: decision.policyId,
767
+ policyVersion: decision.policyVersion,
768
+ policyDigest: decision.policyDigest,
769
+ },
770
+ ].sort((left, right) => compareUnicode(left.policyId, right.policyId)),
771
+ records: historicalProjection.records,
772
+ contentResolutions: historicalResolutions,
773
+ contentInvalidations: historicalInvalidations,
774
+ causalAuthorizations: causalAuthorizations.filter((item) => item.authorizedAtLogicalMs <= decision.evaluatedAtLogicalMs),
775
+ pendingRecords: historicalProjection.records
776
+ .filter((item) => item.status === "pending")
777
+ .map((item) => item.recordId),
778
+ diagnostics: historicalProjection.diagnostics,
779
+ fusionDecisions: [],
780
+ profileHeads: [
781
+ ...profileHeads.filter((head) => head.profileKey !== decisionProfileKey),
782
+ ...(priorProfile === null ? [] : [trustProfileHeadV1(priorProfile)]),
783
+ ].sort((left, right) => compareUnicode(left.profileKey, right.profileKey)),
784
+ };
785
+ const dependencyBindingDigests = deriveApplicableBindingDigests(historicalState, decision.policyDigest, decision.evaluatedAtLogicalMs);
786
+ const recomputed = evaluateEvidenceFusionV1(historicalState, {
787
+ tenantId: decision.tenantId,
788
+ subject: decision.subject,
789
+ scope: decision.scope,
790
+ policyId: decision.policyId,
791
+ policyVersion: decision.policyVersion,
792
+ policyDigest: decision.policyDigest,
793
+ dependencyBindingDigests,
794
+ }, decision.evaluatedAtLogicalMs);
795
+ if (recomputed.fusionDecisionDigest !== decision.fusionDecisionDigest)
796
+ throw new TrustValidationError("fusion decision does not rederive from state");
797
+ }
798
+ const bytes = canonicalTrustJsonBytesV1({ ...cloned, encodedBytes: 0 }, stateCanonicalLimits(limits.maximumStateCanonicalBytes));
799
+ if (bytes.byteLength !== cloned.encodedBytes)
800
+ throw new TrustValidationError("encodedBytes does not match state");
801
+ return deepFreeze(cloned);
802
+ }
803
+ function snapshotMaterial(snapshot) {
804
+ return new TextEncoder().encode(`agentplat.trust/snapshot-integrity/v1\0${canonicalizeTrustJsonV1({
805
+ stateId: snapshot.stateId,
806
+ generation: snapshot.generation,
807
+ previousSnapshotDigest: snapshot.previousSnapshotDigest,
808
+ createdAtLogicalMs: snapshot.createdAtLogicalMs,
809
+ snapshotDigest: snapshot.snapshotDigest,
810
+ stateDigest: snapshot.stateDigest,
811
+ })}`);
812
+ }
813
+ export function createEvidenceTrustSnapshotV1(input) {
814
+ const state = validateEvidenceTrustStateV1(input.state);
815
+ assertSafeInteger(input.generation, "generation", 1);
816
+ assertSafeInteger(input.createdAtLogicalMs, "createdAtLogicalMs");
817
+ if (input.createdAtLogicalMs < state.logicalTimeHighWaterMs)
818
+ throw new TrustValidationError("snapshot time precedes state high-water");
819
+ if ((input.generation === 1) !== (input.previousSnapshotDigest === null))
820
+ throw new TrustValidationError("snapshot generation and predecessor are incoherent");
821
+ if (input.previousSnapshotDigest !== null)
822
+ assertTrustDigest(input.previousSnapshotDigest, "previousSnapshotDigest");
823
+ assertTrustDigest(input.protector.bindingDigest, "protector.bindingDigest");
824
+ const stateDigest = digestTrustJsonV1("state", state, stateCanonicalLimits(state.limits.maximumStateCanonicalBytes));
825
+ const unsigned = {
826
+ schemaVersion: 1,
827
+ stateId: state.stateId,
828
+ generation: input.generation,
829
+ previousSnapshotDigest: input.previousSnapshotDigest,
830
+ createdAtLogicalMs: input.createdAtLogicalMs,
831
+ stateDigest,
832
+ state,
833
+ };
834
+ const snapshotDigest = digestTrustJsonV1("snapshot", unsigned, stateCanonicalLimits(state.limits.maximumStateCanonicalBytes));
835
+ const proof = input.protector.protect(snapshotMaterial({ ...unsigned, snapshotDigest }));
836
+ return validateEvidenceTrustSnapshotV1({
837
+ ...unsigned,
838
+ snapshotId: `snapshot:${snapshotDigest}`,
839
+ snapshotDigest,
840
+ integrityProof: {
841
+ ...proof,
842
+ protectorBindingDigest: input.protector.bindingDigest,
843
+ },
844
+ });
845
+ }
846
+ export function validateEvidenceTrustSnapshotV1(value) {
847
+ assertExactKeys(value, [
848
+ "schemaVersion",
849
+ "snapshotId",
850
+ "snapshotDigest",
851
+ "stateId",
852
+ "generation",
853
+ "previousSnapshotDigest",
854
+ "createdAtLogicalMs",
855
+ "stateDigest",
856
+ "state",
857
+ "integrityProof",
858
+ ], "snapshot");
859
+ const snapshot = value;
860
+ if (snapshot.schemaVersion !== 1)
861
+ throw new TrustValidationError("snapshot schema is invalid");
862
+ assertIdentifier(snapshot.stateId, "stateId");
863
+ assertSafeInteger(snapshot.generation, "generation", 1);
864
+ assertSafeInteger(snapshot.createdAtLogicalMs, "createdAtLogicalMs");
865
+ if ((snapshot.generation === 1) !==
866
+ (snapshot.previousSnapshotDigest === null))
867
+ throw new TrustValidationError("snapshot generation and predecessor are incoherent");
868
+ if (snapshot.previousSnapshotDigest !== null)
869
+ assertTrustDigest(snapshot.previousSnapshotDigest, "previousSnapshotDigest");
870
+ assertTrustDigest(snapshot.snapshotDigest, "snapshotDigest");
871
+ assertTrustDigest(snapshot.stateDigest, "stateDigest");
872
+ const state = validateEvidenceTrustStateV1(snapshot.state);
873
+ if (snapshot.createdAtLogicalMs < state.logicalTimeHighWaterMs)
874
+ throw new TrustValidationError("snapshot time precedes state high-water");
875
+ if (state.stateId !== snapshot.stateId ||
876
+ digestTrustJsonV1("state", state, stateCanonicalLimits(state.limits.maximumStateCanonicalBytes)) !== snapshot.stateDigest)
877
+ throw new TrustValidationError("snapshot state digest is invalid");
878
+ const unsigned = {
879
+ schemaVersion: 1,
880
+ stateId: snapshot.stateId,
881
+ generation: snapshot.generation,
882
+ previousSnapshotDigest: snapshot.previousSnapshotDigest,
883
+ createdAtLogicalMs: snapshot.createdAtLogicalMs,
884
+ stateDigest: snapshot.stateDigest,
885
+ state,
886
+ };
887
+ const expected = digestTrustJsonV1("snapshot", unsigned, stateCanonicalLimits(state.limits.maximumStateCanonicalBytes));
888
+ if (snapshot.snapshotId !== `snapshot:${expected}` ||
889
+ snapshot.snapshotDigest !== expected)
890
+ throw new TrustValidationError("snapshot digest is invalid");
891
+ assertExactKeys(snapshot.integrityProof, ["protectorBindingDigest", "algorithmId", "keyId", "encoding", "proof"], "integrityProof");
892
+ const proof = snapshot.integrityProof;
893
+ assertTrustDigest(proof.protectorBindingDigest, "protectorBindingDigest");
894
+ for (const key of ["algorithmId", "keyId", "proof"])
895
+ assertIdentifier(proof[key], key);
896
+ if (proof.encoding !== "base64url")
897
+ throw new TrustValidationError("integrity proof encoding is invalid");
898
+ return deepFreeze({
899
+ ...snapshot,
900
+ state,
901
+ integrityProof: proof,
902
+ });
903
+ }
904
+ export function restoreEvidenceTrustSnapshotV1(snapshotValue, anchor, protector, options = {}) {
905
+ assertExactKeys(options, [
906
+ "verifiedMeshAdmissionVerifierRegistry",
907
+ "currentContentResolverBindingDigest",
908
+ "causalAuthorityVerifierRegistry",
909
+ ].filter((key) => key in options), "restore options");
910
+ if (options.currentContentResolverBindingDigest !== undefined &&
911
+ options.currentContentResolverBindingDigest !== null)
912
+ assertTrustDigest(options.currentContentResolverBindingDigest, "currentContentResolverBindingDigest");
913
+ const causalRegistry = options.causalAuthorityVerifierRegistry;
914
+ if (causalRegistry !== undefined) {
915
+ assertExactKeys(causalRegistry, ["resolve"], "causal authority verifier registry");
916
+ if (typeof causalRegistry.resolve !== "function")
917
+ throw new TrustValidationError("causal authority verifier registry is invalid");
918
+ }
919
+ const snapshot = validateEvidenceTrustSnapshotV1(snapshotValue);
920
+ assertExactKeys(anchor, [
921
+ "schemaVersion",
922
+ "stateId",
923
+ "requiredGeneration",
924
+ "requiredSnapshotDigest",
925
+ "minimumLogicalHighWaterMs",
926
+ "protectorBindingDigest",
927
+ ], "rollback anchor");
928
+ if (anchor.schemaVersion !== 1)
929
+ throw new TrustValidationError("rollback anchor schema is invalid");
930
+ assertIdentifier(anchor.stateId, "anchor stateId");
931
+ assertSafeInteger(anchor.requiredGeneration, "requiredGeneration", 1);
932
+ assertTrustDigest(anchor.requiredSnapshotDigest, "requiredSnapshotDigest");
933
+ assertSafeInteger(anchor.minimumLogicalHighWaterMs, "minimumLogicalHighWaterMs");
934
+ assertTrustDigest(anchor.protectorBindingDigest, "protectorBindingDigest");
935
+ if (snapshot.stateId !== anchor.stateId ||
936
+ snapshot.generation !== anchor.requiredGeneration ||
937
+ snapshot.snapshotDigest !== anchor.requiredSnapshotDigest ||
938
+ snapshot.integrityProof.protectorBindingDigest !==
939
+ anchor.protectorBindingDigest ||
940
+ protector.bindingDigest !== anchor.protectorBindingDigest ||
941
+ snapshot.state.logicalTimeHighWaterMs < anchor.minimumLogicalHighWaterMs)
942
+ throw new TrustValidationError("snapshot rollback validation failed");
943
+ if (!protector.verify(snapshotMaterial(snapshot), snapshot.integrityProof))
944
+ throw new TrustValidationError("snapshot integrity validation failed");
945
+ const verifiedRecords = snapshot.state.records.filter((record) => record.origin === "verified_mesh");
946
+ const registry = options.verifiedMeshAdmissionVerifierRegistry;
947
+ if (verifiedRecords.length > 0 && !registry)
948
+ throw new TrustValidationError("verified mesh restore requires admission verifier");
949
+ if (registry) {
950
+ assertExactKeys(registry, ["resolve"], "mesh admission verifier registry");
951
+ if (typeof registry.resolve !== "function")
952
+ throw new TrustValidationError("mesh admission verifier registry is invalid");
953
+ for (const record of verifiedRecords) {
954
+ const verifier = resolveVerifiedMeshAdmissionVerifierV1(registry, record.originVerifierBindingDigest);
955
+ if (!verifier ||
956
+ verifier.verifierBindingDigest !== record.originVerifierBindingDigest ||
957
+ verifier.upstreamBindingDigest !== record.originBindingDigest ||
958
+ record.originProofDigest === null ||
959
+ !verifier.verify({
960
+ recordId: record.recordId,
961
+ recordDigest: record.recordDigest,
962
+ originBindingDigest: record.originBindingDigest,
963
+ originVerifierBindingDigest: record.originVerifierBindingDigest,
964
+ originProofDigest: record.originProofDigest,
965
+ effectiveAtLogicalMs: record.effectiveAtLogicalMs,
966
+ }))
967
+ throw new TrustValidationError("verified mesh restore validation failed");
968
+ }
969
+ }
970
+ if (snapshot.state.causalAuthorizations.length > 0 && !causalRegistry)
971
+ throw new TrustValidationError("causal authority restore requires verifier registry");
972
+ if (causalRegistry) {
973
+ for (const authorization of snapshot.state.causalAuthorizations) {
974
+ const binding = snapshot.state.dependencyBindings.find((item) => item.bindingDigest === authorization.authorityBindingDigest);
975
+ const verifier = resolveEvidenceCausalAuthorityVerifierV1(causalRegistry, authorization.authorityBindingDigest);
976
+ if (!binding ||
977
+ !verifier ||
978
+ binding.bindingKind !== "causal_authority" ||
979
+ binding.policyDigest !== authorization.policyDigest ||
980
+ verifier.authorityBindingDigest !==
981
+ authorization.authorityBindingDigest ||
982
+ verifier.policyDigest !== authorization.policyDigest ||
983
+ verifier.upstreamBindingDigest !== binding.upstreamBindingDigest ||
984
+ !verifier.verify(authorization))
985
+ throw new TrustValidationError("causal authority restore validation failed");
986
+ }
987
+ }
988
+ const projected = projectEvidenceLifecycleV1({
989
+ records: snapshot.state.records,
990
+ logicalTimeMs: snapshot.state.logicalTimeHighWaterMs,
991
+ limits: snapshot.state.limits,
992
+ contentResolutions: snapshot.state.contentResolutions,
993
+ contentInvalidations: snapshot.state.contentInvalidations,
994
+ currentContentResolverBindingDigest: options.currentContentResolverBindingDigest,
995
+ });
996
+ const expectedPending = projected.records
997
+ .filter((record) => record.status === "pending")
998
+ .map((record) => record.recordId)
999
+ .sort(compareUnicode);
1000
+ const expectedDiagnostics = projected.diagnostics.slice(0, snapshot.state.limits.maximumDiagnostics);
1001
+ if (projected.records.length !== snapshot.state.records.length ||
1002
+ projected.records.some((record, index) => record.recordDigest !== snapshot.state.records[index]?.recordDigest ||
1003
+ record.status !== snapshot.state.records[index]?.status ||
1004
+ record.effectiveAtLogicalMs !==
1005
+ snapshot.state.records[index]?.effectiveAtLogicalMs) ||
1006
+ expectedDiagnostics.length !== snapshot.state.diagnostics.length ||
1007
+ expectedDiagnostics.some((diagnostic, index) => diagnostic.recordDigest !==
1008
+ snapshot.state.diagnostics[index]?.recordDigest ||
1009
+ diagnostic.reasonCode !== snapshot.state.diagnostics[index]?.reasonCode) ||
1010
+ expectedPending.length !== snapshot.state.pendingRecords.length ||
1011
+ expectedPending.some((record, index) => record !== snapshot.state.pendingRecords[index]))
1012
+ throw new TrustValidationError("snapshot lifecycle projection is invalid");
1013
+ return snapshot.state;
1014
+ }
1015
+ //# sourceMappingURL=state.js.map