@piagent/platform 1.5.5 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +9 -8
  3. package/README.vi.md +9 -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 +68 -0
  24. package/benchmarks/production-v1/suite.json +42 -3
  25. package/catalog/capabilities.json +6 -6
  26. package/docs/command-reference-vietnamese.md +6 -6
  27. package/docs/context-engine.md +7 -1
  28. package/docs/distribution-standard.md +7 -7
  29. package/docs/en/architecture.md +1 -0
  30. package/docs/languages.json +4 -4
  31. package/docs/model-options.md +8 -5
  32. package/docs/operator-manual-vietnamese.md +13 -11
  33. package/docs/project-adapters.md +2 -2
  34. package/docs/publishing-for-teams.md +5 -0
  35. package/docs/quality-benchmark.md +271 -62
  36. package/docs/release-install-policy.md +10 -9
  37. package/docs/runtime-policy-design.md +4 -4
  38. package/docs/runtime-quality-baseline.md +13 -6
  39. package/docs/security-threat-model.md +2 -2
  40. package/docs/task-lifecycle-vietnamese.md +2 -2
  41. package/docs/team-onboarding.md +2 -2
  42. package/docs/usage-observability.md +7 -2
  43. package/docs/vi/architecture.md +1 -0
  44. package/docs-site/architecture.html +1 -1
  45. package/docs-site/benchmark.html +1 -1
  46. package/docs-site/commands.html +1 -1
  47. package/docs-site/context-engine.html +1 -1
  48. package/docs-site/en/architecture.html +1 -1
  49. package/docs-site/en/benchmark.html +1 -1
  50. package/docs-site/en/commands.html +1 -1
  51. package/docs-site/en/context-engine.html +1 -1
  52. package/docs-site/en/index.html +1 -1
  53. package/docs-site/en/mcp-approval.html +1 -1
  54. package/docs-site/en/mcp-auth.html +1 -1
  55. package/docs-site/en/mcp-servers.html +1 -1
  56. package/docs-site/en/mcp.html +1 -1
  57. package/docs-site/en/permissions.html +1 -1
  58. package/docs-site/en/profiles.html +1 -1
  59. package/docs-site/en/quickstart.html +1 -1
  60. package/docs-site/en/runtime.html +1 -1
  61. package/docs-site/en/scope.html +1 -1
  62. package/docs-site/en/security.html +1 -1
  63. package/docs-site/en/team.html +1 -1
  64. package/docs-site/en/whats-new.html +1 -1
  65. package/docs-site/en/workflows.html +1 -1
  66. package/docs-site/index.html +1 -1
  67. package/docs-site/mcp-approval.html +1 -1
  68. package/docs-site/mcp-auth.html +1 -1
  69. package/docs-site/mcp-servers.html +1 -1
  70. package/docs-site/mcp.html +1 -1
  71. package/docs-site/permissions.html +1 -1
  72. package/docs-site/profiles.html +1 -1
  73. package/docs-site/quickstart.html +1 -1
  74. package/docs-site/runtime.html +1 -1
  75. package/docs-site/scope.html +1 -1
  76. package/docs-site/security.html +1 -1
  77. package/docs-site/team.html +1 -1
  78. package/docs-site/whats-new.html +1 -1
  79. package/docs-site/workflows.html +1 -1
  80. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
  81. package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
  82. package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
  83. package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
  84. package/evals/fs4-readiness-matrix.v1.json +2 -2
  85. package/evals/fs5-causal-arm.v1.json +2 -2
  86. package/evals/fs5-pilot-protocol.v1.json +2 -2
  87. package/evals/fs5-pilot-protocol.v4.json +6 -5
  88. package/evals/fs5-pilot-protocol.v5.json +7 -6
  89. package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
  90. package/package.json +3 -1
  91. package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
  92. package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
  93. package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
  94. package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
  95. package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
  96. package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
  97. package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
  98. package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
  99. package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -4
  100. package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
  101. package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
  102. package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
  103. package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
  104. package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
  105. package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
  106. package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
  107. package/packages/piagent-core/benchmark/benchmark-report.js +116 -12
  108. package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
  109. package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
  110. package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +782 -0
  111. package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
  112. package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
  113. package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
  114. package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
  115. package/packages/piagent-core/benchmark/benchmark-suite.js +128 -2
  116. package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
  117. package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
  118. package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
  119. package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
  120. package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
  121. package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
  122. package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
  123. package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
  124. package/packages/piagent-core/extensions/acceptance-receipt.js +34 -11
  125. package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
  126. package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
  127. package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
  128. package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
  129. package/packages/piagent-core/extensions/context-engine.js +148 -145
  130. package/packages/piagent-core/extensions/context-evidence.js +48 -0
  131. package/packages/piagent-core/extensions/context-import-links.js +296 -0
  132. package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
  133. package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
  134. package/packages/piagent-core/extensions/criterion-graph.js +78 -8
  135. package/packages/piagent-core/extensions/guard-types.ts +2 -0
  136. package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
  137. package/packages/piagent-core/extensions/piagent-guard.ts +233 -41
  138. package/packages/piagent-core/extensions/task-contract-view.js +17 -3
  139. package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
  140. package/packages/piagent-core/extensions/task-state.js +40 -134
  141. package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
  142. package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
  143. package/packages/piagent-core/package.json +1 -1
  144. package/packages/piagent-core/policies/base-policy.json +2 -0
  145. package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
  146. package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
  147. package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
  148. package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
  149. package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
  150. package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
  151. package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
  152. package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
  153. package/packages/piagent-core/runtime/hooks/completion-hook.ts +5 -6
  154. package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
  155. package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
  156. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
  157. package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
  158. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
  159. package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
  160. package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
  161. package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
  162. package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
  163. package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +53 -0
  164. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
  165. package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
  166. package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
  167. package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
  168. package/packages/piagent-core/runtime/orchestration/role-policy.ts +1 -1
  169. package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
  170. package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
  171. package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
  172. package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
  173. package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
  174. package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
  175. package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
  176. package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +55 -2
  177. package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
  178. package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
  179. package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
  180. package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
  181. package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
  182. package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
  183. package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
  184. package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
  185. package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
  186. package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
  187. package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
  188. package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
  189. package/packages/piagent-core/security/sensitive-data.js +13 -218
  190. package/packages/piagent-core/security/sensitive-project-file.js +42 -0
  191. package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
  192. package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
  193. package/packages/piagent-core/security/sensitive-text.js +234 -0
  194. package/packages/piagent-core/subagents/piagent-worker.md +1 -1
  195. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
  196. package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
  197. package/packages/piagent-webui/dist/client/index.html +1 -1
  198. package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
  199. package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
  200. package/packages/piagent-webui/gateway/gateway-session-stream.ts +93 -7
  201. package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
  202. package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
  203. package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
  204. package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
  205. package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
  206. package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
  207. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -124
  208. package/packages/piagent-webui/server/loopback-server.ts +8 -0
  209. package/packages/piagent-webui/server/transcript-projection.ts +22 -4
  210. package/packages/piagent-webui/shared/text-visibility.ts +10 -0
  211. package/schemas/piagent-webui/catalog-v1.json +9 -0
  212. package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
  213. package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
  214. package/schemas/task-contract.schema.json +12 -1
  215. package/scripts/benchmark-runner-core.mjs +355 -359
  216. package/scripts/benchmark-runner-finalization.mjs +309 -0
  217. package/scripts/benchmark-runner-host-readiness.mjs +94 -0
  218. package/scripts/benchmark-runner-support.mjs +200 -0
  219. package/scripts/benchmark-session.mjs +95 -41
  220. package/scripts/configure-model-scope.sh +4 -3
  221. package/scripts/helper-evaluation.mjs +1 -1
  222. package/scripts/install-global.sh +2 -2
  223. package/scripts/pi-usage-history.mjs +37 -10
  224. package/scripts/setup.sh +2 -2
  225. package/scripts/verify-local.sh +5 -1
  226. package/templates/global/settings.json +2 -1
  227. package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
