@piagent/platform 1.5.1 → 1.5.3

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 (125) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/README.md +8 -2
  3. package/README.vi.md +23 -1
  4. package/benchmarks/capability-v1/DESIGN.md +30 -0
  5. package/benchmarks/capability-v1/coverage.json +23 -1
  6. package/benchmarks/capability-v1/grade.mjs +246 -0
  7. package/benchmarks/capability-v1/project/apps/web/src/control-view.js +3 -0
  8. package/benchmarks/capability-v1/project/apps/web/src/timeline-view.js +3 -0
  9. package/benchmarks/capability-v1/project/packages/session/src/admission.js +7 -0
  10. package/benchmarks/capability-v1/project/packages/session/src/runtime-route.js +3 -0
  11. package/benchmarks/capability-v1/project/packages/timeline/src/checkpoint.js +7 -0
  12. package/benchmarks/capability-v1/project/packages/timeline/src/project.js +7 -0
  13. package/benchmarks/capability-v1/project/test/smoke.test.js +19 -1
  14. package/benchmarks/capability-v1/prompts/causal-timeline-recovery.md +47 -0
  15. package/benchmarks/capability-v1/prompts/durable-session-control-plane.md +47 -0
  16. package/benchmarks/capability-v1/references/causal-timeline-recovery/apps/web/src/timeline-view.js +9 -0
  17. package/benchmarks/capability-v1/references/causal-timeline-recovery/packages/timeline/src/checkpoint.js +22 -0
  18. package/benchmarks/capability-v1/references/causal-timeline-recovery/packages/timeline/src/project.js +77 -0
  19. package/benchmarks/capability-v1/references/durable-session-control-plane/apps/web/src/control-view.js +4 -0
  20. package/benchmarks/capability-v1/references/durable-session-control-plane/packages/session/src/admission.js +52 -0
  21. package/benchmarks/capability-v1/references/durable-session-control-plane/packages/session/src/runtime-route.js +27 -0
  22. package/benchmarks/capability-v1/rubric.json +26 -0
  23. package/benchmarks/capability-v1/suite.json +32 -4
  24. package/benchmarks/capability-v1/variant.mjs +11 -0
  25. package/benchmarks/deep-logic-v1/grade.mjs +165 -0
  26. package/benchmarks/deep-logic-v1/project/README.md +3 -0
  27. package/benchmarks/deep-logic-v1/project/package.json +6 -0
  28. package/benchmarks/deep-logic-v1/project/src/config-transaction.js +3 -0
  29. package/benchmarks/deep-logic-v1/project/src/context-pack.js +3 -0
  30. package/benchmarks/deep-logic-v1/project/src/event-reconcile.js +3 -0
  31. package/benchmarks/deep-logic-v1/project/src/policy.js +3 -0
  32. package/benchmarks/deep-logic-v1/project/src/scheduler.js +3 -0
  33. package/benchmarks/deep-logic-v1/project/src/stream.js +3 -0
  34. package/benchmarks/deep-logic-v1/project/test/smoke.test.js +20 -0
  35. package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +13 -0
  36. package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +13 -0
  37. package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +14 -0
  38. package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +13 -0
  39. package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +14 -0
  40. package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +13 -0
  41. package/benchmarks/deep-logic-v1/suite.json +95 -0
  42. package/benchmarks/deep-logic-v1/variant.mjs +28 -0
  43. package/docs/command-reference-vietnamese.md +5 -2
  44. package/docs/distribution-standard.md +7 -7
  45. package/docs/languages.json +2 -2
  46. package/docs/operator-manual-vietnamese.md +7 -7
  47. package/docs/publishing-for-teams.md +7 -0
  48. package/docs/quality-benchmark.md +49 -10
  49. package/docs/release-install-policy.md +6 -6
  50. package/docs/security-threat-model.md +1 -1
  51. package/docs/team-onboarding.md +2 -2
  52. package/docs-site/architecture.html +1 -1
  53. package/docs-site/benchmark.html +1 -1
  54. package/docs-site/commands.html +1 -1
  55. package/docs-site/context-engine.html +1 -1
  56. package/docs-site/en/architecture.html +1 -1
  57. package/docs-site/en/benchmark.html +1 -1
  58. package/docs-site/en/commands.html +1 -1
  59. package/docs-site/en/context-engine.html +1 -1
  60. package/docs-site/en/index.html +1 -1
  61. package/docs-site/en/mcp-approval.html +1 -1
  62. package/docs-site/en/mcp-auth.html +1 -1
  63. package/docs-site/en/mcp-servers.html +1 -1
  64. package/docs-site/en/mcp.html +1 -1
  65. package/docs-site/en/permissions.html +1 -1
  66. package/docs-site/en/profiles.html +1 -1
  67. package/docs-site/en/quickstart.html +1 -1
  68. package/docs-site/en/runtime.html +1 -1
  69. package/docs-site/en/scope.html +1 -1
  70. package/docs-site/en/security.html +1 -1
  71. package/docs-site/en/team.html +1 -1
  72. package/docs-site/en/whats-new.html +1 -1
  73. package/docs-site/en/workflows.html +1 -1
  74. package/docs-site/index.html +1 -1
  75. package/docs-site/mcp-approval.html +1 -1
  76. package/docs-site/mcp-auth.html +1 -1
  77. package/docs-site/mcp-servers.html +1 -1
  78. package/docs-site/mcp.html +1 -1
  79. package/docs-site/permissions.html +1 -1
  80. package/docs-site/profiles.html +1 -1
  81. package/docs-site/quickstart.html +1 -1
  82. package/docs-site/runtime.html +1 -1
  83. package/docs-site/scope.html +1 -1
  84. package/docs-site/security.html +1 -1
  85. package/docs-site/team.html +1 -1
  86. package/docs-site/whats-new.html +1 -1
  87. package/docs-site/workflows.html +1 -1
  88. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +2 -2
  89. package/evals/fixtures/benchmark-assurance-evidence.valid.json +2 -2
  90. package/evals/fixtures/piagent-webui/runtime-command-v1.invalid.json +12 -0
  91. package/evals/fixtures/piagent-webui/runtime-command-v1.valid.json +11 -0
  92. package/evals/fs4-readiness-matrix.v1.json +2 -2
  93. package/evals/fs5-causal-arm.v1.json +1 -1
  94. package/evals/fs5-pilot-protocol.v1.json +1 -1
  95. package/evals/fs5-pilot-protocol.v4.json +1 -1
  96. package/evals/fs5-pilot-protocol.v5.json +1 -1
  97. package/evals/private-holdout-v1/public-exposure.v1.json +3 -3
  98. package/evals/real-task-taxonomy.v1.json +5 -1
  99. package/package.json +3 -1
  100. package/packages/piagent-core/benchmark/benchmark-bootstrap.js +3 -2
  101. package/packages/piagent-core/benchmark/benchmark-cli.js +5 -1
  102. package/packages/piagent-core/benchmark/benchmark-matrix.js +1 -1
  103. package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +1 -0
  104. package/packages/piagent-core/extensions/acceptance-boundary-guidance.js +64 -0
  105. package/packages/piagent-core/extensions/acceptance-contract-semantics.js +3 -2
  106. package/packages/piagent-core/package.json +1 -1
  107. package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -4
  108. package/packages/piagent-core/runtime/registration/task-start-tool.ts +2 -2
  109. package/packages/piagent-core/runtime/workflows/webui-runtime-command.ts +89 -0
  110. package/packages/piagent-core/runtime/workflows/webui-workflow.ts +51 -0
  111. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-Uouhx1QO.js → MarkdownMessage-BnvXTpou.js} +1 -1
  112. package/packages/piagent-webui/dist/client/assets/index-DZg979Ht.js +174 -0
  113. package/packages/piagent-webui/dist/client/index.html +1 -1
  114. package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
  115. package/packages/piagent-webui/gateway/runtime-command-controller.ts +126 -0
  116. package/packages/piagent-webui/gateway/runtime-session-controls.ts +29 -0
  117. package/packages/piagent-webui/gateway/session-command-controller.ts +23 -7
  118. package/packages/piagent-webui/gateway/session-inspection-registry.ts +22 -1
  119. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +11 -9
  120. package/packages/piagent-webui/package.json +2 -1
  121. package/packages/piagent-webui/server/loopback-server.ts +17 -0
  122. package/schemas/piagent-webui/catalog-v1.json +9 -0
  123. package/schemas/piagent-webui/runtime-command-v1.schema.json +70 -0
  124. package/schemas/piagent-webui/session-command-v1.schema.json +8 -0
  125. package/packages/piagent-webui/dist/client/assets/index-MBZq8XLV.js +0 -174
