import type { Pool as MySqlPool } from "mysql2/promise"; import type { Pool as PgPool, PoolClient } from "pg"; import { type ImageStatus, type ImageIndexEntry, type ImageIndexUpsert, type ImageViewer, type ImageListFilter } from "./store-contracts.js"; import { type SqlDriver } from "./sql-driver.js"; import { type IndexSpec } from "./ensure-index.js"; export type { ImageNestedBuildMode, ImageCapabilities, ImagePodContract, ImageStatus, ImageVisibility, ImageIndexEntry, ImageIndexUpsert, ImageViewer, ImageListFilter, ImageRow, } from "./store-contracts.js"; /** PG translation of the `sandbox_image_index` DDL in tidb-pool.ts (JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3), * TINYINT(1)→SMALLINT(布尔列 PG 映射归一,clay 批 2026-07-26 —— 两方言读回同为 number,写入 0/1, * 读侧 `!!` 保持,出口 JS 类型不变), inline UNIQUE KEY/KEY → separate CREATE UNIQUE INDEX / CREATE INDEX). */ export declare const PG_IMAGE_INDEX_SCHEMA: string[]; /** S-287:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensureSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */ export declare const IMAGE_INDEX_INDEXES: readonly IndexSpec[]; /** Idempotent self-contained schema apply (for the integration test; central aggregation is done separately). */ export declare function ensureSchema(pool: PgPool | PoolClient): Promise; /** Dual-dialect INDEX store. See the file header for the dialect-delta ledger. */ export declare class SqlImageIndex { protected readonly db: SqlDriver; constructor(db: SqlDriver); /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */ private q; /** Stable index id = sha256(repo@digest) — identity is the digest, not the mutable tag. */ static idFor(repo: string, digest: string): string; /** * Register or update an index row (idempotent on repo+digest). Returns the row id. * * @deprecated [ref] 车7(staleness-sweep P2-11):**生产零调用**——曾声称的 operator * `/v1/images/register` 路径实际走 `registerBuilding`(routes/images.ts),本方法只剩集成测试当 * seed 口在用。危险语义原样保留着:UNCONDITIONAL upsert 会把 `published` 行打回 `building` * (正是 §P2.10 静默 de-publish 病的成因,`registerBuilding` 因此才带 STATUS-MONOTONIC 守卫)。 * **禁止新增生产调用**;测试 seed 请优先走 `registerBuilding`(不需要覆盖语义时)。留而不删的 * 唯一理由=16 处 pg 集成测试 seed 依赖,整删随该套件下次翻新一并做。 */ upsert(e: ImageIndexUpsert): Promise; /** * STATUS-MONOTONIC register (IMAGE-API-DESIGN.md §P2.10 MUST-FIX) — the auto-register path the bake runner uses. * * `upsert` does an UNCONDITIONAL upsert: a re-ingested `done` (at-least-once from the runner) or a P2 bake of * a digest P3 already PROMOTED would flip a live `published` row back to `building`, and `latestPublished` * (`WHERE status='published'`) then DE-PUBLISHES a live, in-use image → sandbox-create 404s. This guarded path * NEVER downgrades a `published` row: on a fresh row it inserts (`status:'building'` by default); on an * existing row it updates the manifest projection + bumps `updated_at` but writes `status` only when the row * isn't already `published` — a late/duplicate `done` for an already-promoted digest is a no-op on the * lifecycle, never a regression. Returns the row id. * * Distinct from the unconditional `upsert` ([ref] 车7 更正:那条 operator `/v1/images/register` 路径 * **也走本方法**——routes/images.ts:446 `idx.registerBuilding(...)`;`upsert` 已 @deprecated,只剩 * 测试 seed 在用)。This is the bake auto-register's ONLY write into the index. Default status is `building` (P2 registers * node-local/quarantine `building` artifacts; P3's verify gate is what flips `building → published`). * * The `IF(...published...)` (TiDB) / `CASE WHEN...published...` (PG) guards are the monotonic de-publish * defense: once a row is `published` and live, a re-register must NOT downgrade `status` NOR flip the * columns `latestPublished` filters on. wq64gmm5e: guarding only `status` left `visibility`/`tenant_id` * (and `signed`) overwritable, so a re-register with visibility=private/tenant or a different tenant_id * silently dropped a live row out of every viewer's `latestPublished` — an effective de-publish. ALL FOUR * (status/visibility/tenant_id/signed) are gated identically in BOTH dialects; non-published rows still * take the freshest projection. */ registerBuilding(e: ImageIndexUpsert): Promise; getById(id: string): Promise; /** By immutable digest — what a running sandbox was built from. */ getByDigest(digest: string): Promise; /** The newest PUBLISHED, viewer-visible digest for a profile — the `select` resolution. * 🔴 genuinely-divergent shape (not just SQL text): TiDB's `visibilityClauseTidb` needs no placeholder * index (anonymous `?`); PG's `visibilityClausePg` threads a running `$n` index the caller continues * from. Also the null-safe ORDER BY trick differs ((build_date IS NULL) vs NULLS LAST) — same semantics. */ latestPublished(profile: string, viewer: ImageViewer): Promise; /** * Paginated, viewer-scoped catalog query (the UI's list). Keyset cursor on (created_at, id) desc. * 🔴 genuinely-divergent shape: the PG arm threads an explicit `$n` index through every optional predicate * (node-pg has no anonymous placeholder); the TiDB arm just appends `?` in call order. Kept as two full * branches rather than a shared placeholder-numbering helper (sql-driver.ts STEP 3/4) — a reviewer sees each * dialect's real query shape, not a generated abstraction over it. */ list(filter: ImageListFilter): Promise<{ entries: ImageIndexEntry[]; nextCursor: string | null; }>; /** Lifecycle transition (atomic-publish: building→published, or →failed/deprecated). */ setStatus(id: string, status: ImageStatus, opts?: { supersedes?: string | null; }): Promise; } /** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */ export declare class TiDBImageIndex extends SqlImageIndex { constructor(pool: MySqlPool); } /** PostgreSQL binding — historical class name + ctor shape preserved. */ export declare class PgImageIndex extends SqlImageIndex { constructor(pool: PgPool); } //# sourceMappingURL=image-index-sql.d.ts.map