@piagent/platform 1.5.5 → 1.6.1

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 (323) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/README.md +25 -21
  3. package/README.vi.md +34 -5
  4. package/adapters/web-frontend/profile.json +2 -4
  5. package/architecture/layers.json +18 -3
  6. package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
  7. package/benchmarks/deep-logic-v1/grade.mjs +272 -10
  8. package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
  9. package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
  10. package/benchmarks/deep-logic-v1/project/package.json +1 -1
  11. package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
  12. package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
  13. package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
  14. package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
  15. package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
  16. package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
  17. package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
  18. package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
  19. package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
  20. package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
  21. package/benchmarks/deep-logic-v1/suite.json +51 -3
  22. package/benchmarks/deep-logic-v1/variant.mjs +22 -1
  23. package/benchmarks/production-v1/spend-control.v1.json +92 -0
  24. package/benchmarks/production-v1/suite.json +46 -3
  25. package/catalog/capabilities.json +18 -18
  26. package/docs/authority-manifest.md +1 -1
  27. package/docs/auto-delegation-policy.md +38 -73
  28. package/docs/command-reference-vietnamese.md +27 -35
  29. package/docs/context-engine.md +12 -4
  30. package/docs/context-window-policy.md +37 -1
  31. package/docs/distribution-standard.md +7 -7
  32. package/docs/en/architecture.md +27 -13
  33. package/docs/languages.json +4 -4
  34. package/docs/model-options.md +8 -5
  35. package/docs/operator-manual-vietnamese.md +27 -45
  36. package/docs/project-adapters.md +2 -2
  37. package/docs/publishing-for-teams.md +10 -0
  38. package/docs/quality-benchmark.md +427 -62
  39. package/docs/release-install-policy.md +10 -9
  40. package/docs/runtime-harness-standard.md +3 -3
  41. package/docs/runtime-policy-design.md +4 -4
  42. package/docs/runtime-quality-baseline.md +13 -6
  43. package/docs/security-threat-model.md +2 -2
  44. package/docs/subagent-orchestration-capabilities.md +19 -51
  45. package/docs/subagents-and-multiagent.md +21 -47
  46. package/docs/task-implementation-contract.md +3 -3
  47. package/docs/task-lifecycle-vietnamese.md +3 -3
  48. package/docs/team-onboarding.md +2 -2
  49. package/docs/usage-observability.md +7 -2
  50. package/docs/vi/architecture.md +27 -13
  51. package/docs-site/architecture.html +1 -1
  52. package/docs-site/benchmark.html +89 -95
  53. package/docs-site/commands.html +1 -1
  54. package/docs-site/content/benchmark.html +88 -94
  55. package/docs-site/content/en/benchmark.html +134 -90
  56. package/docs-site/content/en/index.html +19 -19
  57. package/docs-site/content/en/runtime.html +4 -4
  58. package/docs-site/content/en/whats-new.html +12 -1
  59. package/docs-site/content/en/workflows.html +2 -2
  60. package/docs-site/content/index.html +19 -19
  61. package/docs-site/content/runtime.html +4 -4
  62. package/docs-site/content/whats-new.html +12 -1
  63. package/docs-site/content/workflows.html +2 -2
  64. package/docs-site/context-engine.html +1 -1
  65. package/docs-site/en/architecture.html +1 -1
  66. package/docs-site/en/benchmark.html +135 -91
  67. package/docs-site/en/commands.html +1 -1
  68. package/docs-site/en/context-engine.html +1 -1
  69. package/docs-site/en/index.html +20 -20
  70. package/docs-site/en/mcp-approval.html +1 -1
  71. package/docs-site/en/mcp-auth.html +1 -1
  72. package/docs-site/en/mcp-servers.html +1 -1
  73. package/docs-site/en/mcp.html +1 -1
  74. package/docs-site/en/permissions.html +1 -1
  75. package/docs-site/en/profiles.html +1 -1
  76. package/docs-site/en/quickstart.html +1 -1
  77. package/docs-site/en/runtime.html +5 -5
  78. package/docs-site/en/scope.html +1 -1
  79. package/docs-site/en/security.html +1 -1
  80. package/docs-site/en/team.html +1 -1
  81. package/docs-site/en/whats-new.html +13 -2
  82. package/docs-site/en/workflows.html +3 -3
  83. package/docs-site/index.html +20 -20
  84. package/docs-site/mcp-approval.html +1 -1
  85. package/docs-site/mcp-auth.html +1 -1
  86. package/docs-site/mcp-servers.html +1 -1
  87. package/docs-site/mcp.html +1 -1
  88. package/docs-site/permissions.html +1 -1
  89. package/docs-site/profiles.html +1 -1
  90. package/docs-site/quickstart.html +1 -1
  91. package/docs-site/runtime.html +5 -5
  92. package/docs-site/scope.html +1 -1
  93. package/docs-site/security.html +1 -1
  94. package/docs-site/team.html +1 -1
  95. package/docs-site/whats-new.html +13 -2
  96. package/docs-site/workflows.html +3 -3
  97. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
  98. package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
  99. package/evals/fixtures/codex-relative-efficiency-v1.invalid.json +4 -0
  100. package/evals/fixtures/codex-relative-efficiency-v1.valid.json +94 -0
  101. package/evals/fixtures/helper-request.invalid.json +1 -1
  102. package/evals/fixtures/helper-request.valid.json +1 -1
  103. package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
  104. package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
  105. package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +7 -0
  106. package/evals/fixtures/task-authority-snapshot.valid.json +4 -4
  107. package/evals/fs4-readiness-matrix.v1.json +2 -2
  108. package/evals/fs5-causal-arm.v1.json +3 -3
  109. package/evals/fs5-pilot-protocol.v1.json +3 -3
  110. package/evals/fs5-pilot-protocol.v2.json +8 -8
  111. package/evals/fs5-pilot-protocol.v3.json +8 -8
  112. package/evals/fs5-pilot-protocol.v4.json +7 -6
  113. package/evals/fs5-pilot-protocol.v5.json +9 -7
  114. package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
  115. package/evals/runtime-conformance-v1/README.md +43 -0
  116. package/evals/runtime-conformance-v1/evaluate-paid-canary.mjs +198 -0
  117. package/evals/runtime-conformance-v1/lane.json +57 -0
  118. package/evals/runtime-conformance-v1/paid-canary.json +31 -0
  119. package/evals/runtime-conformance-v1/runner.mjs +456 -0
  120. package/package.json +3 -1
  121. package/packages/piagent-core/README.md +13 -4
  122. package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
  123. package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
  124. package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
  125. package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
  126. package/packages/piagent-core/benchmark/benchmark-codex-relative-efficiency.js +698 -0
  127. package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
  128. package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
  129. package/packages/piagent-core/benchmark/benchmark-core.js +736 -664
  130. package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
  131. package/packages/piagent-core/benchmark/benchmark-forensics.js +441 -4
  132. package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
  133. package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
  134. package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
  135. package/packages/piagent-core/benchmark/benchmark-preflight.js +5 -1
  136. package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
  137. package/packages/piagent-core/benchmark/benchmark-provider-free-evidence.js +345 -0
  138. package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
  139. package/packages/piagent-core/benchmark/benchmark-record-validation.js +252 -0
  140. package/packages/piagent-core/benchmark/benchmark-report.js +130 -12
  141. package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
  142. package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
  143. package/packages/piagent-core/benchmark/benchmark-runtime-causal.js +244 -0
  144. package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +979 -0
  145. package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
  146. package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
  147. package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
  148. package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
  149. package/packages/piagent-core/benchmark/benchmark-suite.js +149 -2
  150. package/packages/piagent-core/benchmark/benchmark-summary-support.js +380 -0
  151. package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
  152. package/packages/piagent-core/benchmark/benchmark-usage.js +322 -0
  153. package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
  154. package/packages/piagent-core/capabilities/runtime-integrity.js +3 -0
  155. package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
  156. package/packages/piagent-core/extensions/acceptance-behavior-proof.js +806 -0
  157. package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
  158. package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
  159. package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
  160. package/packages/piagent-core/extensions/acceptance-receipt.js +123 -59
  161. package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
  162. package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
  163. package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
  164. package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
  165. package/packages/piagent-core/extensions/context-engine.js +149 -156
  166. package/packages/piagent-core/extensions/context-evidence.js +48 -0
  167. package/packages/piagent-core/extensions/context-import-links.js +296 -0
  168. package/packages/piagent-core/extensions/context-index-policy.js +4 -0
  169. package/packages/piagent-core/extensions/context-query-paths.js +57 -0
  170. package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
  171. package/packages/piagent-core/extensions/criterion-context-pack.js +435 -7
  172. package/packages/piagent-core/extensions/criterion-graph.js +82 -9
  173. package/packages/piagent-core/extensions/fallback-base-policy.ts +101 -0
  174. package/packages/piagent-core/extensions/guard-types.ts +6 -0
  175. package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
  176. package/packages/piagent-core/extensions/piagent-guard.ts +420 -260
  177. package/packages/piagent-core/extensions/task-contract-view.js +17 -3
  178. package/packages/piagent-core/extensions/task-lifecycle.js +39 -2
  179. package/packages/piagent-core/extensions/task-state.js +109 -142
  180. package/packages/piagent-core/extensions/verification-intelligence.js +52 -13
  181. package/packages/piagent-core/extensions/workspace-evidence-roots.js +193 -0
  182. package/packages/piagent-core/package.json +1 -1
  183. package/packages/piagent-core/policies/base-policy.json +44 -2
  184. package/packages/piagent-core/policy/authority-manifest.v1.json +3 -3
  185. package/packages/piagent-core/prompts/be-to-fe.md +2 -8
  186. package/packages/piagent-core/prompts/plan.md +1 -1
  187. package/packages/piagent-core/prompts/platform-improve.md +2 -6
  188. package/packages/piagent-core/prompts/review.md +2 -7
  189. package/packages/piagent-core/prompts/scout.md +1 -1
  190. package/packages/piagent-core/prompts/task.md +4 -3
  191. package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
  192. package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
  193. package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
  194. package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
  195. package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
  196. package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
  197. package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
  198. package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
  199. package/packages/piagent-core/runtime/hooks/completion-hook.ts +12 -12
  200. package/packages/piagent-core/runtime/hooks/input-hook.ts +17 -1
  201. package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
  202. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +107 -3
  203. package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
  204. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +134 -166
  205. package/packages/piagent-core/runtime/hooks/tool-result-value-helpers.ts +66 -0
  206. package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
  207. package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
  208. package/packages/piagent-core/runtime/inspection/bounded-emission-guard.ts +286 -0
  209. package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +37 -7
  210. package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
  211. package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
  212. package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +27 -4
  213. package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +70 -0
  214. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +73 -9
  215. package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
  216. package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
  217. package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
  218. package/packages/piagent-core/runtime/orchestration/helper-lifecycle.ts +16 -12
  219. package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +71 -0
  220. package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +28 -20
  221. package/packages/piagent-core/runtime/orchestration/role-policy.ts +27 -9
  222. package/packages/piagent-core/runtime/orchestration/subagent-tool-policy.ts +329 -0
  223. package/packages/piagent-core/runtime/policy/block-remedy.ts +0 -4
  224. package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
  225. package/packages/piagent-core/runtime/product/efficiency-metrics.ts +7 -2
  226. package/packages/piagent-core/runtime/quality/edit-freshness-guard.ts +102 -0
  227. package/packages/piagent-core/runtime/quality/exact-output-contract.ts +22 -11
  228. package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
  229. package/packages/piagent-core/runtime/quality/performance-assurance.ts +9 -5
  230. package/packages/piagent-core/runtime/recovery/continuation-budget.ts +1 -1
  231. package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
  232. package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
  233. package/packages/piagent-core/runtime/recovery/handoff-projection.ts +4 -1
  234. package/packages/piagent-core/runtime/recovery/pristine-task-policy-refresh.ts +91 -0
  235. package/packages/piagent-core/runtime/recovery/recovery-policy.ts +7 -4
  236. package/packages/piagent-core/runtime/recovery/resume-state.ts +112 -14
  237. package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +60 -13
  238. package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
  239. package/packages/piagent-core/runtime/recovery/task-evidence-coverage-refresh.ts +217 -0
  240. package/packages/piagent-core/runtime/registration/context-commands.ts +82 -7
  241. package/packages/piagent-core/runtime/registration/knowledge-tools.ts +9 -5
  242. package/packages/piagent-core/runtime/registration/operator-request-intake.ts +49 -0
  243. package/packages/piagent-core/runtime/registration/policy-tools.ts +50 -15
  244. package/packages/piagent-core/runtime/registration/session-commands.ts +1 -1
  245. package/packages/piagent-core/runtime/registration/task-completion-tools.ts +61 -25
  246. package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +21 -12
  247. package/packages/piagent-core/runtime/registration/task-start-guidance.ts +169 -0
  248. package/packages/piagent-core/runtime/registration/task-start-manifest.ts +54 -0
  249. package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
  250. package/packages/piagent-core/runtime/registration/task-start-tool.ts +108 -103
  251. package/packages/piagent-core/runtime/runtime-limits.ts +16 -2
  252. package/packages/piagent-core/runtime/session/adaptive-context-analysis.ts +461 -0
  253. package/packages/piagent-core/runtime/session/adaptive-context-governor.ts +464 -0
  254. package/packages/piagent-core/runtime/session/adaptive-context-ledger.ts +265 -0
  255. package/packages/piagent-core/runtime/session/message-signals.ts +7 -3
  256. package/packages/piagent-core/runtime/session/operator-request-carry.ts +44 -0
  257. package/packages/piagent-core/runtime/session/runtime-state.ts +161 -5
  258. package/packages/piagent-core/runtime/session/source-checkout-read-grants.ts +35 -0
  259. package/packages/piagent-core/runtime/session/system-prompt.ts +76 -14
  260. package/packages/piagent-core/runtime/session/task-contract-presentation.ts +39 -0
  261. package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
  262. package/packages/piagent-core/runtime/tools/phase-tools.ts +8 -8
  263. package/packages/piagent-core/runtime/tools/tool-groups.ts +12 -2
  264. package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
  265. package/packages/piagent-core/runtime/verification/exact-verifier-reuse.ts +101 -0
  266. package/packages/piagent-core/runtime/workflows/runtime-intake-compaction.ts +164 -0
  267. package/packages/piagent-core/runtime/workflows/task-intake.ts +207 -38
  268. package/packages/piagent-core/security/sensitive-data.js +13 -218
  269. package/packages/piagent-core/security/sensitive-project-file.js +42 -0
  270. package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
  271. package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
  272. package/packages/piagent-core/security/sensitive-text.js +234 -0
  273. package/packages/piagent-core/subagents/piagent-oracle.md +5 -3
  274. package/packages/piagent-core/subagents/piagent-planner.md +6 -4
  275. package/packages/piagent-core/subagents/piagent-reviewer.md +5 -3
  276. package/packages/piagent-core/subagents/piagent-scout.md +4 -2
  277. package/packages/piagent-core/subagents/piagent-worker.md +7 -5
  278. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-B4XehTCU.js} +1 -1
  279. package/packages/piagent-webui/dist/client/assets/index-CgOEyRPG.js +176 -0
  280. package/packages/piagent-webui/dist/client/index.html +1 -1
  281. package/packages/piagent-webui/extension/piagent-webui.ts +3 -1
  282. package/packages/piagent-webui/extension/same-session-bridge.ts +1 -1
  283. package/packages/piagent-webui/extension/session-stream-adapter.ts +33 -10
  284. package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
  285. package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
  286. package/packages/piagent-webui/gateway/gateway-session-stream.ts +122 -20
  287. package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
  288. package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
  289. package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
  290. package/packages/piagent-webui/gateway/session-operation-lifecycle.ts +237 -0
  291. package/packages/piagent-webui/gateway/session-operation-retry-control.ts +38 -0
  292. package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
  293. package/packages/piagent-webui/gateway/session-operation-watchdog.ts +165 -0
  294. package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
  295. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +141 -125
  296. package/packages/piagent-webui/ownership/session-lease-store.ts +5 -2
  297. package/packages/piagent-webui/server/core-inspection-provider.ts +2 -1
  298. package/packages/piagent-webui/server/loopback-server.ts +8 -0
  299. package/packages/piagent-webui/server/transcript-projection.ts +113 -9
  300. package/packages/piagent-webui/shared/text-visibility.ts +10 -0
  301. package/schemas/codex-relative-efficiency-v1.schema.json +155 -0
  302. package/schemas/piagent-webui/catalog-v1.json +9 -0
  303. package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
  304. package/schemas/piagent-webui/runtime-event-v2.schema.json +15 -5
  305. package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
  306. package/schemas/piagent-webui/subagent-tree-v1.schema.json +11 -1
  307. package/schemas/project-profile.schema.json +1 -1
  308. package/schemas/task-contract.schema.json +27 -1
  309. package/scripts/benchmark-runner-core.mjs +367 -363
  310. package/scripts/benchmark-runner-finalization.mjs +313 -0
  311. package/scripts/benchmark-runner-host-readiness.mjs +94 -0
  312. package/scripts/benchmark-runner-support.mjs +200 -0
  313. package/scripts/benchmark-session.mjs +95 -41
  314. package/scripts/configure-model-scope.sh +4 -3
  315. package/scripts/configure-subagents.sh +42 -34
  316. package/scripts/helper-evaluation.mjs +2 -2
  317. package/scripts/install-global.sh +2 -2
  318. package/scripts/pi-usage-history.mjs +37 -10
  319. package/scripts/setup.sh +2 -2
  320. package/scripts/team-doctor.sh +1 -1
  321. package/scripts/verify-local.sh +10 -1
  322. package/templates/global/settings.json +2 -1
  323. package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
