Skip to content

Repository files navigation

OpenGameDesign

正在构建基于 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 里的工作区边; ② tsconfig references 的构建期边(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: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 同源推导,永远一致)。"真跑"分支在当前环境无动态证据,不得据此宣称 浏览器门已验证;也不许为了让它变绿而新增依赖、或伪造"跑过了"的证据。

取证前置检查:src 与 lib 是否同批(check:lib-freshness)

它不是构建门,而是"在记录任何门口证据之前先跑一次"的前置检查 —— 刻意不进 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 同批; 凡记录为证据的测量必须带测量时刻。

共享台账写入规程(results.json 多人共写、没有锁)

工具:node scripts/runtime/append-result.mjs --ledger <json 路径> --key <键> --value-file <json 文件> [--replace] [--dry-run] (非门禁:不进 build 的 16 步串,也不进 verify:gates)。三条硬约束任何一条不成立就不写并报红:

  1. 固定规范序列化:JSON.stringify(value, null, 2) + LF 行尾;写前先验证「盘上字节 == 规范序列化 (解析后对象)」,不相等说明这次写入会重排既有内容 ⇒ 拒绝写入 (APPEND_RESULT_LAYOUT_NOT_CANONICAL,退出码 3)。这条来自真实事故:一次整体重排把台账从 87717B 改成 88441B,多出的 724B 至今无法归因,而 .taf 未纳入版本控制 ⇒ 原始布局不可恢复; 此后不做第二次无法解释的改动(宁可不动)。
  2. 写前快照 + 写后核对:写前记录 {bytes, sha256, keyCount, mtime},写后核对 keyCount、 未触碰键的解析后内容逐字不变、以及字节增量 == 预期增量;任何差异都必须能解释,否则停止并上报 (APPEND_RESULT_UNEXPLAINED,退出码 6),不做第二次写入。
  3. 无锁 ⇒ 写前紧邻重读 + 乐观校验:写入前紧邻的一次重读若 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整合尚未完成。

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages