/** * [ref] §3.1 / [ref] §一([ref] S-2)—— per-principal **memory-capture opt-out 授权表** * `memory_optout_grant`:`RuntimeCaps.allowMemoryOptOut` 的本地 verdict 源 + operator 管理面的持久层。 * SINGLE-FILE DUAL-DIALECT([ref] A12 形,照 approval-ask-store-sql.ts:一个 `SqlMemoryOptOutGrantStore` * 接 `SqlDriver`,两个薄 ctor 子类落方言绑定)。 * * ── 数据形([ref] §1.1 的施工形;**稿偏离如实记**)──────────────────────────────────────────────── * [ref] 稿写的是 `PRIMARY KEY (org_id, principal)` + `principal=''` 为 org 级缺省哨兵行。亲读本仓 * (侦察 2026-08-30):server **没有 org 身份维** —— 租户维 = 单 `principal`(`gatedPrincipal` 供给), * `org` 只是记忆 scope 目录里的一个词(`org-memory-admission.ts`:授权目录三态,不是 principal 的属性), * 任何 SQL 表都没有 `org_id` 列。给表铸一根没人能填的 `org_id` 就是编数据(「不编」律)。所以: * · **一台 worker = 一个租户面**,稿中「org 级缺省行」落成**部署级缺省行**(同一哨兵 `principal=''`); * · 折叠仍是三层:per-principal 行 → 部署缺省行 → 代码缺省 `allow`(governed 姿态「verdict 必答」由此 * 结构性满足,新增用户零建行义务 —— 稿 §1.1 F6 折叠律原样保住); * · 将来 server 长出 principal→org 身份面时,升 (org_id, principal) 复合键是一次 additive 迁移 * (缺省行按 org 复制),不是重设计。 * * ── 列 ────────────────────────────────────────────────────────────────────────────────────────── * principal VARCHAR(190) PK '' = 部署缺省哨兵行;宽度与全仓 principal 列同(approval_ask.owner) * allow TINYINT(1)/BOOLEAN NOT NULL * updated_at_ms BIGINT NOT NULL 审计:何时 * updated_by VARCHAR(190) 审计:谁改的(operator principal,恒在场 —— 四端点都过 operator 门) * note VARCHAR(500) NULL 审计:为何(可缺席,不编) * 审计**行内**,不另建审计表([ref] §1.3 v1 体量裁定;变更史需求真出现再升 retention 面同族表)。 * * ── 方言差异(显式写在调用点,A12 doctrine)────────────────────────────────────────────────────── * · `?` vs `$n` 占位符; * · upsert:TiDB `INSERT … ON DUPLICATE KEY UPDATE` vs PG `INSERT … ON CONFLICT (principal) DO UPDATE`; * · boolean:TiDB `TINYINT(1)` 读回来是 0/1 **number**,PG `BOOLEAN` 读回来是 boolean —— `readAllow` * 两形都认、其余一律 throw(一列存 verdict 的表不许 truthiness 读:`"0"`/`"false"` 串读成 true 正是 * 本键最怕的方向)。 * * ── 缓存姿态 ──────────────────────────────────────────────────────────────────────────────────── * **零缓存直读**([ref] §1.4 撤销窗:server 只保证 resolver 下一次读到新 verdict)。core 每任务 prepare 调 * 一次 resolver;本表两行以内的 PK 点查在任何 SQL 后端都是亚毫秒级,加缓存 = 为不存在的负载引入一致性窗 * (若未来实测成压力源,升级形 = TTL ≤ 60s 且写穿,稿已裁)。 */ import type { Pool as MySqlPool } from "mysql2/promise"; import type { Pool as PgPool } from "pg"; import type { PgQueryFn } from "./pg-query.js"; import { type SqlDriver, type SqlDialect } from "./sql-driver.js"; import type { MemoryOptOutVerdictSource } from "../runtime-caps-resolver.js"; export declare const MEMORY_OPTOUT_GRANT_TABLE = "memory_optout_grant"; /** 部署缺省哨兵行的 principal 值([ref] §1.1;'' 是**真值**不是缺席 —— NOT NULL PK)。 */ export declare const MEMORY_OPTOUT_DEFAULT_PRINCIPAL = ""; /** 一行的读投影(wire 形逐字:[ref] §1.3 的 `{principal, allow, updatedAtMs, updatedBy, note?}`)。 */ export interface MemoryOptOutGrantRow { principal: string; allow: boolean; updatedAtMs: number; updatedBy: string; /** 只在真有 note 时在场(不铸 `null` 键:缺席=没写理由,不编)。 */ note?: string; } /** 折叠输入 = 表里与本 principal 相关的行(至多两行:缺省行 + 本 principal 行)。 */ export interface MemoryOptOutGrantFoldRow { principal: string; allow: boolean; } export type MemoryOptOutVerdictSourceKind = "principal" | "deployment-default" | "code-default"; /** * [ref] §1.1 **F6 折叠律**(纯函数,`build*`:产纯数据):per-principal 行 → 部署缺省哨兵行 → 代码缺省 allow。 * 三层折叠后**恒能给显式 verdict**。`principal` 缺席(匿名/系统 run)只看缺省行 —— 别人的覆盖行绝不算到 * 匿名头上。rows 里不相干的 principal 行被忽略(SQL 层已按 `IN (?, '')` 收窄,这里再判一次是让纯函数对 * 任意输入都正确,不依赖调用方的查询形)。 */ export declare function buildMemoryOptOutVerdict(rows: readonly MemoryOptOutGrantFoldRow[], principal: string | undefined): { allow: boolean; source: MemoryOptOutVerdictSourceKind; }; /** 管理面([ref] §1.3 四端点)+ resolver 面(S-1)共用的店接口。 */ export interface MemoryOptOutGrantStore extends MemoryOptOutVerdictSource { /** 全表读投影:缺省行(缺席 ⇒ null,不编)+ 全部覆盖行(按 principal 升序,稳定)。 */ list(): Promise<{ deploymentDefault: MemoryOptOutGrantRow | null; overrides: MemoryOptOutGrantRow[]; }>; /** 部署缺省行 upsert。 */ putDefault(input: { allow: boolean; updatedBy: string; note?: string; }): Promise; /** per-principal 覆盖行 upsert(`principal` 非空:空串是哨兵,走 putDefault —— 调用方先验型,店内再拒一次)。 */ putOverride(principal: string, input: { allow: boolean; updatedBy: string; note?: string; }): Promise; /** 撤覆盖(回落部署缺省)。true = 真删了一行;false = 本来就没有(幂等,诚实)。哨兵串恒拒。 */ deleteOverride(principal: string): Promise; } /** * principal 的**字节形状法**(店与路由共用一只谓词;返回问题句,`undefined` = 合形)。 * * 🔴 codex R1-[high](第二份,验真后修):TiDB/MySQL 的 `utf8mb4_bin` 是 **PAD SPACE** 语义 —— `'alice'` 与 * `'alice '`(尾空格)在 PK 比较上**相等**,PG 的 `COLLATE "C"` 则字节精确。一个带尾空格的 principal 在 TiDB 上会 * 撞进别人的行(PUT 改了别人的 verdict、DELETE 删了别人的覆盖),在 PG 上则是另一条行 —— 双方言分叉,而且方向是 * 授权面上的串台。不改 collation(TiDB 对 NO PAD 排序规则的支持随版本走,且存量表要迁移);改在**边界**:任何 * 首尾空白 / 控制字符的 principal 一律拒(合法身份从不长这样 —— `verifiedPrincipal` 铸的 principal 是 header/JWT * sub 的裁剪值),于是 PAD SPACE 永远碰不到两条只差空白的键。同时拒内部哨兵(`RESERVED_PRINCIPALS`,security.ts * 的单一登记表)与超列宽。 */ export declare function memoryOptOutPrincipalShapeProblem(principal: string): string | undefined; /** 入口断言(店内第二道墙;路由已用同一只谓词答 400):哨兵串不许经覆盖面进出;形状法见上。 */ export declare function assertOverridePrincipal(principal: string): void; /** 双方言 MemoryOptOutGrantStore。见文件头的姿态 + 方言差异账。 */ export declare class SqlMemoryOptOutGrantStore implements MemoryOptOutGrantStore { private readonly driver; private readonly dialect; private readonly now; constructor(driver: SqlDriver, dialect: SqlDialect, now?: () => number); resolveAllow(principal: string | undefined): Promise; list(): Promise<{ deploymentDefault: MemoryOptOutGrantRow | null; overrides: MemoryOptOutGrantRow[]; }>; putDefault(input: { allow: boolean; updatedBy: string; note?: string; }): Promise; putOverride(principal: string, input: { allow: boolean; updatedBy: string; note?: string; }): Promise; deleteOverride(principal: string): Promise; private upsert; } export declare class TiDBMemoryOptOutGrantStore extends SqlMemoryOptOutGrantStore { constructor(pool: MySqlPool, now?: () => number); } export declare class PgMemoryOptOutGrantStore extends SqlMemoryOptOutGrantStore { constructor(pool: PgPool, now?: () => number); } export declare const TIDB_MEMORY_OPTOUT_GRANT_STATEMENTS: readonly string[]; export declare function ensurePgMemoryOptOutGrantSchema(q: PgQueryFn): Promise; //# sourceMappingURL=memory-optout-grant-store-sql.d.ts.map