@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
@@ -11,6 +11,13 @@ const data = oracle.graderData;
11
11
  const checks = [];
12
12
  async function check(id, action) { try { await action(); checks.push({ id, passed: true }); } catch { checks.push({ id, passed: false }); } }
13
13
  async function load(file) { const url = pathToFileURL(path.join(workspace, file)); url.searchParams.set("benchmark", `${Date.now()}-${Math.random()}`); return import(url.href); }
14
+ function canonicalJson(value) {
15
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
16
+ if (value && typeof value === "object") {
17
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
18
+ }
19
+ return JSON.stringify(value);
20
+ }
14
21
 
15
22
  switch (scenario) {
16
23
  case "revision-event-reconciliation": {
@@ -109,29 +116,97 @@ switch (scenario) {
109
116
  }
110
117
  case "resumable-stream-assembly": {
111
118
  const { assembleStream } = await load("src/stream.js");
112
- await check("interleaved-replay-and-gap", () => {
113
- const snapshot = { cursor: 2, messages: [{ id: data.message, text: data.first, complete: false }] };
119
+ await check("contiguous-interleaving-exact-duplicates-and-order", () => {
120
+ const snapshot = { cursor: data.start, messages: [{ id: data.message, text: data.first, complete: false }] };
121
+ const first = {
122
+ cursor: data.start + 1,
123
+ messageId: data.message,
124
+ offset: data.first.length,
125
+ text: data.second,
126
+ complete: false
127
+ };
128
+ const second = {
129
+ cursor: data.start + 2,
130
+ messageId: data.other,
131
+ offset: 0,
132
+ text: data.third,
133
+ complete: false
134
+ };
135
+ const final = {
136
+ cursor: data.start + 3,
137
+ messageId: data.message,
138
+ offset: (data.first + data.second).length,
139
+ text: "",
140
+ complete: true
141
+ };
142
+ const events = [second, first, structuredClone(second), final];
143
+ const before = structuredClone([snapshot, events]), result = assembleStream(snapshot, events);
144
+ assert.deepEqual([snapshot, events], before);
145
+ assert.equal(result.cursor, data.start + 3);
146
+ assert.deepEqual(result.messages, [
147
+ { id: data.message, text: data.first + data.second, complete: true },
148
+ { id: data.other, text: data.third, complete: false }
149
+ ]);
150
+ assert.deepEqual(result.appliedCursors, [data.start + 1, data.start + 2, data.start + 3]);
151
+ assert.deepEqual(result.replayedCursors, []);
152
+ assert.deepEqual(result.buffered, []);
153
+ assert.equal(result.gap, null);
154
+ });
155
+ await check("replay-gap-and-buffer-order", () => {
156
+ const snapshot = { cursor: data.start, messages: [] };
157
+ const replay = { cursor: data.start, messageId: "replay", offset: 0, text: "old", complete: false };
114
158
  const events = [
115
- { cursor: 5, messageId: "other", offset: 0, text: "late", complete: false },
116
- { cursor: 1, messageId: data.message, offset: 0, text: "old", complete: false },
117
- { cursor: 3, messageId: data.message, offset: data.first.length, text: data.second, complete: true }
159
+ { cursor: data.start + 3, messageId: "late-b", offset: 0, text: data.second, complete: false },
160
+ replay,
161
+ { cursor: data.start + 2, messageId: "late-a", offset: 0, text: data.first, complete: false },
162
+ structuredClone(replay)
118
163
  ];
119
164
  const before = structuredClone([snapshot, events]), result = assembleStream(snapshot, events);
120
165
  assert.deepEqual([snapshot, events], before);
121
- assert.equal(result.cursor, 3); assert.equal(result.messages[0].text, data.first + data.second); assert.equal(result.messages[0].complete, true);
122
- assert.deepEqual(result.appliedCursors, [3]); assert.deepEqual(result.replayedCursors, [1]);
123
- assert.deepEqual(result.gap, { expected: 4, observed: 5 }); assert.equal(result.buffered[0].cursor, 5);
166
+ assert.equal(result.cursor, data.start);
167
+ assert.deepEqual(result.appliedCursors, []);
168
+ assert.deepEqual(result.replayedCursors, [data.start]);
169
+ assert.deepEqual(result.buffered.map((event) => event.cursor), [data.start + 2, data.start + 3]);
170
+ assert.deepEqual(result.gap, { expected: data.start + 1, observed: data.start + 2 });
124
171
  });
125
- await check("offset-completion-and-cursor-conflicts", () => {
172
+ await check("utf16-offset-and-empty-finalization", () => {
173
+ const initial = `${data.astral}${data.first}`;
174
+ const snapshot = { cursor: 0, messages: [{ id: data.message, text: initial, complete: false }] };
175
+ const events = [{ cursor: 1, messageId: data.message, offset: initial.length, text: data.second, complete: true }];
176
+ const result = assembleStream(snapshot, events);
177
+ assert.equal(result.messages[0].text, initial + data.second);
178
+ assert.equal(result.messages[0].complete, true);
179
+ const emptyFinal = assembleStream(
180
+ { cursor: 0, messages: [{ id: data.other, text: initial, complete: false }] },
181
+ [{ cursor: 1, messageId: data.other, offset: initial.length, text: "", complete: true }]
182
+ );
183
+ assert.equal(emptyFinal.messages[0].text, initial);
184
+ assert.equal(emptyFinal.messages[0].complete, true);
185
+ });
186
+ await check("invalid-offset-rejected", () => {
126
187
  const base = { cursor: 0, messages: [] };
127
188
  assert.throws(() => assembleStream(base, [{ cursor: 1, messageId: "m", offset: 1, text: "x", complete: false }]));
189
+ });
190
+ await check("conflicting-cursor-rejected", () => {
191
+ const base = { cursor: 0, messages: [] };
128
192
  assert.throws(() => assembleStream(base, [
129
193
  { cursor: 1, messageId: "m", offset: 0, text: "x", complete: false },
130
194
  { cursor: 1, messageId: "m", offset: 0, text: "y", complete: false }
131
195
  ]));
196
+ });
197
+ await check("event-after-completion-rejected", () => {
132
198
  assert.throws(() => assembleStream({ cursor: 0, messages: [{ id: "m", text: "x", complete: true }] },
133
199
  [{ cursor: 1, messageId: "m", offset: 1, text: "", complete: true }]));
134
200
  });
201
+ await check("input-shape-validation", () => {
202
+ assert.throws(() => assembleStream({ cursor: -1, messages: [] }, []));
203
+ assert.throws(() => assembleStream({ cursor: 0, messages: [] }, [
204
+ { cursor: 1, messageId: "m", offset: Number.MAX_SAFE_INTEGER + 1, text: "x", complete: false }
205
+ ]));
206
+ assert.throws(() => assembleStream({ cursor: 0, messages: [] }, [
207
+ { cursor: 1, messageId: "m", offset: 0, text: 1, complete: false }
208
+ ]));
209
+ });
135
210
  break;
136
211
  }
137
212
  case "transactional-config-merge": {
@@ -142,7 +217,10 @@ switch (scenario) {
142
217
  const schema = { service: { type: "object", required: true }, remove: { type: "string" }, enabled: { type: "boolean", default: false } };
143
218
  const before = structuredClone([base, layers, schema]), result = planConfigTransaction(base, layers, schema);
144
219
  assert.deepEqual([base, layers, schema], before);
145
- assert.deepEqual(result.next, { service: { port: 0, stale: true, [data.key]: data.value }, enabled: false });
220
+ assert.equal(
221
+ canonicalJson(result.next),
222
+ canonicalJson({ service: { port: 0, stale: true, [data.key]: data.value }, enabled: false })
223
+ );
146
224
  assert.deepEqual(result.changes.map((item) => item.path), [...result.changes.map((item) => item.path)].sort());
147
225
  assert.ok(result.changes.some((item) => item.path === "/remove" && item.kind === "delete"));
148
226
  assert.ok(result.changes.some((item) => item.path === "/enabled" && item.kind === "add"));
@@ -158,6 +236,190 @@ switch (scenario) {
158
236
  });
159
237
  break;
160
238
  }
239
+ case "temporal-usage-billing-close": {
240
+ const { normalizePlanTimeline } = await load("packages/billing/src/plan-timeline.js");
241
+ const { closeUsagePeriod } = await load("packages/billing/src/close-period.js");
242
+ const { billingSummary } = await load("apps/admin/src/billing-summary.js");
243
+ const period = { start: "2026-08-01T00:00:00.000Z", end: "2026-09-01T00:00:00.000Z" };
244
+ const [planA1, planA2, planB1] = data.plans;
245
+ const [price1, price2, price3] = data.prices;
246
+ const tiers = (prices = data.prices) => [
247
+ { upTo: data.firstLimit, unitPriceMicros: prices[0] },
248
+ { upTo: data.secondLimit, unitPriceMicros: prices[1] },
249
+ { upTo: null, unitPriceMicros: prices[2] }
250
+ ];
251
+ const plan = ({ id, tenantId, effectiveAt, currency = "USD", meters = { [data.meter]: tiers() } }) => (
252
+ { id, tenantId, currency, effectiveAt, meters }
253
+ );
254
+ const plans = () => [
255
+ plan({ id: planA2, tenantId: data.tenantA, effectiveAt: "2026-08-15T00:00:00.000Z" }),
256
+ plan({ id: planB1, tenantId: data.tenantB, effectiveAt: "2026-07-01T00:00:00.000Z" }),
257
+ plan({ id: planA1, tenantId: data.tenantA, effectiveAt: "2026-07-01T00:00:00.000Z" })
258
+ ];
259
+ const usage = (id, tenantId, at, units, meter = data.meter) => ({ id, kind: "usage", tenantId, meter, at, units });
260
+ const reversal = (id, tenantId, at, targetId) => ({ id, kind: "reversal", tenantId, at, targetId });
261
+ const canonical = (value) => {
262
+ if (value === null || typeof value === "string" || typeof value === "boolean" || Number.isSafeInteger(value)) return JSON.stringify(value);
263
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
264
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
265
+ };
266
+ await check("period-and-timeline-normalization", () => {
267
+ const source = plans();
268
+ source[0].meters = { zeta: tiers(), [data.meter]: tiers() };
269
+ const before = structuredClone(source), normalized = normalizePlanTimeline(source, period);
270
+ assert.deepEqual(source, before);
271
+ assert.notEqual(normalized.find((item) => item.id === planA1), source[2]);
272
+ assert.deepEqual(normalized.map((item) => item.id), before.toSorted((left, right) => left.tenantId.localeCompare(right.tenantId)
273
+ || left.effectiveAt.localeCompare(right.effectiveAt) || left.id.localeCompare(right.id)).map((item) => item.id));
274
+ assert.deepEqual(Object.keys(normalized.find((item) => item.id === planA2).meters), [data.meter, "zeta"].sort());
275
+ assert.deepEqual(Object.keys(normalized[0]), ["id", "tenantId", "currency", "effectiveAt", "meters"]);
276
+ });
277
+ await check("timeline-validation-fail-closed", () => {
278
+ assert.throws(() => normalizePlanTimeline(plans(), { start: period.end, end: period.start }), TypeError);
279
+ assert.throws(() => normalizePlanTimeline([plans()[0], { ...plans()[1], id: plans()[0].id }], period), TypeError);
280
+ assert.throws(() => normalizePlanTimeline([plans()[0], { ...plans()[2], effectiveAt: plans()[0].effectiveAt }], period), TypeError);
281
+ assert.throws(() => normalizePlanTimeline([plans()[2], { ...plans()[0], currency: "EUR" }], period), TypeError);
282
+ assert.throws(() => normalizePlanTimeline([plan({ id: "bad", tenantId: data.tenantA, effectiveAt: "2026-07-01T00:00:00.000Z", meters: { [data.meter]: [{ upTo: 3, unitPriceMicros: 1 }] } })], period), TypeError);
283
+ assert.throws(() => normalizePlanTimeline([plan({ id: "bad", tenantId: data.tenantA, effectiveAt: "2026-07-01", meters: { [data.meter]: tiers() } })], period), TypeError);
284
+ });
285
+ await check("tier-boundary-and-multi-tier-allocation", () => {
286
+ const firstUnits = data.firstLimit - 1, secondUnits = data.secondLimit - data.firstLimit + 3;
287
+ const result = closeUsagePeriod({ period, plans: plans(), events: [
288
+ usage("u-1", data.tenantA, "2026-08-02T00:00:00.000Z", firstUnits),
289
+ usage("u-2", data.tenantA, "2026-08-03T00:00:00.000Z", secondUnits)
290
+ ] });
291
+ const [first, second] = result.invoices[0].lines;
292
+ assert.deepEqual(first.allocations, [{ tierIndex: 0, units: firstUnits, unitPriceMicros: String(price1), chargeMicros: String(BigInt(firstUnits) * BigInt(price1)) }]);
293
+ assert.deepEqual(second.allocations.map((item) => [item.tierIndex, item.units]), [
294
+ [0, 1], [1, data.secondLimit - data.firstLimit], [2, 2]
295
+ ]);
296
+ assert.equal(second.chargeMicros, String(BigInt(price1) + BigInt(data.secondLimit - data.firstLimit) * BigInt(price2) + 2n * BigInt(price3)));
297
+ });
298
+ await check("effective-boundary-and-plan-reset", () => {
299
+ const result = closeUsagePeriod({ period, plans: plans(), events: [
300
+ usage("before", data.tenantA, "2026-08-14T23:59:59.999Z", data.firstLimit),
301
+ usage("boundary", data.tenantA, "2026-08-15T00:00:00.000Z", data.firstLimit + 1)
302
+ ] });
303
+ const [before, boundary] = result.invoices[0].lines;
304
+ assert.equal(before.planId, planA1); assert.equal(boundary.planId, planA2);
305
+ assert.deepEqual(boundary.allocations.map((item) => [item.tierIndex, item.units]), [[0, data.firstLimit], [1, 1]]);
306
+ });
307
+ await check("tenant-and-meter-counter-isolation", () => {
308
+ const otherMeter = `${data.meter}-secondary`;
309
+ const expanded = plans().map((item) => ({ ...item, meters: { ...item.meters, [otherMeter]: tiers() } }));
310
+ const result = closeUsagePeriod({ period, plans: expanded, events: [
311
+ usage("a-main", data.tenantA, "2026-08-02T00:00:00.000Z", data.firstLimit),
312
+ usage("a-other", data.tenantA, "2026-08-03T00:00:00.000Z", data.firstLimit, otherMeter),
313
+ usage("b-main", data.tenantB, "2026-08-02T00:00:00.000Z", data.firstLimit)
314
+ ] });
315
+ assert.equal(result.invoices.length, 2);
316
+ for (const invoice of result.invoices) for (const line of invoice.lines) {
317
+ assert.deepEqual(line.allocations.map((item) => item.tierIndex), [0]);
318
+ }
319
+ });
320
+ await check("reversal-applied-before-rating", () => {
321
+ const result = closeUsagePeriod({ period, plans: plans(), events: [
322
+ reversal("reverse-first", data.tenantA, "2026-08-10T00:00:00.000Z", "charged-first"),
323
+ usage("charged-second", data.tenantA, "2026-08-03T00:00:00.000Z", data.firstLimit),
324
+ usage("charged-first", data.tenantA, "2026-08-02T00:00:00.000Z", data.firstLimit)
325
+ ] });
326
+ assert.deepEqual(result.reversedEventIds, ["charged-first"]);
327
+ assert.deepEqual(result.invoices[0].lines.map((item) => item.eventId), ["charged-second"]);
328
+ assert.deepEqual(result.invoices[0].lines[0].allocations.map((item) => item.tierIndex), [0]);
329
+ });
330
+ await check("reversal-validation", () => {
331
+ const target = usage("target", data.tenantA, "2026-08-05T00:00:00.000Z", 1);
332
+ assert.throws(() => closeUsagePeriod({ period, plans: plans(), events: [target, reversal("r", data.tenantB, "2026-08-06T00:00:00.000Z", "target")] }), TypeError);
333
+ assert.throws(() => closeUsagePeriod({ period, plans: plans(), events: [target, reversal("r", data.tenantA, "2026-08-04T00:00:00.000Z", "target")] }), TypeError);
334
+ assert.throws(() => closeUsagePeriod({ period, plans: plans(), events: [target, reversal("r1", data.tenantA, "2026-08-06T00:00:00.000Z", "target"), reversal("r2", data.tenantA, "2026-08-07T00:00:00.000Z", "target")] }), TypeError);
335
+ assert.throws(() => closeUsagePeriod({ period, plans: plans(), events: [reversal("r", data.tenantA, "2026-08-06T00:00:00.000Z", "missing")] }), TypeError);
336
+ });
337
+ await check("permutation-independent-canonical-output", () => {
338
+ const events = [
339
+ usage("z-last", data.tenantA, "2026-08-04T00:00:00.000Z", 2),
340
+ usage("a-first", data.tenantA, "2026-08-02T00:00:00.000Z", 2),
341
+ usage("b-same-time", data.tenantA, "2026-08-02T00:00:00.000Z", 2),
342
+ usage("tenant-b", data.tenantB, "2026-08-03T00:00:00.000Z", 2)
343
+ ];
344
+ const forward = closeUsagePeriod({ period, plans: plans(), events });
345
+ const reversed = closeUsagePeriod({ period, plans: plans().reverse(), events: [...events].reverse() });
346
+ assert.deepEqual(reversed, forward);
347
+ assert.deepEqual(forward.invoices.find((item) => item.tenantId === data.tenantA).lines.map((item) => item.eventId), ["a-first", "b-same-time", "z-last"]);
348
+ });
349
+ await check("bigint-charge-exactness", () => {
350
+ const units = Number.MAX_SAFE_INTEGER, unitPriceMicros = Number.MAX_SAFE_INTEGER;
351
+ const hugePlan = [plan({ id: planA1, tenantId: data.tenantA, effectiveAt: "2026-07-01T00:00:00.000Z", meters: { [data.meter]: [{ upTo: null, unitPriceMicros }] } })];
352
+ const result = closeUsagePeriod({ period, plans: hugePlan, events: [usage("huge", data.tenantA, "2026-08-02T00:00:00.000Z", units)] });
353
+ const expected = BigInt(units) * BigInt(unitPriceMicros);
354
+ assert.equal(result.invoices[0].lines[0].chargeMicros, String(expected));
355
+ assert.equal(result.invoices[0].subtotalMicros, String(expected));
356
+ });
357
+ await check("round-half-to-even-once", () => {
358
+ const closeAt = (price) => closeUsagePeriod({ period, plans: [plan({ id: planA1, tenantId: data.tenantA, effectiveAt: "2026-07-01T00:00:00.000Z", meters: { [data.meter]: [{ upTo: null, unitPriceMicros: price }] } })], events: [usage("tie", data.tenantA, "2026-08-02T00:00:00.000Z", 1)] });
359
+ assert.equal(closeAt(2_500_000).invoices[0].totalMinor, "2");
360
+ assert.equal(closeAt(3_500_000).invoices[0].totalMinor, "4");
361
+ });
362
+ await check("invoice-shape-total-and-nested-digest", () => {
363
+ const result = closeUsagePeriod({ period, plans: plans(), events: [usage("digest-line", data.tenantA, "2026-08-02T00:00:00.000Z", data.firstLimit + 2)] });
364
+ assert.deepEqual(Object.keys(result), ["period", "invoices", "reversedEventIds"]);
365
+ const { digest: observed, ...content } = result.invoices[0];
366
+ assert.equal(observed, crypto.createHash("sha256").update(canonical(content)).digest("hex"));
367
+ assert.equal(content.subtotalMicros, String(content.lines.reduce((sum, line) => sum + BigInt(line.chargeMicros), 0n)));
368
+ const mutated = structuredClone(content); mutated.lines[0].allocations[0].units += 1;
369
+ assert.notEqual(observed, crypto.createHash("sha256").update(canonical(mutated)).digest("hex"));
370
+ });
371
+ await check("input-immutability", () => {
372
+ const input = { period, plans: plans(), events: [usage("valid", data.tenantA, "2026-08-02T00:00:00.000Z", 1)] };
373
+ const before = structuredClone(input); closeUsagePeriod(input); assert.deepEqual(input, before);
374
+ const finalInvalid = { period, plans: plans(), events: [usage("ok", data.tenantA, "2026-08-02T00:00:00.000Z", 1), usage("bad", data.tenantA, period.end, 1)] };
375
+ const finalBefore = structuredClone(finalInvalid); assert.throws(() => closeUsagePeriod(finalInvalid), TypeError); assert.deepEqual(finalInvalid, finalBefore);
376
+ });
377
+ await check("event-validation", () => {
378
+ for (const invalid of [
379
+ { ...usage("x", data.tenantA, period.end, 1) },
380
+ { ...usage("x", data.tenantA, "2026-08-02T00:00:00.000Z", 1), extra: true },
381
+ { ...usage("x", data.tenantA, "2026-08-02T00:00:00.000Z", 1), units: Number.MAX_SAFE_INTEGER + 1 }
382
+ ]) assert.throws(() => closeUsagePeriod({ period, plans: plans(), events: [invalid] }), TypeError);
383
+ });
384
+ await check("missing-plan-or-meter-fails-closed", () => {
385
+ assert.throws(() => closeUsagePeriod({
386
+ period,
387
+ plans: plans(),
388
+ events: [usage("missing-meter", data.tenantA, "2026-08-02T00:00:00.000Z", 1, "missing-meter")]
389
+ }), TypeError);
390
+ assert.throws(() => closeUsagePeriod({
391
+ period,
392
+ plans: plans(),
393
+ events: [usage("missing-plan", "tenant-without-plan", "2026-08-02T00:00:00.000Z", 1)]
394
+ }), TypeError);
395
+ });
396
+ await check("non-json-and-poison-values-rejected", () => {
397
+ const sparse = []; sparse.length = 1;
398
+ assert.throws(() => normalizePlanTimeline(sparse, period), TypeError);
399
+ const poisonedMeters = JSON.parse(`{"__proto__":[{"upTo":null,"unitPriceMicros":1}]}`);
400
+ assert.throws(() => normalizePlanTimeline([plan({ id: planA1, tenantId: data.tenantA, effectiveAt: "2026-07-01T00:00:00.000Z", meters: poisonedMeters })], period), TypeError);
401
+ const invalidPrice = plans(); invalidPrice[0].meters[data.meter][0].unitPriceMicros = Number.NaN;
402
+ assert.throws(() => normalizePlanTimeline(invalidPrice, period), TypeError);
403
+ const accessorPeriod = { end: period.end }; Object.defineProperty(accessorPeriod, "start", { enumerable: true, get: () => period.start });
404
+ assert.throws(() => normalizePlanTimeline(plans(), accessorPeriod), TypeError);
405
+ const symbolPlan = plans()[0]; symbolPlan[Symbol("hidden")] = true;
406
+ assert.throws(() => normalizePlanTimeline([symbolPlan], period), TypeError);
407
+ });
408
+ await check("terminal-webui-summary-format-parity", () => {
409
+ const result = closeUsagePeriod({ period, plans: plans(), events: [usage("summary", data.tenantA, "2026-08-02T00:00:00.000Z", 1)] });
410
+ const invoice = result.invoices[0];
411
+ assert.equal(billingSummary(result), [
412
+ `period=${period.start}..${period.end}; invoices=1; reversed=0`,
413
+ `tenant=${JSON.stringify(data.tenantA)}; currency=USD; lines=1; subtotalMicros=${invoice.subtotalMicros}; totalMinor=${invoice.totalMinor}; digest=${invoice.digest}`
414
+ ].join("\n"));
415
+ });
416
+ await check("terminal-webui-summary-rejects-malformed-result", () => {
417
+ const result = closeUsagePeriod({ period, plans: plans(), events: [usage("summary-invalid", data.tenantA, "2026-08-02T00:00:00.000Z", 1)] });
418
+ const invoice = result.invoices[0];
419
+ assert.throws(() => billingSummary({ ...result, invoices: [{ ...invoice, digest: "wrong" }] }), TypeError);
420
+ });
421
+ break;
422
+ }
161
423
  default: throw new Error(`unsupported deep logic scenario ${scenario}`);
162
424
  }
163
425
 
@@ -0,0 +1,3 @@
1
+ export function billingSummary(_result) {
2
+ throw new Error("not implemented");
3
+ }
@@ -0,0 +1,166 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import { billingSummary } from "../apps/admin/src/billing-summary.js";
5
+ import { closeUsagePeriod } from "../packages/billing/src/close-period.js";
6
+ import { normalizePlanTimeline } from "../packages/billing/src/plan-timeline.js";
7
+ import { planConfigTransaction } from "../src/config-transaction.js";
8
+ import { buildContextPack } from "../src/context-pack.js";
9
+ import { reconcileSession } from "../src/event-reconcile.js";
10
+ import { decideAccess } from "../src/policy.js";
11
+ import { scheduleJobs } from "../src/scheduler.js";
12
+ import { assembleStream } from "../src/stream.js";
13
+
14
+ const scenario = process.env.PIAGENT_BENCHMARK_SCENARIO;
15
+
16
+ function canonicalJson(value) {
17
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
18
+ if (value && typeof value === "object") {
19
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
20
+ }
21
+ return JSON.stringify(value);
22
+ }
23
+
24
+ test("the selected benchmark entrypoint handles a fixed public happy path", { skip: !scenario }, () => {
25
+ switch (scenario) {
26
+ case "revision-event-reconciliation": {
27
+ const snapshot = { revision: "r0", nextSequence: 1, state: { keep: true } };
28
+ const events = [{ id: "e1", sequence: 1, baseRevision: "r0", revision: "r1", patch: { value: 1 } }];
29
+ const before = structuredClone([snapshot, events]);
30
+ assert.equal(canonicalJson(reconcileSession(snapshot, events)), canonicalJson({
31
+ revision: "r1",
32
+ nextSequence: 2,
33
+ state: { keep: true, value: 1 },
34
+ appliedIds: ["e1"],
35
+ duplicateIds: [],
36
+ pending: [],
37
+ gap: null
38
+ }));
39
+ assert.deepEqual([snapshot, events], before);
40
+ break;
41
+ }
42
+ case "fair-dependency-scheduler": {
43
+ const jobs = [{ id: "job-1", tenant: "tenant-a", weight: 1, dependsOn: [] }];
44
+ const before = structuredClone(jobs);
45
+ assert.deepEqual(scheduleJobs(jobs, 1), [["job-1"]]);
46
+ assert.deepEqual(jobs, before);
47
+ break;
48
+ }
49
+ case "layered-policy-resolution": {
50
+ const result = decideAccess({ path: "lib/tool.js", operation: "inspect" }, [
51
+ { name: "local", rules: [{ pattern: "lib/*", operations: ["inspect"], effect: "allow" }] }
52
+ ]);
53
+ assert.equal(result.allowed, true);
54
+ assert.equal(result.effect, "allow");
55
+ assert.equal(result.layer, "local");
56
+ assert.equal(result.ruleIndex, 0);
57
+ assert.equal(result.protected, false);
58
+ assert.equal(typeof result.reason, "string");
59
+ break;
60
+ }
61
+ case "budgeted-context-graph": {
62
+ const nodes = [{ id: "note", path: "notes/item.txt", text: "Needle note", tokens: 1 }];
63
+ const before = structuredClone(nodes);
64
+ const result = buildContextPack(
65
+ "needle",
66
+ nodes,
67
+ [],
68
+ 1
69
+ );
70
+ assert.deepEqual(result.selected.map((item) => item.id), ["note"]);
71
+ assert.equal(result.selected[0].directScore, 1);
72
+ assert.equal(result.selected[0].score, 1);
73
+ assert.equal(result.usedTokens, 1);
74
+ assert.deepEqual(result.omitted, []);
75
+ assert.equal(result.confidence, "high");
76
+ assert.deepEqual(nodes, before);
77
+ break;
78
+ }
79
+ case "resumable-stream-assembly": {
80
+ const snapshot = { cursor: 0, messages: [] };
81
+ const events = [{ cursor: 1, messageId: "m1", offset: 0, text: "hello", complete: true }];
82
+ const before = structuredClone([snapshot, events]);
83
+ assert.equal(canonicalJson(assembleStream(snapshot, events)), canonicalJson({
84
+ cursor: 1,
85
+ messages: [{ id: "m1", text: "hello", complete: true }],
86
+ appliedCursors: [1],
87
+ replayedCursors: [],
88
+ buffered: [],
89
+ gap: null
90
+ }));
91
+ assert.deepEqual([snapshot, events], before);
92
+
93
+ const openSnapshot = { cursor: 0, messages: [{ id: "open", text: "ready", complete: false }] };
94
+ const emptyFinal = [{ cursor: 1, messageId: "open", offset: 5, text: "", complete: true }];
95
+ assert.equal(canonicalJson(assembleStream(openSnapshot, emptyFinal)), canonicalJson({
96
+ cursor: 1,
97
+ messages: [{ id: "open", text: "ready", complete: true }],
98
+ appliedCursors: [1],
99
+ replayedCursors: [],
100
+ buffered: [],
101
+ gap: null
102
+ }));
103
+
104
+ const completedSnapshot = { cursor: 0, messages: [{ id: "done", text: "ready", complete: true }] };
105
+ const completedEvents = [{ cursor: 1, messageId: "done", offset: 5, text: "", complete: true }];
106
+ assert.throws(() => assembleStream(completedSnapshot, completedEvents));
107
+ break;
108
+ }
109
+ case "transactional-config-merge": {
110
+ const base = { label: "old", legacy: true };
111
+ const layers = [{ label: "new", legacy: { $delete: true } }];
112
+ const schema = {
113
+ label: { type: "string", required: true },
114
+ legacy: { type: "boolean" }
115
+ };
116
+ const before = structuredClone([base, layers, schema]);
117
+ const result = planConfigTransaction(
118
+ base,
119
+ layers,
120
+ schema
121
+ );
122
+ assert.equal(canonicalJson(result.next), canonicalJson({ label: "new" }));
123
+ assert.ok(result.changes.some((item) => item.path === "/label" && item.kind === "replace"));
124
+ assert.ok(result.changes.some((item) => item.path === "/legacy" && item.kind === "delete"));
125
+ assert.match(result.digest, /^[a-f0-9]{64}$/);
126
+ assert.deepEqual([base, layers, schema], before);
127
+ break;
128
+ }
129
+ case "temporal-usage-billing-close": {
130
+ const period = { start: "2025-01-01T00:00:00.000Z", end: "2025-02-01T00:00:00.000Z" };
131
+ const plans = [{
132
+ id: "basic",
133
+ tenantId: "customer-1",
134
+ currency: "USD",
135
+ effectiveAt: "2024-12-01T00:00:00.000Z",
136
+ meters: { requests: [{ upTo: null, unitPriceMicros: 1_000_000 }] }
137
+ }];
138
+ const normalized = normalizePlanTimeline(plans, period);
139
+ assert.equal(canonicalJson(normalized), canonicalJson(plans));
140
+ assert.notEqual(normalized[0], plans[0]);
141
+ const result = closeUsagePeriod({
142
+ period,
143
+ plans,
144
+ events: [{
145
+ id: "usage-1",
146
+ kind: "usage",
147
+ tenantId: "customer-1",
148
+ meter: "requests",
149
+ at: "2025-01-02T00:00:00.000Z",
150
+ units: 2
151
+ }]
152
+ });
153
+ assert.equal(result.invoices.length, 1);
154
+ assert.equal(result.invoices[0].subtotalMicros, "2000000");
155
+ assert.equal(result.invoices[0].totalMinor, "2");
156
+ assert.match(result.invoices[0].digest, /^[a-f0-9]{64}$/);
157
+ assert.deepEqual(billingSummary(result).split("\n"), [
158
+ `period=${period.start}..${period.end}; invoices=1; reversed=0`,
159
+ `tenant="customer-1"; currency=USD; lines=1; subtotalMicros=2000000; totalMinor=2; digest=${result.invoices[0].digest}`
160
+ ]);
161
+ break;
162
+ }
163
+ default:
164
+ assert.fail(`unsupported benchmark scenario ${scenario}`);
165
+ }
166
+ });
@@ -2,5 +2,5 @@
2
2
  "name": "piagent-deep-logic-fixture",
3
3
  "private": true,
4
4
  "type": "module",
5
- "scripts": { "test": "node --test test/*.test.js" }
5
+ "scripts": { "test": "node --test benchmark-contract/*.test.js test/*.test.js" }
6
6
  }
@@ -0,0 +1,3 @@
1
+ export function closeUsagePeriod(_input) {
2
+ throw new Error("not implemented");
3
+ }
@@ -0,0 +1,3 @@
1
+ export function normalizePlanTimeline(_plans, _period) {
2
+ throw new Error("not implemented");
3
+ }
@@ -7,6 +7,9 @@ import { reconcileSession } from "../src/event-reconcile.js";
7
7
  import { decideAccess } from "../src/policy.js";
8
8
  import { scheduleJobs } from "../src/scheduler.js";
9
9
  import { assembleStream } from "../src/stream.js";
10
+ import { billingSummary } from "../apps/admin/src/billing-summary.js";
11
+ import { closeUsagePeriod } from "../packages/billing/src/close-period.js";
12
+ import { normalizePlanTimeline } from "../packages/billing/src/plan-timeline.js";
10
13
 
11
14
  test("every benchmark entrypoint loads before private grading", () => {
12
15
  for (const entrypoint of [
@@ -15,6 +18,9 @@ test("every benchmark entrypoint loads before private grading", () => {
15
18
  reconcileSession,
16
19
  decideAccess,
17
20
  scheduleJobs,
18
- assembleStream
21
+ assembleStream,
22
+ normalizePlanTimeline,
23
+ closeUsagePeriod,
24
+ billingSummary
19
25
  ]) assert.equal(typeof entrypoint, "function");
20
26
  });
@@ -11,3 +11,5 @@ Required behavior:
11
11
  - Return `{ selected, usedTokens, omitted, confidence }`; selected/omitted entries include `id`, `path`, `tokens`, `directScore`, `score`. Confidence is `none`, `low`, `medium`, or `high` using maximum selected score thresholds 0, <0.34, <0.67, otherwise high.
12
12
 
13
13
  The hidden graph contains ties, cycles (propagation is still one hop), Unicode terms, required zero-score nodes, and candidates larger than the remaining budget.
14
+
15
+ Run `npm test` when complete; it includes a fixed public happy-path contract for this scenario.
@@ -11,3 +11,5 @@ Required invariants:
11
11
  - Never mutate the input. Every job appears exactly once, or the function throws.
12
12
 
13
13
  The hidden cases include dependencies that unlock across waves, weights that temporarily do not fit, punctuation in IDs, cycles, and three-tenant fairness. Add tests if useful.
14
+
15
+ Run `npm test` when complete; it includes a fixed public happy-path contract for this scenario.
@@ -12,3 +12,5 @@ Semantics:
12
12
  - Return `{ allowed, effect, layer, ruleIndex, protected, reason }` without exposing or changing inputs.
13
13
 
14
14
  Keep matching deterministic and do not use shell or filesystem glob expansion.
15
+
16
+ Run `npm test` when complete; it includes a fixed public happy-path contract for this scenario.
@@ -7,7 +7,9 @@ Rules:
7
7
  - Do not mutate inputs. Validate safe non-negative integer cursors/offsets and well-formed strings.
8
8
  - Starting at `snapshot.cursor + 1`, consume only a contiguous cursor prefix. Report later events as `buffered`, ordered by cursor then arrival order, and return `gap` as `null` or `{ expected, observed }`.
9
9
  - Events at or below the snapshot cursor are replay and ignored. Conflicting duplicate cursors inside the provided event list throw; exact duplicates count once.
10
- - For each consumed event, `offset` must equal the current text length of its message. A completed message cannot receive more text. The event text is appended and `complete` is sticky.
10
+ - For each consumed event, `offset` must equal the current JavaScript UTF-16 `String.length` of its message text. An empty final chunk may complete a message that is still open. Once a message is complete, any later event for it must throw, including an empty chunk. The event text is appended and `complete` is sticky.
11
11
  - Return `{ cursor, messages, appliedCursors, replayedCursors, buffered, gap }`. Preserve original message order and append newly seen messages in first-applied order.
12
12
 
13
13
  The hidden cases cover reconnect replay, interleaved messages, gaps, empty final chunks, surrogate-pair text length, and conflict rejection.
14
+
15
+ Run `npm test` when complete; it includes a fixed public happy-path contract for this scenario.
@@ -12,3 +12,5 @@ Requirements:
12
12
  - Reject malformed objects, unsafe sequence numbers, non-string IDs/revisions, arrays as patches, and prototype-pollution keys (`__proto__`, `prototype`, `constructor`). Fail closed.
13
13
 
14
14
  Add focused tests if useful. Keep the implementation dependency-free and deterministic.
15
+
16
+ Run `npm test` when complete; it includes a fixed public happy-path contract for this scenario.