@sema-agent/server 7.146.0 → 7.148.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/MIGRATION.md +1423 -24
- package/README.md +21 -8
- package/README.zh-CN.md +15 -9
- package/USAGE.md +537 -216
- package/deploy/facts/lanes.json +7 -0
- package/deploy/sema-up/README.md +18 -7
- package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
- package/deploy/sema-up/chart/templates/_helpers.tpl +42 -4
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -4
- package/deploy/sema-up/chart/templates/server.yaml +5 -4
- package/deploy/sema-up/chart/values.yaml +4 -3
- package/deploy/sema-up/cluster-up.sh +75 -176
- package/deploy/sema-up/kube-up.sh +29 -49
- package/deploy/sema-up/lib/chart-run.sh +23 -0
- package/deploy/sema-up/lib/dataroot.sh +3 -3
- package/deploy/sema-up/lib/flagshape.sh +4 -0
- package/deploy/sema-up/lib/health.sh +62 -13
- package/deploy/sema-up/lib/helm-values-cluster.py +18 -0
- package/deploy/sema-up/lib/helm-values-kube.py +35 -0
- package/deploy/sema-up/lib/lanes.json +2 -5
- package/deploy/sema-up/lib/regcred.py +8 -0
- package/deploy/sema-up/lib/remote/helm-install.sh +3 -0
- package/deploy/sema-up/lib/remote/image-import.sh +6 -0
- package/deploy/sema-up/lib/remote/machine-precheck.sh +11 -0
- package/deploy/sema-up/lib/remote/region-probe.sh +3 -0
- package/deploy/sema-up/lib/remote/stack-ready.sh +26 -0
- package/deploy/sema-up/lib/remote/vxlan-guard.sh +8 -0
- package/deploy/sema-up/lib/render-stack.py +17 -0
- package/deploy/sema-up/lib/shell-text.sh +87 -0
- package/deploy/sema-up/lib/usage-flags.txt +10 -0
- package/deploy/sema-up/lib/usage-head.txt +1 -0
- package/deploy/sema-up/lib/usage-tail.txt +13 -0
- package/deploy/sema-up/preflight.sh +10 -8
- package/deploy/sema-up/sema-up.sh +26 -48
- package/deploy/sema-up/smoke.sh +7 -6
- package/deploy/sema-up/stack/compose.dataroot.yaml +3 -0
- package/deploy/sema-up/stack/compose.yaml +18 -5
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +25 -3
- package/dist/adoption/plan.d.ts +24 -0
- package/dist/adoption/plan.js +53 -1
- package/dist/adoption/runner.d.ts +10 -1
- package/dist/adoption/runner.js +36 -16
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +32 -34
- package/dist/approval-gate-currency.js +3 -2
- package/dist/approval-reconciler.d.ts +4 -13
- package/dist/approval-reconciler.js +2 -2
- package/dist/approval.d.ts +16 -14
- package/dist/approval.js +3 -3
- package/dist/audit.d.ts +10 -3
- package/dist/audit.js +17 -9
- package/dist/auth-bridge.d.ts +0 -14
- package/dist/auth-bridge.js +0 -7
- package/dist/auto-mode-face.d.ts +10 -7
- package/dist/auto-mode-face.js +7 -4
- package/dist/background-agent-name.d.ts +19 -1
- package/dist/background-agent-name.js +12 -2
- package/dist/background-session-reap.d.ts +1 -1
- package/dist/bake-runner/main.js +9 -6
- package/dist/bench/s1/arms.js +6 -5
- package/dist/bench/s1/live-deps.js +8 -3
- package/dist/bench/s1/run-firm.js +6 -5
- package/dist/bind-host.d.ts +25 -0
- package/dist/bind-host.js +25 -0
- package/dist/boot/adoption.d.ts +3 -0
- package/dist/boot/adoption.js +1 -0
- package/dist/boot/crash-last.js +2 -1
- package/dist/boot/deployment-mods.d.ts +1 -1
- package/dist/boot/device-lane.js +5 -5
- package/dist/boot/engine-lease.d.ts +12 -11
- package/dist/boot/execution-env.d.ts +19 -8
- package/dist/boot/execution-env.js +80 -62
- package/dist/boot/execution-lane.d.ts +37 -9
- package/dist/boot/execution-lane.js +74 -20
- package/dist/boot/exit-boundary.d.ts +3 -3
- package/dist/boot/exit-boundary.js +6 -5
- package/dist/boot/exit-last.js +2 -1
- package/dist/boot/fatal-exit.d.ts +3 -5
- package/dist/boot/fatal-exit.js +2 -2
- package/dist/boot/governance-seams.d.ts +2 -2
- package/dist/boot/leader.d.ts +15 -0
- package/dist/boot/leader.js +18 -16
- package/dist/boot/memory-boundary.d.ts +11 -11
- package/dist/boot/memory-boundary.js +3 -3
- package/dist/boot/memory-consolidation.d.ts +50 -53
- package/dist/boot/memory-consolidation.js +11 -18
- package/dist/boot/org-memory.js +4 -3
- package/dist/boot/parked-revive-gate.d.ts +1 -1
- package/dist/boot/parked-revive-gate.js +3 -3
- package/dist/boot/reapers.d.ts +4 -3
- package/dist/boot/reapers.js +20 -17
- package/dist/boot/resolve-spec.d.ts +5 -3
- package/dist/boot/resolve-spec.js +133 -165
- package/dist/boot/retention-lane.d.ts +7 -45
- package/dist/boot/retention-lane.js +2 -2
- package/dist/boot/runner-deps.d.ts +28 -7
- package/dist/boot/runner-deps.js +11 -4
- package/dist/boot/runtime-caps.d.ts +2 -2
- package/dist/boot/runtime-caps.js +16 -2
- package/dist/boot/session-faces.d.ts +17 -7
- package/dist/boot/session-faces.js +122 -96
- package/dist/boot/shutdown.d.ts +5 -4
- package/dist/boot/shutdown.js +6 -4
- package/dist/boot/stage-01-config.d.ts +1 -1
- package/dist/boot/stage-01-config.js +14 -2
- package/dist/boot/stage-02-web-admin-pull.d.ts +2 -1
- package/dist/boot/stage-02-web-admin-pull.js +15 -8
- package/dist/boot/stage-03-store-backend.d.ts +4 -1
- package/dist/boot/stage-03-store-backend.js +4 -3
- package/dist/boot/stage-04-stores.d.ts +3 -1
- package/dist/boot/stage-04-stores.js +2 -2
- package/dist/boot/stage-05-execution-env.d.ts +9 -5
- package/dist/boot/stage-05-execution-env.js +5 -5
- package/dist/boot/stage-06-runners.d.ts +13 -15
- package/dist/boot/stage-06-runners.js +24 -18
- package/dist/boot/stage-07-capability-layer.d.ts +14 -11
- package/dist/boot/stage-07-capability-layer.js +9 -12
- package/dist/boot/stage-08-reapers.d.ts +14 -11
- package/dist/boot/stage-08-reapers.js +3 -3
- package/dist/boot/stage-09-leader.d.ts +16 -18
- package/dist/boot/stage-09-leader.js +12 -4
- package/dist/boot/stage-10-http-server.d.ts +16 -18
- package/dist/boot/stage-10-http-server.js +9 -5
- package/dist/boot/store-evidence.d.ts +20 -0
- package/dist/boot/store-evidence.js +13 -0
- package/dist/boot/stores.d.ts +12 -0
- package/dist/boot/stores.js +39 -17
- package/dist/boot/task-list-lane.d.ts +4 -4
- package/dist/boot/web-admin.d.ts +4 -2
- package/dist/boot/web-admin.js +58 -38
- package/dist/boot/webfetch-summarize-lane.d.ts +1 -1
- package/dist/boot-reclaim.d.ts +3 -1
- package/dist/boot-reclaim.js +1 -1
- package/dist/brain.d.ts +7 -18
- package/dist/brain.js +5 -17
- package/dist/budget.js +6 -6
- package/dist/capabilities/center-plugins.d.ts +9 -1
- package/dist/capabilities/center-plugins.js +37 -11
- package/dist/capabilities/center-wiring.js +1 -1
- package/dist/capabilities/composite-members.d.ts +3 -2
- package/dist/capabilities/composite-members.js +3 -2
- package/dist/capabilities/execution-lane.d.ts +7 -7
- package/dist/capabilities/execution-lane.js +2 -2
- package/dist/capabilities/hands-lane.d.ts +1 -1
- package/dist/capabilities/repo-tools.d.ts +2 -0
- package/dist/capabilities/repo-tools.js +14 -12
- package/dist/capabilities/scenarios.d.ts +38 -33
- package/dist/capabilities/scenarios.js +24 -25
- package/dist/capabilities/settings-contract.d.ts +29 -0
- package/dist/capabilities/settings-contract.js +14 -0
- package/dist/capabilities/skills.d.ts +6 -6
- package/dist/capabilities/tool-defer.d.ts +1 -1
- package/dist/capability-facts.d.ts +69 -4
- package/dist/capability-facts.js +77 -5
- package/dist/card-void.d.ts +4 -4
- package/dist/card-void.js +1 -1
- package/dist/center-credential-renewer.d.ts +6 -2
- package/dist/center-credential-renewer.js +10 -5
- package/dist/config-catalog.d.ts +16 -1
- package/dist/config-catalog.js +68 -61
- package/dist/config-dir-trust.d.ts +37 -4
- package/dist/config-dir-trust.js +88 -44
- package/dist/config-invariants.d.ts +58 -4
- package/dist/config-invariants.js +104 -23
- package/dist/config-lkg.d.ts +24 -2
- package/dist/config-lkg.js +22 -9
- package/dist/config-provider.d.ts +9 -9
- package/dist/config-provider.js +25 -108
- package/dist/config-pull-failure.d.ts +50 -0
- package/dist/config-pull-failure.js +48 -0
- package/dist/config-types.d.ts +215 -202
- package/dist/config.d.ts +69 -14
- package/dist/config.js +188 -96
- package/dist/degenerate-instrument.js +1 -1
- package/dist/delegated-session-owner.d.ts +1 -1
- package/dist/deleted-session-ids.d.ts +3 -2
- package/dist/deploy-facts.js +3 -2
- package/dist/deployment-governance.d.ts +3 -3
- package/dist/deployment-shape.d.ts +51 -16
- package/dist/deployment-shape.js +31 -6
- package/dist/egress-redirect.d.ts +25 -7
- package/dist/egress-redirect.js +20 -2
- package/dist/elicitation.d.ts +6 -5
- package/dist/elicitation.js +23 -22
- package/dist/engine-identity.d.ts +28 -0
- package/dist/engine-identity.js +10 -0
- package/dist/entry-problems.d.ts +5 -7
- package/dist/entry-problems.js +1 -1
- package/dist/env-facts.d.ts +7 -5
- package/dist/env-facts.js +12 -7
- package/dist/env-witness.d.ts +7 -0
- package/dist/env-witness.js +16 -0
- package/dist/execution-config-root.d.ts +5 -5
- package/dist/execution-config-root.js +5 -5
- package/dist/execution-env-purpose.d.ts +14 -0
- package/dist/execution-env-purpose.js +29 -0
- package/dist/execution-lane-caps.d.ts +53 -103
- package/dist/execution-lane-caps.js +51 -83
- package/dist/execution-placement.d.ts +65 -9
- package/dist/execution-placement.js +58 -0
- package/dist/field-conflict.d.ts +7 -11
- package/dist/field-conflict.js +3 -3
- package/dist/file-history-capture.d.ts +3 -3
- package/dist/file-history-disclosure.d.ts +2 -2
- package/dist/file-history-disclosure.js +1 -0
- package/dist/fleet/drain-entry.d.ts +17 -10
- package/dist/fleet/drain-entry.js +1 -1
- package/dist/fleet/fleet-bus.d.ts +21 -3
- package/dist/fleet/fleet-bus.js +35 -25
- package/dist/fleet/fleet-reconciler.js +2 -2
- package/dist/fleet/serving-lease.js +6 -7
- package/dist/fleet-client.js +3 -3
- package/dist/fleet-lease.d.ts +0 -1
- package/dist/fleet-lease.js +4 -5
- package/dist/history-anchor.d.ts +42 -0
- package/dist/history-anchor.js +34 -0
- package/dist/hitl-delivery.d.ts +90 -0
- package/dist/hitl-delivery.js +98 -0
- package/dist/hooks/classic-stations.d.ts +8 -11
- package/dist/hooks/classic-stations.js +0 -7
- package/dist/hooks/command-spawn.d.ts +1 -4
- package/dist/hooks/command-spawn.js +0 -11
- package/dist/hooks/hook-llm.d.ts +5 -1
- package/dist/hooks/hook-llm.js +24 -15
- package/dist/hooks/hook-runner.d.ts +119 -65
- package/dist/hooks/hook-runner.js +415 -217
- package/dist/host-lsp-manager.d.ts +22 -8
- package/dist/host-lsp-manager.js +4 -1
- package/dist/http/active-run-conflict.d.ts +2 -43
- package/dist/http/active-run-conflict.js +3 -7
- package/dist/http/admission.d.ts +4 -18
- package/dist/http/admission.js +75 -78
- package/dist/http/approval-sweeps.d.ts +5 -8
- package/dist/http/approval-sweeps.js +55 -30
- package/dist/http/caller-trust.d.ts +8 -10
- package/dist/http/caller-trust.js +1 -2
- package/dist/http/checkpoint-ctx.d.ts +17 -0
- package/dist/http/checkpoint-ctx.js +6 -0
- package/dist/http/claim-heartbeat.d.ts +49 -0
- package/dist/http/claim-heartbeat.js +46 -0
- package/dist/http/claim-latency.d.ts +10 -0
- package/dist/http/claim-latency.js +12 -0
- package/dist/http/claimed-run.d.ts +4 -3
- package/dist/http/dispatch.d.ts +8 -1
- package/dist/http/dispatch.js +77 -31
- package/dist/http/durable-run-leg.d.ts +3 -3
- package/dist/http/durable-run-leg.js +18 -12
- package/dist/http/execution-context.d.ts +18 -42
- package/dist/http/execution-context.js +11 -5
- package/dist/http/health-reader.d.ts +36 -0
- package/dist/http/health-reader.js +21 -0
- package/dist/http/health-self-report.d.ts +9 -9
- package/dist/http/health-self-report.js +4 -4
- package/dist/http/idempotency.d.ts +9 -5
- package/dist/http/idempotency.js +4 -4
- package/dist/http/memory-compliance-failure-reply.d.ts +15 -16
- package/dist/http/memory-compliance-failure-reply.js +7 -2
- package/dist/http/memory-face-fault-reply.d.ts +67 -0
- package/dist/http/memory-face-fault-reply.js +71 -0
- package/dist/http/memory-store-refusal-reply.d.ts +3 -3
- package/dist/http/memory-store-refusal-reply.js +6 -0
- package/dist/http/owner-access.d.ts +391 -56
- package/dist/http/owner-access.js +372 -33
- package/dist/http/principal-gate.d.ts +26 -94
- package/dist/http/principal-gate.js +24 -81
- package/dist/http/refusal-body.d.ts +35 -2
- package/dist/http/refusal-body.js +34 -0
- package/dist/http/request-guards.d.ts +10 -14
- package/dist/http/request-guards.js +34 -68
- package/dist/http/resume-composition.d.ts +37 -0
- package/dist/http/resume-composition.js +18 -0
- package/dist/http/resume-ctx-read.d.ts +35 -12
- package/dist/http/resume-ctx-read.js +25 -6
- package/dist/http/resume-legs.d.ts +24 -24
- package/dist/http/resume-legs.js +260 -171
- package/dist/http/retired-skill-manifest.d.ts +1 -1
- package/dist/http/rewind-files-reply.d.ts +34 -0
- package/dist/http/rewind-files-reply.js +36 -0
- package/dist/http/route-ctx.d.ts +56 -47
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/a2a-serve.d.ts +43 -5
- package/dist/http/routes/a2a-serve.js +58 -37
- package/dist/http/routes/admin-config-refresh.js +2 -2
- package/dist/http/routes/admin-drain.js +2 -2
- package/dist/http/routes/admin-memory-optout.js +4 -3
- package/dist/http/routes/adoption.d.ts +3 -0
- package/dist/http/routes/adoption.js +17 -10
- package/dist/http/routes/agents-roster.js +13 -7
- package/dist/http/routes/approvals-assistant.js +73 -88
- package/dist/http/routes/attachments.js +2 -2
- package/dist/http/routes/background.d.ts +3 -3
- package/dist/http/routes/background.js +9 -17
- package/dist/http/routes/capabilities.js +23 -20
- package/dist/http/routes/config-catalog.js +2 -2
- package/dist/http/routes/devices.d.ts +3 -3
- package/dist/http/routes/devices.js +5 -5
- package/dist/http/routes/diagnostics.d.ts +27 -3
- package/dist/http/routes/diagnostics.js +27 -21
- package/dist/http/routes/engine.js +2 -2
- package/dist/http/routes/fleet.js +5 -3
- package/dist/http/routes/images.js +10 -6
- package/dist/http/routes/leader.d.ts +7 -2
- package/dist/http/routes/leader.js +12 -3
- package/dist/http/routes/mcp-reconnect.d.ts +1 -1
- package/dist/http/routes/mcp-reconnect.js +7 -15
- package/dist/http/routes/memory-bundle.js +4 -7
- package/dist/http/routes/memory-compliance.js +4 -4
- package/dist/http/routes/memory-consolidation.js +6 -9
- package/dist/http/routes/memory-origin.d.ts +3 -1
- package/dist/http/routes/memory-origin.js +7 -8
- package/dist/http/routes/memory-policy.js +17 -12
- package/dist/http/routes/notify-wake.js +12 -20
- package/dist/http/routes/observability.js +3 -3
- package/dist/http/routes/principal-inbox.d.ts +1 -1
- package/dist/http/routes/principal-inbox.js +2 -2
- package/dist/http/routes/projects.d.ts +70 -0
- package/dist/http/routes/projects.js +87 -0
- package/dist/http/routes/retention-ops.js +6 -9
- package/dist/http/routes/rules.js +50 -7
- package/dist/http/routes/run-memory-capture.d.ts +2 -2
- package/dist/http/routes/run-memory-capture.js +6 -12
- package/dist/http/routes/run-permission-mode.d.ts +2 -2
- package/dist/http/routes/run-permission-mode.js +10 -17
- package/dist/http/routes/runs.js +148 -233
- package/dist/http/routes/session-memory-sync.d.ts +2 -2
- package/dist/http/routes/session-memory-sync.js +7 -13
- package/dist/http/routes/session-sync.d.ts +4 -2
- package/dist/http/routes/session-sync.js +48 -30
- package/dist/http/routes/sessions-list.d.ts +3 -3
- package/dist/http/routes/sessions-list.js +9 -9
- package/dist/http/routes/sessions.d.ts +7 -1
- package/dist/http/routes/sessions.js +177 -112
- package/dist/http/routes/shared-memory.js +6 -6
- package/dist/http/routes/side-query.js +2 -2
- package/dist/http/routes/tasks.js +144 -122
- package/dist/http/routes/trace-usage.d.ts +0 -12
- package/dist/http/routes/trace-usage.js +40 -46
- package/dist/http/routes/workflows.d.ts +39 -4
- package/dist/http/routes/workflows.js +42 -14
- package/dist/http/run-concurrency-cap.d.ts +20 -12
- package/dist/http/run-concurrency-cap.js +39 -26
- package/dist/http/run-read-face.d.ts +3 -5
- package/dist/http/run-read-face.js +8 -13
- package/dist/http/send.d.ts +12 -3
- package/dist/http/send.js +42 -6
- package/dist/http/server.d.ts +63 -50
- package/dist/http/server.js +52 -16
- package/dist/http/sse-log.d.ts +17 -10
- package/dist/http/sse-log.js +7 -7
- package/dist/http/stored-body-replay.d.ts +17 -3
- package/dist/http/stored-body-replay.js +11 -2
- package/dist/http/submit-ctx.d.ts +16 -8
- package/dist/http/submit-ctx.js +6 -2
- package/dist/http/submit-replay.d.ts +19 -6
- package/dist/http/submit-replay.js +18 -6
- package/dist/http/sync-submit-body.d.ts +3 -4
- package/dist/http/sync-submit-body.js +4 -5
- package/dist/http/wire-types.d.ts +23 -13
- package/dist/identity-source.d.ts +36 -5
- package/dist/identity-source.js +27 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/instruction-source-notice.d.ts +1 -1
- package/dist/key-resolver.d.ts +32 -0
- package/dist/key-resolver.js +23 -9
- package/dist/leader/diffup.js +2 -1
- package/dist/leader/endpoint.d.ts +26 -5
- package/dist/leader/endpoint.js +154 -14
- package/dist/leader/fanout.d.ts +3 -0
- package/dist/leader/fanout.js +78 -63
- package/dist/leader/leader.d.ts +16 -0
- package/dist/leader/leader.js +43 -3
- package/dist/leader/merge.d.ts +9 -1
- package/dist/leader/merge.js +16 -1
- package/dist/leader/repair-wire.js +1 -1
- package/dist/leader/wire.d.ts +36 -3
- package/dist/leader/wire.js +59 -18
- package/dist/lsp-evict.d.ts +6 -4
- package/dist/lsp-evict.js +2 -3
- package/dist/memory-embedder-reembed.d.ts +38 -0
- package/dist/memory-embedder-reembed.js +20 -0
- package/dist/memory-incident-sink.d.ts +0 -28
- package/dist/memory-incident-sink.js +5 -0
- package/dist/memory-operator-faces.d.ts +5 -4
- package/dist/memory-operator-faces.js +1 -1
- package/dist/memory-scope.d.ts +31 -4
- package/dist/memory-scope.js +65 -16
- package/dist/memory-store-refusal.d.ts +20 -4
- package/dist/memory-store-refusal.js +1 -1
- package/dist/memory-sync-client.d.ts +5 -2
- package/dist/memory-sync-client.js +19 -14
- package/dist/memory-sync.js +1 -1
- package/dist/model-compat.d.ts +51 -28
- package/dist/model-compat.js +55 -23
- package/dist/model-context-window.d.ts +18 -0
- package/dist/model-context-window.js +2 -0
- package/dist/model-entry-headers.d.ts +46 -4
- package/dist/model-entry-headers.js +2 -2
- package/dist/model-entry-refusal.d.ts +46 -17
- package/dist/model-entry-refusal.js +47 -27
- package/dist/model-plane-face.d.ts +19 -11
- package/dist/model-plane-face.js +9 -1
- package/dist/model-plane-probe.d.ts +2 -0
- package/dist/model-plane-probe.js +17 -0
- package/dist/model-provider.d.ts +3 -3
- package/dist/model-route-endpoint.d.ts +9 -5
- package/dist/model-route-endpoint.js +29 -7
- package/dist/model-select.d.ts +16 -0
- package/dist/model-select.js +4 -0
- package/dist/mountinfo.d.ts +37 -0
- package/dist/mountinfo.js +47 -0
- package/dist/namespaced-rule-name.d.ts +28 -2
- package/dist/namespaced-rule-name.js +10 -1
- package/dist/net-address.d.ts +1 -1
- package/dist/observability/corrupt-read-seat.d.ts +4 -4
- package/dist/observability/corrupt-read-seat.js +5 -3
- package/dist/observability/fail-open.d.ts +75 -47
- package/dist/observability/fail-open.js +77 -47
- package/dist/observability/logger.d.ts +22 -1
- package/dist/observability/logger.js +89 -12
- package/dist/observability/metrics.d.ts +2 -0
- package/dist/observability/metrics.js +5 -4
- package/dist/observability/otel-exporter.js +2 -1
- package/dist/observability/principal-context.d.ts +14 -7
- package/dist/observability/principal-context.js +8 -3
- package/dist/observability/run-terminal.d.ts +7 -1
- package/dist/observability/run-terminal.js +5 -2
- package/dist/observability/secret-env-scrub.d.ts +30 -4
- package/dist/observability/secret-env-scrub.js +15 -3
- package/dist/orchestration/hardened-vm-runner.d.ts +1 -0
- package/dist/orchestration/hardened-vm-runner.js +11 -5
- package/dist/orchestration/hardened-vm-worker-runner.d.ts +0 -14
- package/dist/orchestration/hardened-vm-worker-runner.js +5 -0
- package/dist/orchestration/hardened-vm-worker.js +2 -1
- package/dist/orchestration/workflow-agent-session-index.d.ts +7 -12
- package/dist/orchestration/workflow-agent-session-index.js +1 -6
- package/dist/orchestration/workflow-completion-inbox.d.ts +29 -9
- package/dist/orchestration/workflow-completion-inbox.js +65 -46
- package/dist/org-delivery-gate.d.ts +5 -4
- package/dist/org-delivery-gate.js +7 -6
- package/dist/org-memory-admission.d.ts +5 -2
- package/dist/org-memory-admission.js +25 -10
- package/dist/outbound-dispatcher.d.ts +1 -1
- package/dist/outbound-dispatcher.js +6 -12
- package/dist/owner-brands.d.ts +81 -0
- package/dist/owner-brands.js +42 -0
- package/dist/owner-faces.d.ts +531 -0
- package/dist/owner-faces.js +126 -0
- package/dist/parked-decide.d.ts +100 -31
- package/dist/parked-decide.js +162 -48
- package/dist/parked-revive-surface.d.ts +14 -4
- package/dist/parked-revive-surface.js +5 -4
- package/dist/peer-directory.d.ts +6 -8
- package/dist/peer-directory.js +3 -2
- package/dist/permission-rule-vocab.d.ts +53 -8
- package/dist/permission-rule-vocab.js +30 -2
- package/dist/plugins/adoption-log-sql.d.ts +0 -22
- package/dist/plugins/adoption-log-sql.js +5 -3
- package/dist/plugins/approval-ask-retired-delete-by-task-m50.d.ts +7 -0
- package/dist/plugins/approval-ask-retired-delete-by-task-m50.js +17 -0
- package/dist/plugins/approval-ask-store-file.d.ts +28 -9
- package/dist/plugins/approval-ask-store-file.js +74 -18
- package/dist/plugins/approval-ask-store-memory.d.ts +17 -7
- package/dist/plugins/approval-ask-store-memory.js +35 -12
- package/dist/plugins/approval-ask-store-sql.d.ts +42 -20
- package/dist/plugins/approval-ask-store-sql.js +44 -11
- package/dist/plugins/approval-exemption-skill-m46.d.ts +20 -0
- package/dist/plugins/approval-exemption-skill-m46.js +25 -0
- package/dist/plugins/approval-exemption-store.d.ts +6 -1
- package/dist/plugins/approval-exemption-store.js +14 -0
- package/dist/plugins/approval-nonce-store.d.ts +39 -12
- package/dist/plugins/approval-nonce-store.js +72 -23
- package/dist/plugins/blob-backend.d.ts +40 -195
- package/dist/plugins/blob-backend.js +41 -234
- package/dist/plugins/caching-session-store.d.ts +22 -5
- package/dist/plugins/caching-session-store.js +43 -4
- package/dist/plugins/checkpoint-index-columns.d.ts +33 -6
- package/dist/plugins/checkpoint-index-columns.js +39 -3
- package/dist/plugins/checkpoint-store-face.d.ts +84 -11
- package/dist/plugins/checkpoint-store-face.js +18 -1
- package/dist/plugins/device-store-sql.d.ts +2 -2
- package/dist/plugins/device-store-sql.js +6 -10
- package/dist/plugins/ensure-index.d.ts +13 -1
- package/dist/plugins/ensure-index.js +154 -21
- package/dist/plugins/ensure-schema-lock-policy.d.ts +0 -15
- package/dist/plugins/ensure-schema-lock-policy.js +3 -2
- package/dist/plugins/execution-lane-rename-m40.d.ts +20 -0
- package/dist/plugins/execution-lane-rename-m40.js +10 -0
- package/dist/plugins/execution-placement-sql.d.ts +63 -88
- package/dist/plugins/execution-placement-sql.js +94 -54
- package/dist/plugins/file-run-store.d.ts +89 -35
- package/dist/plugins/file-run-store.js +197 -142
- package/dist/plugins/host-platform.d.ts +24 -3
- package/dist/plugins/host-platform.js +15 -1
- package/dist/plugins/image-bake-store-sql.js +2 -0
- package/dist/plugins/imported-layer-root-retirement.d.ts +19 -10
- package/dist/plugins/imported-layer-root-retirement.js +4 -8
- package/dist/plugins/inbox-payload-column.d.ts +14 -0
- package/dist/plugins/inbox-payload-column.js +13 -0
- package/dist/plugins/k8s-pod-reaper.d.ts +1 -3
- package/dist/plugins/k8s-pod-reaper.js +0 -1
- package/dist/plugins/leader-run-store-sql.d.ts +22 -0
- package/dist/plugins/leader-run-store-sql.js +34 -4
- package/dist/plugins/local-checkpoint-store.d.ts +63 -23
- package/dist/plugins/local-checkpoint-store.js +204 -81
- package/dist/plugins/local-data-dir-modes-m39.d.ts +48 -0
- package/dist/plugins/local-data-dir-modes-m39.js +183 -0
- package/dist/plugins/local-docker-capacity.d.ts +93 -0
- package/dist/plugins/local-docker-capacity.js +194 -0
- package/dist/plugins/local-docker-reaper.d.ts +92 -0
- package/dist/plugins/local-docker-reaper.js +102 -0
- package/dist/plugins/local-engine-high-water.d.ts +13 -0
- package/dist/plugins/local-engine-high-water.js +62 -0
- package/dist/plugins/local-session-store.d.ts +442 -143
- package/dist/plugins/local-session-store.js +941 -289
- package/dist/plugins/local-session-window.d.ts +3 -2
- package/dist/plugins/local-session-window.js +3 -2
- package/dist/plugins/local-sidecar-quarantine.js +3 -2
- package/dist/plugins/local-task-attachment-store.d.ts +20 -1
- package/dist/plugins/local-task-attachment-store.js +32 -12
- package/dist/plugins/memory-embedder-fingerprint.d.ts +33 -10
- package/dist/plugins/memory-embedder-fingerprint.js +52 -12
- package/dist/plugins/memory-embedder.d.ts +1 -1
- package/dist/plugins/memory-embedder.js +11 -9
- package/dist/plugins/memory-engine-pg.d.ts +7 -5
- package/dist/plugins/memory-engine-pg.js +18 -9
- package/dist/plugins/memory-engine-sql-apply.d.ts +21 -8
- package/dist/plugins/memory-engine-sql-apply.js +7 -9
- package/dist/plugins/memory-engine-tidb.d.ts +12 -7
- package/dist/plugins/memory-engine-tidb.js +18 -10
- package/dist/plugins/memory-optout-grant-store-sql.d.ts +1 -1
- package/dist/plugins/memory-optout-grant-store-sql.js +3 -3
- package/dist/plugins/memory-run-store.d.ts +39 -27
- package/dist/plugins/memory-run-store.js +72 -70
- package/dist/plugins/one-time-migration-marker.d.ts +10 -0
- package/dist/plugins/one-time-migration-marker.js +7 -0
- package/dist/plugins/one-time-migrations.d.ts +325 -74
- package/dist/plugins/one-time-migrations.js +705 -230
- package/dist/plugins/org-delivery-quota-store.js +2 -1
- package/dist/plugins/owner-only-file-mode.d.ts +29 -0
- package/dist/plugins/owner-only-file-mode.js +141 -0
- package/dist/plugins/permission-rule-store-file.d.ts +11 -11
- package/dist/plugins/permission-rule-store-file.js +14 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +4 -63
- package/dist/plugins/permission-rule-store-sql.js +6 -86
- package/dist/plugins/pg-pool.d.ts +3 -32
- package/dist/plugins/pg-pool.js +27 -11
- package/dist/plugins/release-version.d.ts +20 -0
- package/dist/plugins/release-version.js +28 -0
- package/dist/plugins/remote-env-adb.d.ts +20 -4
- package/dist/plugins/remote-env-adb.js +39 -11
- package/dist/plugins/remote-env-e2b.d.ts +19 -28
- package/dist/plugins/remote-env-e2b.js +40 -29
- package/dist/plugins/remote-env-host.d.ts +11 -10
- package/dist/plugins/remote-env-host.js +40 -27
- package/dist/plugins/remote-env-k8s.d.ts +45 -47
- package/dist/plugins/remote-env-k8s.js +122 -62
- package/dist/plugins/remote-env-local-docker.d.ts +152 -15
- package/dist/plugins/remote-env-local-docker.js +370 -75
- package/dist/plugins/remote-env-ssh.d.ts +8 -2
- package/dist/plugins/remote-env-ssh.js +26 -24
- package/dist/plugins/remote-scratchpad.js +4 -0
- package/dist/plugins/remote-shell.d.ts +59 -30
- package/dist/plugins/remote-shell.js +175 -62
- package/dist/plugins/retention-declaration.d.ts +37 -0
- package/dist/plugins/retention-declaration.js +9 -0
- package/dist/plugins/retention-deployment.d.ts +158 -0
- package/dist/plugins/retention-deployment.js +119 -0
- package/dist/plugins/retention-lane-store-sql.d.ts +74 -4
- package/dist/plugins/retention-lane-store-sql.js +103 -1
- package/dist/plugins/retention-sql.d.ts +29 -0
- package/dist/plugins/retention-sql.js +8 -0
- package/dist/plugins/run-preview-m45.d.ts +11 -0
- package/dist/plugins/run-preview-m45.js +19 -0
- package/dist/plugins/run-store-sql.d.ts +56 -68
- package/dist/plugins/run-store-sql.js +127 -116
- package/dist/plugins/s3-answer.d.ts +2 -1
- package/dist/plugins/sandbox-reaper.d.ts +26 -0
- package/dist/plugins/sandbox-reaper.js +2 -0
- package/dist/plugins/scenario-prefix-switch-m42.d.ts +54 -0
- package/dist/plugins/scenario-prefix-switch-m42.js +55 -0
- package/dist/plugins/scheduler-support.d.ts +16 -1
- package/dist/plugins/scheduler-support.js +22 -2
- package/dist/plugins/schema-consistency.d.ts +42 -15
- package/dist/plugins/schema-consistency.js +213 -13
- package/dist/plugins/schema-drift-sentence.d.ts +4 -1
- package/dist/plugins/schema-drift-sentence.js +4 -4
- package/dist/plugins/schema-ensure-legs.d.ts +10 -1
- package/dist/plugins/schema-ensure-legs.js +12 -2
- package/dist/plugins/schema-indexes.js +3 -0
- package/dist/plugins/schema-live-columns.d.ts +14 -5
- package/dist/plugins/schema-live-columns.js +10 -10
- package/dist/plugins/send-user-file.js +2 -1
- package/dist/plugins/session-birth.d.ts +37 -0
- package/dist/plugins/session-birth.js +38 -0
- package/dist/plugins/session-epoch-anchor.d.ts +40 -12
- package/dist/plugins/session-epoch-anchor.js +33 -11
- package/dist/plugins/session-ext-table.d.ts +98 -0
- package/dist/plugins/session-ext-table.js +134 -0
- package/dist/plugins/session-fork.d.ts +16 -2
- package/dist/plugins/session-fork.js +3 -0
- package/dist/plugins/session-ownership.d.ts +1 -1
- package/dist/plugins/session-placement.d.ts +22 -16
- package/dist/plugins/session-placement.js +15 -12
- package/dist/plugins/session-row-sql.d.ts +20 -7
- package/dist/plugins/session-row-sql.js +16 -8
- package/dist/plugins/session-run-rollup.d.ts +29 -0
- package/dist/plugins/session-run-rollup.js +40 -0
- package/dist/plugins/session-store-sql.d.ts +157 -0
- package/dist/plugins/session-store-sql.js +398 -0
- package/dist/plugins/session-withdraw-sql.d.ts +36 -0
- package/dist/plugins/session-withdraw-sql.js +42 -0
- package/dist/plugins/session-workspace-cell.d.ts +2 -2
- package/dist/plugins/session-workspace-cell.js +2 -2
- package/dist/plugins/shared-memory-store-sql.d.ts +10 -69
- package/dist/plugins/shared-memory-store-sql.js +15 -14
- package/dist/plugins/shell-sentinel-owner-m48.d.ts +20 -0
- package/dist/plugins/shell-sentinel-owner-m48.js +3 -0
- package/dist/plugins/snapshot-bucket-presence.d.ts +2 -1
- package/dist/plugins/snapshot-bucket-presence.js +1 -1
- package/dist/plugins/sql-driver.d.ts +24 -8
- package/dist/plugins/sql-driver.js +43 -9
- package/dist/plugins/sql-errors.d.ts +35 -5
- package/dist/plugins/sql-errors.js +55 -3
- package/dist/plugins/sql-json-column.d.ts +8 -7
- package/dist/plugins/sql-json-column.js +10 -6
- package/dist/plugins/store-backend.d.ts +114 -18
- package/dist/plugins/store-backend.js +340 -87
- package/dist/plugins/store-contracts.d.ts +97 -30
- package/dist/plugins/store-contracts.js +23 -1
- package/dist/plugins/store-fault.d.ts +28 -2
- package/dist/plugins/store-fault.js +20 -2
- package/dist/plugins/task-attachment-store.d.ts +21 -11
- package/dist/plugins/task-attachment-store.js +37 -19
- package/dist/plugins/task-list-store-sql.d.ts +1 -1
- package/dist/plugins/text-column-capacity.d.ts +21 -0
- package/dist/plugins/text-column-capacity.js +5 -0
- package/dist/plugins/tidb-pool.d.ts +3 -2
- package/dist/plugins/tidb-pool.js +42 -39
- package/dist/plugins/user-input-ledger-sql.d.ts +1 -1
- package/dist/plugins/web-search.js +5 -3
- package/dist/plugins/workflow-run-store-sql.d.ts +6 -1
- package/dist/plugins/workflow-run-store-sql.js +11 -5
- package/dist/principal-jwt.d.ts +4 -4
- package/dist/principal-jwt.js +20 -5
- package/dist/principal-shape.d.ts +41 -0
- package/dist/principal-shape.js +19 -0
- package/dist/question.d.ts +13 -23
- package/dist/question.js +21 -39
- package/dist/read-face-posture.d.ts +6 -6
- package/dist/read-face-posture.js +2 -2
- package/dist/request-declaration-refusal.d.ts +35 -12
- package/dist/request-declaration-refusal.js +21 -9
- package/dist/request-form.d.ts +58 -0
- package/dist/request-form.js +72 -0
- package/dist/request-image-urls.d.ts +8 -2
- package/dist/request-image-urls.js +2 -1
- package/dist/request-images.d.ts +20 -0
- package/dist/request-images.js +59 -0
- package/dist/request-key-closure.d.ts +6 -7
- package/dist/request-key-closure.js +15 -2
- package/dist/request-value-closure.d.ts +5 -5
- package/dist/request-value-closure.js +1 -1
- package/dist/resource-suspend.d.ts +7 -16
- package/dist/resource-suspend.js +1 -1
- package/dist/retired-name-host-notice.d.ts +21 -0
- package/dist/retired-name-host-notice.js +12 -0
- package/dist/rule-entry-list.d.ts +2 -2
- package/dist/rule-entry-list.js +3 -4
- package/dist/rule-issue-reading.d.ts +58 -0
- package/dist/rule-issue-reading.js +14 -0
- package/dist/rules-consent.d.ts +46 -16
- package/dist/rules-consent.js +102 -53
- package/dist/run-acceptance.d.ts +3 -1
- package/dist/run-acceptance.js +3 -0
- package/dist/run-local.d.ts +14 -6
- package/dist/run-local.js +109 -93
- package/dist/run-stale-default.d.ts +7 -0
- package/dist/run-stale-default.js +2 -0
- package/dist/runs.d.ts +73 -44
- package/dist/runs.js +65 -43
- package/dist/runtime-caps-resolver.d.ts +36 -14
- package/dist/runtime-caps-resolver.js +31 -14
- package/dist/runtime-governance.d.ts +2 -2
- package/dist/runtime-governance.js +5 -3
- package/dist/sealed-key-layout.d.ts +12 -6
- package/dist/sealed-key-layout.js +5 -1
- package/dist/sealed-key.d.ts +12 -2
- package/dist/sealed-key.js +116 -26
- package/dist/security.d.ts +133 -161
- package/dist/security.js +163 -59
- package/dist/sema-config-dir.d.ts +20 -0
- package/dist/sema-config-dir.js +27 -2
- package/dist/server-secret-env.d.ts +23 -5
- package/dist/server-secret-env.js +14 -4
- package/dist/service-version.d.ts +2 -1
- package/dist/service-version.js +2 -2
- package/dist/session-children-retire.d.ts +7 -5
- package/dist/session-children-retire.js +7 -2
- package/dist/session-deletion.d.ts +88 -0
- package/dist/session-deletion.js +8 -0
- package/dist/session-erase.d.ts +145 -0
- package/dist/session-erase.js +115 -0
- package/dist/session-error-wire.d.ts +6 -6
- package/dist/session-error-wire.js +1 -1
- package/dist/session-incarnation.d.ts +60 -10
- package/dist/session-incarnation.js +19 -2
- package/dist/session-policy-wire.d.ts +6 -3
- package/dist/session-policy-wire.js +11 -1
- package/dist/session-resident-background.d.ts +29 -47
- package/dist/session-resident-background.js +77 -8
- package/dist/session-row.d.ts +3 -2
- package/dist/session-rules-seat.d.ts +12 -0
- package/dist/session-rules-seat.js +19 -0
- package/dist/session-sync-kernel.d.ts +1 -1
- package/dist/session-titler.d.ts +7 -15
- package/dist/session-titler.js +1 -1
- package/dist/session-workspace.d.ts +11 -2
- package/dist/session-workspace.js +5 -0
- package/dist/sighup-idle.d.ts +1 -1
- package/dist/spec-fields.d.ts +57 -3
- package/dist/spec-fields.js +72 -51
- package/dist/store-posture.d.ts +138 -21
- package/dist/store-posture.js +188 -3
- package/dist/stored-request-hooks-m20.d.ts +3 -2
- package/dist/stored-request-hooks-m20.js +21 -16
- package/dist/stores-sql/background-agent-store-sql.d.ts +11 -2
- package/dist/stores-sql/background-agent-store-sql.js +73 -20
- package/dist/stores-sql/blob-index-sql.d.ts +148 -0
- package/dist/stores-sql/blob-index-sql.js +154 -0
- package/dist/stores-sql/checkpoint-store-sql.d.ts +34 -29
- package/dist/stores-sql/checkpoint-store-sql.js +86 -71
- package/dist/{plugins → stores-sql}/file-history-store-sql.d.ts +23 -11
- package/dist/{plugins → stores-sql}/file-history-store-sql.js +23 -18
- package/dist/stores-sql/mailbox-store-sql.d.ts +7 -4
- package/dist/stores-sql/mailbox-store-sql.js +31 -32
- package/dist/{plugins → stores-sql}/pg-session-storage.d.ts +30 -81
- package/dist/{plugins → stores-sql}/pg-session-storage.js +123 -244
- package/dist/{plugins → stores-sql}/retention-store-sql.d.ts +197 -223
- package/dist/{plugins → stores-sql}/retention-store-sql.js +148 -207
- package/dist/{plugins → stores-sql}/session-event-payload.d.ts +1 -1
- package/dist/{plugins → stores-sql}/session-event-payload.js +1 -1
- package/dist/stores-sql/session-policy-store-sql.d.ts +33 -11
- package/dist/stores-sql/session-policy-store-sql.js +44 -36
- package/dist/stores-sql/session-write-hooks.d.ts +65 -0
- package/dist/stores-sql/session-write-hooks.js +2 -0
- package/dist/{plugins → stores-sql}/tidb-session-storage.d.ts +14 -15
- package/dist/{plugins → stores-sql}/tidb-session-storage.js +22 -28
- package/dist/{plugins → stores-sql}/tidb-session-store.d.ts +67 -138
- package/dist/{plugins → stores-sql}/tidb-session-store.js +130 -200
- package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
- package/dist/stores-sql/tool-result-store-sql.js +1 -1
- package/dist/stores-sql/windowed-session-storage.d.ts +61 -0
- package/dist/stores-sql/windowed-session-storage.js +70 -0
- package/dist/submission-exit.d.ts +50 -0
- package/dist/submission-exit.js +108 -0
- package/dist/task-a2a.d.ts +7 -1
- package/dist/task-a2a.js +18 -17
- package/dist/task-cwd.d.ts +31 -3
- package/dist/task-cwd.js +25 -1
- package/dist/task-mcp.d.ts +11 -3
- package/dist/task-mcp.js +16 -19
- package/dist/task-settings.d.ts +23 -14
- package/dist/task-settings.js +35 -13
- package/dist/terminal.d.ts +25 -43
- package/dist/terminal.js +31 -11
- package/dist/tool-approval.d.ts +152 -137
- package/dist/tool-approval.js +199 -199
- package/dist/trace/core-keyset-guard.d.ts +22 -9
- package/dist/trace/core-vocab-wire.d.ts +95 -0
- package/dist/trace/core-vocab-wire.js +15 -0
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +52 -4
- package/dist/trace/exit-seat.js +43 -36
- package/dist/trace/extension-call-face.d.ts +3 -2
- package/dist/trace/extension-call-face.js +3 -1
- package/dist/trace/ledger-events.d.ts +4 -7
- package/dist/trace/ledger-sink.d.ts +1 -30
- package/dist/trace/ledger-sink.js +0 -11
- package/dist/trace/model-segment.d.ts +5 -5
- package/dist/trace/model-segment.js +5 -5
- package/dist/trace/project.d.ts +88 -58
- package/dist/trace/project.js +64 -28
- package/dist/trace/redact.d.ts +124 -28
- package/dist/trace/redact.js +936 -328
- package/dist/trace/self-minted-wire.d.ts +750 -29
- package/dist/trace/self-minted-wire.js +262 -25
- package/dist/trace/sema-provenance.d.ts +45 -41
- package/dist/trace/sema-provenance.js +81 -10
- package/dist/trace/task-notification-facets.d.ts +70 -2
- package/dist/trace/task-notification-facets.js +71 -1
- package/dist/trace/wire-projection-faces.d.ts +43 -8
- package/dist/trace/wire-projection-faces.js +28 -3
- package/dist/unread-request-members.d.ts +66 -0
- package/dist/unread-request-members.js +41 -0
- package/dist/upstream-body.d.ts +23 -0
- package/dist/upstream-body.js +16 -0
- package/dist/url-refusal.d.ts +3 -3
- package/dist/url-refusal.js +11 -4
- package/dist/user-authored-text.d.ts +1 -1
- package/dist/user-input-ledger.d.ts +7 -8
- package/dist/user-input-ledger.js +1 -1
- package/dist/web-admin-client/apply-effective.d.ts +6 -3
- package/dist/web-admin-client/apply-effective.js +33 -40
- package/dist/web-admin-client/apply-ledger.d.ts +16 -7
- package/dist/web-admin-client/apply-ledger.js +5 -2
- package/dist/web-admin-client/candidate-gate.d.ts +35 -3
- package/dist/web-admin-client/candidate-gate.js +53 -5
- package/dist/web-admin-client/center-request.d.ts +6 -3
- package/dist/web-admin-client/center-request.js +17 -16
- package/dist/web-admin-client/effective-keys.d.ts +1 -1
- package/dist/web-admin-client/effective-keys.js +1 -2
- package/dist/web-admin-client/entry-metadata.d.ts +8 -0
- package/dist/web-admin-client/entry-metadata.js +13 -0
- package/dist/web-admin-client/facade.d.ts +1 -1
- package/dist/web-admin-client/http-client.d.ts +2 -1
- package/dist/web-admin-client/http-client.js +40 -13
- package/dist/web-admin-client/mcp-revocation.d.ts +1 -1
- package/dist/web-admin-client/mcp-revocation.js +2 -1
- package/dist/web-admin-client/read-warnings.d.ts +1 -0
- package/dist/web-admin-client/read-warnings.js +1 -0
- package/dist/web-admin-client/restart-signal.js +13 -6
- package/dist/web-admin-client/skills-mcp.d.ts +22 -8
- package/dist/web-admin-client/skills-mcp.js +26 -21
- package/dist/web-admin-client/types.d.ts +35 -51
- package/dist/workspace-space.d.ts +3 -2
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +4 -4
- package/package.json +16 -12
- package/scripts/drain-check-local.mjs +118 -173
- package/scripts/drain-check.mysql.sql +20 -16
- package/scripts/drain-check.pg.sql +20 -16
- package/wire-changes/server@7.147.0.json +813 -0
- package/wire-changes/server@7.148.0.json +1517 -0
- package/dist/http/memory-control-plane-corrupt-reply.d.ts +0 -25
- package/dist/http/memory-control-plane-corrupt-reply.js +0 -19
- package/dist/http/memory-layer-reply.d.ts +0 -30
- package/dist/http/memory-layer-reply.js +0 -24
- package/dist/lsp/e2b-bridge.d.ts +0 -57
- package/dist/lsp/e2b-bridge.js +0 -139
- package/dist/lsp/e2b-manager.d.ts +0 -93
- package/dist/lsp/e2b-manager.js +0 -178
- package/dist/lsp/lsp-frames.d.ts +0 -14
- package/dist/lsp/lsp-frames.js +0 -25
- package/dist/lsp/manager.d.ts +0 -129
- package/dist/lsp/manager.js +0 -127
- package/dist/lsp/types.d.ts +0 -12
- package/dist/lsp/types.js +0 -2
- package/dist/lsp/ws-transport.d.ts +0 -40
- package/dist/lsp/ws-transport.js +0 -210
|
@@ -1,31 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { SCENARIO_SUBJECT_PREFIX } from "../owner-brands.js";
|
|
2
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
3
|
+
import { closeSync, existsSync, fsyncSync, lstatSync, openSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
3
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
5
|
import { isDeepStrictEqual } from "node:util";
|
|
5
6
|
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { IS_WIN32 } from "./host-platform.js";
|
|
8
|
+
import { atomicWriteFileAsync, atomicWriteFileSync, proveDurableSync } from "./owner-only-file-mode.js";
|
|
6
9
|
import { assertFirstOwnerWritable, forkOwnerOf, importerOwnerOf, placedSessionOwnerOf } from "./session-ownership.js";
|
|
7
10
|
import { SessionOwnerUnreadableError, StampedSessionArchiveError, sessionUnreadableOf } from "../session-error-wire.js";
|
|
8
|
-
import {
|
|
11
|
+
import { SHELL_SENTINEL_OWNER_M48 } from "./shell-sentinel-owner-m48.js";
|
|
12
|
+
import { FileSessionRepo, SessionError, StoredSession, admitSessionPlacement, atomicWriteFile, ensureDir, forkRowCells, sessionLogMarkOf, settledForkOptions, uuidv7, validateEntriesForImport } from "@sema-agent/core";
|
|
9
13
|
import { placementCapabilityOf } from "./session-placement.js";
|
|
10
14
|
import { z } from "zod";
|
|
11
15
|
import { contentForkRelation, fastForwardSharedContentDiverged, identicalIdsAlsoIdenticalContent } from "../session-sync-content.js";
|
|
12
|
-
import { StoreBootRefusalError } from "./store-contracts.js";
|
|
16
|
+
import { SCHEMA_MISMATCH_ERROR_CODE, StoreBootRefusalError } from "./store-contracts.js";
|
|
17
|
+
import { entryTypeWithdrawable } from "./session-birth.js";
|
|
13
18
|
import { judgeSessionRowWrite, sessionRowTagKey, settleSessionRowWrite, } from "../session-row.js";
|
|
14
19
|
import { quarantineUnreadableSidecar } from "./local-sidecar-quarantine.js";
|
|
15
|
-
import { createZodColumnReader, decodeJsonColumn, isJsonColumnBodyProblem, readStringRecordColumn, SqlJsonColumnError } from "./sql-json-column.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
20
|
+
import { createZodColumnReader, decodeJsonColumn, isJsonColumnBodyProblem, isJsonObject, readJsonObjectColumn, readStringRecordColumn, SqlJsonColumnError } from "./sql-json-column.js";
|
|
21
|
+
import { RENAMED_EXECUTION_LANE_M40, RETIRED_EXECUTION_LANE_WORD_M40 } from "./execution-lane-rename-m40.js";
|
|
22
|
+
import { ExecutionLaneRenameWriteRefusedError, ONE_TIME_MIGRATIONS } from "./one-time-migrations.js";
|
|
23
|
+
import { compareReleaseTriples, releaseText, releaseTriple } from "./release-version.js";
|
|
18
24
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
19
|
-
import { createLogger } from "../observability/logger.js";
|
|
20
|
-
import { UNMANAGED_RETENTION } from "./retention-
|
|
21
|
-
import { UnreadableLeaf, carriedRowCells, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
|
|
25
|
+
import { createLogger, writeLogText } from "../observability/logger.js";
|
|
26
|
+
import { UNMANAGED_RETENTION } from "./retention-declaration.js";
|
|
27
|
+
import { UnreadableLeaf, carriedRowCells, forkLineageOfRow, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
|
|
22
28
|
import { isExecutionLane } from "../execution-lane-caps.js";
|
|
23
|
-
import { SessionForkInProgressError,
|
|
29
|
+
import { SessionForkInProgressError, WRITABLE, assertPlaceableLane, judgeSessionWrite, settledMirrorOutcome, settledPlaceOutcome, SessionDeletingError, } from "../execution-placement.js";
|
|
24
30
|
import { classifyForkTarget, forkByFreshId } from "./session-fork.js";
|
|
25
31
|
import { decodeInstructionSource, encodeInstructionSource, sameInstructionSourceBaseline } from "../instruction-source-notice.js";
|
|
26
32
|
import { decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
|
|
27
33
|
import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
|
|
28
|
-
import { buildObjectivePreview, isAttachmentFrame } from "../user-authored-text.js";
|
|
29
34
|
import { classifySyncRelationshipByIds, ownerCellIs, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, } from "../session-sync-kernel.js";
|
|
30
35
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
31
36
|
const isNotFound = (e) => e instanceof SessionError && e.code === "not_found";
|
|
@@ -49,46 +54,29 @@ function archiveFaultWord(code) {
|
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
|
-
function
|
|
53
|
-
const
|
|
57
|
+
function unusableArchiveOfFault(fault) {
|
|
58
|
+
const engine = fault instanceof SessionOwnerUnreadableError ? "corrupt_session" : fault instanceof SessionError ? archiveFaultWord(fault.code) : "storage";
|
|
59
|
+
const word = engine === "absent" ? "storage" : engine;
|
|
60
|
+
const code = fault instanceof SessionError || fault instanceof SessionOwnerUnreadableError ? undefined : fault?.code;
|
|
54
61
|
return { state: "unusable", word, fault, ...(word === "storage" && typeof code === "string" ? { errno: code } : {}) };
|
|
55
62
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
if (probe.word === "storage" || !(fault instanceof SessionError))
|
|
59
|
-
return probe;
|
|
60
|
-
return { ...probe, fault: new SessionOwnerUnreadableError(fault) };
|
|
63
|
+
function ownerUnreadableFault(fault) {
|
|
64
|
+
return fault instanceof SessionError ? new SessionOwnerUnreadableError(fault) : fault instanceof Error ? fault : new Error(String(fault));
|
|
61
65
|
}
|
|
66
|
+
const ABSENT_ROW = { row: "absent" };
|
|
67
|
+
const NO_CELL = { state: "none" };
|
|
62
68
|
function importRowRefused(sessionId, landing) {
|
|
63
69
|
return new SessionError("conflict", landing === "create"
|
|
64
70
|
? `session ${sessionId} already exists — importEntries only creates a session (replaceEntries rewrites one); nothing was written`
|
|
65
71
|
: `session ${sessionId} is not the importer's row — an import replaces only a log whose row the importer owns (or an anonymous row, by an anonymous import); nothing was written`);
|
|
66
72
|
}
|
|
67
|
-
const SESSION_OBJECTIVE_PREVIEW_CHARS = 200;
|
|
68
|
-
function userText(entry) {
|
|
69
|
-
if (entry.type !== "message")
|
|
70
|
-
return null;
|
|
71
|
-
const msg = entry.message;
|
|
72
|
-
if (msg.role !== "user" || isAttachmentFrame(msg))
|
|
73
|
-
return null;
|
|
74
|
-
const c = msg.content;
|
|
75
|
-
if (typeof c === "string")
|
|
76
|
-
return c;
|
|
77
|
-
if (Array.isArray(c)) {
|
|
78
|
-
return c
|
|
79
|
-
.filter((p) => !!p && typeof p === "object" && p.type === "text")
|
|
80
|
-
.map((p) => p.text)
|
|
81
|
-
.join(" ");
|
|
82
|
-
}
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
73
|
const SIDECAR_DEGRADES = { lastActivity: "recency", title: "title", pinned: "pin state", "instruction-source": "instruction-source baseline", workspace: "each session's last recorded place and project (the next frame records it again)", "session-meta": "session owner + incarnation (the write that needed it failed loudly)" };
|
|
86
74
|
const readPinnedSidecar = createZodColumnReader(z.record(z.string(), z.literal(true)));
|
|
87
75
|
const MIRROR_CELL = { role: "mirror" };
|
|
88
76
|
function readSessionMetaCell(cell) {
|
|
89
77
|
return typeof cell === "string"
|
|
90
|
-
? { incarnation: cell, owner: undefined, execution: undefined, forkKey: undefined, createdAt: undefined, forkedFrom: undefined }
|
|
91
|
-
: { incarnation: cell.incarnation, owner: cell.owner, execution: cell.execution, forkKey: cell.forkKey, createdAt: cell.createdAt, forkedFrom: cell.forkedFrom };
|
|
78
|
+
? { incarnation: cell, owner: undefined, execution: undefined, forkKey: undefined, createdAt: undefined, forkedFrom: undefined, forkedFromIncarnation: undefined, dropping: false, deleting: undefined, clientWrites: undefined }
|
|
79
|
+
: { incarnation: cell.incarnation, owner: cell.owner, execution: cell.execution, forkKey: cell.forkKey, createdAt: cell.createdAt, forkedFrom: cell.forkedFrom, forkedFromIncarnation: cell.forkedFromIncarnation, dropping: cell.dropping === true, deleting: cell.deleting, clientWrites: cell.clientWrites };
|
|
92
80
|
}
|
|
93
81
|
const ownerRecorded = (cell) => cell !== undefined && readSessionMetaCell(cell).owner !== undefined;
|
|
94
82
|
const cellObjectOf = (cell) => (typeof cell === "string" ? { incarnation: cell } : cell);
|
|
@@ -104,9 +92,9 @@ function archivedRowFactsOf(head) {
|
|
|
104
92
|
...(typeof forkedFrom === "string" && forkedFrom !== "" ? { forkedFrom } : {}),
|
|
105
93
|
};
|
|
106
94
|
}
|
|
107
|
-
function rowFactsFilled(before, facts) {
|
|
95
|
+
function rowFactsFilled(sessionId, before, facts, archiveToken) {
|
|
108
96
|
if (before === undefined)
|
|
109
|
-
return { incarnation:
|
|
97
|
+
return { incarnation: firstCellIncarnationOf(sessionId, archiveToken).incarnation, createdAt: facts.createdAt, ...(facts.forkedFrom !== undefined ? { forkedFrom: facts.forkedFrom } : {}) };
|
|
110
98
|
const held = cellObjectOf(before);
|
|
111
99
|
const add = {
|
|
112
100
|
...(held.createdAt === undefined ? { createdAt: facts.createdAt } : {}),
|
|
@@ -123,12 +111,37 @@ function rowCreatedAtOf(sessionId, meta) {
|
|
|
123
111
|
}
|
|
124
112
|
function rowMetadataOf(sessionId, meta) {
|
|
125
113
|
const face = sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation);
|
|
126
|
-
return { id: sessionId, createdAt: rowCreatedAtOf(sessionId, meta), owner: face.owner, incarnation: face.incarnation, ...(meta.forkedFrom
|
|
114
|
+
return { id: sessionId, createdAt: rowCreatedAtOf(sessionId, meta), owner: face.owner, incarnation: face.incarnation, ...forkLineageOfRow(sessionId, meta.forkedFrom, meta.forkedFromIncarnation) };
|
|
127
115
|
}
|
|
128
116
|
const isForkCreatedCell = (meta) => meta !== undefined && meta.forkKey !== undefined && meta.execution === undefined;
|
|
117
|
+
function placementCellOf(meta, forking) {
|
|
118
|
+
return forking ? { role: "forking" } : (meta?.execution ?? { role: "absent" });
|
|
119
|
+
}
|
|
129
120
|
function readingOfCell(sessionId, meta, forking) {
|
|
130
|
-
return { occupant: sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation), cell:
|
|
121
|
+
return { occupant: sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation), cell: placementCellOf(meta, forking) };
|
|
122
|
+
}
|
|
123
|
+
function legacyNeedsMint(seen) {
|
|
124
|
+
return seen.row === "legacy" && seen.placed === undefined && seen.unreadable === undefined;
|
|
131
125
|
}
|
|
126
|
+
function executionReadingOf(sessionId, seen) {
|
|
127
|
+
switch (seen.row) {
|
|
128
|
+
case "absent":
|
|
129
|
+
return undefined;
|
|
130
|
+
case "live":
|
|
131
|
+
return seen.placed !== undefined ? undefined : readingOfCell(sessionId, seen.meta, seen.forking);
|
|
132
|
+
case "legacy":
|
|
133
|
+
if (seen.placed !== undefined)
|
|
134
|
+
return undefined;
|
|
135
|
+
if (seen.unreadable !== undefined)
|
|
136
|
+
throw ownerUnreadableFault(seen.unreadable);
|
|
137
|
+
throw new Error(`LocalSessionStore: session ${sessionId} is a legacy row — its cell is minted under its write lock before an execution reading is given`);
|
|
138
|
+
default: {
|
|
139
|
+
const unknownRow = seen;
|
|
140
|
+
throw new Error(`LocalSessionStore: no row observation ${JSON.stringify(unknownRow)}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const SESSION_META_SIDECAR_FILE = "session-incarnations.json";
|
|
132
145
|
const LOCAL_EXECUTION_CELL = z.discriminatedUnion("role", [
|
|
133
146
|
z.object({ role: z.literal("writer"), lane: z.custom((v) => typeof v === "string" && isExecutionLane(v), { message: "not an execution lane" }) }).strict(),
|
|
134
147
|
z.object({ role: z.literal("mirror") }).strict(),
|
|
@@ -141,31 +154,53 @@ export const SESSION_META_CELL_OBJECT = z
|
|
|
141
154
|
forkKey: z.string().optional(),
|
|
142
155
|
createdAt: z.string().optional(),
|
|
143
156
|
forkedFrom: z.string().optional(),
|
|
157
|
+
forkedFromIncarnation: z.string().optional(),
|
|
158
|
+
dropping: z.literal(true).optional(),
|
|
159
|
+
deleting: z.string().min(1).optional(),
|
|
160
|
+
clientWrites: z.number().int().nonnegative().optional(),
|
|
144
161
|
})
|
|
145
162
|
.strict();
|
|
146
163
|
const SESSION_META_SIDECAR = z.record(z.string(), z.union([z.string(), SESSION_META_CELL_OBJECT]));
|
|
147
164
|
const readSessionMetaSidecar = createZodColumnReader(SESSION_META_SIDECAR);
|
|
148
|
-
function ownerClaimWindowOpen() {
|
|
149
|
-
const window = ONE_TIME_MIGRATIONS.find((w) => w.id === "M10");
|
|
150
|
-
if (window === undefined)
|
|
151
|
-
throw new Error("LocalSessionStore: the one-time migration registry has no M10 row (the local session-owner claim window) — the claim arm outlived its registry row; delete it with that row");
|
|
152
|
-
return migrationWindowOpen(window, new Date().toISOString().slice(0, 10));
|
|
153
|
-
}
|
|
154
165
|
const SIDECAR_MAP_STATE_BY_PATH = new Map();
|
|
155
166
|
const SESSION_WRITE_LOCKS_BY_ROOT = new Map();
|
|
156
167
|
const quotedFieldNames = (names) => `${names.slice(0, 8).map((name) => JSON.stringify(name.slice(0, 64))).join(", ")}${names.length > 8 ? `, … (${names.length} in all)` : ""}`;
|
|
168
|
+
function releaseAtOrAbove(a, b) {
|
|
169
|
+
const ta = releaseTriple(a);
|
|
170
|
+
const tb = releaseTriple(b);
|
|
171
|
+
return ta !== undefined && tb !== undefined && compareReleaseTriples(ta, tb) >= 0;
|
|
172
|
+
}
|
|
173
|
+
function describeDataRootFinding(f) {
|
|
174
|
+
if (f.problem === "newer-engine") {
|
|
175
|
+
return ` ${f.label} at ${f.path}: engine high-water ${f.engineHighWater.highWater} — the data root has been opened by server ${f.engineHighWater.highWater}, NEWER than this build (${f.engineHighWater.engine}).`;
|
|
176
|
+
}
|
|
177
|
+
if (f.problem === "skipped-required-migration") {
|
|
178
|
+
const { highWater, requiredSince, sessions } = f.skippedMigration;
|
|
179
|
+
const consequence = "This build no longer carries that migration: opening the root would give those sessions a new occupant token, and memory records kept under the old one (capture opt-outs, pollution marks) would stop applying to them.";
|
|
180
|
+
if (highWater !== null && releaseAtOrAbove(highWater, requiredSince)) {
|
|
181
|
+
return (` ${f.label} at ${f.path}: ${sessions} session archive(s) have no session-meta cell, and the data root was last opened by server ${highWater}, which carries the one-time migration ` +
|
|
182
|
+
`(introduced in ${requiredSince}) that gives such an archive its row cell under the occupant token the archive already carries — but that start did not finish it ` +
|
|
183
|
+
`(${ARCHIVE_TOKEN_ADOPTION_PENDING_FILE} is still in the data root). ${consequence} ` +
|
|
184
|
+
`Start the root once with a release at or above ${highWater} that still carries that migration (server or run-local) and let that start finish, then start this build.`);
|
|
185
|
+
}
|
|
186
|
+
const lastOpened = highWater === null ? "no engine that records a high-water (run-local before 7.148.0, or a server before 7.144.0)" : `server ${highWater}`;
|
|
187
|
+
return (` ${f.label} at ${f.path}: ${sessions} session archive(s) have no session-meta cell, and the data root was last opened by ${lastOpened} — older than ${requiredSince}, ` +
|
|
188
|
+
`whose one-time migration gives such an archive its row cell under the occupant token the archive already carries. ${consequence} ` +
|
|
189
|
+
`Start the root once with a release at or above ${requiredSince} that still carries that migration (server or run-local) and let that start finish, then start this build.`);
|
|
190
|
+
}
|
|
191
|
+
return f.unknownFields.length > 0
|
|
192
|
+
? ` ${f.label} at ${f.path}: UNKNOWN field(s) ${quotedFieldNames(f.unknownFields)} — the data root is NEWER than this build (a newer server engine wrote it).`
|
|
193
|
+
: ` ${f.label} at ${f.path}: unreadable (${f.problem}) — this build cannot read it, and an unreadable file is treated as a mismatch, not as empty.`;
|
|
194
|
+
}
|
|
157
195
|
function describeLocalDataRootMismatch(findings, when) {
|
|
158
196
|
const lines = [
|
|
159
197
|
when === "boot"
|
|
160
198
|
? `${SCHEMA_MISMATCH_ERROR_CODE}: the local data root does not match this build's shape — refusing to start.`
|
|
161
199
|
: `${SCHEMA_MISMATCH_ERROR_CODE}: the local data root does not match this build's shape — refusing to read or write the file(s) named below until this process is restarted.`,
|
|
162
200
|
];
|
|
163
|
-
for (const f of findings)
|
|
164
|
-
lines.push(f
|
|
165
|
-
|
|
166
|
-
: ` ${f.label} at ${f.path}: unreadable (${f.problem}) — this build cannot read it, and an unreadable file is treated as a mismatch, not as empty.`);
|
|
167
|
-
}
|
|
168
|
-
lines.push("This build never reads such a file as empty and never writes over it: either would silently change what the file records.", "", " Ways out (these three and no others):", " (1) the data root is NEWER than this build (unknown fields): deploy the build that wrote it;", " (2) roll back: restore the data-root backup taken before the upgrade, then start this build — also the way out when the file's bytes are damaged (not-json / malformed);", " (3) the file could not be read at all (an I/O error such as EACCES): repair access to it (permissions, mount, disk) and restart — nothing in the file needs changing.", "The check behind this refusal only reads: the file was left in place and nothing in the data root was changed.");
|
|
201
|
+
for (const f of findings)
|
|
202
|
+
lines.push(describeDataRootFinding(f));
|
|
203
|
+
lines.push("This build never reads such a file as empty and never writes over it: either would silently change what the file records.", "", " Ways out (these four and no others):", " (1) the data root is NEWER than this build (unknown fields, or an engine high-water above this build): deploy the build that wrote it (for a high-water: server at or above it) — to run an older build on this machine, give it its own data root (LOCAL_DATA_ROOT);", " (2) roll back: restore the data-root backup taken before the upgrade, then start this build — also the way out when the file's bytes are damaged (not-json / malformed);", " (3) the file could not be read at all (an I/O error such as EACCES): repair access to it (permissions, mount, disk) and restart — nothing in the file needs changing;", " (4) the data root skipped a required one-time migration, or a release carrying it opened the root and did not finish it (the finding names the release to start with): start the root once with a release at or above that one that still carries the migration (server or run-local), let that start finish, then start this build.", "The check behind this refusal only reads: the file(s) named above were left in place, unchanged.");
|
|
169
204
|
return lines.join("\n");
|
|
170
205
|
}
|
|
171
206
|
export class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
|
|
@@ -179,6 +214,165 @@ export class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
|
|
|
179
214
|
this.name = "LocalDataRootSchemaMismatchError";
|
|
180
215
|
}
|
|
181
216
|
}
|
|
217
|
+
export const ARCHIVE_TOKEN_ADOPTION_SINCE = "7.148.0";
|
|
218
|
+
export function archiveTokenAdoptionCarried() {
|
|
219
|
+
return ONE_TIME_MIGRATIONS.some((w) => w.id === "M47");
|
|
220
|
+
}
|
|
221
|
+
const ARCHIVE_DIR = "sessions";
|
|
222
|
+
const ARCHIVE_SUFFIX = ".jsonl";
|
|
223
|
+
const ARCHIVE_TOKEN_ADOPTION_PENDING_FILE = "archive-token-adoption.pending";
|
|
224
|
+
const archiveTokenAdoptionPendingPath = (root) => resolve(join(root, ARCHIVE_TOKEN_ADOPTION_PENDING_FILE));
|
|
225
|
+
function archiveTokenAdoptionPendingPresent(root) {
|
|
226
|
+
try {
|
|
227
|
+
lstatSync(archiveTokenAdoptionPendingPath(root));
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
if (e?.code === "ENOENT")
|
|
232
|
+
return false;
|
|
233
|
+
throw e;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function recordArchiveTokenAdoptionPending(root, priorHighWater) {
|
|
237
|
+
const path = archiveTokenAdoptionPendingPath(root);
|
|
238
|
+
const prior = priorHighWater === undefined ? "none" : releaseText(priorHighWater);
|
|
239
|
+
try {
|
|
240
|
+
atomicWriteFileSync(path, `opened ${new Date().toISOString()} by a release that adopts archive tokens; engine high-water before that start: ${prior}\n`, { durability: "required" });
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
throw new Error(`DB_BACKEND=local cannot record ${path} durably: ${e instanceof Error ? e.message : String(e)} — refusing to open the data root: ` +
|
|
244
|
+
"this start raises the engine high-water past the release that adopts the occupant tokens of session archives without a session-meta cell, and without this record a later build could not tell that the adoption has not finished. " +
|
|
245
|
+
"Repair write access to the data root and restart.", { cause: e });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function dischargeArchiveTokenAdoptionPending(root) {
|
|
249
|
+
const path = archiveTokenAdoptionPendingPath(root);
|
|
250
|
+
try {
|
|
251
|
+
unlinkSync(path);
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
if (e?.code !== "ENOENT")
|
|
255
|
+
throw e;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!IS_WIN32)
|
|
259
|
+
fsyncPaths(dirname(path));
|
|
260
|
+
}
|
|
261
|
+
function archiveIdsInSync(root) {
|
|
262
|
+
let names;
|
|
263
|
+
try {
|
|
264
|
+
names = readdirSync(join(root, ARCHIVE_DIR));
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
if (e?.code === "ENOENT")
|
|
268
|
+
return [];
|
|
269
|
+
throw e;
|
|
270
|
+
}
|
|
271
|
+
return names.filter((name) => name.endsWith(ARCHIVE_SUFFIX)).map((name) => name.slice(0, -ARCHIVE_SUFFIX.length));
|
|
272
|
+
}
|
|
273
|
+
export function archiveIdsWithoutCellSync(root) {
|
|
274
|
+
const sidecarPath = resolve(join(root, SESSION_META_SIDECAR_FILE));
|
|
275
|
+
const label = `${SESSION_META_SIDECAR_FILE} (the local session-meta sidecar)`;
|
|
276
|
+
const raw = readDataRootFileTextSync(sidecarPath);
|
|
277
|
+
if (raw.state === "unreadable")
|
|
278
|
+
throw new LocalDataRootSchemaMismatchError([dataRootMismatchOf(sidecarPath, label, raw.fault)], "boot");
|
|
279
|
+
let cells = {};
|
|
280
|
+
if (raw.state === "text") {
|
|
281
|
+
try {
|
|
282
|
+
cells = decodeJsonColumn(raw.text, readSessionMetaSidecar, label);
|
|
283
|
+
}
|
|
284
|
+
catch (e) {
|
|
285
|
+
throw new LocalDataRootSchemaMismatchError([dataRootMismatchOf(sidecarPath, label, e)], "boot");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return archiveIdsInSync(root)
|
|
289
|
+
.filter((id) => !Object.hasOwn(cells, id))
|
|
290
|
+
.sort();
|
|
291
|
+
}
|
|
292
|
+
export function holdArchiveTokenAdoptionDuty(root, priorHighWater, adoptionCarried) {
|
|
293
|
+
const since = releaseTriple(ARCHIVE_TOKEN_ADOPTION_SINCE);
|
|
294
|
+
const neverOpenedByAdoptingRelease = priorHighWater === undefined || compareReleaseTriples(priorHighWater, since) < 0;
|
|
295
|
+
if (adoptionCarried) {
|
|
296
|
+
if (neverOpenedByAdoptingRelease && archiveIdsInSync(root).length > 0)
|
|
297
|
+
recordArchiveTokenAdoptionPending(root, priorHighWater);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const owed = archiveTokenAdoptionPendingPresent(root);
|
|
301
|
+
if (!neverOpenedByAdoptingRelease && !owed)
|
|
302
|
+
return;
|
|
303
|
+
const cellLess = archiveIdsWithoutCellSync(root);
|
|
304
|
+
if (cellLess.length === 0) {
|
|
305
|
+
if (owed)
|
|
306
|
+
dischargeArchiveTokenAdoptionPending(root);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
throw new LocalDataRootSchemaMismatchError([
|
|
310
|
+
{
|
|
311
|
+
path: resolve(join(root, ARCHIVE_DIR)),
|
|
312
|
+
label: `${ARCHIVE_DIR} (the local session archives)`,
|
|
313
|
+
problem: "skipped-required-migration",
|
|
314
|
+
unknownFields: [],
|
|
315
|
+
skippedMigration: { highWater: priorHighWater === undefined ? null : releaseText(priorHighWater), requiredSince: ARCHIVE_TOKEN_ADOPTION_SINCE, sessions: cellLess.length },
|
|
316
|
+
},
|
|
317
|
+
], "boot");
|
|
318
|
+
}
|
|
319
|
+
function firstCellIncarnationOf(sessionId, archiveToken) {
|
|
320
|
+
const token = archiveTokenAdoptionCarried() ? incarnationFromCell(sessionId, archiveToken, "absent") : undefined;
|
|
321
|
+
return token !== undefined ? { incarnation: token, adopted: true } : { incarnation: mintSessionIncarnation(), adopted: false };
|
|
322
|
+
}
|
|
323
|
+
function cellChangedUnderLock(sessionId) {
|
|
324
|
+
throw new SessionError("storage", `LocalSessionStore: session ${sessionId}'s session-meta cell changed under its own write lock between the observation and the write — refusing to guess its occupant`);
|
|
325
|
+
}
|
|
326
|
+
export async function readDataRootFileText(path) {
|
|
327
|
+
try {
|
|
328
|
+
return { state: "text", text: await readFile(path, "utf8") };
|
|
329
|
+
}
|
|
330
|
+
catch (e) {
|
|
331
|
+
if (e?.code !== "ENOENT")
|
|
332
|
+
return { state: "unreadable", fault: e };
|
|
333
|
+
return judgedAfterEnoent(e, await lstat(path).then(() => ENTRY_PRESENT, (fault) => ({ fault })));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
export function readDataRootFileTextSync(path) {
|
|
337
|
+
try {
|
|
338
|
+
return { state: "text", text: readFileSync(path, "utf8") };
|
|
339
|
+
}
|
|
340
|
+
catch (e) {
|
|
341
|
+
if (e?.code !== "ENOENT")
|
|
342
|
+
return { state: "unreadable", fault: e };
|
|
343
|
+
let probe = ENTRY_PRESENT;
|
|
344
|
+
try {
|
|
345
|
+
lstatSync(path);
|
|
346
|
+
}
|
|
347
|
+
catch (fault) {
|
|
348
|
+
probe = { fault };
|
|
349
|
+
}
|
|
350
|
+
return judgedAfterEnoent(e, probe);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const ENTRY_PRESENT = { present: true };
|
|
354
|
+
function judgedAfterEnoent(readFault, probe) {
|
|
355
|
+
if ("present" in probe)
|
|
356
|
+
return { state: "unreadable", fault: readFault };
|
|
357
|
+
return probe.fault?.code === "ENOENT" ? { state: "absent" } : { state: "unreadable", fault: probe.fault };
|
|
358
|
+
}
|
|
359
|
+
export function dataRootMismatchOf(path, label, fault) {
|
|
360
|
+
if (fault instanceof SqlJsonColumnError)
|
|
361
|
+
return { path, label, problem: fault.problem, unknownFields: fault.unknownKeys };
|
|
362
|
+
const code = fault?.code;
|
|
363
|
+
return { path, label, problem: typeof code === "string" && /^E[A-Z0-9_]+$/.test(code) ? code : "unreadable", unknownFields: [] };
|
|
364
|
+
}
|
|
365
|
+
async function readSessionMetaForM40(path) {
|
|
366
|
+
const raw = await readDataRootFileText(path);
|
|
367
|
+
if (raw.state !== "text")
|
|
368
|
+
return raw;
|
|
369
|
+
try {
|
|
370
|
+
return { state: "read", entries: decodeJsonColumn(raw.text, readJsonObjectColumn, `${SESSION_META_SIDECAR_FILE} (one-time migration M40)`) };
|
|
371
|
+
}
|
|
372
|
+
catch (e) {
|
|
373
|
+
return { state: "unreadable", fault: e };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
182
376
|
class SidecarMap {
|
|
183
377
|
path;
|
|
184
378
|
label;
|
|
@@ -243,16 +437,27 @@ class SidecarMap {
|
|
|
243
437
|
if (disk.state === "unreadable")
|
|
244
438
|
throw this.refuse(path, disk);
|
|
245
439
|
}
|
|
246
|
-
async
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
440
|
+
async landed(path, changed, draft) {
|
|
441
|
+
const disk = await this.readDisk(path);
|
|
442
|
+
switch (disk.state) {
|
|
443
|
+
case "absent":
|
|
444
|
+
return "not-landed";
|
|
445
|
+
case "unreadable":
|
|
446
|
+
return "unknown";
|
|
447
|
+
case "read":
|
|
448
|
+
return changed.every((key) => (draft.has(key) ? Object.hasOwn(disk.entries, key) && isDeepStrictEqual(disk.entries[key], draft.get(key)) : !Object.hasOwn(disk.entries, key))) ? "landed" : "not-landed";
|
|
449
|
+
default: {
|
|
450
|
+
const unknownRead = disk;
|
|
451
|
+
throw new Error(`SidecarMap: no sidecar read state ${JSON.stringify(unknownRead)}`);
|
|
452
|
+
}
|
|
253
453
|
}
|
|
454
|
+
}
|
|
455
|
+
async readDisk(path) {
|
|
456
|
+
const raw = await readDataRootFileText(path);
|
|
457
|
+
if (raw.state !== "text")
|
|
458
|
+
return raw;
|
|
254
459
|
try {
|
|
255
|
-
return { state: "read", entries: decodeJsonColumn(text, this.read, this.label) };
|
|
460
|
+
return { state: "read", entries: decodeJsonColumn(raw.text, this.read, this.label) };
|
|
256
461
|
}
|
|
257
462
|
catch (e) {
|
|
258
463
|
return { state: "unreadable", fault: e };
|
|
@@ -260,15 +465,13 @@ class SidecarMap {
|
|
|
260
465
|
}
|
|
261
466
|
refuse(path, disk) {
|
|
262
467
|
const shape = isJsonColumnBodyProblem(disk.fault);
|
|
263
|
-
const
|
|
264
|
-
const unknownFields = disk.fault instanceof SqlJsonColumnError ? disk.fault.unknownKeys : [];
|
|
265
|
-
const refusal = new LocalDataRootSchemaMismatchError([{ path, label: this.label, problem, unknownFields }], "serving");
|
|
468
|
+
const refusal = new LocalDataRootSchemaMismatchError([dataRootMismatchOf(path, this.label, disk.fault)], "serving");
|
|
266
469
|
if (shape)
|
|
267
470
|
this.state.refused = refusal;
|
|
268
471
|
return refusal;
|
|
269
472
|
}
|
|
270
473
|
persist() {
|
|
271
|
-
return this.commit(() => true);
|
|
474
|
+
return this.commit(() => true, "best-effort");
|
|
272
475
|
}
|
|
273
476
|
syncToDisk() {
|
|
274
477
|
const path = this.path;
|
|
@@ -276,10 +479,10 @@ class SidecarMap {
|
|
|
276
479
|
return;
|
|
277
480
|
fsyncPaths(path, dirname(path));
|
|
278
481
|
}
|
|
279
|
-
deleteKey(key) {
|
|
280
|
-
return this.commit((draft) => draft.delete(key));
|
|
482
|
+
deleteKey(key, durability) {
|
|
483
|
+
return this.commit((draft) => draft.delete(key), durability);
|
|
281
484
|
}
|
|
282
|
-
commit(change) {
|
|
485
|
+
commit(change, durability) {
|
|
283
486
|
const path = this.path;
|
|
284
487
|
const st = this.state;
|
|
285
488
|
const link = st.writeChain.then(async () => {
|
|
@@ -287,8 +490,27 @@ class SidecarMap {
|
|
|
287
490
|
throw st.refused;
|
|
288
491
|
const base = new Map(st.entries);
|
|
289
492
|
const draft = new Map(base);
|
|
290
|
-
if (!change(draft))
|
|
291
|
-
|
|
493
|
+
if (!change(draft)) {
|
|
494
|
+
if (durability !== "required" || !path || !existsSync(path))
|
|
495
|
+
return true;
|
|
496
|
+
try {
|
|
497
|
+
proveDurableSync(path);
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
catch (e) {
|
|
501
|
+
this.onWriteFailed(e);
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const changed = [...new Set([...base.keys(), ...draft.keys()])].filter((key) => !(draft.has(key) === base.has(key) && draft.get(key) === base.get(key)));
|
|
506
|
+
const publish = () => {
|
|
507
|
+
for (const key of changed) {
|
|
508
|
+
if (draft.has(key))
|
|
509
|
+
st.entries.set(key, draft.get(key));
|
|
510
|
+
else
|
|
511
|
+
st.entries.delete(key);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
292
514
|
if (path) {
|
|
293
515
|
if (this.strict) {
|
|
294
516
|
const disk = await this.readDisk(path);
|
|
@@ -300,23 +522,16 @@ class SidecarMap {
|
|
|
300
522
|
}
|
|
301
523
|
}
|
|
302
524
|
try {
|
|
303
|
-
|
|
304
|
-
await writeFile(tmp, JSON.stringify(Object.fromEntries(draft)), "utf8");
|
|
305
|
-
await rename(tmp, path);
|
|
525
|
+
await atomicWriteFileAsync(path, JSON.stringify(Object.fromEntries(draft)), { durability });
|
|
306
526
|
}
|
|
307
527
|
catch (e) {
|
|
308
528
|
this.onWriteFailed(e);
|
|
529
|
+
if (durability === "required" && (await this.landed(path, changed, draft)) !== "not-landed")
|
|
530
|
+
publish();
|
|
309
531
|
return false;
|
|
310
532
|
}
|
|
311
533
|
}
|
|
312
|
-
|
|
313
|
-
if (draft.has(key) === base.has(key) && draft.get(key) === base.get(key))
|
|
314
|
-
continue;
|
|
315
|
-
if (draft.has(key))
|
|
316
|
-
st.entries.set(key, draft.get(key));
|
|
317
|
-
else
|
|
318
|
-
st.entries.delete(key);
|
|
319
|
-
}
|
|
534
|
+
publish();
|
|
320
535
|
return true;
|
|
321
536
|
});
|
|
322
537
|
st.writeChain = link.then(() => undefined, () => undefined);
|
|
@@ -428,14 +643,12 @@ export class LocalSessionStore {
|
|
|
428
643
|
listPlaced;
|
|
429
644
|
recordedPlacementOf;
|
|
430
645
|
pending = new Map();
|
|
431
|
-
rowsDropped = new Set();
|
|
432
646
|
lastActivity = new Map();
|
|
433
647
|
stagings = new Map();
|
|
434
648
|
loaded;
|
|
435
649
|
flushTimer;
|
|
436
650
|
writeChain = Promise.resolve();
|
|
437
651
|
dirty = false;
|
|
438
|
-
writeSeq = 0;
|
|
439
652
|
lastWarnAt = 0;
|
|
440
653
|
sidecarPath;
|
|
441
654
|
titles;
|
|
@@ -450,10 +663,12 @@ export class LocalSessionStore {
|
|
|
450
663
|
executionPlacementBackfill;
|
|
451
664
|
dataRoot;
|
|
452
665
|
archiveSnapshotsSettled = 0;
|
|
666
|
+
withdrawCheckpointCtx;
|
|
453
667
|
constructor(repo, runs, dataDir, opts) {
|
|
454
668
|
this.repo = repo;
|
|
455
669
|
this.runs = runs;
|
|
456
670
|
this.sidecarPath = dataDir ? join(dataDir, "session-last-activity.json") : undefined;
|
|
671
|
+
this.withdrawCheckpointCtx = opts?.withdrawCheckpointCtx;
|
|
457
672
|
this.log = opts?.log ?? createLogger();
|
|
458
673
|
const repoPlacementOf = repo.placementOf;
|
|
459
674
|
const repoListPlaced = repo.listPlaced;
|
|
@@ -493,7 +708,7 @@ export class LocalSessionStore {
|
|
|
493
708
|
this.pinned = sidecar("session-pinned.json", "pinned", readPinnedSidecar);
|
|
494
709
|
this.instructionSources = sidecar("session-instruction-sources.json", "instruction-source", readStringRecordColumn);
|
|
495
710
|
this.workspaces = sidecar("session-workspaces.json", "workspace", readStringRecordColumn);
|
|
496
|
-
this.meta = sidecar(
|
|
711
|
+
this.meta = sidecar(SESSION_META_SIDECAR_FILE, "session-meta", readSessionMetaSidecar, true);
|
|
497
712
|
const root = dataDir ? resolve(dataDir) : undefined;
|
|
498
713
|
this.dataRoot = root;
|
|
499
714
|
if (root !== undefined && !SESSION_WRITE_LOCKS_BY_ROOT.has(root))
|
|
@@ -504,10 +719,44 @@ export class LocalSessionStore {
|
|
|
504
719
|
place: (sessionId, occupant, cell) => this.placeExecution(sessionId, occupant, cell),
|
|
505
720
|
markMirror: (sessionId, occupant) => this.markMirrorExecution(sessionId, occupant),
|
|
506
721
|
};
|
|
507
|
-
this.executionPlacementBackfill = {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
722
|
+
this.executionPlacementBackfill = { backfill: (sessionId, lanes) => this.backfillPlacement(sessionId, lanes) };
|
|
723
|
+
}
|
|
724
|
+
async scenarioPrefixOwnerRowsM42() {
|
|
725
|
+
await this.meta.ensureLoaded();
|
|
726
|
+
let n = 0;
|
|
727
|
+
for (const id of [...this.liveCellIds(), ...this.droppingIds(), ...this.deletingIds()]) {
|
|
728
|
+
const cell = this.cellStateOf(id);
|
|
729
|
+
if (cell.state !== "none" && typeof cell.meta.owner === "string" && cell.meta.owner.startsWith(SCENARIO_SUBJECT_PREFIX))
|
|
730
|
+
n += 1;
|
|
731
|
+
}
|
|
732
|
+
return n;
|
|
733
|
+
}
|
|
734
|
+
async renameRetiredExecutionLaneWordM40() {
|
|
735
|
+
if (this.dataRoot === undefined)
|
|
736
|
+
return 0;
|
|
737
|
+
const path = join(this.dataRoot, SESSION_META_SIDECAR_FILE);
|
|
738
|
+
const disk = await readSessionMetaForM40(path);
|
|
739
|
+
if (disk.state !== "read")
|
|
740
|
+
return 0;
|
|
741
|
+
let renamed = 0;
|
|
742
|
+
for (const cell of Object.values(disk.entries)) {
|
|
743
|
+
if (!isJsonObject(cell))
|
|
744
|
+
continue;
|
|
745
|
+
const execution = cell["execution"];
|
|
746
|
+
if (!isJsonObject(execution) || execution["role"] !== "writer" || execution["lane"] !== RETIRED_EXECUTION_LANE_WORD_M40)
|
|
747
|
+
continue;
|
|
748
|
+
execution["lane"] = RENAMED_EXECUTION_LANE_M40;
|
|
749
|
+
renamed += 1;
|
|
750
|
+
}
|
|
751
|
+
if (renamed === 0)
|
|
752
|
+
return 0;
|
|
753
|
+
try {
|
|
754
|
+
await atomicWriteFileAsync(path, JSON.stringify(disk.entries), { durability: "best-effort" });
|
|
755
|
+
}
|
|
756
|
+
catch (e) {
|
|
757
|
+
throw new ExecutionLaneRenameWriteRefusedError(path, e);
|
|
758
|
+
}
|
|
759
|
+
return renamed;
|
|
511
760
|
}
|
|
512
761
|
async assertStrictSidecarsReadable() {
|
|
513
762
|
const loads = await Promise.allSettled(this.strictSidecars.map((map) => map.reconcile()));
|
|
@@ -522,39 +771,67 @@ export class LocalSessionStore {
|
|
|
522
771
|
if (findings.length > 0)
|
|
523
772
|
throw new LocalDataRootSchemaMismatchError(findings, "boot");
|
|
524
773
|
}
|
|
525
|
-
async
|
|
774
|
+
async forkingOf(sessionId, meta) {
|
|
526
775
|
if (!isForkCreatedCell(meta))
|
|
527
776
|
return false;
|
|
528
|
-
|
|
529
|
-
if (probe.state === "unusable" && probe.word === "storage")
|
|
530
|
-
return probe;
|
|
531
|
-
return probe.state === "absent";
|
|
777
|
+
return (await this.probeArchive(sessionId)).state === "absent";
|
|
532
778
|
}
|
|
533
|
-
async
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (partition.state === "unusable" && partition.word === "storage")
|
|
549
|
-
return partition;
|
|
550
|
-
const forking = await this.forkingRead(sessionId, read.meta);
|
|
551
|
-
return typeof forking === "boolean" ? readingOfCell(sessionId, read.meta, forking) : forking;
|
|
779
|
+
async observeRow(sessionId, want = {}) {
|
|
780
|
+
await this.meta.ensureLoaded();
|
|
781
|
+
const cell = this.cellStateOf(sessionId);
|
|
782
|
+
if (cell.state === "dropping" || cell.state === "deleting")
|
|
783
|
+
return ABSENT_ROW;
|
|
784
|
+
if (cell.state === "none") {
|
|
785
|
+
const probe = await this.probeArchive(sessionId);
|
|
786
|
+
if (probe.state === "absent")
|
|
787
|
+
return ABSENT_ROW;
|
|
788
|
+
const placed = want.placement === true ? await this.placedRecordOf(sessionId) : undefined;
|
|
789
|
+
return { row: "legacy", placed, ...(probe.state === "corrupt" ? { unreadable: probe.fault } : {}) };
|
|
790
|
+
}
|
|
791
|
+
const placed = want.placement === true ? await this.placedRecordOf(sessionId) : undefined;
|
|
792
|
+
const forking = want.forking === true && placed === undefined ? await this.forkingOf(sessionId, cell.meta) : false;
|
|
793
|
+
return { row: "live", meta: cell.meta, placed, forking };
|
|
552
794
|
}
|
|
553
795
|
async readExecutionPlacement(sessionId) {
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
return
|
|
796
|
+
const seen = await this.observeRow(sessionId, { placement: true, forking: true });
|
|
797
|
+
if (!legacyNeedsMint(seen))
|
|
798
|
+
return executionReadingOf(sessionId, seen);
|
|
799
|
+
return this.withSessionWriteLock(sessionId, async () => {
|
|
800
|
+
const held = await this.observeRow(sessionId, { placement: true, forking: true });
|
|
801
|
+
return legacyNeedsMint(held) ? readingOfCell(sessionId, await this.mintHeld(sessionId), false) : executionReadingOf(sessionId, held);
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
async backfillPlacement(sessionId, lanes) {
|
|
805
|
+
return this.withSessionWriteLock(sessionId, async () => {
|
|
806
|
+
let seen;
|
|
807
|
+
try {
|
|
808
|
+
seen = await this.observeRow(sessionId, { placement: true, forking: true });
|
|
809
|
+
}
|
|
810
|
+
catch (fault) {
|
|
811
|
+
return unusableArchiveOfFault(fault);
|
|
812
|
+
}
|
|
813
|
+
if (seen.row === "absent" || seen.placed !== undefined)
|
|
814
|
+
return { outcome: "skipped" };
|
|
815
|
+
if (seen.row === "legacy" && seen.unreadable !== undefined)
|
|
816
|
+
return unusableArchiveOfFault(ownerUnreadableFault(seen.unreadable));
|
|
817
|
+
if (seen.row === "live" && (seen.meta.execution !== undefined || seen.forking))
|
|
818
|
+
return { outcome: "skipped" };
|
|
819
|
+
if (lanes.length !== 1)
|
|
820
|
+
return { outcome: "ambiguous" };
|
|
821
|
+
const writer = { role: "writer", lane: lanes[0] };
|
|
822
|
+
assertPlaceableLane(sessionId, writer);
|
|
823
|
+
let first;
|
|
824
|
+
if (seen.row === "legacy") {
|
|
825
|
+
try {
|
|
826
|
+
first = await this.firstCellIncarnationHeld(sessionId);
|
|
827
|
+
}
|
|
828
|
+
catch (fault) {
|
|
829
|
+
return unusableArchiveOfFault(fault);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
await this.writeMeta(sessionId, (before) => before === undefined ? { incarnation: first ?? cellChangedUnderLock(sessionId), execution: writer } : readSessionMetaCell(before).execution === undefined ? cellWith(before, { execution: writer }) : undefined);
|
|
833
|
+
return { outcome: "placed" };
|
|
834
|
+
});
|
|
558
835
|
}
|
|
559
836
|
async placeExecution(sessionId, occupant, cell) {
|
|
560
837
|
assertPlaceableLane(sessionId, cell);
|
|
@@ -566,7 +843,7 @@ export class LocalSessionStore {
|
|
|
566
843
|
async markMirrorExecution(sessionId, occupant) {
|
|
567
844
|
return this.withSessionWriteLock(sessionId, async () => {
|
|
568
845
|
await this.meta.ensureLoaded();
|
|
569
|
-
const claim = this.
|
|
846
|
+
const claim = this.liveCellOf(sessionId) === undefined ? undefined : await this.runs.getActiveTaskId(sessionId);
|
|
570
847
|
return this.settleExecutionHeld(sessionId, (reading) => {
|
|
571
848
|
const settled = settledMirrorOutcome(sessionId, reading, occupant);
|
|
572
849
|
if (settled !== WRITABLE)
|
|
@@ -576,9 +853,9 @@ export class LocalSessionStore {
|
|
|
576
853
|
});
|
|
577
854
|
}
|
|
578
855
|
async settleExecutionHeld(sessionId, judge) {
|
|
579
|
-
await this.
|
|
580
|
-
const placed =
|
|
581
|
-
const forking =
|
|
856
|
+
const seen = await this.observeRow(sessionId, { placement: true, forking: true });
|
|
857
|
+
const placed = seen.row !== "absent" && seen.placed !== undefined;
|
|
858
|
+
const forking = seen.row === "live" && seen.forking;
|
|
582
859
|
let settled;
|
|
583
860
|
let fault;
|
|
584
861
|
await this.writeMeta(sessionId, (before) => {
|
|
@@ -595,33 +872,75 @@ export class LocalSessionStore {
|
|
|
595
872
|
throw fault;
|
|
596
873
|
return settled.outcome;
|
|
597
874
|
}
|
|
598
|
-
|
|
875
|
+
cellStateOf(sessionId) {
|
|
599
876
|
const cell = this.meta.state.entries.get(sessionId);
|
|
600
|
-
|
|
877
|
+
if (cell === undefined)
|
|
878
|
+
return NO_CELL;
|
|
879
|
+
const meta = readSessionMetaCell(cell);
|
|
880
|
+
return { state: meta.deleting !== undefined ? "deleting" : meta.dropping ? "dropping" : "live", meta };
|
|
881
|
+
}
|
|
882
|
+
async rowDropping(sessionId) {
|
|
883
|
+
await this.meta.ensureLoaded();
|
|
884
|
+
const state = this.cellStateOf(sessionId).state;
|
|
885
|
+
return state === "dropping" || state === "deleting";
|
|
886
|
+
}
|
|
887
|
+
rowGoneSince(sessionId, hadLiveCell) {
|
|
888
|
+
const state = this.cellStateOf(sessionId).state;
|
|
889
|
+
return state === "dropping" || state === "deleting" || (state === "none" && hadLiveCell);
|
|
890
|
+
}
|
|
891
|
+
liveCellOf(sessionId) {
|
|
892
|
+
const cell = this.cellStateOf(sessionId);
|
|
893
|
+
return cell.state === "live" ? cell.meta : undefined;
|
|
894
|
+
}
|
|
895
|
+
liveCellIds() {
|
|
896
|
+
return [...this.meta.state.entries]
|
|
897
|
+
.filter(([, cell]) => {
|
|
898
|
+
const meta = readSessionMetaCell(cell);
|
|
899
|
+
return !meta.dropping && meta.deleting === undefined;
|
|
900
|
+
})
|
|
901
|
+
.map(([id]) => id)
|
|
902
|
+
.sort();
|
|
903
|
+
}
|
|
904
|
+
deletingIds() {
|
|
905
|
+
return [...this.meta.state.entries].filter(([id]) => this.cellStateOf(id).state === "deleting").map(([id]) => id).sort();
|
|
906
|
+
}
|
|
907
|
+
droppingIds() {
|
|
908
|
+
return [...this.meta.state.entries].filter(([id]) => this.cellStateOf(id).state === "dropping").map(([id]) => id).sort();
|
|
601
909
|
}
|
|
602
910
|
async writeMeta(sessionId, next) {
|
|
603
911
|
await this.meta.ensureLoaded();
|
|
604
912
|
let changed = false;
|
|
605
913
|
const committed = await this.meta.commit((draft) => {
|
|
606
|
-
const
|
|
914
|
+
const before = draft.get(sessionId);
|
|
915
|
+
if (before !== undefined && readSessionMetaCell(before).deleting !== undefined)
|
|
916
|
+
throw new SessionError("not_found", `LocalSessionStore: session ${sessionId} is being deleted — it reads as gone; this write was not made`);
|
|
917
|
+
if (before !== undefined && readSessionMetaCell(before).dropping)
|
|
918
|
+
throw new Error(`LocalSessionStore: session ${sessionId}'s cell carries the drop mark while another writer reached it — every section finishes a pending drop first; refusing to write`);
|
|
919
|
+
const value = next(before);
|
|
607
920
|
if (value === undefined)
|
|
608
921
|
return false;
|
|
609
922
|
draft.set(sessionId, value);
|
|
610
923
|
changed = true;
|
|
611
924
|
return true;
|
|
612
|
-
});
|
|
925
|
+
}, "best-effort");
|
|
613
926
|
if (!committed)
|
|
614
927
|
throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed for session ${sessionId} — nothing was recorded`);
|
|
615
928
|
return changed;
|
|
616
929
|
}
|
|
617
930
|
async dropMeta(sessionId) {
|
|
618
931
|
await this.meta.ensureLoaded();
|
|
619
|
-
if (!(await this.meta.deleteKey(sessionId)))
|
|
620
|
-
throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed while
|
|
932
|
+
if (!(await this.meta.deleteKey(sessionId, "best-effort")))
|
|
933
|
+
throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed while withdrawing session ${sessionId}'s cell — the cell is still on disk`);
|
|
934
|
+
}
|
|
935
|
+
async withRootRow(sessionId, body) {
|
|
936
|
+
return this.withSessionWriteLock(sessionId, async () => body(await this.rowPresentHeld(sessionId)));
|
|
937
|
+
}
|
|
938
|
+
async rowPresentHeld(sessionId) {
|
|
939
|
+
return (await this.observeRow(sessionId)).row !== "absent";
|
|
621
940
|
}
|
|
622
941
|
async withSessionWriteLock(sessionId, body) {
|
|
623
942
|
const locks = this.sessionWriteLocks;
|
|
624
|
-
const link = (locks.get(sessionId) ?? Promise.resolve()).then(body);
|
|
943
|
+
const link = (locks.get(sessionId) ?? Promise.resolve()).then(async () => body(await this.finishPendingDropHeld(sessionId)));
|
|
625
944
|
const tail = link.then(() => undefined, () => undefined);
|
|
626
945
|
locks.set(sessionId, tail);
|
|
627
946
|
try {
|
|
@@ -639,7 +958,9 @@ export class LocalSessionStore {
|
|
|
639
958
|
}
|
|
640
959
|
catch (e) {
|
|
641
960
|
const word = e instanceof SessionError ? archiveFaultWord(e.code) : "storage";
|
|
642
|
-
|
|
961
|
+
if (word === "storage")
|
|
962
|
+
throw e;
|
|
963
|
+
return word === "absent" ? { state: "absent" } : { state: "corrupt", fault: e };
|
|
643
964
|
}
|
|
644
965
|
}
|
|
645
966
|
async placementProbe(sessionId) {
|
|
@@ -649,76 +970,88 @@ export class LocalSessionStore {
|
|
|
649
970
|
}
|
|
650
971
|
catch (e) {
|
|
651
972
|
const word = e instanceof SessionError ? archiveFaultWord(e.code) : "storage";
|
|
652
|
-
|
|
973
|
+
if (word === "storage")
|
|
974
|
+
throw e;
|
|
975
|
+
return word === "absent" ? { state: "ordinary" } : { state: "corrupt", fault: e };
|
|
653
976
|
}
|
|
654
977
|
}
|
|
655
978
|
async placedRecordOf(sessionId) {
|
|
656
979
|
const probe = await this.placementProbe(sessionId);
|
|
657
|
-
if (probe.state === "unusable") {
|
|
658
|
-
if (probe.word === "storage")
|
|
659
|
-
throw probe.fault;
|
|
660
|
-
return undefined;
|
|
661
|
-
}
|
|
662
980
|
return probe.state === "placed" ? probe.record : undefined;
|
|
663
981
|
}
|
|
664
|
-
async isPlacedRow(sessionId) {
|
|
665
|
-
return (await this.placedRecordOf(sessionId)) !== undefined;
|
|
666
|
-
}
|
|
667
982
|
async placedRecordOrThrow(sessionId) {
|
|
668
983
|
const probe = await this.placementProbe(sessionId);
|
|
669
|
-
if (probe.state === "
|
|
984
|
+
if (probe.state === "corrupt")
|
|
670
985
|
throw probe.fault;
|
|
671
986
|
return probe.state === "placed" ? probe.record : undefined;
|
|
672
987
|
}
|
|
673
988
|
async existsInRepo(sessionId) {
|
|
674
989
|
const probe = await this.probeArchive(sessionId);
|
|
675
|
-
if (probe.state === "
|
|
990
|
+
if (probe.state === "corrupt")
|
|
676
991
|
throw probe.fault;
|
|
677
992
|
return probe.state === "readable";
|
|
678
993
|
}
|
|
679
994
|
async metaOf(sessionId) {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
995
|
+
const seen = await this.observeRow(sessionId);
|
|
996
|
+
switch (seen.row) {
|
|
997
|
+
case "absent":
|
|
998
|
+
return undefined;
|
|
999
|
+
case "live":
|
|
1000
|
+
return seen.meta;
|
|
1001
|
+
case "legacy":
|
|
1002
|
+
if (seen.unreadable !== undefined)
|
|
1003
|
+
throw ownerUnreadableFault(seen.unreadable);
|
|
1004
|
+
return this.withSessionWriteLock(sessionId, () => this.metaHeld(sessionId));
|
|
1005
|
+
default: {
|
|
1006
|
+
const unknownRow = seen;
|
|
1007
|
+
throw new Error(`LocalSessionStore: no row observation ${JSON.stringify(unknownRow)}`);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
684
1010
|
}
|
|
685
|
-
async
|
|
686
|
-
await this.
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if (
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
1011
|
+
async metaHeld(sessionId) {
|
|
1012
|
+
const seen = await this.observeRow(sessionId);
|
|
1013
|
+
if (seen.row === "absent")
|
|
1014
|
+
return undefined;
|
|
1015
|
+
if (seen.row === "live")
|
|
1016
|
+
return seen.meta;
|
|
1017
|
+
if (seen.unreadable !== undefined)
|
|
1018
|
+
throw ownerUnreadableFault(seen.unreadable);
|
|
1019
|
+
return this.mintHeld(sessionId);
|
|
1020
|
+
}
|
|
1021
|
+
async mintHeld(sessionId) {
|
|
1022
|
+
const incarnation = await this.firstCellIncarnationHeld(sessionId);
|
|
1023
|
+
await this.writeMeta(sessionId, (before) => (before === undefined ? { incarnation } : undefined));
|
|
1024
|
+
const meta = this.liveCellOf(sessionId);
|
|
1025
|
+
if (meta === undefined)
|
|
1026
|
+
throw new SessionError("storage", `LocalSessionStore: session ${sessionId} has no live session-meta cell right after its cell was minted under its own write lock — refusing to guess`);
|
|
1027
|
+
return meta;
|
|
1028
|
+
}
|
|
1029
|
+
async firstCellIncarnationHeld(sessionId) {
|
|
1030
|
+
const head = archiveTokenAdoptionCarried() ? await this.archiveHeadOf(sessionId) : undefined;
|
|
1031
|
+
return firstCellIncarnationOf(sessionId, head?.incarnation).incarnation;
|
|
703
1032
|
}
|
|
704
1033
|
async headOf(sessionId) {
|
|
705
1034
|
const before = await this.metaOf(sessionId);
|
|
706
1035
|
if (before === undefined)
|
|
707
1036
|
return undefined;
|
|
708
1037
|
const leaf = await this.archiveHeadOf(sessionId).then((archive) => archive?.leafId ?? null, (fault) => new UnreadableLeaf(fault));
|
|
709
|
-
const
|
|
710
|
-
const after = cell === undefined ? undefined : readSessionMetaCell(cell);
|
|
1038
|
+
const after = this.liveCellOf(sessionId);
|
|
711
1039
|
if (after === undefined || after.incarnation !== before.incarnation || (after.owner ?? null) !== (before.owner ?? null))
|
|
712
1040
|
return undefined;
|
|
713
1041
|
return headFromRow(sessionId, before.owner ?? null, before.incarnation, leaf);
|
|
714
1042
|
}
|
|
1043
|
+
async deletionHeadOf(sessionId) {
|
|
1044
|
+
await this.meta.ensureLoaded();
|
|
1045
|
+
const cell = this.cellStateOf(sessionId);
|
|
1046
|
+
if (cell.state === "dropping" || cell.state === "deleting")
|
|
1047
|
+
return headFromRow(sessionId, cell.meta.owner ?? null, cell.meta.incarnation, null);
|
|
1048
|
+
return this.headOf(sessionId);
|
|
1049
|
+
}
|
|
715
1050
|
archiveHeadOf(sessionId) {
|
|
716
1051
|
return this.repo.headOf(sessionId);
|
|
717
1052
|
}
|
|
718
1053
|
async handOut(sessionId, session) {
|
|
719
|
-
const meta = await this.
|
|
720
|
-
if (meta === undefined)
|
|
721
|
-
throw new SessionError("storage", `LocalSessionStore: session ${sessionId} lost its session-meta cell while it was being handed out`);
|
|
1054
|
+
const meta = this.liveCellOf(sessionId) ?? (await this.mintHeld(sessionId));
|
|
722
1055
|
return { session: occupiedBy(session, rowMetadataOf(sessionId, meta)), sessionId };
|
|
723
1056
|
}
|
|
724
1057
|
async sessionMetadataOf(sessionId) {
|
|
@@ -726,11 +1059,15 @@ export class LocalSessionStore {
|
|
|
726
1059
|
return meta === undefined ? undefined : rowMetadataOf(sessionId, meta);
|
|
727
1060
|
}
|
|
728
1061
|
async probeTitle(sessionId) {
|
|
1062
|
+
if (await this.rowDropping(sessionId))
|
|
1063
|
+
return "none";
|
|
729
1064
|
await this.titles.ensureLoaded();
|
|
730
1065
|
return this.titles.state.entries.has(sessionId) ? "titled" : "untitled";
|
|
731
1066
|
}
|
|
732
1067
|
async setTitleIfNull(sessionId, title) {
|
|
733
1068
|
return this.withSessionWriteLock(sessionId, async () => {
|
|
1069
|
+
if (!(await this.rowPresentHeld(sessionId)))
|
|
1070
|
+
return false;
|
|
734
1071
|
await this.titles.ensureLoaded();
|
|
735
1072
|
const entries = this.titles.state.entries;
|
|
736
1073
|
if (entries.has(sessionId))
|
|
@@ -756,6 +1093,7 @@ export class LocalSessionStore {
|
|
|
756
1093
|
case "mirror":
|
|
757
1094
|
return { kind: "mirror" };
|
|
758
1095
|
case "write":
|
|
1096
|
+
await this.bumpClientWritesHeld(sessionId);
|
|
759
1097
|
await this.writeRowCellsHeld(sessionId, cells);
|
|
760
1098
|
return settleSessionRowWrite(how, verdict, await this.listRowOf(sessionId));
|
|
761
1099
|
case "changed":
|
|
@@ -768,7 +1106,7 @@ export class LocalSessionStore {
|
|
|
768
1106
|
});
|
|
769
1107
|
}
|
|
770
1108
|
async lockedRowOf(sessionId) {
|
|
771
|
-
const meta = this.
|
|
1109
|
+
const meta = this.liveCellOf(sessionId);
|
|
772
1110
|
if (meta === undefined || (await this.rowHeaderOf(sessionId)) === undefined)
|
|
773
1111
|
return undefined;
|
|
774
1112
|
return { owner: meta.owner ?? null, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent"), placed: false, mirror: meta.execution?.role === "mirror", cells: this.rowCellsOf(sessionId) };
|
|
@@ -786,7 +1124,7 @@ export class LocalSessionStore {
|
|
|
786
1124
|
return false;
|
|
787
1125
|
draft.set(sessionId, title);
|
|
788
1126
|
return true;
|
|
789
|
-
});
|
|
1127
|
+
}, "best-effort");
|
|
790
1128
|
if (!ok)
|
|
791
1129
|
throw new SessionError("storage", `LocalSessionStore: title sidecar write failed for session ${sessionId} — the title was not recorded`);
|
|
792
1130
|
}
|
|
@@ -798,7 +1136,7 @@ export class LocalSessionStore {
|
|
|
798
1136
|
return false;
|
|
799
1137
|
draft.set(sessionId, true);
|
|
800
1138
|
return true;
|
|
801
|
-
});
|
|
1139
|
+
}, "best-effort");
|
|
802
1140
|
if (!ok)
|
|
803
1141
|
throw new SessionError("storage", `LocalSessionStore: pin sidecar write failed for session ${sessionId} — the pin was not recorded`);
|
|
804
1142
|
}
|
|
@@ -806,23 +1144,28 @@ export class LocalSessionStore {
|
|
|
806
1144
|
async listRowOf(sessionId) {
|
|
807
1145
|
await this.loadRowSidecars();
|
|
808
1146
|
const header = await this.rowHeaderOf(sessionId);
|
|
809
|
-
const meta = this.
|
|
1147
|
+
const meta = this.liveCellOf(sessionId);
|
|
810
1148
|
if (header === undefined || meta === undefined)
|
|
811
1149
|
return undefined;
|
|
812
|
-
return { row:
|
|
1150
|
+
return { row: this.summaryOf(await this.listedRowOf(header), await this.runRollups()), occupant: { owner: meta.owner ?? null, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent") } };
|
|
813
1151
|
}
|
|
814
1152
|
async sessionSummaryOf(sessionId) {
|
|
815
1153
|
await this.loadRowSidecars();
|
|
816
1154
|
const header = await this.rowHeaderOf(sessionId);
|
|
817
|
-
|
|
1155
|
+
if (header === undefined)
|
|
1156
|
+
return undefined;
|
|
1157
|
+
const hadLiveCell = this.cellStateOf(sessionId).state === "live";
|
|
1158
|
+
const listed = await this.listedRowOf(header);
|
|
1159
|
+
const rollups = await this.runRollups();
|
|
1160
|
+
return this.rowGoneSince(sessionId, hadLiveCell) ? undefined : this.summaryOf(listed, rollups);
|
|
818
1161
|
}
|
|
819
1162
|
async rowHeaders() {
|
|
820
1163
|
await this.meta.ensureLoaded();
|
|
821
|
-
const listed = await this.repo.list();
|
|
1164
|
+
const listed = (await this.repo.list()).filter((row) => this.cellStateOf(row.id).state !== "dropping");
|
|
822
1165
|
const listedIds = new Set(listed.map((row) => row.id));
|
|
823
1166
|
const placed = this.createPlacedIdsReader();
|
|
824
1167
|
const rows = listed.map((header) => ({ sessionId: header.id, header }));
|
|
825
|
-
for (const id of
|
|
1168
|
+
for (const id of this.liveCellIds()) {
|
|
826
1169
|
if (listedIds.has(id))
|
|
827
1170
|
continue;
|
|
828
1171
|
const row = await this.headerUnreadableRowOf(id, placed);
|
|
@@ -833,10 +1176,13 @@ export class LocalSessionStore {
|
|
|
833
1176
|
}
|
|
834
1177
|
async rowHeaderOf(sessionId) {
|
|
835
1178
|
await this.meta.ensureLoaded();
|
|
1179
|
+
const cell = this.cellStateOf(sessionId);
|
|
1180
|
+
if (cell.state === "dropping" || cell.state === "deleting")
|
|
1181
|
+
return undefined;
|
|
836
1182
|
const header = (await this.repo.list()).find((m) => m.id === sessionId);
|
|
837
1183
|
if (header !== undefined)
|
|
838
1184
|
return { sessionId, header };
|
|
839
|
-
return
|
|
1185
|
+
return cell.state === "live" ? this.headerUnreadableRowOf(sessionId, this.createPlacedIdsReader()) : undefined;
|
|
840
1186
|
}
|
|
841
1187
|
async headerUnreadableRowOf(sessionId, placed) {
|
|
842
1188
|
try {
|
|
@@ -856,35 +1202,43 @@ export class LocalSessionStore {
|
|
|
856
1202
|
return { sessionId: row.sessionId, unreadable: row.headerFault };
|
|
857
1203
|
try {
|
|
858
1204
|
await this.archiveHeadOf(row.sessionId);
|
|
859
|
-
return { sessionId: row.sessionId, readable:
|
|
1205
|
+
return { sessionId: row.sessionId, readable: true };
|
|
860
1206
|
}
|
|
861
1207
|
catch (fault) {
|
|
862
1208
|
return { sessionId: row.sessionId, unreadable: fault };
|
|
863
1209
|
}
|
|
864
1210
|
}
|
|
865
1211
|
async readInstructionSource(sessionId) {
|
|
1212
|
+
if (await this.rowDropping(sessionId))
|
|
1213
|
+
return decodeInstructionSource(undefined);
|
|
866
1214
|
await this.instructionSources.ensureLoaded();
|
|
867
1215
|
return decodeInstructionSource(this.instructionSources.state.entries.get(sessionId));
|
|
868
1216
|
}
|
|
869
1217
|
async recordInstructionSource(sessionId, expected, source) {
|
|
870
|
-
await this.
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1218
|
+
await this.withSessionWriteLock(sessionId, async () => {
|
|
1219
|
+
if (!(await this.rowPresentHeld(sessionId)))
|
|
1220
|
+
return;
|
|
1221
|
+
await this.instructionSources.ensureLoaded();
|
|
1222
|
+
const committed = await this.instructionSources.commit((draft) => {
|
|
1223
|
+
if (!sameInstructionSourceBaseline(decodeInstructionSource(draft.get(sessionId)), expected))
|
|
1224
|
+
return false;
|
|
1225
|
+
draft.set(sessionId, encodeInstructionSource(source));
|
|
1226
|
+
return true;
|
|
1227
|
+
}, "best-effort");
|
|
1228
|
+
if (!committed)
|
|
1229
|
+
throw new Error("LocalSessionStore: instruction-source sidecar write failed — baseline not recorded");
|
|
876
1230
|
});
|
|
877
|
-
if (!committed)
|
|
878
|
-
throw new Error("LocalSessionStore: instruction-source sidecar write failed — baseline not recorded");
|
|
879
1231
|
}
|
|
880
1232
|
async readSessionWorkspace(sessionId) {
|
|
1233
|
+
if (await this.rowDropping(sessionId))
|
|
1234
|
+
return undefined;
|
|
881
1235
|
await this.workspaces.ensureLoaded();
|
|
882
1236
|
return decodeSessionWorkspaceCell(this.workspaces.state.entries.get(sessionId) ?? null, sessionId);
|
|
883
1237
|
}
|
|
884
1238
|
async recordSessionWorkspace(sessionId, incarnation, workspace) {
|
|
885
1239
|
const text = encodeSessionWorkspaceCell(workspace, JSON.stringify, sessionId);
|
|
886
1240
|
await this.withSessionWriteLock(sessionId, async () => {
|
|
887
|
-
if ((await this.
|
|
1241
|
+
if ((await this.metaHeld(sessionId))?.incarnation !== incarnation)
|
|
888
1242
|
return;
|
|
889
1243
|
await this.workspaces.ensureLoaded();
|
|
890
1244
|
const committed = await this.workspaces.commit((draft) => {
|
|
@@ -892,7 +1246,7 @@ export class LocalSessionStore {
|
|
|
892
1246
|
return false;
|
|
893
1247
|
draft.set(sessionId, text);
|
|
894
1248
|
return true;
|
|
895
|
-
});
|
|
1249
|
+
}, "best-effort");
|
|
896
1250
|
if (!committed)
|
|
897
1251
|
throw new SessionError("storage", `LocalSessionStore: workspace sidecar write failed for session ${sessionId} — its place was not recorded`);
|
|
898
1252
|
});
|
|
@@ -943,10 +1297,8 @@ export class LocalSessionStore {
|
|
|
943
1297
|
this.dirty = false;
|
|
944
1298
|
const snapshot = Object.fromEntries(this.lastActivity);
|
|
945
1299
|
try {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
await writeFile(tmp, JSON.stringify(snapshot), "utf8");
|
|
949
|
-
await rename(tmp, path);
|
|
1300
|
+
ensureDir(dirname(path));
|
|
1301
|
+
await atomicWriteFileAsync(path, JSON.stringify(snapshot), { durability: "best-effort" });
|
|
950
1302
|
}
|
|
951
1303
|
catch (e) {
|
|
952
1304
|
this.warnSidecar("write", e);
|
|
@@ -963,7 +1315,7 @@ export class LocalSessionStore {
|
|
|
963
1315
|
if (now - this.lastWarnAt < 60_000)
|
|
964
1316
|
return;
|
|
965
1317
|
this.lastWarnAt = now;
|
|
966
|
-
|
|
1318
|
+
writeLogText("stderr", `LocalSessionStore: ${which} sidecar ${op} failed (${SIDECAR_DEGRADES[which]} persistence degraded): ${e instanceof Error ? e.message : String(e)}`);
|
|
967
1319
|
}
|
|
968
1320
|
async acquire(sessionId, opts) {
|
|
969
1321
|
if (!sessionId && this.declaredPlacement(opts) !== undefined)
|
|
@@ -992,6 +1344,9 @@ export class LocalSessionStore {
|
|
|
992
1344
|
}
|
|
993
1345
|
async load(sessionId, requireExisting = false, opts) {
|
|
994
1346
|
return this.withSessionWriteLock(sessionId, async () => {
|
|
1347
|
+
await this.meta.ensureLoaded();
|
|
1348
|
+
if (this.cellStateOf(sessionId).state === "deleting")
|
|
1349
|
+
throw new SessionError("not_found", `session ${sessionId} not found (it is being deleted)`);
|
|
995
1350
|
try {
|
|
996
1351
|
return await this.openForItselfHeld(sessionId, opts);
|
|
997
1352
|
}
|
|
@@ -1002,7 +1357,7 @@ export class LocalSessionStore {
|
|
|
1002
1357
|
throw e;
|
|
1003
1358
|
}
|
|
1004
1359
|
await this.meta.ensureLoaded();
|
|
1005
|
-
if (isForkCreatedCell(this.
|
|
1360
|
+
if (isForkCreatedCell(this.liveCellOf(sessionId)))
|
|
1006
1361
|
throw new SessionForkInProgressError(sessionId);
|
|
1007
1362
|
const placement = this.declaredPlacement(opts);
|
|
1008
1363
|
if (placement !== undefined)
|
|
@@ -1019,7 +1374,7 @@ export class LocalSessionStore {
|
|
|
1019
1374
|
return this.handOut(sessionId, session);
|
|
1020
1375
|
}
|
|
1021
1376
|
async createPlacedHeld(sessionId, placement, opts) {
|
|
1022
|
-
if (this.
|
|
1377
|
+
if (this.liveCellOf(sessionId) !== undefined) {
|
|
1023
1378
|
throw new SessionError("placement_refused", `session ${sessionId} is already registered as an ordinary session — refusing to create a subagent transcript on its id`);
|
|
1024
1379
|
}
|
|
1025
1380
|
const { owner: treeOwner, ...tuple } = placement;
|
|
@@ -1046,8 +1401,8 @@ export class LocalSessionStore {
|
|
|
1046
1401
|
return this.handOut(sessionId, session);
|
|
1047
1402
|
}
|
|
1048
1403
|
async holdsThisPlacement(sessionId, opts) {
|
|
1049
|
-
const probe = await this.placementProbe(sessionId);
|
|
1050
|
-
if (probe.state === "
|
|
1404
|
+
const probe = await this.placementProbe(sessionId).then((answer) => answer, (fault) => ({ state: "storage", fault }));
|
|
1405
|
+
if (probe.state === "corrupt" || probe.state === "storage")
|
|
1051
1406
|
return true;
|
|
1052
1407
|
try {
|
|
1053
1408
|
admitSessionPlacement(sessionId, probe.state === "placed" ? probe.record : undefined, opts, placementCapabilityOf(this));
|
|
@@ -1068,28 +1423,31 @@ export class LocalSessionStore {
|
|
|
1068
1423
|
}
|
|
1069
1424
|
}
|
|
1070
1425
|
async register(sessionId, owner) {
|
|
1071
|
-
await this.
|
|
1426
|
+
await this.registerWithReceipt(sessionId, owner);
|
|
1427
|
+
}
|
|
1428
|
+
async registerWithReceipt(sessionId, owner) {
|
|
1429
|
+
return this.withSessionWriteLock(sessionId, async () => {
|
|
1072
1430
|
await this.meta.ensureLoaded();
|
|
1073
|
-
if (
|
|
1074
|
-
return;
|
|
1431
|
+
if (this.cellStateOf(sessionId).state === "deleting")
|
|
1432
|
+
return {};
|
|
1433
|
+
if (this.liveCellOf(sessionId)?.owner !== undefined)
|
|
1434
|
+
return {};
|
|
1075
1435
|
assertFirstOwnerWritable(sessionId, owner);
|
|
1076
1436
|
const existed = await this.existsInRepo(sessionId);
|
|
1077
|
-
if (!existed && isForkCreatedCell(this.
|
|
1437
|
+
if (!existed && isForkCreatedCell(this.liveCellOf(sessionId)))
|
|
1078
1438
|
throw new SessionForkInProgressError(sessionId);
|
|
1079
|
-
const
|
|
1439
|
+
const born = mintSessionIncarnation();
|
|
1440
|
+
const first = existed && this.liveCellOf(sessionId) === undefined ? await this.firstCellIncarnationHeld(sessionId) : undefined;
|
|
1080
1441
|
const wrote = await this.writeMeta(sessionId, (before) => {
|
|
1081
1442
|
if (ownerRecorded(before))
|
|
1082
1443
|
return undefined;
|
|
1083
|
-
if (claim)
|
|
1084
|
-
return before === undefined ? { incarnation: mintSessionIncarnation(), owner } : cellWith(before, { owner });
|
|
1085
1444
|
if (!existed)
|
|
1086
|
-
return { incarnation:
|
|
1087
|
-
return before === undefined ? { incarnation:
|
|
1445
|
+
return { incarnation: born, owner, createdAt: rowBornNow(), clientWrites: 0 };
|
|
1446
|
+
return before === undefined ? { incarnation: first ?? cellChangedUnderLock(sessionId) } : undefined;
|
|
1088
1447
|
});
|
|
1089
1448
|
if (!existed && wrote)
|
|
1090
1449
|
await this.repo.create({ id: sessionId });
|
|
1091
|
-
|
|
1092
|
-
this.log.info("session_owner_claimed", { sessionId });
|
|
1450
|
+
return !existed && wrote ? { minted: { sessionId, owner, incarnation: born } } : {};
|
|
1093
1451
|
});
|
|
1094
1452
|
}
|
|
1095
1453
|
async touch(sessionId) {
|
|
@@ -1101,8 +1459,10 @@ export class LocalSessionStore {
|
|
|
1101
1459
|
await this.withSessionWriteLock(sessionId, async () => {
|
|
1102
1460
|
if ((await this.placedRecordOrThrow(sessionId)) === undefined)
|
|
1103
1461
|
return;
|
|
1462
|
+
const verdict = await judgeSessionWrite({ op: "delete", rootSessionId: sessionId }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => ({ role: "absent" }), deleting: async () => this.deletingTokenOf(sessionId) });
|
|
1463
|
+
if (verdict !== "proceed")
|
|
1464
|
+
throw verdict.refuse;
|
|
1104
1465
|
await this.dropRowHeld(sessionId);
|
|
1105
|
-
await this.repo.delete({ id: sessionId, createdAt: "" });
|
|
1106
1466
|
});
|
|
1107
1467
|
}
|
|
1108
1468
|
async listSessions(opts) {
|
|
@@ -1110,28 +1470,31 @@ export class LocalSessionStore {
|
|
|
1110
1470
|
}
|
|
1111
1471
|
async listSessionRows(opts) {
|
|
1112
1472
|
await this.loadRowSidecars();
|
|
1473
|
+
const hadLiveCell = new Set(this.liveCellIds());
|
|
1113
1474
|
const rows = [];
|
|
1114
1475
|
for (const header of await this.rowHeaders())
|
|
1115
1476
|
rows.push(await this.listedRowOf(header));
|
|
1116
|
-
const rollups = await this.runRollups();
|
|
1477
|
+
const rollups = await this.runRollups(opts.q);
|
|
1117
1478
|
const all = [];
|
|
1118
1479
|
for (const row of rows) {
|
|
1480
|
+
if (opts.q && !rollups.has(row.sessionId))
|
|
1481
|
+
continue;
|
|
1482
|
+
if (this.rowGoneSince(row.sessionId, hadLiveCell.has(row.sessionId)))
|
|
1483
|
+
continue;
|
|
1119
1484
|
const owner = this.ownerCellOf(row.sessionId);
|
|
1120
1485
|
if (opts.owner !== undefined && owner !== opts.owner && !(opts.includeUnowned === true && owner === null))
|
|
1121
1486
|
continue;
|
|
1122
|
-
const meta = this.
|
|
1487
|
+
const meta = this.liveCellOf(row.sessionId);
|
|
1123
1488
|
const incarnation = meta === undefined ? undefined : incarnationFromCell(row.sessionId, meta.incarnation, "absent");
|
|
1124
|
-
all.push({ row:
|
|
1489
|
+
all.push({ row: this.summaryOf(row, rollups), occupant: { owner, incarnation } });
|
|
1125
1490
|
}
|
|
1126
|
-
|
|
1127
|
-
const filtered = qlc ? all.filter(({ row: r }) => r.objectivePreview?.toLowerCase().includes(qlc)) : all;
|
|
1128
|
-
filtered.sort(({ row: a }, { row: b }) => a.lastActivityAt !== b.lastActivityAt
|
|
1491
|
+
all.sort(({ row: a }, { row: b }) => a.lastActivityAt !== b.lastActivityAt
|
|
1129
1492
|
? (a.lastActivityAt < b.lastActivityAt ? 1 : -1)
|
|
1130
1493
|
: (a.sessionId < b.sessionId ? 1 : -1));
|
|
1131
1494
|
const c = opts.cursor;
|
|
1132
1495
|
const after = c
|
|
1133
|
-
?
|
|
1134
|
-
:
|
|
1496
|
+
? all.filter(({ row: r }) => r.lastActivityAt < c.lastActivityAt || (r.lastActivityAt === c.lastActivityAt && r.sessionId < c.sessionId))
|
|
1497
|
+
: all;
|
|
1135
1498
|
return after.slice(0, opts.limit);
|
|
1136
1499
|
}
|
|
1137
1500
|
async loadRowSidecars() {
|
|
@@ -1141,58 +1504,43 @@ export class LocalSessionStore {
|
|
|
1141
1504
|
await this.meta.ensureLoaded();
|
|
1142
1505
|
}
|
|
1143
1506
|
ownerCellOf(sessionId) {
|
|
1144
|
-
|
|
1145
|
-
return cell === undefined ? null : (readSessionMetaCell(cell).owner ?? null);
|
|
1507
|
+
return this.liveCellOf(sessionId)?.owner ?? null;
|
|
1146
1508
|
}
|
|
1147
|
-
|
|
1509
|
+
summaryOf(listed, rollups) {
|
|
1148
1510
|
const { sessionId } = listed;
|
|
1149
|
-
const firstActivityAt = rowCreatedAtOf(sessionId, this.
|
|
1511
|
+
const firstActivityAt = rowCreatedAtOf(sessionId, this.liveCellOf(sessionId));
|
|
1150
1512
|
const owner = this.ownerCellOf(sessionId);
|
|
1151
1513
|
const cells = this.rowCellsOf(sessionId);
|
|
1152
1514
|
const rowTag = this.rowTagKeyOf(sessionId, owner, cells);
|
|
1153
1515
|
const lastActivityAt = this.lastActivity.get(sessionId) ?? firstActivityAt;
|
|
1154
|
-
const objectivePreview = "readable" in listed ? await this.previewOf(listed.readable) : null;
|
|
1155
1516
|
return {
|
|
1156
1517
|
sessionId,
|
|
1157
1518
|
owner,
|
|
1158
1519
|
lastActivityAt,
|
|
1159
1520
|
firstActivityAt,
|
|
1160
|
-
|
|
1161
|
-
...this.runProjectionOf(sessionId, rollups),
|
|
1521
|
+
...this.runRollupOf(sessionId, rollups),
|
|
1162
1522
|
title: cells.title,
|
|
1163
1523
|
pinned: cells.pinned,
|
|
1164
1524
|
...rowTag,
|
|
1165
1525
|
...("unreadable" in listed ? { unreadable: sessionUnreadableOf(listed.unreadable) } : {}),
|
|
1526
|
+
...this.listedLineageOf(sessionId),
|
|
1166
1527
|
};
|
|
1167
1528
|
}
|
|
1529
|
+
listedLineageOf(sessionId) {
|
|
1530
|
+
const lineage = forkLineageOfRow(sessionId, this.liveCellOf(sessionId)?.forkedFrom, undefined);
|
|
1531
|
+
return lineage.forkedFrom !== undefined ? { forkedFrom: lineage.forkedFrom } : {};
|
|
1532
|
+
}
|
|
1168
1533
|
rowTagKeyOf(sessionId, owner, cells) {
|
|
1169
|
-
const meta = this.
|
|
1534
|
+
const meta = this.liveCellOf(sessionId);
|
|
1170
1535
|
if (meta === undefined)
|
|
1171
1536
|
return {};
|
|
1172
1537
|
return sessionRowTagKey({ sessionId, owner, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent"), cells });
|
|
1173
1538
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
return new Map(rows.map((r) => [r.sessionId, { runCount: r.runCount, lastStatus: r.lastStatus, lastRunId: r.lastRunId, ...(r.lastTaskRunId !== undefined ? { lastTaskRunId: r.lastTaskRunId } : {}) }]));
|
|
1539
|
+
runRollups(q) {
|
|
1540
|
+
return this.runs.sessionRunRollups(q ? { q } : {});
|
|
1177
1541
|
}
|
|
1178
|
-
|
|
1179
|
-
return rollups.get(sessionId) ?? { runCount: 0, lastStatus: "", lastRunId: null };
|
|
1180
|
-
}
|
|
1181
|
-
async previewOf(meta) {
|
|
1182
|
-
try {
|
|
1183
|
-
const session = await this.repo.open({ id: meta.id, createdAt: meta.createdAt });
|
|
1184
|
-
const entries = await session.getEntries();
|
|
1185
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1186
|
-
const text = userText(entries[i]);
|
|
1187
|
-
const preview = text === null ? "" : buildObjectivePreview(text, SESSION_OBJECTIVE_PREVIEW_CHARS);
|
|
1188
|
-
if (preview !== "")
|
|
1189
|
-
return preview;
|
|
1190
|
-
}
|
|
1191
|
-
return null;
|
|
1192
|
-
}
|
|
1193
|
-
catch {
|
|
1194
|
-
return null;
|
|
1195
|
-
}
|
|
1542
|
+
runRollupOf(sessionId, rollups) {
|
|
1543
|
+
return rollups.get(sessionId) ?? { runCount: 0, lastStatus: "", lastRunId: null, objectivePreview: null };
|
|
1196
1544
|
}
|
|
1197
1545
|
async fork(sourceId, owner, opts) {
|
|
1198
1546
|
return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner, opts);
|
|
@@ -1205,6 +1553,9 @@ export class LocalSessionStore {
|
|
|
1205
1553
|
return this.withSessionWriteLock(first, () => this.withSessionWriteLock(second, () => this.forkToHeld(sourceId, owner, target)));
|
|
1206
1554
|
}
|
|
1207
1555
|
async forkToHeld(sourceId, owner, target) {
|
|
1556
|
+
await this.meta.ensureLoaded();
|
|
1557
|
+
if (this.cellStateOf(sourceId).state === "deleting")
|
|
1558
|
+
return null;
|
|
1208
1559
|
const sourceMeta = { id: sourceId, createdAt: "" };
|
|
1209
1560
|
let source;
|
|
1210
1561
|
try {
|
|
@@ -1217,15 +1568,25 @@ export class LocalSessionStore {
|
|
|
1217
1568
|
}
|
|
1218
1569
|
await this.assertArchiveAnonymous(sourceId);
|
|
1219
1570
|
const forkOptions = await settledForkOptions(source);
|
|
1220
|
-
const
|
|
1221
|
-
const
|
|
1571
|
+
const sourceCell = this.liveCellOf(sourceId);
|
|
1572
|
+
const forkOwner = forkOwnerOf(owner, sourceCell?.owner);
|
|
1573
|
+
const sourceToken = sourceCell === undefined ? undefined : incarnationFromCell(sourceId, sourceCell.incarnation, "absent");
|
|
1574
|
+
const lineage = forkRowCells({ ...sourceMeta, ...(sourceToken !== undefined ? { incarnation: sourceToken } : {}) });
|
|
1575
|
+
const existing = this.liveCellOf(target.id);
|
|
1222
1576
|
const targetArchived = await this.existsInRepo(target.id);
|
|
1223
1577
|
const placement = this.placements !== undefined ? target.placement : undefined;
|
|
1224
1578
|
const copy = () => this.repo.fork(sourceMeta, { ...forkOptions, id: target.id, ...(placement !== undefined ? { placement } : {}) });
|
|
1225
1579
|
if (existing === undefined && !targetArchived) {
|
|
1226
1580
|
const created = await this.writeMeta(target.id, (before) => before !== undefined
|
|
1227
1581
|
? undefined
|
|
1228
|
-
: {
|
|
1582
|
+
: {
|
|
1583
|
+
incarnation: mintSessionIncarnation(),
|
|
1584
|
+
...(forkOwner !== undefined ? { owner: forkOwner } : {}),
|
|
1585
|
+
...(target.key !== undefined ? { forkKey: target.key } : {}),
|
|
1586
|
+
createdAt: rowBornNow(),
|
|
1587
|
+
...(lineage.forkedFrom !== undefined ? { forkedFrom: lineage.forkedFrom } : {}),
|
|
1588
|
+
...(lineage.forkedFromIncarnation !== undefined ? { forkedFromIncarnation: lineage.forkedFromIncarnation } : {}),
|
|
1589
|
+
});
|
|
1229
1590
|
if (!created)
|
|
1230
1591
|
throw new SessionError("storage", `LocalSessionStore: fork target ${target.id} gained a session-meta cell under its own write lock — refusing to guess`);
|
|
1231
1592
|
let forked;
|
|
@@ -1241,8 +1602,12 @@ export class LocalSessionStore {
|
|
|
1241
1602
|
return { outcome: "created", id: target.id };
|
|
1242
1603
|
}
|
|
1243
1604
|
const out = classifyForkTarget(target, { owner: existing?.owner, forkKey: existing?.forkKey }, forkOwner);
|
|
1244
|
-
if (out.outcome === "replayed" && !targetArchived)
|
|
1605
|
+
if (out.outcome === "replayed" && isForkCreatedCell(existing) && !targetArchived) {
|
|
1606
|
+
const recorded = existing?.forkedFromIncarnation;
|
|
1607
|
+
if (recorded !== undefined && recorded !== lineage.forkedFromIncarnation)
|
|
1608
|
+
return null;
|
|
1245
1609
|
await copy();
|
|
1610
|
+
}
|
|
1246
1611
|
return out;
|
|
1247
1612
|
}
|
|
1248
1613
|
async admitForkedPlacement(sessionId, forked, placement) {
|
|
@@ -1263,6 +1628,9 @@ export class LocalSessionStore {
|
|
|
1263
1628
|
async exportEntries(sessionId) {
|
|
1264
1629
|
if (!this.repo.exportEntries)
|
|
1265
1630
|
return null;
|
|
1631
|
+
await this.meta.ensureLoaded();
|
|
1632
|
+
if (this.cellStateOf(sessionId).state !== "live" && this.cellStateOf(sessionId).state !== "none")
|
|
1633
|
+
return null;
|
|
1266
1634
|
try {
|
|
1267
1635
|
return await this.repo.exportEntries(sessionId);
|
|
1268
1636
|
}
|
|
@@ -1305,11 +1673,9 @@ export class LocalSessionStore {
|
|
|
1305
1673
|
if (landing === "replace")
|
|
1306
1674
|
await this.assertNotPlacedHeld(sessionId);
|
|
1307
1675
|
validateEntriesForImport(entries);
|
|
1308
|
-
const current = this.
|
|
1309
|
-
if (
|
|
1310
|
-
|
|
1311
|
-
if (await this.forkingOf(sessionId, current))
|
|
1312
|
-
throw new SessionForkInProgressError(sessionId);
|
|
1676
|
+
const current = this.liveCellOf(sessionId);
|
|
1677
|
+
if (landing !== "judged")
|
|
1678
|
+
await this.admitImportHeld(sessionId, current);
|
|
1313
1679
|
if (!(await this.importMayLand(sessionId, current, owner, landing)))
|
|
1314
1680
|
throw importRowRefused(sessionId, landing);
|
|
1315
1681
|
const prior = await this.landImportCell(sessionId, owner);
|
|
@@ -1317,8 +1683,8 @@ export class LocalSessionStore {
|
|
|
1317
1683
|
case "create":
|
|
1318
1684
|
case "judged":
|
|
1319
1685
|
try {
|
|
1320
|
-
const
|
|
1321
|
-
await this.repo.importEntries(sessionId, undefined, entries, { expect: { row:
|
|
1686
|
+
const expectRow = landing === "create" ? null : ((await this.archiveHeadOf(sessionId)) ?? null);
|
|
1687
|
+
await this.repo.importEntries(sessionId, undefined, entries, { expect: { row: expectRow } });
|
|
1322
1688
|
}
|
|
1323
1689
|
catch (err) {
|
|
1324
1690
|
await this.withdrawImportCell(sessionId, prior, err);
|
|
@@ -1348,6 +1714,15 @@ export class LocalSessionStore {
|
|
|
1348
1714
|
throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${placed.kind} placement partition (a delegated agent's transcript) — a session import cannot replace its log`);
|
|
1349
1715
|
}
|
|
1350
1716
|
}
|
|
1717
|
+
async admitImportHeld(sessionId, current) {
|
|
1718
|
+
const verdict = await judgeSessionWrite({ op: "import", rootSessionId: sessionId }, {
|
|
1719
|
+
claim: () => this.runs.getActiveTaskId(sessionId),
|
|
1720
|
+
cell: async () => placementCellOf(current, await this.forkingOf(sessionId, current)),
|
|
1721
|
+
deleting: async () => this.deletingTokenOf(sessionId),
|
|
1722
|
+
});
|
|
1723
|
+
if (verdict !== "proceed")
|
|
1724
|
+
throw verdict.refuse;
|
|
1725
|
+
}
|
|
1351
1726
|
async importMayLand(sessionId, current, owner, landing) {
|
|
1352
1727
|
switch (landing) {
|
|
1353
1728
|
case "create":
|
|
@@ -1369,7 +1744,7 @@ export class LocalSessionStore {
|
|
|
1369
1744
|
const cells = carriedRowCells(sessionId, before === undefined ? undefined : readSessionMetaCell(before));
|
|
1370
1745
|
const base = before === undefined ? { createdAt: rowBornNow() } : cellObjectOf(before);
|
|
1371
1746
|
const { forkKey: _carriedByTheRule, ...kept } = base;
|
|
1372
|
-
return { ...kept, incarnation: cells.incarnation, owner, execution: MIRROR_CELL, ...(cells.forkKey !== null ? { forkKey: cells.forkKey } : {}) };
|
|
1747
|
+
return { ...kept, incarnation: cells.incarnation, owner, execution: MIRROR_CELL, ...(cells.forkKey !== null ? { forkKey: cells.forkKey } : {}), clientWrites: (kept.clientWrites ?? 0) + 1 };
|
|
1373
1748
|
});
|
|
1374
1749
|
return prior;
|
|
1375
1750
|
}
|
|
@@ -1401,11 +1776,9 @@ export class LocalSessionStore {
|
|
|
1401
1776
|
}
|
|
1402
1777
|
async commitStagedImport(sessionId, owner, staged, opts) {
|
|
1403
1778
|
return this.withImportLock(sessionId, owner, async (importer) => {
|
|
1404
|
-
const current = await this.
|
|
1779
|
+
const current = await this.metaHeld(sessionId);
|
|
1405
1780
|
if (!ownerCellIs(opts.expectedOwner, current === undefined ? undefined : (current.owner ?? null)))
|
|
1406
1781
|
throw new StagedCommitOwnerChangedError(sessionId);
|
|
1407
|
-
if (await this.forkingOf(sessionId, current))
|
|
1408
|
-
throw new SessionForkInProgressError(sessionId);
|
|
1409
1782
|
await this.assertNotPlacedHeld(sessionId);
|
|
1410
1783
|
const dstEntries = await this.exportEntries(sessionId);
|
|
1411
1784
|
const rel = classifySyncRelationshipByIds(staged.map((e) => e.id), dstEntries === null ? null : dstEntries.map((e) => e.id));
|
|
@@ -1421,8 +1794,7 @@ export class LocalSessionStore {
|
|
|
1421
1794
|
if (diverged)
|
|
1422
1795
|
throw new SyncConflictError(sessionId, contentForkRelation(diverged, rel.newEntryIds));
|
|
1423
1796
|
}
|
|
1424
|
-
|
|
1425
|
-
throw new SessionWriterPresentError(sessionId, current.execution.lane);
|
|
1797
|
+
await this.admitImportHeld(sessionId, current);
|
|
1426
1798
|
if (rel.relation !== "identical" || !contentEqual) {
|
|
1427
1799
|
await this.importHeld(sessionId, importer, staged, "judged");
|
|
1428
1800
|
}
|
|
@@ -1430,46 +1802,326 @@ export class LocalSessionStore {
|
|
|
1430
1802
|
await this.landImportCell(sessionId, importer);
|
|
1431
1803
|
await this.touch(sessionId);
|
|
1432
1804
|
}
|
|
1433
|
-
const meta = await this.
|
|
1805
|
+
const meta = await this.metaHeld(sessionId);
|
|
1434
1806
|
if (meta === undefined)
|
|
1435
1807
|
throw new SessionError("storage", `LocalSessionStore: staged commit for session ${sessionId} left no session face — nothing was settled`);
|
|
1436
1808
|
const face = sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation);
|
|
1437
1809
|
return { relation: rel.relation, face };
|
|
1438
1810
|
});
|
|
1439
1811
|
}
|
|
1440
|
-
async deleteSession(sessionId, owner) {
|
|
1441
|
-
return this.withSessionWriteLock(sessionId, () => this.deleteSessionHeld(sessionId, owner));
|
|
1812
|
+
async deleteSession(sessionId, owner, token, legs) {
|
|
1813
|
+
return this.withSessionWriteLock(sessionId, (dropped) => this.deleteSessionHeld(sessionId, owner, token, legs, dropped));
|
|
1442
1814
|
}
|
|
1443
|
-
async deleteSessionHeld(sessionId, owner) {
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1815
|
+
async deleteSessionHeld(sessionId, owner, token, legs, dropped) {
|
|
1816
|
+
if (dropped !== undefined)
|
|
1817
|
+
return undefined;
|
|
1818
|
+
await this.meta.ensureLoaded();
|
|
1819
|
+
const cell = this.cellStateOf(sessionId);
|
|
1820
|
+
if (cell.state === "none" || !ownerEq(cell.meta.owner ?? null, owner))
|
|
1821
|
+
return undefined;
|
|
1822
|
+
const verdict = await judgeSessionWrite({ op: "erase", rootSessionId: sessionId, token }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => ({ role: "absent" }), deleting: async () => this.deletingTokenOf(sessionId) });
|
|
1823
|
+
if (verdict !== "proceed")
|
|
1824
|
+
throw verdict.refuse;
|
|
1825
|
+
const occupant = { owner: cell.meta.owner ?? null, incarnation: cell.meta.incarnation };
|
|
1826
|
+
await legs(occupant);
|
|
1827
|
+
await this.dropRowHeld(sessionId);
|
|
1828
|
+
return occupant;
|
|
1829
|
+
}
|
|
1830
|
+
deletingTokenOf(sessionId) {
|
|
1831
|
+
const cell = this.cellStateOf(sessionId);
|
|
1832
|
+
return cell.state === "none" ? null : (cell.meta.deleting ?? null);
|
|
1833
|
+
}
|
|
1834
|
+
sessionDeletion = {
|
|
1835
|
+
fence: (sessionId, owner, opts) => this.withSessionWriteLock(sessionId, (dropped) => this.fenceDeletionHeld(sessionId, owner, opts, dropped)),
|
|
1836
|
+
holds: async (sessionId, token) => {
|
|
1837
|
+
await this.meta.ensureLoaded();
|
|
1838
|
+
return this.deletingTokenOf(sessionId) === token;
|
|
1839
|
+
},
|
|
1840
|
+
pending: async () => {
|
|
1841
|
+
await this.meta.ensureLoaded();
|
|
1842
|
+
return this.deletingIds().map((sessionId) => ({ sessionId, owner: this.deletionOwnerOf(sessionId), token: this.deletingTokenOf(sessionId) }));
|
|
1843
|
+
},
|
|
1844
|
+
};
|
|
1845
|
+
deletionOwnerOf(sessionId) {
|
|
1846
|
+
const cell = this.cellStateOf(sessionId);
|
|
1847
|
+
return cell.state === "none" ? null : (cell.meta.owner ?? null);
|
|
1848
|
+
}
|
|
1849
|
+
async fenceDeletionHeld(sessionId, owner, opts, dropped) {
|
|
1850
|
+
if (dropped !== undefined)
|
|
1851
|
+
return undefined;
|
|
1852
|
+
await this.meta.ensureLoaded();
|
|
1853
|
+
const state = this.cellStateOf(sessionId);
|
|
1854
|
+
let current;
|
|
1855
|
+
if (state.state === "deleting")
|
|
1856
|
+
current = state.meta;
|
|
1857
|
+
else {
|
|
1858
|
+
if (opts.expect !== undefined)
|
|
1859
|
+
return undefined;
|
|
1860
|
+
const seen = await this.observeRow(sessionId);
|
|
1861
|
+
if (seen.row === "absent")
|
|
1862
|
+
return undefined;
|
|
1863
|
+
current = seen.row === "live" ? seen.meta : undefined;
|
|
1864
|
+
}
|
|
1865
|
+
if (!ownerEq(current?.owner ?? null, owner))
|
|
1866
|
+
return undefined;
|
|
1867
|
+
if (state.state === "deleting") {
|
|
1868
|
+
const held = state.meta.deleting;
|
|
1869
|
+
if (opts.expect !== undefined && held !== opts.expect)
|
|
1870
|
+
return undefined;
|
|
1871
|
+
if (opts.holder(held) === "running")
|
|
1872
|
+
return { inProgress: true };
|
|
1873
|
+
}
|
|
1874
|
+
else {
|
|
1875
|
+
const verdict = await judgeSessionWrite({ op: "delete", rootSessionId: sessionId }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => ({ role: "absent" }), deleting: async () => this.deletingTokenOf(sessionId) });
|
|
1876
|
+
if (verdict !== "proceed")
|
|
1877
|
+
throw verdict.refuse;
|
|
1878
|
+
}
|
|
1879
|
+
const token = uuidv7();
|
|
1880
|
+
let incarnation;
|
|
1881
|
+
const committed = await this.meta.commit((draft) => {
|
|
1882
|
+
const before = draft.get(sessionId);
|
|
1883
|
+
const base = before === undefined ? { incarnation: mintSessionIncarnation() } : cellObjectOf(before);
|
|
1884
|
+
incarnation = before === undefined ? undefined : base.incarnation;
|
|
1885
|
+
draft.set(sessionId, { ...base, deleting: token });
|
|
1886
|
+
return true;
|
|
1887
|
+
}, "required");
|
|
1888
|
+
if (!committed)
|
|
1889
|
+
throw new SessionError("storage", `LocalSessionStore: the deletion fence of session ${sessionId} could not be made durable — nothing of the session was removed; retry the delete`);
|
|
1890
|
+
return { token, incarnation };
|
|
1891
|
+
}
|
|
1892
|
+
async bumpClientWritesHeld(sessionId) {
|
|
1893
|
+
await this.meta.ensureLoaded();
|
|
1894
|
+
if (this.cellStateOf(sessionId).state === "deleting")
|
|
1895
|
+
throw new SessionDeletingError(sessionId);
|
|
1896
|
+
if (this.liveCellOf(sessionId) === undefined)
|
|
1897
|
+
return;
|
|
1898
|
+
const committed = await this.meta.commit((draft) => {
|
|
1899
|
+
const before = draft.get(sessionId);
|
|
1900
|
+
if (before === undefined)
|
|
1901
|
+
return false;
|
|
1902
|
+
const cell = readSessionMetaCell(before);
|
|
1903
|
+
if (cell.dropping)
|
|
1904
|
+
throw new Error(`LocalSessionStore: session ${sessionId}'s cell carries the drop mark while a client write reached it — every section finishes a pending drop first; refusing to write`);
|
|
1905
|
+
draft.set(sessionId, { ...cellObjectOf(before), clientWrites: (cell.clientWrites ?? 0) + 1 });
|
|
1906
|
+
return true;
|
|
1907
|
+
}, "required");
|
|
1908
|
+
if (!committed)
|
|
1909
|
+
throw new SessionError("storage", `LocalSessionStore: the client-write count of session ${sessionId} could not be made durable — the write was not made; retry it`);
|
|
1910
|
+
}
|
|
1911
|
+
async withClientWrite(sessionId, write) {
|
|
1912
|
+
return this.withSessionWriteLock(sessionId, async () => {
|
|
1913
|
+
let open = true;
|
|
1914
|
+
try {
|
|
1915
|
+
return await write(async () => {
|
|
1916
|
+
if (!open)
|
|
1917
|
+
throw new Error(`LocalSessionStore.withClientWrite: session ${sessionId}'s client-write count was taken after its lock section ended`);
|
|
1918
|
+
await this.bumpClientWritesHeld(sessionId);
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
finally {
|
|
1922
|
+
open = false;
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
async withdrawBirth(birth, ownTaskId) {
|
|
1927
|
+
const runs = this.runs;
|
|
1928
|
+
return this.withSessionWriteLock(birth.sessionId, async (dropped) => {
|
|
1929
|
+
const sessionId = birth.sessionId;
|
|
1930
|
+
const kept = (word) => ({ withdrawn: false, kept: word });
|
|
1931
|
+
if (dropped !== undefined)
|
|
1932
|
+
return kept("absent");
|
|
1933
|
+
await this.meta.ensureLoaded();
|
|
1934
|
+
if (this.cellStateOf(sessionId).state === "deleting")
|
|
1935
|
+
return kept("claim");
|
|
1936
|
+
const seen = await this.observeRow(sessionId);
|
|
1937
|
+
if (seen.row === "absent")
|
|
1938
|
+
return kept("absent");
|
|
1939
|
+
if (seen.row !== "live")
|
|
1940
|
+
return kept("incarnation");
|
|
1941
|
+
const cell = seen.meta;
|
|
1942
|
+
if (cell.incarnation !== birth.incarnation || cell.owner === undefined || cell.owner !== birth.owner)
|
|
1943
|
+
return kept("incarnation");
|
|
1944
|
+
if (cell.clientWrites === undefined)
|
|
1945
|
+
return kept("upgrade_pending");
|
|
1946
|
+
if (await this.archiveHasContentHeld(sessionId))
|
|
1947
|
+
return kept("content");
|
|
1948
|
+
const verdict = await judgeSessionWrite({ op: "delete", rootSessionId: sessionId }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => placementCellOf(cell, false), deleting: async () => this.deletingTokenOf(sessionId) });
|
|
1949
|
+
if (verdict !== "proceed")
|
|
1950
|
+
return kept("claim");
|
|
1951
|
+
if (cell.clientWrites > 0)
|
|
1952
|
+
return kept("client_writes");
|
|
1953
|
+
if (typeof runs.withdrawRunsOf !== "function") {
|
|
1954
|
+
throw new Error(`LocalSessionStore.withdrawBirth: the run store wired into this session store cannot answer the withdraw's run half (withdrawRunsOf) — cannot tell whether session ${sessionId} has other runs; refusing to guess`);
|
|
1955
|
+
}
|
|
1956
|
+
const runsVerdict = runs.withdrawRunsOf.call(this.runs, sessionId, ownTaskId);
|
|
1957
|
+
if (runsVerdict !== "proceed")
|
|
1958
|
+
return kept(runsVerdict.keep);
|
|
1959
|
+
this.withdrawCheckpointCtx?.(sessionId);
|
|
1960
|
+
await this.dropRowHeld(sessionId);
|
|
1961
|
+
return { withdrawn: true };
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
async archiveHasContentHeld(sessionId) {
|
|
1965
|
+
if (!this.repo.exportEntries)
|
|
1966
|
+
return true;
|
|
1967
|
+
let entries;
|
|
1447
1968
|
try {
|
|
1448
|
-
await this.repo.
|
|
1969
|
+
entries = await this.repo.exportEntries(sessionId);
|
|
1449
1970
|
}
|
|
1450
1971
|
catch (e) {
|
|
1451
|
-
if (
|
|
1452
|
-
|
|
1972
|
+
if (isNotFound(e))
|
|
1973
|
+
return false;
|
|
1974
|
+
throw e;
|
|
1453
1975
|
}
|
|
1454
|
-
|
|
1455
|
-
return true;
|
|
1976
|
+
return entries.some((e) => !entryTypeWithdrawable(e.type));
|
|
1456
1977
|
}
|
|
1457
1978
|
async dropRowHeld(sessionId) {
|
|
1458
|
-
this.
|
|
1979
|
+
await this.meta.ensureLoaded();
|
|
1980
|
+
let marked;
|
|
1981
|
+
const s0 = await this.meta.commit((draft) => {
|
|
1982
|
+
const before = draft.get(sessionId);
|
|
1983
|
+
if (before !== undefined && readSessionMetaCell(before).dropping) {
|
|
1984
|
+
marked = cellObjectOf(before);
|
|
1985
|
+
return false;
|
|
1986
|
+
}
|
|
1987
|
+
marked = { ...(before === undefined ? { incarnation: mintSessionIncarnation() } : cellObjectOf(before)), dropping: true };
|
|
1988
|
+
draft.set(sessionId, marked);
|
|
1989
|
+
return true;
|
|
1990
|
+
}, "required");
|
|
1991
|
+
if (!s0)
|
|
1992
|
+
throw new SessionError("storage", `LocalSessionStore: the drop mark of session ${sessionId} could not be made durable — nothing of the session was removed; retry the delete`);
|
|
1459
1993
|
for (const [map, which] of [[this.titles, "title"], [this.pinned, "pinned"], [this.instructionSources, "instruction-source"], [this.workspaces, "workspace"]]) {
|
|
1460
1994
|
await map.ensureLoaded();
|
|
1461
|
-
if (!(await map.deleteKey(sessionId)))
|
|
1462
|
-
throw new SessionError("storage", `LocalSessionStore: ${which} sidecar write failed while deleting session ${sessionId} —
|
|
1995
|
+
if (!(await map.deleteKey(sessionId, "required")))
|
|
1996
|
+
throw new SessionError("storage", `LocalSessionStore: ${which} sidecar write failed while deleting session ${sessionId} — the session already reads as deleted; retry the delete (or restart) to finish it`);
|
|
1463
1997
|
}
|
|
1464
1998
|
await this.ensureLoaded();
|
|
1465
1999
|
this.lastActivity.delete(sessionId);
|
|
1466
2000
|
this.scheduleFlush();
|
|
1467
|
-
|
|
2001
|
+
try {
|
|
2002
|
+
await this.repo.delete({ id: sessionId, createdAt: "" });
|
|
2003
|
+
}
|
|
2004
|
+
catch (e) {
|
|
2005
|
+
if (!isNotFound(e))
|
|
2006
|
+
throw e;
|
|
2007
|
+
}
|
|
2008
|
+
this.syncArchiveDirectory();
|
|
2009
|
+
if (!(await this.meta.deleteKey(sessionId, "required")))
|
|
2010
|
+
throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed while deleting session ${sessionId} — the session already reads as deleted; retry the delete (or restart) to finish it`);
|
|
2011
|
+
return readSessionMetaCell(marked);
|
|
1468
2012
|
}
|
|
1469
2013
|
syncArchiveDirectory() {
|
|
1470
|
-
if (this.dataRoot === undefined)
|
|
2014
|
+
if (this.dataRoot === undefined || !(this.repo instanceof FileSessionRepo) || IS_WIN32)
|
|
1471
2015
|
return;
|
|
1472
|
-
fsyncPaths(join(this.dataRoot,
|
|
2016
|
+
fsyncPaths(join(this.dataRoot, ARCHIVE_DIR));
|
|
2017
|
+
}
|
|
2018
|
+
async finishPendingDropHeld(sessionId) {
|
|
2019
|
+
await this.meta.ensureLoaded();
|
|
2020
|
+
if (this.cellStateOf(sessionId).state !== "dropping")
|
|
2021
|
+
return undefined;
|
|
2022
|
+
return this.dropRowHeld(sessionId);
|
|
2023
|
+
}
|
|
2024
|
+
async finishPendingDrops() {
|
|
2025
|
+
await this.meta.ensureLoaded();
|
|
2026
|
+
for (const sessionId of this.droppingIds()) {
|
|
2027
|
+
try {
|
|
2028
|
+
await this.withSessionWriteLock(sessionId, async () => undefined);
|
|
2029
|
+
}
|
|
2030
|
+
catch (e) {
|
|
2031
|
+
this.log.warn("session_drop_unfinished", {
|
|
2032
|
+
sessionId,
|
|
2033
|
+
error: e instanceof Error ? e.message : String(e),
|
|
2034
|
+
effect: "the session reads as deleted everywhere; its remaining bytes are removed the next time anything touches this session id, or at the next start",
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
async clearShellSentinelOwnersM48() {
|
|
2040
|
+
await this.meta.ensureLoaded();
|
|
2041
|
+
let cleared = 0;
|
|
2042
|
+
const committed = await this.meta.commit((draft) => {
|
|
2043
|
+
cleared = 0;
|
|
2044
|
+
for (const sessionId of [...draft.keys()]) {
|
|
2045
|
+
const cell = draft.get(sessionId);
|
|
2046
|
+
if (cell === undefined || typeof cell === "string" || cell.owner !== SHELL_SENTINEL_OWNER_M48 || readSessionMetaCell(cell).dropping)
|
|
2047
|
+
continue;
|
|
2048
|
+
draft.set(sessionId, { ...cell, owner: null });
|
|
2049
|
+
cleared += 1;
|
|
2050
|
+
}
|
|
2051
|
+
return cleared > 0;
|
|
2052
|
+
}, "best-effort");
|
|
2053
|
+
if (!committed)
|
|
2054
|
+
throw new SessionError("storage", "LocalSessionStore: the one-time migration M48 could not write the session-meta cells it cleared of the shell sentinel owner durably — nothing was changed");
|
|
2055
|
+
return cleared;
|
|
2056
|
+
}
|
|
2057
|
+
async adoptArchiveTokensM47() {
|
|
2058
|
+
const root = this.dataRoot;
|
|
2059
|
+
if (root === undefined)
|
|
2060
|
+
return { adopted: 0, minted: 0 };
|
|
2061
|
+
const owed = archiveTokenAdoptionPendingPresent(root);
|
|
2062
|
+
const rows = await this.rowHeaders();
|
|
2063
|
+
let adopted = 0;
|
|
2064
|
+
let minted = 0;
|
|
2065
|
+
const committed = await this.meta.commit((draft) => {
|
|
2066
|
+
adopted = 0;
|
|
2067
|
+
minted = 0;
|
|
2068
|
+
for (const row of rows) {
|
|
2069
|
+
if (!("header" in row) || draft.get(row.sessionId) !== undefined)
|
|
2070
|
+
continue;
|
|
2071
|
+
const first = firstCellIncarnationOf(row.sessionId, row.header.incarnation);
|
|
2072
|
+
const facts = archivedRowFactsOf(row.header);
|
|
2073
|
+
draft.set(row.sessionId, { incarnation: first.incarnation, createdAt: facts.createdAt, ...(facts.forkedFrom !== undefined ? { forkedFrom: facts.forkedFrom } : {}) });
|
|
2074
|
+
if (first.adopted)
|
|
2075
|
+
adopted += 1;
|
|
2076
|
+
else
|
|
2077
|
+
minted += 1;
|
|
2078
|
+
}
|
|
2079
|
+
return adopted + minted > 0;
|
|
2080
|
+
}, owed ? "required" : "best-effort");
|
|
2081
|
+
if (!committed)
|
|
2082
|
+
throw new SessionError("storage", "LocalSessionStore: the one-time migration M47 could not write the session-meta cells it adopted into the sidecar durably — the adoption stays owed");
|
|
2083
|
+
if (owed) {
|
|
2084
|
+
await this.assertArchiveTokensAdoptedM47(root);
|
|
2085
|
+
dischargeArchiveTokenAdoptionPending(root);
|
|
2086
|
+
}
|
|
2087
|
+
return { adopted, minted };
|
|
2088
|
+
}
|
|
2089
|
+
async assertArchiveTokensAdoptedM47(root) {
|
|
2090
|
+
let left = 0;
|
|
2091
|
+
for (const sessionId of archiveIdsWithoutCellSync(root))
|
|
2092
|
+
if ((await this.adoptionScopeOf(sessionId)) === "in")
|
|
2093
|
+
left += 1;
|
|
2094
|
+
if (left > 0) {
|
|
2095
|
+
throw new SessionError("storage", `LocalSessionStore: the one-time migration M47 did not adopt ${left} session archive(s) without a session-meta cell whose header it can read (the session listing it read did not list them) — refusing to start. ` +
|
|
2096
|
+
`${ARCHIVE_TOKEN_ADOPTION_PENDING_FILE} stays in the data root; the next start of a release carrying this migration tries again.`);
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
async adoptionScopeOf(sessionId) {
|
|
2100
|
+
let placed;
|
|
2101
|
+
try {
|
|
2102
|
+
placed = await this.recordedPlacementOf(sessionId);
|
|
2103
|
+
}
|
|
2104
|
+
catch (fault) {
|
|
2105
|
+
return this.skippedFromAdoption(sessionId, fault);
|
|
2106
|
+
}
|
|
2107
|
+
if (placed !== undefined)
|
|
2108
|
+
return "out";
|
|
2109
|
+
try {
|
|
2110
|
+
return (await this.archiveHeadOf(sessionId)) === undefined ? "out" : "in";
|
|
2111
|
+
}
|
|
2112
|
+
catch (fault) {
|
|
2113
|
+
if (fault instanceof SessionError && archiveFaultWord(fault.code) === "corrupt_session")
|
|
2114
|
+
return this.skippedFromAdoption(sessionId, fault);
|
|
2115
|
+
return "in";
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
skippedFromAdoption(sessionId, fault) {
|
|
2119
|
+
this.log.warn("archive_token_adoption_skipped", {
|
|
2120
|
+
sessionId,
|
|
2121
|
+
error: fault instanceof Error ? fault.message : String(fault),
|
|
2122
|
+
effect: "this session archive's header cannot be read, so the one-time migration M47 cannot keep its occupant token; if it becomes readable only after the migration's window, its first read mints a new token and memory records kept under the old one stop applying",
|
|
2123
|
+
});
|
|
2124
|
+
return "out";
|
|
1473
2125
|
}
|
|
1474
2126
|
archiveSnapshotDir() {
|
|
1475
2127
|
return this.dataRoot === undefined ? undefined : join(this.dataRoot, LOCAL_ARCHIVE_SNAPSHOT_DIR);
|
|
@@ -1538,10 +2190,10 @@ export class LocalSessionStore {
|
|
|
1538
2190
|
catch (e) {
|
|
1539
2191
|
throw refuse("restoring the archive from the snapshot failed", e);
|
|
1540
2192
|
}
|
|
1541
|
-
await this.fillRowFacts(id, record.rowFacts);
|
|
2193
|
+
await this.fillRowFacts(id, record.rowFacts, record.archiveIncarnation);
|
|
1542
2194
|
}
|
|
1543
2195
|
else if (head.owner === null) {
|
|
1544
|
-
await this.fillRowFacts(id, record.rowFacts);
|
|
2196
|
+
await this.fillRowFacts(id, record.rowFacts, record.archiveIncarnation);
|
|
1545
2197
|
}
|
|
1546
2198
|
else if (head.incarnation !== (record.archiveIncarnation ?? undefined)) {
|
|
1547
2199
|
throw refuse("the session's archive is a different generation from the one the snapshot was taken of");
|
|
@@ -1550,8 +2202,8 @@ export class LocalSessionStore {
|
|
|
1550
2202
|
fsyncPaths(dir);
|
|
1551
2203
|
});
|
|
1552
2204
|
}
|
|
1553
|
-
async fillRowFacts(sessionId, facts) {
|
|
1554
|
-
await this.writeMeta(sessionId, (before) => rowFactsFilled(before, facts));
|
|
2205
|
+
async fillRowFacts(sessionId, facts, archiveToken) {
|
|
2206
|
+
await this.writeMeta(sessionId, (before) => rowFactsFilled(sessionId, before, facts, archiveToken));
|
|
1555
2207
|
}
|
|
1556
2208
|
async migrateLocalArchives() {
|
|
1557
2209
|
const dir = this.archiveSnapshotDir();
|
|
@@ -1576,20 +2228,20 @@ export class LocalSessionStore {
|
|
|
1576
2228
|
const committed = await this.meta.commit((draft) => {
|
|
1577
2229
|
rowFactsBackfilled = 0;
|
|
1578
2230
|
for (const row of listed) {
|
|
1579
|
-
const next = rowFactsFilled(draft.get(row.id), archivedRowFactsOf(row));
|
|
2231
|
+
const next = rowFactsFilled(row.id, draft.get(row.id), archivedRowFactsOf(row), row.incarnation);
|
|
1580
2232
|
if (next === undefined)
|
|
1581
2233
|
continue;
|
|
1582
2234
|
draft.set(row.id, next);
|
|
1583
2235
|
rowFactsBackfilled += 1;
|
|
1584
2236
|
}
|
|
1585
2237
|
return rowFactsBackfilled > 0;
|
|
1586
|
-
});
|
|
2238
|
+
}, "best-effort");
|
|
1587
2239
|
if (!committed)
|
|
1588
2240
|
throw new SessionError("storage", "LocalSessionStore: the one-time migration M33 could not write the row facts it backfilled into the session-meta sidecar — no archive was touched");
|
|
1589
2241
|
this.meta.syncToDisk();
|
|
1590
2242
|
let anonymized = 0;
|
|
1591
2243
|
if (listed.some((row) => (row.owner ?? null) !== null)) {
|
|
1592
|
-
|
|
2244
|
+
ensureDir(dir);
|
|
1593
2245
|
fsyncPaths(dir, dirname(dir));
|
|
1594
2246
|
}
|
|
1595
2247
|
const keptStamped = [];
|
|
@@ -1641,7 +2293,7 @@ export class LocalSessionStore {
|
|
|
1641
2293
|
}
|
|
1642
2294
|
if (!sameLog(validated, row.entries))
|
|
1643
2295
|
return { outcome: "kept", reason: "normalizes" };
|
|
1644
|
-
const cell = this.
|
|
2296
|
+
const cell = this.liveCellOf(sessionId);
|
|
1645
2297
|
if (cell?.createdAt === undefined)
|
|
1646
2298
|
throw new Error(`LocalSessionStore: the one-time migration M33 reached session ${sessionId}'s archive before its row facts were backfilled — refusing to rewrite it`);
|
|
1647
2299
|
const record = {
|