import { BaseSessionStorage, type PromptEpochDescriptor, type AcquiredSession, type PlacedSessionRow, type SessionMetadata, type SessionPlacement, type SessionPlacementRecord, type SessionStore, type SessionTreeEntry, type SessionWriteOptions } from "@sema-agent/core"; import type { Pool, PoolClient } from "pg"; import type { SessionSummary } from "./store-contracts.js"; import { type PlacementAcquireOpts } from "./session-placement.js"; import { type StagingHandle } from "../session-sync-kernel.js"; import { type IndexSpec } from "./ensure-index.js"; /** * PG-dialect DDL for the L1 session log, translated from tidb-pool.ts SCHEMA_STATEMENTS: * JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3), inline `KEY`/`UNIQUE KEY`→separate CREATE INDEX (UNIQUE for uq_session_event_entry). * * SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth, schema changes are * drop-and-recreate, and NO new `ALTER TABLE` seams are added here; fold into the CREATE instead. */ export declare const PG_SESSION_SCHEMA: string[]; /** S-287:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensureSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */ export declare const SESSION_INDEXES: readonly IndexSpec[]; /** Idempotent schema apply for this store's tables (the test calls this; production aggregation is separate). */ export declare function ensureSchema(pool: Pool | PoolClient): Promise; /** * PostgreSQL-backed {@link BaseSessionStorage} — the durable L1 event log for one session. * * Behaviourally identical to {@link TiDBSessionStorage}; see that file's class doc for the F2/F3 seams. The only * differences are dialectal (placeholders, `IS NOT DISTINCT FROM`, jsonb, rowCount, transaction handle). */ export declare class PgSessionStorage extends BaseSessionStorage { private readonly pool; private readonly sessionId; private readonly floorSeq; constructor(pool: Pool, sessionId: string, metadata: SessionMetadata, entries: SessionTreeEntry[], leafId: string | null, floorEntryId?: string | null, floorSeq?: number); /** @see TiDBSessionStorage.getEpochAnchor — [ref] S3 [ref] pull seam, PG dialect twin * (keyset-paged to exhaustion: a bounded scan over restatement-less pre-epoch compactions would * misreport an epoch-bearing session as pre-epoch — codex HIGH). */ getEpochAnchor(): Promise; /** @see TiDBSessionStorage.wake — F3 bounded `[floor..leaf]` rebuild; null if the session does not exist. */ static wake(pool: Pool, sessionId: string): Promise; /** The bounded-window floor = last compaction's firstKeptEntryId (and its seq). @see TiDBSessionStorage.computeFloor */ private static computeFloor; /** Sub-floor lookups (e.g. a label target) aren't in the loaded tail — fall back to the durable log. */ getEntry(id: string): Promise; appendEntry(entry: SessionTreeEntry, opts?: SessionWriteOptions): Promise; setLeafId(leafId: string | null, opts?: SessionWriteOptions): Promise; /** * Atomically: CAS the meta leaf (+bump leaf_seq), then append the event at the new seq. * The `(session_id, seq)` primary key is the backstop — a racing writer that grabbed the same * seq makes our INSERT fail with a duplicate-key (23505), which we translate to a conflict too. */ private persist; } /** * PostgreSQL-backed {@link SessionStore} — the PG twin of {@link TiDBSessionStore}. Maps the * `SessionStore` surface (acquire / register / ownerOf / touch / release) onto {@link PgSessionStorage}. */ export declare class PgSessionStore implements SessionStore { private readonly pool; /** [ref] 车1:托管留存声明(TiDB 孪生同格)。读法见 `retention-store-sql.ts` 的 {@link MANAGED_RETENTION}。 */ readonly retention: import("@sema-agent/core").RetentionDeclaration; /** [ref] C18(core 5.48.0 [ref]):placement 声明(TiDB 孪生同格,义务逐条见 * `src/plugins/session-placement.ts` 顶注)。 */ readonly placements: { readonly subagent: { readonly durability: "durable"; }; }; /** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one. */ private readonly pending; constructor(pool: Pool); acquire(sessionId?: string, opts?: PlacementAcquireOpts): Promise; private load; /** Idempotently create a session_meta row with an owner. Existing owner is never overwritten. * [ref]:`placement` 与行同一条 INSERT;`DO NOTHING` 臂 ⇒ 已存在行的元组永不被重写(first-write 不可变)。 */ register(sessionId: string, owner: string | null, placement?: SessionPlacement): Promise; /** [ref] 义务 6 的廉价探针(TiDB 孪生 {@link TiDBSessionStore.placementOf} 同义)。 */ placementOf(sessionId: string): Promise; /** [ref] 义务 5 —— placed 分区枚举(年龄锚/上界/tupleIncomplete 三条读法与 TiDB 孪生逐字同义, * 见 {@link TiDBSessionStore.listPlaced})。 */ listPlaced(kind: "subagent", opts?: { olderThanMs?: number; scope?: string; }): Promise; /** Owner principal of a session, `null` if registered ownerless, `undefined` if no such session. */ ownerOf(sessionId: string): Promise; touch(sessionId: string): Promise; /** E18 — the session's current leaf SessionTreeEntry.id (session_meta.leaf_id). @see TiDBSessionStore.getLeafId * (single SELECT, no acquire/lock, cache-bypassing — safe to read mid-run for the resume-at turn-capture). */ getLeafId(sessionId: string): Promise; /** [ref] 六轮复审:owner+leafId 单行原子快照(@see TiDBSessionStore.getHead——混世代组合防)。 */ getHead(sessionId: string): Promise<{ owner: string | null; leafId: string | null; } | undefined>; /** Durable history is retained; just drop any in-flight cache entry. * 🔴 [ref] 义务 4:**placed 会话例外 —— release 是真删除**(理由/ABA 条件删/失败上浮三条读法逐字见 * {@link TiDBSessionStore.release},本腿是它的 PG 孪生)。 */ release(sessionId: string): Promise; /** @see TiDBSessionStore.forget — 丢引用不动 durable 史(preflight 纪律的落点;PG 孪生)。 */ forget(sessionId: string): void; /** @see TiDBSessionStore.deletePlacedSession — PG 孪生(条件删门 + meta 先删 + 失败上浮同义)。 */ private deletePlacedSession; /** [ref]:placed id 拒绝 session-sync 的整段换装(TiDB 孪生 {@link TiDBSessionStore.assertNotPlaced} 同义)。 */ private assertNotPlaced; /** @see TiDBSessionStore.listSessions — session identity from session_meta (owner-filtered), task_run LEFT JOIN for preview. */ listSessions(opts: { owner?: string; includeUnowned?: boolean; cursor?: { lastActivityAt: string; sessionId: string; }; limit: number; q?: string; }): Promise; /** @see TiDBSessionStore.probeTitle — the PG twin. */ probeTitle(sessionId: string): Promise<"none" | "untitled" | "titled">; /** @see TiDBSessionStore.setTitleIfNull — the PG twin (write-once auto-title). */ setTitleIfNull(sessionId: string, title: string): Promise; /** @see TiDBSessionStore.fork — copy the WHOLE durable history (full session_event log, compaction floor bypassed, * NOT wake's bounded tail) to a NEW uuidv7 session, re-seq dense from 0, ids/parents verbatim, owner=caller. */ fork(sourceId: string, owner: string | null): Promise; /** @see TiDBSessionStore.exportEntries — 2c session-sync EXPORT: the FULL durable session_event log (compaction floor * bypassed, NOT wake's tail) as `SessionTreeEntry[]`, oldest-first, verbatim. null if the source is unknown. */ exportEntries(sessionId: string): Promise; /** @see TiDBSessionStore.listEntryIds — 2c P1d-α (PULL streaming): the IDS-ONLY projection of the full durable log, * oldest-first (`SELECT entry_id … ORDER BY seq ASC`), no payloads. null if the source is unknown. */ listEntryIds(sessionId: string): Promise; /** @see TiDBSessionStore.exportEntriesStream — 2c P1d-α (PULL streaming): KEYSET-paged async generator over the full * durable log (node-pg also buffers a plain SELECT → page it). `… WHERE seq > cursor ORDER BY seq ASC LIMIT K` * (K = batchSize ?? 500), advancing the cursor to the last seq each batch, ONE pooled `pool.query` per batch (no * held connection across the stream → a slow consumer can't pin one). Start cursor = afterSeq ?? -1 (0-based dense). * Resolves to null (NOT an empty iterable) for a missing session — eager `session_meta` probe (BIGINT seq comes * back as a STRING in node-pg → always Number(...) the cursor advance). */ exportEntriesStream(sessionId: string, opts?: { afterSeq?: number; batchSize?: number; }): Promise | null>; /** @see TiDBSessionStore.importEntries — 2c session-sync IMPORT: core `validateEntriesForImport` gate FIRST (fail-closed), * then persist its verbatim output (re-seq dense from 0, ids/parents/payload/ts unchanged) in ONE txn with * session_meta LAST as the commit point; owner = the RE-STAMPED authenticated principal (never the bundle's). */ importEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise; /** @see TiDBSessionStore.replaceEntries — 2c (§7/§8) IDEMPOTENT REPLACE: core `validateEntriesForImport` gate FIRST * (fail-closed), then DELETE events + the owner-guarded meta delete (`owner IS NOT DISTINCT FROM $n` = the PG `<=>`, * defense-in-depth), then re-insert via the SHARED body (entries + session_meta LAST as the commit point), ALL in * ONE txn. Safe to run OVER an existing session (fast-forward / §8 retry) — unlike importEntries' fresh-only INSERT. */ replaceEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise; /** @see TiDBSessionStore.insertValidatedLog — shared insert half of importEntries / replaceEntries (ONE source, no * drift): entries re-seq dense from 0 (ids/parents/payload/ts verbatim), THEN session_meta LAST as the commit point. * Runs inside the caller's open txn (`conn` already in BEGIN); the caller owns COMMIT/ROLLBACK + the validate gate. */ private insertValidatedLog; /** @see TiDBSessionStore.beginImportStaging — 2c P1d-β PUSH staging: stream batches into the shadow id * `${realSessionId}#stg-${token}` (route-minted token), then commit (atomic swap) / abort. */ beginImportStaging(realSessionId: string, token: string): StagingHandle; /** @see TiDBSessionStore.readStagedEntries — 2c P1d-β staged-row inspection: the staged rows under `stagingId`, oldest- * first, no session_meta probe (a staging id has no meta row). No longer the import gate (the per-line * StreamingImportValidator validates during the stream); a tests/diagnostics seam. Returns [] for an empty/unknown id. */ readStagedEntries(stagingId: string): Promise; /** @see TiDBSessionStore.sweepStagingSessions — reaper for abandoned staging sessions: DELETE session_event rows * under a `%#stg-%` id with NO session_meta whose oldest row is older than the grace window (in-flight stays fresh). * Returns the affected row count. Driven by main.ts's reaper. */ sweepStagingSessions(): Promise; /** @see TiDBSessionStore.deleteSession — purge session_meta + session_event; idempotent (false if absent). * Owner-guarded on session_meta.owner (`IS NOT DISTINCT FROM` = the PG `<=>`); the event purge is gated on the * owner-matched meta delete (session_event has no owner column). */ deleteSession(sessionId: string, owner: string | null): Promise; get size(): number; dispose(): void; } //# sourceMappingURL=pg-session-storage.d.ts.map