feat(WP7-06): 增加最终AC预冻结门禁
Dada P0-A isolated Windows CI / validate-and-package (push) Canceled after 0s

This commit is contained in:
suyx
2026-08-04 22:43:35 +08:00
parent ffd1643848
commit 08e9c39e49
4 changed files with 248 additions and 1 deletions
+62
View File
@@ -0,0 +1,62 @@
const EXPECTED_TRACE_SUMMARY = Object.freeze({
acceptanceCriteria: 52,
errorCategories: 9,
featureModules: 13,
parentFamilies: 89,
penProductFrames: 18,
productContracts: 19,
requirements: 109,
tasks: 52,
testCases: 117,
uiPages: 22,
});
const REQUIRED_UPSTREAM = Object.freeze({
"TASK-WP7-01": "passed",
"TASK-WP7-02": "passed",
"TASK-WP7-03": "deferred_nonblocking_first_version",
"TASK-WP7-04": "deferred_nonblocking_first_version",
"TASK-WP7-05": "passed",
});
const shaPattern = /^[0-9a-f]{40}$/i;
export function buildWp706PrefreezeReport({ currentCommit, releaseExists, trace, upstream }) {
if (releaseExists) throw new Error("WP7_06_RELEASE_WRITTEN_PREMATURELY");
if (!shaPattern.test(currentCommit ?? "")) throw new Error("WP7_06_CURRENT_COMMIT_INVALID");
if (trace?.status !== "passed" || !Array.isArray(trace?.errors) || trace.errors.length > 0) {
throw new Error("WP7_06_TRACE_VALIDATION_FAILED");
}
for (const [key, expected] of Object.entries(EXPECTED_TRACE_SUMMARY)) {
if (trace.summary?.[key] !== expected) throw new Error(`WP7_06_TRACE_COUNT_MISMATCH:${key}`);
}
for (const [taskId, expectedStatus] of Object.entries(REQUIRED_UPSTREAM)) {
const item = upstream?.[taskId];
if (!item || !shaPattern.test(item.head ?? "")) throw new Error(`WP7_06_UPSTREAM_HEAD_INVALID:${taskId}`);
if (item.merged !== true) throw new Error(`WP7_06_UPSTREAM_NOT_MERGED:${taskId}`);
if (item.status !== expectedStatus) throw new Error(`WP7_06_UNSUPPORTED_STATUS:${taskId}`);
}
return {
schema_version: "1.0",
task_id: "TASK-WP7-06",
status: "passed",
current_commit: currentCommit.toLowerCase(),
release_json_written: false,
trace_summary: { ...EXPECTED_TRACE_SUMMARY },
upstream: Object.fromEntries(Object.entries(REQUIRED_UPSTREAM).map(([taskId]) => [taskId, {
branch: upstream[taskId].branch,
head: upstream[taskId].head.toLowerCase(),
merged: true,
status: upstream[taskId].status,
}])),
deferred_external_tasks: Object.entries(REQUIRED_UPSTREAM)
.filter(([, status]) => status === "deferred_nonblocking_first_version")
.map(([taskId]) => taskId),
final_release_allowed: false,
next_task: "TASK-WP7-07",
};
}
export { EXPECTED_TRACE_SUMMARY, REQUIRED_UPSTREAM };
+108
View File
@@ -0,0 +1,108 @@
import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { validateTddTrace } from "./lib/tdd-trace.mjs";
import { buildWp706PrefreezeReport, REQUIRED_UPSTREAM } from "./lib/wp7-06-prefreeze.mjs";
const runId = process.env.DADA_TDD_RUN_ID ?? `wp7-06-prefreeze-${new Date().toISOString().replace(/[^0-9]/g, "")}`;
const runDirectory = resolve("artifacts", "tdd", runId);
const caseDirectory = resolve(runDirectory, "cases", "TDD-WP7-AC-001-trace-structure");
if (existsSync(runDirectory)) throw new Error(`Evidence run already exists: ${runId}`);
mkdirSync(caseDirectory, { recursive: true });
function writeJson(path, value) {
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
}
function git(args) {
const result = spawnSync("git", args, { encoding: "utf8", timeout: 60_000 });
if ((result.status ?? 1) !== 0) throw new Error(`WP7_06_GIT_COMMAND_FAILED:${args[0]}`);
return result.stdout.trim();
}
function runCommand(name, command) {
const startedAt = new Date().toISOString();
const result = spawnSync(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", command], {
encoding: "utf8",
env: process.env,
maxBuffer: 64 * 1024 * 1024,
});
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
return {
command,
exit_code: result.status ?? 1,
finished_at: new Date().toISOString(),
name,
started_at: startedAt,
};
}
const commands = [
runCommand("prefreeze-unit", "node --test tests/package/wp7-06-prefreeze.test.mjs"),
runCommand("tdd-trace", "pnpm validate:tdd-trace"),
];
if (commands.some((command) => command.exit_code !== 0)) {
writeJson(resolve(caseDirectory, "commands.json"), { commands, run_id: runId, schema_version: "1.0" });
process.exit(1);
}
const trace = validateTddTrace();
const currentCommit = git(["rev-parse", "HEAD"]);
const remoteLines = git(["ls-remote", "--heads", "origin", "codex/wp7-01", "codex/wp7-02", "codex/wp7-03", "codex/wp7-04", "codex/wp7-05"])
.split(/\r?\n/)
.filter(Boolean);
const remoteHeads = Object.fromEntries(remoteLines.map((line) => {
const [head, reference] = line.split(/\s+/);
return [reference.replace("refs/heads/", ""), head];
}));
const upstream = Object.fromEntries(Object.entries(REQUIRED_UPSTREAM).map(([taskId, status]) => {
const branch = taskId.replace("TASK-", "codex/").toLowerCase();
const head = remoteHeads[branch];
const ancestry = spawnSync("git", ["merge-base", "--is-ancestor", head ?? "missing", "HEAD"], { encoding: "utf8", timeout: 30_000 });
return [taskId, { branch, head, merged: ancestry.status === 0, status }];
}));
const report = buildWp706PrefreezeReport({
currentCommit,
releaseExists: existsSync(resolve("RELEASE.json")),
trace,
upstream,
});
const result = {
acceptance_criteria: Array.from({ length: 56 }, (_, index) => index + 1)
.filter((number) => ![8, 26, 37, 54].includes(number))
.map((number) => `AC-${String(number).padStart(2, "0")}`),
automation: ["automated", "manual_review"],
commit: currentCommit,
evidence_refs: ["commands.json", "trace-summary.json", "upstream-lineage.json", "deferred-external.json"],
finished_at: new Date().toISOString(),
manifest: {
path: "tasks.manifest.json",
sha256: createHash("sha256").update(readFileSync("tasks.manifest.json")).digest("hex").toUpperCase(),
},
missing_evidence: [],
release_gate: ["release:P0-A"],
requirements: ["NFR-05"],
run_id: runId,
schema_version: "1.0",
status: report.status,
task_id: "TASK-WP7-06",
test_id: "TDD-WP7-AC-001-trace-structure",
work_package: "WP-7",
};
writeJson(resolve(caseDirectory, "commands.json"), { commands, run_id: runId, schema_version: "1.0" });
writeJson(resolve(caseDirectory, "trace-summary.json"), { status: trace.status, summary: trace.summary, schema_version: "1.0" });
writeJson(resolve(caseDirectory, "upstream-lineage.json"), { current_commit: currentCommit, tasks: report.upstream, schema_version: "1.0" });
writeJson(resolve(caseDirectory, "deferred-external.json"), {
policy: "product_owner_first_version_nonblocking",
tasks: report.deferred_external_tasks,
treated_as_real_provider_pass: false,
schema_version: "1.0",
});
writeJson(resolve(caseDirectory, "result.json"), result);
writeJson(resolve(runDirectory, "evidence.json"), { cases: [{ missing_evidence: [], status: result.status, test_id: result.test_id }], run_id: runId, status: result.status, schema_version: "1.0" });
console.log(JSON.stringify({ deferred_external_tasks: report.deferred_external_tasks, next_task: report.next_task, run_id: runId, status: report.status }, null, 2));