/** * [ref] 车3([ref] 流内审批协议)—— **审批卡的 schema 属主 + 重放腿的纯函数**。 * * 本模块只出两样东西,**都不带 IO、不带定时器、不发帧**: * 1. `ApprovalCardSchema` / `ApprovalCardEnvelopeSchema` —— 落库 `approval_ask.card_json` 与一切读面 * (重放腿=刀 3c、回决端点=车4、对账扫描=车5)共用的**同一份** schema。写侧(刀 3b 的 `ensureAsk`) * 与读侧走同一个符号,于是「存的形」与「读的形」不可能各自漂。 * 2. `buildReplayFrame` / `buildApprovalPreamble` —— 行 → 帧的**纯投影**(设计稿 §5.2)。 * * 🔴 为什么读面必须 `safeParse` 而不是裸 as-cast:`card_json` 是 JSON 列,驱动回读的是 `unknown`。 * 宪法 [ref](边界必 schema / 禁裸 as-cast / schema 单一属主)在这里是硬约束——一条形状漂了的历史行 * 若被 as-cast 成 `ApprovalCard`,它会带着 `undefined` 字段一路走到 wire 上,消费端拿到的是「结构上 * 合法、语义上空」的卡。`safeParse` 让这种行**当场落地为「跳过 + 一次 warn」**(§5.3)。 * * schema 属主裁定(设计稿 §12-2,属主 §14 裁 (a) 变体):v1 的 schema 属主 = server 本仓,server 加 * **zod 直依赖**(此前 zod 只经 `@sema-agent/settings-schema` 传递到场)。抽进 `@sema-agent/settings-schema` * 的时机 = 出现第二个**运行期**消费者(cli 呈卡校验排期时)——届时是「属主迁移」而不是「复制形状」, * 不违单一属主宪法;此刻抽包 = 为不存在的消费者发一轮 registry-core 版本 + floor bump,零收益。 * * 命名(CLAUDE.md 工厂命名律):本文件全部是 `build*` —— 返回值是纯数据(帧对象 / 帧数组 / 计数), * 没有方法、没有捕获的行为。 */ import { z } from "zod"; import { READ_ROOT_CANDIDATE_DIR_MAX, type AskEvidenceAbsence, type AskRequest as CoreAskRequest, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core"; import type { AskRow } from "./plugins/approval-ask-store-sql.js"; /** 模型自由文本(`sourceAgentName` / `delegation.agentName`)的限长(设计稿 §6.2)。设计 §3.1 把 * 「限长 + 脱敏」写成 **server 新增责任**(引擎无此层):spawning model 挑的名字是自由文本, * 不能指望壳去截——一条 100KB 的 agentName 在 server 侧就该被拒,而不是变成一张撑爆呈卡面的卡。 */ export declare const MAX_AGENT_NAME = 200; /** * OFFER **基数**的 server 执法上限。两条腿同用。 * * 🔴 **两个数,别混**(合并码重扫,档实对齐;core 5.58.0 换形后口径逐字不变):core 的契约是 **≤2** * (whole-string exact `single` 恒 index 0,`batch` 至多一条恒末位 —— core `checkpoint-store.d.ts` 与 * `permission-rule-model.d.ts` 逐字),那是**今天真实的**上限;本常量是 server 侧的**容忍帽**,刻意留在 * 4:一个被改坏/未来放宽的上游不该让运维队列的一行被整只拒收(同步腿这一格进的是 `.strict()` 的卡 * schema,收紧到 2 会把「多给一条展示用 offer」变成铸卡失败——在一条纯展示轴上换来一次 park,方向反了)。 * ⇒ 消费端按 **≤4** 布局,契约文同口径成文。 * * 🔴 **截断只许取前缀**:序是 core 的契约(exact 恒 0、batch 恒末),而**选择键在原始下标上** * (core d.ts:"keeping the ORIGINAL wire index for every element they keep")。取前缀保住了这两条; * 任何重排/中间剔除都会让「人点的第 k 个」与「服务端兑的第 k 个」指向两条不同的规则。 */ export declare const MAX_RULE_OFFERS = 4; /** * 一条 `batch` offer 的**成员**基数容忍帽。core 契约是 1..5(卡道铸造帽),本数同样是**容忍余量** * (理由与 {@link MAX_RULE_OFFERS} 逐字同源:纯展示轴上宁可多渲一条,也不为一条多出来的成员把整只卡 * 铸失败换成一次 park)。⚠️ 与 offer 基数分家的理由:两者数的是不同的东西(几个选项 vs 一个选项里 * 几条规则),混成一个数会让「上游把 batch 放宽到 6 条成员」误伤到 offer 条数这条无关轴。 */ export declare const MAX_RULE_OFFER_BATCH_MEMBERS = 8; /** * **一条**「不再询问」OFFER 的形(core 5.58.0 / [ref] §3.1 —— **BREAKING**,取代退役的 * `RuleSuggestion`)。同步腿(`ApprovalCardSchema.ruleOffers` / `card_json`)与耐久腿(durable park 行的 * `PendingCheckpoint.ruleOffers`,由 `plugins/checkpoint-store-sql.ts` 窄读)**共用这一份**。 * * 🔴 单一属主(宪法 [ref]「schema 单一属主禁复制」):两条腿投的是 core 的**同一个** `RuleOffer` * (同步腿 = `AskRequest.ruleOffers`,耐久腿 = `PendingAction.ruleOffers`,core * `checkpoint-store.d.ts` 明写「CONTRACT (same as the synchronous `AskRequest.ruleOffers`)」)。 * 各写一份 zod 必然漂——闭词表 `match`/`kind` 加词、上限改口径,只改一处就出两种形。 * * 🔴 **闭集判别联合,`kind` 是判别位**(core d.ts 逐字:"A CLOSED discriminated union"): * · `single` —— 一条覆盖**整串**命令的规则(exact 精确形,或简单命令的 reviewed prefix 形); * · `batch` —— 复合命令的**逐段合取**批:选它 = **一次对全部 `rules` 说 yes**,批内**无**逐条子选择。 * 成员是判别联合([ref] B3:`command` 逐段 Bash 规则 | `directoryRead` cd 段铸的目录只读授权)。 * `uncoveredSegments` 是铸批时刻的**诚实溢出披露**(既不被本批覆盖、也不被卡的覆盖快照覆盖的段数), * `0` 读作「这批兑完,这条复合命令在那份快照的眼里就全覆盖了」;`uncoveredDetail`(§3.5,additive) * 逐段说**为什么**还没被覆盖(闭三词集),行数恒等于 count(count 是真源,明细缺席不是断言)。 * * 🔴 **序即契约**(core 逐字,消费端可以依赖):至多 2 条;whole-string exact single 在场时**恒 index 0**; * batch 至多一条、**恒末位**。所以本仓一切截断都必须是**取前缀**(保序、保原始下标),绝不重排。 */ /** * [ref] §3.5 —— `uncoveredDetail` **行数**的 server 容忍帽(理由与 {@link MAX_RULE_OFFER_BATCH_MEMBERS} * 同源:纯展示轴上的一条改坏/放宽的上游不该把一行撑爆)。⚠️ 超帽的处置是**丢整座**而不是截断:core 契约 * 明写「`uncoveredDetail.length === uncoveredSegments` whenever the seat is present」且「count 座是真源、 * 明细缺席不是断言」——截过的座会让一个按契约校验等式的消费端把整只 batch 判假,缺席则只是少一行解释。 */ export declare const MAX_UNCOVERED_DETAIL_ROWS = 32; export declare const RuleOfferSchema: z.ZodUnion; rule: z.ZodString; match: z.ZodEnum<{ path: "path"; exact: "exact"; prefix: "prefix"; wildcard: "wildcard"; subpath: "subpath"; }>; command: z.ZodString; }, z.core.$strict>, z.ZodObject<{ kind: z.ZodLiteral<"batch">; rules: z.ZodArray; rule: z.ZodString; match: z.ZodEnum<{ path: "path"; exact: "exact"; prefix: "prefix"; wildcard: "wildcard"; subpath: "subpath"; }>; command: z.ZodString; segment: z.ZodString; }, z.core.$strict>, z.ZodObject<{ kind: z.ZodLiteral<"directoryRead">; rule: z.ZodString; directory: z.ZodString; segment: z.ZodString; }, z.core.$strict>]>>; uncoveredSegments: z.ZodNumber; uncoveredDetail: z.ZodOptional; }, z.core.$strict>>>; }, z.core.$strict>]>; /** * {@link RuleOfferSchema} 的**不限长**孪生 —— 只判**结构**(闭词表 `kind`/`match`/`reason` + 若干字符串), * 长度由调用方在**脱敏之后**自己截。 * * 🔴 为什么必须有它(codex 交叉复审 [medium],验真后修):`redactSecrets` **会变长**(一条 * `password=…` 换成更长的遮蔽标记)。拿带 `.max()` 的形去校验**脱敏前**的原文,再脱敏,产出的就可能是 * 一条超限的文本;而任何**回读**路径若再跑一遍同一个函数,那一条会在第二次校验时被整条丢掉 —— 于是 * 「落库时在、读回时不在」,并且**只发生在有回读的那条腿上**(SQL 有列回读、LOCAL 直接从 blob 投), * 两个后端对同一份素材给出不同的卡面。同族先例逐字在 `tool-approval.ts` 的 `persistedRuleShadowed` * 发帧点(「先脱敏再截,一次算定」)。 * * ⇒ 正确顺序:**结构判(本 schema)→ 脱敏 → 截长 → 结果按 {@link RuleOfferSchema} 必然合形**。 * 这样函数对自己的输出**幂等**,两条腿同形。 * * 🔴 **未知键 `strip`(合并码重扫放宽,重扫二轮改准形):窄读只判结构,多余键剥掉、且不拒收**。 * base 带 `.strict()`,而 zod 4 的 `.extend()` **保留** strict —— core 给 `RuleOffer` 加一个 additive * 字段(它对这条面一贯的做法),逐条 `safeParse` 会**条条失败**,于是耐久腿的整只 offer 静默清零、列落 * NULL、读口省键,把「有供给」谎报成「无供给」,正是本键存在理由的反面(`checkpoint-store-sql.ts` 顶注与 * wire 契约都写死「缺席=真的没有 offer」)。同步腿的 `buildApprovalCard` 是显式逐键投影、对加字段天然免疫 * ⇒ 不放宽这一份,两条腿会在「上游长一格」这条轴上分家。 * * ⚠️ **写的是 `.strip()` 不是 `.loose()`**(重扫二轮,实测更正):zod 4 的 `loose` = **passthrough** —— * 未知键**原样留在** `parsed.data` 里,与本注和契约文写的「多余键剥掉」相反。放宽要的是「不拒收」, * 剥键是另一件事,`.loose()` 只做到了前者。旧形下真正在剥键的是 {@link boundedRuleOffers} 里那几行 * 显式投影,于是本 schema 对「剥键」这条判据是**空跑**;而它是**导出符号**,output 类型带 index signature, * 任何新消费者拿 `parsed.data` 直投就把上游未知键(其上**没有** `redactSecrets` 覆盖)带上跨租户可见的 * `GET /v1/approvals`。⇒ 改默认 strip 形:additive 加键照收、未知键当场剥掉,两个意图各自成立。 * 落库/读回的 {@link ApprovalCardSchema} 仍 `.strict()`(投影只写已知键,多余键根本到不了那一层)。 * * 🔴 **判别联合上的 strip 要逐臂加**(zod 4:`z.union` 自己没有 unknownKeys 政策,政策在成员上)—— * 两臂各自 `.strip()`,batch 成员的两臂亦然,否则对 additive 加键仍是 strict 的,而那正是本注要拆的雷。 * * 🔴 **闭词表不在放宽之列**([ref] B3 与 [ref] 词表纪律):`kind`(offer 与成员两级)/`match`/`reason` * 是闭集 —— 集外词 = 判假 = 整只 offer 丢(成员级的未知 `kind` 丢的是整只 batch,不是那一个成员;理由见 * {@link RuleOfferBatchMemberSchema} 顶注)。「additive 加键照收」与「闭集加词即拒」是两条轴,别混。 */ export declare const RuleOfferRawSchema: z.ZodUnion; rule: z.ZodString; match: z.ZodEnum<{ path: "path"; exact: "exact"; prefix: "prefix"; wildcard: "wildcard"; subpath: "subpath"; }>; command: z.ZodString; }, z.core.$strip>, z.ZodObject<{ kind: z.ZodLiteral<"batch">; rules: z.ZodArray; rule: z.ZodString; match: z.ZodEnum<{ path: "path"; exact: "exact"; prefix: "prefix"; wildcard: "wildcard"; subpath: "subpath"; }>; command: z.ZodString; segment: z.ZodString; }, z.core.$strip>, z.ZodObject<{ kind: z.ZodLiteral<"directoryRead">; rule: z.ZodString; directory: z.ZodString; segment: z.ZodString; }, z.core.$strip>]>>; uncoveredSegments: z.ZodNumber; uncoveredDetail: z.ZodOptional; }, z.core.$strip>>>; }, z.core.$strip>]>; /** {@link RuleOfferRawSchema} 的输出形(结构已判、文本未限长)—— 素材铸点在它上面做派生一致性与座尺两道后判。 */ export type RuleOfferRaw = z.infer; /** * [ref] §3.5 —— `uncoveredDetail` 明细座的**座级**预筛,两条腿在 {@link RuleOfferRawSchema} 判形之前 * 各调一次。 * * 为什么要在整体判形之前单独筛这一座:core 明写「ABSENCE(an older minter, a degraded consumer's re-emit) * is not a claim」且 count 座是真源 —— 一座坏掉的明细(集外 reason / 非数组 / 与 count 不等 / 超帽)是 * **展示轴**上的损坏,正确处置是「丢这一座、留 batch 本体」;让它把整只 batch 判假会把一条真可兑的合取批 * 从卡上抹掉(方向反了:少一行解释是难看,少一只可兑的 offer 是把人的选项吃掉)。 * 座被丢**必须留痕**([ref]:F 类 fail-open 走 `recordFailOpen`,计数 + 一次 warn)。 * 只做浅层判别(不校验成员、不解构别的键),判形权威仍只有 zod 一处。 */ export declare function screenUncoveredDetailSeat(item: unknown): unknown; /** * [ref] B3 的**前一形**:pre-7.2 的 batch 成员没有 `kind`(那一形只有一种成员 —— 逐段 Bash 规则的四键), * 而本仓的耐久腿在 park 行 `pendingAction.ruleOffers` 里**已经落了**这种成员(`checkpoint.rule_suggestions` * 列),同步腿在 core 7.1.0 npm 字节下(本分支落 main 候 7.2.x)收到的也是它。B3 的降级臂说的是「`kind` * 不识 ⇒ 丢整批」;一个**缺席**的 `kind` 不是「不识」——旧形恰好只有一种成员,缺席就是 `command`,零歧义。 * 所以这里做的是**已知旧形的 additive 回填**(浅层:仅当成员是对象、无 `kind` 键、且四个文本键在场时补 * `kind:"command"`),不是放宽闭集:带着一个陌生 `kind` 值的成员一个字节不动,照旧在 zod 处判假丢整批。 * 不回填的代价是真实的:滚动升级窗里,老二进制铸的每一条 park 行在新二进制的读面上整只 batch 消失 * (「有供给」谎报成「无供给」,本键存在理由的反面)。 */ export declare function backfillLegacyBatchMemberKind(item: unknown): unknown; /** * 卡面/帧面共用的 offer **投影型**(= {@link RuleOfferSchema} 的输出形)。判别位 `kind` 上闭集, * 消费端(SDK/cli 渲染)按 core 契约分臂;`batch.rules` 成员是判别联合(`command` | `directoryRead`), * 各自带 `segment` 渲染座(core 明写「渲染座、永不参与裁决」,与 rule/command/directory 同为 * UNTRUSTED-for-display);`uncoveredDetail` additive 明细座。 */ export type RuleOfferProjection = z.infer; /** * 帧→卡素材的**显式逐键**复制(与旧形 `ruleSuggestions` 的内联三键 map 同职,换形后判别联合需要 * 分臂,抽成具名函数)。为什么不 `structuredClone`/展开:显式逐键投影是本模块对「上游 additive * 加键」的免疫层(见 {@link RuleOfferRawSchema} 顶注)——加键到不了 `card_json`,strip 语义在 * 投影处兑现,不靠 schema 层兜。 * * 到这里的素材已经过素材铸点的结构窄读(`tool-approval.ts` `boundRuleOfferTexts` 拿 {@link RuleOfferRawSchema} * 判过形)⇒ 两级 `kind` 的运行期 `default` 臂在健康进程里不可达;它们守的是「本仓 dist 与 core 版本不同步」 * 那一形([ref] 词表范式:core 加臂 ⇒ `satisfies never` 编译红由人裁投影形;运行期撞到未知臂响亮抛,绝不把一个 * 不认识的臂当 batch/command 硬投)。 */ export declare function copyRuleOffer(o: RuleOffer): RuleOfferProjection; /** * [ref] 件 G1 —— `probeCause` 的形(落库/读面共用这一份;窄读入口是 {@link readProbeCause})。 * * `.strict()` 与卡的其余部分同调:落进 `card_json` 的东西是**本仓投出来的**三键,不可能有未知键 * (未知键在窄读那一层就被 zod 默认 strip 掉了)。 */ export declare const ProbeCauseSchema: z.ZodObject<{ code: z.ZodString; roots: z.ZodObject<{ shown: z.ZodArray; total: z.ZodNumber; }, z.core.$strict>; further: z.ZodOptional; total: z.ZodNumber; }, z.core.$strict>>; }, z.core.$strict>; export type ProbeCauseProjection = z.infer; /** * `AskRequest.probeCause`(以及耐久路同值的 `RiskDescriptor.probeCause`)的**边界窄读** —— 活卡帧与 * `card_json` 的**唯一**铸造点,两面因此结构性同值(不是两处各挑一次键的巧合)。 * * 🔴 姿势与 `RiskAxesEnvelopeSchema` 一致:`safeParse` 而不是裸 `as`(宪法 [ref] 边界必 schema)。 * 形不合 ⇒ **按缺席处置**(绝不半解出一个残缺结构上卡面);未知键被 zod 默认 strip(core additive * 加键不该让整只 ask 的卡面塌掉)。 * * 🔴 截长的两条不同判据(理由全文见 {@link MAX_PROBE_CAUSE_CODE} 顶注):`code` 超限 ⇒ **整只丢** * (截出来的是另一个机器码);`shown` 超限 ⇒ **截条目**,`total` 逐字保真。 */ export declare function readProbeCause(req: unknown): ProbeCauseProjection | undefined; /** * [ref](core 7.19.0)—— `readRootCandidate.dir` 的**上限**。 * * 🔴 **本地孪生退役,改读 core 的导出**(core 7.26.0 / S-538 ⓐ,[ref] 手抄投影下线):此前这里是 * `export const MAX_READ_ROOT_CANDIDATE_DIR = 1024`,注里写着「core 的 `READ_ROOT_CANDIDATE_DIR_MAX` * **未从包根导出**,故钉本地孪生」—— 那句话在 7.26.0 起是过期的,而一份手抄的界与它的属主分家时**不会红**: * core 把界调宽,本仓照旧按旧数扣掉一个 core 认可的目录(卡上少一条出路,而人只会读成「没有出路」)。 * 旧名整条删、不留别名(硬 breaking 三句:本常量是 server 内部导出,零外部消费者,tsc 红即通知)。 * * 🔴 超限的方向是**整只丢,不截** —— core 契约 `shell.read_boundary.grant_candidate_is_the_grant`:卡上 * 显示的串就是人要加进读目录的那个串,截过的目录是**另一个**目录,加进去也清不掉这只 ask。与 * {@link MAX_PROBE_CAUSE_CODE} 的 `code` 超限整只丢逐字同判据。 */ export { READ_ROOT_CANDIDATE_DIR_MAX }; /** 窄读的输出形 —— **就是 core 的那只型**,不在本仓再声明一份同形结构(重声明 = 上游改形那天本仓静默不红)。 */ export type ReadRootCandidateProjection = ReadRootGrantCandidate; /** * [ref] —— `AskRequest.readRootCandidate` 的边界窄读:**把这个目录加进本会话的读目录,这只 ask 就不会 * 再出现**。活卡帧的**唯一**铸造点。 * * 🔴 **零判读**:server 既不从命令文本重推目录、也不校验它是不是真的能清掉这只 ask —— 那是 core 已经 * 做过的判断(它把提议的根放回读边界又走了一遍),在下游再判一次就是同一个事实的第二个判官。 * 🔴 形不合 ⇒ **按缺席处置**;超限 ⇒ **整只丢**(界见 {@link READ_ROOT_CANDIDATE_DIR_MAX},core 的属主值)。 * 🔴 **缺席不是断言**:它同时覆盖「这一族 ask 没有任何目录能清掉」(敏感路径 deny 行、读边界压根读不懂 * 的命令、递归遍历、非读边界 ask……)与「老引擎」两形 —— 消费端只读在场,永远不读缺席。 * * 🔴 **逐成员挑键的代价,与它的解药**(core 7.22.0 #862b 真发生):下面这条 `return` 是**逐键重建**的, * 而返回型是 core 的 `ReadRootGrantCandidate` ⇒ core 给这只座加可选成员时,新成员被**静默剥掉**且 * tsc 一声不响(7.22.0 的 `covers` 就是这样被吞过一次:消费端拿到一条看起来正常的「目录出路」,按目录 * 回授,卡清不掉 —— 而那正是这个成员存在的唯一理由)。解药**不是**改成整只透传(那会让未经校验的远端 * 形上 wire),而是 `src/trace/core-keyset-guard.ts` ⑭ 的**成员级编译门** `_GuardReadRootCandidate`: * core 加成员 ⇒ 那里当场红,由人判该不该投。**改本函数时必须同步那张表。** */ export declare function readReadRootCandidate(req: unknown): ReadRootCandidateProjection | undefined; /** * [ref] 件 G2 —— `AskRequest.ruleEvidence` 的形(帧 / `card_json` / 读面共用;窄读入口是 * {@link readRuleEvidence})。三对成员各 = 值**或**五词命名缺席(`AskEvidenceAbsence`),core 引擎 * 盖章的证据恰一在场;server 只判形转录,**永不**自铸缺席词(server 铸词 = 谎报「某一层没报」)。 */ export declare const RuleEvidenceSchema: z.ZodObject<{ orgRevision: z.ZodOptional; orgRevisionAbsent: z.ZodOptional>; orgRule: z.ZodOptional; orgRuleAbsent: z.ZodOptional>; personalRuleDots: z.ZodOptional>>; personalRuleDotsAbsent: z.ZodOptional>; }, z.core.$strict>; export type RuleEvidenceProjection = z.infer; /** * `AskRequest.ruleEvidence`(core 5.35.0 [ref] G-2)的**边界窄读**——活卡帧(`ToolApprovalFrame. * ruleEvidence`)与 `card_json`(`buildApprovalCard`)的**唯一**铸造点,两面结构性同值(与 * {@link readProbeCause} 同款分工)。姿势逐条: * · `safeParse`,形不合 ⇒ 按缺席处置(宪法 [ref]);未知键 strip(core additive 加键不塌卡)。 * · **恰一不变量**:一对成员(值 / 缺席词)**双在场** ⇒ 整只丢——那是矛盾证据(引擎盖章的恰一, * 双在场只可能是第三方 producer 的坏行),半解上卡比缺席更坏。双缺席**容忍**(老 producer 行)。 * · `orgRule` 内容族(管理员写的规则文本)⇒ `redactSecrets` 后截 `MAX_RULE_TEXT_CHARS`,**先脱敏 * 再截一次算定**(`persistedRuleShadowed` R2-[medium] 教训:redact 会变长,两面各截会漂);空串丢整只。 * · `dots.actor` 身份串 ⇒ `redactSecrets`(对正常 id 恒等);超限**整只丢**(见上限顶注,身份不可截)。 * · `orgRevision` / 缺席词 verbatim(数值 / 闭集词,非内容)。 */ export declare function readRuleEvidence(req: unknown): RuleEvidenceProjection | undefined; /** * [ref] 第五单(core [ref] 修②)—— `ruleOffers` 缺席因由的**闭三词集**。 * * 🔴 **型从 core 派生,不手抄**(S-249):core 刻意**不导出**这个名字,也不导出运行期常量 * (`permission-rule-lanes.d.ts` 顶注逐字:「Deliberately not exported: this is a wire vocabulary, and its * two faces declare it literally … so a consumer reads the closed set on the type it is holding」)—— * 于是本仓**必须**自持一份运行期词表(zod 要字面元组),但**不必**自持一份型。做法:型 = * `AskRequest["ruleOffersAbsence"]` 的联合(那正是 core 要我们读的那张脸),运行期词表另立一行, * 两者之间架**两向编译围栏** ⇒ core 加词 / 改词 / 退役任一,本文件当场 tsc 红。 * (对照 {@link AskEvidenceAbsenceSchema}:那一族 core **给了**运行期常量,所以那边零词表、用 `z.custom`。 * 两族的姿势不同不是分歧,是「上游给什么就用什么」的同一条纪律的两种兑现。) */ export type RuleOffersAbsence = NonNullable; export declare const RuleOffersAbsenceSchema: z.ZodEnum<{ mandated: "mandated"; lane_cannot_speak: "lane_cannot_speak"; shadowed: "shadowed"; }>; /** * `AskRequest.ruleOffersAbsence`(core [ref] 修②)的**边界窄读** —— 活卡帧与 `card_json` 的唯一铸造点 * (与 {@link readProbeCause} 同款分工),两面结构性同值。闭集词 verbatim(非内容族,零 redact); * 形不合/缺席 ⇒ 不铸键(缺席不是断言 —— 它同时覆盖「有 offers」与结构性无车道的门,core 契约逐字)。 */ export declare function readRuleOffersAbsence(req: unknown): RuleOffersAbsence | undefined; /** S-114(core 7.4.0 [ref])—— 回落卡的计数与窗。`limit` 是**闭二词**(core d.ts 逐字),两个计数是 * 非负整数,`autoDenyAfterMs` 是 core 契约里的 `0..2147483647` 非负整数(`0` = 本卡不武装窗:部署把 * 旋钮关了,或 TOTAL 档的卡按 [ref]① 恒 0 等人)。四成员**全必填**——core 的 `DenialLimitFallback` 上 * 没有一个可选位,所以「少一位」不是老 core 而是坏值,整键不铸(与 `readRuleEvidence` 的恰一不变量 * 同款判据:形不合 ⇒ 丢整键,绝不半铸一张让人误读计数的卡)。 */ declare const DenialLimitFallbackSchema: z.ZodObject<{ consecutive: z.ZodNumber; total: z.ZodNumber; limit: z.ZodEnum<{ total: "total"; consecutive: "consecutive"; }>; autoDenyAfterMs: z.ZodNumber; }, z.core.$strict>; export type DenialLimitFallback = z.infer; /** * ⚰️ **退役成员的形(server 7.69.0–7.71.0 铸过,7.72.0 起永不再铸)** —— core 7.14.0 [ref] 把 * `classifierUnavailable` 从 ask 侧**整族**删掉(事实改骑 `GateDisposition.denied.cause`),所以本仓 * 的**铸造**腿(`buildApprovalCard` / 活卡帧 / inbox 行)本批全部摘掉;窄读函数 * `readClassifierUnavailable` 与对 core 型面的合规钉连同它们的素材一起删(素材没了,钉的两端只剩一端)。 * * 🔴 **这只 schema 本身留着,而且是承重的,不是没删干净**:`ApprovalCardSchema` 是 `.strict()`, * 而它同时是 `approval_ask.card_json` 这张**耐久列**的读契约(读侧三个 `safeParse`:重放腿、幂等重入、 * 对账扫描)。把成员从 strict schema 上删掉 ⇒ 7.69.0–7.71.0 铸下、升级时**仍然 pending** 的那些行 * 当场判假 ⇒ 「跳过 + 一次 warn」:人手上那张卡在重连后变不回来、幂等重入退成 park。 * ⇒ 一条耐久投影的 schema **必须读得懂它自己写过的每一版**;写侧只写当前引擎供得出的键,读侧接受 * 全部历史成员。这不是给退役键开特例,这是 strict + durable 这一组合的内生义务(同样的处置将适用于 * 未来任何一次键退役)。 * * 形保持原样(单成员 `cause` 必填、`.strict()`):它描述的是**已经写在库里的字节**,不许再动。 */ declare const ClassifierUnavailableSchema: z.ZodObject<{ cause: z.ZodString; }, z.core.$strict>; export type ClassifierUnavailable = z.infer; /** * `AskRequest.denialLimitFallback`(core 7.4.0 [ref])的**边界窄读** —— 活卡帧与 `card_json` 的唯一铸造点 * (与 {@link readRuleOffersAbsence} / {@link readProbeCause} 同款分工),两面结构性同值。 * * 四成员皆 core 铸的机器数/闭词(非内容族)⇒ **零 redact**;`.strict()` 让 core 将来加成员时本读面当场 * 判假、整键不铸 —— 与 `_GuardAsk` 的差集门是同一件事的两端(编译期点名 + 运行期 fail-closed),新成员 * 必须由人处置过才上 wire。缺席/形不合 ⇒ 不铸键(**缺席不是断言**:绝大多数 ask 根本不是回落卡)。 * * 🔴 **echo-only**:server 不读它做任何裁决,120s 窗的执行归 core 的 resolveAsk(引擎自有 deadline, * `settledBy:"timeout"` + `autoDenied:true` 由 core 盖章)。据 `autoDenyAfterMs` 在本仓自铸第二只定时器 * = 同一语义面两个写者(源头修复纪律),明令禁止。 */ export declare function readDenialLimitFallback(req: unknown): DenialLimitFallback | undefined; /** * [ref] §3.1 的**中性投影**——呈卡面看到的全部内容,与 wire 面的既有 `ToolApprovalFrame` 解耦。 * * `risk` 的三态形(设计稿 §14.1,core [ref] 回帖后定):`AskRequest.riskAxes?.{irreversible,egress}` * 是 **additive optional**,**缺席 = 引擎未判,不是「安全」**。所以两轴在这里是 `optional()`: * `true` / `false` / **缺席(未标注)** 三态各自可分,投影层**禁把缺席折算成 false** —— 那等于替引擎 * 打包票。`requiresRealApproval` 是**粗粒度**安全类标记,两侧的在场契约**不同、别混**:core 侧是 * `AskRequest.requiresRealApproval?: boolean`(**可选、只在为真时带**,缺席 = 这不是一次安全类 ask,是 * 正常的否定形而不是坏形);本卡面这一格是**必填 boolean**,由车2 的入参归一化(`=== true`)而来。 * 它与两轴是两件事而不是新旧替代——原注写的「core 今天唯一在场的标记」是 `riskAxes` 上树之前的现势话, * 自 core **5.14.0**(其 CHANGELOG 的 `AskRequest.riskAxes` additive 条)起两者并存。 */ export declare const ApprovalCardSchema: z.ZodObject<{ toolName: z.ZodString; message: z.ZodString; args: z.ZodOptional; argsOmitted: z.ZodOptional>; toolCallId: z.ZodOptional; risk: z.ZodObject<{ irreversible: z.ZodOptional; egress: z.ZodOptional; requiresRealApproval: z.ZodBoolean; }, z.core.$strict>; governanceForced: z.ZodOptional>; inputHasBidi: z.ZodOptional>; persistedRuleShadowed: z.ZodOptional; ruleOffers: z.ZodOptional; rule: z.ZodString; match: z.ZodEnum<{ path: "path"; exact: "exact"; prefix: "prefix"; wildcard: "wildcard"; subpath: "subpath"; }>; command: z.ZodString; }, z.core.$strict>, z.ZodObject<{ kind: z.ZodLiteral<"batch">; rules: z.ZodArray; rule: z.ZodString; match: z.ZodEnum<{ path: "path"; exact: "exact"; prefix: "prefix"; wildcard: "wildcard"; subpath: "subpath"; }>; command: z.ZodString; segment: z.ZodString; }, z.core.$strict>, z.ZodObject<{ kind: z.ZodLiteral<"directoryRead">; rule: z.ZodString; directory: z.ZodString; segment: z.ZodString; }, z.core.$strict>]>>; uncoveredSegments: z.ZodNumber; uncoveredDetail: z.ZodOptional; }, z.core.$strict>>>; }, z.core.$strict>]>>>; ruleOffersAbsence: z.ZodOptional>; denialLimitFallback: z.ZodOptional; autoDenyAfterMs: z.ZodNumber; }, z.core.$strict>>; classifierUnavailable: z.ZodOptional>; probeCause: z.ZodOptional; total: z.ZodNumber; }, z.core.$strict>; further: z.ZodOptional; total: z.ZodNumber; }, z.core.$strict>>; }, z.core.$strict>>; ruleEvidence: z.ZodOptional; orgRevisionAbsent: z.ZodOptional>; orgRule: z.ZodOptional; orgRuleAbsent: z.ZodOptional>; personalRuleDots: z.ZodOptional>>; personalRuleDotsAbsent: z.ZodOptional>; }, z.core.$strict>>; fromSubagent: z.ZodOptional>; sourceTaskId: z.ZodOptional; sourceAgentName: z.ZodOptional; delegation: z.ZodOptional; }, z.core.$strip>>; }, z.core.$strict>; export type ApprovalCard = z.infer; /** * `approval_ask.card_json` 里真正存的东西(设计稿 §6.2)。存**信封**而不是裸卡的理由:重放帧要回填 * 车2 铸的 `approvalId`(wire 面的回决通道桥 + 消费端与并行 `tool_approval` 帧的去重键),而它不属于 * 「卡的内容」——放进卡里会让 schema 的语义边界糊掉。`schemaVersion` 是形的版本闩(与行上的 * `schema_version` 列同值,列供 SQL 侧过滤,信封里这份供读面在解出内容**之前**判形)。 */ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{ schemaVersion: z.ZodLiteral<1>; approvalId: z.ZodString; card: z.ZodObject<{ toolName: z.ZodString; message: z.ZodString; args: z.ZodOptional; argsOmitted: z.ZodOptional>; toolCallId: z.ZodOptional; risk: z.ZodObject<{ irreversible: z.ZodOptional; egress: z.ZodOptional; requiresRealApproval: z.ZodBoolean; }, z.core.$strict>; governanceForced: z.ZodOptional>; inputHasBidi: z.ZodOptional>; persistedRuleShadowed: z.ZodOptional; ruleOffers: z.ZodOptional; rule: z.ZodString; match: z.ZodEnum<{ path: "path"; exact: "exact"; prefix: "prefix"; wildcard: "wildcard"; subpath: "subpath"; }>; command: z.ZodString; }, z.core.$strict>, z.ZodObject<{ kind: z.ZodLiteral<"batch">; rules: z.ZodArray; rule: z.ZodString; match: z.ZodEnum<{ path: "path"; exact: "exact"; prefix: "prefix"; wildcard: "wildcard"; subpath: "subpath"; }>; command: z.ZodString; segment: z.ZodString; }, z.core.$strict>, z.ZodObject<{ kind: z.ZodLiteral<"directoryRead">; rule: z.ZodString; directory: z.ZodString; segment: z.ZodString; }, z.core.$strict>]>>; uncoveredSegments: z.ZodNumber; uncoveredDetail: z.ZodOptional; }, z.core.$strict>>>; }, z.core.$strict>]>>>; ruleOffersAbsence: z.ZodOptional>; denialLimitFallback: z.ZodOptional; autoDenyAfterMs: z.ZodNumber; }, z.core.$strict>>; classifierUnavailable: z.ZodOptional>; probeCause: z.ZodOptional; total: z.ZodNumber; }, z.core.$strict>; further: z.ZodOptional; total: z.ZodNumber; }, z.core.$strict>>; }, z.core.$strict>>; ruleEvidence: z.ZodOptional; orgRevisionAbsent: z.ZodOptional>; orgRule: z.ZodOptional; orgRuleAbsent: z.ZodOptional>; personalRuleDots: z.ZodOptional>>; personalRuleDotsAbsent: z.ZodOptional>; }, z.core.$strict>>; fromSubagent: z.ZodOptional>; sourceTaskId: z.ZodOptional; sourceAgentName: z.ZodOptional; delegation: z.ZodOptional; }, z.core.$strip>>; }, z.core.$strict>; }, z.core.$strict>; export type ApprovalCardEnvelope = z.infer; /** 本车铸的信封形版本(写侧=刀 3b,读侧=本文件)。 */ export declare const APPROVAL_CARD_SCHEMA_VERSION = 1; /** * `buildApprovalCard` 的入参 —— 已经过车2 洗涤(`redactDeep`/`redactSecrets`)与**字节帽** * (`Buffer.byteLength`,设计稿 §6.5)的那一份投影素材。 * * 🔴 为什么结构声明而不是 `import type { ToolApprovalFrame }`:那会让本模块反向依赖 `tool-approval.ts` * (它已经 import 本模块的 `buildRevokeFrame`),形成一个纯为取一个类型而存在的环。结构形同时让「洗涤 * 与帽在**上游**已经做完」成为签名上的事实——本函数不 redact、不数字节,它只投影(纯数据 `build*`)。 * 既有的 `ToolApprovalFrame` 值结构上满足本接口(多出的 `type`/`approvalId` 两键在变量传参下无碍), * 于是「wire 帧与 card_json 用同一份已洗素材」是**结构上**成立的,不是靠两处各算一遍的巧合(§6.5)。 */ export interface ApprovalCardSource { /** 缺席折空串——`ToolApprovalFrame.toolName` 在 wire 类型上是 optional(闭合帧不带),而卡上必填。 */ toolName?: string; /** 已 redact。 */ message?: string; /** 已 redactDeep + 字节帽;省略时 `argsOmitted` 为真。 */ args?: unknown; argsOmitted?: boolean; toolCallId?: string; /** [ref]/[ref]:治理来源标 —— 与 wire 帧**同一份素材**(`ToolApprovalFrame` 结构上满足本接口), * 于是 live 帧 / `card_json` / 重放帧三面同源,不是三处各判一遍。 */ governanceForced?: true; /** E-14:bidi 在场位 —— 与 wire 帧**同一份素材**(`ToolApprovalFrame` 结构上满足本接口)⇒ live 帧 / * `approval_request` / `card_json` / 重放帧四面同源,不是四处各扫一遍。 */ inputHasBidi?: true; /** [ref](core 5.25.0):被越级的持久规则原文 —— **已 redactSecrets**(发帧点做,本模块只 clip)。 * 与 wire 帧**同一份素材**(`ToolApprovalFrame` 结构上满足本接口)⇒ live 帧 / `card_json` / 重放帧三面同源。 */ persistedRuleShadowed?: string; /** [ref](core 5.58.0):引擎铸的「不再询问」OFFER(**只在规则店装配时**由发帧点填;语义与在场性契约见 * `ApprovalCardSchema.ruleOffers`)。与 wire 帧**同一份素材** ⇒ live 帧 / `card_json` / 重放帧 * 三面同源。 */ ruleOffers?: readonly RuleOffer[]; fromSubagent?: true; sourceTaskId?: string; /** 已 redactSecrets。 */ sourceAgentName?: string; delegation?: { parentToolCallId: string; depth: number; agentName?: string; }; } /** * [ref] §3.1 的**中性投影**(设计稿 §6.2)—— 写侧的唯一铸造点。 * * `risk` 三态(§14.1):两轴 `optional`,`true`/`false`/**缺席(未标注)** 各自可分。`req` 是 core 交来的 * `AskRequest`(可能带、也可能不带 `riskAxes`),按 `unknown` 窄读;`requiresRealApproval` 走**独立入参** * (车2 已归一化的 boolean),不从 `req` 里读。core 侧它是可选、只在为真时带,缺席 = 这不是一次安全类 * ask(core 的铸造点语义,不是我们的折算);卡面这一格恒在,`false` 就是那个否定形的如实投影。 * (原注写的「core 今天唯一在场的粗粒度标记」自 core 5.14.0 的 `riskAxes` 起过期,见 `ApprovalCardSchema` * 头注。) */ export declare function buildApprovalCard(source: ApprovalCardSource, req: unknown, requiresRealApproval: boolean): ApprovalCard; /** 落库信封的纯构造(写侧;读侧 = `ApprovalCardEnvelopeSchema.safeParse`,**同一个 schema**)。 */ export declare function buildApprovalCardEnvelope(approvalId: string, card: ApprovalCard): ApprovalCardEnvelope; /** * [ref] §3.1 的呈卡帧(设计稿 §4.1)—— **live 铸造**形(重放形见 {@link buildReplayFrame}, * 两者投的是同一种帧,区别只在 `card` 的来源:这里是刚投影出来的,那里是从行上回读的)。 * * `expiresAtMs` **入参**而不是在这里现算:同一只 ask 的窗**只铸一次**(§3.1「永不赋新 deadline」), * 调用点已经算好了它(既是定时器时长的来源,也是落库 `expires_at_ms` 的值)——在这里重算会铸出第二个 * 真源,而两个真源必然漂。 */ export declare function buildApprovalRequestFrame(input: { askId: string; taskId: string; approvalId: string; card: ApprovalCard; expiresAtMs: number; nowMs: number; }): ApprovalRequestFrame; /** * [ref] §3.1 的呈卡帧(设计稿 §4.1 逐字)。 * * 与既有 `tool_approval` 帧是**并行加帧、不替换**(§4.2):存量壳只认旧帧,新帧是闭集加员;两帧同带 * `approvalId`,消费端据它去重。 * * 🔴 铸造/发射**不在刀 3c**(那是 3b 的域)。本文件只出「持久行 → 帧」的重放投影 `buildReplayFrame`。 */ export interface ApprovalRequestFrame { /** = SSE 具名事件名(与 `tool_approval` 同约定)。 */ type: "approval_request"; schemaVersion: 1; /** `deriveAskId` 派生(持久层主键;五元组见 approval-ask-machine.ts)。 */ askId: string; /** wire run id(= 协调器 ctx 的 taskId)。 */ taskId: string; /** v1 闭集单员("content" 留位不实现)。 */ kind: "permission"; card: ApprovalCard; /** `max(0, expiresAtMs - serverNowMs)` —— 单调减,**永不续窗**。 */ expiresInMs: number; /** 一次铸定、逐字回读,**永不重算**(§3.1「永不赋新 deadline、永不重启窗」)。 */ expiresAtMs: number; serverNowMs: number; /** * server 扩(不在设计字面):车4 落地前的**回决通道桥**——现行 * `POST /v1/tool-approvals/:id/respond` 的 id。也是消费端把本帧与并行的 `tool_approval` 帧去重的键。 * 车4 上线后仍保留(旧壳过渡窗)。 */ approvalId: string; } /** * [ref] §3.3 的**批级撤卡帧**(车6;设计稿 §3 逐字)。 * * 语义:本批里 `askIds` 列出的那些卡**已经作废**,壳按白名单清掉它们(未知 `reason` 按通用撤卡处理)。 * 已 `DECIDED` 的兄弟**永远不在** `askIds` 里(§3.0「已 DECIDED 不受撤卡影响」)——名单恒等于持久层 * 那一次原子事务真正翻成 `VOID` 的行集(`expireAsk`/`bindBatch` 的 `voidedSiblings` 返回形),不是 * 「本批全体」的推断。 * * 🔴 **发射面 = 逐 ctx 分发,durable 腿落账本、sync 腿 live-only**([ref] [ref] 改述;原句「live emit * only——reaper/收敛器无 seq 可分配」把**调用点**的约束错安在投递面上):帧恒经 `emitRevokeTo` 交给各 * ctx 的 `emitRevoke` 钩子,bg/resume 两条 durable 腿的钩子就是本腿账本的 `append`(seq 由腿的 ledger * 链步分配,与帧从哪个调用点来无关),于是「卡帧在哪个账本、撤帧就在哪个账本」逐 ctx 自洽;sync 腿的 * 钩子仍是 live SSE 写(行为零变)。丢帧(ctx 已退场/append 失败)的结构补偿不变 = 172 §3.1 的 * 「preamble 是壳侧卡集**全量对账基准**,不在基准内的一律清」——壳重连时的 preamble 本身就会让一张 * 没收到撤卡帧的僵尸卡消失;账本里重放的撤帧只用于时间线渲染,不是卡集基准。 * * v1 **不带 per-ask 终态**(§7-4 裁定):中选者另有 `PARKED` 的下行面(410 体 + 重放消失),壳按 * `askIds` 清卡即可。 */ export interface ApprovalRevokeFrame { /** = SSE 具名事件名(同 `approval_request` 约定)。 */ type: "approval_revoke"; schemaVersion: 1; batchId: string; /** 本次被撤(翻成 `VOID`)的 ask 集;空集**不发帧**(调用方判,见各发射点)。 */ askIds: string[]; /** `superseded_by_park` = 降级连坐(窗到期/收敛器绑定中选者,兄弟被撤);`aborted` = run 取消。 */ reason: "superseded_by_park" | "aborted"; serverNowMs: number; /** * [ref](codex R1-[high] 采,additive):**出处 run** 的 wire id(= 同 ask 呈卡帧的 `taskId`, * `AskOriginIdentity.taskId` / 行上 `task_id` 同源)。为什么必须带:分发集合是 broker 的 session 形 * (`boundAsk` 的继承链闭包 + 收敛器/reaper 的 `emitRevoke(frame, target)` 都按 (owner, sessionId) * 反查),集合里可躺着**别的 run** 的 ctx —— durable 腿的 ctx 收帧即落**自己**账本,无此键则那条行 * 无法归属,消费端也无从过滤(收敛器 current-set 形下甚至可能是一条无配对卡帧的孤儿撤帧行)。 */ taskId: string; } /** 撤卡帧的纯构造(`build*`:返回纯数据,无方法无捕获行为)。`askIds` 拷贝一份 —— 调用方传进来的多是 * store 返回的数组,帧不该与它共享可变引用。`originTaskId` 必填([ref] 归属轴,见帧顶注)——发射点 * 恒有出处(askBroadcast 的 `origin.taskId` / 收敛器与 reaper 的行上 `task_id`),可选会让漏传静默。 */ export declare function buildRevokeFrame(batchId: string, askIds: readonly string[], reason: ApprovalRevokeFrame["reason"], serverNowMs: number, originTaskId: string): ApprovalRevokeFrame; /** * 持久行 → 重放帧(设计稿 §5.2 逐字)。 * * 三条不变量都在这几行里: * - **坏行不炸开流**:`safeParse` 失败 ⇒ 返回 `undefined`(调用方跳过 + 记一次 warn,§5.3)。 * - **不续窗**:`expiresAtMs` 逐字回读,**永不重铸**;`expiresInMs` 由它与 `nowMs` 现算 ⇒ 同一张卡 * 连续两次重放必然严格单调减(机器判据 = §10 钉 B-2)。 * - **零写**:本函数不碰 store —— 重放腿只读,不 `transitionAsk`、不改 `expires_at_ms`、不重挂定时器。 * 过窗未收敛的行(`expiresInMs === 0`)**仍然重放**,不在这里代打 `expireAsk` CAS(那会制造第五个 * 竞争者;收敛是车2 的窗到期竞争者与车5 恢复扫描的职责)。 */ export declare function buildReplayFrame(row: AskRow, nowMs: number): ApprovalRequestFrame | undefined; /** {@link buildApprovalPreamble} 的返回形——帧 + 两个**计数**(调用方按自己的 logger 记 warn;本模块 * 保持纯函数,不吃 logger:那会让一个 `build*` 捕获行为)。 */ export interface ApprovalPreamble { /** 按 `createdAtMs` 升序(= 两个读口的 SQL `ORDER BY` 与 InMemory twin 的排序),截帽后仍是升序。 */ frames: ApprovalRequestFrame[]; /** `card_json` 形不合(safeParse 失败)被跳过的行数。>0 ⇒ 调用方记一次 warn。 */ skipped: number; /** 超 `replayMax` 被丢弃的**最旧**卡数。>0 ⇒ 调用方记一次 warn(§5.3 读面帽)。 */ dropped: number; } /** * 开流 preamble 的行集 → 帧集(设计稿 §5.3 的读面帽 + 坏行跳过)。 * * 帽的方向:超限时**只投最新的 N 张**——未决卡的价值随时间倒序递减(最老的那些多半已经在别处过窗/ * 被收敛),而壳的呈卡面容量有限。丢弃是**读面**行为,不写库、不改状态;写侧的准入门(设计稿 §0 X-2) * 是另一件事,在刀 3b。 * * `replayMax <= 0` 视作「不投」(运维显式关掉重放),不当成「无帽」。 */ export declare function buildApprovalPreamble(rows: readonly AskRow[], nowMs: number, replayMax: number): ApprovalPreamble; /** preamble 帧的 SSE 投递形(具名事件,**不带 `id:`** —— 它不是账本行,不参与 Last-Event-ID 游标)。 */ export declare function buildApprovalPreambleSseFrames(frames: readonly ApprovalRequestFrame[]): Array<{ event: string; data: unknown; }>; //# sourceMappingURL=approval-card.d.ts.map