Compare commits

...
Author SHA1 Message Date
suyx 55646ba1b4 feat: build sanitized WP7-05 coverage evidence
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 1m49s
2026-08-04 17:08:44 +08:00
suyx 8abf1397a6 test: require WP7-05 state evidence
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 3m17s
2026-08-04 17:02:52 +08:00
suyx e0e101ef28 fix: align WP7-05 candidate record schema
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 2m30s
2026-08-04 16:59:31 +08:00
suyx 33f87f8db3 test: scaffold WP7-05 candidate UI gate
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 2m18s
2026-08-04 16:58:15 +08:00
suyx 5e2d4e7aaf fix: stabilize TASK-WP5-03 CI toolchain timeout
Dada P0-A isolated Windows CI / validate-and-package (push) Successful in 9m56s
2026-08-04 00:29:02 +08:00
7 changed files with 221 additions and 2 deletions
+3 -1
View File
@@ -92,7 +92,9 @@
"test:wp5-02:red": "node scripts/run-wp5-02-validation.mjs --phase red",
"preview:wp5-02": "node scripts/run-wp5-02-manual-preview.mjs",
"test:wp5-03": "node scripts/run-wp5-03-validation.mjs",
"test:wp5-03:red": "node scripts/run-wp5-03-validation.mjs --phase red"
"test:wp5-03:red": "node scripts/run-wp5-03-validation.mjs --phase red",
"test:wp7-05": "node scripts/run-wp7-05-validation.mjs",
"test:wp7-05:unit": "node --test tests/package/wp7-05-ui-gate.test.mjs tests/package/wp7-05-coverage.test.mjs"
},
"devDependencies": {
"@playwright/test": "1.62.0",
+45
View File
@@ -0,0 +1,45 @@
import path from 'node:path';
import { REQUIRED_COVERAGE_UNITS } from './wp7-05-ui-gate.mjs';
const ABSOLUTE_PATH = /^(?:[A-Za-z]:[\\/]|[\\/]{2}|\\\\)/;
function assertSafeRelative(value, field) {
if (typeof value !== 'string' || !value || ABSOLUTE_PATH.test(value) || path.isAbsolute(value)) {
throw new Error(`WP7_05_UNSAFE_${field}`);
}
const normalized = value.replaceAll('\\', '/');
if (normalized.split('/').includes('..')) throw new Error(`WP7_05_UNSAFE_${field}`);
return normalized;
}
export function buildCoverageEvidence({ runId, candidateSha256, coverageUnits, viewports }) {
if (!runId || !/^[A-Za-z0-9._-]+$/.test(runId)) throw new Error('WP7_05_INVALID_RUN_ID');
if (!/^[A-Fa-f0-9]{64}$/.test(candidateSha256 ?? '')) throw new Error('WP7_05_INVALID_CANDIDATE_HASH');
if (!Array.isArray(coverageUnits)) throw new Error('WP7_05_COVERAGE_UNITS_REQUIRED');
const byPage = new Map();
for (const unit of coverageUnits) {
if (!REQUIRED_COVERAGE_UNITS.includes(unit.page_id)) throw new Error('WP7_05_UNKNOWN_PAGE');
if (byPage.has(unit.page_id)) throw new Error('WP7_05_DUPLICATE_PAGE');
if (!Array.isArray(unit.states) || unit.states.length === 0) throw new Error('WP7_05_STATES_REQUIRED');
const states = unit.states.map((state) => ({
state: assertSafeRelative(state.state, 'STATE'),
screenshot_100pct: assertSafeRelative(state.screenshot_100pct, 'SCREENSHOT'),
screenshot_200pct: assertSafeRelative(state.screenshot_200pct, 'SCREENSHOT'),
trace: assertSafeRelative(state.trace, 'TRACE'),
}));
byPage.set(unit.page_id, { page_id: unit.page_id, states });
}
const missing = REQUIRED_COVERAGE_UNITS.filter((page) => !byPage.has(page));
if (missing.length) throw new Error(`WP7_05_MISSING_PAGES:${missing.join(',')}`);
if (!Array.isArray(viewports) || viewports.length !== 2) throw new Error('WP7_05_VIEWPORTS_REQUIRED');
return {
schema_version: '1.0',
task: 'TASK-WP7-05',
run_id: runId,
candidate_sha256: candidateSha256.toUpperCase(),
viewports,
coverage_units: REQUIRED_COVERAGE_UNITS.map((page) => byPage.get(page)),
};
}
+71
View File
@@ -0,0 +1,71 @@
import fs from 'node:fs';
export const REQUIRED_COVERAGE_UNITS = Object.freeze([
'support-gate', 'user-auth', 'workspace', 'current-task', 'projects',
'project-detail', 'editor', 'export', 'credits', 'settings',
'preview-user-variant', 'admin-auth', 'admin-overview', 'admin-users',
'admin-invites', 'admin-models', 'admin-assets', 'admin-preview',
'admin-generations', 'admin-services-storage', 'admin-audit', 'system-ui',
]);
const REQUIRED_VIEWPORTS = Object.freeze([
{ width: 1920, height: 1080, deviceScaleFactor: 1, zoom: 100 },
{ width: 1920, height: 1080, deviceScaleFactor: 1, zoom: 200 },
]);
function blocked(code, details = {}) {
return { status: 'externally_blocked', code, ...details };
}
export function loadCandidateRecord(path) {
if (!path || !fs.existsSync(path)) return blocked('candidate_record_missing');
try {
const record = JSON.parse(fs.readFileSync(path, 'utf8'));
if (!Array.isArray(record.browsers) || record.browsers.length !== 2) {
return blocked('candidate_browser_record_incomplete');
}
const brands = new Set(record.browsers.map((browser) => browser.brand));
if (brands.size !== 2 || !brands.has('Google Chrome') || !brands.has('Microsoft Edge')) {
return blocked('candidate_browser_pair_invalid');
}
if (record.windows?.build == null || !record.candidate_package?.sha256 || !record.candidate_package?.fixed_port) {
return blocked('candidate_identity_incomplete');
}
if (record.browsers.some((browser) => !browser.full_version || !browser.major)) {
return blocked('candidate_full_version_missing');
}
return { status: 'ready', record };
} catch {
return blocked('candidate_record_invalid');
}
}
export function validateCoverageEvidence(evidence) {
if (!evidence || !Array.isArray(evidence.coverage_units)) {
return blocked('coverage_evidence_missing');
}
const actual = new Set(evidence.coverage_units.map((unit) => unit.page_id));
const missing = REQUIRED_COVERAGE_UNITS.filter((unit) => !actual.has(unit));
if (missing.length) return blocked('coverage_units_incomplete', { missing });
const missingStates = evidence.coverage_units
.filter((unit) => REQUIRED_COVERAGE_UNITS.includes(unit.page_id))
.filter((unit) => !Array.isArray(unit.states) || unit.states.length === 0)
.map((unit) => unit.page_id);
if (missingStates.length) return blocked('coverage_states_incomplete', { missingStates });
const viewportKeys = new Set((evidence.viewports ?? []).map((viewport) => JSON.stringify(viewport)));
const missingViewports = REQUIRED_VIEWPORTS.filter((viewport) => !viewportKeys.has(JSON.stringify(viewport)));
if (missingViewports.length) return blocked('candidate_viewports_incomplete', { missingViewports });
return { status: 'ready' };
}
export function runWp705Gate({ candidatePath, evidence, dependencies = {} }) {
const candidate = loadCandidateRecord(candidatePath);
if (candidate.status !== 'ready') return candidate;
const coverage = validateCoverageEvidence(evidence);
if (coverage.status !== 'ready') return coverage;
const externalBlockers = Object.entries(dependencies)
.filter(([, status]) => status === 'externally_blocked')
.map(([task]) => task);
if (externalBlockers.length) return blocked('upstream_external_blocked', { externalBlockers });
return { status: 'ready_for_execution' };
}
+13
View File
@@ -0,0 +1,13 @@
import { runWp705Gate } from './lib/wp7-05-ui-gate.mjs';
const result = runWp705Gate({
candidatePath: process.env.WP7_01_CANDIDATE_RECORD,
evidence: null,
dependencies: {
'TASK-WP7-03': 'externally_blocked',
'TASK-WP7-04': 'externally_blocked',
},
});
console.log(JSON.stringify({ task: 'TASK-WP7-05', ...result }));
process.exitCode = result.status === 'ready_for_execution' ? 0 : 3;
+41
View File
@@ -0,0 +1,41 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { buildCoverageEvidence } from '../../scripts/lib/wp7-05-coverage.mjs';
import { REQUIRED_COVERAGE_UNITS } from '../../scripts/lib/wp7-05-ui-gate.mjs';
const viewports = [
{ width: 1920, height: 1080, deviceScaleFactor: 1, zoom: 100 },
{ width: 1920, height: 1080, deviceScaleFactor: 1, zoom: 200 },
];
function completeUnits() {
return REQUIRED_COVERAGE_UNITS.map((page_id) => ({
page_id,
states: [{
state: 'normal',
screenshot_100pct: `ui/${page_id}/normal/100pct.png`,
screenshot_200pct: `ui/${page_id}/normal/200pct.png`,
trace: `ui/${page_id}/normal/trace.zip`,
}],
}));
}
test('builds ordered, sanitized evidence for all 22 coverage units', () => {
const evidence = buildCoverageEvidence({
runId: 'wp7-05-red-001',
candidateSha256: 'a'.repeat(64),
coverageUnits: completeUnits(),
viewports,
});
assert.equal(evidence.coverage_units.length, 22);
assert.equal(evidence.coverage_units[0].page_id, 'support-gate');
assert.equal(evidence.candidate_sha256, 'A'.repeat(64));
});
test('rejects absolute evidence paths and incomplete page state', () => {
const units = completeUnits();
units[0].states[0].trace = 'C:\\secret\\trace.zip';
assert.throws(() => buildCoverageEvidence({
runId: 'wp7-05-red-001', candidateSha256: 'a'.repeat(64), coverageUnits: units, viewports,
}), /WP7_05_UNSAFE_TRACE/);
});
+47
View File
@@ -0,0 +1,47 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { loadCandidateRecord, validateCoverageEvidence, runWp705Gate, REQUIRED_COVERAGE_UNITS } from '../../scripts/lib/wp7-05-ui-gate.mjs';
test('WP7-05 accepts the WP7-01 candidate record schema', () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'wp7-05-'));
const file = path.join(directory, 'release-candidate.json');
fs.writeFileSync(file, JSON.stringify({
browsers: [
{ brand: 'Google Chrome', full_version: '150.0.0', major: 150 },
{ brand: 'Microsoft Edge', full_version: '151.0.0', major: 151 },
],
windows: { build: '26200.8875' },
candidate_package: { fixed_port: 43121, sha256: 'A'.repeat(64) },
}));
assert.equal(loadCandidateRecord(file).status, 'ready');
fs.rmSync(directory, { recursive: true, force: true });
});
test('WP7-05 requires all 22 coverage units and both exact candidate viewports', () => {
const result = validateCoverageEvidence({ coverage_units: [], viewports: [] });
assert.equal(result.status, 'externally_blocked');
assert.equal(result.code, 'coverage_units_incomplete');
assert.equal(result.missing.length, REQUIRED_COVERAGE_UNITS.length);
});
test('WP7-05 requires state evidence for every listed coverage unit', () => {
const result = validateCoverageEvidence({
coverage_units: REQUIRED_COVERAGE_UNITS.map((page_id) => ({ page_id, states: [] })),
viewports: [],
});
assert.equal(result.code, 'coverage_states_incomplete');
assert.equal(result.missingStates.length, REQUIRED_COVERAGE_UNITS.length);
});
test('WP7-05 preserves upstream external blockers instead of declaring Green', () => {
const result = runWp705Gate({
candidatePath: 'missing-release-candidate.json',
evidence: null,
dependencies: { 'TASK-WP7-03': 'externally_blocked' },
});
assert.equal(result.status, 'externally_blocked');
assert.equal(result.code, 'candidate_record_missing');
});
+1 -1
View File
@@ -19,7 +19,7 @@ describe("TASK-WP0-01 minimum toolchain", () => {
expect(probe.fabricVersion).toBe("7.4.0");
});
it("loads and closes Fastify with the frozen Swagger plugin", async () => {
it("loads and closes Fastify with the frozen Swagger plugin", { timeout: 15_000 }, async () => {
const app = await createApp();
await app.ready();
expect(app.hasPlugin("@fastify/swagger")).toBe(true);