From c60c882f065353ece0b0db472ca44c2cc8a39f50 Mon Sep 17 00:00:00 2001 From: xcs Date: Sat, 29 Aug 2026 08:45:36 +0800 Subject: [PATCH] =?UTF-8?q?chore(contracts):=20loop-engine=20port=20?= =?UTF-8?q?=E5=BF=AB=E7=85=A7=E4=B8=8E=20CONTRACT.md=20=E5=90=8C=E6=AD=A5(?= =?UTF-8?q?=E9=85=8D=E6=96=B9=E4=B8=93=E7=94=A8=E4=B8=A4=E4=B8=AA=E6=97=97?= =?UTF-8?q?=20+=20caseKey)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 契约变更四步: ① depcruise 反查下游 —— `no dependency violations`;`draftWrite` / `writable` 的调用方 逐个查过(engines 三处、turn-runner 两处、auto-runner 两处),新增字段全是可选, 老调用方一个字不用改; ② CONTRACT.md 已同步(近变一节写明两个旗各治什么、跳过的行不许被 prune 撤, 以及同批三条不变量:配方只认 liveStep / submit 与模型同一条处置 / 办不下去发卡交人); ③ 本快照单独说明:`TaskDataGateway.draftWrite` 增 `skipUnchanged`、`skipExisting` 两个 可选入参与可选出参 `skipped`;`writable` 增可选 `caseKey`。**非破坏性**; ④ 无破坏性变更,不需要三步弃用。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet --- .../api/src/modules/loop-engine/port.d.ts | 21 +++++++++++++++++ apps/api/src/modules/loop-engine/CONTRACT.md | 23 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/api-report/apps/api/src/modules/loop-engine/port.d.ts b/api-report/apps/api/src/modules/loop-engine/port.d.ts index 13f8b17..f313a89 100644 --- a/api-report/apps/api/src/modules/loop-engine/port.d.ts +++ b/api-report/apps/api/src/modules/loop-engine/port.d.ts @@ -38,6 +38,12 @@ export type TaskDataGateway = { object: string; label: string; keys: string[]; + /** + * 单标识列(`declarations.ledger[].caseKey`)—— **摆给人看的行标识要去掉这一段** + * (2026-08-25 柴浩:「COV-…/CU-0142-01 只保留斜杠右边」):人正在这一单里看, + * 单号是脚下的地面,不是区分行的信息。基座那一侧同款做法见 `rowLabel`。 + */ + caseKey?: string | undefined; fields: Array<{ name: string; label: string; @@ -56,8 +62,23 @@ export type TaskDataGateway = { object: string; key: Record; patch: Record; + /** + * **值没变就别落笔**(2026-08-29 终审 I2)—— 配方每一拍都把同一张表重投一遍, + * 而草稿层的 upsert 尾巴上有 `landed_at = null`(「再写一笔就不再是已落地」)。 + * 照写的话:数据工单确认入库之后,下一拍配方一重跑,那些行的「已落地」记号就被**悄悄擦掉**, + * 整单放行时会被再写一遍正式表、再抛一遍 data 事件 —— 而承诺是「整单放行不再写它、不抛事件」。 + * 模型那一路不传这个旗:它的「空转写」要被看见(如实回 `changed:false`),行为一个字不变。 + */ + skipUnchanged?: boolean | undefined; + /** + * **「只建不改」的表碰到已在库的键:跳过,不拒**(2026-08-29 终审 I2)。 + * 那句拒回是写给模型看的(「别再写它,用 drop 把骨架撤掉」)—— 摆到人面前毫无意义, + * 而配方抛出去就是永久停单。对配方来说「这颗料已经在库了」是**常态**,不是错。 + */ + skipExisting?: boolean | undefined; }): Promise<{ changed: boolean; + skipped?: boolean; }>; /** * **撤掉本单草稿层的一行** —— 写错了得有退路。 diff --git a/apps/api/src/modules/loop-engine/CONTRACT.md b/apps/api/src/modules/loop-engine/CONTRACT.md index 7594f8d..55e7b8d 100644 --- a/apps/api/src/modules/loop-engine/CONTRACT.md +++ b/apps/api/src/modules/loop-engine/CONTRACT.md @@ -164,3 +164,26 @@ 决策卡的回答进基座读数 `decision_last` / `decision_<值>_total`(按 card-reply 数)—— 审核通过与否不再由模型自写的旗说了算; 拒回 / 观察 / 事实行按答案分岔(`chosenGuidance`):`custom:` 不是通过,不许写通过旗、不许 finish。 +近变(2026-08-29,终审修复波):`TaskDataGateway` 两处扩面,都是**配方专用**,模型那一路一个字不变 —— + +- `draftWrite` 多两个可选入参与一个可选出参: + - `skipUnchanged` —— 值没变就**不落笔**。`DraftStore.put` 的 upsert 尾巴上有 `landed_at = null` + (「再写一笔就不再是已落地」),而配方每一拍都把同一张表重投一遍:照写会把数据工单确认入库的那些行 + **悄悄 un-land**,整单放行时再写一遍正式表、再抛一遍 data 事件 —— 而对 landed 行的承诺是 + 「整单放行不再写它、不抛事件」。模型不传这个旗:它的「空转写」要被看见(如实回 `changed:false`)。 + - `skipExisting` —— 「只建不改」(`createOnly`)的表碰到已在正式表里的键,回 `{changed:false, skipped:true}` + 而不是抛。那句拒回是**写给模型看的**(「别再写它,用 drop 把骨架撤掉」),摆到人面前毫无意义; + 而配方抛出去就是一单永久停在那儿。对配方来说「这颗料已经在库了」是常态,不是错。 + 跳过的行照旧算「这一笔要的行」,`prune` 不许撤它。 +- `writable` 多一个可选 `caseKey`(单标识列):摆给人看的行标识要**去掉单号那一段** + (2026-08-25 那条口径,基座那一侧是 `rowLabel`)—— 人正在这一单里看,单号是脚下的地面。 + +同批三条不变量: +- **配方由 `liveStep` 驱动,不由 `current` 的兜底驱动**:兜底(在场的第一个有剧本的项)不过滤 + `settledIds`,它只服务提示词;让它驱动副作用 = 已收掉的步再写一遍台账、再 prune 一遍、甚至再发一张卡。 +- **配方的 `submit` 与模型的 submit 同一条处置**:`raiseAcceptance` 抛了不许让回合崩(留痕 + `running`)—— + 裸着调的话,`engine.advance` / `task-service.tick` / `task-advancer` 一路都不捕获,任务留在 `running`, + 推进器无限热循环:不烧预算、不换状态、界面纹丝不动。重做闸的「改过台账」也认配方的写 + (`observation` + `detail.auto` + `detail.ledger`),否则那堵墙对配方永远开不了。 +- **配方办不下去 = 发一张卡交人**,不是 `awaiting-event` 干等:推进器只捡 `running` 的单, + 卡已答完、没立监听、没有时限时再没有任何东西会叫醒它,而界面写着「等外部回音」。