chore: sync the frontend template from a0fbafb to 5434760

Carries eight template commits: the provider sandbox actually running, release
admission to a named environment, the product feature manifest with its runtime
kill switch, architecture and documentation rules that match what is enforced,
the removability fixtures, and the browser, visual and performance evidence.

Product identity is unchanged. `package.json` keeps `tech-log-frontend` and the
catalog keeps the Tech Log naming; the home page was not in the delta. The
visual baselines are this product's own — the template's were excluded from the
transplant and these were regenerated here, where the only difference is the
platform overview's new product-feature section.

What this repository gains operationally: `config/runtime/{local,development,
staging,production}.json` with `FE-GATE-027` refusing an artifact whose runtime
document does not match the environment it is being admitted to, and
`FEATURE_OVERRIDES` for taking an installed feature out of service without a
rebuild.

Verified here: eight gates green, build green, visual 5/5, and 1,858 of 1,859
tests in the suites that do not need a sandbox — the one failure passes in
isolation and is a jsdom lazy-chunk timeout under parallel load. The provider
suites cannot run on this machine at all: `kernel.apparmor_restrict_unprivileged
_userns=1` makes `bwrap --unshare-net` fail, reproducible without any code from
either repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-15 21:34:19 +09:00
co-authored by Claude Opus 5
parent 325a2a0843
commit bdee07a93b
101 changed files with 3116 additions and 448 deletions
@@ -129,6 +129,9 @@ jobs:
outputs:
dist_sha256: \${{ steps.candidate.outputs.dist_sha256 }}
archive_sha256: \${{ steps.candidate.outputs.archive_sha256 }}
env:
APP_PROFILE: "\${{ vars.APP_PROFILE }}"
RELEASE_TARGET: "\${{ vars.RELEASE_TARGET }}"
steps:
- uses: https://github.com/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
+2 -1
View File
@@ -5,7 +5,7 @@ import {
type ApplicationOutputPorts,
} from "../../src/application/create-application.ts";
import { createTestApplication } from "../helpers/create-test-application.ts";
import { createRuntimeCapabilitiesStub } from "../helpers/runtime-capabilities-stub.ts";
import { createProductFeaturesStub, createRuntimeCapabilitiesStub } from "../helpers/runtime-capabilities-stub.ts";
declare module "../../src/application/ports/in/application-api.ts" {
interface ApplicationFeatureInputs {
@@ -99,6 +99,7 @@ describe("application input/output boundary", () => {
}),
},
runtimeCapabilities: createRuntimeCapabilitiesStub(),
productFeatures: createProductFeaturesStub(),
navigation: { reload: () => {} },
} satisfies ApplicationOutputPorts;
const application = createApplication(ports);
+2 -1
View File
@@ -5,7 +5,7 @@ import { createAnonymousSessionAdapter } from "../../src/adapters/auth/external-
import type { StoragePort } from "../../src/application/ports/storage-port.ts";
import type { DiagnosticsPort } from "../../src/application/ports/diagnostics-port.ts";
import type { TelemetryPort } from "../../src/application/ports/telemetry-port.ts";
import { createRuntimeCapabilitiesStub } from "../helpers/runtime-capabilities-stub.ts";
import { createProductFeaturesStub, createRuntimeCapabilitiesStub } from "../helpers/runtime-capabilities-stub.ts";
type ReleaseFixture = {
buildId: string;
@@ -53,6 +53,7 @@ function applicationWith(options: {
diagnostics: options.diagnostics ?? { record: () => {} },
telemetry: options.telemetry ?? { emit: () => {} },
runtimeCapabilities: createRuntimeCapabilitiesStub(),
productFeatures: createProductFeaturesStub(),
releaseInfo: {
getCurrent: async () => current,
refresh:
+181 -38
View File
@@ -1,6 +1,6 @@
import { spawn, spawnSync, type ChildProcess } from "node:child_process";
import { createHash, generateKeyPairSync, sign } from "node:crypto";
import { constants } from "node:fs";
import { constants, readFileSync } from "node:fs";
import { cp, link, lstat, mkdir, mkdtemp, open, readFile, readdir, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
@@ -20,6 +20,7 @@ import {
} from "../../scripts/lib/ci-artifact-validator.ts";
import { writeCiGateLogAtomic } from "../../scripts/lib/ci-gate-log.ts";
import { linkFixtureNodeModules } from "../../scripts/lib/fixture-node-modules.ts";
import { copyReleaseEvidenceTree } from "../../scripts/lib/removal-fixture.ts";
import { captureCiCandidateArchive, verifyCiCandidateArchive } from "../../scripts/lib/ci-candidate-archive.ts";
import {
CANDIDATE_ARCHIVE_USAGE,
@@ -46,6 +47,14 @@ import {
RELEASE_CANDIDATE_MANIFEST_PATH,
} from "../../scripts/lib/release-candidate.ts";
/**
* Budget for the provider suites specifically. They spawn a systemd scope, a
* bubblewrap sandbox and a signing provider, and build a release candidate to
* do it; the 10s default is sized for pure-JS unit tests. Raising the global
* default instead would hide a genuinely hung test.
*/
const PROCESS_HEAVY_TIMEOUT_MS = 30_000;
const temporaryRoots: string[] = [];
let providerBaseRoot: string | undefined;
const sha256 = (value: Buffer | string) =>
@@ -756,6 +765,10 @@ describe("candidate archive and provider upload boundaries", () => {
},
});
const completionStarted = Date.now();
// Start recording before anything is asserted: the provider's whole life is
// shorter than one `systemctl show`, so the tree has to be sampled, not
// sampled once at whatever moment the assertions happen to arrive.
const tree = recordProviderProcessTree(execution.child.pid!);
const unit = await waitForProviderUnit("vulnerability", execution.child.pid);
const properties = showProviderUnit(unit);
expect(properties).toMatchObject({
@@ -775,10 +788,21 @@ describe("candidate archive and provider upload boundaries", () => {
await expect(readFile(path.join(cgroupRoot, "cpu.max"), "utf8")).resolves.toBe("100000 100000\n");
expect(readProviderUnitMetadata(unit)).not.toMatch(new RegExp(`${command}|${credential}`, "u"));
expect(showProcessArguments(execution.child.pid)).not.toMatch(new RegExp(`${command}|${credential}`, "u"));
const cgroupPids = await readCgroupPids(cgroupRoot);
const processArguments = cgroupPids.map((pid) => showProcessArguments(pid));
const directChildPids = await waitForDirectProviderChildren(execution.child.pid!);
const directChildArguments = directChildPids.map((pid) => showProcessArguments(pid));
const reportIdentity = await lstat(fixture.reportPath);
const result = await execution.completion;
tree.stop();
expect(result.code, result.stderr).toBe(0);
expect(result.stdout).not.toContain(credential);
expect(result.stderr).not.toContain(credential);
// Everything below reads the recording of the whole run rather than a live
// snapshot. The sandbox exists for a few hundred milliseconds; asserting
// while it runs meant racing it, and the assertions are about what the run
// contained, not about what a particular instant looked like.
const cgroupPids = tree.cgroupPids();
const processArguments = tree.cgroupArguments();
const directChildPids = tree.directChildPids();
const directChildArguments = tree.directChildArguments();
const observedArguments = [
showProcessArguments(execution.child.pid),
...directChildArguments,
@@ -788,18 +812,15 @@ describe("candidate archive and provider upload boundaries", () => {
expect(directChildArguments.filter((arguments_) => arguments_.includes("/usr/bin/systemd-run"))).toHaveLength(1);
expect(directChildArguments.filter((arguments_) => arguments_.includes("provider-raw-guardian.ts"))).toHaveLength(1);
expect(processArguments.filter((arguments_) => arguments_.includes("provider-scope-wrapper.ts"))).toHaveLength(1);
expect(processArguments.filter((arguments_) => /\/usr\/bin\/(?:bwrap|prlimit)/u.test(arguments_)).length)
.toBeGreaterThan(0);
expect(
processArguments.filter((arguments_) => /\/usr\/bin\/(?:bwrap|prlimit)/u.test(arguments_)).length,
`sandbox never observed in the scope; recorded: ${processArguments.join(" | ")}`,
).toBeGreaterThan(0);
const infrastructureArguments = [...directChildArguments, ...processArguments].filter((arguments_) =>
/provider-(?:scope-wrapper|raw-guardian)|systemd-run|bwrap|prlimit/u.test(arguments_),
);
expect(infrastructureArguments.join("\n")).not.toContain(command);
expect(processArguments.filter((arguments_) => arguments_.includes(providerScript))).toHaveLength(1);
const reportIdentity = await lstat(fixture.reportPath);
const result = await execution.completion;
expect(result.code, result.stderr).toBe(0);
expect(result.stdout).not.toContain(credential);
expect(result.stderr).not.toContain(credential);
expect(Date.now() - completionStarted).toBeLessThan(4_000);
await expectProviderUnitGone(unit);
await expect(lstat(cgroupRoot)).rejects.toMatchObject({ code: "ENOENT" });
@@ -807,7 +828,7 @@ describe("candidate archive and provider upload boundaries", () => {
expect(directChildPids.every((pid) => !processExists(pid))).toBe(true);
await expect(lstat(fixture.reportPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(findProviderRawReferences(fixture.reportPath, reportIdentity)).resolves.toEqual([]);
}, 10_000);
}, PROCESS_HEAVY_TIMEOUT_MS);
it("kills and collects an active provider when its guardian dies", async () => {
const fixture = await createProviderFixture();
@@ -842,12 +863,15 @@ describe("candidate archive and provider upload boundaries", () => {
expect((await readdir(path.dirname(sealedPath))).filter((leaf) => leaf.includes(".guardian-")))
.toEqual([]);
// `providerWriter` is only a path; the retry has to materialise the script
// it names or the clean-retry claim is proven by a module-not-found error.
await writeFile(fixture.providerWriter, providerV2WriterSource());
const retried = runProviderSupervisor(fixture, {
command: `node ${JSON.stringify(fixture.providerWriter)}`,
sealedPath,
});
expect(retried.status, retried.stderr).toBe(0);
}, 15_000);
}, 20_000);
it("collects the whole provider scope when its supervisor dies", async () => {
const fixture = await createProviderFixture();
@@ -1272,9 +1296,17 @@ describe("verified promotion finalizer", () => {
"provider-verification.json": valid["promotion-verification.json"],
"promotion-verification.json": valid["provider-verification.json"],
};
// `{}` never reaches the digest comparison: it fails the report schema
// first, so this case asserted a decode error while claiming to cover the
// digest branch. The substitution has to be a structurally valid report
// that simply is not the one the verification records committed to.
const divergentReport = JSON.parse(
valid["vulnerability-report.json"].toString("utf8"),
) as Record<string, any>;
divergentReport.provider = "divergent-provider";
const reportMismatch = {
...valid,
"vulnerability-report.json": Buffer.from("{}\n"),
"vulnerability-report.json": jsonBytes(divergentReport),
};
const absent = { ...valid } as Partial<typeof valid>;
delete absent["provider-verification.json"];
@@ -1513,7 +1545,10 @@ describe("verified promotion finalizer", () => {
}),
).rejects.toThrow(/leaf.*identity|staging leaf/u);
await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n");
await expect(readdir(saved)).resolves.toEqual([]);
// A substituted leaf aborts the cleanup before anything is removed, so the
// promotion this call owned is still intact and a retry sees a coherent
// directory rather than a half-emptied one.
expect((await readdir(saved)).sort()).toEqual([...PROMOTED_FILE_NAMES].sort());
}, 30_000);
it("never deletes an unrelated leaf substituted after cleanup validation", async () => {
@@ -1678,28 +1713,31 @@ async function readCgroupPids(cgroupRoot: string): Promise<number[]> {
.trim().split("\n").filter(Boolean).map(Number);
}
async function waitForDirectProviderChildren(supervisorPid: number): Promise<number[]> {
for (let attempt = 0; attempt < 120; attempt += 1) {
const childrenPath = `/proc/${supervisorPid}/task/${supervisorPid}/children`;
const children = (await readFile(childrenPath, "utf8"))
.trim().split(/\s+/u).filter(Boolean).map(Number);
const arguments_ = children.flatMap((pid) => {
try {
return [showProcessArguments(pid)];
} catch (error) {
if (!processExists(pid)) return [];
throw error;
}
});
async function waitForRecordedProviderTree(
tree: ReturnType<typeof recordProviderProcessTree>,
providerScript: string,
): Promise<void> {
for (let attempt = 0; attempt < 400; attempt += 1) {
const children = tree.directChildArguments();
const members = tree.cgroupArguments();
// Every process the assertions below reason about. Returning as soon as
// some of them are present is what left bubblewrap out of the recording:
// it enters the scope a few milliseconds after the wrapper does.
if (
arguments_.some((value) => value.includes("/usr/bin/systemd-run")) &&
arguments_.some((value) => value.includes("provider-raw-guardian.ts"))
children.some((value) => value.includes("/usr/bin/systemd-run")) &&
children.some((value) => value.includes("provider-raw-guardian.ts")) &&
members.some((value) => value.includes("provider-scope-wrapper.ts")) &&
members.some((value) => /\/usr\/bin\/(?:bwrap|prlimit)/u.test(value)) &&
members.some((value) => value.includes(providerScript))
) {
return children;
return;
}
await delay(25);
await delay(10);
}
throw new Error("provider supervisor children were not simultaneously observable");
throw new Error(
"the provider run never contained a systemd-run child, a guardian child, " +
"a scope wrapper, a sandbox and the provider itself in its cgroup",
);
}
async function waitForDirectChildMatching(supervisorPid: number, pattern: string): Promise<number> {
@@ -1844,6 +1882,8 @@ function readProviderUnitMetadata(unitName: string): string {
function showProcessArguments(pid: number | undefined): string {
if (!pid) throw new Error("provider supervisor did not expose its PID");
const argv = readProcessArguments(pid);
if (argv !== null) return argv;
const result = spawnSync(
"/usr/bin/ps",
["-o", "args=", "-p", String(pid)],
@@ -1854,6 +1894,102 @@ function showProcessArguments(pid: number | undefined): string {
return result.stdout.trim();
}
/**
* Reads a process's argv straight from `/proc`.
*
* Spawning `ps` per pid costs milliseconds each, and the provider sandbox now
* completes a whole run in well under a second — the observation was losing a
* race against the thing it was observing. Returns null for a process that is
* already gone, which the sampler treats as "nothing more to record".
*/
function readProcessArguments(pid: number): string | null {
try {
return readFileSync(`/proc/${pid}/cmdline`, "utf8")
.split("\0")
.filter(Boolean)
.join(" ")
.trim();
} catch {
return null;
}
}
/**
* Records the provider's process tree for the whole life of the run.
*
* The assertions below are about what the sandbox looked like while it was
* running, and a single snapshot taken afterwards can only ever be a guess at
* that. Sampling from the moment the supervisor starts turns "did we look at
* the right instant?" into "what did this run actually contain?".
*/
function recordProviderProcessTree(supervisorPid: number) {
const directChildren = new Map<number, string>();
const cgroupMembers = new Map<number, string>();
let stopped = false;
const remember = (into: Map<number, string>, pid: number): void => {
if (into.has(pid)) return;
const argv = readProcessArguments(pid);
if (argv !== null && argv.length > 0) into.set(pid, argv);
};
const childrenOf = (pid: number): number[] => {
try {
return readFileSync(`/proc/${pid}/task/${pid}/children`, "utf8")
.trim()
.split(/\s+/u)
.filter(Boolean)
.map(Number);
} catch {
return [];
}
};
/**
* Scope membership is read from the process itself rather than from
* `systemctl show`. Waiting for the unit to be described before watching its
* cgroup meant bubblewrap had usually already exited by the time the first
* sample was taken — the recording missed exactly the process the assertions
* are about.
*/
const inProviderScope = (pid: number): boolean => {
try {
return readFileSync(`/proc/${pid}/cgroup`, "utf8").includes("ca-provider-");
} catch {
return false;
}
};
const sample = (): void => {
if (stopped) return;
const direct = childrenOf(supervisorPid);
for (const pid of direct) remember(directChildren, pid);
// Walk the whole subtree: the scope wrapper, bubblewrap and the provider
// itself sit below systemd-run, not beside it.
const pending = [...direct];
const seen = new Set(direct);
while (pending.length > 0 && seen.size < 512) {
const pid = pending.pop()!;
if (inProviderScope(pid)) remember(cgroupMembers, pid);
for (const child of childrenOf(pid)) {
if (seen.has(child)) continue;
seen.add(child);
pending.push(child);
}
}
};
const timer = setInterval(sample, 5);
timer.unref();
sample();
return Object.freeze({
stop() {
stopped = true;
clearInterval(timer);
},
directChildPids: () => [...directChildren.keys()],
directChildArguments: () => [...directChildren.values()],
cgroupPids: () => [...cgroupMembers.keys()],
cgroupArguments: () => [...cgroupMembers.values()],
});
}
async function startLoopbackCanary(root: string): Promise<Readonly<{
child: ChildProcess;
marker: string;
@@ -2128,10 +2264,17 @@ async function ensureProviderBaseFixture(): Promise<string> {
return ![".release", "artifacts", "dist", "node_modules"].includes(first ?? "");
},
});
await cp(path.join(sourceRoot, "artifacts"), path.join(root, "artifacts"), {
recursive: true,
});
await rm(path.join(root, "artifacts/release"), { recursive: true, force: true });
// The release evidence, minus the trace and Storybook trees. Copying the
// whole `artifacts/` directory pulled ~28MB of test output into every
// provider fixture; sharing the copier with the removal fixture is also what
// makes both fixtures contain the same evidence.
// `artifacts/release` travels with the fixture rather than being deleted and
// rebuilt. Supply-chain generation runs before the candidate is created and
// validates the release evidence paths, so deleting them made that step
// depend on a previous local run having left them behind — which is why this
// fixture only worked in a workspace that had already built a candidate. The
// build chain overwrites them anyway.
await copyReleaseEvidenceTree(sourceRoot, root);
await linkFixtureNodeModules(root, sourceRoot);
const git = spawnSync("git", ["show", "-s", "--format=%H%n%ct", "HEAD"], {
cwd: sourceRoot,
+5 -5
View File
@@ -184,16 +184,16 @@ describe("CI gate contract", () => {
const index = indexCiGateContract(contract);
expect(contract.schemaVersion).toBe(2);
expect(contract.gates.map(({ id }) => id)).toEqual(
Array.from({ length: 26 }, (_, index) =>
Array.from({ length: 27 }, (_, index) =>
`FE-GATE-${String(index + 1).padStart(3, "0")}`,
),
);
expect(contract.jobs).toHaveLength(9);
expect(contract.commands).toHaveLength(81);
expect(contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0)).toBe(93);
expect(contract.commands).toHaveLength(82);
expect(contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0)).toBe(94);
expect(contract.commands.filter(({ expect }) => expect === "fail")).toHaveLength(23);
expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(85);
expect(contract.artifacts).toHaveLength(105);
expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(86);
expect(contract.artifacts).toHaveLength(107);
expect(contract.stages).toHaveLength(5);
expect(contract.retention.classes).toHaveLength(5);
expect(index.gates.get("FE-GATE-015")?.commandIds).toHaveLength(2);
+166
View File
@@ -0,0 +1,166 @@
import { readFile, readdir } from "node:fs/promises";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
DEPLOYMENT_TARGETS,
findAdmissionViolations,
isDeploymentTarget,
type AdmissionInput,
} from "../../src/contracts/deployment-admission.ts";
import { runtimeConfigV2ArtifactSchema } from "../../src/contracts/release-artifacts.ts";
import { generateRuntimeConfig } from "../../scripts/generate-runtime-config.ts";
const PRODUCTION_ARTIFACT: AdmissionInput = Object.freeze({
APP_ENV: "production",
API_BASE_URL: "https://api.example.com/",
REQUEST_TIMEOUT_MS: 10_000,
MAX_RETRY_ATTEMPTS: 2,
TELEMETRY_ENABLED: false,
AUTH_MODE: "external",
CONFIG_SCHEMA_VERSION: "2.0",
RELEASE_MANIFEST_URL: "/release-manifest.json",
BUILD_ID: "20260815.42",
RELEASE_ID: "r-2026.08.15-1",
CAPABILITY_OVERRIDES: Object.freeze({
REALTIME: "DEFAULT",
WEB_WORKER: "DEFAULT",
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
}),
}) as AdmissionInput;
const LOCAL_ARTIFACT: AdmissionInput = Object.freeze({
...PRODUCTION_ARTIFACT,
APP_ENV: "local",
API_BASE_URL: "http://localhost:8080/",
AUTH_MODE: "demo",
BUILD_ID: "local-build",
RELEASE_ID: "local-release",
}) as AdmissionInput;
describe("deployment admission", () => {
it("admits an artifact only to the environment it declares", () => {
expect(findAdmissionViolations("production", PRODUCTION_ARTIFACT)).toEqual([]);
expect(findAdmissionViolations("local", LOCAL_ARTIFACT)).toEqual([]);
});
it("refuses the exact local build that release coherence used to approve", () => {
// The review's strongest reproduction: FE-GATE-015 passed on a build whose
// runtime document was APP_ENV=local / AUTH_MODE=demo / loopback API. Each
// of those is now an independent refusal, so fixing one does not admit it.
const violations = findAdmissionViolations("production", LOCAL_ARTIFACT);
const fields = violations.map((violation) => violation.field);
expect(fields).toContain("APP_ENV");
expect(fields).toContain("AUTH_MODE");
expect(fields).toContain("API_BASE_URL");
expect(fields).toContain("BUILD_ID");
expect(fields).toContain("RELEASE_ID");
});
it("refuses endpoints a browser on the public internet cannot reach", () => {
for (const host of [
"http://api.example.com/",
"https://localhost/",
"https://127.0.0.1/",
"https://10.0.0.5/",
"https://192.168.1.10/",
"https://172.16.4.4/",
"https://169.254.169.254/",
"https://[::1]/",
]) {
const violations = findAdmissionViolations("production", {
...PRODUCTION_ARTIFACT,
API_BASE_URL: host,
} as AdmissionInput);
expect(violations.map((violation) => violation.field), host).toContain(
"API_BASE_URL",
);
}
});
it("permits a routable public host", () => {
expect(
findAdmissionViolations("production", {
...PRODUCTION_ARTIFACT,
API_BASE_URL: "https://api.172.16.example.com/",
} as AdmissionInput),
).toEqual([]);
});
it("refuses a placeholder identity on a public target", () => {
for (const buildId of ["local-build", "local", "dev", "unknown", ""]) {
const violations = findAdmissionViolations("production", {
...PRODUCTION_ARTIFACT,
BUILD_ID: buildId,
} as AdmissionInput);
expect(violations.map((violation) => violation.field), buildId).toContain(
"BUILD_ID",
);
}
});
it("treats an unknown target as not a target at all", () => {
for (const value of ["prod", "PRODUCTION", "", undefined, null, 1]) {
expect(isDeploymentTarget(value), String(value)).toBe(false);
}
for (const target of DEPLOYMENT_TARGETS) {
expect(isDeploymentTarget(target)).toBe(true);
}
});
});
describe("runtime config profiles", () => {
it("ships one valid profile per deployment target", async () => {
const files = (await readdir("config/runtime")).sort();
expect(files).toEqual(
[...DEPLOYMENT_TARGETS].map((target) => `${target}.json`).sort(),
);
for (const target of DEPLOYMENT_TARGETS) {
const source: unknown = JSON.parse(
await readFile(path.join("config/runtime", `${target}.json`), "utf8"),
);
const parsed = runtimeConfigV2ArtifactSchema.safeParse({
...(source as Record<string, unknown>),
BUILD_ID: "20260815.42",
RELEASE_ID: "r-1",
});
expect(parsed.success, `${target}: ${JSON.stringify(parsed.error?.issues)}`).toBe(
true,
);
expect((source as Record<string, unknown>)["APP_ENV"]).toBe(target);
}
});
it("produces an admissible document for every public target", async () => {
for (const target of ["staging", "production"] as const) {
const config = await generateRuntimeConfig(target, {
VITE_BUILD_ID: "20260815.42",
RELEASE_ID: "r-2026.08.15-1",
});
expect(
findAdmissionViolations(target, config as unknown as AdmissionInput),
).toEqual([]);
}
});
it("refuses a deployment override that would make the document unservable", async () => {
await expect(
generateRuntimeConfig("production", {
VITE_BUILD_ID: "20260815.42",
RELEASE_ID: "r-1",
RUNTIME_API_BASE_URL: "http://api.example.com/",
}),
).rejects.toThrow(/runtime config is invalid/u);
});
it("keeps a developer build from carrying a released identity by default", async () => {
const config = await generateRuntimeConfig("local", {});
expect(config["BUILD_ID"]).toBe("local-build");
expect(
findAdmissionViolations("production", config as unknown as AdmissionInput)
.length,
).toBeGreaterThan(0);
});
});
+70
View File
@@ -66,6 +66,76 @@ async function flushMicrotasks(): Promise<void> {
await Promise.resolve();
}
describe("runtime request deadline ceiling", () => {
/**
* `REQUEST_TIMEOUT_MS` was validated by the runtime config schema and then
* never handed to the V3 executor, so the deployment dial did nothing and
* every operation ran on its contract's own deadline. It is a ceiling: it may
* tighten an operation, never loosen one.
*/
async function settlesWithin(
contractDeadlineMs: number,
ceilingMs: number | undefined,
advanceMs: number,
): Promise<boolean> {
vi.useFakeTimers();
try {
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
...(ceilingMs === undefined ? {} : { requestDeadlineCeilingMs: ceilingMs }),
attachCredentials: () => ({ kind: "READY", headers: {} }),
// A request that only ever ends by being cut off, so what settles it is
// exactly the deadline under test.
fetcher: (_input, init) =>
new Promise((_resolve, reject) => {
const signal = (init as RequestInit | undefined)?.signal;
signal?.addEventListener(
"abort",
() => reject(new DOMException("aborted", "AbortError")),
{ once: true },
);
}),
});
let settled = false;
const pending = executor
.execute(operation({ deadlineMs: contractDeadlineMs }), {}, {
routeId: ROUTE_ID,
scope,
})
.then(
() => { settled = true; },
() => { settled = true; },
);
await vi.advanceTimersByTimeAsync(advanceMs);
await flushMicrotasks();
const observed = settled;
if (!observed) await vi.advanceTimersByTimeAsync(contractDeadlineMs + 1_000);
await pending;
return observed;
} finally {
vi.useRealTimers();
}
}
it("applies the tighter of the contract and deployment bounds", async () => {
await expect(settlesWithin(5_000, 500, 800)).resolves.toBe(true);
await expect(settlesWithin(5_000, undefined, 800)).resolves.toBe(false);
});
it("never extends a contract deadline", async () => {
await expect(settlesWithin(500, 60_000, 800)).resolves.toBe(true);
});
it("ignores a ceiling that is not a usable duration", async () => {
for (const ceiling of [0, -1, Number.NaN]) {
await expect(settlesWithin(5_000, ceiling, 800), String(ceiling)).resolves.toBe(
false,
);
}
});
});
describe("descriptor-driven HTTP execution lifetime", () => {
it("normalizes a read-side 429 to the non-applicable effect vocabulary", async () => {
const executor = createContractHttpExecutor({
+36
View File
@@ -242,6 +242,42 @@ describe("presigned transfer", () => {
);
});
it("answers a refused capability envelope with a re-issuable recovery", async () => {
// BT-PRE-04. A capability document the adapter will not accept is closed as
// `POLICY_REJECTED`, and the caller's only way forward is a new capability.
// `NONE` said there was nothing to be done, which contradicted both the
// design record for an unsupported protocol and the vault, which already
// answers `REISSUE_CAPABILITY` for the same class of refusal.
for (const [label, overrides] of [
["unknown protocol", { protocol: "PRESIGNED_TRANSFER_V2" }],
["missing protocol", { protocol: undefined }],
] as const) {
const bytes = new Uint8Array([1, 2, 3]);
const payload: Record<string, unknown> = {
...downloadCapabilityPayload(bytes),
...overrides,
};
if (overrides.protocol === undefined) delete payload["protocol"];
const fetcher = vi.fn(async () => jsonResponse(payload)) as unknown as typeof fetch;
const { provider } = createHarness({ fetcher });
expect(
await provider.issueDownload({
resourceId: "resource-1",
signal: new AbortController().signal,
}),
label,
).toMatchObject({
ok: false,
error: {
code: "POLICY_REJECTED",
retryable: false,
recovery: "REISSUE_CAPABILITY",
},
});
}
});
it("keeps URL and headers adapter-private and streams bounded verified chunks", async () => {
const bytes = new Uint8Array([1, 2, 3, 4, 5]);
const payload = downloadCapabilityPayload(bytes);
+222
View File
@@ -0,0 +1,222 @@
import { describe, expect, it } from "vitest";
import {
activeProductFeatureIds,
resolveProductFeatures,
selectCompiledProductFeatures,
} from "../../src/contracts/product-features.ts";
import { runtimeConfigV2ArtifactSchema } from "../../src/contracts/release-artifacts.ts";
import {
COMPILED_PRODUCT_FEATURE_IDS,
INSTALLED_PRODUCT_FEATURE_IDS,
} from "../../src/features/installed-product-manifest.ts";
import {
ROUTE_FEATURE_OWNER,
ROUTE_REGISTRY,
} from "../../src/features/installed-feature-contracts.ts";
const COMPILED = Object.freeze([
Object.freeze({ featureId: "reference-feature" }),
Object.freeze({ featureId: "billing" }),
]);
describe("build-time product selection", () => {
it("keeps everything when nothing is declared", () => {
for (const declared of [undefined, "", " "]) {
expect(
selectCompiledProductFeatures(COMPILED, declared).map((f) => f.featureId),
String(declared),
).toEqual(["reference-feature", "billing"]);
}
});
it("narrows to the declared subset", () => {
expect(
selectCompiledProductFeatures(COMPILED, "billing").map((f) => f.featureId),
).toEqual(["billing"]);
expect(
selectCompiledProductFeatures(COMPILED, " billing , reference-feature ").map(
(f) => f.featureId,
),
).toEqual(["reference-feature", "billing"]);
});
it("selects nothing only when asked explicitly", () => {
// A blank value keeps everything on purpose: an unset CI variable expands
// to a blank string, and that must not be how a build ships no features.
expect(selectCompiledProductFeatures(COMPILED, "none")).toEqual([]);
expect(selectCompiledProductFeatures(COMPILED, " none ")).toEqual([]);
expect(selectCompiledProductFeatures(COMPILED, "").length).toBe(2);
// A value that parses to no names at all is a typo, not an instruction.
expect(() => selectCompiledProductFeatures(COMPILED, ",")).toThrow(
/names no feature/u,
);
});
it("refuses to name a feature this build does not contain", () => {
// The whole point of the direction rule: an environment value can subtract
// from the source tree and must never be able to add to it. Accepting an
// unknown id silently would let a deployment believe it had switched on
// something that is not in the bundle.
expect(() => selectCompiledProductFeatures(COMPILED, "analytics")).toThrow(
/does not contain: analytics/u,
);
expect(() =>
selectCompiledProductFeatures(COMPILED, "billing,analytics"),
).toThrow(/analytics/u);
});
it("refuses a duplicated feature id in the manifest", () => {
expect(() =>
selectCompiledProductFeatures(
[{ featureId: "a" }, { featureId: "a" }],
undefined,
),
).toThrow(/duplicate product feature id/u);
});
});
describe("runtime product feature resolution", () => {
it("reports active, disabled and not-installed distinctly", () => {
const statuses = resolveProductFeatures(
["reference-feature", "billing"],
["reference-feature"],
{ "reference-feature": "DISABLED" },
);
expect(statuses).toEqual([
{ featureId: "billing", state: "NOT_INSTALLED" },
{ featureId: "reference-feature", state: "DISABLED_BY_CONFIG" },
]);
expect(activeProductFeatureIds(statuses)).toEqual([]);
});
it("cannot switch on a feature the build left out", () => {
// `DEFAULT` on an uninstalled feature is not an instruction to install it.
const statuses = resolveProductFeatures(["billing"], [], {
billing: "DEFAULT",
});
expect(statuses).toEqual([{ featureId: "billing", state: "NOT_INSTALLED" }]);
expect(activeProductFeatureIds(statuses)).toEqual([]);
});
it("ignores an override naming a feature this build never declared", () => {
// A shared runtime document may cover several builds, so a stale key is
// inert rather than fatal.
const statuses = resolveProductFeatures(
["reference-feature"],
["reference-feature"],
{ analytics: "DISABLED" },
);
expect(activeProductFeatureIds(statuses)).toEqual(["reference-feature"]);
});
it("leaves an installed feature active without an override", () => {
const statuses = resolveProductFeatures(
COMPILED_PRODUCT_FEATURE_IDS,
INSTALLED_PRODUCT_FEATURE_IDS,
);
expect(activeProductFeatureIds(statuses)).toEqual([
...INSTALLED_PRODUCT_FEATURE_IDS,
]);
});
});
describe("runtime config carries the switch", () => {
const base = {
APP_ENV: "local" as const,
API_BASE_URL: "http://localhost:8080/",
TELEMETRY_ENABLED: false,
AUTH_MODE: "demo" as const,
CONFIG_SCHEMA_VERSION: "2.0" as const,
RELEASE_MANIFEST_URL: "/release-manifest.json",
};
it("defaults to disabling nothing", () => {
const parsed = runtimeConfigV2ArtifactSchema.parse(base);
expect(parsed.FEATURE_OVERRIDES).toEqual({});
});
it("accepts only DEFAULT or DISABLED", () => {
expect(
runtimeConfigV2ArtifactSchema.parse({
...base,
FEATURE_OVERRIDES: { "reference-feature": "DISABLED" },
}).FEATURE_OVERRIDES,
).toEqual({ "reference-feature": "DISABLED" });
// There is no "ENABLED": the vocabulary itself is what makes the rule
// unbreakable, not a check somewhere downstream.
expect(
runtimeConfigV2ArtifactSchema.safeParse({
...base,
FEATURE_OVERRIDES: { "reference-feature": "ENABLED" },
}).success,
).toBe(false);
});
it("refuses a malformed feature id", () => {
for (const featureId of ["Reference", "reference_feature", "", "-x"]) {
expect(
runtimeConfigV2ArtifactSchema.safeParse({
...base,
FEATURE_OVERRIDES: { [featureId]: "DISABLED" },
}).success,
featureId,
).toBe(false);
}
});
});
describe("every installed registry consults the manifest", () => {
/**
* The manifest only means something if each registry actually asks it. A new
* registry that spreads a feature in directly would reintroduce exactly the
* coupling this file exists to remove, and nothing else would notice.
*/
it("gates every feature contribution on the selection", async () => {
const { readdir, readFile } = await import("node:fs/promises");
const nodePath = (await import("node:path")).default;
const root = "src/features";
const registries = (await readdir(root)).filter((entry) =>
/^installed-.*\.tsx?$/u.test(entry),
);
expect(registries.length).toBeGreaterThan(3);
const exempt = new Set([
// The manifest is the selection.
"installed-product-manifest.ts",
// Capabilities have their own §3.5 selection file and override vocabulary.
"installed-runtime-capabilities.ts",
// Message keys stay total on purpose; see the file for why.
"installed-feature-messages.ts",
]);
for (const registry of registries) {
if (exempt.has(registry)) continue;
const source = await readFile(nodePath.join(root, registry), "utf8");
expect(
/INSTALLED_PRODUCT_FEATURE(S|_IDS)/u.test(source),
`${registry} must compose from the product manifest`,
).toBe(true);
}
});
});
describe("route ownership", () => {
it("attributes every feature route to its feature and no platform route", () => {
for (const featureId of INSTALLED_PRODUCT_FEATURE_IDS) {
expect(Object.values(ROUTE_FEATURE_OWNER)).toContain(featureId);
}
// Platform routes have no owner, so disabling a feature can never withdraw
// the shell's own navigation.
for (const routeId of ["APP_HOME", "NOT_FOUND", "EXAMPLES_PLATFORM"]) {
expect(ROUTE_FEATURE_OWNER[routeId], routeId).toBeUndefined();
expect(Object.keys(ROUTE_REGISTRY)).toContain(routeId);
}
});
it("owns exactly the routes the registry received from features", () => {
const owned = Object.keys(ROUTE_FEATURE_OWNER);
expect(owned.length).toBeGreaterThan(0);
for (const routeId of owned) {
expect(Object.keys(ROUTE_REGISTRY), routeId).toContain(routeId);
}
});
});
@@ -18,6 +18,14 @@ import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
/**
* This suite's budget, not the file's. The 10s default is sized for pure-JS
* unit tests; these spawn processes, build archives and sign evidence, and on a
* machine running the rest of the suite in parallel they legitimately need
* longer. Raising the global default instead would hide a genuinely hung test.
*/
const PROCESS_HEAVY_TIMEOUT_MS = 30_000;
const roots: string[] = [];
afterEach(async () => {
@@ -699,7 +707,7 @@ describe("provider guardian transaction protocol", () => {
await expect(lstat(rawPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(lstat(sealedTempPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(readdir(evidenceRoot)).resolves.toEqual(["untrusted"]);
});
}, PROCESS_HEAVY_TIMEOUT_MS);
it("still publishes near the lease deadline when post-processing completes in time", async () => {
const { startProviderGuardian } = await import(
+1
View File
@@ -74,6 +74,7 @@ const runtimeV2 = {
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
},
FEATURE_OVERRIDES: {},
} as const satisfies RuntimeConfigArtifact;
async function releaseV2With(
+45 -1
View File
@@ -2,7 +2,7 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, expect, it } from "vitest";
import { afterEach, describe, expect, it } from "vitest";
import { loadCiGateContract } from "../../scripts/contracts/ci-gates.ts";
import {
@@ -88,3 +88,47 @@ it("rejects pruning that leaves a reduced gate without commands", async () => {
removedEvidencePathFragments: ["runtime-schema.xml"],
})).rejects.toThrow(/commandIds|too small|at least 1/i);
});
describe("release evidence fixture copy", () => {
it("keeps the release evidence and leaves the regenerated trees behind", async () => {
const { copyReleaseEvidenceTree, RELEASE_EVIDENCE_REGENERATED_TREES } =
await import("../../scripts/lib/removal-fixture.ts");
const { RELEASE_CANDIDATE_EVIDENCE_PATHS } = await import(
"../../scripts/lib/release-candidate.ts"
);
const { mkdtemp, access, readdir } = await import("node:fs/promises");
const { tmpdir } = await import("node:os");
const nodePath = (await import("node:path")).default;
const root = await mkdtemp(nodePath.join(tmpdir(), "release-evidence-"));
await copyReleaseEvidenceTree(process.cwd(), root);
// Every release artifact that exists here has to survive the copy; a
// fixture missing one cannot build a candidate at all, and every provider
// suite then fails while constructing its own fixture.
//
// Which ones exist depends on what this checkout has generated — a product
// repository that has not run the release chain has fewer than the template
// does — so the subject is preservation, not the presence of a full chain.
// Requiring at least one keeps that from quietly asserting nothing.
let preserved = 0;
for (const evidence of RELEASE_CANDIDATE_EVIDENCE_PATHS) {
if (!evidence.startsWith("artifacts/")) continue;
try {
await access(evidence);
} catch {
continue;
}
await expect(access(nodePath.join(root, evidence)), evidence).resolves.toBeUndefined();
preserved += 1;
}
expect(preserved).toBeGreaterThan(0);
// The regenerated trees are why this is a filter and not a plain copy: they
// are tens of megabytes of traces and coverage HTML. They still exist,
// because the repository inventory expects the directories.
for (const tree of RELEASE_EVIDENCE_REGENERATED_TREES) {
await expect(access(nodePath.join(root, tree)), tree).resolves.toBeUndefined();
await expect(readdir(nodePath.join(root, tree)), tree).resolves.toEqual([]);
}
});
});
+9 -1
View File
@@ -24,6 +24,14 @@ import {
type ProductionModuleInventory,
} from "../../scripts/lib/risk-coverage.ts";
/**
* This suite's budget, not the file's. The 10s default is sized for pure-JS
* unit tests; these spawn processes, build archives and sign evidence, and on a
* machine running the rest of the suite in parallel they legitimately need
* longer. Raising the global default instead would hide a genuinely hung test.
*/
const PROCESS_HEAVY_TIMEOUT_MS = 30_000;
const roots: string[] = [];
const now = Date.parse("2026-08-02T00:00:00.000Z");
const execFileAsync = promisify(execFile);
@@ -141,7 +149,7 @@ describe("repository-aware risk coverage", () => {
result.repositoryTotal - result.selectedTotal,
);
expect(result.status).toBe("FAIL");
});
}, PROCESS_HEAVY_TIMEOUT_MS);
it("reports exact inventory and generated-exclusion provenance", async () => {
const repositoryRoot = await repositoryFixture();
+2
View File
@@ -26,6 +26,7 @@ const runtime: Runtime = {
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
},
FEATURE_OVERRIDES: {},
},
configSchema: "V2",
build: {
@@ -262,6 +263,7 @@ describe("runtime adapter composition", () => {
...runtime.config.CAPABILITY_OVERRIDES,
SERVICE_WORKER: "DISABLED",
},
FEATURE_OVERRIDES: {},
},
},
release,
+9 -1
View File
@@ -47,6 +47,14 @@ import {
} from "../../scripts/lib/release-candidate.ts";
import { supplyChainDigest } from "../../scripts/lib/supply-chain.ts";
/**
* This suite's budget, not the file's. The 10s default is sized for pure-JS
* unit tests; these spawn processes, build archives and sign evidence, and on a
* machine running the rest of the suite in parallel they legitimately need
* longer. Raising the global default instead would hide a genuinely hung test.
*/
const PROCESS_HEAVY_TIMEOUT_MS = 30_000;
const digest = (value: string): string =>
createHash("sha256").update(value).digest("hex");
const digestBytes = (value: Buffer): string =>
@@ -1383,7 +1391,7 @@ describe("security follow-up contracts", () => {
} finally {
await rm(root, { recursive: true, force: true });
}
});
}, PROCESS_HEAVY_TIMEOUT_MS);
});
function providerExpectedContext() {
+6 -1
View File
@@ -14,6 +14,7 @@ import {
validateLicensePolicy,
} from "../../scripts/lib/supply-chain.ts";
import { digestReleaseInputFiles } from "../../scripts/lib/release-input-evidence.ts";
import { isReducedCiContractRun } from "../../scripts/contracts/ci-gates.ts";
import { findSecretMatches } from "../../scripts/lib/secret-scan.ts";
import {
parseSecretScanIncludedPaths,
@@ -535,7 +536,11 @@ describe("supply-chain policy", () => {
]);
});
it("covers every mandatory release input in the secret scan policy", async () => {
// A removal fixture deletes some of these inputs on purpose — removing the
// browser file/storage capability takes the whole browser-capability harness
// with it — and prunes them from its own policy. This is a claim about the
// full repository, so it does not describe a deliberately reduced one.
it.skipIf(isReducedCiContractRun())("covers every mandatory release input in the secret scan policy", async () => {
const policy = JSON.parse(
await readFile("config/security/secret-scan-policy.json", "utf8"),
) as { trackedRoots: string[] };
+42 -6
View File
@@ -6,6 +6,7 @@ import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
isReducedCiContractRun,
loadCiGateContract,
parseCiGateContract,
} from "../../scripts/contracts/ci-gates.ts";
@@ -70,8 +71,19 @@ describe("selective Task 3 contract closure", () => {
"--signal=SIGKILL",
unit,
]);
// `bwrap --args FD` stops parsing at the first non-option and never hands
// the remainder back, so a command placed in the args file is dropped and
// bubblewrap exits with its usage text. Refusing `--` in the option stream
// is what keeps that silent no-sandbox launch from returning.
expect(() =>
encodeProviderBwrapInput(
["--unshare-net", "--", "/usr/bin/prlimit"],
{ PROVIDER_COMMAND: command },
),
).toThrow(/terminate the option stream/u);
const frame = encodeProviderScopeFrame({
bwrapInput: Buffer.from("private-bwrap-vector\0"),
bwrapCommand: ["/usr/bin/prlimit", "--nofile=64:64", "--", "/bin/sh", "-eu", "-c", 'exec /bin/sh -eu -c "$PROVIDER_COMMAND"'],
reportPath: "/exact/report.json",
reportDev: 12,
reportIno: 34,
@@ -81,6 +93,27 @@ describe("selective Task 3 contract closure", () => {
Buffer.from("private-bwrap-vector\0").toString("base64"),
);
expect(launch.join("\0")).not.toContain("private-bwrap-vector");
// The command vector rides on real argv, so it must never be able to carry
// the secret that the args file exists to hide.
expect(frame.subarray(4).toString("utf8")).not.toContain(credential);
expect(() =>
encodeProviderScopeFrame({
bwrapInput: Buffer.from("x\0"),
bwrapCommand: [],
reportPath: "/exact/report.json",
reportDev: 12,
reportIno: 34,
}),
).toThrow(/bwrap command is invalid/u);
expect(() =>
encodeProviderScopeFrame({
bwrapInput: Buffer.from("x\0"),
bwrapCommand: ["prlimit"],
reportPath: "/exact/report.json",
reportDev: 12,
reportIno: 34,
}),
).toThrow(/bwrap command is invalid/u);
});
it("removes only the pinned raw inode during parent-loss cleanup", async () => {
@@ -143,12 +176,15 @@ describe("selective Task 3 contract closure", () => {
.toContain("package script missing: root -> missing");
});
it("accepts only the canonical exact-count authority and rejects orphan retention", async () => {
// A removal fixture runs against a pruned contract on purpose, so the
// canonical counts do not describe it. Asserting them there failed the
// fixture for the reduction it exists to demonstrate.
it.skipIf(isReducedCiContractRun())("accepts only the canonical exact-count authority and rejects orphan retention", async () => {
const canonical = await loadCiGateContract(process.cwd());
expect(canonical.gates).toHaveLength(26);
expect(canonical.commands).toHaveLength(81);
expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(93);
expect(canonical.artifacts).toHaveLength(105);
expect(canonical.gates).toHaveLength(27);
expect(canonical.commands).toHaveLength(82);
expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(94);
expect(canonical.artifacts).toHaveLength(107);
expect(canonical.stages).toHaveLength(5);
expect(canonical.retention.classes).toHaveLength(5);
@@ -157,7 +193,7 @@ describe("selective Task 3 contract closure", () => {
expect(() => parseCiGateContract(orphan)).toThrow(/five canonical retention|orphan retention/u);
});
it("rejects the retired validate-candidate-archive grammar", async () => {
it.skipIf(isReducedCiContractRun())("rejects the retired validate-candidate-archive grammar", async () => {
const canonical = JSON.parse(
JSON.stringify(await loadCiGateContract(process.cwd())),
) as Record<string, any>;