Files
JX-BPOS/docs/debt.md
T
ChaiHaoandClaude Opus 5 1fd215cf40 范式退役:活动只剩一种、只跑 Loop;审批活动可管;活动库重做
用户定的两件,一并落地。

**一、活动不再分 flow 和 loop —— 彻底退役 flow**
  · `flow-engine` 模块整个删除(runner 379 行 + step-sequence 184 行 + 测试);
  · `paradigm` 从清单身份、`activity_package`/`task` 两张表、任务卡与活动库一并退场;
    `businessModel` 不再是判别联合 —— 只有案例模型(CMMN + 说明书 + DMN)一形;
  · task 的引擎分派从「按 paradigm 取两个之一」收成**唯一的引擎**;
  · `activities/` 四个 flow 活动(A1 受理 / A4 定价 / A6 议价 / A7 归档)各写了一份
    案例模型:阶段照原来的步分,触达世界的项一律 manualActivationRule,
    能算的交 DMN(A1 的三分类 P-03、A4 的 P-01/P-02)不交 Agent;
  · 六个占位槽位的骨架从「机器步落台账 + 一张验收单」改成案例骨架,包版本 0.8.0 → 0.9.0。

**代价写在明处,不假装没有**:`turn-runner` 每一拍都要 `agent.decide` ——
环境里没有模型 key,案例活动一步也走不动。三套端到端(统一报价 / 新建流程包 / 测试台
Trace)整套跳过,文件头写明恢复条件:①有可用模型 ②走法改写成按 Enabled 集推进。
**不许把断言改松让它变绿** —— 绿得不诚实比红着更坏。debt.md 已记。

**二、审批活动可管**(新能力)
  · `sys.approval_template`:审批点**可复用的那一半** —— 审批人看哪几项、按什么名字看、
    怎么催、说什么话。谁来审不在里面(与活动点同口径,运营时轮值);驳回回哪个节点
    也不在里面(那是每张图自己的事,留在节点上);
  · 三条路由 + 引用反查:还被审批点引着就不许删(409),删了那些节点会**静默**退回旧行为;
  · 运行时 `basisOf`:引了模板就按模板筛与改名,没引即旧行为(载荷全摊开)。
    模板改了只影响新发出的审批单 —— 在途单的依据在发出那一刻已定形。

**三、活动库重做**:一区两格 —— 「活动(装进来的)」与「审批活动(配出来的)」,
都用流程卡那套两段式卡片(头 + 四格数据板),幽灵卡当入口(装入活动 / 新建审批活动);
「标准/案例」那一维随范式一起撤,业务分类成了唯一的一维,以行内小签呈现。

迁移 0020:两张表 drop paradigm、建 approval_template。已在开发库跑过并重播种。
实测(丁子乔账号):建「定价审批」模板 → 流程包 A5 属性里选得到 → 引用反查计数正确。
四闸绿(api:check 随本提交)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-29 23:53:51 +08:00

