正在构建基于 vendored Cordis/DeepSeek Harness 的工作流设计应用。
全流程唯一入口:pnpm run verify:ci(= node scripts/ci/verify-all.mjs,门清单的唯一真相就在该文件)——
静态门 → typecheck → build → 各包默认套件与根级 test:harness → 真实浏览器门,逐门打印真实退出码、
失败即停并点名失败门。CI 已存在:.github/workflows/ci.yml(仓库唯一 workflow),它不再自列门清单,
只调用同一个制品——所以"本地绿"与"CI 绿"说的是同一件事。
pnpm run verify:gates 仍是三道静态门的子集(= check:build-order → check:lib-assets → check:test-selector,
遇错即停并透出该道守卫的真实退出码,三道输出都可见),局部改动时更快;
但"全流程过"以 verify:ci 为准。触发条件:任何改动或新增 packages/**、apps/**
里的 scripts/test.mjs、任何新增工作区包、或任何改动某包 test 脚本之后必跑。
-
pnpm run check:build-order:根build序列必须是工作区依赖拓扑的合法线性化。 判据范围(写清楚,不留含糊):判红两类边 —— ①dependencies里的工作区边; ② tsconfigreferences的构建期边(composite 项目引用:tsc -b会先构被引用项目,所以这是可判定的 构建期先后关系,此前完全没人查)。只露脸不判红:devDependencies/peerDependencies/optionalDependencies里的工作区边 —— 它们以INFO [DEV_DEPENDENCY_EDGE_NOT_JUDGED]列出(含方向), 理由不是"没想过"而是实测:本仓当前有 2 条方向与 build 顺序相反的 devDep 边 (@ogd/templates第 10 步 devDep→@ogd/marketplace第 13 步、→@ogd/storage第 11 步), 而它们只在测试期使用(templates的 build 是tsc -p,其references只有contracts/designgraph) ⇒ 若照dependencies同规则判红,会在当前正确的树上给出 2 条假红。 未覆盖面:非工作区包(vendor 下的 harness)与不在 build 序列里的包一律不判,口径与dependencies一致。 -
pnpm run check:lib-assets:构建时复制进产物目录的非 TS 资产(如src/migrations/*.sql→lib/migrations/) 必须与源集合同步,且已构建产物的STORAGE_SCHEMA_VERSION必须等于产物里最高的迁移文件版本。 退出码 0 同步 / 1 漂移 / 2 检查无法完成(「没东西可比」不会静默通过)。 已作为构建后断言接在pnpm run build的最后一步(build:harness内部),会拦下 「有人改了src/、只跑了tsc、没跑构建里的复制步骤」这类会让走已构建 lib 的消费者 以STORAGE_CORRUPT开不了库的漂移。背景(2026-09-15 真实事故)、退出码语义与负控证据见 .taf/verification/lib-assets/README.md。
-
pnpm run check:test-selector:测试选择器同源守卫——断言每一份scripts/test.mjs(当前 12 份) 的选择器契约一致:逐词守卫存在(任一筛选词未命中 ⇒TEST_FILTER_UNMATCHED+ 退出码 2)、空选择TEST_SELECTION_EMPTY+ 退出码 1、--test-timeout=60000、exitCode透传、selector-selection-mode声明与代码谓词一致。已知有意分叉:any(OR 并集,9 份)/all(AND 交集,3 份:designgraph、plugin-sdk、workflow-execution)——两种都合规,守卫只断言"声明 ↔ 实现"一致,不要"顺手统一" (把all改成any会改变选中集合,属行为变更)。退出码 0 一致 / 1 有 runner 漂移并点名该份与 那条规则 / 2 配置错误;声明了test却没有 runner 的包、以及完全没有test脚本的包(当前 4 个) 会以INFO列出,让"没有测试面"可见。背景(2026-09-16 runner 分叉导致两位属主互相误判;variant C 三个包在"一词命中+一词拼错"时退出码 0 静默吞词)、逐包实测两列与先红后绿证据见 .taf/sdd/2026-09-15-vendored-harness/progress.md 的「测试选择器同源化」两节。
未命中时的失败面(本轮加固,12 份 runner 逐字同一处插入,各 +70 行):除既有的错误码行与可用文件 清单外,还会打印参数口径(一个筛选词 = 一个独立参数),把"把多个词塞进一个带空格参数"的形态 单独点名(
↑ 含空格的参数:"tools authorization" —— 它被当成一个整词)并给出拆分后的正确写法与 正误示例;同时在失败路径落一份回执.taf/verification/test-selector/<包路径>.last-failure.json(at/runner/argv/unmatched/spaceJoined/candidateCount/available/advice/exitCode,每次未命中覆盖该包上一份)。 退出码语义不变:未命中 2、空选择 1、子进程退出码原样透传。为什么需要回执(实测,不是推测):
pnpm -s -r test -- "词1 词2"这种调用下,pnpm 的 silent reporter 在递归模式里丢弃子进程流 ⇒ 实测 exit 2 且零行输出(02:35:59)——此时 stderr 写得再 具名也没人看得见;回执让"静默 exit 2"至少留下可查物证(12 份 runner 已逐一验证都会落回执)。 同一个畸形参数走pnpm --filter <包> test -- …(非 silent)时有 6 行输出、诊断可见;直接调用 本来就具名(2 行,TEST_FILTER_UNMATCHED+ 可用文件清单),所以"完全无输出"只属于被父进程吞流的路径。 另一条会伪装成同样症状的坑(本仓已踩过):spawnSync('pnpm', …, { shell: false })会得到status=null、零输出、error.code=ENOENT(pnpm 是.cmd/.ps1壳)——解读前先看result.error。
澄清:verify:gates 始终只是三道静态门的子集,浏览器门从来不在它里面。全流程入口 verify:ci
(= scripts/ci/verify-all.mjs)的清单里现在包含四道真实浏览器门:renderer、graphview-client,以及下面
这两道 designgraph 系门。
pnpm --filter @ogd/workflow-execution test:browser:载体是packages/designgraph/tests/browser/compile-parity.spec.ts(由 designgraph 的tests/browser/playwright.config.ts驱动,加载lib/构建产物)。pnpm --filter @ogd/designgraph test:browser:载体是packages/designgraph/tests/browser/下的 spec。
两者都只有三种诚实结局,全部由真实退出码表达:运行时与载体齐备 ⇒ 真跑并透出 spec 的真实退出码;
缺任何一项 ⇒ 退出码 2 + [NOT RUN] 并点名缺的具体东西(哪条路径 / 哪个依赖);spec 自身失败 ⇒
透出 spec 的退出码。不存在"载体缺失也仍然 exit 2"的固定分支,因此装上运行时即可转绿。
它们在 verify:ci 里是「被检查的不变式」,不是「已验证」:两道门的 expectation 都是
{ exitCode: 2, outputContains: '[NOT RUN]' },与 @ogd/desktop test:electron 逐字同形状——[NOT RUN]
写在 stderr,所以判据同时看 stdout 与 stderr;两道门都必须带 captureOutput: true(judge() 的
outputContains 只读 result.stdout/result.stderr,而 runGate() 仅在它为真时才填这两个字段,漏了断言
恒判红)。将来载体就位、门开始真跑时,真实退出码会与期望的 2 不符 ⇒ 判红并要求更新 scripts/ci/verify-all.mjs,
不得静默放行。今天的读数因此是 NOT RUN,不是 PASS:子进程确实跑了、真实退出码是 2、判据也真的在检查
(把任一道门的 expectation.exitCode 临时改成 0,该门必然判红、聚合退出码非 0 —— 这就是"它不是在空转"的
证明),但"浏览器门通过"这句话今天无从谈起。
当前状态(本机实测):@playwright/test 全仓未声明、未安装,两道门均为 exit 2 + [NOT RUN];
两者点名的缺口一致 —— 缺 @playwright/test(载体三件套与 lib/ 产物都已就位)。补上它并
npx playwright install chromium 后即可真跑。两道门都刻意不进默认套件(默认 runner 只选 tests/*.test.ts,
真实 spec 住在 designgraph 的 tests/browser/ 下),也不进 verify:gates。
落盘副作用(读者须知):@ogd/workflow-execution 那道门是这几道浏览器门里唯一在"没跑"结局下也留物证
的一道 —— 每次运行都在仓库内写回执:.taf/verification/browser-gate/last-run.json
(结论、退出码、载体与日志的路径+sha256、复现指引)与 last-run.log(子进程原始输出)。NOT RUN 也写回执,
"为什么没跑"同样可复现、可核对。该路径被 .gitignore:16 忽略,实测跑完 git status --porcelain 仍为空。
@ogd/designgraph test:browser 与 @ogd/desktop test:electron 的 NOT RUN 分支不写任何文件。
(renderer 与 graphview-client 两道在真跑时也会由各自用例落 receipt 到 .taf/sdd/**,同被忽略。)
转绿验收点:换到带 @playwright/test(且 npx playwright install chromium 过)的环境后跑一次本门,
期望=spec 的真实退出码 + 回执 "ran": true;当前环境为 "ran": false + "outcome": "NOT RUN" +
exitCode: 2(ran 与 outcome 同源推导,永远一致)。"真跑"分支在当前环境无动态证据,不得据此宣称
浏览器门已验证;也不许为了让它变绿而新增依赖、或伪造"跑过了"的证据。
它不是构建门,而是"在记录任何门口证据之前先跑一次"的前置检查 —— 刻意不进 build 的 16 步串,
也不进 verify:gates(否则并发在途窗口会把聚合键常态弄红,那不是它要表达的意思)。
pnpm run check:lib-freshness # 或 node scripts/runtime/check-lib-freshness.mjs
node scripts/runtime/check-lib-freshness.mjs --json # 机器可读
- 判据:对每个带
lib/的包,逐文件比较src/<rel>.ts(x)与它自己的产物(lib/<rel>.js|.d.ts,以及 rootDir=包根时的lib/src/<rel>.js|.d.ts,取较新者);源比产物新 ⇒ 判红,并点名"包名 + 文件 + 两边时刻 + 差多少 ms"。 - 退出码:0 同批(没有 src 比产物新)/ 1 有包陈旧或正在重建 / 2 用法或配置错误 (没有可比对象时不静默通过)。
- 分档:
packages/**、apps/**判红;vendor/comfyui/litegraph/**只露脸、永不判红;vendor/deepseek-harness/**不扫。找不到对应产物的源文件(纯类型.d.ts、未产出该文件的源)只作[INFO_NO_ARTIFACT],不判红("没有对应关系"不等于"陈旧")。 - 覆盖盲区(显式露脸,不判红):源码不在
src/下的包(当前@ogd/designgraph的client/common/server/)本守卫判不了,会打[INFO_COVERAGE_GAP]—— 不得把这理解成"同批"。
诚实边界:这是启发式——mtime 不可能证明内容一致。它只能挡"明显陈旧 / 正在重建",
不能证明 src 与 lib 语义一致,也不能替代 --force 重建 + 产物内容核对(sha256)。
它回答的只有一句:"这棵树现在看起来是同一批构建出来的吗?"
看到 FAIL 的正确读法:不是"仓库坏了",而是此刻不要把这些门口结果当证据 —— 等构建追上再跑即可
(实测:@ogd/agent-workflow-tools 属主改动期间 FAIL exit 1,约 30 秒后复跑即 exit 0;同一时刻
verify:gates 仍 exit 0,因为本守卫刻意不在聚合键里)。反之 PASS 只说明"看起来同批",
仍不能替代产物内容核对。
背景(2026-09-16 00:35–00:37 实测,非推测):终点门属主两次在"他人在改 packages/storage/src/**、
且 packages/storage/lib/*.js 于同分钟被重建"的并发重建窗口里撞上 RUNTIME_STARTUP_FAILED storage-lock
(00:37 起自行恢复)。⇒ 纪律:并发重建窗口内的门口结果不可信;取证前必须确认 src 与 lib 同批;
凡记录为证据的测量必须带测量时刻。
工具:node scripts/runtime/append-result.mjs --ledger <json 路径> --key <键> --value-file <json 文件> [--replace] [--dry-run]
(非门禁:不进 build 的 16 步串,也不进 verify:gates)。三条硬约束任何一条不成立就不写并报红:
- 固定规范序列化:
JSON.stringify(value, null, 2)+ LF 行尾;写前先验证「盘上字节 == 规范序列化 (解析后对象)」,不相等说明这次写入会重排既有内容 ⇒ 拒绝写入 (APPEND_RESULT_LAYOUT_NOT_CANONICAL,退出码 3)。这条来自真实事故:一次整体重排把台账从 87717B 改成 88441B,多出的 724B 至今无法归因,而.taf未纳入版本控制 ⇒ 原始布局不可恢复; 此后不做第二次无法解释的改动(宁可不动)。 - 写前快照 + 写后核对:写前记录
{bytes, sha256, keyCount, mtime},写后核对 keyCount、 未触碰键的解析后内容逐字不变、以及字节增量 == 预期增量;任何差异都必须能解释,否则停止并上报 (APPEND_RESULT_UNEXPLAINED,退出码 6),不做第二次写入。 - 无锁 ⇒ 写前紧邻重读 + 乐观校验:写入前紧邻的一次重读若
sha256/keyCount与快照不一致 (期间有人写过),放弃写入(APPEND_RESULT_OPTIMISTIC_ABORT,退出码 5),重新读取后再决定, 绝不盲目覆盖。
其它退出码:0 已写入且全部核对通过 / 2 用法错误 / 4 键已存在(未加 --replace)。
量纲必须先声明:本工具所有 bytes 都是 UTF-8 磁盘字节(Buffer.byteLength / statSync().size),
不是 JS 字符串 .length(那是 UTF-16 码元数,中文场景下二者相差约 2.5 倍)。此前一次「台账从 87717B
缩到 70913B、疑似丢数据」的读数是混用量纲得出的假警报(盘上实际 93210B 且在增长)。凡称「疑似丢数据」,
必须同时给出磁盘字节数与键数。
当前仅Harness基线处于实现验收阶段,GraphView、DesignGraph、Spine与Electron整合尚未完成。