@sema-agent/server 7.147.0 → 7.149.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 +1468 -24
- package/README.md +35 -7
- package/README.zh-CN.md +18 -8
- package/USAGE.md +444 -193
- package/deploy/sema-up/README.md +16 -5
- package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
- package/deploy/sema-up/chart/templates/_helpers.tpl +20 -4
- package/deploy/sema-up/chart/values.yaml +3 -2
- package/deploy/sema-up/cluster-up.sh +74 -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/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/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 +14 -39
- 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 +13 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +21 -0
- 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/approval-card.d.ts +36 -38
- package/dist/approval-gate-currency.js +3 -2
- package/dist/approval-leg-origin.d.ts +33 -0
- package/dist/approval-leg-origin.js +4 -0
- package/dist/approval-reconciler.d.ts +4 -13
- package/dist/approval-reconciler.js +2 -2
- package/dist/approval.d.ts +13 -11
- package/dist/approval.js +3 -3
- package/dist/audit.d.ts +10 -3
- package/dist/audit.js +15 -7
- package/dist/auth-bridge.d.ts +9 -15
- package/dist/auth-bridge.js +4 -11
- package/dist/auth-keys.d.ts +10 -1
- package/dist/auth-keys.js +8 -0
- 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 +5 -2
- 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/deployment-mods.d.ts +1 -1
- package/dist/boot/execution-env.d.ts +17 -7
- package/dist/boot/execution-env.js +47 -42
- package/dist/boot/execution-lane.d.ts +6 -4
- package/dist/boot/execution-lane.js +3 -1
- package/dist/boot/exit-boundary.d.ts +2 -2
- package/dist/boot/exit-boundary.js +6 -5
- package/dist/boot/fatal-exit.d.ts +3 -5
- package/dist/boot/fatal-exit.js +2 -2
- package/dist/boot/leader.d.ts +15 -0
- package/dist/boot/leader.js +17 -15
- package/dist/boot/memory-consolidation.d.ts +37 -29
- package/dist/boot/memory-consolidation.js +11 -5
- package/dist/boot/org-delivery.d.ts +3 -2
- package/dist/boot/org-memory.js +1 -1
- package/dist/boot/parked-revive-gate.js +2 -2
- package/dist/boot/reapers.d.ts +4 -3
- package/dist/boot/reapers.js +19 -16
- package/dist/boot/resolve-spec.d.ts +5 -3
- package/dist/boot/resolve-spec.js +60 -70
- package/dist/boot/retention-lane.d.ts +4 -4
- package/dist/boot/retention-lane.js +2 -2
- package/dist/boot/runner-deps.d.ts +28 -7
- package/dist/boot/runner-deps.js +14 -5
- package/dist/boot/runtime-caps.d.ts +2 -2
- package/dist/boot/runtime-caps.js +16 -2
- package/dist/boot/session-faces.d.ts +16 -7
- package/dist/boot/session-faces.js +114 -94
- package/dist/boot/shutdown.d.ts +5 -4
- package/dist/boot/shutdown.js +6 -4
- package/dist/boot/stage-01-config.d.ts +1 -0
- package/dist/boot/stage-01-config.js +13 -4
- package/dist/boot/stage-02-web-admin-pull.d.ts +2 -0
- package/dist/boot/stage-02-web-admin-pull.js +16 -3
- package/dist/boot/stage-03-store-backend.d.ts +4 -0
- package/dist/boot/stage-03-store-backend.js +4 -3
- package/dist/boot/stage-04-stores.d.ts +3 -0
- package/dist/boot/stage-04-stores.js +2 -2
- package/dist/boot/stage-05-execution-env.d.ts +8 -3
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +12 -13
- package/dist/boot/stage-06-runners.js +23 -15
- package/dist/boot/stage-07-capability-layer.d.ts +13 -9
- package/dist/boot/stage-07-capability-layer.js +5 -3
- package/dist/boot/stage-08-reapers.d.ts +13 -9
- package/dist/boot/stage-08-reapers.js +3 -3
- package/dist/boot/stage-09-leader.d.ts +14 -16
- package/dist/boot/stage-09-leader.js +12 -4
- package/dist/boot/stage-10-http-server.d.ts +14 -16
- package/dist/boot/stage-10-http-server.js +7 -4
- package/dist/boot/store-evidence.d.ts +20 -0
- package/dist/boot/store-evidence.js +13 -0
- package/dist/boot/stores.d.ts +6 -0
- package/dist/boot/stores.js +41 -27
- 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 +55 -35
- 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 +7 -1
- package/dist/capabilities/center-plugins.js +32 -10
- package/dist/capabilities/center-wiring.js +1 -1
- package/dist/capabilities/composite-members.d.ts +2 -2
- package/dist/capabilities/composite-members.js +2 -2
- package/dist/capabilities/repo-tools.d.ts +2 -0
- package/dist/capabilities/repo-tools.js +14 -12
- package/dist/capabilities/scenarios.d.ts +6 -15
- package/dist/capabilities/scenarios.js +11 -16
- package/dist/capabilities/skills.d.ts +1 -1
- package/dist/capabilities/tool-defer.d.ts +1 -1
- package/dist/capability-facts.d.ts +39 -11
- package/dist/capability-facts.js +35 -7
- package/dist/card-settle-stored-scope.d.ts +56 -0
- package/dist/card-settle-stored-scope.js +27 -0
- package/dist/card-void.d.ts +8 -13
- package/dist/card-void.js +2 -7
- package/dist/center-credential-renewer.d.ts +6 -2
- package/dist/center-credential-renewer.js +7 -3
- package/dist/config-catalog.d.ts +28 -1
- package/dist/config-catalog.js +95 -85
- package/dist/config-dir-trust.d.ts +37 -4
- package/dist/config-dir-trust.js +88 -44
- package/dist/config-invariants.d.ts +43 -3
- package/dist/config-invariants.js +69 -5
- package/dist/config-lkg.d.ts +23 -1
- package/dist/config-lkg.js +17 -3
- package/dist/config-provider.d.ts +9 -9
- package/dist/config-provider.js +25 -110
- package/dist/config-pull-failure.d.ts +50 -0
- package/dist/config-pull-failure.js +48 -0
- package/dist/config-types.d.ts +62 -21
- package/dist/config.d.ts +14 -21
- package/dist/config.js +141 -64
- package/dist/context-window-notice.d.ts +31 -0
- package/dist/context-window-notice.js +28 -0
- package/dist/delegated-session-owner.d.ts +1 -1
- package/dist/deleted-session-ids.d.ts +3 -2
- package/dist/deployment-governance.d.ts +3 -3
- package/dist/deployment-shape.d.ts +46 -11
- package/dist/deployment-shape.js +8 -0
- 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.js +2 -1
- package/dist/env-name-allowlist-knobs.d.ts +6 -5
- package/dist/env-name-allowlist-knobs.js +5 -0
- package/dist/env-witness.d.ts +7 -0
- package/dist/env-witness.js +16 -0
- package/dist/execution-lane-caps.d.ts +25 -23
- package/dist/execution-lane-caps.js +18 -26
- package/dist/execution-placement.d.ts +58 -18
- package/dist/execution-placement.js +41 -2
- 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.js +1 -0
- 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 +3 -3
- 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 +23 -3
- package/dist/history-anchor.js +24 -4
- 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 +7 -2
- package/dist/hooks/command-spawn.js +13 -6
- package/dist/hooks/hook-llm.d.ts +5 -1
- package/dist/hooks/hook-llm.js +24 -15
- package/dist/hooks/hook-runner.d.ts +72 -57
- package/dist/hooks/hook-runner.js +271 -193
- 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 +76 -79
- package/dist/http/approval-sweeps.d.ts +3 -3
- package/dist/http/approval-sweeps.js +56 -31
- package/dist/http/caller-trust.d.ts +8 -10
- package/dist/http/caller-trust.js +1 -2
- package/dist/http/claim-heartbeat.d.ts +49 -0
- package/dist/http/claim-heartbeat.js +46 -0
- package/dist/http/claimed-run.d.ts +16 -8
- package/dist/http/claimed-run.js +8 -0
- package/dist/http/dispatch.d.ts +8 -1
- package/dist/http/dispatch.js +78 -30
- package/dist/http/durable-run-leg.d.ts +11 -4
- package/dist/http/durable-run-leg.js +45 -21
- package/dist/http/execution-context.d.ts +122 -47
- package/dist/http/execution-context.js +135 -43
- 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 +4 -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 +1 -1
- package/dist/http/memory-store-refusal-reply.js +3 -0
- package/dist/http/owner-access.d.ts +392 -67
- package/dist/http/owner-access.js +370 -35
- package/dist/http/principal-gate.d.ts +39 -89
- package/dist/http/principal-gate.js +26 -68
- package/dist/http/refusal-body.d.ts +35 -2
- package/dist/http/refusal-body.js +34 -0
- package/dist/http/request-guards.d.ts +9 -13
- 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 +46 -12
- package/dist/http/resume-ctx-read.js +31 -6
- package/dist/http/resume-legs.d.ts +54 -19
- package/dist/http/resume-legs.js +408 -196
- package/dist/http/retired-skill-manifest.d.ts +1 -1
- package/dist/http/route-ctx.d.ts +31 -32
- package/dist/http/routes/a2a-serve.d.ts +43 -5
- package/dist/http/routes/a2a-serve.js +60 -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 -67
- 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 +16 -19
- package/dist/http/routes/config-catalog.js +2 -2
- package/dist/http/routes/devices.d.ts +3 -3
- package/dist/http/routes/devices.js +4 -4
- package/dist/http/routes/diagnostics.d.ts +29 -3
- package/dist/http/routes/diagnostics.js +49 -18
- 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 +2 -0
- 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 +10 -19
- package/dist/http/routes/observability.js +3 -3
- package/dist/http/routes/principal-inbox.d.ts +4 -2
- package/dist/http/routes/principal-inbox.js +5 -4
- package/dist/http/routes/projects.d.ts +1 -1
- package/dist/http/routes/projects.js +3 -2
- package/dist/http/routes/retention-ops.js +6 -9
- package/dist/http/routes/rules.js +20 -5
- 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 +155 -237
- 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 +28 -7
- package/dist/http/routes/session-sync.js +63 -50
- package/dist/http/routes/sessions-list.d.ts +3 -3
- package/dist/http/routes/sessions-list.js +9 -9
- package/dist/http/routes/sessions.js +87 -124
- package/dist/http/routes/shared-memory.js +6 -6
- package/dist/http/routes/side-query.js +2 -2
- package/dist/http/routes/tasks.js +237 -187
- 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 +43 -45
- package/dist/http/server.js +61 -11
- 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 +12 -0
- package/dist/http/stored-body-replay.js +2 -2
- package/dist/http/submit-ctx.d.ts +17 -8
- package/dist/http/submit-ctx.js +8 -5
- 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 +4 -3
- package/dist/identity-source.d.ts +36 -5
- package/dist/identity-source.js +27 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -0
- 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/leg-facts.d.ts +7 -2
- package/dist/leg-facts.js +1 -0
- package/dist/local-store-declaration.d.ts +54 -0
- package/dist/local-store-declaration.js +59 -0
- package/dist/lsp-evict.d.ts +6 -4
- package/dist/lsp-evict.js +2 -3
- package/dist/mcp-assertion-header.d.ts +1 -1
- package/dist/mcp-assertion-header.js +1 -1
- package/dist/memory-operator-faces.d.ts +5 -4
- package/dist/memory-operator-faces.js +1 -1
- package/dist/memory-project-layer.js +1 -1
- package/dist/memory-scope.d.ts +33 -6
- package/dist/memory-scope.js +68 -19
- package/dist/memory-store-refusal.d.ts +5 -2
- package/dist/memory-sync-client.d.ts +3 -2
- package/dist/memory-sync-client.js +8 -5
- package/dist/model-compat.d.ts +17 -8
- package/dist/model-compat.js +6 -2
- package/dist/model-context-window.d.ts +57 -0
- package/dist/model-context-window.js +21 -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 +40 -20
- package/dist/model-entry-refusal.js +57 -30
- 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-reach-scope.d.ts +2 -1
- 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 +23 -14
- package/dist/namespaced-rule-name.js +8 -11
- 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 +72 -40
- package/dist/observability/fail-open.js +74 -40
- package/dist/observability/logger.d.ts +22 -1
- package/dist/observability/logger.js +89 -12
- package/dist/observability/metrics.js +4 -5
- 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 +6 -2
- package/dist/observability/secret-env-scrub.d.ts +1 -1
- package/dist/observability/secret-env-scrub.js +1 -1
- 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-completion-inbox.d.ts +3 -3
- package/dist/orchestration/workflow-completion-inbox.js +3 -2
- package/dist/org-delivery-gate.d.ts +36 -10
- package/dist/org-delivery-gate.js +12 -8
- 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 +532 -0
- package/dist/owner-faces.js +126 -0
- package/dist/park-row-commit.d.ts +67 -0
- package/dist/park-row-commit.js +76 -0
- package/dist/parked-decide.d.ts +76 -12
- package/dist/parked-decide.js +152 -11
- package/dist/parked-revive-surface.d.ts +14 -4
- package/dist/parked-revive-surface.js +5 -4
- package/dist/peer-directory.d.ts +7 -9
- 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 +3 -25
- package/dist/plugins/adoption-log-sql.js +23 -21
- 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 +18 -7
- package/dist/plugins/approval-ask-store-file.js +37 -14
- package/dist/plugins/approval-ask-store-memory.d.ts +12 -4
- package/dist/plugins/approval-ask-store-memory.js +24 -10
- package/dist/plugins/approval-ask-store-sql.d.ts +40 -21
- package/dist/plugins/approval-ask-store-sql.js +44 -15
- package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
- package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
- package/dist/plugins/approval-exemption-skill-m46.d.ts +14 -0
- package/dist/plugins/approval-exemption-skill-m46.js +21 -0
- package/dist/plugins/approval-exemption-store.d.ts +32 -2
- package/dist/plugins/approval-exemption-store.js +42 -19
- 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 +2 -0
- package/dist/plugins/blob-backend.js +9 -6
- package/dist/plugins/caching-session-store.d.ts +13 -1
- package/dist/plugins/caching-session-store.js +43 -4
- package/dist/plugins/checkpoint-index-columns.d.ts +32 -5
- package/dist/plugins/checkpoint-index-columns.js +36 -1
- package/dist/plugins/checkpoint-store-face.d.ts +83 -10
- package/dist/plugins/checkpoint-store-face.js +18 -1
- package/dist/plugins/device-store-sql.js +1 -0
- 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 +36 -18
- package/dist/plugins/ensure-schema-lock-policy.js +76 -9
- package/dist/plugins/file-run-store.d.ts +85 -32
- package/dist/plugins/file-run-store.js +177 -94
- 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 +2 -2
- 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 +62 -23
- package/dist/plugins/local-checkpoint-store.js +197 -77
- package/dist/plugins/local-data-dir-modes-m39.js +2 -1
- package/dist/plugins/local-data-root-admission.d.ts +52 -0
- package/dist/plugins/local-data-root-admission.js +100 -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 +29 -6
- package/dist/plugins/local-engine-high-water.js +27 -19
- package/dist/plugins/local-session-store.d.ts +458 -140
- package/dist/plugins/local-session-store.js +906 -292
- 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 +18 -1
- package/dist/plugins/local-task-attachment-store.js +22 -1
- package/dist/plugins/memory-embedder.js +9 -7
- package/dist/plugins/memory-engine-pg.d.ts +3 -2
- package/dist/plugins/memory-engine-pg.js +14 -5
- package/dist/plugins/memory-engine-sql-apply.d.ts +8 -4
- package/dist/plugins/memory-engine-sql-apply.js +6 -8
- package/dist/plugins/memory-engine-tidb.d.ts +4 -2
- package/dist/plugins/memory-engine-tidb.js +11 -2
- 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 +53 -36
- package/dist/plugins/memory-run-store.js +116 -88
- package/dist/plugins/one-time-migration-marker.d.ts +26 -0
- package/dist/plugins/one-time-migration-marker.js +13 -0
- package/dist/plugins/one-time-migrations.d.ts +214 -47
- package/dist/plugins/one-time-migrations.js +481 -135
- package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
- package/dist/plugins/org-delivery-quota-store.js +31 -15
- package/dist/plugins/permission-rule-store-file.d.ts +3 -3
- package/dist/plugins/permission-rule-store-sql.d.ts +5 -64
- package/dist/plugins/permission-rule-store-sql.js +11 -95
- package/dist/plugins/pg-pool.d.ts +2 -32
- package/dist/plugins/pg-pool.js +24 -17
- package/dist/plugins/remote-env-device.d.ts +3 -2
- package/dist/plugins/remote-env-e2b.d.ts +8 -32
- package/dist/plugins/remote-env-e2b.js +44 -32
- package/dist/plugins/remote-env-host.d.ts +51 -17
- package/dist/plugins/remote-env-host.js +111 -42
- package/dist/plugins/remote-env-k8s.d.ts +40 -45
- package/dist/plugins/remote-env-k8s.js +111 -60
- package/dist/plugins/remote-env-local-docker.d.ts +152 -15
- package/dist/plugins/remote-env-local-docker.js +352 -72
- package/dist/plugins/remote-scratchpad.js +4 -0
- package/dist/plugins/retention-deployment.d.ts +49 -17
- package/dist/plugins/retention-deployment.js +39 -8
- 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 +82 -75
- package/dist/plugins/run-store-sql.js +164 -125
- 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 +56 -0
- package/dist/plugins/scenario-prefix-switch-m42.js +56 -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 +131 -14
- package/dist/plugins/schema-consistency.js +388 -18
- package/dist/plugins/schema-drift-sentence.d.ts +10 -6
- package/dist/plugins/schema-drift-sentence.js +15 -7
- package/dist/plugins/schema-ensure-legs.d.ts +9 -0
- package/dist/plugins/schema-ensure-legs.js +1 -1
- 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 +27 -3
- package/dist/plugins/session-ext-table.js +19 -6
- package/dist/plugins/session-fork.d.ts +15 -1
- package/dist/plugins/session-fork.js +3 -0
- package/dist/plugins/session-ownership.d.ts +1 -1
- package/dist/plugins/session-placement.d.ts +27 -20
- package/dist/plugins/session-placement.js +15 -12
- package/dist/plugins/session-row-sql.d.ts +12 -0
- package/dist/plugins/session-row-sql.js +3 -3
- 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 +38 -12
- package/dist/plugins/session-store-sql.js +130 -30
- package/dist/plugins/session-withdraw-sql.d.ts +36 -0
- package/dist/plugins/session-withdraw-sql.js +42 -0
- package/dist/plugins/shared-memory-store-sql.d.ts +0 -69
- package/dist/plugins/shared-memory-store-sql.js +2 -1
- package/dist/plugins/shell-sentinel-owner-m48.d.ts +42 -0
- package/dist/plugins/shell-sentinel-owner-m48.js +9 -0
- package/dist/plugins/snapshot-bucket-presence.d.ts +2 -1
- package/dist/plugins/sql-driver.d.ts +1 -1
- package/dist/plugins/sql-driver.js +2 -1
- package/dist/plugins/sql-errors.d.ts +32 -5
- package/dist/plugins/sql-errors.js +55 -7
- package/dist/plugins/sql-json-column.d.ts +8 -7
- package/dist/plugins/sql-json-column.js +10 -6
- package/dist/plugins/stop-then-start-sentence.d.ts +16 -0
- package/dist/plugins/stop-then-start-sentence.js +7 -0
- package/dist/plugins/store-backend.d.ts +99 -16
- package/dist/plugins/store-backend.js +230 -65
- package/dist/plugins/store-contracts.d.ts +123 -33
- package/dist/plugins/store-contracts.js +38 -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 +15 -3
- package/dist/plugins/task-attachment-store.js +22 -9
- package/dist/plugins/tidb-pool.d.ts +11 -6
- package/dist/plugins/tidb-pool.js +39 -18
- 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/plugins/workspace-custody.d.ts +44 -0
- package/dist/plugins/workspace-custody.js +47 -0
- 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/project-identity.d.ts +8 -7
- package/dist/project-identity.js +40 -17
- package/dist/project-layer-key.d.ts +13 -2
- package/dist/project-layer-key.js +9 -1
- package/dist/question.d.ts +13 -23
- package/dist/question.js +21 -39
- package/dist/read-deny-floor-notice.d.ts +36 -0
- package/dist/read-deny-floor-notice.js +40 -0
- package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
- package/dist/request-declaration-disposition.js +39 -0
- 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/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 +5 -3
- package/dist/rules-consent.js +1 -0
- package/dist/run-acceptance.d.ts +3 -1
- package/dist/run-acceptance.js +3 -0
- package/dist/run-cancel-card.d.ts +28 -0
- package/dist/run-cancel-card.js +19 -0
- package/dist/run-cancel-context.d.ts +10 -0
- package/dist/run-cancel-context.js +1 -0
- package/dist/run-local.d.ts +14 -6
- package/dist/run-local.js +117 -90
- package/dist/run-stale-default.d.ts +7 -0
- package/dist/run-stale-default.js +2 -0
- package/dist/runs.d.ts +56 -21
- package/dist/runs.js +73 -43
- package/dist/runtime-caps-resolver.d.ts +21 -6
- package/dist/runtime-caps-resolver.js +17 -4
- package/dist/runtime-governance.d.ts +2 -2
- package/dist/runtime-governance.js +5 -3
- package/dist/sealed-key-layout.d.ts +12 -5
- package/dist/sealed-key-layout.js +5 -0
- package/dist/sealed-key.d.ts +12 -2
- package/dist/sealed-key.js +115 -27
- package/dist/security.d.ts +128 -158
- package/dist/security.js +163 -59
- package/dist/sema-config-dir.d.ts +20 -0
- package/dist/sema-config-dir.js +25 -0
- package/dist/server-secret-env.d.ts +55 -25
- package/dist/server-secret-env.js +75 -22
- package/dist/session-agent.d.ts +3 -2
- package/dist/session-children-retire.d.ts +13 -8
- package/dist/session-children-retire.js +9 -7
- 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 +5 -5
- package/dist/session-error-wire.js +1 -1
- package/dist/session-incarnation.d.ts +52 -5
- 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 +0 -71
- package/dist/session-resident-background.js +4 -3
- package/dist/session-row.d.ts +2 -1
- package/dist/session-rules-seat.d.ts +12 -0
- package/dist/session-rules-seat.js +19 -0
- package/dist/session-titler.d.ts +7 -15
- package/dist/session-titler.js +1 -1
- package/dist/session-workspace.d.ts +3 -3
- package/dist/spec-fields.d.ts +57 -3
- package/dist/spec-fields.js +72 -51
- package/dist/store-admission.d.ts +33 -0
- package/dist/store-admission.js +49 -0
- package/dist/store-posture.d.ts +141 -20
- package/dist/store-posture.js +205 -3
- package/dist/stored-request-hooks-m20.js +2 -2
- 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/checkpoint-store-sql.d.ts +34 -29
- package/dist/stores-sql/checkpoint-store-sql.js +78 -49
- package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
- package/dist/stores-sql/file-history-store-sql.js +3 -5
- package/dist/stores-sql/mailbox-store-sql.d.ts +7 -4
- package/dist/stores-sql/mailbox-store-sql.js +31 -32
- package/dist/stores-sql/pg-session-storage.d.ts +7 -5
- package/dist/stores-sql/pg-session-storage.js +54 -46
- package/dist/stores-sql/retention-store-sql.d.ts +87 -19
- package/dist/stores-sql/retention-store-sql.js +92 -27
- package/dist/stores-sql/session-policy-store-sql.d.ts +32 -10
- package/dist/stores-sql/session-policy-store-sql.js +44 -36
- package/dist/stores-sql/session-write-hooks.d.ts +12 -6
- package/dist/stores-sql/tidb-session-storage.d.ts +4 -4
- package/dist/stores-sql/tidb-session-storage.js +13 -8
- package/dist/stores-sql/tidb-session-store.d.ts +36 -17
- package/dist/stores-sql/tidb-session-store.js +69 -39
- package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
- package/dist/stores-sql/tool-result-store-sql.js +4 -6
- package/dist/stores-sql/windowed-session-storage.d.ts +5 -0
- package/dist/stores-sql/windowed-session-storage.js +9 -0
- package/dist/submission-exit.d.ts +50 -0
- package/dist/submission-exit.js +108 -0
- package/dist/task-a2a.d.ts +3 -8
- package/dist/task-a2a.js +28 -30
- package/dist/task-cwd.d.ts +43 -7
- package/dist/task-cwd.js +11 -0
- package/dist/task-mcp.d.ts +3 -3
- package/dist/task-mcp.js +4 -4
- package/dist/task-settings.d.ts +14 -10
- package/dist/task-settings.js +19 -7
- package/dist/terminal.d.ts +27 -43
- package/dist/terminal.js +49 -14
- package/dist/tool-approval.d.ts +173 -163
- package/dist/tool-approval.js +247 -232
- package/dist/trace/core-keyset-guard.d.ts +13 -6
- 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 +24 -21
- package/dist/trace/engine-notice-wire.js +7 -3
- package/dist/trace/exit-seat.d.ts +30 -4
- package/dist/trace/exit-seat.js +40 -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 +4 -31
- 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 +59 -44
- package/dist/trace/project.js +60 -26
- package/dist/trace/redact.d.ts +135 -27
- package/dist/trace/redact.js +884 -260
- package/dist/trace/self-minted-wire.d.ts +567 -20
- package/dist/trace/self-minted-wire.js +205 -24
- package/dist/trace/wire-projection-faces.d.ts +5 -6
- package/dist/trace/wire-projection-faces.js +2 -2
- 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 -23
- 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/center-request.d.ts +2 -2
- package/dist/web-admin-client/center-request.js +15 -15
- package/dist/web-admin-client/effective-keys.d.ts +1 -1
- package/dist/web-admin-client/effective-keys.js +1 -1
- 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/http-client.d.ts +1 -1
- package/dist/web-admin-client/http-client.js +23 -4
- 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 +8 -4
- package/dist/web-admin-client/skills-mcp.d.ts +2 -2
- package/dist/web-admin-client/types.d.ts +1 -2
- package/dist/workspace-space.d.ts +3 -2
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +4 -4
- package/docs/error-text-lexicon.json +317 -0
- package/package.json +8 -3
- package/scripts/drain-check-local.mjs +118 -147
- package/scripts/drain-check.mysql.sql +22 -2
- package/scripts/drain-check.pg.sql +22 -2
- package/scripts/embed-minimal.mjs +183 -0
- package/wire-changes/server@7.148.0.json +1517 -0
- package/wire-changes/server@7.149.0.json +372 -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
- package/dist/plain-string-record.js +0 -70
|
@@ -1,32 +1,37 @@
|
|
|
1
|
+
import { SCENARIO_SUBJECT_PREFIX } from "../owner-brands.js";
|
|
1
2
|
import { lstat, readFile } from "node:fs/promises";
|
|
2
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";
|
|
6
|
-
import {
|
|
7
|
+
import { IS_WIN32 } from "./host-platform.js";
|
|
8
|
+
import { atomicWriteFileAsync, atomicWriteFileSync, proveDurableSync } from "./owner-only-file-mode.js";
|
|
7
9
|
import { assertFirstOwnerWritable, forkOwnerOf, importerOwnerOf, placedSessionOwnerOf } from "./session-ownership.js";
|
|
8
10
|
import { SessionOwnerUnreadableError, StampedSessionArchiveError, sessionUnreadableOf } from "../session-error-wire.js";
|
|
9
|
-
import {
|
|
11
|
+
import { isShellSentinelOwnerM48 } from "./shell-sentinel-owner-m48.js";
|
|
12
|
+
import { FileSessionRepo, SessionError, StoredSession, admitSessionPlacement, atomicWriteFile, ensureDir, forkRowCells, sessionLogMarkOf, settledForkOptions, uuidv7, validateEntriesForImport } from "@sema-agent/core";
|
|
10
13
|
import { placementCapabilityOf } from "./session-placement.js";
|
|
11
14
|
import { z } from "zod";
|
|
12
15
|
import { contentForkRelation, fastForwardSharedContentDiverged, identicalIdsAlsoIdenticalContent } from "../session-sync-content.js";
|
|
13
16
|
import { SCHEMA_MISMATCH_ERROR_CODE, StoreBootRefusalError } from "./store-contracts.js";
|
|
17
|
+
import { entryTypeWithdrawable } from "./session-birth.js";
|
|
14
18
|
import { judgeSessionRowWrite, sessionRowTagKey, settleSessionRowWrite, } from "../session-row.js";
|
|
15
19
|
import { quarantineUnreadableSidecar } from "./local-sidecar-quarantine.js";
|
|
16
20
|
import { createZodColumnReader, decodeJsonColumn, isJsonColumnBodyProblem, isJsonObject, readJsonObjectColumn, readStringRecordColumn, SqlJsonColumnError } from "./sql-json-column.js";
|
|
17
21
|
import { RENAMED_EXECUTION_LANE_M40, RETIRED_EXECUTION_LANE_WORD_M40 } from "./execution-lane-rename-m40.js";
|
|
18
|
-
import { ExecutionLaneRenameWriteRefusedError,
|
|
22
|
+
import { ExecutionLaneRenameWriteRefusedError, migrationCarried } from "./one-time-migrations.js";
|
|
23
|
+
import { compareReleaseTriples, releaseText, releaseTriple } from "./release-version.js";
|
|
24
|
+
import { PRE_CUTOFF_SKILL_EXEMPTION_CONSEQUENCE, SKILL_EXEMPTION_TOOL_NAME } from "./approval-exemption-skill-cutoff.js";
|
|
19
25
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
20
|
-
import { createLogger } from "../observability/logger.js";
|
|
26
|
+
import { createLogger, writeLogText } from "../observability/logger.js";
|
|
21
27
|
import { UNMANAGED_RETENTION } from "./retention-declaration.js";
|
|
22
|
-
import { UnreadableLeaf, carriedRowCells, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
|
|
28
|
+
import { UnreadableLeaf, carriedRowCells, forkLineageOfRow, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
|
|
23
29
|
import { isExecutionLane } from "../execution-lane-caps.js";
|
|
24
|
-
import { SessionForkInProgressError, WRITABLE, assertPlaceableLane,
|
|
30
|
+
import { SessionForkInProgressError, WRITABLE, assertPlaceableLane, judgeSessionWrite, settledMirrorOutcome, settledPlaceOutcome, SessionDeletingError, } from "../execution-placement.js";
|
|
25
31
|
import { classifyForkTarget, forkByFreshId } from "./session-fork.js";
|
|
26
32
|
import { decodeInstructionSource, encodeInstructionSource, sameInstructionSourceBaseline } from "../instruction-source-notice.js";
|
|
27
33
|
import { decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
|
|
28
34
|
import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
|
|
29
|
-
import { buildObjectivePreview, isAttachmentFrame } from "../user-authored-text.js";
|
|
30
35
|
import { classifySyncRelationshipByIds, ownerCellIs, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, } from "../session-sync-kernel.js";
|
|
31
36
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
32
37
|
const isNotFound = (e) => e instanceof SessionError && e.code === "not_found";
|
|
@@ -50,46 +55,38 @@ function archiveFaultWord(code) {
|
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
|
-
function
|
|
54
|
-
const
|
|
58
|
+
function unusableArchiveOfFault(fault) {
|
|
59
|
+
const engine = fault instanceof SessionOwnerUnreadableError ? "corrupt_session" : fault instanceof SessionError ? archiveFaultWord(fault.code) : "storage";
|
|
60
|
+
const word = engine === "absent" ? "storage" : engine;
|
|
61
|
+
const code = fault instanceof SessionError || fault instanceof SessionOwnerUnreadableError ? undefined : fault?.code;
|
|
55
62
|
return { state: "unusable", word, fault, ...(word === "storage" && typeof code === "string" ? { errno: code } : {}) };
|
|
56
63
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
if (probe.word === "storage" || !(fault instanceof SessionError))
|
|
60
|
-
return probe;
|
|
61
|
-
return { ...probe, fault: new SessionOwnerUnreadableError(fault) };
|
|
64
|
+
function ownerUnreadableFault(fault) {
|
|
65
|
+
return fault instanceof SessionError ? new SessionOwnerUnreadableError(fault) : fault instanceof Error ? fault : new Error(String(fault));
|
|
62
66
|
}
|
|
67
|
+
const ABSENT_ROW = { row: "absent" };
|
|
68
|
+
const NO_CELL = { state: "none" };
|
|
63
69
|
function importRowRefused(sessionId, landing) {
|
|
64
70
|
return new SessionError("conflict", landing === "create"
|
|
65
71
|
? `session ${sessionId} already exists — importEntries only creates a session (replaceEntries rewrites one); nothing was written`
|
|
66
72
|
: `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`);
|
|
67
73
|
}
|
|
68
|
-
const SESSION_OBJECTIVE_PREVIEW_CHARS = 200;
|
|
69
|
-
function userText(entry) {
|
|
70
|
-
if (entry.type !== "message")
|
|
71
|
-
return null;
|
|
72
|
-
const msg = entry.message;
|
|
73
|
-
if (msg.role !== "user" || isAttachmentFrame(msg))
|
|
74
|
-
return null;
|
|
75
|
-
const c = msg.content;
|
|
76
|
-
if (typeof c === "string")
|
|
77
|
-
return c;
|
|
78
|
-
if (Array.isArray(c)) {
|
|
79
|
-
return c
|
|
80
|
-
.filter((p) => !!p && typeof p === "object" && p.type === "text")
|
|
81
|
-
.map((p) => p.text)
|
|
82
|
-
.join(" ");
|
|
83
|
-
}
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
74
|
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)" };
|
|
87
75
|
const readPinnedSidecar = createZodColumnReader(z.record(z.string(), z.literal(true)));
|
|
88
76
|
const MIRROR_CELL = { role: "mirror" };
|
|
89
77
|
function readSessionMetaCell(cell) {
|
|
90
78
|
return typeof cell === "string"
|
|
91
|
-
? { incarnation: cell, owner: undefined, execution: undefined, forkKey: undefined, createdAt: undefined, forkedFrom: undefined }
|
|
92
|
-
: { incarnation: cell.incarnation, owner: cell.owner, execution: cell.execution, forkKey: cell.forkKey, createdAt: cell.createdAt, forkedFrom: cell.forkedFrom };
|
|
79
|
+
? { incarnation: cell, owner: undefined, execution: undefined, forkKey: undefined, createdAt: undefined, forkedFrom: undefined, forkedFromIncarnation: undefined, dropping: false, deleting: undefined, clientWrites: undefined }
|
|
80
|
+
: { 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 };
|
|
81
|
+
}
|
|
82
|
+
function shellSentinelCellIdsM48(cells) {
|
|
83
|
+
const ids = [];
|
|
84
|
+
for (const [sessionId, cell] of cells) {
|
|
85
|
+
if (typeof cell === "string" || !isShellSentinelOwnerM48(cell.owner) || readSessionMetaCell(cell).dropping)
|
|
86
|
+
continue;
|
|
87
|
+
ids.push(sessionId);
|
|
88
|
+
}
|
|
89
|
+
return ids;
|
|
93
90
|
}
|
|
94
91
|
const ownerRecorded = (cell) => cell !== undefined && readSessionMetaCell(cell).owner !== undefined;
|
|
95
92
|
const cellObjectOf = (cell) => (typeof cell === "string" ? { incarnation: cell } : cell);
|
|
@@ -105,9 +102,9 @@ function archivedRowFactsOf(head) {
|
|
|
105
102
|
...(typeof forkedFrom === "string" && forkedFrom !== "" ? { forkedFrom } : {}),
|
|
106
103
|
};
|
|
107
104
|
}
|
|
108
|
-
function rowFactsFilled(before, facts) {
|
|
105
|
+
function rowFactsFilled(sessionId, before, facts, archiveToken) {
|
|
109
106
|
if (before === undefined)
|
|
110
|
-
return { incarnation:
|
|
107
|
+
return { incarnation: firstCellIncarnationOf(sessionId, archiveToken).incarnation, createdAt: facts.createdAt, ...(facts.forkedFrom !== undefined ? { forkedFrom: facts.forkedFrom } : {}) };
|
|
111
108
|
const held = cellObjectOf(before);
|
|
112
109
|
const add = {
|
|
113
110
|
...(held.createdAt === undefined ? { createdAt: facts.createdAt } : {}),
|
|
@@ -124,7 +121,7 @@ function rowCreatedAtOf(sessionId, meta) {
|
|
|
124
121
|
}
|
|
125
122
|
function rowMetadataOf(sessionId, meta) {
|
|
126
123
|
const face = sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation);
|
|
127
|
-
return { id: sessionId, createdAt: rowCreatedAtOf(sessionId, meta), owner: face.owner, incarnation: face.incarnation, ...(meta.forkedFrom
|
|
124
|
+
return { id: sessionId, createdAt: rowCreatedAtOf(sessionId, meta), owner: face.owner, incarnation: face.incarnation, ...forkLineageOfRow(sessionId, meta.forkedFrom, meta.forkedFromIncarnation) };
|
|
128
125
|
}
|
|
129
126
|
const isForkCreatedCell = (meta) => meta !== undefined && meta.forkKey !== undefined && meta.execution === undefined;
|
|
130
127
|
function placementCellOf(meta, forking) {
|
|
@@ -133,6 +130,27 @@ function placementCellOf(meta, forking) {
|
|
|
133
130
|
function readingOfCell(sessionId, meta, forking) {
|
|
134
131
|
return { occupant: sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation), cell: placementCellOf(meta, forking) };
|
|
135
132
|
}
|
|
133
|
+
function legacyNeedsMint(seen) {
|
|
134
|
+
return seen.row === "legacy" && seen.placed === undefined && seen.unreadable === undefined;
|
|
135
|
+
}
|
|
136
|
+
function executionReadingOf(sessionId, seen) {
|
|
137
|
+
switch (seen.row) {
|
|
138
|
+
case "absent":
|
|
139
|
+
return undefined;
|
|
140
|
+
case "live":
|
|
141
|
+
return seen.placed !== undefined ? undefined : readingOfCell(sessionId, seen.meta, seen.forking);
|
|
142
|
+
case "legacy":
|
|
143
|
+
if (seen.placed !== undefined)
|
|
144
|
+
return undefined;
|
|
145
|
+
if (seen.unreadable !== undefined)
|
|
146
|
+
throw ownerUnreadableFault(seen.unreadable);
|
|
147
|
+
throw new Error(`LocalSessionStore: session ${sessionId} is a legacy row — its cell is minted under its write lock before an execution reading is given`);
|
|
148
|
+
default: {
|
|
149
|
+
const unknownRow = seen;
|
|
150
|
+
throw new Error(`LocalSessionStore: no row observation ${JSON.stringify(unknownRow)}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
136
154
|
const SESSION_META_SIDECAR_FILE = "session-incarnations.json";
|
|
137
155
|
const LOCAL_EXECUTION_CELL = z.discriminatedUnion("role", [
|
|
138
156
|
z.object({ role: z.literal("writer"), lane: z.custom((v) => typeof v === "string" && isExecutionLane(v), { message: "not an execution lane" }) }).strict(),
|
|
@@ -146,27 +164,81 @@ export const SESSION_META_CELL_OBJECT = z
|
|
|
146
164
|
forkKey: z.string().optional(),
|
|
147
165
|
createdAt: z.string().optional(),
|
|
148
166
|
forkedFrom: z.string().optional(),
|
|
167
|
+
forkedFromIncarnation: z.string().optional(),
|
|
168
|
+
dropping: z.literal(true).optional(),
|
|
169
|
+
deleting: z.string().min(1).optional(),
|
|
170
|
+
clientWrites: z.number().int().nonnegative().optional(),
|
|
149
171
|
})
|
|
150
172
|
.strict();
|
|
151
173
|
const SESSION_META_SIDECAR = z.record(z.string(), z.union([z.string(), SESSION_META_CELL_OBJECT]));
|
|
152
174
|
const readSessionMetaSidecar = createZodColumnReader(SESSION_META_SIDECAR);
|
|
153
|
-
function
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
function renameRetiredExecutionLaneWordsInCellsM40(entries) {
|
|
176
|
+
if (!isJsonObject(entries))
|
|
177
|
+
return 0;
|
|
178
|
+
let renamed = 0;
|
|
179
|
+
for (const cell of Object.values(entries)) {
|
|
180
|
+
if (!isJsonObject(cell))
|
|
181
|
+
continue;
|
|
182
|
+
const execution = cell["execution"];
|
|
183
|
+
if (!isJsonObject(execution) || execution["role"] !== "writer" || execution["lane"] !== RETIRED_EXECUTION_LANE_WORD_M40)
|
|
184
|
+
continue;
|
|
185
|
+
execution["lane"] = RENAMED_EXECUTION_LANE_M40;
|
|
186
|
+
renamed += 1;
|
|
187
|
+
}
|
|
188
|
+
return renamed;
|
|
158
189
|
}
|
|
190
|
+
const STRICT_SIDECARS = [{ file: SESSION_META_SIDECAR_FILE, which: "session-meta" }];
|
|
191
|
+
const readSessionMetaSidecarInWindow = (body) => {
|
|
192
|
+
renameRetiredExecutionLaneWordsInCellsM40(body);
|
|
193
|
+
return readSessionMetaSidecar(body);
|
|
194
|
+
};
|
|
195
|
+
const STRICT_SIDECAR_FILES = new Set(STRICT_SIDECARS.map((s) => s.file));
|
|
159
196
|
const SIDECAR_MAP_STATE_BY_PATH = new Map();
|
|
160
197
|
const SESSION_WRITE_LOCKS_BY_ROOT = new Map();
|
|
161
198
|
const quotedFieldNames = (names) => `${names.slice(0, 8).map((name) => JSON.stringify(name.slice(0, 64))).join(", ")}${names.length > 8 ? `, … (${names.length} in all)` : ""}`;
|
|
199
|
+
function releaseAtOrAbove(a, b) {
|
|
200
|
+
const ta = releaseTriple(a);
|
|
201
|
+
const tb = releaseTriple(b);
|
|
202
|
+
return ta !== undefined && tb !== undefined && compareReleaseTriples(ta, tb) >= 0;
|
|
203
|
+
}
|
|
162
204
|
function describeDataRootFinding(f) {
|
|
163
205
|
if (f.problem === "newer-engine") {
|
|
164
206
|
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}).`;
|
|
165
207
|
}
|
|
208
|
+
if (f.problem === "skipped-required-migration")
|
|
209
|
+
return describeSkippedMigration(f);
|
|
166
210
|
return f.unknownFields.length > 0
|
|
167
211
|
? ` ${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).`
|
|
168
212
|
: ` ${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.`;
|
|
169
213
|
}
|
|
214
|
+
function describeSkippedMigration(f) {
|
|
215
|
+
const m = f.skippedMigration;
|
|
216
|
+
switch (m.residue) {
|
|
217
|
+
case "archives-without-cell":
|
|
218
|
+
return describeArchivesWithoutCell(f, m);
|
|
219
|
+
case "pre-cutoff-skill-exemptions": {
|
|
220
|
+
const atLeast = m.highWater !== null && releaseAtOrAbove(m.highWater, m.requiredSince) ? m.highWater : m.requiredSince;
|
|
221
|
+
const cutoff = m.cutoff === null ? "no cutoff is recorded in the data root (no release that records it has fully started on this root)" : `the data root's cutoff is ${m.cutoff}`;
|
|
222
|
+
return (` ${f.label} at ${f.path}: ${m.sessions} session(s) hold a \`${SKILL_EXEMPTION_TOOL_NAME}\` session approval exemption first granted before the Skill exemption cutoff (${cutoff}). ` +
|
|
223
|
+
`${PRE_CUTOFF_SKILL_EXEMPTION_CONSEQUENCE} This build no longer carries the one-time migration (introduced in ${m.requiredSince}) that removes such rows. ` +
|
|
224
|
+
`Start the root once with a release at or above ${atLeast} that still carries that migration (server or run-local) and let that start finish, then start this build.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function describeArchivesWithoutCell(f, m) {
|
|
229
|
+
const { highWater, requiredSince, sessions } = m;
|
|
230
|
+
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.";
|
|
231
|
+
if (highWater !== null && releaseAtOrAbove(highWater, requiredSince)) {
|
|
232
|
+
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 ` +
|
|
233
|
+
`(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 ` +
|
|
234
|
+
`(${ARCHIVE_TOKEN_ADOPTION_PENDING_FILE} is still in the data root). ${consequence} ` +
|
|
235
|
+
`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.`);
|
|
236
|
+
}
|
|
237
|
+
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}`;
|
|
238
|
+
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}, ` +
|
|
239
|
+
`whose one-time migration gives such an archive its row cell under the occupant token the archive already carries. ${consequence} ` +
|
|
240
|
+
`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.`);
|
|
241
|
+
}
|
|
170
242
|
function describeLocalDataRootMismatch(findings, when) {
|
|
171
243
|
const lines = [
|
|
172
244
|
when === "boot"
|
|
@@ -175,7 +247,9 @@ function describeLocalDataRootMismatch(findings, when) {
|
|
|
175
247
|
];
|
|
176
248
|
for (const f of findings)
|
|
177
249
|
lines.push(describeDataRootFinding(f));
|
|
178
|
-
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
|
|
250
|
+
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.", when === "boot"
|
|
251
|
+
? "This refusal comes from the data-root admission, which only reads and runs before this start writes anything to the data root: the data root is exactly as it was — no file, directory, key or lock was created, changed or removed."
|
|
252
|
+
: "The check behind this refusal only reads: the file(s) named above were left in place, unchanged.");
|
|
179
253
|
return lines.join("\n");
|
|
180
254
|
}
|
|
181
255
|
export class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
|
|
@@ -189,6 +263,136 @@ export class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
|
|
|
189
263
|
this.name = "LocalDataRootSchemaMismatchError";
|
|
190
264
|
}
|
|
191
265
|
}
|
|
266
|
+
export const ARCHIVE_TOKEN_ADOPTION_SINCE = "7.148.0";
|
|
267
|
+
const ARCHIVE_DIR = "sessions";
|
|
268
|
+
const ARCHIVE_SUFFIX = ".jsonl";
|
|
269
|
+
const ARCHIVE_TOKEN_ADOPTION_PENDING_FILE = "archive-token-adoption.pending";
|
|
270
|
+
const archiveTokenAdoptionPendingPath = (root) => resolve(join(root, ARCHIVE_TOKEN_ADOPTION_PENDING_FILE));
|
|
271
|
+
export function archiveTokenAdoptionPendingPresent(root) {
|
|
272
|
+
try {
|
|
273
|
+
lstatSync(archiveTokenAdoptionPendingPath(root));
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
if (e?.code === "ENOENT")
|
|
278
|
+
return false;
|
|
279
|
+
throw e;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function recordArchiveTokenAdoptionPending(root, priorHighWater) {
|
|
283
|
+
const path = archiveTokenAdoptionPendingPath(root);
|
|
284
|
+
const prior = priorHighWater === undefined ? "none" : releaseText(priorHighWater);
|
|
285
|
+
try {
|
|
286
|
+
atomicWriteFileSync(path, `opened ${new Date().toISOString()} by a release that adopts archive tokens; engine high-water before that start: ${prior}\n`, { durability: "required" });
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
throw new Error(`DB_BACKEND=local cannot record ${path} durably: ${e instanceof Error ? e.message : String(e)} — refusing to open the data root: ` +
|
|
290
|
+
"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. " +
|
|
291
|
+
"Repair write access to the data root and restart.", { cause: e });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function dischargeArchiveTokenAdoptionPending(root) {
|
|
295
|
+
const path = archiveTokenAdoptionPendingPath(root);
|
|
296
|
+
try {
|
|
297
|
+
unlinkSync(path);
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
if (e?.code !== "ENOENT")
|
|
301
|
+
throw e;
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (!IS_WIN32)
|
|
305
|
+
fsyncPaths(dirname(path));
|
|
306
|
+
}
|
|
307
|
+
export function archiveIdsInSync(root) {
|
|
308
|
+
let names;
|
|
309
|
+
try {
|
|
310
|
+
names = readdirSync(join(root, ARCHIVE_DIR));
|
|
311
|
+
}
|
|
312
|
+
catch (e) {
|
|
313
|
+
if (e?.code === "ENOENT")
|
|
314
|
+
return [];
|
|
315
|
+
throw e;
|
|
316
|
+
}
|
|
317
|
+
return names.filter((name) => name.endsWith(ARCHIVE_SUFFIX)).map((name) => name.slice(0, -ARCHIVE_SUFFIX.length));
|
|
318
|
+
}
|
|
319
|
+
function strictSidecarSite(root, entry) {
|
|
320
|
+
return { path: resolve(join(root, entry.file)), label: `${entry.file} (the local ${entry.which} sidecar)` };
|
|
321
|
+
}
|
|
322
|
+
export function readStrictSidecarTextsSync(root) {
|
|
323
|
+
return STRICT_SIDECARS.map((entry) => readDataRootFileTextSync(strictSidecarSite(root, entry).path));
|
|
324
|
+
}
|
|
325
|
+
function decodeStrictSidecar(root, entry, raw) {
|
|
326
|
+
const { path, label } = strictSidecarSite(root, entry);
|
|
327
|
+
if (raw.state === "absent")
|
|
328
|
+
return raw;
|
|
329
|
+
if (raw.state === "unreadable")
|
|
330
|
+
return { state: "unreadable", finding: dataRootMismatchOf(path, label, raw.fault) };
|
|
331
|
+
try {
|
|
332
|
+
return { state: "read", entries: decodeJsonColumn(raw.text, readSessionMetaSidecarInWindow, label) };
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
return { state: "unreadable", finding: dataRootMismatchOf(path, label, e) };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
export function readSessionMetaCellsSync(root) {
|
|
339
|
+
return decodeStrictSidecar(root, STRICT_SIDECARS[0], readDataRootFileTextSync(strictSidecarSite(root, STRICT_SIDECARS[0]).path));
|
|
340
|
+
}
|
|
341
|
+
export function judgeStrictSidecars(root, texts) {
|
|
342
|
+
const findings = [];
|
|
343
|
+
let sessionMeta = {};
|
|
344
|
+
STRICT_SIDECARS.forEach((entry, i) => {
|
|
345
|
+
const reading = decodeStrictSidecar(root, entry, texts[i] ?? { state: "absent" });
|
|
346
|
+
if (reading.state === "unreadable")
|
|
347
|
+
findings.push(reading.finding);
|
|
348
|
+
else if (reading.state === "read")
|
|
349
|
+
sessionMeta = reading.entries;
|
|
350
|
+
});
|
|
351
|
+
if (findings.length > 0)
|
|
352
|
+
throw new LocalDataRootSchemaMismatchError(findings, "boot");
|
|
353
|
+
return sessionMeta;
|
|
354
|
+
}
|
|
355
|
+
function cellLessArchiveIds(archiveIds, cells) {
|
|
356
|
+
return archiveIds.filter((id) => !Object.hasOwn(cells, id)).sort();
|
|
357
|
+
}
|
|
358
|
+
export function judgeArchiveTokenAdoptionDuty(root, priorHighWater, adoptionCarried, observed) {
|
|
359
|
+
const since = releaseTriple(ARCHIVE_TOKEN_ADOPTION_SINCE);
|
|
360
|
+
const neverOpenedByAdoptingRelease = priorHighWater === undefined || compareReleaseTriples(priorHighWater, since) < 0;
|
|
361
|
+
if (adoptionCarried)
|
|
362
|
+
return neverOpenedByAdoptingRelease && observed.archiveIds.length > 0 ? "record" : "none";
|
|
363
|
+
const owed = observed.adoptionPending;
|
|
364
|
+
if (!neverOpenedByAdoptingRelease && !owed)
|
|
365
|
+
return "none";
|
|
366
|
+
const cellLess = cellLessArchiveIds(observed.archiveIds, observed.cells);
|
|
367
|
+
if (cellLess.length === 0)
|
|
368
|
+
return owed ? "discharge" : "none";
|
|
369
|
+
throw new LocalDataRootSchemaMismatchError([
|
|
370
|
+
{
|
|
371
|
+
path: resolve(join(root, ARCHIVE_DIR)),
|
|
372
|
+
label: `${ARCHIVE_DIR} (the local session archives)`,
|
|
373
|
+
problem: "skipped-required-migration",
|
|
374
|
+
unknownFields: [],
|
|
375
|
+
skippedMigration: { residue: "archives-without-cell", highWater: priorHighWater === undefined ? null : releaseText(priorHighWater), requiredSince: ARCHIVE_TOKEN_ADOPTION_SINCE, sessions: cellLess.length },
|
|
376
|
+
},
|
|
377
|
+
], "boot");
|
|
378
|
+
}
|
|
379
|
+
export function settleArchiveTokenAdoptionDuty(physicalRoot, priorHighWater, action) {
|
|
380
|
+
switch (action) {
|
|
381
|
+
case "none":
|
|
382
|
+
return;
|
|
383
|
+
case "record":
|
|
384
|
+
return recordArchiveTokenAdoptionPending(physicalRoot, priorHighWater);
|
|
385
|
+
case "discharge":
|
|
386
|
+
return dischargeArchiveTokenAdoptionPending(physicalRoot);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function firstCellIncarnationOf(sessionId, archiveToken) {
|
|
390
|
+
const token = migrationCarried("M47") ? incarnationFromCell(sessionId, archiveToken, "absent") : undefined;
|
|
391
|
+
return token !== undefined ? { incarnation: token, adopted: true } : { incarnation: mintSessionIncarnation(), adopted: false };
|
|
392
|
+
}
|
|
393
|
+
function cellChangedUnderLock(sessionId) {
|
|
394
|
+
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`);
|
|
395
|
+
}
|
|
192
396
|
export async function readDataRootFileText(path) {
|
|
193
397
|
try {
|
|
194
398
|
return { state: "text", text: await readFile(path, "utf8") };
|
|
@@ -289,19 +493,20 @@ class SidecarMap {
|
|
|
289
493
|
}
|
|
290
494
|
return st.loaded;
|
|
291
495
|
}
|
|
292
|
-
async
|
|
293
|
-
const path = this.path;
|
|
294
|
-
if (!path)
|
|
295
|
-
return;
|
|
296
|
-
const st = this.state;
|
|
297
|
-
if (st.refused !== undefined)
|
|
298
|
-
throw st.refused;
|
|
299
|
-
if (!st.loaded)
|
|
300
|
-
return this.ensureLoaded();
|
|
301
|
-
await st.loaded;
|
|
496
|
+
async landed(path, changed, draft) {
|
|
302
497
|
const disk = await this.readDisk(path);
|
|
303
|
-
|
|
304
|
-
|
|
498
|
+
switch (disk.state) {
|
|
499
|
+
case "absent":
|
|
500
|
+
return "not-landed";
|
|
501
|
+
case "unreadable":
|
|
502
|
+
return "unknown";
|
|
503
|
+
case "read":
|
|
504
|
+
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";
|
|
505
|
+
default: {
|
|
506
|
+
const unknownRead = disk;
|
|
507
|
+
throw new Error(`SidecarMap: no sidecar read state ${JSON.stringify(unknownRead)}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
305
510
|
}
|
|
306
511
|
async readDisk(path) {
|
|
307
512
|
const raw = await readDataRootFileText(path);
|
|
@@ -322,7 +527,7 @@ class SidecarMap {
|
|
|
322
527
|
return refusal;
|
|
323
528
|
}
|
|
324
529
|
persist() {
|
|
325
|
-
return this.commit(() => true);
|
|
530
|
+
return this.commit(() => true, "best-effort");
|
|
326
531
|
}
|
|
327
532
|
syncToDisk() {
|
|
328
533
|
const path = this.path;
|
|
@@ -330,10 +535,10 @@ class SidecarMap {
|
|
|
330
535
|
return;
|
|
331
536
|
fsyncPaths(path, dirname(path));
|
|
332
537
|
}
|
|
333
|
-
deleteKey(key) {
|
|
334
|
-
return this.commit((draft) => draft.delete(key));
|
|
538
|
+
deleteKey(key, durability) {
|
|
539
|
+
return this.commit((draft) => draft.delete(key), durability);
|
|
335
540
|
}
|
|
336
|
-
commit(change) {
|
|
541
|
+
commit(change, durability) {
|
|
337
542
|
const path = this.path;
|
|
338
543
|
const st = this.state;
|
|
339
544
|
const link = st.writeChain.then(async () => {
|
|
@@ -341,8 +546,27 @@ class SidecarMap {
|
|
|
341
546
|
throw st.refused;
|
|
342
547
|
const base = new Map(st.entries);
|
|
343
548
|
const draft = new Map(base);
|
|
344
|
-
if (!change(draft))
|
|
345
|
-
|
|
549
|
+
if (!change(draft)) {
|
|
550
|
+
if (durability !== "required" || !path || !existsSync(path))
|
|
551
|
+
return true;
|
|
552
|
+
try {
|
|
553
|
+
proveDurableSync(path);
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
catch (e) {
|
|
557
|
+
this.onWriteFailed(e);
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const changed = [...new Set([...base.keys(), ...draft.keys()])].filter((key) => !(draft.has(key) === base.has(key) && draft.get(key) === base.get(key)));
|
|
562
|
+
const publish = () => {
|
|
563
|
+
for (const key of changed) {
|
|
564
|
+
if (draft.has(key))
|
|
565
|
+
st.entries.set(key, draft.get(key));
|
|
566
|
+
else
|
|
567
|
+
st.entries.delete(key);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
346
570
|
if (path) {
|
|
347
571
|
if (this.strict) {
|
|
348
572
|
const disk = await this.readDisk(path);
|
|
@@ -354,21 +578,16 @@ class SidecarMap {
|
|
|
354
578
|
}
|
|
355
579
|
}
|
|
356
580
|
try {
|
|
357
|
-
await atomicWriteFileAsync(path, JSON.stringify(Object.fromEntries(draft)), { durability
|
|
581
|
+
await atomicWriteFileAsync(path, JSON.stringify(Object.fromEntries(draft)), { durability });
|
|
358
582
|
}
|
|
359
583
|
catch (e) {
|
|
360
584
|
this.onWriteFailed(e);
|
|
585
|
+
if (durability === "required" && (await this.landed(path, changed, draft)) !== "not-landed")
|
|
586
|
+
publish();
|
|
361
587
|
return false;
|
|
362
588
|
}
|
|
363
589
|
}
|
|
364
|
-
|
|
365
|
-
if (draft.has(key) === base.has(key) && draft.get(key) === base.get(key))
|
|
366
|
-
continue;
|
|
367
|
-
if (draft.has(key))
|
|
368
|
-
st.entries.set(key, draft.get(key));
|
|
369
|
-
else
|
|
370
|
-
st.entries.delete(key);
|
|
371
|
-
}
|
|
590
|
+
publish();
|
|
372
591
|
return true;
|
|
373
592
|
});
|
|
374
593
|
st.writeChain = link.then(() => undefined, () => undefined);
|
|
@@ -480,7 +699,6 @@ export class LocalSessionStore {
|
|
|
480
699
|
listPlaced;
|
|
481
700
|
recordedPlacementOf;
|
|
482
701
|
pending = new Map();
|
|
483
|
-
rowsDropped = new Set();
|
|
484
702
|
lastActivity = new Map();
|
|
485
703
|
stagings = new Map();
|
|
486
704
|
loaded;
|
|
@@ -494,17 +712,18 @@ export class LocalSessionStore {
|
|
|
494
712
|
instructionSources;
|
|
495
713
|
workspaces;
|
|
496
714
|
meta;
|
|
497
|
-
strictSidecars = [];
|
|
498
715
|
log;
|
|
499
716
|
sessionWriteLocks;
|
|
500
717
|
executionPlacement;
|
|
501
718
|
executionPlacementBackfill;
|
|
502
719
|
dataRoot;
|
|
503
720
|
archiveSnapshotsSettled = 0;
|
|
721
|
+
withdrawCheckpointCtx;
|
|
504
722
|
constructor(repo, runs, dataDir, opts) {
|
|
505
723
|
this.repo = repo;
|
|
506
724
|
this.runs = runs;
|
|
507
725
|
this.sidecarPath = dataDir ? join(dataDir, "session-last-activity.json") : undefined;
|
|
726
|
+
this.withdrawCheckpointCtx = opts?.withdrawCheckpointCtx;
|
|
508
727
|
this.log = opts?.log ?? createLogger();
|
|
509
728
|
const repoPlacementOf = repo.placementOf;
|
|
510
729
|
const repoListPlaced = repo.listPlaced;
|
|
@@ -534,17 +753,15 @@ export class LocalSessionStore {
|
|
|
534
753
|
}
|
|
535
754
|
this.durability = opts.durability;
|
|
536
755
|
}
|
|
537
|
-
const sidecar = (file, which, read
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
this.strictSidecars.push(map);
|
|
541
|
-
return map;
|
|
756
|
+
const sidecar = (file, which, read) => {
|
|
757
|
+
const strict = STRICT_SIDECAR_FILES.has(file);
|
|
758
|
+
return new SidecarMap(dataDir ? resolve(join(dataDir, file)) : undefined, `${file} (the local ${which} sidecar)`, read, (path, e) => this.refuseSidecar(path, `LocalSessionStore ${which} sidecar`, e, which), (e) => this.warnSidecar("write", e, which), strict);
|
|
542
759
|
};
|
|
543
760
|
this.titles = sidecar("session-titles.json", "title", readStringRecordColumn);
|
|
544
761
|
this.pinned = sidecar("session-pinned.json", "pinned", readPinnedSidecar);
|
|
545
762
|
this.instructionSources = sidecar("session-instruction-sources.json", "instruction-source", readStringRecordColumn);
|
|
546
763
|
this.workspaces = sidecar("session-workspaces.json", "workspace", readStringRecordColumn);
|
|
547
|
-
this.meta = sidecar(SESSION_META_SIDECAR_FILE, "session-meta", readSessionMetaSidecar
|
|
764
|
+
this.meta = sidecar(SESSION_META_SIDECAR_FILE, "session-meta", readSessionMetaSidecar);
|
|
548
765
|
const root = dataDir ? resolve(dataDir) : undefined;
|
|
549
766
|
this.dataRoot = root;
|
|
550
767
|
if (root !== undefined && !SESSION_WRITE_LOCKS_BY_ROOT.has(root))
|
|
@@ -555,10 +772,17 @@ export class LocalSessionStore {
|
|
|
555
772
|
place: (sessionId, occupant, cell) => this.placeExecution(sessionId, occupant, cell),
|
|
556
773
|
markMirror: (sessionId, occupant) => this.markMirrorExecution(sessionId, occupant),
|
|
557
774
|
};
|
|
558
|
-
this.executionPlacementBackfill = {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
775
|
+
this.executionPlacementBackfill = { backfill: (sessionId, lanes) => this.backfillPlacement(sessionId, lanes) };
|
|
776
|
+
}
|
|
777
|
+
async scenarioPrefixOwnerRowsM42() {
|
|
778
|
+
await this.meta.ensureLoaded();
|
|
779
|
+
let n = 0;
|
|
780
|
+
for (const id of [...this.liveCellIds(), ...this.droppingIds(), ...this.deletingIds()]) {
|
|
781
|
+
const cell = this.cellStateOf(id);
|
|
782
|
+
if (cell.state !== "none" && typeof cell.meta.owner === "string" && cell.meta.owner.startsWith(SCENARIO_SUBJECT_PREFIX))
|
|
783
|
+
n += 1;
|
|
784
|
+
}
|
|
785
|
+
return n;
|
|
562
786
|
}
|
|
563
787
|
async renameRetiredExecutionLaneWordM40() {
|
|
564
788
|
if (this.dataRoot === undefined)
|
|
@@ -567,16 +791,7 @@ export class LocalSessionStore {
|
|
|
567
791
|
const disk = await readSessionMetaForM40(path);
|
|
568
792
|
if (disk.state !== "read")
|
|
569
793
|
return 0;
|
|
570
|
-
|
|
571
|
-
for (const cell of Object.values(disk.entries)) {
|
|
572
|
-
if (!isJsonObject(cell))
|
|
573
|
-
continue;
|
|
574
|
-
const execution = cell["execution"];
|
|
575
|
-
if (!isJsonObject(execution) || execution["role"] !== "writer" || execution["lane"] !== RETIRED_EXECUTION_LANE_WORD_M40)
|
|
576
|
-
continue;
|
|
577
|
-
execution["lane"] = RENAMED_EXECUTION_LANE_M40;
|
|
578
|
-
renamed += 1;
|
|
579
|
-
}
|
|
794
|
+
const renamed = renameRetiredExecutionLaneWordsInCellsM40(disk.entries);
|
|
580
795
|
if (renamed === 0)
|
|
581
796
|
return 0;
|
|
582
797
|
try {
|
|
@@ -587,52 +802,67 @@ export class LocalSessionStore {
|
|
|
587
802
|
}
|
|
588
803
|
return renamed;
|
|
589
804
|
}
|
|
590
|
-
async
|
|
591
|
-
const loads = await Promise.allSettled(this.strictSidecars.map((map) => map.reconcile()));
|
|
592
|
-
const findings = [];
|
|
593
|
-
for (const load of loads) {
|
|
594
|
-
if (load.status === "fulfilled")
|
|
595
|
-
continue;
|
|
596
|
-
if (!(load.reason instanceof LocalDataRootSchemaMismatchError))
|
|
597
|
-
throw load.reason;
|
|
598
|
-
findings.push(...load.reason.findings);
|
|
599
|
-
}
|
|
600
|
-
if (findings.length > 0)
|
|
601
|
-
throw new LocalDataRootSchemaMismatchError(findings, "boot");
|
|
602
|
-
}
|
|
603
|
-
async forkingRead(sessionId, meta) {
|
|
805
|
+
async forkingOf(sessionId, meta) {
|
|
604
806
|
if (!isForkCreatedCell(meta))
|
|
605
807
|
return false;
|
|
606
|
-
|
|
607
|
-
if (probe.state === "unusable" && probe.word === "storage")
|
|
608
|
-
return probe;
|
|
609
|
-
return probe.state === "absent";
|
|
808
|
+
return (await this.probeArchive(sessionId)).state === "absent";
|
|
610
809
|
}
|
|
611
|
-
async
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (partition.state === "unusable" && partition.word === "storage")
|
|
627
|
-
return partition;
|
|
628
|
-
const forking = await this.forkingRead(sessionId, read.meta);
|
|
629
|
-
return typeof forking === "boolean" ? readingOfCell(sessionId, read.meta, forking) : forking;
|
|
810
|
+
async observeRow(sessionId, want = {}) {
|
|
811
|
+
await this.meta.ensureLoaded();
|
|
812
|
+
const cell = this.cellStateOf(sessionId);
|
|
813
|
+
if (cell.state === "dropping" || cell.state === "deleting")
|
|
814
|
+
return ABSENT_ROW;
|
|
815
|
+
if (cell.state === "none") {
|
|
816
|
+
const probe = await this.probeArchive(sessionId);
|
|
817
|
+
if (probe.state === "absent")
|
|
818
|
+
return ABSENT_ROW;
|
|
819
|
+
const placed = want.placement === true ? await this.placedRecordOf(sessionId) : undefined;
|
|
820
|
+
return { row: "legacy", placed, ...(probe.state === "corrupt" ? { unreadable: probe.fault } : {}) };
|
|
821
|
+
}
|
|
822
|
+
const placed = want.placement === true ? await this.placedRecordOf(sessionId) : undefined;
|
|
823
|
+
const forking = want.forking === true && placed === undefined ? await this.forkingOf(sessionId, cell.meta) : false;
|
|
824
|
+
return { row: "live", meta: cell.meta, placed, forking };
|
|
630
825
|
}
|
|
631
826
|
async readExecutionPlacement(sessionId) {
|
|
632
|
-
const
|
|
633
|
-
if (
|
|
634
|
-
|
|
635
|
-
return
|
|
827
|
+
const seen = await this.observeRow(sessionId, { placement: true, forking: true });
|
|
828
|
+
if (!legacyNeedsMint(seen))
|
|
829
|
+
return executionReadingOf(sessionId, seen);
|
|
830
|
+
return this.withSessionWriteLock(sessionId, async () => {
|
|
831
|
+
const held = await this.observeRow(sessionId, { placement: true, forking: true });
|
|
832
|
+
return legacyNeedsMint(held) ? readingOfCell(sessionId, await this.mintHeld(sessionId), false) : executionReadingOf(sessionId, held);
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
async backfillPlacement(sessionId, lanes) {
|
|
836
|
+
return this.withSessionWriteLock(sessionId, async () => {
|
|
837
|
+
let seen;
|
|
838
|
+
try {
|
|
839
|
+
seen = await this.observeRow(sessionId, { placement: true, forking: true });
|
|
840
|
+
}
|
|
841
|
+
catch (fault) {
|
|
842
|
+
return unusableArchiveOfFault(fault);
|
|
843
|
+
}
|
|
844
|
+
if (seen.row === "absent" || seen.placed !== undefined)
|
|
845
|
+
return { outcome: "skipped" };
|
|
846
|
+
if (seen.row === "legacy" && seen.unreadable !== undefined)
|
|
847
|
+
return unusableArchiveOfFault(ownerUnreadableFault(seen.unreadable));
|
|
848
|
+
if (seen.row === "live" && (seen.meta.execution !== undefined || seen.forking))
|
|
849
|
+
return { outcome: "skipped" };
|
|
850
|
+
if (lanes.length !== 1)
|
|
851
|
+
return { outcome: "ambiguous" };
|
|
852
|
+
const writer = { role: "writer", lane: lanes[0] };
|
|
853
|
+
assertPlaceableLane(sessionId, writer);
|
|
854
|
+
let first;
|
|
855
|
+
if (seen.row === "legacy") {
|
|
856
|
+
try {
|
|
857
|
+
first = await this.firstCellIncarnationHeld(sessionId);
|
|
858
|
+
}
|
|
859
|
+
catch (fault) {
|
|
860
|
+
return unusableArchiveOfFault(fault);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
await this.writeMeta(sessionId, (before) => before === undefined ? { incarnation: first ?? cellChangedUnderLock(sessionId), execution: writer } : readSessionMetaCell(before).execution === undefined ? cellWith(before, { execution: writer }) : undefined);
|
|
864
|
+
return { outcome: "placed" };
|
|
865
|
+
});
|
|
636
866
|
}
|
|
637
867
|
async placeExecution(sessionId, occupant, cell) {
|
|
638
868
|
assertPlaceableLane(sessionId, cell);
|
|
@@ -644,7 +874,7 @@ export class LocalSessionStore {
|
|
|
644
874
|
async markMirrorExecution(sessionId, occupant) {
|
|
645
875
|
return this.withSessionWriteLock(sessionId, async () => {
|
|
646
876
|
await this.meta.ensureLoaded();
|
|
647
|
-
const claim = this.
|
|
877
|
+
const claim = this.liveCellOf(sessionId) === undefined ? undefined : await this.runs.getActiveTaskId(sessionId);
|
|
648
878
|
return this.settleExecutionHeld(sessionId, (reading) => {
|
|
649
879
|
const settled = settledMirrorOutcome(sessionId, reading, occupant);
|
|
650
880
|
if (settled !== WRITABLE)
|
|
@@ -654,9 +884,9 @@ export class LocalSessionStore {
|
|
|
654
884
|
});
|
|
655
885
|
}
|
|
656
886
|
async settleExecutionHeld(sessionId, judge) {
|
|
657
|
-
await this.
|
|
658
|
-
const placed =
|
|
659
|
-
const forking =
|
|
887
|
+
const seen = await this.observeRow(sessionId, { placement: true, forking: true });
|
|
888
|
+
const placed = seen.row !== "absent" && seen.placed !== undefined;
|
|
889
|
+
const forking = seen.row === "live" && seen.forking;
|
|
660
890
|
let settled;
|
|
661
891
|
let fault;
|
|
662
892
|
await this.writeMeta(sessionId, (before) => {
|
|
@@ -673,33 +903,75 @@ export class LocalSessionStore {
|
|
|
673
903
|
throw fault;
|
|
674
904
|
return settled.outcome;
|
|
675
905
|
}
|
|
676
|
-
|
|
906
|
+
cellStateOf(sessionId) {
|
|
677
907
|
const cell = this.meta.state.entries.get(sessionId);
|
|
678
|
-
|
|
908
|
+
if (cell === undefined)
|
|
909
|
+
return NO_CELL;
|
|
910
|
+
const meta = readSessionMetaCell(cell);
|
|
911
|
+
return { state: meta.deleting !== undefined ? "deleting" : meta.dropping ? "dropping" : "live", meta };
|
|
912
|
+
}
|
|
913
|
+
async rowDropping(sessionId) {
|
|
914
|
+
await this.meta.ensureLoaded();
|
|
915
|
+
const state = this.cellStateOf(sessionId).state;
|
|
916
|
+
return state === "dropping" || state === "deleting";
|
|
917
|
+
}
|
|
918
|
+
rowGoneSince(sessionId, hadLiveCell) {
|
|
919
|
+
const state = this.cellStateOf(sessionId).state;
|
|
920
|
+
return state === "dropping" || state === "deleting" || (state === "none" && hadLiveCell);
|
|
921
|
+
}
|
|
922
|
+
liveCellOf(sessionId) {
|
|
923
|
+
const cell = this.cellStateOf(sessionId);
|
|
924
|
+
return cell.state === "live" ? cell.meta : undefined;
|
|
925
|
+
}
|
|
926
|
+
liveCellIds() {
|
|
927
|
+
return [...this.meta.state.entries]
|
|
928
|
+
.filter(([, cell]) => {
|
|
929
|
+
const meta = readSessionMetaCell(cell);
|
|
930
|
+
return !meta.dropping && meta.deleting === undefined;
|
|
931
|
+
})
|
|
932
|
+
.map(([id]) => id)
|
|
933
|
+
.sort();
|
|
934
|
+
}
|
|
935
|
+
deletingIds() {
|
|
936
|
+
return [...this.meta.state.entries].filter(([id]) => this.cellStateOf(id).state === "deleting").map(([id]) => id).sort();
|
|
937
|
+
}
|
|
938
|
+
droppingIds() {
|
|
939
|
+
return [...this.meta.state.entries].filter(([id]) => this.cellStateOf(id).state === "dropping").map(([id]) => id).sort();
|
|
679
940
|
}
|
|
680
941
|
async writeMeta(sessionId, next) {
|
|
681
942
|
await this.meta.ensureLoaded();
|
|
682
943
|
let changed = false;
|
|
683
944
|
const committed = await this.meta.commit((draft) => {
|
|
684
|
-
const
|
|
945
|
+
const before = draft.get(sessionId);
|
|
946
|
+
if (before !== undefined && readSessionMetaCell(before).deleting !== undefined)
|
|
947
|
+
throw new SessionError("not_found", `LocalSessionStore: session ${sessionId} is being deleted — it reads as gone; this write was not made`);
|
|
948
|
+
if (before !== undefined && readSessionMetaCell(before).dropping)
|
|
949
|
+
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`);
|
|
950
|
+
const value = next(before);
|
|
685
951
|
if (value === undefined)
|
|
686
952
|
return false;
|
|
687
953
|
draft.set(sessionId, value);
|
|
688
954
|
changed = true;
|
|
689
955
|
return true;
|
|
690
|
-
});
|
|
956
|
+
}, "best-effort");
|
|
691
957
|
if (!committed)
|
|
692
958
|
throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed for session ${sessionId} — nothing was recorded`);
|
|
693
959
|
return changed;
|
|
694
960
|
}
|
|
695
961
|
async dropMeta(sessionId) {
|
|
696
962
|
await this.meta.ensureLoaded();
|
|
697
|
-
if (!(await this.meta.deleteKey(sessionId)))
|
|
698
|
-
throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed while
|
|
963
|
+
if (!(await this.meta.deleteKey(sessionId, "best-effort")))
|
|
964
|
+
throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed while withdrawing session ${sessionId}'s cell — the cell is still on disk`);
|
|
965
|
+
}
|
|
966
|
+
async withRootRow(sessionId, body) {
|
|
967
|
+
return this.withSessionWriteLock(sessionId, async () => body(await this.rowPresentHeld(sessionId)));
|
|
968
|
+
}
|
|
969
|
+
async rowPresentHeld(sessionId) {
|
|
970
|
+
return (await this.observeRow(sessionId)).row !== "absent";
|
|
699
971
|
}
|
|
700
972
|
async withSessionWriteLock(sessionId, body) {
|
|
701
973
|
const locks = this.sessionWriteLocks;
|
|
702
|
-
const link = (locks.get(sessionId) ?? Promise.resolve()).then(body);
|
|
974
|
+
const link = (locks.get(sessionId) ?? Promise.resolve()).then(async () => body(await this.finishPendingDropHeld(sessionId)));
|
|
703
975
|
const tail = link.then(() => undefined, () => undefined);
|
|
704
976
|
locks.set(sessionId, tail);
|
|
705
977
|
try {
|
|
@@ -717,7 +989,9 @@ export class LocalSessionStore {
|
|
|
717
989
|
}
|
|
718
990
|
catch (e) {
|
|
719
991
|
const word = e instanceof SessionError ? archiveFaultWord(e.code) : "storage";
|
|
720
|
-
|
|
992
|
+
if (word === "storage")
|
|
993
|
+
throw e;
|
|
994
|
+
return word === "absent" ? { state: "absent" } : { state: "corrupt", fault: e };
|
|
721
995
|
}
|
|
722
996
|
}
|
|
723
997
|
async placementProbe(sessionId) {
|
|
@@ -727,76 +1001,88 @@ export class LocalSessionStore {
|
|
|
727
1001
|
}
|
|
728
1002
|
catch (e) {
|
|
729
1003
|
const word = e instanceof SessionError ? archiveFaultWord(e.code) : "storage";
|
|
730
|
-
|
|
1004
|
+
if (word === "storage")
|
|
1005
|
+
throw e;
|
|
1006
|
+
return word === "absent" ? { state: "ordinary" } : { state: "corrupt", fault: e };
|
|
731
1007
|
}
|
|
732
1008
|
}
|
|
733
1009
|
async placedRecordOf(sessionId) {
|
|
734
1010
|
const probe = await this.placementProbe(sessionId);
|
|
735
|
-
if (probe.state === "unusable") {
|
|
736
|
-
if (probe.word === "storage")
|
|
737
|
-
throw probe.fault;
|
|
738
|
-
return undefined;
|
|
739
|
-
}
|
|
740
1011
|
return probe.state === "placed" ? probe.record : undefined;
|
|
741
1012
|
}
|
|
742
|
-
async isPlacedRow(sessionId) {
|
|
743
|
-
return (await this.placedRecordOf(sessionId)) !== undefined;
|
|
744
|
-
}
|
|
745
1013
|
async placedRecordOrThrow(sessionId) {
|
|
746
1014
|
const probe = await this.placementProbe(sessionId);
|
|
747
|
-
if (probe.state === "
|
|
1015
|
+
if (probe.state === "corrupt")
|
|
748
1016
|
throw probe.fault;
|
|
749
1017
|
return probe.state === "placed" ? probe.record : undefined;
|
|
750
1018
|
}
|
|
751
1019
|
async existsInRepo(sessionId) {
|
|
752
1020
|
const probe = await this.probeArchive(sessionId);
|
|
753
|
-
if (probe.state === "
|
|
1021
|
+
if (probe.state === "corrupt")
|
|
754
1022
|
throw probe.fault;
|
|
755
1023
|
return probe.state === "readable";
|
|
756
1024
|
}
|
|
757
1025
|
async metaOf(sessionId) {
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1026
|
+
const seen = await this.observeRow(sessionId);
|
|
1027
|
+
switch (seen.row) {
|
|
1028
|
+
case "absent":
|
|
1029
|
+
return undefined;
|
|
1030
|
+
case "live":
|
|
1031
|
+
return seen.meta;
|
|
1032
|
+
case "legacy":
|
|
1033
|
+
if (seen.unreadable !== undefined)
|
|
1034
|
+
throw ownerUnreadableFault(seen.unreadable);
|
|
1035
|
+
return this.withSessionWriteLock(sessionId, () => this.metaHeld(sessionId));
|
|
1036
|
+
default: {
|
|
1037
|
+
const unknownRow = seen;
|
|
1038
|
+
throw new Error(`LocalSessionStore: no row observation ${JSON.stringify(unknownRow)}`);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
762
1041
|
}
|
|
763
|
-
async
|
|
764
|
-
await this.
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
if (
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1042
|
+
async metaHeld(sessionId) {
|
|
1043
|
+
const seen = await this.observeRow(sessionId);
|
|
1044
|
+
if (seen.row === "absent")
|
|
1045
|
+
return undefined;
|
|
1046
|
+
if (seen.row === "live")
|
|
1047
|
+
return seen.meta;
|
|
1048
|
+
if (seen.unreadable !== undefined)
|
|
1049
|
+
throw ownerUnreadableFault(seen.unreadable);
|
|
1050
|
+
return this.mintHeld(sessionId);
|
|
1051
|
+
}
|
|
1052
|
+
async mintHeld(sessionId) {
|
|
1053
|
+
const incarnation = await this.firstCellIncarnationHeld(sessionId);
|
|
1054
|
+
await this.writeMeta(sessionId, (before) => (before === undefined ? { incarnation } : undefined));
|
|
1055
|
+
const meta = this.liveCellOf(sessionId);
|
|
1056
|
+
if (meta === undefined)
|
|
1057
|
+
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`);
|
|
1058
|
+
return meta;
|
|
1059
|
+
}
|
|
1060
|
+
async firstCellIncarnationHeld(sessionId) {
|
|
1061
|
+
const head = migrationCarried("M47") ? await this.archiveHeadOf(sessionId) : undefined;
|
|
1062
|
+
return firstCellIncarnationOf(sessionId, head?.incarnation).incarnation;
|
|
781
1063
|
}
|
|
782
1064
|
async headOf(sessionId) {
|
|
783
1065
|
const before = await this.metaOf(sessionId);
|
|
784
1066
|
if (before === undefined)
|
|
785
1067
|
return undefined;
|
|
786
1068
|
const leaf = await this.archiveHeadOf(sessionId).then((archive) => archive?.leafId ?? null, (fault) => new UnreadableLeaf(fault));
|
|
787
|
-
const
|
|
788
|
-
const after = cell === undefined ? undefined : readSessionMetaCell(cell);
|
|
1069
|
+
const after = this.liveCellOf(sessionId);
|
|
789
1070
|
if (after === undefined || after.incarnation !== before.incarnation || (after.owner ?? null) !== (before.owner ?? null))
|
|
790
1071
|
return undefined;
|
|
791
1072
|
return headFromRow(sessionId, before.owner ?? null, before.incarnation, leaf);
|
|
792
1073
|
}
|
|
1074
|
+
async deletionHeadOf(sessionId) {
|
|
1075
|
+
await this.meta.ensureLoaded();
|
|
1076
|
+
const cell = this.cellStateOf(sessionId);
|
|
1077
|
+
if (cell.state === "dropping" || cell.state === "deleting")
|
|
1078
|
+
return headFromRow(sessionId, cell.meta.owner ?? null, cell.meta.incarnation, null);
|
|
1079
|
+
return this.headOf(sessionId);
|
|
1080
|
+
}
|
|
793
1081
|
archiveHeadOf(sessionId) {
|
|
794
1082
|
return this.repo.headOf(sessionId);
|
|
795
1083
|
}
|
|
796
1084
|
async handOut(sessionId, session) {
|
|
797
|
-
const meta = await this.
|
|
798
|
-
if (meta === undefined)
|
|
799
|
-
throw new SessionError("storage", `LocalSessionStore: session ${sessionId} lost its session-meta cell while it was being handed out`);
|
|
1085
|
+
const meta = this.liveCellOf(sessionId) ?? (await this.mintHeld(sessionId));
|
|
800
1086
|
return { session: occupiedBy(session, rowMetadataOf(sessionId, meta)), sessionId };
|
|
801
1087
|
}
|
|
802
1088
|
async sessionMetadataOf(sessionId) {
|
|
@@ -804,11 +1090,15 @@ export class LocalSessionStore {
|
|
|
804
1090
|
return meta === undefined ? undefined : rowMetadataOf(sessionId, meta);
|
|
805
1091
|
}
|
|
806
1092
|
async probeTitle(sessionId) {
|
|
1093
|
+
if (await this.rowDropping(sessionId))
|
|
1094
|
+
return "none";
|
|
807
1095
|
await this.titles.ensureLoaded();
|
|
808
1096
|
return this.titles.state.entries.has(sessionId) ? "titled" : "untitled";
|
|
809
1097
|
}
|
|
810
1098
|
async setTitleIfNull(sessionId, title) {
|
|
811
1099
|
return this.withSessionWriteLock(sessionId, async () => {
|
|
1100
|
+
if (!(await this.rowPresentHeld(sessionId)))
|
|
1101
|
+
return false;
|
|
812
1102
|
await this.titles.ensureLoaded();
|
|
813
1103
|
const entries = this.titles.state.entries;
|
|
814
1104
|
if (entries.has(sessionId))
|
|
@@ -834,6 +1124,7 @@ export class LocalSessionStore {
|
|
|
834
1124
|
case "mirror":
|
|
835
1125
|
return { kind: "mirror" };
|
|
836
1126
|
case "write":
|
|
1127
|
+
await this.bumpClientWritesHeld(sessionId);
|
|
837
1128
|
await this.writeRowCellsHeld(sessionId, cells);
|
|
838
1129
|
return settleSessionRowWrite(how, verdict, await this.listRowOf(sessionId));
|
|
839
1130
|
case "changed":
|
|
@@ -846,7 +1137,7 @@ export class LocalSessionStore {
|
|
|
846
1137
|
});
|
|
847
1138
|
}
|
|
848
1139
|
async lockedRowOf(sessionId) {
|
|
849
|
-
const meta = this.
|
|
1140
|
+
const meta = this.liveCellOf(sessionId);
|
|
850
1141
|
if (meta === undefined || (await this.rowHeaderOf(sessionId)) === undefined)
|
|
851
1142
|
return undefined;
|
|
852
1143
|
return { owner: meta.owner ?? null, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent"), placed: false, mirror: meta.execution?.role === "mirror", cells: this.rowCellsOf(sessionId) };
|
|
@@ -864,7 +1155,7 @@ export class LocalSessionStore {
|
|
|
864
1155
|
return false;
|
|
865
1156
|
draft.set(sessionId, title);
|
|
866
1157
|
return true;
|
|
867
|
-
});
|
|
1158
|
+
}, "best-effort");
|
|
868
1159
|
if (!ok)
|
|
869
1160
|
throw new SessionError("storage", `LocalSessionStore: title sidecar write failed for session ${sessionId} — the title was not recorded`);
|
|
870
1161
|
}
|
|
@@ -876,7 +1167,7 @@ export class LocalSessionStore {
|
|
|
876
1167
|
return false;
|
|
877
1168
|
draft.set(sessionId, true);
|
|
878
1169
|
return true;
|
|
879
|
-
});
|
|
1170
|
+
}, "best-effort");
|
|
880
1171
|
if (!ok)
|
|
881
1172
|
throw new SessionError("storage", `LocalSessionStore: pin sidecar write failed for session ${sessionId} — the pin was not recorded`);
|
|
882
1173
|
}
|
|
@@ -884,23 +1175,28 @@ export class LocalSessionStore {
|
|
|
884
1175
|
async listRowOf(sessionId) {
|
|
885
1176
|
await this.loadRowSidecars();
|
|
886
1177
|
const header = await this.rowHeaderOf(sessionId);
|
|
887
|
-
const meta = this.
|
|
1178
|
+
const meta = this.liveCellOf(sessionId);
|
|
888
1179
|
if (header === undefined || meta === undefined)
|
|
889
1180
|
return undefined;
|
|
890
|
-
return { row:
|
|
1181
|
+
return { row: this.summaryOf(await this.listedRowOf(header), await this.runRollups()), occupant: { owner: meta.owner ?? null, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent") } };
|
|
891
1182
|
}
|
|
892
1183
|
async sessionSummaryOf(sessionId) {
|
|
893
1184
|
await this.loadRowSidecars();
|
|
894
1185
|
const header = await this.rowHeaderOf(sessionId);
|
|
895
|
-
|
|
1186
|
+
if (header === undefined)
|
|
1187
|
+
return undefined;
|
|
1188
|
+
const hadLiveCell = this.cellStateOf(sessionId).state === "live";
|
|
1189
|
+
const listed = await this.listedRowOf(header);
|
|
1190
|
+
const rollups = await this.runRollups();
|
|
1191
|
+
return this.rowGoneSince(sessionId, hadLiveCell) ? undefined : this.summaryOf(listed, rollups);
|
|
896
1192
|
}
|
|
897
1193
|
async rowHeaders() {
|
|
898
1194
|
await this.meta.ensureLoaded();
|
|
899
|
-
const listed = await this.repo.list();
|
|
1195
|
+
const listed = (await this.repo.list()).filter((row) => this.cellStateOf(row.id).state !== "dropping");
|
|
900
1196
|
const listedIds = new Set(listed.map((row) => row.id));
|
|
901
1197
|
const placed = this.createPlacedIdsReader();
|
|
902
1198
|
const rows = listed.map((header) => ({ sessionId: header.id, header }));
|
|
903
|
-
for (const id of
|
|
1199
|
+
for (const id of this.liveCellIds()) {
|
|
904
1200
|
if (listedIds.has(id))
|
|
905
1201
|
continue;
|
|
906
1202
|
const row = await this.headerUnreadableRowOf(id, placed);
|
|
@@ -911,10 +1207,13 @@ export class LocalSessionStore {
|
|
|
911
1207
|
}
|
|
912
1208
|
async rowHeaderOf(sessionId) {
|
|
913
1209
|
await this.meta.ensureLoaded();
|
|
1210
|
+
const cell = this.cellStateOf(sessionId);
|
|
1211
|
+
if (cell.state === "dropping" || cell.state === "deleting")
|
|
1212
|
+
return undefined;
|
|
914
1213
|
const header = (await this.repo.list()).find((m) => m.id === sessionId);
|
|
915
1214
|
if (header !== undefined)
|
|
916
1215
|
return { sessionId, header };
|
|
917
|
-
return
|
|
1216
|
+
return cell.state === "live" ? this.headerUnreadableRowOf(sessionId, this.createPlacedIdsReader()) : undefined;
|
|
918
1217
|
}
|
|
919
1218
|
async headerUnreadableRowOf(sessionId, placed) {
|
|
920
1219
|
try {
|
|
@@ -934,35 +1233,43 @@ export class LocalSessionStore {
|
|
|
934
1233
|
return { sessionId: row.sessionId, unreadable: row.headerFault };
|
|
935
1234
|
try {
|
|
936
1235
|
await this.archiveHeadOf(row.sessionId);
|
|
937
|
-
return { sessionId: row.sessionId, readable:
|
|
1236
|
+
return { sessionId: row.sessionId, readable: true };
|
|
938
1237
|
}
|
|
939
1238
|
catch (fault) {
|
|
940
1239
|
return { sessionId: row.sessionId, unreadable: fault };
|
|
941
1240
|
}
|
|
942
1241
|
}
|
|
943
1242
|
async readInstructionSource(sessionId) {
|
|
1243
|
+
if (await this.rowDropping(sessionId))
|
|
1244
|
+
return decodeInstructionSource(undefined);
|
|
944
1245
|
await this.instructionSources.ensureLoaded();
|
|
945
1246
|
return decodeInstructionSource(this.instructionSources.state.entries.get(sessionId));
|
|
946
1247
|
}
|
|
947
1248
|
async recordInstructionSource(sessionId, expected, source) {
|
|
948
|
-
await this.
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1249
|
+
await this.withSessionWriteLock(sessionId, async () => {
|
|
1250
|
+
if (!(await this.rowPresentHeld(sessionId)))
|
|
1251
|
+
return;
|
|
1252
|
+
await this.instructionSources.ensureLoaded();
|
|
1253
|
+
const committed = await this.instructionSources.commit((draft) => {
|
|
1254
|
+
if (!sameInstructionSourceBaseline(decodeInstructionSource(draft.get(sessionId)), expected))
|
|
1255
|
+
return false;
|
|
1256
|
+
draft.set(sessionId, encodeInstructionSource(source));
|
|
1257
|
+
return true;
|
|
1258
|
+
}, "best-effort");
|
|
1259
|
+
if (!committed)
|
|
1260
|
+
throw new Error("LocalSessionStore: instruction-source sidecar write failed — baseline not recorded");
|
|
954
1261
|
});
|
|
955
|
-
if (!committed)
|
|
956
|
-
throw new Error("LocalSessionStore: instruction-source sidecar write failed — baseline not recorded");
|
|
957
1262
|
}
|
|
958
1263
|
async readSessionWorkspace(sessionId) {
|
|
1264
|
+
if (await this.rowDropping(sessionId))
|
|
1265
|
+
return undefined;
|
|
959
1266
|
await this.workspaces.ensureLoaded();
|
|
960
1267
|
return decodeSessionWorkspaceCell(this.workspaces.state.entries.get(sessionId) ?? null, sessionId);
|
|
961
1268
|
}
|
|
962
1269
|
async recordSessionWorkspace(sessionId, incarnation, workspace) {
|
|
963
1270
|
const text = encodeSessionWorkspaceCell(workspace, JSON.stringify, sessionId);
|
|
964
1271
|
await this.withSessionWriteLock(sessionId, async () => {
|
|
965
|
-
if ((await this.
|
|
1272
|
+
if ((await this.metaHeld(sessionId))?.incarnation !== incarnation)
|
|
966
1273
|
return;
|
|
967
1274
|
await this.workspaces.ensureLoaded();
|
|
968
1275
|
const committed = await this.workspaces.commit((draft) => {
|
|
@@ -970,7 +1277,7 @@ export class LocalSessionStore {
|
|
|
970
1277
|
return false;
|
|
971
1278
|
draft.set(sessionId, text);
|
|
972
1279
|
return true;
|
|
973
|
-
});
|
|
1280
|
+
}, "best-effort");
|
|
974
1281
|
if (!committed)
|
|
975
1282
|
throw new SessionError("storage", `LocalSessionStore: workspace sidecar write failed for session ${sessionId} — its place was not recorded`);
|
|
976
1283
|
});
|
|
@@ -1039,7 +1346,7 @@ export class LocalSessionStore {
|
|
|
1039
1346
|
if (now - this.lastWarnAt < 60_000)
|
|
1040
1347
|
return;
|
|
1041
1348
|
this.lastWarnAt = now;
|
|
1042
|
-
|
|
1349
|
+
writeLogText("stderr", `LocalSessionStore: ${which} sidecar ${op} failed (${SIDECAR_DEGRADES[which]} persistence degraded): ${e instanceof Error ? e.message : String(e)}`);
|
|
1043
1350
|
}
|
|
1044
1351
|
async acquire(sessionId, opts) {
|
|
1045
1352
|
if (!sessionId && this.declaredPlacement(opts) !== undefined)
|
|
@@ -1068,6 +1375,9 @@ export class LocalSessionStore {
|
|
|
1068
1375
|
}
|
|
1069
1376
|
async load(sessionId, requireExisting = false, opts) {
|
|
1070
1377
|
return this.withSessionWriteLock(sessionId, async () => {
|
|
1378
|
+
await this.meta.ensureLoaded();
|
|
1379
|
+
if (this.cellStateOf(sessionId).state === "deleting")
|
|
1380
|
+
throw new SessionError("not_found", `session ${sessionId} not found (it is being deleted)`);
|
|
1071
1381
|
try {
|
|
1072
1382
|
return await this.openForItselfHeld(sessionId, opts);
|
|
1073
1383
|
}
|
|
@@ -1078,7 +1388,7 @@ export class LocalSessionStore {
|
|
|
1078
1388
|
throw e;
|
|
1079
1389
|
}
|
|
1080
1390
|
await this.meta.ensureLoaded();
|
|
1081
|
-
if (isForkCreatedCell(this.
|
|
1391
|
+
if (isForkCreatedCell(this.liveCellOf(sessionId)))
|
|
1082
1392
|
throw new SessionForkInProgressError(sessionId);
|
|
1083
1393
|
const placement = this.declaredPlacement(opts);
|
|
1084
1394
|
if (placement !== undefined)
|
|
@@ -1095,7 +1405,7 @@ export class LocalSessionStore {
|
|
|
1095
1405
|
return this.handOut(sessionId, session);
|
|
1096
1406
|
}
|
|
1097
1407
|
async createPlacedHeld(sessionId, placement, opts) {
|
|
1098
|
-
if (this.
|
|
1408
|
+
if (this.liveCellOf(sessionId) !== undefined) {
|
|
1099
1409
|
throw new SessionError("placement_refused", `session ${sessionId} is already registered as an ordinary session — refusing to create a subagent transcript on its id`);
|
|
1100
1410
|
}
|
|
1101
1411
|
const { owner: treeOwner, ...tuple } = placement;
|
|
@@ -1122,8 +1432,8 @@ export class LocalSessionStore {
|
|
|
1122
1432
|
return this.handOut(sessionId, session);
|
|
1123
1433
|
}
|
|
1124
1434
|
async holdsThisPlacement(sessionId, opts) {
|
|
1125
|
-
const probe = await this.placementProbe(sessionId);
|
|
1126
|
-
if (probe.state === "
|
|
1435
|
+
const probe = await this.placementProbe(sessionId).then((answer) => answer, (fault) => ({ state: "storage", fault }));
|
|
1436
|
+
if (probe.state === "corrupt" || probe.state === "storage")
|
|
1127
1437
|
return true;
|
|
1128
1438
|
try {
|
|
1129
1439
|
admitSessionPlacement(sessionId, probe.state === "placed" ? probe.record : undefined, opts, placementCapabilityOf(this));
|
|
@@ -1144,28 +1454,31 @@ export class LocalSessionStore {
|
|
|
1144
1454
|
}
|
|
1145
1455
|
}
|
|
1146
1456
|
async register(sessionId, owner) {
|
|
1147
|
-
await this.
|
|
1457
|
+
await this.registerWithReceipt(sessionId, owner);
|
|
1458
|
+
}
|
|
1459
|
+
async registerWithReceipt(sessionId, owner) {
|
|
1460
|
+
return this.withSessionWriteLock(sessionId, async () => {
|
|
1148
1461
|
await this.meta.ensureLoaded();
|
|
1149
|
-
if (
|
|
1150
|
-
return;
|
|
1462
|
+
if (this.cellStateOf(sessionId).state === "deleting")
|
|
1463
|
+
return {};
|
|
1464
|
+
if (this.liveCellOf(sessionId)?.owner !== undefined)
|
|
1465
|
+
return {};
|
|
1151
1466
|
assertFirstOwnerWritable(sessionId, owner);
|
|
1152
1467
|
const existed = await this.existsInRepo(sessionId);
|
|
1153
|
-
if (!existed && isForkCreatedCell(this.
|
|
1468
|
+
if (!existed && isForkCreatedCell(this.liveCellOf(sessionId)))
|
|
1154
1469
|
throw new SessionForkInProgressError(sessionId);
|
|
1155
|
-
const
|
|
1470
|
+
const born = mintSessionIncarnation();
|
|
1471
|
+
const first = existed && this.liveCellOf(sessionId) === undefined ? await this.firstCellIncarnationHeld(sessionId) : undefined;
|
|
1156
1472
|
const wrote = await this.writeMeta(sessionId, (before) => {
|
|
1157
1473
|
if (ownerRecorded(before))
|
|
1158
1474
|
return undefined;
|
|
1159
|
-
if (claim)
|
|
1160
|
-
return before === undefined ? { incarnation: mintSessionIncarnation(), owner } : cellWith(before, { owner });
|
|
1161
1475
|
if (!existed)
|
|
1162
|
-
return { incarnation:
|
|
1163
|
-
return before === undefined ? { incarnation:
|
|
1476
|
+
return { incarnation: born, owner, createdAt: rowBornNow(), clientWrites: 0 };
|
|
1477
|
+
return before === undefined ? { incarnation: first ?? cellChangedUnderLock(sessionId) } : undefined;
|
|
1164
1478
|
});
|
|
1165
1479
|
if (!existed && wrote)
|
|
1166
1480
|
await this.repo.create({ id: sessionId });
|
|
1167
|
-
|
|
1168
|
-
this.log.info("session_owner_claimed", { sessionId });
|
|
1481
|
+
return !existed && wrote ? { minted: { sessionId, owner, incarnation: born } } : {};
|
|
1169
1482
|
});
|
|
1170
1483
|
}
|
|
1171
1484
|
async touch(sessionId) {
|
|
@@ -1177,8 +1490,10 @@ export class LocalSessionStore {
|
|
|
1177
1490
|
await this.withSessionWriteLock(sessionId, async () => {
|
|
1178
1491
|
if ((await this.placedRecordOrThrow(sessionId)) === undefined)
|
|
1179
1492
|
return;
|
|
1493
|
+
const verdict = await judgeSessionWrite({ op: "delete", rootSessionId: sessionId }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => ({ role: "absent" }), deleting: async () => this.deletingTokenOf(sessionId) });
|
|
1494
|
+
if (verdict !== "proceed")
|
|
1495
|
+
throw verdict.refuse;
|
|
1180
1496
|
await this.dropRowHeld(sessionId);
|
|
1181
|
-
await this.repo.delete({ id: sessionId, createdAt: "" });
|
|
1182
1497
|
});
|
|
1183
1498
|
}
|
|
1184
1499
|
async listSessions(opts) {
|
|
@@ -1186,28 +1501,31 @@ export class LocalSessionStore {
|
|
|
1186
1501
|
}
|
|
1187
1502
|
async listSessionRows(opts) {
|
|
1188
1503
|
await this.loadRowSidecars();
|
|
1504
|
+
const hadLiveCell = new Set(this.liveCellIds());
|
|
1189
1505
|
const rows = [];
|
|
1190
1506
|
for (const header of await this.rowHeaders())
|
|
1191
1507
|
rows.push(await this.listedRowOf(header));
|
|
1192
|
-
const rollups = await this.runRollups();
|
|
1508
|
+
const rollups = await this.runRollups(opts.q);
|
|
1193
1509
|
const all = [];
|
|
1194
1510
|
for (const row of rows) {
|
|
1511
|
+
if (opts.q && !rollups.has(row.sessionId))
|
|
1512
|
+
continue;
|
|
1513
|
+
if (this.rowGoneSince(row.sessionId, hadLiveCell.has(row.sessionId)))
|
|
1514
|
+
continue;
|
|
1195
1515
|
const owner = this.ownerCellOf(row.sessionId);
|
|
1196
1516
|
if (opts.owner !== undefined && owner !== opts.owner && !(opts.includeUnowned === true && owner === null))
|
|
1197
1517
|
continue;
|
|
1198
|
-
const meta = this.
|
|
1518
|
+
const meta = this.liveCellOf(row.sessionId);
|
|
1199
1519
|
const incarnation = meta === undefined ? undefined : incarnationFromCell(row.sessionId, meta.incarnation, "absent");
|
|
1200
|
-
all.push({ row:
|
|
1520
|
+
all.push({ row: this.summaryOf(row, rollups), occupant: { owner, incarnation } });
|
|
1201
1521
|
}
|
|
1202
|
-
|
|
1203
|
-
const filtered = qlc ? all.filter(({ row: r }) => r.objectivePreview?.toLowerCase().includes(qlc)) : all;
|
|
1204
|
-
filtered.sort(({ row: a }, { row: b }) => a.lastActivityAt !== b.lastActivityAt
|
|
1522
|
+
all.sort(({ row: a }, { row: b }) => a.lastActivityAt !== b.lastActivityAt
|
|
1205
1523
|
? (a.lastActivityAt < b.lastActivityAt ? 1 : -1)
|
|
1206
1524
|
: (a.sessionId < b.sessionId ? 1 : -1));
|
|
1207
1525
|
const c = opts.cursor;
|
|
1208
1526
|
const after = c
|
|
1209
|
-
?
|
|
1210
|
-
:
|
|
1527
|
+
? all.filter(({ row: r }) => r.lastActivityAt < c.lastActivityAt || (r.lastActivityAt === c.lastActivityAt && r.sessionId < c.sessionId))
|
|
1528
|
+
: all;
|
|
1211
1529
|
return after.slice(0, opts.limit);
|
|
1212
1530
|
}
|
|
1213
1531
|
async loadRowSidecars() {
|
|
@@ -1217,58 +1535,43 @@ export class LocalSessionStore {
|
|
|
1217
1535
|
await this.meta.ensureLoaded();
|
|
1218
1536
|
}
|
|
1219
1537
|
ownerCellOf(sessionId) {
|
|
1220
|
-
|
|
1221
|
-
return cell === undefined ? null : (readSessionMetaCell(cell).owner ?? null);
|
|
1538
|
+
return this.liveCellOf(sessionId)?.owner ?? null;
|
|
1222
1539
|
}
|
|
1223
|
-
|
|
1540
|
+
summaryOf(listed, rollups) {
|
|
1224
1541
|
const { sessionId } = listed;
|
|
1225
|
-
const firstActivityAt = rowCreatedAtOf(sessionId, this.
|
|
1542
|
+
const firstActivityAt = rowCreatedAtOf(sessionId, this.liveCellOf(sessionId));
|
|
1226
1543
|
const owner = this.ownerCellOf(sessionId);
|
|
1227
1544
|
const cells = this.rowCellsOf(sessionId);
|
|
1228
1545
|
const rowTag = this.rowTagKeyOf(sessionId, owner, cells);
|
|
1229
1546
|
const lastActivityAt = this.lastActivity.get(sessionId) ?? firstActivityAt;
|
|
1230
|
-
const objectivePreview = "readable" in listed ? await this.previewOf(listed.readable) : null;
|
|
1231
1547
|
return {
|
|
1232
1548
|
sessionId,
|
|
1233
1549
|
owner,
|
|
1234
1550
|
lastActivityAt,
|
|
1235
1551
|
firstActivityAt,
|
|
1236
|
-
|
|
1237
|
-
...this.runProjectionOf(sessionId, rollups),
|
|
1552
|
+
...this.runRollupOf(sessionId, rollups),
|
|
1238
1553
|
title: cells.title,
|
|
1239
1554
|
pinned: cells.pinned,
|
|
1240
1555
|
...rowTag,
|
|
1241
1556
|
...("unreadable" in listed ? { unreadable: sessionUnreadableOf(listed.unreadable) } : {}),
|
|
1557
|
+
...this.listedLineageOf(sessionId),
|
|
1242
1558
|
};
|
|
1243
1559
|
}
|
|
1560
|
+
listedLineageOf(sessionId) {
|
|
1561
|
+
const lineage = forkLineageOfRow(sessionId, this.liveCellOf(sessionId)?.forkedFrom, undefined);
|
|
1562
|
+
return lineage.forkedFrom !== undefined ? { forkedFrom: lineage.forkedFrom } : {};
|
|
1563
|
+
}
|
|
1244
1564
|
rowTagKeyOf(sessionId, owner, cells) {
|
|
1245
|
-
const meta = this.
|
|
1565
|
+
const meta = this.liveCellOf(sessionId);
|
|
1246
1566
|
if (meta === undefined)
|
|
1247
1567
|
return {};
|
|
1248
1568
|
return sessionRowTagKey({ sessionId, owner, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent"), cells });
|
|
1249
1569
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
return new Map(rows.map((r) => [r.sessionId, { runCount: r.runCount, lastStatus: r.lastStatus, lastRunId: r.lastRunId, ...(r.lastTaskRunId !== undefined ? { lastTaskRunId: r.lastTaskRunId } : {}) }]));
|
|
1570
|
+
runRollups(q) {
|
|
1571
|
+
return this.runs.sessionRunRollups(q ? { q } : {});
|
|
1253
1572
|
}
|
|
1254
|
-
|
|
1255
|
-
return rollups.get(sessionId) ?? { runCount: 0, lastStatus: "", lastRunId: null };
|
|
1256
|
-
}
|
|
1257
|
-
async previewOf(meta) {
|
|
1258
|
-
try {
|
|
1259
|
-
const session = await this.repo.open({ id: meta.id, createdAt: meta.createdAt });
|
|
1260
|
-
const entries = await session.getEntries();
|
|
1261
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1262
|
-
const text = userText(entries[i]);
|
|
1263
|
-
const preview = text === null ? "" : buildObjectivePreview(text, SESSION_OBJECTIVE_PREVIEW_CHARS);
|
|
1264
|
-
if (preview !== "")
|
|
1265
|
-
return preview;
|
|
1266
|
-
}
|
|
1267
|
-
return null;
|
|
1268
|
-
}
|
|
1269
|
-
catch {
|
|
1270
|
-
return null;
|
|
1271
|
-
}
|
|
1573
|
+
runRollupOf(sessionId, rollups) {
|
|
1574
|
+
return rollups.get(sessionId) ?? { runCount: 0, lastStatus: "", lastRunId: null, objectivePreview: null };
|
|
1272
1575
|
}
|
|
1273
1576
|
async fork(sourceId, owner, opts) {
|
|
1274
1577
|
return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner, opts);
|
|
@@ -1281,6 +1584,9 @@ export class LocalSessionStore {
|
|
|
1281
1584
|
return this.withSessionWriteLock(first, () => this.withSessionWriteLock(second, () => this.forkToHeld(sourceId, owner, target)));
|
|
1282
1585
|
}
|
|
1283
1586
|
async forkToHeld(sourceId, owner, target) {
|
|
1587
|
+
await this.meta.ensureLoaded();
|
|
1588
|
+
if (this.cellStateOf(sourceId).state === "deleting")
|
|
1589
|
+
return null;
|
|
1284
1590
|
const sourceMeta = { id: sourceId, createdAt: "" };
|
|
1285
1591
|
let source;
|
|
1286
1592
|
try {
|
|
@@ -1293,15 +1599,25 @@ export class LocalSessionStore {
|
|
|
1293
1599
|
}
|
|
1294
1600
|
await this.assertArchiveAnonymous(sourceId);
|
|
1295
1601
|
const forkOptions = await settledForkOptions(source);
|
|
1296
|
-
const
|
|
1297
|
-
const
|
|
1602
|
+
const sourceCell = this.liveCellOf(sourceId);
|
|
1603
|
+
const forkOwner = forkOwnerOf(owner, sourceCell?.owner);
|
|
1604
|
+
const sourceToken = sourceCell === undefined ? undefined : incarnationFromCell(sourceId, sourceCell.incarnation, "absent");
|
|
1605
|
+
const lineage = forkRowCells({ ...sourceMeta, ...(sourceToken !== undefined ? { incarnation: sourceToken } : {}) });
|
|
1606
|
+
const existing = this.liveCellOf(target.id);
|
|
1298
1607
|
const targetArchived = await this.existsInRepo(target.id);
|
|
1299
1608
|
const placement = this.placements !== undefined ? target.placement : undefined;
|
|
1300
1609
|
const copy = () => this.repo.fork(sourceMeta, { ...forkOptions, id: target.id, ...(placement !== undefined ? { placement } : {}) });
|
|
1301
1610
|
if (existing === undefined && !targetArchived) {
|
|
1302
1611
|
const created = await this.writeMeta(target.id, (before) => before !== undefined
|
|
1303
1612
|
? undefined
|
|
1304
|
-
: {
|
|
1613
|
+
: {
|
|
1614
|
+
incarnation: mintSessionIncarnation(),
|
|
1615
|
+
...(forkOwner !== undefined ? { owner: forkOwner } : {}),
|
|
1616
|
+
...(target.key !== undefined ? { forkKey: target.key } : {}),
|
|
1617
|
+
createdAt: rowBornNow(),
|
|
1618
|
+
...(lineage.forkedFrom !== undefined ? { forkedFrom: lineage.forkedFrom } : {}),
|
|
1619
|
+
...(lineage.forkedFromIncarnation !== undefined ? { forkedFromIncarnation: lineage.forkedFromIncarnation } : {}),
|
|
1620
|
+
});
|
|
1305
1621
|
if (!created)
|
|
1306
1622
|
throw new SessionError("storage", `LocalSessionStore: fork target ${target.id} gained a session-meta cell under its own write lock — refusing to guess`);
|
|
1307
1623
|
let forked;
|
|
@@ -1317,8 +1633,12 @@ export class LocalSessionStore {
|
|
|
1317
1633
|
return { outcome: "created", id: target.id };
|
|
1318
1634
|
}
|
|
1319
1635
|
const out = classifyForkTarget(target, { owner: existing?.owner, forkKey: existing?.forkKey }, forkOwner);
|
|
1320
|
-
if (out.outcome === "replayed" && !targetArchived)
|
|
1636
|
+
if (out.outcome === "replayed" && isForkCreatedCell(existing) && !targetArchived) {
|
|
1637
|
+
const recorded = existing?.forkedFromIncarnation;
|
|
1638
|
+
if (recorded !== undefined && recorded !== lineage.forkedFromIncarnation)
|
|
1639
|
+
return null;
|
|
1321
1640
|
await copy();
|
|
1641
|
+
}
|
|
1322
1642
|
return out;
|
|
1323
1643
|
}
|
|
1324
1644
|
async admitForkedPlacement(sessionId, forked, placement) {
|
|
@@ -1339,6 +1659,9 @@ export class LocalSessionStore {
|
|
|
1339
1659
|
async exportEntries(sessionId) {
|
|
1340
1660
|
if (!this.repo.exportEntries)
|
|
1341
1661
|
return null;
|
|
1662
|
+
await this.meta.ensureLoaded();
|
|
1663
|
+
if (this.cellStateOf(sessionId).state !== "live" && this.cellStateOf(sessionId).state !== "none")
|
|
1664
|
+
return null;
|
|
1342
1665
|
try {
|
|
1343
1666
|
return await this.repo.exportEntries(sessionId);
|
|
1344
1667
|
}
|
|
@@ -1381,7 +1704,7 @@ export class LocalSessionStore {
|
|
|
1381
1704
|
if (landing === "replace")
|
|
1382
1705
|
await this.assertNotPlacedHeld(sessionId);
|
|
1383
1706
|
validateEntriesForImport(entries);
|
|
1384
|
-
const current = this.
|
|
1707
|
+
const current = this.liveCellOf(sessionId);
|
|
1385
1708
|
if (landing !== "judged")
|
|
1386
1709
|
await this.admitImportHeld(sessionId, current);
|
|
1387
1710
|
if (!(await this.importMayLand(sessionId, current, owner, landing)))
|
|
@@ -1391,8 +1714,8 @@ export class LocalSessionStore {
|
|
|
1391
1714
|
case "create":
|
|
1392
1715
|
case "judged":
|
|
1393
1716
|
try {
|
|
1394
|
-
const
|
|
1395
|
-
await this.repo.importEntries(sessionId, undefined, entries, { expect: { row:
|
|
1717
|
+
const expectRow = landing === "create" ? null : ((await this.archiveHeadOf(sessionId)) ?? null);
|
|
1718
|
+
await this.repo.importEntries(sessionId, undefined, entries, { expect: { row: expectRow } });
|
|
1396
1719
|
}
|
|
1397
1720
|
catch (err) {
|
|
1398
1721
|
await this.withdrawImportCell(sessionId, prior, err);
|
|
@@ -1423,9 +1746,10 @@ export class LocalSessionStore {
|
|
|
1423
1746
|
}
|
|
1424
1747
|
}
|
|
1425
1748
|
async admitImportHeld(sessionId, current) {
|
|
1426
|
-
const verdict = await
|
|
1749
|
+
const verdict = await judgeSessionWrite({ op: "import", rootSessionId: sessionId }, {
|
|
1427
1750
|
claim: () => this.runs.getActiveTaskId(sessionId),
|
|
1428
1751
|
cell: async () => placementCellOf(current, await this.forkingOf(sessionId, current)),
|
|
1752
|
+
deleting: async () => this.deletingTokenOf(sessionId),
|
|
1429
1753
|
});
|
|
1430
1754
|
if (verdict !== "proceed")
|
|
1431
1755
|
throw verdict.refuse;
|
|
@@ -1451,7 +1775,7 @@ export class LocalSessionStore {
|
|
|
1451
1775
|
const cells = carriedRowCells(sessionId, before === undefined ? undefined : readSessionMetaCell(before));
|
|
1452
1776
|
const base = before === undefined ? { createdAt: rowBornNow() } : cellObjectOf(before);
|
|
1453
1777
|
const { forkKey: _carriedByTheRule, ...kept } = base;
|
|
1454
|
-
return { ...kept, incarnation: cells.incarnation, owner, execution: MIRROR_CELL, ...(cells.forkKey !== null ? { forkKey: cells.forkKey } : {}) };
|
|
1778
|
+
return { ...kept, incarnation: cells.incarnation, owner, execution: MIRROR_CELL, ...(cells.forkKey !== null ? { forkKey: cells.forkKey } : {}), clientWrites: (kept.clientWrites ?? 0) + 1 };
|
|
1455
1779
|
});
|
|
1456
1780
|
return prior;
|
|
1457
1781
|
}
|
|
@@ -1483,7 +1807,7 @@ export class LocalSessionStore {
|
|
|
1483
1807
|
}
|
|
1484
1808
|
async commitStagedImport(sessionId, owner, staged, opts) {
|
|
1485
1809
|
return this.withImportLock(sessionId, owner, async (importer) => {
|
|
1486
|
-
const current = await this.
|
|
1810
|
+
const current = await this.metaHeld(sessionId);
|
|
1487
1811
|
if (!ownerCellIs(opts.expectedOwner, current === undefined ? undefined : (current.owner ?? null)))
|
|
1488
1812
|
throw new StagedCommitOwnerChangedError(sessionId);
|
|
1489
1813
|
await this.assertNotPlacedHeld(sessionId);
|
|
@@ -1509,46 +1833,336 @@ export class LocalSessionStore {
|
|
|
1509
1833
|
await this.landImportCell(sessionId, importer);
|
|
1510
1834
|
await this.touch(sessionId);
|
|
1511
1835
|
}
|
|
1512
|
-
const meta = await this.
|
|
1836
|
+
const meta = await this.metaHeld(sessionId);
|
|
1513
1837
|
if (meta === undefined)
|
|
1514
1838
|
throw new SessionError("storage", `LocalSessionStore: staged commit for session ${sessionId} left no session face — nothing was settled`);
|
|
1515
1839
|
const face = sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation);
|
|
1516
1840
|
return { relation: rel.relation, face };
|
|
1517
1841
|
});
|
|
1518
1842
|
}
|
|
1519
|
-
async deleteSession(sessionId, owner) {
|
|
1520
|
-
return this.withSessionWriteLock(sessionId, () => this.deleteSessionHeld(sessionId, owner));
|
|
1843
|
+
async deleteSession(sessionId, owner, token, legs) {
|
|
1844
|
+
return this.withSessionWriteLock(sessionId, (dropped) => this.deleteSessionHeld(sessionId, owner, token, legs, dropped));
|
|
1521
1845
|
}
|
|
1522
|
-
async deleteSessionHeld(sessionId, owner) {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1846
|
+
async deleteSessionHeld(sessionId, owner, token, legs, dropped) {
|
|
1847
|
+
if (dropped !== undefined)
|
|
1848
|
+
return undefined;
|
|
1849
|
+
await this.meta.ensureLoaded();
|
|
1850
|
+
const cell = this.cellStateOf(sessionId);
|
|
1851
|
+
if (cell.state === "none" || !ownerEq(cell.meta.owner ?? null, owner))
|
|
1852
|
+
return undefined;
|
|
1853
|
+
const verdict = await judgeSessionWrite({ op: "erase", rootSessionId: sessionId, token }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => ({ role: "absent" }), deleting: async () => this.deletingTokenOf(sessionId) });
|
|
1854
|
+
if (verdict !== "proceed")
|
|
1855
|
+
throw verdict.refuse;
|
|
1856
|
+
const occupant = { owner: cell.meta.owner ?? null, incarnation: cell.meta.incarnation };
|
|
1857
|
+
await legs(occupant);
|
|
1858
|
+
await this.dropRowHeld(sessionId);
|
|
1859
|
+
return occupant;
|
|
1860
|
+
}
|
|
1861
|
+
deletingTokenOf(sessionId) {
|
|
1862
|
+
const cell = this.cellStateOf(sessionId);
|
|
1863
|
+
return cell.state === "none" ? null : (cell.meta.deleting ?? null);
|
|
1864
|
+
}
|
|
1865
|
+
sessionDeletion = {
|
|
1866
|
+
fence: (sessionId, owner, opts) => this.withSessionWriteLock(sessionId, (dropped) => this.fenceDeletionHeld(sessionId, owner, opts, dropped)),
|
|
1867
|
+
holds: async (sessionId, token) => {
|
|
1868
|
+
await this.meta.ensureLoaded();
|
|
1869
|
+
return this.deletingTokenOf(sessionId) === token;
|
|
1870
|
+
},
|
|
1871
|
+
pending: async () => {
|
|
1872
|
+
await this.meta.ensureLoaded();
|
|
1873
|
+
return this.deletingIds().map((sessionId) => ({ sessionId, owner: this.deletionOwnerOf(sessionId), token: this.deletingTokenOf(sessionId) }));
|
|
1874
|
+
},
|
|
1875
|
+
};
|
|
1876
|
+
deletionOwnerOf(sessionId) {
|
|
1877
|
+
const cell = this.cellStateOf(sessionId);
|
|
1878
|
+
return cell.state === "none" ? null : (cell.meta.owner ?? null);
|
|
1879
|
+
}
|
|
1880
|
+
async fenceDeletionHeld(sessionId, owner, opts, dropped) {
|
|
1881
|
+
if (dropped !== undefined)
|
|
1882
|
+
return undefined;
|
|
1883
|
+
await this.meta.ensureLoaded();
|
|
1884
|
+
const state = this.cellStateOf(sessionId);
|
|
1885
|
+
let current;
|
|
1886
|
+
if (state.state === "deleting")
|
|
1887
|
+
current = state.meta;
|
|
1888
|
+
else {
|
|
1889
|
+
if (opts.expect !== undefined)
|
|
1890
|
+
return undefined;
|
|
1891
|
+
const seen = await this.observeRow(sessionId);
|
|
1892
|
+
if (seen.row === "absent")
|
|
1893
|
+
return undefined;
|
|
1894
|
+
current = seen.row === "live" ? seen.meta : undefined;
|
|
1895
|
+
}
|
|
1896
|
+
if (!ownerEq(current?.owner ?? null, owner))
|
|
1897
|
+
return undefined;
|
|
1898
|
+
if (state.state === "deleting") {
|
|
1899
|
+
const held = state.meta.deleting;
|
|
1900
|
+
if (opts.expect !== undefined && held !== opts.expect)
|
|
1901
|
+
return undefined;
|
|
1902
|
+
if (opts.holder(held) === "running")
|
|
1903
|
+
return { inProgress: true };
|
|
1904
|
+
}
|
|
1905
|
+
else {
|
|
1906
|
+
const verdict = await judgeSessionWrite({ op: "delete", rootSessionId: sessionId }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => ({ role: "absent" }), deleting: async () => this.deletingTokenOf(sessionId) });
|
|
1907
|
+
if (verdict !== "proceed")
|
|
1908
|
+
throw verdict.refuse;
|
|
1909
|
+
}
|
|
1910
|
+
const token = uuidv7();
|
|
1911
|
+
let incarnation;
|
|
1912
|
+
const committed = await this.meta.commit((draft) => {
|
|
1913
|
+
const before = draft.get(sessionId);
|
|
1914
|
+
const base = before === undefined ? { incarnation: mintSessionIncarnation() } : cellObjectOf(before);
|
|
1915
|
+
incarnation = before === undefined ? undefined : base.incarnation;
|
|
1916
|
+
draft.set(sessionId, { ...base, deleting: token });
|
|
1917
|
+
return true;
|
|
1918
|
+
}, "required");
|
|
1919
|
+
if (!committed)
|
|
1920
|
+
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`);
|
|
1921
|
+
return { token, incarnation };
|
|
1922
|
+
}
|
|
1923
|
+
async bumpClientWritesHeld(sessionId) {
|
|
1924
|
+
await this.meta.ensureLoaded();
|
|
1925
|
+
if (this.cellStateOf(sessionId).state === "deleting")
|
|
1926
|
+
throw new SessionDeletingError(sessionId);
|
|
1927
|
+
if (this.liveCellOf(sessionId) === undefined)
|
|
1928
|
+
return;
|
|
1929
|
+
const committed = await this.meta.commit((draft) => {
|
|
1930
|
+
const before = draft.get(sessionId);
|
|
1931
|
+
if (before === undefined)
|
|
1932
|
+
return false;
|
|
1933
|
+
const cell = readSessionMetaCell(before);
|
|
1934
|
+
if (cell.dropping)
|
|
1935
|
+
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`);
|
|
1936
|
+
draft.set(sessionId, { ...cellObjectOf(before), clientWrites: (cell.clientWrites ?? 0) + 1 });
|
|
1937
|
+
return true;
|
|
1938
|
+
}, "required");
|
|
1939
|
+
if (!committed)
|
|
1940
|
+
throw new SessionError("storage", `LocalSessionStore: the client-write count of session ${sessionId} could not be made durable — the write was not made; retry it`);
|
|
1941
|
+
}
|
|
1942
|
+
async withClientWrite(sessionId, write) {
|
|
1943
|
+
return this.withSessionWriteLock(sessionId, async () => {
|
|
1944
|
+
let open = true;
|
|
1945
|
+
try {
|
|
1946
|
+
return await write(async () => {
|
|
1947
|
+
if (!open)
|
|
1948
|
+
throw new Error(`LocalSessionStore.withClientWrite: session ${sessionId}'s client-write count was taken after its lock section ended`);
|
|
1949
|
+
await this.bumpClientWritesHeld(sessionId);
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
finally {
|
|
1953
|
+
open = false;
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
async withdrawBirth(birth, ownTaskId) {
|
|
1958
|
+
const runs = this.runs;
|
|
1959
|
+
return this.withSessionWriteLock(birth.sessionId, async (dropped) => {
|
|
1960
|
+
const sessionId = birth.sessionId;
|
|
1961
|
+
const kept = (word) => ({ withdrawn: false, kept: word });
|
|
1962
|
+
if (dropped !== undefined)
|
|
1963
|
+
return kept("absent");
|
|
1964
|
+
await this.meta.ensureLoaded();
|
|
1965
|
+
if (this.cellStateOf(sessionId).state === "deleting")
|
|
1966
|
+
return kept("claim");
|
|
1967
|
+
const seen = await this.observeRow(sessionId);
|
|
1968
|
+
if (seen.row === "absent")
|
|
1969
|
+
return kept("absent");
|
|
1970
|
+
if (seen.row !== "live")
|
|
1971
|
+
return kept("incarnation");
|
|
1972
|
+
const cell = seen.meta;
|
|
1973
|
+
if (cell.incarnation !== birth.incarnation || cell.owner === undefined || cell.owner !== birth.owner)
|
|
1974
|
+
return kept("incarnation");
|
|
1975
|
+
if (cell.clientWrites === undefined)
|
|
1976
|
+
return kept("upgrade_pending");
|
|
1977
|
+
if (await this.archiveHasContentHeld(sessionId))
|
|
1978
|
+
return kept("content");
|
|
1979
|
+
const verdict = await judgeSessionWrite({ op: "delete", rootSessionId: sessionId }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => placementCellOf(cell, false), deleting: async () => this.deletingTokenOf(sessionId) });
|
|
1980
|
+
if (verdict !== "proceed")
|
|
1981
|
+
return kept("claim");
|
|
1982
|
+
if (cell.clientWrites > 0)
|
|
1983
|
+
return kept("client_writes");
|
|
1984
|
+
if (typeof runs.withdrawRunsOf !== "function") {
|
|
1985
|
+
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`);
|
|
1986
|
+
}
|
|
1987
|
+
const runsVerdict = runs.withdrawRunsOf.call(this.runs, sessionId, ownTaskId);
|
|
1988
|
+
if (runsVerdict !== "proceed")
|
|
1989
|
+
return kept(runsVerdict.keep);
|
|
1990
|
+
this.withdrawCheckpointCtx?.(sessionId);
|
|
1991
|
+
await this.dropRowHeld(sessionId);
|
|
1992
|
+
return { withdrawn: true };
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
async archiveHasContentHeld(sessionId) {
|
|
1996
|
+
if (!this.repo.exportEntries)
|
|
1997
|
+
return true;
|
|
1998
|
+
let entries;
|
|
1526
1999
|
try {
|
|
1527
|
-
await this.repo.
|
|
2000
|
+
entries = await this.repo.exportEntries(sessionId);
|
|
1528
2001
|
}
|
|
1529
2002
|
catch (e) {
|
|
1530
|
-
if (
|
|
1531
|
-
|
|
2003
|
+
if (isNotFound(e))
|
|
2004
|
+
return false;
|
|
2005
|
+
throw e;
|
|
1532
2006
|
}
|
|
1533
|
-
|
|
1534
|
-
return true;
|
|
2007
|
+
return entries.some((e) => !entryTypeWithdrawable(e.type));
|
|
1535
2008
|
}
|
|
1536
2009
|
async dropRowHeld(sessionId) {
|
|
1537
|
-
this.
|
|
2010
|
+
await this.meta.ensureLoaded();
|
|
2011
|
+
let marked;
|
|
2012
|
+
const s0 = await this.meta.commit((draft) => {
|
|
2013
|
+
const before = draft.get(sessionId);
|
|
2014
|
+
if (before !== undefined && readSessionMetaCell(before).dropping) {
|
|
2015
|
+
marked = cellObjectOf(before);
|
|
2016
|
+
return false;
|
|
2017
|
+
}
|
|
2018
|
+
marked = { ...(before === undefined ? { incarnation: mintSessionIncarnation() } : cellObjectOf(before)), dropping: true };
|
|
2019
|
+
draft.set(sessionId, marked);
|
|
2020
|
+
return true;
|
|
2021
|
+
}, "required");
|
|
2022
|
+
if (!s0)
|
|
2023
|
+
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`);
|
|
1538
2024
|
for (const [map, which] of [[this.titles, "title"], [this.pinned, "pinned"], [this.instructionSources, "instruction-source"], [this.workspaces, "workspace"]]) {
|
|
1539
2025
|
await map.ensureLoaded();
|
|
1540
|
-
if (!(await map.deleteKey(sessionId)))
|
|
1541
|
-
throw new SessionError("storage", `LocalSessionStore: ${which} sidecar write failed while deleting session ${sessionId} —
|
|
2026
|
+
if (!(await map.deleteKey(sessionId, "required")))
|
|
2027
|
+
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`);
|
|
1542
2028
|
}
|
|
1543
2029
|
await this.ensureLoaded();
|
|
1544
2030
|
this.lastActivity.delete(sessionId);
|
|
1545
2031
|
this.scheduleFlush();
|
|
1546
|
-
|
|
2032
|
+
try {
|
|
2033
|
+
await this.repo.delete({ id: sessionId, createdAt: "" });
|
|
2034
|
+
}
|
|
2035
|
+
catch (e) {
|
|
2036
|
+
if (!isNotFound(e))
|
|
2037
|
+
throw e;
|
|
2038
|
+
}
|
|
2039
|
+
this.syncArchiveDirectory();
|
|
2040
|
+
if (!(await this.meta.deleteKey(sessionId, "required")))
|
|
2041
|
+
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`);
|
|
2042
|
+
return readSessionMetaCell(marked);
|
|
1547
2043
|
}
|
|
1548
2044
|
syncArchiveDirectory() {
|
|
1549
|
-
if (this.dataRoot === undefined)
|
|
2045
|
+
if (this.dataRoot === undefined || !(this.repo instanceof FileSessionRepo) || IS_WIN32)
|
|
1550
2046
|
return;
|
|
1551
|
-
fsyncPaths(join(this.dataRoot,
|
|
2047
|
+
fsyncPaths(join(this.dataRoot, ARCHIVE_DIR));
|
|
2048
|
+
}
|
|
2049
|
+
async finishPendingDropHeld(sessionId) {
|
|
2050
|
+
await this.meta.ensureLoaded();
|
|
2051
|
+
if (this.cellStateOf(sessionId).state !== "dropping")
|
|
2052
|
+
return undefined;
|
|
2053
|
+
return this.dropRowHeld(sessionId);
|
|
2054
|
+
}
|
|
2055
|
+
async finishPendingDrops() {
|
|
2056
|
+
await this.meta.ensureLoaded();
|
|
2057
|
+
for (const sessionId of this.droppingIds()) {
|
|
2058
|
+
try {
|
|
2059
|
+
await this.withSessionWriteLock(sessionId, async () => undefined);
|
|
2060
|
+
}
|
|
2061
|
+
catch (e) {
|
|
2062
|
+
this.log.warn("session_drop_unfinished", {
|
|
2063
|
+
sessionId,
|
|
2064
|
+
error: e instanceof Error ? e.message : String(e),
|
|
2065
|
+
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",
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
async clearShellSentinelOwnersM48() {
|
|
2071
|
+
await this.meta.ensureLoaded();
|
|
2072
|
+
let cleared = 0;
|
|
2073
|
+
const committed = await this.meta.commit((draft) => {
|
|
2074
|
+
const hits = shellSentinelCellIdsM48(draft);
|
|
2075
|
+
for (const sessionId of hits)
|
|
2076
|
+
draft.set(sessionId, { ...cellObjectOf(draft.get(sessionId)), owner: null });
|
|
2077
|
+
cleared = hits.length;
|
|
2078
|
+
return cleared > 0;
|
|
2079
|
+
}, "best-effort");
|
|
2080
|
+
if (!committed)
|
|
2081
|
+
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");
|
|
2082
|
+
return cleared;
|
|
2083
|
+
}
|
|
2084
|
+
async countShellSentinelOwnersM48() {
|
|
2085
|
+
await this.meta.ensureLoaded();
|
|
2086
|
+
let count = 0;
|
|
2087
|
+
await this.meta.commit((draft) => {
|
|
2088
|
+
count = shellSentinelCellIdsM48(draft).length;
|
|
2089
|
+
return false;
|
|
2090
|
+
}, "best-effort");
|
|
2091
|
+
return count;
|
|
2092
|
+
}
|
|
2093
|
+
async adoptArchiveTokensM47() {
|
|
2094
|
+
const root = this.dataRoot;
|
|
2095
|
+
if (root === undefined)
|
|
2096
|
+
return { adopted: 0, minted: 0 };
|
|
2097
|
+
const owed = archiveTokenAdoptionPendingPresent(root);
|
|
2098
|
+
const rows = await this.rowHeaders();
|
|
2099
|
+
let adopted = 0;
|
|
2100
|
+
let minted = 0;
|
|
2101
|
+
const committed = await this.meta.commit((draft) => {
|
|
2102
|
+
adopted = 0;
|
|
2103
|
+
minted = 0;
|
|
2104
|
+
for (const row of rows) {
|
|
2105
|
+
if (!("header" in row) || draft.get(row.sessionId) !== undefined)
|
|
2106
|
+
continue;
|
|
2107
|
+
const first = firstCellIncarnationOf(row.sessionId, row.header.incarnation);
|
|
2108
|
+
const facts = archivedRowFactsOf(row.header);
|
|
2109
|
+
draft.set(row.sessionId, { incarnation: first.incarnation, createdAt: facts.createdAt, ...(facts.forkedFrom !== undefined ? { forkedFrom: facts.forkedFrom } : {}) });
|
|
2110
|
+
if (first.adopted)
|
|
2111
|
+
adopted += 1;
|
|
2112
|
+
else
|
|
2113
|
+
minted += 1;
|
|
2114
|
+
}
|
|
2115
|
+
return adopted + minted > 0;
|
|
2116
|
+
}, owed ? "required" : "best-effort");
|
|
2117
|
+
if (!committed)
|
|
2118
|
+
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");
|
|
2119
|
+
if (owed) {
|
|
2120
|
+
await this.assertArchiveTokensAdoptedM47(root);
|
|
2121
|
+
dischargeArchiveTokenAdoptionPending(root);
|
|
2122
|
+
}
|
|
2123
|
+
return { adopted, minted };
|
|
2124
|
+
}
|
|
2125
|
+
async assertArchiveTokensAdoptedM47(root) {
|
|
2126
|
+
const reading = readSessionMetaCellsSync(root);
|
|
2127
|
+
if (reading.state === "unreadable") {
|
|
2128
|
+
throw new SessionError("storage", `LocalSessionStore: the one-time migration M47 cannot re-read ${reading.finding.label} to prove the adoption finished (${reading.finding.problem}) — refusing to start. ` +
|
|
2129
|
+
`${ARCHIVE_TOKEN_ADOPTION_PENDING_FILE} stays in the data root; the next start of a release carrying this migration tries again.`);
|
|
2130
|
+
}
|
|
2131
|
+
let left = 0;
|
|
2132
|
+
for (const sessionId of cellLessArchiveIds(archiveIdsInSync(root), reading.state === "read" ? reading.entries : {}))
|
|
2133
|
+
if ((await this.adoptionScopeOf(sessionId)) === "in")
|
|
2134
|
+
left += 1;
|
|
2135
|
+
if (left > 0) {
|
|
2136
|
+
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. ` +
|
|
2137
|
+
`${ARCHIVE_TOKEN_ADOPTION_PENDING_FILE} stays in the data root; the next start of a release carrying this migration tries again.`);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
async adoptionScopeOf(sessionId) {
|
|
2141
|
+
let placed;
|
|
2142
|
+
try {
|
|
2143
|
+
placed = await this.recordedPlacementOf(sessionId);
|
|
2144
|
+
}
|
|
2145
|
+
catch (fault) {
|
|
2146
|
+
return this.skippedFromAdoption(sessionId, fault);
|
|
2147
|
+
}
|
|
2148
|
+
if (placed !== undefined)
|
|
2149
|
+
return "out";
|
|
2150
|
+
try {
|
|
2151
|
+
return (await this.archiveHeadOf(sessionId)) === undefined ? "out" : "in";
|
|
2152
|
+
}
|
|
2153
|
+
catch (fault) {
|
|
2154
|
+
if (fault instanceof SessionError && archiveFaultWord(fault.code) === "corrupt_session")
|
|
2155
|
+
return this.skippedFromAdoption(sessionId, fault);
|
|
2156
|
+
return "in";
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
skippedFromAdoption(sessionId, fault) {
|
|
2160
|
+
this.log.warn("archive_token_adoption_skipped", {
|
|
2161
|
+
sessionId,
|
|
2162
|
+
error: fault instanceof Error ? fault.message : String(fault),
|
|
2163
|
+
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",
|
|
2164
|
+
});
|
|
2165
|
+
return "out";
|
|
1552
2166
|
}
|
|
1553
2167
|
archiveSnapshotDir() {
|
|
1554
2168
|
return this.dataRoot === undefined ? undefined : join(this.dataRoot, LOCAL_ARCHIVE_SNAPSHOT_DIR);
|
|
@@ -1617,10 +2231,10 @@ export class LocalSessionStore {
|
|
|
1617
2231
|
catch (e) {
|
|
1618
2232
|
throw refuse("restoring the archive from the snapshot failed", e);
|
|
1619
2233
|
}
|
|
1620
|
-
await this.fillRowFacts(id, record.rowFacts);
|
|
2234
|
+
await this.fillRowFacts(id, record.rowFacts, record.archiveIncarnation);
|
|
1621
2235
|
}
|
|
1622
2236
|
else if (head.owner === null) {
|
|
1623
|
-
await this.fillRowFacts(id, record.rowFacts);
|
|
2237
|
+
await this.fillRowFacts(id, record.rowFacts, record.archiveIncarnation);
|
|
1624
2238
|
}
|
|
1625
2239
|
else if (head.incarnation !== (record.archiveIncarnation ?? undefined)) {
|
|
1626
2240
|
throw refuse("the session's archive is a different generation from the one the snapshot was taken of");
|
|
@@ -1629,8 +2243,8 @@ export class LocalSessionStore {
|
|
|
1629
2243
|
fsyncPaths(dir);
|
|
1630
2244
|
});
|
|
1631
2245
|
}
|
|
1632
|
-
async fillRowFacts(sessionId, facts) {
|
|
1633
|
-
await this.writeMeta(sessionId, (before) => rowFactsFilled(before, facts));
|
|
2246
|
+
async fillRowFacts(sessionId, facts, archiveToken) {
|
|
2247
|
+
await this.writeMeta(sessionId, (before) => rowFactsFilled(sessionId, before, facts, archiveToken));
|
|
1634
2248
|
}
|
|
1635
2249
|
async migrateLocalArchives() {
|
|
1636
2250
|
const dir = this.archiveSnapshotDir();
|
|
@@ -1655,14 +2269,14 @@ export class LocalSessionStore {
|
|
|
1655
2269
|
const committed = await this.meta.commit((draft) => {
|
|
1656
2270
|
rowFactsBackfilled = 0;
|
|
1657
2271
|
for (const row of listed) {
|
|
1658
|
-
const next = rowFactsFilled(draft.get(row.id), archivedRowFactsOf(row));
|
|
2272
|
+
const next = rowFactsFilled(row.id, draft.get(row.id), archivedRowFactsOf(row), row.incarnation);
|
|
1659
2273
|
if (next === undefined)
|
|
1660
2274
|
continue;
|
|
1661
2275
|
draft.set(row.id, next);
|
|
1662
2276
|
rowFactsBackfilled += 1;
|
|
1663
2277
|
}
|
|
1664
2278
|
return rowFactsBackfilled > 0;
|
|
1665
|
-
});
|
|
2279
|
+
}, "best-effort");
|
|
1666
2280
|
if (!committed)
|
|
1667
2281
|
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");
|
|
1668
2282
|
this.meta.syncToDisk();
|
|
@@ -1720,7 +2334,7 @@ export class LocalSessionStore {
|
|
|
1720
2334
|
}
|
|
1721
2335
|
if (!sameLog(validated, row.entries))
|
|
1722
2336
|
return { outcome: "kept", reason: "normalizes" };
|
|
1723
|
-
const cell = this.
|
|
2337
|
+
const cell = this.liveCellOf(sessionId);
|
|
1724
2338
|
if (cell?.createdAt === undefined)
|
|
1725
2339
|
throw new Error(`LocalSessionStore: the one-time migration M33 reached session ${sessionId}'s archive before its row facts were backfilled — refusing to rewrite it`);
|
|
1726
2340
|
const record = {
|