@m4ike1/ion-agent-core 0.1.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/README.md +622 -0
- package/dist/agent-loop.d.ts +24 -0
- package/dist/agent-loop.d.ts.map +1 -0
- package/dist/agent-loop.js +560 -0
- package/dist/agent-loop.js.map +1 -0
- package/dist/agent.d.ts +122 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +422 -0
- package/dist/agent.js.map +1 -0
- package/dist/harness/agent-harness.d.ts +716 -0
- package/dist/harness/agent-harness.d.ts.map +1 -0
- package/dist/harness/agent-harness.js +6 -0
- package/dist/harness/agent-harness.js.map +1 -0
- package/dist/harness/compaction/branch-summarization.d.ts +68 -0
- package/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/harness/compaction/branch-summarization.js +176 -0
- package/dist/harness/compaction/branch-summarization.js.map +1 -0
- package/dist/harness/compaction/compaction.d.ts +126 -0
- package/dist/harness/compaction/compaction.d.ts.map +1 -0
- package/dist/harness/compaction/compaction.js +560 -0
- package/dist/harness/compaction/compaction.js.map +1 -0
- package/dist/harness/compaction/utils.d.ts +25 -0
- package/dist/harness/compaction/utils.d.ts.map +1 -0
- package/dist/harness/compaction/utils.js +120 -0
- package/dist/harness/compaction/utils.js.map +1 -0
- package/dist/harness/config.d.ts +9 -0
- package/dist/harness/config.d.ts.map +1 -0
- package/dist/harness/config.js +35 -0
- package/dist/harness/config.js.map +1 -0
- package/dist/harness/context.d.ts +9 -0
- package/dist/harness/context.d.ts.map +1 -0
- package/dist/harness/context.js +13 -0
- package/dist/harness/context.js.map +1 -0
- package/dist/harness/env/nodejs.d.ts +43 -0
- package/dist/harness/env/nodejs.d.ts.map +1 -0
- package/dist/harness/env/nodejs.js +865 -0
- package/dist/harness/env/nodejs.js.map +1 -0
- package/dist/harness/events.d.ts +25 -0
- package/dist/harness/events.d.ts.map +1 -0
- package/dist/harness/events.js +250 -0
- package/dist/harness/events.js.map +1 -0
- package/dist/harness/execution/assistant.d.ts +42 -0
- package/dist/harness/execution/assistant.d.ts.map +1 -0
- package/dist/harness/execution/assistant.js +82 -0
- package/dist/harness/execution/assistant.js.map +1 -0
- package/dist/harness/execution/effect-gate.d.ts +22 -0
- package/dist/harness/execution/effect-gate.d.ts.map +1 -0
- package/dist/harness/execution/effect-gate.js +49 -0
- package/dist/harness/execution/effect-gate.js.map +1 -0
- package/dist/harness/execution/tools.d.ts +67 -0
- package/dist/harness/execution/tools.d.ts.map +1 -0
- package/dist/harness/execution/tools.js +121 -0
- package/dist/harness/execution/tools.js.map +1 -0
- package/dist/harness/hooks.d.ts +38 -0
- package/dist/harness/hooks.d.ts.map +1 -0
- package/dist/harness/hooks.js +406 -0
- package/dist/harness/hooks.js.map +1 -0
- package/dist/harness/messages.d.ts +51 -0
- package/dist/harness/messages.d.ts.map +1 -0
- package/dist/harness/messages.js +102 -0
- package/dist/harness/messages.js.map +1 -0
- package/dist/harness/prompt-templates.d.ts +49 -0
- package/dist/harness/prompt-templates.d.ts.map +1 -0
- package/dist/harness/prompt-templates.js +225 -0
- package/dist/harness/prompt-templates.js.map +1 -0
- package/dist/harness/result.d.ts +133 -0
- package/dist/harness/result.d.ts.map +1 -0
- package/dist/harness/result.js +81 -0
- package/dist/harness/result.js.map +1 -0
- package/dist/harness/runtime/drive/boundary.d.ts +28 -0
- package/dist/harness/runtime/drive/boundary.d.ts.map +1 -0
- package/dist/harness/runtime/drive/boundary.js +173 -0
- package/dist/harness/runtime/drive/boundary.js.map +1 -0
- package/dist/harness/runtime/drive/checkpoint.d.ts +8 -0
- package/dist/harness/runtime/drive/checkpoint.d.ts.map +1 -0
- package/dist/harness/runtime/drive/checkpoint.js +133 -0
- package/dist/harness/runtime/drive/checkpoint.js.map +1 -0
- package/dist/harness/runtime/drive/deferred.d.ts +15 -0
- package/dist/harness/runtime/drive/deferred.d.ts.map +1 -0
- package/dist/harness/runtime/drive/deferred.js +179 -0
- package/dist/harness/runtime/drive/deferred.js.map +1 -0
- package/dist/harness/runtime/drive/generation.d.ts +8 -0
- package/dist/harness/runtime/drive/generation.d.ts.map +1 -0
- package/dist/harness/runtime/drive/generation.js +205 -0
- package/dist/harness/runtime/drive/generation.js.map +1 -0
- package/dist/harness/runtime/drive/reconcile.d.ts +5 -0
- package/dist/harness/runtime/drive/reconcile.d.ts.map +1 -0
- package/dist/harness/runtime/drive/reconcile.js +140 -0
- package/dist/harness/runtime/drive/reconcile.js.map +1 -0
- package/dist/harness/runtime/drive/recovery.d.ts +8 -0
- package/dist/harness/runtime/drive/recovery.d.ts.map +1 -0
- package/dist/harness/runtime/drive/recovery.js +85 -0
- package/dist/harness/runtime/drive/recovery.js.map +1 -0
- package/dist/harness/runtime/drive/response.d.ts +23 -0
- package/dist/harness/runtime/drive/response.d.ts.map +1 -0
- package/dist/harness/runtime/drive/response.js +394 -0
- package/dist/harness/runtime/drive/response.js.map +1 -0
- package/dist/harness/runtime/drive/retry.d.ts +4 -0
- package/dist/harness/runtime/drive/retry.d.ts.map +1 -0
- package/dist/harness/runtime/drive/retry.js +34 -0
- package/dist/harness/runtime/drive/retry.js.map +1 -0
- package/dist/harness/runtime/drive/structural.d.ts +32 -0
- package/dist/harness/runtime/drive/structural.d.ts.map +1 -0
- package/dist/harness/runtime/drive/structural.js +929 -0
- package/dist/harness/runtime/drive/structural.js.map +1 -0
- package/dist/harness/runtime/drive/terminal.d.ts +7 -0
- package/dist/harness/runtime/drive/terminal.d.ts.map +1 -0
- package/dist/harness/runtime/drive/terminal.js +49 -0
- package/dist/harness/runtime/drive/terminal.js.map +1 -0
- package/dist/harness/runtime/drive/tool-placement.d.ts +14 -0
- package/dist/harness/runtime/drive/tool-placement.d.ts.map +1 -0
- package/dist/harness/runtime/drive/tool-placement.js +244 -0
- package/dist/harness/runtime/drive/tool-placement.js.map +1 -0
- package/dist/harness/runtime/drive/tools.d.ts +6 -0
- package/dist/harness/runtime/drive/tools.d.ts.map +1 -0
- package/dist/harness/runtime/drive/tools.js +449 -0
- package/dist/harness/runtime/drive/tools.js.map +1 -0
- package/dist/harness/runtime/drive.d.ts +6 -0
- package/dist/harness/runtime/drive.d.ts.map +1 -0
- package/dist/harness/runtime/drive.js +93 -0
- package/dist/harness/runtime/drive.js.map +1 -0
- package/dist/harness/runtime/harness.d.ts +59 -0
- package/dist/harness/runtime/harness.d.ts.map +1 -0
- package/dist/harness/runtime/harness.js +311 -0
- package/dist/harness/runtime/harness.js.map +1 -0
- package/dist/harness/runtime/index.d.ts +2 -0
- package/dist/harness/runtime/index.d.ts.map +1 -0
- package/dist/harness/runtime/index.js +2 -0
- package/dist/harness/runtime/index.js.map +1 -0
- package/dist/harness/runtime/lane.d.ts +101 -0
- package/dist/harness/runtime/lane.d.ts.map +1 -0
- package/dist/harness/runtime/lane.js +1575 -0
- package/dist/harness/runtime/lane.js.map +1 -0
- package/dist/harness/runtime/progress.d.ts +15 -0
- package/dist/harness/runtime/progress.d.ts.map +1 -0
- package/dist/harness/runtime/progress.js +66 -0
- package/dist/harness/runtime/progress.js.map +1 -0
- package/dist/harness/runtime/reducer.d.ts +5 -0
- package/dist/harness/runtime/reducer.d.ts.map +1 -0
- package/dist/harness/runtime/reducer.js +240 -0
- package/dist/harness/runtime/reducer.js.map +1 -0
- package/dist/harness/runtime/restore.d.ts +24 -0
- package/dist/harness/runtime/restore.d.ts.map +1 -0
- package/dist/harness/runtime/restore.js +117 -0
- package/dist/harness/runtime/restore.js.map +1 -0
- package/dist/harness/runtime/transcript.d.ts +21 -0
- package/dist/harness/runtime/transcript.d.ts.map +1 -0
- package/dist/harness/runtime/transcript.js +72 -0
- package/dist/harness/runtime/transcript.js.map +1 -0
- package/dist/harness/runtime/types.d.ts +105 -0
- package/dist/harness/runtime/types.d.ts.map +1 -0
- package/dist/harness/runtime/types.js +61 -0
- package/dist/harness/runtime/types.js.map +1 -0
- package/dist/harness/session/commit.d.ts +53 -0
- package/dist/harness/session/commit.d.ts.map +1 -0
- package/dist/harness/session/commit.js +57 -0
- package/dist/harness/session/commit.js.map +1 -0
- package/dist/harness/session/context.d.ts +10 -0
- package/dist/harness/session/context.d.ts.map +1 -0
- package/dist/harness/session/context.js +49 -0
- package/dist/harness/session/context.js.map +1 -0
- package/dist/harness/session/fork-policy.d.ts +21 -0
- package/dist/harness/session/fork-policy.d.ts.map +1 -0
- package/dist/harness/session/fork-policy.js +55 -0
- package/dist/harness/session/fork-policy.js.map +1 -0
- package/dist/harness/session/fork.d.ts +16 -0
- package/dist/harness/session/fork.d.ts.map +1 -0
- package/dist/harness/session/fork.js +79 -0
- package/dist/harness/session/fork.js.map +1 -0
- package/dist/harness/session/in-memory-storage-state.d.ts +39 -0
- package/dist/harness/session/in-memory-storage-state.d.ts.map +1 -0
- package/dist/harness/session/in-memory-storage-state.js +276 -0
- package/dist/harness/session/in-memory-storage-state.js.map +1 -0
- package/dist/harness/session/index.d.ts +11 -0
- package/dist/harness/session/index.d.ts.map +1 -0
- package/dist/harness/session/index.js +9 -0
- package/dist/harness/session/index.js.map +1 -0
- package/dist/harness/session/jsonl/codec.d.ts +21 -0
- package/dist/harness/session/jsonl/codec.d.ts.map +1 -0
- package/dist/harness/session/jsonl/codec.js +45 -0
- package/dist/harness/session/jsonl/codec.js.map +1 -0
- package/dist/harness/session/jsonl/fork.d.ts +38 -0
- package/dist/harness/session/jsonl/fork.d.ts.map +1 -0
- package/dist/harness/session/jsonl/fork.js +264 -0
- package/dist/harness/session/jsonl/fork.js.map +1 -0
- package/dist/harness/session/jsonl/index.d.ts +4 -0
- package/dist/harness/session/jsonl/index.d.ts.map +1 -0
- package/dist/harness/session/jsonl/index.js +4 -0
- package/dist/harness/session/jsonl/index.js.map +1 -0
- package/dist/harness/session/jsonl/io.d.ts +14 -0
- package/dist/harness/session/jsonl/io.d.ts.map +1 -0
- package/dist/harness/session/jsonl/io.js +86 -0
- package/dist/harness/session/jsonl/io.js.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts +43 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.js +462 -0
- package/dist/harness/session/jsonl/legacy-v3.js.map +1 -0
- package/dist/harness/session/jsonl/repo.d.ts +35 -0
- package/dist/harness/session/jsonl/repo.d.ts.map +1 -0
- package/dist/harness/session/jsonl/repo.js +301 -0
- package/dist/harness/session/jsonl/repo.js.map +1 -0
- package/dist/harness/session/jsonl/storage.d.ts +40 -0
- package/dist/harness/session/jsonl/storage.d.ts.map +1 -0
- package/dist/harness/session/jsonl/storage.js +200 -0
- package/dist/harness/session/jsonl/storage.js.map +1 -0
- package/dist/harness/session/jsonl/types.d.ts +39 -0
- package/dist/harness/session/jsonl/types.d.ts.map +1 -0
- package/dist/harness/session/jsonl/types.js +3 -0
- package/dist/harness/session/jsonl/types.js.map +1 -0
- package/dist/harness/session/memory.d.ts +48 -0
- package/dist/harness/session/memory.d.ts.map +1 -0
- package/dist/harness/session/memory.js +369 -0
- package/dist/harness/session/memory.js.map +1 -0
- package/dist/harness/session/mutation-line.d.ts +8 -0
- package/dist/harness/session/mutation-line.d.ts.map +1 -0
- package/dist/harness/session/mutation-line.js +21 -0
- package/dist/harness/session/mutation-line.js.map +1 -0
- package/dist/harness/session/session.d.ts +82 -0
- package/dist/harness/session/session.d.ts.map +1 -0
- package/dist/harness/session/session.js +361 -0
- package/dist/harness/session/session.js.map +1 -0
- package/dist/harness/session/testing/benchmark/datasets.d.ts +12 -0
- package/dist/harness/session/testing/benchmark/datasets.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/datasets.js +24 -0
- package/dist/harness/session/testing/benchmark/datasets.js.map +1 -0
- package/dist/harness/session/testing/benchmark/session-repo.d.ts +42 -0
- package/dist/harness/session/testing/benchmark/session-repo.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/session-repo.js +127 -0
- package/dist/harness/session/testing/benchmark/session-repo.js.map +1 -0
- package/dist/harness/session/testing/benchmark/storage.d.ts +25 -0
- package/dist/harness/session/testing/benchmark/storage.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/storage.js +114 -0
- package/dist/harness/session/testing/benchmark/storage.js.map +1 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts +23 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts.map +1 -0
- package/dist/harness/session/testing/conformance/session-repo.js +809 -0
- package/dist/harness/session/testing/conformance/session-repo.js.map +1 -0
- package/dist/harness/session/testing/conformance/storage.d.ts +4 -0
- package/dist/harness/session/testing/conformance/storage.d.ts.map +1 -0
- package/dist/harness/session/testing/conformance/storage.js +600 -0
- package/dist/harness/session/testing/conformance/storage.js.map +1 -0
- package/dist/harness/session/testing/gating-storage.d.ts +26 -0
- package/dist/harness/session/testing/gating-storage.d.ts.map +1 -0
- package/dist/harness/session/testing/gating-storage.js +100 -0
- package/dist/harness/session/testing/gating-storage.js.map +1 -0
- package/dist/harness/session/testing/index.d.ts +10 -0
- package/dist/harness/session/testing/index.d.ts.map +1 -0
- package/dist/harness/session/testing/index.js +9 -0
- package/dist/harness/session/testing/index.js.map +1 -0
- package/dist/harness/session/testing/instrumented-storage.d.ts +11 -0
- package/dist/harness/session/testing/instrumented-storage.d.ts.map +1 -0
- package/dist/harness/session/testing/instrumented-storage.js +16 -0
- package/dist/harness/session/testing/instrumented-storage.js.map +1 -0
- package/dist/harness/session/testing/storage-decorator.d.ts +20 -0
- package/dist/harness/session/testing/storage-decorator.d.ts.map +1 -0
- package/dist/harness/session/testing/storage-decorator.js +41 -0
- package/dist/harness/session/testing/storage-decorator.js.map +1 -0
- package/dist/harness/session/testing/types.d.ts +12 -0
- package/dist/harness/session/testing/types.d.ts.map +1 -0
- package/dist/harness/session/testing/types.js +2 -0
- package/dist/harness/session/testing/types.js.map +1 -0
- package/dist/harness/session/types.d.ts +512 -0
- package/dist/harness/session/types.d.ts.map +1 -0
- package/dist/harness/session/types.js +9 -0
- package/dist/harness/session/types.js.map +1 -0
- package/dist/harness/session/values.d.ts +95 -0
- package/dist/harness/session/values.d.ts.map +1 -0
- package/dist/harness/session/values.js +81 -0
- package/dist/harness/session/values.js.map +1 -0
- package/dist/harness/skills.d.ts +46 -0
- package/dist/harness/skills.d.ts.map +1 -0
- package/dist/harness/skills.js +326 -0
- package/dist/harness/skills.js.map +1 -0
- package/dist/harness/system-prompt.d.ts +3 -0
- package/dist/harness/system-prompt.d.ts.map +1 -0
- package/dist/harness/system-prompt.js +30 -0
- package/dist/harness/system-prompt.js.map +1 -0
- package/dist/harness/telemetry.d.ts +1242 -0
- package/dist/harness/telemetry.d.ts.map +1 -0
- package/dist/harness/telemetry.js +527 -0
- package/dist/harness/telemetry.js.map +1 -0
- package/dist/harness/tools/bash.d.ts +27 -0
- package/dist/harness/tools/bash.d.ts.map +1 -0
- package/dist/harness/tools/bash.js +105 -0
- package/dist/harness/tools/bash.js.map +1 -0
- package/dist/harness/tools/edit-diff.d.ts +89 -0
- package/dist/harness/tools/edit-diff.d.ts.map +1 -0
- package/dist/harness/tools/edit-diff.js +386 -0
- package/dist/harness/tools/edit-diff.js.map +1 -0
- package/dist/harness/tools/edit.d.ts +19 -0
- package/dist/harness/tools/edit.d.ts.map +1 -0
- package/dist/harness/tools/edit.js +108 -0
- package/dist/harness/tools/edit.js.map +1 -0
- package/dist/harness/tools/file-mutation-queue.d.ts +5 -0
- package/dist/harness/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/harness/tools/file-mutation-queue.js +46 -0
- package/dist/harness/tools/file-mutation-queue.js.map +1 -0
- package/dist/harness/tools/image.d.ts +3 -0
- package/dist/harness/tools/image.d.ts.map +1 -0
- package/dist/harness/tools/image.js +106 -0
- package/dist/harness/tools/image.js.map +1 -0
- package/dist/harness/tools/index.d.ts +6 -0
- package/dist/harness/tools/index.d.ts.map +1 -0
- package/dist/harness/tools/index.js +5 -0
- package/dist/harness/tools/index.js.map +1 -0
- package/dist/harness/tools/path-utils.d.ts +5 -0
- package/dist/harness/tools/path-utils.d.ts.map +1 -0
- package/dist/harness/tools/path-utils.js +26 -0
- package/dist/harness/tools/path-utils.js.map +1 -0
- package/dist/harness/tools/read.d.ts +35 -0
- package/dist/harness/tools/read.d.ts.map +1 -0
- package/dist/harness/tools/read.js +108 -0
- package/dist/harness/tools/read.js.map +1 -0
- package/dist/harness/tools/tool-context.d.ts +6 -0
- package/dist/harness/tools/tool-context.d.ts.map +1 -0
- package/dist/harness/tools/tool-context.js +2 -0
- package/dist/harness/tools/tool-context.js.map +1 -0
- package/dist/harness/tools/write.d.ts +11 -0
- package/dist/harness/tools/write.d.ts.map +1 -0
- package/dist/harness/tools/write.js +31 -0
- package/dist/harness/tools/write.js.map +1 -0
- package/dist/harness/types.d.ts +305 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +75 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/utils/adaptive-publisher.d.ts +24 -0
- package/dist/harness/utils/adaptive-publisher.d.ts.map +1 -0
- package/dist/harness/utils/adaptive-publisher.js +79 -0
- package/dist/harness/utils/adaptive-publisher.js.map +1 -0
- package/dist/harness/utils/output-capture.d.ts +31 -0
- package/dist/harness/utils/output-capture.d.ts.map +1 -0
- package/dist/harness/utils/output-capture.js +217 -0
- package/dist/harness/utils/output-capture.js.map +1 -0
- package/dist/harness/utils/shell-output.d.ts +29 -0
- package/dist/harness/utils/shell-output.d.ts.map +1 -0
- package/dist/harness/utils/shell-output.js +71 -0
- package/dist/harness/utils/shell-output.js.map +1 -0
- package/dist/harness/utils/truncate.d.ts +71 -0
- package/dist/harness/utils/truncate.d.ts.map +1 -0
- package/dist/harness/utils/truncate.js +296 -0
- package/dist/harness/utils/truncate.js.map +1 -0
- package/dist/harness/utils/usage.d.ts +4 -0
- package/dist/harness/utils/usage.d.ts.map +1 -0
- package/dist/harness/utils/usage.js +33 -0
- package/dist/harness/utils/usage.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +3 -0
- package/dist/node.js.map +1 -0
- package/dist/proxy.d.ts +72 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +310 -0
- package/dist/proxy.js.map +1 -0
- package/dist/search/index.d.ts +29 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +2 -0
- package/dist/search/index.js.map +1 -0
- package/dist/stream-fn.d.ts +10 -0
- package/dist/stream-fn.d.ts.map +1 -0
- package/dist/stream-fn.js +17 -0
- package/dist/stream-fn.js.map +1 -0
- package/dist/types.d.ts +416 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { BACKGROUND_CONTEXT } from "../../../context.js";
|
|
2
|
+
import { branchTip, laneConfig, laneState, setValue } from "../../values.js";
|
|
3
|
+
import { STORAGE_BENCHMARK_DATASETS } from "./datasets.js";
|
|
4
|
+
import { generateStorageBenchmarkSeedTransactions } from "./storage.js";
|
|
5
|
+
/** Package-internal deterministic session id shared by repository benchmark workloads. */
|
|
6
|
+
export function sessionRepoBenchmarkSessionId(index) {
|
|
7
|
+
return `benchmark-session-${index.toString().padStart(8, "0")}`;
|
|
8
|
+
}
|
|
9
|
+
const BENCHMARK_SESSION_ID = sessionRepoBenchmarkSessionId(0);
|
|
10
|
+
const FORK_DESTINATION_SESSION_ID = sessionRepoBenchmarkSessionId(1);
|
|
11
|
+
function createCatalogDataset(scale, sessionCount) {
|
|
12
|
+
return {
|
|
13
|
+
name: `synthetic catalog: ${scale} closed sessions`,
|
|
14
|
+
sessionCount,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Deterministic closed-session catalogs shared by repository measurements. */
|
|
18
|
+
export const SESSION_REPO_CATALOG_BENCHMARK_DATASETS = [
|
|
19
|
+
createCatalogDataset("100", 100),
|
|
20
|
+
createCatalogDataset("1k", 1_000),
|
|
21
|
+
createCatalogDataset("10k", 10_000),
|
|
22
|
+
];
|
|
23
|
+
/** Seeds one deterministic catalog and returns its durable metadata in creation order. */
|
|
24
|
+
export async function seedSessionRepoCatalogBenchmark(repo, dataset) {
|
|
25
|
+
const metadata = [];
|
|
26
|
+
for (let index = 0; index < dataset.sessionCount; index++) {
|
|
27
|
+
const session = await repo.create({ id: sessionRepoBenchmarkSessionId(index) }, BACKGROUND_CONTEXT);
|
|
28
|
+
metadata.push(session.metadata);
|
|
29
|
+
await session.close(BACKGROUND_CONTEXT);
|
|
30
|
+
}
|
|
31
|
+
return metadata;
|
|
32
|
+
}
|
|
33
|
+
/** Shared catalog reads. Returning a number ensures each result is consumed. */
|
|
34
|
+
export const SESSION_REPO_CATALOG_READ_BENCHMARK_SCENARIOS = [
|
|
35
|
+
{
|
|
36
|
+
name: "list sessions",
|
|
37
|
+
expectedResult(dataset) {
|
|
38
|
+
return dataset.sessionCount;
|
|
39
|
+
},
|
|
40
|
+
async run(repo) {
|
|
41
|
+
return (await repo.list(undefined, BACKGROUND_CONTEXT)).length;
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
/** Shared catalog writes. Every invocation receives an equivalent independently prepared repository. */
|
|
46
|
+
export const SESSION_REPO_CATALOG_WRITE_BENCHMARK_SCENARIOS = [
|
|
47
|
+
{
|
|
48
|
+
name: "create empty session",
|
|
49
|
+
expectedResult: 1,
|
|
50
|
+
prepare(repo) {
|
|
51
|
+
return Promise.resolve({
|
|
52
|
+
async run() {
|
|
53
|
+
const session = await repo.create({ id: BENCHMARK_SESSION_ID }, BACKGROUND_CONTEXT);
|
|
54
|
+
return session.metadata.id === BENCHMARK_SESSION_ID ? 1 : 0;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "open closed empty session",
|
|
61
|
+
expectedResult: 1,
|
|
62
|
+
async prepare(repo) {
|
|
63
|
+
const session = await repo.create({ id: BENCHMARK_SESSION_ID }, BACKGROUND_CONTEXT);
|
|
64
|
+
const { metadata } = session;
|
|
65
|
+
await session.close(BACKGROUND_CONTEXT);
|
|
66
|
+
return {
|
|
67
|
+
async run() {
|
|
68
|
+
const reopened = await repo.open(metadata, BACKGROUND_CONTEXT);
|
|
69
|
+
return reopened.metadata.id === metadata.id ? 1 : 0;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "delete closed empty session",
|
|
76
|
+
expectedResult: 1,
|
|
77
|
+
async prepare(repo) {
|
|
78
|
+
const session = await repo.create({ id: BENCHMARK_SESSION_ID }, BACKGROUND_CONTEXT);
|
|
79
|
+
const { metadata } = session;
|
|
80
|
+
await session.close(BACKGROUND_CONTEXT);
|
|
81
|
+
return {
|
|
82
|
+
async run() {
|
|
83
|
+
await repo.delete(metadata, BACKGROUND_CONTEXT);
|
|
84
|
+
return 1;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
/** Initial fork timing uses a bounded source because each iteration owns an equivalent seeded repository. */
|
|
91
|
+
export const SESSION_REPO_FORK_BENCHMARK_DATASETS = [
|
|
92
|
+
STORAGE_BENCHMARK_DATASETS[0],
|
|
93
|
+
STORAGE_BENCHMARK_DATASETS[1],
|
|
94
|
+
];
|
|
95
|
+
/** Seeds one deterministic open source session with a linear main branch. */
|
|
96
|
+
export async function seedSessionRepoForkBenchmark(repo, dataset) {
|
|
97
|
+
const session = await repo.create({ id: BENCHMARK_SESSION_ID }, BACKGROUND_CONTEXT);
|
|
98
|
+
for (const transaction of generateStorageBenchmarkSeedTransactions(dataset)) {
|
|
99
|
+
await session.mutate((mutator) => mutator.commit(transaction, BACKGROUND_CONTEXT), BACKGROUND_CONTEXT);
|
|
100
|
+
}
|
|
101
|
+
await session.mutate((mutator) => mutator.commit([
|
|
102
|
+
setValue(branchTip("main"), dataset.tipId),
|
|
103
|
+
setValue(laneConfig("main"), {
|
|
104
|
+
model: { provider: "benchmark", modelId: "benchmark" },
|
|
105
|
+
thinkingLevel: "off",
|
|
106
|
+
activeToolNames: [],
|
|
107
|
+
}),
|
|
108
|
+
setValue(laneState("main"), { currentOperationId: null, lastOperationId: null, inbox: [] }),
|
|
109
|
+
], BACKGROUND_CONTEXT), BACKGROUND_CONTEXT);
|
|
110
|
+
return session.metadata;
|
|
111
|
+
}
|
|
112
|
+
/** Shared fork writes. Every invocation receives an equivalent source repository. */
|
|
113
|
+
export const SESSION_REPO_FORK_WRITE_BENCHMARK_SCENARIOS = [
|
|
114
|
+
{
|
|
115
|
+
name: "fork open current branch",
|
|
116
|
+
expectedResult(dataset) {
|
|
117
|
+
return dataset.entryCount;
|
|
118
|
+
},
|
|
119
|
+
async run(repo, source, dataset) {
|
|
120
|
+
const fork = await repo.fork(source, { id: FORK_DESTINATION_SESSION_ID, scope: "branch", branch: "main" }, BACKGROUND_CONTEXT);
|
|
121
|
+
return fork.metadata.id === FORK_DESTINATION_SESSION_ID && fork.metadata.parentSessionId === source.id
|
|
122
|
+
? dataset.entryCount
|
|
123
|
+
: 0;
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
//# sourceMappingURL=session-repo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-repo.js","sourceRoot":"","sources":["../../../../../src/harness/session/testing/benchmark/session-repo.ts"],"names":[],"mappings":"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","sourcesContent":["import { BACKGROUND_CONTEXT } from \"../../../context.ts\";\nimport type { SessionMetadata, SessionRepo } from \"../../types.ts\";\nimport { branchTip, laneConfig, laneState, setValue } from \"../../values.ts\";\nimport { STORAGE_BENCHMARK_DATASETS, type StorageBenchmarkDataset } from \"./datasets.ts\";\nimport { generateStorageBenchmarkSeedTransactions } from \"./storage.ts\";\n\nexport interface SessionRepoCatalogBenchmarkDataset {\n\treadonly name: string;\n\treadonly sessionCount: number;\n}\n\ninterface SessionRepoCatalogReadBenchmarkScenario {\n\treadonly name: string;\n\texpectedResult(dataset: SessionRepoCatalogBenchmarkDataset): number;\n\trun(repo: SessionRepo): Promise<number>;\n}\n\ninterface SessionRepoCatalogWriteBenchmarkOperation {\n\trun(): Promise<number>;\n}\n\ninterface SessionRepoCatalogWriteBenchmarkScenario {\n\treadonly name: string;\n\treadonly expectedResult: number;\n\tprepare(repo: SessionRepo): Promise<SessionRepoCatalogWriteBenchmarkOperation>;\n}\n\ninterface SessionRepoForkWriteBenchmarkScenario {\n\treadonly name: string;\n\texpectedResult(dataset: StorageBenchmarkDataset): number;\n\trun(repo: SessionRepo, source: SessionMetadata, dataset: StorageBenchmarkDataset): Promise<number>;\n}\n\n/** Package-internal deterministic session id shared by repository benchmark workloads. */\nexport function sessionRepoBenchmarkSessionId(index: number): string {\n\treturn `benchmark-session-${index.toString().padStart(8, \"0\")}`;\n}\n\nconst BENCHMARK_SESSION_ID = sessionRepoBenchmarkSessionId(0);\nconst FORK_DESTINATION_SESSION_ID = sessionRepoBenchmarkSessionId(1);\n\nfunction createCatalogDataset(scale: string, sessionCount: number): SessionRepoCatalogBenchmarkDataset {\n\treturn {\n\t\tname: `synthetic catalog: ${scale} closed sessions`,\n\t\tsessionCount,\n\t};\n}\n\n/** Deterministic closed-session catalogs shared by repository measurements. */\nexport const SESSION_REPO_CATALOG_BENCHMARK_DATASETS: readonly SessionRepoCatalogBenchmarkDataset[] = [\n\tcreateCatalogDataset(\"100\", 100),\n\tcreateCatalogDataset(\"1k\", 1_000),\n\tcreateCatalogDataset(\"10k\", 10_000),\n];\n\n/** Seeds one deterministic catalog and returns its durable metadata in creation order. */\nexport async function seedSessionRepoCatalogBenchmark(\n\trepo: SessionRepo,\n\tdataset: SessionRepoCatalogBenchmarkDataset,\n): Promise<SessionMetadata[]> {\n\tconst metadata: SessionMetadata[] = [];\n\tfor (let index = 0; index < dataset.sessionCount; index++) {\n\t\tconst session = await repo.create({ id: sessionRepoBenchmarkSessionId(index) }, BACKGROUND_CONTEXT);\n\t\tmetadata.push(session.metadata);\n\t\tawait session.close(BACKGROUND_CONTEXT);\n\t}\n\treturn metadata;\n}\n\n/** Shared catalog reads. Returning a number ensures each result is consumed. */\nexport const SESSION_REPO_CATALOG_READ_BENCHMARK_SCENARIOS: readonly SessionRepoCatalogReadBenchmarkScenario[] = [\n\t{\n\t\tname: \"list sessions\",\n\t\texpectedResult(dataset) {\n\t\t\treturn dataset.sessionCount;\n\t\t},\n\t\tasync run(repo) {\n\t\t\treturn (await repo.list(undefined, BACKGROUND_CONTEXT)).length;\n\t\t},\n\t},\n];\n\n/** Shared catalog writes. Every invocation receives an equivalent independently prepared repository. */\nexport const SESSION_REPO_CATALOG_WRITE_BENCHMARK_SCENARIOS: readonly SessionRepoCatalogWriteBenchmarkScenario[] = [\n\t{\n\t\tname: \"create empty session\",\n\t\texpectedResult: 1,\n\t\tprepare(repo) {\n\t\t\treturn Promise.resolve({\n\t\t\t\tasync run() {\n\t\t\t\t\tconst session = await repo.create({ id: BENCHMARK_SESSION_ID }, BACKGROUND_CONTEXT);\n\t\t\t\t\treturn session.metadata.id === BENCHMARK_SESSION_ID ? 1 : 0;\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t},\n\t{\n\t\tname: \"open closed empty session\",\n\t\texpectedResult: 1,\n\t\tasync prepare(repo) {\n\t\t\tconst session = await repo.create({ id: BENCHMARK_SESSION_ID }, BACKGROUND_CONTEXT);\n\t\t\tconst { metadata } = session;\n\t\t\tawait session.close(BACKGROUND_CONTEXT);\n\t\t\treturn {\n\t\t\t\tasync run() {\n\t\t\t\t\tconst reopened = await repo.open(metadata, BACKGROUND_CONTEXT);\n\t\t\t\t\treturn reopened.metadata.id === metadata.id ? 1 : 0;\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t},\n\t{\n\t\tname: \"delete closed empty session\",\n\t\texpectedResult: 1,\n\t\tasync prepare(repo) {\n\t\t\tconst session = await repo.create({ id: BENCHMARK_SESSION_ID }, BACKGROUND_CONTEXT);\n\t\t\tconst { metadata } = session;\n\t\t\tawait session.close(BACKGROUND_CONTEXT);\n\t\t\treturn {\n\t\t\t\tasync run() {\n\t\t\t\t\tawait repo.delete(metadata, BACKGROUND_CONTEXT);\n\t\t\t\t\treturn 1;\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t},\n];\n\n/** Initial fork timing uses a bounded source because each iteration owns an equivalent seeded repository. */\nexport const SESSION_REPO_FORK_BENCHMARK_DATASETS: readonly StorageBenchmarkDataset[] = [\n\tSTORAGE_BENCHMARK_DATASETS[0]!,\n\tSTORAGE_BENCHMARK_DATASETS[1]!,\n];\n\n/** Seeds one deterministic open source session with a linear main branch. */\nexport async function seedSessionRepoForkBenchmark(\n\trepo: SessionRepo,\n\tdataset: StorageBenchmarkDataset,\n): Promise<SessionMetadata> {\n\tconst session = await repo.create({ id: BENCHMARK_SESSION_ID }, BACKGROUND_CONTEXT);\n\tfor (const transaction of generateStorageBenchmarkSeedTransactions(dataset)) {\n\t\tawait session.mutate((mutator) => mutator.commit(transaction, BACKGROUND_CONTEXT), BACKGROUND_CONTEXT);\n\t}\n\tawait session.mutate(\n\t\t(mutator) =>\n\t\t\tmutator.commit(\n\t\t\t\t[\n\t\t\t\t\tsetValue(branchTip(\"main\"), dataset.tipId),\n\t\t\t\t\tsetValue(laneConfig(\"main\"), {\n\t\t\t\t\t\tmodel: { provider: \"benchmark\", modelId: \"benchmark\" },\n\t\t\t\t\t\tthinkingLevel: \"off\",\n\t\t\t\t\t\tactiveToolNames: [],\n\t\t\t\t\t}),\n\t\t\t\t\tsetValue(laneState(\"main\"), { currentOperationId: null, lastOperationId: null, inbox: [] }),\n\t\t\t\t],\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t),\n\t\tBACKGROUND_CONTEXT,\n\t);\n\treturn session.metadata;\n}\n\n/** Shared fork writes. Every invocation receives an equivalent source repository. */\nexport const SESSION_REPO_FORK_WRITE_BENCHMARK_SCENARIOS: readonly SessionRepoForkWriteBenchmarkScenario[] = [\n\t{\n\t\tname: \"fork open current branch\",\n\t\texpectedResult(dataset) {\n\t\t\treturn dataset.entryCount;\n\t\t},\n\t\tasync run(repo, source, dataset) {\n\t\t\tconst fork = await repo.fork(\n\t\t\t\tsource,\n\t\t\t\t{ id: FORK_DESTINATION_SESSION_ID, scope: \"branch\", branch: \"main\" },\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\t\t\treturn fork.metadata.id === FORK_DESTINATION_SESSION_ID && fork.metadata.parentSessionId === source.id\n\t\t\t\t? dataset.entryCount\n\t\t\t\t: 0;\n\t\t},\n\t},\n];\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Storage, Write } from "../../types.ts";
|
|
2
|
+
import { type StorageBenchmarkDataset } from "./datasets.ts";
|
|
3
|
+
/** Generates deterministic seed transactions without claiming a production data distribution. */
|
|
4
|
+
export declare function generateStorageBenchmarkSeedTransactions(dataset: StorageBenchmarkDataset): Generator<Write[]>;
|
|
5
|
+
/** Seeds one deterministic synthetic linear branch. */
|
|
6
|
+
export declare function seedStorageBenchmark(storage: Storage, dataset: StorageBenchmarkDataset): Promise<void>;
|
|
7
|
+
/** A steady-state read operation run against a pre-seeded fixture. */
|
|
8
|
+
interface StorageReadBenchmarkScenario {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
expectedResult(dataset: StorageBenchmarkDataset): number;
|
|
11
|
+
run(storage: Storage, dataset: StorageBenchmarkDataset): Promise<number>;
|
|
12
|
+
}
|
|
13
|
+
/** A write operation run once against each independently prepared fixture. */
|
|
14
|
+
interface StorageWriteBenchmarkScenario {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly writeCount: number;
|
|
17
|
+
prepare?(storage: Storage): Promise<void>;
|
|
18
|
+
run(storage: Storage): Promise<number>;
|
|
19
|
+
}
|
|
20
|
+
/** Shared read scenarios. Returning a number ensures each result is consumed. */
|
|
21
|
+
export declare const STORAGE_READ_BENCHMARK_SCENARIOS: readonly StorageReadBenchmarkScenario[];
|
|
22
|
+
/** Shared writes. Every invocation receives equivalent pre-benchmark state. */
|
|
23
|
+
export declare const STORAGE_WRITE_BENCHMARK_SCENARIOS: readonly StorageWriteBenchmarkScenario[];
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../src/harness/session/testing/benchmark/storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA0B,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE7E,OAAO,EAA8B,KAAK,uBAAuB,EAA2B,MAAM,eAAe,CAAC;AA2BlH,iGAAiG;AACjG,wBAAiB,wCAAwC,CAAC,OAAO,EAAE,uBAAuB,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,CAQ9G;AAED,uDAAuD;AACvD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5G;AAED,sEAAsE;AACtE,UAAU,4BAA4B;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAAC;IACzD,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACzE;AAED,8EAA8E;AAC9E,UAAU,6BAA6B;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACvC;AAuBD,iFAAiF;AACjF,eAAO,MAAM,gCAAgC,EAAE,SAAS,4BAA4B,EA6BnF,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,iCAAiC,EAAE,SAAS,6BAA6B,EAyBrF,CAAC","sourcesContent":["import { BACKGROUND_CONTEXT } from \"../../../context.ts\";\nimport { insertEntry, insertUsage } from \"../../commit.ts\";\nimport type { MessageEntry, NewEntry, Storage, Write } from \"../../types.ts\";\nimport { branchTip, setValue } from \"../../values.ts\";\nimport { STORAGE_BENCHMARK_DATASETS, type StorageBenchmarkDataset, storageBenchmarkEntryId } from \"./datasets.ts\";\n\nconst MESSAGE_TIMESTAMP = 1_650_000_000_000;\nconst SEED_BATCH_SIZE = 250;\nconst WRITE_BASELINE_DATASET = STORAGE_BENCHMARK_DATASETS[0]!;\n\nfunction createEntry(index: number, payloadBytes: number): NewEntry<MessageEntry> {\n\tconst id = storageBenchmarkEntryId(index);\n\tconst prefix = `${id}:`;\n\treturn {\n\t\tid,\n\t\tparentId: index === 0 ? null : storageBenchmarkEntryId(index - 1),\n\t\ttype: \"message\",\n\t\tmessage: {\n\t\t\trole: \"user\",\n\t\t\tcontent: [{ type: \"text\", text: prefix + \"x\".repeat(Math.max(0, payloadBytes - prefix.length)) }],\n\t\t\ttimestamp: MESSAGE_TIMESTAMP,\n\t\t},\n\t};\n}\n\nfunction createStorageBenchmarkTransaction(startIndex: number, entryCount: number, payloadBytes: number): Write[] {\n\treturn Array.from({ length: entryCount }, (_, offset) =>\n\t\tinsertEntry(createEntry(startIndex + offset, payloadBytes)),\n\t);\n}\n\n/** Generates deterministic seed transactions without claiming a production data distribution. */\nexport function* generateStorageBenchmarkSeedTransactions(dataset: StorageBenchmarkDataset): Generator<Write[]> {\n\tfor (let startIndex = 0; startIndex < dataset.entryCount; startIndex += SEED_BATCH_SIZE) {\n\t\tyield createStorageBenchmarkTransaction(\n\t\t\tstartIndex,\n\t\t\tMath.min(SEED_BATCH_SIZE, dataset.entryCount - startIndex),\n\t\t\tdataset.payloadBytes,\n\t\t);\n\t}\n}\n\n/** Seeds one deterministic synthetic linear branch. */\nexport async function seedStorageBenchmark(storage: Storage, dataset: StorageBenchmarkDataset): Promise<void> {\n\tfor (const transaction of generateStorageBenchmarkSeedTransactions(dataset))\n\t\tawait storage.commit(transaction, BACKGROUND_CONTEXT);\n}\n\n/** A steady-state read operation run against a pre-seeded fixture. */\ninterface StorageReadBenchmarkScenario {\n\treadonly name: string;\n\texpectedResult(dataset: StorageBenchmarkDataset): number;\n\trun(storage: Storage, dataset: StorageBenchmarkDataset): Promise<number>;\n}\n\n/** A write operation run once against each independently prepared fixture. */\ninterface StorageWriteBenchmarkScenario {\n\treadonly name: string;\n\treadonly writeCount: number;\n\tprepare?(storage: Storage): Promise<void>;\n\trun(storage: Storage): Promise<number>;\n}\n\nconst singleEntryTransaction = createStorageBenchmarkTransaction(0, 1, 256);\nconst hundredEntryTransaction = createStorageBenchmarkTransaction(0, 100, 256);\nconst appendedEntryId = storageBenchmarkEntryId(WRITE_BASELINE_DATASET.entryCount);\nconst mixedAppendTransaction: Write[] = [\n\t...createStorageBenchmarkTransaction(WRITE_BASELINE_DATASET.entryCount, 1, WRITE_BASELINE_DATASET.payloadBytes),\n\tsetValue(branchTip(\"main\"), appendedEntryId),\n\tinsertUsage({\n\t\tid: \"benchmark-usage\",\n\t\tentryId: appendedEntryId,\n\t\tadjustment: false,\n\t\tusage: {\n\t\t\tinput: 1_000,\n\t\t\toutput: 250,\n\t\t\tcacheRead: 500,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 1_250,\n\t\t\tcost: { input: 0.001, output: 0.001, cacheRead: 0.0001, cacheWrite: 0, total: 0.0021 },\n\t\t},\n\t}),\n];\n\n/** Shared read scenarios. Returning a number ensures each result is consumed. */\nexport const STORAGE_READ_BENCHMARK_SCENARIOS: readonly StorageReadBenchmarkScenario[] = [\n\t{\n\t\tname: \"get 100 distributed entries\",\n\t\texpectedResult(dataset) {\n\t\t\treturn dataset.lookupIds.length;\n\t\t},\n\t\tasync run(storage, dataset) {\n\t\t\treturn (await storage.getEntries([...dataset.lookupIds], BACKGROUND_CONTEXT)).size;\n\t\t},\n\t},\n\t{\n\t\tname: \"scan latest 50 entries\",\n\t\texpectedResult(dataset) {\n\t\t\treturn Math.min(50, dataset.entryCount);\n\t\t},\n\t\tasync run(storage) {\n\t\t\treturn (await storage.scanEntries({ order: \"desc\", limit: 50 }, BACKGROUND_CONTEXT)).length;\n\t\t},\n\t},\n\t{\n\t\tname: \"scan full branch structure\",\n\t\texpectedResult(dataset) {\n\t\t\treturn dataset.entryCount;\n\t\t},\n\t\tasync run(storage, dataset) {\n\t\t\treturn (await storage.scanBranchStructure({ start: dataset.tipId, order: \"newestFirst\" }, BACKGROUND_CONTEXT))\n\t\t\t\t.length;\n\t\t},\n\t},\n];\n\n/** Shared writes. Every invocation receives equivalent pre-benchmark state. */\nexport const STORAGE_WRITE_BENCHMARK_SCENARIOS: readonly StorageWriteBenchmarkScenario[] = [\n\t{\n\t\tname: \"commit one message entry\",\n\t\twriteCount: 1,\n\t\tasync run(storage) {\n\t\t\treturn (await storage.commit(singleEntryTransaction, BACKGROUND_CONTEXT)).seqs.length;\n\t\t},\n\t},\n\t{\n\t\tname: \"commit 100 message entries\",\n\t\twriteCount: 100,\n\t\tasync run(storage) {\n\t\t\treturn (await storage.commit(hundredEntryTransaction, BACKGROUND_CONTEXT)).seqs.length;\n\t\t},\n\t},\n\t{\n\t\tname: `commit mixed append (${WRITE_BASELINE_DATASET.name})`,\n\t\twriteCount: 3,\n\t\tprepare(storage) {\n\t\t\treturn seedStorageBenchmark(storage, WRITE_BASELINE_DATASET);\n\t\t},\n\t\tasync run(storage) {\n\t\t\treturn (await storage.commit(mixedAppendTransaction, BACKGROUND_CONTEXT)).seqs.length;\n\t\t},\n\t},\n];\n"]}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { BACKGROUND_CONTEXT } from "../../../context.js";
|
|
2
|
+
import { insertEntry, insertUsage } from "../../commit.js";
|
|
3
|
+
import { branchTip, setValue } from "../../values.js";
|
|
4
|
+
import { STORAGE_BENCHMARK_DATASETS, storageBenchmarkEntryId } from "./datasets.js";
|
|
5
|
+
const MESSAGE_TIMESTAMP = 1_650_000_000_000;
|
|
6
|
+
const SEED_BATCH_SIZE = 250;
|
|
7
|
+
const WRITE_BASELINE_DATASET = STORAGE_BENCHMARK_DATASETS[0];
|
|
8
|
+
function createEntry(index, payloadBytes) {
|
|
9
|
+
const id = storageBenchmarkEntryId(index);
|
|
10
|
+
const prefix = `${id}:`;
|
|
11
|
+
return {
|
|
12
|
+
id,
|
|
13
|
+
parentId: index === 0 ? null : storageBenchmarkEntryId(index - 1),
|
|
14
|
+
type: "message",
|
|
15
|
+
message: {
|
|
16
|
+
role: "user",
|
|
17
|
+
content: [{ type: "text", text: prefix + "x".repeat(Math.max(0, payloadBytes - prefix.length)) }],
|
|
18
|
+
timestamp: MESSAGE_TIMESTAMP,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function createStorageBenchmarkTransaction(startIndex, entryCount, payloadBytes) {
|
|
23
|
+
return Array.from({ length: entryCount }, (_, offset) => insertEntry(createEntry(startIndex + offset, payloadBytes)));
|
|
24
|
+
}
|
|
25
|
+
/** Generates deterministic seed transactions without claiming a production data distribution. */
|
|
26
|
+
export function* generateStorageBenchmarkSeedTransactions(dataset) {
|
|
27
|
+
for (let startIndex = 0; startIndex < dataset.entryCount; startIndex += SEED_BATCH_SIZE) {
|
|
28
|
+
yield createStorageBenchmarkTransaction(startIndex, Math.min(SEED_BATCH_SIZE, dataset.entryCount - startIndex), dataset.payloadBytes);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Seeds one deterministic synthetic linear branch. */
|
|
32
|
+
export async function seedStorageBenchmark(storage, dataset) {
|
|
33
|
+
for (const transaction of generateStorageBenchmarkSeedTransactions(dataset))
|
|
34
|
+
await storage.commit(transaction, BACKGROUND_CONTEXT);
|
|
35
|
+
}
|
|
36
|
+
const singleEntryTransaction = createStorageBenchmarkTransaction(0, 1, 256);
|
|
37
|
+
const hundredEntryTransaction = createStorageBenchmarkTransaction(0, 100, 256);
|
|
38
|
+
const appendedEntryId = storageBenchmarkEntryId(WRITE_BASELINE_DATASET.entryCount);
|
|
39
|
+
const mixedAppendTransaction = [
|
|
40
|
+
...createStorageBenchmarkTransaction(WRITE_BASELINE_DATASET.entryCount, 1, WRITE_BASELINE_DATASET.payloadBytes),
|
|
41
|
+
setValue(branchTip("main"), appendedEntryId),
|
|
42
|
+
insertUsage({
|
|
43
|
+
id: "benchmark-usage",
|
|
44
|
+
entryId: appendedEntryId,
|
|
45
|
+
adjustment: false,
|
|
46
|
+
usage: {
|
|
47
|
+
input: 1_000,
|
|
48
|
+
output: 250,
|
|
49
|
+
cacheRead: 500,
|
|
50
|
+
cacheWrite: 0,
|
|
51
|
+
totalTokens: 1_250,
|
|
52
|
+
cost: { input: 0.001, output: 0.001, cacheRead: 0.0001, cacheWrite: 0, total: 0.0021 },
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
];
|
|
56
|
+
/** Shared read scenarios. Returning a number ensures each result is consumed. */
|
|
57
|
+
export const STORAGE_READ_BENCHMARK_SCENARIOS = [
|
|
58
|
+
{
|
|
59
|
+
name: "get 100 distributed entries",
|
|
60
|
+
expectedResult(dataset) {
|
|
61
|
+
return dataset.lookupIds.length;
|
|
62
|
+
},
|
|
63
|
+
async run(storage, dataset) {
|
|
64
|
+
return (await storage.getEntries([...dataset.lookupIds], BACKGROUND_CONTEXT)).size;
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "scan latest 50 entries",
|
|
69
|
+
expectedResult(dataset) {
|
|
70
|
+
return Math.min(50, dataset.entryCount);
|
|
71
|
+
},
|
|
72
|
+
async run(storage) {
|
|
73
|
+
return (await storage.scanEntries({ order: "desc", limit: 50 }, BACKGROUND_CONTEXT)).length;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "scan full branch structure",
|
|
78
|
+
expectedResult(dataset) {
|
|
79
|
+
return dataset.entryCount;
|
|
80
|
+
},
|
|
81
|
+
async run(storage, dataset) {
|
|
82
|
+
return (await storage.scanBranchStructure({ start: dataset.tipId, order: "newestFirst" }, BACKGROUND_CONTEXT))
|
|
83
|
+
.length;
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
/** Shared writes. Every invocation receives equivalent pre-benchmark state. */
|
|
88
|
+
export const STORAGE_WRITE_BENCHMARK_SCENARIOS = [
|
|
89
|
+
{
|
|
90
|
+
name: "commit one message entry",
|
|
91
|
+
writeCount: 1,
|
|
92
|
+
async run(storage) {
|
|
93
|
+
return (await storage.commit(singleEntryTransaction, BACKGROUND_CONTEXT)).seqs.length;
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "commit 100 message entries",
|
|
98
|
+
writeCount: 100,
|
|
99
|
+
async run(storage) {
|
|
100
|
+
return (await storage.commit(hundredEntryTransaction, BACKGROUND_CONTEXT)).seqs.length;
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: `commit mixed append (${WRITE_BASELINE_DATASET.name})`,
|
|
105
|
+
writeCount: 3,
|
|
106
|
+
prepare(storage) {
|
|
107
|
+
return seedStorageBenchmark(storage, WRITE_BASELINE_DATASET);
|
|
108
|
+
},
|
|
109
|
+
async run(storage) {
|
|
110
|
+
return (await storage.commit(mixedAppendTransaction, BACKGROUND_CONTEXT)).seqs.length;
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../../../src/harness/session/testing/benchmark/storage.ts"],"names":[],"mappings":"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","sourcesContent":["import { BACKGROUND_CONTEXT } from \"../../../context.ts\";\nimport { insertEntry, insertUsage } from \"../../commit.ts\";\nimport type { MessageEntry, NewEntry, Storage, Write } from \"../../types.ts\";\nimport { branchTip, setValue } from \"../../values.ts\";\nimport { STORAGE_BENCHMARK_DATASETS, type StorageBenchmarkDataset, storageBenchmarkEntryId } from \"./datasets.ts\";\n\nconst MESSAGE_TIMESTAMP = 1_650_000_000_000;\nconst SEED_BATCH_SIZE = 250;\nconst WRITE_BASELINE_DATASET = STORAGE_BENCHMARK_DATASETS[0]!;\n\nfunction createEntry(index: number, payloadBytes: number): NewEntry<MessageEntry> {\n\tconst id = storageBenchmarkEntryId(index);\n\tconst prefix = `${id}:`;\n\treturn {\n\t\tid,\n\t\tparentId: index === 0 ? null : storageBenchmarkEntryId(index - 1),\n\t\ttype: \"message\",\n\t\tmessage: {\n\t\t\trole: \"user\",\n\t\t\tcontent: [{ type: \"text\", text: prefix + \"x\".repeat(Math.max(0, payloadBytes - prefix.length)) }],\n\t\t\ttimestamp: MESSAGE_TIMESTAMP,\n\t\t},\n\t};\n}\n\nfunction createStorageBenchmarkTransaction(startIndex: number, entryCount: number, payloadBytes: number): Write[] {\n\treturn Array.from({ length: entryCount }, (_, offset) =>\n\t\tinsertEntry(createEntry(startIndex + offset, payloadBytes)),\n\t);\n}\n\n/** Generates deterministic seed transactions without claiming a production data distribution. */\nexport function* generateStorageBenchmarkSeedTransactions(dataset: StorageBenchmarkDataset): Generator<Write[]> {\n\tfor (let startIndex = 0; startIndex < dataset.entryCount; startIndex += SEED_BATCH_SIZE) {\n\t\tyield createStorageBenchmarkTransaction(\n\t\t\tstartIndex,\n\t\t\tMath.min(SEED_BATCH_SIZE, dataset.entryCount - startIndex),\n\t\t\tdataset.payloadBytes,\n\t\t);\n\t}\n}\n\n/** Seeds one deterministic synthetic linear branch. */\nexport async function seedStorageBenchmark(storage: Storage, dataset: StorageBenchmarkDataset): Promise<void> {\n\tfor (const transaction of generateStorageBenchmarkSeedTransactions(dataset))\n\t\tawait storage.commit(transaction, BACKGROUND_CONTEXT);\n}\n\n/** A steady-state read operation run against a pre-seeded fixture. */\ninterface StorageReadBenchmarkScenario {\n\treadonly name: string;\n\texpectedResult(dataset: StorageBenchmarkDataset): number;\n\trun(storage: Storage, dataset: StorageBenchmarkDataset): Promise<number>;\n}\n\n/** A write operation run once against each independently prepared fixture. */\ninterface StorageWriteBenchmarkScenario {\n\treadonly name: string;\n\treadonly writeCount: number;\n\tprepare?(storage: Storage): Promise<void>;\n\trun(storage: Storage): Promise<number>;\n}\n\nconst singleEntryTransaction = createStorageBenchmarkTransaction(0, 1, 256);\nconst hundredEntryTransaction = createStorageBenchmarkTransaction(0, 100, 256);\nconst appendedEntryId = storageBenchmarkEntryId(WRITE_BASELINE_DATASET.entryCount);\nconst mixedAppendTransaction: Write[] = [\n\t...createStorageBenchmarkTransaction(WRITE_BASELINE_DATASET.entryCount, 1, WRITE_BASELINE_DATASET.payloadBytes),\n\tsetValue(branchTip(\"main\"), appendedEntryId),\n\tinsertUsage({\n\t\tid: \"benchmark-usage\",\n\t\tentryId: appendedEntryId,\n\t\tadjustment: false,\n\t\tusage: {\n\t\t\tinput: 1_000,\n\t\t\toutput: 250,\n\t\t\tcacheRead: 500,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 1_250,\n\t\t\tcost: { input: 0.001, output: 0.001, cacheRead: 0.0001, cacheWrite: 0, total: 0.0021 },\n\t\t},\n\t}),\n];\n\n/** Shared read scenarios. Returning a number ensures each result is consumed. */\nexport const STORAGE_READ_BENCHMARK_SCENARIOS: readonly StorageReadBenchmarkScenario[] = [\n\t{\n\t\tname: \"get 100 distributed entries\",\n\t\texpectedResult(dataset) {\n\t\t\treturn dataset.lookupIds.length;\n\t\t},\n\t\tasync run(storage, dataset) {\n\t\t\treturn (await storage.getEntries([...dataset.lookupIds], BACKGROUND_CONTEXT)).size;\n\t\t},\n\t},\n\t{\n\t\tname: \"scan latest 50 entries\",\n\t\texpectedResult(dataset) {\n\t\t\treturn Math.min(50, dataset.entryCount);\n\t\t},\n\t\tasync run(storage) {\n\t\t\treturn (await storage.scanEntries({ order: \"desc\", limit: 50 }, BACKGROUND_CONTEXT)).length;\n\t\t},\n\t},\n\t{\n\t\tname: \"scan full branch structure\",\n\t\texpectedResult(dataset) {\n\t\t\treturn dataset.entryCount;\n\t\t},\n\t\tasync run(storage, dataset) {\n\t\t\treturn (await storage.scanBranchStructure({ start: dataset.tipId, order: \"newestFirst\" }, BACKGROUND_CONTEXT))\n\t\t\t\t.length;\n\t\t},\n\t},\n];\n\n/** Shared writes. Every invocation receives equivalent pre-benchmark state. */\nexport const STORAGE_WRITE_BENCHMARK_SCENARIOS: readonly StorageWriteBenchmarkScenario[] = [\n\t{\n\t\tname: \"commit one message entry\",\n\t\twriteCount: 1,\n\t\tasync run(storage) {\n\t\t\treturn (await storage.commit(singleEntryTransaction, BACKGROUND_CONTEXT)).seqs.length;\n\t\t},\n\t},\n\t{\n\t\tname: \"commit 100 message entries\",\n\t\twriteCount: 100,\n\t\tasync run(storage) {\n\t\t\treturn (await storage.commit(hundredEntryTransaction, BACKGROUND_CONTEXT)).seqs.length;\n\t\t},\n\t},\n\t{\n\t\tname: `commit mixed append (${WRITE_BASELINE_DATASET.name})`,\n\t\twriteCount: 3,\n\t\tprepare(storage) {\n\t\t\treturn seedStorageBenchmark(storage, WRITE_BASELINE_DATASET);\n\t\t},\n\t\tasync run(storage) {\n\t\t\treturn (await storage.commit(mixedAppendTransaction, BACKGROUND_CONTEXT)).seqs.length;\n\t\t},\n\t},\n];\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SessionMetadata, SessionRepo } from "../../types.ts";
|
|
2
|
+
import type { ConformanceCase } from "../types.ts";
|
|
3
|
+
/** Creates lifecycle cases for repositories that support creation, discovery, open, and deletion. */
|
|
4
|
+
export declare function createSessionRepoLifecycleConformance<TMetadata extends SessionMetadata>(backendFactory: () => Promise<Pick<SessionRepo<TMetadata>, "create" | "open" | "list" | "delete">>, onClose?: () => void | Promise<void>): readonly ConformanceCase[];
|
|
5
|
+
/** Creates exclusive-open cases for repositories that own active session handles. */
|
|
6
|
+
export declare function createSessionRepoOwnershipConformance<TMetadata extends SessionMetadata>(backendFactory: () => Promise<Pick<SessionRepo<TMetadata>, "create" | "open">>, onClose?: () => void | Promise<void>): readonly ConformanceCase[];
|
|
7
|
+
/** Creates message cases for repositories that support session creation. */
|
|
8
|
+
export declare function createSessionRepoMessageConformance<TMetadata extends SessionMetadata>(backendFactory: () => Promise<Pick<SessionRepo<TMetadata>, "create">>, onClose?: () => void | Promise<void>): readonly ConformanceCase[];
|
|
9
|
+
/** Creates fork-content cases that do not require concurrent repository coordination. */
|
|
10
|
+
export declare function createSessionRepoForkBehaviorConformance<TMetadata extends SessionMetadata>(backendFactory: () => Promise<Pick<SessionRepo<TMetadata>, "create" | "list" | "fork">>, onClose?: () => void | Promise<void>): readonly ConformanceCase[];
|
|
11
|
+
/** WP08 cases enabled for Memory and JSONL while SQLite's streaming fork implementation is pending. */
|
|
12
|
+
export declare function createSessionRepoStreamingForkConformance<TMetadata extends SessionMetadata>(backendFactory: () => Promise<Pick<SessionRepo<TMetadata>, "create" | "fork">>, onClose?: () => void | Promise<void>): readonly ConformanceCase[];
|
|
13
|
+
/** Creates fork cases that require destination reservation across create and fork. */
|
|
14
|
+
export declare function createSessionRepoForkDestinationReservationConformance<TMetadata extends SessionMetadata>(backendFactory: () => Promise<Pick<SessionRepo<TMetadata>, "create" | "fork">>, onClose?: () => void | Promise<void>): readonly ConformanceCase[];
|
|
15
|
+
/** Creates fork cases that require a snapshot boundary on an active source storage queue. */
|
|
16
|
+
export declare function createSessionRepoForkSourceSnapshotConformance<TMetadata extends SessionMetadata>(backendFactory: () => Promise<Pick<SessionRepo<TMetadata>, "create" | "fork">>, onClose?: () => void | Promise<void>): readonly ConformanceCase[];
|
|
17
|
+
/** Creates every fork coordination case. */
|
|
18
|
+
export declare function createSessionRepoForkCoordinationConformance<TMetadata extends SessionMetadata>(backendFactory: () => Promise<Pick<SessionRepo<TMetadata>, "create" | "fork">>, onClose?: () => void | Promise<void>): readonly ConformanceCase[];
|
|
19
|
+
/** Creates every fork conformance case. */
|
|
20
|
+
export declare function createSessionRepoForkConformance<TMetadata extends SessionMetadata>(backendFactory: () => Promise<Pick<SessionRepo<TMetadata>, "create" | "list" | "fork">>, onClose?: () => void | Promise<void>): readonly ConformanceCase[];
|
|
21
|
+
/** Creates every SessionRepo conformance case. */
|
|
22
|
+
export declare function createSessionRepoConformance<TMetadata extends SessionMetadata>(factory: () => Promise<SessionRepo<TMetadata>>, onClose?: () => void | Promise<void>): readonly ConformanceCase[];
|
|
23
|
+
//# sourceMappingURL=session-repo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-repo.d.ts","sourceRoot":"","sources":["../../../../../src/harness/session/testing/conformance/session-repo.ts"],"names":[],"mappings":"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","sourcesContent":["import { deepStrictEqual, rejects, strictEqual } from \"node:assert/strict\";\nimport type { AssistantMessage, StopReason } from \"@m4ike1/ion-ai\";\nimport { BACKGROUND_CONTEXT } from \"../../../context.ts\";\nimport { insertEntry, insertUsage } from \"../../commit.ts\";\nimport type {\n\tJsonValue,\n\tLaneConfiguration,\n\tLaneState,\n\tSession,\n\tSessionMetadata,\n\tSessionRepo,\n\tUsageRow,\n} from \"../../types.ts\";\nimport {\n\tappendList,\n\tbranchTip,\n\tdeleteList,\n\tentryLabel,\n\tlaneConfig,\n\tlaneState,\n\tlist,\n\toperationMeta,\n\toperationPreparation,\n\toperationResult,\n\toperationState,\n\toperationToolArgs,\n\tpendingEntry,\n\tsessionName,\n\tsetValue,\n\tvalue,\n} from \"../../values.ts\";\nimport type { ConformanceCase } from \"../types.ts\";\n\nconst ROOT_ID = \"00000000-0000-7000-8000-000000000001\";\nconst CHILD_ID = \"00000000-0000-7000-8000-000000000002\";\nconst SIBLING_ID = \"00000000-0000-7000-8000-000000000003\";\nconst USAGE_ID = \"00000000-0000-7000-8000-000000000004\";\nconst OPERATION_ID = \"00000000-0000-7000-8000-000000000005\";\nconst PENDING_ID = \"00000000-0000-7000-8000-000000000006\";\nconst UNKNOWN_ID = \"00000000-0000-7000-8000-000000000007\";\nconst idleLaneState = {\n\tcurrentOperationId: null,\n\tlastOperationId: null,\n\tinbox: [],\n} satisfies LaneState;\nconst applicationValue = value<JsonValue>(\"test.application.value\");\nconst applicationList = list<JsonValue>(\"test.application.list\");\nconst configuration = {\n\tmodel: { provider: \"provider\", modelId: \"model\" },\n\tthinkingLevel: \"off\",\n\tactiveToolNames: [\"read\"],\n} satisfies LaneConfiguration;\n\nasync function getBranchTip(session: Session, name = \"main\"): Promise<string | null | undefined> {\n\treturn (await session.branch(name, BACKGROUND_CONTEXT))?.getTipId(BACKGROUND_CONTEXT);\n}\n\nfunction assistantMessage(stopReason: StopReason): AssistantMessage {\n\treturn {\n\t\trole: \"assistant\",\n\t\tcontent:\n\t\t\tstopReason === \"toolUse\"\n\t\t\t\t? [{ type: \"toolCall\", id: \"call\", name: \"read\", arguments: {} }]\n\t\t\t\t: [{ type: \"text\", text: stopReason }],\n\t\tapi: \"anthropic-messages\",\n\t\tprovider: \"anthropic\",\n\t\tmodel: \"claude-sonnet-4-5\",\n\t\tusage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason,\n\t\t...(stopReason === \"deferred\"\n\t\t\t? {\n\t\t\t\t\tdeferred: {\n\t\t\t\t\t\tprovider: \"anthropic\",\n\t\t\t\t\t\tmodelId: \"claude-sonnet-4-5\",\n\t\t\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\t\t\tid: \"job\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t\ttimestamp: 1,\n\t};\n}\n\nfunction usageRow(): Omit<UsageRow, \"seq\"> {\n\treturn {\n\t\tid: USAGE_ID,\n\t\tadjustment: true,\n\t\tusage: {\n\t\t\tinput: 1,\n\t\t\toutput: 2,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 3,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t};\n}\n\ninterface RepoCaseContext<TRepo> {\n\trepo: TRepo;\n\tclose?: () => void | Promise<void>;\n}\n\nfunction prepareRepoCaseFactory<TRepo>(\n\tfactory: () => Promise<TRepo>,\n\tonClose?: () => void | Promise<void>,\n): () => Promise<RepoCaseContext<TRepo>> {\n\treturn async () => ({\n\t\trepo: await factory(),\n\t\t...(onClose === undefined ? {} : { close: onClose }),\n\t});\n}\n\nfunction createCase<TRepo>(\n\tfactory: () => Promise<RepoCaseContext<TRepo>>,\n\tgroup: string,\n\tname: string,\n\ttest: (context: { repo: TRepo }) => Promise<void>,\n): ConformanceCase {\n\treturn {\n\t\tgroup,\n\t\tname,\n\t\tasync run() {\n\t\t\tconst context = await factory();\n\t\t\ttry {\n\t\t\t\tawait test(context);\n\t\t\t} finally {\n\t\t\t\tawait context.close?.();\n\t\t\t}\n\t\t},\n\t};\n}\n\n/** Creates lifecycle cases for repositories that support creation, discovery, open, and deletion. */\nexport function createSessionRepoLifecycleConformance<TMetadata extends SessionMetadata>(\n\t// Require only the methods this group exercises so partial backends can run it independently.\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"open\" | \"list\" | \"delete\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\tconst factory = prepareRepoCaseFactory(backendFactory, onClose);\n\treturn [\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"lifecycle\",\n\t\t\t\"creates a session with no implicit branch and rejects duplicate ids\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\tconst session = await repo.create({ id: \"session\" }, BACKGROUND_CONTEXT);\n\n\t\t\t\tstrictEqual(session.metadata.id, \"session\");\n\t\t\t\tstrictEqual(Number.isSafeInteger(session.metadata.createdAt), true);\n\t\t\t\tstrictEqual(session.metadata.storageVersion, 1);\n\t\t\t\tstrictEqual(await session.branch(\"main\", BACKGROUND_CONTEXT), undefined);\n\t\t\t\tstrictEqual(await session.getValue(laneState(\"main\"), BACKGROUND_CONTEXT), undefined);\n\t\t\t\tstrictEqual(await session.getValue(laneConfig(\"main\"), BACKGROUND_CONTEXT), undefined);\n\t\t\t\tawait rejects(repo.create({ id: \"session\" }, BACKGROUND_CONTEXT));\n\t\t\t\tawait session.close(BACKGROUND_CONTEXT);\n\t\t\t},\n\t\t),\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"lifecycle\",\n\t\t\t\"close drains an acquired scope and rejects a queued mutation callback\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\tconst session = await repo.create({ id: \"session\" }, BACKGROUND_CONTEXT);\n\t\t\t\tconst active = await session.beginMutation(BACKGROUND_CONTEXT);\n\t\t\t\tlet queuedStarted = false;\n\t\t\t\tconst queued = session.mutate(() => {\n\t\t\t\t\tqueuedStarted = true;\n\t\t\t\t}, BACKGROUND_CONTEXT);\n\t\t\t\tvoid queued.catch(() => {});\n\t\t\t\tconst closing = session.close(BACKGROUND_CONTEXT);\n\n\t\t\t\tawait active.end(BACKGROUND_CONTEXT);\n\t\t\t\tawait rejects(queued);\n\t\t\t\tawait closing;\n\t\t\t\tstrictEqual(queuedStarted, false);\n\t\t\t},\n\t\t),\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"lifecycle\",\n\t\t\t\"lists metadata and preserves state across close and reopen\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\tconst first = await repo.create({ id: \"first\" }, BACKGROUND_CONTEXT);\n\t\t\t\tawait first.setName(\"preserved\", BACKGROUND_CONTEXT);\n\t\t\t\tconst second = await repo.create({ id: \"second\", parentSessionId: \"parent\" }, BACKGROUND_CONTEXT);\n\t\t\t\tawait second.close(BACKGROUND_CONTEXT);\n\n\t\t\t\tconst listed = await repo.list(undefined, BACKGROUND_CONTEXT);\n\t\t\t\tdeepStrictEqual(\n\t\t\t\t\tlisted\n\t\t\t\t\t\t.map(({ id, parentSessionId }) => ({ id, parentSessionId }))\n\t\t\t\t\t\t.sort((left, right) => left.id.localeCompare(right.id)),\n\t\t\t\t\t[\n\t\t\t\t\t\t{ id: \"first\", parentSessionId: undefined },\n\t\t\t\t\t\t{ id: \"second\", parentSessionId: \"parent\" },\n\t\t\t\t\t],\n\t\t\t\t);\n\t\t\t\tawait first.close(BACKGROUND_CONTEXT);\n\t\t\t\tawait rejects(first.getName(BACKGROUND_CONTEXT));\n\t\t\t\tconst reopened = await repo.open(first.metadata, BACKGROUND_CONTEXT);\n\t\t\t\tstrictEqual(reopened === first, false);\n\t\t\t\tstrictEqual(await reopened.getName(BACKGROUND_CONTEXT), \"preserved\");\n\t\t\t\tawait reopened.close(BACKGROUND_CONTEXT);\n\t\t\t},\n\t\t),\n\t\tcreateCase(factory, \"lifecycle\", \"deletes closed sessions without affecting other sessions\", async ({ repo }) => {\n\t\t\tconst removed = await repo.create({ id: \"removed\" }, BACKGROUND_CONTEXT);\n\t\t\tconst retained = await repo.create({ id: \"retained\" }, BACKGROUND_CONTEXT);\n\t\t\tawait Promise.all([removed.close(BACKGROUND_CONTEXT), retained.close(BACKGROUND_CONTEXT)]);\n\n\t\t\tawait repo.delete(removed.metadata, BACKGROUND_CONTEXT);\n\t\t\tdeepStrictEqual(\n\t\t\t\t(await repo.list(undefined, BACKGROUND_CONTEXT)).map(({ id }) => id),\n\t\t\t\t[\"retained\"],\n\t\t\t);\n\t\t\tawait rejects(repo.open(removed.metadata, BACKGROUND_CONTEXT));\n\t\t\tawait rejects(repo.delete(removed.metadata, BACKGROUND_CONTEXT));\n\t\t}),\n\t];\n}\n\n/** Creates exclusive-open cases for repositories that own active session handles. */\nexport function createSessionRepoOwnershipConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"open\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\tconst factory = prepareRepoCaseFactory(backendFactory, onClose);\n\treturn [\n\t\tcreateCase(factory, \"ownership\", \"rejects opening an already-open session\", async ({ repo }) => {\n\t\t\tconst session = await repo.create({ id: \"session\" }, BACKGROUND_CONTEXT);\n\t\t\tawait rejects(repo.open(session.metadata, BACKGROUND_CONTEXT));\n\t\t\tawait session.close(BACKGROUND_CONTEXT);\n\n\t\t\tconst reopened = await repo.open(session.metadata, BACKGROUND_CONTEXT);\n\t\t\tawait rejects(repo.open(session.metadata, BACKGROUND_CONTEXT));\n\t\t\tawait reopened.close(BACKGROUND_CONTEXT);\n\t\t}),\n\t];\n}\n\n/** Creates message cases for repositories that support session creation. */\nexport function createSessionRepoMessageConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\tconst factory = prepareRepoCaseFactory(backendFactory, onClose);\n\treturn [\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"messages\",\n\t\t\t\"rejects pending assistant messages without changing the tree\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\tconst session = await repo.create({ id: \"session\" }, BACKGROUND_CONTEXT);\n\t\t\t\tconst branch = await session.createBranch(\"main\", null, BACKGROUND_CONTEXT);\n\n\t\t\t\tawait rejects(branch.appendMessage(assistantMessage(\"pending\"), BACKGROUND_CONTEXT));\n\n\t\t\t\tstrictEqual(await getBranchTip(session), null);\n\t\t\t\tdeepStrictEqual(await session.findEntries(undefined, BACKGROUND_CONTEXT), []);\n\t\t\t\tawait session.close(BACKGROUND_CONTEXT);\n\t\t\t},\n\t\t),\n\t\tcreateCase(factory, \"messages\", \"preserves every settled assistant stop reason\", async ({ repo }) => {\n\t\t\tconst session = await repo.create({ id: \"session\" }, BACKGROUND_CONTEXT);\n\t\t\tconst messagesByStopReason = {\n\t\t\t\tstop: assistantMessage(\"stop\"),\n\t\t\t\tlength: assistantMessage(\"length\"),\n\t\t\t\ttoolUse: assistantMessage(\"toolUse\"),\n\t\t\t\terror: assistantMessage(\"error\"),\n\t\t\t\taborted: assistantMessage(\"aborted\"),\n\t\t\t\tdeferred: assistantMessage(\"deferred\"),\n\t\t\t} satisfies Record<Exclude<StopReason, \"pending\">, AssistantMessage>;\n\t\t\tconst branch = await session.createBranch(\"main\", null, BACKGROUND_CONTEXT);\n\t\t\tconst messages = Object.values(messagesByStopReason);\n\t\t\tconst ids: string[] = [];\n\n\t\t\tfor (const message of messages) ids.push(await branch.appendMessage(message, BACKGROUND_CONTEXT));\n\n\t\t\tconst entries = await session.findEntries({ order: \"asc\", type: \"message\" }, BACKGROUND_CONTEXT);\n\t\t\tdeepStrictEqual(\n\t\t\t\tentries.map((entry) => entry.id),\n\t\t\t\tids,\n\t\t\t);\n\t\t\tfor (const [index, entry] of entries.entries()) {\n\t\t\t\tif (entry.type !== \"message\") throw new Error(\"Expected message entry\");\n\t\t\t\tdeepStrictEqual(entry.message, messages[index]);\n\t\t\t}\n\t\t\tstrictEqual(await getBranchTip(session), ids.at(-1));\n\t\t\tawait session.close(BACKGROUND_CONTEXT);\n\t\t}),\n\t];\n}\n\n/** Creates fork-content cases that do not require concurrent repository coordination. */\nexport function createSessionRepoForkBehaviorConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"list\" | \"fork\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\tconst factory = prepareRepoCaseFactory(backendFactory, onClose);\n\treturn [\n\t\tcreateCase(factory, \"forks\", \"tree-forks a fresh session before first attachment\", async ({ repo }) => {\n\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\tconst fork = await repo.fork(source.metadata, { id: \"fork\", scope: \"tree\" }, BACKGROUND_CONTEXT);\n\n\t\t\tstrictEqual(fork.metadata.id, \"fork\");\n\t\t\tstrictEqual(fork.metadata.parentSessionId, \"source\");\n\t\t\tstrictEqual(await fork.branch(\"main\", BACKGROUND_CONTEXT), undefined);\n\t\t\tstrictEqual(await fork.getValue(laneConfig(\"main\"), BACKGROUND_CONTEXT), undefined);\n\t\t\tstrictEqual(await fork.getValue(laneState(\"main\"), BACKGROUND_CONTEXT), undefined);\n\t\t\tdeepStrictEqual(await fork.findEntries(undefined, BACKGROUND_CONTEXT), []);\n\t\t\tdeepStrictEqual(await fork.getStats(BACKGROUND_CONTEXT), {\n\t\t\t\tmessageCount: 0,\n\t\t\t\tusage: {\n\t\t\t\t\tinput: 0,\n\t\t\t\t\toutput: 0,\n\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotalTokens: 0,\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: 0,\n\t\t\t\t\t\toutput: 0,\n\t\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t});\n\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), fork.close(BACKGROUND_CONTEXT)]);\n\t\t}),\n\t\tcreateCase(factory, \"forks\", \"rejects a data-only branch and releases its destination id\", async ({ repo }) => {\n\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\tawait source.createBranch(\"data\", null, BACKGROUND_CONTEXT);\n\n\t\t\tawait rejects(\n\t\t\t\trepo.fork(source.metadata, { id: \"destination\", scope: \"branch\", branch: \"data\" }, BACKGROUND_CONTEXT),\n\t\t\t);\n\t\t\tdeepStrictEqual(\n\t\t\t\t(await repo.list(undefined, BACKGROUND_CONTEXT)).map(({ id }) => id),\n\t\t\t\t[\"source\"],\n\t\t\t);\n\n\t\t\tconst destination = await repo.create({ id: \"destination\" }, BACKGROUND_CONTEXT);\n\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), destination.close(BACKGROUND_CONTEXT)]);\n\t\t}),\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"forks\",\n\t\t\t\"forks one named configured branch with scoped values and a zero ledger\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\t\tawait source.mutate(\n\t\t\t\t\t(mutator) =>\n\t\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\tinsertEntry({\n\t\t\t\t\t\t\t\t\tid: ROOT_ID,\n\t\t\t\t\t\t\t\t\tparentId: null,\n\t\t\t\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\t\t\t\tcustomType: \"root\",\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tinsertEntry({\n\t\t\t\t\t\t\t\t\tid: CHILD_ID,\n\t\t\t\t\t\t\t\t\tparentId: ROOT_ID,\n\t\t\t\t\t\t\t\t\ttype: \"message\",\n\t\t\t\t\t\t\t\t\tmessage: { role: \"user\", content: \"child\", timestamp: 1 },\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tinsertEntry({\n\t\t\t\t\t\t\t\t\tid: SIBLING_ID,\n\t\t\t\t\t\t\t\t\tparentId: ROOT_ID,\n\t\t\t\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\t\t\t\tcustomType: \"sibling\",\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tsetValue(branchTip(\"main\"), SIBLING_ID),\n\t\t\t\t\t\t\t\tsetValue(branchTip(\"review\"), CHILD_ID),\n\t\t\t\t\t\t\t\tsetValue(laneConfig(\"review\"), configuration),\n\t\t\t\t\t\t\t\tsetValue(laneState(\"review\"), {\n\t\t\t\t\t\t\t\t\tcurrentOperationId: OPERATION_ID,\n\t\t\t\t\t\t\t\t\tlastOperationId: \"previous\",\n\t\t\t\t\t\t\t\t\tinbox: [{ entryId: PENDING_ID, kind: \"write\" }],\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tsetValue(operationResult(\"previous\"), {\n\t\t\t\t\t\t\t\t\toperationId: \"previous\",\n\t\t\t\t\t\t\t\t\tkind: \"navigation\",\n\t\t\t\t\t\t\t\t\tstatus: \"completed\",\n\t\t\t\t\t\t\t\t\tfromTipId: ROOT_ID,\n\t\t\t\t\t\t\t\t\ttipId: CHILD_ID,\n\t\t\t\t\t\t\t\t\tstartedAt: 1,\n\t\t\t\t\t\t\t\t\tendedAt: 2,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tsetValue(sessionName, \"source name\"),\n\t\t\t\t\t\t\t\tsetValue(applicationValue, { copied: false }),\n\t\t\t\t\t\t\t\tappendList(applicationList, { copied: false }),\n\t\t\t\t\t\t\t\tsetValue(entryLabel(ROOT_ID), \"root label\"),\n\t\t\t\t\t\t\t\tsetValue(entryLabel(SIBLING_ID), \"sibling label\"),\n\t\t\t\t\t\t\t\tsetValue(pendingEntry(PENDING_ID), {\n\t\t\t\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\t\t\t\tcustomType: \"pending\",\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tsetValue(operationMeta(OPERATION_ID), {\n\t\t\t\t\t\t\t\t\toperationId: OPERATION_ID,\n\t\t\t\t\t\t\t\t\tlane: \"review\",\n\t\t\t\t\t\t\t\t\tsourceTipId: CHILD_ID,\n\t\t\t\t\t\t\t\t\tstartedAt: 1,\n\t\t\t\t\t\t\t\t\tintent: { kind: \"compaction\" },\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tsetValue(operationState(OPERATION_ID), {\n\t\t\t\t\t\t\t\t\tat: \"summary.deciding\",\n\t\t\t\t\t\t\t\t\tcontrol: { status: \"running\" },\n\t\t\t\t\t\t\t\t\tsettings: {\n\t\t\t\t\t\t\t\t\t\tcompaction: { enabled: true, reserveTokens: 1, keepRecentTokens: 1 },\n\t\t\t\t\t\t\t\t\t\tsteeringMode: \"all\",\n\t\t\t\t\t\t\t\t\t\tfollowUpMode: \"all\",\n\t\t\t\t\t\t\t\t\t\ttoolExecution: \"sequential\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tlatestAssistantEntryId: null,\n\t\t\t\t\t\t\t\t\ttask: { taskId: OPERATION_ID, reason: \"manual\", boundary: { kind: \"finish\" } },\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tsetValue(operationToolArgs(OPERATION_ID, ROOT_ID, 0), {\n\t\t\t\t\t\t\t\t\targument: true,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tsetValue(operationPreparation(OPERATION_ID, OPERATION_ID), {\n\t\t\t\t\t\t\t\t\tkind: \"compaction\",\n\t\t\t\t\t\t\t\t\tmessagesToSummarize: [],\n\t\t\t\t\t\t\t\t\tturnPrefixMessages: [],\n\t\t\t\t\t\t\t\t\tretainedTail: [],\n\t\t\t\t\t\t\t\t\tisSplitTurn: false,\n\t\t\t\t\t\t\t\t\ttokensBefore: 0,\n\t\t\t\t\t\t\t\t\tfileOps: { read: [], written: [], edited: [] },\n\t\t\t\t\t\t\t\t\tsettings: {\n\t\t\t\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\t\t\t\treserveTokens: 1,\n\t\t\t\t\t\t\t\t\t\tkeepRecentTokens: 1,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tinsertUsage(usageRow()),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t\t),\n\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t);\n\n\t\t\t\tconst fork = await repo.fork(\n\t\t\t\t\tsource.metadata,\n\t\t\t\t\t{ id: \"fork\", scope: \"branch\", branch: \"review\", entryId: CHILD_ID, position: \"at\" },\n\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t);\n\n\t\t\t\tdeepStrictEqual(\n\t\t\t\t\t(await fork.findEntries({ order: \"asc\" }, BACKGROUND_CONTEXT)).map(({ id }) => id),\n\t\t\t\t\t[ROOT_ID, CHILD_ID],\n\t\t\t\t);\n\t\t\t\tstrictEqual(await fork.branch(\"main\", BACKGROUND_CONTEXT), undefined);\n\t\t\t\tstrictEqual(await getBranchTip(fork, \"review\"), CHILD_ID);\n\t\t\t\tdeepStrictEqual((await fork.getValue(laneConfig(\"review\"), BACKGROUND_CONTEXT))?.value, configuration);\n\t\t\t\tdeepStrictEqual((await fork.getValue(laneState(\"review\"), BACKGROUND_CONTEXT))?.value, idleLaneState);\n\t\t\t\tstrictEqual(await fork.getValue(laneConfig(\"main\"), BACKGROUND_CONTEXT), undefined);\n\t\t\t\tstrictEqual(await fork.getValue(laneState(\"main\"), BACKGROUND_CONTEXT), undefined);\n\t\t\t\tstrictEqual(await fork.getName(BACKGROUND_CONTEXT), \"source name\");\n\t\t\t\tstrictEqual(await fork.getValue(applicationValue, BACKGROUND_CONTEXT), undefined);\n\t\t\t\tdeepStrictEqual(await fork.readList(applicationList, undefined, BACKGROUND_CONTEXT), []);\n\t\t\t\tstrictEqual(await fork.getLabel(ROOT_ID, BACKGROUND_CONTEXT), \"root label\");\n\t\t\t\tstrictEqual(await fork.getLabel(SIBLING_ID, BACKGROUND_CONTEXT), undefined);\n\t\t\t\tstrictEqual(await fork.getValue(operationResult(\"previous\"), BACKGROUND_CONTEXT), undefined);\n\t\t\t\tstrictEqual(await fork.getValue(pendingEntry(PENDING_ID), BACKGROUND_CONTEXT), undefined);\n\t\t\t\tstrictEqual(await fork.getValue(operationMeta(OPERATION_ID), BACKGROUND_CONTEXT), undefined);\n\t\t\t\tstrictEqual(await fork.getValue(operationState(OPERATION_ID), BACKGROUND_CONTEXT), undefined);\n\t\t\t\tstrictEqual(\n\t\t\t\t\tawait fork.getValue(operationToolArgs(OPERATION_ID, ROOT_ID, 0), BACKGROUND_CONTEXT),\n\t\t\t\t\tundefined,\n\t\t\t\t);\n\t\t\t\tstrictEqual(\n\t\t\t\t\tawait fork.getValue(operationPreparation(OPERATION_ID, OPERATION_ID), BACKGROUND_CONTEXT),\n\t\t\t\t\tundefined,\n\t\t\t\t);\n\t\t\t\tconst stats = await fork.getStats(BACKGROUND_CONTEXT);\n\t\t\t\tstrictEqual(stats.messageCount, 1);\n\t\t\t\tdeepStrictEqual(stats.usage, {\n\t\t\t\t\tinput: 0,\n\t\t\t\t\toutput: 0,\n\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotalTokens: 0,\n\t\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t\t});\n\t\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), fork.close(BACKGROUND_CONTEXT)]);\n\t\t\t},\n\t\t),\n\t\tcreateCase(factory, \"forks\", \"enforces branch ancestry for at and before placement\", async ({ repo }) => {\n\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\tawait source.mutate(\n\t\t\t\t(mutator) =>\n\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\tinsertEntry({ id: ROOT_ID, parentId: null, type: \"custom\", customType: \"root\" }),\n\t\t\t\t\t\t\tinsertEntry({ id: CHILD_ID, parentId: ROOT_ID, type: \"custom\", customType: \"child\" }),\n\t\t\t\t\t\t\tinsertEntry({ id: SIBLING_ID, parentId: ROOT_ID, type: \"custom\", customType: \"sibling\" }),\n\t\t\t\t\t\t\tsetValue(branchTip(\"main\"), CHILD_ID),\n\t\t\t\t\t\t\tsetValue(laneConfig(\"main\"), configuration),\n\t\t\t\t\t\t\tsetValue(laneState(\"main\"), idleLaneState),\n\t\t\t\t\t\t\tsetValue(branchTip(\"empty\"), null),\n\t\t\t\t\t\t\tsetValue(laneConfig(\"empty\"), configuration),\n\t\t\t\t\t\t\tsetValue(laneState(\"empty\"), idleLaneState),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t),\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\n\t\t\tconst before = await repo.fork(\n\t\t\t\tsource.metadata,\n\t\t\t\t{ id: \"before\", scope: \"branch\", branch: \"main\", entryId: CHILD_ID, position: \"before\" },\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\t\t\tstrictEqual(await getBranchTip(before), ROOT_ID);\n\t\t\tdeepStrictEqual(\n\t\t\t\t(await before.findEntries({ order: \"asc\" }, BACKGROUND_CONTEXT)).map(({ id }) => id),\n\t\t\t\t[ROOT_ID],\n\t\t\t);\n\n\t\t\tconst mid = await repo.fork(\n\t\t\t\tsource.metadata,\n\t\t\t\t{ id: \"mid\", scope: \"branch\", branch: \"main\", entryId: ROOT_ID, position: \"at\" },\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\t\t\tstrictEqual(await getBranchTip(mid), ROOT_ID);\n\t\t\tdeepStrictEqual(\n\t\t\t\t(await mid.findEntries({ order: \"asc\" }, BACKGROUND_CONTEXT)).map(({ id }) => id),\n\t\t\t\t[ROOT_ID],\n\t\t\t);\n\n\t\t\tconst beforeRoot = await repo.fork(\n\t\t\t\tsource.metadata,\n\t\t\t\t{ id: \"before-root\", scope: \"branch\", branch: \"main\", entryId: ROOT_ID, position: \"before\" },\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\t\t\tstrictEqual(await getBranchTip(beforeRoot), null);\n\t\t\tdeepStrictEqual(await beforeRoot.findEntries({ order: \"asc\" }, BACKGROUND_CONTEXT), []);\n\n\t\t\tconst empty = await repo.fork(\n\t\t\t\tsource.metadata,\n\t\t\t\t{ id: \"empty\", scope: \"branch\", branch: \"empty\" },\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\t\t\tstrictEqual(await getBranchTip(empty, \"empty\"), null);\n\t\t\tdeepStrictEqual(await empty.findEntries({ order: \"asc\" }, BACKGROUND_CONTEXT), []);\n\n\t\t\tfor (const [id, branch, entryId] of [\n\t\t\t\t[\"off-branch\", \"main\", SIBLING_ID],\n\t\t\t\t[\"unknown\", \"main\", UNKNOWN_ID],\n\t\t\t\t[\"null-tip\", \"empty\", ROOT_ID],\n\t\t\t] as const) {\n\t\t\t\tawait rejects(repo.fork(source.metadata, { id, scope: \"branch\", branch, entryId }, BACKGROUND_CONTEXT));\n\t\t\t}\n\t\t\tdeepStrictEqual((await repo.list(undefined, BACKGROUND_CONTEXT)).map(({ id }) => id).sort(), [\n\t\t\t\t\"before\",\n\t\t\t\t\"before-root\",\n\t\t\t\t\"empty\",\n\t\t\t\t\"mid\",\n\t\t\t\t\"source\",\n\t\t\t]);\n\t\t\tawait Promise.all([\n\t\t\t\tsource.close(BACKGROUND_CONTEXT),\n\t\t\t\tbefore.close(BACKGROUND_CONTEXT),\n\t\t\t\tbeforeRoot.close(BACKGROUND_CONTEXT),\n\t\t\t\tempty.close(BACKGROUND_CONTEXT),\n\t\t\t\tmid.close(BACKGROUND_CONTEXT),\n\t\t\t]);\n\t\t}),\n\t\tcreateCase(factory, \"forks\", \"forks a closed source session\", async ({ repo }) => {\n\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\tawait source.mutate(\n\t\t\t\t(mutator) =>\n\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\tinsertEntry({\n\t\t\t\t\t\t\t\tid: ROOT_ID,\n\t\t\t\t\t\t\t\tparentId: null,\n\t\t\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\t\t\tcustomType: \"root\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tsetValue(branchTip(\"main\"), ROOT_ID),\n\t\t\t\t\t\t\tsetValue(laneConfig(\"main\"), configuration),\n\t\t\t\t\t\t\tsetValue(laneState(\"main\"), idleLaneState),\n\t\t\t\t\t\t\tsetValue(applicationValue, \"excluded\"),\n\t\t\t\t\t\t\tappendList(applicationList, \"excluded\"),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t),\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\t\t\tawait source.close(BACKGROUND_CONTEXT);\n\n\t\t\tconst fork = await repo.fork(\n\t\t\t\tsource.metadata,\n\t\t\t\t{ id: \"fork\", scope: \"branch\", branch: \"main\" },\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\t\t\tstrictEqual(await getBranchTip(fork), ROOT_ID);\n\t\t\tdeepStrictEqual((await fork.getValue(laneConfig(\"main\"), BACKGROUND_CONTEXT))?.value, configuration);\n\t\t\tdeepStrictEqual((await fork.getValue(laneState(\"main\"), BACKGROUND_CONTEXT))?.value, idleLaneState);\n\t\t\tstrictEqual(await fork.getValue(applicationValue, BACKGROUND_CONTEXT), undefined);\n\t\t\tdeepStrictEqual(await fork.readList(applicationList, undefined, BACKGROUND_CONTEXT), []);\n\t\t\tawait fork.close(BACKGROUND_CONTEXT);\n\t\t}),\n\n\t\tcreateCase(factory, \"forks\", \"forks the whole configured tree with fresh lane state\", async ({ repo }) => {\n\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\tawait source.mutate(\n\t\t\t\t(mutator) =>\n\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\tinsertEntry({\n\t\t\t\t\t\t\t\tid: ROOT_ID,\n\t\t\t\t\t\t\t\tparentId: null,\n\t\t\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\t\t\tcustomType: \"root\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tinsertEntry({\n\t\t\t\t\t\t\t\tid: CHILD_ID,\n\t\t\t\t\t\t\t\tparentId: ROOT_ID,\n\t\t\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\t\t\tcustomType: \"child\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tinsertEntry({\n\t\t\t\t\t\t\t\tid: SIBLING_ID,\n\t\t\t\t\t\t\t\tparentId: ROOT_ID,\n\t\t\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\t\t\tcustomType: \"sibling\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tsetValue(branchTip(\"main\"), CHILD_ID),\n\t\t\t\t\t\t\tsetValue(laneConfig(\"main\"), configuration),\n\t\t\t\t\t\t\tsetValue(laneState(\"main\"), idleLaneState),\n\t\t\t\t\t\t\tsetValue(branchTip(\"review\"), SIBLING_ID),\n\t\t\t\t\t\t\tsetValue(laneConfig(\"review\"), configuration),\n\t\t\t\t\t\t\tsetValue(laneState(\"review\"), idleLaneState),\n\t\t\t\t\t\t\tsetValue(branchTip(\"notes\"), ROOT_ID),\n\t\t\t\t\t\t\tsetValue(applicationValue, { copied: true }),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t),\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\n\t\t\tconst fork = await repo.fork(source.metadata, { id: \"fork\", scope: \"tree\" }, BACKGROUND_CONTEXT);\n\n\t\t\tdeepStrictEqual(\n\t\t\t\t(await fork.findEntries({ order: \"asc\" }, BACKGROUND_CONTEXT)).map(({ id }) => id),\n\t\t\t\t[ROOT_ID, CHILD_ID, SIBLING_ID],\n\t\t\t);\n\t\t\tstrictEqual(await getBranchTip(fork), CHILD_ID);\n\t\t\tstrictEqual(await getBranchTip(fork, \"review\"), SIBLING_ID);\n\t\t\tstrictEqual(await getBranchTip(fork, \"notes\"), ROOT_ID);\n\t\t\tstrictEqual(await fork.getValue(laneConfig(\"notes\"), BACKGROUND_CONTEXT), undefined);\n\t\t\tstrictEqual(await fork.getValue(laneState(\"notes\"), BACKGROUND_CONTEXT), undefined);\n\t\t\tdeepStrictEqual((await fork.getValue(laneConfig(\"review\"), BACKGROUND_CONTEXT))?.value, configuration);\n\t\t\tdeepStrictEqual((await fork.getValue(laneState(\"review\"), BACKGROUND_CONTEXT))?.value, idleLaneState);\n\t\t\tdeepStrictEqual((await fork.getValue(applicationValue, BACKGROUND_CONTEXT))?.value, { copied: true });\n\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), fork.close(BACKGROUND_CONTEXT)]);\n\t\t}),\n\t\tcreateCase(factory, \"forks\", \"rejects only surviving unknown reserved scalar state\", async ({ repo }) => {\n\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\tawait source.createBranch(\"main\", null, BACKGROUND_CONTEXT);\n\t\t\tawait source.mutate(\n\t\t\t\t(mutator) =>\n\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t[setValue(laneConfig(\"main\"), configuration), setValue(laneState(\"main\"), idleLaneState)],\n\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t),\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\n\t\t\tfor (const namespace of [\"ion\", \"ion.unknown\"] as const) {\n\t\t\t\tconst address = value<JsonValue>(namespace);\n\t\t\t\tawait source.setValue(address, true, BACKGROUND_CONTEXT);\n\t\t\t\tawait rejects(repo.fork(source.metadata, { id: \"tree\", scope: \"tree\" }, BACKGROUND_CONTEXT));\n\t\t\t\tawait rejects(\n\t\t\t\t\trepo.fork(source.metadata, { id: \"branch\", scope: \"branch\", branch: \"main\" }, BACKGROUND_CONTEXT),\n\t\t\t\t);\n\t\t\t\tawait source.deleteValue(address, BACKGROUND_CONTEXT);\n\t\t\t}\n\t\t\tawait source.close(BACKGROUND_CONTEXT);\n\n\t\t\tconst tree = await repo.fork(source.metadata, { id: \"tree\", scope: \"tree\" }, BACKGROUND_CONTEXT);\n\t\t\tconst branch = await repo.fork(\n\t\t\t\tsource.metadata,\n\t\t\t\t{ id: \"branch\", scope: \"branch\", branch: \"main\" },\n\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t);\n\t\t\tawait Promise.all([tree.close(BACKGROUND_CONTEXT), branch.close(BACKGROUND_CONTEXT)]);\n\t\t}),\n\t];\n}\n\n/** WP08 cases enabled for Memory and JSONL while SQLite's streaming fork implementation is pending. */\nexport function createSessionRepoStreamingForkConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"fork\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\t// Merge into createSessionRepoForkConformance once SQLite supports these cases.\n\treturn [\n\t\t...createSessionRepoForkApplicationListConformance(backendFactory, onClose),\n\t\t...createSessionRepoBranchForkApplicationStateConformance(backendFactory, onClose),\n\t\t...createSessionRepoForkLaneValidationConformance(backendFactory, onClose),\n\t];\n}\n\nfunction createSessionRepoForkLaneValidationConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"fork\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\tconst factory = prepareRepoCaseFactory(backendFactory, onClose);\n\treturn [\n\t\tcreateCase(factory, \"fork lane validation\", \"ignores malformed unrelated lanes\", async ({ repo }) => {\n\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\tlet tree: Session<TMetadata> | undefined;\n\t\t\tlet branch: Session<TMetadata> | undefined;\n\t\t\ttry {\n\t\t\t\tawait source.createBranch(\"main\", null, BACKGROUND_CONTEXT);\n\t\t\t\tawait source.mutate(\n\t\t\t\t\t(mutator) =>\n\t\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\tsetValue(laneConfig(\"main\"), configuration),\n\t\t\t\t\t\t\t\tsetValue(laneState(\"main\"), idleLaneState),\n\t\t\t\t\t\t\t\tsetValue(laneConfig(\"unrelated\"), configuration),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t\t),\n\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t);\n\n\t\t\t\ttree = await repo.fork(source.metadata, { id: \"tree\", scope: \"tree\" }, BACKGROUND_CONTEXT);\n\t\t\t\tbranch = await repo.fork(\n\t\t\t\t\tsource.metadata,\n\t\t\t\t\t{ id: \"branch\", scope: \"branch\", branch: \"main\" },\n\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t);\n\t\t\t\tstrictEqual(await getBranchTip(branch), null);\n\t\t\t} finally {\n\t\t\t\tawait Promise.all([\n\t\t\t\t\tsource.close(BACKGROUND_CONTEXT),\n\t\t\t\t\ttree?.close(BACKGROUND_CONTEXT),\n\t\t\t\t\tbranch?.close(BACKGROUND_CONTEXT),\n\t\t\t\t]);\n\t\t\t}\n\t\t}),\n\t];\n}\n\n/** Creates application-list fork cases. */\nfunction createSessionRepoForkApplicationListConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"fork\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\tconst factory = prepareRepoCaseFactory(backendFactory, onClose);\n\treturn ([\"open\", \"closed\"] as const).flatMap((sourceState) => [\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t`fork application lists (${sourceState} source)`,\n\t\t\t\"tree fork copies lists at distinct addresses\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\t// Each list keeps its own contents: keys \"\" and \"other\" in one namespace must not merge.\n\t\t\t\t// A different namespace also stays separate; pi2.events is application-owned, not reserved.\n\t\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\t\tlet fork: Session<TMetadata> | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tconst events = list<string>(\"test.application.events\");\n\t\t\t\t\tconst sibling = list<string>(events.namespace, \"other\");\n\t\t\t\t\tconst otherNamespace = list<string>(\"pi2.events\");\n\t\t\t\t\tconst absent = list<string>(events.namespace, \"absent\");\n\t\t\t\t\tawait source.appendList(events, \"event\", BACKGROUND_CONTEXT);\n\t\t\t\t\tawait source.appendList(sibling, \"sibling\", BACKGROUND_CONTEXT);\n\t\t\t\t\tawait source.appendList(otherNamespace, \"other namespace\", BACKGROUND_CONTEXT);\n\n\t\t\t\t\tif (sourceState === \"closed\") await source.close(BACKGROUND_CONTEXT);\n\t\t\t\t\tfork = await repo.fork(source.metadata, { id: \"fork\", scope: \"tree\" }, BACKGROUND_CONTEXT);\n\n\t\t\t\t\tdeepStrictEqual(\n\t\t\t\t\t\t(await fork.readList(events, undefined, BACKGROUND_CONTEXT)).map(({ value }) => value),\n\t\t\t\t\t\t[\"event\"],\n\t\t\t\t\t);\n\t\t\t\t\tdeepStrictEqual(\n\t\t\t\t\t\t(await fork.readList(sibling, undefined, BACKGROUND_CONTEXT)).map(({ value }) => value),\n\t\t\t\t\t\t[\"sibling\"],\n\t\t\t\t\t);\n\t\t\t\t\tdeepStrictEqual(\n\t\t\t\t\t\t(await fork.readList(otherNamespace, undefined, BACKGROUND_CONTEXT)).map(({ value }) => value),\n\t\t\t\t\t\t[\"other namespace\"],\n\t\t\t\t\t);\n\t\t\t\t\tdeepStrictEqual(await fork.readList(absent, undefined, BACKGROUND_CONTEXT), []);\n\t\t\t\t} finally {\n\t\t\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), fork?.close(BACKGROUND_CONTEXT)]);\n\t\t\t\t}\n\t\t\t},\n\t\t),\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t`fork application lists (${sourceState} source)`,\n\t\t\t\"tree fork copies only survivors after list deletion and reappend\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\t// Append old -> delete -> append new must copy only new, even when changes share a transaction.\n\t\t\t\t// A list deleted without a later append must remain empty in the fork.\n\t\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\t\tlet fork: Session<TMetadata> | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tconst events = list<string>(\"test.application.events\");\n\t\t\t\t\tconst deleted = list<string>(events.namespace, \"deleted\");\n\t\t\t\t\tawait source.appendList(events, \"old\", BACKGROUND_CONTEXT);\n\t\t\t\t\tawait source.appendList(deleted, \"removed\", BACKGROUND_CONTEXT);\n\t\t\t\t\tawait source.mutate(\n\t\t\t\t\t\t(mutator) =>\n\t\t\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\tdeleteList(events),\n\t\t\t\t\t\t\t\t\tappendList(events, \"temporary\"),\n\t\t\t\t\t\t\t\t\tdeleteList(events),\n\t\t\t\t\t\t\t\t\tappendList(events, \"first survivor\"),\n\t\t\t\t\t\t\t\t\tdeleteList(deleted),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t);\n\t\t\t\t\tawait source.appendList(events, \"second survivor\", BACKGROUND_CONTEXT);\n\n\t\t\t\t\tif (sourceState === \"closed\") await source.close(BACKGROUND_CONTEXT);\n\t\t\t\t\tfork = await repo.fork(source.metadata, { id: \"fork\", scope: \"tree\" }, BACKGROUND_CONTEXT);\n\n\t\t\t\t\tdeepStrictEqual(\n\t\t\t\t\t\t(await fork.readList(events, undefined, BACKGROUND_CONTEXT)).map(({ value }) => value),\n\t\t\t\t\t\t[\"first survivor\", \"second survivor\"],\n\t\t\t\t\t);\n\t\t\t\t\tdeepStrictEqual(await fork.readList(deleted, undefined, BACKGROUND_CONTEXT), []);\n\t\t\t\t} finally {\n\t\t\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), fork?.close(BACKGROUND_CONTEXT)]);\n\t\t\t\t}\n\t\t\t},\n\t\t),\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t`fork application lists (${sourceState} source)`,\n\t\t\t\"tree fork preserves list element sequences including gaps\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\t// Other writes consume sequences too. List elements at seq 2 and 4 must stay at 2 and 4\n\t\t\t\t// in the fork, not be renumbered to 1 and 2.\n\t\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\t\tlet fork: Session<TMetadata> | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tconst events = list<string>(\"test.application.events\");\n\t\t\t\t\tconst committed = await source.mutate(\n\t\t\t\t\t\t(mutator) =>\n\t\t\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\tsetValue(sessionName, \"before\"),\n\t\t\t\t\t\t\t\t\tappendList(events, \"first\"),\n\t\t\t\t\t\t\t\t\tsetValue(sessionName, \"between\"),\n\t\t\t\t\t\t\t\t\tappendList(events, \"second\"),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (sourceState === \"closed\") await source.close(BACKGROUND_CONTEXT);\n\t\t\t\t\tfork = await repo.fork(source.metadata, { id: \"fork\", scope: \"tree\" }, BACKGROUND_CONTEXT);\n\n\t\t\t\t\tdeepStrictEqual(await fork.readList(events, undefined, BACKGROUND_CONTEXT), [\n\t\t\t\t\t\t{ seq: committed.seqs[1]!, value: \"first\" },\n\t\t\t\t\t\t{ seq: committed.seqs[3]!, value: \"second\" },\n\t\t\t\t\t]);\n\t\t\t\t} finally {\n\t\t\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), fork?.close(BACKGROUND_CONTEXT)]);\n\t\t\t\t}\n\t\t\t},\n\t\t),\n\t\t...([\"asc\", \"desc\"] as const).map((order) =>\n\t\t\tcreateCase(\n\t\t\t\tfactory,\n\t\t\t\t`fork application lists (${sourceState} source)`,\n\t\t\t\t`tree fork continues ${order} pagination using source cursors`,\n\t\t\t\tasync ({ repo }) => {\n\t\t\t\t\t// A cursor from the source must resume at the same place in the fork. After reading\n\t\t\t\t\t// [first, second] ascending, continue with [third]; descending does the reverse.\n\t\t\t\t\t// Continuing after the final element must return an empty page, not repeat that element.\n\t\t\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\t\t\tlet fork: Session<TMetadata> | undefined;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst events = list<string>(\"test.application.events\");\n\t\t\t\t\t\tfor (const item of [\"first\", \"second\", \"third\"]) {\n\t\t\t\t\t\t\tawait source.appendList(events, item, BACKGROUND_CONTEXT);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst firstPage = await source.readList(events, { order, limit: 2 }, BACKGROUND_CONTEXT);\n\t\t\t\t\t\tstrictEqual(firstPage.length, 2);\n\t\t\t\t\t\tconst cursor = { seq: firstPage[1]!.seq };\n\t\t\t\t\t\tconst lastPage = await source.readList(events, { order, cursor, limit: 2 }, BACKGROUND_CONTEXT);\n\t\t\t\t\t\tdeepStrictEqual(\n\t\t\t\t\t\t\tlastPage.map(({ value }) => value),\n\t\t\t\t\t\t\t[order === \"asc\" ? \"third\" : \"first\"],\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst endCursor = { seq: lastPage[0]!.seq };\n\n\t\t\t\t\t\tif (sourceState === \"closed\") await source.close(BACKGROUND_CONTEXT);\n\t\t\t\t\t\tfork = await repo.fork(source.metadata, { id: \"fork\", scope: \"tree\" }, BACKGROUND_CONTEXT);\n\n\t\t\t\t\t\tdeepStrictEqual(await fork.readList(events, { order, limit: 2 }, BACKGROUND_CONTEXT), firstPage);\n\t\t\t\t\t\tdeepStrictEqual(\n\t\t\t\t\t\t\tawait fork.readList(events, { order, cursor, limit: 2 }, BACKGROUND_CONTEXT),\n\t\t\t\t\t\t\tlastPage,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tdeepStrictEqual(\n\t\t\t\t\t\t\tawait fork.readList(events, { order, cursor: endCursor, limit: 2 }, BACKGROUND_CONTEXT),\n\t\t\t\t\t\t\t[],\n\t\t\t\t\t\t);\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), fork?.close(BACKGROUND_CONTEXT)]);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t),\n\t\t),\n\t]);\n}\n\n/** Creates branch application-state cases. */\nfunction createSessionRepoBranchForkApplicationStateConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"fork\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\tconst factory = prepareRepoCaseFactory(backendFactory, onClose);\n\treturn ([\"open\", \"closed\"] as const).flatMap((sourceState) => [\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t`branch fork application state (${sourceState} source)`,\n\t\t\t\"excludes overwritten and unchanged application values\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\t// WP08 §1.1: set v1 -> fork point -> overwrite with v2. A branch fork copies neither\n\t\t\t\t// version: it cannot reconstruct v1 and must not copy v2. Even unchanged older values\n\t\t\t\t// are excluded, so filtering current values by the fork point's sequence is not enough.\n\t\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\t\tlet fork: Session<TMetadata> | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tconst state = value<string>(\"test.application.state\");\n\t\t\t\t\tconst unchanged = value<string>(\"test.application.settings\");\n\t\t\t\t\tconst branch = await source.createBranch(\"review\", null, BACKGROUND_CONTEXT);\n\t\t\t\t\tawait source.mutate(\n\t\t\t\t\t\t(mutator) =>\n\t\t\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\tsetValue(laneConfig(\"review\"), configuration),\n\t\t\t\t\t\t\t\t\tsetValue(laneState(\"review\"), idleLaneState),\n\t\t\t\t\t\t\t\t\tsetValue(state, \"v1\"),\n\t\t\t\t\t\t\t\t\tsetValue(unchanged, \"predates fork point\"),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t);\n\t\t\t\t\tconst entryId = await branch.appendCustomEntry(\"fork-point\", undefined, BACKGROUND_CONTEXT);\n\t\t\t\t\tawait source.setValue(state, \"v2\", BACKGROUND_CONTEXT);\n\t\t\t\t\tstrictEqual((await source.getValue(state, BACKGROUND_CONTEXT))?.value, \"v2\");\n\n\t\t\t\t\tif (sourceState === \"closed\") await source.close(BACKGROUND_CONTEXT);\n\t\t\t\t\tfork = await repo.fork(\n\t\t\t\t\t\tsource.metadata,\n\t\t\t\t\t\t{ scope: \"branch\", branch: \"review\", entryId },\n\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t);\n\n\t\t\t\t\tstrictEqual(await getBranchTip(fork, \"review\"), entryId);\n\t\t\t\t\tstrictEqual(await fork.getValue(state, BACKGROUND_CONTEXT), undefined);\n\t\t\t\t\t// A survivor older than the fork point must also be excluded, not copied by a seq cutoff.\n\t\t\t\t\tstrictEqual(await fork.getValue(unchanged, BACKGROUND_CONTEXT), undefined);\n\t\t\t\t} finally {\n\t\t\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), fork?.close(BACKGROUND_CONTEXT)]);\n\t\t\t\t}\n\t\t\t},\n\t\t),\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t`branch fork application state (${sourceState} source)`,\n\t\t\t\"excludes deleted/reappended and untouched application lists\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\t// WP08 §1.1: append old -> fork point -> delete -> append new. A branch fork copies\n\t\t\t\t// neither the deleted elements nor the new ones. A separate untouched list is also\n\t\t\t\t// excluded, even though its elements still survive from before the fork point.\n\t\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\t\tlet fork: Session<TMetadata> | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tconst events = list<string>(\"test.application.events\");\n\t\t\t\t\tconst untouched = list<string>(events.namespace, \"untouched\");\n\t\t\t\t\tconst branch = await source.createBranch(\"review\", null, BACKGROUND_CONTEXT);\n\t\t\t\t\tawait source.mutate(\n\t\t\t\t\t\t(mutator) =>\n\t\t\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\tsetValue(laneConfig(\"review\"), configuration),\n\t\t\t\t\t\t\t\t\tsetValue(laneState(\"review\"), idleLaneState),\n\t\t\t\t\t\t\t\t\tappendList(events, \"old first\"),\n\t\t\t\t\t\t\t\t\tappendList(events, \"old second\"),\n\t\t\t\t\t\t\t\t\tappendList(untouched, \"predates fork point\"),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t);\n\t\t\t\t\tconst entryId = await branch.appendCustomEntry(\"fork-point\", undefined, BACKGROUND_CONTEXT);\n\t\t\t\t\tawait source.deleteList(events, BACKGROUND_CONTEXT);\n\t\t\t\t\tawait source.appendList(events, \"new\", BACKGROUND_CONTEXT);\n\t\t\t\t\tdeepStrictEqual(\n\t\t\t\t\t\t(await source.readList(events, undefined, BACKGROUND_CONTEXT)).map(({ value }) => value),\n\t\t\t\t\t\t[\"new\"],\n\t\t\t\t\t);\n\n\t\t\t\t\tif (sourceState === \"closed\") await source.close(BACKGROUND_CONTEXT);\n\t\t\t\t\tfork = await repo.fork(\n\t\t\t\t\t\tsource.metadata,\n\t\t\t\t\t\t{ scope: \"branch\", branch: \"review\", entryId },\n\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t);\n\n\t\t\t\t\tstrictEqual(await getBranchTip(fork, \"review\"), entryId);\n\t\t\t\t\tdeepStrictEqual(await fork.readList(events, undefined, BACKGROUND_CONTEXT), []);\n\t\t\t\t\t// Even elements still surviving from before the fork point are excluded.\n\t\t\t\t\tdeepStrictEqual(await fork.readList(untouched, undefined, BACKGROUND_CONTEXT), []);\n\t\t\t\t} finally {\n\t\t\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), fork?.close(BACKGROUND_CONTEXT)]);\n\t\t\t\t}\n\t\t\t},\n\t\t),\n\t]);\n}\n\n/** Creates fork cases that require destination reservation across create and fork. */\nexport function createSessionRepoForkDestinationReservationConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"fork\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\tconst factory = prepareRepoCaseFactory(backendFactory, onClose);\n\treturn [\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"fork coordination\",\n\t\t\t\"publishes create when it reserves a shared destination id first\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\t\tconst results = await Promise.allSettled([\n\t\t\t\t\trepo.create({ id: \"destination\" }, BACKGROUND_CONTEXT),\n\t\t\t\t\trepo.fork(source.metadata, { id: \"destination\", scope: \"tree\" }, BACKGROUND_CONTEXT),\n\t\t\t\t]);\n\t\t\t\tstrictEqual(results[0].status, \"fulfilled\");\n\t\t\t\tstrictEqual(results[1].status, \"rejected\");\n\t\t\t\tif (results[0].status === \"fulfilled\") await results[0].value.close(BACKGROUND_CONTEXT);\n\t\t\t\tawait source.close(BACKGROUND_CONTEXT);\n\t\t\t},\n\t\t),\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"fork coordination\",\n\t\t\t\"publishes fork when it reserves a shared destination id first\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\t\tconst results = await Promise.allSettled([\n\t\t\t\t\trepo.fork(source.metadata, { id: \"destination\", scope: \"tree\" }, BACKGROUND_CONTEXT),\n\t\t\t\t\trepo.create({ id: \"destination\" }, BACKGROUND_CONTEXT),\n\t\t\t\t]);\n\t\t\t\tstrictEqual(results[0].status, \"fulfilled\");\n\t\t\t\tstrictEqual(results[1].status, \"rejected\");\n\t\t\t\tif (results[0].status === \"fulfilled\") await results[0].value.close(BACKGROUND_CONTEXT);\n\t\t\t\tawait source.close(BACKGROUND_CONTEXT);\n\t\t\t},\n\t\t),\n\t];\n}\n\n/** Creates fork cases that require a snapshot boundary on an active source storage queue. */\nexport function createSessionRepoForkSourceSnapshotConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"fork\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\tconst factory = prepareRepoCaseFactory(backendFactory, onClose);\n\treturn [\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"fork coordination\",\n\t\t\t\"captures one coherent boundary between source commits\",\n\t\t\tasync ({ repo }) => {\n\t\t\t\tconst source = await repo.create({ id: \"source\" }, BACKGROUND_CONTEXT);\n\t\t\t\tconst firstMutation = await source.beginMutation(BACKGROUND_CONTEXT);\n\t\t\t\tconst firstCommit = firstMutation.commit(\n\t\t\t\t\t[\n\t\t\t\t\t\tinsertEntry({\n\t\t\t\t\t\t\tid: ROOT_ID,\n\t\t\t\t\t\t\tparentId: null,\n\t\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\t\tcustomType: \"first\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tsetValue(branchTip(\"main\"), ROOT_ID),\n\t\t\t\t\t\tsetValue(laneConfig(\"main\"), configuration),\n\t\t\t\t\t\tsetValue(laneState(\"main\"), idleLaneState),\n\t\t\t\t\t\tsetValue(sessionName, \"first name\"),\n\t\t\t\t\t\tsetValue(entryLabel(ROOT_ID), \"first label\"),\n\t\t\t\t\t],\n\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t);\n\t\t\t\tconst fork = repo.fork(\n\t\t\t\t\tsource.metadata,\n\t\t\t\t\t{ id: \"fork\", scope: \"branch\", branch: \"main\" },\n\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t);\n\t\t\t\tconst secondCommit = source.mutate(\n\t\t\t\t\t(mutator) =>\n\t\t\t\t\t\tmutator.commit(\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\tinsertEntry({\n\t\t\t\t\t\t\t\t\tid: CHILD_ID,\n\t\t\t\t\t\t\t\t\tparentId: ROOT_ID,\n\t\t\t\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\t\t\t\tcustomType: \"second\",\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tsetValue(branchTip(\"main\"), CHILD_ID),\n\t\t\t\t\t\t\t\tsetValue(sessionName, \"second name\"),\n\t\t\t\t\t\t\t\tsetValue(entryLabel(ROOT_ID), \"second label\"),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t\t\t),\n\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t);\n\n\t\t\t\tconst [, forked] = await Promise.all([firstCommit, fork]);\n\t\t\t\tawait firstMutation.end(BACKGROUND_CONTEXT);\n\t\t\t\tawait secondCommit;\n\t\t\t\tstrictEqual(await getBranchTip(forked), ROOT_ID);\n\t\t\t\tdeepStrictEqual(\n\t\t\t\t\t(await forked.findEntries({ order: \"asc\" }, BACKGROUND_CONTEXT)).map(({ id }) => id),\n\t\t\t\t\t[ROOT_ID],\n\t\t\t\t);\n\t\t\t\tstrictEqual(await forked.getName(BACKGROUND_CONTEXT), \"first name\");\n\t\t\t\tstrictEqual(await forked.getLabel(ROOT_ID, BACKGROUND_CONTEXT), \"first label\");\n\t\t\t\tawait Promise.all([source.close(BACKGROUND_CONTEXT), forked.close(BACKGROUND_CONTEXT)]);\n\t\t\t},\n\t\t),\n\t];\n}\n\n/** Creates every fork coordination case. */\nexport function createSessionRepoForkCoordinationConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"fork\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\treturn [\n\t\t...createSessionRepoForkDestinationReservationConformance(backendFactory, onClose),\n\t\t...createSessionRepoForkSourceSnapshotConformance(backendFactory, onClose),\n\t];\n}\n\n/** Creates every fork conformance case. */\nexport function createSessionRepoForkConformance<TMetadata extends SessionMetadata>(\n\tbackendFactory: () => Promise<Pick<SessionRepo<TMetadata>, \"create\" | \"list\" | \"fork\">>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\treturn [\n\t\t...createSessionRepoForkBehaviorConformance(backendFactory, onClose),\n\t\t...createSessionRepoForkCoordinationConformance(backendFactory, onClose),\n\t];\n}\n\n/** Creates every SessionRepo conformance case. */\nexport function createSessionRepoConformance<TMetadata extends SessionMetadata>(\n\tfactory: () => Promise<SessionRepo<TMetadata>>,\n\tonClose?: () => void | Promise<void>,\n): readonly ConformanceCase[] {\n\treturn [\n\t\t...createSessionRepoLifecycleConformance(factory, onClose),\n\t\t...createSessionRepoOwnershipConformance(factory, onClose),\n\t\t...createSessionRepoMessageConformance(factory, onClose),\n\t\t...createSessionRepoForkConformance(factory, onClose),\n\t];\n}\n"]}
|