@@ -0,0 +1,22 @@
1
+ import crypto from "node:crypto";
2
+
3
+ import { normalizeTimelineSnapshot } from "./project.js";
4
+
5
+ function canonical(value) {
6
+ if (value === null || typeof value === "string" || typeof value === "boolean" || Number.isFinite(value)) return JSON.stringify(value);
7
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
8
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
9
+ }
10
+ function checksum(payload) { return crypto.createHash("sha256").update(canonical(payload)).digest("hex"); }
11
+
12
+ export function encodeTimelineCheckpoint(state) {
13
+ const payload = normalizeTimelineSnapshot(state);
14
+ return JSON.stringify({ schemaVersion: 1, payload, checksum: checksum(payload) });
15
+ }
16
+
17
+ export function decodeTimelineCheckpoint(serialized) {
18
+ if (typeof serialized !== "string") throw new TypeError("checkpoint must be a string");
19
+ let value; try { value = JSON.parse(serialized); } catch { throw new TypeError("checkpoint must be JSON"); }
20
+ if (!value || value.schemaVersion !== 1 || !value.payload || value.checksum !== checksum(value.payload)) throw new TypeError("checkpoint integrity failure");
21
+ return normalizeTimelineSnapshot(value.payload);
22
+ }
@@ -0,0 +1,77 @@
1
+ import crypto from "node:crypto";
2
+
3
+ function record(value, label) {
4
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${label} must be an object`);
5
+ return value;
6
+ }
7
+ function text(value, label) { if (typeof value !== "string" || !value.trim()) throw new TypeError(`${label} must be non-empty`); return value; }
8
+ function integer(value, minimum, label) { if (!Number.isSafeInteger(value) || value < minimum) throw new TypeError(`${label} is invalid`); return value; }
9
+ function eventDigest(event) {
10
+ return crypto.createHash("sha256").update(JSON.stringify({ id: event.id, cursor: event.cursor, messageId: event.messageId, offset: event.offset, text: event.text, complete: event.complete })).digest("hex");
11
+ }
12
+
13
+ export function normalizeTimelineSnapshot(snapshot) {
14
+ record(snapshot, "snapshot"); integer(snapshot.cursor, 0, "snapshot cursor");
15
+ if (!Array.isArray(snapshot.messages)) throw new TypeError("messages must be an array");
16
+ const ids = new Set();
17
+ const messages = snapshot.messages.map((message) => {
18
+ record(message, "message"); text(message.id, "message id");
19
+ if (ids.has(message.id) || typeof message.text !== "string" || typeof message.complete !== "boolean") throw new TypeError("invalid message");
20
+ ids.add(message.id); return { id: message.id, text: message.text, complete: message.complete };
21
+ });
22
+ record(snapshot.seen, "seen");
23
+ const seen = {};
24
+ for (const [id, digest] of Object.entries(snapshot.seen)) {
25
+ text(id, "seen id");
26
+ if (!/^[a-f0-9]{64}$/.test(digest)) throw new TypeError("invalid seen digest");
27
+ seen[id] = digest;
28
+ }
29
+ return { cursor: snapshot.cursor, messages, seen };
30
+ }
31
+
32
+ export function projectTimeline(snapshot, events, options = {}) {
33
+ const base = normalizeTimelineSnapshot(snapshot);
34
+ if (!Array.isArray(events) || !options || typeof options !== "object" || Array.isArray(options)) throw new TypeError("invalid projection input");
35
+ const maxChars = options.maxChars;
36
+ integer(maxChars, 0, "maxChars");
37
+ const uniqueById = new Map(); const digestByCursor = new Map(); const replayEvidence = new Set();
38
+ for (const event of events) {
39
+ record(event, "event"); text(event.id, "event id"); text(event.messageId, "message id");
40
+ integer(event.cursor, 1, "event cursor"); integer(event.offset, 0, "event offset");
41
+ if (typeof event.text !== "string" || typeof event.complete !== "boolean") throw new TypeError("invalid event payload");
42
+ const digest = eventDigest(event);
43
+ if (uniqueById.has(event.id)) {
44
+ if (uniqueById.get(event.id).digest !== digest) throw new TypeError("event id conflict");
45
+ replayEvidence.add(event.id); continue;
46
+ }
47
+ if (digestByCursor.has(event.cursor) && digestByCursor.get(event.cursor) !== digest) throw new TypeError("cursor conflict");
48
+ digestByCursor.set(event.cursor, digest); uniqueById.set(event.id, { event: { ...event }, digest });
49
+ }
50
+ const future = [];
51
+ for (const { event, digest } of uniqueById.values()) {
52
+ if (event.cursor <= base.cursor) {
53
+ if (base.seen[event.id] !== digest) throw new TypeError("unknown or conflicting historical event");
54
+ replayEvidence.add(event.id);
55
+ } else future.push({ event, digest });
56
+ }
57
+ future.sort((left, right) => left.event.cursor - right.event.cursor);
58
+ const messages = base.messages.map((item) => ({ ...item })); const byId = new Map(messages.map((item) => [item.id, item]));
59
+ const seen = { ...base.seen }; const appliedIds = []; let cursor = base.cursor;
60
+ let totalChars = messages.reduce((sum, item) => sum + item.text.length, 0); let gap = null; let backpressure = null; let stopIndex = future.length;
61
+ for (let index = 0; index < future.length; index += 1) {
62
+ const { event, digest } = future[index]; const expected = cursor + 1;
63
+ if (event.cursor !== expected) { gap = { expected, observed: event.cursor }; stopIndex = index; break; }
64
+ let message = byId.get(event.messageId);
65
+ const isNewMessage = !message;
66
+ if (!message) message = { id: event.messageId, text: "", complete: false };
67
+ if (message.complete || event.offset !== message.text.length) throw new TypeError("invalid message continuation");
68
+ const neededChars = totalChars + event.text.length;
69
+ if (neededChars > maxChars) { backpressure = { eventId: event.id, cursor: event.cursor, neededChars, maxChars }; stopIndex = index; break; }
70
+ if (isNewMessage) { messages.push(message); byId.set(event.messageId, message); }
71
+ message.text += event.text; message.complete = event.complete; totalChars = neededChars; cursor = event.cursor; seen[event.id] = digest; appliedIds.push(event.id);
72
+ }
73
+ return {
74
+ cursor, messages, seen, appliedIds, replayedIds: [...uniqueById.keys()].filter((id) => replayEvidence.has(id)),
75
+ buffered: future.slice(stopIndex).map(({ event }) => ({ ...event })), gap, backpressure
76
+ };
77
+ }
@@ -0,0 +1,4 @@
1
+ export function controlSummary(receipt) {
2
+ if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) throw new TypeError("receipt must be an object");
3
+ return `kind=${receipt.kind}; command=${JSON.stringify(receipt.terminalCommand)}; revision=${receipt.revisionBefore}->${receipt.revisionAfter}; effect=${receipt.effect}; model=${receipt.expectedModelCalls}; replayed=${receipt.replayed}`;
4
+ }
@@ -0,0 +1,52 @@
1
+ import crypto from "node:crypto";
2
+
3
+ import { routeRuntimeCommand } from "./runtime-route.js";
4
+
5
+ function record(value, label) {
6
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${label} must be an object`);
7
+ return value;
8
+ }
9
+
10
+ function canonical(value) {
11
+ if (value === null || typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
12
+ if (Number.isFinite(value)) return JSON.stringify(value);
13
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
14
+ if (!value || typeof value !== "object") throw new TypeError("command payload is not canonical JSON");
15
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
16
+ }
17
+
18
+ export function runtimeCommandDigest(input) {
19
+ record(input, "digest input");
20
+ if (Object.keys(input).length !== 2 || !Object.hasOwn(input, "kind") || !Object.hasOwn(input, "payload")) throw new TypeError("digest input must contain exactly kind and payload");
21
+ return crypto.createHash("sha256").update(canonical({ kind: input.kind, payload: input.payload })).digest("hex");
22
+ }
23
+
24
+ export function admitRuntimeCommand(state, input) {
25
+ record(state, "state"); record(input, "command");
26
+ if (!Number.isSafeInteger(state.revision) || state.revision < 0 || !record(state.receipts, "receipts")) throw new TypeError("invalid session state");
27
+ if (typeof input.idempotencyKey !== "string" || !input.idempotencyKey.trim() || ["__proto__", "prototype", "constructor"].includes(input.idempotencyKey)) throw new TypeError("invalid idempotency key");
28
+ const route = routeRuntimeCommand(input);
29
+ const commandDigest = runtimeCommandDigest({ kind: input.kind, payload: input.payload });
30
+ const prior = Object.hasOwn(state.receipts, input.idempotencyKey) ? state.receipts[input.idempotencyKey] : undefined;
31
+ if (prior) {
32
+ if (prior.commandDigest !== commandDigest) throw new TypeError("idempotency key conflict");
33
+ return { state, receipt: { ...prior, replayed: true } };
34
+ }
35
+ if (!Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0 || input.expectedRevision !== state.revision) throw new TypeError("stale or invalid revision");
36
+ if (route.confirmationRequired && input.confirmed !== true) throw new TypeError("explicit confirmation required");
37
+ const revisionAfter = state.revision + 1;
38
+ if (!Number.isSafeInteger(revisionAfter)) throw new TypeError("revision overflow");
39
+ const stored = {
40
+ idempotencyKey: input.idempotencyKey,
41
+ commandDigest,
42
+ kind: input.kind,
43
+ terminalCommand: route.terminalCommand,
44
+ effect: route.effect,
45
+ expectedModelCalls: route.expectedModelCalls,
46
+ revisionBefore: state.revision,
47
+ revisionAfter,
48
+ replayed: false
49
+ };
50
+ const next = { revision: revisionAfter, receipts: { ...state.receipts, [input.idempotencyKey]: stored } };
51
+ return { state: next, receipt: { ...stored } };
52
+ }
@@ -0,0 +1,27 @@
1
+ const KINDS = new Set(["status", "scout", "compact", "abort"]);
2
+
3
+ function record(value, label) {
4
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${label} must be an object`);
5
+ return value;
6
+ }
7
+
8
+ function exactKeys(value, allowed, label) {
9
+ if (Object.keys(value).some((key) => !allowed.includes(key))) throw new TypeError(`${label} has unsupported fields`);
10
+ }
11
+
12
+ export function routeRuntimeCommand(input) {
13
+ record(input, "command");
14
+ exactKeys(input, ["idempotencyKey", "expectedRevision", "kind", "payload", "confirmed"], "command");
15
+ if (!KINDS.has(input.kind)) throw new TypeError("unknown runtime command kind");
16
+ const payload = record(input.payload, "payload");
17
+ if (input.kind === "scout") {
18
+ exactKeys(payload, ["objective"], "scout payload");
19
+ if (typeof payload.objective !== "string" || !payload.objective.trim()) throw new TypeError("scout objective must be non-empty");
20
+ const objective = payload.objective.trim().replace(/\s+/gu, " ");
21
+ return { terminalCommand: `/scout ${objective}`, confirmationRequired: true, expectedModelCalls: "bounded", effect: "model" };
22
+ }
23
+ exactKeys(payload, [], `${input.kind} payload`);
24
+ if (input.kind === "status") return { terminalCommand: "/status", confirmationRequired: false, expectedModelCalls: 0, effect: "read" };
25
+ if (input.kind === "compact") return { terminalCommand: "/compact", confirmationRequired: true, expectedModelCalls: "bounded", effect: "semantic" };
26
+ return { terminalCommand: "/abort", confirmationRequired: true, expectedModelCalls: 0, effect: "state" };
27
+ }
@@ -55,6 +55,32 @@
55
55
  { "id": "migration-skip-write-return", "clause": "M3", "critical": true, "weight": 2 },
56
56
  { "id": "migration-crash-resume", "clause": "M4", "critical": true, "weight": 2 },
57
57
  { "id": "migration-return-isolation", "clause": "M4", "critical": true, "weight": 2 }
58
+ ],
59
+ "durable-session-control-plane": [
60
+ { "id": "runtime-route-validation", "clause": "D1", "critical": true, "weight": 2 },
61
+ { "id": "runtime-route-exact-contract", "clause": "D2", "critical": true, "weight": 2 },
62
+ { "id": "runtime-confirmation-authority", "clause": "D2", "critical": true, "weight": 2 },
63
+ { "id": "runtime-revision-validation", "clause": "D3", "critical": true, "weight": 2 },
64
+ { "id": "runtime-idempotent-replay", "clause": "D3", "critical": true, "weight": 2 },
65
+ { "id": "runtime-idempotency-conflict", "clause": "D3", "critical": true, "weight": 2 },
66
+ { "id": "runtime-digest-canonical", "clause": "D4", "critical": true, "weight": 2 },
67
+ { "id": "runtime-receipt-shape", "clause": "D5", "critical": true, "weight": 2 },
68
+ { "id": "runtime-state-isolation", "clause": "D5", "critical": true, "weight": 2 },
69
+ { "id": "runtime-control-summary", "clause": "D6", "critical": true, "weight": 2 }
70
+ ],
71
+ "causal-timeline-recovery": [
72
+ { "id": "timeline-snapshot-validation", "clause": "C1", "critical": true, "weight": 2 },
73
+ { "id": "timeline-event-validation", "clause": "C1", "critical": true, "weight": 2 },
74
+ { "id": "timeline-replay-and-conflicts", "clause": "C2", "critical": true, "weight": 2 },
75
+ { "id": "timeline-historical-validation", "clause": "C2", "critical": true, "weight": 2 },
76
+ { "id": "timeline-contiguous-gap", "clause": "C3", "critical": true, "weight": 2 },
77
+ { "id": "timeline-offset-completion", "clause": "C3", "critical": true, "weight": 2 },
78
+ { "id": "timeline-budget-boundary", "clause": "C4", "critical": true, "weight": 2 },
79
+ { "id": "timeline-input-isolation", "clause": "C4", "critical": true, "weight": 2 },
80
+ { "id": "timeline-checkpoint-roundtrip", "clause": "C5", "critical": true, "weight": 2 },
81
+ { "id": "timeline-checkpoint-tamper", "clause": "C5", "critical": true, "weight": 2 },
82
+ { "id": "timeline-render-exact", "clause": "C6", "critical": true, "weight": 2 },
83
+ { "id": "timeline-render-validation", "clause": "C6", "critical": true, "weight": 2 }
58
84
  ]
59
85
  }
60
86
  }
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "id": "capability-v1",
4
- "title": "Piagent Public Capability V1 · Calibrated Contract Revision",
5
- "description": "Unsaturated multi-file capability tasks with explicit atomic weighted rubrics for diagnosis and hill-climbing; not a release or generalization gate. Historical runs remain identified by their pinned suite digest.",
4
+ "title": "Piagent Public Capability V1 · Frontier Control-Plane Revision",
5
+ "description": "Six unsaturated multi-file capability tasks with issue-style repository work, run-private variants, comprehensive execution tests, and explicit atomic weighted rubrics; not a release or generalization gate. Historical runs remain identified by their pinned suite digest.",
6
6
  "profile": "node-typescript",
7
7
  "defaultRepeats": 3,
8
8
  "timeoutSeconds": 1200,
9
9
  "assurance": {
10
- "taskSource": "maintainer-authored-multi-component-capability-archetypes",
10
+ "taskSource": "maintainer-authored-multi-component-capability-archetypes-informed-by-swe-terminal-paper-re-tau-bench",
11
11
  "visibility": "public-suite-with-run-private-oracles",
12
12
  "claimTier": "capability",
13
13
  "generatedVariants": true,
14
14
  "familyDisjointSplit": false,
15
15
  "reviewed": true,
16
- "refreshedAt": "2026-08-09"
16
+ "refreshedAt": "2026-08-20"
17
17
  },
18
18
  "releaseGate": {
19
19
  "minimumQualityScore": 8,
@@ -83,6 +83,34 @@
83
83
  "grader": "grade.mjs",
84
84
  "variantGenerator": "variant.mjs",
85
85
  "allowedChanges": ["packages/migration/src/**", "test/**"]
86
+ },
87
+ {
88
+ "id": "durable-session-control-plane",
89
+ "title": "Durable WebUI and Terminal session control plane",
90
+ "kind": "source-change",
91
+ "category": "operator-runtime",
92
+ "difficulty": "large",
93
+ "profile": "fullstack",
94
+ "lifecycle": "cold-start",
95
+ "fixture": "project",
96
+ "prompt": "prompts/durable-session-control-plane.md",
97
+ "grader": "grade.mjs",
98
+ "variantGenerator": "variant.mjs",
99
+ "allowedChanges": ["packages/session/src/**", "apps/web/src/control-view.js", "test/**"]
100
+ },
101
+ {
102
+ "id": "causal-timeline-recovery",
103
+ "title": "Causal reconnect timeline recovery and checkpoint integrity",
104
+ "kind": "source-change",
105
+ "category": "streaming",
106
+ "difficulty": "large",
107
+ "profile": "fullstack",
108
+ "lifecycle": "steady-state",
109
+ "fixture": "project",
110
+ "prompt": "prompts/causal-timeline-recovery.md",
111
+ "grader": "grade.mjs",
112
+ "variantGenerator": "variant.mjs",
113
+ "allowedChanges": ["packages/timeline/src/**", "apps/web/src/timeline-view.js", "test/**"]
86
114
  }
87
115
  ]
88
116
  }
@@ -43,6 +43,17 @@ if (scenarioId === "multi-package-rollout") {
43
43
  oracle.graderData = {
44
44
  ids: [token("prepare"), token("transform"), token("finalize")]
45
45
  };
46
+ } else if (scenarioId === "durable-session-control-plane") {
47
+ oracle.graderData = {
48
+ idempotencyKey: token("command"),
49
+ objective: `${token("inspect")} production incident`
50
+ };
51
+ } else if (scenarioId === "causal-timeline-recovery") {
52
+ oracle.graderData = {
53
+ messageA: token("message"), messageB: token("message"),
54
+ eventA: token("event"), eventB: token("event"), eventC: token("event"),
55
+ first: token("first"), second: token("second"), maxChars: integer(24, 48)
56
+ };
46
57
  } else throw new Error(`unsupported capability scenario ${scenarioId}`);
47
58
 
48
59
  fs.writeFileSync(oraclePath, `${JSON.stringify(oracle)}\n`, { mode: 0o600 });
@@ -0,0 +1,165 @@
1
+ import assert from "node:assert/strict";
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ const workspace = process.argv[2];
8
+ const oracle = JSON.parse(fs.readFileSync(process.argv[3], "utf8"));
9
+ const scenario = process.env.PIAGENT_BENCHMARK_SCENARIO;
10
+ const data = oracle.graderData;
11
+ const checks = [];
12
+ async function check(id, action) { try { await action(); checks.push({ id, passed: true }); } catch { checks.push({ id, passed: false }); } }
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
+
15
+ switch (scenario) {
16
+ case "revision-event-reconciliation": {
17
+ const { reconcileSession } = await load("src/event-reconcile.js");
18
+ await check("contiguous-revision-application", () => {
19
+ const snapshot = { revision: data.revisions[0], nextSequence: data.start, state: { keep: true, remove: "old" } };
20
+ const first = { id: "a", sequence: data.start, baseRevision: data.revisions[0], revision: data.revisions[1], patch: { [data.key]: 1, remove: null } };
21
+ const second = { id: "b", sequence: data.start + 1, baseRevision: data.revisions[1], revision: data.revisions[2], patch: { [data.key]: 2 } };
22
+ const inputs = structuredClone([snapshot, [second, first, structuredClone(first)]]);
23
+ const result = reconcileSession(snapshot, [second, first, structuredClone(first)]);
24
+ assert.deepEqual(result, { revision: data.revisions[2], nextSequence: data.start + 2, state: { keep: true, [data.key]: 2 },
25
+ appliedIds: ["a", "b"], duplicateIds: ["a"], pending: [], gap: null });
26
+ assert.deepEqual([snapshot, [second, first, structuredClone(first)]], inputs);
27
+ });
28
+ await check("gap-and-conflict-fail-closed", () => {
29
+ const snapshot = { revision: "r0", nextSequence: 4, state: {} };
30
+ const late = { id: "late", sequence: 6, baseRevision: "r1", revision: "r2", patch: {} };
31
+ assert.deepEqual(reconcileSession(snapshot, [late]).gap, { expected: 4, observed: 6 });
32
+ assert.throws(() => reconcileSession(snapshot, [
33
+ { id: "x", sequence: 4, baseRevision: "r0", revision: "r1", patch: {} },
34
+ { id: "y", sequence: 4, baseRevision: "r0", revision: "r2", patch: {} }
35
+ ]));
36
+ assert.throws(() => reconcileSession(snapshot, [{ id: "x", sequence: 4, baseRevision: "wrong", revision: "r1", patch: {} }]));
37
+ assert.throws(() => reconcileSession(snapshot, [{ id: "x", sequence: 4, baseRevision: "r0", revision: "r1", patch: JSON.parse('{"__proto__":1}') }]));
38
+ });
39
+ break;
40
+ }
41
+ case "fair-dependency-scheduler": {
42
+ const { scheduleJobs } = await load("src/scheduler.js");
43
+ await check("dependency-capacity-fairness", () => {
44
+ const [a, b, c] = data.tenants, cap = data.capacity;
45
+ const jobs = [
46
+ { id: "a1", tenant: a, weight: 1, dependsOn: [] }, { id: "a2", tenant: a, weight: 1, dependsOn: [] },
47
+ { id: "b1", tenant: b, weight: 1, dependsOn: [] }, { id: "c1", tenant: c, weight: 1, dependsOn: [] },
48
+ { id: "after", tenant: a, weight: Math.min(2, cap), dependsOn: ["b1", "c1"] }
49
+ ];
50
+ const before = structuredClone(jobs), waves = scheduleJobs(jobs, cap);
51
+ assert.deepEqual(jobs, before);
52
+ assert.equal(new Set(waves.flat()).size, jobs.length);
53
+ assert.deepEqual(new Set(waves[0].slice(0, 3)), new Set(["a1", "b1", "c1"]));
54
+ const waveOf = new Map(waves.flatMap((wave, index) => wave.map((id) => [id, index])));
55
+ assert.ok(waveOf.get("after") > waveOf.get("b1") && waveOf.get("after") > waveOf.get("c1"));
56
+ for (const wave of waves) assert.ok(wave.reduce((sum, id) => sum + jobs.find((job) => job.id === id).weight, 0) <= cap);
57
+ });
58
+ await check("invalid-graph-rejected", () => {
59
+ assert.throws(() => scheduleJobs([{ id: "a", tenant: "t", weight: 1, dependsOn: ["b"] }], 2));
60
+ assert.throws(() => scheduleJobs([{ id: "a", tenant: "t", weight: 1, dependsOn: ["b"] }, { id: "b", tenant: "t", weight: 1, dependsOn: ["a"] }], 2));
61
+ assert.throws(() => scheduleJobs([{ id: "a", tenant: "t", weight: 3, dependsOn: [] }], 2));
62
+ });
63
+ break;
64
+ }
65
+ case "layered-policy-resolution": {
66
+ const { decideAccess } = await load("src/policy.js");
67
+ await check("precedence-and-protected-deny", () => {
68
+ const layers = [
69
+ { name: "global", rules: [{ pattern: "src/**", operations: [data.operation], effect: "allow" },
70
+ { pattern: "src/**", operations: [data.operation], effect: "deny" },
71
+ { pattern: "private/**", operations: ["*"], effect: "deny", protected: true }] },
72
+ { name: "project", rules: [{ pattern: "src/**", operations: [data.operation], effect: "allow" }] }
73
+ ];
74
+ assert.equal(decideAccess({ path: "src/a/config.json", operation: data.operation }, layers).allowed, true);
75
+ const denied = decideAccess({ path: data.protectedPath, operation: data.operation }, layers);
76
+ assert.equal(denied.allowed, false); assert.equal(denied.protected, true); assert.equal(denied.layer, "global");
77
+ assert.equal(decideAccess({ path: "docs/readme.md", operation: data.operation }, layers).allowed, false);
78
+ });
79
+ await check("path-and-rule-validation", () => {
80
+ const allow = [{ name: "x", rules: [{ pattern: "**", operations: ["*"], effect: "allow" }] }];
81
+ for (const unsafe of ["../secret", "/root", "a\\b", "a//b", "./a", "a/../b"]) assert.throws(() => decideAccess({ path: unsafe, operation: "read" }, allow));
82
+ assert.throws(() => decideAccess({ path: "a", operation: "read" }, [{ name: "x", rules: [{ pattern: "[a]", operations: ["read"], effect: "allow" }] }]));
83
+ });
84
+ break;
85
+ }
86
+ case "budgeted-context-graph": {
87
+ const { buildContextPack } = await load("src/context-pack.js");
88
+ await check("ranking-propagation-budget", () => {
89
+ const nodes = [
90
+ { id: "guide", path: "docs/guide.md", text: `${data.term} architecture`, tokens: 3 },
91
+ { id: "impl", path: "src/impl.js", text: "implementation details", tokens: 4 },
92
+ { id: "required", path: "AGENTS.md", text: "rules", tokens: 2, required: true },
93
+ { id: "large", path: "src/large.js", text: data.term, tokens: data.budget + 1 }
94
+ ];
95
+ const before = structuredClone(nodes), result = buildContextPack(data.term, nodes, [{ from: "guide", to: "impl", weight: 1 }], data.budget);
96
+ assert.deepEqual(nodes, before);
97
+ assert.deepEqual(result.selected.map((item) => item.id).slice(0, 3), ["required", "guide", "impl"]);
98
+ assert.ok(result.usedTokens <= data.budget); assert.equal(result.confidence, "high");
99
+ assert.equal(result.selected.find((item) => item.id === "impl").score, 0.5);
100
+ assert.ok(result.omitted.some((item) => item.id === "large"));
101
+ });
102
+ await check("graph-validation-and-required-budget", () => {
103
+ assert.throws(() => buildContextPack("x", [{ id: "a", path: "a", text: "x", tokens: 5, required: true }], [], 4));
104
+ assert.throws(() => buildContextPack("x", [{ id: "a", path: "a", text: "x", tokens: 1 }], [{ from: "a", to: "missing", weight: 1 }], 4));
105
+ const result = buildContextPack("Đặng", [{ id: "a", path: "a", text: "đặng", tokens: 1 }], [], 1);
106
+ assert.equal(result.selected[0].directScore, 1);
107
+ });
108
+ break;
109
+ }
110
+ case "resumable-stream-assembly": {
111
+ 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 }] };
114
+ 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 }
118
+ ];
119
+ const before = structuredClone([snapshot, events]), result = assembleStream(snapshot, events);
120
+ 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);
124
+ });
125
+ await check("offset-completion-and-cursor-conflicts", () => {
126
+ const base = { cursor: 0, messages: [] };
127
+ assert.throws(() => assembleStream(base, [{ cursor: 1, messageId: "m", offset: 1, text: "x", complete: false }]));
128
+ assert.throws(() => assembleStream(base, [
129
+ { cursor: 1, messageId: "m", offset: 0, text: "x", complete: false },
130
+ { cursor: 1, messageId: "m", offset: 0, text: "y", complete: false }
131
+ ]));
132
+ assert.throws(() => assembleStream({ cursor: 0, messages: [{ id: "m", text: "x", complete: true }] },
133
+ [{ cursor: 1, messageId: "m", offset: 1, text: "", complete: true }]));
134
+ });
135
+ break;
136
+ }
137
+ case "transactional-config-merge": {
138
+ const { planConfigTransaction } = await load("src/config-transaction.js");
139
+ await check("merge-delete-default-audit-digest", () => {
140
+ const base = { service: { port: 3000, stale: true }, remove: "x" };
141
+ const layers = [{ service: { port: 0, [data.key]: data.value }, remove: { $delete: true } }];
142
+ const schema = { service: { type: "object", required: true }, remove: { type: "string" }, enabled: { type: "boolean", default: false } };
143
+ const before = structuredClone([base, layers, schema]), result = planConfigTransaction(base, layers, schema);
144
+ assert.deepEqual([base, layers, schema], before);
145
+ assert.deepEqual(result.next, { service: { port: 0, stale: true, [data.key]: data.value }, enabled: false });
146
+ assert.deepEqual(result.changes.map((item) => item.path), [...result.changes.map((item) => item.path)].sort());
147
+ assert.ok(result.changes.some((item) => item.path === "/remove" && item.kind === "delete"));
148
+ assert.ok(result.changes.some((item) => item.path === "/enabled" && item.kind === "add"));
149
+ assert.match(result.digest, /^[a-f0-9]{64}$/);
150
+ const canonical = JSON.stringify({ enabled: false, service: { [data.key]: data.value, port: 0, stale: true } });
151
+ assert.equal(result.digest, crypto.createHash("sha256").update(canonical).digest("hex"));
152
+ });
153
+ await check("schema-and-pollution-fail-closed", () => {
154
+ assert.throws(() => planConfigTransaction({}, [{ unknown: true }], { known: { type: "boolean" } }));
155
+ assert.throws(() => planConfigTransaction({}, [{}], { required: { type: "string", required: true } }));
156
+ assert.throws(() => planConfigTransaction({}, [{ x: { $delete: false } }], { x: { type: "object" } }));
157
+ assert.throws(() => planConfigTransaction(JSON.parse('{"__proto__":1}'), [], {}));
158
+ });
159
+ break;
160
+ }
161
+ default: throw new Error(`unsupported deep logic scenario ${scenario}`);
162
+ }
163
+
164
+ const passed = checks.length > 0 && checks.every((item) => item.passed);
165
+ process.stdout.write(`${JSON.stringify({ passed, score: passed ? 10 : 0, checks })}\n`);
@@ -0,0 +1,3 @@
1
+ # Deep Logic Service
2
+
3
+ Small dependency-free JavaScript modules used to exercise interacting invariants. Run `npm test` for the public smoke check; the benchmark uses additional run-private cases.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "piagent-deep-logic-fixture",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": { "test": "node --test test/*.test.js" }
6
+ }
@@ -0,0 +1,3 @@
1
+ export function planConfigTransaction(_base, _layers, _schema) {
2
+ throw new Error("not implemented");
3
+ }
@@ -0,0 +1,3 @@
1
+ export function buildContextPack(_query, _nodes, _edges, _budgetTokens) {
2
+ throw new Error("not implemented");
3
+ }
@@ -0,0 +1,3 @@
1
+ export function reconcileSession(_snapshot, _events) {
2
+ throw new Error("not implemented");
3
+ }
@@ -0,0 +1,3 @@
1
+ export function decideAccess(_request, _layers) {
2
+ throw new Error("not implemented");
3
+ }
@@ -0,0 +1,3 @@
1
+ export function scheduleJobs(_jobs, _capacity) {
2
+ throw new Error("not implemented");
3
+ }
@@ -0,0 +1,3 @@
1
+ export function assembleStream(_snapshot, _events) {
2
+ throw new Error("not implemented");
3
+ }
@@ -0,0 +1,20 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import { planConfigTransaction } from "../src/config-transaction.js";
5
+ import { buildContextPack } from "../src/context-pack.js";
6
+ import { reconcileSession } from "../src/event-reconcile.js";
7
+ import { decideAccess } from "../src/policy.js";
8
+ import { scheduleJobs } from "../src/scheduler.js";
9
+ import { assembleStream } from "../src/stream.js";
10
+
11
+ test("every benchmark entrypoint loads before private grading", () => {
12
+ for (const entrypoint of [
13
+ planConfigTransaction,
14
+ buildContextPack,
15
+ reconcileSession,
16
+ decideAccess,
17
+ scheduleJobs,
18
+ assembleStream
19
+ ]) assert.equal(typeof entrypoint, "function");
20
+ });
@@ -0,0 +1,13 @@
1
+ Implement `buildContextPack(query, nodes, edges, budgetTokens)` in `src/context-pack.js`.
2
+
3
+ Nodes are `{ id, path, text, tokens, required? }`; directed edges are `{ from, to, weight }` with weight in `(0, 1]`.
4
+
5
+ Required behavior:
6
+
7
+ - Validate the graph, unique IDs, finite positive integer token costs, known endpoints, a positive integer budget, and plain inputs. Do not mutate inputs.
8
+ - Tokenize the query and node text case-insensitively on Unicode non-letter/non-number boundaries. A node's direct score is the count of distinct query terms it contains divided by the number of distinct query terms.
9
+ - Add one-hop propagation: for every directly scoring source, add `sourceDirectScore * edge.weight * 0.5` to its outgoing target. Cap final score at 1.
10
+ - All `required` nodes must fit or the function throws. Select them first, then select positive-score nodes by descending final score, descending direct score, ascending tokens, then original node order, skipping candidates that do not fit. Never select a node twice.
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
+
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.
@@ -0,0 +1,13 @@
1
+ Implement `scheduleJobs(jobs, capacity)` in `src/scheduler.js`.
2
+
3
+ Jobs are `{ id, tenant, weight, dependsOn }`. Return an array of waves, each an array of job IDs.
4
+
5
+ Required invariants:
6
+
7
+ - Validate a positive integer capacity, unique non-empty IDs, non-empty tenants, integer weights from 1 through capacity, existing dependencies, no self-dependency, and an acyclic graph.
8
+ - A job may run only in a wave after every dependency completed in an earlier wave. A wave's total weight cannot exceed capacity.
9
+ - Selection is deterministic and stable relative to input order within each selection pass. Order inside a returned wave records selection order: first include the fair-pass choice for every fitting tenant, then append stable fill-pass choices.
10
+ - Tenant fairness: while another ready tenant with a job that fits is not represented in the current wave, do not give a represented tenant a second slot. After each ready tenant has had a fair opportunity, fill remaining capacity stably.
11
+ - Never mutate the input. Every job appears exactly once, or the function throws.
12
+
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.
@@ -0,0 +1,14 @@
1
+ Implement `decideAccess(request, layers)` in `src/policy.js`.
2
+
3
+ `request` is `{ path, operation }`. `layers` is ordered from least to most authoritative; each is `{ name, rules }`. A rule is `{ pattern, operations, effect, protected? }`, where effect is `allow` or `deny`, and operations is a non-empty array of exact operation names or `*`.
4
+
5
+ Semantics:
6
+
7
+ - Accept only POSIX project-relative paths. Invalid requests, layers, rules, operations, and paths must throw before returning a decision. Unsafe paths include absolute paths, empty segments, `.`, `..`, backslashes, NUL, and paths outside the project.
8
+ - Support glob segments `*` (one segment) and `**` (zero or more segments). A literal segment may contain only ASCII letters, digits, `.`, `_`, and `-`; reject embedded `*` and other glob syntax such as `[a]`, `?`, or `{a,b}`.
9
+ - Within one layer, the last matching rule wins. A match in a more authoritative layer wins over a lower layer.
10
+ - A matching `protected: true` deny is permanent and cannot be overridden by any later allow.
11
+ - If nothing matches, deny. Invalid layers/rules fail closed by throwing.
12
+ - Return `{ allowed, effect, layer, ruleIndex, protected, reason }` without exposing or changing inputs.
13
+
14
+ Keep matching deterministic and do not use shell or filesystem glob expansion.
@@ -0,0 +1,13 @@
1
+ Implement `assembleStream(snapshot, events)` in `src/stream.js`.
2
+
3
+ The snapshot is `{ cursor, messages }`; each message is `{ id, text, complete }`. Events are `{ cursor, messageId, offset, text, complete }` and may arrive out of order or be replayed.
4
+
5
+ Rules:
6
+
7
+ - Do not mutate inputs. Validate safe non-negative integer cursors/offsets and well-formed strings.
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
+ - 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.
11
+ - Return `{ cursor, messages, appliedCursors, replayedCursors, buffered, gap }`. Preserve original message order and append newly seen messages in first-applied order.
12
+
13
+ The hidden cases cover reconnect replay, interleaved messages, gaps, empty final chunks, surrogate-pair text length, and conflict rejection.
@@ -0,0 +1,14 @@
1
+ Implement `reconcileSession(snapshot, events)` in `src/event-reconcile.js`.
2
+
3
+ The snapshot is `{ revision, nextSequence, state }`. Each event is `{ id, sequence, baseRevision, revision, patch }`; `patch` is a plain object and a `null` value deletes that top-level key.
4
+
5
+ Requirements:
6
+
7
+ - Do not mutate any input.
8
+ - Accept events in any order. Exact duplicate IDs with identical event content count once; the same ID with different content is an error.
9
+ - Apply only the contiguous sequence beginning at `snapshot.nextSequence`. Stop before the first gap and return all later unique events in `pending`, ordered by sequence then original arrival order.
10
+ - At a sequence, two non-identical events are a conflict and must throw. Before applying an event, its `baseRevision` must exactly equal the current revision; otherwise throw without returning a partial result.
11
+ - Return `{ revision, nextSequence, state, appliedIds, duplicateIds, pending, gap }`. `gap` is `null` or `{ expected, observed }`.
12
+ - Reject malformed objects, unsafe sequence numbers, non-string IDs/revisions, arrays as patches, and prototype-pollution keys (`__proto__`, `prototype`, `constructor`). Fail closed.
13
+
14
+ Add focused tests if useful. Keep the implementation dependency-free and deterministic.