diff --git a/src/features/tech-log/contracts/tech-log-studio-contract-contribution.ts b/src/features/tech-log/contracts/tech-log-studio-contract-contribution.ts index 1fe194f..7ab71ef 100644 --- a/src/features/tech-log/contracts/tech-log-studio-contract-contribution.ts +++ b/src/features/tech-log/contracts/tech-log-studio-contract-contribution.ts @@ -47,8 +47,9 @@ const passthrough = (schemaId: string) => const problemSchema = z .object({ - type: z.string().min(1).max(512), - title: z.string().min(1).max(240), + // canonical: `format: uri-reference` only, no length bound. + type: z.string().min(1), + title: z.string().min(1).max(200), status: z.int().min(400).max(599), detail: z.string().min(1).max(5000), code: z.enum(STUDIO_ERROR_CODES as unknown as [string, ...string[]]), diff --git a/tests/runtime-schema/release-manifest.test.ts b/tests/runtime-schema/release-manifest.test.ts index 56dbc71..9367f75 100644 --- a/tests/runtime-schema/release-manifest.test.ts +++ b/tests/runtime-schema/release-manifest.test.ts @@ -96,6 +96,26 @@ function jsonResponse(body: unknown): Response { }); } +/** + * 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( @@ -192,6 +212,33 @@ describe("release manifest boot boundary", () => { }); }); + /** + * 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(