fix: 修复迁移预检状态误判(09-06-release-gate-integrity)
这个提交包含在:
@@ -2441,6 +2441,12 @@ run_logged_deploy_stage "build-init" "${compose_cmd[@]}" build init
|
||||
stop before Compose config and Prisma status.
|
||||
- Status output missing/unreadable, failed/rolled-back/diverged history or an
|
||||
unknown pending migration -> `PAUSED`; do not reset or resolve in place.
|
||||
- Status predicates use one `status_matches` helper with direct here-string
|
||||
input, not `printf | grep -q` under pipefail. An early-exiting consumer can
|
||||
yield producer=141/consumer=0 and falsely skip unknown-pending rejection.
|
||||
Matcher exit 0 means match, 1 means no match, any other status immediately
|
||||
fails with `PROD_MIGRATION_STATUS_UNREADABLE`. Preserve candidate/history
|
||||
reconciliation; do not infer safety from the absence of parsed pending IDs.
|
||||
- Non-zero remote exit with a safe `code=...`, with or without a Prisma marker
|
||||
-> propagate that code and stop. Non-zero exit without a parseable code ->
|
||||
`PRODUCTION_PREFLIGHT_REMOTE_FAILED`; never continue to marker validation.
|
||||
@@ -2484,6 +2490,11 @@ run_logged_deploy_stage "build-init" "${compose_cmd[@]}" build init
|
||||
- `bash -n deploy/trialrun/production-preflight-remote.sh`.
|
||||
- `node deploy/trialrun/validate-env-contract.js`.
|
||||
- `cd server && npm test -- tests/unit/scripts/deployment-safety-contract.test.js`.
|
||||
- Inject the observed producer 141 into the actual status parsing/rejection
|
||||
source; restored old-predicate mutation must demonstrate lost rejection.
|
||||
Cover safe/allowlisted/unknown/unsafe/unreadable states, 96 KiB trailing output
|
||||
and matcher failure. The multi-subprocess remote fixture has a bounded 15 s
|
||||
test budget (not a production timeout change); assertions are unchanged.
|
||||
- Static ordering must prove production preflight precedes `deploy.sh`, and the
|
||||
remote status marker precedes migration-history reconciliation. Fixtures must
|
||||
reject loopback/staging target identities without invoking SSH.
|
||||
|
||||
@@ -258,3 +258,10 @@ local-candidate 原验证器对其选定集合的严格行为保持;共享解
|
||||
Wrong:caller 声明 online=true → 未核验的包装启动浏览器 → 无 bootstrap 证据也汇总 207 为待修清单。
|
||||
|
||||
Correct:当前候选策略定义环境 → 受管 runner 创建并实测 → 真实 bootstrap 在线才启动业务集合 → 环境、覆盖与来源由验证器共同判定 → 错误环境回执留存但撤销决策资格。
|
||||
|
||||
|
||||
## 迁移状态谓词修复增量
|
||||
|
||||
按 implement.md 的追加阶段,用 status_matches 集中四处状态判断;grep 直接读取 here-string,不使用 printf 生产者或提前退出模式。退出 0/1 分别代表匹配/不匹配,其余退出码立即停止。保留日志、SQL 查询、候选 allowlist、迁移和回滚判定顺序。
|
||||
|
||||
回归直接提取现有源码状态解析与未知迁移拒绝分支;注入真实观察到的 producer=141,并恢复旧谓词做负向变异。长输出与四处 matcher 工具错误单列;原多子进程远端 fixture 使用有界 15 秒测试预算,非生产超时修改。产品差异单独保存在 preflight-fix 工作树;与 seed 的组合验证只用一次性本地测试副本,不能称为已集成或新发布候选。
|
||||
|
||||
@@ -413,3 +413,59 @@ cd server && npm test -- tests/unit/scripts/deployment-db-lifecycle.test.js test
|
||||
- 实验实际 argv 已显式带 trace,并成功产生真实目标 trace;实验后正式诊断入口的参数传递由上述捕获回归验证。没有声称正式诊断新源码完成过额外整套运行。新策略与实验冻结源码的两处差异单列,后续运行必须重新绑定届时获准候选。
|
||||
- 普通门禁 retain-on-failure 值得独立审阅,但用户本轮只明确要求诊断 trace;不将“考虑”扩成修改普通门禁策略的授权。任何真实发布或 trace 对外分享仍各自授权。
|
||||
- 本轮交审包:artifacts/qa-flow/release-gate-integrity/trace-policy-review-20260907-01/。review.diff 为当前完整 21 文件;incremental.diff 相对获准 diagnostic-review-20260906-01 冻结快照仅变 8 文件,13 文件未变,无增减文件。旧快照在独立临时目录由 HEAD 与旧 diff 重建并逐项核对旧 SHA-256,不向真实工作树应用补丁。review-files.json 保存当前摘要、范围及两份 diff 摘要;未跟踪文件另做 whitespace 核对。实验原始摘要及中断回执不覆盖;快速 task/governance/sensitive/syntax/whitespace 复验放在新交审目录,35 项测试沿用未再改动源码对应的原日志。本轮只有主会话验证,不冒充独立复核。
|
||||
|
||||
|
||||
## 追加阶段:迁移状态误判修复(归属确认,尚未实施)
|
||||
|
||||
用户同意将较小修复归入已有合适 task,不为本 finding 新建任务。此阶段承接 seed 分离回归发现的预检问题,原冻结交审包、33 文件门禁诊断包及历史验收保持原状;不将新增阶段倒填为旧包已通过。
|
||||
|
||||
事实证据入口:09-09-existing-deploy-seed-separation/implement.md 的继续核查记录。原 printf | grep -q 管道在 pipefail 下实际出现 producer=141/consumer=0;合成分支复现未知迁移被 ACCEPTED,直接输入候选正确拒绝。2000 次候选匹配无误判仅为本地实验,不表示生产修复。
|
||||
|
||||
最小拟修范围:production-preflight-remote.sh 状态解析区四处谓词、原部署安全回归和部署规约。消除提前退出消费者的生产者管道;匹配工具非 0/1 错误失败关闭。验证合法/未知待执行迁移、无待执行、失败状态、长输出和工具错误;保持候选与已应用集合核对及 allowlist,不全库改 grep、不扩成完整门禁重建。
|
||||
|
||||
- [x] 复用本 task 承接,关联已复现证据,撤回必须新建 task 的建议。
|
||||
- [ ] 在本 task 收敛并审阅具体修复增量;新增风险/范围按实际授权确认,不为相同已批准动作反复索权。
|
||||
- [ ] 与 seed 产品差异物理隔离,实施最小修复并做确定性回归。
|
||||
- [ ] 记录最新候选/回归结果与独立复核缺口,不继承旧冻结回执。
|
||||
|
||||
本轮仅更新归属与方案记录,未修改该预检脚本或测试;未新建 task、未提交/推送/部署。seed 的发布前置仍未解除。
|
||||
|
||||
### 后续实施授权与最小方案
|
||||
|
||||
用户在确认归入本 task 后要求“继续推进主要工作”,本次按上述已明确的小范围方案继续本地修复,不再新建 task 或重复索取相同范围授权。执行副本为 artifacts/isolated-worktrees/20260909-preflight-fix,detached b918530;原 seed 产品工作树不改,主工作树只同步任务状态。提交/推送/部署仍未授权。
|
||||
|
||||
实现同一 status_matches 谓词:grep 直接读取 here-string,取消 printf 生产者管道;0 为匹配、1 为不匹配、其他码立即 PROD_MIGRATION_STATUS_UNREADABLE。四处谓词共用,候选迁移集合及 DB 对照不变。定向测试提取实际状态解析与未知迁移拒绝分支,注入已观测的 producer=141;恢复旧写法的源码变异必须失去拒绝能力。另验长输出、合法 pending、unsafe 和工具错误,最后跑原完整部署安全回归与 seed 组合验证。独立 reviewer 受 inline 限制时明确留待交审。
|
||||
|
||||
|
||||
### 当前结果:迁移状态修复本地闭合
|
||||
|
||||
- 实际预检改动仅四处谓词及共享 status_matches:直接输入、完整读取,0/1/其他码分别匹配/不匹配/停止;保留 SQL、候选 allowlist 和应用发布顺序。旧谓词变异在 producer=141 注入下仍会误放行,新逻辑明确拒绝,未删改原未知迁移断言。
|
||||
- 最新预检测试 39 项通过,包括四处匹配器错误、96 KiB 输出和源码变异。首次单独运行的旧多子进程 fixture 超过默认 5 秒;仅将该用例预算设为有界 15 秒,未改变生产超时或任何断言。随后完整场景通过,不用重试屏蔽失败。
|
||||
- 从 b918530 创建一次性验证副本,只覆盖 seed 的 9 个代码/测试文件及本修复 2 个文件,共 11 个;联合执行 deploy-init、deployment-db-lifecycle、deployment-safety-contract 三文件,109/109 PASS,81.10 秒。覆盖集合未缩减,没有 skip 或重试。覆盖输入 SHA-256:087d5004337ba631d152bd099c6f19bfad34f16cd00de29fc00e14e891b8dfac。
|
||||
- 该临时组合仅用于测试,未合入主工作树或任一原执行副本,结束后精确删除验证 worktree。第一次准备因整包补丁参数过长停止于测试前,改为逐文件 apply_patch;第一次与最终临时副本均已清理,不把准备失败计为测试结果。
|
||||
- 沿用 seed 已完成的真实合成 PG 数据库测试(该运行时代码未改);本轮不再次操作数据库或 ARM。组合测试不等于镜像构建、完整候选门禁或真人业务验收。
|
||||
- Bash syntax、环境契约、task validate、Trellis governance、process contract、git diff --check 通过;docs registry errors=0、warnings=1,22 个未注册文档未扩范围处理。
|
||||
- 主会话完成只读差异复核;inline 下未派 reviewer,独立复核待交审。未提交、未推送、未部署。产品增量位于 artifacts/isolated-worktrees/20260909-preflight-fix;seed 产品增量保留在原独立副本。主工作树只同步本 task 状态和证据。
|
||||
|
||||
本追加修复阶段本地验收通过;原门禁 task 整体、旧冻结包及完整候选发布资格不因此完成或改变。上一阶段 seed 的那一次 93/94 失败保留,当前由确定性复现、根因修复和同基线组合回归闭合。
|
||||
### 分包交审(2026-09-09)
|
||||
|
||||
seed 交审前补正 Compose init 参数数组并新增执行测试后,再次临时组合验证:deploy-init、deployment-db-lifecycle、deployment-safety-contract 三文件 110/110 通过(22 + 49 + 39)。共同基线 b918530,11 个输入文件摘要 a3f91e3d1fcdbc522347725659e3b8b5a0438df62a7eea52b03f05fb3ba6d70a;临时 worktree 已删除。本预检运行时没有追加变化,旧 109/109 证据只对应旧组合。
|
||||
|
||||
交审包:artifacts/qa-flow/20260909-seed-preflight-review-v1/preflight/。只冻结本追加阶段的六文件增量,不覆盖原 33 文件证据包。与 seed 包共享部署 spec 文件,后续集成须合并各自段落,不可互相覆盖。
|
||||
|
||||
当前追加阶段:本地实施、主会话复核和回归完成,待独立复核与提交授权;未提交、推送、部署。原门禁任务整体仍未完成,不因此取得完整候选发布资格。
|
||||
## 2026-09-09 独立复核接受与集成准备
|
||||
|
||||
用户回传独立审查:seed 通过(3 条非阻断备注),preflight 通过(无 finding)。审查者核验冻结 diff 并阅读合同与代码,另做本地 bash SIGPIPE 合成复现;未重跑项目测试、未审完整历史叙述、未构建镜像或访问生产/ARM,未验证后续集成。不得把该结论扩大为独立执行了 110 项测试或批准部署。
|
||||
|
||||
集成副本:artifacts/isolated-worktrees/20260909-seed-preflight-integration,基线 b918530,归当前门禁 task 协调整合;两个原 task 归属不变。按共同祖先合并部署 spec,零冲突,不用整文件覆盖。原两份冻结包和执行副本保留未改,主工作树不混入产品实现。
|
||||
|
||||
备注处理:
|
||||
- F1:集成副本的既有运维手册明确补充,有意停用全部费用类型、审批规则或报销流程也会挡住下次发布;不得自动补齐或改用 initialize 绕过。
|
||||
- F2:保留并强化“迁移先执行,配置检查失败时迁移可能已经落库”,不简化成零写入。
|
||||
- F3:deploy.sh 的 sh -lc 与 Compose 的 sh -c 环境加载不同,记录为待环境验收关注项;本轮不改 shell 行为。
|
||||
- Compose folded scalar 是基线已有问题,本 task 因承诺两入口真实到达而一并修复,不是本次引入。
|
||||
- 预检缺陷定性是“该拦时可能拦不住”,未证明生产曾误放行。独立审查在其环境较大输出触发 141,但约 64KB 不是通用安全阈值;此前已有短输出捕获 producer=141/consumer=0 的记录,调度和管道实现都会影响触发,不采纳“日常绝不会触发”的推断。
|
||||
|
||||
两笔提交准备顺序:先 preflight(门禁 task 三文档、预检脚本、测试及 spec 对应增量),再 seed(原 17 文件范围,包含 F1/F2 手册补充和 seed task 最新状态;spec 只取 seed 增量)。共享 spec 第二笔以第一笔结果为基线。提交、推送、完整候选门禁与部署均未因此授权;当前只做隔离集成和定向验证。
|
||||
|
||||
@@ -137,3 +137,12 @@ full-local 包含本地数据库准备、服务端回归和全量浏览器,耗
|
||||
最新交审状态:§9 实施及一次完整隔离诊断完成,固定 PARTIAL/exit 2;65 文件/275 用例有对应结果:50 passed、76 failed、131 timedOut、18 skipped,0 重试、0 缺失回执用例。207 项失败不是 207 个已确认产品缺陷;合同/R102 本次先卡登录前置,不能证明下游原断言已执行。18 个 skipped 中 14 个 expectedStatus=skipped、4 个 expectedStatus=passed,不能把条件跳过删出集合或开启 live 环境凑绿。普通 full-local 全绿成本仍未取得,诊断约 197 分 24 秒不能替代它。执行器两项自有问题已在原始运行结束后最小修正,34 项 QA 与原身份/金额 36 项定向单测通过;聚合器净库定向验证保持内部 fail-fast,场景 8 后面的分母仍未补齐。经理盘点 7 条 active 在职记录中 4 条缺直接经理,实际规则和 smoke/真人未消歧,不宣称六路全部锁死。全 task AC 尚未完成;R1、其他阶段 A、阶段 B、提交/push/归档/部署仍未授权,本增量待用户独立复核。
|
||||
|
||||
后续状态(2026-09-07):上述 21 文件已获用户独立复核通过。追加只读结果与两次带 trace 的条件对照见 implement.md §13:三张 rejected 的 CEO owner 存在 finance 显式兜底,不能因缺经理否定本人重提;至少五条浏览器登录用例通过。当前构建下代表性合同登录在单条 1 worker 与全套 4 workers 中均失败,不能据此宣称“只有并发才失败”或排除一切竞态。全套在后续 263 条结果后以 143 中断,完整终态未取得,孤立残留已补清理;不重跑、不补造通过。新获批诊断 trace=on 已最小实施,35 项 QA 通过,待本增量独立复核;普通门禁 trace 配置未改。207 仍仅原始失败观测,根因分类/去重后的可执行红项清单尚未取得;R1 与场景 8 等其他边界继续挂起。
|
||||
|
||||
|
||||
## 追加验收:迁移状态误判最小修复
|
||||
|
||||
用户确认复用本 task 并要求继续主线,授权上述本地修复与验证;不改旧冻结验收、不部署。范围仅迁移状态四处谓词及其定向回归/规约。
|
||||
|
||||
- [x] 消除 producer=141 导致的匹配假阴性;工具错误失败关闭,候选/已应用迁移核对保持。
|
||||
- [x] 确定性回归覆盖未知/合法待执行、无待执行、unsafe、长输出、四处工具错误及旧谓词源码变异。
|
||||
- [x] 与 seed 改动的临时组合回归通过,记录本地边界、独立复核缺口及未部署状态。
|
||||
|
||||
@@ -190,20 +190,30 @@ printf '[production-preflight][PRISMA_STATUS_BEGIN]\n'
|
||||
printf '%s\n' "$prisma_status_output"
|
||||
printf '[production-preflight][PRISMA_STATUS_END] exit=%s\n' "$prisma_status_code"
|
||||
|
||||
if printf '%s' "$prisma_status_output" | grep -Eiq 'failed migration|migration(s)? have failed|migration failed|rolled back|diverged|drift|could not find migration|database schema is not in sync'; then
|
||||
status_matches() {
|
||||
local status
|
||||
if grep -Ei -- "$1" <<<"$prisma_status_output" >/dev/null; then
|
||||
return 0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
[[ "$status" -eq 1 ]] || fail "PROD_MIGRATION_STATUS_UNREADABLE"
|
||||
return 1
|
||||
}
|
||||
|
||||
if status_matches 'failed migration|migration(s)? have failed|migration failed|rolled back|diverged|drift|could not find migration|database schema is not in sync'; then
|
||||
fail "PROD_MIGRATION_STATUS_UNSAFE"
|
||||
fi
|
||||
if ! printf '%s' "$prisma_status_output" \
|
||||
| grep -Eiq 'Database schema is up to date|have not yet been applied|No pending migrations'; then
|
||||
if ! status_matches 'Database schema is up to date|have not yet been applied|No pending migrations'; then
|
||||
fail "PROD_MIGRATION_STATUS_UNREADABLE"
|
||||
fi
|
||||
if [[ "$prisma_status_code" -ne 0 ]] \
|
||||
&& ! printf '%s' "$prisma_status_output" | grep -Eiq 'have not yet been applied'; then
|
||||
&& ! status_matches 'have not yet been applied'; then
|
||||
fail "PROD_MIGRATION_STATUS_UNREADABLE"
|
||||
fi
|
||||
|
||||
declare -a status_pending_ids=()
|
||||
if printf '%s' "$prisma_status_output" | grep -Eiq 'have not yet been applied'; then
|
||||
if status_matches 'have not yet been applied'; then
|
||||
status_pending_text="$(printf '%s\n' "$prisma_status_output" | awk '
|
||||
tolower($0) ~ /have not yet been applied/ { capture=1; next }
|
||||
capture { print }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { execFile, execFileSync } = require("node:child_process");
|
||||
const { execFile, execFileSync, spawnSync } = require("node:child_process");
|
||||
const { createHash } = require("node:crypto");
|
||||
const { chmod, mkdtemp, mkdir, rm, symlink, writeFile, readFile } = require("node:fs/promises");
|
||||
const os = require("node:os");
|
||||
@@ -17,6 +17,95 @@ const PRODUCTION_PREFLIGHT_REMOTE = path.join(ROOT_DIR, "deploy/trialrun/product
|
||||
const PRODUCTION_IDENTITY_BOOTSTRAP = path.join(ROOT_DIR, "deploy/trialrun/production-identity-bootstrap.mjs");
|
||||
const PRODUCTION_IDENTITY_BOOTSTRAP_REMOTE = path.join(ROOT_DIR, "deploy/trialrun/production-identity-bootstrap-remote.sh");
|
||||
|
||||
describe("migration status predicate fail-closed regression", () => {
|
||||
const known = "20260807193000_contract_registry_termination_workspace";
|
||||
const unknown = "20260807193002_unknown_migration";
|
||||
|
||||
function runStatus(text, { statusCode = 0, grepFailure = false, oldPredicate = false } = {}) {
|
||||
const source = require("node:fs").readFileSync(PRODUCTION_PREFLIGHT_REMOTE, "utf8");
|
||||
const start = source.indexOf("\nstatus_matches() {");
|
||||
const end = source.indexOf("\nmigration_rows=", start);
|
||||
expect(start).toBeGreaterThan(0);
|
||||
expect(end).toBeGreaterThan(start);
|
||||
let parsing = source.slice(start, end);
|
||||
if (oldPredicate) {
|
||||
const target = "if status_matches 'have not yet been applied'; then";
|
||||
expect(parsing.split(target)).toHaveLength(2);
|
||||
parsing = parsing.replace(target, "if printf '%s' \"$prisma_status_output\" | grep -Eiq 'have not yet been applied'; then");
|
||||
}
|
||||
const guardStart = source.indexOf('if [[ "${#status_pending_ids[@]}" -gt 0 ]]; then', end);
|
||||
const guardEnd = source.indexOf("\npending_ids=()", guardStart);
|
||||
expect(guardStart).toBeGreaterThan(end);
|
||||
expect(guardEnd).toBeGreaterThan(guardStart);
|
||||
const script = [
|
||||
"set -euo pipefail",
|
||||
'fail() { echo "$1"; exit 1; }',
|
||||
'prisma_status_output="$(cat)"',
|
||||
"prisma_status_code=" + statusCode,
|
||||
"declare -A candidate_set=([" + known + "]=1)",
|
||||
'printf() { builtin printf "$@"; if [[ "$1" == "%s" && "${2:-}" == "$prisma_status_output" ]]; then return 141; fi; }',
|
||||
"matcher_calls=0",
|
||||
grepFailure ? 'grep() { matcher_calls=$((matcher_calls + 1)); if [[ "$matcher_calls" -eq ' + grepFailure + ' ]]; then return 2; fi; command grep "$@"; }' : "",
|
||||
parsing,
|
||||
source.slice(guardStart, guardEnd),
|
||||
"echo ACCEPTED",
|
||||
].join("\n");
|
||||
return spawnSync("bash", ["-c", script], {
|
||||
input: text, encoding: "utf8", timeout: 10000, maxBuffer: 1024 * 1024,
|
||||
env: { PATH: process.env.PATH },
|
||||
});
|
||||
}
|
||||
|
||||
it.each(["Database schema is up to date!", "No pending migrations", "DATABASE SCHEMA IS UP TO DATE!"])("accepts recognized safe status: %s", (text) => {
|
||||
const result = runStatus(text);
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("ACCEPTED");
|
||||
});
|
||||
|
||||
it("accepts an allowlisted pending migration with Prisma exit one", () => {
|
||||
const result = runStatus("Following migration have not yet been applied:\n" + known, { statusCode: 1 });
|
||||
expect(result.status).toBe(0);
|
||||
});
|
||||
|
||||
it.each([0, 96 * 1024])("rejects unknown pending despite producer 141 with padding %s", (padding) => {
|
||||
const text = "Following migration have not yet been applied:\n" + unknown + "\n" + "x".repeat(padding);
|
||||
const result = runStatus(text, { statusCode: 1 });
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toContain("PROD_MIGRATION_STATUS_UNKNOWN_PENDING");
|
||||
expect(result.stdout).not.toContain("ACCEPTED");
|
||||
});
|
||||
|
||||
it("demonstrates the old predicate mutation loses unknown-pending rejection", () => {
|
||||
const text = "Following migration have not yet been applied:\n" + unknown;
|
||||
const result = runStatus(text, { statusCode: 1, oldPredicate: true });
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("ACCEPTED");
|
||||
expect(runStatus(text, { statusCode: 1 }).stdout).toContain("PROD_MIGRATION_STATUS_UNKNOWN_PENDING");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["Database schema is up to date!\nmigration failed", "PROD_MIGRATION_STATUS_UNSAFE"],
|
||||
["unrecognized status", "PROD_MIGRATION_STATUS_UNREADABLE"],
|
||||
["Following migration have not yet been applied:", "PROD_MIGRATION_STATUS_PENDING_IDS_UNREADABLE"],
|
||||
])("fails closed on %s", (text, code) => {
|
||||
const result = runStatus(text);
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toContain(code);
|
||||
});
|
||||
|
||||
it.each([1, 2, 3, 4])("fails closed on matcher error at predicate %s", (grepFailure) => {
|
||||
const result = runStatus("Following migration have not yet been applied:\n" + unknown, { grepFailure, statusCode: 1 });
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toContain("PROD_MIGRATION_STATUS_UNREADABLE");
|
||||
});
|
||||
|
||||
it("rejects a nonzero status without pending migrations", () => {
|
||||
const result = runStatus("Database schema is up to date!", { statusCode: 1 });
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toContain("PROD_MIGRATION_STATUS_UNREADABLE");
|
||||
});
|
||||
});
|
||||
|
||||
describe("deployment safety contract", () => {
|
||||
let fixtureRoot;
|
||||
let remoteDir;
|
||||
@@ -724,7 +813,7 @@ describe("deployment safety contract", () => {
|
||||
await expect(runRemote("happy", "", { VITE_BUILD_SHA: "b".repeat(12) })).rejects.toMatchObject({
|
||||
stderr: expect.stringContaining("PROD_TARGET_BUILD_SHA_MISMATCH"),
|
||||
});
|
||||
});
|
||||
}, 15000);
|
||||
|
||||
it("aggregates preflight checks and keeps the root helper self-contained", async () => {
|
||||
const aggregate = await require("node:fs/promises").readFile(AGGREGATE_PREFLIGHT, "utf8");
|
||||
|
||||
在新工单中引用
屏蔽一个用户