Files
tech-log-frontend/tests/runtime-schema/release-manifest.test.ts
T
DongHyeonkaandClaude Opus 5 9313018ef5 fix: restore contract-set regression coverage and mirror canonical problem bounds
release-manifest.test.ts's fixture derived manifest.contractSet.packages
from EXPECTED_CONTRACT_SET_PACKAGES itself, so the equality it checked was
satisfied by construction and CONTRACT_SET_PACKAGE_MISSING became
unreachable from any test in the repo. Adds two independent checks: a
literal (not derived) assertion that EXPECTED_CONTRACT_SET_PACKAGES really
contains @tech-log/studio-contract@2.0.0, and a negative test with a
manifest that omits a package the real expected set requires, asserting
CONTRACT_SET_PACKAGE_MISSING. Confirmed the negative test has teeth by
temporarily disabling the missing-package branch in
verifyContractSet (src/contracts/contract-set.ts) and observing the test
fail before reverting.

Also narrows tech-log-studio-contract-contribution.ts's problemSchema to
match canonical ProblemDetails exactly: title max 200 (was 240) and type
unbounded (was max 512; canonical only constrains it as
format: uri-reference). Both prior values were over-permissive, so no
previously-accepted document is now rejected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 01:21:14 +09:00

304 lines
9.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
loadReleaseManifest,
ReleaseManifestError,
} from "../../src/bootstrap/load-release-manifest.ts";
import {
computeContractSetDigest,
type ContractSetPackage,
} from "../../src/contracts/contract-set-canonical.ts";
import { EXPECTED_CONTRACT_SET_PACKAGES } from "../../src/features/installed-contract-contributions.ts";
const EMPTY_SET_DIGEST = await computeContractSetDigest([]);
/**
* TechLog's Studio contribution is always installed (Task 3), so the build's
* real expected contract set is no longer empty. A coherent manifest fixture
* must declare exactly what this build actually compiled in, or the boot-time
* `verifyContractSet` check rejects it as `CONTRACT_SET_PACKAGE_MISSING`.
*/
const EXPECTED_PACKAGES =
EXPECTED_CONTRACT_SET_PACKAGES as readonly ContractSetPackage[];
const EXPECTED_SET_DIGEST = await computeContractSetDigest(EXPECTED_PACKAGES);
const runtime: Parameters<typeof loadReleaseManifest>[0] = {
build: {
buildId: "build-a",
commitSha: "abc123",
routerBasePath: "/",
runtimeConfigUrl: "/config.json",
},
config: {
APP_ENV: "local",
API_BASE_URL: "https://api.test",
REQUEST_TIMEOUT_MS: 10_000,
MAX_RETRY_ATTEMPTS: 2,
TELEMETRY_ENABLED: false,
AUTH_MODE: "external",
RELEASE_MANIFEST_URL: "/release-manifest.json",
BUILD_ID: "build-a",
RELEASE_ID: "release-a",
CONFIG_SCHEMA_VERSION: "2.0",
CAPABILITY_OVERRIDES: {
REALTIME: "DEFAULT",
WEB_WORKER: "DEFAULT",
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
},
FEATURE_OVERRIDES: {},
},
configSchema: "V2",
validationDurationMs: 0,
};
const manifest = {
schemaVersion: 2,
appVersion: "0.1.0",
buildId: "build-a",
commitSha: "abc123",
configSchemaVersion: "2.0",
assetManifestHash: "hash-a",
releaseId: "release-a",
builtAt: "2026-07-25T00:00:00Z",
routeChunks: { "route-home": "assets/home.js" },
contractSet: {
setAlgorithm: "CA_CONTRACT_SET_V1",
setDigest: EXPECTED_SET_DIGEST,
packages: EXPECTED_PACKAGES,
},
};
const runtimeV1: Parameters<typeof loadReleaseManifest>[0] = {
...runtime,
config: {
...runtime.config,
CONFIG_SCHEMA_VERSION: "1",
LEGACY_API_CONTRACT_VERSION: "1",
},
configSchema: "V1",
};
const manifestV1 = {
schemaVersion: 1,
appVersion: "0.1.0",
buildId: "build-a",
commitSha: "abc123",
configSchemaVersion: "1",
apiContractVersion: "1",
assetManifestHash: "hash-a",
releaseId: "release-a",
builtAt: "2026-07-25T00:00:00Z",
routeChunks: { "route-home": "assets/home.js" },
};
function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
headers: { "content-type": "application/json" },
});
}
/**
* Round 2 review finding: a fixture built entirely from
* `EXPECTED_CONTRACT_SET_PACKAGES` proves the manifest matches itself, not
* that composition actually produced the TechLog package. This assertion is
* independent of that derivation — the expected value is a literal written
* here, not read back from `EXPECTED_PACKAGES`/`EXPECTED_SET_DIGEST` above —
* so if the unconditional install in `installed-contract-contributions.ts`
* is ever reverted and `EXPECTED_CONTRACT_SET_PACKAGES` silently shrinks to
* empty, this fails on its own regardless of what the fixture below does.
*/
describe("expected contract set composition", () => {
it("actually contains the TechLog Studio contract, not just an empty set matching itself", () => {
const techLog = EXPECTED_CONTRACT_SET_PACKAGES.find(
(entry) => entry.packageId === "@tech-log/studio-contract",
);
expect(techLog).toBeTruthy();
expect(techLog?.version).toBe("2.0.0");
});
});
describe("release manifest boot boundary", () => {
it("loads a coherent release tuple", async () => {
await expect(
loadReleaseManifest(
runtime,
{ fetcher: async () => jsonResponse(manifest) },
),
).resolves.toMatchObject({ releaseId: "release-a" });
});
it("fails before mount when release and runtime differ", async () => {
await expect(
loadReleaseManifest(
runtime,
{
fetcher: async () =>
jsonResponse({ ...manifest, buildId: "build-b" }),
},
),
).rejects.toMatchObject({
kind: "BUILD_MISMATCH",
code: "MANIFEST_BUILD_MISMATCH",
});
});
it.each([
[
{ releaseId: "release-b" },
{},
"RELEASE_MISMATCH",
"MANIFEST_RELEASE_MISMATCH",
],
[
{},
{ expectedAssetManifestHash: "different" },
"ASSET_MISMATCH",
"MANIFEST_ASSET_MISMATCH",
],
])(
"classifies tuple mismatch %# without a generic deploy error",
async (manifestOverride, options, kind, code) => {
await expect(
loadReleaseManifest(
runtime,
{
fetcher: async () =>
jsonResponse({ ...manifest, ...manifestOverride }),
...options,
},
),
).rejects.toMatchObject({ kind, code });
},
);
it.each(["3.0", "9.0"])(
"rejects unsupported V2 manifest config version %s at the schema boundary",
async (configSchemaVersion) => {
await expect(
loadReleaseManifest(runtime, {
fetcher: async () =>
jsonResponse({ ...manifest, configSchemaVersion }),
}),
).rejects.toMatchObject({
kind: "RELEASE_MANIFEST_FAILURE",
code: "MANIFEST_SCHEMA_INVALID",
});
},
);
it("rejects a contract set the build did not compile", async () => {
await expect(
loadReleaseManifest(runtime, {
fetcher: async () =>
jsonResponse({
...manifest,
contractSet: {
setAlgorithm: "CA_CONTRACT_SET_V1",
setDigest: EMPTY_SET_DIGEST,
packages: [
{
packageId: "@org-contracts/worklog",
version: "1.2.3",
digest: `sha256:${"a".repeat(64)}`,
runtimeProtocolVersion: 1,
sourceRevision: "abc1234",
},
],
},
}),
}),
).rejects.toMatchObject({
kind: "CONTRACT_SET_MISMATCH",
code: "CONTRACT_SET_PACKAGE_UNEXPECTED",
});
});
/**
* Round 2 review finding: deriving the fixture's `contractSet.packages`
* from `EXPECTED_CONTRACT_SET_PACKAGES` made `CONTRACT_SET_PACKAGE_MISSING`
* unreachable from any test — the equality was satisfied by construction.
* This restores that failure path with an independently built manifest
* that omits a package the real expected set actually requires, mirroring
* the shape of "rejects a contract set the build did not compile" above.
*/
it("rejects a manifest that omits a package the build actually compiled in", async () => {
await expect(
loadReleaseManifest(runtime, {
fetcher: async () =>
jsonResponse({
...manifest,
contractSet: {
setAlgorithm: "CA_CONTRACT_SET_V1",
setDigest: EMPTY_SET_DIGEST,
packages: [],
},
}),
}),
).rejects.toMatchObject({
kind: "CONTRACT_SET_MISMATCH",
code: "CONTRACT_SET_PACKAGE_MISSING",
});
});
it("still reads a V1 manifest during the compatibility window", async () => {
await expect(
loadReleaseManifest(
runtimeV1,
{
fetcher: async () => jsonResponse(manifestV1),
},
),
).resolves.toMatchObject({ schemaVersion: 1, contractSet: null });
});
it("rejects a V2 runtime paired with a V1 manifest", async () => {
await expect(
loadReleaseManifest(runtime, {
fetcher: async () => jsonResponse({
...manifestV1,
configSchemaVersion: "2.0",
}),
}),
).rejects.toMatchObject({
kind: "PROTOCOL_PAIR_MISMATCH",
code: "MANIFEST_PROTOCOL_PAIR_MISMATCH",
});
});
it("rejects a V1 runtime paired with a V2 manifest", async () => {
await expect(
loadReleaseManifest(runtimeV1, {
fetcher: async () => jsonResponse(manifest),
}),
).rejects.toMatchObject({
kind: "PROTOCOL_PAIR_MISMATCH",
code: "MANIFEST_PROTOCOL_PAIR_MISMATCH",
});
});
it("requires matching legacy scalar versions for a V1 pair", async () => {
await expect(
loadReleaseManifest(runtimeV1, {
fetcher: async () => jsonResponse({
...manifestV1,
apiContractVersion: "2",
}),
}),
).rejects.toMatchObject({
kind: "API_CONTRACT_MISMATCH",
code: "MANIFEST_API_CONTRACT_MISMATCH",
});
});
it("rejects a manifest without a complete route chunk map", async () => {
const malformed = Object.fromEntries(
Object.entries(manifest).filter(([key]) => key !== "routeChunks"),
);
await expect(
loadReleaseManifest(
runtime,
{ fetcher: async () => jsonResponse(malformed) },
),
).rejects.toBeInstanceOf(ReleaseManifestError);
});
});