@sema-agent/server 7.43.0 → 7.44.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/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
|
@@ -1,47 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PostgreSQL-backed {@link BaseSessionStorage} — the PG twin of {@link TiDBSessionStorage}
|
|
3
|
-
* (src/plugins/tidb-session-storage.ts; clay 2026-06-21, the second session backend).
|
|
4
|
-
*
|
|
5
|
-
* A behavioural TWIN: identical class/method surface + identical semantics (the F2 optimistic-lock CAS on
|
|
6
|
-
* `leaf_id`/`leaf_seq`, the F3 bounded `[floor..leaf]` wake, the sub-floor durable getEntry fallback), only the
|
|
7
|
-
* SQL dialect differs. Kept in lock-step with the TiDB store by ONE shared real-DB integration suite
|
|
8
|
-
* (test/pg-session-storage-integration.test.ts) that drives the SAME scenarios against BOTH engines. Dialect deltas:
|
|
9
|
-
* - placeholders `$1..$n` (not `?`) - affected rows = `result.rowCount` (not affectedRows)
|
|
10
|
-
* - `leaf_id IS NOT DISTINCT FROM $n` (not `<=>`) - dup-key SQLSTATE `23505` (not ER_DUP_ENTRY)
|
|
11
|
-
* - `payload` is `jsonb`, bound `$n::jsonb`; node-pg PARSES jsonb → object on read (parseJson passes objects through)
|
|
12
|
-
* - BIGINT (`seq`/`leaf_seq`) comes back as a STRING → always `Number(...)`
|
|
13
|
-
* - transaction via `pool.connect()` + BEGIN/COMMIT/ROLLBACK/release (not getConnection/beginTransaction)
|
|
14
|
-
*
|
|
15
|
-
* Unlike the run/checkpoint PG stores, this store carries its OWN schema (PG_SESSION_SCHEMA + ensureSchema): the
|
|
16
|
-
* session_meta/session_event tables are DISJOINT from the centrally-owned run-store/checkpoint tables, so a
|
|
17
|
-
* self-contained CREATE IF NOT EXISTS cannot shadow another store's table. (Central aggregation into pg-pool is
|
|
18
|
-
* done separately afterward.)
|
|
19
|
-
*/
|
|
20
1
|
import { emitLeafAdvance } from "../session-leaf-bus.js";
|
|
21
|
-
// 🔴 core 1.403 迁移(2026-07-25):runtime 构造用法,`Session` → `StoredSession`(见 audit.ts 同款注释)。
|
|
22
2
|
import { BaseSessionStorage, StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry, normalizePromptEpoch, } from "@sema-agent/core";
|
|
23
3
|
import { escapeLike } from "./sql-escape.js";
|
|
24
4
|
import { pgSafeJsonStringify } from "./pg-safe-json.js";
|
|
25
5
|
import { toIso } from "./sql-row-helpers.js";
|
|
26
6
|
import { assertPlacementAdmission, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertValues, SQL_SESSION_PLACEMENTS, } from "./session-placement.js";
|
|
27
7
|
import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
|
|
28
|
-
import { classifySyncRelationshipByIds, SyncConflictError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS,
|
|
29
|
-
} from "../session-sync-kernel.js";
|
|
8
|
+
import { classifySyncRelationshipByIds, SyncConflictError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
|
|
30
9
|
import { isPgUniqueViolation } from "./sql-errors.js";
|
|
31
10
|
import { MANAGED_RETENTION } from "./retention-store-sql.js";
|
|
32
|
-
/** 判据属主 = `sql-errors.ts`(A-032 P1-①)。 */
|
|
33
11
|
const isDupKey = isPgUniqueViolation;
|
|
34
|
-
/** node-pg parses jsonb → object already; only strings need JSON.parse, everything else passes through. */
|
|
35
12
|
function parseEntry(p) {
|
|
36
13
|
return (typeof p === "string" ? JSON.parse(p) : p);
|
|
37
14
|
}
|
|
38
|
-
/**
|
|
39
|
-
* PG-dialect DDL for the L1 session log, translated from tidb-pool.ts SCHEMA_STATEMENTS:
|
|
40
|
-
* JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3), inline `KEY`/`UNIQUE KEY`→separate CREATE INDEX (UNIQUE for uq_session_event_entry).
|
|
41
|
-
*
|
|
42
|
-
* SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth, schema changes are
|
|
43
|
-
* drop-and-recreate, and NO new `ALTER TABLE` seams are added here; fold into the CREATE instead.
|
|
44
|
-
*/
|
|
45
15
|
export const PG_SESSION_SCHEMA = [
|
|
46
16
|
`CREATE TABLE IF NOT EXISTS session_meta (
|
|
47
17
|
session_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -67,7 +37,6 @@ export const PG_SESSION_SCHEMA = [
|
|
|
67
37
|
PRIMARY KEY (session_id)
|
|
68
38
|
)`,
|
|
69
39
|
`CREATE INDEX IF NOT EXISTS idx_session_meta_owner ON session_meta (owner)`,
|
|
70
|
-
// listPlaced 的检索腿(TiDB 孪生的 KEY idx_session_meta_placement 同形)。
|
|
71
40
|
`CREATE INDEX IF NOT EXISTS idx_session_meta_placement ON session_meta (placement_kind, placement_scope, placed_at)`,
|
|
72
41
|
`CREATE TABLE IF NOT EXISTS session_event (
|
|
73
42
|
session_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -81,17 +50,10 @@ export const PG_SESSION_SCHEMA = [
|
|
|
81
50
|
)`,
|
|
82
51
|
`CREATE UNIQUE INDEX IF NOT EXISTS uq_session_event_entry ON session_event (session_id, entry_id)`,
|
|
83
52
|
];
|
|
84
|
-
/** Idempotent schema apply for this store's tables (the test calls this; production aggregation is separate). */
|
|
85
53
|
export async function ensureSchema(pool) {
|
|
86
54
|
for (const stmt of PG_SESSION_SCHEMA)
|
|
87
55
|
await pool.query(stmt);
|
|
88
56
|
}
|
|
89
|
-
/**
|
|
90
|
-
* PostgreSQL-backed {@link BaseSessionStorage} — the durable L1 event log for one session.
|
|
91
|
-
*
|
|
92
|
-
* Behaviourally identical to {@link TiDBSessionStorage}; see that file's class doc for the F2/F3 seams. The only
|
|
93
|
-
* differences are dialectal (placeholders, `IS NOT DISTINCT FROM`, jsonb, rowCount, transaction handle).
|
|
94
|
-
*/
|
|
95
57
|
export class PgSessionStorage extends BaseSessionStorage {
|
|
96
58
|
pool;
|
|
97
59
|
sessionId;
|
|
@@ -102,17 +64,14 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
102
64
|
this.sessionId = sessionId;
|
|
103
65
|
this.floorSeq = floorSeq;
|
|
104
66
|
}
|
|
105
|
-
/** @see TiDBSessionStorage.getEpochAnchor — [976] S3 RB-14 pull seam, PG dialect twin
|
|
106
|
-
* (keyset-paged to exhaustion: a bounded scan over restatement-less pre-epoch compactions would
|
|
107
|
-
* misreport an epoch-bearing session as pre-epoch — codex HIGH). */
|
|
108
67
|
async getEpochAnchor() {
|
|
109
68
|
if (this.floorSeq <= 0)
|
|
110
|
-
return undefined;
|
|
69
|
+
return undefined;
|
|
111
70
|
let cursor = this.floorSeq;
|
|
112
71
|
for (;;) {
|
|
113
72
|
const res = await this.pool.query("SELECT seq, payload FROM session_event WHERE session_id = $1 AND seq < $2 AND type IN ('prompt_epoch','compaction') ORDER BY seq DESC LIMIT 100", [this.sessionId, cursor]);
|
|
114
73
|
if (res.rows.length === 0)
|
|
115
|
-
return undefined;
|
|
74
|
+
return undefined;
|
|
116
75
|
for (const r of res.rows) {
|
|
117
76
|
const e = parseEntry(r.payload);
|
|
118
77
|
const d = e.type === "prompt_epoch" ? normalizePromptEpoch(e) : normalizePromptEpoch(e.details?.promptEpoch);
|
|
@@ -122,23 +81,18 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
122
81
|
cursor = Number(res.rows[res.rows.length - 1].seq);
|
|
123
82
|
}
|
|
124
83
|
}
|
|
125
|
-
/** @see TiDBSessionStorage.wake — F3 bounded `[floor..leaf]` rebuild; null if the session does not exist. */
|
|
126
84
|
static async wake(pool, sessionId) {
|
|
127
|
-
const metaRes = await pool.query(
|
|
128
|
-
// #292 C18(TiDB 孪生同形):placement 六列捎带在既有 meta SELECT 上,acquire 热路径零额外往返。
|
|
129
|
-
`SELECT created_at, leaf_id, leaf_seq, ${PLACEMENT_SELECT_COLUMNS} FROM session_meta WHERE session_id = $1`, [sessionId]);
|
|
85
|
+
const metaRes = await pool.query(`SELECT created_at, leaf_id, leaf_seq, ${PLACEMENT_SELECT_COLUMNS} FROM session_meta WHERE session_id = $1`, [sessionId]);
|
|
130
86
|
const meta = metaRes.rows[0];
|
|
131
87
|
if (!meta)
|
|
132
88
|
return null;
|
|
133
89
|
const { floorEntryId, floorSeq } = await PgSessionStorage.computeFloor(pool, sessionId);
|
|
134
90
|
const eventRes = await pool.query("SELECT payload FROM session_event WHERE session_id = $1 AND seq >= $2 ORDER BY seq ASC", [sessionId, floorSeq]);
|
|
135
91
|
const entries = eventRes.rows.map((r) => parseEntry(r.payload));
|
|
136
|
-
// #292 C18:持久化元组骑 core 5.48 的 `SessionMetadata.placement`(TiDB 孪生逐字同义)。
|
|
137
92
|
const placement = placementFromRow(meta);
|
|
138
93
|
const metadata = { id: sessionId, createdAt: toIso(meta.created_at), ...(placement !== undefined ? { placement } : {}) };
|
|
139
94
|
return new PgSessionStorage(pool, sessionId, metadata, entries, meta.leaf_id ?? null, floorEntryId, floorSeq);
|
|
140
95
|
}
|
|
141
|
-
/** The bounded-window floor = last compaction's firstKeptEntryId (and its seq). @see TiDBSessionStorage.computeFloor */
|
|
142
96
|
static async computeFloor(pool, sessionId) {
|
|
143
97
|
const compRes = await pool.query("SELECT payload FROM session_event WHERE session_id = $1 AND type = 'compaction' ORDER BY seq DESC LIMIT 1", [sessionId]);
|
|
144
98
|
if (!compRes.rows[0])
|
|
@@ -148,12 +102,10 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
148
102
|
if (!fk)
|
|
149
103
|
return { floorEntryId: null, floorSeq: 0 };
|
|
150
104
|
const seqRes = await pool.query("SELECT seq FROM session_event WHERE session_id = $1 AND entry_id = $2", [sessionId, fk]);
|
|
151
|
-
// firstKeptEntryId missing from the log (shouldn't happen) → degrade to full load.
|
|
152
105
|
if (!seqRes.rows[0])
|
|
153
106
|
return { floorEntryId: null, floorSeq: 0 };
|
|
154
107
|
return { floorEntryId: fk, floorSeq: Number(seqRes.rows[0].seq) };
|
|
155
108
|
}
|
|
156
|
-
/** Sub-floor lookups (e.g. a label target) aren't in the loaded tail — fall back to the durable log. */
|
|
157
109
|
async getEntry(id) {
|
|
158
110
|
const inMem = await super.getEntry(id);
|
|
159
111
|
if (inMem)
|
|
@@ -169,16 +121,10 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
169
121
|
this.recordEntry(entry);
|
|
170
122
|
}
|
|
171
123
|
async setLeafId(leafId, opts) {
|
|
172
|
-
// A leaf move is itself a persisted `leaf` entry whose parent is the current leaf.
|
|
173
124
|
const leafEntry = this.createLeafEntry(leafId);
|
|
174
125
|
await this.persist(leafEntry, opts);
|
|
175
126
|
this.recordEntry(leafEntry);
|
|
176
127
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Atomically: CAS the meta leaf (+bump leaf_seq), then append the event at the new seq.
|
|
179
|
-
* The `(session_id, seq)` primary key is the backstop — a racing writer that grabbed the same
|
|
180
|
-
* seq makes our INSERT fail with a duplicate-key (23505), which we translate to a conflict too.
|
|
181
|
-
*/
|
|
182
128
|
async persist(entry, opts) {
|
|
183
129
|
const newLeaf = leafIdAfterEntry(entry);
|
|
184
130
|
const conditional = opts !== undefined && opts.expectedLeafId !== undefined;
|
|
@@ -195,17 +141,16 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
195
141
|
}
|
|
196
142
|
const seqRes = await conn.query("SELECT leaf_seq, owner FROM session_meta WHERE session_id = $1", [this.sessionId]);
|
|
197
143
|
const seq = Number(seqRes.rows[0].leaf_seq);
|
|
198
|
-
const commitOwner = seqRes.rows[0].owner ?? null;
|
|
144
|
+
const commitOwner = seqRes.rows[0].owner ?? null;
|
|
199
145
|
await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7)", [this.sessionId, seq, entry.id, entry.parentId ?? null, entry.type, pgSafeJsonStringify(entry), new Date(entry.timestamp)]);
|
|
200
146
|
await conn.query("COMMIT");
|
|
201
|
-
emitLeafAdvance(this.sessionId, newLeaf, commitOwner, seq);
|
|
147
|
+
emitLeafAdvance(this.sessionId, newLeaf, commitOwner, seq);
|
|
202
148
|
}
|
|
203
149
|
catch (err) {
|
|
204
150
|
try {
|
|
205
151
|
await conn.query("ROLLBACK");
|
|
206
152
|
}
|
|
207
153
|
catch {
|
|
208
|
-
/* ignore rollback error */
|
|
209
154
|
}
|
|
210
155
|
if (err instanceof SessionError)
|
|
211
156
|
throw err;
|
|
@@ -219,19 +164,10 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
219
164
|
}
|
|
220
165
|
}
|
|
221
166
|
}
|
|
222
|
-
// ────────────────────────────────── PG twin of TiDBSessionStore ──────────────────────────────────
|
|
223
|
-
/**
|
|
224
|
-
* PostgreSQL-backed {@link SessionStore} — the PG twin of {@link TiDBSessionStore}. Maps the
|
|
225
|
-
* `SessionStore` surface (acquire / register / ownerOf / touch / release) onto {@link PgSessionStorage}.
|
|
226
|
-
*/
|
|
227
167
|
export class PgSessionStore {
|
|
228
168
|
pool;
|
|
229
|
-
/** #270 车1:托管留存声明(TiDB 孪生同格)。读法见 `retention-store-sql.ts` 的 {@link MANAGED_RETENTION}。 */
|
|
230
169
|
retention = MANAGED_RETENTION;
|
|
231
|
-
/** #292 C18(core 5.48.0 design/323):placement 声明(TiDB 孪生同格,义务逐条见
|
|
232
|
-
* `src/plugins/session-placement.ts` 顶注)。 */
|
|
233
170
|
placements = SQL_SESSION_PLACEMENTS;
|
|
234
|
-
/** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one. */
|
|
235
171
|
pending = new Map();
|
|
236
172
|
constructor(pool) {
|
|
237
173
|
this.pool = pool;
|
|
@@ -239,15 +175,11 @@ export class PgSessionStore {
|
|
|
239
175
|
async acquire(sessionId, opts) {
|
|
240
176
|
if (!sessionId) {
|
|
241
177
|
const id = uuidv7();
|
|
242
|
-
await this.register(id, null, opts?.placement);
|
|
178
|
+
await this.register(id, null, opts?.placement);
|
|
243
179
|
return this.load(id, false, opts);
|
|
244
180
|
}
|
|
245
|
-
// design/114 Phase3 (F1): a `requireExisting` acquire NEVER creates and can REJECT (not_found) — it MUST bypass
|
|
246
|
-
// the shared create-dedup `pending`, else a concurrent create-capable waiter awaiting that same promise would
|
|
247
|
-
// inherit the not_found rejection instead of creating-on-miss. (Twin of TiDBSessionStore.acquire.)
|
|
248
181
|
if (opts?.requireExisting)
|
|
249
182
|
return this.load(sessionId, true, opts);
|
|
250
|
-
// #292:准入按调用方逐一判(共享 promise 不许让 claim 形继承可信创建方的准入;TiDB 孪生同义)。
|
|
251
183
|
const inflight = this.pending.get(sessionId);
|
|
252
184
|
if (inflight) {
|
|
253
185
|
const acquired = await inflight;
|
|
@@ -261,39 +193,29 @@ export class PgSessionStore {
|
|
|
261
193
|
async load(sessionId, requireExisting = false, opts) {
|
|
262
194
|
let storage = await PgSessionStorage.wake(this.pool, sessionId);
|
|
263
195
|
if (storage) {
|
|
264
|
-
assertPlacementAdmission(sessionId, (await storage.getMetadata()).placement, opts);
|
|
196
|
+
assertPlacementAdmission(sessionId, (await storage.getMetadata()).placement, opts);
|
|
265
197
|
return { session: new StoredSession(storage), sessionId };
|
|
266
198
|
}
|
|
267
|
-
// design/114 Phase3: `requireExisting` ⇒ FAIL LOUD on a genuinely missing session (no session_meta row),
|
|
268
|
-
// never the silent create-on-miss. core's prepare-task maps this `not_found` to `resume.session_not_found`.
|
|
269
199
|
if (requireExisting)
|
|
270
200
|
throw new SessionError("not_found", `session ${sessionId} not found`);
|
|
271
201
|
await this.register(sessionId, null, opts?.placement);
|
|
272
202
|
storage = await PgSessionStorage.wake(this.pool, sessionId);
|
|
273
|
-
// 刚 register 完却唤不醒 = 并发 purge 抢在两步之间(TiDB 孪生同款注:旧形的 `storage!` 断言会炸成
|
|
274
|
-
// 一条无信息的 TypeError)。
|
|
275
203
|
if (!storage) {
|
|
276
204
|
throw new SessionError("storage", `session ${sessionId} vanished between register and wake (a concurrent purge won the race) — retry`);
|
|
277
205
|
}
|
|
278
|
-
// #292 镜像方向(pre-claim squat):回读缺失/异组 ⇒ 响亮拒(TiDB 孪生同义)。
|
|
279
206
|
assertPlacementAdmission(sessionId, (await storage.getMetadata()).placement, opts);
|
|
280
207
|
return { session: new StoredSession(storage), sessionId };
|
|
281
208
|
}
|
|
282
|
-
/** Idempotently create a session_meta row with an owner. Existing owner is never overwritten.
|
|
283
|
-
* #292:`placement` 与行同一条 INSERT;`DO NOTHING` 臂 ⇒ 已存在行的元组永不被重写(first-write 不可变)。 */
|
|
284
209
|
async register(sessionId, owner, placement) {
|
|
285
210
|
const now = new Date();
|
|
286
211
|
await this.pool.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, " +
|
|
287
212
|
"placement_kind, placement_scope, placement_parent_session_id, placement_root_session_id, placement_handle, placed_at) " +
|
|
288
213
|
"VALUES ($1,$2,$3,$4,NULL,0,$5,$6,$7,$8,$9,$10) ON CONFLICT (session_id) DO NOTHING", [sessionId, owner, now, now, ...placementInsertValues(placement, now)]);
|
|
289
214
|
}
|
|
290
|
-
/** #292 义务 6 的廉价探针(TiDB 孪生 {@link TiDBSessionStore.placementOf} 同义)。 */
|
|
291
215
|
async placementOf(sessionId) {
|
|
292
216
|
const res = await this.pool.query(`SELECT ${PLACEMENT_SELECT_COLUMNS} FROM session_meta WHERE session_id = $1`, [sessionId]);
|
|
293
217
|
return placementFromRow(res.rows[0]);
|
|
294
218
|
}
|
|
295
|
-
/** #292 义务 5 —— placed 分区枚举(年龄锚/上界/tupleIncomplete 三条读法与 TiDB 孪生逐字同义,
|
|
296
|
-
* 见 {@link TiDBSessionStore.listPlaced})。 */
|
|
297
219
|
async listPlaced(kind, opts) {
|
|
298
220
|
const params = [kind];
|
|
299
221
|
const ph = (v) => {
|
|
@@ -309,7 +231,6 @@ export class PgSessionStore {
|
|
|
309
231
|
const res = await this.pool.query(sql, params);
|
|
310
232
|
return res.rows.map((r) => placedRowFromRow(String(r.session_id), r, r.updated_at instanceof Date ? r.updated_at.getTime() : undefined));
|
|
311
233
|
}
|
|
312
|
-
/** Owner principal of a session, `null` if registered ownerless, `undefined` if no such session. */
|
|
313
234
|
async ownerOf(sessionId) {
|
|
314
235
|
const res = await this.pool.query("SELECT owner FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
315
236
|
if (res.rows.length === 0)
|
|
@@ -322,34 +243,26 @@ export class PgSessionStore {
|
|
|
322
243
|
sessionId,
|
|
323
244
|
]);
|
|
324
245
|
}
|
|
325
|
-
/** E18 — the session's current leaf SessionTreeEntry.id (session_meta.leaf_id). @see TiDBSessionStore.getLeafId
|
|
326
|
-
* (single SELECT, no acquire/lock, cache-bypassing — safe to read mid-run for the resume-at turn-capture). */
|
|
327
246
|
async getLeafId(sessionId) {
|
|
328
247
|
const r = await this.pool.query("SELECT leaf_id FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
329
248
|
return r.rows[0] ? (r.rows[0].leaf_id ?? null) : null;
|
|
330
249
|
}
|
|
331
|
-
/** [1196] 六轮复审:owner+leafId 单行原子快照(@see TiDBSessionStore.getHead——混世代组合防)。 */
|
|
332
250
|
async getHead(sessionId) {
|
|
333
251
|
const r = await this.pool.query("SELECT owner, leaf_id FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
334
252
|
if (r.rows.length === 0)
|
|
335
253
|
return undefined;
|
|
336
254
|
return { owner: r.rows[0].owner ?? null, leafId: r.rows[0].leaf_id ?? null };
|
|
337
255
|
}
|
|
338
|
-
/** Durable history is retained; just drop any in-flight cache entry.
|
|
339
|
-
* 🔴 #292 义务 4:**placed 会话例外 —— release 是真删除**(理由/ABA 条件删/失败上浮三条读法逐字见
|
|
340
|
-
* {@link TiDBSessionStore.release},本腿是它的 PG 孪生)。 */
|
|
341
256
|
async release(sessionId) {
|
|
342
257
|
this.pending.delete(sessionId);
|
|
343
258
|
const placed = await this.placementOf(sessionId);
|
|
344
259
|
if (placed === undefined)
|
|
345
|
-
return;
|
|
260
|
+
return;
|
|
346
261
|
await this.deletePlacedSession(sessionId);
|
|
347
262
|
}
|
|
348
|
-
/** @see TiDBSessionStore.forget — 丢引用不动 durable 史(preflight 纪律的落点;PG 孪生)。 */
|
|
349
263
|
forget(sessionId) {
|
|
350
264
|
this.pending.delete(sessionId);
|
|
351
265
|
}
|
|
352
|
-
/** @see TiDBSessionStore.deletePlacedSession — PG 孪生(条件删门 + meta 先删 + 失败上浮同义)。 */
|
|
353
266
|
async deletePlacedSession(sessionId) {
|
|
354
267
|
const conn = await this.pool.connect();
|
|
355
268
|
try {
|
|
@@ -364,7 +277,6 @@ export class PgSessionStore {
|
|
|
364
277
|
await conn.query("ROLLBACK");
|
|
365
278
|
}
|
|
366
279
|
catch {
|
|
367
|
-
/* ignore */
|
|
368
280
|
}
|
|
369
281
|
throw err instanceof SessionError
|
|
370
282
|
? err
|
|
@@ -374,29 +286,21 @@ export class PgSessionStore {
|
|
|
374
286
|
conn.release();
|
|
375
287
|
}
|
|
376
288
|
}
|
|
377
|
-
/** #292:placed id 拒绝 session-sync 的整段换装(TiDB 孪生 {@link TiDBSessionStore.assertNotPlaced} 同义)。 */
|
|
378
289
|
async assertNotPlaced(sessionId, what) {
|
|
379
290
|
const placed = await this.placementOf(sessionId);
|
|
380
291
|
if (placed !== undefined) {
|
|
381
292
|
throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${placed.kind} placement partition — a session-sync ${what} cannot overwrite a delegation transcript`);
|
|
382
293
|
}
|
|
383
294
|
}
|
|
384
|
-
// ───────────────────────── SessionRepo seam (§0.5) — PG twin of TiDBSessionStore ─────────────────────────
|
|
385
|
-
// Behaviourally identical to {@link TiDBSessionStore.listSessions/fork/deleteSession}; only the SQL dialect
|
|
386
|
-
// differs ($n placeholders, jsonb, rowCount, BEGIN/COMMIT handle). Kept in lock-step by the shared real-DB suite.
|
|
387
|
-
/** @see TiDBSessionStore.listSessions — session identity from session_meta (owner-filtered), task_run LEFT JOIN for preview. */
|
|
388
295
|
async listSessions(opts) {
|
|
389
296
|
const params = [];
|
|
390
297
|
const ph = (v) => {
|
|
391
298
|
params.push(v);
|
|
392
299
|
return `$${params.length}`;
|
|
393
300
|
};
|
|
394
|
-
// 🔴 #292 义务 2:placed 会话不上宿主枚举面(TiDB 孪生逐字同义,理由见那里)。
|
|
395
301
|
const where = ["sm.placement_kind IS NULL"];
|
|
396
|
-
// [4198] 窄互认第二臂的第三条件(TiDB 孪生逐字同义):`includeUnowned` ⇒「本人的 ∪ 无主的」。
|
|
397
302
|
if (opts.owner)
|
|
398
303
|
where.push(opts.includeUnowned ? `(sm.owner = ${ph(opts.owner)} OR sm.owner IS NULL)` : `sm.owner = ${ph(opts.owner)}`);
|
|
399
|
-
// `?q=` — the PG twin of the TiDB EXISTS predicate (LOWER-LIKE + ESCAPE, dialect-mirrored).
|
|
400
304
|
if (opts.q)
|
|
401
305
|
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 '\\')`);
|
|
402
306
|
if (opts.cursor) {
|
|
@@ -409,7 +313,7 @@ export class PgSessionStore {
|
|
|
409
313
|
" COALESCE(r.run_count, 0) AS run_count, latest.objective_preview, latest.status, latest.task_id AS last_run_id " +
|
|
410
314
|
"FROM session_meta sm " +
|
|
411
315
|
"LEFT JOIN (SELECT session_id, COUNT(*) AS run_count FROM task_run GROUP BY session_id) r ON r.session_id = sm.session_id " +
|
|
412
|
-
"LEFT JOIN (SELECT DISTINCT ON (session_id) session_id, objective_preview, status, task_id FROM task_run " +
|
|
316
|
+
"LEFT JOIN (SELECT DISTINCT ON (session_id) session_id, objective_preview, status, task_id FROM task_run " +
|
|
413
317
|
" ORDER BY session_id, created_at DESC, task_id DESC) latest ON latest.session_id = sm.session_id " +
|
|
414
318
|
(where.length ? "WHERE " + where.join(" AND ") + " " : "") +
|
|
415
319
|
`ORDER BY sm.updated_at DESC, sm.session_id DESC LIMIT ${ph(opts.limit)}`;
|
|
@@ -422,11 +326,10 @@ export class PgSessionStore {
|
|
|
422
326
|
runCount: Number(r.run_count),
|
|
423
327
|
objectivePreview: r.objective_preview ?? null,
|
|
424
328
|
lastStatus: r.status == null ? "" : String(r.status),
|
|
425
|
-
lastRunId: r.last_run_id ?? null,
|
|
329
|
+
lastRunId: r.last_run_id ?? null,
|
|
426
330
|
title: r.title ?? null,
|
|
427
331
|
}));
|
|
428
332
|
}
|
|
429
|
-
/** @see TiDBSessionStore.probeTitle — the PG twin. */
|
|
430
333
|
async probeTitle(sessionId) {
|
|
431
334
|
const res = await this.pool.query("SELECT title FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
432
335
|
const row = res.rows[0];
|
|
@@ -434,19 +337,15 @@ export class PgSessionStore {
|
|
|
434
337
|
return "none";
|
|
435
338
|
return row.title == null ? "untitled" : "titled";
|
|
436
339
|
}
|
|
437
|
-
/** @see TiDBSessionStore.setTitleIfNull — the PG twin (write-once auto-title). */
|
|
438
340
|
async setTitleIfNull(sessionId, title) {
|
|
439
341
|
const res = await this.pool.query("UPDATE session_meta SET title = $1 WHERE session_id = $2 AND title IS NULL", [title, sessionId]);
|
|
440
342
|
return (res.rowCount ?? 0) > 0;
|
|
441
343
|
}
|
|
442
|
-
/** @see TiDBSessionStore.fork — copy the WHOLE durable history (full session_event log, compaction floor bypassed,
|
|
443
|
-
* NOT wake's bounded tail) to a NEW uuidv7 session, re-seq dense from 0, ids/parents verbatim, owner=caller. */
|
|
444
344
|
async fork(sourceId, owner) {
|
|
445
345
|
const metaRes = await this.pool.query("SELECT leaf_id FROM session_meta WHERE session_id = $1", [sourceId]);
|
|
446
346
|
if (!metaRes.rows[0])
|
|
447
347
|
return null;
|
|
448
348
|
const leafId = metaRes.rows[0].leaf_id ?? null;
|
|
449
|
-
// FULL durable history (compaction floor bypassed) — every entry, oldest first. node-pg parses jsonb → object.
|
|
450
349
|
const eventRes = await this.pool.query("SELECT entry_id, parent_id, type, payload, ts FROM session_event WHERE session_id = $1 ORDER BY seq ASC", [sourceId]);
|
|
451
350
|
const rows = eventRes.rows;
|
|
452
351
|
const newId = uuidv7();
|
|
@@ -456,8 +355,6 @@ export class PgSessionStore {
|
|
|
456
355
|
await conn.query("BEGIN");
|
|
457
356
|
for (let seq = 0; seq < rows.length; seq++) {
|
|
458
357
|
const r = rows[seq];
|
|
459
|
-
// Copy the payload blob VERBATIM; re-stringify only an object so $6::jsonb binds a JSON string (node-pg
|
|
460
|
-
// returns jsonb already parsed, so a string here means it was stored stringly — rare; pass either through).
|
|
461
358
|
const payload = typeof r.payload === "string" ? r.payload : pgSafeJsonStringify(r.payload);
|
|
462
359
|
await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7)", [newId, seq, r.entry_id, r.parent_id ?? null, r.type, payload, r.ts]);
|
|
463
360
|
}
|
|
@@ -470,7 +367,6 @@ export class PgSessionStore {
|
|
|
470
367
|
await conn.query("ROLLBACK");
|
|
471
368
|
}
|
|
472
369
|
catch {
|
|
473
|
-
/* ignore */
|
|
474
370
|
}
|
|
475
371
|
throw err instanceof SessionError ? err : new SessionError("storage", `fork of session ${sourceId} failed`, err instanceof Error ? err : undefined);
|
|
476
372
|
}
|
|
@@ -478,60 +374,44 @@ export class PgSessionStore {
|
|
|
478
374
|
conn.release();
|
|
479
375
|
}
|
|
480
376
|
}
|
|
481
|
-
/** @see TiDBSessionStore.exportEntries — 2c session-sync EXPORT: the FULL durable session_event log (compaction floor
|
|
482
|
-
* bypassed, NOT wake's tail) as `SessionTreeEntry[]`, oldest-first, verbatim. null if the source is unknown. */
|
|
483
377
|
async exportEntries(sessionId) {
|
|
484
378
|
const metaRes = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
485
379
|
if (metaRes.rows.length === 0)
|
|
486
380
|
return null;
|
|
487
381
|
const eventRes = await this.pool.query("SELECT payload FROM session_event WHERE session_id = $1 ORDER BY seq ASC", [sessionId]);
|
|
488
|
-
// node-pg parses jsonb → object already (parseEntry passes objects through; a string would be rare/legacy).
|
|
489
382
|
return eventRes.rows.map((r) => parseEntry(r.payload));
|
|
490
383
|
}
|
|
491
|
-
/** @see TiDBSessionStore.listEntryIds — 2c P1d-α (PULL streaming): the IDS-ONLY projection of the full durable log,
|
|
492
|
-
* oldest-first (`SELECT entry_id … ORDER BY seq ASC`), no payloads. null if the source is unknown. */
|
|
493
384
|
async listEntryIds(sessionId) {
|
|
494
385
|
const metaRes = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
495
386
|
if (metaRes.rows.length === 0)
|
|
496
|
-
return null;
|
|
387
|
+
return null;
|
|
497
388
|
const res = await this.pool.query("SELECT entry_id FROM session_event WHERE session_id = $1 ORDER BY seq ASC", [sessionId]);
|
|
498
389
|
return res.rows.map((r) => String(r.entry_id));
|
|
499
390
|
}
|
|
500
|
-
/** @see TiDBSessionStore.exportEntriesStream — 2c P1d-α (PULL streaming): KEYSET-paged async generator over the full
|
|
501
|
-
* durable log (node-pg also buffers a plain SELECT → page it). `… WHERE seq > cursor ORDER BY seq ASC LIMIT K`
|
|
502
|
-
* (K = batchSize ?? 500), advancing the cursor to the last seq each batch, ONE pooled `pool.query` per batch (no
|
|
503
|
-
* held connection across the stream → a slow consumer can't pin one). Start cursor = afterSeq ?? -1 (0-based dense).
|
|
504
|
-
* Resolves to null (NOT an empty iterable) for a missing session — eager `session_meta` probe (BIGINT seq comes
|
|
505
|
-
* back as a STRING in node-pg → always Number(...) the cursor advance). */
|
|
506
391
|
async exportEntriesStream(sessionId, opts) {
|
|
507
392
|
const metaRes = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
508
393
|
if (metaRes.rows.length === 0)
|
|
509
|
-
return null;
|
|
394
|
+
return null;
|
|
510
395
|
const pool = this.pool;
|
|
511
396
|
const batchSize = opts?.batchSize ?? 500;
|
|
512
397
|
async function* gen() {
|
|
513
|
-
let cursor = opts?.afterSeq ?? -1;
|
|
398
|
+
let cursor = opts?.afterSeq ?? -1;
|
|
514
399
|
for (;;) {
|
|
515
400
|
const res = await pool.query("SELECT seq, payload FROM session_event WHERE session_id = $1 AND seq > $2 ORDER BY seq ASC LIMIT $3", [sessionId, cursor, batchSize]);
|
|
516
401
|
for (const r of res.rows)
|
|
517
402
|
yield parseEntry(r.payload);
|
|
518
403
|
if (res.rows.length < batchSize)
|
|
519
|
-
return;
|
|
520
|
-
cursor = Number(res.rows[res.rows.length - 1].seq);
|
|
404
|
+
return;
|
|
405
|
+
cursor = Number(res.rows[res.rows.length - 1].seq);
|
|
521
406
|
}
|
|
522
407
|
}
|
|
523
408
|
return gen();
|
|
524
409
|
}
|
|
525
|
-
/** @see TiDBSessionStore.importEntries — 2c session-sync IMPORT: core `validateEntriesForImport` gate FIRST (fail-closed),
|
|
526
|
-
* then persist its verbatim output (re-seq dense from 0, ids/parents/payload/ts unchanged) in ONE txn with
|
|
527
|
-
* session_meta LAST as the commit point; owner = the RE-STAMPED authenticated principal (never the bundle's). */
|
|
528
410
|
async importEntries(sessionId, owner, entries) {
|
|
529
|
-
const validated = validateEntriesForImport(entries);
|
|
411
|
+
const validated = validateEntriesForImport(entries);
|
|
530
412
|
const conn = await this.pool.connect();
|
|
531
413
|
try {
|
|
532
414
|
await conn.query("BEGIN");
|
|
533
|
-
// Shared insert body (entries re-seq dense from 0 + session_meta LAST = commit point). Plain INSERT (fresh-only);
|
|
534
|
-
// replaceEntries is the idempotent-over-existing path (DELETEs first inside the same txn). @see TiDB twin.
|
|
535
415
|
await this.insertValidatedLog(conn, sessionId, owner, validated);
|
|
536
416
|
await conn.query("COMMIT");
|
|
537
417
|
}
|
|
@@ -540,37 +420,28 @@ export class PgSessionStore {
|
|
|
540
420
|
await conn.query("ROLLBACK");
|
|
541
421
|
}
|
|
542
422
|
catch {
|
|
543
|
-
/* ignore */
|
|
544
423
|
}
|
|
545
424
|
if (err instanceof SessionError)
|
|
546
|
-
throw err;
|
|
425
|
+
throw err;
|
|
547
426
|
throw new SessionError("storage", `import of session ${sessionId} failed`, err instanceof Error ? err : undefined);
|
|
548
427
|
}
|
|
549
428
|
finally {
|
|
550
429
|
conn.release();
|
|
551
430
|
}
|
|
552
431
|
}
|
|
553
|
-
/** @see TiDBSessionStore.replaceEntries — 2c (§7/§8) IDEMPOTENT REPLACE: core `validateEntriesForImport` gate FIRST
|
|
554
|
-
* (fail-closed), then DELETE events + the owner-guarded meta delete (`owner IS NOT DISTINCT FROM $n` = the PG `<=>`,
|
|
555
|
-
* defense-in-depth), then re-insert via the SHARED body (entries + session_meta LAST as the commit point), ALL in
|
|
556
|
-
* ONE txn. Safe to run OVER an existing session (fast-forward / §8 retry) — unlike importEntries' fresh-only INSERT. */
|
|
557
432
|
async replaceEntries(sessionId, owner, entries) {
|
|
558
|
-
await this.assertNotPlaced(sessionId, "replace");
|
|
559
|
-
const validated = validateEntriesForImport(entries);
|
|
433
|
+
await this.assertNotPlaced(sessionId, "replace");
|
|
434
|
+
const validated = validateEntriesForImport(entries);
|
|
560
435
|
const conn = await this.pool.connect();
|
|
561
436
|
try {
|
|
562
437
|
await conn.query("BEGIN");
|
|
563
|
-
// #292(codex R1-[high] 同族窗):事务内锁读 + DELETE 的 `placement_kind IS NULL` 硬条件
|
|
564
|
-
// (TiDB 孪生逐字同义 —— placed 行 owner 恒 NULL,而 replace(owner=null) 的属主门恰好匹配它)。
|
|
565
438
|
const placedRes = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = $1 FOR UPDATE", [sessionId]);
|
|
566
439
|
if (placedRes.rows[0]?.placement_kind != null) {
|
|
567
440
|
await conn.query("ROLLBACK");
|
|
568
441
|
throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${placedRes.rows[0].placement_kind} placement partition — a session-sync replace cannot overwrite a delegation transcript`);
|
|
569
442
|
}
|
|
570
|
-
// Purge first (idempotent: a missing session deletes 0 rows). Events, then the owner-guarded meta delete.
|
|
571
443
|
await conn.query("DELETE FROM session_event WHERE session_id = $1", [sessionId]);
|
|
572
444
|
await conn.query("DELETE FROM session_meta WHERE session_id = $1 AND owner IS NOT DISTINCT FROM $2 AND placement_kind IS NULL", [sessionId, owner]);
|
|
573
|
-
// …then re-insert the validated log (entries + meta-as-commit-point) via the SHARED body.
|
|
574
445
|
await this.insertValidatedLog(conn, sessionId, owner, validated);
|
|
575
446
|
await conn.query("COMMIT");
|
|
576
447
|
}
|
|
@@ -579,23 +450,19 @@ export class PgSessionStore {
|
|
|
579
450
|
await conn.query("ROLLBACK");
|
|
580
451
|
}
|
|
581
452
|
catch {
|
|
582
|
-
/* ignore */
|
|
583
453
|
}
|
|
584
454
|
if (err instanceof SessionError)
|
|
585
|
-
throw err;
|
|
455
|
+
throw err;
|
|
586
456
|
throw new SessionError("storage", `replace of session ${sessionId} failed`, err instanceof Error ? err : undefined);
|
|
587
457
|
}
|
|
588
458
|
finally {
|
|
589
459
|
conn.release();
|
|
590
460
|
}
|
|
591
461
|
}
|
|
592
|
-
/** @see TiDBSessionStore.insertValidatedLog — shared insert half of importEntries / replaceEntries (ONE source, no
|
|
593
|
-
* drift): entries re-seq dense from 0 (ids/parents/payload/ts verbatim), THEN session_meta LAST as the commit point.
|
|
594
|
-
* Runs inside the caller's open txn (`conn` already in BEGIN); the caller owns COMMIT/ROLLBACK + the validate gate. */
|
|
595
462
|
async insertValidatedLog(conn, sessionId, owner, validated) {
|
|
596
463
|
let leafId = null;
|
|
597
464
|
for (const e of validated)
|
|
598
|
-
leafId = leafIdAfterEntry(e);
|
|
465
|
+
leafId = leafIdAfterEntry(e);
|
|
599
466
|
const now = new Date();
|
|
600
467
|
for (let seq = 0; seq < validated.length; seq++) {
|
|
601
468
|
const e = validated[seq];
|
|
@@ -603,21 +470,13 @@ export class PgSessionStore {
|
|
|
603
470
|
}
|
|
604
471
|
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq) VALUES ($1,$2,$3,$4,$5,$6)", [sessionId, owner, now, now, leafId, validated.length]);
|
|
605
472
|
}
|
|
606
|
-
/** @see TiDBSessionStore.beginImportStaging — 2c P1d-β PUSH staging: stream batches into the shadow id
|
|
607
|
-
* `${realSessionId}#stg-${token}` (route-minted token), then commit (atomic swap) / abort. */
|
|
608
473
|
beginImportStaging(realSessionId, token) {
|
|
609
474
|
return new PgStagingHandle(this.pool, realSessionId, stagingIdFor(realSessionId, token));
|
|
610
475
|
}
|
|
611
|
-
/** @see TiDBSessionStore.readStagedEntries — 2c P1d-β staged-row inspection: the staged rows under `stagingId`, oldest-
|
|
612
|
-
* first, no session_meta probe (a staging id has no meta row). No longer the import gate (the per-line
|
|
613
|
-
* StreamingImportValidator validates during the stream); a tests/diagnostics seam. Returns [] for an empty/unknown id. */
|
|
614
476
|
async readStagedEntries(stagingId) {
|
|
615
477
|
const res = await this.pool.query("SELECT payload FROM session_event WHERE session_id = $1 ORDER BY seq ASC", [stagingId]);
|
|
616
478
|
return res.rows.map((r) => parseEntry(r.payload));
|
|
617
479
|
}
|
|
618
|
-
/** @see TiDBSessionStore.sweepStagingSessions — reaper for abandoned staging sessions: DELETE session_event rows
|
|
619
|
-
* under a `%#stg-%` id with NO session_meta whose oldest row is older than the grace window (in-flight stays fresh).
|
|
620
|
-
* Returns the affected row count. Driven by main.ts's reaper. */
|
|
621
480
|
async sweepStagingSessions() {
|
|
622
481
|
const cutoff = new Date(Date.now() - STAGING_GC_GRACE_MS);
|
|
623
482
|
const cands = await this.pool.query("SELECT se.session_id FROM session_event se LEFT JOIN session_meta sm ON sm.session_id = se.session_id " +
|
|
@@ -630,9 +489,6 @@ export class PgSessionStore {
|
|
|
630
489
|
}
|
|
631
490
|
return removed;
|
|
632
491
|
}
|
|
633
|
-
/** @see TiDBSessionStore.deleteSession — purge session_meta + session_event; idempotent (false if absent).
|
|
634
|
-
* Owner-guarded on session_meta.owner (`IS NOT DISTINCT FROM` = the PG `<=>`); the event purge is gated on the
|
|
635
|
-
* owner-matched meta delete (session_event has no owner column). */
|
|
636
492
|
async deleteSession(sessionId, owner) {
|
|
637
493
|
const conn = await this.pool.connect();
|
|
638
494
|
try {
|
|
@@ -648,7 +504,6 @@ export class PgSessionStore {
|
|
|
648
504
|
await conn.query("ROLLBACK");
|
|
649
505
|
}
|
|
650
506
|
catch {
|
|
651
|
-
/* ignore */
|
|
652
507
|
}
|
|
653
508
|
throw err;
|
|
654
509
|
}
|
|
@@ -663,28 +518,18 @@ export class PgSessionStore {
|
|
|
663
518
|
this.pending.clear();
|
|
664
519
|
}
|
|
665
520
|
}
|
|
666
|
-
/**
|
|
667
|
-
* 2c session-sync P1d-β — the PG twin of {@link TiDBStagingHandle} (behaviourally identical; only the SQL dialect
|
|
668
|
-
* differs: `$n` placeholders, `$n::jsonb`, `ON CONFLICT (session_id, entry_id) DO NOTHING`, rowCount, FOR UPDATE,
|
|
669
|
-
* BEGIN/COMMIT handle). Streams entry batches into a shadow session_event partition, then atomically RENAMEs them
|
|
670
|
-
* onto the real session id in ONE txn with session_meta LAST = the commit point.
|
|
671
|
-
*/
|
|
672
521
|
class PgStagingHandle {
|
|
673
522
|
pool;
|
|
674
523
|
realSessionId;
|
|
675
524
|
stagingId;
|
|
676
525
|
nextSeq = 0;
|
|
677
|
-
seen = new Set();
|
|
526
|
+
seen = new Set();
|
|
678
527
|
constructor(pool, realSessionId, stagingId) {
|
|
679
528
|
this.pool = pool;
|
|
680
529
|
this.realSessionId = realSessionId;
|
|
681
530
|
this.stagingId = stagingId;
|
|
682
531
|
}
|
|
683
|
-
/** @see TiDBStagingHandle.appendBatch — DENSE re-seq (skip already-staged ids), idempotent ON CONFLICT (session_id,
|
|
684
|
-
* entry_id) DO NOTHING, one txn/batch. */
|
|
685
532
|
async appendBatch(entries) {
|
|
686
|
-
// ONE multi-row INSERT per batch (bounded) — @see TiDBStagingHandle.appendBatch. Build $-placeholders for the NEW
|
|
687
|
-
// rows (skip already-staged ids → dense seqs), ON CONFLICT (session_id, entry_id) DO NOTHING as the durable backstop.
|
|
688
533
|
const params = [];
|
|
689
534
|
const tuples = [];
|
|
690
535
|
for (const e of entries) {
|
|
@@ -692,7 +537,7 @@ class PgStagingHandle {
|
|
|
692
537
|
continue;
|
|
693
538
|
const b = params.length;
|
|
694
539
|
tuples.push(`($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6}::jsonb,$${b + 7})`);
|
|
695
|
-
params.push(this.stagingId, this.nextSeq, e.id, e.parentId ?? null, e.type, pgSafeJsonStringify(e), new Date()
|
|
540
|
+
params.push(this.stagingId, this.nextSeq, e.id, e.parentId ?? null, e.type, pgSafeJsonStringify(e), new Date());
|
|
696
541
|
this.seen.add(e.id);
|
|
697
542
|
this.nextSeq++;
|
|
698
543
|
}
|
|
@@ -706,15 +551,12 @@ class PgStagingHandle {
|
|
|
706
551
|
throw err instanceof SessionError ? err : new SessionError("storage", `staged append for ${this.realSessionId} failed`, err instanceof Error ? err : undefined);
|
|
707
552
|
}
|
|
708
553
|
}
|
|
709
|
-
/** @see TiDBStagingHandle.commit — the atomic swap: in-commit re-classify (FOR UPDATE) → purge dst → rename
|
|
710
|
-
* staging→real → session_meta LAST. Throws SyncConflictError on an unconsented fork/stale flip. */
|
|
711
554
|
async commit(owner, opts) {
|
|
712
555
|
const conn = await this.pool.connect();
|
|
713
556
|
try {
|
|
714
557
|
await conn.query("BEGIN");
|
|
715
558
|
const staged = await conn.query("SELECT entry_id FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE", [this.stagingId]);
|
|
716
559
|
const dst = await conn.query("SELECT entry_id FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE", [this.realSessionId]);
|
|
717
|
-
// `placement_kind` 捎带在存在性探针上(#292:placed 目的地拒整段换装;TiDB 孪生同形)。
|
|
718
560
|
const dstMeta = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = $1", [this.realSessionId]);
|
|
719
561
|
if (dstMeta.rows[0]?.placement_kind != null) {
|
|
720
562
|
await conn.query("ROLLBACK");
|
|
@@ -727,8 +569,6 @@ class PgStagingHandle {
|
|
|
727
569
|
await conn.query("ROLLBACK");
|
|
728
570
|
throw new SyncConflictError(this.realSessionId, rel);
|
|
729
571
|
}
|
|
730
|
-
// 🔴 [2557] A5/F2(2026-08-05):fast_forward 的**载荷**判据(tidb twin 同注)——共享段分叉 ⇒ 409,
|
|
731
|
-
// 除非显式 overwrite-dst;载荷只在这条臂上读,行已被 FOR UPDATE 锁住,同 txn 重读是一致读。
|
|
732
572
|
if (rel.relation === "fast_forward" && opts?.resolution !== "overwrite-dst" && dstIds !== null && dstIds.length > 0) {
|
|
733
573
|
const dstFull = await conn.query("SELECT payload FROM session_event WHERE session_id = $1 ORDER BY seq ASC", [this.realSessionId]);
|
|
734
574
|
const stagedFull = await conn.query("SELECT entry_id, payload FROM session_event WHERE session_id = $1 ORDER BY seq ASC", [this.stagingId]);
|
|
@@ -750,9 +590,6 @@ class PgStagingHandle {
|
|
|
750
590
|
const now = new Date();
|
|
751
591
|
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq) VALUES ($1,$2,$3,$4,$5,$6) " +
|
|
752
592
|
"ON CONFLICT (session_id) DO UPDATE SET owner = EXCLUDED.owner, updated_at = EXCLUDED.updated_at, leaf_id = EXCLUDED.leaf_id, leaf_seq = EXCLUDED.leaf_seq", [this.realSessionId, owner, now, now, leafId, srcIds.length]);
|
|
753
|
-
// 🔴 #292(codex R1-[high]):**提交前的最后一道门** —— upsert 之后这一行必被本事务锁住,故这次
|
|
754
|
-
// 复读是权威的;它关的是「探针时行还不存在、并发方随后把同 id 铸成 placed」那半个窗(PG 的
|
|
755
|
-
// FOR UPDATE 对缺席行不锁任何东西)。理由与失败经济学逐字见 TiDB 孪生同处的注。
|
|
756
593
|
const afterMeta = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = $1", [this.realSessionId]);
|
|
757
594
|
if (afterMeta.rows[0]?.placement_kind != null) {
|
|
758
595
|
await conn.query("ROLLBACK");
|
|
@@ -765,7 +602,7 @@ class PgStagingHandle {
|
|
|
765
602
|
try {
|
|
766
603
|
await conn.query("ROLLBACK");
|
|
767
604
|
}
|
|
768
|
-
catch {
|
|
605
|
+
catch { }
|
|
769
606
|
if (err instanceof SyncConflictError || err instanceof SessionError)
|
|
770
607
|
throw err;
|
|
771
608
|
throw new SessionError("storage", `staged commit for ${this.realSessionId} failed`, err instanceof Error ? err : undefined);
|
|
@@ -774,7 +611,6 @@ class PgStagingHandle {
|
|
|
774
611
|
conn.release();
|
|
775
612
|
}
|
|
776
613
|
}
|
|
777
|
-
/** @see TiDBStagingHandle.abort — DELETE the staging rows. Idempotent. */
|
|
778
614
|
async abort() {
|
|
779
615
|
await this.pool.query("DELETE FROM session_event WHERE session_id = $1", [this.stagingId]);
|
|
780
616
|
}
|