@@ -0,0 +1,639 @@
1
+ import crypto from "node:crypto";
2
+ import os from "node:os";
3
+ import { performance } from "node:perf_hooks";
4
+ import { setTimeout as delay } from "node:timers/promises";
5
+
6
+ const DIGEST_ALGORITHM = "sha256-canonical-json-v1";
7
+ const POLICY_DOMAIN = "piagent-benchmark-host-readiness-policy-v1\0";
8
+ const HOST_DOMAIN = "piagent-benchmark-host-fingerprint-v1\0";
9
+ const RECEIPT_DOMAIN = "piagent-benchmark-host-readiness-receipt-v1\0";
10
+ const TIMING_UNDERRUN_TOLERANCE_RATIO = 0.05;
11
+ const POLICY_FIELDS = Object.freeze([
12
+ "schemaVersion", "required", "sampleCount", "sampleIntervalMilliseconds",
13
+ "cpuSampleMilliseconds", "maximumNormalizedLoad1", "minimumCpuIdlePercent",
14
+ "maximumTimingOverrunRatio", "maximumStartDelayMilliseconds", "failFast"
15
+ ]);
16
+ const RECEIPT_FIELDS = Object.freeze([
17
+ "schemaVersion", "kind", "digestAlgorithm", "policyDigest", "hostFingerprint",
18
+ "runId", "configurationDigest", "collectedAt", "completedRuns", "authorizedThroughRuns",
19
+ "expectedSamples", "observedSamples", "status", "failureReasons", "samples", "digest"
20
+ ]);
21
+ const HOST_FIELDS = Object.freeze(["schemaVersion", "platform", "architecture", "logicalCpuCount", "digest"]);
22
+ const SAMPLE_FIELDS = Object.freeze([
23
+ "index", "startedOffsetMilliseconds", "cpuWindowMilliseconds", "normalizedLoad1",
24
+ "cpuIdlePercent", "passed", "failures"
25
+ ]);
26
+ const CPU_TIME_FIELDS = Object.freeze(["user", "nice", "sys", "idle", "irq"]);
27
+ const SHA256 = /^[a-f0-9]{64}$/;
28
+ const SAFE_HOST_LABEL = /^[a-z0-9][a-z0-9._-]{0,31}$/i;
29
+ const SAFE_RUN_ID = /^[a-z0-9][a-z0-9._:-]{0,255}$/i;
30
+
31
+ function plainObject(value) {
32
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
33
+ }
34
+
35
+ function exactFields(value, fields, label, errors) {
36
+ if (!plainObject(value)) {
37
+ errors.push(`${label}-must-be-an-object`);
38
+ return false;
39
+ }
40
+ const allowed = new Set(fields);
41
+ for (const field of Object.keys(value)) {
42
+ if (!allowed.has(field)) errors.push(`${label}-unsupported-field:${field}`);
43
+ }
44
+ for (const field of fields) {
45
+ if (!Object.hasOwn(value, field)) errors.push(`${label}-missing-field:${field}`);
46
+ }
47
+ return true;
48
+ }
49
+
50
+ function canonicalJson(value, seen = new Set()) {
51
+ if (value === null) return "null";
52
+ if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
53
+ if (typeof value === "number") {
54
+ if (!Number.isFinite(value)) throw new TypeError("canonical JSON refuses non-finite numbers");
55
+ return JSON.stringify(Object.is(value, -0) ? 0 : value);
56
+ }
57
+ if (typeof value !== "object") throw new TypeError(`canonical JSON refuses ${typeof value}`);
58
+ if (seen.has(value)) throw new TypeError("canonical JSON refuses cycles");
59
+ seen.add(value);
60
+ let encoded;
61
+ if (Array.isArray(value)) {
62
+ encoded = `[${value.map((item) => canonicalJson(item, seen)).join(",")}]`;
63
+ } else {
64
+ const keys = Object.keys(value).sort((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right)));
65
+ encoded = `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key], seen)}`).join(",")}}`;
66
+ }
67
+ seen.delete(value);
68
+ return encoded;
69
+ }
70
+
71
+ function sha256(domain, value) {
72
+ return crypto.createHash("sha256").update(domain).update(canonicalJson(value)).digest("hex");
73
+ }
74
+
75
+ function deepFreeze(value) {
76
+ if (!value || typeof value !== "object" || Object.isFrozen(value)) return value;
77
+ for (const child of Object.values(value)) deepFreeze(child);
78
+ return Object.freeze(value);
79
+ }
80
+
81
+ function canonicalPolicy(policy) {
82
+ return {
83
+ schemaVersion: policy.schemaVersion,
84
+ required: policy.required,
85
+ sampleCount: policy.sampleCount,
86
+ sampleIntervalMilliseconds: policy.sampleIntervalMilliseconds,
87
+ cpuSampleMilliseconds: policy.cpuSampleMilliseconds,
88
+ maximumNormalizedLoad1: policy.maximumNormalizedLoad1,
89
+ minimumCpuIdlePercent: policy.minimumCpuIdlePercent,
90
+ maximumTimingOverrunRatio: policy.maximumTimingOverrunRatio,
91
+ maximumStartDelayMilliseconds: policy.maximumStartDelayMilliseconds,
92
+ failFast: policy.failFast
93
+ };
94
+ }
95
+
96
+ /** Validate the closed, immutable host-readiness policy from spend-control.v1.json. */
97
+ export function benchmarkHostReadinessPolicyValidationErrors(policy) {
98
+ const errors = [];
99
+ if (!exactFields(policy, POLICY_FIELDS, "host-readiness-policy", errors)) return deepFreeze(errors);
100
+ if (policy.schemaVersion !== 1) errors.push("host-readiness-policy-schema-version-must-be-1");
101
+ if (policy.required !== true) errors.push("host-readiness-policy-required-must-be-true");
102
+ if (policy.sampleCount !== 3) errors.push("host-readiness-policy-sample-count-must-be-3");
103
+ if (!Number.isSafeInteger(policy.sampleIntervalMilliseconds) || policy.sampleIntervalMilliseconds < 0
104
+ || policy.sampleIntervalMilliseconds > 600_000) {
105
+ errors.push("host-readiness-policy-sample-interval-milliseconds-invalid");
106
+ }
107
+ if (!Number.isSafeInteger(policy.cpuSampleMilliseconds) || policy.cpuSampleMilliseconds <= 0
108
+ || policy.cpuSampleMilliseconds > 60_000) {
109
+ errors.push("host-readiness-policy-cpu-sample-milliseconds-invalid");
110
+ }
111
+ if (!Number.isFinite(policy.maximumNormalizedLoad1) || policy.maximumNormalizedLoad1 <= 0
112
+ || policy.maximumNormalizedLoad1 > 1) {
113
+ errors.push("host-readiness-policy-maximum-normalized-load1-invalid");
114
+ }
115
+ if (!Number.isFinite(policy.minimumCpuIdlePercent) || policy.minimumCpuIdlePercent < 0
116
+ || policy.minimumCpuIdlePercent > 100) {
117
+ errors.push("host-readiness-policy-minimum-cpu-idle-percent-invalid");
118
+ }
119
+ if (!Number.isFinite(policy.maximumTimingOverrunRatio) || policy.maximumTimingOverrunRatio < 0
120
+ || policy.maximumTimingOverrunRatio > 1) {
121
+ errors.push("host-readiness-policy-maximum-timing-overrun-ratio-invalid");
122
+ }
123
+ if (!Number.isSafeInteger(policy.maximumStartDelayMilliseconds)
124
+ || policy.maximumStartDelayMilliseconds <= 0 || policy.maximumStartDelayMilliseconds > 600_000) {
125
+ errors.push("host-readiness-policy-maximum-start-delay-milliseconds-invalid");
126
+ }
127
+ if (policy.failFast !== true) errors.push("host-readiness-policy-fail-fast-must-be-true");
128
+ return deepFreeze(errors);
129
+ }
130
+
131
+ /** Return the order-independent SHA-256 binding for an exact, valid policy. */
132
+ export function benchmarkHostReadinessPolicyDigest(policy) {
133
+ const errors = benchmarkHostReadinessPolicyValidationErrors(policy);
134
+ if (errors.length > 0) {
135
+ const error = new Error(`Invalid benchmark host-readiness policy: ${errors.join(", ")}`);
136
+ error.code = "BENCHMARK_HOST_READINESS_POLICY_INVALID";
137
+ throw error;
138
+ }
139
+ return sha256(POLICY_DOMAIN, canonicalPolicy(policy));
140
+ }
141
+
142
+ function finiteNonnegative(value) {
143
+ return Number.isFinite(value) && value >= 0;
144
+ }
145
+
146
+ function measuredCpus(value, expectedCount = null) {
147
+ if (!Array.isArray(value) || value.length === 0 || (expectedCount !== null && value.length !== expectedCount)) {
148
+ throw measurementError("cpu-topology-invalid");
149
+ }
150
+ return value.map((cpu) => {
151
+ if (!plainObject(cpu?.times)) throw measurementError("cpu-times-invalid");
152
+ const times = {};
153
+ for (const field of CPU_TIME_FIELDS) {
154
+ if (!finiteNonnegative(cpu.times[field])) throw measurementError(`cpu-time-${field}-invalid`);
155
+ times[field] = cpu.times[field];
156
+ }
157
+ return times;
158
+ });
159
+ }
160
+
161
+ function measurementError(reason) {
162
+ const error = new Error(`Benchmark host-readiness measurement failed closed: ${reason}`);
163
+ error.code = "BENCHMARK_HOST_READINESS_MEASUREMENT_INVALID";
164
+ return error;
165
+ }
166
+
167
+ function monotonicValue(read) {
168
+ const value = read();
169
+ if (!finiteNonnegative(value)) throw measurementError("monotonic-clock-invalid");
170
+ return value;
171
+ }
172
+
173
+ function cpuIdlePercent(before, after) {
174
+ if (before.length !== after.length) throw measurementError("cpu-topology-drift");
175
+ let idleDelta = 0;
176
+ let totalDelta = 0;
177
+ for (let index = 0; index < before.length; index += 1) {
178
+ for (const field of CPU_TIME_FIELDS) {
179
+ const delta = after[index][field] - before[index][field];
180
+ if (!finiteNonnegative(delta)) throw measurementError(`cpu-time-${field}-not-monotonic`);
181
+ totalDelta += delta;
182
+ if (field === "idle") idleDelta += delta;
183
+ }
184
+ }
185
+ if (!(totalDelta > 0)) throw measurementError("cpu-tick-delta-empty");
186
+ return idleDelta * 100 / totalDelta;
187
+ }
188
+
189
+ function rounded(value, digits) {
190
+ const factor = 10 ** digits;
191
+ return Math.round((value + Number.EPSILON) * factor) / factor;
192
+ }
193
+
194
+ function hostFingerprint(platform, architecture, logicalCpuCount) {
195
+ if (typeof platform !== "string" || !SAFE_HOST_LABEL.test(platform)) throw measurementError("platform-invalid");
196
+ if (typeof architecture !== "string" || !SAFE_HOST_LABEL.test(architecture)) throw measurementError("architecture-invalid");
197
+ if (!Number.isSafeInteger(logicalCpuCount) || logicalCpuCount <= 0 || logicalCpuCount > 65_536) {
198
+ throw measurementError("logical-cpu-count-invalid");
199
+ }
200
+ const basis = { schemaVersion: 1, platform, architecture, logicalCpuCount };
201
+ return deepFreeze({ ...basis, digest: sha256(HOST_DOMAIN, basis) });
202
+ }
203
+
204
+ function timingBounds(expectedMilliseconds, policy) {
205
+ return {
206
+ minimum: expectedMilliseconds * (1 - TIMING_UNDERRUN_TOLERANCE_RATIO),
207
+ maximum: expectedMilliseconds * (1 + policy.maximumTimingOverrunRatio)
208
+ };
209
+ }
210
+
211
+ function sampleFailures(sample, policy, previous = null) {
212
+ const failures = [];
213
+ const cpuWindow = timingBounds(policy.cpuSampleMilliseconds, policy);
214
+ if (sample.cpuWindowMilliseconds < cpuWindow.minimum) failures.push("cpu-sample-window-underrun");
215
+ if (sample.cpuWindowMilliseconds > cpuWindow.maximum) failures.push("cpu-sample-window-overrun");
216
+ if (previous && policy.sampleIntervalMilliseconds > 0) {
217
+ const startDelta = sample.startedOffsetMilliseconds - previous.startedOffsetMilliseconds;
218
+ const cadence = timingBounds(policy.sampleIntervalMilliseconds, policy);
219
+ if (startDelta < cadence.minimum) failures.push("sample-start-cadence-underrun");
220
+ if (startDelta > cadence.maximum) failures.push("sample-start-cadence-overrun");
221
+ }
222
+ if (sample.normalizedLoad1 > policy.maximumNormalizedLoad1) failures.push("maximum-normalized-load1-exceeded");
223
+ if (sample.cpuIdlePercent < policy.minimumCpuIdlePercent) failures.push("minimum-cpu-idle-percent-not-met");
224
+ return failures;
225
+ }
226
+
227
+ function receiptDigest(receipt) {
228
+ const { digest: _digest, ...basis } = receipt;
229
+ return sha256(RECEIPT_DOMAIN, basis);
230
+ }
231
+
232
+ function collectionDependencies(overrides = {}) {
233
+ const dependencies = {
234
+ loadavg: () => os.loadavg(),
235
+ cpus: () => os.cpus(),
236
+ platform: () => os.platform(),
237
+ arch: () => os.arch(),
238
+ monotonicNow: () => performance.now(),
239
+ wallClockNow: () => Date.now(),
240
+ sleep: (milliseconds) => delay(milliseconds),
241
+ ...overrides
242
+ };
243
+ for (const field of ["loadavg", "cpus", "platform", "arch", "monotonicNow", "wallClockNow", "sleep"]) {
244
+ if (typeof dependencies[field] !== "function") throw new TypeError(`host readiness dependency ${field} must be a function`);
245
+ }
246
+ return dependencies;
247
+ }
248
+
249
+ function validRunId(value) {
250
+ return typeof value === "string" && SAFE_RUN_ID.test(value);
251
+ }
252
+
253
+ function validConfigurationDigest(value) {
254
+ return typeof value === "string" && SHA256.test(value);
255
+ }
256
+
257
+ function canonicalWallClockTimestamp(read) {
258
+ const value = read();
259
+ if (!finiteNonnegative(value)) throw measurementError("wall-clock-invalid");
260
+ try { return new Date(value).toISOString(); }
261
+ catch { throw measurementError("wall-clock-invalid"); }
262
+ }
263
+
264
+ function timestampMilliseconds(value) {
265
+ if (typeof value !== "string") return null;
266
+ const milliseconds = Date.parse(value);
267
+ if (!Number.isFinite(milliseconds)) return null;
268
+ try {
269
+ if (new Date(milliseconds).toISOString() !== value) return null;
270
+ } catch { return null; }
271
+ return milliseconds;
272
+ }
273
+
274
+ function validWindow(completedRuns, authorizedThroughRuns) {
275
+ return Number.isSafeInteger(completedRuns) && completedRuns >= 0
276
+ && Number.isSafeInteger(authorizedThroughRuns) && authorizedThroughRuns > completedRuns;
277
+ }
278
+
279
+ /**
280
+ * Collect a privacy-safe, three-sample host receipt before a provider window.
281
+ * The injected clock and sleeper make tests deterministic without real waiting.
282
+ */
283
+ export async function collectBenchmarkHostReadinessReceipt(input, dependencyOverrides = {}) {
284
+ const inputErrors = [];
285
+ if (!exactFields(input, ["policy", "runId", "configurationDigest", "completedRuns", "authorizedThroughRuns"], "host-readiness-input", inputErrors)) {
286
+ throw new TypeError(inputErrors.join(", "));
287
+ }
288
+ const policyErrors = benchmarkHostReadinessPolicyValidationErrors(input.policy);
289
+ if (policyErrors.length > 0) {
290
+ const error = new Error(`Invalid benchmark host-readiness policy: ${policyErrors.join(", ")}`);
291
+ error.code = "BENCHMARK_HOST_READINESS_POLICY_INVALID";
292
+ throw error;
293
+ }
294
+ if (!validWindow(input.completedRuns, input.authorizedThroughRuns)) {
295
+ const error = new Error("Benchmark host-readiness authorization window is invalid");
296
+ error.code = "BENCHMARK_HOST_READINESS_WINDOW_INVALID";
297
+ throw error;
298
+ }
299
+ if (!validRunId(input.runId)) {
300
+ const error = new Error("Benchmark host-readiness run binding is invalid");
301
+ error.code = "BENCHMARK_HOST_READINESS_RUN_ID_INVALID";
302
+ throw error;
303
+ }
304
+ if (!validConfigurationDigest(input.configurationDigest)) {
305
+ const error = new Error("Benchmark host-readiness configuration binding is invalid");
306
+ error.code = "BENCHMARK_HOST_READINESS_CONFIGURATION_DIGEST_INVALID";
307
+ throw error;
308
+ }
309
+
310
+ const policy = deepFreeze(canonicalPolicy(input.policy));
311
+ const dependencies = collectionDependencies(dependencyOverrides);
312
+ const firstBefore = measuredCpus(dependencies.cpus());
313
+ const fingerprint = hostFingerprint(dependencies.platform(), dependencies.arch(), firstBefore.length);
314
+ const origin = monotonicValue(dependencies.monotonicNow);
315
+ let before = firstBefore;
316
+ const samples = [];
317
+
318
+ for (let index = 0; index < policy.sampleCount; index += 1) {
319
+ if (index > 0) before = measuredCpus(dependencies.cpus(), fingerprint.logicalCpuCount);
320
+ const started = index === 0 ? origin : monotonicValue(dependencies.monotonicNow);
321
+ const load = dependencies.loadavg();
322
+ if (!Array.isArray(load) || !finiteNonnegative(load[0])) throw measurementError("load-average-invalid");
323
+ await dependencies.sleep(policy.cpuSampleMilliseconds);
324
+ const after = measuredCpus(dependencies.cpus(), fingerprint.logicalCpuCount);
325
+ const ended = monotonicValue(dependencies.monotonicNow);
326
+ if (ended < started) throw measurementError("monotonic-clock-regressed");
327
+
328
+ const sample = {
329
+ index: index + 1,
330
+ startedOffsetMilliseconds: rounded(started - origin, 3),
331
+ cpuWindowMilliseconds: rounded(ended - started, 3),
332
+ normalizedLoad1: rounded(load[0] / fingerprint.logicalCpuCount, 6),
333
+ cpuIdlePercent: rounded(cpuIdlePercent(before, after), 6)
334
+ };
335
+ if (!(sample.cpuWindowMilliseconds > 0)) throw measurementError("cpu-window-empty");
336
+ const failures = sampleFailures(sample, policy, samples.at(-1) ?? null);
337
+ samples.push(deepFreeze({ ...sample, passed: failures.length === 0, failures: deepFreeze(failures) }));
338
+ if (failures.length > 0) break;
339
+
340
+ if (index + 1 < policy.sampleCount) {
341
+ const remainingInterval = Math.max(0, policy.sampleIntervalMilliseconds - (ended - started));
342
+ await dependencies.sleep(remainingInterval);
343
+ }
344
+ }
345
+
346
+ const failureReasons = samples.find((sample) => !sample.passed)?.failures ?? [];
347
+ const collectedAt = canonicalWallClockTimestamp(dependencies.wallClockNow);
348
+ const receipt = {
349
+ schemaVersion: 1,
350
+ kind: "benchmark-host-readiness",
351
+ digestAlgorithm: DIGEST_ALGORITHM,
352
+ policyDigest: benchmarkHostReadinessPolicyDigest(policy),
353
+ hostFingerprint: fingerprint,
354
+ runId: input.runId,
355
+ configurationDigest: input.configurationDigest,
356
+ collectedAt,
357
+ completedRuns: input.completedRuns,
358
+ authorizedThroughRuns: input.authorizedThroughRuns,
359
+ expectedSamples: policy.sampleCount,
360
+ observedSamples: samples.length,
361
+ status: failureReasons.length === 0 && samples.length === policy.sampleCount ? "ready" : "blocked",
362
+ failureReasons: [...failureReasons],
363
+ samples
364
+ };
365
+ return deepFreeze({ ...receipt, digest: receiptDigest(receipt) });
366
+ }
367
+
368
+ function validateHostFingerprint(value, errors) {
369
+ if (!exactFields(value, HOST_FIELDS, "host-fingerprint", errors)) return;
370
+ if (value.schemaVersion !== 1) errors.push("host-fingerprint-schema-version-must-be-1");
371
+ if (typeof value.platform !== "string" || !SAFE_HOST_LABEL.test(value.platform)) errors.push("host-fingerprint-platform-invalid");
372
+ if (typeof value.architecture !== "string" || !SAFE_HOST_LABEL.test(value.architecture)) errors.push("host-fingerprint-architecture-invalid");
373
+ if (!Number.isSafeInteger(value.logicalCpuCount) || value.logicalCpuCount <= 0 || value.logicalCpuCount > 65_536) {
374
+ errors.push("host-fingerprint-logical-cpu-count-invalid");
375
+ }
376
+ if (!SHA256.test(String(value.digest ?? ""))) errors.push("host-fingerprint-digest-invalid");
377
+ else {
378
+ try {
379
+ const basis = {
380
+ schemaVersion: value.schemaVersion,
381
+ platform: value.platform,
382
+ architecture: value.architecture,
383
+ logicalCpuCount: value.logicalCpuCount
384
+ };
385
+ if (sha256(HOST_DOMAIN, basis) !== value.digest) errors.push("host-fingerprint-digest-mismatch");
386
+ } catch { errors.push("host-fingerprint-not-canonical"); }
387
+ }
388
+ }
389
+
390
+ function validateSample(sample, index, policy, previous, errors) {
391
+ const label = `host-readiness-sample-${index + 1}`;
392
+ if (!exactFields(sample, SAMPLE_FIELDS, label, errors)) return;
393
+ if (sample.index !== index + 1) errors.push(`${label}-index-invalid`);
394
+ if (!finiteNonnegative(sample.startedOffsetMilliseconds)) errors.push(`${label}-start-offset-invalid`);
395
+ if (index === 0 && sample.startedOffsetMilliseconds !== 0) errors.push(`${label}-first-start-offset-must-be-zero`);
396
+ if (!Number.isFinite(sample.cpuWindowMilliseconds) || sample.cpuWindowMilliseconds <= 0) errors.push(`${label}-cpu-window-invalid`);
397
+ if (!finiteNonnegative(sample.normalizedLoad1)) errors.push(`${label}-normalized-load1-invalid`);
398
+ if (!Number.isFinite(sample.cpuIdlePercent) || sample.cpuIdlePercent < 0 || sample.cpuIdlePercent > 100) {
399
+ errors.push(`${label}-cpu-idle-percent-invalid`);
400
+ }
401
+ if (previous && finiteNonnegative(sample.startedOffsetMilliseconds)
402
+ && sample.startedOffsetMilliseconds < previous.startedOffsetMilliseconds) errors.push(`${label}-clock-regressed`);
403
+ if (!Array.isArray(sample.failures)) errors.push(`${label}-failures-must-be-an-array`);
404
+ if (typeof sample.passed !== "boolean") errors.push(`${label}-passed-must-be-boolean`);
405
+ if (finiteNonnegative(sample.normalizedLoad1) && Number.isFinite(sample.cpuIdlePercent)
406
+ && sample.cpuIdlePercent >= 0 && sample.cpuIdlePercent <= 100
407
+ && Number.isFinite(sample.cpuWindowMilliseconds) && sample.cpuWindowMilliseconds > 0
408
+ && finiteNonnegative(sample.startedOffsetMilliseconds) && Array.isArray(sample.failures)) {
409
+ const expected = sampleFailures(sample, policy, previous);
410
+ if (JSON.stringify(sample.failures) !== JSON.stringify(expected)) errors.push(`${label}-failure-reasons-mismatch`);
411
+ if (sample.passed !== (expected.length === 0)) errors.push(`${label}-decision-mismatch`);
412
+ }
413
+ }
414
+
415
+ /** Validate receipt integrity, policy binding, decision invariants and an optional exact window/host. */
416
+ export function validateBenchmarkHostReadinessReceipt(receipt, options = {}) {
417
+ const errors = [];
418
+ const policyErrors = benchmarkHostReadinessPolicyValidationErrors(options.policy);
419
+ if (policyErrors.length > 0) errors.push(...policyErrors);
420
+ if (!exactFields(receipt, RECEIPT_FIELDS, "host-readiness-receipt", errors)) {
421
+ return deepFreeze({ valid: false, ready: false, errors, receipt: null });
422
+ }
423
+ if (receipt.schemaVersion !== 1) errors.push("host-readiness-receipt-schema-version-must-be-1");
424
+ if (receipt.kind !== "benchmark-host-readiness") errors.push("host-readiness-receipt-kind-invalid");
425
+ if (receipt.digestAlgorithm !== DIGEST_ALGORITHM) errors.push("host-readiness-receipt-digest-algorithm-invalid");
426
+ if (!SHA256.test(String(receipt.policyDigest ?? ""))) errors.push("host-readiness-receipt-policy-digest-invalid");
427
+ if (!SHA256.test(String(receipt.digest ?? ""))) errors.push("host-readiness-receipt-digest-invalid");
428
+ validateHostFingerprint(receipt.hostFingerprint, errors);
429
+ if (!validRunId(receipt.runId)) errors.push("host-readiness-receipt-run-id-invalid");
430
+ if (!validConfigurationDigest(receipt.configurationDigest)) errors.push("host-readiness-receipt-configuration-digest-invalid");
431
+ if (timestampMilliseconds(receipt.collectedAt) === null) errors.push("host-readiness-receipt-collected-at-invalid");
432
+ if (!validRunId(options.runId)) errors.push("host-readiness-receipt-expected-run-id-invalid");
433
+ else if (receipt.runId !== options.runId) errors.push("host-readiness-receipt-run-id-mismatch");
434
+ if (!validConfigurationDigest(options.configurationDigest)) {
435
+ errors.push("host-readiness-receipt-expected-configuration-digest-invalid");
436
+ } else if (receipt.configurationDigest !== options.configurationDigest) {
437
+ errors.push("host-readiness-receipt-configuration-digest-mismatch");
438
+ }
439
+ if (!validWindow(receipt.completedRuns, receipt.authorizedThroughRuns)) errors.push("host-readiness-receipt-window-invalid");
440
+ if (options.completedRuns !== undefined && receipt.completedRuns !== options.completedRuns) errors.push("host-readiness-receipt-completed-runs-mismatch");
441
+ if (options.authorizedThroughRuns !== undefined && receipt.authorizedThroughRuns !== options.authorizedThroughRuns) {
442
+ errors.push("host-readiness-receipt-authorized-through-runs-mismatch");
443
+ }
444
+ if (options.hostFingerprintDigest !== undefined && receipt.hostFingerprint?.digest !== options.hostFingerprintDigest) {
445
+ errors.push("host-readiness-receipt-host-drift");
446
+ }
447
+
448
+ if (policyErrors.length === 0) {
449
+ const policy = canonicalPolicy(options.policy);
450
+ if (receipt.policyDigest !== benchmarkHostReadinessPolicyDigest(policy)) errors.push("host-readiness-receipt-policy-digest-mismatch");
451
+ if (receipt.expectedSamples !== policy.sampleCount) errors.push("host-readiness-receipt-expected-samples-mismatch");
452
+ if (!Array.isArray(receipt.samples)) errors.push("host-readiness-receipt-samples-must-be-an-array");
453
+ else {
454
+ if (receipt.observedSamples !== receipt.samples.length) errors.push("host-readiness-receipt-observed-samples-mismatch");
455
+ if (receipt.samples.length < 1 || receipt.samples.length > policy.sampleCount) errors.push("host-readiness-receipt-sample-count-invalid");
456
+ receipt.samples.forEach((sample, index) => validateSample(sample, index, policy, receipt.samples[index - 1], errors));
457
+ const firstFailed = receipt.samples.findIndex((sample) => sample?.passed === false);
458
+ const expectedFailures = firstFailed < 0 ? [] : receipt.samples[firstFailed].failures;
459
+ const expectedStatus = firstFailed < 0 && receipt.samples.length === policy.sampleCount ? "ready" : "blocked";
460
+ if (firstFailed >= 0 && firstFailed !== receipt.samples.length - 1) errors.push("host-readiness-receipt-did-not-fail-fast");
461
+ if (firstFailed < 0 && receipt.samples.length !== policy.sampleCount) errors.push("host-readiness-receipt-incomplete-without-failure");
462
+ if (receipt.status !== expectedStatus) errors.push("host-readiness-receipt-status-mismatch");
463
+ if (!Array.isArray(receipt.failureReasons)
464
+ || JSON.stringify(receipt.failureReasons) !== JSON.stringify(expectedFailures)) {
465
+ errors.push("host-readiness-receipt-failure-reasons-mismatch");
466
+ }
467
+ }
468
+ }
469
+ try {
470
+ if (SHA256.test(String(receipt.digest ?? "")) && receiptDigest(receipt) !== receipt.digest) {
471
+ errors.push("host-readiness-receipt-digest-mismatch");
472
+ }
473
+ } catch { errors.push("host-readiness-receipt-not-canonical"); }
474
+
475
+ const valid = errors.length === 0;
476
+ let canonicalReceipt = null;
477
+ if (valid) {
478
+ try { canonicalReceipt = deepFreeze(JSON.parse(canonicalJson(receipt))); }
479
+ catch { /* The digest check above already records canonicalization errors. */ }
480
+ }
481
+ return deepFreeze({ valid, ready: valid && receipt.status === "ready", errors, receipt: canonicalReceipt });
482
+ }
483
+
484
+ /**
485
+ * Validate that a ready, bound receipt is still fresh immediately before provider start.
486
+ * `nowMilliseconds` is injectable so tests do not depend on wall-clock time.
487
+ */
488
+ export function benchmarkHostReadinessStartValidationErrors(receipt, options = {}) {
489
+ const validation = validateBenchmarkHostReadinessReceipt(receipt, options);
490
+ const errors = [...validation.errors];
491
+ if (validation.valid && !validation.ready) errors.push("host-readiness-start-receipt-not-ready");
492
+ const now = options.nowMilliseconds ?? Date.now();
493
+ if (!finiteNonnegative(now)) errors.push("host-readiness-start-clock-invalid");
494
+ const collectedAt = timestampMilliseconds(receipt?.collectedAt);
495
+ if (validation.valid && finiteNonnegative(now) && collectedAt !== null) {
496
+ const age = now - collectedAt;
497
+ if (age < 0) errors.push("host-readiness-start-receipt-from-future");
498
+ else if (age > options.policy.maximumStartDelayMilliseconds) {
499
+ errors.push("host-readiness-start-delay-exceeded");
500
+ }
501
+ }
502
+ return deepFreeze(errors);
503
+ }
504
+
505
+ function validatedStageBoundaries(value, errors) {
506
+ if (!Array.isArray(value) || value.length < 2) {
507
+ errors.push("host-readiness-history-stage-boundaries-invalid");
508
+ return [];
509
+ }
510
+ if (value[0] !== 0) errors.push("host-readiness-history-stage-boundaries-must-start-at-zero");
511
+ for (let index = 0; index < value.length; index += 1) {
512
+ if (!Number.isSafeInteger(value[index]) || value[index] < 0) {
513
+ errors.push(`host-readiness-history-stage-boundary-invalid:${index + 1}`);
514
+ }
515
+ if (index > 0 && value[index] <= value[index - 1]) {
516
+ errors.push(`host-readiness-history-stage-boundaries-not-increasing:${index + 1}`);
517
+ }
518
+ }
519
+ return value;
520
+ }
521
+
522
+ /** Validate an ordered receipt history, including authorization continuity and host drift. */
523
+ export function summarizeBenchmarkHostReadinessHistory(input, options = {}) {
524
+ const request = Array.isArray(input) ? { ...options, receipts: input } : input;
525
+ const errors = [];
526
+ const blockingReasons = [];
527
+ if (!plainObject(request)) {
528
+ return deepFreeze({
529
+ schemaVersion: 1, policyDigest: null, receiptCount: 0, validReceiptCount: 0,
530
+ hostFingerprintDigest: null, completedRuns: null, windowStartedAtRuns: null,
531
+ authorizedThroughRuns: null, windowCoverage: "unavailable",
532
+ valid: false, ready: false, errors: ["host-readiness-history-must-be-an-object"], blockingReasons: []
533
+ });
534
+ }
535
+ const policyErrors = benchmarkHostReadinessPolicyValidationErrors(request.policy);
536
+ if (policyErrors.length > 0) errors.push(...policyErrors);
537
+ if (!validRunId(request.runId)) errors.push("host-readiness-history-run-id-invalid");
538
+ if (!validConfigurationDigest(request.configurationDigest)) {
539
+ errors.push("host-readiness-history-configuration-digest-invalid");
540
+ }
541
+ const stageBoundaries = validatedStageBoundaries(request.stageBoundaries, errors);
542
+ const receipts = Array.isArray(request.receipts) ? request.receipts : [];
543
+ if (!Array.isArray(request.receipts)) errors.push("host-readiness-history-receipts-must-be-an-array");
544
+ if (receipts.length === 0) errors.push("host-readiness-history-empty");
545
+
546
+ let hostDigest = null;
547
+ let previous = null;
548
+ let validReceiptCount = 0;
549
+ for (let index = 0; index < receipts.length; index += 1) {
550
+ const result = validateBenchmarkHostReadinessReceipt(receipts[index], {
551
+ policy: request.policy,
552
+ runId: request.runId,
553
+ configurationDigest: request.configurationDigest
554
+ });
555
+ if (!result.valid) errors.push(...result.errors.map((error) => `host-readiness-history-receipt-${index + 1}:${error}`));
556
+ else {
557
+ validReceiptCount += 1;
558
+ const currentHost = result.receipt.hostFingerprint.digest;
559
+ if (hostDigest === null) hostDigest = currentHost;
560
+ else if (currentHost !== hostDigest) errors.push(`host-readiness-history-host-drift:${index + 1}`);
561
+ if (index === 0 && result.receipt.completedRuns !== 0) {
562
+ errors.push("host-readiness-history-does-not-start-at-zero");
563
+ }
564
+ const boundaryIndex = stageBoundaries.indexOf(result.receipt.authorizedThroughRuns);
565
+ if (stageBoundaries.length > 0 && boundaryIndex < 1) {
566
+ errors.push(`host-readiness-history-authorization-not-a-stage-boundary:${index + 1}`);
567
+ }
568
+ if (index === 0 && stageBoundaries.length > 1
569
+ && result.receipt.authorizedThroughRuns !== stageBoundaries[1]) {
570
+ errors.push("host-readiness-history-skipped-stage-window:1");
571
+ }
572
+ if (previous) {
573
+ if (result.receipt.completedRuns < previous.completedRuns) {
574
+ errors.push(`host-readiness-history-completed-runs-regressed:${index + 1}`);
575
+ }
576
+ if (result.receipt.completedRuns > previous.authorizedThroughRuns) {
577
+ errors.push(`host-readiness-history-window-gap:${index + 1}`);
578
+ }
579
+ if (result.receipt.authorizedThroughRuns < previous.authorizedThroughRuns) {
580
+ errors.push(`host-readiness-history-authorization-regressed:${index + 1}`);
581
+ } else if (result.receipt.authorizedThroughRuns > previous.authorizedThroughRuns) {
582
+ const previousBoundaryIndex = stageBoundaries.indexOf(previous.authorizedThroughRuns);
583
+ if (previousBoundaryIndex < 0
584
+ || result.receipt.authorizedThroughRuns !== stageBoundaries[previousBoundaryIndex + 1]) {
585
+ errors.push(`host-readiness-history-skipped-stage-window:${index + 1}`);
586
+ }
587
+ if (result.receipt.completedRuns !== previous.authorizedThroughRuns) {
588
+ errors.push(`host-readiness-history-window-discontinuity:${index + 1}`);
589
+ }
590
+ }
591
+ const previousCollectedAt = timestampMilliseconds(previous.collectedAt);
592
+ const currentCollectedAt = timestampMilliseconds(result.receipt.collectedAt);
593
+ if (previousCollectedAt !== null && currentCollectedAt !== null
594
+ && currentCollectedAt <= previousCollectedAt) {
595
+ errors.push(`host-readiness-history-collected-at-not-increasing:${index + 1}`);
596
+ }
597
+ }
598
+ if (previous?.status === "blocked") errors.push(`host-readiness-history-continued-after-block:${index + 1}`);
599
+ previous = result.receipt;
600
+ if (!result.ready) blockingReasons.push(`host-readiness-history-receipt-blocked:${index + 1}`);
601
+ }
602
+ }
603
+ const last = previous;
604
+ if (!Number.isSafeInteger(request.completedRuns) || request.completedRuns < 0) {
605
+ errors.push("host-readiness-history-completed-runs-invalid");
606
+ } else if (last && (request.completedRuns < last.completedRuns
607
+ || request.completedRuns > last.authorizedThroughRuns)) {
608
+ errors.push("host-readiness-history-completed-runs-outside-authorized-window");
609
+ }
610
+ if (!Number.isSafeInteger(request.authorizedThroughRuns) || request.authorizedThroughRuns <= 0) {
611
+ errors.push("host-readiness-history-authorized-through-runs-invalid");
612
+ } else if (last?.authorizedThroughRuns !== request.authorizedThroughRuns) {
613
+ errors.push("host-readiness-history-authorized-through-runs-mismatch");
614
+ }
615
+ if (request.windowStartedAtRuns !== undefined && last?.completedRuns !== request.windowStartedAtRuns) {
616
+ errors.push("host-readiness-history-window-started-at-runs-mismatch");
617
+ }
618
+ const windowCoverage = !last || !Number.isSafeInteger(request.completedRuns)
619
+ ? "unavailable"
620
+ : request.completedRuns === last.completedRuns
621
+ ? "not-started"
622
+ : request.completedRuns === last.authorizedThroughRuns ? "complete" : "in-progress";
623
+ const valid = errors.length === 0;
624
+ return deepFreeze({
625
+ schemaVersion: 1,
626
+ policyDigest: policyErrors.length === 0 ? benchmarkHostReadinessPolicyDigest(request.policy) : null,
627
+ receiptCount: receipts.length,
628
+ validReceiptCount,
629
+ hostFingerprintDigest: hostDigest,
630
+ completedRuns: Number.isSafeInteger(request.completedRuns) ? request.completedRuns : null,
631
+ windowStartedAtRuns: last?.completedRuns ?? null,
632
+ authorizedThroughRuns: last?.authorizedThroughRuns ?? null,
633
+ windowCoverage,
634
+ valid,
635
+ ready: valid && blockingReasons.length === 0 && receipts.length > 0,
636
+ errors,
637
+ blockingReasons
638
+ });
639
+ }