Merge branch 'main' into feature/techlog-ui-migration
Integrates the frontend template sync (a0fbafb → 5434760) into the TechLog UI migration. Merged in this direction so every conflict is resolved and proved in the worktree; main is only fast-forwarded afterwards and never holds a state that was not verified here. 15 conflicts. The rule throughout: keep the template's mechanism, keep the product's content, and never invent a third state neither branch would accept. The template's product manifest and its runtime feature kill switch are adopted. The route registry is deliberately not composed from contract.routes: the reference feature still declares screens this product deleted, and reducing over them would register paths with no component behind them. ROUTE_FEATURE_OWNER is narrowed to registered routes for the same reason. The first resolution did compose from contract.routes and was rejected by product-features.test.ts. Three files pinned counts and a digest describing the gate contract. Neither side's numbers describe the merged config/ci/gates.json, so they were recomputed from it rather than chosen: 27 gates, 82 commands, 94 command references, 107 evidence references, 128 artifacts, shape sha256 5063586d. README.md and docs/accessibility/manual-checklist.md now enumerate this product's 27 routes, which the template's own verify:documentation requires. product-feature-switch.test.tsx was rewritten around the invariant that still applies here — no registered route without a component — rather than deleted with the screens it used to exercise. docs/operations/template-merge-2026-08-17.md records every decision, the gate results, and the three follow-ups this merge deliberately did not decide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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,18 @@ 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(126);
|
||||
// Template merge. The template added one gate and one command to the
|
||||
// product's 26/81; the artifact set is the product's 126 plus the
|
||||
// template's 2.
|
||||
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(128);
|
||||
expect(canonical.stages).toHaveLength(5);
|
||||
expect(canonical.retention.classes).toHaveLength(5);
|
||||
|
||||
@@ -157,7 +196,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>;
|
||||
|
||||
Reference in New Issue
Block a user