文件
hyb-oa/.trellis/tasks/archive/2026-08/08-09-deployment-contract-single-source-harness/design.md
T

5.3 KiB

部署合同单一事实源与失败路径覆盖:设计

1. 根因与设计目标

历史缺陷不是 Compose 本身无法校验,而是调用链在不同位置重新定义“候选构建身份”:

deploy.sh
  -> provenance.commit + provenance.shortCommit
  -> BUILD_SHA / VITE_BUILD_SHA

production-preflight.mjs
  -> CANDIDATE_COMMIT
  -> 自行 slice(0, 12)
  -> BUILD_SHA / VITE_BUILD_SHA

两条路径的键名和派生规则相同,却没有共享可执行来源。新增 Compose 必填值或调整短标识规则时,其中一条可能漏改。设计目标是把该合同收敛到已有 provenance 模块,同时保留远端消费校验,并让 synthetic harness 执行真实 orchestration/remote helper 进程。

2. 单一合同形态

在 check-source-provenance.mjs 导出:

export function deriveCandidateBuildEnv(commit) {
  // validate 40..64 lowercase hex
  return Object.freeze({
    BUILD_SHA: commit,
    VITE_BUILD_SHA: commit.slice(0, 12),
  });
}

具体实现可在不改变合同的前提下调整错误类或冻结方式,但调用方不得自己派生 VITE_BUILD_SHA。

collectSourceProvenance 先调用该函数,再以同一结果形成:

commit = buildEnv.BUILD_SHA
shortCommit = buildEnv.VITE_BUILD_SHA  # 兼容字段
buildEnv = { BUILD_SHA, VITE_BUILD_SHA }

共享 API 只导出一次返回完整对象的派生函数,不另行导出可能与对象漂移的键数组,也不提供 getBuildSha() / getViteBuildSha() 之类可分别调用的接口。两个调用方必须通过 Object.entries(buildEnv) 或等价的完整对象遍历生成整组远端环境赋值;单独选择某个键注入属于合同失败。

非法输入在共享函数处失败。provenance 自身使用既有 SourceProvenanceError;production preflight 将该失败稳定映射为既有 CANDIDATE_COMMIT_INVALID,不把内部错误名暴露为新的操作合同。

3. 调用链

3.1 正式部署

check-source-provenance CLI
  -> buildEnv
  -> deploy.sh 读取 BUILD_SHA / VITE_BUILD_SHA
  -> 远端 shell env
  -> docker compose config/build/runtime

deploy.sh 可继续保留 SOURCE_COMMIT / SOURCE_SHORT_COMMIT 局部名称用于日志和公网检查,但值必须来自 buildEnv,不能从 commit 再截取。

3.2 生产 preflight

CANDIDATE_COMMIT
  -> deriveCandidateBuildEnv
  -> remote command env assignments
  -> production-preflight-remote.sh
  -> 格式 + 前缀 + compose config 消费校验

远端 helper 的校验不是第二个来源:它不派生值,只验证接收到的值符合共享合同。这一层用于防止命令组装、环境传输或未来调用方漂移。

4. Harness 设计

继续使用 deployment-safety-contract.test.js 的临时目录:

  • 假 ssh 捕获命令、按 mode 输出 marker/error 并返回指定状态;
  • 假 docker 只识别明确 allowlist 的 compose/ps/inspect/exec 调用;
  • 所有数据库、origin、migration 和 SHA 均为 synthetic;
  • 测试进程不得解析真实 SSH config、调用真实 Docker 或读取仓库外 secret。

4.1 Orchestrator 失败矩阵

SSH 退出 Prisma marker stable code 预期
非零 缺失 有 传播 stable code;summary/report 先落地
非零 任意 无 PRODUCTION_PREFLIGHT_REMOTE_FAILED
零 缺失 无 PRISMA_STATUS_OUTPUT_MISSING
零 存在 无 进入现有 status/reconciliation 成功路径

纯函数四组合可保留,但四个象限还必须全部通过真实 node production-preflight.mjs + fake SSH 子进程验证,防止 main 中的证据顺序或 exit handling 漂移。测试必须对真实缺陷敏感:将远端状态判断与 marker 判断次序调回错误实现时至少一个用例变红;去掉共享对象中的任一远端赋值时,成功/缺失合同用例变红。

4.2 Remote helper 失败矩阵

条件 预期码 必须早于
BUILD_SHA 缺失 BUILD_SHA_MISSING Compose / Prisma
完整 SHA 非法 PROD_TARGET_BUILD_SHA_INVALID Compose / Prisma
12 位 SHA 非法 PROD_TARGET_VITE_BUILD_SHA_INVALID Compose / Prisma
前缀不一致 PROD_TARGET_BUILD_SHA_MISMATCH Compose / Prisma
Compose config 非零 PROD_TARGET_COMPOSE_CONFIG_INVALID runtime/Prisma

假 Docker 对意外调用返回固定失败码,使测试能证明早停,而不是仅匹配最终 stderr。

5. 兼容性与停止条件

  • provenance JSON 保留既有顶层字段;新增 buildEnv 是向后兼容扩展。
  • production preflight 的外部 env、报告文件、稳定错误码和远端脚本输入语义不变。
  • 若实现需要让 preflight import 会触发 provenance main()、执行 Git/SSH,必须停止并修正模块边界;导入必须无副作用。
  • 若共享 helper 迫使 production preflight 接受 unpublished HEAD、减少远端校验或把 secret 写入报告,停止并回到规划。
  • 本任务不在生产验证。上线收益通过本地合同和 reviewer 证明,下一次获批部署自然消费该改动。

6. Rollback

本任务只改本地 tooling。回滚为恢复三个调用点及测试到前一提交;不涉及 schema、生产配置或数据。若共享合同在本地门禁中暴露兼容问题,提交前直接修正,不以生产试跑验证。