345 lines
13 KiB
Markdown
345 lines
13 KiB
Markdown
# 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.
|