Files
tech-log-frontend/docs/superpowers/plans/2026-08-01-release-boot-integrity.md

13 KiB

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

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
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

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
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

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
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:

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.