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

16 KiB
Raw Blame History

技术债登记簿

编码规范 §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 可退回,这条不存在了。