@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
@@ -1,26 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import crypto from "node:crypto";
3
3
  import fs from "node:fs";
4
- import os from "node:os";
5
4
  import path from "node:path";
6
- import readline from "node:readline/promises";
7
- import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import { fileURLToPath } from "node:url";
8
6
 
9
- import {
10
- aggregateSessionUsage, benchmarkSurfaceLabel,
11
- renderBenchmarkHtml,
12
- renderBenchmarkText,
13
- summarizeBenchmark
14
- } from "../packages/piagent-core/benchmark/benchmark-core.js";
7
+ import { aggregateSessionUsage, benchmarkSurfaceLabel } from "../packages/piagent-core/benchmark/benchmark-core.js";
15
8
  import { benchmarkUsage, parseBenchmarkArgs } from "../packages/piagent-core/benchmark/benchmark-cli.js";
16
9
  import { codexModelName, codexThinkingEffort } from "../packages/piagent-core/benchmark/benchmark-codex.js";
17
- import { benchmarkTrustChecklist } from "../packages/piagent-core/benchmark/benchmark-matrix.js";
18
- import { applyBenchmarkClaimRestrictions } from "../packages/piagent-core/benchmark/benchmark-claim-restrictions.js";
19
10
  import { benchmarkEnvironment, benchmarkEnvironmentPolicy, comparisonSurfaces, createCodexRuntime, piagentTreatment } from "../packages/piagent-core/benchmark/benchmark-runtime.js";
20
11
  import { assertBenchmarkPiCredentialReady, assertBenchmarkPiCredentialWritebackPolicy, cleanupBenchmarkPiRuntimeHome, createBenchmarkPiRuntimeHome, resetBenchmarkPiRuntimeEphemeralState, withBenchmarkPiCredentialWriteback } from "../packages/piagent-core/benchmark/benchmark-pi-home.js";
21
12
  import { benchmarkPreflight, benchmarkPreflightReceipt } from "../packages/piagent-core/benchmark/benchmark-preflight.js";
