import { type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type MemoryBackend, type MemoryConsolidationDriverDeps, type RunMemoryConsolidationOptions, type SessionCaptureRecordStore } from "@sema-agent/core"; /** D3 投影的座标签闭集。`role:` 里的 name 只可能是 core 的 role 链两跳之一。 */ export type ConsolidationSeatLabel = "explicit" | "role:consolidate" | "role:summarize"; /** {@link buildConsolidationValveAudit} 的告警名闭集(事件名进日志 ⇒ 运维可按名过滤/告警)。 */ export type ConsolidationValveWarningTag = "memory_consolidation_valve_unreachable" | "memory_consolidation_run_credential_gated"; /** 座解析的产物:core 的 options 原样 + 给投影用的两位自证。 */ export interface ConsolidationDriverSeat { /** `runMemoryConsolidationDriver` 的第三参,core 的 `resolveMemoryConsolidationDriver` 原样产出。 */ readonly options: RunMemoryConsolidationOptions; /** D3 的 `seat` 位。 */ readonly seat: ConsolidationSeatLabel; /** 真正会被记进 run 行与 mint 归档的模型 id(= `options.model`,单独拎出来是投影面的便利)。 */ readonly model: string; } /** * 座解析 + **拒启**包装(D1)。 * * ## 标签怎么来的:拿 core 自己的解析器**再问一次**,不抄它的判据 * * core 的链是 `显式 chat → roles.consolidate → roles.summarize → coded refusal`,而"哪一跳答的"这件事 * 它不回报。本层**不**照着 `roleModelIfSet` 再写一份判断(那就是第二真源,而且真会答错: * `ROLE_TIER_DEFAULTS` 给 `summarize` 一个 `flash` 档位缺省、给 `consolidate` 一个都没有,所以 * 「roles 表里有没有写 consolidate」这个直觉判据在档位部署上恒错)。 * * 做法是**行为探针**:把 summarize 那一跳**结构性关掉**,再调**同一只** * `resolveMemoryConsolidationDriver` 问一次 —— 它答得出来 ⇒ consolidate 那一跳赢;它拒 ⇒ 完整解析既然 * 成功,赢的就只能是 summarize。于是标签与 core 的判决**结构上**不可能分歧,上游哪天调整链条,这只探针 * 跟着变,不需要有人记得回来改。「怎么关掉那一跳」的两个细节(以及两版被证否的做法)见 {@link seatLabel} * 体内的注 —— 那里才是它真正生效的地方,写在这里会随实现漂。 * (探针只构造闭包、不发任何请求、无副作用。) */ export declare function resolveConsolidationDriverSeat(deps: MemoryConsolidationDriverDeps): ConsolidationDriverSeat; /** * **每 scope 单飞**(codex 交叉复审 [high],验真后修)—— 同一 scope 上在飞的那一轮被后来者**并入**, * 而不是各起一轮。`create*`:返回带状态的活对象。 * * ## 病(不是理论) * * 这一口是**分钟级**的:出厂缺省下一次冷启动折叠是十几个 cycle,cycle 之间还有 60s 硬节流。于是 HTTP * 客户端**必然**会先超时 —— 而超时**不取消**服务端那一轮。文档教人「原样重发」,重发恰好落在**重叠** * 这一格上,而 core 明写它的 attempt CAS 序列化的是**账**、不是模型开销("model spend under a genuine * concurrent double-call is still at-least-once")。⇒ 一次超时重试可以买到**两份**整库蒸馏的账单。 * * ## 边界(必须说准,不许当成分布式互斥卖) * * 本单飞是**进程内**的:同副本上的重叠被消灭,跨副本仍是 at-least-once(那需要一把 durable 租约, * 本仓没有 —— 同 §遗留 遗-1 的那条基建缺口)。今天够得着这条阀门的部署形态是 file 记忆后端,那种部署 * 实际上是单副本,所以进程内单飞覆盖了真实的失败形;但话按**它真正保证的**说,不按它常常够用说。 * * 单飞**不是缓存**:那一轮落定(无论成败)即刻放行下一轮 —— 否则一次失败会把这个 scope 永久卡住。 */ export declare function createPerScopeSingleFlight(): (scope: string, run: () => Promise) => Promise; /** * 阀门自持的 boot 不变式(`boot/retention-lane.ts` 的 `assertRetentionLaneWirable` 同族同姿态)—— * 「阀门开着的时候,它真的跑得起来吗」。三条判据都是**永久性**事实(换部署形态才会变),所以判在启动期: * * ① **记忆引擎没接线**(`MEMORY_ENGINE=off`,或多租户形——本仓的记忆引擎只在单用户部署上装配): * 没有引擎就没有库可折叠,阀门是一句空话; * ② **后端不自带控制面归属**(`controlPlaneRoot`):driver 的 run 账(`distiller-runs.json`)、mint 归档 * 与引擎的 consolidation gate 全住在控制面上。缺它时 core 会把控制面**回落到本进程的本地盘** * (`deriveControlPlaneDir`),而本仓是 stateless replicas ⇒ 一轮分钟级 run 的续跑账会落在某个随机 * pod 的盘上,pod 重建即失,而 run 是**可续跑**设计的(重跑=整库重新 mint,真金白银)。判据与 * `createMemoryComplianceFaces` 逐字同源(读的是后端的**面**,不看后端方言名); * ③ **`MEMORY_PROVENANCE=off`**:core 的 `screenConsolidationOptions` 明写 consolidation 不能在 * `provenance:"off"` 下开(fold law 必须铸得出 origin 标记,否则一个被标记过的输入折出来的产物会 * **不带标记**地提交)。这一条本来就会在引擎构造期抛,提前判只为一件事:把**两根**旋钮的名字同时 * 写进句子 —— core 的那句话只提到 provenance,而运维要知道是哪一根把这台机器拦下的。 * * 阀门关着 ⇒ 整条门不判(缺省姿态,[ref]「缺席不在律内」)。 */ export declare function assertConsolidationDriverWirable(input: { enabled: boolean; /** 本部署接了记忆引擎吗(`boot/stores.ts` 的 `memoryEngine !== undefined`)。 */ memoryEngineWired: boolean; /** 记忆后端自带控制面归属吗(`typeof backend.controlPlaneRoot === "string"` 且非空)。 */ controlPlaneOwned: boolean; /** 只进文案 —— 让「该去拧哪根旋钮」这句话指向该腿上**真的**有效的那一根。 */ memoryEngineBackend: "file" | "pg" | "tidb"; /** 部署声明的 provenance 姿态(`config.memoryProvenance`;缺席 ≡ core 缺省 `carry`)。 */ provenance?: "off" | "carry"; }): void; /** * 阀门上了场之后的**可达性**告警两条(自查 + codex 交叉复审;姿态=warn 不拒启)。 * * ## 病(结构性,而且**只在**这条阀门够得着的那种部署上成立) * * consolidation 要后端自带引擎控制面归属 ⇒ 今天只有 file 记忆后端满足 ⇒ 本仓只在**单用户**形 * (`REQUIRE_PRINCIPAL !== true`)上装配记忆引擎 —— 而那种部署的 `OPERATOR_PRINCIPALS` 通常是**空的**。 * 空名单下 `explicitOperatorOk` 对任何身份恒假(那是它与 `isOperator` 刻意分家的地方:空名单绝不等于 * 「人人都是 operator」),于是:阀门开着、启动成功、两口挂着、**每一次调用 403**,能力位诚实地报 * `false` —— 唯独运维不知道为什么,而他刚刚显式拧了一根写着 "on" 的旋钮。 * * ## 为什么是 warn 而不是拒启(与 D1 的三条拒启臂**刻意不同档**) * * `OPERATOR_PRINCIPALS` 是**跨面共用**的部署级旋钮(adoption / retention-ops / memory-bundle / * memory-compliance / admin-drain 全在同一张名单上)。为一条阀门拒掉整台机器,比这条阀门本身的价值重; * 而那三条拒启臂拒的是「这台机器**结构上**跑不了这件事」(没有库 / 账会丢 / 折叠法铸不出标记), * 本条拒的是「配齐了但没人够得着」——后者是一次**配置遗漏**,补一行 env 即可,不该要一次重启失败来教。 * 同族先例逐字:`permission_rules_lane_unavailable`(旋钮开着却上不了场 ⇒ 打一行,不拒启)。 * 反过来说,**沉默**同样不行:那正是「设了但没生效」病族,而这一形连能力位都是诚实的假,唯一缺的 * 就是一句把因果说清楚的话。 */ export declare function buildConsolidationValveAudit(input: { enabled: boolean; operatorPrincipals: readonly string[]; /** 这台部署配了任何一把 service 凭证吗(`config.authToken` 或 `config.authTokens` 非空)。 */ hasServiceAuth: boolean; /** 本地开发的显式逃生口(`ALLOW_UNAUTHED_WRITES=true`)。 */ allowUnauthedWrites: boolean; }): { warnings: Array<{ tag: ConsolidationValveWarningTag; detail: string; }>; }; /** * 停因诊断行的**运维日志**投影(codex 交叉复审 R2-[medium],验真后修)。`build*`:纯数据。 * * `stopDetail` 已经不上 wire(`routes/memory-consolidation.ts` 的同名段:core 的 `driver_failed` 支把 * 模型座抛出的 `Error.message` 逐字拼进去)。但**落日志同样要有信任边界** —— 集中式日志是人读的, * 而一段来路不明的文本混在自家诊断里,既可能夹带凭据,也可能把不当调用方写的"操作建议"摆成本服务的口吻。 * 三件: * ① 字段名 `untrustedDetail` **自陈**(一个叫 `detail` 的键会被下一个读日志的人当成本服务自己的诊断); * ② `redactSecrets` 脱敏(与审批卡片面同一只属主函数,不在本层另写一份模式表); * ③ **先脱敏再截长** —— `redactSecrets` 会**变长**,先截后脱敏会把一半的凭据留在窗口外 * (approval-card.ts 的同款先例逐字,那里踩过)。 * 调用点在 {@link createMemoryConsolidationFaces} 的单飞回调**之内** ⇒ 一轮真 run 只落一行,而不是 * 每个并入的 HTTP 等待者各落一行。 */ export declare function buildStopDetailAudit(input: { scope: string; runId: string; outcome: string; stopDetail: string; }): { scope: string; runId: string; outcome: string; untrustedDetail: string; }; /** * S-362 / S-399 —— **`RunnerDeps.autoRunOnRecommendation` 武装席的装配不变量**(D1 的姊妹门)。 * * 与 {@link assertConsolidationDriverWirable} 刻意**分家**而不是加第四臂:那道门问「阀门这根旋钮 * (`MEMORY_CONSOLIDATION_DRIVER`)开着而这台机器结构上跑不了」,本门问「武装这根旋钮 * (`MEMORY_AUTO_CONSOLIDATION`)开着而引擎那半场没有整理席」。两句诊断指向的动作不同,合成一道门就会 * 指错方向。 * * ## 为什么是**拒启**而不是让 core 在 prepare 抛 * * core 对「`autoRunOnRecommendation: true` 但没接整理席」的处置是**每一次 prepare 都抛** * `config.auto_consolidation`(带码错,不是通知;逐字见 core `RunnerDeps.autoRunOnRecommendation` 的 d.ts)。 * 留到运行期买到的是一台**启动成功、每一个任务都死**的 worker —— 比阀门那三条(「启动成功、什么都没 * 折叠」)更坏。所以按 D1 的同一条(**安全/成本控件不得半开**)提前到启动期,并把**该去拧哪根**写进句子。 * * ## 判据取的是**结构事实**,不是一个手抄的布尔 * * `memoryConsolidationWired` 由调用点从**它刚刚铸出来的那只 `RunnerDeps`** 上读(`deps.memoryConsolidation * !== undefined`),不是在这里写一个常量 —— S-399 把两席接上之后,本门**自动**从「恒拒」变成「阀门开着 * 就放行」,没有人需要记得回来改一个数。 * * 🔴 **core 的拒绝谓词是两个析取项,本门只覆盖第一个**(对抗复审 F5 [low],验真后如实登记): * core d.ts 逐字「`true` with nothing to run (**no `RunnerDeps.memoryConsolidation`, or no resolvable * driver model seat**), or a non-boolean, is refused at prepare」。第二项(模型席解不出)本门不判, * 而这在本仓**结构上不可达**:两席是**同一只已解析座**一起写上去的(`boot/runner-deps.ts` 的 * `ctx.consolidationSeat` 那一段)——座解析不出来时 {@link resolveConsolidationDriverSeat} 早在 * D1 那道门上就**拒启**了,轮不到这里。哪天有人把两席拆开各走各的路,这条「不可达」就失效,那时 * 本门要补第二项(core 侧的判据是 `consolidationSeatResolvable`,**不从包根导出**,届时要么向 core * 要导出,要么在本层复现 —— 这一句留在这里就是为了那一天不必重新推一遍)。 * * 未武装(`undefined` / `false`)⇒ 整条门不判。 */ export declare function assertAutoConsolidationWirable(input: { /** 部署席的三态读数(`config.memoryConsolidationDriver.autoRunOnRecommendation`)。 */ armed: boolean | undefined; /** 铸好的 `RunnerDeps` 上**真的**有整理席吗(`deps.memoryConsolidation !== undefined`)。 */ memoryConsolidationWired: boolean; }): void; /** 两个 admin 口消费的窄能力面(`ServiceDeps.memoryConsolidation` 的实参来源)。 */ export interface MemoryConsolidationFaces { /** D3 的座位自证(投影用;boot 期解析一次,restart-to-apply)。 */ readonly seat: ConsolidationSeatLabel; /** 座位解析出来的模型 id —— 它就是会被记进 run 行与 mint 归档的那一个。 */ readonly model: string; /** 配置表的 scope(`MEMORY_CONSOLIDATION_SCOPES`)。恰好一条时是两个口的**唯一**缺省。 */ readonly scopes: readonly string[]; /** 跑(或**续跑**)一轮。core 的 run 行是持久的:同一 scope 上有 pending run 时本调用 CONTINUE 它 * (同一 plan 缓存、同一 force requestId、零新 mint)—— 所以这一口对**重试安全**。 */ run(scope: string): Promise; /** 该 scope 最近一次 driver run 的账(`undefined` = 从没跑过 —— 诚实缺席,不编一个空壳)。 */ lastRun(scope: string): ConsolidationDriverRunRow | undefined; } /** * 在**已装配的记忆后端**上建 consolidation 操作面。命名照 CLAUDE.md 工厂律:返回带行为的活对象 ⇒ `create*`。 * * ## 为什么这里现构一只引擎(与 `createMemoryComplianceFaces` / `createMemoryBundleFaces` 同一条理由) * * 本仓**没有**一个长命的 `MemoryEngine` 实例:`boot/stores.ts` 装配出来的 `memoryEngine` 是 * `{ backend, root }` 二元组,真引擎由 core 的 Runner **每任务现构**。HTTP 面要一个引擎就得自己构。 * 两只引擎不会各说各话:控制面由后端写死(`backend.controlPlaneRoot`),gate / run 账 / plan 归档 * 全住在那里,所以操作面引擎与每任务引擎看的是**同一本**账。 * * ## `consolidation: {}` = 出厂缺省,**显式在场** * * core 的 `MemoryEngineOptions.consolidation` 是 **absent = OFF**(v3 缺省),而 driver 的四个 verb 在 * OFF 的引擎上一律 coded-refuse。所以这只引擎必须**显式**带上它 —— 否则能力位说 yes 而每一次调用 * 确定性失败,正是本仓「says yes ⟺ route works」明令禁止的那种恒假的 yes。传空对象 = 逐项取 core 的 * 出厂缺省(fuse 0.25/floor 4、每轮 64 产物、24h 时间闸、5 会话闸),**刻意不为它们各开一根旋钮**: * 那七个数是 core 自己校准的写协议参数,本仓没有任何测量说明该动它们(要动时先有部署案例,再开旋钮)。 * `multiNode` 也**刻意不声明**:声明它就必须注入一只全局租约,而本仓今天没有(见顶注「v1 没有周期腿」 * 那一段的同一条理由)——不声明 = 单机 plan-seat CAS 是唯一互斥,core 的契约原话如此,不是偷偷降级。 */ export declare function createMemoryConsolidationFaces(store: { backend: MemoryBackend; root: string; }, input: { seat: ConsolidationDriverSeat; scopes: readonly string[]; /** 部署的 provenance 姿态,原样交给引擎(缺席 ⇒ core 缺省 `carry`)。 */ provenance?: "off" | "carry"; /** 引擎的 advisory incident 座(与 `createMemoryComplianceFaces` 同一条理由:只走这个座的码不接就没了)。 */ onIncident?: (err: Error & { code?: string; }) => void; /** 停因自由文本的**运维日志**座(缺席 = 没人听,不影响任何行为)。载荷由 {@link buildStopDetailAudit} * 铸,调用点在单飞回调**之内** ⇒ 一轮真 run 一行,并入的等待者不各落一行。 */ onStopDetail?: (audit: ReturnType) => void; /** [ref]②(383 §2.1b):capture opt-out 记录的 SQL 载体(main.ts 单实例,与 Runner 同源)。 * 🔴 这只引擎是四只 operator 面里**真读名册**的那只:`consolidationEligibility` 第四臂 * (engine.js `listCaptureOptOutSessions`)拿它筛掉 opt-out 会话的贡献条目 —— SQL 部署上漏喂 * = 驱动器读文件空名册 ⇒ 蒸馏器放大用户明说不要记的内容(S-5 真实失效形;全站同批注入律, * 机器钉 capture-optout-lane ②)。S-215 起 local 车道在引擎接线时也供(core 文件三腿,同一只控制面); * 缺席 ⇒ 记忆面暗(引擎未接线),与 Runner 同本。 */ captureRecordStore?: SessionCaptureRecordStore; }): MemoryConsolidationFaces; //# sourceMappingURL=memory-consolidation.d.ts.map