@@ -1,5 +1,6 @@
1
1
  export const BENCHMARK_SCOPE_BANDS = [
2
2
  { id: "core", suite: "core-v1", scenarios: 4, availability: "runnable", purpose: "Fast regression check for task quality, scope, safety, and exact usage." },
3
+ { id: "deep-logic", suite: "deep-logic-v1", scenarios: 7, availability: "runnable", claimTier: "capability", purpose: "Luna-medium-locked specialist regression across seven generated families, including exact temporal usage billing; full-suite only, not a generalization claim." },
3
4
  { id: "production", suite: "production-v1", scenarios: 18, availability: "runnable", claimTier: "public-regression", purpose: "Broader public regression matrix across archetypes, lifecycle states, and verifier profiles; not a generalization or production-stability claim." },
4
5
  { id: "capability", suite: "capability-v1", scenarios: 6, availability: "runnable", claimTier: "capability", purpose: "Unsaturated multi-file and multi-component tasks for hill-climbing; not a release gate." },
5
6
  { id: "e2-framework", suite: "e2-framework-v1", scenarios: 4, availability: "runnable", claimTier: "capability", purpose: "Taxonomy-bound real Hono, SQLite, and workspace tasks with reference, mutation, alternative-valid, scope, and grader-sensitivity calibration; not a release gate." },
@@ -25,6 +26,8 @@ export function requiresLongHorizonEvidence({ recoveryChange = false, contextCha
25
26
  export function benchmarkTrustChecklist(report = {}) {
26
27
  const comparison = report.comparison ?? {};
27
28
  const hasPairedUsage = Number(comparison.pairedUsageRuns ?? 0) > 0;
29
+ const successfulPairEfficiencyRequired = comparison.successfulPairEfficiencyRole !== "diagnostic";
30
+ const failureAwareEfficiencyRequired = comparison.failureAwareEfficiencyRole !== "diagnostic";
28
31
  const qualityPassed = comparison.qualityGate === true;
29
32
  const safetyPassed = comparison.safetyGate === true;
30
33
  const reliabilityPassed = comparison.reliabilityGate === true;
@@ -35,7 +38,13 @@ export function benchmarkTrustChecklist(report = {}) {
35
38
  const outcomeEvidencePassed = comparison.outcomeEvidenceGate !== false;
36
39
  const pairedRegressionPassed = comparison.pairedRegressionGate !== false;
37
40
  const failureAwareEfficiencyPassed = comparison.failureAwareEfficiencyGate !== false;
41
+ const primaryEfficiencyEvidencePassed = comparison.primaryEfficiencyEvidenceGate === true;
42
+ const primaryEfficiencyBandCoveragePassed = comparison.primaryEfficiencyBandCoverageGate !== false;
43
+ const primaryEfficiencyPassed = comparison.primaryEfficiencyGate === true;
38
44
  const comparisonProtocolPassed = comparison.comparisonProtocolGate?.passed === true;
45
+ const fullSuitePassed = comparison.fullSuiteGate !== false;
46
+ const performancePassed = comparison.performanceGate !== false;
47
+ const stabilityPassed = comparison.stabilityGate !== false;
39
48
  return {
40
49
  hasPairedUsage,
41
50
  hasQualityGate: typeof comparison.qualityGate === "boolean",
@@ -48,21 +57,30 @@ export function benchmarkTrustChecklist(report = {}) {
48
57
  hasOutcomeEvidenceGate: typeof comparison.outcomeEvidenceGate === "boolean",
49
58
  hasPairedRegressionGate: typeof comparison.pairedRegressionGate === "boolean",
50
59
  hasFailureAwareEfficiencyGate: typeof comparison.failureAwareEfficiencyGate === "boolean",
60
+ hasPrimaryEfficiencyEvidenceGate: typeof comparison.primaryEfficiencyEvidenceGate === "boolean",
61
+ hasPrimaryEfficiencyBandCoverageGate: typeof comparison.primaryEfficiencyBandCoverageGate === "boolean",
62
+ hasPrimaryEfficiencyGate: typeof comparison.primaryEfficiencyGate === "boolean",
51
63
  hasComparisonProtocolGate: typeof comparison.comparisonProtocolGate?.passed === "boolean",
52
64
  achievedClaimTier: comparison.claimEligibility?.achievedTier ?? "unavailable",
53
65
  generalizationClaimAllowed: comparison.claimEligibility?.generalizationClaimAllowed === true,
54
66
  tokenSavingClaimAllowed: comparison.tokenClaimAllowed === true
55
- && hasPairedUsage
67
+ && (!successfulPairEfficiencyRequired || hasPairedUsage)
56
68
  && qualityPassed
57
69
  && safetyPassed
58
70
  && reliabilityPassed
59
71
  && workflowPassed
60
72
  && qualityNonInferior
61
- && efficiencyEvidencePassed
62
- && efficiencyBandCoveragePassed
73
+ && (!successfulPairEfficiencyRequired || efficiencyEvidencePassed)
74
+ && (!successfulPairEfficiencyRequired || efficiencyBandCoveragePassed)
63
75
  && outcomeEvidencePassed
64
76
  && pairedRegressionPassed
65
- && failureAwareEfficiencyPassed
77
+ && (!failureAwareEfficiencyRequired || failureAwareEfficiencyPassed)
78
+ && primaryEfficiencyEvidencePassed
79
+ && primaryEfficiencyBandCoveragePassed
80
+ && primaryEfficiencyPassed
81
+ && fullSuitePassed
82
+ && performancePassed
83
+ && stabilityPassed
66
84
  && comparisonProtocolPassed
67
85
  };
68
86
  }
@@ -0,0 +1,317 @@
1
+ import {
2
+ atMostWithinFloatingPrecision,
3
+ geometricMean,
4
+ geometricMeanConfidence95,
5
+ geometricMeanConfidence95Raw,
6
+ rounded
7
+ } from "./benchmark-statistics.js";
8
+
9
+ const SNAPSHOT_FIELDS = new Set([
10
+ "schemaVersion", "id", "model", "currency", "unitTokens", "rates",
11
+ "cacheWrite", "longContext", "source"
12
+ ]);
13
+ const RATE_FIELDS = new Set(["freshInput", "cachedInput", "output"]);
14
+ const CACHE_WRITE_FIELDS = new Set(["basis", "multiplier"]);
15
+ const LONG_CONTEXT_FIELDS = new Set(["thresholdInputTokens", "condition", "inputMultiplier", "outputMultiplier"]);
16
+ const SOURCE_FIELDS = new Set(["url", "retrievedAt"]);
17
+ const USAGE_BUCKETS = Object.freeze(["input", "output", "cacheRead", "cacheWrite"]);
18
+ const BAND_DIMENSIONS = Object.freeze({
19
+ categories: "category",
20
+ profiles: "profile",
21
+ lifecycles: "lifecycle",
22
+ difficulties: "difficulty"
23
+ });
24
+
25
+ function plainObject(value) {
26
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
27
+ }
28
+
29
+ function unsupportedFields(value, allowed, label, errors) {
30
+ if (!plainObject(value)) return;
31
+ for (const field of Object.keys(value)) {
32
+ if (!allowed.has(field)) errors.push(`${label} has unsupported field ${field}`);
33
+ }
34
+ }
35
+
36
+ function positiveFinite(value) {
37
+ return Number.isFinite(value) && value > 0;
38
+ }
39
+
40
+ /** Validate the immutable API-equivalent pricing input used by benchmark reports. */
41
+ export function benchmarkPricingSnapshotValidationErrors(snapshot) {
42
+ if (!plainObject(snapshot)) return ["pricingSnapshot must be an object"];
43
+ const errors = [];
44
+ unsupportedFields(snapshot, SNAPSHOT_FIELDS, "pricingSnapshot", errors);
45
+ if (snapshot.schemaVersion !== 1) errors.push("pricingSnapshot.schemaVersion must be 1");
46
+ if (typeof snapshot.id !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(snapshot.id)) {
47
+ errors.push("pricingSnapshot.id must use lowercase kebab-case");
48
+ }
49
+ if (typeof snapshot.model !== "string" || !/^[^/\s]+\/[^/\s]+$/.test(snapshot.model)) {
50
+ errors.push("pricingSnapshot.model must use provider/model form");
51
+ }
52
+ if (snapshot.currency !== "USD") errors.push("pricingSnapshot.currency must be USD");
53
+ if (snapshot.unitTokens !== 1_000_000) errors.push("pricingSnapshot.unitTokens must be 1000000");
54
+
55
+ if (!plainObject(snapshot.rates)) errors.push("pricingSnapshot.rates must be an object");
56
+ else {
57
+ unsupportedFields(snapshot.rates, RATE_FIELDS, "pricingSnapshot.rates", errors);
58
+ for (const field of RATE_FIELDS) {
59
+ if (!positiveFinite(snapshot.rates[field])) errors.push(`pricingSnapshot.rates.${field} must be greater than 0`);
60
+ }
61
+ }
62
+
63
+ if (!plainObject(snapshot.cacheWrite)) errors.push("pricingSnapshot.cacheWrite must be an object");
64
+ else {
65
+ unsupportedFields(snapshot.cacheWrite, CACHE_WRITE_FIELDS, "pricingSnapshot.cacheWrite", errors);
66
+ if (snapshot.cacheWrite.basis !== "fresh-input") errors.push("pricingSnapshot.cacheWrite.basis must be fresh-input");
67
+ if (!positiveFinite(snapshot.cacheWrite.multiplier)) errors.push("pricingSnapshot.cacheWrite.multiplier must be greater than 0");
68
+ }
69
+
70
+ if (!plainObject(snapshot.longContext)) errors.push("pricingSnapshot.longContext must be an object");
71
+ else {
72
+ unsupportedFields(snapshot.longContext, LONG_CONTEXT_FIELDS, "pricingSnapshot.longContext", errors);
73
+ if (!Number.isSafeInteger(snapshot.longContext.thresholdInputTokens) || snapshot.longContext.thresholdInputTokens <= 0) {
74
+ errors.push("pricingSnapshot.longContext.thresholdInputTokens must be a positive safe integer");
75
+ }
76
+ if (snapshot.longContext.condition !== "per-request-input-greater-than") {
77
+ errors.push("pricingSnapshot.longContext.condition must be per-request-input-greater-than");
78
+ }
79
+ for (const field of ["inputMultiplier", "outputMultiplier"]) {
80
+ if (!positiveFinite(snapshot.longContext[field])) errors.push(`pricingSnapshot.longContext.${field} must be greater than 0`);
81
+ }
82
+ }
83
+
84
+ if (!plainObject(snapshot.source)) errors.push("pricingSnapshot.source must be an object");
85
+ else {
86
+ unsupportedFields(snapshot.source, SOURCE_FIELDS, "pricingSnapshot.source", errors);
87
+ if (typeof snapshot.source.url !== "string" || !snapshot.source.url.startsWith("https://developers.openai.com/")) {
88
+ errors.push("pricingSnapshot.source.url must be an official developers.openai.com HTTPS URL");
89
+ }
90
+ if (typeof snapshot.source.retrievedAt !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(snapshot.source.retrievedAt)) {
91
+ errors.push("pricingSnapshot.source.retrievedAt must use YYYY-MM-DD");
92
+ }
93
+ }
94
+ return errors;
95
+ }
96
+
97
+ function exactUsageIssue(usage, snapshot) {
98
+ if (!plainObject(usage) || usage.usageCompleteness !== "exact") return "usage-not-exact";
99
+ if (!Number.isSafeInteger(usage.sessions) || usage.sessions <= 0) return "usage-session-count-not-exact";
100
+ for (const field of USAGE_BUCKETS) {
101
+ if (!Number.isSafeInteger(usage[field]) || usage[field] < 0) return `usage-${field}-not-exact`;
102
+ }
103
+ if (!Number.isSafeInteger(usage.fresh) || usage.fresh !== usage.input + usage.output) return "usage-fresh-invariant-failed";
104
+ if (!Number.isSafeInteger(usage.total)
105
+ || usage.total !== usage.input + usage.cacheRead + usage.cacheWrite + usage.output) {
106
+ return "usage-total-invariant-failed";
107
+ }
108
+ if (usage.model !== snapshot.model) return "pricing-model-mismatch";
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * Convert exact token buckets to an API-equivalent amount without treating it as
114
+ * OAuth/provider-billed cost. Aggregate prompt usage at or below the threshold
115
+ * is a valid upper bound for every constituent request. Above it, aggregate-only
116
+ * evidence cannot identify which per-request multiplier applies, so we fail closed.
117
+ */
118
+ export function normalizeBenchmarkUsageCost(usage, snapshot) {
119
+ const snapshotErrors = benchmarkPricingSnapshotValidationErrors(snapshot);
120
+ if (snapshotErrors.length > 0) {
121
+ return { status: "unavailable", reason: "pricing-snapshot-invalid", errors: snapshotErrors };
122
+ }
123
+ const usageIssue = exactUsageIssue(usage, snapshot);
124
+ if (usageIssue) return { status: "unavailable", reason: usageIssue, errors: [] };
125
+ const aggregatePromptTokens = usage.input + usage.cacheRead + usage.cacheWrite;
126
+ if (aggregatePromptTokens > snapshot.longContext.thresholdInputTokens) {
127
+ return {
128
+ status: "unavailable",
129
+ reason: "long-context-per-request-usage-unavailable",
130
+ errors: [],
131
+ aggregatePromptTokens,
132
+ thresholdInputTokens: snapshot.longContext.thresholdInputTokens,
133
+ pricingApplicability: "unknown"
134
+ };
135
+ }
136
+ const cacheWriteRate = snapshot.rates.freshInput * snapshot.cacheWrite.multiplier;
137
+ const amount = (
138
+ (usage.input * snapshot.rates.freshInput)
139
+ + (usage.cacheRead * snapshot.rates.cachedInput)
140
+ + (usage.cacheWrite * cacheWriteRate)
141
+ + (usage.output * snapshot.rates.output)
142
+ ) / snapshot.unitTokens;
143
+ return {
144
+ status: "measured",
145
+ source: "versioned-api-equivalent-text-token-pricing",
146
+ pricingSnapshotId: snapshot.id,
147
+ pricingApplicability: "standard-context-proven-by-aggregate-upper-bound",
148
+ currency: snapshot.currency,
149
+ amount,
150
+ amountUsd: rounded(amount, 9),
151
+ aggregatePromptTokens,
152
+ thresholdInputTokens: snapshot.longContext.thresholdInputTokens,
153
+ buckets: Object.fromEntries(USAGE_BUCKETS.map((field) => [field, usage[field]]))
154
+ };
155
+ }
156
+
157
+ function pairRecord(pair, snapshot) {
158
+ const baseline = normalizeBenchmarkUsageCost(pair.baseline?.usage, snapshot);
159
+ const candidate = normalizeBenchmarkUsageCost(pair.candidate?.usage, snapshot);
160
+ const issues = [];
161
+ if (baseline.status !== "measured") issues.push(`baseline:${baseline.reason}`);
162
+ if (candidate.status !== "measured") issues.push(`candidate:${candidate.reason}`);
163
+ if (pair.baseline?.usage?.thinkingLevel !== pair.candidate?.usage?.thinkingLevel) issues.push("thinking-mismatch");
164
+ const comparable = issues.length === 0 && baseline.amount > 0 && candidate.amount > 0;
165
+ if (issues.length === 0 && !comparable) issues.push("non-positive-normalized-cost");
166
+ return {
167
+ scenarioId: pair.candidate?.scenarioId ?? pair.baseline?.scenarioId ?? null,
168
+ repeat: pair.candidate?.repeat ?? pair.baseline?.repeat ?? null,
169
+ resolved: pair.baseline?.resolved === true && pair.candidate?.resolved === true,
170
+ comparable,
171
+ issues,
172
+ baseline,
173
+ candidate,
174
+ ratio: comparable ? candidate.amount / baseline.amount : null
175
+ };
176
+ }
177
+
178
+ function bandReport(suite, familyRatios) {
179
+ return Object.fromEntries(Object.entries(BAND_DIMENSIONS).map(([dimension, field]) => {
180
+ const bands = new Map();
181
+ for (const scenario of suite.scenarios) {
182
+ const name = scenario[field] ?? "unspecified";
183
+ const item = bands.get(name) ?? { expectedScenarioFamilies: 0, ratios: [], missingScenarioIds: [] };
184
+ item.expectedScenarioFamilies += 1;
185
+ const family = familyRatios.find((candidate) => candidate.scenarioId === scenario.id);
186
+ if (Number.isFinite(family?.ratio)) item.ratios.push(family.ratio);
187
+ else item.missingScenarioIds.push(scenario.id);
188
+ bands.set(name, item);
189
+ }
190
+ return [dimension, Object.fromEntries([...bands.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([name, item]) => [name, {
191
+ expectedScenarioFamilies: item.expectedScenarioFamilies,
192
+ comparableScenarioFamilies: item.ratios.length,
193
+ complete: item.missingScenarioIds.length === 0,
194
+ ratio: item.missingScenarioIds.length === 0 ? geometricMean(item.ratios) : null,
195
+ missingScenarioIds: item.missingScenarioIds
196
+ }]))];
197
+ }));
198
+ }
199
+
200
+ /** Build the independent normalized-cost evidence and family-clustered estimand. */
201
+ export function normalizedCostComparison({ suite, allPairs, repeats }) {
202
+ const snapshot = suite.pricingSnapshot;
203
+ const snapshotErrors = benchmarkPricingSnapshotValidationErrors(snapshot);
204
+ const records = allPairs.map((pair) => pairRecord(pair, snapshot));
205
+ const resolvedComparable = records.filter((record) => record.resolved && record.comparable);
206
+ const recordsByScenario = new Map();
207
+ for (const record of resolvedComparable) {
208
+ const family = recordsByScenario.get(record.scenarioId) ?? [];
209
+ family.push(record);
210
+ recordsByScenario.set(record.scenarioId, family);
211
+ }
212
+ const familyRatios = suite.scenarios.map((scenario) => {
213
+ const records = recordsByScenario.get(scenario.id) ?? [];
214
+ const observedRepeats = new Set(records.map((record) => record.repeat));
215
+ const complete = Number.isInteger(repeats) && repeats > 0
216
+ && records.length === repeats && observedRepeats.size === repeats;
217
+ return {
218
+ scenarioId: scenario.id,
219
+ pairs: records.length,
220
+ complete,
221
+ ratio: complete ? geometricMean(records.map((record) => record.ratio)) : null,
222
+ issues: complete ? [] : ["missing-comparable-normalized-cost"]
223
+ };
224
+ });
225
+ const completeFamilies = familyRatios.filter((family) => family.complete);
226
+ const ratio = completeFamilies.length === suite.scenarios.length
227
+ ? geometricMean(completeFamilies.map((family) => family.ratio))
228
+ : null;
229
+ const confidence95 = completeFamilies.length === suite.scenarios.length
230
+ ? geometricMeanConfidence95(completeFamilies.map((family) => family.ratio))
231
+ : null;
232
+ const confidence95Raw = completeFamilies.length === suite.scenarios.length
233
+ ? geometricMeanConfidence95Raw(completeFamilies.map((family) => family.ratio))
234
+ : null;
235
+ const applicabilityFailures = records
236
+ .filter((record) => !record.comparable)
237
+ .map((record) => ({ scenarioId: record.scenarioId, repeat: record.repeat, issues: record.issues }));
238
+ const expectedPairs = suite.scenarios.length * repeats;
239
+ const exactAndApplicable = snapshotErrors.length === 0
240
+ && records.length === expectedPairs
241
+ && records.length > 0
242
+ && applicabilityFailures.length === 0;
243
+ const complete = exactAndApplicable && completeFamilies.length === suite.scenarios.length;
244
+ const amounts = records.filter((record) => record.comparable);
245
+ return {
246
+ schemaVersion: 1,
247
+ source: "versioned-api-equivalent-text-token-pricing",
248
+ billedCost: false,
249
+ scope: "model-text-token-input-cache-output-only",
250
+ pricingSnapshot: snapshotErrors.length === 0 ? {
251
+ id: snapshot.id,
252
+ model: snapshot.model,
253
+ currency: snapshot.currency,
254
+ source: snapshot.source
255
+ } : null,
256
+ snapshotErrors,
257
+ expectedPairs,
258
+ observedPairs: allPairs.length,
259
+ comparablePairs: records.filter((record) => record.comparable).length,
260
+ resolvedComparablePairs: resolvedComparable.length,
261
+ exactUsageAndPricingApplicability: exactAndApplicable,
262
+ applicabilityFailures,
263
+ complete,
264
+ expectedScenarioFamilies: suite.scenarios.length,
265
+ completeScenarioFamilies: completeFamilies.length,
266
+ ratio: complete ? ratio : null,
267
+ ratioConfidence95: complete ? confidence95 : null,
268
+ ratioConfidence95Raw: complete ? confidence95Raw : null,
269
+ deltaPercent: complete ? (ratio - 1) * 100 : null,
270
+ familyRatios,
271
+ bands: bandReport(suite, familyRatios),
272
+ totals: {
273
+ baselineUsd: rounded(amounts.reduce((sum, record) => sum + record.baseline.amount, 0), 9),
274
+ candidateUsd: rounded(amounts.reduce((sum, record) => sum + record.candidate.amount, 0), 9)
275
+ }
276
+ };
277
+ }
278
+
279
+ export function normalizedCostGate(comparison, thresholds) {
280
+ const evidenceGate = comparison.complete === true;
281
+ const applicabilityGate = comparison.exactUsageAndPricingApplicability === true;
282
+ const confidenceGate = evidenceGate
283
+ && Number.isFinite(comparison.ratioConfidence95Raw?.upper)
284
+ && atMostWithinFloatingPrecision(comparison.ratioConfidence95Raw.upper, thresholds.maximumRatioUpper95);
285
+ const bandFailures = [];
286
+ for (const [dimension, bands] of Object.entries(comparison.bands ?? {})) {
287
+ for (const [name, band] of Object.entries(bands)) {
288
+ if (!Number.isFinite(band.ratio) || !atMostWithinFloatingPrecision(band.ratio, thresholds.maximumBandRatio)) {
289
+ bandFailures.push({
290
+ dimension,
291
+ name,
292
+ ratio: Number.isFinite(band.ratio) ? band.ratio : null,
293
+ reason: Number.isFinite(band.ratio) ? "ratio-above-limit" : "missing-comparable-normalized-cost",
294
+ missingScenarioIds: band.missingScenarioIds ?? []
295
+ });
296
+ }
297
+ }
298
+ }
299
+ const familyFailures = (comparison.familyRatios ?? [])
300
+ .filter((family) => !Number.isFinite(family.ratio)
301
+ || !atMostWithinFloatingPrecision(family.ratio, thresholds.maximumFamilyRatio))
302
+ .map((family) => ({
303
+ scenarioId: family.scenarioId,
304
+ ratio: Number.isFinite(family.ratio) ? family.ratio : null,
305
+ reason: Number.isFinite(family.ratio) ? "ratio-above-limit" : "missing-comparable-normalized-cost"
306
+ }));
307
+ return {
308
+ evidenceGate,
309
+ applicabilityGate,
310
+ confidenceGate,
311
+ bandGate: bandFailures.length === 0,
312
+ familyGate: familyFailures.length === 0,
313
+ bandFailures,
314
+ familyFailures,
315
+ passed: evidenceGate && applicabilityGate && confidenceGate && bandFailures.length === 0 && familyFailures.length === 0
316
+ };
317
+ }
@@ -59,7 +59,7 @@ function publicCommandIdentity(value) {
59
59
  export function benchmarkPreflightReceipt({
60
60
  packageVersion, source, candidateProvenance, suite, suiteDigest,
61
61
  runtimeDependencies, runtimeCommands, environmentPolicy, configurationDigest,
62
- rootSeedDigest, options, runtime
62
+ rootSeedDigest, options, runtime, hostReadinessPolicyDigest = null, hostReadiness = null
63
63
  }) {
64
64
  return {
65
65
  schemaVersion: 1,
@@ -72,6 +72,7 @@ export function benchmarkPreflightReceipt({
72
72
  suite: { id: suite.id, contentDigest: suiteDigest, scenarioCount: suite.scenarios.length },
73
73
  configuration: {
74
74
  contentDigest: configurationDigest,
75
+ ...(hostReadinessPolicyDigest ? { hostReadinessPolicyDigest } : {}),
75
76
  runtimeDependencyDigest: runtimeDependencies?.digest ?? null,
76
77
  environmentPolicyDigest: environmentPolicy.digest,
77
78
  rootSeedDigest,
@@ -87,6 +88,7 @@ export function benchmarkPreflightReceipt({
87
88
  maxSessions: options.maxSessions ?? null,
88
89
  stopAfterFailedPair: options.stopAfterFailedPair
89
90
  },
91
+ ...(hostReadiness ? { hostReadiness } : {}),
90
92
  runtime: {
91
93
  gitVersion: runtime.gitVersion,
92
94
  piVersion: runtime.piVersion,
@@ -1,5 +1,6 @@
1
1
  import crypto from "node:crypto";
2
2
  import { spawn } from "node:child_process";
3
+ import { performance } from "node:perf_hooks";
3
4
 
4
5
  const outputLimit = 4 * 1024 * 1024;
5
6
 
@@ -43,7 +44,7 @@ export function createBenchmarkProcessController(interrupted) {
43
44
  reject(new Error("Benchmark was interrupted before the child process started"));
44
45
  return;
45
46
  }
46
- const started = Date.now();
47
+ const started = performance.now();
47
48
  const child = spawn(command, args, {
48
49
  cwd: options.cwd,
49
50
  env: options.env ?? process.env,
@@ -80,7 +81,7 @@ export function createBenchmarkProcessController(interrupted) {
80
81
  if (!options.inherit) {
81
82
  child.stdout.on("data", (chunk) => {
82
83
  stdoutDigest.update(chunk);
83
- options.onStdoutChunk?.(chunk);
84
+ options.onStdoutChunk?.(chunk, { observedAtSeconds: (performance.now() - started) / 1000 });
84
85
  inspect("stdout", chunk);
85
86
  stdout = append(stdout, chunk);
86
87
  });
@@ -116,7 +117,7 @@ export function createBenchmarkProcessController(interrupted) {
116
117
  if (settled) return;
117
118
  settled = true;
118
119
  cleanup();
119
- resolve({ code: code ?? 1, signal, timedOut, stdout, stdoutHash: options.inherit ? undefined : stdoutDigest.digest("hex"), stderr, forbiddenHits: [...forbiddenHits], requiredHits: [...requiredHits], durationSeconds: (Date.now() - started) / 1000 });
120
+ resolve({ code: code ?? 1, signal, timedOut, stdout, stdoutHash: options.inherit ? undefined : stdoutDigest.digest("hex"), stderr, forbiddenHits: [...forbiddenHits], requiredHits: [...requiredHits], durationSeconds: (performance.now() - started) / 1000 });
120
121
  };
121
122
  if (!groupAlive(child)) {
122
123
  finish();
@@ -125,10 +126,10 @@ export function createBenchmarkProcessController(interrupted) {
125
126
  // A detached descendant can close its inherited stdio before exiting. Keep
126
127
  // the process group owned until TERM has had time to work and the bounded
127
128
  // KILL escalation has run, rather than treating stream close as cleanup.
128
- const deadline = Date.now() + 1_000;
129
+ const deadline = performance.now() + 1_000;
129
130
  const drain = () => {
130
131
  if (!groupAlive(child)) finish();
131
- else if (Date.now() >= deadline) {
132
+ else if (performance.now() >= deadline) {
132
133
  settled = true;
133
134
  cleanup();
134
135
  reject(new Error(`Benchmark child process group ${child.pid} survived bounded SIGKILL cleanup`));
@@ -0,0 +1,126 @@
1
+ const SHA256 = /^[a-f0-9]{64}$/;
2
+
3
+ function requestedProviderModelId(requestedModel) {
4
+ const value = typeof requestedModel === "string" ? requestedModel.trim() : "";
5
+ if (!value) return null;
6
+ const separator = value.indexOf("/");
7
+ const modelId = separator >= 0 ? value.slice(separator + 1) : value;
8
+ return modelId && !modelId.includes("/") ? modelId : null;
9
+ }
10
+
11
+ function requestedProviderReasoningEffort(requestedThinking) {
12
+ const value = typeof requestedThinking === "string" ? requestedThinking.trim() : "";
13
+ if (!value) return null;
14
+ if (value === "off") return "none";
15
+ if (value === "minimal") return "low";
16
+ return value;
17
+ }
18
+
19
+ function sortedUnique(values) {
20
+ return [...new Set(values)].sort();
21
+ }
22
+
23
+ /**
24
+ * Reduces privacy-safe provider request fingerprints into per-run release
25
+ * evidence. Deferred tool-search batches are observed separately: changes to
26
+ * those hashes do not count as base system/tool prefix drift.
27
+ */
28
+ export function buildBenchmarkProviderWireEvidence({
29
+ events,
30
+ requestedModel,
31
+ requestedThinking,
32
+ telemetryTruncated = false
33
+ }) {
34
+ const expectedModelId = requestedProviderModelId(requestedModel);
35
+ const expectedReasoningEffort = requestedProviderReasoningEffort(requestedThinking);
36
+ const wireEvents = (Array.isArray(events) ? events : [])
37
+ .filter((event) => event?.event === "provider_request_wire_surface");
38
+ const knownEvents = wireEvents.filter((event) => event.state === "known");
39
+ const instructionHashes = sortedUnique(knownEvents
40
+ .map((event) => event.instructionsHash)
41
+ .filter((value) => SHA256.test(value ?? "")));
42
+ const baseInstructionHashes = sortedUnique(knownEvents
43
+ .map((event) => event.baseInstructionsHash)
44
+ .filter((value) => SHA256.test(value ?? "")));
45
+ const orderedToolSurfaceHashes = sortedUnique(knownEvents
46
+ .map((event) => event.orderedToolSurfaceHash)
47
+ .filter((value) => SHA256.test(value ?? "")));
48
+ const deferredToolSurfaceHashes = sortedUnique(knownEvents
49
+ .map((event) => event.deferredToolSurfaceHash)
50
+ .filter((value) => SHA256.test(value ?? "")));
51
+ const unknownEvents = wireEvents.length - knownEvents.length;
52
+ const missingBaseHashEvents = knownEvents.filter((event) => (
53
+ !SHA256.test(event.instructionsHash ?? "")
54
+ || !SHA256.test(event.baseInstructionsHash ?? "")
55
+ || !SHA256.test(event.orderedToolSurfaceHash ?? "")
56
+ )).length;
57
+ const modelMismatchEvents = knownEvents.filter((event) => (
58
+ expectedModelId === null || event.providerModelId !== expectedModelId
59
+ )).length;
60
+ const reasoningMismatchEvents = knownEvents.filter((event) => (
61
+ expectedReasoningEffort === null || event.providerReasoningEffort !== expectedReasoningEffort
62
+ )).length;
63
+ const deferredToolBatchEvents = knownEvents.filter((event) => Number(event.deferredToolBatchCount ?? 0) > 0).length;
64
+ const checks = {
65
+ "telemetry-complete": telemetryTruncated !== true,
66
+ "wire-events-observed": wireEvents.length > 0,
67
+ "wire-events-known": wireEvents.length > 0 && unknownEvents === 0,
68
+ "requested-model-exact": wireEvents.length > 0 && modelMismatchEvents === 0,
69
+ "requested-reasoning-effort-exact": wireEvents.length > 0 && reasoningMismatchEvents === 0,
70
+ "base-instructions-available-and-stable": wireEvents.length > 0
71
+ && missingBaseHashEvents === 0
72
+ && instructionHashes.length === 1
73
+ && baseInstructionHashes.length === 1,
74
+ "base-tool-surface-available-and-stable": wireEvents.length > 0 && missingBaseHashEvents === 0 && orderedToolSurfaceHashes.length === 1
75
+ };
76
+ return {
77
+ schemaVersion: 1,
78
+ state: Object.values(checks).every(Boolean) ? "verified" : wireEvents.length > 0 ? "failed" : "unavailable",
79
+ passed: Object.values(checks).every(Boolean),
80
+ expectedModelId,
81
+ expectedReasoningEffort,
82
+ telemetryTruncated: telemetryTruncated === true,
83
+ wireEvents: wireEvents.length,
84
+ knownEvents: knownEvents.length,
85
+ unknownEvents,
86
+ missingBaseHashEvents,
87
+ modelMismatchEvents,
88
+ reasoningMismatchEvents,
89
+ instructionHashes,
90
+ baseInstructionHashes,
91
+ orderedToolSurfaceHashes,
92
+ deferred: {
93
+ toolSurfaceHashes: deferredToolSurfaceHashes,
94
+ toolBatchEvents: deferredToolBatchEvents,
95
+ maximumBatchCount: knownEvents.reduce((maximum, event) => Math.max(maximum, Number(event.deferredToolBatchCount ?? 0)), 0),
96
+ maximumToolCount: knownEvents.reduce((maximum, event) => Math.max(maximum, Number(event.deferredToolCount ?? 0)), 0)
97
+ },
98
+ checks
99
+ };
100
+ }
101
+
102
+ export function benchmarkProviderWireEvidenceMatchesRequest(evidence, requestedModel, requestedThinking) {
103
+ const expectedModelId = requestedProviderModelId(requestedModel);
104
+ const expectedReasoningEffort = requestedProviderReasoningEffort(requestedThinking);
105
+ return evidence?.schemaVersion === 1
106
+ && evidence.passed === true
107
+ && evidence.state === "verified"
108
+ && evidence.expectedModelId === expectedModelId
109
+ && evidence.expectedReasoningEffort === expectedReasoningEffort
110
+ && expectedModelId !== null
111
+ && expectedReasoningEffort !== null
112
+ && evidence.telemetryTruncated === false
113
+ && Number.isInteger(evidence.wireEvents) && evidence.wireEvents > 0
114
+ && evidence.knownEvents === evidence.wireEvents
115
+ && evidence.unknownEvents === 0
116
+ && evidence.missingBaseHashEvents === 0
117
+ && evidence.modelMismatchEvents === 0
118
+ && evidence.reasoningMismatchEvents === 0
119
+ && Array.isArray(evidence.instructionHashes) && evidence.instructionHashes.length === 1 && SHA256.test(evidence.instructionHashes[0])
120
+ && Array.isArray(evidence.baseInstructionHashes) && evidence.baseInstructionHashes.length === 1 && SHA256.test(evidence.baseInstructionHashes[0])
121
+ && Array.isArray(evidence.orderedToolSurfaceHashes) && evidence.orderedToolSurfaceHashes.length === 1 && SHA256.test(evidence.orderedToolSurfaceHashes[0]);
122
+ }
123
+
124
+ export function benchmarkRequestedProviderReasoningEffort(requestedThinking) {
125
+ return requestedProviderReasoningEffort(requestedThinking);
126
+ }