206 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 技术债登记簿
> 编码规范 §17:历史违规集中登记于此——旧路径 `warn`、新代码 `error`,清零后升级。
> 例外必须注明原因与预期清理时间;散落的 `eslint-disable` 不算例外,算违规。
| # | 债 | 原因 | 预期清理 | 状态 |
|---|---|---|---|---|
| M0-1 | `pnpm db:seed` 只打印种子计划,未真正写库 | 种子数据(五岗位 / 五员工)已在 `apps/api/src/app/seed.ts` 冻结为常量并定义 `OrgSeeder` 端口;写入实现依赖 `org` 模块的 Drizzle schema,属 M1 | M1 | 已清理(M1) |
| M0-2 | `packages/{sfeel,activity-sdk,ui-kit}` 仅有版本常量 | M0 只立骨架,内容按里程碑序填(§15);`@contracts` 已于 M1 落首版 | M3–M5 | 进行中 |
| M1-1 | `container.ts` 里两个占位端口:`InFlightWorkProbe`(空集)、`TriggerMatcher`(恒不命中) | 二者的真身在 task(L6,M4)与 trigger(L7,M5);`MessageFallback` 已于 M2 接真身 | M4 / M5 | 进行中 |
| M1-2 | 条件求值 `passthroughEvaluator` 恒真 | `@sfeel` 属 M3;M1 的监听只用类型 + 关联键过滤,不用内容条件 | M3 | 进行中 |
| M1-3 | ~~投递记录写入即止,尚无派发 worker~~ | 派发器已于 M2 落地(advisory lock 串行 + 消费幂等);消费者(任务 / 触发器)随 M4 / M5 注册 | M2 | 已清理 |
| M2-1 | 派发器的消费者表为空 —— 投递只标记已送达 | 任务消费者属 task(M4),触发器消费者属 trigger(M5);投递链路本身已可测 | M4 / M5 | 进行中 |
| M5-3 | 活动 .JS 产物是源码文本,未经 tsup 单文件 ESM 真编译 | 静态审扫描的正是产物文本(import 与全局引用),当前形态已可审;真编译在装载器落地时接入 | M6 前 | 进行中 |
| M5-2 | loop 的 Agent 回路有两个实现:单拍版(缺省,经 `ModelGateway`,两缝完整)与**开环版**(`JCAI_AGENT_OPEN_LOOP=1`:Mastra `Agent` + 工具总线,拍内自主连环调工具) | 端口 `AgentPort` 不变,usecase 未动。**开环的代价**:模型逐轮调用不经模型网关的录制回放(tool-calling 往返 `generate` 的 text-in/text-out 承载不了)—— 四元绑定与凭证隔离照旧走 `resolve`,**工具调用一缝完整**;要恢复逐轮录制须给 Mastra 供一个自实现的 AI SDK LanguageModel。开环待第一个真 loop 活动压测后再考虑转缺省 | 第一个真 loop 活动 | 进行中 |
| M5-1 | `process` 未用 Mastra workflow 编译器 —— **运行时切面已立**(2026-07-28:`FlowRuntime` 端口 + 自持实现整体迁入 `internal/runtime/`,usecase 成薄壳,八例 E2E 为等价性护栏),编译器实现仍欠 | 编码规范 §6.1;接入路线已定(端口化+双实现,回边 reopen 先 spike);冲突清单见 process/CONTRACT.md | 编译器随 spike 结论落地 | 进行中(切面已立) |
| M2-2 | pg-boss 已装未用 | 定时作业(监听时限扫描、周期触发器)随 M5 落地;当下派发器用 setInterval | M5 | 进行中 |
| M5-4 | AND 并行没有声明机制 —— 网关废除后(2026-07-28)多出边一律异或,画不出并行分叉 | 分支=条件出边、汇合=多入边异或已够 MVP;真要并行时以「并行组」声明加回(对应 Mastra `.parallel` 结构块),与 M5-1 编译器同批设计 | 需要并行的第一条流 | 进行中 |
| M5-5 | ~~含网关历史版本的运行时兼容三处~~ | 用户令库只留一条流一个版本(外科手术清空),在途与历史归零 —— schema 的 `kind: 'gateway'`、`joinReady`、汇合等待、`gateway-deprecated` 违规项已于次轮全部摘除 | 2026-07-28 | 已清理 |
## 节点分派 · 员工组轮值(2026-07-28 第五轮更新)
编排不带岗位(活动点与审批点同一口径)——「谁来干」是运营时分配:
工作流绑了员工组按组轮值(组员并集最闲者先),没绑组**全员轮值**;
分派结果记在实例 assignee 上,审批箱/裁决权/链条/治理列表都按它认人。
ADR-011 的「绑定先于策略」随岗位一起退场 —— 工作组快照的**成员集合**成了轮值池,
按岗位点名的绑定语义不复存在。still 欠:起单时选组、按人指派、认领池 —— 运营中心的
分派界面落地后,这里的默认轮值让位。
## 编排层 Mastra 化 · 阶段状态(2026-07-28)
四阶段路线(用户拍板:端口化 + 双实现;回边先 spike;Agent 放开工具循环):
| 阶段 | 内容 | 状态 |
|---|---|---|
| 0 | loop 三修(提交放行发验收卡 / 事件只记不跑 / 轨迹喂回叙事面) | ✅ 已落 |
| 1 | 抽 `FlowRuntime` 端口:执行面成可替换切面,usecase 成薄壳 | ✅ 已落(八例 E2E 为等价性护栏) |
| 2 | reopen 回边 spike:`.dowhile` 子图 vs 外部胶合新 run | ✅ 已定案:**路线 B**(`spike/mastra-reopen/FINDINGS.md`) |
| 3 | Mastra 编译器 = `FlowRuntime` 第二实现 | ✅ 编译器 + 胶合层已落(路线 B);**尚欠 `MastraRunner` 的真实现与接线开关** —— 见下 |
| 4 | Agent 开环:Mastra `Agent` + 工具总线 | ✅ 已落(`JCAI_AGENT_OPEN_LOOP=1` 开启,缺省仍单拍) |
**解耦本身已达成**(不依赖阶段 3):编排层对活动只有两个触点(`TaskGateway.start`
起活动 / 终态回报),执行面可整体替换而活动侧无感 —— 证据是
`new-package-flow.it.test.ts`:从零建一条新流(建包 → 链接已装载的活动 →
双判据激活 → 起单 → 走完),全程没碰活动一行代码。
**阶段 3 的剩余欠账**:`MastraRunner`(startRun / resumeRun 的真实现,需要
`Mastra` 实例 + `PostgresStore` 挂 `mastra` schema)与接线开关未做 ——
编译器与胶合层都已落且有单测,但**没有在真库上跑过一整条流**;
转正之前自持实现仍是唯一在跑的路径。真跑时要验的两条(spike 已点名):
跨进程 `createRun({ runId })` 取句柄再 resume、Postgres 下的快照并发写。
已绕过的语义冲突(详见 process/CONTRACT.md):回边 reopen(路线 B 解决)、
`.parallel()` 任一失败即整体失败(失败走向不进 Mastra)、三态聚合投影
(读实例表不读 run 状态)、`node_instance` 投影表照写(发号权在主工程)。
## 鉴权(ADR-010 落地后的已知欠账)
- **登录失败无次数限制**:可无限次试口令。单组织 ≤50 人、内网部署下暂缓;
接入外网前必须补(按账号 + 来源 IP 双维度退避)。
- **无口令强度校验、无首登强制改密**:全员初始口令相同(`JCAI_SEED_PASSWORD`),
谁都没改过就等于没有口令。治理台「员工」面需补:改密 / 重置 / 强制改密标记。
- **`setPassword` 已在 org 出口就位但无路由**:改密只能经播种或直接调用例,界面上没有入口。
- **会话无「登出全部设备」**:`revokeAllOf` 已实现(停用员工时调),但本人无从触发。
## 工作组(ADR-011 落地后的已知欠账)
- ~~**没有治理台管理界面**~~ —— 2026-07-27 清账:运营中心 · 员工 · 工作组页签可建组 / 改组
(`GET|PUT /api/gov/work-groups`);实测改绑定后新起工作流的 `work_group` 快照即含新成员表。
- **一人一组**:`workGroupOf` 取该人名下稳定的第一条;起单时选哪个组这一步没做。
- **组成员离岗无提醒**:绑定的人离岗时静默退回四策略,不告诉任何人。
## 工作成果 · 文件
- **Markdown 只以原文纸面呈现**:富文本渲染(GFM 表格 / 内联 SVG)要 `react-markdown` +
`remark-gfm` + `rehype-raw` 三个依赖,当前成果文件绝大多数是 HTML,先不引。
引入前不假装渲染过 —— FileViewer 里以 `<pre>` 出原文。
- **过程成果(deliverable)尚无来源**:活动还不发布 deliverable,所以成果文件目前
只有活动推的那一屏。装配已接好(`desk.ts` 的 `results`),活动一产出就自动进列表。
## 沟通记录(占位)
工作区第二个页签「沟通记录」目前是空占位。它要汇集这一单**与外界的往来**:
邮件线程、通道消息、原件存档 —— 数据在 `channel` 与 `event` 里已有,尚未做投影。
界面上明说「还没接」,不伪装成「暂无数据」。
2026-07-28 对账:这条曾**只写在注释里没落到文案上** —— 注释写着「明说没接」,
正文却写「都会归到这里」,等于把没接说成了空,人会一直等。已改为「往来记录还没接通」,
并给空态加了第三色调 `muted`(通路没接,灰着不许诺;区别于 `brand` 的「等着就会有」)。
`outbound_mail` 至今只有 channel 建表与清库脚本碰过,任何界面都读不到 —— 所以文案也
不把人指去「消息」面。
## 工作区页签条装不下五个页签
五个四字页签 380px + 翻页囊 112px + 间距 30px = 522px,工作区仅 429px,差 94px。
当前先做成**整条可横滚**(不裁、不溢出卡片),最终形态待定,备选:
- 缩短页签文字(沟通 / 成果 / 详情 / 日志)—— 省 ~100px,但文案变短
- 翻页囊只留箭头、页码收进 title —— 省 ~50px,但看不见「第几件/共几件」
- 页签条换行两排 —— 全都放得下,但工作区正文少一行高
## 驾驶舱 · 死信卡(2026-07-27)
运营驾驶舱首屏收了「异常工作流 / 未闭合草稿 / 在途任务」三卡,**没有死信卡**。
死信目前只以治理者消息要素送出(`deadLetters.report` → `message`),没有一个按 kind 计数的
查询面 —— 要补这一卡,得给 system_message 加一条按 kind 聚合的治理路由。
在那之前,驾驶舱不摆一个查不出数的卡位。
## 工具箱 · MCP 连接(2026-07-28)
工具箱「接入」段有两件:**导入自定义工具已实装**(与第一方同门两审,端点调不通就不入目录);
**MCP 连接仅呈现**。系统还没有 MCP 客户端 —— 接进来之后 MCP 的工具与自定义工具同等待遇:
经工具调用一缝去调、过两审入目录、按白名单授给活动。卡上如实标「未连接」,不摆点不动的按钮。
## 活动槽位未实现(2026-07-28)
六个槽位(rfq-intake / alt-selection / sourcing-inquiry / cost-pricing / quote-negotiation /
intel-archive)只有契约面,入口产物是抛「未实现」的占位 —— 底座备齐后按 A1→A7 顺序逐个开发,
届时以新包版本经安装通道换入,槽位契约不动。
2026-07-28 更正:这条原写「起单后任务会停在 A1」,手工走查后发现说得不准 ——
**A1 本身走得完**(六步人工代办 + 收口验收放行,活动 `released`),卡的是**它后面的 G1**:
出口的必填去向旗产不出来,网关判不了。准确的诚实状态见下文「编排层 · 手工走查」。
## 编排层 · 走查已清项(2026-07-28)
一轮系统走查后已修:
- 卡片被压扁剪裁(`.g-page` 是滚动 flex 列,子块要 `flex-shrink: 0`)
- 数据对象只见键不见中文名(label 随物化落 PG 表/列注释,全链读回)
- LLM 工具错做成模型实例(改为五件大模型驱动的系统内通用工具)
- 元素盘点击落子落不下(原型里就坏:`create.move` 无 hover 目标 → `create.rejected`)
- 卡点还原凭空造边(`e-A5-A4`:审批点的驳回牌子被当成边;改为只认亲不新建)
- 已发布包改图找不到路(编排工作区就地给「起草新版本并去编辑」)
- 活动契约面治理台看不到(列表出参 + 活动详情 + 导出依赖文件三处打通)
已验为**正常**、不需要改的:编辑器往返无损(16 边 14 节点、映射与驳回声明全留)、
边映射校验逐边点名、发布门硬(缺条件即 412)、调起规则四类齐、导出依赖文件内容完整。
## 编排层 · 手工走查(2026-07-28)
把 `unified-quote` 从起单一路手工答到底,发现并已修 **5 项**:
| 症状 | 根因 | 修法 |
|---|---|---|
| 流程链读作 A1→A7→A2→A6→A3 | `toChain` 按 `layout.col` 排,而 MVP 图是回形走线 | 新增 `advanceOrder`,按边拓扑排、排除回边 |
| 七个活动派出七张一样的「请处理」 | 槽位骨架 `card: {}`,工单落回默认 | 卡面自述:标题 = 步骤标签,依据带步序 |
| 走到第 3 步就 `exhausted` 终态 | 六槽位共用 `humanCalls: 3`,A1 有 6 步 | `budgetFor(n)` 按各自步数给 |
| 六步答完卡在「等人却没东西给人答」 | 末步全是 execution,而 `released` 只出自末步验收放行 | 骨架末尾追加一步「收口验收」 |
| 只走了 A1 却报 `completed` | 状态聚合把「没有在跑的了」当完成 | 「完成」凭到过终点;收敛未到终点 = `abnormal` |
**当前诚实状态**:A1 六步可人工代办、收口验收可放行、活动 `released`;但流**过不了 G1** ——
出口 `intake` 的必填去向旗(`needAlternative` / `needSourcing`)产不出来。这不是缺陷:
出口载荷由**台账落地事实**推导(数组←落地行、标量←铺平事实),槽位没实现台账写入就没有事实,
让它凭空捏一个就是在系统内部 mock,规矩不许。现在这个状态会**如实上报**:
工作流 `abnormal` + 一条 `route-dead-end` 事件写明「G1 的两条出边要 needAlternative,
当时可用的键只有 body / customerCode / records / rfqRef / subject」。
**于是 A1 的开发完成判据就具体了**:`rfq-intake` 必须落 `project.intake_record` 等行、
并让 `needAlternative` / `needSourcing` 成为落地事实 —— 否则 G1 永远判不了。A2…A7 同理:
先看自己出口的必填字段能不能由落地事实推出来。
> 后记(2026-07-28 稍后):占位骨架落地后整条流已在真库上八例端到端全绿;
> 且**网关已废除**(G1…G4 不再是节点,上表按当日图记述)—— 判据不变,只是「G1 判不了」
> 如今读作「A1 的条件出边判不了」:要 `needAlternative` 的还是那两条边,只是边挂在 A1 身上了。
## api:check 的盲点(2026-07-28)
契约面只收 `index.d.ts` / `contract.d.ts` / `port.d.ts`,而 `index.d.ts` 是 re-export **名单**。
所以**给纯函数加形参、改返回类型,这道闸看不见** —— 实测:`aggregateWorkflowState` 加了
第三个入参,api:check 报「公开契约无变化 ✔」。名单变化(增删导出)照样能拦住。
要补得让契约面也收 domain 的 .d.ts,代价是全部纯函数签名进快照、日常改动噪声变大;
在那之前:**改纯函数签名时手动同步 CONTRACT.md**,别指望闸提醒。
## 触发链路 · 邮件分类 Agent(2026-07-30)
用户定的触发逻辑五步:接收邮件 → **分类 Agent 识别消息类别** → 命中 RFQ 事件 →
(可选)系统消息到指定人工作台 → 工作台确认起单。第 ④⑤ 步(确认链路)本轮已落地
(`sys.trigger_pending` + 消息 `eventRef` + 幂等确认);第 ② 步的「分类」目前由
**通道抽键正则 + 触发器 S-FEEL 规则**代位 —— 判得了「主题带不带询价/RFQ」,判不了语义类别。
升级路径:通道入站归一后挂一个 LLM 分类工具(工具箱五件之 classify,经模型网关一缝),
产出 `messageCategory` 进事件载荷,触发器规则改判 `messageCategory = "rfq"`。
前置:模型实例已绑定(大模型区)、分类样例集与回放审。不阻塞现有 RFQ 流 —— 正则今天够用。
## 范式统一的欠账:占位活动没有模型就跑不动(2026-07-29)
用户定:**活动不再分 flow 和 loop,内部实现都是 Loop**。本轮照此退役:
`flow-engine` 模块整个删除、`paradigm` 字段从清单与两张表退场、
`activities/` 里四个 flow 活动(A1/A4/A6/A7)与六个占位槽位改写成案例模型(CMMN + 说明书)。
**直接后果,写在明处**:`turn-runner.runTurn` 每一拍都要 `agent.decide` ——
环境里没有模型 key(`JCAI_DEEPSEEK_API_KEY` 或等价的实例绑定),案例活动**一步也走不动**。
于是三套端到端整套跳过,并在文件头写明恢复条件:
| 套件 | 为什么跳 |
|---|---|
| `unified-quote-flow.it.test.ts` | 靠答验收工单走完整条流;工单从前由 flow 末步发,现在由 Agent 每拍决定 |
| `new-package-flow.it.test.ts` | 同上(新建流程包后走一遍) |
| `testbed-trace.it.test.ts` | 同上(测试台起单 → 真任务 → 全链 Trace) |
恢复要两件,缺一不可:①环境有可用模型;②走法改写成 loop 的口径 —— 按 Enabled 集推进,
而不是按步序答工单。**在此之前不许把断言改松让它变绿**:绿得不诚实比红着更坏。
顺带作废的旧欠账:「占位期六个槽位一律 flow 范式,真开发时装回 loop」——
已经没有 flow 可退回,这条不存在了。