@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.
- package/CHANGELOG.md +117 -0
- package/README.md +9 -8
- package/README.vi.md +9 -5
- package/adapters/web-frontend/profile.json +2 -4
- package/architecture/layers.json +18 -3
- package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
- package/benchmarks/deep-logic-v1/grade.mjs +272 -10
- package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
- package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
- package/benchmarks/deep-logic-v1/project/package.json +1 -1
- package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
- package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
- package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
- package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
- package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
- package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
- package/benchmarks/deep-logic-v1/suite.json +51 -3
- package/benchmarks/deep-logic-v1/variant.mjs +22 -1
- package/benchmarks/production-v1/spend-control.v1.json +68 -0
- package/benchmarks/production-v1/suite.json +42 -3
- package/catalog/capabilities.json +6 -6
- package/docs/command-reference-vietnamese.md +6 -6
- package/docs/context-engine.md +7 -1
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +1 -0
- package/docs/languages.json +4 -4
- package/docs/model-options.md +8 -5
- package/docs/operator-manual-vietnamese.md +13 -11
- package/docs/project-adapters.md +2 -2
- package/docs/publishing-for-teams.md +5 -0
- package/docs/quality-benchmark.md +271 -62
- package/docs/release-install-policy.md +10 -9
- package/docs/runtime-policy-design.md +4 -4
- package/docs/runtime-quality-baseline.md +13 -6
- package/docs/security-threat-model.md +2 -2
- package/docs/task-lifecycle-vietnamese.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +7 -2
- package/docs/vi/architecture.md +1 -0
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +1 -1
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
- package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
- package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
- package/evals/fs4-readiness-matrix.v1.json +2 -2
- package/evals/fs5-causal-arm.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v4.json +6 -5
- package/evals/fs5-pilot-protocol.v5.json +7 -6
- package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
- package/package.json +3 -1
- package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
- package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
- package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
- package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
- package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
- package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
- package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
- package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -4
- package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
- package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
- package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
- package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
- package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
- package/packages/piagent-core/benchmark/benchmark-report.js +116 -12
- package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
- package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
- package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +782 -0
- package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
- package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
- package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
- package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
- package/packages/piagent-core/benchmark/benchmark-suite.js +128 -2
- package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
- package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
- package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
- package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
- package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
- package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
- package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
- package/packages/piagent-core/extensions/acceptance-receipt.js +34 -11
- package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
- package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
- package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
- package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
- package/packages/piagent-core/extensions/context-engine.js +148 -145
- package/packages/piagent-core/extensions/context-evidence.js +48 -0
- package/packages/piagent-core/extensions/context-import-links.js +296 -0
- package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
- package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
- package/packages/piagent-core/extensions/criterion-graph.js +78 -8
- package/packages/piagent-core/extensions/guard-types.ts +2 -0
- package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +233 -41
- package/packages/piagent-core/extensions/task-contract-view.js +17 -3
- package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
- package/packages/piagent-core/extensions/task-state.js +40 -134
- package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +2 -0
- package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
- package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
- package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
- package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
- package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
- package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
- package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
- package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
- package/packages/piagent-core/runtime/hooks/completion-hook.ts +5 -6
- package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
- package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
- package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
- package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
- package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +53 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
- package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
- package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
- package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
- package/packages/piagent-core/runtime/orchestration/role-policy.ts +1 -1
- package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
- package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
- package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
- package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
- package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
- package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
- package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
- package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +55 -2
- package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
- package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
- package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
- package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
- package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
- package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
- package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
- package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
- package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
- package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
- package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
- package/packages/piagent-core/security/sensitive-data.js +13 -218
- package/packages/piagent-core/security/sensitive-project-file.js +42 -0
- package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
- package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
- package/packages/piagent-core/security/sensitive-text.js +234 -0
- package/packages/piagent-core/subagents/piagent-worker.md +1 -1
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
- package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +93 -7
- package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
- package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
- package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
- package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
- package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
- package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -124
- package/packages/piagent-webui/server/loopback-server.ts +8 -0
- package/packages/piagent-webui/server/transcript-projection.ts +22 -4
- package/packages/piagent-webui/shared/text-visibility.ts +10 -0
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
- package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
- package/schemas/task-contract.schema.json +12 -1
- package/scripts/benchmark-runner-core.mjs +355 -359
- package/scripts/benchmark-runner-finalization.mjs +309 -0
- package/scripts/benchmark-runner-host-readiness.mjs +94 -0
- package/scripts/benchmark-runner-support.mjs +200 -0
- package/scripts/benchmark-session.mjs +95 -41
- package/scripts/configure-model-scope.sh +4 -3
- package/scripts/helper-evaluation.mjs +1 -1
- package/scripts/install-global.sh +2 -2
- package/scripts/pi-usage-history.mjs +37 -10
- package/scripts/setup.sh +2 -2
- package/scripts/verify-local.sh +5 -1
- package/templates/global/settings.json +2 -1
- 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("
|
|
113
|
-
const snapshot = { cursor:
|
|
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:
|
|
116
|
-
|
|
117
|
-
{ cursor:
|
|
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,
|
|
122
|
-
assert.deepEqual(result.appliedCursors, [
|
|
123
|
-
assert.deepEqual(result.
|
|
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-
|
|
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.
|
|
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,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
|
+
});
|
|
@@ -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
|
|
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.
|