chore: initialize from frontend template 4dc033c

This commit is contained in:
DongHyeonka
2026-08-13 18:23:26 +09:00
commit 40107eec84
897 changed files with 234824 additions and 0 deletions
@@ -0,0 +1,856 @@
# 플랫폼 구성 화면 완성 구현 계획
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** `/examples/platform` 화면이 정적 선택뿐 아니라 런타임 해석 결과까지 보여주고, 진입 경로와 브라우저 회귀 커버리지를 갖춘다.
**Architecture:** 능력 상태 파생은 `src/contracts`의 순수 함수가 소유한다. 합성 루트가 런타임 오버라이드를 그 함수에 넣어 경계된 스냅샷을 만들고, 애플리케이션 포트를 통해 표현 계층에 전달한다. 표현 계층은 bootstrap을 알지 못한 채 해석 결과를 읽는다.
**Tech Stack:** TypeScript 7 (strict), React 19, Zod 4, Vitest 4, Playwright 1.62
## Global Constraints
- `src/presentation``src/adapters``src/bootstrap`을 import 할 수 없다 (`presentation-does-not-know-adapters`).
- `src/application``src/presentation`, `src/adapters`, `src/bootstrap`, `react`, `@tanstack`을 import 할 수 없다.
- 모든 소스는 `.ts` / `.tsx`. `allowJs: false`.
- `src/presentation/{boundaries,components,design-system,forms,layouts,routes,templates}`에는 한글 리터럴을 둘 수 없다. `src/presentation/examples`는 대상이 아니다.
- i18n 카탈로그는 ko-KR / en-US / pseudo 키 집합이 완전히 일치해야 한다.
- 화면은 레지스트리에서 파생한 값만 렌더한다. 샘플 기능 이름을 하드코딩하면 `test:sample-removal`의 잔재 스캔이 실패한다.
- 각 태스크는 `corepack pnpm check:types`, `corepack pnpm lint`, `corepack pnpm check:architecture`를 통과해야 한다.
## 배경: 왜 이 작업이 남았나
`docs/superpowers/specs/2026-07-31-platform-overview-page-design.md` 5절이 런타임 해석 결과를 의도적으로 제외했다. 해석은 `createRuntimeComposition`에서 일어나고 그 결과가 애플리케이션 계층으로 전달되지 않기 때문이다. 그래서 화면은 "정적 선택 기준"이라고만 말할 수 있고, 운영자가 `CAPABILITY_OVERRIDES`로 능력을 껐는지는 화면에서 알 수 없다.
이 계획은 그 경로를 만든다. 오버라이드는 이미 선택된 능력을 끄는 것만 가능하므로, 스냅샷은 선택 수와 활성 수를 함께 실어 둘의 차이가 곧 오버라이드 효과가 되게 한다.
## 파일 구조
| 파일 | 책임 |
| --- | --- |
| `src/contracts/runtime-capabilities.ts` (수정) | 능력 스냅샷 파생 순수 함수를 추가한다. 기존 `resolveRuntimeCapabilities`와 같은 입력을 받는다. |
| `src/application/ports/runtime-capabilities-port.ts` (생성) | 애플리케이션이 능력 상태를 얻는 출력 포트. |
| `src/application/create-application.ts` (수정) | `runtime.getCapabilitySnapshot()`을 인바운드 API에 노출한다. |
| `src/application/ports/in/application-api.ts` (수정) | 인바운드 계약에 스냅샷 접근자를 선언한다. |
| `src/bootstrap/runtime-adapters.ts` (수정) | 런타임 설정의 오버라이드로 포트를 합성한다. |
| `src/presentation/examples/platform-overview-page.tsx` (수정) | 5번 섹션이 선택과 활성을 함께 표시한다. |
| `src/presentation/pages/home-page.tsx` (수정) | 플랫폼 구성 화면 진입점. |
| `tests/unit/runtime-capability-snapshot.test.ts` (생성) | 순수 함수 검증. |
| `tests/unit/runtime-adapters.test.ts` (수정) | 포트 합성 검증. |
| `tests/component/platform-overview-page.test.tsx` (수정) | 화면이 해석 결과를 표시하는지 검증. |
| `tests/e2e/platform-overview.spec.ts` (생성) | 실제 번들에서 파생 불변식 검증. |
| `tests/visual/platform.visual.spec.ts` (수정) | 시각 회귀 기준선. |
---
### Task 1: 능력 스냅샷 파생 계약
**Files:**
- Modify: `src/contracts/runtime-capabilities.ts`
- Test: `tests/unit/runtime-capability-snapshot.test.ts`
**Interfaces:**
- Consumes: 같은 파일의 `InstalledRuntimeCapabilities`, `CapabilityOverrideMap`, `RuntimeCapabilityOverride`
- Produces: `RuntimeCapabilityId`, `RuntimeCapabilityStatus`, `RuntimeCapabilitySnapshot`, `describeRuntimeCapabilities(installed, overrides)`
- [ ] **Step 1: 실패하는 테스트 작성**
`tests/unit/runtime-capability-snapshot.test.ts`:
```ts
import { describe, expect, it } from "vitest";
import {
describeRuntimeCapabilities,
type CapabilityOverrideMap,
type InstalledRuntimeCapabilities,
} from "../../src/contracts/runtime-capabilities.ts";
const DEFAULTS: CapabilityOverrideMap = Object.freeze({
REALTIME: "DEFAULT",
WEB_WORKER: "DEFAULT",
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
});
const EMPTY: InstalledRuntimeCapabilities = Object.freeze({
realtime: Object.freeze([]),
webWorkers: Object.freeze([]),
serviceWorker: null,
offlineCommands: null,
});
const SELECTED: InstalledRuntimeCapabilities = Object.freeze({
realtime: Object.freeze([]),
webWorkers: Object.freeze([]),
serviceWorker: Object.freeze({ mode: "ACTIVE" as const }),
offlineCommands: null,
});
describe("runtime capability snapshot", () => {
it("describes every capability id in a fixed order", () => {
const snapshot = describeRuntimeCapabilities(EMPTY, DEFAULTS);
expect(snapshot.map((status) => status.capabilityId)).toEqual([
"REALTIME",
"WEB_WORKER",
"SERVICE_WORKER",
"OFFLINE_COMMANDS",
]);
});
it("reports nothing selected and nothing active for an empty selection", () => {
const snapshot = describeRuntimeCapabilities(EMPTY, DEFAULTS);
for (const status of snapshot) {
expect(status.selected).toBe(0);
expect(status.active).toBe(0);
expect(status.override).toBe("DEFAULT");
}
});
it("keeps a selected capability active while the override is DEFAULT", () => {
const snapshot = describeRuntimeCapabilities(SELECTED, DEFAULTS);
const serviceWorker = snapshot.find(
(status) => status.capabilityId === "SERVICE_WORKER",
);
expect(serviceWorker).toMatchObject({
selected: 1,
active: 1,
override: "DEFAULT",
});
});
it("keeps a disabled capability selected but not active", () => {
const snapshot = describeRuntimeCapabilities(SELECTED, {
...DEFAULTS,
SERVICE_WORKER: "DISABLED",
});
const serviceWorker = snapshot.find(
(status) => status.capabilityId === "SERVICE_WORKER",
);
expect(serviceWorker).toMatchObject({
selected: 1,
active: 0,
override: "DISABLED",
});
});
it("cannot activate a capability that was never selected", () => {
const snapshot = describeRuntimeCapabilities(EMPTY, {
...DEFAULTS,
REALTIME: "DEFAULT",
});
const realtime = snapshot.find(
(status) => status.capabilityId === "REALTIME",
);
expect(realtime).toMatchObject({ selected: 0, active: 0 });
});
it("returns a frozen snapshot and frozen entries", () => {
const snapshot = describeRuntimeCapabilities(EMPTY, DEFAULTS);
expect(Object.isFrozen(snapshot)).toBe(true);
expect(snapshot.every((status) => Object.isFrozen(status))).toBe(true);
});
});
```
- [ ] **Step 2: 실패 확인**
실행: `corepack pnpm exec vitest run tests/unit/runtime-capability-snapshot.test.ts`
기대: `describeRuntimeCapabilities` is not exported 로 FAIL
- [ ] **Step 3: 최소 구현**
`src/contracts/runtime-capabilities.ts` 끝에 추가한다. `resolveRuntimeCapabilities`를 재사용해 활성 판정 규칙이 한 곳에만 존재하게 한다.
```ts
export type RuntimeCapabilityId =
| "REALTIME"
| "WEB_WORKER"
| "SERVICE_WORKER"
| "OFFLINE_COMMANDS";
/**
* §3.5. A bounded, serialisable view of what a capability is. `selected` is the
* static SSOT count and `active` is what survived the runtime override, so the
* difference between the two is exactly the operator's effect.
*/
export type RuntimeCapabilityStatus = Readonly<{
capabilityId: RuntimeCapabilityId;
selected: number;
active: number;
override: RuntimeCapabilityOverride;
}>;
export type RuntimeCapabilitySnapshot = readonly RuntimeCapabilityStatus[];
export function describeRuntimeCapabilities(
installed: InstalledRuntimeCapabilities,
overrides: CapabilityOverrideMap,
): RuntimeCapabilitySnapshot {
const resolved = resolveRuntimeCapabilities(installed, overrides);
const counts: Readonly<
Record<RuntimeCapabilityId, Readonly<{ selected: number; active: number }>>
> = {
REALTIME: {
selected: installed.realtime.length,
active: resolved.realtime.length,
},
WEB_WORKER: {
selected: installed.webWorkers.length,
active: resolved.webWorkers.length,
},
SERVICE_WORKER: {
selected: installed.serviceWorker === null ? 0 : 1,
active: resolved.serviceWorker === null ? 0 : 1,
},
OFFLINE_COMMANDS: {
selected: installed.offlineCommands === null ? 0 : 1,
active: resolved.offlineCommands === null ? 0 : 1,
},
};
return Object.freeze(
(
[
"REALTIME",
"WEB_WORKER",
"SERVICE_WORKER",
"OFFLINE_COMMANDS",
] as const
).map((capabilityId) =>
Object.freeze({
capabilityId,
selected: counts[capabilityId].selected,
active: counts[capabilityId].active,
override: overrides[capabilityId],
}),
),
);
}
```
- [ ] **Step 4: 통과 확인**
실행: `corepack pnpm exec vitest run tests/unit/runtime-capability-snapshot.test.ts`
기대: 6 passed
- [ ] **Step 5: 게이트와 커밋**
```bash
corepack pnpm check:types && corepack pnpm lint && corepack pnpm check:architecture
git add src/contracts/runtime-capabilities.ts tests/unit/runtime-capability-snapshot.test.ts
git commit -m "feat: derive a bounded runtime capability snapshot"
```
---
### Task 2: 능력 스냅샷을 애플리케이션 포트로 노출
**Files:**
- Create: `src/application/ports/runtime-capabilities-port.ts`
- Modify: `src/application/create-application.ts`
- Modify: `src/application/ports/in/application-api.ts`
- Modify: `src/bootstrap/runtime-adapters.ts`
- Modify: `tests/helpers/create-test-application.ts`
- Test: `tests/unit/runtime-adapters.test.ts`
**Interfaces:**
- Consumes: Task 1의 `describeRuntimeCapabilities`, `RuntimeCapabilitySnapshot`
- Produces: `RuntimeCapabilitiesPort` (`getSnapshot(): RuntimeCapabilitySnapshot`), `ApplicationApi.runtime.getCapabilitySnapshot(): RuntimeCapabilitySnapshot`
- [ ] **Step 1: 실패하는 테스트 작성**
`tests/unit/runtime-adapters.test.ts``describe` 블록 안에 추가한다.
```ts
it("reports the static selection when no capability override disables it", async () => {
const adapters = await createRuntimeAdapters({ runtime, release, host: {} });
const snapshot = adapters.outputPorts.runtimeCapabilities.getSnapshot();
expect(snapshot.map((status) => status.capabilityId)).toEqual([
"REALTIME",
"WEB_WORKER",
"SERVICE_WORKER",
"OFFLINE_COMMANDS",
]);
expect(snapshot.every((status) => status.override === "DEFAULT")).toBe(true);
});
it("carries a disabling override into the capability snapshot", async () => {
const adapters = await createRuntimeAdapters({
runtime: {
...runtime,
config: {
...runtime.config,
CAPABILITY_OVERRIDES: {
...runtime.config.CAPABILITY_OVERRIDES,
SERVICE_WORKER: "DISABLED",
},
},
},
release,
host: {},
});
const serviceWorker = adapters.outputPorts.runtimeCapabilities
.getSnapshot()
.find((status) => status.capabilityId === "SERVICE_WORKER");
expect(serviceWorker?.override).toBe("DISABLED");
expect(serviceWorker?.active).toBe(0);
});
```
- [ ] **Step 2: 실패 확인**
실행: `corepack pnpm exec vitest run tests/unit/runtime-adapters.test.ts`
기대: `runtimeCapabilities``outputPorts`에 없어 타입/런타임 FAIL
- [ ] **Step 3: 포트 정의**
`src/application/ports/runtime-capabilities-port.ts` 생성:
```ts
import type { RuntimeCapabilitySnapshot } from "../../contracts/runtime-capabilities.ts";
export type { RuntimeCapabilitySnapshot };
/**
* §3.5. The application reads capability state; it never resolves it. Only the
* composition root knows the runtime overrides.
*/
export type RuntimeCapabilitiesPort = Readonly<{
getSnapshot(): RuntimeCapabilitySnapshot;
}>;
```
- [ ] **Step 4: 출력 포트 등록과 인바운드 노출**
`src/application/create-application.ts``ApplicationOutputPorts`에 필드를 더하고, `runtime` 그룹에 접근자를 추가한다.
```ts
import type { RuntimeCapabilitiesPort } from "./ports/runtime-capabilities-port.ts";
```
`ApplicationOutputPorts` 안:
```ts
runtimeCapabilities: RuntimeCapabilitiesPort;
```
`runtime` 그룹 안, `getReleaseSummary` 다음:
```ts
getCapabilitySnapshot() {
return ports.runtimeCapabilities.getSnapshot();
},
```
`src/application/ports/in/application-api.ts``runtime` 그룹에 선언을 추가하고 타입을 import 한다.
```ts
import type { RuntimeCapabilitySnapshot } from "../runtime-capabilities-port.ts";
export type { RuntimeCapabilitySnapshot };
```
```ts
runtime: Readonly<{
getReleaseSummary(): Promise<ReleaseSummary>;
getCapabilitySnapshot(): RuntimeCapabilitySnapshot;
}>;
```
- [ ] **Step 5: 합성 루트에서 포트 생성**
`src/bootstrap/runtime-adapters.ts`에 import를 추가한다.
```ts
import {
describeRuntimeCapabilities,
} from "../contracts/runtime-capabilities.ts";
import { INSTALLED_RUNTIME_CAPABILITIES } from "../features/installed-runtime-capabilities.ts";
```
`releaseInfo` 정의 다음에 포트를 만들고 `outputPorts`에 넣는다.
```ts
const runtimeCapabilities = Object.freeze({
getSnapshot() {
return describeRuntimeCapabilities(
INSTALLED_RUNTIME_CAPABILITIES,
config.CAPABILITY_OVERRIDES,
);
},
});
```
- [ ] **Step 6: 테스트 헬퍼 기본값**
`tests/helpers/create-test-application.ts``createApplication` 첫 인자에 추가한다.
```ts
runtimeCapabilities:
overrides.runtimeCapabilities ??
{
getSnapshot: () =>
Object.freeze([
Object.freeze({
capabilityId: "REALTIME" as const,
selected: 0,
active: 0,
override: "DEFAULT" as const,
}),
Object.freeze({
capabilityId: "WEB_WORKER" as const,
selected: 0,
active: 0,
override: "DEFAULT" as const,
}),
Object.freeze({
capabilityId: "SERVICE_WORKER" as const,
selected: 0,
active: 0,
override: "DEFAULT" as const,
}),
Object.freeze({
capabilityId: "OFFLINE_COMMANDS" as const,
selected: 0,
active: 0,
override: "DEFAULT" as const,
}),
]),
},
```
- [ ] **Step 7: 통과 확인**
실행: `corepack pnpm exec vitest run tests/unit/runtime-adapters.test.ts`
기대: 9 passed
- [ ] **Step 8: 게이트와 커밋**
```bash
corepack pnpm check:types && corepack pnpm lint && corepack pnpm check:architecture && corepack pnpm test:all
git add src/application src/bootstrap/runtime-adapters.ts tests/helpers/create-test-application.ts tests/unit/runtime-adapters.test.ts
git commit -m "feat: expose the resolved runtime capability snapshot through the application port"
```
---
### Task 3: 화면이 선택과 활성을 함께 보여준다
**Files:**
- Modify: `src/presentation/examples/platform-overview-page.tsx`
- Test: `tests/component/platform-overview-page.test.tsx`
**Interfaces:**
- Consumes: Task 2의 `useApplication().runtime.getCapabilitySnapshot()`
- Produces: 없음 (화면 종단)
- [ ] **Step 1: 실패하는 테스트 작성**
`tests/component/platform-overview-page.test.tsx`의 기존 능력 테스트를 다음 두 개로 교체한다.
```ts
it("labels a capability that was never selected as unselected", () => {
renderPage();
const region = screen.getByRole("region", { name: "선택적 런타임 능력" });
expect(within(region).getAllByText("미선택")).toHaveLength(4);
expect(within(region).queryByText("운영자가 비활성화함")).toBeNull();
});
it("separates an operator disable from a capability that was never selected", () => {
render(
<ApplicationProvider
application={createTestApplication({
runtimeCapabilities: {
getSnapshot: () =>
Object.freeze([
Object.freeze({
capabilityId: "REALTIME" as const,
selected: 0,
active: 0,
override: "DEFAULT" as const,
}),
Object.freeze({
capabilityId: "WEB_WORKER" as const,
selected: 0,
active: 0,
override: "DEFAULT" as const,
}),
Object.freeze({
capabilityId: "SERVICE_WORKER" as const,
selected: 1,
active: 0,
override: "DISABLED" as const,
}),
Object.freeze({
capabilityId: "OFFLINE_COMMANDS" as const,
selected: 1,
active: 1,
override: "DEFAULT" as const,
}),
]),
},
})}
>
<LocaleProvider>
<PlatformOverviewPage />
</LocaleProvider>
</ApplicationProvider>,
);
const region = screen.getByRole("region", { name: "선택적 런타임 능력" });
expect(within(region).getByText("운영자가 비활성화함")).toBeVisible();
expect(within(region).getByText("활성 (1)")).toBeVisible();
expect(within(region).getAllByText("미선택")).toHaveLength(2);
});
```
- [ ] **Step 2: 실패 확인**
실행: `corepack pnpm exec vitest run tests/component/platform-overview-page.test.tsx`
기대: "운영자가 비활성화함" 을 찾지 못해 FAIL
- [ ] **Step 3: 화면 구현**
`src/presentation/examples/platform-overview-page.tsx`에서 `INSTALLED_RUNTIME_CAPABILITIES` import를 제거하고 스냅샷 소비로 바꾼다.
능력 설명은 정적 표이므로 id로 찾는다.
```ts
const CAPABILITY_COPY: Readonly<Record<RuntimeCapabilityId, Readonly<{
label: string;
description: string;
}>>> = Object.freeze({
REALTIME: Object.freeze({
label: "실시간 수신",
description:
"WebSocket, SSE, 경계 폴링 런타임은 구현되어 있습니다. 제품 기여물이 엔드포인트와 이벤트 서술자를 제공해야 설치됩니다.",
}),
WEB_WORKER: Object.freeze({
label: "웹 워커",
description:
"워커 실행 계약과 전용 타입 프로젝트가 준비되어 있습니다. 프로파일링으로 확인된 CPU 작업이 있어야 설치됩니다.",
}),
SERVICE_WORKER: Object.freeze({
label: "서비스 워커",
description:
"참조 런타임과 두 단계 빌드가 준비되어 있습니다. 설치하면 검증된 정적 자산 캐시와 등록 해제 경로가 함께 켜집니다.",
}),
OFFLINE_COMMANDS: Object.freeze({
label: "오프라인 명령",
description:
"명령 큐 상태 기계가 준비되어 있습니다. 복구 서술자를 가진 KEYED 오퍼레이션이 있어야 설치됩니다.",
}),
});
function capabilityBadge(
status: RuntimeCapabilityStatus,
): Readonly<{ text: string; variant: "success" | "warning" | "neutral" }> {
if (status.selected === 0) {
return { text: "미선택", variant: "neutral" };
}
if (status.active === 0) {
return { text: "운영자가 비활성화함", variant: "warning" };
}
return { text: `활성 (${status.active})`, variant: "success" };
}
```
컴포넌트 본문에서:
```ts
const capabilities = runtime.getCapabilitySnapshot();
const activeCapabilityCount = capabilities.filter(
(status) => status.active > 0,
).length;
```
카드 렌더링을 바꾼다.
```tsx
{capabilities.map((status) => {
const copy = CAPABILITY_COPY[status.capabilityId];
const badge = capabilityBadge(status);
return (
<Card
key={status.capabilityId}
title={copy.label}
footer={<Badge variant={badge.variant}>{badge.text}</Badge>}
>
<p>{copy.description}</p>
</Card>
);
})}
```
설치 요약의 능력 지표 힌트를 `"런타임 오버라이드 반영"`으로 바꾸고, 5번 섹션 설명 마지막 문장을 다음으로 교체한다.
```
여기 표시되는 상태는 정적 선택에 런타임 오버라이드를 적용한 결과입니다.
```
- [ ] **Step 4: 통과 확인**
실행: `corepack pnpm exec vitest run tests/component/platform-overview-page.test.tsx`
기대: 7 passed
- [ ] **Step 5: 게이트와 커밋**
```bash
corepack pnpm check:types && corepack pnpm lint && corepack pnpm check:i18n && corepack pnpm check:design-system && corepack pnpm test:all
git add src/presentation/examples/platform-overview-page.tsx tests/component/platform-overview-page.test.tsx
git commit -m "feat: show resolved capability state on the platform overview"
```
---
### Task 4: 홈에서 플랫폼 구성 화면으로 가는 진입점
**Files:**
- Modify: `src/presentation/pages/home-page.tsx`
- Test: `tests/component/router.test.tsx`
**Interfaces:**
- Consumes: `routePath("EXAMPLES_PLATFORM")`
- Produces: 없음
- [ ] **Step 1: 실패하는 테스트 작성**
`tests/component/router.test.tsx`에 추가한다.
```ts
it("reaches the platform overview from the home starter actions", async () => {
const user = userEvent.setup();
window.history.pushState({}, "", "/");
renderRouter();
await user.click(
await screen.findByRole("link", { name: "플랫폼 구성 보기" }),
);
await waitFor(() =>
expect(
screen.getByRole("heading", { name: "플랫폼 구성", level: 1 }),
).toHaveFocus(),
);
});
```
- [ ] **Step 2: 실패 확인**
실행: `corepack pnpm exec vitest run tests/component/router.test.tsx`
기대: "플랫폼 구성 보기" 링크가 없어 FAIL
- [ ] **Step 3: 구현**
`src/presentation/pages/home-page.tsx``button-row`에 첫 번째 링크로 추가한다.
```tsx
<Link className="ui-button" to={routePath("EXAMPLES_PLATFORM")}>
</Link>
```
기존 "UI 구성요소 보기" 링크의 클래스를 `ui-button ui-button--secondary`로 바꾸어 주 행동이 하나가 되게 한다.
- [ ] **Step 4: 통과 확인**
실행: `corepack pnpm exec vitest run tests/component/router.test.tsx`
기대: 4 passed
`tests/e2e/accessibility.spec.ts`가 홈의 첫 탭 대상을 `"UI 구성요소 보기"`로 단언하므로 함께 바꾼다.
```ts
const action = page.getByRole("link", { name: "플랫폼 구성 보기" });
```
- [ ] **Step 5: 게이트와 커밋**
```bash
corepack pnpm check:types && corepack pnpm lint && corepack pnpm test:component
git add src/presentation/pages/home-page.tsx tests/component/router.test.tsx tests/e2e/accessibility.spec.ts
git commit -m "feat: link the platform overview from the home starter actions"
```
---
### Task 5: 브라우저 회귀 커버리지
**Files:**
- Create: `tests/e2e/platform-overview.spec.ts`
- Modify: `tests/visual/platform.visual.spec.ts`
- Create: `tests/visual/__snapshots__/platform.visual.spec.ts-snapshots/platform-overview-light-chromium-visual-linux.png`
**Interfaces:**
- Consumes: 빌드된 번들의 `/examples/platform`
- Produces: 없음
**전제:** Playwright 브라우저가 설치되어 있어야 한다. 없으면 `corepack pnpm exec playwright install chromium`을 먼저 실행한다. 설치가 불가능한 환경이면 이 태스크를 건너뛰고 그 사실을 보고한다 — 기준선 없이 스냅샷 테스트를 커밋하면 안 된다.
- [ ] **Step 1: e2e 스펙 작성**
`tests/e2e/platform-overview.spec.ts`:
```ts
import { expect, test } from "../support/browser/strict-browser-test.ts";
import { ROUTE_REGISTRY } from "../../src/features/installed-feature-contracts.ts";
import { SERVER_STATE_PROFILES } from "../../src/contracts/server-state.ts";
test("projects the installed route registry into the shipped bundle", async ({
page,
}) => {
await page.goto("/examples/platform");
await expect(
page.getByRole("heading", { name: "플랫폼 구성", level: 1 }),
).toBeVisible();
const table = page.getByRole("table", { name: "설치된 라우트 목록" });
await expect(table.locator("tbody tr")).toHaveCount(
Object.keys(ROUTE_REGISTRY).length,
);
for (const routeId of Object.keys(ROUTE_REGISTRY)) {
await expect(table.getByText(routeId, { exact: true })).toBeVisible();
}
});
test("shows every fixed server-state profile with its budget", async ({
page,
}) => {
await page.goto("/examples/platform");
const table = page.getByRole("table", { name: "서버 상태 프로파일" });
await expect(table.locator("tbody tr")).toHaveCount(
Object.keys(SERVER_STATE_PROFILES).length,
);
});
test("states the release contract identity verified at boot", async ({
page,
}) => {
await page.goto("/examples/platform");
const release = page.locator(
"section[aria-labelledby='platform-release-title']",
);
await expect(release.getByText(/^sha256:/)).toBeVisible();
});
```
- [ ] **Step 2: e2e 실행**
실행: `corepack pnpm exec playwright test tests/e2e/platform-overview.spec.ts --project=chromium`
기대: 3 passed
- [ ] **Step 3: 시각 회귀 케이스 추가**
`tests/visual/platform.visual.spec.ts` 끝에 추가한다.
```ts
test("platform composition overview visual contract", async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 1100 });
await page.goto("/examples/platform");
await expect(
page.locator("section[aria-labelledby='platform-capabilities-title']"),
).toBeVisible();
await expect(page.getByRole("main")).toHaveScreenshot(
"platform-overview-light.png",
);
});
```
- [ ] **Step 4: 기준선 생성과 검증**
```bash
corepack pnpm test:visual:update
corepack pnpm test:visual
```
기대: 5 passed, 새 PNG 기준선이 `tests/visual/__snapshots__/platform.visual.spec.ts-snapshots/`에 생긴다.
- [ ] **Step 5: 커밋**
```bash
git add tests/e2e/platform-overview.spec.ts tests/visual
git commit -m "test: cover the platform overview with browser and visual regression"
```
---
### Task 6: 최종 검증과 문서 갱신
**Files:**
- Modify: `docs/superpowers/specs/2026-07-31-platform-overview-page-design.md`
- [ ] **Step 1: 설계 문서의 제외 항목 갱신**
5절 "의도적 제외"를 다음으로 교체한다.
```markdown
## 5. 런타임 해석 결과
초판은 런타임 오버라이드 해석 결과를 제외했다. 해석이 bootstrap에서 일어나고 표현 계층이 그 계층을
import 할 수 없었기 때문이다.
이후 `RuntimeCapabilitiesPort`가 추가되어 합성 루트가 경계된 스냅샷을 애플리케이션으로 전달한다.
화면은 정적 선택 수와 활성 수를 함께 표시하므로, 운영자가 능력을 껐다는 사실과 애초에 선택되지
않았다는 사실이 구분된다.
```
- [ ] **Step 2: 전체 게이트**
```bash
corepack pnpm check:types
corepack pnpm lint
corepack pnpm check:architecture
corepack pnpm test:all
corepack pnpm test:coverage
corepack pnpm check:registries
corepack pnpm check:i18n
corepack pnpm check:design-system
corepack pnpm check:diagnostics
corepack pnpm check:ci
corepack pnpm check:test-evidence:source
corepack pnpm verify:compatibility
corepack pnpm build
```
기대: 전부 PASS
- [ ] **Step 3: 제거 하네스 4종**
```bash
corepack pnpm test:sample-removal
corepack pnpm test:optional-recipe-removal
corepack pnpm test:browser-file-storage-removal
corepack pnpm test:realtime-removal
```
기대: 전부 PASS. 특히 `test:sample-removal`은 화면이 여전히 파생 전용인지 확인한다.
- [ ] **Step 4: 커밋**
```bash
git add docs/superpowers/specs/2026-07-31-platform-overview-page-design.md
git commit -m "docs: record the resolved capability path in the overview design"
```
## Self-Review
**Spec coverage:** 설계 문서 5절(의도적 제외)이 Task 1–3으로 닫힌다. 6절 변경 파일 목록은 이미 반영됐다. 7절 검증은 Task 6이 수행한다. 진입 경로와 브라우저 커버리지는 초판 범위 밖이었으므로 Task 4–5로 추가했다.
**Placeholder scan:** TBD 없음. 모든 코드 단계에 실제 코드가 있다.
**Type consistency:** `describeRuntimeCapabilities`(Task 1) → `RuntimeCapabilitiesPort.getSnapshot`(Task 2) → `runtime.getCapabilitySnapshot()`(Task 3)로 이름과 반환 타입이 일치한다. `RuntimeCapabilityStatus`의 필드명 `capabilityId` / `selected` / `active` / `override`가 세 태스크에서 동일하다.
@@ -0,0 +1,158 @@
# HTTP Worker and Adapter Remediation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Remove duplicate HTTP authorities, harden Service Worker activation bounds and identity, and decompose large browser adapters only after shared golden behavior is locked.
**Architecture:** Installed contract contributions are the HTTP source of truth and expose provider-neutral typed outcomes. Worker protocol V2 exchanges a canonical full-identity digest. Browser adapter facades remain stable while shared persisted schemas and cohesive internal modules are extracted.
**Tech Stack:** TypeScript 7, Fetch API, Service Worker API, IndexedDB, OPFS, React 19, Vitest 4, Playwright.
## Global Constraints
- Contract and application layers never import concrete adapter outcome types.
- Production exports accept only `BoundQuery` and `BoundMutation` after migration.
- Runtime timeout is a global ceiling applied over descriptor deadlines.
- Worker marker reads are bounded even without `Content-Length` and cancel oversized/non-terminating streams.
- Public adapter facades and product-default optional capability selection remain unchanged.
- Extraction follows characterization tests; file length alone does not justify a split.
---
### Task 1: Installed HTTP contract as single source of truth
**Files:**
- Modify: `src/features/reference-feature/contracts/reference-feature-contract.ts`
- Modify: `src/features/reference-feature/contracts/reference-schemas.ts`
- Modify: `src/contracts/external-contract-runtime.ts`
- Modify: `src/contracts/api-operations.ts`
- Modify: `src/contracts/rest-profiles.ts`
- Modify: `src/contracts/schema-registry.ts`
- Modify: `src/features/installed-feature-contracts.ts`
- Modify: `src/bootstrap/runtime-adapters.ts`
- Modify: `tests/features/reference-feature/reference-contract.test.ts`
- Modify: `tests/runtime-schema/http-schema.test.ts`
- Modify: `tests/integration/http-execution-contract.test.ts`
- Modify: `tests/unit/http-execution-v3.test.ts`
- [ ] Add parity tests showing method/path/validators/retry/effect/deadline/byte bounds come from one contribution; `createdAt` accepts omitted or RFC3339 datetime and rejects arbitrary strings.
- [ ] Run focused tests and confirm RED on duplicated descriptors and permissive date schema.
- [ ] Make the installed contribution authoritative; generate temporary legacy views from it and migrate all production callers before deleting the legacy registries/codecs.
- [ ] Apply `REQUEST_TIMEOUT_MS` as `min(runtimeCeiling, descriptorDeadline)` without replacing shorter descriptor deadlines.
- [ ] Keep the deadline authoritative through response admission and body reads: a deadline-owned abort after headers must still return `TRANSPORT_FAILURE / TIMEOUT`, perform one attempt, and never enter retry sleep instead of being masked as `RESPONSE_STREAM_FAILURE`.
- [ ] Treat credential resolution as a credential-only patch boundary: reject attempts to inject or overwrite `Idempotency-Key` (including case variants) or transport/query authority, and cover hostile patches in the executor regression table.
- [ ] Prove diagnostic privacy against the full runtime seam with non-empty identity references; assertions over an empty diagnostic projection do not count as evidence that intent/key/query identity is absent.
- [ ] Re-run focused tests, prove `rg` has zero production callers of removed registries, and commit with `git commit -m "refactor: consolidate installed HTTP contracts"`.
### Task 2: Provider-neutral typed operation outcomes
**Files:**
- Create: `src/contracts/operation-outcome.ts`
- Create: `src/application/ports/contract-operation-executor.ts`
- Modify: `src/features/reference-feature/adapters/reference-http-gateway.ts`
- Modify: `src/bootstrap/runtime-adapters.ts`
- Modify: `src/adapters/http/http-execution-v3.ts`
- Modify: `tests/features/reference-feature/reference-contract.test.ts`
- Modify: `tests/unit/runtime-adapters.test.ts`
- [ ] Add compile/runtime tests that unknown operation IDs and mismatched input/output types fail, and that the feature gateway has no import from `src/adapters/http`.
- [ ] Run focused tests/typecheck and confirm RED because the port is `operationId: string`, `input: unknown`, and concrete `HttpExecutionOutcome` leaks inward.
- [ ] Derive `InstalledOperationMap` from installed contracts, expose generic `execute<K extends keyof Map>(operationId: K, input: Map[K]["input"], context)` and map HTTP outcomes to provider-neutral contract outcomes at the adapter boundary.
- [ ] Re-run tests, typecheck, and architecture; commit with `git commit -m "refactor: type installed contract operations"`.
### Task 3: Bound-only server-state exports
**Files:**
- Modify: `src/presentation/adapters/query/application-query.ts`
- Modify: `src/presentation/adapters/query/index.ts`
- Modify: `src/features/reference-feature/presentation/use-reference-feature.ts`
- Create: `tests/helpers/legacy-application-query-harness.tsx`
- Modify: `tests/component/application-query.test.tsx`
- [ ] Add type tests that production hooks reject raw query keys and raw mutation executors while bound definitions still compile.
- [ ] Run typecheck and confirm current overloads accept raw forms.
- [ ] Move legacy raw harness behavior under `tests/helpers`; remove `LegacyMutationOptions` and the raw query union from production exports; migrate feature callers to `bindQuery`/bound mutations.
- [ ] Run focused component tests and typecheck; commit with `git commit -m "refactor: expose bound server-state hooks only"`.
### Task 4: Bounded Service Worker marker reader
**Files:**
- Create: `src/adapters/service-worker/bounded-worker-response.ts`
- Modify: `src/adapters/service-worker/service-worker-lifecycle.ts`
- Modify: `tests/unit/service-worker-runtime.test.ts`
- [ ] Add tests for oversized declared length, headerless oversized chunks, invalid UTF-8, malformed JSON, and a non-terminating stream. Assert reader cancellation and bounded completion.
- [ ] Run `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` and confirm RED because lifecycle calls `response.text()`.
- [ ] Implement realm-safe stream reads up to `maxBytes + 1`, fatal `TextDecoder`, explicit cancellation, and strict marker parsing. Never call `Response.text()` for protocol data.
- [ ] Re-run tests and commit with `git commit -m "fix: bound Service Worker activation markers"`.
### Task 5: Service Worker protocol V2 full identity
**Files:**
- Modify: `src/contracts/service-worker.ts`
- Modify: `src/adapters/service-worker/service-worker-protocol.ts`
- Modify: `src/adapters/service-worker/service-worker-entry.ts`
- Modify: `src/adapters/service-worker/service-worker-lifecycle.ts`
- Modify: `src/adapters/service-worker/service-worker-page-controller.ts`
- Modify: `src/bootstrap/register-service-worker.ts`
- Modify: `scripts/generate-build-manifest.ts`
- Modify: `tests/unit/service-worker-runtime.test.ts`
- Modify: `tests/unit/service-worker-build-input.test.ts`
- [ ] Add a tuple-mutation table for protocol/cache schema/build/release/contract/static set; each mutation must change the digest and reject activation. Add a valid ACTIVE fixture build that recomputes static set digest from asset entries.
- [ ] Run focused tests and confirm RED because protocol V1 compares partial fields.
- [ ] Set `SERVICE_WORKER_PROTOCOL_VERSION = 2`, define canonical sorted identity serialization, compute SHA-256 over every identity field, and exchange/validate the digest on every page-worker message.
- [ ] Keep default capability selection `null`; use ACTIVE only in the explicit fixture build.
- [ ] Re-run focused tests and the supported fixture build; commit with `git commit -m "fix: bind Service Worker activation to full identity"`.
### Task 6: Shared IndexedDB persisted-row schema
**Files:**
- Create: `src/adapters/storage/indexeddb/indexeddb-persisted-schema.ts`
- Modify: `src/adapters/storage/indexeddb/indexeddb-types.ts`
- Modify: `src/adapters/storage/indexeddb/indexeddb-runtime.ts`
- Modify: `src/adapters/storage/indexeddb/indexeddb-maintenance.ts`
- Create: `tests/fixtures/indexeddb/persisted-rows.ts`
- Modify: `tests/unit/indexeddb-runtime.test.ts`
- Modify: `tests/unit/indexeddb-maintenance.test.ts`
- [ ] Before extraction, run the same accepted/rejected record, receipt, retention, and budget golden rows through runtime and maintenance and assert identical verdicts.
- [ ] Confirm RED on at least one drift fixture using the duplicate current guards.
- [ ] Move persisted types/guards into the shared module; runtime and maintenance import it without behavior changes.
- [ ] Re-run both large suites and commit with `git commit -m "refactor: share IndexedDB persisted schemas"`.
### Task 7: Cohesive browser adapter decomposition
**Files:**
- Modify: `src/adapters/storage/opfs/opfs-worker-runtime.ts`
- Create: `src/adapters/storage/opfs/opfs-worker-bootstrap.ts`
- Create: `src/adapters/storage/opfs/opfs-worker-message-host.ts`
- Create: `src/adapters/storage/opfs/opfs-worker-core.ts`
- Create: `src/adapters/storage/opfs/opfs-worker-lock.ts`
- Create: `src/adapters/storage/opfs/opfs-physical-io.ts`
- Modify: `src/adapters/cache-storage/public-response-cache-adapter.ts`
- Create: `src/adapters/cache-storage/public-cache-manifest.ts`
- Create: `src/adapters/cache-storage/cache-lock.ts`
- Modify: `src/adapters/browser-files/download-delivery-adapter.ts`
- Create: `src/adapters/browser-files/download-browser-managed.ts`
- Create: `src/adapters/browser-files/download-picker-stream.ts`
- Create: `src/adapters/browser-files/download-object-url.ts`
- Modify: `tests/unit/opfs-worker-runtime.test.ts`
- Modify: `tests/unit/public-response-cache.test.ts`
- Modify: `tests/unit/browser-file-download.test.ts`
- [ ] Add golden facade tests for all success/failure/cancellation/lock-loss branches before moving code; snapshot externally observable operation order and error kinds.
- [ ] Run the three focused suites and capture GREEN characterization evidence.
- [ ] Extract OPFS bootstrap, host, core state machine, Web Lock, and physical I/O without changing public exports. Do not split the core state machine further.
- [ ] Extract public-cache manifest codec/digest and generic lock logic behind the same facade.
- [ ] Extract browser-managed, picker streaming, and object-URL download strategies behind the same delivery facade.
- [ ] Re-run the same golden suites after each extraction. Any failure is a refactor regression, not a fixture update.
- [ ] Commit each adapter independently with `refactor: decompose OPFS worker adapter`, `refactor: extract public cache internals`, and `refactor: extract download delivery strategies`.
### Task 8: HTTP/worker/adapter verification
- [ ] Run all focused tests named in Tasks 17.
- [ ] Run `corepack pnpm check:architecture`, `corepack pnpm check:types`, and `corepack pnpm lint`.
- [ ] Run `corepack pnpm test:all`.
- [ ] Run supported Service Worker, IndexedDB, OPFS, public-cache, and download Playwright capability specs.
- [ ] Run `git diff --check` and report unsupported browser gates without claiming success.
@@ -0,0 +1,124 @@
# Quality and Architecture Remediation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make coverage, scenario, CI, and architecture gates measure executable production behavior and fail when their measured universe is empty or incomplete.
**Architecture:** One typed gate schema drives both the local runner and generated workflow. Coverage separates repository inventory from instrumented totals. The Babel/resolver graph is the sole architecture authority while TypeScript 7 is unsupported by dependency-cruiser.
**Tech Stack:** TypeScript 7, Node.js 24, Babel parser, Vitest 4, Playwright, Gitea Actions.
## Global Constraints
- A zero-file or zero-module result is failure, never success.
- High-risk changed modules need explicit coverage ownership or an owned, expiring waiver.
- Scenario declarations count only when a table-driven test executes all required assertions.
- Checked-in workflow content is generated deterministically from the same gate model used locally.
- Every enforcement change begins with a failing fixture.
---
### Task 1: Repository-aware risk coverage
**Files:**
- Modify: `vitest.config.ts`
- Modify: `config/testing/risk-coverage.json`
- Modify: `scripts/check-risk-coverage.ts`
- Modify: `tests/fixtures/coverage/below-threshold.json`
- Create: `tests/fixtures/coverage/repository-omission.json`
- Create: `tests/unit/risk-coverage.test.ts`
- [ ] Add tests asserting `selectedTotal`, `repositoryTotal`, uncovered repository modules, and changed high-risk ownership. A summary covering 14 files while production inventory is larger must fail.
- [ ] Run `corepack pnpm exec vitest run tests/unit/risk-coverage.test.ts` and confirm RED because only selected totals exist.
- [ ] Enumerate every production `.ts`/`.tsx` module under `src`, exclude declarations/stories/generated files explicitly, and emit both totals. Expand coverage instrumentation to `src/**/*.{ts,tsx}` with documented exclusions.
- [ ] Seed the critical registry with HTTP V3, bounded request/response readers, boot bounds, Service Worker lifecycle, scope generation, and release loading. Validate waiver owner, reason, and future expiry.
- [ ] Run focused tests and `corepack pnpm test:coverage`; commit with `git commit -m "fix: measure repository-wide risk coverage"`.
### Task 2: Executable HTTP scenario catalog
**Files:**
- Modify: `tests/mocks/scenarios/catalog.ts`
- Create: `tests/integration/http-scenario-catalog.test.ts`
- Modify: `tests/mocks/handlers/reference-resources.ts`
- Modify: `scripts/check-test-evidence.ts`
- Modify: `config/testing/test-evidence.json`
- [ ] Define typed expectations for status/outcome/effect/retry/fetch count/media type/body bound/scope fence for every declared scenario.
- [ ] Add a table-driven test that executes each operation/scenario pair through `ContractHttpExecutor` and asserts every expectation field. Add a deliberately declared-but-unexecuted fixture and make the evidence checker reject it.
- [ ] Run `corepack pnpm exec vitest run tests/integration/http-scenario-catalog.test.ts && node scripts/check-test-evidence.ts` and confirm RED because the current gate counts source tokens.
- [ ] Export execution receipts from the test artifact and make the checker compare exact catalog IDs to exact executed IDs; source-token counts become diagnostics only.
- [ ] Re-run tests/checker and commit with `git commit -m "test: execute the HTTP scenario catalog"`.
### Task 3: Shared CI gate schema and deterministic workflow generation
**Files:**
- Create: `scripts/contracts/ci-gates.ts`
- Create: `scripts/generate-ci-workflow.ts`
- Modify: `scripts/run-ci-gate.ts`
- Modify: `config/ci/gates.json`
- Modify: `.gitea/workflows/quality-gates.yml`
- Create: `tests/unit/ci-workflow-generation.test.ts`
- [ ] Add invalid gate fixtures for unknown fields, duplicate IDs, missing artifact schemas, unknown dependencies, and cycles. Add a snapshot test for the full generated workflow plus `--check` drift.
- [ ] Run `corepack pnpm exec vitest run tests/unit/ci-workflow-generation.test.ts` and confirm RED because no shared parser/generator exists.
- [ ] Parse gates once with strict Zod schemas. Generate every job, dependency, command, environment mapping, timeout, artifact upload/download, and schema validation deterministically.
- [ ] Replace regex/token workflow checks with `node scripts/generate-ci-workflow.ts --check`; generated YAML must match byte-for-byte.
- [ ] Re-run tests and check mode; commit with `git commit -m "refactor: generate CI workflow from gate contracts"`.
### Task 3b: Semantic validation for every CI evidence format
**Files:**
- Modify: `scripts/contracts/release-artifacts.ts`
- Modify: producer scripts for the remaining generic JSON evidence
- Modify: `scripts/lib/ci-artifact-validator.ts`
- Modify: `config/ci/gates.json`
- Modify: `tests/unit/ci-artifact-contract.test.ts`
- [x] Inventory every artifact still mapped to `generic-json-object` and export/reuse the producer's strict schema, including cross-field status/failure/count invariants. Do not treat a non-empty JSON object as semantic evidence.
- [x] Replace substring-only JUnit/HTML acceptance with bounded well-formed document validation. Reject DTD/entities, malformed nesting, duplicate/invalid roots, and trailing non-whitespace content.
- [x] Add invalid-but-pattern-matching fixtures for all structured kinds and a table proving every configured artifact resolves to a semantic validator.
- [ ] Run focused artifact tests, `corepack pnpm check:ci`, types, lint, and diff checks; commit separately so this evidence-quality closeout is independently reviewable.
### Task 4: One authoritative architecture graph
**Files:**
- Modify: `scripts/check-architecture.ts`
- Modify: `config/architecture/layers.json`
- Modify: `.dependency-cruiser.json`
- Create: `tests/fixtures/architecture/forbidden/contracts-import-application.ts`
- Create: `tests/fixtures/architecture/forbidden/feature-adapter-imports-global-adapter.ts`
- Create: `tests/unit/architecture-policy.test.ts`
- [ ] Add fixtures proving contracts cannot import application/runtime layers, feature adapters cannot import concrete global adapters, unresolved imports fail, cycles fail, and a zero-module root fails.
- [ ] Run `corepack pnpm exec vitest run tests/unit/architecture-policy.test.ts` and confirm missing rules/zero-module behavior fail.
- [ ] Make the Babel parser plus Node/TS resolver graph authoritative. Keep dependency-cruiser output informational while it sees zero TS7 modules, and explicitly fail authoritative counts of zero modules or zero dependencies in a non-empty source tree.
- [ ] Add the two dependency-direction rules to the typed layer policy and ensure aliases/extensions resolve identically to TypeScript.
- [ ] Run focused tests and `corepack pnpm check:architecture`; commit with `git commit -m "fix: enforce architecture with the TS7 graph"`.
### Task 5: Test hygiene and production read/write E2E
**Files:**
- Modify: `vitest.config.ts`
- Modify: `tests/setup.ts`
- Modify: `playwright.config.ts`
- Modify: `playwright.dev.config.ts`
- Modify: `playwright.storybook.config.ts`
- Modify: `playwright.visual.config.ts`
- Create: `tests/e2e/reference-resource-write.spec.ts`
- Modify: `scripts/check-test-evidence.ts`
- [ ] Add a fixture containing `.only` and a leaking fake timer; assert the gate rejects/isolation restores them. Assert every Playwright config resolves `forbidOnly: true`.
- [ ] Add E2E that loads a real mocked GET response, submits POST, verifies request body/header contract, verifies response-rendered resource, then reloads and verifies read-after-write.
- [ ] Run focused Vitest and Playwright tests and confirm RED for inherited configs/current shallow E2E.
- [ ] Enable Vitest sequence hook that rejects `.only`, restore real timers in common `afterEach`, and centralize a Playwright base config with `forbidOnly: true` inherited by all configs.
- [ ] Make E2E evidence require both observed response and observed mutation receipt.
- [ ] Re-run supported tests; commit with `git commit -m "test: harden test isolation and read-write E2E"`.
### Task 6: Quality verification
- [ ] Run `corepack pnpm check:architecture`.
- [ ] Run `corepack pnpm test:coverage`.
- [ ] Run `node scripts/check-test-evidence.ts`.
- [ ] Run `node scripts/generate-ci-workflow.ts --check`.
- [ ] Run `corepack pnpm test:all`, `corepack pnpm check:types`, `corepack pnpm lint`, and `git diff --check`.
- [ ] Run browser/E2E gates only when the environment supports them and report exact commands separately.
@@ -0,0 +1,344 @@
# Release and Boot Integrity Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make generated Release Manifest V2 artifacts verifiable and make the browser accept only coherent V1/V1 or V2/V2 boot protocol pairs.
**Architecture:** Zod schemas define artifact shapes and version-specific token projection. Runtime config preserves an exact V1/V2 discriminator through release-manifest loading, where mixed pairs fail before contract or application composition.
**Tech Stack:** TypeScript 7, Node.js 24, Zod 4, Vitest 4, Vite 8, pnpm 11.
## Global Constraints
- Preserve all pre-existing dirty-worktree changes; never reset or restore them.
- Do not stage or commit mixed existing source/test files without explicit user authorization.
- Runtime Config versions accepted by browser boot are exactly `"1"` and `"2.0"`.
- Release Manifest V1 is read-only compatibility; all writers emit V2.
- V2 never requires or emits `API_CONTRACT_VERSION`/`apiContractVersion`.
- V2 contract identity is `contractSet.setDigest` and the full package set.
- Local/development endpoints allow only HTTP or HTTPS; staging/production allow only HTTPS.
- Every production behavior change must be preceded by a failing test.
---
### Task 1: Executable release artifact schemas and token projection
**Files:**
- Create: `scripts/contracts/release-artifacts.ts`
- Create: `tests/unit/release-artifacts.test.ts`
- Modify: `src/contracts/release-tokens.ts`
**Interfaces:**
- Produces: `releaseManifestV1ArtifactSchema`, `releaseManifestV2ArtifactSchema`, `releaseManifestArtifactSchema`, `runtimeConfigV1ArtifactSchema`, `runtimeConfigV2ArtifactSchema`, `runtimeConfigArtifactSchema`, and `buildManifestArtifactSchema`.
- Produces: `parseReleaseArtifact(value)`, `parseRuntimeConfigArtifact(value)`, `parseBuildManifestArtifact(value)`.
- Produces: `projectReleaseTokens(release)` returning common tokens plus exactly one of `apiContractVersion` or `contractSetDigest`.
- Consumes: `contractSetSchema` from `src/contracts/contract-set.ts`.
- [ ] **Step 1: Write failing V2 projection tests**
```ts
it("projects the nested V2 contract-set digest without a legacy scalar", () => {
const release = parseReleaseArtifact(v2ReleaseFixture);
expect(projectReleaseTokens(release)).toMatchObject({
schemaVersion: 2,
contractSetDigest: v2ReleaseFixture.contractSet.setDigest,
});
expect(projectReleaseTokens(release)).not.toHaveProperty("apiContractVersion");
});
it("rejects a V2 release carrying the removed scalar", () => {
expect(() => parseReleaseArtifact({
...v2ReleaseFixture,
apiContractVersion: "1",
})).toThrow();
});
```
- [ ] **Step 2: Run tests and confirm RED**
Run: `corepack pnpm exec vitest run tests/unit/release-artifacts.test.ts`
Expected: module/export resolution failure because the artifact contract module does not exist.
- [ ] **Step 3: Implement discriminated artifact schemas**
```ts
export const releaseManifestArtifactSchema = z.discriminatedUnion(
"schemaVersion",
[releaseManifestV1ArtifactSchema, releaseManifestV2ArtifactSchema],
);
export type ReleaseArtifact = z.output<typeof releaseManifestArtifactSchema>;
export function projectReleaseTokens(release: ReleaseArtifact) {
const common = {
schemaVersion: release.schemaVersion,
appVersion: release.appVersion,
buildId: release.buildId,
commitSha: release.commitSha,
configSchemaVersion: release.configSchemaVersion,
assetManifestHash: release.assetManifestHash,
releaseId: release.releaseId,
builtAt: release.builtAt,
} as const;
return release.schemaVersion === 1
? { ...common, apiContractVersion: release.apiContractVersion }
: { ...common, contractSetDigest: release.contractSet.setDigest };
}
```
Build Manifest V1 must include the fields currently emitted by the generator:
`releaseId`, `moduleInventoryHash`, `buildContext.sourceDateEpoch`, and output
paths for module inventory, route chunks, and runtime-config schema.
- [ ] **Step 4: Run focused tests and confirm GREEN**
Run: `corepack pnpm exec vitest run tests/unit/release-artifacts.test.ts tests/unit/release-coherence.test.ts`
Expected: all tests pass; V1 projection retains `apiContractVersion`; V2 projection contains only `contractSetDigest`.
### Task 2: Generate and verify artifacts through the same contracts
**Files:**
- Modify: `scripts/generate-build-manifest.ts`
- Modify: `scripts/verify-release.ts`
- Modify: `schemas/artifacts/build-manifest.schema.json`
- Test: `tests/unit/release-artifacts.test.ts`
**Interfaces:**
- Consumes Task 1 parsers and token projection.
- Produces V2 release and V1 build manifest that have been parsed before write.
- [ ] **Step 1: Add failing parser/writer round-trip tests**
Assert that the exact generator shapes parse, that unknown root/output fields
fail, and that a runtime-config V2 artifact parses without
`API_CONTRACT_VERSION`.
- [ ] **Step 2: Run the tests and confirm RED**
Run: `corepack pnpm exec vitest run tests/unit/release-artifacts.test.ts`
Expected: current build-manifest schema/parser rejects emitted fields or the V2 runtime parser requires the removed scalar.
- [ ] **Step 3: Parse before every write and parse before verification**
In `generate-build-manifest.ts`, wrap the existing build-manifest object with
`buildManifestArtifactSchema.parse(...)`, wrap the existing release-manifest
object with `releaseManifestV2ArtifactSchema.parse(...)`, and replace the
runtime-config parser with `runtimeConfigV2ArtifactSchema.parse(...)`. Preserve
the exact existing values and output paths; the schema call is the only writer
boundary added in this step.
In `verify-release.ts`, replace `CompatibilityTuple` parsing and the loop over
all registry keys with version-specific projection. Keep legacy coherence
fixtures on the existing numeric compatibility policy, but do not apply that
legacy tuple parser to V2 artifacts.
- [ ] **Step 4: Generate JSON Schema from the executable build schema**
Replace the checked-in `schemas/artifacts/build-manifest.schema.json` with the
deterministic `z.toJSONSchema(buildManifestArtifactSchema)` representation.
The generated schema must use draft 2020-12 and `additionalProperties: false`.
- [ ] **Step 5: Run focused tests and confirm GREEN**
Run: `corepack pnpm exec vitest run tests/unit/release-artifacts.test.ts tests/unit/release-coherence.test.ts`
Expected: all release artifact and legacy compatibility tests pass.
- [ ] **Step 6: Run the actual release pipeline in an isolated temporary copy**
Run the existing contract generation, app build, manifest generation, and
`node scripts/verify-release.ts` with local build environment values.
Expected: release verification exits 0 and reports no
`releaseToken:apiContractVersion` or `releaseToken:contractSetDigest` mismatch.
### Task 3: Exact runtime-config version and endpoint selection
**Files:**
- Modify: `src/bootstrap/runtime-config-schema.ts`
- Modify: `tests/runtime-schema/runtime-config.test.ts`
**Interfaces:**
- Produces `RuntimeConfigValidation` with a reliable `schema: "V1" | "V2"` discriminator.
- Keeps the existing normalized `RuntimeConfig` facade for downstream callers.
- [ ] **Step 1: Add failing future-version and protocol tests**
```ts
it.each(["0", "1.0", "2.0.1", "3.0"])(
"rejects unsupported boot config version %s",
(version) => {
expect(validateRuntimeConfig({
...validV1Config,
CONFIG_SCHEMA_VERSION: version,
}).success).toBe(false);
},
);
it.each(["file:///tmp/api/", "data:text/plain,x", "blob:https://test/id"])(
"rejects non-http endpoint %s",
(API_BASE_URL) => {
expect(validateRuntimeConfig({ ...validConfig, API_BASE_URL }).success).toBe(false);
},
);
```
- [ ] **Step 2: Run tests and confirm RED**
Run: `corepack pnpm exec vitest run tests/runtime-schema/runtime-config.test.ts`
Expected: `3.0` and at least `file:`/`data:` cases are currently accepted.
- [ ] **Step 3: Implement literal version dispatch and scheme allow-list**
```ts
export const runtimeConfigV1Schema = base.extend({
CONFIG_SCHEMA_VERSION: z.literal("1"),
API_CONTRACT_VERSION: z.string().regex(VERSION_PATTERN),
}).strict().superRefine(runtimeConfigInvariants);
const selectedSchema = declared === "1"
? runtimeConfigV1Schema
: declared === "2.0"
? runtimeConfigV2Schema
: null;
```
`assertEndpointUrl` must reject every protocol outside `http:` and `https:`
before applying the non-local HTTPS rule.
- [ ] **Step 4: Run tests and confirm GREEN**
Run: `corepack pnpm exec vitest run tests/runtime-schema/runtime-config.test.ts`
Expected: exact V1/V2 cases pass and future/non-HTTP cases fail.
### Task 4: Enforce config/manifest protocol pairing
**Files:**
- Modify: `src/bootstrap/load-release-manifest.ts`
- Modify: `tests/runtime-schema/release-manifest.test.ts`
**Interfaces:**
- Adds `MANIFEST_PROTOCOL_PAIR_MISMATCH` to `ReleaseManifestErrorCode`.
- Requires V1 runtime config with V1 manifest and V2 runtime config with V2 manifest.
- [ ] **Step 1: Replace the permissive compatibility test with a pairing matrix**
```ts
it.each([
["V1", 2],
["V2", 1],
] as const)("rejects %s runtime with manifest V%s", async (configSchema, schemaVersion) => {
await expect(loadReleaseManifest(
runtimeFor(configSchema),
{ fetcher: async () => jsonResponse(manifestFor(schemaVersion)) },
)).rejects.toMatchObject({ code: "MANIFEST_PROTOCOL_PAIR_MISMATCH" });
});
```
Also test that a V1 scalar mismatch fails, and that V2 contract-set verification
is mandatory rather than conditional.
- [ ] **Step 2: Run tests and confirm RED**
Run: `corepack pnpm exec vitest run tests/runtime-schema/release-manifest.test.ts`
Expected: V2 runtime plus V1 manifest currently resolves successfully.
- [ ] **Step 3: Implement pair validation before tuple checks**
```ts
const expectedManifestVersion = runtime.configSchema === "V1" ? 1 : 2;
if (manifest.schemaVersion !== expectedManifestVersion) {
throw new ReleaseManifestError("MANIFEST_PROTOCOL_PAIR_MISMATCH", identity);
}
```
For V1, require both legacy scalar values and compare them. For V2, require the
contract set and always call `verifyContractSet`. Do not use presence checks to
choose security validation.
- [ ] **Step 4: Run tests and confirm GREEN**
Run: `corepack pnpm exec vitest run tests/runtime-schema/release-manifest.test.ts tests/runtime-schema/runtime-config.test.ts`
Expected: complete pairing matrix passes and all tampered V2 sets fail.
### Task 5: Close boot cancellation and timing semantics
**Files:**
- Modify: `src/bootstrap/read-bounded-boot-json.ts`
- Modify: `src/bootstrap/load-runtime-config.ts`
- Modify: `tests/runtime-schema/runtime-config.test.ts`
- Create: `tests/unit/read-bounded-boot-json.test.ts`
**Interfaces:**
- Pre-aborted external signals prevent fetch admission.
- `validationDurationMs` excludes network acquisition.
- [ ] **Step 1: Add a failing pre-abort test**
Create an already-aborted controller, call `readBoundedBootJson`, and assert the
fetcher is never called and the outcome is a stable fetch/abort failure.
- [ ] **Step 2: Add a failing network-exclusion timing test**
Use a deferred fetcher and a deterministic `now()` sequence. Assert that elapsed
network time does not contribute to `validationDurationMs`.
- [ ] **Step 3: Run both tests and confirm RED**
Run: `corepack pnpm exec vitest run tests/unit/read-bounded-boot-json.test.ts tests/runtime-schema/runtime-config.test.ts`
- [ ] **Step 4: Implement admission precheck and move the timer start**
Check `options.signal?.aborted` before installing listeners or invoking fetch.
In `loadRuntimeConfig`, set `startedAt` immediately after a successful bounded
read and before safe-name/schema validation.
- [ ] **Step 5: Run both tests and confirm GREEN**
Run the same focused command and expect all tests to pass with no leaked abort
listeners or timers.
### Task 6: Full verification and handoff
**Files:**
- Verify all files touched by Tasks 1-5.
- [ ] **Step 1: Run focused suites**
Run:
```sh
corepack pnpm exec vitest run \
tests/unit/release-artifacts.test.ts \
tests/unit/release-coherence.test.ts \
tests/unit/read-bounded-boot-json.test.ts \
tests/runtime-schema/runtime-config.test.ts \
tests/runtime-schema/release-manifest.test.ts
```
- [ ] **Step 2: Run repository static and non-browser suites**
Run `corepack pnpm check:types`, `corepack pnpm lint`, and
`corepack pnpm test:all`.
- [ ] **Step 3: Run release verification from a clean generated output**
Run the complete local build and `corepack pnpm verify:release`. Record the
actual exit status and mismatch list.
- [ ] **Step 4: Run diff hygiene**
Run `git diff --check` and a NUL-byte scan. Do not attribute pre-existing
unrelated failures to this sub-project.
- [ ] **Step 5: Report exact remaining gates**
List passing commands, failing commands, files changed, and any browser-only
coverage that still requires a Playwright-capable environment.
@@ -0,0 +1,110 @@
# Release Evidence Remediation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build one immutable release bundle and fail promotion unless every artifact, provider report, schema, and digest proves it describes those exact bytes.
**Architecture:** Executable Zod contracts validate artifacts at every writer boundary. One fail-closed tracked-file inventory feeds security and provenance. CI creates the candidate once, scans that candidate, then promotes it without rebuilding.
**Tech Stack:** TypeScript 7, Node.js 24, Zod 4, Vite 8, Gitea Actions, Vitest 4.
## Global Constraints
- Repository code validates but never fabricates external vulnerability or signed provenance evidence.
- Missing evidence, unreadable required roots, tool crashes, signals, timeouts, and digest mismatch fail closed.
- V2 release identity is its exact contract package set and recomputed set digest; no scalar version is synthesized.
- Promotion consumes the same archived `dist` and `distSha256` produced by `immutable_build`.
- All production changes start with a failing fixture or unit test.
---
### Task 1: One V1/V2 runtime coherence verifier
**Files:**
- Create: `scripts/lib/release-runtime-coherence.ts`
- Modify: `scripts/verify-release.ts`
- Modify: `scripts/drill-runbook.ts`
- Modify: `src/contracts/release-tokens.ts`
- Modify: `tests/unit/release-coherence.test.ts`
- Modify: `tests/unit/release-artifacts.test.ts`
- [ ] Add a shared matrix covering V1 scalar success/mismatch and V2 package add/remove/version/digest tampering. Assert verifier and rollback drill return identical verdicts.
- [ ] Run `corepack pnpm exec vitest run tests/unit/release-coherence.test.ts tests/unit/release-artifacts.test.ts` and confirm RED because the drill compares only scalar release tokens.
- [ ] Implement async `verifyReleaseRuntimeCoherence({ release, runtime, contractPackages })`; V1 delegates to legacy scalar policy, V2 checks exact sorted package tuples then recomputes `contractSet.setDigest`.
- [ ] Remove V2 synthetic `0`/legacy scalar projection from `release-tokens.ts`; call the shared verifier from both scripts.
- [ ] Re-run the focused tests and commit with `git commit -m "fix: unify release runtime coherence verification"`.
### Task 2: Validated artifact writers and generated JSON schemas
**Files:**
- Create: `scripts/lib/validated-json-artifact.ts`
- Create: `scripts/generate-artifact-schemas.ts`
- Modify: `scripts/contracts/release-artifacts.ts`
- Modify: `scripts/generate-build-manifest.ts`
- Modify: `scripts/generate-supply-chain.ts`
- Modify: `scripts/collect-web-vitals-evidence.ts`
- Modify: `scripts/test-performance.ts`
- Modify: `scripts/verify-release.ts`
- Modify: `scripts/drill-runbook.ts`
- Modify: `schemas/artifacts/build-manifest.schema.json`
- Modify: `schemas/artifacts/dependency-inventory.schema.json`
- Modify: `schemas/artifacts/registry-snapshot.schema.json`
- Modify: `schemas/artifacts/supply-chain-verification.schema.json`
- Create: `tests/unit/validated-json-artifact.test.ts`
- Modify: `tests/unit/release-artifacts.test.ts`
- Modify: `tests/unit/json-schema.test.ts`
- [ ] Add tests proving invalid values do not touch the destination, a valid write is atomic, and `generate-artifact-schemas.ts --check` reports checked-in drift.
- [ ] Run focused tests and confirm RED because writers call `writeFile` directly and schemas are hand-maintained.
- [ ] Implement `writeValidatedJsonArtifact({ path, schema, value })`: parse first, write a sibling temporary file, rename atomically, and clean only its explicit temp file on failure.
- [ ] Route every listed writer through the helper. Generate draft-2020-12 schemas deterministically with `additionalProperties: false` and stable final newline.
- [ ] Add `generate:artifact-schemas` and `check:artifact-schemas` scripts; run generation then check mode.
- [ ] Run `corepack pnpm exec vitest run tests/unit/validated-json-artifact.test.ts tests/unit/release-artifacts.test.ts tests/unit/json-schema.test.ts` and commit with `git commit -m "refactor: validate generated evidence artifacts"`.
### Task 3: Manifest outputs and fail-closed repository inventory
**Files:**
- Create: `scripts/lib/repository-file-inventory.ts`
- Create: `scripts/lib/build-manifest-outputs.ts`
- Modify: `scripts/generate-supply-chain.ts`
- Modify: `scripts/security-scan.ts`
- Modify: `scripts/verify-release.ts`
- Modify: `config/security/secret-scan-policy.json`
- Modify: `tests/unit/supply-chain.test.ts`
- Create: `tests/unit/repository-file-inventory.test.ts`
- Modify: `tests/unit/release-artifacts.test.ts`
- [ ] Add fixtures for missing required root, optional `ENOENT`, unreadable file, untracked omission, path traversal, module-inventory tamper, and hash mismatch.
- [ ] Run focused tests and confirm current discovery skips read failures and verification accepts a stale `moduleInventoryHash`.
- [ ] Build inventory from `git ls-files -z` plus explicitly generated inputs; normalize and confine every path under repository root. Only configured optional roots may ignore exact `ENOENT`.
- [ ] Make provenance and secret scan consume the same inventory. Add `index.html`, Vite configs, all TS configs, `.nvmrc`, package/lock files, scripts, schemas, configs, and `.gitea/workflows/quality-gates.yml` to mandatory policy coverage.
- [ ] Implement `verifyBuildManifestOutputs` to confine declared output paths, read module inventory bytes, and compare raw SHA-256 to `moduleInventoryHash`.
- [ ] Re-run focused tests and commit with `git commit -m "fix: fail closed on release input discovery"`.
### Task 4: Immutable candidate, provider evidence, and promotion
**Files:**
- Modify: `package.json`
- Modify: `scripts/generate-supply-chain.ts`
- Modify: `scripts/verify-supply-chain-artifacts.ts`
- Modify: `scripts/verify-supply-chain-promotion.ts`
- Modify: `scripts/check-supply-chain-provider-fixtures.ts`
- Modify: `tests/unit/supply-chain.test.ts`
- Modify: `.gitea/workflows/quality-gates.yml`
- [ ] Add fixtures for absent provider evidence, valid matching digest, wrong digest, and post-attestation byte change. Assert only the valid immutable fixture passes promotion.
- [ ] Run `corepack pnpm exec vitest run tests/unit/supply-chain.test.ts && corepack pnpm check:supply-chain:provider-fixtures` and confirm RED for promotion wiring.
- [ ] Split scripts into `build:release-candidate`, `verify:local-evidence`, `verify:provider-evidence`, and `verify:promotion`; remove any build command from promotion.
- [ ] `immutable_build` archives `dist`, build manifest, module inventory, and local evidence together and publishes `distSha256`. Provider jobs download that archive and emit reports bound to the digest.
- [ ] Promotion downloads the same archive plus provider reports, exports `VULNERABILITY_REPORT_PATH` and `PROVENANCE_ATTESTATION_PATH`, verifies all schemas/signatures/digests, and uploads/deploys the unchanged bundle.
- [ ] Verify missing external evidence remains `FAIL_UNVERIFIED`; do not add a repository-generated passing provider fixture to production flow.
- [ ] Re-run fixtures and the workflow contract check, then commit with `git commit -m "fix: promote immutable verified release bundles"`.
### Task 5: Release/evidence verification
- [ ] Run `corepack pnpm check:artifact-schemas`.
- [ ] Run `corepack pnpm exec vitest run tests/unit/release-artifacts.test.ts tests/unit/release-coherence.test.ts tests/unit/validated-json-artifact.test.ts tests/unit/repository-file-inventory.test.ts tests/unit/supply-chain.test.ts tests/unit/json-schema.test.ts`.
- [ ] Run `corepack pnpm check:supply-chain:fixtures` and `corepack pnpm check:supply-chain:provider-fixtures`.
- [ ] Run the candidate build and local release verification with deterministic local environment values.
- [ ] Confirm promotion fails specifically with `FAIL_UNVERIFIED` when real external evidence paths are absent.
- [ ] Run `corepack pnpm check:types`, `corepack pnpm lint`, and `git diff --check`.
@@ -0,0 +1,281 @@
# Runtime Correctness Remediation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make topic invalidation hit every real bound query and make each logical mutation preserve a unique intent and effect-aware optimistic state.
**Architecture:** Contracts own query-key and mutation-intent shapes. Bootstrap indexes feature invalidation contributions once. Presentation creates one intent per admitted logical submit, while HTTP consumes that intent and optimistic settlement follows the returned effect certainty.
**Tech Stack:** TypeScript 7, React 19, TanStack Query 5, Zod 4, Vitest 4.
## Global Constraints
- Query persistence remains disabled; the key-version change has no persisted migration.
- Query keys and invalidation prefixes are created only by `src/contracts/query-keys.ts`.
- Cross-context messages carry topics, never namespace IDs, query keys, input identities, intent IDs, or idempotency keys.
- A logical mutation creates one intent after duplicate admission and reuses it for every physical attempt.
- A missing `KEYED` idempotency key fails before credentials, fetch admission, or diagnostics containing caller data.
- `MAYBE_APPLIED` never rolls back, commits, invalidates, or retries automatically.
- Every production behavior change is preceded by a focused failing test.
---
### Task 1: Query-key V2 and namespace identities
**Files:**
- Modify: `src/contracts/query-keys.ts`
- Modify: `src/contracts/server-state.ts`
- Modify: `tests/component/application-query.test.tsx`
- Modify: `tests/unit/query-invalidation-registry.test.ts`
**Interfaces:**
- Adds `QUERY_KEY_SCHEMA_VERSION = 2`.
- Adds `QueryNamespaceIdentity = { namespaceId: string; namespaceVersion: number }`.
- Adds `defineQueryNamespaceIdentity`, `createBoundQueryKey`, `createQueryInvalidationPrefix`, and `queryNamespaceIdentityKey`.
- Changes `bindQuery` to delegate key construction to `createBoundQueryKey`.
- [ ] **Step 1: Add failing key/prefix parity tests**
```ts
const namespace = defineQueryNamespaceIdentity("reference-resource", 1);
const bound = bindQuery(definition, input, scope);
expect(bound.queryKey).toEqual([
"query", 2, "reference-resource", 1,
scope.fingerprint, definition.definitionVersion, bound.identity.token,
]);
expect(bound.queryKey.slice(0, 4)).toEqual(
createQueryInvalidationPrefix(namespace),
);
```
Also reject empty/control-character IDs, non-positive versions, and excessive UTF-8 length.
- [ ] **Step 2: Run RED**
Run: `corepack pnpm exec vitest run tests/component/application-query.test.tsx tests/unit/query-invalidation-registry.test.ts`
Expected: missing helper exports and current V1 key order mismatch.
- [ ] **Step 3: Implement the shared constructors**
`createBoundQueryKey` must return exactly:
```ts
Object.freeze([
"query", QUERY_KEY_SCHEMA_VERSION,
namespace.namespaceId, namespace.namespaceVersion,
scopeFingerprint, definitionVersion, identityToken,
]);
```
`createQueryInvalidationPrefix` returns the first four entries. `bindQuery` constructs the namespace identity from the definition rather than duplicating the tuple.
- [ ] **Step 4: Run GREEN**
Run: `corepack pnpm exec vitest run tests/component/application-query.test.tsx tests/unit/query-invalidation-registry.test.ts`
- [ ] **Step 5: Commit**
```bash
git add src/contracts/query-keys.ts src/contracts/server-state.ts tests/component/application-query.test.tsx tests/unit/query-invalidation-registry.test.ts
git commit -m "fix: align bound query keys with invalidation prefixes"
```
### Task 2: Many-to-many invalidation in production composition
**Files:**
- Modify: `src/contracts/query-invalidation.ts`
- Modify: `src/features/reference-feature/contracts/reference-feature-contract.ts`
- Modify: `src/features/installed-feature-contracts.ts`
- Modify: `src/adapters/query-cache/tanstack-cache-coordinator.ts`
- Modify: `src/bootstrap/runtime-adapters.ts`
- Modify: `tests/unit/query-invalidation-registry.test.ts`
- Modify: `tests/unit/tanstack-cache-coordinator.test.ts`
- Modify: `tests/features/reference-feature/reference-contract.test.ts`
- Modify: `tests/unit/runtime-adapters.test.ts`
**Interfaces:**
- `InvalidationRegistry.namespaces` and edges use `QueryNamespaceIdentity`.
- `indexInvalidationRegistry` returns every namespace identity for each topic.
- `createTanStackCacheCoordinator` consumes `InvalidationRegistryIndex`; topic versions remain a separate bounded map used only by cross-context transport.
- Installed features export `INVALIDATION_REGISTRY`, composed once at bootstrap.
- [ ] **Step 1: Add failing real-key invalidation and fan-out tests**
Seed `QueryClient` with real `bindQuery(...).queryKey` values, map one topic to two namespaces, call local and remote invalidation, and assert both matching queries are invalidated while an unrelated namespace is not. Assert the published event contains only topic/version.
- [ ] **Step 2: Run RED**
Run: `corepack pnpm exec vitest run tests/unit/query-invalidation-registry.test.ts tests/unit/tanstack-cache-coordinator.test.ts tests/features/reference-feature/reference-contract.test.ts tests/unit/runtime-adapters.test.ts`
Expected: the coordinator accepts the legacy flat registry and invalidates prefixes that do not match bound keys.
- [ ] **Step 3: Compose and index contributions once**
Feature contribution shape:
```ts
invalidation: Object.freeze({
topics: [REFERENCE_RESOURCE_INVALIDATION_TOPIC],
namespaces: [defineQueryNamespaceIdentity("reference-resource", 1)],
edges: [{
topicId: REFERENCE_RESOURCE_INVALIDATION_TOPIC,
namespace: defineQueryNamespaceIdentity("reference-resource", 1),
}],
})
```
`installed-feature-contracts.ts` concatenates these bounded contributions. `runtime-adapters.ts` calls `indexInvalidationRegistry(INVALIDATION_REGISTRY)` exactly once, derives transport topic versions, and passes the index to each generation's coordinator.
- [ ] **Step 4: Make the coordinator invalidate every indexed prefix**
For each topic, iterate `namespacesForTopic.get(topic)`, create the V2 prefix with `createQueryInvalidationPrefix`, and call `invalidateQueries({ exact: false, refetchType: "active" })`. Sequence-gap handling visits all indexed topics without duplicating namespace work.
- [ ] **Step 5: Remove legacy authorities**
Delete `QUERY_REGISTRY` from `src/contracts/query-keys.ts`, the flat installed `QUERY_REGISTRY`, `InstalledQueryInvalidationDefinition`, and feature-owned concrete TanStack namespace tuples after `rg` shows zero callers.
- [ ] **Step 6: Run GREEN**
Run: `corepack pnpm exec vitest run tests/unit/query-invalidation-registry.test.ts tests/unit/tanstack-cache-coordinator.test.ts tests/features/reference-feature/reference-contract.test.ts tests/unit/runtime-adapters.test.ts`
- [ ] **Step 7: Commit**
```bash
git add src/contracts/query-invalidation.ts src/features/reference-feature/contracts/reference-feature-contract.ts src/features/installed-feature-contracts.ts src/adapters/query-cache/tanstack-cache-coordinator.ts src/bootstrap/runtime-adapters.ts tests/unit/query-invalidation-registry.test.ts tests/unit/tanstack-cache-coordinator.test.ts tests/features/reference-feature/reference-contract.test.ts tests/unit/runtime-adapters.test.ts
git commit -m "fix: index many-to-many query invalidation"
```
### Task 3: Application-owned mutation intent
**Files:**
- Create: `src/contracts/mutation-intent.ts`
- Create: `src/application/ports/mutation-intent-factory.ts`
- Create: `src/adapters/platform/browser-mutation-intent-factory.ts`
- Modify: `src/contracts/server-state.ts`
- Modify: `src/presentation/adapters/query/application-query.ts`
- Create: `src/presentation/adapters/query/mutation-intent-provider.tsx`
- Modify: `src/presentation/adapters/query/server-state-generation-provider.tsx`
- Modify: `src/bootstrap/runtime-adapters.ts`
- Modify: `src/features/reference-feature/adapters/reference-http-gateway.ts`
- Modify: `src/adapters/http/http-effect-certainty.ts`
- Modify: `src/adapters/http/http-execution-v3.ts`
- Modify: `tests/component/application-query.test.tsx`
- Modify: `tests/unit/runtime-adapters.test.ts`
- Modify: `tests/unit/http-execution-v3.test.ts`
- Modify: `tests/integration/http-execution-contract.test.ts`
**Interfaces:**
- `MutationIntent` has the exact approved immutable shape.
- `MutationIntentFactory.create({ operationId, canonicalInputIdentity, requiresIdempotencyKey })` returns one intent.
- `BoundMutation.execute` context adds `intent: MutationIntent`.
- `HttpExecutionContext.intent` consumes the application intent without regenerating it.
- [ ] **Step 1: Add failing lifecycle tests**
Assert two independent submits receive different intent/key pairs; a `JOIN_IDENTICAL` waiter shares the admitted submit; physical HTTP retry sees the same key; queries have no intent header; diagnostics and URLs contain neither intent ID nor key.
- [ ] **Step 2: Run RED**
Run: `corepack pnpm exec vitest run tests/component/application-query.test.tsx tests/unit/runtime-adapters.test.ts tests/unit/http-execution-v3.test.ts tests/integration/http-execution-contract.test.ts`
Expected: bound mutation context has no intent and bootstrap produces resettable `http-key-N` values.
- [ ] **Step 3: Define intent validation and browser factory**
Validate bounded non-empty strings and finite non-negative monotonic timestamps. Use `crypto.randomUUID()` independently for `intentId` and required idempotency key; permit deterministic injected factories in tests.
- [ ] **Step 4: Create intent after duplicate admission**
Keep canonical identity calculation before duplicate lookup. Only the execution that wins admission calls the factory. Pass the same frozen intent through `mutation.mutateAsync({ input, intent })` and every bound mutation/feature gateway call.
- [ ] **Step 5: Remove adapter-local sequence identity**
Delete `contractExecutionSequence`, `http-intent-N`, `http-key-N`, and the unused HTTP-layer `MutationIntent` factory. Bootstrap passes the supplied intent into `ContractHttpExecutor` unchanged.
- [ ] **Step 6: Run GREEN**
Run the command from Step 2 and expect all intent lifecycle assertions to pass.
- [ ] **Step 7: Commit**
```bash
git add src/contracts/mutation-intent.ts src/application/ports/mutation-intent-factory.ts src/adapters/platform/browser-mutation-intent-factory.ts src/contracts/server-state.ts src/presentation/adapters/query/application-query.ts src/presentation/adapters/query/mutation-intent-provider.tsx src/presentation/adapters/query/server-state-generation-provider.tsx src/bootstrap/runtime-adapters.ts src/features/reference-feature/adapters/reference-http-gateway.ts src/adapters/http/http-effect-certainty.ts src/adapters/http/http-execution-v3.ts tests/component/application-query.test.tsx tests/unit/runtime-adapters.test.ts tests/unit/http-execution-v3.test.ts tests/integration/http-execution-contract.test.ts
git commit -m "fix: preserve logical mutation intent"
```
### Task 4: Fail KEYED commands before dispatch
**Files:**
- Modify: `src/adapters/http/http-execution-v3.ts`
- Modify: `tests/unit/http-execution-v3.test.ts`
- Modify: `tests/integration/http-execution-contract.test.ts`
- [ ] Add tests for absent, empty, control-character, and over-budget keys. Spy on `attachCredentials` and `fetch`; both must remain at zero and the result must be `CONTRACT_VIOLATION` with `effect: "NOT_STARTED"`.
- [ ] Run `corepack pnpm exec vitest run tests/unit/http-execution-v3.test.ts tests/integration/http-execution-contract.test.ts` and confirm RED because KEYED commands currently dispatch without a key.
- [ ] Add `MISSING_IDEMPOTENCY_KEY` to the request violation union and validate before credential resolution. Reject a key on `NONE`/query descriptors as the same pre-dispatch contract class.
- [ ] Re-run the focused tests and confirm GREEN, including same-key physical retry.
- [ ] Commit with `git commit -m "fix: reject invalid keyed mutation intents"`.
### Task 5: Effect-aware optimistic settlement and reconciliation
**Files:**
- Modify: `src/presentation/adapters/query/optimistic-layer-runtime.ts`
- Modify: `src/presentation/adapters/query/application-query.ts`
- Modify: `src/application/view-models/async-state.ts`
- Modify: `src/contracts/errors.ts`
- Modify: `tests/unit/optimistic-layer-runtime.test.ts`
- Modify: `tests/component/application-query.test.tsx`
**Interfaces:**
- `OptimisticLayerLease` adds `markUncertain()` and `reconcile("APPLIED" | "NOT_APPLIED")`.
- Layer status becomes `pending | uncertain | committed`; collapse stops before unresolved uncertain layers.
- Controller adds `reconcileUnknownEffect(resolution)` tied to the original intent.
- Async state adds `mutation-effect-unknown`.
- [ ] **Step 1: Add failing certainty matrix tests**
Cover `NOT_STARTED`, `NOT_APPLIED`, `APPLIED_CONFIRMED`, and `MAYBE_APPLIED`; out-of-order later commits; applied/not-applied reconciliation; scope closure. Assert unknown effect does not call `invalidate` or expose generic retry.
- [ ] **Step 2: Run RED**
Run: `corepack pnpm exec vitest run tests/unit/optimistic-layer-runtime.test.ts tests/component/application-query.test.tsx`
Expected: current catch path rolls every failure back.
- [ ] **Step 3: Derive settlement before touching optimistic state**
Use `failure.effect ?? "NOT_STARTED"` only for failures known to be pre-dispatch. The mutation bridge switches explicitly:
```ts
switch (effect) {
case "NOT_STARTED":
case "NOT_APPLIED": rollback(); break;
case "APPLIED_CONFIRMED": commit(); scheduleInvalidation(); break;
case "MAYBE_APPLIED": markUncertain(); exposeReconciliation(); break;
}
```
- [ ] **Step 4: Preserve ordered uncertain layers**
Projection still applies uncertain layers. `collapse` may consume committed layers only until the first pending/uncertain layer. Reconciliation converts uncertain to committed or removes it, then reprojects all later layers.
- [ ] **Step 5: Run GREEN**
Run the command from Step 2 and expect all certainty and ordering cases to pass.
- [ ] **Step 6: Commit**
```bash
git add src/presentation/adapters/query/optimistic-layer-runtime.ts src/presentation/adapters/query/application-query.ts src/application/view-models/async-state.ts src/contracts/errors.ts tests/unit/optimistic-layer-runtime.test.ts tests/component/application-query.test.tsx
git commit -m "fix: retain uncertain optimistic mutations"
```
### Task 6: Runtime correctness verification
- [ ] Run `corepack pnpm exec vitest run tests/unit/query-invalidation-registry.test.ts tests/unit/tanstack-cache-coordinator.test.ts tests/unit/http-execution-v3.test.ts tests/unit/optimistic-layer-runtime.test.ts tests/component/application-query.test.tsx tests/integration/http-execution-contract.test.ts tests/features/reference-feature/reference-contract.test.ts tests/unit/runtime-adapters.test.ts`.
- [ ] Run `corepack pnpm check:types`.
- [ ] Run `corepack pnpm lint`.
- [ ] Run `corepack pnpm test:all`.
- [ ] Run `git diff --check`.
- [ ] Record any browser-only gate as unverified unless its Playwright command actually ran.
@@ -0,0 +1,99 @@
# Scope-Owned Server State Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Prevent previous-account query and optimistic state from remaining renderable or writable after an authentication scope transition.
**Architecture:** The scope runtime is a fail-closed lifecycle authority. A bootstrap generation store owns a QueryClient and its concrete invalidation coordinator, swaps them only after the previous generation is closed, and exposes a stable invalidation facade plus a subscribable generation snapshot to React.
**Tech Stack:** TypeScript 7, React 19, TanStack Query 5, Vitest 4, Testing Library.
## Global Constraints
- FENCED is synchronous and no previous-account UI may render after it.
- Mandatory participant or cache cleanup failure never publishes READY.
- Snapshot subscriber defects never prevent reset scheduling.
- Every READY generation owns a different QueryClient identity.
- Late work from an old generation cannot write through the stable coordinator.
- Preserve existing public application ports and dirty-worktree changes.
---
### Task 1: Fail-closed scope lifecycle
**Files:**
- Modify: `src/contracts/server-state-scope.ts`
- Modify: `src/adapters/query-cache/server-state-scope-runtime.ts`
- Modify: `tests/unit/server-state-scope-runtime.test.ts`
**Interfaces:**
- Adds phase `FAILED` and lifecycle event `{ kind: "FAILED"; generation: number }`.
- Adds dependency callback `activateNextGeneration(): void | Promise<void>` executed after old local reset and before READY.
- [ ] Add failing tests for a throwing snapshot listener, participant failure, reset failure, and next-generation activation failure.
- [ ] Run `corepack pnpm exec vitest run tests/unit/server-state-scope-runtime.test.ts` and confirm each new behavior fails for the intended reason.
- [ ] Notify listeners from a stable snapshot with per-listener exception isolation.
- [ ] Continue all cleanup participants, remember the first mandatory failure, and always attempt local reset.
- [ ] Close old identities after cleanup, then publish FAILED if any mandatory step failed.
- [ ] Call `activateNextGeneration` only after successful reset and publish READY only after it succeeds.
- [ ] Re-run the focused unit test and confirm all lifecycle cases pass.
### Task 2: Render fence surface synchronously
**Files:**
- Modify: `src/presentation/adapters/query/server-state-scope-provider.tsx`
- Create: `tests/component/server-state-scope-provider.test.tsx`
**Interfaces:**
- `ServerStateScopeProvider` subscribes to both snapshot and phase through one store notification.
- Props add optional `transitionFallback?: ReactNode`; default is `null`.
- Existing `useServerStateScope()` continues returning `CacheScopeSnapshot` for feature hooks.
- [ ] Add a failing component test that renders private child data, triggers a deferred reset, and expects the child to disappear in the same `act()` turn.
- [ ] Add a failing test that FAILED never remounts children.
- [ ] Run the new component test and confirm old children remain with the current provider.
- [ ] Subscribe in the provider and render children only for READY; render the supplied fallback for FENCED/FAILED and nothing after DISPOSED.
- [ ] Re-run the test and confirm no old-data frame is observable.
### Task 3: QueryClient generation store and stable coordinator facade
**Files:**
- Create: `src/bootstrap/server-state-generation-store.ts`
- Modify: `src/bootstrap/runtime-adapters.ts`
- Modify: `src/bootstrap/runtime-application.tsx`
- Modify: `tests/unit/runtime-adapters.test.ts`
- Modify: `tests/component/runtime-application.test.tsx`
**Interfaces:**
- `ServerStateGenerationSnapshot = { generation: number; queryClient: QueryClient; coordinator: QueryInvalidationCoordinator; crossContextStatus(): CrossContextInvalidationStatus }`.
- Store methods: `getSnapshot()`, `subscribe(listener)`, `activateNext()`, `resetCurrent()`, `dispose()`.
- A stable `QueryInvalidationCoordinator` facade delegates only to the current snapshot and fences delegates by generation.
- [ ] Add a failing unit test that captures the initial QueryClient, triggers session transition, completes reset, and expects a different current QueryClient.
- [ ] Add a failing test that an old captured coordinator cannot invalidate after activation.
- [ ] Add a component test that QueryClientProvider receives and renders against the new generation.
- [ ] Implement generation factory ownership in bootstrap; create cross-context transport and concrete coordinator per generation.
- [ ] Pass `resetCurrent` and `activateNext` to the scope runtime in the required order.
- [ ] Make `RuntimeApplication` subscribe to the generation store and key the QueryClient provider by generation.
- [ ] Dispose the current generation and store exactly once during application shutdown.
- [ ] Run runtime-adapter and runtime-application tests until all pass.
### Task 4: Conditional validator and cleanup ordering
**Files:**
- Modify: `src/bootstrap/runtime-adapters.ts`
- Modify: `tests/unit/runtime-adapters.test.ts`
**Interfaces:**
- Conditional validator clearing becomes an ordered mandatory scope participant rather than a general snapshot subscriber.
- [ ] Add a failing order test proving validator clear occurs after admission fence and before cache disposal.
- [ ] Register the validator closer with an explicit order and remove the generic subscription.
- [ ] Re-run runtime-adapter tests and verify each closer executes once.
### Task 5: Verification
- [ ] Run scope runtime, provider, runtime-adapter, runtime-application, and application-query focused suites.
- [ ] Run `corepack pnpm check:types` and `corepack pnpm lint`.
- [ ] Run the complete non-browser suite.
- [ ] Record browser-only follow-up separately; do not claim it passed without Playwright evidence.
@@ -0,0 +1,614 @@
# CI/CD Frontend Assurance and Delivery Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make `cicd-platform` the sole owner of frontend workflow orchestration, remote projection of test-assurance plans, deterministic candidate production, supply-chain/provider evidence, immutable publication, and exact-candidate promotion.
**Architecture:** `cicdctl` compiles typed stages and consumes the digest-pinned test-assurance v3 external contract without interpreting test reports. One source revision produces one verified candidate; artifact-bound testing and every supply-chain stage consume that candidate by SHA-256. `release-control` is the only mutating publication/promotion boundary. The centrally installed required workflow remains a four-step pinned bootstrap and is never copied into a product repository.
**Tech Stack:** Go, JSON Schema 2020-12, Gitea Actions, Node.js/pnpm runners, canonical SHA-256, Ed25519, content-addressed static publication.
## Global Constraints
- Repository: `/home/donghyeon/workspace/desktop-server-git/cicd-platform`; every command runs from its isolated worktree root.
- Start only from the immutable Test Assurance Task 10 commit, its v3 distribution digest, and its v3 schema-bundle digest. No current v2 binary may satisfy this dependency.
- Resolve Go 1.26.5 from `toolchains/toolchain-lock.yaml` into `.platform/toolchains/go-1.26.5/bin/go`, verify the distribution SHA-256 before extraction, and set `CICD_GO_BIN` to that absolute path. The host currently has no `go`; PATH fallback is forbidden.
- CI may choose runner placement and parallelism but may not change test selection, timeout, retry, artifact requirements, result status, or obligation satisfaction.
- Test raw reports remain opaque to CI; only testctl v3 plans, normalized results, evidence, assessments, IDs, digests, attempts, and exit codes are consumed.
- The platform path is the only candidate producer in shadow. Legacy product release/promotion commands may only compare bytes and evidence read-only.
- The first deterministic build is the candidate. The isolated comparison build is destroyed and cannot be promoted.
- Exact new IDs are `ci-test-assurance`, `ci-dependency-vulnerability`, `ci-artifact-signing`, `ci-static-artifact-supply-chain`, and `ci-static-site-publish`.
- Existing `ci-sbom` and `ci-provenance` gain static-archive subjects without weakening container subjects.
- All new capabilities remain P1/shadow until named Gitea, runner, scanner, signer, and provider evidence supports P2.
- Each P1 transition is atomic: canonical ID, descriptor/policy/acceptance/runbook, provider registration, readiness registry row, fixture, immutable evidence, `docs/decisions/readiness/<capability>-P1.yaml`, and `Makefile` capability run land in the same commit. A P0 capability may not have an active provider.
- Rollback selects a previous signed platform catalog and immutable subject through platform control; it never restores a copied workflow or a legacy writer.
---
### Task 1: Define typed stage, artifact, and release-identity contracts
- [ ] Materialize the locked Go toolchain before writing tests. Run from the isolated CICD worktree (network download requires the normal escalation approval):
```bash
mkdir -p .platform/downloads .platform/toolchains/go-1.26.5
curl --fail --location --proto '=https' --tlsv1.3 https://go.dev/dl/go1.26.5.linux-amd64.tar.gz --output .platform/downloads/go1.26.5.linux-amd64.tar.gz
printf '%s %s\n' '5c2c3b16caefa1d968a94c1daca04a7ca301a496d9b086e17ad77bb81393f053' '.platform/downloads/go1.26.5.linux-amd64.tar.gz' | sha256sum --check
tar -xzf .platform/downloads/go1.26.5.linux-amd64.tar.gz --strip-components=1 -C .platform/toolchains/go-1.26.5
export CICD_GO_BIN="$PWD/.platform/toolchains/go-1.26.5/bin/go"
"$CICD_GO_BIN" version
```
Expected: exact `go version go1.26.5 linux/amd64`. Stop on download/digest mismatch; never use `/usr/bin/go` or another PATH binary.
**Files:**
- Modify: `packages/contracts/models.go`
- Modify: `packages/contracts/schema.go`
- Preserve unchanged: `contracts/schemas/execution-plan.schema.json` and `contracts/schemas/release-manifest.schema.json` v1 contracts
- Create: `contracts/schemas/execution-plan-v2.schema.json`
- Create: `contracts/schemas/release-manifest-v2.schema.json`
- Create: `contracts/schemas/promotion-subject.schema.json`
- Modify: `contracts/schemas/delivery-platform.schema.json`
- Create: `contracts/schemas/artifact-reference.schema.json`
- Create: `contracts/schemas/candidate-bundle.schema.json`
- Create: `contracts/schemas/test-assurance-result.schema.json`
- Create: `contracts/schemas/supply-chain-evidence.schema.json`
- Create: `contracts/schemas/publication-record.schema.json`
- Sync generated copies under: `packages/contracts/schemas/`
- Modify: `packages/contracts/schema_test.go`
- Modify: `packages/canonicalid/id.go`
- Modify: `packages/canonicalid/id_test.go`
- Create: `contracts/examples/valid/execution-plan-v2.json`
- Create: `contracts/examples/valid/release-manifest-v2.json`
- Create: `contracts/examples/valid/promotion-subject.json`
- Create: `contracts/examples/invalid/13-v2-stage-with-shell-payload.json`
- Create: `contracts/examples/invalid/14-release-manifest-with-promotion-state.json`
- Modify: `contracts/snapshots/` only by adding a new v2 snapshot set; do not rewrite `contracts/snapshots/v1/`
- Modify: `contracts/readiness-cards.yaml`
- Create P0 directories: `capabilities/ci-test-assurance/`, `capabilities/ci-dependency-vulnerability/`, `capabilities/ci-artifact-signing/`, `capabilities/ci-static-artifact-supply-chain/`, `capabilities/ci-static-site-publish/`
**Interfaces:**
```go
type StageInvocation struct {
Kind string // internal | platform-adapter | external-contract
Engine string // cicdctl | release-control | test-assurance | provider
AdapterID string
Operation string
Payload json.RawMessage // validated by the adapter/operation-specific schema
WorkItemID string
Inputs []ArtifactReference
Outputs []ArtifactDeclaration
}
type TestAssuranceReference struct {
APIVersion string
ManifestDigest string
PolicyDigest string
ExecutionPhase string
PlanDigest string
EvidenceDigest string
AssessmentDigest string
InputArtifacts []ArtifactReference
}
type PromotionSubject struct {
ReleaseManifestDigest string
TargetEnvironment string
ApprovalID string
ApprovalPolicyDigest string
PublicationRecordDigest string
ExpectedGitRevision string
ExpectedTreeDigest string
}
```
`ReleaseManifestV2` is immutable and carries separate source and artifact `TestAssuranceReference` values plus source revision, candidate archive/member-manifest digests, dependency-vulnerability/SBOM/provenance/signature digests, and platform policy/catalog/toolchain digests. Target environment, approval, publication, and Git CAS belong only to `PromotionSubject`; promotion history never rewrites the signed release manifest. The combined signed release identity is `ReleaseManifestV2 + PromotionSubject`.
Canonical kinds are fixed: `ci-test-assurance` is `KindComposite`; `ci-dependency-vulnerability`, `ci-artifact-signing`, and `ci-static-site-publish` are `KindArtifact`; `ci-static-artifact-supply-chain` is `KindComposite`.
- [ ] Add RED tests rejecting free-form shell payload, missing invocation on a planned v2 stage, mutable artifact reference, duplicate output ID, test-assurance v2 reference, source/artifact reference aliasing, wrong candidate digest, release manifest missing either assessment, promotion fields inside the immutable manifest, and target/approval missing from `PromotionSubject`.
- [ ] Run:
```bash
"$CICD_GO_BIN" test ./packages/contracts ./packages/canonicalid
make GO="$CICD_GO_BIN" contracts
```
Expected: RED because the typed contracts do not exist.
- [ ] Implement v2 contracts and document-version mappings while retaining v1 dual-read behavior. `StageInvocation.Payload` is a discriminated typed payload validated by `(AdapterID, Operation)`; the adapter passes its internal argv directly through `exec.CommandContext` and never invokes a shell.
- [ ] Run:
```bash
make GO="$CICD_GO_BIN" contracts-sync
make GO="$CICD_GO_BIN" contracts
"$CICD_GO_BIN" test ./packages/contracts ./packages/canonicalid
```
Expected: PASS.
- [ ] Commit:
```bash
git add packages/contracts packages/canonicalid contracts capabilities/ci-test-assurance capabilities/ci-dependency-vulnerability capabilities/ci-artifact-signing capabilities/ci-static-artifact-supply-chain capabilities/ci-static-site-publish
git commit -m "feat(contracts): define frontend delivery identities"
```
---
### Task 2: Execute typed stages with verified artifact fan-out
**Files:**
- Create: `apps/cicdctl/internal/execution/adapter.go`
- Create: `apps/cicdctl/internal/execution/engine.go`
- Create: `apps/cicdctl/internal/execution/artifact_store.go`
- Create: `apps/cicdctl/internal/execution/local_artifact_store.go`
- Create: `apps/cicdctl/internal/execution/engine_test.go`
- Create: `apps/cicdctl/internal/execution/artifact_store_test.go`
- Modify: `apps/cicdctl/internal/execution/scheduler.go`
- Modify: `apps/cicdctl/internal/execution/completeness.go`
- Modify: `apps/cicdctl/internal/app/run_command.go`
**Interfaces:**
```go
type InvocationAdapter interface {
AdapterID() string
Execute(context.Context, InvocationRequest) (InvocationOutcome, error)
}
type ArtifactStore interface {
Put(context.Context, ArtifactInput) (contracts.ArtifactReference, error)
MaterializeVerified(context.Context, contracts.ArtifactReference, string) error
}
```
- [ ] Add RED tests proving source checkout is read-only, independent stages do not share mutable output paths, mutation after `Put` fails, missing adapters are platform defects, failed dependencies block descendants, and a missing terminal result cannot pass.
- [ ] Run:
```bash
"$CICD_GO_BIN" test ./apps/cicdctl/internal/execution ./apps/cicdctl/internal/app -count=1
```
Expected: RED because no invocation engine/artifact store exists.
- [ ] Implement bounded scheduler waves over existing state-machine rules. Every downstream artifact is materialized to a private directory and rehashed before adapter invocation.
- [ ] Add `cicdctl run execute --plan --checkout --results --artifact-root` with atomic result writes.
- [ ] Re-run `"$CICD_GO_BIN" test ./apps/cicdctl/internal/execution ./apps/cicdctl/internal/app -count=1`; expected PASS.
- [ ] Commit:
```bash
git add apps/cicdctl/internal/execution apps/cicdctl/internal/app
git commit -m "feat(cicdctl): execute typed artifact-bound stages"
```
---
### Task 3: Integrate the digest-pinned test-assurance v3 contract
**Files:**
- Create: `apps/cicdctl/internal/adapters/testassurance/contract.go`
- Create: `apps/cicdctl/internal/adapters/testassurance/runner.go`
- Create: `apps/cicdctl/internal/adapters/testassurance/projector.go`
- Create: `apps/cicdctl/internal/adapters/testassurance/contract_test.go`
- Create: `apps/cicdctl/internal/adapters/testassurance/runner_test.go`
- Create: `apps/cicdctl/internal/adapters/testassurance/projector_test.go`
- Modify: `apps/cicdctl/internal/planner/plan.go`
- Modify: `apps/cicdctl/internal/capabilities/capabilities.go`
- Modify: `apps/cicdctl/internal/app/run_command.go`
- Create imported immutable schema bundle: `toolchains/external-contracts/test-assurance-v3/`
- Create: `toolchains/external-contracts/test-assurance-v3/contract-lock.json`
- Create: `contracts/schemas/external-contract-lock.schema.json`
- Create: `packages/contracts/schemas/external-contract-lock.schema.json`
- Modify: `toolchains/platform-release-catalog.yaml`
- Modify: `contracts/schemas/platform-release-catalog.schema.json`
- Modify: `packages/contracts/schemas/platform-release-catalog.schema.json`
- Modify: `packages/contracts/schema.go`
- Modify: `packages/contracts/schema_test.go`
- Modify: `contracts/examples/valid/platform-release-catalog.json`
- Create/complete: `capabilities/ci-test-assurance/descriptor.yaml`, `capabilities/ci-test-assurance/policy.yaml`, `capabilities/ci-test-assurance/acceptance.yaml`, `capabilities/ci-test-assurance/runbook-index.yaml`
- Create: `docs/decisions/readiness/ci-test-assurance-P1.yaml`
- Create evidence under: `docs/decisions/readiness/evidence/ci-test-assurance/`
- Modify: `contracts/readiness-cards.yaml`
- Modify: `Makefile`
**External calls:**
```text
testctl validate
testctl lock
testctl compile
testctl select
testctl plan
testctl execute-one
testctl normalize
testctl bundle
testctl assess
```
All calls use argv arrays and a distribution/schema digest from the signed platform catalog. `contract-lock.json` binds test-assurance source revision, v3 schema major, distribution digest, and schema-bundle digest. The catalog schema adds a typed `test-assurance` engine reference; without that engine, `ci-test-assurance` compilation fails.
`projector.go` preserves the external plan bytes/digest unchanged and creates a separate CI projection whose nodes reference only `{externalPlanDigest, workItemId}` plus runner placement and CI dependency edges. It never writes a modified testctl plan.
The source request is planned before the build with no input artifacts. After `ci-frontend` freezes the first candidate, CI creates an artifact request containing its exact `{artifactId, mediaType, sha256}`. Testctl, not CI, matches that request to repository `ArtifactSuiteTemplate` declarations and materializes executable v3 suites/work items. CI rejects any unresolved template, placeholder digest, or work item whose input tuple differs from the candidate reference.
- [ ] Add RED tests for source-revision/schema/distribution digest mismatch, absent catalog engine, non-v3 output, altered external plan bytes, altered timeout/retry, missing work-item result, source/artifact plan mixing, opaque evidence preservation, and exact testctl exit-code mapping from the published external contract.
- [ ] Run:
```bash
"$CICD_GO_BIN" test ./apps/cicdctl/internal/adapters/testassurance ./apps/cicdctl/internal/planner ./apps/cicdctl/internal/capabilities -count=1
```
Expected: RED.
- [ ] Implement the adapter without importing JUnit, Playwright, coverage, HTTP, accessibility, or visual parsing code. Invoke v3 artifact work as `testctl execute-one --plan <plan> --work-item-id <id> --artifact-map <map> --output <dir>`.
- [ ] Raise `ci-test-assurance` to P1/shadow only after a local v3 source→artifact conformance fixture completes.
- [ ] Re-run `"$CICD_GO_BIN" test ./apps/cicdctl/internal/adapters/testassurance ./apps/cicdctl/internal/planner ./apps/cicdctl/internal/capabilities -count=1` and `make GO="$CICD_GO_BIN" registry`; expected PASS.
- [ ] Commit:
```bash
git add apps/cicdctl toolchains/external-contracts/test-assurance-v3 toolchains/platform-release-catalog.yaml contracts/schemas/platform-release-catalog.schema.json contracts/examples/valid/platform-release-catalog.json packages/contracts capabilities/ci-test-assurance contracts/readiness-cards.yaml docs/decisions/readiness/ci-test-assurance-P1.yaml docs/decisions/readiness/evidence/ci-test-assurance Makefile
git commit -m "feat(test-assurance): project external v3 test plans"
```
---
### Task 4: Split Node ownership and freeze the first deterministic candidate
**Files:**
- Modify: `apps/cicdctl/internal/manifest/model.go`
- Modify: `apps/cicdctl/internal/manifest/compiler.go`
- Modify: `apps/cicdctl/internal/manifest/compiler_test.go`
- Modify: `apps/cicdctl/internal/templates/nodetypescript/resolver.go`
- Modify: `apps/cicdctl/internal/templates/nodetypescript/resolver_test.go`
- Modify: `apps/cicdctl/internal/templates/frontend/resolver.go`
- Modify: `apps/cicdctl/internal/templates/frontend/resolver_test.go`
- Create: `apps/cicdctl/internal/artifacts/candidate/builder.go`
- Create: `apps/cicdctl/internal/artifacts/candidate/archive.go`
- Create: `apps/cicdctl/internal/artifacts/candidate/builder_test.go`
- Create: `apps/cicdctl/internal/artifacts/candidate/archive_test.go`
- Modify: `capabilities/ci-node-typescript/*`
- Modify: `capabilities/ci-frontend/*`
- Create: `docs/decisions/readiness/ci-frontend-P1.yaml`
- Create: `docs/decisions/readiness/evidence/ci-frontend-P1.bundle.json`
- Modify: `contracts/readiness-cards.yaml`
- Modify: `Makefile`
**Manifest config:**
```go
type NodeTypeScriptConfig struct {
LintScript string
TypecheckScript string
TestOwner string // empty or ci-test-assurance
}
type FrontendConfig struct {
BuildScript string
OutputDirectory string
SizeBudgetBytes int64
ForbiddenEnvironment []string
}
```
Package manager/version/install mode are repository facts derived from `package.json.packageManager` and `pnpm-lock.yaml`; capability config cannot override them. The execution-plan compiler permits exactly one `candidate-producer` operation for a selected release output. A second producer, a legacy product candidate command, or a writer without platform writer identity/operation ID/idempotency key is a contract error.
- [ ] Add RED tests proving capability config is decoded, pnpm is derived and uses frozen install, manifest package-manager override is rejected, lint/typecheck remain in CI, unit/coverage stages disappear when test owner is `ci-test-assurance`, source revision is built twice in isolated workspaces, only the first byte-identical candidate is retained, and a plan with zero/two candidate producers is rejected.
- [ ] Add archive adversarial tests for empty output, traversal, symlink, host path, undeclared member, duplicate path, environment leak, size overflow, and changed tree digest.
- [ ] Run `"$CICD_GO_BIN" test ./apps/cicdctl/internal/manifest ./apps/cicdctl/internal/templates/nodetypescript ./apps/cicdctl/internal/templates/frontend ./apps/cicdctl/internal/artifacts/candidate -count=1`; expected RED.
- [ ] Implement strict config decoding and canonical archive/member manifest generation. Destroy the verification workspace before returning the candidate reference.
- [ ] Re-run `"$CICD_GO_BIN" test ./apps/cicdctl/internal/manifest ./apps/cicdctl/internal/templates/nodetypescript ./apps/cicdctl/internal/templates/frontend ./apps/cicdctl/internal/artifacts/candidate -count=1` and `make GO="$CICD_GO_BIN" registry`; expected PASS.
- [ ] Commit:
```bash
git add apps/cicdctl/internal/manifest apps/cicdctl/internal/templates apps/cicdctl/internal/artifacts/candidate capabilities/ci-node-typescript capabilities/ci-frontend contracts/readiness-cards.yaml docs/decisions/readiness/ci-frontend-P1.yaml docs/decisions/readiness/evidence/ci-frontend-P1.bundle.json Makefile
git commit -m "feat(frontend): freeze one deterministic static candidate"
```
---
### Task 5: Bind dependency-vulnerability evidence to source and candidate
**Files:**
- Create: `apps/cicdctl/internal/artifacts/vulnerability/contract.go`
- Create: `apps/cicdctl/internal/artifacts/vulnerability/adapter.go`
- Create: `apps/cicdctl/internal/artifacts/vulnerability/validator.go`
- Create: `apps/cicdctl/internal/artifacts/vulnerability/adapter_test.go`
- Create: `apps/cicdctl/internal/artifacts/vulnerability/validator_test.go`
- Modify: `apps/cicdctl/internal/capabilities/capabilities.go`
- Create/complete: `capabilities/ci-dependency-vulnerability/descriptor.yaml`, `capabilities/ci-dependency-vulnerability/policy.yaml`, `capabilities/ci-dependency-vulnerability/acceptance.yaml`, `capabilities/ci-dependency-vulnerability/runbook-index.yaml`
- Create: `docs/decisions/readiness/ci-dependency-vulnerability-P1.yaml`
- Create evidence under: `docs/decisions/readiness/evidence/ci-dependency-vulnerability/`
- Modify: `contracts/readiness-cards.yaml`
- Modify: `Makefile`
**Evidence identity:** source revision, lockfile digest, candidate subject digest, provider ID, scanner/tool digest, vulnerability DB snapshot digest/time, invocation digest, normalized finding set, and evidence signature.
The adapter executes a digest-pinned provider engine through a typed contract and validates its output. It contains no scanner HTTP client and receives no provider credential; network/credential handling stays inside the provider trust boundary.
- [ ] Add RED cases for absent report, wrong lockfile/candidate, stale DB, provider crash, malformed report, invalid signature, and zero findings without valid invocation metadata.
- [ ] Run `"$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/vulnerability -count=1`; expected RED.
- [ ] Implement a typed provider profile selected by manifest ID and direct process execution of the pinned engine; arbitrary repository commands and in-process provider clients are forbidden.
- [ ] Raise to P1/shadow with signed local fixtures; keep P2 blocked on a named real scanner/provider.
- [ ] Re-run `"$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/vulnerability ./apps/cicdctl/internal/capabilities -count=1` and `make GO="$CICD_GO_BIN" registry`; expected PASS.
- [ ] Commit:
```bash
git add apps/cicdctl/internal/artifacts/vulnerability apps/cicdctl/internal/capabilities capabilities/ci-dependency-vulnerability contracts/readiness-cards.yaml docs/decisions/readiness/ci-dependency-vulnerability-P1.yaml docs/decisions/readiness/evidence/ci-dependency-vulnerability Makefile
git commit -m "feat(security): bind dependency findings to candidates"
```
---
### Task 6: Generalize SBOM and provenance to static archives
**Files:**
- Create: `apps/cicdctl/internal/artifacts/subject/subject.go`
- Create: `apps/cicdctl/internal/artifacts/subject/subject_test.go`
- Create: `apps/cicdctl/internal/artifacts/sbom/contract.go`
- Create: `apps/cicdctl/internal/artifacts/sbom/validator.go`
- Create: `apps/cicdctl/internal/artifacts/sbom/sbom_test.go`
- Create: `apps/cicdctl/internal/artifacts/provenance/contract.go`
- Create: `apps/cicdctl/internal/artifacts/provenance/validator.go`
- Create: `apps/cicdctl/internal/artifacts/provenance/provenance_test.go`
- Modify: `capabilities/ci-sbom/*`
- Modify: `capabilities/ci-provenance/*`
- Modify: `apps/cicdctl/internal/capabilities/capabilities.go`
- Create: `docs/decisions/readiness/ci-sbom-P1.yaml`
- Create: `docs/decisions/readiness/ci-provenance-P1.yaml`
- Create: `docs/decisions/readiness/evidence/ci-sbom-P1.bundle.json`
- Create: `docs/decisions/readiness/evidence/ci-provenance-P1.bundle.json`
- Modify: `contracts/readiness-cards.yaml`
- Modify: `Makefile`
**Interface:** `ImmutableSubject` is a tagged union of container image or static archive. Both evidence types bind subject kind/digest; static provenance additionally binds source revision, build invocation, member-manifest digest, platform/toolchain digests, and determinism evidence. Digest-pinned external SBOM/provenance engines generate documents; cicdctl validates and binds returned evidence but implements no provider network client.
- [ ] Add RED tests for empty/incomplete SBOM, duplicate package identity, wrong subject kind/digest, missing build invocation, changed source revision, changed member manifest, and fabricated provenance.
- [ ] Run `"$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/subject ./apps/cicdctl/internal/artifacts/sbom ./apps/cicdctl/internal/artifacts/provenance -count=1`; expected RED.
- [ ] Implement output-kind adapters while preserving every existing container test unchanged.
- [ ] Re-run `"$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/subject ./apps/cicdctl/internal/artifacts/sbom ./apps/cicdctl/internal/artifacts/provenance ./apps/cicdctl/internal/capabilities -count=1`, `make GO="$CICD_GO_BIN" contracts`, and `make GO="$CICD_GO_BIN" registry`; expected PASS.
- [ ] Commit:
```bash
git add apps/cicdctl/internal/artifacts/subject apps/cicdctl/internal/artifacts/sbom apps/cicdctl/internal/artifacts/provenance apps/cicdctl/internal/capabilities capabilities/ci-sbom capabilities/ci-provenance contracts/readiness-cards.yaml docs/decisions/readiness/ci-sbom-P1.yaml docs/decisions/readiness/ci-provenance-P1.yaml docs/decisions/readiness/evidence/ci-sbom-P1.bundle.json docs/decisions/readiness/evidence/ci-provenance-P1.bundle.json Makefile
git commit -m "feat(supply-chain): support static archive subjects"
```
---
### Task 7: Sign the artifact and aggregate reference-only supply-chain evidence
**Files:**
- Create: `apps/cicdctl/internal/artifacts/signing/request.go`
- Create: `apps/cicdctl/internal/artifacts/signing/verifier.go`
- Create: `apps/cicdctl/internal/artifacts/signing/signing_test.go`
- Create: `apps/cicdctl/internal/artifacts/supplychain/aggregate.go`
- Create: `apps/cicdctl/internal/artifacts/supplychain/aggregate_test.go`
- Modify: `apps/cicdctl/internal/capabilities/capabilities.go`
- Create/complete: `capabilities/ci-artifact-signing/descriptor.yaml`, `capabilities/ci-artifact-signing/policy.yaml`, `capabilities/ci-artifact-signing/acceptance.yaml`, `capabilities/ci-artifact-signing/runbook-index.yaml`
- Create/complete: `capabilities/ci-static-artifact-supply-chain/descriptor.yaml`, `capabilities/ci-static-artifact-supply-chain/policy.yaml`, `capabilities/ci-static-artifact-supply-chain/acceptance.yaml`, `capabilities/ci-static-artifact-supply-chain/runbook-index.yaml`
- Create: `docs/decisions/readiness/ci-artifact-signing-P1.yaml`
- Create: `docs/decisions/readiness/ci-static-artifact-supply-chain-P1.yaml`
- Create evidence under: `docs/decisions/readiness/evidence/ci-artifact-signing/` and `docs/decisions/readiness/evidence/ci-static-artifact-supply-chain/`
- Modify: `contracts/readiness-cards.yaml`
- Modify: `Makefile`
**Interfaces:**
```go
type SigningRequest struct { SubjectDigest, KeyID, OperationID string }
func VerifyStaticSupplyChain(
candidate contracts.CandidateBundle,
dependencyVulnerability, sbom, provenance, signature contracts.EvidenceRef,
) (contracts.SupplyChainEvidenceSet, error)
```
- [ ] Add RED tests proving the signer receives only identity data, not source/candidate bytes; reject wrong subject, expired key, missing/duplicate evidence kind, invalid signature, altered candidate, and copied/rewritten child evidence.
- [ ] Generate ephemeral Ed25519 test keys only.
- [ ] Run `"$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/signing ./apps/cicdctl/internal/artifacts/supplychain -count=1`; expected RED.
- [ ] Implement signing on the `isolated-signer` trust partition and a composite containing immutable child references only.
- [ ] Raise both to P1/shadow; keep P2 blocked on real signer evidence.
- [ ] Re-run `"$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/signing ./apps/cicdctl/internal/artifacts/supplychain ./apps/cicdctl/internal/capabilities -count=1` and `make GO="$CICD_GO_BIN" registry`; expected PASS.
- [ ] Commit:
```bash
git add apps/cicdctl/internal/artifacts/signing apps/cicdctl/internal/artifacts/supplychain apps/cicdctl/internal/capabilities capabilities/ci-artifact-signing capabilities/ci-static-artifact-supply-chain contracts/readiness-cards.yaml docs/decisions/readiness/ci-artifact-signing-P1.yaml docs/decisions/readiness/ci-static-artifact-supply-chain-P1.yaml docs/decisions/readiness/evidence/ci-artifact-signing docs/decisions/readiness/evidence/ci-static-artifact-supply-chain Makefile
git commit -m "feat(supply-chain): sign static candidates and aggregate evidence"
```
---
### Task 8: Publish static candidates without rebuilding
**Files:**
- Create: `apps/release-control/internal/publish/publisher.go`
- Create: `apps/release-control/internal/publish/local.go`
- Create: `apps/release-control/internal/publish/service.go`
- Create: `apps/release-control/internal/publish/publisher_test.go`
- Create: `apps/release-control/internal/publish/local_test.go`
- Modify: `apps/release-control/internal/app/app.go`
- Create/complete: `capabilities/ci-static-site-publish/descriptor.yaml`, `capabilities/ci-static-site-publish/policy.yaml`, `capabilities/ci-static-site-publish/acceptance.yaml`, `capabilities/ci-static-site-publish/runbook-index.yaml`
- Create: `docs/decisions/readiness/ci-static-site-publish-P1.yaml`
- Create evidence under: `docs/decisions/readiness/evidence/ci-static-site-publish/`
- Modify: `contracts/readiness-cards.yaml`
- Modify: `Makefile`
**Interface:**
```go
type PublishRequest struct {
OperationID, SubjectDigest, CandidateDigest, ReleaseManifestDigest string
}
type PublicationRecord struct {
OperationID, ProviderID, ImmutableURI, SubjectDigest, ServedContentDigest, State string
}
```
- [ ] Add RED tests for idempotent put, same digest already present, different digest collision, response loss after mutation, failed reconciliation, served-content mismatch, mutable URI, and any attempted build/repackage operation.
- [ ] Run `"$CICD_GO_BIN" test ./apps/release-control/internal/publish ./apps/release-control/internal/app -count=1`; expected RED.
- [ ] Implement a content-addressed local P1 publisher. Response loss returns `INDETERMINATE`; retry requires reconciliation by operation ID.
- [ ] Require provider subject and served-content digests both equal the approved candidate.
- [ ] Re-run `"$CICD_GO_BIN" test ./apps/release-control/internal/publish ./apps/release-control/internal/app -count=1` and `make GO="$CICD_GO_BIN" registry`; expected PASS.
- [ ] Raise to P1/shadow; keep real provider P2 blocked.
- [ ] Commit:
```bash
git add apps/release-control/internal/publish apps/release-control/internal/app capabilities/ci-static-site-publish contracts/readiness-cards.yaml docs/decisions/readiness/ci-static-site-publish-P1.yaml docs/decisions/readiness/evidence/ci-static-site-publish Makefile
git commit -m "feat(release-control): publish immutable static candidates"
```
---
### Task 9: Promote the exact signed static candidate
**Files:**
- Modify: `apps/release-control/internal/candidate/candidate.go`
- Modify: `apps/release-control/internal/candidate/candidate_test.go`
- Modify: `apps/release-control/internal/approval/approval.go`
- Modify: `apps/release-control/internal/approval/approval_test.go`
- Modify: `apps/release-control/internal/gitops/cas.go`
- Modify: `apps/release-control/internal/gitops/cas_test.go`
- Create: `apps/release-control/internal/promotion/service.go`
- Create: `apps/release-control/internal/promotion/service_test.go`
- Modify: `apps/release-control/internal/app/app.go`
- Modify: `capabilities/delivery-release-control/*`
- Create: `docs/decisions/readiness/delivery-release-control-P1.yaml`
- Create: `docs/decisions/readiness/evidence/delivery-release-control-P1.bundle.json`
- Modify: `contracts/readiness-cards.yaml`
- Modify: `Makefile`
**Promotion request:** signed release manifest, confirmed publication, source and artifact test-assurance references, supply-chain evidence index, approval subject/expiry, expected Git revision/tree digest, and operation ID. No source path, build command, or mutable provider URL is accepted.
```go
type PromotionRequest struct {
OperationID string
ReleaseManifest contracts.ReleaseManifestV2
Subject contracts.PromotionSubject
Publication contracts.PublicationRecord
EvidenceIndexDigest string
}
```
The compiler selects exactly one environment adapter by output profile: static archives require the static-site desired-state adapter, while container images require the existing Kubernetes/GitOps adapter. Zero or multiple environment adapters is invalid.
- [ ] Add RED tests rejecting failed/missing source or artifact assessment, changed candidate, unconfirmed publication, served-content mismatch, unsigned evidence, stale approval, stale Git base, direct mutable URL, rebuild/repackage request, and zero/multiple/wrong-kind environment adapters.
- [ ] Preserve and run existing real local Git CAS/response-loss tests.
- [ ] Implement static desired-state promotion without an unconditional Kubernetes dependency; container releases keep their environment adapter.
- [ ] Raise `delivery-release-control` only to P1/shadow using local Git and local content-addressed publication.
- [ ] Run `"$CICD_GO_BIN" test ./apps/release-control/internal/candidate ./apps/release-control/internal/approval ./apps/release-control/internal/gitops ./apps/release-control/internal/publish ./apps/release-control/internal/promotion ./apps/release-control/internal/app -count=1`; expected PASS.
- [ ] Commit:
```bash
git add apps/release-control capabilities/delivery-release-control contracts/readiness-cards.yaml docs/decisions/readiness/delivery-release-control-P1.yaml docs/decisions/readiness/evidence/delivery-release-control-P1.bundle.json Makefile
git commit -m "feat(release-control): promote verified static subjects"
```
---
### Task 10: Keep the centrally installed required workflow thin
**Files:**
- Create: `apps/cicdctl/cmd/sourcectl/main.go`
- Create: `apps/cicdctl/cmd/platform-bootstrap/main.go`
- Create: `apps/cicdctl/internal/bootstrap/catalog.go`
- Create: `apps/cicdctl/internal/bootstrap/pipeline.go`
- Create: `apps/cicdctl/internal/bootstrap/status.go`
- Create tests under: `apps/cicdctl/internal/bootstrap/`
- Modify: `.gitea/workflows/required-delivery-guard.yaml`
- Modify: `tools/contractctl/internal/workflow/required_status.go`
- Modify: `tools/contractctl/internal/workflow/required_status_test.go`
- Modify: `Makefile`
- Create: `images/platform-bootstrap/Dockerfile`
- Create: `images/platform-bootstrap/entrypoint.sh`
- Create: `images/platform-bootstrap/README.md`
- Modify: `toolchains/platform-release-catalog.yaml`
**Workflow sequence:** exact source checkout → signed catalog verification → pinned bootstrap execution → one terminal sentinel publication. All language/test/build/provider/promotion stages are compiled inside the platform plan, not written in YAML. The only required status name is exactly `platform/delivery-pipeline`.
- [ ] Add RED tests for exactly one stable required status, no language/build logic, no floating action references, digest-pinned binaries, a sentinel on every exit path, and absence of any rule requiring a product-repository workflow copy.
- [ ] Run:
```bash
"$CICD_GO_BIN" test ./tools/contractctl/internal/workflow ./apps/cicdctl/internal/bootstrap ./apps/cicdctl/internal/app -count=1
```
Expected: RED until bootstrap binaries and status finalization exist.
- [ ] Implement the four-step bootstrap and atomic sentinel finalization.
- [ ] Re-run `"$CICD_GO_BIN" test ./tools/contractctl/internal/workflow ./apps/cicdctl/internal/bootstrap ./apps/cicdctl/internal/app -count=1`; expected PASS.
- [ ] Commit:
```bash
git add apps/cicdctl/cmd apps/cicdctl/internal/bootstrap .gitea/workflows/required-delivery-guard.yaml tools/contractctl/internal/workflow images/platform-bootstrap Makefile toolchains/platform-release-catalog.yaml
git commit -m "feat(workflow): run the pinned delivery platform"
```
---
### Task 11: Prove the complete frontend vertical in shadow
**Files:**
- Create: `fixtures/frontend-delivery-vertical/` with a minimal pnpm frontend, both consumer manifests, v3 source/artifact testctl fixtures, deterministic build, adversarial reports, local signer, local static publisher, and local Git desired state
- Create: `apps/cicdctl/internal/reports/shadow_parity.go`
- Create: `apps/cicdctl/internal/reports/shadow_parity_test.go`
- Create: `contracts/schemas/shadow-parity.schema.json`
- Sync: `packages/contracts/schemas/shadow-parity.schema.json`
- Modify: `packages/contracts/schema.go`
- Modify: `packages/contracts/schema_test.go`
- Create: `contracts/snapshots/v2/shadow-parity.schema.json`
- Create: `contracts/examples/valid/shadow-parity.json`
- Create: `docs/migration/frontend-template.md`
- Create P1 evidence under: `docs/decisions/readiness/evidence/`
- Modify: `docs/decisions/blocked-tasks.md`
- Modify: `contracts/readiness-cards.yaml`
- Modify: `Makefile`
- Modify fixture registry files consumed by: `tools/fixturectl/`
- Modify: `README.md`
**End-to-end order:** source test plan/assessment → one deterministic candidate → artifact test plan/assessment → vulnerability/SBOM/provenance/signature → static supply-chain composite → content-addressed publication → Git CAS promotion → evidence-index sentinel.
- [ ] Add RED vertical tests plus faults for missing work item, changed candidate, wrong-subject provider result, missing signature, response loss, concurrent Git writer, expired approval, and missing sentinel.
- [ ] Add parity comparison over source revision; selected suites/counts/outcomes; coverage universe; HTTP scenario IDs; three browser outcomes; candidate/member digests; provider/supply-chain digests; and promotion readiness. Exclude timestamps, durations, temp paths, and runner IDs.
- [ ] Verify one-writer behavior: the platform fixture produces the only candidate; legacy probes receive read-only references and cannot publish/promote.
- [ ] Run before evidence updates:
```bash
make GO="$CICD_GO_BIN" capabilities
```
Expected: RED because the P1 chain lacks complete evidence.
- [ ] Add only locally observed P1/shadow evidence. Keep P2 blocked with named missing Gitea/runner/scanner/signer/provider prerequisites.
- [ ] Run:
```bash
make GO="$CICD_GO_BIN" contracts
make GO="$CICD_GO_BIN" registry
make GO="$CICD_GO_BIN" capabilities
make GO="$CICD_GO_BIN" boundary
make GO="$CICD_GO_BIN" verify
git diff --check
```
Expected: PASS at P1/shadow; no P2/active claim.
- [ ] Commit:
```bash
git add fixtures/frontend-delivery-vertical apps/cicdctl/internal/reports contracts packages/contracts Makefile docs README.md
git commit -m "test(vertical): prove frontend delivery in shadow"
```
## Handoff to the frontend consumer
The consumer migration may begin from the immutable Task 11 platform release. The product manifest pins its signed catalog version and selects all required capabilities. Central workflow/status installation remains an environment/platform operation. Product files never copy the workflow, provider orchestration, test normalizers, or promotion engine.
@@ -0,0 +1,133 @@
# Counter-bearing Coverage Provenance Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Align repository coverage provenance names and static classification with the counters that Vitest/V8 actually emits, without making claims about JavaScript runtime executability.
**Architecture:** The inventory parser classifies source files only by whether their top-level AST contains statements known to receive V8 counters. Coverage evaluation requires exact agreement between that static counter-bearing/counterless partition and producer rows, while policy-sensitive modules must remain counter-bearing. The JSON artifact exposes the same terminology as the inventory and diagnostics.
**Tech Stack:** TypeScript 7, Node.js 24, `@babel/eslint-parser`, Vitest 4, V8 coverage.
## Global Constraints
- Runtime declarations/initializers and direct execution statements are counter-bearing.
- Type-only modules, `import type {}`, `import {}`, bare side-effect imports, value imports, named value re-exports, and star value re-exports are counterless under the observed Vitest/V8 producer.
- Counterless does not mean non-executable; code, artifacts, diagnostics, tests, and documentation must not make that claim.
- Exact all-zero rows are accepted only for statically counterless modules.
- Critical and high-risk policy modules cannot be counterless.
- All source edits use `apply_patch` and behavior changes follow RED-GREEN TDD.
---
### Task 1: Lock the Vitest/V8 classifier contract with RED tests
**Files:**
- Modify: `tests/unit/risk-coverage.test.ts`
**Interfaces:**
- Consumes: `buildProductionModuleInventory()` and `evaluateRiskCoverage()`.
- Produces: expectations for `counterBearingModules`, `counterlessModules`, `counterBearingTotal`, `instrumentedCounterBearingTotal`, `counterlessTotal`, and `counterlessModules`.
- [x] **Step 1: Rename the test inventory helper and artifact assertions to the desired API.**
```ts
function inventory(
files: readonly string[],
generatedExclusions: readonly string[] = [],
counterlessModules: readonly string[] = [],
): ProductionModuleInventory {
return {
files,
preExclusionTotal: files.length + generatedExclusions.length,
generatedExclusions,
counterBearingModules: files.filter((file) => !counterlessModules.includes(file)),
counterlessModules,
};
}
```
- [x] **Step 2: Add a real-source inventory regression table.**
```ts
const counterlessSources = {
"import-type-empty.ts": "import type {} from './a.ts';\n",
"import-value-empty.ts": "import {} from './a.ts';\n",
"import-side-effect.ts": "import './a.ts';\n",
"import-value.ts": "import { a } from './a.ts';\n",
"reexport-named.ts": "export { a } from './a.ts';\n",
"reexport-star.ts": "export * from './a.ts';\n",
};
```
Assert every key appears in `counterlessModules`, while `export const runtimeValue = 1` and `void globalThis` appear in `counterBearingModules`.
- [x] **Step 3: Run the focused test and verify RED.**
Run: `./node_modules/.bin/vitest run tests/unit/risk-coverage.test.ts --reporter=dot`
Expected: TypeScript/test failures because the counter-bearing API fields do not exist and the current bare import classifier is executable-labelled.
### Task 2: Rename and align static coverage provenance
**Files:**
- Modify: `scripts/lib/risk-coverage.ts`
- Modify: `tests/unit/risk-coverage.test.ts`
**Interfaces:**
- Consumes: Babel `Program.body` nodes and parsed Istanbul/V8 counters.
- Produces: `hasCoverageCounterBearingStatements(source, relativePath)`, a complete `counterBearingModules`/`counterlessModules` partition, and consistently named `RiskCoverageResult` fields.
- [x] **Step 1: Implement the minimal classifier needed by the RED cases.**
`ImportDeclaration`, `ExportAllDeclaration`, and export declarations without a local declaration return `false`; `importKind === "type"` therefore remains counterless even with an empty specifier list. Runtime declarations/initializers and direct statements return `true`.
- [x] **Step 2: Rename inventory, evaluator sets, totals, diagnostics, and policy guards.**
Use these exact artifact fields: `counterBearingTotal`, `instrumentedCounterBearingTotal`, `counterlessTotal`, `counterlessModules`. Use diagnostics containing `counter-bearing`, `counterless`, and `policy-sensitive module cannot be counterless`; remove executable/non-executable terminology from the risk-coverage implementation and tests.
- [x] **Step 3: Run focused GREEN verification.**
Run: `./node_modules/.bin/vitest run tests/unit/risk-coverage.test.ts tests/unit/risk-coverage-files.test.ts tests/unit/bounded-body-reader.test.ts --reporter=dot`
Expected: all focused tests pass and both static partition directions remain fail-closed.
### Task 3: Refresh documentation, repository evidence, and removal evidence
**Files:**
- Modify: `docs/testing/frontend-platform-testing-strategy.md`
- Modify: `.superpowers/sdd/2026-08-01-quality-architecture-remediation/task-1-report.md`
- Modify: `.superpowers/sdd/2026-08-01-quality-architecture-remediation/progress.md`
**Interfaces:**
- Consumes: root and sample-removal checker output after Task 2.
- Produces: documented V8 counter-bearing semantics and current 285/285 plus 268/268 evidence.
- [x] **Step 1: Document that counterless imports/re-exports may execute at runtime but receive no file counters in the observed producer.**
- [x] **Step 2: Run relevant verification.**
```sh
./node_modules/.bin/tsc --noEmit -p tsconfig.node.json
./node_modules/.bin/tsc --noEmit -p tsconfig.test.json
./node_modules/.bin/eslint scripts/lib/risk-coverage.ts tests/unit/risk-coverage.test.ts --max-warnings=0
node scripts/check-risk-coverage.ts
corepack pnpm test:sample-removal
git diff --check
```
Expected root checker: `Risk coverage: PASS (285/285 production modules, 80 thresholds)`.
Expected removal checker: `Risk coverage: PASS (268/268 production modules, 76 thresholds)`; the already-known dependency-cruiser architecture diagnostic may remain the sole removal failure.
- [x] **Step 3: Commit the independently verified follow-up.**
```sh
git add docs/superpowers/plans/2026-08-02-counter-bearing-coverage-provenance.md docs/testing/frontend-platform-testing-strategy.md scripts/lib/risk-coverage.ts tests/unit/risk-coverage.test.ts
git commit -m "refactor: align coverage counter provenance"
```
## Self-review
- Spec coverage: terminology, import/re-export edge cases, policy diagnostics, artifact fields, root/removal evidence, report, and ledger are each assigned above.
- Placeholder scan: no deferred implementation or unspecified test step remains.
- Type consistency: inventory and result names use `counterBearing*`/`counterless*` throughout; the classifier is `hasCoverageCounterBearingStatements`.
@@ -0,0 +1,380 @@
# Frontend Thin Platform Consumer Migration Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. Use `superpowers:using-git-worktrees` before editing.
**Goal:** Convert `clean-architecture-frontend-template` into a thin consumer of `test-assurance-platform` and `cicd-platform` while preserving product source, tests, fixtures, raw product artifact codecs, and all origin-unknown worktree changes.
**Architecture:** Product manifests declare risks, obligations, suites, components, outputs, and platform capabilities. Product scripts execute one bounded product purpose and emit raw artifacts. Test assurance owns selection through assessment; CI/CD owns workflow through promotion. Shadow mode has one candidate writer—the platform—and legacy release/promotion code is read-only until removed. The central required workflow is installed by the platform and is never copied into this repository.
**Tech Stack:** TypeScript 7.0.2, Node.js 24.14.0, pnpm 11.17.0, Vitest 4.1.10, Playwright 1.62.0, YAML/JSON Schema, `testctl` v3, `cicdctl`.
## Global Constraints
- Repository: `/home/donghyeon/workspace/desktop-server-git/clean-architecture-frontend-template`; every command runs from its isolated migration worktree root unless it is explicitly marked read-only against the original dirty worktree.
- Begin only from a clean worktree whose HEAD contains the approved spec amendment and all three 2026-08-02 plans in one immutable planning commit; never modify or clean the original dirty worktree.
- Consume the immutable Test Assurance Task 10 release and CI/CD Task 11 P1/shadow release from the two companion plans.
- Resolve `CICDCTL_BIN` and `TESTCTL_BIN` to absolute executable paths from the signed CICD catalog, and resolve `CICDCTL_DIGEST` and `TESTCTL_DIGEST` from the same catalog. Before every plan/manifest command, `sha256sum` must equal the signed value; PATH fallback is forbidden.
- Do not claim P2/active or delete the legacy path until actual Gitea/runner/scanner/signer/provider evidence and required-status installation are observed.
- Use only the exact capability IDs approved in the design.
- Keep production code, product tests/assertions, mocks, scenarios, fixtures, Vitest/Playwright config, V8 instrumentation inputs, and runtime/release artifact codecs.
- Remove local workflow compilation, risk selection, waiver/normalization/assessment, test scheduling, provider invocation, signing, retention, candidate publication, and promotion engines after cutover.
- `check:types` remains a CI Node responsibility. `check:architecture` alone maps to `architecture-typescript`.
- Artifact browser suites consume the platform candidate; their Playwright `webServer` must never rebuild it.
- The product does not add `.gitea/workflows/required-delivery-guard.yaml` or any equivalent copied central workflow.
- Rollback changes only the signed platform catalog/version pin and promotes a previous immutable subject through `release-control`; it never re-enables a legacy writer.
---
### Task 1: Preserve and classify the existing dirty Task 3 work
**Files:**
- Create in the clean migration worktree: `docs/migration/task3-wip-provenance.json`
- Create: `docs/migration/task3-wip-disposition.md`
- Read only: original dirty worktree tracked diff and untracked files
**Ledger contract:** Each entry records `path`, `workingBlobSha256`, and an ordered `hunks` array. Every hunk records `oldRange`, `newRange`, `diffSha256`, `owner` (`product`, `test-assurance`, `cicd`, `unrelated-or-unknown`), `provenance`, and `disposition` (`preserve-in-original`, `reimplement-in-platform`, `retain-product-codec`, `remove-after-cutover`). Mixed-responsibility files therefore have multiple hunk owners. The ledger does not copy source content or create a patch archive.
- [ ] Verify the migration worktree entry commit before inspecting WIP:
```bash
git show HEAD:docs/superpowers/specs/2026-08-02-platform-owned-frontend-assurance-delivery-design.md
git show HEAD:docs/superpowers/plans/2026-08-02-frontend-platform-consumer-migration.md
git show HEAD:docs/superpowers/plans/2026-08-02-test-assurance-frontend-capabilities.md
git show HEAD:docs/superpowers/plans/2026-08-02-cicd-frontend-assurance-delivery.md
git status --short
```
Expected: all four documents exist and migration-worktree status is empty.
- [ ] Capture `git status --short`, `git diff --name-status`, `git diff --numstat`, `git diff | sha256sum`, per-hunk unified diffs, and `git hash-object` for each present untracked/modified file without writing to the original worktree.
- [ ] Add a failing migration test in `tests/unit/platform-migration-provenance.test.ts` that requires every hunk to have one owner/provenance/disposition, permits mixed owners per file, and rejects a blanket `frontend`/`keep-all` classification.
- [ ] Run:
```bash
corepack pnpm exec vitest run tests/unit/platform-migration-provenance.test.ts
```
Expected: RED because the ledger does not exist.
- [ ] Create the ledger and disposition document with `apply_patch`. Explicitly classify workflow/gate/test-normalization/provider/promotion WIP to its platform owner and origin-unknown files as preserved in the original worktree.
- [ ] Recompute every original working-blob and hunk digest, re-run the test, and compare original status plus digests with the capture; expected PASS with no drift.
- [ ] Commit only the ledger, disposition, and test:
```bash
git add docs/migration/task3-wip-provenance.json docs/migration/task3-wip-disposition.md tests/unit/platform-migration-provenance.test.ts
git commit -m "docs: preserve Task 3 migration provenance"
```
---
### Task 2: Declare the CI/CD platform consumer
**Files:**
- Create: `delivery-platform.yaml`
- Create: `tests/unit/delivery-platform-manifest.test.ts`
**Selected capabilities:**
```text
ci-standard-core
ci-node-typescript
ci-frontend
ci-test-assurance
ci-dependency-vulnerability
ci-sbom
ci-provenance
ci-artifact-signing
ci-static-artifact-supply-chain
ci-static-site-publish
delivery-release-control
```
The manifest declares one frontend component rooted at `.`, one static-site output rooted at `dist`, pnpm frozen install, `lint`, `check:types`, `build`, the size/environment/determinism policy, and a signed `platformVersion` that exists in the released CI/CD catalog. It does not contain a workflow DAG, provider command, test command, or mutable tool image.
- [ ] Add RED tests that invoke `$CICDCTL_BIN manifest compile` after digest verification and reject missing capabilities, a floating platform version, product-owned test stages, arbitrary shell/provider commands, or a second candidate output.
- [ ] Run:
```bash
corepack pnpm exec vitest run tests/unit/delivery-platform-manifest.test.ts
```
Expected: RED because `delivery-platform.yaml` is absent.
- [ ] Add the manifest only; do not add a package script that could resolve an arbitrary PATH binary or local compilation logic.
- [ ] Compile the manifest with `"$CICDCTL_BIN" manifest compile --manifest delivery-platform.yaml --source-revision "$PLATFORM_SOURCE_REVISION" --output artifacts/platform/effective-project.json`; expected PASS at shadow activation.
- [ ] Commit:
```bash
git add delivery-platform.yaml tests/unit/delivery-platform-manifest.test.ts
git commit -m "chore(platform): declare frontend delivery capabilities"
```
---
### Task 3: Declare risks, obligations, change surfaces, and source suites
**Files:**
- Create: `test-assurance.yaml`
- Create generated: `test-assurance.lock.json`
- Create: `config/test-assurance/risks/frontend.json`
- Create: `config/test-assurance/obligations/frontend.json`
- Create: `config/test-assurance/suites/source.json`
- Create: `config/test-assurance/suites/artifact-templates.json`
- Create: `config/test-assurance/change-surfaces/frontend.json`
- Create: `config/test-assurance/legacy-command-disposition.json`
- Create: `scripts/run-contract-negative-fixtures.ts`
- Create: `scripts/reporters/vitest-discovery-reporter.ts`
- Create: `scripts/write-production-module-inventory.ts`
- Modify: `scripts/check-architecture.ts`
- Modify: `vitest.config.ts`
- Create: `tests/unit/test-assurance-source-manifest.test.ts`
- Create: `tests/unit/raw-source-reporters.test.ts`
- Create: `tests/unit/legacy-command-migration-completeness.test.ts`
- Modify: `package.json`
**Source suite mapping:**
| Suite ID | Product argv | Capability |
|---|---|---|
| `runtime-schema` | `corepack pnpm test:runtime-schema` | `unit-typescript-vitest` |
| `unit` | `corepack pnpm test:unit` | `unit-typescript-vitest` |
| `reference-feature` | `corepack pnpm test:reference-feature` | `unit-typescript-vitest` |
| `optional-recipes` | `corepack pnpm test:recipes` | `unit-typescript-vitest` |
| `component` | `corepack pnpm test:component` | `component-react-vitest` |
| `integration` | `corepack pnpm test:integration` | `integration-http-msw` |
| `http-scenarios` | `corepack pnpm test:http-scenario-evidence` | `integration-http-msw` |
| `architecture` | `corepack pnpm check:architecture` | `architecture-typescript` |
| `coverage` | `corepack pnpm test:coverage` | `coverage-v8` |
| `contract-negative-fixtures` | `corepack pnpm test:contract-negative-fixtures` | `unit-typescript-vitest` |
Every source suite declares `executionPhase: SOURCE`, an empty `requiredInputArtifacts`, a bounded argv array, timeouts, environment allowlist, and exact raw artifacts. `check:types` is absent because CI owns it. `test-assurance.yaml` references `artifact-templates.json` through `artifactSuiteTemplateFiles`; those templates are completed in Task 4.
- [ ] Add RED tests for exact capability IDs, one purpose per suite, no aggregate `test:all`, no `check:types`, no shell string, no `--passWithNoTests` on required suites, exact report paths, and source phase with zero input artifacts.
- [ ] Parse every unique command and argument tuple in legacy `config/ci/gates.json` and require exactly one disposition: `ci-node`, `test-assurance-suite`, `cicd-release-or-security`, `product-dev-only`, or `retired-with-platform-evidence`. Missing or duplicate classification fails.
- [ ] Add `test:contract-negative-fixtures` as one bounded product harness: it runs every expected-fail type/coverage/design-system/i18n/diagnostics/registry/route fixture, asserts the exact expected exit code and diagnostic identity from the disposition file, and exits 0 only when all negative contracts fail for the intended reason.
- [ ] Classify `test:browser-capabilities` into the three artifact browser suites, `test:storybook` as a retained source test harness until an explicit platform disposition is proven, and `playwright.dev.config.ts` as `product-dev-only`; no legacy gate command disappears without platform evidence.
- [ ] Add RED reporter tests requiring Vitest discovery IDs/counts alongside JUnit, a canonical production-module inventory alongside V8 coverage, an architecture graph/violations report, and typed HTTP receipts. These are raw observations only and contain no PASS/waiver/threshold decision.
- [ ] Run the focused test; expected RED.
- [ ] Add declarations. Remove `--passWithNoTests` from required source suite scripts while retaining optional behavior only for suites whose obligation explicitly permits no applicable tests. Wire the Vitest reporter into each required Vitest suite, make `check:architecture` emit the typed graph/violation JSON, and rewrite `test:coverage` to emit V8 summary plus production inventory without calling the local risk/threshold assessor.
- [ ] Use released `testctl validate`, `lock`, and `compile` to generate the lock; never type toolchain digests by hand.
- [ ] Re-run the focused test and:
```bash
"$TESTCTL_BIN" validate --repository . --out artifacts/platform/source-validation.json
"$TESTCTL_BIN" lock --repository . --out test-assurance.lock.json
"$TESTCTL_BIN" compile --repository . --lock test-assurance.lock.json --out artifacts/platform/compiled-policy.json
```
Expected: PASS with v3 and exact Node/pnpm/TypeScript/Vitest/Playwright pins.
- [ ] Commit:
```bash
git add test-assurance.yaml test-assurance.lock.json config/test-assurance scripts/reporters/vitest-discovery-reporter.ts scripts/write-production-module-inventory.ts scripts/check-architecture.ts scripts/run-contract-negative-fixtures.ts vitest.config.ts tests/unit/test-assurance-source-manifest.test.ts tests/unit/raw-source-reporters.test.ts tests/unit/legacy-command-migration-completeness.test.ts package.json
git commit -m "chore(test-assurance): declare frontend source suites"
```
---
### Task 4: Make browser suites consume the immutable candidate
**Files:**
- Modify: `playwright.config.ts`
- Modify: `playwright.capabilities.config.ts`
- Modify: `playwright.visual.config.ts`
- Modify: `playwright.storybook.config.ts` only if it remains a required artifact suite
- Create: `scripts/serve-test-candidate.ts`
- Create: `scripts/reporters/playwright-evidence-reporter.ts`
- Create: `tests/support/browser/mutation-evidence.ts`
- Modify: `tests/e2e/reference-form.spec.ts`
- Modify: `package.json`
- Modify: `config/test-assurance/suites/artifact-templates.json`
- Modify: `config/test-assurance/obligations/frontend.json`
- Create: `tests/unit/artifact-suite-contract.test.ts`
**Artifact suite mapping:**
| Suite ID | Product argv | Capability |
|---|---|---|
| `e2e-chromium` | `corepack pnpm test:e2e:chromium` | `e2e-playwright-chromium` |
| `e2e-firefox` | `corepack pnpm test:e2e:firefox` | `e2e-playwright-firefox` |
| `e2e-webkit` | `corepack pnpm test:e2e:webkit` | `e2e-playwright-webkit` |
| `accessibility` | `corepack pnpm test:a11y` | `accessibility-web` |
| `visual-regression` | `corepack pnpm test:visual` | `visual-regression-web` |
All five repository templates declare `executionPhase: ARTIFACT` and one input declaration containing `artifactId: frontend-site` and `mediaType: application/vnd.delivery.static-site.v1+tar`. Templates contain no `sha256` field. After the platform builds the candidate, CI creates an artifact `ExecutionRequest` containing the actual candidate SHA-256; testctl materializes executable v3 `SuiteDefinition` and `WorkItem` documents with that exact digest.
- [ ] Add RED tests requiring separate Chromium/Firefox/WebKit commands and artifacts, rejecting a SHA/digest placeholder in committed templates, rejecting build commands in Playwright `webServer`, requiring candidate-root environment input, and checking typed Playwright JSON/JUnit, trace/screenshot/console/network indexes, browser provider identity, write mutation receipts, accessibility/manual-review identity, and visual baseline/diff identity.
- [ ] Run the focused test; expected RED because browser configuration currently rebuilds the site.
- [ ] Implement `serve-test-candidate.ts` as a bounded read-only static server over the executor-verified candidate directory. It reads the candidate root from the allowlisted environment and never verifies or substitutes the platform digest itself.
- [ ] Change `playwright.config.ts` and `playwright.capabilities.config.ts` to call that server and add per-browser package scripts using `--project`; each browser suite includes both `tests/e2e/` and `tests/browser-capabilities/`. Add the product-owned Playwright reporter and mutation-evidence helper so tests emit traces, screenshots, console/network indexes, response/mutation/reload receipts, accessibility findings/manual records, and visual baselines/diffs without assessing them. Extend `reference-form.spec.ts` with the existing production-shaped create handler: observe the successful HTTP response, read the created resource, reload, read it again, and write one typed receipt keyed by the test/scenario ID.
- [ ] Validate/lock/compile with testctl and run each product suite against a local candidate materialized by the platform fixture.
- [ ] Commit:
```bash
git add playwright.config.ts playwright.capabilities.config.ts playwright.visual.config.ts playwright.storybook.config.ts scripts/serve-test-candidate.ts scripts/reporters/playwright-evidence-reporter.ts tests/support/browser/mutation-evidence.ts tests/e2e/reference-form.spec.ts package.json config/test-assurance tests/unit/artifact-suite-contract.test.ts
git commit -m "refactor(browser): test the immutable platform candidate"
```
---
### Task 5: Separate raw reporters and freeze legacy assurance as read-only
**Files:**
- Retain/refactor: `scripts/run-http-scenario-evidence.ts`
- Retain/refactor: `scripts/lib/http-scenario-evidence.ts`
- Retain/refactor: `scripts/write-a11y-report.ts`
- Create: `scripts/lib/manual-a11y-record.ts`
- Retain: product V8 instrumentation and module-inventory code in `vite.config.ts`, `vitest.config.ts`, and product codecs
- Retain read-only until Task 8: `scripts/check-test-evidence.ts`, `scripts/verify-browser-capability-evidence.ts`, `scripts/check-risk-coverage.ts`, `scripts/lib/risk-coverage.ts`, `scripts/lib/local-policy-evidence.ts`, `scripts/lib/manual-a11y-evidence.ts`, `scripts/verify-a11y-manual.ts`
- Create: `scripts/run-legacy-assurance-probe.ts`
- Modify: `package.json`
- Create: `tests/unit/raw-product-evidence-contract.test.ts`
- Create: `tests/unit/legacy-assurance-readonly.test.ts`
**Boundary:** Product emitters may validate their own artifact schema and cross-fields, but the new manifests may not invoke local obligation satisfaction, waiver, quarantine, retry/flaky, coverage threshold, scenario completeness, browser matrix completeness, or evidence freshness logic. Legacy assessors remain callable only through `run-legacy-assurance-probe.ts`, which writes comparison output to a dedicated read-only shadow namespace and has no candidate/provider/promotion operation.
- [ ] Add RED boundary tests proving raw emitters contain no verdict semantics, new manifests never invoke a legacy assessor, and every legacy assessor is reachable only from the comparison probe.
- [ ] Run `corepack pnpm exec vitest run tests/unit/raw-product-evidence-contract.test.ts tests/unit/legacy-assurance-readonly.test.ts`; expected RED before the boundary is enforced.
- [ ] Move any reusable false-green fixture to the Test Assurance platform implementation commit; in this repository keep only the product input fixture needed to reproduce the report.
- [ ] Extract production-module inventory generation from `check-risk-coverage.ts` and raw manual-review codecs from `manual-a11y-evidence.ts`; keep threshold/completeness logic unchanged solely for the read-only probe until parity.
- [ ] Update suite declarations to point directly at raw artifacts and ensure no automated workflow invokes the legacy probe.
- [ ] Re-run `corepack pnpm exec vitest run tests/unit/raw-product-evidence-contract.test.ts tests/unit/legacy-assurance-readonly.test.ts tests/unit/test-assurance-source-manifest.test.ts tests/unit/artifact-suite-contract.test.ts`; expected PASS.
- [ ] Commit:
```bash
git add scripts/run-http-scenario-evidence.ts scripts/lib/http-scenario-evidence.ts scripts/write-a11y-report.ts scripts/lib/manual-a11y-record.ts scripts/write-production-module-inventory.ts scripts/run-legacy-assurance-probe.ts package.json tests/unit/raw-product-evidence-contract.test.ts tests/unit/legacy-assurance-readonly.test.ts config/test-assurance
git commit -m "refactor(testing): isolate raw and legacy assurance paths"
```
---
### Task 6: Run one-writer shadow parity
**Files:**
- Create: `docs/operations/platform-shadow-parity.md`
- Create: `docs/operations/evidence/platform-shadow-readiness.json` only from an actual platform run
- Create: `tests/unit/platform-shadow-contract.test.ts`
- Modify: `delivery-platform.yaml` and `test-assurance.lock.json` only to pin the released shadow versions
**Parity identity:** source revision, delivery manifest/catalog digests, test manifest/lock/policy digests, selected suite IDs, discovered/executed counts, terminal classifications, coverage production-module universe, HTTP declared/executed IDs, three browser results, candidate/member digests, source and artifact plan/evidence/assessment digests, supply-chain/provider digests, and promotion readiness. Exclude timestamp, duration, temp path, and runner identity.
- [ ] Add RED tests proving there is no product-local central workflow, the platform is the only command allowed to build/freeze a candidate in shadow, legacy release/promotion commands are read-only probes, and no product script invokes a provider or mutates desired state.
- [ ] Before shadow execution, query actual Gitea registration/status state and require: legacy `quality-gates.yml` registration `disabled`; legacy required status `detached`; central workflow installed externally with activation `shadow`; no product `required-delivery-guard.yaml`; legacy candidate/provider/promotion invocation count `0`; platform candidate writer count exactly `1`. Record actual workflow/status IDs and writer identity.
- [ ] Run the released platform fixture against this exact source revision for a passing run and deliberate failures: zero discovery, missing report, retry-only pass, missing browser, changed candidate, wrong provider digest, and response loss.
- [ ] Have the platform runner invoke `run-legacy-assurance-probe.ts` read-only and compare its test classifications with platform assessment. Do not register/run the legacy workflow and do not run legacy candidate creation, publication, or promotion.
- [ ] Record the actual signed parity report digest and environment identities only after the run exists. If the required external environment is unavailable, leave this task incomplete and retain shadow activation.
- [ ] Re-run focused contract tests; expected PASS for repository constraints even if P2 evidence remains blocked.
- [ ] Commit the shadow contract and runbook before external evidence:
```bash
git add docs/operations/platform-shadow-parity.md tests/unit/platform-shadow-contract.test.ts delivery-platform.yaml test-assurance.lock.json
git commit -m "test(shadow): define parity and one-writer contract"
```
- [ ] Only after the real run creates `docs/operations/evidence/platform-shadow-readiness.json`, verify its digest/signature and commit that file alone as `test(shadow): record platform parity evidence`. If the environment is unavailable, do not create or stage the file and leave Task 6 incomplete.
---
### Task 7: Activate the platform and prove platform-only rollback
**Files:**
- Modify: `delivery-platform.yaml` only to select the signed active catalog version
- Modify: `test-assurance.lock.json` only through the verified testctl binary
- Modify: `docs/operations/release-cache-rollback.md`
- Modify: `docs/operations/platform-shadow-parity.md`
- Create: `tests/unit/platform-rollback-contract.test.ts`
- Create from a real drill only: `docs/operations/evidence/platform-cutover-rollback.json`
**Cutover gate:** all ten frontend test capabilities are R1 or higher; every selected CI/CD capability is P1/shadow or higher; source revision and manifest/catalog/lock digests match; the passing run and every named fault fixture have zero parity mismatch; legacy writer count is zero and platform writer count is one; named P2 Gitea/runner/scanner/signer/provider evidence exists; central status `platform/delivery-pipeline` is installed; the previous signed platform pin rollback drill verifies served-content digest; and the WIP ledger has zero unclassified hunks.
**Rollback sequence:** pause new promotions → reconcile every indeterminate operation ID → pin the previous signed platform catalog/version → verify and use the previous `CICDCTL_BIN`/`TESTCTL_BIN` digests → compile both manifests → promote the previous stable immutable subject through `release-control` → verify served-content digest → resume. No product workflow, legacy writer, rebuild, repackage, or mutable tag is permitted.
- [ ] Add RED tests rejecting rollback text/code that restores `quality-gates.yml`, invokes `ci:gate`, enables a legacy writer, rebuilds a candidate, or omits signed catalog, executable, and subject digest checks.
- [ ] Activate the externally installed central workflow/status only after every cutover-gate predicate is machine-verified; do not delete repository files in this task.
- [ ] Run a staging rollback to the previous signed platform pin and previous immutable subject, then roll forward again. Record operation IDs, catalog/executable/release/served-content digests, Gitea status ID, writer identity, and reconciliation outcome.
- [ ] Run `corepack pnpm exec vitest run tests/unit/platform-shadow-contract.test.ts tests/unit/platform-rollback-contract.test.ts`; expected PASS.
- [ ] Commit runbook/test first. Commit `platform-cutover-rollback.json` separately only after a real signed drill exists; otherwise leave Task 7 incomplete.
---
### Task 8: Remove local assurance and delivery engines after rollback evidence
**Entry gate:** Task 7 has a signed evidence digest and served-content equality; removing local engines is forbidden before it.
**Files:**
- Delete: `.gitea/workflows/quality-gates.yml`
- Delete: `config/ci/gates.json`
- Delete: `scripts/generate-ci-workflow.ts`, `scripts/check-ci-contract.ts`, `scripts/run-ci-gate.ts`, `scripts/contracts/ci-gates.ts`
- Delete: `scripts/lib/ci-contract-report.ts`, `scripts/lib/ci-gate-log.ts`, `scripts/lib/ci-step-result.ts`, `scripts/lib/ci-artifact-validator.ts`, `scripts/lib/ci-candidate-archive-cli.ts`, `scripts/lib/ci-candidate-archive.ts`, `scripts/lib/package-script-graph.ts`
- Delete: `scripts/check-test-evidence.ts`, `scripts/verify-browser-capability-evidence.ts`, `scripts/check-risk-coverage.ts`, `scripts/lib/risk-coverage.ts`, `scripts/lib/local-policy-evidence.ts`, `scripts/verify-a11y-manual.ts`, `scripts/run-legacy-assurance-probe.ts`
- Delete after raw codec extraction: `scripts/lib/manual-a11y-evidence.ts`
- Delete: `scripts/create-release-candidate.ts`, `scripts/verify-reproducible-build.ts`, `scripts/verify-ci-candidate-archive.ts`, `scripts/verify-release-candidate.ts`, `scripts/run-and-validate-provider.ts`, `scripts/stage-verified-promotion.ts`, `scripts/verify-provider-evidence.ts`, `scripts/verify-supply-chain-promotion.ts`, `scripts/lib/release-candidate.ts`, `scripts/lib/promotion-stager.ts`, `scripts/lib/promotion-verifier.ts`, `scripts/lib/provider-evidence.ts`, `scripts/lib/provider-upload-validator.ts`
- Delete: `scripts/security-scan.ts`, `scripts/generate-supply-chain.ts`, `scripts/verify-supply-chain-artifacts.ts`, `scripts/verify-archived-local-evidence.ts`, `scripts/check-supply-chain-provider-fixtures.ts`, `scripts/lib/local-release-evidence.ts`, `scripts/lib/release-input-evidence.ts`, `scripts/lib/supply-chain.ts`
- Delete: `tests/unit/ci-workflow-generation.test.ts`, `tests/unit/__snapshots__/ci-workflow-generation.test.ts.snap`, `tests/unit/ci-step-result.test.ts`, and `tests/fixtures/ci-contract/`
- Preserve/refactor: `scripts/contracts/release-artifacts.ts`, `scripts/generate-build-manifest.ts`, `scripts/generate-artifact-schemas.ts`, `scripts/lib/build-manifest-outputs.ts`, `scripts/lib/release-runtime-coherence.ts`, `scripts/lib/manual-a11y-record.ts`, and their product-schema tests
- Modify: `package.json`
- Modify: `docs/operations/ci-quality-gates.md`
- Modify: `docs/security/supply-chain.md`
- Create: `tests/unit/platform-engine-removal.test.ts`
- [ ] Recompute original dirty-worktree blob/hunk digests and stop on drift. Compare the current migration diff to the exact allowed path/hunk set in the ledger before deleting anything.
- [ ] Add a RED removal test that rejects workflow-DAG types, Gitea expressions, artifact upload/download orchestration, test normalizers/assessors, provider/promotion mutation, exact job counts, `ci:gate`, and workflow generator/checker scripts in product paths.
- [ ] Extract the raw manual accessibility record codec to `scripts/lib/manual-a11y-record.ts`, then delete only the assessment/expiry-completeness engine. Delete all other listed platform-owned files hunk-by-hunk; preserve product behavior, assertions, fixtures, and raw artifact codecs.
- [ ] Remove `generate:ci-workflow`, `check:ci-workflow`, `ci:gate`, local `check:ci`, legacy assessor, provider/promotion, supply-chain, and candidate orchestration scripts from `package.json`. Keep build, lint/typecheck, one-purpose suites, raw reporters, and developer-only commands.
- [ ] Run `corepack pnpm exec vitest run tests/unit/platform-engine-removal.test.ts tests/unit/delivery-platform-manifest.test.ts tests/unit/test-assurance-source-manifest.test.ts tests/unit/artifact-suite-contract.test.ts`, then `corepack pnpm check:types` and `corepack pnpm lint`; expected PASS.
- [ ] Verify `git diff --name-status` exactly matches the ledger's Task 8 allowlist. Stage only the explicit files listed in this task; never use `git add scripts`, `git add tests`, or `git add -A`.
- [ ] Commit as `refactor(platform): remove copied assurance and delivery engines`.
---
### Task 9: Finalize documentation, provenance, and consumer verification
**Files:**
- Modify: `docs/operations/release-cache-rollback.md`
- Modify: `docs/operations/platform-shadow-parity.md`
- Modify: `docs/security/supply-chain.md`
- Modify: `README.md`
- Modify and close: `docs/migration/task3-wip-provenance.json`
- Modify: `docs/migration/task3-wip-disposition.md`
- [ ] Mark every WIP hunk `retained`, `reimplemented-in-platform`, `removed-after-cutover`, or `preserved-in-original`; require zero open disposition and reverify original dirty-worktree hashes.
- [ ] Verify both binaries before invoking them:
```bash
printf '%s %s\n' "$CICDCTL_DIGEST" "$CICDCTL_BIN" | sha256sum --check
printf '%s %s\n' "$TESTCTL_DIGEST" "$TESTCTL_BIN" | sha256sum --check
```
- [ ] Run final repository verification:
```bash
corepack pnpm install --frozen-lockfile
corepack pnpm check:types
corepack pnpm lint
corepack pnpm test:all
corepack pnpm exec vitest run tests/unit/delivery-platform-manifest.test.ts tests/unit/test-assurance-source-manifest.test.ts tests/unit/artifact-suite-contract.test.ts tests/unit/platform-engine-removal.test.ts tests/unit/platform-rollback-contract.test.ts
"$TESTCTL_BIN" validate --repository . --out artifacts/platform/final-validation.json
"$CICDCTL_BIN" manifest compile --manifest delivery-platform.yaml --source-revision "$PLATFORM_SOURCE_REVISION" --output artifacts/platform/effective-project.json
git diff --check
```
Expected: PASS. `PLATFORM_SOURCE_REVISION` is the exact 40-hex revision recorded by the platform run, not a branch or mutable lookup. Product tree contains manifests and product tests, not copied platform engines.
- [ ] Commit the two migration ledger files and exact modified docs/README as `docs(platform): finalize consumer cutover`.
## Cross-repository execution order
1. Complete and release the Test Assurance plan through R1/v3 conformance.
2. Complete and release the CI/CD plan through P1/shadow vertical conformance.
3. Execute frontend Tasks 15 and validate both manifests locally.
4. Execute Task 6 only with external one-writer state verified.
5. Execute Task 7 only after every named P2 gate exists and prove rollback before deletion.
6. Execute Tasks 89 only after signed rollback evidence. Until then, the correct state is shadow with legacy comparison code retained and no false active claim.
@@ -0,0 +1,598 @@
# Promotion Security Review Fixes Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make the captured candidate archive and a strict exact-five bundle validator the only promotion authority while closing evidence, lifecycle, staging, output, freshness, and workflow gaps found by security review.
**Architecture:** Candidate identity and local verification are derived exclusively from the inode-captured tar stream. The finalizer revalidates archived subordinate evidence, provider signatures, freshness, role-separated trust, and then creates and validates an exact-five bundle before returning descriptor- and inode-bound cleanup metadata. Provider execution, CLI output publication, and workflow gating expose small injectable boundaries so failure and cleanup behavior can be tested directly.
**Tech Stack:** Node.js 24, TypeScript, Zod, Vitest, GNU tar, bubblewrap, Gitea Actions workflow generation.
## Global Constraints
- Work sequentially on the current clean `develop` HEAD and produce one review-fix commit.
- Every production change follows focused RED, observed expected failure, minimal GREEN, and regression verification.
- Candidate verification performs no checkout reads; test subprocesses from outside the checkout with contradictory canaries.
- Preserve real GNU tar and bubblewrap coverage; do not claim native uploader or atomic `renameat2`/`unlinkat` semantics.
- Gitea 1.26.4 and act_runner 1.0.0 exact-five upload/download/cancel behavior remains an explicitly documented external smoke boundary.
---
### Task 1: Canonical Captured Archive and Archived Local Authority
**Files:**
- Modify: `scripts/lib/release-candidate.ts`
- Modify: `scripts/lib/local-release-evidence.ts`
- Modify: `scripts/lib/ci-candidate-archive.ts`
- Delete: `scripts/lib/promotion-verifier.ts`
- Delete: `scripts/verify-provider-evidence.ts`
- Delete: `scripts/verify-supply-chain-promotion.ts`
- Modify: `tests/unit/security-followup.test.ts`
- Modify: `tests/unit/supply-chain.test.ts`
- Modify: `tests/integration/security-followup-archive.test.ts`
**Interfaces:**
- Consumes: captured tar bytes plus expected SHA-256.
- Produces: `withVerifiedCapturedCandidate()` callback data derived only from the extracted, exact-member, digest-verified tar; `verifyArchivedLocalEvidence()` independently recomputes all feasible archived checks.
- [x] Add failing tests for invalid tar, archive/tree mismatch, contradictory archived subordinate FAIL, exact archived policy bytes, and checkout-independent execution.
- [x] Run focused tests and record the expected RED diagnostics in the durable task report.
- [x] Archive the exact policy/verifier inputs required for independent release, supply-chain, dependency, license, vulnerability, and secret-scan checks.
- [x] Re-run producer checks against the extracted archive and require their result to agree with the assessment and member identities.
- [x] Remove the obsolete standalone PASS issuers and route all fixture checking through real captured tar/finalizer validation.
- [ ] Run focused archive, supply-chain, and integration tests to GREEN.
### Task 2: Exact-Five Validator and Role-Separated Trust
**Files:**
- Create: `scripts/lib/exact-promotion-bundle.ts`
- Create: `scripts/verify-exact-promotion-bundle.ts`
- Modify: `scripts/lib/provider-evidence.ts`
- Modify: `scripts/lib/promotion-stager.ts`
- Modify: `scripts/contracts/promotion-artifacts.ts`
- Modify: `tests/unit/security-followup.test.ts`
- Modify: `tests/unit/ci-artifact-contract.test.ts`
**Interfaces:**
- Consumes: exactly five captured byte buffers and two trusted Ed25519 identities.
- Produces: `verifyExactPromotionBundle()` that requires literal verifier identity/version, provider and subordinate PASS states, exact hashes, and equal run/source/candidate/provider/trust fields.
- [x] Add failing tests for provider FAIL/absence, arbitrary provider hash, swapped roles, shared-field mismatch, archive/report digest mismatch, and identical role keys.
- [x] Run focused tests and record RED.
- [x] Implement strict exact-five parsing/cross-record validation and expose a downstream CLI command.
- [x] Reject equal DER-SPKI fingerprints and equal role key identity before evaluation/finalization.
- [x] Invoke exact-five validation inside the finalizer before publication; the full real-build fixture rerun remains sandbox-blocked below.
### Task 3: Provider Lifecycle and Freshness
**Files:**
- Modify: `scripts/lib/provider-supervisor.ts`
- Create: `scripts/lib/provider-process-runner.ts`
- Modify: `scripts/run-and-validate-provider.ts`
- Modify: `tests/unit/security-followup.test.ts`
- Modify: `tests/unit/ci-artifact-contract.test.ts`
**Interfaces:**
- Consumes: a spawned bubblewrap child, injected timeout/clock, captured report.
- Produces: a runner that SIGKILLs on timeout but rejects only after `close`, and supervision that samples freshness after provider/report capture.
- [x] Add failing stubborn-descendant/short-timeout and sequence-clock expiry tests.
- [x] Run focused tests and record RED.
- [x] Extract the process runner, wait for close after timeout, and preserve the timeout diagnostic.
- [x] Issue provider timestamps immediately before execution, validate with a fresh clock after capture, and reject crossing expiry.
- [x] Run focused lifecycle tests, including real stubborn descendants, to GREEN; the shared real-build/bubblewrap fixture remains sandbox-blocked below.
### Task 4: Inode-Pinned Staging and Output-Failure Cleanup
**Files:**
- Modify: `scripts/lib/promotion-stager.ts`
- Create: `scripts/lib/stage-verified-promotion-cli.ts`
- Modify: `scripts/stage-verified-promotion.ts`
- Modify: `scripts/cleanup-verified-promotion.ts`
- Modify: `tests/unit/ci-artifact-contract.test.ts`
**Interfaces:**
- Produces: `FinalizedPromotion.stagingIdentity` and a testable CLI function whose append failure invokes cleanup from the in-memory result.
- [x] Add failing tests for leaf replacement during writes, final visibility mismatch, partial-failure cleanup, GITHUB_OUTPUT open/write failure, and expiry during staging.
- [x] Run focused tests and record RED.
- [x] Open the created leaf with `O_DIRECTORY|O_NOFOLLOW`, write through `/proc/self/fd/<leafFd>`, pin dev/ino, require visible identity equality, and propagate identity through cleanup.
- [x] Force directory/file modes with `fchmod(0700/0400)` independent of a restrictive owner-preserving umask.
- [x] Extract CLI dependencies; on any post-finalization output failure call direct cleanup before rethrowing.
- [x] Revalidate evidence freshness before sealing and immediately before publication; isolated lifecycle/mode/output tests are GREEN and the shared real-build fixture remains sandbox-blocked below.
### Task 5: Workflow and Install Policy
**Files:**
- Modify: `package.json`
- Modify: `scripts/check-ci-contract.ts`
- Modify: `scripts/contracts/ci-gates.ts`
- Modify: `scripts/generate-ci-workflow.ts`
- Modify: `config/ci/gates.json`
- Modify: `.gitea/workflows/quality-gates.yml`
- Modify: `tests/unit/ci-workflow-generation.test.ts`
- Modify: `tests/unit/__snapshots__/ci-workflow-generation.test.ts.snap`
**Interfaces:**
- Produces: promotion job `if: ${{ always() && needs.immutable_build.result == 'success' && needs.vulnerability_provider.result == 'success' && needs.provenance_provider.result == 'success' }}` and install-bearing script graph enforcement.
- [x] Add failing contract/generator tests for the job condition, upload without `always()`, missing cleanup outputs, and nested install scripts lacking `--ignore-scripts`.
- [x] Run focused tests and record RED.
- [x] Add `--ignore-scripts` to `verify:lockfile` and recursively reject each reachable install invocation without it.
- [x] Extend the typed job condition and render the explicit cancellation-resistant exact-needs predicate.
- [x] Regenerate workflow/snapshot and run workflow contract/byte tests to GREEN.
### Task 6: Fixtures, Documentation, Full Verification, and Commit
**Files:**
- Modify: `scripts/check-supply-chain-provider-fixtures.ts`
- Modify: `docs/security/supply-chain.md`
- Modify: `docs/operations/ci-quality-gates.md`
- Modify: `.superpowers/sdd/2026-08-01-release-evidence-remediation/task-4-report.md` (ignored durable report)
- [x] Replace plaintext candidate fixtures with a real tar and canonical captured-archive/exact-five validation.
- [x] Rewrite operator docs around the sole captured-archive/exact-five authority and retain the Gitea/runner external-smoke residual.
- [ ] Run focused fixtures, archive integration, workflow snapshot/bytes, full unit, types, lint, `check:ci`, and diff checks; escalate only a sandbox-caused EPERM.
- [x] Append all RED/GREEN and verification evidence/constraints to the durable report.
- [ ] Invoke verification-before-completion, review the complete diff, commit once, and report commit/range/status.
---
## Review-Fix Wave D: Sealed Bytes, Replay Context, Scan Trust, and Cancellation
**Constraint:** Work only in the existing uncommitted tree. Do not write `.git`, stage, or commit. Each task follows a focused RED→GREEN cycle and records sandbox `EPERM` separately from product failures.
### Task D1: Seal the actual staged inode bytes
**Files:** `scripts/lib/promotion-stager.ts`, `tests/unit/security-followup.test.ts`, `tests/unit/ci-artifact-contract.test.ts`
**Interface:** The staging writer captures each canonical file through the already-open leaf FD using `O_NOFOLLOW`; it requires a regular single-link inode, mode `0400`, stable dev/ino/size, and the declared SHA-256. `verifyExactPromotionBundle` receives only these captured staged buffers immediately before return.
- [x] Add RED tests for unlink/recreate and chmod/mutation after a file write.
- [x] Implement bounded descriptor-relative capture and exact-five seal validation.
- [x] Run focused staging tests to GREEN.
### Task D2: Bind downstream verification to external expected identity
**Files:** `scripts/lib/exact-promotion-bundle.ts`, `scripts/verify-exact-promotion-bundle.ts`, `tests/unit/ci-artifact-contract.test.ts`
**Interface:** `verifyExactPromotionBundle` requires `expected.run.id`, `expected.run.attempt`, `expected.sourceRevision`, and `expected.archiveSha256`; optional bundle/dist/lock/source-set digests are compared when supplied. The CLI obtains these values from dedicated environment variables and never derives them from the bundle.
- [x] Add a RED signed other-run replay test.
- [x] Implement external expected-context comparison in library and CLI.
- [x] Run exact-bundle tests to GREEN where the sandbox permits.
### Task D3: Pin mkdir-to-open identity
**Files:** `scripts/lib/promotion-stager.ts`, `tests/unit/security-followup.test.ts`, `docs/security/supply-chain.md`, `docs/operations/ci-quality-gates.md`
**Interface:** A post-mkdir/pre-open test hook can replace the leaf. The implementation compares mkdir-returned pathname metadata with the `O_DIRECTORY|O_NOFOLLOW` handle `fstat` before any write; it never uses pathname chmod.
- [x] Add a RED pre-open replacement test.
- [x] Compare created and opened metadata and reject replacement.
- [x] Document the residual portable Node same-UID pre-lstat/native-privilege boundary.
### Task D4: Conservatively parse install invocations
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`
**Interface:** A bounded shell/token parser recognizes `pnpm install|i`, `npm install|ci|i`, and `yarn install` after supported manager-global options with split or `=` values. Any reachable package-manager invocation that cannot be classified is rejected.
- [x] Add the eight required global-option/alias RED cases plus malformed fail-closed cases.
- [x] Implement tokenization and manager-specific invocation classification.
- [x] Run install-policy tests to GREEN.
### Task D5: Signed secret-scan attestation
**Files:** `scripts/lib/provider-evidence.ts`, `scripts/lib/provider-supervisor.ts`, `scripts/lib/provider-upload-validator.ts`, `scripts/lib/promotion-stager.ts`, `scripts/lib/exact-promotion-bundle.ts`, relevant unit/integration tests and docs.
**Interface:** Vulnerability evidence v2 contains a strict `secretScanAttestation` with `status: PASS`, local-assessment, source-set, policy, SARIF, and scan-input digests. The supervisor derives the expected tuple from captured archive members, exports it to the provider, and upload/final verification requires exact equality under the Ed25519 signature.
- [x] Add RED forged-empty-SARIF and attestation-mismatch tests.
- [x] Derive one captured-archive scan context and bind it through supervisor, signed schema, finalizer records, and exact validation.
- [x] Run provider/security tests to GREEN where the sandbox permits.
### Task D6: Cancellation-safe workflow and exact upload paths
**Files:** `scripts/contracts/ci-gates.ts`, `scripts/generate-ci-workflow.ts`, `config/ci/gates.json`, generated workflow/snapshot, workflow tests, and operations/security docs.
**Interface:** Promotion uses a typed dependency-success/no-job-if variant, so cancellation cannot be overridden by job-level `always()`. Step cleanup retains bare `always()` for ordinary failures. Upload documentation names the five canonical paths under `staging_root` and states cancellation cleanup remains a runner/native smoke boundary.
- [x] Add RED generator/contract assertions for no promotion job `if` and retained cleanup `always()`.
- [x] Regenerate workflow and snapshot after the typed condition change.
- [x] Correct operator/security wording and run workflow/CI checks to GREEN.
### Task D7: Verification
- [x] Run focused suites after each GREEN, then affected/full unit tests, all TypeScript targets, ESLint, `check:ci`, generated-byte check, and both diff checks.
- [x] Append exact PASS totals and sandbox-blocked commands to the ignored durable report.
- [x] Report modified files and remaining native/Gitea/unsandboxed verification boundaries; do not attempt git staging or commit.
## Wave E: Unified parser and downstream boundary review
### Task E1: One tokenized manager parser
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`
**Interface:** One parse result reports manager invocations, package-script dependencies, unsupported controls, and effective lifecycle suppression. Both graph traversal and install policy consume it. Single `&`, unknown manager grammar, and malformed options fail closed. The last valid `--ignore-scripts` assignment controls the effective value; false, contradictory, valueless, and malformed assignments are unsafe. Lifecycle-capable mutation builtins are never implicit repository scripts and require effective suppression.
- [x] Add RED tables for single-ampersand segmentation, false/override/malformed suppression, global-option run/implicit dependencies, yarn/corepack reachability, and builtin/script-name collisions.
- [x] Replace the regex traversal and separate install scan with one parser result.
- [x] Run the parser-focused and full workflow-generation suites.
### Task E2: Evaluator-owned secret-scan equality
**Files:** `scripts/lib/provider-evidence.ts`, `tests/unit/security-followup.test.ts`, finalizer tests.
**Interface:** `evaluatePromotionEvidence` itself compares the parsed vulnerability report's signed `secretScanAttestation` with `expected.secretScanAttestation`. A mismatch makes vulnerability and overall promotion status `FAIL_UNVERIFIED`, including the production finalizer path.
- [x] Add a RED evaluator mismatch test.
- [x] Implement exact equality before vulnerability PASS assignment.
- [x] Run security/provider-focused tests.
### Task E3: Downstream CLI exact-five contract
**Files:** `tests/unit/ci-artifact-contract.test.ts`, `tests/unit/security-followup.test.ts`, `scripts/verify-exact-promotion-bundle.ts` if required.
**Interface:** A real finalizer-produced canonical exact-five directory passes the downstream CLI when all required external expected values and trust keys are supplied. Every required expected variable missing or mismatched exits non-zero. Optional digests remain exact when present.
- [x] Add RED happy-path and required-env negative coverage using real finalizer output where sandbox execution permits.
- [x] Make only the minimal CLI/library changes needed for GREEN.
- [x] Separate child-process sandbox blockers from library assertions.
### Task E4: Verification
- [x] Run focused parser/security/CLI suites, TypeScript, ESLint, `check:ci`, and `git diff --check`.
- [x] Run full unit if feasible and report nested-process `EPERM` separately.
- [x] Do not stage or commit.
## Wave F: Manager parser boundary hardening
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`, stage, or commit. Add behavior tests before production changes and keep unsupported manager grammar fail-closed.
### Task F1: Workspace dispatch and authoritative script lookup
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`
**Interface:** The parser receives the authoritative root `scripts` record. Explicit `run` resolves a root script; pnpm/yarn implicit dispatch resolves only a known root script. Yarn `workspace` and `workspaces` dispatch are unsupported because the root graph does not load workspace package scripts. Builtin aliases are canonicalized before root-script lookup.
- [x] Add RED policy and graph tables for the three Yarn workspace dispatchers, pnpm `ln`, and unknown manager subcommands.
- [x] Remove workspace dispatchers from safe builtins, pass known root scripts into the parser, and canonicalize `pnpm ln` to lifecycle `link` before implicit lookup.
- [x] Run the focused dependency/lifecycle cases to GREEN.
### Task F2: Shell comments and lifecycle option state
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`
**Interface:** Unquoted `#` in a manager-bearing command is unsupported control syntax; quoted `#` remains ordinary token content. Lifecycle options are parsed in order into a canonical suppression state covering `--ignore-scripts`, `--no-ignore-scripts`, and `--config.ignore-scripts`; conflicting, malformed, unknown, or ineffective states fail closed. Other option-like lifecycle arguments require an explicit manager allowlist.
- [x] Add RED comment, negative suppression, supported positive, and unknown lifecycle-option tables.
- [x] Implement comment-aware tokenization and one ordered lifecycle argument parser.
- [x] Preserve the checked-in `--frozen-lockfile --ignore-scripts` path and run focused tests to GREEN.
### Task F3: Verification
- [x] Validate every checked-in package script through graph/install consumers without false positives.
- [x] Run the full workflow-generation file and related security tests.
- [x] Run all TypeScript targets, ESLint, `check:ci`, and `git diff --check`; report nested-process `EPERM` separately.
- [x] Update the durable report; do not stage or commit.
## Wave G: Verified cleanup and complete gate/parser preflight
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`, stage, or commit. Every production change follows a focused failing behavior test.
### Task G1: Verified-FD-only failure cleanup
**Files:** `scripts/lib/promotion-stager.ts`, `tests/unit/security-followup.test.ts`, security/operations documentation.
**Interface:** `openedIdentityVerified` becomes true only after the opened directory descriptor matches the post-`mkdir` device/inode. Failure cleanup may unlink canonical files or `rmdir` only through that verified descriptor and a still-matching visible identity. A mismatched opened descriptor and any visible replacement are close-only; a moved original directory remains for fixture/operator cleanup because portable Node cannot safely recover it.
- [x] Change the pre-open replacement regression to require both the replacement canary and displaced original directory to survive the failure.
- [x] Run the focused test to RED against parent-directory identity scanning.
- [x] Remove unverified inode discovery/recovery and gate descriptor cleanup on explicit identity verification.
- [x] Run staging race and cleanup tests to GREEN and document the native residual.
### Task G2: Contract-wide lifecycle preflight
**Files:** `scripts/contracts/ci-gates.ts`, `scripts/run-ci-gate.ts`, optional focused runner helper, `tests/unit/ci-workflow-generation.test.ts`.
**Interface:** `loadCiGateContract` runs `validateInstallScriptPolicy` over every unique contract command script after script existence and graph checks. The runner enters its execution callback only after this loader succeeds, enabling a no-execute regression without relying on a nested child process.
- [x] Add RED loader tables for contradictory npm suppression, pnpm config false, and `pnpm ln`, plus a production runner-boundary no-execute spy.
- [x] Enforce contract-command install policy and route runner execution through the preflight boundary.
- [x] Run loader/runner preflight tests to GREEN.
### Task G3: Foreign manifest scope by command class
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`.
**Interface:** Manager-global options that change cwd, manifest or workspace scope are recorded during parsing. Explicit and implicit package-script dispatch with any such option is unsupported under the root-only graph. Lifecycle commands remain classifiable and are accepted only when their own ordered suppression/option grammar is safe.
- [x] Add RED policy+graph tables for pnpm filter/dir/`-C`, npm workspace/prefix, and Yarn cwd dispatch.
- [x] Add positive externally scoped lifecycle cases with verified suppression.
- [x] Track scope options and reject only package-script dispatch; run focused tests to GREEN.
### Task G4: Argument-sensitive builtin grammar and verification
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`, durable report.
**Interface:** Broad command-name-only safe builtins are replaced by exact per-manager read-only invocations. Init/explore/Yarn npm namespaces are unsupported. Audit is accepted only as an exact bare read-only command; `fix` and all unknown arguments are rejected.
- [x] Add RED policy+graph coverage for npm init/explore/audit-fix and Yarn npm publish, plus a bare-audit positive.
- [x] Replace permissive builtin lookup with exact argument grammar.
- [x] Audit every current package script for graph/policy false positives.
- [x] Run staging/parser/no-execute/workflow/security suites, all TypeScript targets, ESLint, `check:ci`, and `git diff --check`; record sandbox `EPERM` separately and do not stage or commit.
## Wave H: npm post-script scope-option boundary
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`, stage, or commit. Reproduce every reviewer command in a failing test before changing the parser.
### Task H1: Explicit and implicit npm dispatch arguments
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`.
**Interface:** After an explicit `npm run`/`run-script` dependency or an implicit npm lifecycle script, manager options before the first literal `--` are parsed using an exact harmless allowlist. Workspace/prefix selectors (`--workspace`, `-w`, `--workspaces`, `--prefix`, including supported attached/equal forms) and unknown manager options fail closed. Tokens after the first literal `--` are script arguments and cannot change the authoritative manifest scope.
- [x] Add RED policy-and-graph coverage for all seven reviewer inputs, short/equal forms, unknown pre-delimiter options, the literal `--` boundary, and ordinary current-tree dispatch.
- [x] Implement one npm post-script argument parser shared by explicit and implicit dispatch.
- [x] Run focused parser tests to GREEN.
### Task H2: Contract loader and runner boundary
**Files:** `tests/unit/ci-workflow-generation.test.ts`, contract preflight only if the RED test exposes a separate integration defect.
**Interface:** Every reviewer input is rejected by contract loading while the referenced root scripts exist and are otherwise safe. `withCiGatePreflight` must not enter its callback for any rejected command.
- [x] Add a table-driven loader/no-callback regression for the same seven reviewer inputs.
- [x] Run focused preflight tests to GREEN.
### Task H3: Verification
- [x] Re-audit current package scripts through graph and policy consumers.
- [x] Run workflow/security suites, all TypeScript targets, ESLint, `check:ci`, and `git diff --check`.
- [x] Record results in the durable report and do not stage or commit.
**Verification evidence:** The focused npm parser/preflight selection passed
31/31. The workflow file passed 208/210; its two remaining tests reached the
known nested-spawn sandbox boundary and reported `EPERM`. Security, supply-chain,
and local-promotion tests passed 64/64. All six TypeScript targets, ESLint,
`check:ci`, and `git diff --check` passed. Auditing the checked-in package found
zero policy failures across 109 scripts and zero graph failures across 108
entries (excluding the intentionally direct runner entry `ci:gate`). A broader
artifact-contract run passed 102 assertions and blocked 23 fixture cases at the
same nested `git ls-files` `EPERM` boundary. No `.git` write was performed.
Local pnpm 11.17 execution showed post-script `--filter`/`--dir` tokens arriving
in the root script's argv, and official Yarn run documentation defines all
parameters after the script name as script arguments. Those pre-existing
negative expectations were therefore corrected to positive regressions; only
npm receives the new post-script manager-option grammar.
## Wave I: npm hook closure and environment scope
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`,
stage, or commit. Add focused behavior tests and observe RED before each
production change.
### Task I1: npm pre/main/post dependency closure
**Files:** `scripts/lib/package-script-graph.ts`,
`tests/unit/ci-workflow-generation.test.ts`.
**Interface:** Explicit npm `run`/`run-script` and implicit
`start`/`stop`/`restart`/`test` return existing root-manifest lifecycle hooks in
`pre`, main, `post` order. Hooks are omitted only when ordered manager/tail
suppression is unambiguously effective before the first literal `--`; bare,
false, negative, contradictory, malformed, or post-delimiter suppression keeps
hook traversal active or fails closed.
- [x] Add RED policy/graph tables for nested, test, and restart pre/post hooks.
- [x] Add RED suppression positives and false/negative/contradictory/delimiter negatives.
- [x] Make npm tail parsing update the invocation suppression state and expand dependencies.
- [x] Run hook/parser tests to GREEN.
### Task I2: Tokenized npm scope environment
**Files:** `scripts/lib/package-script-graph.ts`, `scripts/contracts/ci-gates.ts`,
`tests/unit/ci-workflow-generation.test.ts`.
**Interface:** Case-insensitive assignments to `npm_config_workspace`,
`npm_config_workspaces`, or `npm_config_prefix` fail closed when their shell
segment executes npm. Direct assignment, `env`, `/usr/bin/env`, and an exported
assignment inherited by a later npm segment are covered without raw-substring
false positives for quoted text. `withCiGatePreflight` also rejects the same
sensitive keys inherited through `process.env` before entering its callback.
- [x] Add RED policy/graph coverage for all reviewer assignment forms and quoted/current-tree positives.
- [x] Add RED loader/no-callback coverage for command assignments and inherited process environment.
- [x] Implement token/segment assignment state and the preflight environment boundary.
- [x] Run environment/parser/preflight tests to GREEN.
### Task I3: Verification
- [x] Audit every current script through graph and policy consumers.
- [x] Run focused parser/preflight, workflow/security, all TypeScript targets,
ESLint, `check:ci`, and `git diff --check`.
- [x] Update durable operations/security documentation and record sandbox-only
nested spawn failures separately; do not stage or commit.
**Verification evidence:** Hook closure began RED 9/9 and GREEN 9/9;
ordered suppression began with 6 expected failures and finished GREEN 22/22;
wrapper/export/inherited environment coverage began with 7 expected failures
and finished GREEN 28/28. A final all-command-class environment RED 3/3
closed scoped lifecycle and builtin invocations. The combined Wave I focused
selection passed 59/59. The complete workflow file passed 269/271; its only
two failures were the existing nested child-spawn `EPERM` fixtures. Security,
supply-chain, and local-promotion tests passed 64/64. All six TypeScript
targets, ESLint, `check:ci`, and `git diff --check` passed. The checked-in tree
had zero policy failures across 109 scripts, zero graph failures across 108
entries after excluding the intentional direct runner entry `ci:gate`, and no
sensitive inherited npm scope environment. No `.git` write was performed.
## Wave J: coherent npm environment state and hook semantics
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`,
stage, or commit. Add each reviewer form as a failing regression before changing
the parser.
### Task J1: Stateful shell npm-scope environment analysis
**Files:** `scripts/lib/package-script-graph.ts`,
`tests/unit/ci-workflow-generation.test.ts`.
**Interface:** Shell segments carry a conservative npm-scope environment state
to later reachable npm invocations. The parser recognizes any static path whose
basename is `env`, optionally behind `command`, and rejects case-insensitive
scope assignments in direct or env-wrapper contexts. Static assignment/export
and `set -a` transitions are modeled across segments. Dynamic assignment names
and environment mutations that cannot be modeled accurately (`set +a`,
`unset`, `export -n`, `eval`, dot/source) make later npm dispatch unsupported.
Quoted harmless text and non-scope static assignments remain accepted; analysis
uses token and segment structure rather than raw substring matching.
- [x] Add RED policy/graph tables for every reviewer state transition, env path,
command wrapper, dynamic assignment name, and unsupported mutation.
- [x] Implement a shared tokenized shell-environment state machine and immediate
npm invocation environment inspection.
- [x] Add harmless quoted/static positive regressions and run focused tests GREEN.
### Task J2: Actual npm lifecycle-hook suppression semantics
**Files:** `scripts/lib/package-script-graph.ts`,
`tests/unit/ci-workflow-generation.test.ts`.
**Interface:** Existing pre/main/post hooks are traversed for `run`,
`run-script`, `start`, `stop`, `restart`, and `test`. Bare `--ignore-scripts`
means true and omits hooks just like explicit true. False, negative,
contradictory, malformed, and post-delimiter forms retain hook traversal or fail
closed according to the existing ordered grammar.
- [x] Add hook safety/order coverage for run-script, start, and stop.
- [x] Move bare suppression forms to positive regressions and retain all false,
negative, contradictory, and delimiter negatives.
- [x] Remove the explicitly-valued distinction and run focused tests GREEN.
### Task J3: Contract boundary and verification
- [x] Run every environment reviewer command through policy, graph, contract
loading, and `withCiGatePreflight`, asserting the callback is never entered.
- [x] Retain the inherited process-environment regression and audit the current
package tree for policy/graph false positives.
- [x] Run workflow/security suites, all TypeScript targets, ESLint, `check:ci`,
and `git diff --check`; record sandbox-only failures and do not stage or commit.
**Verification evidence:** The initial Wave J selection produced 38 expected
failures across loader/policy/graph environment cases and bare hook suppression,
then passed 84/84 after implementation. A separate unsupported dynamic env-wrapper
expansion regression went RED 2/2 and GREEN 2/2; the final combined selection
passed 86/86. The full workflow file passed 327/329, with only the existing two
nested child-spawn `EPERM` fixtures failing at the sandbox boundary. Security,
supply-chain, local-promotion, and promotion-readiness tests passed 71/71. All
six TypeScript targets, ESLint, `check:ci`, and `git diff --check` passed. The
checked-in tree had zero policy failures across 109 scripts, zero graph failures
across 108 entries after excluding the intentional direct runner entry `ci:gate`,
and no sensitive inherited npm scope environment. No `.git` write was performed.
## Wave K: common shell-prefix grammar
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`,
stage, or commit. Every wrapper/prefix reviewer command must be RED in all four
public enforcement paths before production changes.
### Task K1: Shared prefix parser and state-builtin targeting
**Files:** `scripts/lib/package-script-graph.ts`,
`tests/unit/ci-workflow-generation.test.ts`.
**Interface:** A single token-based prefix helper consumes leading static
assignments, then exact `command`/`exec` wrapper chains and their supported
separator syntax. It reports the effective command token/index, whether parsing
is uncertain, and the leading assignments. Both immediate npm env inspection
and persistent `export`/`set` state updates use this result. `command --` is
accepted; unknown `command` options and unmodeled `exec` options before npm/env
fail closed. Static paths retain basename-`env` behavior.
- [x] Add common policy/graph RED cases for `exec env`, `exec /bin/env`,
`command exec env`, `command -- env`, assignment-prefixed `export`, and
assignment-prefixed `set -a`.
- [x] Reuse the same reviewer table through `loadCiGateContract` and
`withCiGatePreflight`, asserting rejection and no callback entry.
- [x] Implement the shared prefix parser, route immediate env inspection and
state-builtin updates through it, and run the reviewer selection GREEN.
- [x] Preserve split assignment/export ordering, dynamic LHS, quoted text,
harmless `MESSAGE=...`, and supported command-wrapper positives.
### Task K2: Hook selection and final verification
**Files:** `tests/unit/ci-workflow-generation.test.ts`, durable report.
- [x] Ensure the final focused selection explicitly includes the bare npm hook
suppression table as well as prefix/environment policy and runner tests.
- [x] Run the full workflow file and security/supply/local-promotion suites;
classify only the known nested-spawn sandbox failures separately.
- [x] Audit all current scripts through policy and graph, then run all TypeScript
targets, ESLint, `check:ci`, and `git diff --check`; do not stage or commit.
**Verification evidence:** The nine shared shell-prefix reviewer commands began
RED in both enforcement tables, producing 18 expected failures across
loader/runner and policy/graph, then passed 18/18 after the common parser was
connected. The prefix negatives plus harmless positives passed 39/39. The final
focused selection explicitly combined prefix cases, dynamic environment cases,
effective/bare npm hook suppression, and harmless positives and passed 106/106.
The complete workflow file passed 353/355; its only two failures were the known
nested child-spawn `EPERM` fixtures. Security, supply-chain, local-promotion, and
promotion-readiness tests passed 71/71. The current package tree had zero policy
failures across 109 scripts, zero graph failures across 108 entries after
excluding `ci:gate`, and no sensitive inherited npm scope environment. All six
TypeScript targets, ESLint, `check:ci`, and `git diff --check` passed. The shared
workspace was preserved and no `.git` write was performed.
## Wave L: structural manager-prefix gap rejection
**Constraint:** Continue in the shared uncommitted tree. Do not write `.git`,
stage, or commit. Generalize the existing parser; do not add wrapper names to an
allowlist.
### Task L1: Reject unmodeled tokens before package managers
**Files:** `scripts/lib/package-script-graph.ts`,
`tests/unit/ci-workflow-generation.test.ts`.
**Interface:** The common shell-prefix result identifies the first effective
command after modeled assignments and `command`/`exec` wrappers. When manager
scanning later finds a package manager, every token between that effective
command position and the manager position must belong to a grammar explicitly
consumed by immediate env or corepack parsing. Otherwise the invocation is
unsupported. This structural rule covers `nice`, absolute-path `nice`, `nohup`,
and future unknown wrappers without naming them.
- [x] Add policy/graph RED coverage for the four env-wrapper reviewer commands
and direct unknown-wrapper manager commands (`nice npm`, `time pnpm`).
- [x] Reuse the env-wrapper reviewer commands through contract loading and
`withCiGatePreflight`, asserting the callback remains false.
- [x] Implement one structural gap check in manager parsing and run RED cases
GREEN without adding wrapper names.
- [x] Retain modeled assignment, `command`/`exec`/env/corepack, current-tree,
quoted echo, and harmless assignment positives.
### Task L2: Verification
- [x] Run a focused selection containing structural negatives and all modeled
prefix/environment positives.
- [x] Run the workflow and security/supply/local-promotion suites, current-tree
policy/graph/environment audit, all TypeScript targets, ESLint, `check:ci`, and
`git diff --check`; record sandbox-only failures and do not stage or commit.
**Verification evidence:** The six unmodeled-prefix reviewer commands began RED
in both enforcement tables, producing 12 expected loader/runner and policy/graph
failures, then passed 12/12 after one structural prefix-gap check was added. The
unmodeled negatives plus harmless/modeled positives passed 36/36. The final
focused Wave HL environment/prefix and effective/bare hook selection passed
121/121. The full workflow file passed 368/370, with only the two known nested
child-spawn `EPERM` fixtures failing at the sandbox boundary. Security,
supply-chain, local-promotion, and promotion-readiness tests passed 71/71. The
current package tree had zero policy failures across 109 scripts, zero graph
failures across 108 entries after excluding `ci:gate`, and no sensitive inherited
npm scope environment. All six TypeScript targets, ESLint, `check:ci`, and
`git diff --check` passed. No wrapper-name allowlist was added, the workspace was
preserved, and no `.git` write was performed.
@@ -0,0 +1,499 @@
# Provider Evidence Guardian Transaction Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make guardian startup cleanup derive authority only from identities allocated before spawn while preserving no-replace raw/sealed publication and immediate safe retry.
**Architecture:** Before spawn, the client pins the canonical raw/evidence directories and exclusively allocates nonce-private raw-staging/sealed-temp inodes whose handles and identities it retains. The guardian inherits directory fd 3/fd 4 and private-file fd 5/fd 6, binds strictly validated aliases to those inherited identities, and transfers raw authority with a no-replace hard link before authenticated READY. The client and guardian clean only pre-recorded identities; neither promotes a pathname-discovered inode to ownership.
**Tech Stack:** Node.js 24 TypeScript, Vitest, Linux file identities and procfs, systemd user scopes, bubblewrap, cgroup v2.
## Global Constraints
- Tasks 1-7 are the historical round-four/five record. Round-six Task 8 runs only in `/tmp/guardian-race-fix-y05lvLi1/repo` on top of `d781692`; never modify the original workspace, `/tmp/task3-integration-mU4L7J2u`, or the security-finalizer repository.
- Use RED-GREEN-REFACTOR for every production behavior change.
- Guardian argv contains only `process.execPath` and the trusted guardian script; its environment is empty, fd 3/fd 4 are the identity-pinned raw/evidence directories, and fd 5/fd 6 are the identity-pinned private raw/sealed allocations.
- Every request/ack is canonical length-prefixed JSON with exact ordered fields, strict UTF-8, no NUL, total bounds, a 32-byte nonce, and constant-time authentication.
- Canonical raw and sealed paths are derived from guardian `cwd` and provider kind; paths and identities are not accepted in the guard request.
- Provider wall timeout is at most 30 minutes and post-processing allowance is exactly 10 minutes; the guardian maximum lease is 40 minutes.
- Publication is no-replace and directory-durable. Abort/death/deadline cleans every raw/temp/final path that still names a pinned owned inode.
- Preserve all cleanup failures with the primary failure using `AggregateError`.
- Do not add PID-exhaustion loops or claim `RLIMIT_NPROC` enforcement.
- Do not run or report live systemd/bwrap tests as passing while the approval limit prevents execution.
- Never forward raw provider stdout/stderr bytes to supervisor or CI logs.
---
### Task 1: Versioned Transaction Protocol
**Files:**
- Modify: `scripts/lib/provider-guardian-protocol.ts`
- Modify: `tests/unit/task3-selective-integration.test.ts`
**Interfaces:**
- Produces:
```ts
type ProviderGuardianGuard = Readonly<{
kind: "vulnerability" | "provenance";
nonce: Buffer;
deadlineEpochMs: number;
}>;
type ProviderGuardianReady = Readonly<{
nonce: Buffer;
rawDev: number;
rawIno: number;
sealedTempLeaf: string;
sealedDev: number;
sealedIno: number;
}>;
type ProviderGuardianPublish = Readonly<{
nonce: Buffer;
sealedDev: number;
sealedIno: number;
size: number;
sha256: string;
}>;
function encodeProviderGuardianGuard(input: ProviderGuardianGuard): Buffer;
function decodeProviderGuardianReady(payload: Buffer, nonce: Buffer): ProviderGuardianReady;
function encodeProviderGuardianPublish(input: ProviderGuardianPublish): Buffer;
function decodeProviderGuardianPublished(payload: Buffer, nonce: Buffer): void;
function encodeProviderGuardianCommit(nonce: Buffer): Buffer;
```
- [ ] **Step 1: Write failing exact-protocol tests**
Assert that guard contains no path or identity, READY returns authenticated identities, publish binds exact identity/size/SHA-256, PUBLISHED authenticates the same nonce, and duplicate/reordered/trailing/oversized/invalid UTF-8/NUL/short-nonce frames fail.
```ts
expect(JSON.parse(encodeProviderGuardianGuard(guard).subarray(4).toString())).toEqual({
type: "guard", version: 2, kind: "vulnerability",
nonce: nonce.toString("hex"), deadlineEpochMs,
});
expect(() => decodeProviderGuardianReady(duplicateNoncePayload, nonce)).toThrow(/canonical|fields/u);
```
- [ ] **Step 2: Run focused RED**
Run: `node_modules/.bin/vitest run tests/unit/task3-selective-integration.test.ts --reporter=default --maxWorkers=1`
Expected: FAIL because the v2 guard/READY/publish/PUBLISHED APIs do not exist and the old guard still accepts identities.
- [ ] **Step 3: Implement the minimal v2 codecs**
Use one bounded prefix/strict decode utility, exact ordered key arrays, canonical re-encoding, lowercase 64-hex nonces/SHA-256, safe positive integers, and `timingSafeEqual` for every acknowledgement/authentication comparison.
- [ ] **Step 4: Run focused GREEN**
Run the Step 2 command and require the protocol tests to pass.
### Task 2: Guardian-Owned Raw and Sealed Transaction
**Files:**
- Modify: `scripts/lib/provider-raw-guardian.ts`
- Modify: `scripts/lib/provider-raw-cleanup.ts`
- Modify: `tests/unit/task3-selective-integration.test.ts`
**Interfaces:**
- Consumes: Task 1 codecs.
- Produces: real process state machine `guard -> READY -> publish -> PUBLISHED -> commitPending -> EOF success`.
- [ ] **Step 1: Write failing real-process creation tests**
Cover no-frame and partial-frame EOF with no files, authenticated READY-created raw/temp identities and modes, full-frame parent EOF cleanup before commit, deadline cleanup, and a near-timeout successful transaction.
```ts
child.stdin.end(partialFrame);
await completion;
await expect(lstat(rawPath)).rejects.toMatchObject({ code: "ENOENT" });
expect(await lstat(rawPath)).toMatchObject({ mode: expect.any(Number) });
```
- [ ] **Step 2: Verify creation RED**
Run the focused test and require failure because the existing guardian expects supervisor-created identity and emits line-based READY.
- [ ] **Step 3: Implement exclusive creation and READY**
Derive canonical leaves, create raw and random sealed sibling temp with `O_EXCL|O_NOFOLLOW`, set raw/temp `0600`, fstat identities, close raw, keep temp handle, and emit bounded READY. On every error, attempt all owned cleanup before nonzero exit.
- [ ] **Step 4: Write failing publish/state tests**
Write validated bytes to the pinned temp, request publish, require PUBLISHED and final mode/hash/identity, then verify commit waits for EOF. Send one later trailing byte after commit and require final cleanup/nonzero exit. Kill the parent after PUBLISHED and require raw/temp/final absence.
- [ ] **Step 5: Implement no-replace durable publish and serialized terminal cleanup**
Verify held descriptor/path identity, `nlink=1`, `0400`, size, and SHA-256. Use `link(temp, final)`, `unlink(temp)`, final lstat identity, and parent-directory fsync. Serialize frame and EOF handling so a publish/death race cannot bypass cleanup. Commit removes raw and sets `commitPending`; only clean EOF exits zero.
- [ ] **Step 6: Run real-process GREEN**
Run focused tests and require zero raw/temp/final/process residuals in every failure case.
### Task 3: Authenticated Client Lease and Fallback Cleanup
**Files:**
- Modify: `scripts/lib/provider-guardian-client.ts`
- Modify: `scripts/lib/validated-json-artifact.ts`
- Modify: `tests/unit/task3-selective-integration.test.ts`
- Modify: `tests/unit/validated-json-artifact.test.ts`
**Interfaces:**
- Produces:
```ts
type ProviderGuardianLease = Readonly<{
pid: number;
rawPath: string;
rawIdentity: Readonly<{ dev: number; ino: number }>;
sealedPath: string;
sealedTempPath: string;
sealedIdentity: Readonly<{ dev: number; ino: number }>;
prematureExit: Promise<Error>;
publish(bytes: Buffer): Promise<void>;
commit(): Promise<void>;
abort(): Promise<void>;
}>;
function serializeValidatedJsonArtifact(input: ValidatedJsonArtifactInput): Buffer;
```
- [ ] **Step 1: Write failing client transaction tests**
Require exact guardian argv and empty environment, READY identity capture, pinned temp write/fsync/mode, PUBLISHED wait, exactly-one terminal action, post-READY guardian SIGKILL cleanup of raw/temp/final, and cleanup error aggregation.
- [ ] **Step 2: Verify client RED**
Run focused and validated-writer tests. Expect missing publish/identity/serializer APIs.
- [ ] **Step 3: Implement serialization and lease**
Extract the existing schema-parse/pretty-JSON/newline serialization without changing `writeValidatedJsonArtifact`. Open the returned temp with `O_NOFOLLOW`, fstat identity, truncate/write/chmod `0400`/fsync/fstat/close, send authenticated publish metadata, and wait for PUBLISHED. Fallback cleanup attempts raw, temp, and final using READY identities and aggregates failures.
- [ ] **Step 4: Run client GREEN**
Run the Step 2 tests and require exact bytes, identities, cleanup, and no residual child.
### Task 4: Supervisor Transaction and Scope-Active Latch
**Files:**
- Modify: `scripts/run-and-validate-provider.ts`
- Modify: `tests/unit/task3-selective-integration.test.ts`
- Modify: `tests/unit/ci-artifact-contract.test.ts`
**Interfaces:**
- Consumes: Task 3 lease and serializer.
- Produces: guardian-owned raw/provider execution, awaited publication, output append, commit/EOF, and scope-confined kill ownership.
- [ ] **Step 1: Write failing supervisor ordering/latch tests**
Require no `createProviderOutput`, lease start before provider, lease raw identity passed to scope, serialized bytes published before output append, commit after output append, and postprocess allowance included in lease. Add a pure scope-latch unit boundary or static contract proving the guardian callback can call `killProviderUnit` only while `scopeActive` is true.
- [ ] **Step 2: Verify supervisor RED**
Run focused tests and expect the old create/write/cleanup ordering assertions to fail.
- [ ] **Step 3: Integrate the lease transaction**
Start guardian in `executeProvider`, use READY raw path/identity for provider bind and capture, publish serialized validated evidence through the lease, append output, then commit. Remove supervisor raw creation and normal sealed writer publication. Keep only identity-bound lease fallback cleanup.
Set `PROVIDER_POSTPROCESS_TIMEOUT_MS = 600_000` and request `providerWallTimeoutMs + PROVIDER_POSTPROCESS_TIMEOUT_MS`.
- [ ] **Step 4: Implement scope-active guardian exit ownership**
Race an awaited scope-completion promise against termination. The guardian callback records its error and invokes termination only while `scopeActive`; the same function sets the latch false exactly once when kill/collection or normal collection completes. The callback never throws or creates an unobserved kill promise after the latch closes.
- [ ] **Step 5: Run supervisor GREEN**
Run focused tests and type/lint checks. Live systemd tests remain unexecuted and are not reported as passing.
### Task 5: Regression Fixtures and Documentation
**Files:**
- Modify: `tests/unit/task3-selective-integration.test.ts`
- Modify: `tests/unit/ci-artifact-contract.test.ts`
- Modify: `docs/operations/ci-quality-gates.md`
- Modify: `docs/security/supply-chain.md`
- Modify: `docs/superpowers/specs/2026-08-02-provider-raw-guardian-design.md`
- [ ] **Step 1: Complete real-process regressions**
Cover no/partial frame, parent kill near READY, post-READY guardian kill, PUBLISHED parent death, publish/commit race, later-chunk commit trailing data, deadline/near-timeout, same-workspace retry, and zero guardian/raw/temp/final residuals.
- [ ] **Step 2: Specify live regressions**
Add active-scope guardian kill, post-scope/precommit guardian kill, supervisor hard death after PUBLISHED with same-workspace retry, and detached descendant attempts for both an external marker and raw append. Every case requires zero cgroup/process/file residuals. Do not execute these tests under the current approval limit.
- [ ] **Step 3: Correct operations and security docs**
Document guardian-owned creation/publication, READY/PUBLISHED identities, ten-minute postprocess lease, commitPending/EOF success, no-replace link publication, scopeActive kill ownership, regular-file `GITHUB_OUTPUT`, and explicit live-test limitation.
- [ ] **Step 4: Fresh verification**
Run focused real-process tests, validated artifact tests, direct Node/test/recipe TypeScript configs, full lint, artifact schemas, CI contract, generated workflow byte check, and `git diff --check`. Record broad-suite sandbox `EPERM` separately and never convert unexecuted live tests into PASS.
- [ ] **Step 5: Review and commit round four**
Confirm only the isolated worktree changed, no protocol secret/path enters argv, cleanup checks both sealed names by identity, and only the temp `node_modules` symlink remains untracked. Create a separate round-four implementation commit above the design/plan commit.
### Task 6: Round-Five Pre-READY Recovery Authority
**Files:**
- Modify: `scripts/lib/provider-guardian-protocol.ts`
- Modify: `scripts/lib/provider-guardian-client.ts`
- Modify: `scripts/lib/provider-raw-guardian.ts`
- Test: `tests/unit/provider-guardian-transaction.test.ts`
**Interfaces:**
- Produces: `providerGuardianSealedTempLeaf(kind, nonce): string`, inherited raw/evidence directory fds 3/4, and descriptor-relative startup/lease cleanup.
- [x] **Step 1: Write failing pre-READY hard-death tests**
Start the real client without awaiting READY, observe its direct guardian child,
kill the guardian when either deterministic transaction leaf first appears, and
require startup rejection, zero raw/temp/final residuals, and a successful
same-workspace `startProviderGuardian(...).abort()` retry. Also require the temp
leaf computed before spawn to equal READY exactly and inherited fd 3/fd 4 to
remain directories during the lease.
- [x] **Step 2: Run focused RED**
Run: `node_modules/.bin/vitest run tests/unit/provider-guardian-transaction.test.ts --reporter=default --maxWorkers=1`
Expected: FAIL because the client has neither pre-spawn directory handles nor a
deterministic temp leaf and cannot clean a guardian killed before READY.
- [x] **Step 3: Implement pinned descriptor recovery**
Open and verify the canonical raw/evidence directories with
`O_DIRECTORY|O_NOFOLLOW`; derive the temp leaf from provider kind and the first
16 nonce bytes; spawn with those handles at fd 3/fd 4. Use only
`/proc/self/fd/<fd>/<leaf>` for guardian creation, publication, sync, and cleanup.
On startup failure, open each exact leaf through the still-live client
descriptor, fstat a regular single-link inode, close the discovery handle, and
run identity-bound quarantine/unlink. Aggregate primary, cleanup, and directory
close errors. Retain both handles until commit/abort terminates.
- [x] **Step 4: Run focused GREEN**
Run the Step 2 command and require the pre-READY kill/retry and all round-four
transaction tests to pass.
### Task 7: Round-Five Log Privacy and Terminal Fail-Closed Behavior
**Files:**
- Modify: `scripts/run-and-validate-provider.ts`
- Modify: `scripts/lib/provider-raw-guardian.ts`
- Test: `tests/unit/provider-guardian-transaction.test.ts`
- Test: `tests/unit/ci-artifact-contract.test.ts`
**Interfaces:**
- Consumes: Task 6 descriptor-pinned transaction.
- Produces: bounded discard of provider output and nonzero guardian termination even when diagnostic fds are closed.
- [x] **Step 1: Write failing privacy and closed-stderr tests**
Run a successful provider that both receives and prints a unique
`VULNERABILITY_PROVIDER_*` credential, then assert the credential is absent from
supervisor stdout/stderr while the sealed signed evidence succeeds. Replace the
FD-limit provider's stderr marker expectations with evidence/side-channel state.
Spawn a real guardian with stderr's read side destroyed, establish owned files,
then abort or send invalid input and require zero files plus a nonzero exit.
- [x] **Step 2: Run targeted RED**
Run the focused guardian and selected CI artifact tests. Expect credential
disclosure and the existing raw provider stderr marker assertions to fail the
new contract; the EPIPE case can exit without the required nonzero terminal.
The executable non-live RED observed six expected failures: missing deterministic
leaf/fd inheritance/output limiter, retained pre-READY raw, and closed-stderr
exit 0. The live credential-printing fixture is authored but remains NOT RUN.
- [x] **Step 3: Implement minimal privacy and terminal fixes**
Continue counting provider stdout/stderr bytes against the aggregate output
limit but discard captured bytes instead of retaining or forwarding them. Make
guardian fd-close and stderr diagnostics best effort, run cleanup first, and
place `process.exit(exitCode)` or self-`SIGKILL` in an unconditional final
branch that cannot be skipped by `EPIPE`/`EBADF`.
- [x] **Step 4: Run targeted GREEN and regression verification**
Run focused guardian tests, selected non-live privacy tests, Node/test
TypeScript, affected ESLint, docs readiness, and `git diff --check`. Do not run
live systemd/bwrap tests under the approval limit.
- [x] **Step 5: Commit round five implementation**
Commit production, tests, and operational/security documentation separately
above this round-five design/plan commit. Record live systemd/bwrap as NOT RUN.
Round-five verification record:
- Focused real-process/unit GREEN: 4 files, 52 tests passed.
- Direct Node and test TypeScript projects: PASS.
- Affected ESLint with zero warnings: PASS.
- Documentation readiness: `PASS_SCOPED`.
- `git diff --check`: PASS.
- Live systemd/bwrap credential, FD-limit, cgroup, and hard-death fixtures:
**NOT RUN** because the active approval limit forbids those executions.
### Task 8: Round-Six Pre-READY Inode Ownership
**Files:**
- Modify: `scripts/lib/provider-guardian-protocol.ts`
- Modify: `scripts/lib/provider-guardian-client.ts`
- Modify: `scripts/lib/provider-raw-guardian.ts`
- Test: `tests/unit/provider-guardian-transaction.test.ts`
- Modify: `docs/security/supply-chain.md`
- Modify: `docs/operations/ci-quality-gates.md`
- Modify: `docs/superpowers/specs/2026-08-02-provider-raw-guardian-design.md`
**Interfaces:**
- Produces:
```ts
function providerGuardianRawStagingLeaf(
kind: ProviderGuardianKind,
nonce: Buffer,
): string;
type RecoveryAuthority = Readonly<{
rawDirectoryHandle: FileHandle;
evidenceDirectoryHandle: FileHandle;
rawStagingHandle: FileHandle;
sealedTempHandle: FileHandle;
rawIdentity: Readonly<{ dev: number; ino: number }>;
sealedIdentity: Readonly<{ dev: number; ino: number }>;
rawStagingPinnedPath: string;
rawPinnedPath: string;
sealedTempPinnedPath: string;
sealedPinnedPath: string;
}>;
```
- [ ] **Step 1: Write the deterministic external-canary RED**
Create a temporary guardian fixture that writes a spawn marker and remains
alive without producing READY. Start the real client, wait for that marker (so
`assertRecoveryLeavesMissing` has completed), create a fixed-raw canary, kill
the direct guardian, and require startup rejection without canary deletion or
mutation.
```ts
const canaryBytes = Buffer.from("external-canary\n");
const canaryHandle = await open(rawPath, constants.O_CREAT | constants.O_EXCL |
constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
await canaryHandle.writeFile(canaryBytes);
const canaryIdentity = await canaryHandle.stat();
await canaryHandle.close();
process.kill(guardianPid, "SIGKILL");
await expect(starting).rejects.toThrow(/provider guardian/u);
expect(await readFile(rawPath)).toEqual(canaryBytes);
expect(await lstat(rawPath)).toMatchObject({
dev: canaryIdentity.dev,
ino: canaryIdentity.ino,
});
```
- [ ] **Step 2: Run the canary RED and confirm the ownership bug**
Run:
`node_modules/.bin/vitest run tests/unit/provider-guardian-transaction.test.ts -t "preserves an external raw canary" --reporter=default --maxWorkers=1`
Expected: FAIL with `ENOENT` when reading the canary because
`discoverAndCleanupOwnedLeaf` opens the current raw pathname and promotes the
external inode to cleanup authority.
- [ ] **Step 3: Add private-leaf derivation and client allocations**
Derive raw staging and sealed temp from the same first 16 nonce bytes:
```ts
return `.${baseLeaf(kind)}.guardian-${nonce.subarray(0, 16).toString("hex")}.raw.tmp`;
```
Through the pinned directory paths, create raw staging and sealed temp with
`O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW`, mode `0600`; require regular file, link count
one, mode `0600`, and size zero; store identities before spawn. Spawn with fd
3-fd 6. If allocation, validation, or spawn fails, identity-clean every private
alias and close every opened handle while preserving primary and cleanup/close
errors in one `AggregateError`.
- [ ] **Step 4: Add concurrency, bootstrap, and link-before-READY RED tests**
Add real-process tests that require:
```ts
// no/partial frame: bootstrap-owned private aliases are removed
child.stdin!.end(partialFrame);
await expect(readdir(rawDirectory)).resolves.toEqual([]);
// same kind: exactly one READY lease, loser never removes winner raw
const results = await Promise.allSettled([startProviderGuardian(input), startProviderGuardian(input)]);
expect(results.filter(({ status }) => status === "fulfilled")).toHaveLength(1);
expect(results.filter(({ status }) => status === "rejected")).toHaveLength(1);
// canonical raw link exists but READY has not been accepted
process.kill(guardianPid, "SIGKILL");
await expect(starting).rejects.toThrow(/provider guardian/u);
await expect(lstat(rawPath)).rejects.toMatchObject({ code: "ENOENT" });
```
The link-before-READY test watches only the fixed raw basename, obtains the
direct child pid before the event, and kills on that exact link event so private
allocation events cannot satisfy the synchronization point. Each test performs
a same-workspace retry and requires no owned private/canonical residue.
- [ ] **Step 5: Implement guardian bootstrap identity binding**
At process bootstrap, fstat fd 5/fd 6 and read `/proc/self/fd/5|6`. Accept an
alias only if `dirname(readlink)` is the canonical expected directory, basename
is a direct child matching the exact raw-staging or sealed-temp lowercase-hex
grammar, both names encode the same kind/nonce prefix, and descriptor-relative
lstat equals the inherited fd identity/type/mode/size/link count. Store the fd
identity before reading any pathname; the pathname only becomes an alias for
that identity.
On valid guard, require exact `providerGuardianRawStagingLeaf(kind, nonce)` and
`providerGuardianSealedTempLeaf(kind, nonce)` matches. Use
`link(rawStaging, rawCanonical)` without replacement, check both aliases equal
the inherited raw identity with link count two, unlink raw staging, fsync fd 3,
and check raw canonical remains the same identity with link count one before
READY. Use the inherited sealed identity for READY and publication.
- [ ] **Step 6: Replace discovery cleanup and close all private fds**
Delete `discoverAndCleanupOwnedLeaf`. Client pre-READY and fallback cleanup
attempts raw staging/canonical with only `recovery.rawIdentity`, then sealed
temp/final with only `recovery.sealedIdentity`. Guardian no/partial-frame and
terminal cleanup uses only its bootstrap fd identities and bound aliases.
On success and every failure branch, attempt all cleanup first, close guardian
fd 5/fd 6 duplicates and client fd 3-fd 6 handles exactly once, and append every
close failure to the existing aggregate. Never open a current leaf to obtain a
new cleanup identity.
- [ ] **Step 7: Run focused GREEN and regressions**
Run:
```bash
node_modules/.bin/vitest run tests/unit/provider-guardian-transaction.test.ts --reporter=default --maxWorkers=1
node_modules/.bin/vitest run tests/unit/provider-output-limiter.test.ts tests/unit/ci-artifact-contract.test.ts --reporter=default --maxWorkers=1
node_modules/.bin/tsc --project tsconfig.node.json
node_modules/.bin/tsc --project tsconfig.test.json
node_modules/.bin/eslint scripts/lib/provider-guardian-protocol.ts scripts/lib/provider-guardian-client.ts scripts/lib/provider-raw-guardian.ts tests/unit/provider-guardian-transaction.test.ts --max-warnings=0
node scripts/verify-documentation-readiness.ts
git diff --check
```
Require focused tests, Node/test TypeScript, affected ESLint, documentation
readiness, and whitespace verification to pass. Live systemd/bwrap fixtures
remain **NOT RUN** under the current approval limit.
- [ ] **Step 8: Review and commit round six**
Confirm the original workspace, `/tmp/task3-integration-mU4L7J2u`, and the
security-finalizer repository are unchanged; only the temporary `node_modules`
symlink is untracked. Commit production/tests/docs together above design commit
`0b1a1db` and report the isolated path, commit SHA, RED evidence, and fresh GREEN
evidence.
@@ -0,0 +1,82 @@
# Security Finalizer Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Finalize a captured immutable candidate into one private random exact-five staging directory with strict v3 verification records and deterministic cleanup.
**Architecture:** `finalizeVerifiedPromotion` captures the archive, provider reports, and public keys before validation, evaluates only those captured bytes against archived local evidence, generates both v3 records in memory, and publishes five read-only files under a descriptor-relative runner-temp directory. The generated workflow consumes the returned staging path immediately and always invokes the token-bound cleanup CLI.
**Tech Stack:** Node.js 24, TypeScript, Zod, Vitest, bubblewrap-independent filesystem primitives, generated Gitea Actions YAML.
## Global Constraints
- Never accept pre-existing provider- or promotion-verification JSON as an input.
- Stage exactly archive, vulnerability report, provenance attestation, provider verification v3, and promotion verification v3.
- Use injected time and randomness for deterministic tests.
- Use a random runner-temp directory at mode `0700`, files at `0400`, and `O_EXCL | O_NOFOLLOW` creation.
- Do not claim that TypeScript closes the Gitea upload action pathname-reopen issue or guarantees `renameat2` semantics.
---
### Task 1: Exact-five finalizer contract
**Files:**
- Modify: `tests/unit/ci-artifact-contract.test.ts`
- Modify: `scripts/lib/promotion-stager.ts`
- Modify: `scripts/contracts/promotion-artifacts.ts`
**Interfaces:**
- Consumes: `finalizeVerifiedPromotion(input, dependencies)` with captured archive/report/key inputs.
- Produces: `{ stagingRoot, cleanupToken, files }` where `files` is the canonical exact-five name/digest list.
- [ ] **Step 1: Write failing tests** for no pre-existing records, strict distinct v3 roles, exact provider-record and local-assessment hashes, full run/source/candidate/nonces/key/trust bindings, key rotation, captured-source mutation, and no output on failures.
- [ ] **Step 2: Run RED:** `corepack pnpm exec vitest run tests/unit/ci-artifact-contract.test.ts -t "verified promotion finalizer" --maxWorkers=1` and retain the first contract failure.
- [ ] **Step 3: Implement minimal finalizer changes** so all validation and record generation consume captured bytes and both PASS records are created only after local/provider PASS.
- [ ] **Step 4: Run GREEN:** rerun the focused Vitest command and require zero failures.
### Task 2: Private staging and cleanup
**Files:**
- Modify: `tests/unit/ci-artifact-contract.test.ts`
- Modify: `scripts/lib/promotion-stager.ts`
- Modify: `scripts/cleanup-verified-promotion.ts`
**Interfaces:**
- Consumes: injected `randomBytes`, runner-temp root, cleanup token.
- Produces: descriptor-relative random staging at `0700`, exact files at `0400`, and token-bound cleanup.
- [ ] **Step 1: Write failing tests** for deterministic naming, modes, stable-path absence, exclusive no-follow creation, parent/leaf substitution, success cleanup, and failure cleanup.
- [ ] **Step 2: Run RED:** use the Task 1 focused Vitest command and retain the first filesystem-boundary failure.
- [ ] **Step 3: Implement minimal private publication and cleanup changes** using `/proc/self/fd` where available, bounded writes, identity rechecks, and removal of owned partial roots.
- [ ] **Step 4: Run GREEN:** rerun the focused Vitest command and require zero failures.
### Task 3: Workflow handoff
**Files:**
- Modify: `config/ci/gates.json`
- Modify: `scripts/contracts/ci-gates.ts`
- Modify: `scripts/stage-verified-promotion.ts`
- Modify: `.gitea/workflows/quality-gates.yml`
- Modify: `tests/unit/ci-workflow-generation.test.ts`
**Interfaces:**
- Consumes: finalizer step outputs `staging_root` and `cleanup_token`.
- Produces: setup, three downloads, finalizer, immediate non-`always()` exact-five upload, and `always()` cleanup ordering.
- [ ] **Step 1: Write/update failing workflow assertions** that reject standalone extraction, stable staging paths, missing `--ignore-scripts`, upload indirection, or cleanup ordering drift.
- [ ] **Step 2: Run RED:** `node scripts/generate-ci-workflow.ts --check` and the workflow snapshot test.
- [ ] **Step 3: Update the CI contract/config and regenerate YAML** with the finalizer output path and cleanup environment.
- [ ] **Step 4: Run GREEN:** require workflow byte check and snapshot test PASS.
### Task 4: Full verification and durable report
**Files:**
- Modify: `.superpowers/sdd/2026-08-01-quality-architecture-remediation/task-3-report.md`
**Interfaces:**
- Consumes: focused finalizer, provider, workflow, type, and lint evidence.
- Produces: durable RED/GREEN evidence and a commit-ready report without overclaiming platform handoff guarantees.
- [ ] **Step 1: Run verification:** focused finalizer/provider tests, `check:supply-chain:provider-fixtures`, workflow `--check`, `check:types`, and `lint`.
- [ ] **Step 2: Append exact RED/GREEN commands and outcomes** to the task report, including the remaining Gitea upload and `renameat2` limitations.
- [ ] **Step 3: Inspect diff/status** and report completion before committing.
@@ -0,0 +1,572 @@
# Test Assurance Frontend Capability Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Extend `test-assurance-platform` so it is the sole authority for selecting, planning, executing, normalizing, evidencing, and assessing every frontend product suite, including tests bound to an immutable static-site candidate.
**Architecture:** The platform keeps v2 JVM documents readable, emits v3 documents for new work, and requires an explicit `SOURCE` or `ARTIFACT` phase plus exact input-artifact identities throughout the execution/evidence chain. A normalizer registry dispatches strict report adapters by capability. Each frontend capability has an independent catalog contract and readiness card; no aggregate frontend readiness is introduced.
**Tech Stack:** Java 21, Gradle Kotlin DSL, Jackson, JSON Schema 2020-12, Node.js 24.14.0, pnpm 11.17.0, TypeScript 7.0.2, Vitest 4.1.10, Playwright 1.62.0.
## Global Constraints
- Repository: `/home/donghyeon/workspace/desktop-server-git/test-assurance-platform`; every command runs from its isolated worktree root.
- Test assurance owns test meaning; it does not create Gitea jobs, allocate remote runners, publish builds, or promote releases.
- New frontend suites require `test-assurance.platform/v3`; there is no implicit phase or host-toolchain fallback.
- `requiredInputArtifacts` is an ordered canonical list of `{artifactId, mediaType, sha256}`. Paths are execution-local data and never artifact identity.
- v2 and v3 evidence cannot be bundled or assessed together.
- Missing, empty, oversized, malformed, symlinked, mismatched, zero-discovery, all-skipped, or retry-only-green evidence fails closed.
- The exact capability IDs are `unit-typescript-vitest`, `component-react-vitest`, `integration-http-msw`, `architecture-typescript`, `coverage-v8`, `e2e-playwright-chromium`, `e2e-playwright-firefox`, `e2e-playwright-webkit`, `accessibility-web`, and `visual-regression-web`.
---
### Task 1: Introduce the v3 artifact-bound execution identity
**Files:**
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/ApiVersion.java`
- Create: `modules/assurance-model/src/main/java/io/testassurance/model/ExecutionPhase.java`
- Create: `modules/assurance-model/src/main/java/io/testassurance/model/InputArtifact.java`
- Create: `modules/assurance-model/src/main/java/io/testassurance/model/ArtifactInputDeclaration.java`
- Create: `modules/assurance-model/src/main/java/io/testassurance/model/ArtifactSuiteTemplate.java`
- Create: `modules/assurance-model/src/main/java/io/testassurance/model/InputArtifactMap.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/SuiteDefinition.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/WorkItem.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/ExecutionRequest.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/ExecutionPlan.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/RawSuiteResult.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/RawResultSet.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/NormalizedSuiteResult.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/EvidenceBundle.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/Assessment.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/RepositoryManifest.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/CompiledPolicy.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/json/JsonReader.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/json/JsonWriter.java`
- Create: `modules/assurance-model/src/main/java/io/testassurance/model/LegacyV2Decoder.java`
- Modify: `modules/assurance-model/src/test/java/io/testassurance/model/ModelRoundTripTest.java`
- Modify: `modules/assurance-model/src/test/java/io/testassurance/model/RepositoryFixtures.java`
- Preserve unchanged: all 26 current v2 files under `schemas/*.schema.json`
- Create: 27 complete v3 wire schemas under `schemas/v3/`, one for every current kind plus `artifact-suite-template.schema.json`
- Create: `schemas/v3/input-artifact-map.schema.json` and register it as an execution-only v3 kind
- Modify: `modules/assurance-schema/src/main/java/io/testassurance/schema/SchemaRegistry.java`
- Modify: `modules/assurance-schema/src/test/java/io/testassurance/schema/SchemaRegistryTest.java`
- Modify: `tools/validate_package.py`
- Modify: `machine/example-schema-map.json`
- Modify: `machine/adversarial-schema-map.json`
- Create: `examples/v3/artifact-suite-template.json`
- Create: `examples/v3/input-artifact-map.json`
- Modify: `modules/assurance-schema/src/main/java/io/testassurance/schema/SchemaRegistry.java`
- Modify: `modules/assurance-schema/src/test/java/io/testassurance/schema/SchemaRegistryTest.java`
- Create: `conformance/adversarial/v3-artifact-suite-missing-input.json`
- Create: `conformance/adversarial/v3-source-suite-with-input.json`
- Create: `conformance/adversarial/v2-v3-evidence-mix.json`
**Interfaces:**
```java
public enum ExecutionPhase { SOURCE, ARTIFACT }
public record InputArtifact(String artifactId, String mediaType, String sha256) {
public InputArtifact {
Identifier.require(artifactId, "inputArtifact.artifactId");
Values.requireText(mediaType, "inputArtifact.mediaType");
Digests.require(sha256, "inputArtifact.sha256");
}
}
public record ArtifactInputDeclaration(String artifactId, String mediaType) {}
public record ArtifactSuiteTemplate(
String id,
String capabilityId,
List<String> command,
int caseTimeoutSeconds,
int suiteTimeoutSeconds,
List<String> requiredArtifacts,
List<String> environmentAllowlist,
List<ArtifactInputDeclaration> requiredInputArtifacts,
Optional<ObjectNode> parameters) {}
public final class ApiVersion {
public static final String V2 = "test-assurance.platform/v2";
public static final String V3 = "test-assurance.platform/v3";
public static final String CURRENT = V3;
}
```
All nine execution-chain records expose `apiVersion()`, `executionPhase()`, and `requiredInputArtifacts()`; `SOURCE` requires an empty input list and `ARTIFACT` requires at least one item. `JsonWriter.envelope(String kind, String apiVersion)` requires an explicit version. Existing `toJson()` methods remain v2-compatible; every new v3 output path calls `toJson(ApiVersion.V3)`. `LegacyV2Decoder` reads the complete v2 repository graph explicitly and ordinary v3 readers never guess a phase. `ExecutionRequest.applicationArtifactDigest` exists only in the frozen v2 decoder and is absent from v3.
In v3, `RawSuiteResult.artifacts` is `List<ArtifactReference>` rather than path strings. The executor computes each digest immediately after the child exits and before results become visible to normalization. This makes raw artifact identity, normalized references, and evidence chain equality directly verifiable.
The committed repository cannot know a future candidate digest. Therefore the approved design amendment adds `RepositoryManifest.artifactSuiteTemplateFiles`: those files declare only artifact ID and media type and are not executable wire `SuiteDefinition` documents. During artifact planning, testctl matches them to concrete `ExecutionRequest.requiredInputArtifacts` and materializes v3 `SuiteDefinition`/`WorkItem` values containing the actual SHA-256. The compiler rejects a digest or digest placeholder in a template. `LegacyV2Decoder` is the only API that maps an approved v2 JVM graph into the legacy SOURCE compatibility path.
- [ ] Add model and schema tests for valid SOURCE v3, valid materialized ARTIFACT v3, absent phase, blank digest, duplicate artifact ID, SOURCE with artifacts, ARTIFACT without artifacts, a template containing any SHA field, v2 JVM graph acceptance, v2 frontend rejection, v2/v3 evidence mixing rejection, and byte-identical frozen v2 schema files.
- [ ] Run:
```bash
./gradlew :modules:assurance-model:test :modules:assurance-schema:test
```
Expected: RED because `ExecutionPhase`, `InputArtifact`, explicit-version envelopes, and the v3 schema registry do not exist.
- [ ] Implement explicit-version codecs and a complete v3 schema set. `SchemaRegistry` keys every schema by `(apiVersion, kind)`. `tools/validate_package.py` validates 26 v2 wire schemas, 28 v3 wire/execution schemas, and 11 report schemas as separate inventories rather than one hard-coded total.
- [ ] Run `./gradlew :modules:assurance-model:test :modules:assurance-schema:test`; expected PASS.
- [ ] Commit:
```bash
git add modules/assurance-model modules/assurance-schema schemas conformance/adversarial tools/validate_package.py machine examples/v3
git commit -m "feat(contracts): add artifact-bound execution v3"
```
---
### Task 2: Propagate phase and input identity through compile, selection, and planning
**Files:**
- Modify: `modules/assurance-compiler/src/main/java/io/testassurance/compiler/PolicyCompiler.java`
- Modify: `modules/assurance-compiler/src/test/java/io/testassurance/compiler/PolicyCompilerTest.java`
- Modify: `modules/assurance-selector/src/main/java/io/testassurance/selector/ChangeSelector.java`
- Modify: `modules/assurance-selector/src/test/java/io/testassurance/selector/ChangeSelectorTest.java`
- Modify: `modules/assurance-plan/src/main/java/io/testassurance/plan/PlanEngine.java`
- Modify: `modules/assurance-plan/src/main/java/io/testassurance/plan/ResourceProfiles.java`
- Modify: `modules/assurance-plan/src/test/java/io/testassurance/plan/PlanEngineTest.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/CompiledPolicy.java`
- Modify: `modules/assurance-model/src/main/java/io/testassurance/model/SelectionResult.java`
**Contract:** The compiler rejects any new frontend capability on v2. The selector preserves the suite phase without changing selected obligations. The planner copies the exact ordered artifact tuple from the request into every selected ARTIFACT work item and rejects a request whose tuple differs from the suite declaration. Plan digest calculation includes phase and artifact tuple.
`PolicyCompiler` stores source `SuiteDefinition` values and artifact `ArtifactSuiteTemplate` values separately. `PlanEngine` exposes `materializeArtifactSuite(ArtifactSuiteTemplate, ExecutionRequest)`; it requires an exact one-to-one match on artifact ID/media type, supplies the request SHA-256, and returns an executable v3 `SuiteDefinition`. No template or unresolved artifact can be serialized into `ExecutionPlan`.
- [ ] Add RED tests named `frontendCapabilityRequiresV3`, `artifactTemplateRejectsDigestPlaceholder`, `artifactTemplateMaterializesConcreteSuite`, `selectorPreservesExecutionPhase`, `artifactPlanRejectsWrongCandidateDigest`, `sourcePlanRejectsArtifactInput`, and `planDigestChangesWithArtifactDigest`.
- [ ] Run:
```bash
./gradlew :modules:assurance-compiler:test :modules:assurance-selector:test :modules:assurance-plan:test
```
Expected: RED because the current compiler and planner do not carry phase/input identity.
- [ ] Implement exact propagation. Do not infer phase from capability name or from `applicationArtifactDigest`.
- [ ] Run `./gradlew :modules:assurance-compiler:test :modules:assurance-selector:test :modules:assurance-plan:test`; expected PASS.
- [ ] Commit:
```bash
git add modules/assurance-compiler modules/assurance-selector modules/assurance-plan modules/assurance-model
git commit -m "feat(plan): preserve source and artifact execution identity"
```
---
### Task 3: Register the frontend capability family and immutable toolchains
**Files:**
- Modify: `machine/capability-catalog.json`
- Modify: `machine/toolchain-catalog.json`
- Modify: `gradle/libs.versions.toml`
- Modify: `modules/assurance-catalog/src/main/java/io/testassurance/catalog/CapabilityCatalog.java`
- Modify: `modules/assurance-catalog/src/main/java/io/testassurance/catalog/CapabilityContract.java`
- Modify: `modules/assurance-catalog/src/main/java/io/testassurance/catalog/ToolchainCatalog.java`
- Create: `modules/assurance-catalog/src/main/java/io/testassurance/catalog/FrontendToolchainInspector.java`
- Create: `modules/assurance-catalog/src/test/java/io/testassurance/catalog/FrontendToolchainInspectorTest.java`
- Modify: `modules/assurance-catalog/src/test/java/io/testassurance/catalog/CatalogsTest.java`
- Modify: `modules/assurance-catalog/src/test/java/io/testassurance/catalog/ReadinessCardsTest.java`
- Modify: `modules/assurance-plan/src/main/java/io/testassurance/plan/ResourceProfiles.java`
- Modify: `modules/testctl-cli/src/main/java/io/testassurance/cli/Commands.java`
- Modify: `modules/testctl-cli/src/test/java/io/testassurance/cli/TestctlMainTest.java`
- Modify: `tools/validate_package.py`
- Modify: `docs/04-test-type-contracts.md`
- Modify: `README.md`
- Create: `readiness/unit-typescript-vitest.json`
- Create: `readiness/component-react-vitest.json`
- Create: `readiness/integration-http-msw.json`
- Create: `readiness/architecture-typescript.json`
- Create: `readiness/coverage-v8.json`
- Modify: `readiness/e2e-playwright-chromium.json`
- Create: `readiness/e2e-playwright-firefox.json`
- Create: `readiness/e2e-playwright-webkit.json`
- Create: `readiness/accessibility-web.json`
- Create: `readiness/visual-regression-web.json`
**Catalog entries:** The catalog grows from 16 to 25 capabilities: nine new entries plus the upgraded existing Chromium entry form the ten-capability frontend family. Every capability declares contract revision v3, execution phase, required raw artifact IDs, case/suite timeout bounds, isolation/resource claims, maximum evidence age, false-green rule IDs, and minimum readiness. Toolchain entries pin version plus observed SHA-256 for Node.js 24.14.0, pnpm 11.17.0, TypeScript 7.0.2, Vitest 4.1.10, Playwright 1.62.0, and independent Chromium/Firefox/WebKit payload IDs.
`FrontendToolchainInspector` reads `.nvmrc`, `package.json.packageManager`, exact devDependency versions, and the root importer in `pnpm-lock.yaml`. It rejects semver ranges, workspace/host fallbacks, missing artifact digests, and lock resolution drift with `TA-LOCK-002`/`TA-LOCK-003`. `Commands.lock()` uses this inspector for any selected frontend capability.
| Capability | Phase | Required raw artifacts |
|---|---|---|
| `unit-typescript-vitest` | SOURCE | JUnit XML, Vitest discovery JSON |
| `component-react-vitest` | SOURCE | JUnit XML, Vitest discovery JSON |
| `integration-http-msw` | SOURCE | JUnit XML, Vitest discovery JSON, typed HTTP scenario receipts |
| `architecture-typescript` | SOURCE | TypeScript architecture graph/violations JSON |
| `coverage-v8` | SOURCE | V8 coverage summary, production-module inventory |
| `e2e-playwright-chromium` | ARTIFACT | Playwright report, provider identity, trace/screenshot/console/network indexes, mutation receipts |
| `e2e-playwright-firefox` | ARTIFACT | Playwright report, provider identity, trace/screenshot/console/network indexes, mutation receipts |
| `e2e-playwright-webkit` | ARTIFACT | Playwright report, provider identity, trace/screenshot/console/network indexes, mutation receipts |
| `accessibility-web` | ARTIFACT | Playwright report, provider identity, accessibility findings, manual-review records |
| `visual-regression-web` | ARTIFACT | Playwright report, provider identity, baseline identity, image-diff results |
- [ ] Add tests that require exactly 25 catalog capabilities and one readiness card per capability, and fail on a missing digest, Node 22 fallback, semver range, pnpm lock drift, capability aliasing, a combined browser readiness card, or missing Firefox/WebKit resource profile.
- [ ] Run:
```bash
./gradlew :modules:assurance-catalog:test :modules:assurance-plan:test
python3 tools/validate_package.py
```
Expected: RED because the capabilities and toolchains are absent.
- [ ] Add independent catalog/resource entries and keep all nine new cards plus Chromium at R0 with explicit `nonGuarantees`. R1 is set only in Task 10 after real adapter and conformance evidence exists.
- [ ] Run `./gradlew :modules:assurance-catalog:test :modules:assurance-plan:test` and `python3 tools/validate_package.py`; expected PASS.
- [ ] Commit:
```bash
git add machine readiness gradle/libs.versions.toml modules/assurance-catalog modules/assurance-plan modules/testctl-cli tools/validate_package.py docs/04-test-type-contracts.md README.md
git commit -m "feat(catalog): register frontend assurance capabilities"
```
---
### Task 4: Replace hard-coded JUnit normalization with a strict registry
**Files:**
- Modify: `modules/assurance-normalizer/src/main/java/io/testassurance/normalizer/SuiteResultNormalizer.java`
- Modify: `modules/assurance-normalizer/src/main/java/io/testassurance/normalizer/NormalizationContext.java`
- Create: `modules/assurance-normalizer/src/main/java/io/testassurance/normalizer/NormalizerRegistry.java`
- Create: `modules/assurance-normalizer/src/main/java/io/testassurance/normalizer/NormalizationArtifactReader.java`
- Create: `modules/assurance-normalizer/src/test/java/io/testassurance/normalizer/NormalizerRegistryTest.java`
- Create: `modules/assurance-normalizer/src/test/java/io/testassurance/normalizer/NormalizationArtifactReaderTest.java`
- Create: `modules/assurance-schema/src/main/java/io/testassurance/schema/ReportSchemaRegistry.java`
- Create: `modules/assurance-schema/src/test/java/io/testassurance/schema/ReportSchemaRegistryTest.java`
- Create report schemas under `schemas/reports/`: `vitest-discovery-report.schema.json`, `http-scenario-receipt-set.schema.json`, `v8-coverage-summary.schema.json`, `production-module-inventory.schema.json`, `typescript-architecture-report.schema.json`, `playwright-suite-report.schema.json`, `browser-provider-report.schema.json`, `browser-mutation-receipt-set.schema.json`, `accessibility-report.schema.json`, `accessibility-manual-review.schema.json`, `visual-regression-report.schema.json`
- Modify: `modules/assurance-schema/build.gradle.kts`
- Modify: `modules/testctl-cli/src/main/java/io/testassurance/cli/Commands.java`
- Modify: `modules/testctl-cli/src/test/java/io/testassurance/cli/TestctlMainTest.java`
- Modify: `modules/testctl-cli/build.gradle.kts`
**Interfaces:**
```java
public interface SuiteResultNormalizer {
String adapterId();
Set<String> capabilityIds();
NormalizedSuiteResult normalize(NormalizationContext context);
}
public final class NormalizerRegistry {
public NormalizerRegistry(Collection<SuiteResultNormalizer> normalizers);
public SuiteResultNormalizer requireFor(String capabilityId);
}
public final class NormalizationArtifactReader {
public NormalizationArtifactReader(ReportSchemaRegistry schemas);
public byte[] readRequired(Path outputRoot, String repositoryRelativePath, long maximumBytes);
public JsonNode readRequiredJson(Path outputRoot, String repositoryRelativePath, long maximumBytes, ReportSchemaRegistry.ReportKind reportKind);
public ArtifactReference reference(Path outputRoot, String repositoryRelativePath, String mediaType, long maximumBytes);
}
```
The registry rejects duplicate adapter IDs, duplicate capability ownership, and zero matches. `Commands.normalize()` asks the registry for exactly one adapter and no longer constructs `JunitXmlNormalizer` directly. The common reader uses `NOFOLLOW_LINKS`, checks a regular file before reading, bounds bytes, rejects empty or malformed UTF-8, canonicalizes repository-relative paths, and validates JSON against `ReportSchemaRegistry` before adapters inspect fields.
- [ ] Add RED tests for no match, two owners, stable registration order, adapter exception mapping, preservation of v3 input artifacts, path escape, symlink, empty file, oversized file, malformed UTF-8, unknown report schema, and `additionalProperties` rejection.
- [ ] Run:
```bash
./gradlew :modules:assurance-normalizer:test :modules:testctl-cli:test
```
Expected: RED because dispatch is hard-coded.
- [ ] Implement registry wiring with explicit constructors in `testctl-cli`; do not use classpath scanning.
- [ ] Run `./gradlew :modules:assurance-normalizer:test :modules:testctl-cli:test`; expected PASS.
- [ ] Commit:
```bash
git add modules/assurance-normalizer modules/assurance-schema modules/testctl-cli schemas/reports
git commit -m "refactor(normalizer): dispatch by capability contract"
```
---
### Task 5: Normalize Vitest results and V8 production coverage
**Files:**
- Create: `adapters/vitest/build.gradle.kts`
- Create: `adapters/vitest/src/main/java/io/testassurance/adapter/vitest/VitestJunitNormalizer.java`
- Create: `adapters/vitest/src/main/java/io/testassurance/adapter/vitest/VitestDiscoveryReport.java`
- Create: `adapters/vitest/src/test/java/io/testassurance/adapter/vitest/VitestJunitNormalizerTest.java`
- Create fixture directories: `adapters/vitest/src/test/resources/valid/`, `adapters/vitest/src/test/resources/zero-tests/`, `adapters/vitest/src/test/resources/all-skipped/`, `adapters/vitest/src/test/resources/retry-green/`, `adapters/vitest/src/test/resources/missing-discovery/`, `adapters/vitest/src/test/resources/malformed/`, `adapters/vitest/src/test/resources/oversized/`, `adapters/vitest/src/test/resources/symlink/`
- Create: `adapters/v8-coverage/build.gradle.kts`
- Create: `adapters/v8-coverage/src/main/java/io/testassurance/adapter/coverage/V8CoverageNormalizer.java`
- Create: `adapters/v8-coverage/src/main/java/io/testassurance/adapter/coverage/ProductionModuleInventory.java`
- Create: `adapters/v8-coverage/src/test/java/io/testassurance/adapter/coverage/V8CoverageNormalizerTest.java`
- Create fixture directories: `adapters/v8-coverage/src/test/resources/valid/`, `adapters/v8-coverage/src/test/resources/missing-module/`, `adapters/v8-coverage/src/test/resources/zero-universe/`, `adapters/v8-coverage/src/test/resources/path-mismatch/`, `adapters/v8-coverage/src/test/resources/malformed/`, `adapters/v8-coverage/src/test/resources/oversized/`, `adapters/v8-coverage/src/test/resources/symlink/`
- Modify: `settings.gradle.kts`
- Modify: `build.gradle.kts`
- Modify: `modules/testctl-cli/build.gradle.kts`
- Modify: `modules/testctl-cli/src/main/java/io/testassurance/cli/Commands.java`
**Module dependencies:** Both modules expose the model/normalizer APIs, implement report schema support, and are added to root `javaModules`. Vitest additionally depends on `:adapters:junit-gradle` to reuse secure XML parsing.
**Rules:** `VitestJunitNormalizer` owns only `unit-typescript-vitest` and `component-react-vitest`; it exports `VitestReportReader` for HTTP composition. `V8CoverageNormalizer` alone owns `coverage-v8`. Missing production module is `INCOMPLETE_DELETED_TEST`; non-empty inventory with zero counter-bearing modules is `INCOMPLETE_ZERO_TESTS`; summary/inventory count or digest mismatch is `INVALID_RESULT`; configured threshold miss is `FAIL_PRODUCT`.
- [ ] Write all adapter tests before implementation and confirm RED:
```bash
./gradlew :adapters:vitest:test :adapters:v8-coverage:test
```
- [ ] Implement bounded regular-file reads, fatal UTF-8, secure XML, canonical repository-relative paths, cross-file count reconciliation, and no symlink following.
- [ ] Register the three capability owners in the CLI registry.
- [ ] Re-run focused tests plus `:modules:testctl-cli:test`; expected PASS.
- [ ] Commit:
```bash
git add adapters/vitest adapters/v8-coverage settings.gradle.kts build.gradle.kts modules/testctl-cli
git commit -m "feat(normalizers): add vitest and v8 coverage evidence"
```
---
### Task 6: Normalize HTTP scenarios and TypeScript architecture
**Files:**
- Create: `adapters/http-scenario/build.gradle.kts`
- Create: `adapters/http-scenario/src/main/java/io/testassurance/adapter/http/HttpScenarioNormalizer.java`
- Create: `adapters/http-scenario/src/test/java/io/testassurance/adapter/http/HttpScenarioNormalizerTest.java`
- Create fixture directories: `adapters/http-scenario/src/test/resources/valid/`, `adapters/http-scenario/src/test/resources/missing-receipt/`, `adapters/http-scenario/src/test/resources/duplicate-receipt/`, `adapters/http-scenario/src/test/resources/unknown-scenario/`, `adapters/http-scenario/src/test/resources/status-mismatch/`, `adapters/http-scenario/src/test/resources/malformed/`
- Create: `adapters/typescript-architecture/build.gradle.kts`
- Create: `adapters/typescript-architecture/src/main/java/io/testassurance/adapter/architecture/TypeScriptArchitectureNormalizer.java`
- Create: `adapters/typescript-architecture/src/test/java/io/testassurance/adapter/architecture/TypeScriptArchitectureNormalizerTest.java`
- Create fixture directories: `adapters/typescript-architecture/src/test/resources/valid/`, `adapters/typescript-architecture/src/test/resources/empty-graph/`, `adapters/typescript-architecture/src/test/resources/unresolved-import/`, `adapters/typescript-architecture/src/test/resources/cycle/`, `adapters/typescript-architecture/src/test/resources/violation/`, `adapters/typescript-architecture/src/test/resources/path-escape/`, `adapters/typescript-architecture/src/test/resources/malformed/`
- Modify: `settings.gradle.kts`
- Modify: `build.gradle.kts`
- Modify: `modules/testctl-cli/build.gradle.kts`
- Modify: `modules/testctl-cli/src/main/java/io/testassurance/cli/Commands.java`
**Module dependencies:** Both modules are added to root `javaModules`; HTTP depends on `:adapters:vitest`, and both depend on model, normalizer, and schema modules.
**Typed inputs:** `HttpScenarioNormalizer` is the sole owner of `integration-http-msw` and composes `VitestReportReader`. It compares exact declared/executed IDs; a missing receipt is `INCOMPLETE_DELETED_TEST`, duplicate/unknown receipt or catalog mismatch is `INVALID_RESULT`, and unmatched/catch-all behavior is `FAIL_TEST`. Architecture requires non-empty modules/dependencies; empty graph is `INCOMPLETE_ZERO_TESTS`, malformed/parser mismatch is `ERROR_TOOLING` or `INVALID_RESULT`, and unresolved import/cycle/rule violation is `FAIL_PRODUCT`.
- [ ] Add fixtures/tests and run focused Gradle tests; expected RED.
- [ ] Implement bounded `ReportSchemaRegistry` parsing, exact scenario/graph cross-field reconciliation, and sole capability ownership for both adapters.
- [ ] Re-run focused tests and CLI tests; expected PASS.
- [ ] Commit:
```bash
git add adapters/http-scenario adapters/typescript-architecture settings.gradle.kts build.gradle.kts modules/testctl-cli
git commit -m "feat(normalizers): add http and architecture evidence"
```
---
### Task 7: Upgrade Playwright into three independent browser capabilities
**Files:**
- Create: `adapters/playwright/src/main/java/io/testassurance/adapter/playwright/PlaywrightResultNormalizer.java`
- Create: `adapters/playwright/src/main/java/io/testassurance/adapter/playwright/BrowserArtifactIndex.java`
- Create: `adapters/playwright/src/main/java/io/testassurance/adapter/playwright/MutationReceipt.java`
- Modify: `adapters/playwright/src/main/java/io/testassurance/adapter/playwright/BrowserRunPolicy.java`
- Create: `adapters/playwright/src/test/java/io/testassurance/adapter/playwright/PlaywrightResultNormalizerTest.java`
- Create fixture directories: `adapters/playwright/src/test/resources/chromium/`, `adapters/playwright/src/test/resources/firefox/`, `adapters/playwright/src/test/resources/webkit/`, `adapters/playwright/src/test/resources/missing-project/`, `adapters/playwright/src/test/resources/retry-green/`, `adapters/playwright/src/test/resources/zero-tests/`, `adapters/playwright/src/test/resources/missing-trace/`, `adapters/playwright/src/test/resources/console-error/`, `adapters/playwright/src/test/resources/network-error/`, `adapters/playwright/src/test/resources/valid-mutation/`, `adapters/playwright/src/test/resources/missing-mutation-read/`, `adapters/playwright/src/test/resources/malformed/`
- Modify: `modules/testctl-cli/src/main/java/io/testassurance/cli/Commands.java`
**Rules:** The selected browser capability must match the report project exactly. Required trace/screenshot/console/network indexes bind repository-relative files by digest. Retry-only pass is FLAKY, not PASS. Write journeys require observed response, mutation read, and reload read receipts bound to the same scenario/candidate.
- [ ] Add RED tests for each browser and every false-green case.
- [ ] Run:
```bash
./gradlew :adapters:playwright:test
```
Expected: RED because only policy helpers exist.
- [ ] Implement one normalizer parameterized by three independently registered capability IDs; do not aggregate readiness or results.
- [ ] Run focused and CLI tests; expected PASS.
- [ ] Commit:
```bash
git add adapters/playwright modules/testctl-cli
git commit -m "feat(playwright): normalize independent browser evidence"
```
---
### Task 8: Add accessibility and visual-regression evidence
**Files:**
- Create: `adapters/accessibility-web/build.gradle.kts`
- Create: `adapters/accessibility-web/src/main/java/io/testassurance/adapter/accessibility/AccessibilityNormalizer.java`
- Create: `adapters/accessibility-web/src/test/java/io/testassurance/adapter/accessibility/AccessibilityNormalizerTest.java`
- Create fixture directories: `adapters/accessibility-web/src/test/resources/valid/`, `adapters/accessibility-web/src/test/resources/violation/`, `adapters/accessibility-web/src/test/resources/missing-provider/`, `adapters/accessibility-web/src/test/resources/missing-manual-review/`, `adapters/accessibility-web/src/test/resources/expired-review/`, `adapters/accessibility-web/src/test/resources/malformed/`
- Create: `adapters/visual-regression-web/build.gradle.kts`
- Create: `adapters/visual-regression-web/src/main/java/io/testassurance/adapter/visual/VisualRegressionNormalizer.java`
- Create: `adapters/visual-regression-web/src/test/java/io/testassurance/adapter/visual/VisualRegressionNormalizerTest.java`
- Create fixture directories: `adapters/visual-regression-web/src/test/resources/valid/`, `adapters/visual-regression-web/src/test/resources/diff/`, `adapters/visual-regression-web/src/test/resources/missing-baseline/`, `adapters/visual-regression-web/src/test/resources/wrong-baseline-digest/`, `adapters/visual-regression-web/src/test/resources/missing-provider/`, `adapters/visual-regression-web/src/test/resources/malformed/`
- Modify: `settings.gradle.kts`
- Modify: `build.gradle.kts`
- Modify: `modules/testctl-cli/build.gradle.kts`
- Modify: `modules/testctl-cli/src/main/java/io/testassurance/cli/Commands.java`
**Module dependencies:** Both modules are added to root `javaModules` and depend on model, normalizer, schema, and Playwright evidence types.
**Rules:** Accessibility evidence carries scanner/provider identity, rule-set digest, findings, and required manual-review records. Manual records require `reviewedAt`, `expiresAt`, `sourceRevision`, `candidateSha256`, `reviewerId`, and `rulesetDigest`; expiry is evaluated against the raw result's `finishedAt`, never wall-clock time. Visual evidence carries baseline ID/digest, provider identity, current image digest, diff digest, and threshold outcome. Absent identities can never PASS.
- [ ] Add RED tests `rejectsAccessibilityWithoutProvider`, `rejectsExpiredManualReviewAtFinishedAt`, `reportsUnwaivedAccessibilityViolation`, `rejectsVisualWithoutBaseline`, `rejectsWrongVisualProvider`, and `reportsVisualDiffOverThreshold`, each backed by the named fixture directory above.
- [ ] Implement and register both adapters.
- [ ] Run adapter and CLI tests; expected PASS.
- [ ] Commit:
```bash
git add adapters/accessibility-web adapters/visual-regression-web settings.gradle.kts build.gradle.kts modules/testctl-cli
git commit -m "feat(normalizers): add accessibility and visual evidence"
```
---
### Task 9: Enforce artifact materialization in bounded execution
**Files:**
- Modify: `modules/assurance-executor/src/main/java/io/testassurance/executor/WorkItemExecutor.java`
- Modify: `modules/assurance-executor/src/main/java/io/testassurance/executor/LocalProcessExecutor.java`
- Create: `modules/assurance-executor/src/main/java/io/testassurance/executor/InputArtifactVerifier.java`
- Modify: `modules/assurance-executor/src/test/java/io/testassurance/executor/LocalProcessExecutorTest.java`
- Modify: `modules/testctl-cli/src/main/java/io/testassurance/cli/Commands.java`
- Modify: `modules/testctl-cli/src/test/java/io/testassurance/cli/TestctlMainTest.java`
- Modify: `docs/06-testctl-external-executor-contract.md`
**External contract:** `testctl execute-one` retains `--plan`, `--work-item-id`, and `--output`, and v3 adds `--artifact-map`. The artifact map resolves artifact IDs to local regular archive files, while expected media type and SHA-256 come only from the signed v3 work item. Verification occurs before the product command starts.
The compatible public syntax is:
```text
testctl execute-one --plan <plan.json> --work-item-id <sha256> --artifact-map <input-artifact-map.json> --output <work-dir>
testctl execute-plan --plan <plan.json> --artifact-map <input-artifact-map.json> --output <run-dir>
```
SOURCE plans reject `--artifact-map`; ARTIFACT plans require it. V3 maps only regular archive files—directories must already be represented by a canonical archive subject. `InputArtifactVerifier` copies each file into a private staging directory while hashing it, then exports only the verified copy path to the child.
- [ ] Add RED tests for missing artifact, wrong digest, wrong media type, symlink, path escape, directory input, mutation between verification and use, SOURCE receiving an artifact map, ARTIFACT missing a map, unknown work-item ID, and work item not belonging to the plan digest.
- [ ] Run executor and CLI tests; expected RED.
- [ ] Implement verification with opened handles or verified private copies, bounded hashing, and exact environment projection; never trust a caller-supplied digest.
- [ ] Re-run tests; expected PASS.
- [ ] Commit:
```bash
git add modules/assurance-executor modules/testctl-cli docs/06-testctl-external-executor-contract.md
git commit -m "feat(executor): verify artifact-bound work inputs"
```
---
### Task 10: Prove the v3 chain, frontend fixture, and independent R1 readiness
**Files:**
- Modify: `modules/assurance-normalizer/src/main/java/io/testassurance/normalizer/FalseGreenRules.java`
- Modify: `modules/assurance-evidence/src/main/java/io/testassurance/evidence/EvidenceBundler.java`
- Modify: `modules/assurance-evidence/src/test/java/io/testassurance/evidence/EvidenceBundlerTest.java`
- Modify: `modules/assurance-assessor/src/main/java/io/testassurance/assessor/Assessor.java`
- Modify: `modules/assurance-assessor/src/test/java/io/testassurance/assessor/AssessorTest.java`
- Create: `modules/assurance-catalog/src/main/java/io/testassurance/catalog/ReadinessCatalog.java`
- Create: `modules/assurance-catalog/src/test/java/io/testassurance/catalog/ReadinessCatalogTest.java`
- Modify: `modules/assurance-catalog/build.gradle.kts`
- Modify: `modules/testctl-cli/src/main/java/io/testassurance/cli/ConformanceRunner.java`
- Modify: `modules/testctl-cli/src/main/java/io/testassurance/cli/Commands.java`
- Modify: `modules/testctl-cli/src/test/java/io/testassurance/cli/TestctlMainTest.java`
- Create: `conformance/golden-v3/source/case.json`
- Create: `conformance/golden-v3/source/01-execution-request.json`
- Create: `conformance/golden-v3/source/02-execution-plan.json`
- Create: `conformance/golden-v3/source/03-raw-result-set.json`
- Create: `conformance/golden-v3/source/normalized.json`
- Create: `conformance/golden-v3/source/04-evidence-bundle.json`
- Create: `conformance/golden-v3/source/05-assessment.json`
- Create: `conformance/golden-v3/artifact/case.json`
- Create: `conformance/golden-v3/artifact/01-execution-request.json`
- Create: `conformance/golden-v3/artifact/02-execution-plan.json`
- Create: `conformance/golden-v3/artifact/03-raw-result-set.json`
- Create: `conformance/golden-v3/artifact/normalized.json`
- Create: `conformance/golden-v3/artifact/04-evidence-bundle.json`
- Create: `conformance/golden-v3/artifact/05-assessment.json`
- Create: `conformance/adversarial/v3-wrong-candidate-normalized-result.json`
- Create: `conformance/adversarial/v3-missing-browser-result.json`
- Create: `fixtures/frontend-reference/test-assurance.yaml`
- Create: `fixtures/frontend-reference/test-assurance.lock.json`
- Create: `fixtures/frontend-reference/.nvmrc`
- Create: `fixtures/frontend-reference/package.json`
- Create: `fixtures/frontend-reference/pnpm-lock.yaml`
- Create: `fixtures/frontend-reference/config/test-assurance/risks/frontend.json`
- Create: `fixtures/frontend-reference/config/test-assurance/obligations/frontend.json`
- Create: `fixtures/frontend-reference/config/test-assurance/suites/source.json`
- Create: `fixtures/frontend-reference/config/test-assurance/suites/artifact-templates.json`
- Create: `fixtures/frontend-reference/config/test-assurance/change-surfaces.json`
- Create: `fixtures/frontend-reference/artifacts/frontend-static.tar`
- Create: `fixtures/frontend-reference/results/source-valid/raw-result-set.json`
- Create: `fixtures/frontend-reference/results/source-valid/normalized.json`
- Create: `fixtures/frontend-reference/results/artifact-valid/raw-result-set.json`
- Create: `fixtures/frontend-reference/results/artifact-valid/normalized.json`
- Create: `fixtures/frontend-reference/results/adversarial-zero-discovery/raw-result-set.json`
- Create: `fixtures/frontend-reference/results/adversarial-missing-browser/raw-result-set.json`
- Create: `fixtures/frontend-reference/results/adversarial-wrong-candidate/raw-result-set.json`
- Modify: all ten frontend files under `readiness/`
- Modify: `docs/03-manifest-schema-contracts.md`
- Modify: `docs/04-test-type-contracts.md`
- Modify: `docs/06-testctl-external-executor-contract.md`
- Modify: `docs/07-evidence-flaky-contract.md`
- Modify: `docs/09-implementation-handoff-contract.md`
- Modify: `README.md`
- Modify: `validation.json`
**Identity rule:** Bundling requires exact equality of API major, request digest, plan digest, execution phase, ordered input artifacts, work-item IDs, attempt identities, and raw artifact digests. Assessment emits the same tuple plus `readinessDigest` and never converts an integrity error into an unsatisfied product obligation. `ReadinessCatalog` packages the independent cards into the distribution; `Commands.assess()` supplies it to `AssessmentInputs`, and the assessor applies the higher of capability-contract minimum and obligation minimum readiness.
- [ ] Add RED chain tests for v2/v3 mixing, source/artifact mixing, wrong candidate, missing work item, duplicate attempt, retry-only green, absent terminal result, empty readiness map, readiness digest drift, and an aggregate frontend readiness card.
- [ ] Run:
```bash
./gradlew :modules:assurance-evidence:test :modules:assurance-assessor:test :modules:assurance-catalog:test :modules:testctl-cli:test
```
Expected: RED until the entire tuple is checked.
- [ ] Implement fail-closed chain verification, readiness binding, and a `ConformanceRunner` case format that validates the five canonical stages plus listed `normalizedFiles`.
- [ ] Build the frontend fixture with all ten capabilities. Compute the artifact SHA from the committed `frontend-static.tar` bytes and use it consistently in artifact request, materialized suites, plan, raw results, normalized results, bundle, and assessment; no repeated-character or hand-entered digest is allowed.
- [ ] Run focused tests; expected PASS.
- [ ] Install the CLI and run both golden cases:
```bash
./gradlew :modules:testctl-cli:installDist
modules/testctl-cli/build/install/testctl-cli/bin/testctl conformance --case conformance/golden-v3/source/case.json
modules/testctl-cli/build/install/testctl-cli/bin/testctl conformance --case conformance/golden-v3/artifact/case.json
```
- [ ] Run the fixture twice, once for SOURCE and once for ARTIFACT, through `validate → lock → compile → select → plan → normalize → bundle → assess`; assert five selected source capability IDs and five selected artifact capability IDs with separate plan/evidence/assessment digests.
- [ ] Update each frontend card from R0 to R1 only when its own adapter fixture and chain evidence digest exists. Keep missing evidence at R0 with explicit `nonGuarantees`.
- [ ] Add documentation checks naming every capability, exact toolchain pin, v3 CLI argument, phase/template/materialization rule, false-green rule, readiness digest, and v2 compatibility limit.
- [ ] Run full verification:
```bash
./gradlew clean build
python3 tools/validate_package.py
git diff --check
```
Expected: PASS. If a toolchain artifact cannot be observed locally, keep affected cards at R0; do not fabricate a digest.
- [ ] Commit:
```bash
git add modules/assurance-normalizer modules/assurance-evidence modules/assurance-assessor modules/assurance-catalog modules/testctl-cli conformance fixtures/frontend-reference readiness docs README.md validation.json
git commit -m "test(conformance): publish frontend v3 readiness"
```
## Handoff to CI/CD
The consuming CI/CD plan may begin only from the immutable commit produced by Task 11. It imports the v3 schemas and CLI distribution by digest, invokes only documented argument-array commands, treats normalized/evidence/assessment documents as opaque test semantics, and carries both source and artifact plan/evidence/assessment digests into release identity.
@@ -0,0 +1,176 @@
# V8 Coverage Counter Contract Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Publish risk-coverage artifact schema version 3 and continuously verify the installed Vitest/V8 producer's counter-bearing/counterless row semantics in an isolated child run.
**Architecture:** A real CLI contract test owns the serialized artifact assertion. A standalone producer checker copies fixed source templates into one OS-temp root, creates its child config and report there, validates an exact JSON summary, bounds child diagnostics, and removes the owned root in `finally`. `test:coverage` invokes the checker before repository coverage, which also carries it into CI and sample removal.
**Tech Stack:** TypeScript 7, Node.js 24 child processes and filesystem APIs, Vitest 4, V8 coverage.
## Global Constraints
- Policy schema remains version 2; serialized risk-coverage artifact schema becomes version 3.
- Child root, config, and reports directory are all below one owned OS temporary directory.
- Main Vitest must not discover the child `.fixture.ts` file.
- Child exit, summary absence, malformed/missing/additional rows, counterless nonzero drift, and runtime all-zero drift fail closed.
- Child stdout/stderr included in diagnostics is bounded.
- Cleanup uses `finally` and targets only the exact owned temporary root.
- Source edits use `apply_patch`; behavior changes follow RED-GREEN TDD.
---
### Task 1: Version the real serialized artifact
**Files:**
- Modify: `tests/unit/risk-coverage.test.ts`
- Modify: `scripts/check-risk-coverage.ts`
**Interfaces:**
- Consumes: the real `check-risk-coverage.ts` CLI, current policy structure, and an exact temporary coverage summary.
- Produces: serialized artifact schema version 3 with `counterBearingTotal`, `instrumentedCounterBearingTotal`, `counterlessTotal`, and `counterlessModules` only.
- [x] **Step 1: Add the actual CLI serialization contract test.**
Create a temporary repository with the 19 current policy paths, write each as `export const covered = true`, set the cloned policy baseline to 19, write one full counter row per module, run the CLI with `process.execPath`, and assert:
```ts
expect(artifact).toMatchObject({
schemaVersion: 3,
counterBearingTotal: 19,
instrumentedCounterBearingTotal: 19,
counterlessTotal: 0,
counterlessModules: [],
});
expect(artifact).not.toHaveProperty("executableTotal");
expect(artifact).not.toHaveProperty("instrumentedExecutableTotal");
expect(artifact).not.toHaveProperty("nonExecutableTotal");
expect(artifact).not.toHaveProperty("nonExecutableModules");
```
- [x] **Step 2: Run the single test and verify RED.**
Run: `./node_modules/.bin/vitest run tests/unit/risk-coverage.test.ts -t "publishes artifact schema version 3" --reporter=dot`
Expected: FAIL because the actual artifact contains `schemaVersion: 2`.
- [x] **Step 3: Change only the serialized envelope to version 3.**
Change `schemaVersion: 2` to `schemaVersion: 3` in the value passed to `writeRiskCoverageArtifactAtomic`; do not change policy parsing.
- [x] **Step 4: Re-run the single test and verify GREEN.**
Run the Step 2 command and expect one passing test.
### Task 2: Lock actual Vitest/V8 counter semantics
**Files:**
- Create: `tests/fixtures/v8-coverage-counter-semantics/counter-semantics.fixture.ts`
- Create: `tests/fixtures/v8-coverage-counter-semantics/src/runtime.ts`
- Create: `tests/fixtures/v8-coverage-counter-semantics/src/import-type-empty.ts`
- Create: `tests/fixtures/v8-coverage-counter-semantics/src/import-empty.ts`
- Create: `tests/fixtures/v8-coverage-counter-semantics/src/import-side-effect.ts`
- Create: `tests/fixtures/v8-coverage-counter-semantics/src/import-value.ts`
- Create: `tests/fixtures/v8-coverage-counter-semantics/src/reexport-named.ts`
- Create: `tests/fixtures/v8-coverage-counter-semantics/src/reexport-star.ts`
- Create: `tests/fixtures/v8-coverage-counter-semantics/src/type-only.ts`
- Create: `tests/unit/v8-coverage-counter-semantics.test.ts`
- Create: `scripts/lib/v8-coverage-counter-semantics.ts`
- Create: `scripts/check-v8-coverage-counter-semantics.ts`
- Modify: `vitest.config.ts`
**Interfaces:**
- Produces: `assertV8CoverageCounterSemantics(summary, fixtureRoot)` and `checkV8CoverageCounterSemantics(options?)`.
- Consumes: fixed fixture templates, owned temp paths, a shell-free Vitest child result, and `coverage-summary.json`.
- [x] **Step 1: Add fixture templates and failing checker tests.**
The fixture test imports the seven counterless modules and observes the direct/named/star runtime values. The unit tests use literal summaries to require exact rows and mutate them for missing row, extra row, counterless nonzero, and runtime all-zero failures. Runner tests inject child exit and successful-without-summary results and require bounded diagnostics plus removal of the owned root.
- [x] **Step 2: Run the new unit file and verify RED.**
Run: `./node_modules/.bin/vitest run tests/unit/v8-coverage-counter-semantics.test.ts --reporter=dot`
Expected: FAIL because `scripts/lib/v8-coverage-counter-semantics.ts` does not exist.
- [x] **Step 3: Implement exact summary validation and owned child execution.**
The default runner executes:
```ts
execFile(process.execPath, [
path.join(repositoryRoot, "node_modules/vitest/vitest.mjs"),
"run",
"--config",
configPath,
"--coverage",
"--reporter=dot",
"--no-color",
], { cwd: ownedRoot, timeout: 30_000, maxBuffer: 256 * 1024 });
```
The generated config has `root`, `include`, `coverage.reportsDirectory`, and `coverage.include` paths inside the owned root. Always remove the root in `finally`.
- [x] **Step 4: Add a behavioral main-discovery assertion.**
Run main `vitest list` filtered to the fixture directory with `--filesOnly --passWithNoTests`; require empty stdout. Add an explicit fixture-directory exclude in `vitest.config.ts`.
- [x] **Step 5: Run the new unit file and standalone checker for GREEN.**
Run:
```sh
./node_modules/.bin/vitest run tests/unit/v8-coverage-counter-semantics.test.ts --reporter=dot
node scripts/check-v8-coverage-counter-semantics.ts
```
Expected checker output: `V8 coverage counter semantics: PASS (1 counter-bearing, 7 counterless)`.
### Task 3: Wire coverage/CI and refresh documentation
**Files:**
- Modify: `package.json`
- Modify: `docs/testing/frontend-platform-testing-strategy.md`
- Modify: `.superpowers/sdd/2026-08-01-quality-architecture-remediation/task-1-report.md`
- Modify: `.superpowers/sdd/2026-08-01-quality-architecture-remediation/progress.md`
**Interfaces:**
- Consumes: `check:v8-coverage-counter-semantics` and existing FE-GATE-005 `test:coverage` step.
- Produces: package/CI/sample-removal execution and current 19-module/80-threshold documentation.
- [x] **Step 1: Add the package checker and prepend it to `test:coverage`.**
```json
"check:v8-coverage-counter-semantics": "node scripts/check-v8-coverage-counter-semantics.ts",
"test:coverage": "corepack pnpm check:v8-coverage-counter-semantics && vitest run ..."
```
- [x] **Step 2: Synchronize documentation.**
Replace stale `12개 high-risk module` and `52개 scoped threshold` with `19개` and `80개`; document artifact schema 3 and policy schema 2 separately.
- [x] **Step 3: Run full relevant verification.**
```sh
./node_modules/.bin/vitest run tests/unit/risk-coverage.test.ts tests/unit/risk-coverage-files.test.ts tests/unit/v8-coverage-counter-semantics.test.ts tests/unit/bounded-body-reader.test.ts --reporter=dot
./node_modules/.bin/tsc --noEmit -p tsconfig.node.json
./node_modules/.bin/tsc --noEmit -p tsconfig.test.json
./node_modules/.bin/eslint scripts/check-risk-coverage.ts scripts/check-v8-coverage-counter-semantics.ts scripts/lib/v8-coverage-counter-semantics.ts tests/unit/risk-coverage.test.ts tests/unit/v8-coverage-counter-semantics.test.ts vitest.config.ts --max-warnings=0
corepack pnpm check:v8-coverage-counter-semantics
node scripts/check-risk-coverage.ts
corepack pnpm test:sample-removal
git diff --check
```
- [x] **Step 4: Commit the verified closeout.**
```sh
git add package.json vitest.config.ts scripts/check-risk-coverage.ts scripts/check-v8-coverage-counter-semantics.ts scripts/lib/v8-coverage-counter-semantics.ts tests/fixtures/v8-coverage-counter-semantics tests/unit/risk-coverage.test.ts tests/unit/v8-coverage-counter-semantics.test.ts docs/testing/frontend-platform-testing-strategy.md docs/superpowers/plans/2026-08-02-v8-coverage-counter-contract.md
git commit -m "test: lock V8 coverage counter semantics"
```
## Self-review
- Spec coverage: artifact versioning, actual producer rows, discovery isolation, every fail-closed path, bounded diagnostics, cleanup, coverage/CI linkage, sample-removal preservation, and documentation counts are assigned.
- Placeholder scan: no deferred implementation remains.
- Type consistency: parser and runner names match in tests, script, and plan.