/** * 终局因由(`TaskResult.terminal`)的 server 侧**读**面 —— 一个叶子模块,只依赖 `@sema-agent/core`。 * * core 7.6.0([ref] S6 D-8,BREAKING)把 `TaskResult` 上并列的八个平面键 * (`status` / `errorCode` / `errorMessage` / `blockedReason` / `checkpointToken` / `checkpointId` / * `checkpointGate` / `workspaceRestoreMode`)删成**一条**带标因由 `terminal: TerminalCause` * (`completed` | `failed{code?,message?,nestedPause?}` | `blocked{reason}` | `paused{gate,token,…}`)。 * 合法组合从此是**形状**而不是一串跨字段的口头约定(「`suspended` ⇔ 有 token」「review park 与 approval * park 不同时置位」)。 * * ## 本仓的两条纪律(与 core 的单铸律配套) * * 1. **判断读因由,不读平面词**。`terminal.kind === "paused"` 是判据;`terminalProjection(terminal).status * === "suspended"` 不是 —— 后者把一次投影塞进读点,于是同一个事实有了两个真源。 * 2. **`terminalProjection` 只在明确的「投影出口」用一次**:必须继续说五词 `TaskStatus` 的那几张脸 * (run 行的 `status` / `error` 列、`tasks_total{status}` 计量标签、fleet 行的终态词)。出口之外不拼。 * * wire 面(HTTP 任务体、SSE `done.result`)**就是结果本身**,所以它跟着 core 一起丢八键、带 `terminal` * —— 那里没有投影,只有转发(见 {@link sealResultForExit} 的例外)。 */ import { terminalProjection, type PausedCause, type TaskStatus, type TerminalCause } from "@sema-agent/core"; /** * 这次失败带的**机器码**(`terminal.failed.code`);非失败终局 ⇒ `undefined`。 * * 旧形要两键合读(`status === "failed" && errorCode === X`),而「非 failed 的行上有 errorCode」在类型上 * 是可表达的 —— 那正是本键此前每个读点都要自己重述一遍 `status` 判断的原因。因由形下「码只属于失败」 * 是形状本身,所以判据收成这一个函数:**读点问码,不再各自复述状态**。 * * 码是开集(core 的点名族 `limits.*` / `config.*` / `resume_at.*` / brain 扁平码 + 工具层自注册码), * 消费点一律带 default 臂,禁按穷举写。 */ export declare function failureCodeOf(terminal: TerminalCause): string | undefined; /** * 本仓**对外机器码**的词法(S-548)。附录 A 那张表、core 的点名族(`limits.*` / `config.*` / * `resume_at.*` / `output.*` / brain 扁平码)、工具层自注册码 —— 全体成员都是这一形:**小写**的 * `a-z0-9_`,用 `.` 分族。门 {@link OUTWARD_CODE_SHAPE} 在 `test/terminal-store-error-fold.test.ts` * 里对**目录里真有的每一个码**断言这条词法(判据由 catalog 机器派生,不是手抄的一张清单)。 */ export declare const OUTWARD_CODE_SHAPE: RegExp; /** 域外失败码的**唯一**落点(附录外的新码;原码进日志与 message,不丢)。 */ export declare const STORE_ERROR_CODE = "internal.store_error"; /** * 一个失败码要上 wire 之前的**归一**(S-548)。在词法内 ⇒ 原样;词法外 ⇒ {@link STORE_ERROR_CODE}。 * * ── 病(test T-47,三库一致)──────────────────────────────────────────────────────────────────── * 本仓的 SQL 店把**驱动原生错误**原样抛回引擎,而 core 的 `errorCodeOf` 沿 `err.code`/`cause` 链取 * **第一个非空字符串**当这次失败的机器码 —— 于是 mysql2 的 `ER_BAD_FIELD_ERROR` / node-pg 的 SQLSTATE * `42703` **逐字**成了 `POST /v1/tasks` 回给消费端的 `terminal.failed.code`。消费端拿到的是一个它的码表里 * 根本没有、而且随数据库厂商/版本变化的串。 * * ── 为什么判据是**词法**而不是「在不在附录 A 那张表里」──────────────────────────────────────── * 亲读实现证伪了白名单形:`output.invalid` / `suspend.loop` / `resume_at.*` 这些**合法的引擎终局码** * 今天**不在**附录 A(那张表的辖域是 4xx/5xx 错误体,不是 run 终局),白名单会把它们一起折掉 —— * 那不仅丢诊断,还会当场打死 `resumeAtHttpStatus` 的 404/422 分诊腿。词法判据则是一条**正面**规则: * 「本仓与引擎的码全是小写点分」,而驱动码全是 SCREAMING_SNAKE(`ER_*` / `PROTOCOL_*` / `ECONNRESET`) * 或五位 SQLSTATE(`42703`)—— 两个字符集不相交,一条规则覆盖整类,**零逐码特判**。 * * ⚠️ **辖域如实**:本臂触发条件是「码出了本仓的词法」,所以任何依赖铸出的域外码都落这里,不只 SQL 驱动。 * 今天可观察的产出方**只有**耐久店那一层(引擎自己的传输/brain 错误在 core 侧已被分类成小写码),故码名 * 取 `internal.store_error`;原码逐字进 message 与 warn 行,归因从不丢失。 */ export declare function outwardFailureCode(code: string): string; /** * **一条结果离开引擎、进入本仓(可重放账本 / run 行 / wire)的唯一封口** —— 两件事,一个出口: * ①摘掉**耐久恢复能力凭据**(`CheckpointToken`);②把**域外的失败码**折成本仓的 `internal.store_error` * ({@link outwardFailureCode},S-548)。 * * 🔴 为什么两件事合在一只函数里而不是并列两只(7.92.2 改名 `stripResumeToken` → `sealResultForExit`): * 它们是**同一个出口上的同一件事** —— 「这条结果可以见人了吗」。并列两只 = 六个结算点各自要记得调两次, * 而漏调的那一次不会红、只会在某天的 wire 上露出一个不该露的东西(凭据或驱动码)。名字跟着职责改: * 旧名只说了摘凭据那一半,一个只做一半的名字会让下一个人在另一半上另开出口。 * * ── ① 摘凭据 ── * token 就是 resume 凭据(token-as-auth,search [18] Q1b),**永不离开本服务**;非密的 `gate` / * `checkpointId` / `restoreMode` 有意保留(对账位)。 * * 🔴 core 7.6.0 起凭据换了住址:它不再是 `TaskResult` 顶层的 `checkpointToken`,而是因由里 * `paused` 那一臂的 `token`。而**一条结果上够得着 paused 因由的位置有两处**,这是类型的形状,不是特判: * · `terminal`(kind `paused`)—— 本腿自己 park 了; * · `terminal.nestedPause`(kind `failed`)—— 嵌套边界(verify / cascade)遇到自己驱动不了的耐久暂停, * 在**它的**边界上如实报 `failed`,而把那次暂停的因由整只带在这里(顶层调用方据此仍能恢复)。 * 正是这条路径当年让 `checkpointToken` 漏进过账本 —— 摘除腿从来不是「理论上的对称」。 * 两处走**同一条**摘除规则({@link stripPausedToken}),不是两条。 * * wire 上的形:paused 因由**缺席 `token`**(core 类型里它是必填座,那是**引擎内**的契约;本服务的 wire * 契约逐字声明这一座不出服务边界 —— 见 `docs/ASSISTANT-WIRE-CONTRACT.md`)。消费端要动这次暂停,走 * `/v1/approvals` 决策端点,凭 `checkpointId` 对账,从来不是拿 token 直连引擎。 * * 对任何输入安全(非普通对象 / 无 `terminal` / 因由不带 token ⇒ **同一引用**原样返回,不重塑)。 */ export declare function sealResultForExit(result: T): T; /** * **投影出口的唯一属主**(S-548)—— core 的 `terminalProjection` + 对外码归一,一次。 * * `docs/ASSISTANT-WIRE-CONTRACT.md` §5.0 说清了终局有**两张脸**:①结果**本身**(`done.result` / 200 体 / * `GET /v1/runs/:id → result`,零投影)与②**五词投影**(run 行的 `status` / `error_code` 列、fleet 终态词、 * 决策腿 200 体的三格平面词)。两张脸各有一个属主:①是 {@link sealResultForExit},②就是本函数。 * **两个属主都折码**,否则同一次失败在两张脸上说两个不同的码 —— 而缺的那一张正是 S-548 修前仍在泄漏的 * 那条路(codex 对抗复审本轮 [medium],亲读证实:决策腿的 200 体从**未封口的** result 投影 `errorCode`, * 于是驱动原生码照旧上 wire,尽管同一段代码落库那份已经折过)。 * * ⇒ 纪律:**`src/` 里除本文件外不许再直接调 core 的 `terminalProjection`**(门在 * `test/terminal-store-error-fold.test.ts` ④)。折叠**幂等**(折过的码再折还是它自己),所以对一个已经封口的 * 结果调本函数是安全的 no-op —— 调用方不需要记得"这个 result 封过了没有"。 */ export declare function outwardPlane(terminal: TerminalCause): ReturnType; /** * 这条腿的结果里**藏着的那次耐久暂停** —— 嵌套边界(verify / cascade)遇到自己驱动不了的耐久 park 时, * 在**它的**边界上如实报 `failed`(码是 `unexpected.suspended` / `unexpected.needs_review`),而把那次 * park 的因由**整只**带在这里,顶层调用方据此仍能恢复。非嵌套 park ⇒ `undefined`。 * * 🔴 core 7.6.0 之前这件事只能从**码的字面量**推:`status` 恒被改写成 `"failed"`(真 park 词挪进 * `errorCode`,token 顺着 `checkpointToken` 原样透传),于是本仓凡是按 `status` 判 park 的门在这条腿上 * **恒假**([ref] 二的真案:待批工具卡被谎报成 tool-less park),补救办法是「先把 status 换成真词再递进去」 * 这种再造事实的写法。因由形下 park 的门、门上的卡、恢复凭据都在同一个 `PausedCause` 上 —— 那些补救全部 * 消失,判据回到一条:**问因由**。 */ export declare function nestedPauseOf(terminal: TerminalCause): PausedCause | undefined; /** * 这条结果**停在耐久门上**了吗 —— 若是,给出那次暂停的因由。两条路径合成一个判据: * · 本腿自己 park(`terminal.kind === "paused"`); * · 嵌套边界替它带回来的那次 park({@link nestedPauseOf})。 * * 🔴 7.6.0 之前这两条路径要用**两个不同的判据**读:平面腿按 `status` 的五词穷举 switch * (`suspended` / `needs_review` 是 park),嵌套腿按「还带着 `checkpointToken` 就是 park」——同一个问题 * 两套写法,而两套写法各漏过一次真事故(fanout 的手抄 `=== "suspended"` 漏 `needs_review`;verify 腿的 * status 恒 `"failed"`)。因由形下两条路径合成一句话,规则从二变一。 */ export declare function pausedCauseOf(terminal: TerminalCause): PausedCause | undefined; /** * **持久 blob 的平面读面** —— 把一条**任何年代**的持久终局结果读成五词 `status` + 三个平面文本格。 * * 🔴 为什么这不是「兼容读臂」(codex 对抗复审 r1 [high] ×2,亲读验真后修;首版把它当成兼容 shim 删掉了, * 那是**错的**,理由如下): * · 本仓 wire 面的纪律「不为老键留兼容读臂」管的是**同一条 wire 上两种形并存** —— 那会让两个写者共用一个 * 语义面。而这里是**读盘**:`task_run.result` / `task_event(done).result` / `workflow_journal` 里躺着 * 7.63.0 及以前写下的**真字节**,它们本来就是平面形。一个读盘面认识盘上真实存在的两种形,不是第二个写者 * ——**没有任何东西会再写出旧形**(写口只剩因由一条)。 * · 不认旧形的代价是**可复现的两条真回归**:① `terminalProjection(undefined)` 直接抛 TypeError ⇒ 历史 run 的 * `GET /v1/runs/:id` 整个 500;② 读面的二次脱敏(旧行纵深)对旧行**恒不生效** ⇒ 上线前落盘的凭据在 * `GET /v1/runs/:id/events` 回放时重新明文外发。 * ⇒ 规则仍然只有一条:**读盘认盘上真有的形,写盘只写一种形**。 * * 形不合(非对象 / 两种形都不是)⇒ 全格缺席,不抛(读面永不因为一条坏行整只 500)。 */ export declare function persistedPlaneOf(result: unknown): { status?: TaskStatus; errorCode?: string; errorMessage?: string; blockedReason?: string; }; //# sourceMappingURL=terminal.d.ts.map