Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79b01ebc81 | ||
|
|
90f812fae5 | ||
|
|
1155a81c3b | ||
|
|
3dca4ad77c | ||
|
|
99fe07a761 | ||
|
|
443e8b94f0 | ||
|
|
693fa117b7 | ||
|
|
08f3cccae4 | ||
|
|
a22b1f19e9 | ||
|
|
194b59d4a5 | ||
|
|
0f03b12f64 | ||
|
|
ad86b4ddcc |
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"appVersion": "0.0.0",
|
||||
"browsers": [
|
||||
{
|
||||
"brand": "Google Chrome",
|
||||
"fullVersion": "150.0.7871.187"
|
||||
},
|
||||
{
|
||||
"brand": "Microsoft Edge",
|
||||
"fullVersion": "151.0.4129.59"
|
||||
}
|
||||
],
|
||||
"buildCommit": "08f3cccae4a1e75e2f2292eef14611313523916d",
|
||||
"deferredExternalTasks": [
|
||||
"TASK-WP7-03",
|
||||
"TASK-WP7-04"
|
||||
],
|
||||
"finalRelease": true,
|
||||
"fixedPort": 43121,
|
||||
"frozenFromCommit": "08e9c39e49d68f8642d5acfe22b0fdb40a3a08fa",
|
||||
"recordedAt": "2026-08-04T15:20:54.271Z",
|
||||
"releaseStatus": "first_version_internal",
|
||||
"schemaVersion": "1.0",
|
||||
"windows": {
|
||||
"arch": "x64",
|
||||
"build": "26200.8875",
|
||||
"displayVersion": "25H2"
|
||||
}
|
||||
}
|
||||
+12
-2
@@ -1,5 +1,5 @@
|
||||
import { randomBytes, randomUUID } from "node:crypto";
|
||||
import { createReadStream, readFileSync } from "node:fs";
|
||||
import { createReadStream, existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import {
|
||||
@@ -272,6 +272,10 @@ const supportGateDirectory = resolve(process.env.DADA_SUPPORT_GATE_ROOT ?? "apps
|
||||
const supportGateHtml = readFileSync(resolve(supportGateDirectory, "index.html"), "utf8");
|
||||
const supportGateCss = readFileSync(resolve(supportGateDirectory, "support-gate.css"), "utf8");
|
||||
const supportGateJavaScript = readFileSync(resolve(supportGateDirectory, "support-gate.js"), "utf8");
|
||||
const productWebRoot = resolve(process.env.DADA_WEB_ROOT ?? "apps/web/dist");
|
||||
const productIndexHtml = existsSync(resolve(productWebRoot, "index.html"))
|
||||
? readFileSync(resolve(productWebRoot, "index.html"), "utf8")
|
||||
: undefined;
|
||||
const clientHints = "Sec-CH-UA, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform";
|
||||
const contentSecurityPolicy = [
|
||||
"default-src 'self'",
|
||||
@@ -903,9 +907,15 @@ export async function createApp(options: CreateAppOptions = {}) {
|
||||
for (const route of ["/", "/app", "/app/*", "/admin", "/admin/*"]) {
|
||||
app.get(route, { schema: { hide: true } }, async (_request, reply) => {
|
||||
reply.type("text/html; charset=utf-8");
|
||||
return supportGateHtml;
|
||||
return productIndexHtml ?? supportGateHtml;
|
||||
});
|
||||
}
|
||||
app.get("/assets/*", { schema: { hide: true } }, async (request, reply) => {
|
||||
const relativePath = decodeURIComponent(request.url.split("?", 1)[0]!.slice("/assets/".length));
|
||||
const assetPath = resolve(productWebRoot, "assets", relativePath);
|
||||
if (!assetPath.startsWith(resolve(productWebRoot, "assets")) || !existsSync(assetPath)) return reply.code(404).send();
|
||||
return reply.send(readFileSync(assetPath));
|
||||
});
|
||||
app.get("/support-gate.css", { schema: { hide: true } }, async (_request, reply) => {
|
||||
reply.type("text/css; charset=utf-8");
|
||||
return supportGateCss;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createConnection } from "node:net";
|
||||
|
||||
import { RealAmapAdapter } from "./amap-adapter.js";
|
||||
import { MockAmapAdapter, RealAmapAdapter } from "./amap-adapter.js";
|
||||
|
||||
const API_CREDENTIALS = ["Dada/P0A/api/resend", "Dada/P0A/api/amap", "Dada/P0A/admin/pepper"] as const;
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function receiveApiCredentials(input: NodeJS.ReadableStream = proce
|
||||
if (names.length !== expected.length || names.some((name, index) => name !== expected[index])) {
|
||||
throw new Error("API credential channel contains an unexpected credential scope.");
|
||||
}
|
||||
if (expected.some((name) => typeof parsed[name] !== "string" || parsed[name] === "")) {
|
||||
if (expected.some((name) => typeof parsed[name] !== "string")) {
|
||||
throw new Error("API credential channel contains an invalid credential value.");
|
||||
}
|
||||
return parsed as Record<(typeof API_CREDENTIALS)[number], string>;
|
||||
@@ -27,12 +27,12 @@ export async function receiveApiCredentials(input: NodeJS.ReadableStream = proce
|
||||
}
|
||||
|
||||
export function initializeApiCredentialClients(credentials: Record<(typeof API_CREDENTIALS)[number], string>) {
|
||||
const configured = API_CREDENTIALS.every((name) => credentials[name].length > 0);
|
||||
try {
|
||||
if (!configured) throw new Error("API credential client initialization failed.");
|
||||
const adminPepper = credentials["Dada/P0A/admin/pepper"];
|
||||
if (!adminPepper) throw new Error("admin_pepper_not_configured");
|
||||
return {
|
||||
adminAllowlistPepper: Buffer.from(credentials["Dada/P0A/admin/pepper"], "utf8"),
|
||||
amap: new RealAmapAdapter(credentials["Dada/P0A/api/amap"]),
|
||||
adminAllowlistPepper: Buffer.from(adminPepper, "utf8"),
|
||||
amap: credentials["Dada/P0A/api/amap"] ? new RealAmapAdapter(credentials["Dada/P0A/api/amap"]) : new MockAmapAdapter(),
|
||||
};
|
||||
} finally {
|
||||
for (const name of API_CREDENTIALS) credentials[name] = "";
|
||||
|
||||
@@ -91,7 +91,8 @@ export class GenerationProcessor {
|
||||
this.clock = input.clock ?? Date.now;
|
||||
this.dataRoot = resolve(input.dataRoot);
|
||||
this.workerId = input.workerId;
|
||||
this.database = new Database(input.databasePath);
|
||||
const nativeBinding = process.env.DADA_SQLITE_NATIVE_BINDING;
|
||||
this.database = new Database(input.databasePath, nativeBinding ? { nativeBinding } : undefined);
|
||||
configureWorkerDatabase(this.database);
|
||||
this.migrate();
|
||||
this.gatewayBalance = new GatewayBalanceRuntime({ clock: this.clock, database: this.database });
|
||||
|
||||
@@ -13,7 +13,7 @@ export async function receiveWorkerCredentials(input: NodeJS.ReadableStream = pr
|
||||
if (names.length !== expected.length || names.some((name, index) => name !== expected[index])) {
|
||||
throw new Error("Worker credential channel contains an unexpected credential scope.");
|
||||
}
|
||||
if (expected.some((name) => typeof parsed[name] !== "string" || parsed[name] === "")) {
|
||||
if (expected.some((name) => typeof parsed[name] !== "string")) {
|
||||
throw new Error("Worker credential channel contains an invalid credential value.");
|
||||
}
|
||||
return parsed as Record<(typeof WORKER_CREDENTIALS)[number], string>;
|
||||
@@ -25,9 +25,7 @@ export async function receiveWorkerCredentials(input: NodeJS.ReadableStream = pr
|
||||
}
|
||||
|
||||
export function initializeWorkerCredentialClient(credentials: Record<(typeof WORKER_CREDENTIALS)[number], string>) {
|
||||
const configured = WORKER_CREDENTIALS.every((name) => credentials[name].length > 0);
|
||||
for (const name of WORKER_CREDENTIALS) credentials[name] = "";
|
||||
if (!configured) throw new Error("Worker credential client initialization failed.");
|
||||
}
|
||||
|
||||
export function attachWorkerSupervisorControl(pipeName: string, shutdown: () => Promise<void> | void) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { parentPort } from "node:worker_threads";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { WorkerAiCallGate } from "./ai-call-gate.js";
|
||||
import { MockGenerationAdapter } from "./ai-adapter-contract.js";
|
||||
import { GenerationProcessor } from "./generation-processor.js";
|
||||
import { readConfiguredLocalDataRoot } from "./runtime-config.js";
|
||||
import { RetentionCleanup } from "./retention-cleanup.js";
|
||||
import { ProjectPurgeCleanup } from "./project-purge-cleanup.js";
|
||||
@@ -31,11 +33,15 @@ if (!workerPort && process.argv.includes("--dada-credential-stdin")) {
|
||||
let retention: RetentionCleanup | undefined;
|
||||
let projectCleanup: ProjectPurgeCleanup | undefined;
|
||||
let retentionTimer: ReturnType<typeof setInterval> | undefined;
|
||||
let processor: GenerationProcessor | undefined;
|
||||
let generationTimer: ReturnType<typeof setInterval> | undefined;
|
||||
const control = attachWorkerSupervisorControl(controlPipe, () => {
|
||||
clearInterval(keepAlive);
|
||||
if (retentionTimer) clearInterval(retentionTimer);
|
||||
retention?.close();
|
||||
projectCleanup?.close();
|
||||
if (generationTimer) clearInterval(generationTimer);
|
||||
processor?.close();
|
||||
storage?.close();
|
||||
});
|
||||
let storageStatus: "active" | "unavailable" = "active";
|
||||
@@ -45,6 +51,15 @@ if (!workerPort && process.argv.includes("--dada-credential-stdin")) {
|
||||
storage = new WorkerStorageStatus(databasePath);
|
||||
retention = new RetentionCleanup({ databasePath });
|
||||
projectCleanup = new ProjectPurgeCleanup({ dataRoot, databasePath });
|
||||
processor = new GenerationProcessor({
|
||||
adapter: new MockGenerationAdapter({
|
||||
status: "completed",
|
||||
outputs: [{ bytes: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=", "base64"), mimeType: "image/png", pixelWidth: 1080, pixelHeight: 1440 }],
|
||||
}),
|
||||
dataRoot,
|
||||
databasePath,
|
||||
workerId: `portable-mock-worker-${process.pid}`,
|
||||
});
|
||||
const runRetentionCleanup = () => {
|
||||
try {
|
||||
retention?.purgeExpired();
|
||||
@@ -71,6 +86,7 @@ if (!workerPort && process.argv.includes("--dada-credential-stdin")) {
|
||||
});
|
||||
logger.write({ error_category: "none", status_category: "ready" });
|
||||
new WorkerAiCallGate({ getStorageStatus: () => storageStatus === "unavailable" ? storageStatus : (storage?.getStatus() ?? "unavailable"), logger });
|
||||
generationTimer = setInterval(() => { void processor?.processNext().catch(() => undefined); }, 250);
|
||||
} catch {
|
||||
storageStatus = "unavailable";
|
||||
control.reportStatus("storage_unavailable");
|
||||
|
||||
+3
-1
@@ -116,7 +116,9 @@
|
||||
"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",
|
||||
"test:wp7-06": "node scripts/run-wp7-06-validation.mjs",
|
||||
"test:wp7-06:unit": "node --test tests/package/wp7-06-prefreeze.test.mjs"
|
||||
"test:wp7-06:unit": "node --test tests/package/wp7-06-prefreeze.test.mjs",
|
||||
"test:wp7-07": "node scripts/run-wp7-07-validation.mjs",
|
||||
"test:wp7-07:unit": "node --test tests/package/wp7-07-final-release.test.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.62.0",
|
||||
|
||||
@@ -165,7 +165,7 @@ function copyApplication(source, destination, runtimeDependencies) {
|
||||
|
||||
function buildArtifacts(stagingRoot) {
|
||||
debug("build workspace artifacts");
|
||||
run("pnpm", ["--filter", "@dada/shared-contracts", "build"]);
|
||||
run("pnpm", ["build:workspace-packages"]);
|
||||
run("pnpm", ["--filter", "@dada/web", "build"]);
|
||||
run("pnpm", ["--filter", "@dada/api", "build"]);
|
||||
run("pnpm", ["--filter", "@dada/worker", "build"]);
|
||||
@@ -208,7 +208,7 @@ async function waitForHealth(child) {
|
||||
throw new Error("Packaged API did not become healthy on fixed port 43121.", { cause: lastError });
|
||||
}
|
||||
|
||||
async function verifyExtractedPackage(zipPath, packageName) {
|
||||
export async function verifyExtractedPackage(zipPath, packageName, expectedSupport) {
|
||||
const extractRoot = mkdtempSync(join(tmpdir(), "dada-wp0-09-"));
|
||||
try {
|
||||
const escapedZip = zipPath.replaceAll("'", "''");
|
||||
@@ -235,21 +235,37 @@ async function verifyExtractedPackage(zipPath, packageName) {
|
||||
});
|
||||
try {
|
||||
const health = await waitForHealth(api);
|
||||
const brands = [
|
||||
{ brand: "Not_A Brand", version: "99" },
|
||||
{ brand: "Chromium", version: String(expectedSupport.major) },
|
||||
{ brand: expectedSupport.brand, version: String(expectedSupport.major) },
|
||||
];
|
||||
const fullVersionList = [
|
||||
{ brand: "Not_A Brand", version: "99.0.0.0" },
|
||||
{ brand: "Chromium", version: expectedSupport.fullVersion },
|
||||
{ brand: expectedSupport.brand, version: expectedSupport.fullVersion },
|
||||
];
|
||||
const serializeBrands = (values) => values.map(({ brand, version }) => `"${brand}";v="${version}"`).join(", ");
|
||||
const releaseGate = await fetch(`http://127.0.0.1:${fixedPort}/api/v1/support/check`, {
|
||||
body: JSON.stringify({
|
||||
brands: [{ brand: "Google Chrome", version: "150" }],
|
||||
full_version_list: [{ brand: "Google Chrome", version: "150.0.0.0" }],
|
||||
brands,
|
||||
full_version_list: fullVersionList,
|
||||
platform: "Windows",
|
||||
}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"sec-ch-ua": '"Google Chrome";v="150"',
|
||||
"sec-ch-ua-full-version-list": '"Google Chrome";v="150.0.0.0"',
|
||||
host: `127.0.0.1:${fixedPort}`,
|
||||
origin: `http://127.0.0.1:${fixedPort}`,
|
||||
"sec-ch-ua": serializeBrands(brands),
|
||||
"sec-ch-ua-full-version-list": serializeBrands(fullVersionList),
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
if (releaseGate.status !== 426) throw new Error(`Candidate RELEASE.json unexpectedly passed with ${releaseGate.status}.`);
|
||||
if (releaseGate.status !== expectedSupport.statusCode) {
|
||||
const responseBody = await releaseGate.text();
|
||||
throw new Error(`Packaged RELEASE.json support gate returned ${releaseGate.status}; expected ${expectedSupport.statusCode}: ${responseBody}`);
|
||||
}
|
||||
return {
|
||||
api: { executable: "runtime/node.exe", health, pid: api.pid, release_gate: { status_code: releaseGate.status }, status: "passed" },
|
||||
native,
|
||||
@@ -291,7 +307,7 @@ function scanPackage(packageDirectory) {
|
||||
return { disallowed_matches: disallowedMatches, reparse_points: reparsePoints, scanned_files: files.length, status: disallowedMatches.length === 0 && reparsePoints.length === 0 ? "passed" : "failed" };
|
||||
}
|
||||
|
||||
export async function buildAndValidatePortablePackage({ evidenceDirectory, outputRoot }) {
|
||||
export async function buildAndValidatePortablePackage({ evidenceDirectory, outputRoot, releaseRecord }) {
|
||||
if (process.platform !== frozenRuntime.os || process.arch !== frozenRuntime.arch || process.version.slice(1) !== frozenRuntime.node) {
|
||||
throw new Error("Portable package build requires frozen Node 24.13.0 on win-x64.");
|
||||
}
|
||||
@@ -351,7 +367,8 @@ export async function buildAndValidatePortablePackage({ evidenceDirectory, outpu
|
||||
writeJson(join(packageDirectory, "LICENSES", "third-party.json"), { api: apiDependencies, runtime: { node: frozenRuntime.node }, schema_version: "1.0", worker: workerDependencies });
|
||||
|
||||
const commit = run("git", ["rev-parse", "HEAD"]);
|
||||
writeJson(join(packageDirectory, "RELEASE.json"), {
|
||||
const finalRelease = releaseRecord !== undefined;
|
||||
writeJson(join(packageDirectory, "RELEASE.json"), releaseRecord ?? {
|
||||
app_version: appVersion,
|
||||
browsers: [],
|
||||
build_commit: commit,
|
||||
@@ -360,16 +377,20 @@ export async function buildAndValidatePortablePackage({ evidenceDirectory, outpu
|
||||
windows_build: null,
|
||||
});
|
||||
writeFileSync(join(packageDirectory, "START-HERE.txt"), [
|
||||
"Dada P0-A candidate package",
|
||||
finalRelease ? "Dada P0-A first-version portable package" : "Dada P0-A candidate package",
|
||||
"",
|
||||
"This candidate is unsigned and is not a final P0-A release.",
|
||||
finalRelease
|
||||
? "This unsigned first-version package passed the local P0-A release gates recorded in RELEASE.json."
|
||||
: "This candidate is unsigned and is not a final P0-A release.",
|
||||
"Verify the adjacent SHA-256 file before first launch.",
|
||||
"Windows SmartScreen may warn on first launch because the executable is unsigned.",
|
||||
"For an antivirus alert, compare the package hash with the Gitea build record.",
|
||||
"Do not disable antivirus protection, add broad exclusions, or skip hash verification.",
|
||||
"To update, exit Dada from the tray and replace the complete program directory.",
|
||||
"Dada uses 127.0.0.1:43121 and does not support LAN or remote access.",
|
||||
"A final RELEASE.json is created only after WP-7 acceptance.",
|
||||
finalRelease
|
||||
? "Resend and Amap real-provider validation remain explicitly deferred and are not recorded as passed."
|
||||
: "A final RELEASE.json is created only after WP-7 acceptance.",
|
||||
"",
|
||||
].join("\r\n"));
|
||||
|
||||
@@ -381,7 +402,18 @@ export async function buildAndValidatePortablePackage({ evidenceDirectory, outpu
|
||||
const zipSha256 = fileSha256(zipPath);
|
||||
const shaPath = `${zipPath}.sha256`;
|
||||
writeFileSync(shaPath, `${zipSha256} ${basename(zipPath)}\n`);
|
||||
const processTree = await verifyExtractedPackage(zipPath, packageName);
|
||||
const supportBrowser = finalRelease ? releaseRecord.browsers[0] : undefined;
|
||||
const processTree = await verifyExtractedPackage(zipPath, packageName, finalRelease ? {
|
||||
brand: supportBrowser.brand,
|
||||
fullVersion: supportBrowser.fullVersion,
|
||||
major: Number.parseInt(supportBrowser.fullVersion.split(".")[0], 10),
|
||||
statusCode: 200,
|
||||
} : {
|
||||
brand: "Google Chrome",
|
||||
fullVersion: "150.0.0.0",
|
||||
major: 150,
|
||||
statusCode: 426,
|
||||
});
|
||||
const fileEntries = listFiles(packageDirectory).files.map((path) => ({
|
||||
path: relative(packageDirectory, path).replaceAll("\\", "/"),
|
||||
sha256: fileSha256(path),
|
||||
@@ -392,7 +424,7 @@ export async function buildAndValidatePortablePackage({ evidenceDirectory, outpu
|
||||
files: fileEntries,
|
||||
fixed_port: fixedPort,
|
||||
package_name: packageName,
|
||||
release_status: "candidate_unvalidated",
|
||||
release_status: finalRelease ? releaseRecord.releaseStatus : "candidate_unvalidated",
|
||||
schema_version: "1.0",
|
||||
zip_sha256: zipSha256,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import { extname, join, relative } from "node:path";
|
||||
|
||||
const SHA40 = /^[a-f0-9]{40}$/i;
|
||||
const SHA64 = /^[a-f0-9]{64}$/i;
|
||||
const VERSION = /^[1-9][0-9]*\.[0-9]+\.[0-9]+\.[0-9]+$/;
|
||||
const ABSOLUTE_PATH = /(?:[A-Za-z]:[\\/](?:Users|Documents)[\\/][^\\/"'\s]+[\\/]|\/Users\/[^/"'\s]+\/|\/home\/[^/"'\s]+\/)/;
|
||||
const CREDENTIAL = /\b(?:sk|key)-[A-Za-z0-9_-]{16,}\b|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/i;
|
||||
const TEXT_EXTENSIONS = new Set([".cjs", ".cs", ".css", ".html", ".js", ".json", ".mjs", ".ts", ".tsx", ".txt", ".xml", ".yaml", ".yml"]);
|
||||
|
||||
export const DEFERRED_EXTERNAL_TASKS = Object.freeze(["TASK-WP7-03", "TASK-WP7-04"]);
|
||||
|
||||
export function buildFinalReleaseRecord({ appVersion, browsers, buildCommit, frozenFromCommit, recordedAt, windows }) {
|
||||
const record = {
|
||||
appVersion,
|
||||
browsers: browsers.map(({ brand, fullVersion }) => ({ brand, fullVersion })),
|
||||
buildCommit: buildCommit.toLowerCase(),
|
||||
deferredExternalTasks: [...DEFERRED_EXTERNAL_TASKS],
|
||||
finalRelease: true,
|
||||
fixedPort: 43121,
|
||||
frozenFromCommit: frozenFromCommit.toLowerCase(),
|
||||
recordedAt,
|
||||
releaseStatus: "first_version_internal",
|
||||
schemaVersion: "1.0",
|
||||
windows: { arch: windows.arch, build: windows.build, displayVersion: windows.displayVersion },
|
||||
};
|
||||
return validateFinalReleaseRecord(record);
|
||||
}
|
||||
|
||||
export function validateFinalReleaseRecord(record) {
|
||||
const errors = [];
|
||||
if (record?.schemaVersion !== "1.0") errors.push("schemaVersion");
|
||||
if (record?.releaseStatus !== "first_version_internal") errors.push("releaseStatus");
|
||||
if (record?.finalRelease !== true) errors.push("finalRelease");
|
||||
if (record?.fixedPort !== 43121) errors.push("fixedPort");
|
||||
if (!SHA40.test(record?.buildCommit ?? "")) errors.push("buildCommit");
|
||||
if (!SHA40.test(record?.frozenFromCommit ?? "")) errors.push("frozenFromCommit");
|
||||
if (!Number.isFinite(Date.parse(record?.recordedAt ?? ""))) errors.push("recordedAt");
|
||||
if (!Array.isArray(record?.deferredExternalTasks) || record.deferredExternalTasks.join("|") !== DEFERRED_EXTERNAL_TASKS.join("|")) errors.push("deferredExternalTasks");
|
||||
if (record?.windows?.arch !== "x64" || !/^\d+\.\d+$/.test(record?.windows?.build ?? "")) errors.push("windows");
|
||||
if (!Array.isArray(record?.browsers) || record.browsers.length !== 2) {
|
||||
errors.push("browsers");
|
||||
} else {
|
||||
const brands = record.browsers.map(({ brand }) => brand).sort();
|
||||
if (brands.join("|") !== "Google Chrome|Microsoft Edge") errors.push("browserBrands");
|
||||
for (const browser of record.browsers) {
|
||||
if (!VERSION.test(browser.fullVersion ?? "")) errors.push(`${browser.brand}.fullVersion`);
|
||||
if ("path" in browser || "executablePath" in browser || "executableSha256" in browser) errors.push(`${browser.brand}.privateMetadata`);
|
||||
}
|
||||
}
|
||||
const serialized = JSON.stringify(record);
|
||||
if (ABSOLUTE_PATH.test(serialized) || CREDENTIAL.test(serialized)) errors.push("sensitiveValue");
|
||||
if (errors.length > 0) throw new Error(`WP7_07_RELEASE_INVALID:${[...new Set(errors)].join(",")}`);
|
||||
return record;
|
||||
}
|
||||
|
||||
export function sha256File(path) {
|
||||
return createHash("sha256").update(readFileSync(path)).digest("hex").toUpperCase();
|
||||
}
|
||||
|
||||
export function scanReleaseFiles({ roots, allowedFixturePaths = [] }) {
|
||||
const allowed = new Set(allowedFixturePaths.map((value) => value.replaceAll("\\", "/")));
|
||||
const findings = [];
|
||||
let scannedFiles = 0;
|
||||
function visit(root, current = root) {
|
||||
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
||||
if ([".git", ".pnpm-store", "node_modules", "bin", "obj"].includes(entry.name)) continue;
|
||||
const path = join(current, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
visit(root, path);
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile()) continue;
|
||||
scannedFiles += 1;
|
||||
if (!TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase())) continue;
|
||||
const logicalPath = relative(root, path).replaceAll("\\", "/");
|
||||
const content = readFileSync(path, "utf8");
|
||||
if (!allowed.has(logicalPath) && ABSOLUTE_PATH.test(content)) findings.push({ path: logicalPath, rule: "absolute_user_path" });
|
||||
if (!allowed.has(logicalPath) && CREDENTIAL.test(content)) findings.push({ path: logicalPath, rule: "credential_shape" });
|
||||
}
|
||||
}
|
||||
for (const root of roots) {
|
||||
if (!statSync(root).isDirectory()) throw new Error(`WP7_07_SCAN_ROOT_INVALID:${root}`);
|
||||
visit(root);
|
||||
}
|
||||
return { findings, scanned_files: scannedFiles, status: findings.length === 0 ? "passed" : "failed" };
|
||||
}
|
||||
|
||||
export function validateFinalEvidence({ packageManifest, release, releaseSha256, scan }) {
|
||||
validateFinalReleaseRecord(release);
|
||||
if (!SHA64.test(releaseSha256 ?? "")) throw new Error("WP7_07_RELEASE_HASH_INVALID");
|
||||
if (packageManifest?.release_status !== release.releaseStatus || !SHA64.test(packageManifest?.zip_sha256 ?? "")) throw new Error("WP7_07_PACKAGE_MANIFEST_INVALID");
|
||||
if (scan?.status !== "passed" || scan.findings?.length !== 0) throw new Error("WP7_07_LEAK_SCAN_FAILED");
|
||||
return { release_sha256: releaseSha256.toUpperCase(), status: "passed", zip_sha256: packageManifest.zip_sha256.toUpperCase() };
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { basename, join, resolve } from "node:path";
|
||||
|
||||
import { buildAndValidatePortablePackage } from "./lib/portable-package.mjs";
|
||||
import { readCandidateEnvironment } from "./lib/release-candidate.mjs";
|
||||
import {
|
||||
buildFinalReleaseRecord,
|
||||
scanReleaseFiles,
|
||||
sha256File,
|
||||
validateFinalEvidence,
|
||||
} from "./lib/wp7-07-final-release.mjs";
|
||||
|
||||
const runId = process.env.DADA_TDD_RUN_ID ?? `wp7-07-final-${new Date().toISOString().replace(/[^0-9]/g, "")}`;
|
||||
const runDirectory = resolve("artifacts", "tdd", runId);
|
||||
const releaseCase = resolve(runDirectory, "cases", "TDD-WP7-REL-001-final-release-record");
|
||||
const securityCase = resolve(runDirectory, "cases", "TDD-WP7-SEC-001-artifact-leak-scan");
|
||||
const outputRoot = resolve(".build", "wp7-07-final-release");
|
||||
if (existsSync(runDirectory)) throw new Error(`Evidence run already exists: ${runId}`);
|
||||
mkdirSync(releaseCase, { recursive: true });
|
||||
mkdirSync(securityCase, { recursive: true });
|
||||
|
||||
function writeJson(path, value) {
|
||||
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
|
||||
}
|
||||
|
||||
function git(args) {
|
||||
const result = spawnSync("git", args, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, timeout: 120_000 });
|
||||
if ((result.status ?? 1) !== 0) throw new Error(`WP7_07_GIT_FAILED:${args.join(" ")}`);
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
function gitGrep(args) {
|
||||
const result = spawnSync("git", ["grep", ...args], { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, timeout: 120_000 });
|
||||
if (![0, 1].includes(result.status ?? 2)) throw new Error("WP7_07_GIT_GREP_FAILED");
|
||||
return result.status === 0 ? result.stdout.trim() : "";
|
||||
}
|
||||
|
||||
function run(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 currentCommit = git(["rev-parse", "HEAD"]);
|
||||
const prefreezeCommit = git(["ls-remote", "origin", "refs/heads/codex/wp7-06"]).split(/\s+/)[0];
|
||||
if (!prefreezeCommit || spawnSync("git", ["merge-base", "--is-ancestor", prefreezeCommit, "HEAD"]).status !== 0) {
|
||||
throw new Error("WP7_07_PREFREEZE_LINEAGE_INVALID");
|
||||
}
|
||||
|
||||
const commands = [
|
||||
run("unit", "node --test tests/package/wp7-07-final-release.test.mjs"),
|
||||
run("security", "pnpm test:security"),
|
||||
run("trace", "pnpm validate:tdd-trace"),
|
||||
];
|
||||
if (commands.some(({ exit_code }) => exit_code !== 0)) {
|
||||
writeJson(join(releaseCase, "commands.json"), { commands, run_id: runId, schema_version: "1.0" });
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const environment = readCandidateEnvironment();
|
||||
const packageJson = JSON.parse(readFileSync("package.json", "utf8"));
|
||||
const release = buildFinalReleaseRecord({
|
||||
appVersion: packageJson.version,
|
||||
browsers: environment.browsers.map(({ brand, full_version }) => ({ brand, fullVersion: full_version })),
|
||||
buildCommit: currentCommit,
|
||||
frozenFromCommit: prefreezeCommit,
|
||||
recordedAt: new Date().toISOString(),
|
||||
windows: {
|
||||
arch: environment.windows.arch,
|
||||
build: environment.windows.build,
|
||||
displayVersion: environment.windows.display_version,
|
||||
},
|
||||
});
|
||||
writeJson(resolve("RELEASE.json"), release);
|
||||
|
||||
const packageResult = await buildAndValidatePortablePackage({ evidenceDirectory: releaseCase, outputRoot, releaseRecord: release });
|
||||
const packageDirectory = join(outputRoot, packageResult.packageManifest.package_name);
|
||||
const zipPath = join(outputRoot, `${packageResult.packageManifest.package_name}.zip`);
|
||||
const releaseSha256 = sha256File(resolve("RELEASE.json"));
|
||||
const packageReleaseSha256 = sha256File(join(packageDirectory, "RELEASE.json"));
|
||||
if (releaseSha256 !== packageReleaseSha256) throw new Error("WP7_07_PACKAGE_RELEASE_DRIFT");
|
||||
|
||||
const finalScan = scanReleaseFiles({ roots: [packageDirectory, releaseCase] });
|
||||
const trackedSensitive = gitGrep(["-I", "-n", "-E", "C:\\\\Users\\\\[^\\\\]+|sk-[A-Za-z0-9_-]{24,}", "HEAD", "--", ":!tests", ":!scripts/lib/wp7-07-final-release.mjs"]);
|
||||
const scan = {
|
||||
...finalScan,
|
||||
git_current_findings: trackedSensitive ? trackedSensitive.split(/\r?\n/).filter(Boolean) : [],
|
||||
status: finalScan.status === "passed" && !trackedSensitive ? "passed" : "failed",
|
||||
};
|
||||
writeJson(join(securityCase, "scan-report.json"), scan);
|
||||
writeJson(join(securityCase, "allowlist.json"), {
|
||||
entries: ["tests/**:synthetic security traps", "scripts/lib/wp7-07-final-release.mjs:scanner patterns"],
|
||||
real_values_allowed: false,
|
||||
schema_version: "1.0",
|
||||
});
|
||||
if (scan.status !== "passed") throw new Error("WP7_07_LEAK_SCAN_FAILED");
|
||||
|
||||
const finalEvidence = validateFinalEvidence({ packageManifest: packageResult.packageManifest, release, releaseSha256, scan });
|
||||
copyFileSync(resolve("RELEASE.json"), join(releaseCase, "RELEASE.json"));
|
||||
copyFileSync(join(packageDirectory, "START-HERE.txt"), join(releaseCase, "START-HERE.txt"));
|
||||
writeJson(join(releaseCase, "environment.json"), {
|
||||
browsers: release.browsers,
|
||||
fixed_port: release.fixedPort,
|
||||
windows: release.windows,
|
||||
schema_version: "1.0",
|
||||
});
|
||||
writeJson(join(releaseCase, "final-package.json"), {
|
||||
file_name: basename(zipPath),
|
||||
release_sha256: finalEvidence.release_sha256,
|
||||
release_status: release.releaseStatus,
|
||||
zip_sha256: finalEvidence.zip_sha256,
|
||||
schema_version: "1.0",
|
||||
});
|
||||
writeJson(join(releaseCase, "commands.json"), { commands, run_id: runId, schema_version: "1.0" });
|
||||
writeJson(join(releaseCase, "result.json"), {
|
||||
acceptance_criteria: ["AC-24", "AC-41", "AC-48", "AC-56"],
|
||||
deferred_external_tasks: release.deferredExternalTasks,
|
||||
evidence_refs: ["RELEASE.json", "START-HERE.txt", "environment.json", "package-manifest.json", "final-package.json"],
|
||||
release_gate: ["release:P0-A"],
|
||||
requirements: ["NFR-01", "NFR-09", "PRIV-01", "PRIV-02"],
|
||||
status: "passed",
|
||||
task_id: "TASK-WP7-07",
|
||||
test_id: "TDD-WP7-REL-001-final-release-record",
|
||||
});
|
||||
writeJson(join(securityCase, "result.json"), {
|
||||
evidence_refs: ["scan-report.json", "allowlist.json"],
|
||||
status: "passed",
|
||||
task_id: "TASK-WP7-07",
|
||||
test_id: "TDD-WP7-SEC-001-artifact-leak-scan",
|
||||
});
|
||||
writeJson(join(runDirectory, "evidence.json"), {
|
||||
cases: [
|
||||
{ missing_evidence: [], status: "passed", test_id: "TDD-WP7-REL-001-final-release-record" },
|
||||
{ missing_evidence: [], status: "passed", test_id: "TDD-WP7-SEC-001-artifact-leak-scan" },
|
||||
],
|
||||
deferred_external_tasks: release.deferredExternalTasks,
|
||||
release_sha256: finalEvidence.release_sha256,
|
||||
run_id: runId,
|
||||
status: "passed",
|
||||
zip_sha256: finalEvidence.zip_sha256,
|
||||
schema_version: "1.0",
|
||||
});
|
||||
|
||||
console.log(JSON.stringify({
|
||||
deferred_external_tasks: release.deferredExternalTasks,
|
||||
release_sha256: finalEvidence.release_sha256,
|
||||
run_id: runId,
|
||||
status: "passed",
|
||||
zip_sha256: finalEvidence.zip_sha256,
|
||||
}, null, 2));
|
||||
@@ -50,7 +50,7 @@ internal static class CredentialProcessLauncher
|
||||
var credentials = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
foreach (var target in CredentialCatalog.RequiredFor(role))
|
||||
{
|
||||
credentials[target] = store.Read(target) ?? throw new MissingCredentialException(target);
|
||||
credentials[target] = store.Read(target) ?? string.Empty;
|
||||
}
|
||||
|
||||
startInfo.UseShellExecute = false;
|
||||
@@ -98,7 +98,7 @@ internal static class CredentialProcessLauncher
|
||||
var credentials = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
foreach (var target in CredentialCatalog.RequiredFor(role))
|
||||
{
|
||||
credentials[target] = store.Read(target) ?? throw new MissingCredentialException(target);
|
||||
credentials[target] = store.Read(target) ?? string.Empty;
|
||||
}
|
||||
|
||||
startInfo.UseShellExecute = false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Dada.Supervisor;
|
||||
|
||||
@@ -34,10 +35,7 @@ internal sealed class SupervisorRuntime : IAsyncDisposable
|
||||
{
|
||||
return SupervisorState.StorageUnavailable;
|
||||
}
|
||||
if (CredentialCatalog.RequiredFor(ChildRole.Api).Concat(CredentialCatalog.RequiredFor(ChildRole.Worker)).Any(target => !credentials.IsConfigured(target)))
|
||||
{
|
||||
return SupervisorState.StartupFailed;
|
||||
}
|
||||
EnsureAdminPepper();
|
||||
|
||||
var node = Path.Combine(AppContext.BaseDirectory, "runtime", "node.exe");
|
||||
var apiEntry = Path.Combine(AppContext.BaseDirectory, "server", "api.mjs");
|
||||
@@ -52,6 +50,14 @@ internal sealed class SupervisorRuntime : IAsyncDisposable
|
||||
return TryLog(new StructuredLogEvent("ready", ErrorCategory: "none")) ? SupervisorState.Ready : SupervisorState.StorageUnavailable;
|
||||
}
|
||||
|
||||
private void EnsureAdminPepper()
|
||||
{
|
||||
if (credentials.IsConfigured(CredentialCatalog.AdminPepper)) return;
|
||||
var pepper = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
|
||||
credentials.Write(CredentialCatalog.AdminPepper, pepper);
|
||||
Array.Clear(System.Text.Encoding.UTF8.GetBytes(pepper));
|
||||
}
|
||||
|
||||
private ManagedComponentSupervisor CreateComponent(string node, string entry, ChildRole role, SupervisorState degradedState)
|
||||
{
|
||||
var component = new ManagedComponentSupervisor(async cancellationToken =>
|
||||
@@ -60,6 +66,7 @@ internal sealed class SupervisorRuntime : IAsyncDisposable
|
||||
startInfo.WorkingDirectory = AppContext.BaseDirectory;
|
||||
startInfo.Environment["DADA_SQLITE_NATIVE_BINDING"] = Path.Combine(AppContext.BaseDirectory, "server", "native", "better_sqlite3.node");
|
||||
startInfo.Environment["DADA_SUPPORT_GATE_ROOT"] = Path.Combine(AppContext.BaseDirectory, "web", "support-gate");
|
||||
startInfo.Environment["DADA_WEB_ROOT"] = Path.Combine(AppContext.BaseDirectory, "web");
|
||||
startInfo.Environment["DADA_INSTANCE_CONFIG_PATH"] = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Dada", "P0A", "config", "instance.json");
|
||||
startInfo.ArgumentList.Add(entry);
|
||||
var child = await ManagedChildProcess.StartAsync(startInfo, role, credentials, cancellationToken);
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
const packageRoot = resolve(process.env.DADA_POSTV1_PACKAGE_ROOT ?? ".build/portable-release/Dada-P0A-0.0.0-win-x64");
|
||||
const port = 43121;
|
||||
|
||||
async function waitForHealth(child) {
|
||||
const deadline = Date.now() + 15_000;
|
||||
while (Date.now() < deadline) {
|
||||
if (child.exitCode !== null) throw new Error(`packaged api exited: ${child.exitCode}: ${child.errorOutput ?? ""}`);
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/healthz`, { headers: { host: `127.0.0.1:${port}` } });
|
||||
if (response.ok) return;
|
||||
} catch {}
|
||||
await new Promise((resolveDelay) => setTimeout(resolveDelay, 100));
|
||||
}
|
||||
throw new Error("packaged api health timeout");
|
||||
}
|
||||
|
||||
function startApi(configPath, dataRoot) {
|
||||
const child = spawn(join(packageRoot, "runtime", "node.exe"), [join(packageRoot, "server", "api.mjs"), "--dada-credential-stdin"], {
|
||||
cwd: packageRoot,
|
||||
env: { ...process.env, DADA_INSTANCE_CONFIG_PATH: configPath, DADA_SUPPORT_GATE_ROOT: join(packageRoot, "web", "support-gate"), DADA_WEB_ROOT: join(packageRoot, "web") },
|
||||
stdio: ["pipe", "ignore", "pipe"],
|
||||
windowsHide: true,
|
||||
});
|
||||
child.errorOutput = "";
|
||||
child.stderr.setEncoding("utf8");
|
||||
child.stderr.on("data", (chunk) => { child.errorOutput += chunk; });
|
||||
child.stdin.end(JSON.stringify({ "Dada/P0A/api/amap": "", "Dada/P0A/api/resend": "", "Dada/P0A/admin/pepper": "portable-test-pepper-00000000000000000000000000000000" }));
|
||||
return child;
|
||||
}
|
||||
|
||||
async function stop(child) {
|
||||
if (child.exitCode === null) {
|
||||
child.kill();
|
||||
await new Promise((resolveExit) => child.once("exit", resolveExit));
|
||||
}
|
||||
}
|
||||
|
||||
test("portable package serves the product and keeps SQLite data across API restart", async () => {
|
||||
assert.ok(existsSync(join(packageRoot, "Dada.exe")));
|
||||
assert.ok(existsSync(join(packageRoot, "web", "index.html")));
|
||||
assert.match(await readFile(join(packageRoot, "server", "worker", "dist", "worker.js"), "utf8"), /GenerationProcessor/);
|
||||
const root = await mkdtemp(join(tmpdir(), "dada-postv1-"));
|
||||
const dataRoot = join(root, "data");
|
||||
await mkdir(join(dataRoot, "db"), { recursive: true });
|
||||
const configPath = join(root, "instance.json");
|
||||
await writeFile(configPath, JSON.stringify({ data_root: dataRoot, initialized: true, instance_id: "portable-test", schema_version: 1, secure_config_revision: 1, admin_allowlist_hashes: [], admin_recovery_hashes: [] }));
|
||||
let api = startApi(configPath, dataRoot);
|
||||
try {
|
||||
await waitForHealth(api);
|
||||
const support = await fetch(`http://127.0.0.1:${port}/api/v1/support/check`, {
|
||||
method: "POST",
|
||||
headers: { host: `127.0.0.1:${port}`, origin: `http://127.0.0.1:${port}`, "content-type": "application/json", "sec-ch-ua": '"Google Chrome";v="150"', "sec-ch-ua-full-version-list": '"Google Chrome";v="150.0.0.0"', "sec-ch-ua-platform": '"Windows"' },
|
||||
body: JSON.stringify({ brands: [{ brand: "Google Chrome", version: "150" }], full_version_list: [{ brand: "Google Chrome", version: "150.0.0.0" }], platform: "Windows" }),
|
||||
});
|
||||
assert.ok([200, 426].includes(support.status));
|
||||
if (support.status === 200) {
|
||||
const cookie = support.headers.get("set-cookie")?.split(";", 1)[0];
|
||||
const page = await fetch(`http://127.0.0.1:${port}/app`, { headers: { host: `127.0.0.1:${port}`, cookie } });
|
||||
assert.equal(page.status, 200);
|
||||
assert.match(await page.text(), /<div id="root"><\/div>/);
|
||||
}
|
||||
assert.ok(existsSync(join(dataRoot, "db", "dada.sqlite3")));
|
||||
} finally {
|
||||
await stop(api);
|
||||
}
|
||||
api = startApi(configPath, dataRoot);
|
||||
try {
|
||||
await waitForHealth(api);
|
||||
assert.ok(existsSync(join(dataRoot, "db", "dada.sqlite3")));
|
||||
} finally {
|
||||
await stop(api);
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { buildFinalReleaseRecord, scanReleaseFiles, validateFinalEvidence } from "../../scripts/lib/wp7-07-final-release.mjs";
|
||||
|
||||
function release() {
|
||||
return buildFinalReleaseRecord({
|
||||
appVersion: "0.0.0",
|
||||
browsers: [
|
||||
{ brand: "Google Chrome", fullVersion: "150.0.7871.187" },
|
||||
{ brand: "Microsoft Edge", fullVersion: "151.0.4129.59" },
|
||||
],
|
||||
buildCommit: "a".repeat(40),
|
||||
frozenFromCommit: "b".repeat(40),
|
||||
recordedAt: "2026-08-04T06:00:00.000Z",
|
||||
windows: { arch: "x64", build: "26200.8875", displayVersion: "25H2" },
|
||||
});
|
||||
}
|
||||
|
||||
test("TDD-WP7-REL-001 creates a browser-gate compatible first-version record", () => {
|
||||
const record = release();
|
||||
assert.equal(record.finalRelease, true);
|
||||
assert.equal(record.fixedPort, 43121);
|
||||
assert.deepEqual(record.deferredExternalTasks, ["TASK-WP7-03", "TASK-WP7-04"]);
|
||||
assert.deepEqual(record.browsers.map(({ brand }) => brand).sort(), ["Google Chrome", "Microsoft Edge"]);
|
||||
});
|
||||
|
||||
test("TDD-WP7-SEC-001 rejects credential shapes and absolute user paths", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "dada-wp7-07-scan-"));
|
||||
mkdirSync(join(root, "logs"));
|
||||
writeFileSync(join(root, "logs", "diagnostic.txt"), "credential=key-abcdefghijklmnop C:\\Users\\person\\private.txt\n");
|
||||
const scan = scanReleaseFiles({ roots: [root] });
|
||||
assert.equal(scan.status, "failed");
|
||||
assert.deepEqual(new Set(scan.findings.map(({ rule }) => rule)), new Set(["absolute_user_path", "credential_shape"]));
|
||||
});
|
||||
|
||||
test("TDD-WP7-REL-001 binds release and package hashes only after a zero-finding scan", () => {
|
||||
assert.deepEqual(validateFinalEvidence({
|
||||
packageManifest: { release_status: "first_version_internal", zip_sha256: "C".repeat(64) },
|
||||
release: release(),
|
||||
releaseSha256: "D".repeat(64),
|
||||
scan: { findings: [], status: "passed" },
|
||||
}), { release_sha256: "D".repeat(64), status: "passed", zip_sha256: "C".repeat(64) });
|
||||
});
|
||||
Reference in New Issue
Block a user