# 技术债登记簿 > 编码规范 §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 | 进行中(`spike/mastra-reopen/`) | | 3 | Mastra 编译器 = `FlowRuntime` 第二实现 | 待 spike 结论 | | 4 | Agent 开环:Mastra `Agent` + 工具总线 | ✅ 已落(`JCAI_AGENT_OPEN_LOOP=1` 开启,缺省仍单拍) | **解耦本身已达成**(不依赖阶段 3):编排层对活动只有两个触点(`TaskGateway.start` 起活动 / 终态回报),执行面可整体替换而活动侧无感 —— 证据是 `new-package-flow.it.test.ts`:从零建一条新流(建包 → 链接已装载的活动 → 双判据激活 → 起单 → 走完),全程没碰活动一行代码。 阶段 3 的已知语义冲突(编译器必须绕,详见 process/CONTRACT.md): 回边 reopen 无原语、`.parallel()` 任一失败即整体失败(与「局部失败不连坐」相反)、 三态聚合投影(`abnormal` 防假成功,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 里以 `
` 出原文。
- **过程成果(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**,别指望闸提醒。