/** * [ref] v2-c —— **治理携出 bundle 的 operator 面**(core 5.40.0 `MemoryEngine.exportMemoryScopes` / * `importMemoryBundle`,[ref] v2-c;判据帖 [ref] G1/G2/G3)。 * * · `POST /v1/memory/export` —— 请求 `{scopes: string[]}`,回**治理完备包**(bundle 原样); * · `POST /v1/memory/import` —— 请求 `{bundle}`,回 `MemoryImportReport` 的白名单投影。 * * ── 为什么是 operator-only(而不是属主自助)──────────────────────────────────────────────────── * 同路径上的 `GET /v1/memory/export?scope=`(routes/memory-policy.ts,142-S5)是**属主自助的条目读**: * 一个 principal 导出自己那张盘。本面不是它的 POST 版本,是**另一件东西**: * · import 是**治理写面** —— 它往本部署的记忆平面里落别处铸的条目、挑战、污染标记与托管链。写错= * 把一份外来治理史当成本店的账。fail-closed 是唯一可接受的缺省,门=`explicitOperatorOk`(与 * approvals RBAC / adoption / retention-ops 同一条门,不是第二套判据); * · export 出的包**跨 scope 携带治理行**(custody 链、跨会话污染标记、lineage),按定义超出任何单个 * principal 的自助边界 —— 与 import 同级授权,不给「读半场松一格」的斜坡。 * 缺 principal 的形照 sibling mutating 端点(retention-ops / adoption)401 `auth.principal_required`。 * * ── 门序(逐字同 routes/adoption.ts / retention-ops.ts)──────────────────────────────────────── * 身份(401)→ 授权(403)→ 能力(501)→ 验型(400)。**授权在能力之前**:一个够不着任何东西的调用方 * 不该从「这个部署有没有记忆引擎」上读出部署形态。 * * ── 能力面的诚实形(与 GET 面**方向相反**,这不是笔误)────────────────────────────────────── * `GET /v1/memory/export` 要的是 **DB 记忆平面**(pg|tidb),file 形态 501。本面要的是 core 的 * **bundle 复合面**(`exportSnapshotOf` / `importBundleCommit`),而它今天只在 core 自带的 **File** * 后端上实装:本仓的两只 SQL 记忆孪生(plugins/memory-engine-{pg,tidb}.ts)没有实现这两个可选面。 * 因此**两个成因同落 501** `capability.memory_engine_required`(都是「换部署形态才行」,消费端分支相同): * · 记忆引擎整个没接线(`MEMORY_ENGINE=off` / 多租 file 底座等暗态); * · 引擎接了但后端没有 bundle 复合面(pg/tidb 孪生)—— 这一支在**挂载期**就判掉了 * (`createMemoryBundleFaces` 返回 undefined ⇒ 两口整个不挂),能力位 `memoryBundle` 因此与「路由真能用」 * 严格同真。🔴 首版把它当运行期事实(照常挂载、让 core 每次抛 409/422),被 codex 交叉复审驳倒并改此形: * 面在不在是一次**属性检查**、boot 期判得出、且是**永久**缺席 ⇒ 照常挂着就是一个恒假的 yes, * 壳会据此给用户开出一条走不通的迁移流程。 * ⚠️ 与**运行期**拒别混:后端**有**复合面但托管链 chain-degraded 时,core 照样抛 * `memory.export_incomplete`(409)—— 那是诚实的运行期拒,附录 A 单列。 * ⚠️ 绝不在本层为 SQL 孪生现造一个「够用的」导出:那正是 core 明写要拒绝的 * 「a governance-less export is the laundering shape this API refuses by design」。 * SQL 孪生补齐两面是上游/本仓 store 层的独立工单(消费台账 debt 行)。 * * ── 验型的分权:server 验**形**,core 验**义** ───────────────────────────────────────────────── * 本层只用 zod 校 JSON **结构**(宪法 [ref]:边界必 schema、禁裸 as-cast)。scopes 的「非空/去重/ * 元素非空」与 bundle 的整包判决(section hash 键集、逐条目 rev 重算、批内唯一性、scope 授权信封) * **全部**归 core —— 在本层抄一遍等于第二真源:core 收紧一格我方不会跟着动,而两边都"绿"。 * 于是 `{scopes:["a","a"]}` 这种**形对义错**的请求会走到引擎并带回 `config.memory_export_request`。 * * ── 披露的传导([ref] 权威句;打分帖 [ref] 扣点① 的 server 半场)──────────────────────────── * bundle 体里的 `doc: string[]` 是**铸包方的治理披露**(自由串字面的租户隔离界限等),core 铸包时恒 * 写入。本面把它**原样透传**:不剥离、不复述、不另铸 `disclosure` 键。理由是单一属主 —— 复述会随 * core 版本漂移,而转发即真话。端点文档(openapi 段 + docs/ASSISTANT-WIRE-CONTRACT.md)负责告诉消费方 * 「`bundle.doc` 要呈现给最终用户」。 * * ── 幂等 ──────────────────────────────────────────────────────────────────────────────────── * 同 bundle 重放的收敛是 **core 的**契约(崩溃后重导同一包收敛;已完成的导入幂等答同一份记录报告)。 * 本端点**不加**第二层去重:两个写者压同一条语义面,其重叠通常比原缺陷更坏且静默。 * * 计费/lane:两口都是**部署级治理动作**,零模型工作 ⇒ `billable=false`(与 adoption / retention-ops * 同族,申明在 test/billable-route-declaration.test.ts)。 * * 分层:本模块不值 import `server.ts`(那条边闭合运行时装载环),只 `import type`。 */ import type { IncomingMessage, ServerResponse } from "node:http"; import type { MemoryImportReport } from "@sema-agent/core"; import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js"; export declare const MEMORY_BUNDLE_EXPORT_PATH = "/v1/memory/export"; export declare const MEMORY_BUNDLE_IMPORT_PATH = "/v1/memory/import"; /** * `MemoryImportReport` → wire 的**显式白名单**投影。 * * 🔴 为什么是显式白名单而不是整行透传:报告是**治理处置**的清单,core 日后加一个新的处置座(例如又一 * 类"被扣下的行"),整行透传会让它**静默**上 wire 而没人给它写过契约;白名单让它变成**编译错误** * (`test/memory-bundle-http.test.ts` 的差集断言 = `wire-whitelist-exhaustiveness` 同款门形)。 * 今天**每一个**键都在场:每一条都是 operator 判「这次导入到底发生了什么」必须看到的东西,一个都 * 不该省 —— 「每个否定处置都被报告,绝不静默」是 core 对这份报告的原话。 */ export declare function buildImportReportWire(r: MemoryImportReport): { v: 1; bundleHash: string; sourceStoreId: string; landed: { id: string; scope: string; slug: string; }[]; alreadyPresent: string[]; refusedErased: string[]; refusedUntrusted: string[]; conflicts: { id: string; reason: string; }[]; governanceWithheld: string[]; erasedAtSource: string[]; pollutionDivergence: { sessionId: string; kept: "destination"; }[]; referentialOrphans: string[]; lineageDivergence: { entryId: string; sessionId: string; kept: "destination"; }[]; custodyWithheld: { srcEv: string; channel: string; }[]; custodyAppended: number; }; export declare function handleMemoryBundle(req: IncomingMessage, res: ServerResponse, match: RouteMatch, ctx: RouteCtx): Promise; export declare const MEMORY_BUNDLE_ROUTES: readonly [{ readonly id: "memory-import"; readonly path: "/v1/memory/import"; readonly credentialGated: readonly ["POST"]; readonly methods: readonly ["POST"]; }, { readonly id: "memory-bundle-export"; readonly path: "/v1/memory/export"; readonly credentialGated: readonly ["POST"]; readonly methods: readonly ["POST"]; }]; /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */ export type MemoryBundleRouteId = RouteIdsOf; //# sourceMappingURL=memory-bundle.d.ts.map