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