13
+ import { prepareProductionProviderFreeEvidence, productionProviderFreeEvidenceError } from "../packages/piagent-core/benchmark/benchmark-provider-free-evidence.js";
14
+ import { applyBenchmarkExecutionDefaults } from "../packages/piagent-core/benchmark/benchmark-runner-policy.js";
22
15
  import {
23
- cleanupUnretainedWorkspaces,
24
16
  appendPrivateJsonl,
25
17
  createBenchmarkCandidateGuard,
26
18
  loadReplayFailurePlan,
@@ -28,7 +20,6 @@ import {
28
20
  safeInfrastructureDiagnostic,
29
21
  writeBenchmarkAbort,
30
22
  writeBenchmarkRunManifest,
31
- writePrivate,
32
23
  writePrivateAtomic
33
24
  } from "../packages/piagent-core/benchmark/benchmark-forensics.js";
34
25
  import { benchmarkBootstrapCandidateIndex, benchmarkBootstrapMetadata } from "../packages/piagent-core/benchmark/benchmark-bootstrap.js";
@@ -37,8 +28,16 @@ import { benchmarkCommandIdentity } from "../packages/piagent-core/benchmark/ben
37
28
  import { benchmarkTreeIdentity } from "../packages/piagent-core/benchmark/benchmark-tree-identity.js";
38
29
  import { completedBenchmarkRecord, expectedBenchmarkRecord } from "../packages/piagent-core/benchmark/benchmark-record-validation.js";
39
30
  import { pairedOutcomeFloorStop } from "../packages/piagent-core/benchmark/benchmark-stop-policy.js";
31
+ import {
32
+ approveProductionStageControl,
33
+ buildBenchmarkStageDiagnostic,
34
+ createProductionStageControl,
35
+ durablePairedOutcomeFloorStop,
36
+ productionGuardBindingMatches, productionSpendControlValidationErrors,
37
+ productionStageResumeDisposition
38
+ } from "../packages/piagent-core/benchmark/benchmark-stage-diagnostic.js";
40
39
  import { loadBenchmarkAssuranceEvidence, loadBenchmarkSuite, resolveBenchmarkSuiteEntry, validateBenchmarkSuiteFiles } from "../packages/piagent-core/benchmark/benchmark-suite-runtime.js";
41
- import { appendBenchmarkLedger, assertBenchmarkLedgerBinding, benchmarkLedgerCheckpoint, emptyBenchmarkLedgerBinding, inspectBenchmarkLedger, validateBenchmarkLedgerPrefix } from "../packages/piagent-core/benchmark/benchmark-ledger.js";
40
+ import { appendBenchmarkLedger, assertBenchmarkLedgerBinding, emptyBenchmarkLedgerBinding, inspectBenchmarkLedger, validateBenchmarkLedgerPrefix } from "../packages/piagent-core/benchmark/benchmark-ledger.js";
42
41
  import { acquireBenchmarkRunLock } from "../packages/piagent-core/benchmark/benchmark-run-lock.js";
43
42
  import { createBenchmarkProcessController } from "../packages/piagent-core/benchmark/benchmark-process.js";
44
43
  import {
@@ -49,7 +48,33 @@ import {
49
48
  recoverPendingBenchmarkRecord,
50
49
  stageMeasuredBenchmarkRecord
51
50
  } from "../packages/piagent-core/benchmark/benchmark-resume-recovery.js";
51
+ import { finalizeBenchmarkRun } from "./benchmark-runner-finalization.mjs";
52
+ import {
53
+ benchmarkRunKey,
54
+ confirmPlan,
55
+ createRunId,
56
+ defaultOutputRoot,
57
+ ensureEmptyOutput,
58
+ executionOrder,
59
+ fail,
60
+ formatDuration,
61
+ frozenRuntimeCommandsForFinalization,
62
+ invokedAsEntrypoint,
63
+ isLegacyInvocation,
64
+ loadResumeState,
65
+ pairedChunk,
66
+ privateDirectory,
67
+ readJsonFile,
68
+ samePairedBlock,
69
+ sameStringList
70
+ } from "./benchmark-runner-support.mjs";
52
71
  import { runBenchmarkSession } from "./benchmark-session.mjs";
72
+ import {
73
+ assertBenchmarkHostReadinessStartReady,
74
+ assertExistingBenchmarkHostReadiness,
75
+ benchmarkHostReadinessPolicyDigest,
76
+ collectReadyBenchmarkHostReadiness
77
+ } from "./benchmark-runner-host-readiness.mjs";
53
78
  export { parseBenchmarkArgs };
54
79
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
55
80
  const packageManifest = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
@@ -58,11 +83,6 @@ const bootstrapCandidateIndex = bootstrapMetadata ? benchmarkBootstrapCandidateI
58
83
  let interruptedSignal;
59
84
  const processController = createBenchmarkProcessController(() => Boolean(interruptedSignal));
60
85
  const runCommand = processController.run;
61
- function fail(message, code = 2) {
62
- const error = new Error(message);
63
- error.exitCode = code;
64
- throw error;
65
- }
66
86
 
67
87
  function installSignalForwarding() {
68
88
  const handlers = new Map();
@@ -80,159 +100,10 @@ function installSignalForwarding() {
80
100
  };
81
101
  }
82
102
 
83
- function isLegacyInvocation(argv) {
84
- return argv.includes("--record") || argv.includes("--init");
85
- }
86
-
87
- function privateDirectory(target) {
88
- fs.mkdirSync(target, { recursive: true, mode: 0o700 });
89
- try { fs.chmodSync(target, 0o700); } catch { /* Non-POSIX filesystem. */ }
90
- return target;
91
- }
92
-
93
- function defaultOutputRoot() {
94
- if (bootstrapMetadata?.defaultOutputRoot) return bootstrapMetadata.defaultOutputRoot;
95
- const agentRoot = process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
96
- return path.join(agentRoot, "benchmarks", "piagent");
97
- }
98
-
99
- function createRunId(suiteId) {
100
- const timestamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
101
- return `${suiteId}-${timestamp}-${crypto.randomBytes(3).toString("hex")}`;
102
- }
103
-
104
- function ensureEmptyOutput(target) {
105
- if (fs.existsSync(target) && fs.readdirSync(target).length > 0) fail(`Output directory is not empty: ${target}`, 1);
106
- return privateDirectory(target);
107
- }
108
-
109
- function benchmarkRunKey(value) {
110
- return `${value.scenarioId ?? value.scenario?.id}\0${value.surface}\0${value.repeat}`;
111
- }
112
-
113
- function samePairedBlock(left, right) {
114
- return left?.scenario?.id === right?.scenario?.id && left?.repeat === right?.repeat;
115
- }
116
-
117
- function pairedChunk(order, maximum) {
118
- if (maximum === undefined || order.length <= maximum) return order;
119
- let length = maximum;
120
- while (length > 0 && samePairedBlock(order[length - 1], order[length])) length -= 1;
121
- if (length === 0) fail(`--max-sessions ${maximum} would split the first paired benchmark block; increase the chunk size`, 1);
122
- return order.slice(0, length);
123
- }
124
-
125
- function readJsonFile(file, label) {
126
- try {
127
- return JSON.parse(fs.readFileSync(file, "utf8"));
128
- } catch (error) {
129
- fail(`Cannot read ${label} ${file}: ${error.message}`, 1);
130
- }
131
- }
132
-
133
- function resolveResumeRunRoot(input) {
134
- const target = path.resolve(input);
135
- try {
136
- const stat = fs.statSync(target);
137
- if (stat.isDirectory()) return target;
138
- if (stat.isFile()) return path.dirname(target);
139
- } catch (error) {
140
- fail(`Cannot resume benchmark; path does not exist: ${target}`, 1);
141
- }
142
- fail(`Cannot resume benchmark; path is not a file or directory: ${target}`, 1);
143
- }
144
-
145
- function loadResumeState(input) {
146
- const runRoot = resolveResumeRunRoot(input);
147
- const releaseRunLock = acquireBenchmarkRunLock(runRoot, "resume-pending");
148
- try {
149
- const manifestPath = path.join(runRoot, "run-manifest.json");
150
- if (!fs.existsSync(manifestPath)) {
151
- fail(`Cannot resume ${runRoot}: missing run-manifest.json. This run was created before resume metadata was written, so its root seed cannot be recovered safely. Start a new run with --max-sessions or --max-runtime-minutes to make it resumable.`, 1);
152
- }
153
- const manifest = readJsonFile(manifestPath, "benchmark resume manifest");
154
- if (fs.existsSync(path.join(runRoot, "stopped.json"))) fail(`Cannot resume ${runRoot}: the paired release stop is terminal`, 1);
155
- if (manifest?.schemaVersion !== 1 || typeof manifest.runId !== "string") {
156
- fail(`Cannot resume ${runRoot}: run-manifest.json has an unsupported shape`, 1);
157
- }
158
- const ledger = benchmarkLedgerCheckpoint(
159
- manifest.ledger,
160
- inspectBenchmarkLedger(path.join(runRoot, "runs.jsonl")),
161
- "benchmark resume ledger"
162
- );
163
- const pendingPath = path.join(runRoot, "pending-record.json");
164
- const pendingRecord = fs.existsSync(pendingPath) ? readJsonFile(pendingPath, "benchmark pending record") : null;
165
- const measuredPath = path.join(runRoot, "measured-record-ready.json");
166
- const measuredReady = fs.existsSync(measuredPath) ? readJsonFile(measuredPath, "measured benchmark record") : null;
167
- return {
168
- runRoot,
169
- manifest,
170
- completedRuns: ledger.records,
171
- ledgerBinding: ledger.binding,
172
- recoveredLedgerSuffix: ledger.recovered,
173
- pendingRecord,
174
- measuredReady,
175
- releaseRunLock
176
- };
177
- } catch (error) {
178
- releaseRunLock();
179
- throw error;
180
- }
181
- }
182
-
183
- function sameStringList(left, right) {
184
- return Array.isArray(left)
185
- && Array.isArray(right)
186
- && left.length === right.length
187
- && left.every((value, index) => value === right[index]);
188
- }
189
-
190
- function formatDuration(ms) {
191
- const totalSeconds = Math.max(0, Math.round(ms / 1_000));
192
- const hours = Math.floor(totalSeconds / 3600);
193
- const minutes = Math.floor((totalSeconds % 3600) / 60);
194
- const seconds = totalSeconds % 60;
195
- if (hours > 0) return `${hours}h${String(minutes).padStart(2, "0")}m`;
196
- if (minutes > 0) return `${minutes}m${String(seconds).padStart(2, "0")}s`;
197
- return `${seconds}s`;
198
- }
199
-
200
- function executionOrder(suite, repeats, surfaces, rootSeed) {
201
- const order = [];
202
- for (let repeat = 1; repeat <= repeats; repeat += 1) {
203
- const scenarios = suite.schemaVersion === 2
204
- ? [...suite.scenarios].sort((left, right) => {
205
- const rank = (scenario) => crypto.createHmac("sha256", rootSeed).update(`order\0${repeat}\0${scenario.id}`).digest("hex");
206
- return rank(left).localeCompare(rank(right));
207
- })
208
- : suite.scenarios;
209
- for (const [index, scenario] of scenarios.entries()) {
210
- const reverse = suite.schemaVersion === 2
211
- ? (crypto.createHmac("sha256", rootSeed).update(`surface\0${repeat}\0${scenario.id}`).digest()[0] & 1) === 1
212
- : (repeat + index) % 2 !== 0;
213
- const ordered = reverse ? [...surfaces].reverse() : surfaces;
214
- for (const surface of ordered) order.push({ scenario, surface, repeat });
215
- }
216
- }
217
- return order;
218
- }
219
-
220
- async function confirmPlan(message) {
221
- if (!process.stdin.isTTY || !process.stdout.isTTY) fail("Refusing to start billed model runs without --yes in a non-interactive terminal", 1);
222
- const terminal = readline.createInterface({ input: process.stdin, output: process.stdout });
223
- try {
224
- const answer = await terminal.question(`${message}\nContinue? [y/N] `);
225
- return /^(?:y|yes)$/i.test(answer.trim());
226
- } finally {
227
- terminal.close();
228
- }
229
- }
230
-
231
103
  async function runLegacy(argv) {
232
104
  const result = await runCommand("bash", [path.join(packageRoot, "scripts", "quality-benchmark.sh"), ...argv], { cwd: process.cwd(), inherit: true });
233
105
  process.exitCode = result.code;
234
106
  }
235
-
236
107
  async function main() {
237
108
  const argv = process.argv.slice(2);
238
109
  if (isLegacyInvocation(argv)) return runLegacy(argv);
@@ -264,6 +135,7 @@ async function main() {
264
135
  options.timeoutSeconds = manifest.timeoutSeconds;
265
136
  options.infrastructureRetries = manifest.infrastructureRetries;
266
137
  options.retryDelaySeconds = manifest.retryDelaySeconds;
138
+ options.stopAfterFailedPair = manifest.stopAfterFailedPair === true;
267
139
  options.output = resumeState.runRoot;
268
140
  }
269
141
  if (options.replayFailures) {
@@ -283,14 +155,43 @@ async function main() {
283
155
  evidenceComplete: bootstrapMetadata?.replay?.evidenceComplete ?? replay.source.evidenceComplete
284
156
  };
285
157
  }
286
- if (bootstrapMetadata?.suite?.snapshot) options.suite = bootstrapMetadata.suite.snapshot;
158
+ if (bootstrapMetadata?.suite?.builtInId) options.suite = bootstrapMetadata.suite.builtInId;
159
+ else if (bootstrapMetadata?.suite?.snapshot) options.suite = bootstrapMetadata.suite.snapshot;
287
160
  if (options.help) {
288
161
  process.stdout.write(benchmarkUsage);
289
162
  return;
290
163
  }
291
164
  piagentTreatment(options.piagentTreatment);
292
- const { suite, manifestPath, suiteRoot } = loadBenchmarkSuite(options.suite, packageRoot);
165
+ const { suite, manifestPath, suiteRoot, builtInId } = loadBenchmarkSuite(options.suite, packageRoot);
293
166
  validateBenchmarkSuiteFiles(suite, suiteRoot);
167
+ if (bootstrapMetadata && bootstrapMetadata.suite.builtInId !== builtInId) {
168
+ fail("Frozen benchmark suite origin no longer matches its canonical built-in identity", 1);
169
+ }
170
+ const canonicalProductionSuite = builtInId === "production-v1";
171
+ const productionSpendControlPath = path.join(suiteRoot, "spend-control.v1.json");
172
+ const productionSpendControl = canonicalProductionSuite || fs.existsSync(productionSpendControlPath)
173
+ ? readJsonFile(productionSpendControlPath, "production spend-control contract")
174
+ : null;
175
+ const productionExpectedSessions = productionSpendControl
176
+ && Number.isSafeInteger(productionSpendControl.execution?.repeats)
177
+ && Array.isArray(productionSpendControl.execution?.surfaces)
178
+ ? suite.scenarios.length * productionSpendControl.execution.repeats * productionSpendControl.execution.surfaces.length
179
+ : undefined;
180
+ const productionSpendControlErrors = productionSpendControl
181
+ ? productionSpendControlValidationErrors(productionSpendControl, {
182
+ suiteId: suite.id,
183
+ expectedSessions: productionExpectedSessions,
184
+ requireHostReadiness: canonicalProductionSuite
185
+ && suite.releaseGate?.requireHostReadinessForClaim === true
186
+ })
187
+ : [];
188
+ if (productionSpendControlErrors.length > 0) {
189
+ fail(`Production spend-control contract is invalid (${productionSpendControlErrors.join(", ")})`, 1);
190
+ }
191
+ const productionAllStageBoundaries = productionSpendControl
192
+ ? productionSpendControl.stages.map((stage) => stage.cumulativeSessions)
193
+ : [];
194
+ const productionStageBoundaries = productionAllStageBoundaries.slice(1);
294
195
  const assuranceEvidence = loadBenchmarkAssuranceEvidence(suite, suiteRoot);
295
196
  const declaredScenarioCount = suite.scenarios.length;
296
197
  if (options.scenarioIds) {
@@ -305,12 +206,24 @@ async function main() {
305
206
  if (missing.length) fail(`Replay report references unknown scenario: ${missing.join(", ")}`, 1);
306
207
  suite.scenarios = [...new Set(options.replayRuns.map((run) => run.scenarioId))].map((id) => byId.get(id));
307
208
  }
308
- options.repeats = options.repeats ?? suite.defaultRepeats;
309
- options.infrastructureRetries = options.infrastructureRetries ?? (suite.schemaVersion === 2 ? 2 : 0);
310
- options.retryDelaySeconds = options.retryDelaySeconds ?? (suite.schemaVersion === 2 ? 60 : 0);
311
- options.timeoutSeconds = options.timeoutSeconds ?? suite.timeoutSeconds;
209
+ applyBenchmarkExecutionDefaults(options, suite);
210
+ const productionFullMatrixRequested = Boolean(productionSpendControl)
211
+ && !options.replayRuns
212
+ && suite.scenarios.length === declaredScenarioCount;
213
+ if (productionFullMatrixRequested) {
214
+ const spendExecution = productionSpendControl.execution;
215
+ if (options.seed === undefined) options.seed = productionSpendControl.rootSeed;
216
+ if (options.seed !== productionSpendControl.rootSeed) fail("Production spend control requires its frozen root seed", 1);
217
+ if (!sameStringList(options.surfaces, spendExecution.surfaces)) fail("Production spend control requires its frozen surface order", 1);
218
+ if ((options.model ?? null) !== spendExecution.model) fail("Production spend control requires its frozen model", 1);
219
+ if ((options.thinking ?? null) !== spendExecution.thinking) fail("Production spend control requires its frozen thinking level", 1);
220
+ if (options.repeats !== spendExecution.repeats) fail("Production spend control requires its frozen repeat count", 1);
221
+ if (options.infrastructureRetries !== spendExecution.infrastructureRetries) fail("Production spend control requires zero infrastructure retries", 1);
222
+ }
312
223
  if (options.stopAfterFailedPair && !Number.isFinite(suite.releaseGate?.minimumOutcomeScoreExclusive)) fail("--stop-after-failed-pair requires a suite outcome floor", 1);
313
224
  if (options.surfaces.includes("codex-cli")) {
225
+ options.model = options.model ?? "openai-codex/gpt-5.6-luna";
226
+ options.thinking = options.thinking ?? "medium";
314
227
  codexModelName(options.model);
315
228
  codexThinkingEffort(options.thinking);
316
229
  }
@@ -334,9 +247,28 @@ async function main() {
334
247
  repeat: run.repeat
335
248
  }))
336
249
  : executionOrder(suite, options.repeats, options.surfaces, rootSeed);
250
+ const productionSpendControlled = productionFullMatrixRequested;
251
+ const productionHostReadinessRequired = productionSpendControlled
252
+ && canonicalProductionSuite
253
+ && suite.releaseGate?.requireHostReadinessForClaim === true;
254
+ const productionHostReadinessPolicy = productionHostReadinessRequired
255
+ ? productionSpendControl.hostReadiness
256
+ : null;
257
+ const productionHostReadinessPolicyDigest = productionHostReadinessRequired
258
+ ? benchmarkHostReadinessPolicyDigest(productionHostReadinessPolicy)
259
+ : null;
260
+ const productionReleaseClaimRun = productionSpendControlled
261
+ && suite.schemaVersion === 2
262
+ && suite.releaseGate?.requireEfficiencyClaim === true
263
+ && suite.releaseGate?.requireFullSuiteForClaim === true;
264
+ if (productionSpendControlled && productionStageBoundaries.at(-1) !== fullOrder.length) {
265
+ fail("Production spend control final stage does not match the frozen full execution order", 1);
266
+ }
267
+ let deferredProductionStageApproval;
337
268
  if (resumeState) {
338
269
  const manifest = resumeState.manifest;
339
270
  if (manifest.suiteDigest !== suiteDigest) fail("Cannot resume benchmark: suite files changed since the original run", 1);
271
+ if (productionSpendControlled && !productionGuardBindingMatches(manifest, productionSpendControl)) fail("Cannot resume production benchmark: frozen production guard binding is missing or changed", 1);
340
272
  if (manifest.rootSeed !== rootSeed) fail("Cannot resume benchmark: root seed mismatch", 1);
341
273
  if (manifest.repeats !== options.repeats) fail("Cannot resume benchmark: repeat count mismatch", 1);
342
274
  if (!sameStringList(manifest.surfaces, options.surfaces)) fail("Cannot resume benchmark: surface list mismatch", 1);
@@ -381,7 +313,104 @@ async function main() {
381
313
  });
382
314
  throw provenanceError;
383
315
  }
316
+ const recoveredTerminalStop = durablePairedOutcomeFloorStop({
317
+ enabled: options.stopAfterFailedPair,
318
+ suite,
319
+ runs: resumeState.completedRuns,
320
+ fullOrder
321
+ });
322
+ if (recoveredTerminalStop) {
323
+ for (const marker of ["paused.json", "stage-diagnostic.json", "interrupted.json", "aborted.json"]) {
324
+ fs.rmSync(path.join(resumeState.runRoot, marker), { force: true });
325
+ }
326
+ writePrivateAtomic(path.join(resumeState.runRoot, "stopped.json"), `${JSON.stringify({
327
+ ...recoveredTerminalStop,
328
+ runId: manifest.runId,
329
+ completedRuns: resumeState.completedRuns.length,
330
+ expectedRuns: fullOrder.length,
331
+ stoppedAt: new Date().toISOString(),
332
+ resumeAllowed: false,
333
+ recoveredFromAcceptedLedger: true,
334
+ ledger: resumeState.ledgerBinding,
335
+ provenanceStamp: candidateGuard.stamp("resume-terminal-floor")
336
+ }, null, 2)}\n`);
337
+ fail("Cannot resume benchmark: the accepted ledger already contains a terminal paired outcome-floor failure", 1);
338
+ }
339
+ if (productionSpendControlled) {
340
+ if (productionHostReadinessRequired) {
341
+ if (manifest.hostReadinessPolicyDigest !== productionHostReadinessPolicyDigest
342
+ && resumeState.completedRuns.length < fullOrder.length) {
343
+ fail("Cannot resume production benchmark: host-readiness policy binding is missing or changed. Start a new staged run.", 1);
344
+ }
345
+ if (resumeState.completedRuns.length < fullOrder.length) {
346
+ assertExistingBenchmarkHostReadiness({
347
+ policy: productionHostReadinessPolicy,
348
+ receipts: manifest.hostReadinessReceipts,
349
+ completedRuns: resumeState.completedRuns.length,
350
+ authorizedThroughRuns: manifest.stageControl?.authorizedThroughRuns,
351
+ runId: manifest.runId,
352
+ configurationDigest: manifest.configurationDigest,
353
+ stageBoundaries: productionAllStageBoundaries
354
+ });
355
+ }
356
+ }
357
+ const stageDisposition = productionStageResumeDisposition(manifest.stageControl, {
358
+ completedRuns: resumeState.completedRuns.length,
359
+ ledger: resumeState.ledgerBinding,
360
+ stageBoundaries: productionAllStageBoundaries
361
+ });
362
+ if (!stageDisposition.passed) {
363
+ fail(`Cannot resume production benchmark: invalid durable stage-control state (${stageDisposition.errors.join(", ")}). Start a new staged run.`, 1);
364
+ }
365
+ if (stageDisposition.requiresStageGate && resumeState.completedRuns.length < fullOrder.length) {
366
+ const recoveredBoundaryReason = `max-sessions:recovered:${resumeState.completedRuns.length}`;
367
+ const resumeStageDiagnostic = buildBenchmarkStageDiagnostic({
368
+ runId: manifest.runId,
369
+ reason: manifest.stageControl.pendingBoundary?.reason ?? recoveredBoundaryReason,
370
+ runs: resumeState.completedRuns,
371
+ fullOrder,
372
+ candidateSurface: comparison.candidateSurface,
373
+ baselineSurface: comparison.baselineSurface,
374
+ requestedModel: options.model,
375
+ requestedThinking: options.thinking,
376
+ suite,
377
+ manifest,
378
+ hostReadinessPolicy: productionHostReadinessPolicy,
379
+ hostReadinessStageBoundaries: productionAllStageBoundaries
380
+ });
381
+ writePrivateAtomic(path.join(resumeState.runRoot, "stage-diagnostic.json"), `${JSON.stringify(resumeStageDiagnostic, null, 2)}\n`);
382
+ if (!resumeStageDiagnostic.stageAdvanceAllowed) {
383
+ fail(`Cannot resume production benchmark: the provider-free stage gate blocked further paid sessions (${resumeStageDiagnostic.blockingReasons.join(", ")}). Fix the candidate and start a new staged run.`, 1);
384
+ }
385
+ const currentWindow = manifest.stageControl.authorizedThroughRuns;
386
+ const nextAuthorizedThroughRuns = resumeState.completedRuns.length < currentWindow
387
+ ? currentWindow
388
+ : productionStageBoundaries.find((boundary) => boundary > resumeState.completedRuns.length);
389
+ if (!Number.isSafeInteger(nextAuthorizedThroughRuns)) {
390
+ fail("Cannot resume production benchmark: no bounded spend-control window remains", 1);
391
+ }
392
+ const requiredResumeSessions = nextAuthorizedThroughRuns - resumeState.completedRuns.length;
393
+ if (!options.dryRun && options.maxSessions !== requiredResumeSessions) {
394
+ fail(`Production spend control requires --max-sessions ${requiredResumeSessions} for the next authorized window; durable stage state was not changed.`, 1);
395
+ }
396
+ if (!options.dryRun) {
397
+ deferredProductionStageApproval = {
398
+ completedRuns: resumeState.completedRuns.length,
399
+ authorizedThroughRuns: nextAuthorizedThroughRuns
400
+ };
401
+ }
402
+ } else if (stageDisposition.requiresStageGate) {
403
+ // A crash after the final accepted record must not change the verdict by
404
+ // applying an early-stage heuristic. The complete report gates the same
405
+ // frozen ledger without starting another provider session.
406
+ manifest.ledger = resumeState.ledgerBinding;
407
+ writeBenchmarkRunManifest(resumeState.runRoot, manifest);
408
+ }
409
+ }
384
410
  }
