@openplanr/protocol 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/lib/dashboard/closed-json-contract.mjs +141 -0
  4. package/lib/dashboard/generated/operate-experience-surface-schema-data.mjs +67 -0
  5. package/lib/dashboard/generated/operate-review-schema-data.mjs +35 -0
  6. package/lib/dashboard/generated/operate-schema-token-codec.mjs +127 -0
  7. package/lib/dashboard/operate-experience-audit-display-contract.d.mts +196 -0
  8. package/lib/dashboard/operate-experience-audit-display-contract.mjs +700 -0
  9. package/lib/dashboard/operate-experience-display-contract.d.mts +572 -0
  10. package/lib/dashboard/operate-experience-display-contract.mjs +1007 -0
  11. package/lib/dashboard/operate-experience-surface-contract.d.mts +157 -0
  12. package/lib/dashboard/operate-experience-surface-contract.mjs +91 -0
  13. package/lib/dashboard/operate-review-contract.d.mts +34 -0
  14. package/lib/dashboard/operate-review-contract.mjs +175 -0
  15. package/lib/dashboard/operate-review-display-workspace-contract.d.mts +54 -0
  16. package/lib/dashboard/operate-review-display-workspace-contract.mjs +258 -0
  17. package/lib/dashboard/operate-review-payload-safety.d.mts +7 -0
  18. package/lib/dashboard/operate-review-payload-safety.mjs +42 -0
  19. package/lib/dashboard/operate-review-workspace-projection-v2.d.mts +218 -0
  20. package/lib/protocol/index.d.ts +2638 -0
  21. package/package.json +115 -0
  22. package/registries/commands.json +1952 -0
  23. package/registries/diagram-grammars.json +2407 -0
  24. package/registries/diagram-semantic-patterns.json +111 -0
  25. package/registries/output-paths.json +40 -0
  26. package/registries/outputs.json +864 -0
  27. package/registries/roles.json +661 -0
  28. package/registries/rules.json +261 -0
  29. package/registries/skill-host-profiles.json +247 -0
  30. package/registries/skill-modules.json +1112 -0
  31. package/registries/skill-routing.json +389 -0
  32. package/registries/skills.json +1545 -0
  33. package/registries/task-kinds.json +220 -0
  34. package/registry/adapters.json +110 -0
  35. package/registry/artifact-theme.json +60 -0
  36. package/registry/evaluation-graders.json +303 -0
  37. package/registry/evaluation-host-profiles.json +131 -0
  38. package/registry/frozen-commands.json +15 -0
  39. package/registry/landing-operations.json +20 -0
  40. package/registry/landing-workflows.json +70 -0
  41. package/registry/live-evidence-providers.json +1 -0
  42. package/registry/operate-v2-contracts.json +5965 -0
  43. package/registry/professional-skills.json +115 -0
  44. package/registry/roles.json +14 -0
  45. package/registry/ship-review-specialists.json +42 -0
  46. package/schemas/v1.0.0/design-approved.schema.json +35 -0
  47. package/schemas/v1.0.0/design-feedback.schema.json +169 -0
  48. package/schemas/v1.0.0/design-manifest.schema.json +90 -0
  49. package/schemas/v1.0.0/design-session.schema.json +73 -0
  50. package/schemas/v1.0.0/graph.schema.json +100 -0
  51. package/schemas/v1.0.0/pipeline-shipped.schema.json +154 -0
  52. package/schemas/v1.0.0/run-manifest.schema.json +81 -0
  53. package/schemas/v1.0.0/spec.schema.json +120 -0
  54. package/schemas/v1.0.0/stack.schema.json +184 -0
  55. package/schemas/v1.0.0/story.schema.json +95 -0
  56. package/schemas/v1.0.0/task.schema.json +171 -0
  57. package/schemas/v1.0.0/taste-profile.schema.json +96 -0
  58. package/schemas/v1.1.0/adapter-registry.schema.json +72 -0
  59. package/schemas/v1.1.0/artifact-envelope.schema.json +142 -0
  60. package/schemas/v1.1.0/artifact-paste.schema.json +49 -0
  61. package/schemas/v1.1.0/artifact-review.schema.json +97 -0
  62. package/schemas/v1.1.0/artifact-room-descriptor.schema.json +39 -0
  63. package/schemas/v1.1.0/artifact-room-event.schema.json +184 -0
  64. package/schemas/v1.1.0/artifact-room-signed-event.schema.json +58 -0
  65. package/schemas/v1.1.0/artifact-theme.schema.json +139 -0
  66. package/schemas/v1.1.0/browser-qa-gate.schema.json +21 -0
  67. package/schemas/v1.1.0/browser-qa-result.schema.json +24 -0
  68. package/schemas/v1.1.0/browser-qa-session.schema.json +12 -0
  69. package/schemas/v1.1.0/ecosystem-manifest.schema.json +46 -0
  70. package/schemas/v1.1.0/investigation-diagnosis.schema.json +12 -0
  71. package/schemas/v1.1.0/investigation-event.schema.json +15 -0
  72. package/schemas/v1.1.0/investigation-experiment.schema.json +17 -0
  73. package/schemas/v1.1.0/investigation-fix.schema.json +11 -0
  74. package/schemas/v1.1.0/investigation-hypothesis.schema.json +6 -0
  75. package/schemas/v1.1.0/investigation-observation.schema.json +10 -0
  76. package/schemas/v1.1.0/investigation-receipt.schema.json +7 -0
  77. package/schemas/v1.1.0/investigation-record.schema.json +7 -0
  78. package/schemas/v1.1.0/investigation-request.schema.json +34 -0
  79. package/schemas/v1.1.0/planning-review-event.schema.json +64 -0
  80. package/schemas/v1.1.0/planning-review-receipt.schema.json +19 -0
  81. package/schemas/v1.1.0/planning-review-record.schema.json +176 -0
  82. package/schemas/v1.1.0/planning-review-state.schema.json +19 -0
  83. package/schemas/v1.1.0/professional-skills.schema.json +105 -0
  84. package/schemas/v1.1.0/professional-specification.schema.json +111 -0
  85. package/schemas/v1.1.0/provenance-event.schema.json +82 -0
  86. package/schemas/v1.1.0/role-registry.schema.json +28 -0
  87. package/schemas/v1.1.0/runtime-lock.schema.json +39 -0
  88. package/schemas/v1.1.0/ship-closure.schema.json +353 -0
  89. package/schemas/v1.1.0/ship-review-specialist-registry.schema.json +29 -0
  90. package/schemas/v1.1.0/ship-risk-classification.schema.json +48 -0
  91. package/schemas/v1.1.0/specialist-review-result.schema.json +19 -0
  92. package/schemas/v1.10.0/design-review-bundle.schema.json +584 -0
  93. package/schemas/v1.10.0/design-review-context.schema.json +191 -0
  94. package/schemas/v1.10.0/design-review-handoff.schema.json +407 -0
  95. package/schemas/v1.10.0/design-review-metadata-payload.schema.json +106 -0
  96. package/schemas/v1.11.0/design-review-metadata-payload.schema.json +107 -0
  97. package/schemas/v1.12.0/enterprise-access-context.schema.json +217 -0
  98. package/schemas/v1.12.0/enterprise-agent-handoff.schema.json +508 -0
  99. package/schemas/v1.12.0/enterprise-artifact-revision.schema.json +87 -0
  100. package/schemas/v1.12.0/enterprise-change-proposal.schema.json +359 -0
  101. package/schemas/v1.12.0/enterprise-evidence-reference.schema.json +147 -0
  102. package/schemas/v1.12.0/enterprise-review-thread.schema.json +278 -0
  103. package/schemas/v1.12.0/enterprise-sync-state.schema.json +169 -0
  104. package/schemas/v1.2.0/adapter-registry.schema.json +83 -0
  105. package/schemas/v1.2.0/dashboard-bootstrap.schema.json +219 -0
  106. package/schemas/v1.2.0/ecosystem-release-operation.schema.json +90 -0
  107. package/schemas/v1.2.0/ecosystem-saga.schema.json +54 -0
  108. package/schemas/v1.2.0/evidence-diagnostic.schema.json +77 -0
  109. package/schemas/v1.2.0/guided-answer-envelope.schema.json +73 -0
  110. package/schemas/v1.2.0/guided-confirmation.schema.json +128 -0
  111. package/schemas/v1.2.0/guided-question.schema.json +242 -0
  112. package/schemas/v1.2.0/guided-questionnaire.schema.json +278 -0
  113. package/schemas/v1.2.0/guided-session.schema.json +141 -0
  114. package/schemas/v1.2.0/landing-confirmation.schema.json +17 -0
  115. package/schemas/v1.2.0/landing-event.schema.json +12 -0
  116. package/schemas/v1.2.0/landing-operation-registry.schema.json +16 -0
  117. package/schemas/v1.2.0/landing-phase-receipt.schema.json +19 -0
  118. package/schemas/v1.2.0/landing-plan.schema.json +36 -0
  119. package/schemas/v1.2.0/landing-receipt.schema.json +12 -0
  120. package/schemas/v1.2.0/landing-workflow-catalog.schema.json +143 -0
  121. package/schemas/v1.2.0/operate-action-display-workspace.schema.json +119 -0
  122. package/schemas/v1.2.0/operate-cycle-display-workspace.schema.json +109 -0
  123. package/schemas/v1.2.0/operate-executive-board-display-surface.schema.json +72 -0
  124. package/schemas/v1.2.0/operate-experience-audit-display-surface.schema.json +328 -0
  125. package/schemas/v1.2.0/operate-experience-display-surface.schema.json +46 -0
  126. package/schemas/v1.2.0/operate-experience-surface.schema.json +590 -0
  127. package/schemas/v1.2.0/operate-recovery-display-surface.schema.json +85 -0
  128. package/schemas/v1.2.0/structured-action.schema.json +91 -0
  129. package/schemas/v1.3.0/adapter-registry.schema.json +84 -0
  130. package/schemas/v1.3.0/ecosystem-manifest.schema.json +120 -0
  131. package/schemas/v1.3.0/release-compatibility-claim.schema.json +46 -0
  132. package/schemas/v1.3.0/release-ledger-receipt.schema.json +45 -0
  133. package/schemas/v1.3.0/release-ledger.schema.json +60 -0
  134. package/schemas/v1.4.0/adapter-registry.schema.json +62 -0
  135. package/schemas/v1.4.0/evaluation-aggregate-report.schema.json +166 -0
  136. package/schemas/v1.4.0/evaluation-budget.schema.json +49 -0
  137. package/schemas/v1.4.0/evaluation-corpus.schema.json +93 -0
  138. package/schemas/v1.4.0/evaluation-fixture.schema.json +64 -0
  139. package/schemas/v1.4.0/evaluation-gate-policy.schema.json +33 -0
  140. package/schemas/v1.4.0/evaluation-grader-registration.schema.json +141 -0
  141. package/schemas/v1.4.0/evaluation-grader-registry.schema.json +20 -0
  142. package/schemas/v1.4.0/evaluation-host-profile-registry.schema.json +20 -0
  143. package/schemas/v1.4.0/evaluation-host-profile.schema.json +73 -0
  144. package/schemas/v1.4.0/evaluation-observation.schema.json +76 -0
  145. package/schemas/v1.4.0/evaluation-run-result.schema.json +84 -0
  146. package/schemas/v1.4.0/evaluation-scenario.schema.json +231 -0
  147. package/schemas/v1.4.0/evaluation-waiver.schema.json +65 -0
  148. package/schemas/v1.4.0/skill-certification-receipt.schema.json +103 -0
  149. package/schemas/v1.5.0/command-catalog.schema.json +296 -0
  150. package/schemas/v1.5.0/common.schema.json +261 -0
  151. package/schemas/v1.5.0/generated-asset-manifest.schema.json +313 -0
  152. package/schemas/v1.5.0/implementation-result.schema.json +152 -0
  153. package/schemas/v1.5.0/migration-preservation-manifest.schema.json +749 -0
  154. package/schemas/v1.5.0/output-catalog.schema.json +210 -0
  155. package/schemas/v1.5.0/output-path-catalog.schema.json +81 -0
  156. package/schemas/v1.5.0/role-registry.schema.json +261 -0
  157. package/schemas/v1.5.0/rule-catalog.schema.json +130 -0
  158. package/schemas/v1.5.0/skill-catalog.schema.json +303 -0
  159. package/schemas/v1.5.0/task-kind-registry.schema.json +167 -0
  160. package/schemas/v1.5.0/task-manifest.schema.json +373 -0
  161. package/schemas/v1.5.0/task-output-manifest.schema.json +462 -0
  162. package/schemas/v1.6.0/common.schema.json +462 -0
  163. package/schemas/v1.6.0/diagram-consumer-reference.schema.json +167 -0
  164. package/schemas/v1.6.0/diagram-document.schema.json +699 -0
  165. package/schemas/v1.6.0/diagram-fidelity-report.schema.json +137 -0
  166. package/schemas/v1.6.0/diagram-manifest.schema.json +162 -0
  167. package/schemas/v1.6.0/diagram-quality-report.schema.json +148 -0
  168. package/schemas/v1.6.0/diagram-review-binding.schema.json +132 -0
  169. package/schemas/v1.6.0/diagram-semantic-pattern-registry.schema.json +90 -0
  170. package/schemas/v1.6.0/diagram-type-registry.schema.json +373 -0
  171. package/schemas/v1.6.0/generated-asset-manifest.schema.json +103 -0
  172. package/schemas/v1.6.0/skill-catalog.schema.json +84 -0
  173. package/schemas/v1.6.0/skill-completion-receipt.schema.json +72 -0
  174. package/schemas/v1.6.0/skill-consent-record.schema.json +74 -0
  175. package/schemas/v1.6.0/skill-host-profile-registry.schema.json +526 -0
  176. package/schemas/v1.6.0/skill-learning-record.schema.json +116 -0
  177. package/schemas/v1.6.0/skill-module-registry.schema.json +108 -0
  178. package/schemas/v1.6.0/skill-routing-registry.schema.json +93 -0
  179. package/schemas/v1.6.0/skill-session.schema.json +73 -0
  180. package/schemas/v1.6.0/skill-source.schema.json +169 -0
  181. package/schemas/v1.7.0/common.schema.json +464 -0
  182. package/schemas/v1.7.0/generated-asset-manifest.schema.json +103 -0
  183. package/schemas/v1.7.0/planning-id-sequence.schema.json +50 -0
  184. package/schemas/v1.7.0/request-authority.schema.json +71 -0
  185. package/schemas/v1.7.0/skill-catalog.schema.json +84 -0
  186. package/schemas/v1.7.0/skill-completion-receipt.schema.json +72 -0
  187. package/schemas/v1.7.0/skill-consent-record.schema.json +74 -0
  188. package/schemas/v1.7.0/skill-host-profile-registry.schema.json +526 -0
  189. package/schemas/v1.7.0/skill-learning-record.schema.json +116 -0
  190. package/schemas/v1.7.0/skill-module-registry.schema.json +108 -0
  191. package/schemas/v1.7.0/skill-routing-registry.schema.json +93 -0
  192. package/schemas/v1.7.0/skill-session.schema.json +73 -0
  193. package/schemas/v1.7.0/skill-source.schema.json +169 -0
  194. package/schemas/v1.7.0/spec.schema.json +117 -0
  195. package/schemas/v1.7.0/story.schema.json +107 -0
  196. package/schemas/v1.7.0/task.schema.json +217 -0
  197. package/schemas/v1.8.0/common.schema.json +96 -0
  198. package/schemas/v1.8.0/skill-package.schema.json +63 -0
  199. package/schemas/v1.8.0/utility-command-catalog.schema.json +82 -0
  200. package/schemas/v1.9.0/design-document.schema.json +306 -0
  201. package/schemas/v1.9.0/design-review-bundle.schema.json +346 -0
  202. package/schemas/v1.9.0/design-review-workspace.schema.json +108 -0
  203. package/schemas/v1.9.0/design-workspace-create.schema.json +170 -0
  204. package/schemas/v1.9.0/design-workspace-event.schema.json +97 -0
  205. package/schemas/v1.9.0/design-workspace-revision.schema.json +56 -0
  206. package/schemas/v2.0.0/agent-runtime-manifest.schema.json +59 -0
  207. package/schemas/v2.0.0/business-operating-snapshot-projection.schema.json +14 -0
  208. package/schemas/v2.0.0/operate-allowed-action.schema.json +181 -0
  209. package/schemas/v2.0.0/operate-api-envelope.schema.json +225 -0
  210. package/schemas/v2.0.0/operate-capability-provider-registration.schema.json +30 -0
  211. package/schemas/v2.0.0/operate-domain-registration.schema.json +284 -0
  212. package/schemas/v2.0.0/operate-evidence-provider-registration.schema.json +55 -0
  213. package/schemas/v2.0.0/operate-evidence-resolver-registration.schema.json +46 -0
  214. package/schemas/v2.0.0/operate-executor-registration.schema.json +27 -0
  215. package/schemas/v2.0.0/operate-experience-live-patch.schema.json +47 -0
  216. package/schemas/v2.0.0/operate-experience-preview.schema.json +51 -0
  217. package/schemas/v2.0.0/operate-experience-view.schema.json +281 -0
  218. package/schemas/v2.0.0/operate-live-evidence-provider-registration.schema.json +48 -0
  219. package/schemas/v2.0.0/operate-live-evidence-provider-registry.schema.json +17 -0
  220. package/schemas/v2.0.0/operate-metric-provider-registration.schema.json +12 -0
  221. package/schemas/v2.0.0/operate-policy-provider-registration.schema.json +33 -0
  222. package/schemas/v2.0.0/operate-review-bound-submission.schema.json +29 -0
  223. package/schemas/v2.0.0/operate-review-display-workspace.schema.json +390 -0
  224. package/schemas/v2.0.0/operate-snapshot-provider-registration.schema.json +16 -0
  225. package/schemas/v2.0.0/operate-tool-call.schema.json +172 -0
  226. package/schemas/v2.0.0/operate-verification-provider-registration.schema.json +12 -0
  227. package/schemas/v2.0.0/operating-action-policy.schema.json +31 -0
  228. package/schemas/v2.0.0/operating-action-verification-plan.schema.json +13 -0
  229. package/schemas/v2.0.0/operating-action.schema.json +86 -0
  230. package/schemas/v2.0.0/operating-advisor-result.schema.json +394 -0
  231. package/schemas/v2.0.0/operating-approval-record.schema.json +23 -0
  232. package/schemas/v2.0.0/operating-approval-requirement.schema.json +31 -0
  233. package/schemas/v2.0.0/operating-artifact.schema.json +52 -0
  234. package/schemas/v2.0.0/operating-assignment.schema.json +437 -0
  235. package/schemas/v2.0.0/operating-assumption.schema.json +16 -0
  236. package/schemas/v2.0.0/operating-capability-availability.schema.json +15 -0
  237. package/schemas/v2.0.0/operating-capability-grant.schema.json +14 -0
  238. package/schemas/v2.0.0/operating-challenger-review.schema.json +204 -0
  239. package/schemas/v2.0.0/operating-checkpoint.schema.json +43 -0
  240. package/schemas/v2.0.0/operating-claim.schema.json +17 -0
  241. package/schemas/v2.0.0/operating-connector-checkpoint.schema.json +28 -0
  242. package/schemas/v2.0.0/operating-context-capture.schema.json +74 -0
  243. package/schemas/v2.0.0/operating-cycle-input-binding.schema.json +30 -0
  244. package/schemas/v2.0.0/operating-cycle.schema.json +42 -0
  245. package/schemas/v2.0.0/operating-decision-ledger.schema.json +237 -0
  246. package/schemas/v2.0.0/operating-decision.schema.json +137 -0
  247. package/schemas/v2.0.0/operating-delivery-evidence.schema.json +46 -0
  248. package/schemas/v2.0.0/operating-delivery-route.schema.json +43 -0
  249. package/schemas/v2.0.0/operating-delta.schema.json +13 -0
  250. package/schemas/v2.0.0/operating-domain-projection.schema.json +15 -0
  251. package/schemas/v2.0.0/operating-event-trigger.schema.json +11 -0
  252. package/schemas/v2.0.0/operating-event.schema.json +505 -0
  253. package/schemas/v2.0.0/operating-evidence-candidate.schema.json +88 -0
  254. package/schemas/v2.0.0/operating-evidence-edge.schema.json +39 -0
  255. package/schemas/v2.0.0/operating-evidence-graph.schema.json +25 -0
  256. package/schemas/v2.0.0/operating-evidence-observation.schema.json +48 -0
  257. package/schemas/v2.0.0/operating-evidence-ref.schema.json +51 -0
  258. package/schemas/v2.0.0/operating-evidence-resolution.schema.json +54 -0
  259. package/schemas/v2.0.0/operating-execution-result.schema.json +23 -0
  260. package/schemas/v2.0.0/operating-executive-board.schema.json +78 -0
  261. package/schemas/v2.0.0/operating-finding.schema.json +116 -0
  262. package/schemas/v2.0.0/operating-governed-operation.schema.json +54 -0
  263. package/schemas/v2.0.0/operating-intelligence-input-bundle.schema.json +128 -0
  264. package/schemas/v2.0.0/operating-intelligence-plan.schema.json +302 -0
  265. package/schemas/v2.0.0/operating-learning-receipt.schema.json +31 -0
  266. package/schemas/v2.0.0/operating-learning.schema.json +13 -0
  267. package/schemas/v2.0.0/operating-live-evidence-consent-record.schema.json +42 -0
  268. package/schemas/v2.0.0/operating-live-evidence-ingestion.schema.json +31 -0
  269. package/schemas/v2.0.0/operating-measurement-plan.schema.json +18 -0
  270. package/schemas/v2.0.0/operating-measurement-schedule-receipt.schema.json +7 -0
  271. package/schemas/v2.0.0/operating-measurement-schedule.schema.json +16 -0
  272. package/schemas/v2.0.0/operating-metric-observation.schema.json +15 -0
  273. package/schemas/v2.0.0/operating-metric.schema.json +18 -0
  274. package/schemas/v2.0.0/operating-model-state.schema.json +36 -0
  275. package/schemas/v2.0.0/operating-objective.schema.json +21 -0
  276. package/schemas/v2.0.0/operating-origin.schema.json +31 -0
  277. package/schemas/v2.0.0/operating-outcome-evaluation.schema.json +46 -0
  278. package/schemas/v2.0.0/operating-outcome.schema.json +13 -0
  279. package/schemas/v2.0.0/operating-planning-proposal.schema.json +45 -0
  280. package/schemas/v2.0.0/operating-policy-evaluation.schema.json +28 -0
  281. package/schemas/v2.0.0/operating-review-read.schema.json +217 -0
  282. package/schemas/v2.0.0/operating-review-receipt.schema.json +64 -0
  283. package/schemas/v2.0.0/operating-review.schema.json +47 -0
  284. package/schemas/v2.0.0/operating-risk.schema.json +96 -0
  285. package/schemas/v2.0.0/operating-rollback-plan.schema.json +10 -0
  286. package/schemas/v2.0.0/operating-rollback-result.schema.json +20 -0
  287. package/schemas/v2.0.0/operating-runtime-state.schema.json +289 -0
  288. package/schemas/v2.0.0/operating-scenario.schema.json +13 -0
  289. package/schemas/v2.0.0/operating-snapshot.schema.json +38 -0
  290. package/schemas/v2.0.0/operating-submission.schema.json +34 -0
  291. package/schemas/v2.0.0/operating-trace-matrix.schema.json +176 -0
  292. package/schemas/v2.0.0/operating-work-change-set.schema.json +89 -0
  293. package/schemas/v2.0.0/operating-work-ledger.schema.json +40 -0
  294. package/schemas/v2.0.0/software-operating-snapshot-projection.schema.json +14 -0
  295. package/src/browser-contracts.d.mts +5 -0
  296. package/src/browser-contracts.mjs +40 -0
  297. package/src/canonical-json.d.mts +5 -0
  298. package/src/canonical-json.mjs +142 -0
  299. package/src/compat/live-evidence-v2.mjs +1019 -0
  300. package/src/compat/operate-experience-live-patch.mjs +66 -0
  301. package/src/compat/operating-planning-contracts.mjs +114 -0
  302. package/src/contracts.d.mts +42 -0
  303. package/src/contracts.mjs +990 -0
  304. package/src/design-contracts.d.mts +62 -0
  305. package/src/design-contracts.mjs +186 -0
  306. package/src/design-publication-contracts.d.mts +4 -0
  307. package/src/design-publication-contracts.mjs +85 -0
  308. package/src/diagram-contracts.d.mts +19 -0
  309. package/src/diagram-contracts.mjs +28 -0
  310. package/src/enterprise-contracts.d.mts +107 -0
  311. package/src/enterprise-contracts.mjs +463 -0
  312. package/src/errors.d.mts +11 -0
  313. package/src/errors.mjs +109 -0
  314. package/src/generated/canonical-registries.mjs +5554 -0
  315. package/src/generated/contract-catalog-v2.mjs +7076 -0
  316. package/src/generated/diagram-registries.mjs +2529 -0
  317. package/src/index.d.mts +10 -0
  318. package/src/index.mjs +40 -0
  319. package/src/json-schema.d.mts +7 -0
  320. package/src/json-schema.mjs +283 -0
  321. package/src/planning-contracts.d.mts +10 -0
  322. package/src/planning-contracts.mjs +53 -0
  323. package/src/registries.d.mts +40 -0
  324. package/src/registries.mjs +261 -0
  325. package/src/review-experience-contracts.d.mts +11 -0
  326. package/src/review-experience-contracts.mjs +103 -0
  327. package/src/semver.d.mts +2 -0
  328. package/src/semver.mjs +7 -0
  329. package/src/skill-source-contracts.d.mts +25 -0
  330. package/src/skill-source-contracts.mjs +105 -0
  331. package/src/task-contracts.d.mts +7 -0
  332. package/src/task-contracts.mjs +130 -0
  333. package/src/workspace-contracts.d.mts +26 -0
  334. package/src/workspace-contracts.mjs +83 -0
