import type { Logger } from "./logger.js"; import type { Metrics } from "./metrics.js"; /** 登记项:`cls` 是判据表的分类(见模块头——只有这两类允许走本封装),`note` 说清"放行的最坏后果"。 */ export interface FailOpenTagEntry { /** `F`=纯体验/缓存回退,放行最坏后果只是"难看/不够好";`P-DEBT`=保护型欠账,方向明知不对、显式记债。 */ readonly cls: "F" | "P-DEBT"; /** 这条兜底放行了什么、最坏后果是什么。写给未来读遥测的人,不写来历。 */ readonly note: string; } /** 闭集 tag 词表。形=`..`(跨仓同形,便于三仓遥测并表)。 */ export declare const FAIL_OPEN_TAGS: { readonly "server.approval-ask-journal.compaction-failed": { readonly cls: "F"; readonly note: "S-384(`plugins/approval-ask-store-file.ts` 的 `write()` 尾巴,local 车道的 File 形 ask 账):账本**压实**(整本重写成行快照)失败 —— 盘满 / 只读挂载 / `.tmp` 不可写。压实是**纯管家动作**,走到它的时候这一次写**已经 append+fsync 落盘、也已经翻进内存** ⇒ 把它的 I/O 错误抛给调用方 = 把一次已生效的人类批准报成失败(客户端重试会拿到 `ask_not_pending`),正是本店存在的理由被反过来用。所以吞 + 留痕:账本继续长(core 的 `AppendLog` 在 `closeForSwap` 之后惰性重开,一次瞬时错误不会把它变成砖),下一次写再试压实。放行的最坏后果 = 账本不再收缩(盘占用按写入量线性增长),**语义零影响**(行与转移一个不差)。计数非零 = 这台机器的数据根写不动了,运维要去看盘。"; }; readonly "server.approval-ask-journal.corrupt-record-skipped": { readonly cls: "F"; readonly note: "S-384(`plugins/approval-ask-store-file.ts` 的启动重放,local 车道的 File 形 ask 账):账本**中间**有一行解析不出(位腐 / 被人手改 / 文件系统故障)⇒ core `readJsonlRecords` 跳过它,本店继续重放其余记录。**撕尾不走本 tag**(崩在半行是这个格式存在的理由,由 quarantine + warn 单独处置),**词表外动词也不走**(那是降级,直接拒启)。放行的最坏后果之所以有界,靠的是店的 CAS 形:每一条写都带 `WHERE state = ` 谓词 ⇒ 丢一条记录只能让**后续转移失败**(no-op),绝不可能让一条非法转移成功 —— 一条已决 ask 退回 STREAM_PENDING 的方向是 fail-closed(工具不会因此被放行),一条待决 ask 消失的方向是「等卡的那条腿窗到期走 park」= S-384 之前的行为。所以这条兜底丢的是**进度**,不是门。同批还有 quarantine 原件 + 一条点名路径的 warn;计数让「某台机器的 ask 账一直在腐」与「这台机器本来就没有审批」在遥测上分得开。"; }; readonly "server.terminal.persisted-plane-malformed": { readonly cls: "F"; readonly note: "S-136 合并重扫确认项(`terminal.ts persistedPlaneOf`,持久 blob 的平面读面):盘上一条 `terminal` 因由形不合(`paused` 缺 gate / 词出闭集)让 core 的 `terminalProjection` 抛 ⇒ 本读面对这一行答**全格缺席**而不是整只 500。放行的最坏后果=一条坏行在列表/舰队读面上显示为无终局词;计数 + probe 带原句,便于按行回溯。"; }; readonly "server.mcp-panel.last-leg-unreadable": { readonly cls: "F"; readonly note: "S-297(`http/routes/sessions.ts lastLegMcpFor`,`/mcp` 面板的可选键 `lastLegMcp`):按会话取最近 run 的索引读 / 那条 run 的账本读**抛错或超期** ⇒ 该键缺席,面板本体照常 200。放行的最坏后果=这一屏少一格「上一次真挂上了什么」(壳退回翻账本),不是门、不是权限、不是凭证 —— 而反方向(让附加键的读失败打掉整张面板)会给一口此前零 MCP 配置下根本不碰账本的**既有**读面新加一条挂掉的理由。缺席本就是本键 wire 上的一等状态(消费端必须会读),所以这条兜底不产生任何新的消费端形态;计数让「某台部署的账本一直读不出来」显形。"; }; readonly "server.workflow-park-index.sync-failed": { readonly cls: "P-DEBT"; readonly note: "S-185 车CM(codex r1-F2 [high],验真后登记):workflow 出身 park 的 join 索引(`workflow_agent_session` / local 账本)在**一次成功的 run 写之后**同步失败 ⇒ 该次同步被吞,重试要等这条 run 的**下一次**写。而 park 恰好常常是这条 run 的**最后**一次写(park = run 终局 `failed`)⇒ 那条 park 可能永久 join 不到,`/decide` 结构性退回本批之前的行为(409 `conflict.resume_context_unavailable`)——**方向是 fail-closed**(不误配、不伪造、不放行任何东西),丢的是「人能不能把这条批准送出去」这个能力。归 P 类的理由是它决定**车道选择**(缺行 ⇒ 选中 legacy 腿),归 DEBT 的理由是真正的解要一条**按子代 checkpoint 键的耐久决议回执 + 重启期回填**(与 codex r1-F3 同一件欠账,另车)。计数非零 = 有 park 因此决不掉,运维要按 detail 里的 runId 去查。"; }; readonly "server.trace.gate-outcome-defective": { readonly cls: "F"; readonly note: "S-136(core 7.6.0 design/390 S6-A,`trace/project.ts` 的 `screenedGate`:写腿 `toolEndEventData` 直用;**三条**耐久读腿(投影回放 `toolResultFieldsOf`,`GET /v1/tasks/:id/turns` 与 `GET /v1/tasks/:id/stream` 共用 / 账本裸回放 `GET /v1/runs/:id/events`,经 `screenedStoredEventData` / `/decide` 幂等回放 `executionOutcome`)同走这一只筛,零双读 —— 本服务上一版写下的退役词旧拼法由启动期的一次性迁移在库里原地改完,读腿因此没有第二种读法):一条 `tool_end` / 账本行携带的 `GateOutcome` 过不了 core 的 `screenGateOutcome`(I1–I4 不变量,或某一格词出闭集),**或投影期抛出**(S-239:`settlement.note` / `who.approver` 在 ≥2^23 单段上撞 `redactSecrets` 既有的回溯上界 —— 筛子总函数化后同走这一口,而不是逃出去断掉整条账本流)⇒ **整条 gate 记录不上帧**(report + withhold,core 契约逐字给出的两种响亮形之一;绝不「静默修补」成一条自洽的记录)。放行的最坏后果=这一帧的消费端渲染不出「谁拒的 / 那次等待怎么结束的」,退回到 `isError` + 文案;它**不改变任何裁决**(门早已在 core 里判完,这里只是观测面)。反方向(把一条自相矛盾的记录照发)更坏:消费端按 I2/I3 读出来的结论会是假的。计数让「某个世代的产生者一直在发坏记录」显形。"; }; readonly "server.trace.tool-roster-malformed": { readonly cls: "F"; readonly note: "⑪(core 7.8.0 design/388,`trace/project.ts` 的 `wiring_manifest.tools` 段与 `tool_roster_delta` 构造器共用):载荷过不了 core 自己的 typebox 判形器(`ToolRoster` / `ToolRosterDelta`)⇒ 那一段缺席 / 那一帧不发,**绝不半张脸上 wire**。放行的最坏后果=消费端这条腿看不到工具名册(壳退回按名字分组、用通用审批卡),不影响任何裁决——名册是观测/渲染面,门在引擎里早已判完。反方向(把一份判不了形的名册照发)更坏:消费端会把读不出的行当成「这只工具没挂」。计数让「某个世代的产生者一直在发坏名册」显形。"; }; readonly "server.trace.projection-input-malformed": { readonly cls: "F"; readonly note: "7.78.0(core 7.18.0 #741/#786/#789/#790,`trace/project.ts` 的五张新投影脸共用一条规则):`tool_progress` / `tool_disclosure` 两帧的必需位读不出 ⇒ **整帧不发**;`wiring_manifest.hooks[]` / `wiring_manifest.lsp` / `context_usage.sections[]` 三段的容器形或成员形读不出 ⇒ 那一行/那一段被丢、全丢光即键缺席。规则一句话:**在场但读不出 ⇒ 计一次并丢弃;缺席从不计数**(缺席是这几张脸上的一等状态,契约把它定义成一个正面事实 —— `hooks` 缺席 = 「本条腿没有自己的钩子记录」、`sections` 缺席 = 「这条腿没装配出段 IR」、`lsp` 缺席 = 「本部署没接 LSP」)。放行的最坏后果=这条腿的消费端少一张观测/渲染面(壳退回上一份已知状态或通用卡片),**不影响任何裁决**——门在引擎里早已判完。反方向(半张脸照发)严格更坏:消费端会把读不出的行当成那个正面事实。计数是**唯一**能把「坏世代 / 第三方 producer 一直在发坏载荷」与那个正面事实分开的位。"; }; readonly "server.task-mcp.source-label-dropped": { readonly cls: "F"; readonly note: "B-036 / S-143(`mcp-source-label.ts readMcpSourceLabel`,请求腿 + center 腿共用):`mcpServers[].source`(分组标签,显示用、开集)坏形——空串 / 超 80 字 / 非字符串——**丢键留 server**。放行的最坏后果=这台 server 在 wiring_manifest.mcp[] 里没有分组标签(难看),不是门/权限/凭证;本地 config.d 腿由 settings-schema 1.7.0 在解析处拒,故本臂只在请求腿与远端 /effective 直读 JSON 腿可达。"; }; readonly "server.memory-optout-grant.onfault-hook-threw": { readonly cls: "F"; readonly note: "design/383 S-1(applyMemoryOptOutGrant):本地 grant 腿故障时的**披露钩子**(onFault → warn 日志)自己抛了。钩子只是观测面:吞掉它保住的是「resolver 不因一行日志失败而 reject」(那会把一条腿的故障扩成整车故障 —— core 的 resolver-throw 降级臂作废同一次解析里 center 已答好的 allowWorkflows/allowFork);代价是**那一行披露丢了**,而 `allowMemoryOptOut` 键缺席这件事本身不受影响(fail-closed/fail-safe 极性由 core 按姿态判)。本计数让「披露钩子一直在炸」显形。"; }; readonly "server.memory-optout-verdict-absence.warn-hook-threw": { readonly cls: "F"; readonly note: "S-76(applyGovernedVerdictAbsenceWarning):`governed` + center-only verdict 源的部署上,**首次**解析到 `allowMemoryOptOut` 缺席时那一行**一次性披露**钩子自己抛了。与上一条同族、同理由:钩子只是观测面,吞掉保住的是「resolver 不因一行日志失败而 reject」(core 的 resolver-throw 降级臂会作废同一次解析里 center 已答好的 allowWorkflows/allowFork);代价是**「center 从不发这个键」这件事少了一行运行期证据**(启动期那行 `memory_capture_governed_center_only_source` 仍在,拒/放的极性也不受影响 —— 缺席由 core 按姿态判 fail-closed)。本计数让「披露钩子一直在炸」显形。"; }; readonly "server.run-store.file-hydrate-unreadable": { readonly cls: "P-DEBT"; readonly note: "件6(#43 案 [4281]):`FileRunStore.hydrate()` 的 `readdirSync(runs/)` 失败 ⇒ 引擎以**空账**起动。旧形是空 catch + return:data root 不可读(权限/挂载丢失/路径被换走)时零告警,此后每一次 getRun 恒 miss —— 而「恒 miss」正是 #43 现场看到的形,一次环境故障被伪装成「本来就没有这条 run」。方向问题在于吞掉之后被读成的是「证不出有行 ⇒ 没有行」,两句话不等价。**缺省不拒启是刻意保留的行为**,所以这条留作债:响亮化(error 级启动日志带 runs 绝对路径+异常文案)+ 本计数,让它在遥测里显形而不是只活在一行日志里。⚠️ 2026-08-29(S-46 件B)起终局格已兑现:部署可用 `RUN_STORE_STRICT_HYDRATE=on` 把这一格换成 **fail-closed 启动门**(拒启,遗言同样带绝对路径+异常文案);本计数因此只在**缺省 off** 档上产生 —— 读遥测时别把「计数为零」直接读成「没有部署踩到这条路」,也可能是它们开了拒启门、根本起不来。"; }; readonly "server.fork-routing.placed-probe-host-error": { readonly cls: "F"; readonly note: "#292 placement 半场:hostPlacedProbe 对 host 店的 requireExisting 探针失败且**不是 not_found**(host 读故障)时折叠为 absent —— 路由回旧规则(claim-create 会在 transient 建新会话)。放行的是「host 暂不可达时 transient 腿保持独立可用」;最坏后果=故障窗内对一个真 placed id 铸出 transient 影子(host 恢复后双身,requireExisting 冷探针会重新指回 host 真身)。not_found 是常态路径不计数;只有真故障臂进本计数。"; }; readonly "server.fork-routing.placed-roster-meta-unreadable": { readonly cls: "F"; readonly note: "#292 placement 半场:host-fallback 命中后顺手读 meta 入 placed 名册失败(stub 会话无 getMetadata / 读故障)。名册只是省探针的缓存,不是判定 —— 缺一条=该 id 下次 acquire 多付一次 hostPlacedProbe,正确性零影响。计数留痕是为了让「meta 读一直在失败」这种环境病显形。"; }; readonly "server.hitl.frame-undelivered-stream-closed": { readonly cls: "P-DEBT"; readonly note: "HITL 的 **open** 帧写向一条已断/已关的 SSE 连接 ⇒ 静默丢弃,上游据此把投递记成成功,该 ask 挂到 TTL 才按无人应答结算。⚠️ #173 后 question 开帧改走断流 THROW(立即结算 unavailable),因此本 tag 在产的只剩 **elicitation** 一族——读计数时勿把它当作 question 的人在环缺口。no-op 而非 throw 对 elicitation 仍是既有的刻意决定,本条只保证它不再无声。"; }; readonly "server.stream.frame-dropped-stream-closed": { readonly cls: "F"; readonly note: "非 HITL-open 的运行流帧写向已断连接 ⇒ 丢弃。连接都没了,这一帧本就无人可看;单独立项是为了不让它挤进上面那条保护型计数。"; }; readonly "server.question.open-frame-undelivered": { readonly cls: "P-DEBT"; readonly note: "AskUserQuestion 的 open 帧投递失败 ⇒ 该问按 unavailable 结算(#166 前是合成空答)。⚠️ 本臂**恒在门判 allow 之后**(工具已在执行),core 已过挂起点 ⇒ 落点**不是 park**:普通腿发 declined_unavailable 自答续跑卡,赎回既有审批的腿返 isError(engine 拒绝自答)。也就是说这一形**不铸 checkpoint、不可恢复**,人在环这道门被一次投递故障真的跳过了(门判 allow 之前就不可达的那些腿走的是 durable park,不经本臂)。「绝不把 run 挂死」是刻意的产品姿态,但债要在遥测里显形——记债,不当合法兜底。"; }; readonly "server.elicitation.open-frame-undelivered": { readonly cls: "F"; readonly note: "MCP elicitation 的 open 帧投递失败 ⇒ 结算 decline。方向本身是 fail-closed(问不到人就是拒),缺的只是留痕。"; }; readonly "server.question.complete-breadcrumb-dropped": { readonly cls: "F"; readonly note: "question_complete 面包屑(对话框消解提示)持久化失败。答案本身走的是另一条路且早已返回,丢的只是壳里一次收尾渲染。"; }; readonly "server.elicitation.complete-breadcrumb-dropped": { readonly cls: "F"; readonly note: "elicitation_complete 面包屑持久化失败。同上——答案已经在壳手里,丢的是收尾渲染。"; }; readonly "server.session-titler.gateway-unreachable": { readonly cls: "F"; readonly note: "S-178(cli [6662] ① clay 裁定转达,`session-titler.ts logFailure`):自动会话标题那一次廉价模型调用失败(gateway 不可达 / 凭据缺失 / 超时)。标题是**纯装饰**——列表面回退 objectivePreview,零行为影响,门/裁决/账本一个字节不受它管。此前每个新会话首次同 cause 打一条 `warn`,于是一台没配模型 gateway 的单机部署在日志里被这条噪音刷屏,壳因此去关 `SESSION_AUTO_TITLE`(替上游压噪),而那是把一个部署级旋钮当消音器用。现在:逐次 `debug` + 本计数 + 一次性 warn(recorder 自己的),运维仍看得见「这台机器的 titler 一直在失败」,用户面零噪音。观测面另有 `session_title_total{outcome}` 逐次记账。"; }; readonly "server.prompts.artifact-cache-write-failed": { readonly cls: "F"; readonly note: "已验签的提示词工件回填本地缓存失败。工件本身已返回给调用方;代价是下次同 digest 还要再取一次网络。"; }; readonly "server.fleet.snapshot-terminal-window-unavailable": { readonly cls: "F"; readonly note: "`/v1/fleet/stream` 连接时快照的**近期终态行窗**(#189:引擎重启后 boot 判死的 workflow 行,pull 自 durable store)读失败或超预算(2s)⇒ 本次快照少这几行历史。放行的最坏后果=面板首屏看不到刚结束的 workflow(与本修之前的行为等同,不是新损失);真源不受影响(`GET /v1/workflows` 照常)。方向刻意 fail-open:一次慢查询不该把整条 SSE 的握手拖住——少几行是难看,连不上流是坏掉。"; }; readonly "server.rules.rejected-prepare-row-left": { readonly cls: "F"; readonly note: "被拒的 CC 导入 prepare(候选超帽)未能收掉 core 已落盘的那条 pending 审批记录。放行的最坏后果 = 共享库里留一条**永远没人要的** pending 行(零权限影响:没有票就兑不动它,而且它连确认都没过)。不留痕就没人知道清理面在漏,故记 F 类;真解是给 permission_rule_approval / permission_rule_ticket 加保留期清扫腿(已列后续件)。"; }; readonly "server.rule-offers.uncovered-detail-dropped": { readonly cls: "F"; readonly note: "design/382 §3.5(S-44):batch offer 的 `uncoveredDetail` 明细座形不合(集外 reason / 非数组 / 行数与 count 座不等 / 超 MAX_UNCOVERED_DETAIL_ROWS)⇒ 只丢这一座、batch 本体照投。放行的最坏后果 = 卡上少几行「这段为什么还会问」的解释(count 座仍在,core 契约明写明细缺席不是断言);把整只 batch 判假才是错的方向(一只真可兑的合取批从卡上消失)。纯展示轴 ⇒ F 类;必须留痕,否则上游明细座漂形在遥测里零痕迹。"; }; readonly "server.rules.ticket-claim-release-failed": { readonly cls: "F"; readonly note: "CC 规则导入票的**认领回滚**失败(认领之后的某一步没成 ⇒ 本该把认领放回去,而这次放回本身也抛了)。放行的最坏后果 = 这张票留在已认领态、属主这一轮不能重试 —— 恰好等于加认领回滚**之前**的行为,不是新损失;票随 TTL 自然消失,属主重走一次 prepare 即可拿新票(导入按 core 的设计幂等:同一条规则再兑付只是同一个 dot 的重放)。方向上没有任何权限被放宽(规则**没有**落地才走到这条臂),故 F 类;必须留痕,否则「票为什么突然不能用了」在遥测里没有任何痕迹。"; }; readonly "server.approvals.rule-suggestions-cell-unreadable": { readonly cls: "F"; readonly note: "运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint.rule_suggestions` 这一格**不是合法 JSON 文本**(手改过的行 / 未来列语义漂了 / 回滚残留)。放行的最坏后果 = **这一行**的「不再询问」候选缺席(展示/分诊材料,永不参与 resume、也不是任何判据),队列其余部分照常;不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 cell 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空)。故 F 类,但必须留痕:静默吞掉之后「候选为什么消失了」在遥测里没有任何痕迹,而本键存在的全部理由就是「缺席 = 真的没有候选」。"; }; readonly "server.approvals.risk-descriptor-cell-unreadable": { readonly cls: "F"; readonly note: "运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint.risk_descriptor` 这一格**不是合法 JSON 文本**(手改过的行 / 回滚残留 / 列语义漂;与同一 `rows.map` 里的 `rule_suggestions` 是同族坏法,两方言都是 TEXT 列)。放行的最坏后果 = **这一行**的分诊 descriptor 落 null:severity 折 0 排到有 descriptor 的行之后、`shadowedRule` 无可脱敏、读面派生的 `governanceForced` 徽章不亮 —— 三者都是**展示/分诊**,不是执法判据(门早已 park,真按 `shellGateDoctrine` 判的 `active-run-conflict.ts` 读的是 checkpoint blob 的 `get()`,不经本读面),且 `governanceForced` 的成文语义本就是「缺席 = 没有治理来源的**证据**」。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 cell 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),连同其余所有行一起消失。故 F 类;tag 与候选那格**分开计**,并计会让「哪一格在坏」在遥测里读不出来。"; }; readonly "server.retention.sweep-report-dropped": { readonly cls: "F"; readonly note: "留存 sweep 的**失败上报**自己抛了(poison error 的 toString / 一条抛异常的 logger transport)⇒ 那一条 warn 与 stuck 指标发不出去。放行的最坏后果是**可观测性**损失而不是数据面损失:删除与审计的正确性完全在店事务里,本臂只影响运维看不看得见「这个域连败了」。不放行的后果反而更坏 —— 一个从上报里逃出去的异常会打断 per-domain 循环、饿死后面每一个域(reapers 的同款保护逐字同理由)。"; }; readonly "server.parked-revive.ancestor-classifier-unreachable": { readonly cls: "P-DEBT"; readonly note: "跨副本赎回 parked 审批时,祖先层在 park 时是 **auto-mode 武装**的,但那只分类器是祖先任务上的活闭包(绑着它自己的转写窗+brain),跨进程重建不出来 ⇒ 本仓交一只如实拒答的 decider,core 收到 `unavailable` 后**不产生任何自动裁决**、原样落到祖先冻结审批席那条链(本腿的席位又是无 ALS 的降级形 ⇒ 再 park 给人)。方向:分类器本会 allow 的改成问人(更严),本会 block 的也改成问人(**不是自动放行**,但比自动拒松一档)⇒ 记债不当合法兜底。计数 = 「丢了祖先分类器判决的继承 ask」次数。收口件二选一:core 把分类器判据持久进链条目,或让祖先 decider 有可跨进程重建的形。"; }; readonly "server.e2b.orphan-skip-unknown": { readonly cls: "F"; readonly note: "#242 E2B 孤儿回收对一只沙盒**判不了属主/活性**(metadata 缺 taskId 的病态形 / run 行状态词在 LIVE 与 TERMINAL 两表之外 / getRun 抛错)⇒ 恒不杀,本轮跳过。方向纪律:回收是清理型能力,缺席判据=不动作——误杀是杀活任务的手,漏杀只是钱;但每次跳过必须计数,否则「回收在跑却总有几只杀不掉」的病灶(店故障/新状态词未收编/别家部署共用 key)永不显形。"; }; readonly "server.fleet.subscriber-callback-threw": { readonly cls: "F"; readonly note: "fleet bus 某订阅回调抛错 ⇒ 该回调本帧作废,其余订阅方与发布方不受影响。隔离是承重的:扇出同步,修前异常会传回发布方 put/update 投影点,core 持久化 catch{} 且不推进 storeRev ⇒ durable 行冻在 running 而 notify 已 ack(#183 复审 R3 HIGH)。丢的只是一个消费方的一帧渲染,故 F 类;但必须留痕——静默吞掉等于订阅方病灶永不显形。"; }; readonly "server.fleet.reconcile-read-failed": { readonly cls: "F"; readonly note: "#261 fleet 存活对账腿的一次 durable 读失败/超预算(2s)⇒ **本周期整轮跳过**并开熔断,不做任何投影。放行的最坏后果 = 幽灵行(发布方已死、行僵在内存 Map 里)在面板上多活几个周期,与本腿存在之前的行为等同(那时它永远活着),不是新损失;真源不受影响(`GET /v1/runs/:id` 照常)。方向刻意 fail-open 且**只向留行一侧**:读不出来 ≠ 该退休——库抖一下就把一屏在跑的任务从面板上抹掉,比多留一条幽灵行伤得多。必须留痕:静默吞掉之后「对账腿为什么一直没生效」在遥测里没有任何痕迹,而本腿的全部价值就是「幽灵行会自己走」。"; }; readonly "server.fleet.terminal-notify": { readonly cls: "F"; readonly note: "S-450:**两条臂共用**(detail 里 `read-failed` 分得开)—— ① `observability/run-terminal.ts` 的**行为订阅者**扇出(唯一铸点 `notifyRunTerminal`,三只 store 的 CAS 赢分支各调一次):某个订阅者(今天只有 fleet 对账腿的候补窗升级腿)在 `setTerminal` 的调用栈上抛错 ⇒ 吞掉并计数,终局写照常成功;② `fleet/fleet-reconciler.ts` 的事件腿在收到事件后那一次 durable 点读失败 ⇒ 这条行留在候补窗里等周期臂。⚠️ 同一只铸点上的**日志腿**(`setRunTerminalLogObserver`)走的是**相反**契约(刻意不 try、抛出让 `setTerminal` reject),**不计本 tag** —— 读遥测别把两席混成一件。方向承重:这条腿的全部价值是「把候补窗行的成对终帧从『下一个对账周期(≤60s)』提前到『durable 写落地的同一 tick』」,它是一格**投影提速**;把它的 bug 外溢成终局写 reject 会让 run 卡 running 到 reaper、claim 悬挂([868] 指纹),代价高出几个量级。放行的最坏后果 = 这条行的成对终帧退回到周期臂补发,**与本件存在之前的行为逐字等同**,不是新损失;故 F 类。必须留痕:静默吞掉之后「事件腿为什么一直没生效」在遥测里没有任何痕迹,而面板上看到的只是『偶尔慢一分钟』这种没人会立案的症状。"; }; readonly "server.fleet.run-status-unmapped": { readonly cls: "F"; readonly note: "A-032 P1-④:`runStatusToFleet` 收到**词表外**的 run 状态词(闭集签名之外的唯一来路 = `rowToRun` 把无约束的 status 文本列裸 cast,滚动升级里更新的副本写的新词被旧副本读到)⇒ 折成 `idle`。放行的最坏后果 = 该行在 fleet 面板上**永久滞留**(removal 集 {completed,failed,killed} 收不到 idle),纯展示面、不影响 run 本身与任何执法判据,故 F 类。方向刻意不改(把 miss 折成终局会把一条还在跑的任务从面板上摘掉,误摘比多一行更伤)。留痕是承重的:2026-06-28 on-box e2e 逮到的 `blocked` 泄漏当年只能靠人眼在面板上发现——计数让下一次词表漂移在遥测里当场显形。编译期一侧已由闭集入参 + `never` 臂执法。"; }; readonly "server.run-store.stale-claim-quarantine-unremoved": { readonly cls: "F"; readonly note: "#213 陈旧 claim 接管:claim 已 rename 进 tmp/ 隔离名(接管本体已完成、正确性不受影响),但隔离件 rmSync 失败留在 tmp。放行的最坏后果 = tmp 里多一个死文件(不在 activeDir,hydrate 永不把它当 claim 复活);故 F 类。必须留痕:反复删不掉 = tmp 权限/fs 病灶,静默吞掉会让 tmp 无声膨胀。"; }; readonly "server.otel.export-failed": { readonly cls: "F"; readonly note: "#193 车5(staleness-sweep P1-3):OTLP 周期导出失败(HTTP 非 2xx 或 fetch 拒绝)⇒ 丢弃本轮快照继续服务。导出本就 best-effort(collector 打嗝绝不影响 serving,方向不改),但此前唯一留痕是装配层 onError 的一条 warn——**metrics 轴零信号**,而 USAGE 力荐的 Prometheus-only 部署恰好只看 metrics ⇒ collector 持续宕机对运维完全不可见(遥测腿自盲)。放行的最坏后果 = 一段时间的指标断供;计数让「断供正在发生」本身成为一条指标。"; }; readonly "server.runs.cross-slice-usage-unavailable": { readonly cls: "F"; readonly note: "[3833] S-4:`GET /v1/runs/:id` 的 running 态跨片用量投影(`crossSliceUsage`)在**已登记跨片窗**的 run 上,因账本读不可用而整键缺席 —— 两形:`runStore.getEvents` 抛(store 抖动/连接断),或降级/部分实现的 store 根本没有 `getEvents`。放行的最坏后果 = **预警面缺料**:运维读不到「已耗 / 上限」这一对,退回既有的事后姿势(等行翻 `suspended` + `resource_limit` gate 才知道逼近过)——不参与任何门/CAS/resume 判定(跨片额度的执法读的是 core 挂在 checkpoint 行上的 `ResourceLedger`,不经本投影),故 F 类。**方向刻意选缺席而不是铸零**:`spentTokens:0` / `spentMicroUsd:0` 在观测不到用量的那一刻恰好长得像「还没花钱」,是比缺席更坏的假象(RB-368「不知道≠免费」同一条纪律)。留痕理由:缺席与「这条 run 本来就没配跨片窗」在 wire 上完全同形,不计数则「投影为什么一直不出现」只能靠间接症状发现。红先=`test/resource-suspend.test.ts` 的 S-4 账本不可用两格(读抛 / 店无 getEvents ⇒ 计数 +1 且**无**零值投影)"; }; readonly "server.approvals.active-run-conflict-material-unavailable": { readonly cls: "F"; readonly note: "A-057.47:`buildActiveRunConflict` 的材料装配段(getRun / turnActivity / peekPendingScope / findPendingTokenBySession / checkpoint `get`)任一失败 ⇒ 整段退化成裸 409(只剩 error + errorCode + activeTaskId),`activeTaskStatus` / `msSinceLastActivity` / `pendingGate`(kind·decidePath·governanceForced·checkpointId)全部蒸发。放行的最坏后果 = **展示/分诊**面缺料:壳画不出「这条 run 卡在哪道门、去哪儿决」,人退回 `POST /v1/runs/{activeTaskId}/cancel` 这条保底真路 —— 不参与任何门/CAS/resume 判定(执法面读的是 checkpoint blob 的 `get()`,不经本材料;与 census 第 21 行同一条判据),故 F 类。方向刻意不改:本材料是 best-effort 增强,让它抛会把一次 store 抖动变成整个 409 路径的 500(顶注第 11 行逐字成文「store 面任何失败都不得挡 409 本体」)。必须留痕的理由:本文件零 logger 席、pool 层无 per-query 错误日志、调用侧(tasks.ts / runs.ts / server.ts 五处)因本函数恒不 reject 结构上拿不到信号 ⇒ 「approval 出路材料系统性丢失」此前在遥测里与「一切正常」同形;滚动升级期 `checkpoint-store-sql.ts` 的 version-too-new throw 与 strict parseJson 也一并被吞在这里。"; }; readonly "server.engine-notice.durable-append-failed": { readonly cls: "F"; readonly note: "#310(codex 对抗复审 R1-[medium],验真后修):三条 run 腿把一条白名单通告写进 durable 账本时,那次 `appendEvent` **异步 reject**(store 抖动 / 连接断 / 行冲突)。结构上不能 await —— `onNotice` 是 core 的同步回调,拿一次账本写去阻塞引擎是更坏的交易 —— 所以写口只能 fire-and-forget,`route` 的同步 try 结构性观察不到这次失败 —— 「投出去了」与「真落盘了」不是同一件事。放行的最坏后果按腿分档,如实登记:**sync stream 腿**还有 live SSE 那一份(在线的人看得见,丢的是断连补看);**bg / resume 两腿账本是唯一的用户可见终点** ⇒ 那条通告对用户永久消失。仍判 F 而不是 P:通告是**披露**面不是执法面(不参与任何门/裁决),且运维面那一份(结构化日志 `engine_notice`)在分流之前就已经落定、一条不丢。此前是裸 `.catch(() => undefined)` —— 同文件 resume 腿的子代 forward 写口早在 C2/C5 批2 就因为「裸吞 = 事件从 durable log 永久消失且零信号」补过 `warnAppend`,本 tag 是同一条纪律在通告口的落实(warn 留细节 + 计数让「系统性在漏」显形)。不做重试队列是刻意的:账本里没有任何一族行做有界重试,单给通告开一条会造出第二套持久化语义。"; }; readonly "server.engine-notice.wire-sink-threw": { readonly cls: "F"; readonly note: "#310:`engine_notice` 分流器把一条白名单通告投给某条 run 腿的登记口时,那只口抛了(live 口写向已断/已撕裂的 SSE socket,或 durable 口的账本写同步抛)。放行的最坏后果 = **这一条通告的这一个终点**缺席:①日志终点在分流之前已经逐字打过(事实一条不丢);②两个终点注册成两只独立 sink ⇒ live 抛不牵连 durable(断连后仍看得见的那半保住);③同会话其余口照投。故 F 类。不放行的代价是把异常回抛给 core 的 `deliverEngineNotice` —— 它会吞掉,于是同一次失败**既没有留痕也没有第二只口**,正是本 tag 要根除的形。留痕是承重的:静默吞掉之后「wire 腿为什么总有几条通告不到」在遥测里与「core 本来就没发」同形。"; }; readonly "server.hooks.notice-sink-threw": { readonly cls: "F"; readonly note: "#281:hook 观测回调(`onHookNotice`,部署把它接到 fleet 流)自己抛了。放行的最坏后果 = **这一条观测帧**缺席;补偿是结构性的:①日志终点在投递之前就已逐字落定(`hook_command_failed` / `hook_command_timeout` / `hook_command_spawn_failed` 一族),运维面零回退;②观测帧不参与任何门/裁决(纯 observe,#281 全族判据里「裁决面一个字节不动」是承重条款),故 F 类。不放行的代价严重不对称:异常会掀回 core 的 hook 回调栈,把一次**观察动作**变成一次**工具调用失败** —— 「观察一件事的动作反过来改变了那件事」正是本 tag 要根除的形,而它伤的恰恰是 hook 已经坏掉、用户最需要任务照跑的那一刻。留痕承重:静默吞掉之后「这台机器为什么一条 hook 通知都没有」在遥测里与「hook 从来没坏过」同形。"; }; readonly "server.park.toolcallid-read-failed": { readonly cls: "F"; readonly note: "[4913]:durable park 投影腿在 strip 之前拿结果里的 checkpointToken 换 `pendingAction.toolCallId`(cs.get 一次读),那次读**抛了**(store 抖动 / 滚动升级期 checkpoint 格式 version guard / 行已被并发消费)⇒ 本次 park 的 wire 投影**缺 `toolCallId` 键**,其余键(gate/checkpointId)照旧。放行的最坏后果 = 消费方(cli 判别子 v3 写者②)退回该键到货前的行为——同族多兄弟 durable park 分不出主角、诚实全不标(cli 侧 S3 局限,成文的旧现状),纯展示/关联面,不参与任何门/CAS/resume 判定,故 F 类。绝不挡终局路径是承重的:park 投影点全在 done/suspended 事件写链上,让它抛会把一次 store 抖动升级成整条 run 终局写失败。必须留痕:键缺席与「tool-less park 本就无键」([1995]② OMITTED 契约)在 wire 上同形,不计数则「投影为什么总缺」在遥测里永不显形。"; }; readonly "server.exec-lane.ssh-host-key-unverified": { readonly cls: "P-DEBT"; readonly note: "#296(A-052.2,clay 裁定 C-R21):`REMOTE_EXEC=ssh` 且**两只主机密钥旋钮都缺席**(`SSH_HOST_FINGERPRINT` / `SSH_KNOWN_HOSTS`)⇒ 本次连接接受**任何**主机密钥,等价 `ssh -o StrictHostKeyChecking=no`。放行的最坏后果:一次中间人拿到这条会话的**全部命令、全部产物、以及 agent 在那台机器上的全部动作**(私钥仍在控制面手里、拿不走,design/61 §5 #2)。⇒ 保护型欠账,不是体验回退。**方向是裁定的、不是遗漏**:本 lane 的既有部署形是「AI 编排批量部署到一批 SSH 可达的机器」,那些机器的指纹通常不在任何 known_hosts 里,默认拒连 = 一次静默的可用性断裂;C-R21 裁「在场即严格,缺席即响亮」。补偿三件:①**每连接**一条 `ssh_host_key_unverified` warn(不是每进程一次 —— 一条长期裸连的 lane 必须在每条连接上都看得见);②本计数(遥测里「有多少条连接是裸连」是一个可观测的数,曲线应当只降不升);③两旋钮任一在场即**严格校验、不符拒连**(拒因带两半指纹 + `ssh-keyscan` 指路)。终局 = 部署把旋钮配上(那时本计数归零);旋钮不配而想收紧的部署级开关(「无旋钮即拒启」)是下一件,不在 C-R21 射程。"; }; readonly "server.approval.park-tombstone-global-cap-refused": { readonly cls: "F"; readonly note: "#343(黑板 [5100] Q3):park 墓碑表(#329 迟到回决的 wire approvalId → 店内坐标指路条)**全局帽已满、而本 principal 还没到自己的 per-principal 子帽** ⇒ 这条新墓碑**不落**(拒新,一格都不挤别人)。放行的最坏后果 = 这一只 ask 的 **live 腿指路便利**缺席,壳按 Yes 拿到的是 404 `unknown_or_other_replica` —— 也就是 #329 之前的那一格逐字行为,既不会错兑也不会双兑;**完整兑付面一个字节没动**(park 行本体在 durable 店未失,`GET /v1/approvals` 列表 + `/v1/approvals/:sessionId/decide` 仍可回决),故 F 类而不是保护型。为什么方向是「拒新」而不是修前的「FIFO 挤最旧」:那个形让**甲的用量决定乙的可用性**(已鉴权租户量产 park 终局 ask 即可把别家墓碑全部挤出,merge-rescan 744 confirmed),而 park 是常态路由、不需要任何异常手法。必须留痕:「表被别人打满」与「这张卡本来就没有墓碑」在 wire 上完全同形,不计数则运维只能靠「用户说按 Yes 老是 404」这种间接症状发现,而那正是 [4845] 排障代价实证过的那条路。detail 带 owner(判「压力来自谁」的第一手线索)。本 tag 有**两条臂、两个根因**,detail 判别:(a) 全局帽满且本 principal 未超子帽 ⇒ 拒新(detail 带 owner);(b) 本 principal 超子帽但名下条目**全在飞**、无一可逐 ⇒ 拒新(detail 带 `reason=all-inflight`)。超子帽且**有**可逐条目的常态臂逐自己最旧的一条(量产者自伤,设计内有界表纪律)才不走本 tag。"; }; readonly "server.approvals.stale-echoed-token-unreadable": { readonly cls: "F"; readonly note: "S-02 codex R1-[medium](验真后修):decide 的 409 `approval_stale` 臂对**调用方回显的旧 token** 做归属判定(`cs.get(binding.checkpointToken)`),那次读**抛了**(store 抖动 / 版本守卫 / 行恰被清理)。修前这里是静默 `catch(()=>null)`,「读不出」被折成「确认不在库」——叠上 S-02 的 404 收口后,一次 store 抖动就会把合法旧 token 的 409+terminal 谎报成 404「没有这条审批」。修后:unreadable ⇒ **保持 409**(`terminal` 不披露——证不出归属就不说,与 2026-07-26 纪律同向;404 折叠只在读成功且未命中时走,探测方无法自控 store 故障 ⇒ 谕示封闭不破)。放行的最坏后果 = 该次拒体 `terminal` 诚实缺席(`currentPending` 由它自己的读口与属主复核另判,不受本臂牵连),壳退回重拉列表,judgment 面 fail-safe,故 F 类。留痕理由:这次读是 terminal 披露与 404 折叠两个判定的输入,长期抖动必须在遥测显形。"; }; readonly "server.resume.reopen-confirm-unreadable": { readonly cls: "F"; readonly note: "S-447(#60 M2 自查项,`http/resume-legs.ts` 的 `confirmCheckpointReopened`):续跑腿在 core 给出 reopen 族失败后按 **token** 复核「这张 checkpoint 是不是又 pending 了」,这一读抛(库拖/版本太新/店不可达)。**方向本来就是 fail-closed**:读不出 ⇒ `return true` ⇒ 「不写任何终局、重新 park」(`:1206-1212` 头注已把取舍与有界代价写死:错 park 由 `reapSuspended` 兑,错终局会孤儿化一张活 park 到 TTL)—— 所以这条腿不是 P 类,缺的只是**留痕**。放行的最坏后果 = 多一次多余的 re-park(一张已经被决掉的卡重新挂到 reaper 窗里),人面上是「这条续跑没动」而不是谁被放行了。必须登记的理由是 **uniformity**:同文件三只同形读(`:249` `server.approvals.stale-echoed-token-unreadable` · `:288` `server.approvals.stale-current-pending-unreadable` · 本腿)中只有它是黑的,于是「店一直读不出所以每条续跑都在白白 re-park」与「一切正常」在遥测上同形。计数非零 = 这台机器的 checkpoint 店在拖,运维要去看库。"; }; readonly "server.approvals.stale-current-pending-unreadable": { readonly cls: "F"; readonly note: "S-02 件2([5522]④→[5525]):409 `approval_stale` 拒体铸 additive 指路键 `currentPending`(本会话当前 pending 的 D-1 坐标,壳一跳重定位)时,当前 pending 行的 `cs.get(token)` 读**抛了**(store 抖动 / 滚动升级期 checkpoint 格式 version guard / 行恰被并发消费)⇒ 本次拒体**缺 `currentPending` 键**,409 本体与 `terminal` 判定照旧。放行的最坏后果 = 壳退回该键到货前的行为(重拉 GET /v1/approvals 自行重定位),纯指路/便利面,不参与任何门/CAS/resume 判定,故 F 类(同族先例:`server.park.toolcallid-read-failed`,同为 cs.get 投影读失败臂)。必须留痕:键缺席与「此刻确实没有 pending」在 wire 上同形,不计数则「指路为什么总缺」在遥测里永不显形。"; }; readonly "server.file-history.retention-trim-failed": { readonly cls: "F"; readonly note: "S-15 twin 保留条款(core 7.0.0 design/381 §片2 的 SQL 孪生):boundary 提交后的自剪 pass(core 共享选择函数 `fileHistoryBoundariesToKeep` + 既有 `reap` 级联)失败 ⇒ 本轮 boundary **已提交**、rewind 座照常,只是这一轮没剪。放行的是「GC 失败不失败用户这一轮」(core InMemory/File 参照同形 `reap(...).catch(() => {})`,但参照是无声吞,本仓走留痕三件套);最坏后果 = 该 scope 暂时超上限一个 boundary,下一次提交的 pass 按**当前全集**重算、连它一起剪(不累积欠账)。detail 带 scope / entry / 异常文案。"; }; readonly "server.approval.ambiguous-cas.expire": { readonly cls: "P-DEBT"; readonly note: "S-25(设计件 2026-08-31 §3 步骤 3;DEBTS S-25 / #290)→ S-69 收窄(S-25-R1 终局,core design/384 §4.3 七律 / §4.4 键形对表,[5934]):流内 ask 的**窗到期臂**打原子终局 claim(`claimTerminal(expire)`,S-69 前 `expireAsk` CAS)被 reject(抛错 / 超墙钟,什么也证明不了 —— 律 3 K2)之后,有界退避**重发同一 claim**(6 格 ⇒ 至多 7 把,在飞帽 2 按持久 askId 记、撞帽拍不计 attempt、让路探针读同帽同界、帽满或同 askId 尚有在飞操作时探针不计 rung、真相广播同 askId 全部登记(本登记已 done 亦广播)—— codex S-69 R2-1/R3-1/R3-2/R4-1/R5-1/R6-1/R6-2/R7-1;末格睡后仍再发 —— codex R1-F1 同律;总墙钟硬上界 TERMINAL_LADDER_WALL_MS ≈10.15s —— R2-2 修正算术 + R3-3 硬封顶)**全部 reject** ⇒ 按本臂意图投影结算 = park 路由(`\"unavailable\"`;`deny` 政策下 `{allow:false,settledBy:\"timeout\"}`)并留 park 墓碑。方向 fail-closed(无 DECIDED=approve 行绝不执行工具),但行**可能**已被另一副本判成 DECIDED(approve) 而本腿没兑现 —— 人批了、工具不跑、run 再挂起要人再批一次 = 丢一次人类决议,故记债不当合法兜底。补偿:core 侧仍 pending 的 checkpoint(人再批一次就走)+ 跨副本收敛器兜底(A-075.116②:其兜底覆盖本形经确认后本 tag 降 F)。**不计**的形(S-69 起命中条件收窄到「店对整条阶梯持续不可用」):claim 输 ⇒ 败方答案**携真相**(DECIDED ⇒ 按人的决议结算,零补读;PARKING/PARKED ⇒ park 路由;absent = 行不在/错 scope ⇒ K1 零退避投影),迟到回来的 claim 同样自足(迟到赢 ⇒ 副作用补做,迟到输 ⇒ 按真决议结算 —— 此前迟到回调只认 won 的那条残余窗即 S-25-R1,本批闭合);编辑在飞标记在场 ⇒ 有界让路(不重打 claim)、用尽走 park 路由(标记的事不是店的债,codex R1-F2/R2-F2;负终态与带载荷 approve 不受标记压制,R2-F1/R3-F1);阶梯里曾拿到真相而标记已放开 ⇒ 按那份真相结算。detail 带 askId 与 claim 把数。"; }; readonly "server.approval.ambiguous-cas.cancel": { readonly cls: "P-DEBT"; readonly note: "S-25 同族第二员 → S-69 收窄:**取消臂**(task signal abort / 连接全灭同路)打 `claimTerminal(cancel)`(STREAM_PENDING→VOID;S-69 前 `transitionAsk` CAS)被 reject 之后,有界重发至多 7 把 claim(在飞帽 2,墙钟上界同 expire 员)全部 reject ⇒ 按本臂意图投影结算 = 裸 `settle(false,\"expired\")`(D1 纯进程内语义,不留墓碑:run 已拆,迟到的 Yes 无处兑)。S-25 之前这条 catch **不读行**直接 fail-open —— 行已 DECIDED(approve) 时把人的批准翻成拒绝、回决端点只能如实 404(round6 钉曾把这一形记成「局部竞态残影」);S-25 起先读行;S-69 起干净输的答案本身携真相、迟到答案同样自足,只有店对整条阶梯持续 reject 才走到这里,债的语义与 expire 员同(行可能已 DECIDED 而本腿没兑现),按臂分计是为了让「哪条臂在丢决议」在遥测里读得出。"; }; readonly "server.approval.ambiguous-cas.unreachable": { readonly cls: "P-DEBT"; readonly note: "S-25 同族第三员 → S-69 收窄:**emit 全灭臂**(开卡帧两族都没送到任何连接)打 `claimTerminal(expire)`(S-69 前 `expireAsk` CAS)被 reject 之后,有界重发至多 7 把 claim(在飞帽 2,墙钟上界同 expire 员)全部 reject ⇒ 按本臂意图投影结算 = `emitFailed` ⇒ `\"unavailable\"`(park 路由;`deny` 政策下 deny),不留墓碑(卡没到任何人手上,无人持有这把 approvalId)。S-25 之前这条 catch 不读行直接强改 —— 正是 round2 finding A「durable 行已批准、活终局被 emit 全灭强改成 unavailable」的 catch 同形缺口;S-25 起行读出 DECIDED ⇒ 按人的决议结算;S-69 起真相随败方答案回来、迟到答案自足,只有店对整条阶梯持续 reject 才计数。"; }; readonly "server.approval-ask-audit.append-failed": { readonly cls: "F"; readonly note: "S-38 裁 (c) 审计半场:local 车道 File ask 审计店(`approval-ask-audit-store.ts`)的**运行期** append(铸造/决议/腿闭/收敛四类行)失败(盘满/只读/fd 失效)⇒ 丢**这一条**审计行,审批路径照常。方向刻意 fail-open:审批可用性不押在审计盘上 —— 盘满时人还得能批,把一次审计写失败升级成审批失败是把「痕迹缺一条」换成「门整个不可用」,方向更坏。boot 期(mkdir/重放)失败则**拒启**(File 店族同律,store 头注成文),不经本 tag。放行的最坏后果 = 该 ask 在崩溃收敛读面上缺席或分臂失真(与 A-075.55 的零痕迹病同向)—— 所以必须留痕:「审计一直在漏写」与「没有审批发生」在读面上同形,不计数则病灶永不显形。detail 带行类与异常文案。"; }; readonly "server.run-terminal.redact-threw": { readonly cls: "F"; readonly note: "三个发射座(errorMessage 终态文案 / 诊断文本 diag / 工件 artifact,probe 前缀判别;普查 56 行;补丁头 patch-head 那座随 7.83.0 / S-113 的 redactPatchHead 一并退役):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError:关键字/前缀臂那一形已由 S-219 的 atLeast 铸点闭掉,仍能抛的是 JSON 秘密字段臂的转义对循环,既有未闭,S-106)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。"; }; readonly "server.run-terminal.redact-capped": { readonly cls: "F"; readonly note: "S-107:终态自由文本(errorMessage/blockedReason/remoteEnvFailures[].message/error 列/done·failed 行)长度 > REDACT_INPUT_CAP(64KB)⇒ 整段换 «redacted:oversized»(fail-closed 取证丢弃,不截断不扫);计数=多少条终态文案被整段丢弃"; }; readonly "server.run-terminal.artifact-capped": { readonly cls: "F"; readonly note: "S-107:leader_run 行上的工件字节(salvaged[].patch / candidatePatch.patch / merge.candidatePatch / merge.rej)长度 > ARTIFACT_INPUT_CAP(256KB)⇒ 整份换 «redacted:oversized-artifact»(fail-closed 取证丢弃;原字节仍在 worker 分支/merge 咽喉);计数=多少份工件被整份丢弃"; }; readonly "server.run-terminal.diag-capped": { readonly cls: "F"; readonly note: "S-107:诊断文本座(merge details/measure/grader trace、leader 日志座、planner 样本、journal 预览、workflow_run error cut)长度 > DIAG_INPUT_CAP(256KB)⇒ 整段换 «redacted:oversized-diagnostic» [len=N](fail-closed 取证丢弃;固定窗不 fail-closed 故不开窗);计数=多少段诊断文本被整段丢弃"; }; readonly "server.run-terminal.output-truncated": { readonly cls: "F"; readonly note: "S-107:持久 error 脱后 UTF-8 字节 > 65,000(TiDB TEXT 65,535 字节)⇒ 脱后切并带标记(切在占位里不是泄漏);计数=多少条终态文案被字节封顶截断"; }; readonly "server.runs.terminal-ledger-write-failed": { readonly cls: "F"; readonly note: "S-107(全量门 silent-degradation 棘轮抓出的五处静默 `catch {}`):run 终局链里**账本腿**(flush 缓冲 text/reasoning、append `failed` 行)与终态翻转(setTerminal)已不共命运——账本 INSERT 抛不再让 run 卡 running 到 reaper;但那条抛掉的账本行之前是**静默**吞的。detail `arm=` 四臂(flush-clean-cancel / append-clean-cancel / flush-catch / append-thrown-cancel / append-failed):缺的是 run_events 尾巴(缓冲文本或 failed 事件),run 行本身仍是 durable truth,读面/reaper 容忍缺失,故 F 类;计数=多少次终局账本写失败。"; }; readonly "server.redact.ppk-eat-to-end": { readonly cls: "F"; readonly note: "S-107 第三十二轮重扫:PuTTY .ppk 容器扫描器(redact.ts redactPpkContainers)在「无合格终止 / 收口后同容器范围再见私钥字段 / 收口后到下一版本头之间夹非空白」三形下从头**吃到输入末尾**(fail-closed 取证丢弃)——终态 errorMessage 座上=整条错误文案变成 «redacted:private-key»,与 redactions_applied_total{private-key} 的诚实替换同形不可分,故要有自己的计数/探针;影子扫不计;计数=多少次整段吃尾。"; }; readonly "server.approval-card.closed-word-out-of-set": { readonly cls: "F"; readonly note: "S-249(`approval-card.ts` 的边界窄读族;今天的唯一成员是 `readRuleOffersAbsence`):活卡帧 / `card_json` 的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40)。"; }; readonly "server.pg-pool.idle-client-error": { readonly cls: "F"; readonly note: "S-55(test [5864] 独立复现):pg 连接池里一条连接被外部终止(pg_terminate_backend / 云端故障切换 / LB 空闲回收)或自身网络错误。**两态一 tag,detail 判别**(`state=idle` / `state=checked-out`;先例=park 墓碑行的两臂 detail 判别):①idle 态 —— node-postgres 语义(pg-pool@3.14.0 makeIdleListener):池在 emit 'error' **之前**已 `_remove` 掉该 client,坏连接自然淘汰,下次 acquire 发新连接,在飞查询零影响;修前 Pool 上无 'error' 监听 ⇒ EventEmitter 把事件转 throw ⇒ uncaughtException ⇒ 整进程 exit 1。②checked-out 态(codex R1-[high] 补,同批修)—— pg-pool 借出时摘 idle 监听(index.js),`pool.connect()` 显式持有的连接(事务)在 checkout 期死时 client 直接 emit 'error' 同样打死进程;守卫只观察:中毒 client 的后续查询由 pg 以 `_queryable=false` 响亮拒,归还时池淘汰;同一次 checkout 的死可能双发事件(FATAL 消息 + socket end),warn 逐枚、计数按 checkout 去重。放行的最坏后果 = 下一次 acquire 多付一次建连,故 F 类。必须留痕:「连接不断被外部杀」(故障切换风暴 / 回收策略过激 / 有人在库上清连接)与「一切正常」在服务面同形;每次事件另有逐次结构化 warn(`pg_pool_idle_client_error` / `pg_pool_checked_out_client_error`,含 code/severity),本计数让频率曲线可读。`pool.query()` 快路不在洞内(pg-pool 自带 checkout 期 once('error') 守卫)。mysql2 孪生(tidb-pool)**不同病**:PoolConnection 构造器恒挂 once('error') 库内吞并淘汰、借出期同在(坐标成文在 createTidbPool 头注),故无同族 tag。"; }; readonly "server.ensure-index.concurrently-unsupported": { readonly cls: "F"; readonly note: "S-287(`plugins/ensure-index.ts` 的 PG 臂):这口 PG **协议**面不认 `CREATE INDEX CONCURRENTLY`。今天唯一的真消费者是**仓内离线镜像的手搓 `PgQueryFn` 假件**(`pg-mem` 不是本仓依赖,`test/memory-embedder-fingerprint.test.ts` 头注已为此记过一次档,这里不重复那个错)。判据是**能力面**不是名字,且**只认阳性信号**:SQLSTATE ∈ {`0A000` feature_not_supported、`42601` syntax_error},或错误**完全不带 `code`**(= 不是驱动也不是服务端给的错误)⇒ 判「不认」,回退成普通 `CREATE INDEX IF NOT EXISTS`;**带任何其它 `code` 的一律原样抛** —— SQLSTATE 的 `55P03`/`57014`/`23505`/`42501`/`25001` 与 libuv 的 `ECONNRESET`/`EPIPE`/`ETIMEDOUT` 都在这一侧(⚠️ 不要按「5 位大写」的形状去分这两族:`EPIPE` 恰好也是 5 位大写)。放行的最坏后果 = 这一次建索引持表级 `SHARE` 锁阻塞该表的写(纯**性能/可用性**面,索引本身照样建成、语义零差别),而能走到这条臂的面**按定义**不是生产 PG。计数非零 = 有一口本以为是真 PG 的部署其实不认 CONCURRENTLY,运维要去看它到底是什么。detail = `<表>.<索引名>`。"; }; }; /** 词表键推导的闭集类型——未登记的 tag 传不进 {@link recordFailOpen}(编译期拒)。 */ export type FailOpenTag = keyof typeof FAIL_OPEN_TAGS; /** * 断流丢帧该记哪个 tag —— **按帧型分类**,纯函数(与写流的那条闭包解耦,才单测得动)。 * * 一刀切记同一个 tag 是错的:承载 HITL 帧的那条闭包同时驮着完成面包屑与其它运行流帧,于是**一次** * 断连会让保护型计数涨两次(open 一次、complete 又一次),真正的 open 失败率被面包屑的假阳性盖住。 * 只有 open 帧是"人在环这道门被跳过"的证据,其余都是体验损失。 * * ⚠️ **两条 question 臂在生产上已不可达**([ref] 之后,2026-08-07):本函数唯一的生产调用点是 * `routes/tasks.ts` 的 `emitAsk` 断流臂,而 question 与 question_complete 两型帧都先过同文件 * `emitQuestion` 的断流守卫(两道守卫同步相邻、其间无 await)并在断流时 throw,永不抵达这里;另一条 * question 路(`runs.ts` 的 durable append)根本不经 emitAsk。在产的只剩 elicitation 族与 default。 * 两臂**保留不删**:它们是本映射的语义定义(单测直打的纯函数),elicit 侧若哪天照 question 收紧、或 * 新增第四条 emit 腿忘了守卫,留着的臂是正确落点而不是死码。读遥测时按上面这段判在产分布。 */ export declare function failOpenTagForDroppedFrame(frameType: string): FailOpenTag; /** {@link createFailOpenRecorder} 返回的活对象(有行为、有状态 ⇒ `create*` 而非 `build*`)。 */ export interface FailOpenRecorder { /** 走到一条登记过的兜底臂。总不抛——观测本身绝不能变成故障(见 reapers 同族判据)。 */ record(tag: FailOpenTag, detail?: string): void; /** 本进程各 tag 的累计次数(逐次计数,与一次性 warn 无关)。 */ counts(): ReadonlyMap; /** 已经喊过 stderr 契约行的 tag 集合——装配时由新实例继承,避免 install 让同一 tag 再喊一次。 */ warnedTags(): ReadonlySet; /** 还没进过任何 metrics 汇的次数(装配前命中的欠账)。install 用它补账,补完清零。 */ metricsBacklog(): ReadonlyMap; /** 已经进过结构化日志的 tag 集合。与 {@link warnedTags} **分开**:stderr 契约行装配前就能喊, * 结构化 warn 却要等 logger 到场——两者共用一个集合会让装配前命中的 tag 永远拿不到结构化痕迹。 */ loggedTags(): ReadonlySet; } /** 探针文件的 env 名(跨仓同名同形:`audits/failopen-governance-176.md` §2-r2,壳/引擎同认)。 */ export declare const FAIL_OPEN_PROBE_ENV = "SEMA_FAILOPEN_PROBE"; export interface FailOpenRecorderDeps { logger?: Logger; metrics?: Metrics; /** 探针文件路径。缺省=读 `SEMA_FAILOPEN_PROBE`(读一次,不每次调用都碰 env)。 */ probePath?: string; /** 继承自上一个实例的状态(install 换实例时不重置计数、不重喊 warn、不丢欠账)。 */ seedCounts?: ReadonlyMap; seedWarned?: ReadonlySet; seedBacklog?: ReadonlyMap; seedLogged?: ReadonlySet; /** * `detail` 落探针文件前的脱敏器(装配层注入 `trace/redact.ts` 的 `redactSecrets`)。 * * 🔴 **缺席 ⇒ detail 原文不落盘,只落 tag + 一个「为何扣下」的标记**(fail-closed 且响亮);脱敏器抛 ⇒ 同。理由:探针行是本模块**唯一** * 把调用方文本写到盘上的地方,而三十多个调用方里不少传的是异常文本 —— 异常文本里带网关 URL、URL 的 query 里带 * `api_key=` 都是常态。「不放密钥」此前只是 {@link recordFailOpen} 注释里的一句自觉,现在是这一个铸点上的结构。 * 用注入而不是直接 import:脱敏器自己 import 了本模块(它有一条 fail-open 留痕臂),直接 import 会成环。 */ redactDetail?: (detail: string) => string; /** 测试注入:替掉 stderr 契约行的落点。缺省直写 `process.stderr`。 */ writeStderr?: (line: string) => void; /** 测试注入:替掉探针文件追加。缺省 `appendFileSync`(同步=进程猝死也不丢已记的行)。 */ appendProbe?: (path: string, line: string) => void; } export declare function createFailOpenRecorder(deps?: FailOpenRecorderDeps): FailOpenRecorder; /** * 装配层接线:把 logger/metrics 接到进程单例上。搬四份状态过去,各有各的理由: * - `seedCounts` —— 逻辑总数续算,不从 0 重来; * - `seedWarned` —— boot 期已喊过的 stderr 契约行不再喊(「每进程每 tag 一次」是它的全部价值); * - `seedBacklog` —— 装配**前**命中的次数还没进任何 metrics 汇,交给新实例补账(补完清零, * 所以第二次 install 不会重放); * - `seedLogged` —— 已真发出去的结构化 warn 不重发;没发过的由新 logger 补一条。 */ export declare function installFailOpenRecorder(deps: Omit): FailOpenRecorder; /** 兜底臂的调用口。`tag` 必须已登记(闭集类型),`detail` 是可选的一行现场(路径/原因)。落盘前由记录器统一脱敏 * ({@link FailOpenRecorderDeps.redactDetail});调用方仍不该**刻意**往里放密钥,但不再靠这句自觉兜底。 */ export declare function recordFailOpen(tag: FailOpenTag, detail?: string): void; /** 本进程各 tag 累计次数——`/metrics` 之外的进程内读数(测试与自检面用)。 */ export declare function failOpenCounts(): ReadonlyMap; /** test-only:换一个全新单例(计数与 warn 集合清零),让每个用例都从"第一次"看起。 */ export declare function resetFailOpenRecorderForTest(deps?: FailOpenRecorderDeps): FailOpenRecorder; //# sourceMappingURL=fail-open.d.ts.map