/** * [ref] 车2 —— **托管留存的 operator 治理面**(设计稿 `docs/DESIGN-270-retention-lane.md` §5/§6)。 * * · `PUT /v1/ops/retention/holds/:domain` —— 放置 legal hold; * · `DELETE /v1/ops/retention/holds/:domain` —— 解除; * · `GET /v1/ops/retention/audit?domain=&limit=&before=` —— 审计读面(keyset 分页)。 * * 形态与 `routes/adoption.ts` 同族:**部署级动作**,不是常驻业务面 ⇒ billable=false(零模型工作)、 * operator-only、没有 SQL 后端的部署上诚实 501。门序也逐字同它:身份(401)→ 授权(403)→ 能力(501) * → 验型(400)——授权在能力之前,一个够不着任何东西的调用方不该从「有没有 SQL 后端」上读出部署形态。 * * ── 🔴 hold 的写形是**唯一**的一条纪律(车1 交接件②,逐字)──────────────────────────────────── * 放置与解除都走 `setHold`(同一 PK 的 upsert 改 `held` 列),**绝不 INSERT/DELETE 行**。 * `retention_hold` 的行同时是**域级互斥哨兵**:车1 的三条破坏性事务首步锁读它,而 * `SELECT … FOR UPDATE` **锁不住一条不存在的行**(TiDB 无 gap lock;PG READ COMMITTED 同病)。 * 删掉行 = 下一个 PUT 与一条在飞的删除事务又能同时读到「没有 hold」⇒ operator 拿到成功回执之后数据 * 仍被删。本模块因此**结构上**够不着删行:它拿到的 `RetentionLaneStore` 接口上根本没有那个方法。 * * ── 域(domain)的两个边界 ────────────────────────────────────────────────────────────────── * · **无主桶**:域键 `""`(`session_meta.owner` 为 NULL/空串的那一桶)在单用户部署里是**唯一**的域。 * 路径段允许为空(`/v1/ops/retention/holds/`)正是为了它 —— 不给它一条路 = 单用户部署根本放不了 * legal hold,而那是这条面最该管用的部署形态之一。这也是为什么路由正则是 `([^/]*)` 而不是 `+`。 * · **列宽**:`retention_hold.domain` 是 `VARCHAR(190)`。超宽在**铸行之前**拒(与 adoption 的 * `checkAdoptionWidths` 同一条判据):一次静默截断会让 hold 落在一个**别的**域上。 * * ── 审计行为什么放置/解除都要写 ──────────────────────────────────────────────────────────── * hold 本身就是一次留存治理动作,不留痕的 hold 是审计缺陷(设计稿 §5 逐字)。行**每次成功调用都写**, * 而不是"只在状态真的翻转时写":审计表记的是**发生过的动作**(operator 在某时刻按了这个按钮),不是 * 状态机的边。一次对已冻结域的重复 PUT 同样是一次需要留痕的操作。 * * 分层:本模块不值 import `server.ts`(那条边闭合运行时装载环),只 `import type`。 */ import type { IncomingMessage, ServerResponse } from "node:http"; import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js"; export declare function handleRetention(req: IncomingMessage, res: ServerResponse, match: RouteMatch, ctx: RouteCtx): Promise; export declare const RETENTION_ROUTES: readonly [{ readonly id: "retention-audit"; readonly path: "/v1/ops/retention/audit"; readonly methods: readonly ["GET"]; }, { readonly id: "retention-hold-place"; readonly pattern: RegExp; readonly label: "/v1/ops/retention/holds/:domain"; readonly methods: readonly ["PUT"]; }, { readonly id: "retention-hold-release"; readonly pattern: RegExp; readonly label: "/v1/ops/retention/holds/:domain"; readonly methods: readonly ["DELETE"]; }]; /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */ export type RetentionRouteId = RouteIdsOf; //# sourceMappingURL=retention-ops.d.ts.map