@@ -0,0 +1,1019 @@
1
+ import { assertProtocolArtifact } from '../contracts.mjs';
2
+ import { sha256Jcs } from '../canonical-json.mjs';
3
+
4
+ const HASH = /^sha256:[a-f0-9]{64}$/u;
5
+ const CHECKPOINT_TRANSITIONS = Object.freeze({
6
+ prepared: Object.freeze({ request: 'requesting', absent: 'absent' }),
7
+ requesting: Object.freeze({ materialize: 'materialized', absent: 'absent', uncertain: 'uncertain' }),
8
+ materialized: Object.freeze({ commit: 'committed', uncertain: 'uncertain' }),
9
+ committed: Object.freeze({}), absent: Object.freeze({}), uncertain: Object.freeze({}),
10
+ });
11
+ const SCHEDULE_TRANSITIONS = Object.freeze({
12
+ disabled: Object.freeze({ enable: 'enabled', revoke: 'revoked', expire: 'expired' }),
13
+ enabled: Object.freeze({ disable: 'disabled', pause: 'paused', revoke: 'revoked', expire: 'expired', complete: 'completed', 'run-issued': 'enabled' }),
14
+ paused: Object.freeze({ disable: 'disabled', resume: 'enabled', revoke: 'revoked', expire: 'expired' }),
15
+ revoked: Object.freeze({}), expired: Object.freeze({}), completed: Object.freeze({}),
16
+ });
17
+ const IMMUTABLE_CHECKPOINT_FIELDS = Object.freeze([
18
+ 'checkpointId', 'requestId', 'requestHash', 'effect', 'provider', 'providerRegistrationHash',
19
+ 'adapterVersion', 'sourceIdentityHash', 'credentialRefHash', 'boundsHash', 'queryHash',
20
+ 'windowHash', 'redactionVersion', 'createdAt',
21
+ ]);
22
+ const CONNECTOR_EVENT_FIELDS = Object.freeze([
23
+ 'eventId', 'eventHash', 'type', 'expectedGeneration', 'expectedCheckpointHash', 'at', 'patch',
24
+ ]);
25
+ const CONNECTOR_PATCH_FIELDS = Object.freeze({
26
+ request: Object.freeze(['consentRecordId', 'consentRecordHash', 'consentExpiresAt', 'runtimeCapabilityHash', 'health']),
27
+ materialize: Object.freeze(['artifactId', 'artifactHash', 'watermarkDigest', 'health']),
28
+ commit: Object.freeze(['watermarkDigest']),
29
+ absent: Object.freeze(['absenceDigest', 'health']),
30
+ uncertain: Object.freeze(['absenceDigest', 'health']),
31
+ });
32
+ const SCHEDULE_TRANSITION_FIELDS = Object.freeze([
33
+ 'receiptId', 'type', 'expectedGeneration', 'expectedScheduleHash', 'expectedState',
34
+ 'expectedRunCount', 'expectedNextEventIdentity', 'expectedNextDueAt', 'planHash',
35
+ 'consentRecordHashes', 'ceilingsHash', 'clockHash', 'eventId', 'nextEventIdentity',
36
+ 'nextDueAt', 'at',
37
+ ]);
38
+ const SOURCE_CONTRACTS = Object.freeze([
39
+ 'capacity-throughput', 'channel-economics', 'ci-test-evidence', 'competitor-positioning',
40
+ 'context-manifest', 'demand-market', 'finance-metrics', 'incident-history',
41
+ 'objective-metrics', 'operations-customer-health', 'planning-acceptance', 'prior-decisions',
42
+ 'product-activation', 'repository-architecture', 'retention-discovery', 'support-incidents',
43
+ ]);
44
+
45
+ export const LIVE_EVIDENCE_ABSENCE_KINDS_V2 = Object.freeze([
46
+ 'insufficient-evidence', 'missing-consent', 'missing-credential', 'provider-unavailable',
47
+ 'rate-limited', 'stale', 'unhealthy', 'unsupported',
48
+ ]);
49
+ export const LIVE_EVIDENCE_EFFECT_CLASS_V2 = 'provider-call';
50
+ export const LIVE_EVIDENCE_PORTABLE_AUTHORITY_V2 = 'none';
51
+ export const OPERATING_OUTCOME_EVALUATION_OPERATORS_V2 = Object.freeze(['gt', 'gte', 'lt', 'lte', 'eq', 'between']);
52
+
53
+ export class OperatingLiveEvidenceErrorV2 extends Error {
54
+ constructor(code, message, details = {}) {
55
+ super(message);
56
+ this.name = 'OperatingLiveEvidenceErrorV2';
57
+ this.code = code;
58
+ this.details = Object.freeze({ ...details });
59
+ }
60
+ }
61
+
62
+ function fail(code, message, details) {
63
+ throw new OperatingLiveEvidenceErrorV2(code, message, details);
64
+ }
65
+
66
+ function clone(value) {
67
+ return structuredClone(value);
68
+ }
69
+
70
+ function freeze(value) {
71
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
72
+ for (const nested of Object.values(value)) freeze(nested);
73
+ Object.freeze(value);
74
+ }
75
+ return value;
76
+ }
77
+
78
+ function safePortable(value, path = '$') {
79
+ if (typeof value === 'function' || typeof value === 'symbol' || typeof value === 'bigint') {
80
+ fail('LIVE_EVIDENCE_NON_PORTABLE', 'Live-evidence artifacts must be JSON-only.', { path });
81
+ }
82
+ if (Array.isArray(value)) {
83
+ value.forEach((entry, index) => safePortable(entry, `${path}[${index}]`));
84
+ return;
85
+ }
86
+ if (value && typeof value === 'object') {
87
+ Object.entries(value).forEach(([key, entry]) => safePortable(entry, `${path}.${key}`));
88
+ return;
89
+ }
90
+ if (typeof value !== 'string') return;
91
+ if (value.includes('\u0000') || value.startsWith('/') || value.startsWith('~')
92
+ || /(?:^|[\s"'`(])\/(?:Users|home|private|var|etc|tmp)\//u.test(value)
93
+ || /(?:^|[\s"'`(])[A-Za-z]:[\\/][^\s]+/u.test(value)
94
+ || value.split(/[\\/]/u).includes('..')
95
+ || /-----BEGIN [A-Z ]*PRIVATE KEY-----/u.test(value)
96
+ || /\b(?:Bearer|Basic)\s+[A-Za-z0-9+/=._-]+/u.test(value)
97
+ || /\b(?:api[-_ ]?key|access[-_ ]?token|client[-_ ]?secret|password|passwd|secret|token)\s*[:=]\s*[^\s]+/iu.test(value)) {
98
+ fail('LIVE_EVIDENCE_UNSAFE', 'Live-evidence artifacts contain private path or credential material.', { path });
99
+ }
100
+ }
101
+
102
+ function protocol(kind, value, protocolVersion = '2.0.0') {
103
+ try {
104
+ assertProtocolArtifact(kind, value, { protocolVersion });
105
+ } catch (cause) {
106
+ fail('LIVE_EVIDENCE_CONTRACT_INVALID', `Invalid ${kind} contract.`, { cause: cause.code ?? cause.message });
107
+ }
108
+ }
109
+
110
+ function selfHash(record, field) {
111
+ const { [field]: claimed, ...body } = record;
112
+ const expected = sha256Jcs(body);
113
+ if (!HASH.test(claimed ?? '') || claimed !== expected) {
114
+ fail('LIVE_EVIDENCE_DIGEST_MISMATCH', `${field} does not bind the exact canonical record.`, { field, expected, actual: claimed ?? null });
115
+ }
116
+ return expected;
117
+ }
118
+
119
+ function exactDigest(record, expected, label) {
120
+ const actual = sha256Jcs(record);
121
+ if (actual !== expected) fail('LIVE_EVIDENCE_BASE_RECORD_MISMATCH', `${label} bytes do not match their bound digest.`, { expected, actual });
122
+ }
123
+
124
+ function same(left, right, label) {
125
+ if (sha256Jcs(left) !== sha256Jcs(right)) fail('LIVE_EVIDENCE_BINDING_MISMATCH', `${label} binding changed.`, { left, right });
126
+ }
127
+
128
+ function assertExactKeys(value, expected, label) {
129
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
130
+ fail('LIVE_EVIDENCE_CONTRACT_INVALID', `${label} must be one closed object.`);
131
+ }
132
+ const actual = Object.keys(value).sort();
133
+ const wanted = [...expected].sort();
134
+ if (!sameValue(actual, wanted)) {
135
+ fail('LIVE_EVIDENCE_CONTRACT_INVALID', `${label} contains missing or unsupported fields.`, { actual, expected: wanted });
136
+ }
137
+ }
138
+
139
+ function currentAt(timestamp, expiresAt, label) {
140
+ if (!(Date.parse(timestamp) < Date.parse(expiresAt))) {
141
+ fail('LIVE_EVIDENCE_STALE', `${label} is not current at the bound time.`, { timestamp, expiresAt });
142
+ }
143
+ }
144
+
145
+ function exactBaseRecord(options, singularKey, pluralKey, reference, identityFields, label) {
146
+ const supplied = [
147
+ options?.[singularKey],
148
+ ...(Array.isArray(options?.[pluralKey]) ? options[pluralKey] : []),
149
+ ].filter((entry) => entry !== undefined);
150
+ const matching = supplied.filter((entry) => identityFields.every((field) => entry?.[field] === reference[field]));
151
+ const unique = [...new Map(matching.map((entry) => [sha256Jcs(entry), entry])).values()];
152
+ if (unique.length !== 1) {
153
+ fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', `${label} requires exactly one matching frozen base record.`, {
154
+ identity: Object.fromEntries(identityFields.map((field) => [field, reference[field]])),
155
+ matches: unique.length,
156
+ });
157
+ }
158
+ return unique[0];
159
+ }
160
+
161
+ function assertProviderBaseRecords(record, options = {}) {
162
+ const baseEvidenceProvider = exactBaseRecord(options, 'baseEvidenceProvider', 'baseEvidenceProviders',
163
+ record.baseEvidenceProvider, ['providerId', 'providerVersion'], 'Live provider registration');
164
+ protocol('operate-evidence-provider-registration', baseEvidenceProvider);
165
+ exactDigest(baseEvidenceProvider, record.baseEvidenceProvider.recordDigest, 'Evidence-provider registration');
166
+ const baseResolver = exactBaseRecord(options, 'baseResolver', 'baseResolvers',
167
+ record.baseResolver, ['resolverId', 'resolverVersion'], 'Live provider registration');
168
+ protocol('operate-evidence-resolver-registration', baseResolver);
169
+ exactDigest(baseResolver, record.baseResolver.recordDigest, 'Evidence-resolver registration');
170
+ if (!baseResolver.supportedEvidenceKinds.includes('operate-artifact')) {
171
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'The base resolver must use the canonical operate-artifact boundary.');
172
+ }
173
+ }
174
+
175
+ function assertLiveEvidenceProviderRegistrationStructureV2(record) {
176
+ protocol('operate-live-evidence-provider-registration', record);
177
+ safePortable(record);
178
+ selfHash(record, 'registrationHash');
179
+ if (record.effects.length !== 1 || record.effects[0] !== 'provider-call'
180
+ || record.consent.required !== true || record.consent.mode !== 'named-owner-current'
181
+ || record.consent.runtimeCapability !== 'opaque-local-preissued'
182
+ || record.consent.noDefaultChoice !== true || record.consent.previewEffect !== 'none') {
183
+ fail('LIVE_EVIDENCE_AUTHORITY_INVALID', 'Registration must require current consent and a pre-issued opaque capability for provider-call.');
184
+ }
185
+ return freeze(clone(record));
186
+ }
187
+
188
+ export function assertLiveEvidenceProviderRegistrationV2(record, options = {}) {
189
+ const normalized = assertLiveEvidenceProviderRegistrationStructureV2(record);
190
+ assertProviderBaseRecords(normalized, options);
191
+ return normalized;
192
+ }
193
+
194
+ export function assertLiveEvidenceProviderRegistryV2(registry, options = {}) {
195
+ protocol('operate-live-evidence-provider-registry', registry);
196
+ safePortable(registry);
197
+ selfHash(registry, 'registryHash');
198
+ let previous = null;
199
+ const identities = new Set();
200
+ for (const provider of registry.providers) {
201
+ assertLiveEvidenceProviderRegistrationV2(provider, options);
202
+ const identity = `${provider.providerId}@${provider.providerVersion}`;
203
+ if (identities.has(identity) || (previous !== null && identity.localeCompare(previous) <= 0)) {
204
+ fail('LIVE_EVIDENCE_REGISTRY_CONFLICT', 'Provider registry membership must be unique and canonically ordered.', { identity });
205
+ }
206
+ identities.add(identity);
207
+ previous = identity;
208
+ }
209
+ return freeze(clone(registry));
210
+ }
211
+
212
+ export function registerLiveEvidenceProviderV2(registry, registration, options = {}) {
213
+ const current = assertLiveEvidenceProviderRegistryV2(registry, options);
214
+ const provider = assertLiveEvidenceProviderRegistrationV2(registration, options);
215
+ const index = current.providers.findIndex((entry) => entry.providerId === provider.providerId && entry.providerVersion === provider.providerVersion);
216
+ if (index >= 0) {
217
+ if (current.providers[index].registrationHash !== provider.registrationHash) {
218
+ fail('LIVE_EVIDENCE_REGISTRY_CONFLICT', 'Divergent provider bytes cannot reuse one provider identity.');
219
+ }
220
+ return current;
221
+ }
222
+ const next = clone(current);
223
+ next.providers.push(clone(provider));
224
+ next.providers.sort((left, right) => `${left.providerId}@${left.providerVersion}`.localeCompare(`${right.providerId}@${right.providerVersion}`));
225
+ next.registryHash = sha256Jcs(Object.fromEntries(Object.entries(next).filter(([key]) => key !== 'registryHash')));
226
+ return assertLiveEvidenceProviderRegistryV2(next, options);
227
+ }
228
+
229
+ export function assertOperatingLiveEvidenceConsentRecordV2(record) {
230
+ protocol('operating-live-evidence-consent-record', record);
231
+ safePortable(record);
232
+ selfHash(record, 'consentHash');
233
+ if (record.authority !== 'none' || record.docket.defaultChoice !== null || record.docket.cancelEffect !== 'none') {
234
+ fail('LIVE_EVIDENCE_AUTHORITY_INVALID', 'Consent proof and docket cannot grant access authority or preselect an owner choice.');
235
+ }
236
+ if (sha256Jcs(record.docket) !== record.docketHash) fail('LIVE_EVIDENCE_DIGEST_MISMATCH', 'Consent docket bytes changed.');
237
+ same(record.docket.provider, record.provider, 'Consent provider');
238
+ for (const field of ['adapterVersion', 'accountIdentityHash', 'selectors', 'scopes', 'queryHash', 'window', 'ceilings', 'classification', 'purpose', 'expiresAt']) {
239
+ same(record.docket[field], record[field], `Consent ${field}`);
240
+ }
241
+ currentAt(record.issuedAt, record.expiresAt, 'Consent');
242
+ return freeze(clone(record));
243
+ }
244
+
245
+ export function assertOperatingLiveEvidenceIngestionV2(record, {
246
+ liveProviderRegistration, baseEvidenceProvider, baseResolver, assignment,
247
+ consentRecord, connectorCheckpoint,
248
+ } = {}) {
249
+ protocol('operating-live-evidence-ingestion', record);
250
+ safePortable(record);
251
+ selfHash(record, 'ingestionHash');
252
+ if (!SOURCE_CONTRACTS.includes(record.sourceContract.id)) fail('LIVE_EVIDENCE_SOURCE_CONTRACT_INVALID', 'Ingestion source contract is not accepted by the frozen operate-artifact resolver.');
253
+ if (record.status === 'materialized') {
254
+ if (record.health.status !== 'available' || record.absences.length !== 0 || record.records.length === 0) {
255
+ fail('LIVE_EVIDENCE_FALSE_PASS', 'Materialized evidence requires available health, records, and no absence.');
256
+ }
257
+ if (record.materialization.contentDigest !== deriveOperatingLiveEvidenceContentDigestV2(record)) {
258
+ fail('LIVE_EVIDENCE_DIGEST_MISMATCH', 'Materialization contentDigest does not bind the exact redacted record manifest.');
259
+ }
260
+ currentAt(record.sourceTiming.capturedAt, record.health.freshUntil, 'Provider health');
261
+ currentAt(record.sourceTiming.capturedAt, record.sourceTiming.freshUntil, 'Source evidence');
262
+ }
263
+ if (record.assignment.requestHash !== record.requestHash || record.submission.operation !== 'operate.assignment.submit') {
264
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'Ingestion must bind its issued Assignment request and normal submission operation.');
265
+ }
266
+ if (record.assignment.assignmentRevision !== 1) {
267
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'The immutable Protocol 2.0 Assignment revision must be one.');
268
+ }
269
+ if (record.materialization !== null) {
270
+ for (const field of ['artifactId', 'artifactCreatedEventId', 'evidenceRefId']) same(record.materialization[field], record.submission[field], `Materialization ${field}`);
271
+ }
272
+ for (const candidate of record.evidenceCandidates) {
273
+ if (candidate.evidenceKind !== 'operate-artifact' || candidate.sourceArtifactId !== record.submission.artifactId
274
+ || candidate.locator?.artifactId !== record.submission.artifactId
275
+ || candidate.locator?.expectedArtifactType !== 'live-evidence-ingestion'
276
+ || candidate.locator?.expectedSchemaId !== 'operating-live-evidence-ingestion'
277
+ || candidate.locator?.expectedSchemaVersion !== '2.0.0'
278
+ || candidate.provider.id !== record.providerRegistration.providerId
279
+ || candidate.provider.version !== record.providerRegistration.providerVersion
280
+ || candidate.resolver.id !== record.resolverRegistration.resolverId
281
+ || candidate.resolver.version !== record.resolverRegistration.resolverVersion) {
282
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'Evidence candidates must reference the pre-issued accepted ingestion Artifact.');
283
+ }
284
+ }
285
+ for (const link of record.evidenceClaimLinks) {
286
+ if (link.sourceArtifactId !== record.submission.artifactId
287
+ || !record.evidenceCandidates.some((candidate) => candidate.candidateId === link.candidateId)) {
288
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'Evidence claim links must bind a declared candidate and accepted source Artifact.');
289
+ }
290
+ }
291
+ if (liveProviderRegistration !== undefined) {
292
+ const registration = assertLiveEvidenceProviderRegistrationV2(liveProviderRegistration, { baseEvidenceProvider, baseResolver });
293
+ if (registration.registrationHash !== record.providerRegistrationHash) fail('LIVE_EVIDENCE_BASE_RECORD_MISMATCH', 'Live provider registration changed.');
294
+ same([registration.providerId, registration.providerVersion, registration.adapterVersion], [record.provider.id, record.provider.version, record.adapterVersion], 'Live provider');
295
+ same(registration.baseEvidenceProvider, record.providerRegistration, 'Base evidence-provider reference');
296
+ same(registration.baseResolver, record.resolverRegistration, 'Base evidence-resolver reference');
297
+ }
298
+ if (record.resolverRegistrationHash !== record.resolverRegistration.recordDigest) fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Resolver registration digest fields disagree.');
299
+ if (baseEvidenceProvider !== undefined) {
300
+ protocol('operate-evidence-provider-registration', baseEvidenceProvider);
301
+ exactDigest(baseEvidenceProvider, record.providerRegistration.recordDigest, 'Base evidence-provider registration');
302
+ }
303
+ if (baseResolver !== undefined) {
304
+ protocol('operate-evidence-resolver-registration', baseResolver);
305
+ exactDigest(baseResolver, record.resolverRegistration.recordDigest, 'Base evidence-resolver registration');
306
+ if (!baseResolver.supportedSourceContracts.some((entry) => sameValue(entry, record.sourceContract))) {
307
+ fail('LIVE_EVIDENCE_SOURCE_CONTRACT_INVALID', 'The selected resolver does not support the ingestion source contract.');
308
+ }
309
+ }
310
+ if (assignment !== undefined) {
311
+ protocol('operating-assignment', assignment);
312
+ exactDigest(assignment, record.assignment.assignmentHash, 'Issued Assignment');
313
+ if (assignment.assignmentId !== record.assignment.assignmentId || assignment.assignmentKind !== 'verification'
314
+ || assignment.outputContract.schemaId !== 'operating-live-evidence-ingestion'
315
+ || assignment.outputContract.schemaVersion !== '2.0.0') {
316
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'The canonical Assignment does not authorize this ingestion output.');
317
+ }
318
+ }
319
+ if (record.status === 'materialized' && (assignment === undefined || consentRecord === undefined
320
+ || connectorCheckpoint === undefined || liveProviderRegistration === undefined
321
+ || baseEvidenceProvider === undefined || baseResolver === undefined)) {
322
+ fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'Materialized ingestion requires exact Assignment, consent, checkpoint, and provider registrations.');
323
+ }
324
+ if (consentRecord !== undefined) {
325
+ const consent = assertOperatingLiveEvidenceConsentRecordV2(consentRecord);
326
+ if (consent.consentHash !== record.consentRecordHash) fail('LIVE_EVIDENCE_BASE_RECORD_MISMATCH', 'Consent record changed.');
327
+ same([consent.provider, consent.adapterVersion, consent.classification], [record.provider, record.adapterVersion, record.classification], 'Consent provider');
328
+ if (sha256Jcs(consent.ceilings) !== record.boundsHash) fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Consent ceilings changed.');
329
+ currentAt(record.sourceTiming.capturedAt, consent.expiresAt, 'Consent');
330
+ }
331
+ if (connectorCheckpoint !== undefined) {
332
+ const checkpoint = assertOperatingConnectorCheckpointV2(connectorCheckpoint);
333
+ if (checkpoint.state !== 'requesting' || checkpoint.checkpointId !== record.checkpoint.checkpointId
334
+ || checkpoint.checkpointHash !== record.checkpoint.checkpointHash
335
+ || checkpoint.predecessorDigest !== record.checkpoint.predecessorDigest
336
+ || checkpoint.requestId !== record.requestId || checkpoint.providerRegistrationHash !== record.providerRegistrationHash
337
+ || checkpoint.consentRecordHash !== record.consentRecordHash
338
+ || checkpoint.runtimeCapabilityHash !== record.runtimeCapabilityHash
339
+ || checkpoint.boundsHash !== record.boundsHash || !sameValue(checkpoint.provider, record.provider)
340
+ || checkpoint.adapterVersion !== record.adapterVersion || !sameValue(checkpoint.health, record.health)) {
341
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Ingestion changed its exact requesting connector checkpoint.');
342
+ }
343
+ if (consentRecord !== undefined && (checkpoint.queryHash !== consentRecord.queryHash
344
+ || checkpoint.windowHash !== sha256Jcs(consentRecord.window)
345
+ || checkpoint.boundsHash !== sha256Jcs(consentRecord.ceilings))) {
346
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Checkpoint query, window, or bounds changed from owner consent.');
347
+ }
348
+ }
349
+ if (assignment !== undefined && consentRecord !== undefined && connectorCheckpoint !== undefined) {
350
+ const expectedRequestHash = deriveOperatingLiveEvidenceRequestHashV2({
351
+ assignment, consentRecord, connectorCheckpoint, liveProviderRegistration, sourceContract: record.sourceContract,
352
+ baseEvidenceProvider, baseResolver, classification: record.classification,
353
+ });
354
+ if (record.requestHash !== expectedRequestHash || connectorCheckpoint.requestHash !== expectedRequestHash) {
355
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Ingestion request is not the engine-derived Assignment/provider request.');
356
+ }
357
+ }
358
+ return freeze(clone(record));
359
+ }
360
+
361
+ function sameValue(left, right) {
362
+ return sha256Jcs(left) === sha256Jcs(right);
363
+ }
364
+
365
+ export function deriveOperatingLiveEvidenceRequestHashV2({
366
+ assignment, consentRecord, connectorCheckpoint, liveProviderRegistration, baseEvidenceProvider, baseResolver,
367
+ sourceContract, classification,
368
+ }) {
369
+ protocol('operating-assignment', assignment);
370
+ const consent = assertOperatingLiveEvidenceConsentRecordV2(consentRecord);
371
+ const checkpoint = assertOperatingConnectorCheckpointV2(connectorCheckpoint);
372
+ const registration = assertLiveEvidenceProviderRegistrationV2(liveProviderRegistration, {
373
+ baseEvidenceProvider, baseResolver,
374
+ });
375
+ return sha256Jcs({
376
+ domain: 'openplanr-operating-live-evidence-request-v2',
377
+ assignment: { assignmentId: assignment.assignmentId, assignmentHash: sha256Jcs(assignment) },
378
+ requestId: checkpoint.requestId,
379
+ provider: checkpoint.provider,
380
+ providerRegistrationHash: registration.registrationHash,
381
+ adapterVersion: checkpoint.adapterVersion,
382
+ consentRecordHash: consent.consentHash,
383
+ runtimeCapabilityHash: checkpoint.runtimeCapabilityHash,
384
+ sourceIdentityHash: checkpoint.sourceIdentityHash,
385
+ credentialRefHash: checkpoint.credentialRefHash,
386
+ boundsHash: checkpoint.boundsHash,
387
+ queryHash: checkpoint.queryHash,
388
+ windowHash: checkpoint.windowHash,
389
+ sourceContract,
390
+ classification,
391
+ });
392
+ }
393
+
394
+ export function deriveOperatingLiveEvidenceContentDigestV2(record) {
395
+ if (record?.materialization === null || record?.materialization === undefined || !Array.isArray(record?.records)) {
396
+ fail('LIVE_EVIDENCE_CONTRACT_INVALID', 'Materialized live evidence requires a redacted record manifest.');
397
+ }
398
+ const records = clone(record.records).sort((left, right) => left.recordIdentityHash.localeCompare(right.recordIdentityHash));
399
+ return sha256Jcs({
400
+ domain: 'openplanr-operating-live-evidence-redacted-content-v2',
401
+ redactionVersion: record.materialization.redactionVersion,
402
+ records,
403
+ });
404
+ }
405
+
406
+ export function assertAcceptedLiveEvidenceSourceV2(record, {
407
+ assignment, artifact, submittedEvent, artifactCreatedEvent, validatedEvent,
408
+ ...options
409
+ }) {
410
+ const ingestion = assertOperatingLiveEvidenceIngestionV2(record, { assignment, ...options });
411
+ protocol('operating-artifact', artifact);
412
+ protocol('operating-event', submittedEvent);
413
+ protocol('operating-event', artifactCreatedEvent);
414
+ protocol('operating-event', validatedEvent);
415
+ if (artifact.artifactId !== ingestion.submission.artifactId || artifact.assignmentId !== ingestion.assignment.assignmentId
416
+ || artifact.artifactType !== 'live-evidence-ingestion'
417
+ || artifact.schemaId !== 'operating-live-evidence-ingestion' || artifact.artifactSchemaVersion !== '2.0.0'
418
+ || artifact.canonicalHash !== sha256Jcs(ingestion)) {
419
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'Accepted Artifact does not bind the exact ingestion bytes and Assignment.');
420
+ }
421
+ const eventBindings = [[submittedEvent, 'assignment.submitted', ingestion.submission.submittedEventId], [artifactCreatedEvent, 'artifact.created', ingestion.submission.artifactCreatedEventId], [validatedEvent, 'assignment.validated', ingestion.submission.validatedEventId]];
422
+ for (const [event, type, eventId] of eventBindings) {
423
+ selfHash(event, 'eventHash');
424
+ if (event.eventId !== eventId || event.type !== type || event.cycleId !== artifact.cycleId
425
+ || event.timestamp !== artifact.createdAt || event.actor.kind !== 'runtime' || event.actor.id !== 'openplanr') {
426
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'Accepted Event lineage does not match the pre-issued ingestion identities.', { type });
427
+ }
428
+ }
429
+ if (submittedEvent.entityId !== assignment.assignmentId || artifactCreatedEvent.entityId !== artifact.artifactId
430
+ || validatedEvent.entityId !== assignment.assignmentId
431
+ || artifactCreatedEvent.causationId !== submittedEvent.eventId || validatedEvent.causationId !== artifactCreatedEvent.eventId
432
+ || submittedEvent.correlationId !== artifactCreatedEvent.correlationId || submittedEvent.correlationId !== validatedEvent.correlationId
433
+ || artifactCreatedEvent.previousEventHash !== submittedEvent.eventHash || validatedEvent.previousEventHash !== artifactCreatedEvent.eventHash
434
+ || artifactCreatedEvent.sequence !== submittedEvent.sequence + 1 || validatedEvent.sequence !== artifactCreatedEvent.sequence + 1
435
+ || !sameValue(submittedEvent.payload, {
436
+ assignmentId: assignment.assignmentId, submissionId: ingestion.submission.submissionId,
437
+ rawHash: artifact.rawHash, canonicalHash: artifact.canonicalHash, sizeBytes: artifact.sizeBytes,
438
+ mediaType: artifact.mediaType, encoding: artifact.encoding,
439
+ }) || !sameValue(artifactCreatedEvent.payload, artifact)
440
+ || validatedEvent.payload.assignmentId !== assignment.assignmentId
441
+ || validatedEvent.payload.submissionId !== ingestion.submission.submissionId
442
+ || validatedEvent.payload.artifactId !== artifact.artifactId
443
+ || typeof validatedEvent.payload.validatorVersion !== 'string') {
444
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'Runtime-created Assignment/Event payload or chain changed.');
445
+ }
446
+ return ingestion;
447
+ }
448
+
449
+ export function assertAcceptedLiveEvidenceBridgeV2(record, {
450
+ assignment, artifact, submittedEvent, artifactCreatedEvent, validatedEvent, evidenceRef,
451
+ evidenceArtifact, evidenceResolution, resultCheckpoint, checkpointHistory = [],
452
+ ...options
453
+ }) {
454
+ const ingestion = assertAcceptedLiveEvidenceSourceV2(record, {
455
+ assignment, artifact, submittedEvent, artifactCreatedEvent, validatedEvent, ...options,
456
+ });
457
+ protocol('operating-evidence-ref', evidenceRef);
458
+ protocol('operating-artifact', evidenceArtifact);
459
+ protocol('operating-evidence-resolution', evidenceResolution);
460
+ const candidate = ingestion.evidenceCandidates.find((entry) => entry.candidateId === evidenceRef.candidateId);
461
+ const expectedLocator = candidate === undefined ? null : {
462
+ artifactId: candidate.locator.artifactId,
463
+ expectedArtifactType: candidate.locator.expectedArtifactType,
464
+ expectedSchemaId: candidate.locator.expectedSchemaId,
465
+ expectedSchemaVersion: candidate.locator.expectedSchemaVersion,
466
+ expectedRawHash: artifact.rawHash,
467
+ expectedCanonicalHash: artifact.canonicalHash,
468
+ };
469
+ if (evidenceRef.evidenceRefId !== ingestion.submission.evidenceRefId || evidenceRef.sourceArtifactId !== artifact.artifactId
470
+ || candidate === undefined || evidenceRef.evidenceKind !== 'operate-artifact'
471
+ || !sameValue(evidenceRef.sourceContract, ingestion.sourceContract)
472
+ || (candidate.locator.expectedRawHash !== undefined && candidate.locator.expectedRawHash !== artifact.rawHash)
473
+ || (candidate.locator.expectedCanonicalHash !== undefined && candidate.locator.expectedCanonicalHash !== artifact.canonicalHash)
474
+ || !sameValue(evidenceRef.locator, expectedLocator)
475
+ || !sameValue(evidenceRef.provider, candidate.provider) || !sameValue(evidenceRef.resolver, candidate.resolver)
476
+ || evidenceRef.classification !== ingestion.classification
477
+ || evidenceRef.evidenceArtifactId !== evidenceArtifact.artifactId
478
+ || evidenceRef.evidenceArtifactRawHash !== evidenceArtifact.rawHash
479
+ || evidenceRef.evidenceArtifactCanonicalHash !== evidenceArtifact.canonicalHash
480
+ || evidenceArtifact.artifactId === artifact.artifactId
481
+ || evidenceArtifact.artifactType !== 'evidence-snapshot'
482
+ || evidenceArtifact.assignmentId !== artifact.assignmentId || evidenceArtifact.cycleId !== artifact.cycleId
483
+ || evidenceArtifact.scopeId !== artifact.scopeId || evidenceArtifact.domainId !== artifact.domainId
484
+ || evidenceArtifact.domainVersion !== artifact.domainVersion
485
+ || evidenceArtifact.schemaId !== 'operating-evidence-snapshot' || evidenceArtifact.artifactSchemaVersion !== '1.0.0'
486
+ || evidenceArtifact.mediaType !== 'application/octet-stream' || evidenceArtifact.encoding !== 'binary'
487
+ || evidenceArtifact.rawHash !== artifact.rawHash || evidenceArtifact.canonicalHash !== artifact.canonicalHash
488
+ || evidenceArtifact.sizeBytes !== artifact.sizeBytes || evidenceArtifact.sensitivity !== artifact.sensitivity
489
+ || evidenceArtifact.retentionClass !== artifact.retentionClass
490
+ || !sameValue(evidenceArtifact.inputArtifactIds, [artifact.artifactId])
491
+ || evidenceArtifact.createdAt !== evidenceRef.resolvedAt
492
+ || !sameValue(evidenceArtifact.producer, { actorId: 'openplanr', roleId: 'evidence-resolver', runtime: 'openplanr' })) {
493
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'Accepted EvidenceRef does not bind the ingestion Artifact.');
494
+ }
495
+ if (evidenceResolution.outcome !== 'resolved' || evidenceResolution.error !== null
496
+ || evidenceResolution.candidateId !== candidate.candidateId
497
+ || evidenceResolution.sourceArtifactId !== artifact.artifactId
498
+ || evidenceResolution.evidenceKind !== 'operate-artifact'
499
+ || !sameValue(evidenceResolution.sourceContract, ingestion.sourceContract)
500
+ || !sameValue(evidenceResolution.provider, candidate.provider)
501
+ || !sameValue(evidenceResolution.resolver, candidate.resolver)
502
+ || evidenceResolution.evidenceRefId !== evidenceRef.evidenceRefId
503
+ || evidenceResolution.evidenceArtifactId !== evidenceArtifact.artifactId
504
+ || evidenceResolution.resolvedAt !== evidenceRef.resolvedAt
505
+ || evidenceArtifact.createdAt !== evidenceResolution.resolvedAt
506
+ || evidenceResolution.scopeId !== artifact.scopeId || evidenceResolution.domainId !== artifact.domainId
507
+ || evidenceResolution.domainVersion !== artifact.domainVersion) {
508
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'Evidence resolution changed the runtime-built snapshot or EvidenceRef custody.');
509
+ }
510
+ if (resultCheckpoint === undefined) fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'Accepted ingestion requires the exact post-acceptance connector checkpoint.');
511
+ const requestCheckpoint = assertOperatingConnectorCheckpointV2(options.connectorCheckpoint);
512
+ const chain = [requestCheckpoint, ...checkpointHistory.map(assertOperatingConnectorCheckpointV2), assertOperatingConnectorCheckpointV2(resultCheckpoint)];
513
+ for (let index = 1; index < chain.length; index += 1) {
514
+ if (chain[index].predecessorDigest !== chain[index - 1].checkpointHash
515
+ || chain[index].generation !== chain[index - 1].generation + 1) fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'Connector checkpoint lineage is not contiguous.');
516
+ }
517
+ const terminalCheckpoint = chain.at(-1);
518
+ if (!['materialized', 'committed'].includes(terminalCheckpoint.state)
519
+ || terminalCheckpoint.artifactId !== artifact.artifactId || terminalCheckpoint.artifactHash !== artifact.canonicalHash
520
+ || terminalCheckpoint.requestHash !== ingestion.requestHash
521
+ || terminalCheckpoint.consentRecordHash !== ingestion.consentRecordHash
522
+ || terminalCheckpoint.runtimeCapabilityHash !== ingestion.runtimeCapabilityHash
523
+ || !sameValue(terminalCheckpoint.health, ingestion.health)) {
524
+ fail('LIVE_EVIDENCE_BRIDGE_INVALID', 'Post-acceptance checkpoint changed Artifact or request custody.');
525
+ }
526
+ return ingestion;
527
+ }
528
+
529
+ export function assertOperatingConnectorCheckpointV2(checkpoint) {
530
+ protocol('operating-connector-checkpoint', checkpoint);
531
+ safePortable(checkpoint);
532
+ selfHash(checkpoint, 'checkpointHash');
533
+ if (checkpoint.generation === 0 && (checkpoint.state !== 'prepared' || checkpoint.predecessorDigest !== null || checkpoint.lastEventId !== null || checkpoint.lastEventHash !== null)) {
534
+ fail('LIVE_EVIDENCE_CHECKPOINT_INVALID', 'Generation-zero connector checkpoint must be an unadvanced prepared record.');
535
+ }
536
+ if (checkpoint.state === 'prepared' && (checkpoint.consentRecordId !== null || checkpoint.consentRecordHash !== null
537
+ || checkpoint.consentExpiresAt !== null || checkpoint.runtimeCapabilityHash !== null
538
+ || checkpoint.artifactId !== null || checkpoint.artifactHash !== null || checkpoint.absenceDigest !== null)) {
539
+ fail('LIVE_EVIDENCE_CHECKPOINT_INVALID', 'Prepared checkpoint cannot claim consent, capability, Artifact, or absence custody.');
540
+ }
541
+ if (['requesting', 'materialized', 'committed'].includes(checkpoint.state)) {
542
+ if (checkpoint.consentRecordId === null || checkpoint.consentRecordHash === null
543
+ || checkpoint.consentExpiresAt === null || checkpoint.runtimeCapabilityHash === null) {
544
+ fail('LIVE_EVIDENCE_CHECKPOINT_INVALID', 'Requested checkpoint requires exact consent and runtime capability custody.');
545
+ }
546
+ currentAt(checkpoint.updatedAt, checkpoint.consentExpiresAt, 'Connector consent');
547
+ }
548
+ if (['materialized', 'committed'].includes(checkpoint.state)) {
549
+ if (checkpoint.health.status !== 'available' || checkpoint.artifactId === null || checkpoint.artifactHash === null
550
+ || checkpoint.absenceDigest !== null) fail('LIVE_EVIDENCE_FALSE_PASS', 'Materialized connector checkpoint requires available health and exact Artifact custody.');
551
+ currentAt(checkpoint.updatedAt, checkpoint.health.freshUntil, 'Provider health');
552
+ }
553
+ if (['absent', 'uncertain'].includes(checkpoint.state)
554
+ && (checkpoint.absenceDigest === null || checkpoint.artifactId !== null || checkpoint.artifactHash !== null)) {
555
+ fail('LIVE_EVIDENCE_CHECKPOINT_INVALID', 'Absent or uncertain checkpoint requires one typed absence and no Artifact custody.');
556
+ }
557
+ return freeze(clone(checkpoint));
558
+ }
559
+
560
+ export function reduceOperatingConnectorCheckpointV2(currentValue, eventValue) {
561
+ const current = assertOperatingConnectorCheckpointV2(currentValue);
562
+ const event = clone(eventValue);
563
+ assertExactKeys(event, CONNECTOR_EVENT_FIELDS, 'Connector transition Event');
564
+ if (!CONNECTOR_PATCH_FIELDS[event.type]) fail('LIVE_EVIDENCE_TRANSITION_INVALID', 'Unknown connector transition type.', { type: event.type });
565
+ assertExactKeys(event.patch, CONNECTOR_PATCH_FIELDS[event.type], `${event.type} connector patch`);
566
+ const claimed = event.eventHash;
567
+ delete event.eventHash;
568
+ const eventHash = sha256Jcs(event);
569
+ if (claimed !== eventHash) fail('LIVE_EVIDENCE_DIGEST_MISMATCH', 'Connector event hash does not bind the exact event.');
570
+ if (current.lastEventId === event.eventId) {
571
+ if (current.lastEventHash !== eventHash) fail('LIVE_EVIDENCE_REPLAY_CONFLICT', 'Divergent connector event reused one identity.');
572
+ return current;
573
+ }
574
+ if (event.expectedGeneration !== current.generation || event.expectedCheckpointHash !== current.checkpointHash) {
575
+ fail('LIVE_EVIDENCE_CONCURRENT_MODIFICATION', 'Connector checkpoint generation or digest changed.');
576
+ }
577
+ const target = CHECKPOINT_TRANSITIONS[current.state]?.[event.type];
578
+ if (target === undefined) fail('LIVE_EVIDENCE_TRANSITION_INVALID', 'Illegal connector checkpoint transition.', { from: current.state, type: event.type });
579
+ const patch = event.patch ?? {};
580
+ for (const field of IMMUTABLE_CHECKPOINT_FIELDS) {
581
+ if (Object.hasOwn(patch, field) && !sameValue(patch[field], current[field])) fail('LIVE_EVIDENCE_BINDING_MISMATCH', `Connector ${field} cannot change.`);
582
+ }
583
+ if (current.state !== 'prepared') {
584
+ for (const field of ['consentRecordId', 'consentRecordHash', 'consentExpiresAt', 'runtimeCapabilityHash']) {
585
+ if (Object.hasOwn(patch, field) && !sameValue(patch[field], current[field])) {
586
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', `Connector ${field} is frozen after request issuance.`);
587
+ }
588
+ }
589
+ }
590
+ if (event.type === 'request' && (patch.consentRecordId === null || patch.consentRecordHash === null
591
+ || patch.consentExpiresAt === null || patch.runtimeCapabilityHash === null)) {
592
+ fail('LIVE_EVIDENCE_AUTHORITY_INVALID', 'Requesting a provider call requires current consent and one opaque runtime capability.');
593
+ }
594
+ if (event.type === 'materialize' && (patch.artifactId === null || patch.artifactHash === null
595
+ || patch.health?.status !== 'available')) {
596
+ fail('LIVE_EVIDENCE_FALSE_PASS', 'Materialize transition requires exact Artifact and current available-health proof.');
597
+ }
598
+ if (['absent', 'uncertain'].includes(event.type) && patch.absenceDigest === null) {
599
+ fail('LIVE_EVIDENCE_CHECKPOINT_INVALID', 'Absent or uncertain transition requires a typed absence digest.');
600
+ }
601
+ const next = { ...clone(current), ...patch, state: target, generation: current.generation + 1, lastEventId: event.eventId, lastEventHash: eventHash, predecessorDigest: current.checkpointHash, updatedAt: event.at };
602
+ delete next.checkpointHash;
603
+ next.checkpointHash = sha256Jcs(next);
604
+ return assertOperatingConnectorCheckpointV2(next);
605
+ }
606
+
607
+ export function assertOperatingMeasurementPlanV2(plan, { verificationPlan } = {}) {
608
+ protocol('operating-measurement-plan', plan);
609
+ safePortable(plan);
610
+ selfHash(plan, 'planHash');
611
+ const terminal = [...plan.terminalResults].sort();
612
+ if (!sameValue(terminal, ['failed', 'insufficient-evidence', 'passed'])) fail('LIVE_EVIDENCE_PLAN_INVALID', 'Measurement plan must declare every terminal result.');
613
+ if (plan.target.unit !== plan.metric.unit || plan.baseline.unit !== plan.metric.unit) fail('LIVE_EVIDENCE_UNIT_MISMATCH', 'Baseline and target units must match the metric.');
614
+ if (![plan.baseline.value, ...(Array.isArray(plan.target.value) ? plan.target.value : [plan.target.value])].every(Number.isFinite)
615
+ || Date.parse(plan.window.from) > Date.parse(plan.window.to) || Date.parse(plan.dueAt) < Date.parse(plan.window.to)) {
616
+ fail('LIVE_EVIDENCE_PLAN_INVALID', 'Measurement values must be finite and the inclusive window/due chronology must be ordered.');
617
+ }
618
+ const algebra = {
619
+ increase: ['gt', 'gte'], decrease: ['lt', 'lte'], maintain: ['eq'], range: ['between'],
620
+ };
621
+ if (!algebra[plan.target.direction]?.includes(plan.target.operator)) {
622
+ fail('LIVE_EVIDENCE_PLAN_INVALID', 'Measurement direction and operator are incompatible.');
623
+ }
624
+ if (plan.target.operator === 'between') {
625
+ if (!Array.isArray(plan.target.value) || plan.target.value.length !== 2 || plan.target.value[0] > plan.target.value[1]) {
626
+ fail('LIVE_EVIDENCE_PLAN_INVALID', 'Between target must be one ordered finite range.');
627
+ }
628
+ } else if (Array.isArray(plan.target.value)) {
629
+ fail('LIVE_EVIDENCE_PLAN_INVALID', 'Scalar measurement operators cannot use a range target.');
630
+ }
631
+ if ((plan.target.direction === 'increase' && plan.target.value <= plan.baseline.value)
632
+ || (plan.target.direction === 'decrease' && plan.target.value >= plan.baseline.value)
633
+ || (plan.target.direction === 'maintain' && plan.target.value !== plan.baseline.value)) {
634
+ fail('LIVE_EVIDENCE_PLAN_INVALID', 'Measurement target does not match the declared baseline direction.');
635
+ }
636
+ if (verificationPlan !== undefined) {
637
+ protocol('operating-action-verification-plan', verificationPlan);
638
+ exactDigest(verificationPlan, plan.verificationPlan.recordDigest, 'Action verification plan');
639
+ same([verificationPlan.verificationPlanId, verificationPlan.actionId, verificationPlan.metricId, verificationPlan.baseline, verificationPlan.target], [plan.verificationPlan.verificationPlanId, plan.action.actionId, plan.metric.metricId, plan.baseline.value, plan.verificationPlan.canonicalTarget], 'Measurement-plan specialization');
640
+ same(sha256Jcs(verificationPlan.window), plan.verificationPlan.windowDigest, 'Verification window digest');
641
+ same(sha256Jcs(verificationPlan.method), plan.verificationPlan.methodDigest, 'Verification method digest');
642
+ same(sha256Jcs(verificationPlan.evaluationRules), plan.verificationPlan.evaluationRulesDigest, 'Verification rule digest');
643
+ const targetValues = Array.isArray(plan.target.value) ? plan.target.value : [plan.target.value];
644
+ if (!targetValues.includes(verificationPlan.target)) fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Measurement target is not derivable from the canonical verification target.');
645
+ }
646
+ return freeze(clone(plan));
647
+ }
648
+
649
+ export function assertOperatingMeasurementScheduleV2(schedule) {
650
+ protocol('operating-measurement-schedule', schedule);
651
+ safePortable(schedule);
652
+ selfHash(schedule, 'scheduleHash');
653
+ if (schedule.generation === 0 && (schedule.state !== 'disabled' || schedule.runCount !== 0
654
+ || schedule.nextEventIdentity !== null || schedule.nextDueAt !== null
655
+ || schedule.lastReceiptId !== null || schedule.lastTransitionHash !== null)) {
656
+ fail('LIVE_EVIDENCE_SCHEDULE_INVALID', 'New schedules must be disabled and unadvanced.');
657
+ }
658
+ if (schedule.recurrence.kind === 'once' && schedule.recurrence.intervalSeconds !== null) fail('LIVE_EVIDENCE_SCHEDULE_INVALID', 'One-time schedules cannot carry an interval.');
659
+ if (schedule.recurrence.kind === 'interval' && schedule.recurrence.intervalSeconds === null) fail('LIVE_EVIDENCE_SCHEDULE_INVALID', 'Interval schedules require a bounded interval.');
660
+ if (Date.parse(schedule.startsAt) > Date.parse(schedule.endsAt ?? schedule.startsAt)
661
+ || schedule.runCount > schedule.maxRuns) fail('LIVE_EVIDENCE_SCHEDULE_INVALID', 'Schedule time bounds or run count are invalid.');
662
+ if (schedule.state === 'enabled' && (schedule.consentRecordHashes.length === 0
663
+ || schedule.nextEventIdentity === null || schedule.nextDueAt === null
664
+ || schedule.runCount >= schedule.maxRuns || Date.parse(schedule.nextDueAt) < Date.parse(schedule.startsAt)
665
+ || (schedule.endsAt !== null && Date.parse(schedule.nextDueAt) > Date.parse(schedule.endsAt)))) {
666
+ fail('LIVE_EVIDENCE_SCHEDULE_INVALID', 'Enabled schedule requires current consent, one due identity, and remaining bounded runs.');
667
+ }
668
+ if (['disabled', 'revoked', 'expired', 'completed'].includes(schedule.state)
669
+ && (schedule.nextEventIdentity !== null || schedule.nextDueAt !== null)) {
670
+ fail('LIVE_EVIDENCE_SCHEDULE_INVALID', 'Inactive schedule cannot retain an executable due identity.');
671
+ }
672
+ return freeze(clone(schedule));
673
+ }
674
+
675
+ function assertOperatingMeasurementScheduleReceiptStructureV2(receipt) {
676
+ protocol('operating-measurement-schedule-receipt', receipt);
677
+ safePortable(receipt);
678
+ selfHash(receipt, 'receiptHash');
679
+ if (receipt.authority !== 'none' || receipt.scheduleHash !== receipt.resultingScheduleHash) fail('LIVE_EVIDENCE_SCHEDULE_INVALID', 'Schedule receipt is non-authoritative and must bind its exact result schedule.');
680
+ return freeze(clone(receipt));
681
+ }
682
+
683
+ function assertScheduleReceiptPredecessor(receipt, previousSchedule) {
684
+ const previous = assertOperatingMeasurementScheduleV2(previousSchedule);
685
+ if (previous.scheduleHash !== receipt.previousScheduleHash || previous.scheduleId !== receipt.scheduleId
686
+ || previous.state !== receipt.fromState || previous.runCount !== receipt.previousRunCount
687
+ || previous.nextEventIdentity !== receipt.previousNextEventIdentity || previous.nextDueAt !== receipt.previousNextDueAt) {
688
+ fail('LIVE_EVIDENCE_BASE_RECORD_MISMATCH', 'Schedule receipt changed its exact predecessor.');
689
+ }
690
+ }
691
+
692
+ function assertScheduleReceiptResult(receipt, resultingSchedule) {
693
+ const result = assertOperatingMeasurementScheduleV2(resultingSchedule);
694
+ if (result.scheduleHash !== receipt.resultingScheduleHash || result.scheduleId !== receipt.scheduleId
695
+ || result.state !== receipt.toState || result.runCount !== receipt.resultingRunCount
696
+ || result.nextEventIdentity !== receipt.resultingNextEventIdentity || result.nextDueAt !== receipt.resultingNextDueAt) {
697
+ fail('LIVE_EVIDENCE_BASE_RECORD_MISMATCH', 'Schedule receipt changed its exact result.');
698
+ }
699
+ }
700
+
701
+ export function assertOperatingMeasurementScheduleReceiptV2(receipt, { previousSchedule, resultingSchedule } = {}) {
702
+ const normalized = assertOperatingMeasurementScheduleReceiptStructureV2(receipt);
703
+ if (previousSchedule === undefined || resultingSchedule === undefined) {
704
+ fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'A certifying schedule receipt requires its exact predecessor and resulting schedules.');
705
+ }
706
+ assertScheduleReceiptPredecessor(normalized, previousSchedule);
707
+ assertScheduleReceiptResult(normalized, resultingSchedule);
708
+ return normalized;
709
+ }
710
+
711
+ function scheduleReceipt(schedule, transition, next) {
712
+ const receipt = {
713
+ kind: 'operating-measurement-schedule-receipt', schemaVersion: '1.0.0', protocolVersion: '2.0.0', receiptId: transition.receiptId,
714
+ authority: 'none', scheduleId: schedule.scheduleId, scheduleHash: next.scheduleHash,
715
+ previousScheduleHash: transition.expectedScheduleHash, resultingScheduleHash: next.scheduleHash,
716
+ measurementPlanId: schedule.measurementPlanId, planHash: schedule.planHash,
717
+ consentRecordHashes: clone(schedule.consentRecordHashes), ceilingsHash: schedule.ceilingsHash, clockHash: transition.clockHash,
718
+ recurrenceHash: sha256Jcs(schedule.recurrence), startsAt: schedule.startsAt, endsAt: schedule.endsAt, maxRuns: schedule.maxRuns,
719
+ misfirePolicy: schedule.misfirePolicy, dstPolicy: schedule.dstPolicy, transition: transition.type,
720
+ fromState: transition.expectedState, toState: next.state,
721
+ previousRunCount: transition.expectedRunCount, resultingRunCount: next.runCount,
722
+ previousNextEventIdentity: transition.expectedNextEventIdentity, resultingNextEventIdentity: next.nextEventIdentity,
723
+ previousNextDueAt: transition.expectedNextDueAt, resultingNextDueAt: next.nextDueAt,
724
+ ownerActorId: schedule.ownerActorId, eventId: transition.eventId, nextEventIdentity: next.nextEventIdentity,
725
+ at: transition.at,
726
+ };
727
+ receipt.receiptHash = sha256Jcs(receipt);
728
+ return receipt;
729
+ }
730
+
731
+ export function reduceOperatingMeasurementScheduleV2(scheduleValue, transitionValue, { priorReceipt } = {}) {
732
+ const schedule = assertOperatingMeasurementScheduleV2(scheduleValue);
733
+ const transition = clone(transitionValue);
734
+ assertExactKeys(transition, SCHEDULE_TRANSITION_FIELDS, 'Measurement schedule transition');
735
+ const transitionHash = sha256Jcs(transition);
736
+ if (schedule.lastReceiptId === transition.receiptId) {
737
+ if (schedule.lastTransitionHash !== transitionHash) fail('LIVE_EVIDENCE_REPLAY_CONFLICT', 'Divergent schedule transition reused one receipt identity.');
738
+ if (priorReceipt === undefined) fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'Exact schedule replay requires the stored immutable prior receipt.');
739
+ const receipt = assertOperatingMeasurementScheduleReceiptStructureV2(priorReceipt);
740
+ const expected = scheduleReceipt(schedule, transition, schedule);
741
+ if (!sameValue(receipt, expected)) fail('LIVE_EVIDENCE_REPLAY_CONFLICT', 'Stored schedule receipt does not match the exact replay request and resulting schedule.');
742
+ assertScheduleReceiptResult(receipt, schedule);
743
+ return freeze({ replay: true, schedule, receipt });
744
+ }
745
+ if (transition.expectedGeneration !== schedule.generation || transition.expectedScheduleHash !== schedule.scheduleHash) fail('LIVE_EVIDENCE_CONCURRENT_MODIFICATION', 'Schedule generation or digest changed.');
746
+ if (transition.expectedState !== schedule.state || transition.expectedRunCount !== schedule.runCount
747
+ || transition.expectedNextEventIdentity !== schedule.nextEventIdentity || transition.expectedNextDueAt !== schedule.nextDueAt) {
748
+ fail('LIVE_EVIDENCE_CONCURRENT_MODIFICATION', 'Schedule state, run count, or due identity changed.');
749
+ }
750
+ let toState = SCHEDULE_TRANSITIONS[schedule.state]?.[transition.type];
751
+ if (toState === undefined) fail('LIVE_EVIDENCE_TRANSITION_INVALID', 'Illegal measurement schedule transition.', { from: schedule.state, type: transition.type });
752
+ if (transition.planHash !== schedule.planHash || !sameValue(transition.consentRecordHashes, schedule.consentRecordHashes)
753
+ || transition.ceilingsHash !== schedule.ceilingsHash) fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Schedule transition changed plan or consent custody.');
754
+ let runCount = schedule.runCount;
755
+ if (transition.type === 'enable') {
756
+ if (schedule.consentRecordHashes.length === 0 || transition.nextEventIdentity === null || transition.nextDueAt === null
757
+ || Date.parse(transition.nextDueAt) < Date.parse(schedule.startsAt)) {
758
+ fail('LIVE_EVIDENCE_AUTHORITY_INVALID', 'Enabling measurement requires consent custody and a bounded runtime-issued due identity.');
759
+ }
760
+ } else if (transition.type === 'run-issued') {
761
+ if (schedule.nextEventIdentity !== transition.eventId || schedule.nextDueAt === null
762
+ || Date.parse(transition.at) < Date.parse(schedule.nextDueAt)
763
+ || (schedule.endsAt !== null && Date.parse(transition.at) > Date.parse(schedule.endsAt))) {
764
+ fail('LIVE_EVIDENCE_TRANSITION_INVALID', 'Measurement run is not the exact due runtime identity within its schedule window.');
765
+ }
766
+ runCount += 1;
767
+ const terminalRun = runCount >= schedule.maxRuns || schedule.recurrence.kind === 'once';
768
+ toState = terminalRun ? 'completed' : 'enabled';
769
+ if (terminalRun ? (transition.nextEventIdentity !== null || transition.nextDueAt !== null)
770
+ : (transition.nextEventIdentity === null || transition.nextDueAt === null
771
+ || transition.nextEventIdentity === transition.eventId
772
+ || Date.parse(transition.nextDueAt) <= Date.parse(schedule.nextDueAt))) {
773
+ fail('LIVE_EVIDENCE_TRANSITION_INVALID', 'Measurement run successor identity or completion state is invalid.');
774
+ }
775
+ } else if (['disable', 'revoke', 'expire', 'complete'].includes(transition.type)) {
776
+ if (transition.nextEventIdentity !== null || transition.nextDueAt !== null) fail('LIVE_EVIDENCE_TRANSITION_INVALID', 'Inactive transition cannot retain an executable due identity.');
777
+ } else if (transition.nextEventIdentity !== schedule.nextEventIdentity || transition.nextDueAt !== schedule.nextDueAt) {
778
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Pause or resume cannot substitute the scheduled due identity.');
779
+ }
780
+ const next = { ...clone(schedule), generation: schedule.generation + 1, state: toState, runCount,
781
+ nextEventIdentity: transition.nextEventIdentity, nextDueAt: transition.nextDueAt,
782
+ lastReceiptId: transition.receiptId, lastTransitionHash: transitionHash };
783
+ delete next.scheduleHash;
784
+ next.scheduleHash = sha256Jcs(next);
785
+ const normalizedNext = assertOperatingMeasurementScheduleV2(next);
786
+ const receipt = scheduleReceipt(schedule, transition, normalizedNext);
787
+ return freeze({ replay: false, schedule: normalizedNext,
788
+ receipt: assertOperatingMeasurementScheduleReceiptV2(receipt, { previousSchedule: schedule, resultingSchedule: normalizedNext }) });
789
+ }
790
+
791
+ export function assertOperatingEvidenceObservationV2(observation, {
792
+ measurementPlan, metricObservation, ingestion, connectorCheckpoint, artifact, evidenceRefs, acceptedBridge,
793
+ } = {}) {
794
+ protocol('operating-evidence-observation', observation);
795
+ safePortable(observation);
796
+ selfHash(observation, 'observationHash');
797
+ if (measurementPlan === undefined) fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'Evidence observation requires the exact measurement plan.');
798
+ const plan = assertOperatingMeasurementPlanV2(measurementPlan);
799
+ if (plan.measurementPlanId !== observation.measurementPlan.recordId || plan.planHash !== observation.measurementPlan.recordDigest
800
+ || plan.metric.metricId !== observation.metricId || plan.metric.unit !== observation.unit || plan.metric.dimension !== observation.dimension) {
801
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Evidence observation changed measurement-plan metric custody.');
802
+ }
803
+ if (ingestion === undefined || connectorCheckpoint === undefined || artifact === undefined || !Array.isArray(evidenceRefs)
804
+ || acceptedBridge === undefined) {
805
+ fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'Evidence observation requires the exact accepted ingestion bridge, checkpoint, Artifact, and EvidenceRef custody.');
806
+ }
807
+ const accepted = assertAcceptedLiveEvidenceBridgeV2(ingestion, acceptedBridge);
808
+ if (accepted.ingestionHash !== ingestion.ingestionHash
809
+ || acceptedBridge.artifact?.artifactId !== artifact.artifactId
810
+ || acceptedBridge.resultCheckpoint?.checkpointId !== connectorCheckpoint.checkpointId
811
+ || evidenceRefs.length !== 1
812
+ || acceptedBridge.evidenceRef?.evidenceRefId !== evidenceRefs[0]?.evidenceRefId
813
+ || sha256Jcs(acceptedBridge.evidenceRef) !== sha256Jcs(evidenceRefs[0])) {
814
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Observation custody is not the exact accepted live-evidence bridge.');
815
+ }
816
+ const checkpoint = assertOperatingConnectorCheckpointV2(connectorCheckpoint);
817
+ protocol('operating-artifact', artifact);
818
+ if (observation.ingestion.ingestionId !== ingestion.ingestionId || observation.ingestion.ingestionHash !== ingestion.ingestionHash
819
+ || observation.checkpoint.checkpointId !== checkpoint.checkpointId || observation.checkpoint.checkpointHash !== checkpoint.checkpointHash
820
+ || observation.artifact.artifactId !== artifact.artifactId || observation.artifact.recordDigest !== sha256Jcs(artifact)
821
+ || artifact.canonicalHash !== sha256Jcs(ingestion) || ingestion.providerRegistrationHash !== observation.providerRegistrationHash
822
+ || !sameValue(ingestion.provider, observation.provider)) {
823
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Evidence observation changed live-ingestion Artifact or provider custody.');
824
+ }
825
+ const refs = evidenceRefs.map((entry) => {
826
+ protocol('operating-evidence-ref', entry);
827
+ return { evidenceRefId: entry.evidenceRefId, recordDigest: sha256Jcs(entry) };
828
+ }).sort((left, right) => left.evidenceRefId.localeCompare(right.evidenceRefId));
829
+ const claimedRefs = [...observation.evidenceRefs].sort((left, right) => left.evidenceRefId.localeCompare(right.evidenceRefId));
830
+ if (!sameValue(refs, claimedRefs)) fail('LIVE_EVIDENCE_BASE_RECORD_MISMATCH', 'Evidence observation changed EvidenceRef membership or bytes.');
831
+ if (observation.status === 'observed') {
832
+ currentAt(observation.observedAt, observation.freshUntil, 'Observation');
833
+ if (metricObservation === undefined) fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'Observed evidence requires the exact canonical metric observation.');
834
+ protocol('operating-metric-observation', metricObservation);
835
+ exactDigest(metricObservation, observation.metricObservation.recordDigest, 'Metric observation');
836
+ same([metricObservation.observationId, metricObservation.metricId, metricObservation.value, metricObservation.unit, metricObservation.observedAt, metricObservation.sourceArtifactId, metricObservation.evidenceRefIds], [observation.metricObservation.recordId, observation.metricId, observation.value, observation.unit, observation.observedAt, observation.artifact.artifactId, observation.evidenceRefs.map((entry) => entry.evidenceRefId)], 'Metric observation');
837
+ } else if ((observation.status === 'absent' && observation.absences.length === 0)
838
+ || (observation.status === 'contradicted' && observation.contradictions.length === 0)) {
839
+ fail('LIVE_EVIDENCE_FALSE_PASS', 'Non-observed evidence must retain its typed absence or contradiction.');
840
+ }
841
+ return freeze(clone(observation));
842
+ }
843
+
844
+ function observationCustodyFor(observation, custody) {
845
+ const matches = custody.filter((entry) => entry?.evidenceObservationId === observation.evidenceObservationId);
846
+ if (matches.length !== 1) fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'Each evidence observation requires one exact custody bundle.', { evidenceObservationId: observation.evidenceObservationId });
847
+ return matches[0];
848
+ }
849
+
850
+ function canonicalEvaluationInputs({ evaluation, plan, evidenceObservations, metricObservations, observationCustody }) {
851
+ if (!Array.isArray(observationCustody)) fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'Outcome evaluation requires exact observation custody bundles.');
852
+ const ids = new Set();
853
+ const normalizedEvidence = evidenceObservations.map((entry) => {
854
+ if (ids.has(entry.evidenceObservationId)) fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Duplicate evidence observation identity is forbidden.', { evidenceObservationId: entry.evidenceObservationId });
855
+ ids.add(entry.evidenceObservationId);
856
+ const custody = observationCustodyFor(entry, observationCustody);
857
+ const metricObservation = entry.metricObservation === null ? undefined
858
+ : metricObservations.find((candidate) => candidate.observationId === entry.metricObservation.recordId);
859
+ return assertOperatingEvidenceObservationV2(entry, { measurementPlan: plan, metricObservation, ...custody });
860
+ }).sort((left, right) => left.evidenceObservationId.localeCompare(right.evidenceObservationId));
861
+ if (normalizedEvidence.length !== evaluation.observations.length || observationCustody.length !== normalizedEvidence.length) {
862
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Evaluation observation membership changed.');
863
+ }
864
+ const wrappers = normalizedEvidence.map((entry) => ({
865
+ evidenceObservationId: entry.evidenceObservationId,
866
+ observationId: entry.metricObservation?.recordId ?? null,
867
+ observationHash: entry.observationHash,
868
+ metricObservationDigest: entry.metricObservation?.recordDigest ?? null,
869
+ value: entry.value,
870
+ unit: entry.unit,
871
+ observedAt: entry.observedAt,
872
+ freshUntil: entry.freshUntil,
873
+ status: entry.status,
874
+ }));
875
+ if (!sameValue(evaluation.observations, wrappers)) fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Evaluation wrappers are not the canonical unique observation set.');
876
+ const inWindow = normalizedEvidence.filter((entry) => Date.parse(entry.observedAt) >= Date.parse(plan.window.from)
877
+ && Date.parse(entry.observedAt) <= Date.parse(plan.window.to));
878
+ const observed = inWindow.filter((entry) => entry.status === 'observed' && Date.parse(entry.freshUntil) > Date.parse(evaluation.evaluatedAt));
879
+ const absences = inWindow.flatMap((entry) => entry.absences.map((absence) => absence.detailsHash));
880
+ for (const entry of inWindow.filter((item) => item.status === 'observed' && Date.parse(item.freshUntil) <= Date.parse(evaluation.evaluatedAt))) {
881
+ absences.push(sha256Jcs({ kind: 'stale', evidenceObservationId: entry.evidenceObservationId, observationHash: entry.observationHash, evaluatedAt: evaluation.evaluatedAt }));
882
+ }
883
+ const contradictions = inWindow.flatMap((entry) => entry.contradictions);
884
+ return {
885
+ normalizedEvidence, wrappers, inWindow, observed,
886
+ absences: [...new Set(absences)].sort(), contradictions: [...new Set(contradictions)].sort(),
887
+ };
888
+ }
889
+
890
+ export function assertOperatingOutcomeEvaluationV2(evaluation, {
891
+ measurementPlan, verificationPlan, outcome, evidenceObservations, metricObservations = [],
892
+ observationCustody, previousEvaluation,
893
+ } = {}) {
894
+ protocol('operating-outcome-evaluation', evaluation);
895
+ safePortable(evaluation);
896
+ selfHash(evaluation, 'evaluationHash');
897
+ if (measurementPlan === undefined || verificationPlan === undefined || outcome === undefined || !Array.isArray(evidenceObservations)) fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'Outcome evaluation requires its exact plan, Outcome, and evidence observations.');
898
+ const plan = assertOperatingMeasurementPlanV2(measurementPlan, { verificationPlan });
899
+ if (plan.planHash !== evaluation.measurementPlan.recordDigest || plan.measurementPlanId !== evaluation.measurementPlan.recordId) fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Evaluation changed its measurement plan.');
900
+ const canonical = canonicalEvaluationInputs({ evaluation, plan, evidenceObservations, metricObservations, observationCustody });
901
+ if (!sameValue([evaluation.window, evaluation.operator, evaluation.unit, evaluation.dimension, evaluation.baseline, evaluation.target, evaluation.minimumEvidence],
902
+ [plan.window, plan.target.operator, plan.metric.unit, plan.metric.dimension, plan.baseline.value, plan.target.value, plan.minimumEvidence])) {
903
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Evaluation changed its exact plan algebra, window, or threshold.');
904
+ }
905
+ const computedResult = canonical.absences.length > 0 || canonical.contradictions.length > 0 || canonical.observed.length < plan.minimumEvidence
906
+ ? 'insufficient-evidence' : canonical.observed.every((entry) => compare(plan.target.operator, entry.value, plan.target.value)) ? 'passed' : 'failed';
907
+ const computedConfidence = canonical.inWindow.length === 0 ? 0 : canonical.observed.length / Math.max(canonical.inWindow.length, plan.minimumEvidence);
908
+ const computedInputDigest = sha256Jcs(canonical.wrappers.map((entry) => entry.observationHash));
909
+ const computedProviders = [...new Map(canonical.inWindow.map((entry) => [`${entry.provider.id}@${entry.provider.version}`, entry.provider])).values()].sort((a, b) => `${a.id}@${a.version}`.localeCompare(`${b.id}@${b.version}`));
910
+ if (evaluation.result !== computedResult || evaluation.confidence !== computedConfidence || evaluation.exactInputDigest !== computedInputDigest
911
+ || !sameValue(evaluation.providerVersions, computedProviders) || !sameValue(evaluation.absences, canonical.absences)
912
+ || !sameValue(evaluation.contradictions, canonical.contradictions)) fail('LIVE_EVIDENCE_FALSE_PASS', 'Outcome result, confidence, input digest, provider, absence, or contradiction truth is not deterministic from bound evidence.');
913
+ if (verificationPlan !== undefined) {
914
+ protocol('operating-action-verification-plan', verificationPlan);
915
+ exactDigest(verificationPlan, evaluation.verificationPlan.recordDigest, 'Verification plan');
916
+ same([verificationPlan.verificationPlanId, verificationPlan.actionId, verificationPlan.metricId], [evaluation.verificationPlan.recordId, evaluation.actionId, evaluation.metricId], 'Evaluation verification plan');
917
+ }
918
+ if (outcome !== undefined) {
919
+ protocol('operating-outcome', outcome);
920
+ exactDigest(outcome, evaluation.outcome.recordDigest, 'Outcome');
921
+ const status = evaluation.result === 'passed' ? 'succeeded' : evaluation.result === 'failed' ? 'failed' : 'insufficient-evidence';
922
+ same([outcome.outcomeId, outcome.actionId, outcome.verificationPlanId, outcome.status], [evaluation.outcome.recordId, evaluation.actionId, evaluation.verificationPlan.recordId, status], 'Outcome evaluation');
923
+ }
924
+ if (evaluation.previousEvaluationHash === null) {
925
+ if (previousEvaluation !== undefined && previousEvaluation !== null) fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Initial evaluation cannot carry a predecessor.');
926
+ } else {
927
+ if (previousEvaluation === undefined || previousEvaluation === null) fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'Reevaluation requires the exact immutable predecessor.');
928
+ protocol('operating-outcome-evaluation', previousEvaluation);
929
+ selfHash(previousEvaluation, 'evaluationHash');
930
+ if (previousEvaluation.evaluationHash !== evaluation.previousEvaluationHash
931
+ || !sameValue([previousEvaluation.measurementPlan, previousEvaluation.verificationPlan, previousEvaluation.outcome],
932
+ [evaluation.measurementPlan, evaluation.verificationPlan, evaluation.outcome])
933
+ || Date.parse(previousEvaluation.evaluatedAt) >= Date.parse(evaluation.evaluatedAt)
934
+ || previousEvaluation.exactInputDigest === evaluation.exactInputDigest) {
935
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Reevaluation predecessor, lineage, time, or changed evidence custody is invalid.');
936
+ }
937
+ }
938
+ if (evaluation.revisit.required !== (evaluation.result !== 'passed')
939
+ || !sameValue(evaluation.revisit.decisionIds, verificationPlan.revisitDecisionIds)) {
940
+ fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Evaluation revisit truth changed from its result or verification plan.');
941
+ }
942
+ return freeze(clone(evaluation));
943
+ }
944
+
945
+ function compare(operator, value, target) {
946
+ if (operator === 'gt') return value > target;
947
+ if (operator === 'gte') return value >= target;
948
+ if (operator === 'lt') return value < target;
949
+ if (operator === 'lte') return value <= target;
950
+ if (operator === 'eq') return value === target;
951
+ return value >= target[0] && value <= target[1];
952
+ }
953
+
954
+ export function evaluateOperatingOutcomeV2({
955
+ plan, observations, metricObservations, observationCustody, evaluationId, outcome, verificationPlan,
956
+ evaluatedAt, previousEvaluation = null,
957
+ }) {
958
+ const normalizedPlan = assertOperatingMeasurementPlanV2(plan, { verificationPlan });
959
+ const normalized = observations.map((entry) => {
960
+ const custody = observationCustodyFor(entry, observationCustody);
961
+ return assertOperatingEvidenceObservationV2(entry, { measurementPlan: normalizedPlan,
962
+ metricObservation: entry.metricObservation === null ? undefined : metricObservations.find((candidate) => candidate.observationId === entry.metricObservation.recordId),
963
+ ...custody });
964
+ }).sort((left, right) => left.evidenceObservationId.localeCompare(right.evidenceObservationId));
965
+ const inWindow = normalized.filter((entry) => Date.parse(entry.observedAt) >= Date.parse(plan.window.from) && Date.parse(entry.observedAt) <= Date.parse(plan.window.to));
966
+ const observed = inWindow.filter((entry) => entry.status === 'observed' && Date.parse(entry.freshUntil) > Date.parse(evaluatedAt));
967
+ const absences = inWindow.flatMap((entry) => entry.absences.map((absence) => absence.detailsHash));
968
+ for (const entry of inWindow.filter((item) => item.status === 'observed' && Date.parse(item.freshUntil) <= Date.parse(evaluatedAt))) {
969
+ absences.push(sha256Jcs({ kind: 'stale', evidenceObservationId: entry.evidenceObservationId, observationHash: entry.observationHash, evaluatedAt }));
970
+ }
971
+ const contradictions = inWindow.flatMap((entry) => entry.contradictions);
972
+ let result = 'insufficient-evidence';
973
+ if (absences.length === 0 && contradictions.length === 0 && observed.length >= plan.minimumEvidence) {
974
+ result = observed.every((entry) => compare(plan.target.operator, entry.value, plan.target.value)) ? 'passed' : 'failed';
975
+ }
976
+ const evaluation = {
977
+ kind: 'operating-outcome-evaluation', schemaVersion: '1.0.0', protocolVersion: '2.0.0', evaluationId, previousEvaluationHash: previousEvaluation?.evaluationHash ?? null,
978
+ measurementPlan: { contractId: 'operating-measurement-plan', schemaVersion: '1.0.0', protocolVersion: '2.0.0', recordId: plan.measurementPlanId, recordDigest: plan.planHash },
979
+ verificationPlan: { contractId: 'operating-action-verification-plan', schemaVersion: '1.0.0', protocolVersion: '2.0.0', recordId: verificationPlan.verificationPlanId, recordDigest: sha256Jcs(verificationPlan) },
980
+ outcome: { contractId: 'operating-outcome', schemaVersion: '1.0.0', protocolVersion: '2.0.0', recordId: outcome.outcomeId, recordDigest: sha256Jcs(outcome) },
981
+ actionId: plan.action.actionId, metricId: plan.metric.metricId,
982
+ observations: normalized.map((entry) => ({ evidenceObservationId: entry.evidenceObservationId, observationId: entry.metricObservation?.recordId ?? null, observationHash: entry.observationHash, metricObservationDigest: entry.metricObservation?.recordDigest ?? null, value: entry.value, unit: entry.unit, observedAt: entry.observedAt, freshUntil: entry.freshUntil, status: entry.status })),
983
+ providerVersions: [...new Map(inWindow.map((entry) => [`${entry.provider.id}@${entry.provider.version}`, entry.provider])).values()].sort((a, b) => `${a.id}@${a.version}`.localeCompare(`${b.id}@${b.version}`)),
984
+ window: clone(plan.window), operator: plan.target.operator, unit: plan.metric.unit, dimension: plan.metric.dimension,
985
+ baseline: plan.baseline.value, target: clone(plan.target.value), minimumEvidence: plan.minimumEvidence,
986
+ exactInputDigest: sha256Jcs(normalized.map((entry) => entry.observationHash)), result,
987
+ confidence: inWindow.length === 0 ? 0 : observed.length / Math.max(inWindow.length, plan.minimumEvidence), absences: [...new Set(absences)].sort(),
988
+ contradictions: [...new Set(contradictions)].sort(), evaluatedAt,
989
+ revisit: { required: result !== 'passed', condition: result === 'passed' ? null : 'Evidence did not prove the declared target.', decisionIds: clone(verificationPlan.revisitDecisionIds) },
990
+ };
991
+ evaluation.evaluationHash = sha256Jcs(evaluation);
992
+ return assertOperatingOutcomeEvaluationV2(evaluation, { measurementPlan: normalizedPlan, verificationPlan, outcome,
993
+ evidenceObservations: normalized, metricObservations, observationCustody, previousEvaluation });
994
+ }
995
+
996
+ export function assertOperatingLearningReceiptV2(receipt, {
997
+ learning, outcome, evaluation, measurementPlan, verificationPlan, evidenceObservations,
998
+ metricObservations, observationCustody, previousEvaluation,
999
+ } = {}) {
1000
+ protocol('operating-learning-receipt', receipt);
1001
+ safePortable(receipt);
1002
+ selfHash(receipt, 'receiptHash');
1003
+ if (learning === undefined || outcome === undefined || evaluation === undefined
1004
+ || measurementPlan === undefined || verificationPlan === undefined
1005
+ || !Array.isArray(evidenceObservations) || !Array.isArray(metricObservations)
1006
+ || !Array.isArray(observationCustody)) {
1007
+ fail('LIVE_EVIDENCE_BASE_RECORD_REQUIRED', 'A certifying Learning receipt requires exact Learning, Outcome, evaluation, plan, and observation custody.');
1008
+ }
1009
+ protocol('operating-learning', learning);
1010
+ exactDigest(learning, receipt.learning.recordDigest, 'Learning');
1011
+ same([learning.learningId, learning.outcomeId, learning.assumptionIds, learning.decisionIds, learning.evidenceRefIds], [receipt.learning.recordId, receipt.outcome.recordId, receipt.assumptionIds, receipt.decisionIds, receipt.evidenceRefIds], 'Learning receipt');
1012
+ same(sha256Jcs(learning.statement), receipt.statementDigest, 'Learning statement digest');
1013
+ protocol('operating-outcome', outcome);
1014
+ exactDigest(outcome, receipt.outcome.recordDigest, 'Learning outcome');
1015
+ assertOperatingOutcomeEvaluationV2(evaluation, { measurementPlan, verificationPlan, outcome,
1016
+ evidenceObservations, metricObservations, observationCustody, previousEvaluation });
1017
+ if (evaluation.evaluationId !== receipt.evaluation.evaluationId || evaluation.evaluationHash !== receipt.evaluation.evaluationHash || evaluation.result !== receipt.evaluation.result) fail('LIVE_EVIDENCE_BINDING_MISMATCH', 'Learning receipt changed the evaluation result.');
1018
+ return freeze(clone(receipt));
1019
+ }