/** * S-185(core 7.10.0 [ref] / B-049)—— `WorkflowJournalEntry` **两臂**的单点读写。 * * core 7.10.0 把 journal 记录从一个 interface 换成了判别联合: * `{ callKey, result }` —— 这一序数的子代跑完了,`result` 是它的 `TaskResult`; * `{ callKey, parked }` —— 这一序数**停在耐久审批门上**,`parked` 是子代那份 `terminal.kind:"paused"` * 的 `TaskResult`(令牌 / 门 / 钉住的 session / 花费都在里面),**没有 `result`**。 * * 两臂的载荷同型(都是一只 `TaskResult`),差的只是**这一位是不是 park**。所以本模块把整条规则收敛成 * 两只互逆的函数:读一条记录 ⇒ `(payload, parked)` 二元组;拿到 `(payload, parked)` ⇒ 铸回记录。 * * 🔴 **为什么必须单点**:本仓有三个消费面要判这一臂 —— SQL 店的写(`append`)、SQL 店的读(`load`)、 * HTTP journal 读面(`GET /v1/workflows/:id/journal`)。每处各写一次 `entry.parked !== undefined ? … : …` * 就是同一条判别式的三个写者;core 哪天加第三臂(它已经加过一次),三处里漏改一处就是**静默丢一条 * 承重记录**——而 journal 是 LOAD-BEARING(丢一条 = 一次已完成的子代被当成没跑过而重跑,外部副作用重复)。 * 收敛到这里之后,第三臂到货 ⇒ 本文件**一处**编译红。 * * 🔴 **park 臂的形是 fail-closed 的**:core 的类型要求 `parked.terminal.kind === "paused"`。从盘上读回来的 * 字节没有类型,所以 {@link journalEntryOfRow} 在铸 park 臂时**亲验**这一位,不合就**抛**——它是 LOAD-BEARING * 面上的坏行(一条说自己 park 了却拿不出暂停终局的记录,既不能驱动、也不能当成跑完),吞掉它会让 resume * 在一个被钉住的子代 session 旁边重新起一只活跑,正是 core 那条「一次性、响亮」的 schema 台阶要防的形。 * * 🔴 **盘上的编码是那道台阶的一部分**,不是实现细节:见 {@link journalRowPayloadOf} 的顶注(park 载荷走 * `{"parked": …}` 信封,好让**回滚/混版**的老读者拿到一个没有 `terminal` 的对象、落进 core 的响亮拒绝臂, * 而不是把一个暂停终局当成可重放结果)。 */ import type { TaskResult, WorkflowJournalEntry } from "@sema-agent/core"; /** park 臂的载荷型(core 的 `WorkflowJournalEntry` park 成员逐字):一只 `terminal` 收敛到 `paused` 的 `TaskResult`。 */ export type ParkedTaskResult = TaskResult & { terminal: Extract; }; /** * 读一条记录的两臂 —— **唯一**的判别点。`parked` 为真时 `payload` 是子代那份暂停终局,否则是它的完成结果。 * * ⚠️ 判据逐字照 core 自己的 file 店(`entry.parked !== undefined`),不是真值判:一只 `parked` 位上的 * 假值在 core 的类型上拼不出来,而按真值判会在将来某个 falsy 载荷上把 park 读成 result。 */ export declare function journalEntryPayload(entry: WorkflowJournalEntry): { payload: TaskResult; parked: boolean; }; /** * 盘上一行的 **payload 文本编码** —— SQL 店 `append` 的唯一写者。 * * · result 臂 ⇒ `JSON.stringify(entry.result)`,与 7.68.0 及以前**逐字节相同**(历史行零迁移); * · park 臂 ⇒ **信封** `{"parked": }`。 * * 🔴 **信封不是包装,它是 core 那道「一次性、响亮」的 schema 台阶在 SQL 店上的兑现**(codex 对抗复审 * r1 [high],验真后采纳)。core 的设计逐字是:park 序数journal 在 `parked` 键下、**没有 `result`**, * 于是「一个本版本之前的 journal 读者找不到终局因由,会**拒绝**整次 resume(`WorkflowJournalIncompatibleError`) * 而不是把一个暂停终局当成跑完了拿去重跑」。 * * 本仓最初的实现把 park 载荷直接写进 `result` 列、判别位另开一列 —— 那**破坏了这条屏障**:一台回滚到 * 7.68.0(core 7.9.2)的副本、或滚动升级期的混版机队,它的 `load` 是 `SELECT call_key, result` + 逐行 * `{callKey, result: JSON.parse(result)}`,于是拿到一条**带 paused 终局的 result 臂**,照 core 的老语义 * 当作可重放结果 —— 在那个子代 session 已被一条 pending checkpoint 钉住的同时**再起一只活跑**, * 批准前的外部副作用重复一遍。这正是那道屏障存在的理由。 * * 信封形让老读者拿到一个**没有 `terminal`** 的对象 ⇒ 落进 core 那条「找不到终局因由 ⇒ 拒绝 resume」的 * 臂,方向与 core 的 file 店逐字一致。附带的好处是 **零 DDL、零重建库**:判别位活在载荷里,不占列。 */ export declare function journalRowPayloadOf(entry: WorkflowJournalEntry): string; /** * {@link journalRowPayloadOf} 的逆 —— 盘上一行的 payload 文本 → 记录。**唯一**的回装点(SQL 店的 `load` * 与 HTTP journal 读面共读它)。 * * 判别式 = 解出来的对象上有没有**自有** `parked` 键。它与 `TaskResult` 结构上不相交(core 的 `TaskResult` * 没有这个成员),所以历史字节(裸 TaskResult)恒判 result 臂 —— 不需要任何「老字节 = result」的兼容规则, * 判别式自己就说清了。 * * park 臂验 `terminal.kind === "paused"`;不合 ⇒ **抛**,由 LOAD-BEARING 的 `load` 把它当店故障传上去。 * 一条「说自己 park 了却拿不出暂停终局」的行既驱动不了、也不能当跑完,吞掉它会让 resume 在被钉住的子代 * session 旁边重新起一只活跑 —— 与上面那条屏障要防的是同一件事。 */ export declare function journalEntryOfRow(callKey: string, json: string): WorkflowJournalEntry; //# sourceMappingURL=workflow-journal-entry.d.ts.map