@sema-agent/server 7.146.0 → 7.147.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 +384 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +170 -66
- package/deploy/facts/lanes.json +7 -0
- package/deploy/sema-up/README.md +2 -2
- package/deploy/sema-up/chart/templates/_helpers.tpl +22 -0
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -4
- package/deploy/sema-up/chart/templates/server.yaml +5 -4
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/deploy/sema-up/cluster-up.sh +2 -1
- package/deploy/sema-up/lib/flagshape.sh +4 -0
- package/deploy/sema-up/lib/lanes.json +2 -5
- package/deploy/sema-up/sema-up.sh +12 -9
- package/deploy/sema-up/stack/compose.yaml +5 -4
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -3
- package/dist/approval-card.d.ts +4 -4
- package/dist/approval.d.ts +3 -3
- package/dist/audit.js +2 -2
- package/dist/bench/s1/live-deps.js +3 -1
- package/dist/boot/crash-last.js +2 -1
- package/dist/boot/device-lane.js +5 -5
- package/dist/boot/engine-lease.d.ts +12 -11
- package/dist/boot/execution-env.d.ts +2 -1
- package/dist/boot/execution-env.js +45 -33
- package/dist/boot/execution-lane.d.ts +37 -9
- package/dist/boot/execution-lane.js +74 -20
- package/dist/boot/exit-boundary.d.ts +1 -1
- package/dist/boot/exit-last.js +2 -1
- package/dist/boot/governance-seams.d.ts +2 -2
- package/dist/boot/leader.js +1 -1
- package/dist/boot/memory-boundary.d.ts +11 -11
- package/dist/boot/memory-boundary.js +3 -3
- package/dist/boot/memory-consolidation.d.ts +13 -24
- package/dist/boot/memory-consolidation.js +0 -13
- package/dist/boot/org-memory.js +3 -2
- package/dist/boot/parked-revive-gate.d.ts +1 -1
- package/dist/boot/parked-revive-gate.js +1 -1
- package/dist/boot/reapers.js +1 -1
- package/dist/boot/resolve-spec.js +91 -111
- package/dist/boot/retention-lane.d.ts +4 -42
- package/dist/boot/session-faces.d.ts +1 -0
- package/dist/boot/session-faces.js +8 -2
- package/dist/boot/stage-01-config.d.ts +1 -1
- package/dist/boot/stage-01-config.js +6 -2
- package/dist/boot/stage-02-web-admin-pull.d.ts +1 -1
- package/dist/boot/stage-02-web-admin-pull.js +10 -8
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.js +3 -3
- package/dist/boot/stage-06-runners.d.ts +3 -3
- package/dist/boot/stage-06-runners.js +1 -3
- package/dist/boot/stage-07-capability-layer.d.ts +3 -3
- package/dist/boot/stage-07-capability-layer.js +4 -9
- package/dist/boot/stage-08-reapers.d.ts +3 -3
- package/dist/boot/stage-09-leader.d.ts +4 -3
- package/dist/boot/stage-10-http-server.d.ts +4 -3
- package/dist/boot/stage-10-http-server.js +2 -1
- package/dist/boot/stores.d.ts +9 -0
- package/dist/boot/stores.js +18 -7
- package/dist/boot/web-admin.js +3 -3
- package/dist/capabilities/center-plugins.d.ts +2 -0
- package/dist/capabilities/center-plugins.js +6 -2
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capabilities/execution-lane.d.ts +7 -7
- package/dist/capabilities/execution-lane.js +2 -2
- package/dist/capabilities/hands-lane.d.ts +1 -1
- package/dist/capabilities/scenarios.d.ts +32 -18
- package/dist/capabilities/scenarios.js +13 -9
- package/dist/capabilities/settings-contract.d.ts +29 -0
- package/dist/capabilities/settings-contract.js +14 -0
- package/dist/capabilities/skills.d.ts +5 -5
- package/dist/capability-facts.d.ts +38 -0
- package/dist/capability-facts.js +34 -1
- package/dist/center-credential-renewer.js +3 -2
- package/dist/config-catalog.js +6 -5
- package/dist/config-invariants.d.ts +18 -3
- package/dist/config-invariants.js +36 -19
- package/dist/config-lkg.d.ts +1 -1
- package/dist/config-lkg.js +5 -6
- package/dist/config-provider.js +2 -0
- package/dist/config-types.d.ts +190 -186
- package/dist/config.d.ts +68 -5
- package/dist/config.js +80 -54
- package/dist/degenerate-instrument.js +1 -1
- package/dist/deploy-facts.js +3 -2
- package/dist/deployment-shape.d.ts +21 -12
- package/dist/deployment-shape.js +24 -6
- package/dist/env-facts.d.ts +7 -5
- package/dist/env-facts.js +10 -6
- package/dist/execution-config-root.d.ts +5 -5
- package/dist/execution-config-root.js +5 -5
- package/dist/execution-env-purpose.d.ts +14 -0
- package/dist/execution-env-purpose.js +29 -0
- package/dist/execution-lane-caps.d.ts +49 -86
- package/dist/execution-lane-caps.js +52 -62
- package/dist/execution-placement.d.ts +18 -2
- package/dist/execution-placement.js +19 -0
- package/dist/file-history-disclosure.d.ts +2 -2
- package/dist/fleet/drain-entry.d.ts +17 -10
- package/dist/fleet/drain-entry.js +1 -1
- package/dist/fleet/serving-lease.js +3 -4
- package/dist/history-anchor.d.ts +22 -0
- package/dist/history-anchor.js +14 -0
- package/dist/hooks/command-spawn.d.ts +1 -4
- package/dist/hooks/command-spawn.js +0 -11
- package/dist/hooks/hook-runner.d.ts +52 -8
- package/dist/hooks/hook-runner.js +160 -36
- package/dist/http/approval-sweeps.d.ts +2 -5
- package/dist/http/approval-sweeps.js +3 -2
- package/dist/http/checkpoint-ctx.d.ts +17 -0
- package/dist/http/checkpoint-ctx.js +6 -0
- package/dist/http/claim-latency.d.ts +10 -0
- package/dist/http/claim-latency.js +12 -0
- package/dist/http/dispatch.js +1 -1
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/execution-context.d.ts +3 -1
- package/dist/http/execution-context.js +4 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
- package/dist/http/memory-compliance-failure-reply.js +3 -0
- package/dist/http/memory-store-refusal-reply.d.ts +3 -3
- package/dist/http/memory-store-refusal-reply.js +3 -0
- package/dist/http/owner-access.d.ts +11 -0
- package/dist/http/owner-access.js +7 -4
- package/dist/http/principal-gate.d.ts +3 -14
- package/dist/http/principal-gate.js +0 -13
- package/dist/http/request-guards.d.ts +1 -1
- package/dist/http/resume-legs.d.ts +7 -10
- package/dist/http/resume-legs.js +79 -50
- package/dist/http/rewind-files-reply.d.ts +34 -0
- package/dist/http/rewind-files-reply.js +36 -0
- package/dist/http/route-ctx.d.ts +32 -16
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/approvals-assistant.js +5 -25
- package/dist/http/routes/capabilities.js +8 -2
- package/dist/http/routes/devices.js +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/notify-wake.js +2 -1
- package/dist/http/routes/projects.d.ts +70 -0
- package/dist/http/routes/projects.js +86 -0
- package/dist/http/routes/rules.js +30 -2
- package/dist/http/routes/runs.js +8 -8
- package/dist/http/routes/session-sync.js +27 -10
- package/dist/http/routes/sessions.d.ts +7 -1
- package/dist/http/routes/sessions.js +104 -2
- package/dist/http/routes/tasks.js +9 -3
- package/dist/http/server.d.ts +24 -10
- package/dist/http/server.js +17 -6
- package/dist/http/stored-body-replay.d.ts +5 -3
- package/dist/http/stored-body-replay.js +10 -1
- package/dist/http/wire-types.d.ts +19 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/instruction-source-notice.d.ts +1 -1
- package/dist/memory-embedder-reembed.d.ts +38 -0
- package/dist/memory-embedder-reembed.js +20 -0
- package/dist/memory-incident-sink.d.ts +0 -28
- package/dist/memory-incident-sink.js +5 -0
- package/dist/memory-scope.d.ts +2 -2
- package/dist/memory-scope.js +1 -1
- package/dist/memory-store-refusal.d.ts +17 -4
- package/dist/memory-store-refusal.js +1 -1
- package/dist/memory-sync-client.d.ts +2 -0
- package/dist/memory-sync-client.js +13 -11
- package/dist/memory-sync.js +1 -1
- package/dist/model-compat.d.ts +35 -21
- package/dist/model-compat.js +49 -21
- package/dist/model-entry-refusal.d.ts +11 -1
- package/dist/model-entry-refusal.js +7 -0
- package/dist/namespaced-rule-name.d.ts +18 -1
- package/dist/namespaced-rule-name.js +12 -0
- package/dist/observability/fail-open.d.ts +17 -9
- package/dist/observability/fail-open.js +17 -9
- package/dist/observability/metrics.d.ts +2 -0
- package/dist/observability/metrics.js +2 -0
- package/dist/observability/secret-env-scrub.d.ts +30 -4
- package/dist/observability/secret-env-scrub.js +15 -3
- package/dist/orchestration/workflow-agent-session-index.d.ts +7 -12
- package/dist/orchestration/workflow-agent-session-index.js +1 -6
- package/dist/orchestration/workflow-completion-inbox.d.ts +26 -6
- package/dist/orchestration/workflow-completion-inbox.js +63 -45
- package/dist/org-memory-admission.d.ts +5 -2
- package/dist/org-memory-admission.js +25 -10
- package/dist/parked-decide.d.ts +33 -26
- package/dist/parked-decide.js +36 -44
- package/dist/plugins/approval-ask-store-file.d.ts +10 -2
- package/dist/plugins/approval-ask-store-file.js +37 -4
- package/dist/plugins/approval-ask-store-memory.d.ts +5 -3
- package/dist/plugins/approval-ask-store-memory.js +11 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +3 -0
- package/dist/plugins/approval-ask-store-sql.js +3 -1
- package/dist/plugins/blob-backend.d.ts +38 -195
- package/dist/plugins/blob-backend.js +33 -229
- package/dist/plugins/caching-session-store.d.ts +9 -4
- package/dist/plugins/checkpoint-index-columns.d.ts +1 -1
- package/dist/plugins/checkpoint-index-columns.js +3 -2
- package/dist/plugins/checkpoint-store-face.d.ts +1 -1
- package/dist/plugins/device-store-sql.d.ts +2 -2
- package/dist/plugins/device-store-sql.js +5 -10
- package/dist/plugins/execution-lane-rename-m40.d.ts +20 -0
- package/dist/plugins/execution-lane-rename-m40.js +10 -0
- package/dist/plugins/execution-placement-sql.d.ts +63 -88
- package/dist/plugins/execution-placement-sql.js +94 -54
- package/dist/plugins/file-run-store.d.ts +26 -12
- package/dist/plugins/file-run-store.js +63 -63
- package/dist/plugins/imported-layer-root-retirement.d.ts +17 -8
- package/dist/plugins/imported-layer-root-retirement.js +4 -8
- package/dist/plugins/inbox-payload-column.d.ts +14 -0
- package/dist/plugins/inbox-payload-column.js +13 -0
- package/dist/plugins/local-checkpoint-store.d.ts +5 -0
- package/dist/plugins/local-checkpoint-store.js +11 -7
- package/dist/plugins/local-data-dir-modes-m39.d.ts +48 -0
- package/dist/plugins/local-data-dir-modes-m39.js +182 -0
- package/dist/plugins/local-engine-high-water.d.ts +11 -0
- package/dist/plugins/local-engine-high-water.js +59 -0
- package/dist/plugins/local-session-store.d.ts +91 -25
- package/dist/plugins/local-session-store.js +124 -45
- package/dist/plugins/local-task-attachment-store.d.ts +2 -0
- package/dist/plugins/local-task-attachment-store.js +10 -11
- package/dist/plugins/memory-embedder-fingerprint.d.ts +33 -10
- package/dist/plugins/memory-embedder-fingerprint.js +52 -12
- package/dist/plugins/memory-embedder.d.ts +1 -1
- package/dist/plugins/memory-embedder.js +2 -2
- package/dist/plugins/memory-engine-pg.d.ts +4 -3
- package/dist/plugins/memory-engine-pg.js +4 -4
- package/dist/plugins/memory-engine-sql-apply.d.ts +13 -4
- package/dist/plugins/memory-engine-sql-apply.js +1 -1
- package/dist/plugins/memory-engine-tidb.d.ts +8 -5
- package/dist/plugins/memory-engine-tidb.js +7 -8
- package/dist/plugins/one-time-migrations.d.ts +145 -35
- package/dist/plugins/one-time-migrations.js +272 -120
- package/dist/plugins/owner-only-file-mode.d.ts +29 -0
- package/dist/plugins/owner-only-file-mode.js +141 -0
- package/dist/plugins/permission-rule-store-file.d.ts +8 -8
- package/dist/plugins/permission-rule-store-file.js +14 -2
- package/dist/plugins/pg-pool.d.ts +2 -1
- package/dist/plugins/pg-pool.js +11 -4
- package/dist/plugins/release-version.d.ts +20 -0
- package/dist/plugins/release-version.js +28 -0
- package/dist/plugins/remote-env-adb.d.ts +20 -4
- package/dist/plugins/remote-env-adb.js +39 -11
- package/dist/plugins/remote-env-e2b.d.ts +12 -3
- package/dist/plugins/remote-env-e2b.js +6 -4
- package/dist/plugins/remote-env-host.d.ts +9 -1
- package/dist/plugins/remote-env-host.js +6 -5
- package/dist/plugins/remote-env-k8s.d.ts +5 -2
- package/dist/plugins/remote-env-k8s.js +15 -4
- package/dist/plugins/remote-env-local-docker.d.ts +4 -1
- package/dist/plugins/remote-env-local-docker.js +3 -3
- package/dist/plugins/remote-env-ssh.d.ts +8 -2
- package/dist/plugins/remote-env-ssh.js +26 -24
- package/dist/plugins/remote-shell.d.ts +59 -30
- package/dist/plugins/remote-shell.js +175 -62
- package/dist/plugins/retention-declaration.d.ts +37 -0
- package/dist/plugins/retention-declaration.js +9 -0
- package/dist/plugins/retention-deployment.d.ts +126 -0
- package/dist/plugins/retention-deployment.js +88 -0
- package/dist/plugins/retention-lane-store-sql.d.ts +74 -4
- package/dist/plugins/retention-lane-store-sql.js +103 -1
- package/dist/plugins/retention-sql.d.ts +29 -0
- package/dist/plugins/retention-sql.js +8 -0
- package/dist/plugins/schema-consistency.d.ts +0 -6
- package/dist/plugins/schema-consistency.js +1 -2
- package/dist/plugins/schema-ensure-legs.d.ts +1 -1
- package/dist/plugins/schema-ensure-legs.js +11 -1
- package/dist/plugins/session-ext-table.d.ts +74 -0
- package/dist/plugins/session-ext-table.js +121 -0
- package/dist/plugins/session-fork.d.ts +1 -1
- package/dist/plugins/session-row-sql.d.ts +8 -7
- package/dist/plugins/session-row-sql.js +15 -7
- package/dist/plugins/session-store-sql.d.ts +131 -0
- package/dist/plugins/session-store-sql.js +298 -0
- package/dist/plugins/session-workspace-cell.d.ts +2 -2
- package/dist/plugins/session-workspace-cell.js +2 -2
- package/dist/plugins/shared-memory-store-sql.d.ts +10 -0
- package/dist/plugins/shared-memory-store-sql.js +13 -13
- package/dist/plugins/snapshot-bucket-presence.js +1 -1
- package/dist/plugins/sql-driver.d.ts +23 -7
- package/dist/plugins/sql-driver.js +41 -8
- package/dist/plugins/sql-errors.d.ts +5 -2
- package/dist/plugins/sql-errors.js +4 -0
- package/dist/plugins/store-backend.d.ts +35 -8
- package/dist/plugins/store-backend.js +184 -62
- package/dist/plugins/store-contracts.d.ts +9 -2
- package/dist/plugins/store-contracts.js +1 -0
- package/dist/plugins/task-attachment-store.d.ts +6 -8
- package/dist/plugins/task-attachment-store.js +15 -10
- package/dist/plugins/task-list-store-sql.d.ts +1 -1
- package/dist/plugins/text-column-capacity.d.ts +21 -0
- package/dist/plugins/text-column-capacity.js +5 -0
- package/dist/plugins/tidb-pool.d.ts +3 -2
- package/dist/plugins/tidb-pool.js +19 -31
- package/dist/plugins/user-input-ledger-sql.d.ts +1 -1
- package/dist/read-face-posture.d.ts +6 -6
- package/dist/read-face-posture.js +2 -2
- package/dist/request-value-closure.d.ts +5 -5
- package/dist/request-value-closure.js +1 -1
- package/dist/rules-consent.d.ts +41 -13
- package/dist/rules-consent.js +101 -53
- package/dist/run-local.js +17 -13
- package/dist/runs.d.ts +48 -30
- package/dist/runs.js +26 -16
- package/dist/runtime-caps-resolver.d.ts +15 -8
- package/dist/runtime-caps-resolver.js +14 -10
- package/dist/sealed-key-layout.d.ts +2 -3
- package/dist/sealed-key-layout.js +0 -1
- package/dist/sealed-key.js +7 -5
- package/dist/security.d.ts +5 -3
- package/dist/sema-config-dir.js +2 -2
- package/dist/server-secret-env.d.ts +2 -0
- package/dist/service-version.d.ts +2 -1
- package/dist/service-version.js +2 -2
- package/dist/session-error-wire.d.ts +1 -1
- package/dist/session-incarnation.d.ts +8 -5
- package/dist/session-resident-background.d.ts +85 -32
- package/dist/session-resident-background.js +75 -7
- package/dist/session-row.d.ts +1 -1
- package/dist/session-sync-kernel.d.ts +1 -1
- package/dist/session-workspace.d.ts +11 -2
- package/dist/session-workspace.js +5 -0
- package/dist/sighup-idle.d.ts +1 -1
- package/dist/store-posture.d.ts +2 -1
- package/dist/stored-request-hooks-m20.d.ts +3 -2
- package/dist/stored-request-hooks-m20.js +19 -14
- package/dist/stores-sql/blob-index-sql.d.ts +148 -0
- package/dist/stores-sql/blob-index-sql.js +154 -0
- package/dist/stores-sql/checkpoint-store-sql.d.ts +1 -1
- package/dist/stores-sql/checkpoint-store-sql.js +11 -27
- package/dist/{plugins → stores-sql}/file-history-store-sql.d.ts +23 -11
- package/dist/{plugins → stores-sql}/file-history-store-sql.js +23 -18
- package/dist/{plugins → stores-sql}/pg-session-storage.d.ts +23 -76
- package/dist/{plugins → stores-sql}/pg-session-storage.js +80 -209
- package/dist/{plugins → stores-sql}/retention-store-sql.d.ts +123 -217
- package/dist/{plugins → stores-sql}/retention-store-sql.js +66 -190
- package/dist/{plugins → stores-sql}/session-event-payload.d.ts +1 -1
- package/dist/{plugins → stores-sql}/session-event-payload.js +1 -1
- package/dist/stores-sql/session-policy-store-sql.d.ts +1 -1
- package/dist/stores-sql/session-write-hooks.d.ts +59 -0
- package/dist/stores-sql/session-write-hooks.js +2 -0
- package/dist/{plugins → stores-sql}/tidb-session-storage.d.ts +10 -11
- package/dist/{plugins → stores-sql}/tidb-session-storage.js +10 -21
- package/dist/{plugins → stores-sql}/tidb-session-store.d.ts +32 -122
- package/dist/{plugins → stores-sql}/tidb-session-store.js +71 -171
- package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
- package/dist/stores-sql/tool-result-store-sql.js +1 -1
- package/dist/stores-sql/windowed-session-storage.d.ts +56 -0
- package/dist/stores-sql/windowed-session-storage.js +61 -0
- package/dist/task-a2a.d.ts +7 -1
- package/dist/task-a2a.js +15 -14
- package/dist/task-cwd.d.ts +11 -3
- package/dist/task-cwd.js +17 -1
- package/dist/task-mcp.d.ts +10 -2
- package/dist/task-mcp.js +13 -16
- package/dist/task-settings.d.ts +9 -4
- package/dist/task-settings.js +17 -6
- package/dist/tool-approval.d.ts +21 -8
- package/dist/tool-approval.js +10 -6
- package/dist/trace/core-keyset-guard.d.ts +9 -3
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +23 -1
- package/dist/trace/exit-seat.js +3 -0
- package/dist/trace/project.d.ts +33 -16
- package/dist/trace/project.js +12 -9
- package/dist/trace/redact.d.ts +1 -1
- package/dist/trace/redact.js +61 -73
- package/dist/trace/self-minted-wire.d.ts +263 -10
- package/dist/trace/self-minted-wire.js +90 -2
- package/dist/trace/sema-provenance.d.ts +45 -41
- package/dist/trace/sema-provenance.js +81 -10
- package/dist/trace/task-notification-facets.d.ts +70 -2
- package/dist/trace/task-notification-facets.js +71 -1
- package/dist/trace/wire-projection-faces.d.ts +38 -2
- package/dist/trace/wire-projection-faces.js +26 -1
- package/dist/unread-request-members.d.ts +66 -0
- package/dist/unread-request-members.js +41 -0
- package/dist/user-input-ledger.d.ts +2 -2
- package/dist/web-admin-client/apply-effective.js +22 -26
- package/dist/web-admin-client/candidate-gate.d.ts +35 -3
- package/dist/web-admin-client/candidate-gate.js +53 -5
- package/dist/web-admin-client/center-request.d.ts +4 -1
- package/dist/web-admin-client/center-request.js +2 -1
- package/dist/web-admin-client/effective-keys.js +0 -1
- package/dist/web-admin-client/facade.d.ts +1 -1
- package/dist/web-admin-client/http-client.d.ts +1 -0
- package/dist/web-admin-client/http-client.js +17 -9
- package/dist/web-admin-client/restart-signal.js +5 -2
- package/dist/web-admin-client/skills-mcp.d.ts +20 -6
- package/dist/web-admin-client/skills-mcp.js +26 -21
- package/dist/web-admin-client/types.d.ts +34 -49
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +2 -2
- package/package.json +13 -12
- package/scripts/drain-check-local.mjs +2 -28
- package/scripts/drain-check.mysql.sql +2 -18
- package/scripts/drain-check.pg.sql +2 -18
- package/wire-changes/server@7.147.0.json +813 -0
|
@@ -1,33 +1,38 @@
|
|
|
1
1
|
import { emitLeafAdvance } from "../session-leaf-bus.js";
|
|
2
|
-
import {
|
|
3
|
-
import { SESSION_TITLE_MAX_CHARS, sessionRowTagKey } from "../session-row.js";
|
|
4
|
-
import { writeSessionRowCellsSql } from "./session-row-sql.js";
|
|
5
|
-
import { escapeLike } from "./sql-escape.js";
|
|
2
|
+
import { StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry, admitSessionPlacement, } from "@sema-agent/core";
|
|
6
3
|
import { decodeSessionEventPayload } from "./session-event-payload.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
4
|
+
import { WindowedSessionStorage } from "./windowed-session-storage.js";
|
|
5
|
+
import { scanEpochAnchor } from "../plugins/session-epoch-anchor.js";
|
|
6
|
+
import { jsonColumnText } from "../plugins/sql-json-column.js";
|
|
7
|
+
import { pgSafeJsonStringify } from "../plugins/pg-safe-json.js";
|
|
8
|
+
import { toIso } from "../plugins/sql-row-helpers.js";
|
|
9
|
+
import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertValues, SESSION_DURABILITY, SESSION_PLACEMENTS, } from "../plugins/session-placement.js";
|
|
12
10
|
import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
|
|
13
11
|
import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
|
|
14
|
-
import { isPgUniqueViolation } from "
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { ensureIndexes } from "./ensure-index.js";
|
|
20
|
-
import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue, placedSessionOwnerOf } from "./session-ownership.js";
|
|
12
|
+
import { isPgUniqueViolation } from "../plugins/sql-errors.js";
|
|
13
|
+
import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "../plugins/retention-declaration.js";
|
|
14
|
+
import { SESSION_META_INCARNATION_COLUMN, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
|
|
15
|
+
import { ensureIndexes } from "../plugins/ensure-index.js";
|
|
16
|
+
import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue, placedSessionOwnerOf } from "../plugins/session-ownership.js";
|
|
21
17
|
import { selectForkRows } from "@sema-agent/core";
|
|
22
|
-
import { FORK_LOG_ROW_SHAPE, ForkTargetUnsettledError, classifyForkTarget, forkByFreshId, forkPointOf } from "
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
import { FORK_LOG_ROW_SHAPE, ForkTargetUnsettledError, classifyForkTarget, forkByFreshId, forkPointOf } from "../plugins/session-fork.js";
|
|
19
|
+
const statementsOn = (conn) => ({
|
|
20
|
+
async query(sql, params) {
|
|
21
|
+
const res = await conn.query(sql, params ?? []);
|
|
22
|
+
return { rows: res.rows, affected: res.rowCount ?? 0 };
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
class HookRefusal {
|
|
26
|
+
refuse;
|
|
27
|
+
constructor(refuse) {
|
|
28
|
+
this.refuse = refuse;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function settleHookVerdict(verdict) {
|
|
32
|
+
if (verdict !== "proceed")
|
|
33
|
+
throw new HookRefusal(verdict.refuse);
|
|
30
34
|
}
|
|
35
|
+
const isDupKey = isPgUniqueViolation;
|
|
31
36
|
export const PG_SESSION_SCHEMA = [
|
|
32
37
|
`CREATE TABLE IF NOT EXISTS session_meta (
|
|
33
38
|
session_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -36,16 +41,8 @@ export const PG_SESSION_SCHEMA = [
|
|
|
36
41
|
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
37
42
|
leaf_id VARCHAR(64) COLLATE "C",
|
|
38
43
|
leaf_seq BIGINT NOT NULL DEFAULT 0,
|
|
39
|
-
--
|
|
40
|
-
-- (
|
|
41
|
-
-- — fills NULL only; NULL = "untitled", the three-state probe distinguishes it from "no such session").
|
|
42
|
-
title VARCHAR(${SESSION_TITLE_MAX_CHARS}) COLLATE "C",
|
|
43
|
-
-- pinned: the user's pin (TiDB twin); every list row carries it, false by default.
|
|
44
|
-
pinned BOOLEAN NOT NULL DEFAULT FALSE,
|
|
45
|
-
-- instruction_source(S-690):换源通告的基线 —— TiDB 孪生同名同宽,三态读法(NULL / '' / 链成员相对路径)见那里的列注。
|
|
46
|
-
instruction_source VARCHAR(64) COLLATE "C",
|
|
47
|
-
-- workspace:本会话上一条腿自己的最后一帧 workspace_changed —— TiDB 孪生同名同义(列注与唯一属主见那里:src/plugins/session-workspace-cell.ts)。
|
|
48
|
-
${SESSION_META_WORKSPACE_COLUMN.pg},
|
|
44
|
+
-- 引擎座表只带引擎的列;本服务记在会话上的格(标题 / 置顶 / 指令基线 / 位置与项目 / 执行放置 / fork 键)住部署旁表 session_ext
|
|
45
|
+
-- (外键级联随本行走;唯一属主 src/plugins/session-ext-table.ts)。
|
|
49
46
|
-- #292 C18(core 5.48.0 design/323)subagent 转录持久化 —— placement 元组(TiDB 孪生同名同宽,
|
|
50
47
|
-- 语义与六条义务见 src/plugins/session-placement.ts 顶注):创建时一次落行、first-write 不可变;
|
|
51
48
|
-- placement_kind 在场 ⇒ 住在 placement 分区(不上宿主枚举 / claim 形 acquire 恒拒 / release=真删除 /
|
|
@@ -59,8 +56,6 @@ export const PG_SESSION_SCHEMA = [
|
|
|
59
56
|
placed_at TIMESTAMPTZ(3),
|
|
60
57
|
-- S-790 / S-506:会话化身(TiDB 孪生同名同义;列定义唯一属主 = src/session-incarnation.ts 的 SESSION_META_INCARNATION_COLUMN)。
|
|
61
58
|
${SESSION_META_INCARNATION_COLUMN.pg},
|
|
62
|
-
-- 执行放置(根会话行上这一位住客的一格:execution_* 七列)+ fork 键(fork_key)—— TiDB 孪生同名同义;列定义与语句的唯一属主 = src/plugins/execution-placement-sql.ts。
|
|
63
|
-
${sessionMetaPlacementColumnsDdl("pg")},
|
|
64
59
|
PRIMARY KEY (session_id)
|
|
65
60
|
)`,
|
|
66
61
|
`CREATE TABLE IF NOT EXISTS session_event (
|
|
@@ -85,12 +80,12 @@ export async function ensureSchema(pool) {
|
|
|
85
80
|
await pool.query(stmt);
|
|
86
81
|
await ensureIndexes((t, p) => pool.query(t, p), "pg", SESSION_INDEXES);
|
|
87
82
|
}
|
|
88
|
-
export class PgSessionStorage extends
|
|
83
|
+
export class PgSessionStorage extends WindowedSessionStorage {
|
|
89
84
|
pool;
|
|
90
85
|
sessionId;
|
|
91
86
|
floorSeq;
|
|
92
87
|
constructor(pool, sessionId, metadata, entries, leafId, floorEntryId = null, floorSeq = 0) {
|
|
93
|
-
super(metadata, entries, leafId,
|
|
88
|
+
super(metadata, entries, leafId, floorEntryId);
|
|
94
89
|
this.pool = pool;
|
|
95
90
|
this.sessionId = sessionId;
|
|
96
91
|
this.floorSeq = floorSeq;
|
|
@@ -126,24 +121,12 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
126
121
|
return { floorEntryId: null, floorSeq: 0 };
|
|
127
122
|
return { floorEntryId: fk, floorSeq: Number(seqRes.rows[0].seq) };
|
|
128
123
|
}
|
|
129
|
-
async
|
|
130
|
-
const inMem = await super.getEntry(id);
|
|
131
|
-
if (inMem)
|
|
132
|
-
return inMem;
|
|
124
|
+
async readBelowFloor(id) {
|
|
133
125
|
const res = await this.pool.query(`SELECT ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 AND entry_id = $2`, [this.sessionId, id]);
|
|
134
126
|
if (res.rows[0] === undefined)
|
|
135
127
|
return undefined;
|
|
136
128
|
return decodeSessionEventPayload(res.rows[0].payload, this.sessionId, id);
|
|
137
129
|
}
|
|
138
|
-
async appendEntry(entry, opts) {
|
|
139
|
-
await this.persist(entry, opts);
|
|
140
|
-
this.recordEntry(entry);
|
|
141
|
-
}
|
|
142
|
-
async setLeafId(leafId, opts) {
|
|
143
|
-
const leafEntry = this.createLeafEntry(leafId);
|
|
144
|
-
await this.persist(leafEntry, opts);
|
|
145
|
-
this.recordEntry(leafEntry);
|
|
146
|
-
}
|
|
147
130
|
async persist(entry, opts) {
|
|
148
131
|
const newLeaf = leafIdAfterEntry(entry);
|
|
149
132
|
const conditional = opts !== undefined && opts.expectedLeafId !== undefined;
|
|
@@ -189,13 +172,11 @@ export class PgSessionStore {
|
|
|
189
172
|
placements = SESSION_PLACEMENTS;
|
|
190
173
|
durability = SESSION_DURABILITY;
|
|
191
174
|
pending = new Map();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
constructor(pool, opts = {}) {
|
|
175
|
+
hooks;
|
|
176
|
+
constructor(pool, opts) {
|
|
195
177
|
this.pool = pool;
|
|
196
178
|
this.retention = opts.retention ?? UNMANAGED_RETENTION;
|
|
197
|
-
this.
|
|
198
|
-
this.executionPlacement = createSqlExecutionPlacementSeat(this.db);
|
|
179
|
+
this.hooks = opts.hooks;
|
|
199
180
|
}
|
|
200
181
|
async acquire(sessionId, opts) {
|
|
201
182
|
if (!sessionId) {
|
|
@@ -319,109 +300,6 @@ export class PgSessionStore {
|
|
|
319
300
|
throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${placed.kind} placement partition — a session-sync ${what} cannot overwrite a delegation transcript`);
|
|
320
301
|
}
|
|
321
302
|
}
|
|
322
|
-
async listSessions(opts) {
|
|
323
|
-
return (await this.listSessionRows(opts)).map((a) => a.row);
|
|
324
|
-
}
|
|
325
|
-
async listSessionRows(opts) {
|
|
326
|
-
const params = [];
|
|
327
|
-
const ph = (v) => {
|
|
328
|
-
params.push(v);
|
|
329
|
-
return `$${params.length}`;
|
|
330
|
-
};
|
|
331
|
-
const where = ["sm.placement_kind IS NULL"];
|
|
332
|
-
if (opts.owner !== undefined)
|
|
333
|
-
where.push(opts.includeUnowned ? `(sm.owner = ${ph(opts.owner)} OR sm.owner IS NULL)` : `sm.owner = ${ph(opts.owner)}`);
|
|
334
|
-
if (opts.q)
|
|
335
|
-
where.push(`EXISTS (SELECT 1 FROM task_run tr WHERE tr.session_id = sm.session_id AND LOWER(tr.objective_preview) LIKE LOWER(${ph(`%${escapeLike(opts.q)}%`)}) ESCAPE '\\')`);
|
|
336
|
-
if (opts.cursor) {
|
|
337
|
-
const a = ph(new Date(opts.cursor.lastActivityAt));
|
|
338
|
-
const b = ph(new Date(opts.cursor.lastActivityAt));
|
|
339
|
-
const sid = ph(opts.cursor.sessionId);
|
|
340
|
-
where.push(`(sm.updated_at < ${a} OR (sm.updated_at = ${b} AND sm.session_id < ${sid}))`);
|
|
341
|
-
}
|
|
342
|
-
return this.selectListRows({ where, params, limit: opts.limit });
|
|
343
|
-
}
|
|
344
|
-
async sessionSummaryOf(sessionId) {
|
|
345
|
-
return (await this.listRowOf(sessionId))?.row;
|
|
346
|
-
}
|
|
347
|
-
async listRowOf(sessionId) {
|
|
348
|
-
return (await this.selectListRows({ where: ["sm.placement_kind IS NULL", "sm.session_id = $1"], params: [sessionId], limit: 1, runSessionId: sessionId }))[0];
|
|
349
|
-
}
|
|
350
|
-
async writeSessionRowCells(sessionId, cells, how) {
|
|
351
|
-
return writeSessionRowCellsSql(this.db, sessionId, cells, how, (id) => this.listRowOf(id));
|
|
352
|
-
}
|
|
353
|
-
async selectListRows(q) {
|
|
354
|
-
const params = [...q.params];
|
|
355
|
-
const ph = (v) => {
|
|
356
|
-
params.push(v);
|
|
357
|
-
return `$${params.length}`;
|
|
358
|
-
};
|
|
359
|
-
const runWhere = q.runSessionId !== undefined ? ` WHERE session_id = ${ph(q.runSessionId)}` : "";
|
|
360
|
-
const sql = "SELECT sm.session_id, sm.owner, sm.incarnation, sm.updated_at AS last_activity, sm.created_at AS first_activity, sm.title, sm.pinned, " +
|
|
361
|
-
" COALESCE(r.run_count, 0) AS run_count, latest.objective_preview, latest.status, latest.task_id AS last_run_id, latest.last_task_run_id " +
|
|
362
|
-
"FROM session_meta sm " +
|
|
363
|
-
"LEFT JOIN (SELECT session_id, COUNT(*) AS run_count FROM task_run" + runWhere + " GROUP BY session_id) r ON r.session_id = sm.session_id " +
|
|
364
|
-
"LEFT JOIN (SELECT DISTINCT ON (session_id) session_id, objective_preview, status, task_id, result ->> 'runId' AS last_task_run_id FROM task_run" + runWhere +
|
|
365
|
-
" ORDER BY session_id, created_at DESC, task_id DESC) latest ON latest.session_id = sm.session_id " +
|
|
366
|
-
(q.where.length ? "WHERE " + q.where.join(" AND ") + " " : "") +
|
|
367
|
-
`ORDER BY sm.updated_at DESC, sm.session_id DESC LIMIT ${ph(q.limit)}`;
|
|
368
|
-
const res = await this.pool.query(sql, params);
|
|
369
|
-
return res.rows.map((r) => {
|
|
370
|
-
const runId = r.last_task_run_id;
|
|
371
|
-
const sessionId = String(r.session_id);
|
|
372
|
-
const owner = r.owner ?? null;
|
|
373
|
-
const incarnation = incarnationFromCell(sessionId, r.incarnation, "absent");
|
|
374
|
-
const title = r.title ?? null;
|
|
375
|
-
const pinned = r.pinned === true;
|
|
376
|
-
const row = {
|
|
377
|
-
sessionId,
|
|
378
|
-
owner,
|
|
379
|
-
lastActivityAt: r.last_activity instanceof Date ? r.last_activity.toISOString() : String(r.last_activity),
|
|
380
|
-
firstActivityAt: r.first_activity instanceof Date ? r.first_activity.toISOString() : String(r.first_activity),
|
|
381
|
-
runCount: Number(r.run_count),
|
|
382
|
-
objectivePreview: r.objective_preview ?? null,
|
|
383
|
-
lastStatus: r.status == null ? "" : String(r.status),
|
|
384
|
-
lastRunId: r.last_run_id ?? null,
|
|
385
|
-
...(typeof runId === "string" && runId !== "" ? { lastTaskRunId: runId } : {}),
|
|
386
|
-
title,
|
|
387
|
-
pinned,
|
|
388
|
-
...sessionRowTagKey({ sessionId, owner, incarnation, cells: { title, pinned } }),
|
|
389
|
-
};
|
|
390
|
-
return { row, occupant: { owner, incarnation } };
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
async probeTitle(sessionId) {
|
|
394
|
-
const res = await this.pool.query("SELECT title FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
395
|
-
const row = res.rows[0];
|
|
396
|
-
if (!row)
|
|
397
|
-
return "none";
|
|
398
|
-
return row.title == null ? "untitled" : "titled";
|
|
399
|
-
}
|
|
400
|
-
async setTitleIfNull(sessionId, title) {
|
|
401
|
-
const res = await this.pool.query("UPDATE session_meta SET title = $1 WHERE session_id = $2 AND title IS NULL", [title, sessionId]);
|
|
402
|
-
return (res.rowCount ?? 0) > 0;
|
|
403
|
-
}
|
|
404
|
-
async readInstructionSource(sessionId) {
|
|
405
|
-
const res = await this.pool.query(baselineQuery("SELECT instruction_source FROM session_meta WHERE session_id = $1", [sessionId]));
|
|
406
|
-
return decodeInstructionSource(res.rows[0]?.instruction_source);
|
|
407
|
-
}
|
|
408
|
-
async recordInstructionSource(sessionId, expected, source) {
|
|
409
|
-
const next = encodeInstructionSource(source);
|
|
410
|
-
if (expected.recorded) {
|
|
411
|
-
await this.pool.query(baselineQuery("UPDATE session_meta SET instruction_source = $1 WHERE session_id = $2 AND instruction_source = $3", [next, sessionId, encodeInstructionSource(expected.source)]));
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
await this.pool.query(baselineQuery("UPDATE session_meta SET instruction_source = $1 WHERE session_id = $2 AND instruction_source IS NULL", [next, sessionId]));
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
async readSessionWorkspace(sessionId) {
|
|
418
|
-
const res = await this.pool.query(`SELECT ${jsonColumnText("pg", "workspace")} FROM session_meta WHERE session_id = $1`, [sessionId]);
|
|
419
|
-
const row = res.rows[0];
|
|
420
|
-
return row === undefined ? undefined : decodeSessionWorkspaceCell(row.workspace, sessionId);
|
|
421
|
-
}
|
|
422
|
-
async recordSessionWorkspace(sessionId, incarnation, workspace) {
|
|
423
|
-
await this.pool.query("UPDATE session_meta SET workspace = $1::jsonb WHERE session_id = $2 AND incarnation = $3", [encodeSessionWorkspaceCell(workspace, pgSafeJsonStringify, sessionId), sessionId, incarnation]);
|
|
424
|
-
}
|
|
425
303
|
async fork(sourceId, owner, opts) {
|
|
426
304
|
return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner, opts);
|
|
427
305
|
}
|
|
@@ -451,8 +329,8 @@ export class PgSessionStore {
|
|
|
451
329
|
try {
|
|
452
330
|
await conn.query("BEGIN");
|
|
453
331
|
try {
|
|
454
|
-
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation,
|
|
455
|
-
"placement_kind, placement_scope, placement_parent_session_id, placement_root_session_id, placement_handle, placed_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13
|
|
332
|
+
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, " +
|
|
333
|
+
"placement_kind, placement_scope, placement_parent_session_id, placement_root_session_id, placement_handle, placed_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)", [target.id, forkOwner, now, now, forkLeafId, rows.length, mintSessionIncarnation(), ...placementInsertValues(target.placement, now)]);
|
|
456
334
|
}
|
|
457
335
|
catch (err) {
|
|
458
336
|
if (!isDupKey(err))
|
|
@@ -460,6 +338,8 @@ export class PgSessionStore {
|
|
|
460
338
|
collided = true;
|
|
461
339
|
}
|
|
462
340
|
if (!collided) {
|
|
341
|
+
if (target.key !== undefined)
|
|
342
|
+
await this.hooks.keyedFork.record(statementsOn(conn), { rootSessionId: target.id, forkKey: target.key });
|
|
463
343
|
for (let seq = 0; seq < rows.length; seq++) {
|
|
464
344
|
const r = rows[seq];
|
|
465
345
|
const payload = String(r.payload);
|
|
@@ -476,14 +356,15 @@ export class PgSessionStore {
|
|
|
476
356
|
}
|
|
477
357
|
catch {
|
|
478
358
|
}
|
|
359
|
+
if (err instanceof HookRefusal)
|
|
360
|
+
throw err.refuse;
|
|
479
361
|
throw err instanceof SessionError ? err : new SessionError("storage", `fork of session ${sourceId} failed`, err instanceof Error ? err : undefined);
|
|
480
362
|
}
|
|
481
363
|
finally {
|
|
482
364
|
conn.release();
|
|
483
365
|
}
|
|
484
|
-
const existing = await this.
|
|
485
|
-
|
|
486
|
-
return row === undefined ? "unsettled" : classifyForkTarget(target, { owner: row.owner ?? null, forkKey: row.fork_key ?? null }, forkOwner);
|
|
366
|
+
const existing = await this.hooks.keyedFork.targetOf(target.id);
|
|
367
|
+
return existing === undefined ? "unsettled" : classifyForkTarget(target, existing, forkOwner);
|
|
487
368
|
}
|
|
488
369
|
async exportEntries(sessionId) {
|
|
489
370
|
const metaRes = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
@@ -523,7 +404,7 @@ export class PgSessionStore {
|
|
|
523
404
|
const conn = await this.pool.connect();
|
|
524
405
|
try {
|
|
525
406
|
await conn.query("BEGIN");
|
|
526
|
-
await this.writeImportedLog(conn, sessionId, owner, validated,
|
|
407
|
+
await this.writeImportedLog(conn, sessionId, owner, validated, "create");
|
|
527
408
|
await conn.query("COMMIT");
|
|
528
409
|
}
|
|
529
410
|
catch (err) {
|
|
@@ -532,7 +413,9 @@ export class PgSessionStore {
|
|
|
532
413
|
}
|
|
533
414
|
catch {
|
|
534
415
|
}
|
|
535
|
-
if (err instanceof
|
|
416
|
+
if (err instanceof HookRefusal)
|
|
417
|
+
throw err.refuse;
|
|
418
|
+
if (err instanceof SessionError)
|
|
536
419
|
throw err;
|
|
537
420
|
throw new SessionError("storage", `import of session ${sessionId} failed`, err instanceof Error ? err : undefined);
|
|
538
421
|
}
|
|
@@ -546,21 +429,13 @@ export class PgSessionStore {
|
|
|
546
429
|
const conn = await this.pool.connect();
|
|
547
430
|
try {
|
|
548
431
|
await conn.query("BEGIN");
|
|
549
|
-
const
|
|
550
|
-
const prior =
|
|
432
|
+
const lockedRes = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = $1 FOR UPDATE", [sessionId]);
|
|
433
|
+
const prior = lockedRes.rows[0];
|
|
551
434
|
if (prior?.placement_kind != null) {
|
|
552
435
|
await conn.query("ROLLBACK");
|
|
553
436
|
throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${String(prior.placement_kind)} placement partition — a session-sync replace cannot overwrite a delegation transcript`);
|
|
554
437
|
}
|
|
555
|
-
|
|
556
|
-
const swapped = (purged.rowCount ?? 0) === 1 ? prior : undefined;
|
|
557
|
-
const cells = carriedRowCells(sessionId, swapped === undefined ? undefined : { incarnation: swapped.incarnation, forkKey: swapped.fork_key });
|
|
558
|
-
const priorCell = swapped === undefined ? undefined : placementCellFromRow(sessionId, swapped);
|
|
559
|
-
if (priorCell?.role === "writer") {
|
|
560
|
-
await conn.query("ROLLBACK");
|
|
561
|
-
throw new SessionWriterPresentError(sessionId, priorCell.lane);
|
|
562
|
-
}
|
|
563
|
-
await this.writeImportedLog(conn, sessionId, owner, validated, cells, "replace");
|
|
438
|
+
await this.writeImportedLog(conn, sessionId, owner, validated, prior === undefined ? "create" : "replace");
|
|
564
439
|
await conn.query("COMMIT");
|
|
565
440
|
}
|
|
566
441
|
catch (err) {
|
|
@@ -569,7 +444,9 @@ export class PgSessionStore {
|
|
|
569
444
|
}
|
|
570
445
|
catch {
|
|
571
446
|
}
|
|
572
|
-
if (err instanceof
|
|
447
|
+
if (err instanceof HookRefusal)
|
|
448
|
+
throw err.refuse;
|
|
449
|
+
if (err instanceof SessionError)
|
|
573
450
|
throw err;
|
|
574
451
|
throw new SessionError("storage", `replace of session ${sessionId} failed`, err instanceof Error ? err : undefined);
|
|
575
452
|
}
|
|
@@ -577,23 +454,26 @@ export class PgSessionStore {
|
|
|
577
454
|
conn.release();
|
|
578
455
|
}
|
|
579
456
|
}
|
|
580
|
-
async writeImportedLog(conn, sessionId, owner, validated,
|
|
457
|
+
async writeImportedLog(conn, sessionId, owner, validated, mode) {
|
|
581
458
|
let leafId = null;
|
|
582
459
|
for (const e of validated)
|
|
583
460
|
leafId = leafIdAfterEntry(e);
|
|
584
461
|
const now = new Date();
|
|
585
|
-
const imported = importedRowCells(cells.forkKey);
|
|
586
|
-
await conn.query(`INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, ${imported.columns}) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)` +
|
|
587
|
-
(mode === "create" ? " ON CONFLICT (session_id) DO NOTHING" : ""), [sessionId, owner, now, now, leafId, validated.length, cells.incarnation, ...imported.values]);
|
|
588
462
|
if (mode === "create") {
|
|
463
|
+
const minted = mintSessionIncarnation();
|
|
464
|
+
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES ($1,$2,$3,$4,$5,$6,$7) ON CONFLICT (session_id) DO NOTHING", [sessionId, owner, now, now, leafId, validated.length, minted]);
|
|
589
465
|
const held = await conn.query("SELECT incarnation FROM session_meta WHERE session_id = $1 FOR UPDATE", [sessionId]);
|
|
590
|
-
if (String(held.rows[0]?.incarnation) !==
|
|
466
|
+
if (String(held.rows[0]?.incarnation) !== minted) {
|
|
591
467
|
throw new SessionError("storage", `import of session ${sessionId} failed`, new Error(`session ${sessionId} already exists — importEntries only creates a session (replaceEntries rewrites one); nothing was written`));
|
|
592
468
|
}
|
|
593
469
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
470
|
+
else {
|
|
471
|
+
const rewritten = await conn.query("UPDATE session_meta SET owner = $1, created_at = $2, updated_at = $3, leaf_id = $4, leaf_seq = $5 WHERE session_id = $6 AND owner IS NOT DISTINCT FROM $1 AND placement_kind IS NULL", [owner, now, now, leafId, validated.length, sessionId]);
|
|
472
|
+
if ((rewritten.rowCount ?? 0) !== 1) {
|
|
473
|
+
throw new SessionError("storage", `replace of session ${sessionId} failed`, new Error(`session ${sessionId} belongs to another owner — a replace rewrites only the importer's own session; nothing was written`));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
settleHookVerdict(await this.hooks.beforeCommit(statementsOn(conn), { op: "import", rootSessionId: sessionId }));
|
|
597
477
|
if (mode === "replace")
|
|
598
478
|
await conn.query("DELETE FROM session_event WHERE session_id = $1", [sessionId]);
|
|
599
479
|
for (let seq = 0; seq < validated.length; seq++) {
|
|
@@ -602,7 +482,7 @@ export class PgSessionStore {
|
|
|
602
482
|
}
|
|
603
483
|
}
|
|
604
484
|
beginImportStaging(realSessionId, token) {
|
|
605
|
-
return new PgStagingHandle(this.pool, realSessionId, stagingIdFor(realSessionId, token));
|
|
485
|
+
return new PgStagingHandle(this.pool, this.hooks, realSessionId, stagingIdFor(realSessionId, token));
|
|
606
486
|
}
|
|
607
487
|
async readStagedEntries(stagingId) {
|
|
608
488
|
const res = await this.pool.query(`SELECT seq, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq ASC`, [stagingId]);
|
|
@@ -624,11 +504,9 @@ export class PgSessionStore {
|
|
|
624
504
|
const conn = await this.pool.connect();
|
|
625
505
|
try {
|
|
626
506
|
await conn.query("BEGIN");
|
|
627
|
-
const
|
|
628
|
-
if (
|
|
629
|
-
await
|
|
630
|
-
throw new SessionActiveRunError(sessionId, claim.activeTaskId);
|
|
631
|
-
}
|
|
507
|
+
const locked = await conn.query("SELECT session_id FROM session_meta WHERE session_id = $1 FOR UPDATE", [sessionId]);
|
|
508
|
+
if (locked.rows.length > 0)
|
|
509
|
+
settleHookVerdict(await this.hooks.beforeCommit(statementsOn(conn), { op: "delete", rootSessionId: sessionId }));
|
|
632
510
|
const metaRes = await conn.query("DELETE FROM session_meta WHERE session_id = $1 AND owner IS NOT DISTINCT FROM $2", [sessionId, owner]);
|
|
633
511
|
if ((metaRes.rowCount ?? 0) > 0)
|
|
634
512
|
await conn.query("DELETE FROM session_event WHERE session_id = $1", [sessionId]);
|
|
@@ -641,6 +519,8 @@ export class PgSessionStore {
|
|
|
641
519
|
}
|
|
642
520
|
catch {
|
|
643
521
|
}
|
|
522
|
+
if (err instanceof HookRefusal)
|
|
523
|
+
throw err.refuse;
|
|
644
524
|
throw err;
|
|
645
525
|
}
|
|
646
526
|
finally {
|
|
@@ -656,12 +536,14 @@ export class PgSessionStore {
|
|
|
656
536
|
}
|
|
657
537
|
class PgStagingHandle {
|
|
658
538
|
pool;
|
|
539
|
+
hooks;
|
|
659
540
|
realSessionId;
|
|
660
541
|
stagingId;
|
|
661
542
|
nextSeq = 0;
|
|
662
543
|
seen = new Set();
|
|
663
|
-
constructor(pool, realSessionId, stagingId) {
|
|
544
|
+
constructor(pool, hooks, realSessionId, stagingId) {
|
|
664
545
|
this.pool = pool;
|
|
546
|
+
this.hooks = hooks;
|
|
665
547
|
this.realSessionId = realSessionId;
|
|
666
548
|
this.stagingId = stagingId;
|
|
667
549
|
}
|
|
@@ -708,11 +590,7 @@ class PgStagingHandle {
|
|
|
708
590
|
throw new StagedCommitOwnerChangedError(this.realSessionId);
|
|
709
591
|
}
|
|
710
592
|
}
|
|
711
|
-
|
|
712
|
-
if (claim.activeTaskId !== null) {
|
|
713
|
-
await conn.query("ROLLBACK");
|
|
714
|
-
throw new SessionActiveRunError(this.realSessionId, claim.activeTaskId);
|
|
715
|
-
}
|
|
593
|
+
settleHookVerdict(await this.hooks.beforeCommit(statementsOn(conn), { op: "import", rootSessionId: this.realSessionId }));
|
|
716
594
|
const staged = await conn.query("SELECT entry_id FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE", [this.stagingId]);
|
|
717
595
|
const dst = await conn.query("SELECT entry_id FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE", [this.realSessionId]);
|
|
718
596
|
const dstMeta = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = $1", [this.realSessionId]);
|
|
@@ -745,16 +623,7 @@ class PgStagingHandle {
|
|
|
745
623
|
let leafId = null;
|
|
746
624
|
if (lastRes.rows[0])
|
|
747
625
|
leafId = leafIdAfterEntry(decodeSessionEventPayload(lastRes.rows[0].payload, this.realSessionId, "last staged row"));
|
|
748
|
-
|
|
749
|
-
if ((settle.rowCount ?? 0) !== 1) {
|
|
750
|
-
const locked = readPlacementRowStatement("pg", this.realSessionId, { lock: true });
|
|
751
|
-
const lockedRow = (await conn.query(locked.text, [...locked.values])).rows[0];
|
|
752
|
-
const cell = lockedRow === undefined ? undefined : placementCellFromRow(this.realSessionId, lockedRow);
|
|
753
|
-
await conn.query("ROLLBACK");
|
|
754
|
-
if (cell?.role === "writer")
|
|
755
|
-
throw new SessionWriterPresentError(this.realSessionId, cell.lane);
|
|
756
|
-
throw new SessionError("storage", `staged commit for ${this.realSessionId}: the commit point matched no row under a held row lock and the row is not a writer — refusing to guess`);
|
|
757
|
-
}
|
|
626
|
+
await conn.query("UPDATE session_meta SET owner = $1, updated_at = $2, leaf_id = $3, leaf_seq = $4 WHERE session_id = $5", [owner, now, leafId, srcIds.length, this.realSessionId]);
|
|
758
627
|
const afterMeta = await conn.query("SELECT placement_kind, owner, incarnation FROM session_meta WHERE session_id = $1", [this.realSessionId]);
|
|
759
628
|
if (afterMeta.rows[0]?.placement_kind != null) {
|
|
760
629
|
await conn.query("ROLLBACK");
|
|
@@ -769,7 +638,9 @@ class PgStagingHandle {
|
|
|
769
638
|
await conn.query("ROLLBACK");
|
|
770
639
|
}
|
|
771
640
|
catch { }
|
|
772
|
-
if (err instanceof
|
|
641
|
+
if (err instanceof HookRefusal)
|
|
642
|
+
throw err.refuse;
|
|
643
|
+
if (err instanceof SyncConflictError || err instanceof SessionError)
|
|
773
644
|
throw err;
|
|
774
645
|
throw new SessionError("storage", `staged commit for ${this.realSessionId} failed`, err instanceof Error ? err : undefined);
|
|
775
646
|
}
|