411
+ const productionFinalizationOnly = productionSpendControlled
412
+ && Boolean(resumeState)
413
+ && resumeState.completedRuns.length === fullOrder.length;
385
414
  const pendingOrder = resumeState
386
415
  ? fullOrder.filter((item) => !resumeState.completedKeys.has(benchmarkRunKey(item)))
387
416
  : fullOrder;
@@ -416,24 +445,64 @@ async function main() {
416
445
  const codexPlan = options.surfaces.includes("codex-cli")
417
446
  ? `\n codex: ${options.codexMode} mode · model ${codexModelName(options.model)} · effort ${codexThinkingEffort(options.thinking)}${options.codexMode === "controlled" ? " · isolated home" : ""}`
418
447
  : "";
448
+ const nativeWarning = options.surfaces.includes("codex-cli") && options.codexMode === "native"
449
+ ? "\nNative Codex mode loads the operator's global AGENTS.md, configuration, rules, hooks, MCP servers, and plugins."
450
+ : "";
419
451
  if (options.dryRun) {
420
452
  process.stdout.write(`${plan}${codexPlan}\n manifest: ${manifestPath}\nDRY RUN: no model session started.\n`);
421
453
  return;
422
454
  }
455
+ if (productionSpendControlled) {
456
+ if (options.stopAfterFailedPair !== productionSpendControl.execution.stopAfterFailedPair) {
457
+ fail("Production spend control requires --stop-after-failed-pair before any provider session", 1);
458
+ }
459
+ const completedRuns = resumeState?.completedRuns.length ?? 0;
460
+ const authorizedThroughRuns = deferredProductionStageApproval?.authorizedThroughRuns
461
+ ?? resumeState?.manifest.stageControl?.authorizedThroughRuns
462
+ ?? productionStageBoundaries[0];
463
+ const requiredChunkSessions = authorizedThroughRuns - completedRuns;
464
+ if (!options.preflightOnly && !productionFinalizationOnly
465
+ && (!Number.isSafeInteger(requiredChunkSessions) || requiredChunkSessions <= 0
466
+ || options.maxSessions !== requiredChunkSessions)) {
467
+ fail(`Production spend control requires --max-sessions ${requiredChunkSessions} for the current authorized window; refusing an unbounded or oversized paid chunk.`, 1);
468
+ }
469
+ }
423
470
  if (!bootstrapMetadata) fail("Modern billed benchmarks must start through scripts/benchmark-runner.mjs so execution assets are frozen", 1);
471
+ if (productionReleaseClaimRun && bootstrapMetadata.sourceIdentity.dirty !== false) {
472
+ fail("Production benchmark requires a clean release source before auth, tool preflight, or any provider session", 1);
473
+ }
424
474
  if (options.replayFailures && bootstrapMetadata.replay?.evidenceComplete !== true) {
425
475
  fail("Billed replay requires the original run-manifest.json and runs.jsonl beside the source report", 1);
426
476
  }
427
- const runtimeCommands = {
428
- pi: benchmarkCommandIdentity(piCommand, { cwd: bootstrapMetadata?.originalCwd ?? process.cwd() }),
429
- codex: options.surfaces.includes("codex-cli")
430
- ? benchmarkCommandIdentity(codexCommand, { cwd: bootstrapMetadata?.originalCwd ?? process.cwd() })
431
- : null,
432
- node: benchmarkCommandIdentity(process.execPath),
433
- git: benchmarkCommandIdentity("git"),
434
- bash: benchmarkCommandIdentity("bash")
435
- };
477
+ if (!options.preflightOnly) {
478
+ if (productionFinalizationOnly) {
479
+ process.stdout.write(`${plan}${codexPlan}\n mode: provider-free finalization of the complete frozen ledger\n`);
480
+ } else if (options.yes) {
481
+ process.stdout.write(`${plan}${codexPlan}${nativeWarning}\n`);
482
+ } else if (!(await confirmPlan(`${plan}${codexPlan}${nativeWarning}\nThis may use paid model quota.`))) {
483
+ process.stdout.write("Benchmark cancelled; no model session started.\n");
484
+ return;
485
+ }
486
+ }
487
+ const runId = resumeState?.manifest.runId ?? createRunId(suite.id);
488
+ const runtimeCommands = productionFinalizationOnly
489
+ ? frozenRuntimeCommandsForFinalization(resumeState.manifest, options.surfaces)
490
+ : {
491
+ pi: benchmarkCommandIdentity(piCommand, { cwd: bootstrapMetadata?.originalCwd ?? process.cwd() }),
492
+ codex: options.surfaces.includes("codex-cli")
493
+ ? benchmarkCommandIdentity(codexCommand, { cwd: bootstrapMetadata?.originalCwd ?? process.cwd() })
494
+ : null,
495
+ node: benchmarkCommandIdentity(process.execPath),
496
+ git: benchmarkCommandIdentity("git"),
497
+ bash: benchmarkCommandIdentity("bash")
498
+ };
436
499
  const environmentPolicy = benchmarkEnvironmentPolicy();
500
+ const configurationPiAgentHome = productionFinalizationOnly
501
+ ? resumeState.manifest.piAgentHome?.identity
502
+ : bootstrapMetadata.piAgentHome.identity;
503
+ const configurationCodexCredential = productionFinalizationOnly
504
+ ? resumeState.manifest.codexCredentialIdentity ?? null
505
+ : bootstrapMetadata.codexCredential?.identity ?? null;
437
506
  const configuration = {
438
507
  schemaVersion: 1,
439
508
  source: bootstrapMetadata.sourceIdentity,
@@ -442,8 +511,9 @@ async function main() {
442
511
  runtimeDependencyDigest: bootstrapMetadata.runtimeDependencies?.digest ?? null,
443
512
  runtimeCommands,
444
513
  environmentPolicy,
445
- piAgentHome: bootstrapMetadata.piAgentHome.identity,
446
- codexCredential: bootstrapMetadata.codexCredential?.identity ?? null,
514
+ ...(productionHostReadinessRequired ? { hostReadinessPolicyDigest: productionHostReadinessPolicyDigest } : {}),
515
+ piAgentHome: configurationPiAgentHome,
516
+ codexCredential: configurationCodexCredential,
447
517
  rootSeedDigest,
448
518
  surfaces: options.surfaces,
449
519
  model: options.model ?? null,
@@ -459,6 +529,10 @@ async function main() {
459
529
  order: fullOrder.map((item) => ({ scenarioId: item.scenario.id, surface: item.surface, repeat: item.repeat }))
460
530
  };
461
531
  const configurationDigest = crypto.createHash("sha256").update(JSON.stringify(configuration)).digest("hex");
532
+ const source = bootstrapMetadata?.sourceIdentity;
533
+ if (!source) fail("Modern benchmark is missing its frozen Git source identity", 1);
534
+ const { receipt: providerFreeEvidence, binding: providerFreeEvidenceBinding } = await prepareProductionProviderFreeEvidence({
535
+ required: productionSpendControl?.productionGuards?.providerFreeEvidence?.requiredBeforeFirstPaidSession === true, packageRoot, bootstrapMetadata, candidateProvenance: candidateGuard.provenance, configurationDigest, runCommand, resumedReceipt: resumeState ? resumeState.manifest.providerFreeEvidence ?? null : undefined });
462
536
  piCommand = runtimeCommands.pi.resolvedPath;
463
537
  if (runtimeCommands.codex) codexCommand = runtimeCommands.codex.resolvedPath;
464
538
  if (resumeState && JSON.stringify(resumeState.manifest.runtimeCommands ?? null) !== JSON.stringify(runtimeCommands)) {
@@ -475,48 +549,79 @@ async function main() {
475
549
  completedKeys: resumeState.completedKeys
476
550
  });
477
551
  }
552
+ let hostReadinessReceipt = null;
553
+ const currentAuthorizedThroughRuns = resumeState?.manifest.stageControl?.authorizedThroughRuns
554
+ ?? productionStageBoundaries[0];
555
+ const nextAuthorizedThroughRuns = deferredProductionStageApproval?.authorizedThroughRuns
556
+ ?? currentAuthorizedThroughRuns;
557
+ if (productionHostReadinessRequired && !productionFinalizationOnly) {
558
+ hostReadinessReceipt = await collectReadyBenchmarkHostReadiness({
559
+ policy: productionHostReadinessPolicy,
560
+ completedRuns: resumeState?.completedRuns.length ?? 0,
561
+ authorizedThroughRuns: nextAuthorizedThroughRuns,
562
+ runId,
563
+ configurationDigest,
564
+ stageBoundaries: productionAllStageBoundaries,
565
+ existingReceipts: resumeState?.manifest.hostReadinessReceipts ?? []
566
+ });
567
+ }
568
+ const assertHostReadinessStartReady = () => assertBenchmarkHostReadinessStartReady(hostReadinessReceipt, {
569
+ policy: productionHostReadinessPolicy,
570
+ runId,
571
+ configurationDigest
572
+ });
478
573
  const executionGuard = createBenchmarkExecutionGuard({
479
574
  candidateGuard,
480
575
  suiteRoot,
481
576
  suiteIdentity: bootstrapMetadata?.suite?.identity ?? suiteIdentity,
482
- piAgentHome: bootstrapMetadata?.piAgentHome,
483
- codexCredential: bootstrapMetadata?.codexCredential,
577
+ piAgentHome: productionFinalizationOnly ? null : bootstrapMetadata?.piAgentHome,
578
+ codexCredential: productionFinalizationOnly ? null : bootstrapMetadata?.codexCredential,
484
579
  runtimeDependencies: bootstrapMetadata?.runtimeDependencies,
485
- commands: runtimeCommands
580
+ commands: runtimeCommands,
581
+ verifyCommandAssets: !productionFinalizationOnly
486
582
  });
487
- assertBenchmarkPiCredentialReady(bootstrapMetadata.piAgentHome.credentialReadiness, options.model);
488
- assertBenchmarkPiCredentialWritebackPolicy(bootstrapMetadata.piAgentHome);
489
- piRuntimeHome = createBenchmarkPiRuntimeHome(bootstrapMetadata.piAgentHome);
490
- const preflightAssetError = executionGuard.check("before-preflight", [piRuntimeHome]);
491
- if (preflightAssetError) throw preflightAssetError;
492
- codexRuntime = createCodexRuntime(options);
493
583
  let runtime;
494
- try { runtime = await withBenchmarkPiCredentialWriteback(bootstrapMetadata.piAgentHome, piRuntimeHome, () => benchmarkPreflight({ runCommand, packageRoot, piCommand, piEnvironment: benchmarkEnvironment({ PI_CODING_AGENT_DIR: piRuntimeHome.path }), codexCommand, gitCommand: runtimeCommands.git.resolvedPath, surfaces: options.surfaces, codexMode: options.codexMode, codexRuntime })); }
495
- catch (error) { preservePiRuntime ||= error.code === "BENCHMARK_PI_CREDENTIAL_RECONCILIATION_FAILED"; throw error; }
496
- const postPreflightAssetError = executionGuard.check("after-preflight", [piRuntimeHome]);
497
- if (postPreflightAssetError) throw postPreflightAssetError;
498
- resetBenchmarkPiRuntimeEphemeralState(piRuntimeHome);
499
- const source = bootstrapMetadata?.sourceIdentity;
500
- if (!source) fail("Modern benchmark is missing its frozen Git source identity", 1);
584
+ let codexCredentialBridge;
585
+ if (productionFinalizationOnly) {
586
+ runtime = resumeState.manifest.preflightRuntime;
587
+ if (!runtime || typeof runtime.gitVersion !== "string" || typeof runtime.piVersion !== "string"
588
+ || !Array.isArray(runtime.codexDisabledFeatures)
589
+ || (options.surfaces.includes("codex-cli") && typeof runtime.codexVersion !== "string")) {
590
+ fail("Cannot finalize production benchmark provider-free: frozen preflight runtime evidence is missing or invalid", 1);
591
+ }
592
+ codexCredentialBridge = resumeState.manifest.codexCredentialBridge ?? null;
593
+ const finalizationAssetError = executionGuard.check("provider-free-finalization");
594
+ if (finalizationAssetError) throw finalizationAssetError;
595
+ } else {
596
+ assertBenchmarkPiCredentialReady(bootstrapMetadata.piAgentHome.credentialReadiness, options.model);
597
+ assertBenchmarkPiCredentialWritebackPolicy(bootstrapMetadata.piAgentHome);
598
+ piRuntimeHome = createBenchmarkPiRuntimeHome(bootstrapMetadata.piAgentHome);
599
+ const preflightAssetError = executionGuard.check("before-preflight", [piRuntimeHome]);
600
+ if (preflightAssetError) throw preflightAssetError;
601
+ codexRuntime = createCodexRuntime(options);
602
+ try { runtime = await withBenchmarkPiCredentialWriteback(bootstrapMetadata.piAgentHome, piRuntimeHome, () => benchmarkPreflight({ runCommand, packageRoot, piCommand, piEnvironment: benchmarkEnvironment({ PI_CODING_AGENT_DIR: piRuntimeHome.path }), codexCommand, gitCommand: runtimeCommands.git.resolvedPath, surfaces: options.surfaces, codexMode: options.codexMode, codexRuntime })); }
603
+ catch (error) { preservePiRuntime ||= error.code === "BENCHMARK_PI_CREDENTIAL_RECONCILIATION_FAILED"; throw error; }
604
+ const postPreflightAssetError = executionGuard.check("after-preflight", [piRuntimeHome]);
605
+ if (postPreflightAssetError) throw postPreflightAssetError;
606
+ resetBenchmarkPiRuntimeEphemeralState(piRuntimeHome);
607
+ codexCredentialBridge = codexRuntime.credentialBridge;
608
+ if (resumeState && resumeState.manifest.preflightRuntime !== undefined
609
+ && JSON.stringify(resumeState.manifest.preflightRuntime) !== JSON.stringify(runtime)) {
610
+ fail("Cannot resume benchmark: provider-free preflight runtime evidence changed since the original run", 1);
611
+ }
612
+ if (resumeState && resumeState.manifest.codexCredentialBridge !== undefined
613
+ && resumeState.manifest.codexCredentialBridge !== codexCredentialBridge) {
614
+ fail("Cannot resume benchmark: Codex credential bridge changed since the original run", 1);
615
+ }
616
+ }
617
+ assertHostReadinessStartReady();
501
618
  if (options.preflightOnly) {
502
- const receipt = benchmarkPreflightReceipt({ packageVersion: packageManifest.version, source, candidateProvenance: candidateGuard.report(), suite, suiteDigest, runtimeDependencies: bootstrapMetadata.runtimeDependencies, runtimeCommands, environmentPolicy, configurationDigest, rootSeedDigest, options, runtime });
619
+ const receipt = benchmarkPreflightReceipt({ packageVersion: packageManifest.version, source, candidateProvenance: candidateGuard.report(), suite, suiteDigest, runtimeDependencies: bootstrapMetadata.runtimeDependencies, runtimeCommands, environmentPolicy, configurationDigest, rootSeedDigest, options, runtime, hostReadinessPolicyDigest: productionHostReadinessPolicyDigest, hostReadiness: hostReadinessReceipt, providerFreeEvidence });
503
620
  process.stdout.write(options.json ? `${JSON.stringify(receipt, null, 2)}\n` : `${plan}${codexPlan}\nPREFLIGHT READY: no model session started.\n${JSON.stringify(receipt, null, 2)}\n`);
504
621
  return;
505
622
  }
506
- const nativeWarning = options.surfaces.includes("codex-cli") && options.codexMode === "native"
507
- ? "\nNative Codex mode loads the operator's global AGENTS.md, configuration, rules, hooks, MCP servers, and plugins."
508
- : "";
509
- if (options.yes) {
510
- process.stdout.write(`${plan}${codexPlan}${nativeWarning}\n`);
511
- } else {
512
- if (!(await confirmPlan(`${plan}${codexPlan}${nativeWarning}\nThis may use paid model quota.`))) {
513
- process.stdout.write("Benchmark cancelled; no model session started.\n");
514
- return;
515
- }
516
- }
517
623
  candidateGuard.freeze();
518
- const runId = resumeState?.manifest.runId ?? createRunId(suite.id);
519
- const output = options.output ?? path.join(defaultOutputRoot(), runId);
624
+ const output = options.output ?? path.join(defaultOutputRoot(bootstrapMetadata), runId);
520
625
  const runRoot = resumeState ? privateDirectory(output) : ensureEmptyOutput(output);
521
626
  releaseRunLock ??= acquireBenchmarkRunLock(runRoot, runId);
522
627
  privateDirectory(path.join(runRoot, "workspaces"));
@@ -535,13 +640,17 @@ async function main() {
535
640
  suite: {
536
641
  id: suite.id,
537
642
  source: bootstrapMetadata?.suite?.origin ?? manifestPath,
538
- manifestPath: bootstrapMetadata?.suite?.snapshot ? bootstrapMetadata.suite.origin : null
643
+ manifestPath: bootstrapMetadata?.suite?.snapshot ? bootstrapMetadata.suite.origin : null,
644
+ builtInId
539
645
  },
540
646
  suiteDigest,
541
647
  suiteIdentity,
542
648
  candidateProvenance: candidateGuard.provenance,
543
649
  runtimeDependencies: bootstrapMetadata?.runtimeDependencies ?? null,
544
650
  runtimeCommands,
651
+ preflightRuntime: runtime,
652
+ codexCredentialIdentity: bootstrapMetadata.codexCredential?.identity ?? null,
653
+ codexCredentialBridge,
545
654
  configurationDigest,
546
655
  environmentPolicy,
547
656
  piAgentHome: {
@@ -569,13 +678,39 @@ async function main() {
569
678
  retryDelaySeconds: options.retryDelaySeconds,
570
679
  stopAfterFailedPair: options.stopAfterFailedPair,
571
680
  scenarioIds: options.scenarioIds ?? null,
681
+ ...(productionSpendControlled ? {
682
+ productionGuards: productionSpendControl.productionGuards,
683
+ providerFreeEvidence,
684
+ stageControl: createProductionStageControl({
685
+ authorizedThroughRuns: productionStageBoundaries[0],
686
+ generatedAt: startedAt
687
+ }),
688
+ ...(productionHostReadinessRequired ? {
689
+ hostReadinessPolicyDigest: productionHostReadinessPolicyDigest,
690
+ hostReadinessReceipts: []
691
+ } : {})
692
+ } : {}),
572
693
  order: fullOrder.map((item) => ({
573
694
  scenarioId: item.scenario.id,
574
695
  surface: item.surface,
575
696
  repeat: item.repeat
576
697
  }))
577
698
  };
699
+ assertHostReadinessStartReady();
700
+ if (productionSpendControlled && deferredProductionStageApproval) {
701
+ manifest.stageControl = approveProductionStageControl(manifest.stageControl, deferredProductionStageApproval);
702
+ }
703
+ if (productionHostReadinessRequired && hostReadinessReceipt) {
704
+ manifest.hostReadinessPolicyDigest = productionHostReadinessPolicyDigest;
705
+ manifest.hostReadinessReceipts = [...(manifest.hostReadinessReceipts ?? []), hostReadinessReceipt];
706
+ }
578
707
  writeBenchmarkRunManifest(runRoot, manifest);
708
+ if (resumeState) {
709
+ fs.rmSync(path.join(runRoot, "stage-diagnostic.json"), { force: true });
710
+ if (productionSpendControlled && manifest.stageControl?.state === "window-authorized") {
711
+ fs.rmSync(path.join(runRoot, "paused.json"), { force: true });
712
+ }
713
+ }
579
714
  const fullIndexByKey = new Map(fullOrder.map((item, index) => [benchmarkRunKey(item), index + 1]));
580
715
  const wallStartedAt = Date.now();
581
716
  const runtimeDeadline = options.maxRuntimeMinutes === undefined
@@ -782,150 +917,24 @@ async function main() {
782
917
  }
783
918
  let finalizationReceipt;
784
919
  if (!interruptedSignal && !fatalRunError) {
920
+ if (productionSpendControl?.productionGuards?.providerFreeEvidence?.requiredBeforeFirstPaidSession === true) {
921
+ fatalRunError ??= productionProviderFreeEvidenceError(manifest.providerFreeEvidence, providerFreeEvidenceBinding, "final production provider-free evidence");
922
+ }
785
923
  finalizationReceipt = executionGuard.receipt("finalization", [piRuntimeHome]);
786
- fatalRunError = finalizationReceipt.error;
924
+ fatalRunError ??= finalizationReceipt.error;
787
925
  }
788
- if (interruptedSignal) {
789
- const provenanceStamp = executionGuard.stamp("interrupted", [piRuntimeHome]);
790
- piRuntimeHome = undefined;
791
- recoverOrphanedBenchmarkAttempts({ runRoot, manifest, fullOrder, completedKeys: new Set(runs.map(benchmarkRunKey)) });
792
- cleanupUnretainedWorkspaces(runRoot, options.keepWorkspaces);
793
- writePrivateAtomic(path.join(runRoot, "interrupted.json"), `${JSON.stringify({ schemaVersion: 1, runId, signal: interruptedSignal, completedRuns: runs.filter(completedBenchmarkRecord).length, expectedRuns: fullOrder.length, interruptedAt: new Date().toISOString(), resumeCommand: `piagent-benchmark --resume ${runRoot} --yes`, ledger: ledgerBinding, provenanceStamp }, null, 2)}\n`);
794
- process.stderr.write(`Benchmark interrupted by ${interruptedSignal}. Partial ledger: ${ledgerPath}\n`);
795
- process.exitCode = interruptedSignal === "SIGINT" ? 130 : interruptedSignal === "SIGHUP" ? 129 : 143;
796
- return;
797
- }
798
- if (fatalRunError) {
799
- const provenanceStamp = finalizationReceipt?.stamp ?? fatalExecutionReceipt?.stamp ?? executionGuard.stamp("fatal", piRuntimeHome ? [piRuntimeHome] : []);
800
- if (!preservePiRuntime) cleanupBenchmarkPiRuntimeHome(bootstrapMetadata.piAgentHome, piRuntimeHome);
801
- piRuntimeHome = undefined;
802
- recoverOrphanedBenchmarkAttempts({ runRoot, manifest, fullOrder, completedKeys: new Set(runs.map(benchmarkRunKey)) });
803
- cleanupUnretainedWorkspaces(runRoot, options.keepWorkspaces);
804
- const provenanceFailure = writeBenchmarkAbort(runRoot, { runId, completedRuns: runs.filter(completedBenchmarkRecord).length, expectedRuns: fullOrder.length }, fatalRunError, {
805
- ledger: ledgerBinding,
806
- provenanceStamp
807
- });
808
- process.stderr.write(provenanceFailure
809
- ? `Benchmark aborted because candidate provenance changed. Partial ledger: ${ledgerPath}\n`
810
- : `Benchmark aborted after an infrastructure error. Partial ledger: ${ledgerPath}\n`);
811
- process.exitCode = 1;
812
- return;
813
- }
814
- if (terminalStop) {
815
- cleanupBenchmarkPiRuntimeHome(bootstrapMetadata.piAgentHome, piRuntimeHome); piRuntimeHome = undefined;
816
- cleanupUnretainedWorkspaces(runRoot, options.keepWorkspaces);
817
- for (const marker of ["paused.json", "interrupted.json", "aborted.json"]) fs.rmSync(path.join(runRoot, marker), { force: true });
818
- writePrivateAtomic(path.join(runRoot, "stopped.json"), `${JSON.stringify({ ...terminalStop, runId, completedRuns: runs.filter(completedBenchmarkRecord).length, expectedRuns: fullOrder.length, stoppedAt: new Date().toISOString(), resumeAllowed: false, ledger: ledgerBinding, provenanceStamp: finalizationReceipt.stamp }, null, 2)}\n`);
819
- process.stderr.write(`Benchmark terminal-stopped after paired outcome-floor failure. Partial ledger: ${ledgerPath}\n`);
820
- process.exitCode = 1;
821
- return;
822
- }
823
- const completedRuns = runs.filter(completedBenchmarkRecord);
824
- if (completedRuns.length < fullOrder.length) {
825
- piRuntimeHome = undefined;
826
- cleanupUnretainedWorkspaces(runRoot, options.keepWorkspaces);
827
- const paused = {
828
- schemaVersion: 1,
829
- runId,
830
- reason: pauseReason ?? "partial-run",
831
- completedRuns: completedRuns.length,
832
- expectedRuns: fullOrder.length,
833
- remainingRuns: fullOrder.length - completedRuns.length,
834
- pausedAt: new Date().toISOString(),
835
- resumeCommand: `piagent-benchmark --resume ${runRoot} --yes`,
836
- ledger: ledgerBinding,
837
- provenanceStamp: executionGuard.stamp("paused")
838
- };
839
- writePrivateAtomic(path.join(runRoot, "paused.json"), `${JSON.stringify(paused, null, 2)}\n`);
840
- process.stdout.write(`Benchmark paused after ${completedRuns.length}/${fullOrder.length} completed sessions (${paused.reason}).\nResume: ${paused.resumeCommand}\nPartial ledger: ${ledgerPath}\n`);
841
- return;
842
- }
843
- cleanupBenchmarkPiRuntimeHome(bootstrapMetadata.piAgentHome, piRuntimeHome); piRuntimeHome = undefined;
844
- const report = summarizeBenchmark({
845
- suite,
846
- runId,
847
- startedAt,
848
- completedAt: new Date().toISOString(),
849
- repeats: options.repeats,
850
- environment: {
851
- platformVersion: packageManifest.version,
852
- suiteDigest,
853
- variantRootSeed: suite.scenarios.some((scenario) => scenario.variantGenerator) ? rootSeed : null,
854
- variantRootSeedDigest: suite.scenarios.some((scenario) => scenario.variantGenerator) ? rootSeedDigest : null,
855
- executionOrder: suite.schemaVersion === 2 ? "seeded-paired-block-randomized" : "paired-alternating",
856
- replaySource: options.replaySource ?? null,
857
- profile: suite.profile,
858
- requestedModel: options.model ?? null,
859
- requestedThinking: options.thinking ?? null,
860
- piagentTreatment: piagentTreatment(options.piagentTreatment),
861
- treatmentBaseline: lifecycles.length === 1 && lifecycles[0] === "steady-state"
862
- ? options.surfaces.includes("codex-cli")
863
- ? "piagent-initialized-and-onboarded; codex-clean-fixture"
864
- : "initialized-and-onboarded"
865
- : "scenario-defined-mixed-lifecycle",
866
- timeoutSeconds: options.timeoutSeconds,
867
- nodeVersion: process.version,
868
- piVersion: runtime.piVersion,
869
- codexVersion: runtime.codexVersion ?? null,
870
- codexMode: options.surfaces.includes("codex-cli") ? options.codexMode : null,
871
- codexAuth: runtime.codexAuth ?? null,
872
- codexIsolation: options.surfaces.includes("codex-cli")
873
- ? options.codexMode === "controlled" ? "per-session-temporary-home" : "operator-home"
874
- : null,
875
- codexCredentialBridge: options.surfaces.includes("codex-cli") ? codexRuntime.credentialBridge : null,
876
- codexGlobalInstructions: options.surfaces.includes("codex-cli")
877
- ? options.codexMode === "controlled" ? "excluded" : "operator-home"
878
- : null,
879
- piGlobalInstructions: "excluded",
880
- piAgentHome: manifest.piAgentHome,
881
- usageIntegrity: manifest.tokenClaimsUnavailableReason ?? "measured",
882
- codexDisabledFeatures: runtime.codexDisabledFeatures,
883
- surfaces: options.surfaces,
884
- scenarioSelection: options.scenarioIds ?? null,
885
- surfaceModels: options.surfaces.includes("codex-cli") ? {
886
- piagent: options.model,
887
- "codex-cli": codexModelName(options.model)
888
- } : { "raw-pi": options.model ?? null, piagent: options.model ?? null },
889
- modelParityEvidence: options.surfaces.includes("codex-cli") ? "command-line-pinned" : "session-reported",
890
- gitVersion: runtime.gitVersion,
891
- source,
892
- candidateProvenance: candidateGuard.report(),
893
- suiteIdentity,
894
- runtimeCommands,
895
- configurationDigest,
896
- environmentPolicy,
897
- runtimeDependencies: bootstrapMetadata?.runtimeDependencies ?? null,
898
- assuranceEvidence
899
- },
900
- runs: completedRuns,
901
- ...comparison
926
+ finalizeBenchmarkRun({
927
+ assuranceEvidence, bootstrapMetadata, candidateGuard, canonicalProductionSuite,
928
+ codexCredentialBridge, comparison, configurationDigest, declaredScenarioCount,
929
+ detachPiRuntimeHome: () => { piRuntimeHome = undefined; },
930
+ environmentPolicy, executionGuard, fatalExecutionReceipt, fatalRunError,
931
+ finalizationReceipt, fullOrder, interruptedSignal, ledgerBinding, ledgerPath,
932
+ lifecycles, manifest, options, packageVersion: packageManifest.version,
933
+ pauseReason, piRuntimeHome, preservePiRuntime,
934
+ productionAllStageBoundaries, productionHostReadinessPolicy, productionSpendControlled, rootSeed, rootSeedDigest,
935
+ runId, runRoot, runs, runtime, runtimeCommands, source, startedAt, suite,
936
+ suiteDigest, suiteIdentity, terminalStop
902
937
  });
903
- report.ledger = ledgerBinding;
904
- applyBenchmarkClaimRestrictions(report, { tokenReason: manifest.tokenClaimsUnavailableReason, replaySource: options.replaySource, codexMode: options.codexMode, surfaces: options.surfaces });
905
- report.trustChecklist = benchmarkTrustChecklist(report);
906
- const text = renderBenchmarkText(report);
907
- const reportLedger = inspectBenchmarkLedger(ledgerPath);
908
- assertBenchmarkLedgerBinding(ledgerBinding, reportLedger.binding, "benchmark report ledger");
909
- validateBenchmarkLedgerPrefix(reportLedger.records, fullOrder, (record, index, expected) => expectedBenchmarkRecord(record, index, expected, runId, suite, configurationDigest));
910
- writePrivate(path.join(runRoot, "report.html"), renderBenchmarkHtml(report));
911
- writePrivate(path.join(runRoot, "summary.txt"), text);
912
- cleanupUnretainedWorkspaces(runRoot, options.keepWorkspaces);
913
- const prepublishReceipt = executionGuard.receipt("prepublish");
914
- const prepublishError = prepublishReceipt.error;
915
- if (prepublishError) { writeBenchmarkAbort(runRoot, { runId, completedRuns: completedRuns.length, expectedRuns: fullOrder.length }, prepublishError, { ledger: ledgerBinding, provenanceStamp: prepublishReceipt.stamp }); throw prepublishError; }
916
- for (const marker of ["paused.json", "interrupted.json", "aborted.json", "stopped.json"]) fs.rmSync(path.join(runRoot, marker), { force: true });
917
- writePrivateAtomic(path.join(runRoot, "report.json"), `${JSON.stringify(report, null, 2)}\n`);
918
- process.stdout.write(options.json ? `${JSON.stringify(report, null, 2)}\n` : text);
919
- process.stdout.write(`Reports: ${runRoot}\n`);
920
- if (
921
- report.comparison.qualityGate === false
922
- || report.comparison.safetyGate === false
923
- || report.comparison.reliabilityGate === false
924
- || report.comparison.qualityNonInferior === false
925
- || report.comparison.workflowGate === false
926
- || report.comparison.categoryGate === false
927
- || report.comparison.suiteGate?.passed === false
928
- ) process.exitCode = 1;
929
938
  } finally {
930
939
  releaseRunLock?.();
931
940
  codexRuntime?.cleanup();
@@ -933,12 +942,7 @@ async function main() {
933
942
  }
934
943
  }
935
944
 
936
- function invokedAsEntrypoint() {
937
- try { return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(process.argv[1] || ""); }
938
- catch { return import.meta.url === pathToFileURL(process.argv[1] || "").href; }
939
- }
940
-
941
- if (invokedAsEntrypoint()) {
945
+ if (invokedAsEntrypoint(import.meta.url, process.argv[1])) {
942
946
  main().catch((error) => {
943
947
  console.error(`FAIL: ${error.message}`);
944
948
  process.exit(error.exitCode ?? 1);