From 3635b3d5789acbcff2b0ca6769ab5773d109f6e1 Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Thu, 13 Aug 2026 15:53:28 +0200 Subject: [PATCH] fix(stylesheet): skip parity on explicit activation (#3316) --- docs/spec.json | 40 ----------------- packages/api/src/dto/resume.ts | 3 -- .../resume/stylesheet-observability.ts | 1 - .../resume/stylesheet-preflight.test.ts | 41 +---------------- .../features/resume/stylesheet-preflight.ts | 45 ------------------- .../features/resume/stylesheet-route.test.ts | 8 ---- .../resume/stylesheet-service.test.ts | 42 +---------------- .../src/features/resume/stylesheet-service.ts | 23 +--------- .../api/src/features/resume/stylesheet.ts | 5 --- 9 files changed, 4 insertions(+), 204 deletions(-) diff --git a/docs/spec.json b/docs/spec.json index 0cdcbc856..a4846559c 100644 --- a/docs/spec.json +++ b/docs/spec.json @@ -10921,46 +10921,6 @@ "data" ] }, - { - "type": "object", - "properties": { - "defined": { - "const": true - }, - "code": { - "const": "STYLESHEET_PARITY_FAILED" - }, - "status": { - "const": 400 - }, - "message": { - "type": "string", - "default": "The converted stylesheet does not preserve legacy PDF presentation." - }, - "data": { - "type": "object", - "properties": { - "mismatches": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "mismatches" - ], - "additionalProperties": false - } - }, - "required": [ - "defined", - "code", - "status", - "message", - "data" - ] - }, { "type": "object", "properties": { diff --git a/packages/api/src/dto/resume.ts b/packages/api/src/dto/resume.ts index ba230eaf8..2ffa78472 100644 --- a/packages/api/src/dto/resume.ts +++ b/packages/api/src/dto/resume.ts @@ -202,9 +202,6 @@ export const resumeDto = { validation: z.strictObject({ diagnostics: z.array(stylesheetDiagnosticSchema), }), - parity: z.strictObject({ - mismatches: z.array(z.string()), - }), revisionConflict: z.strictObject({ state: stylesheetStateSchema, }), diff --git a/packages/api/src/features/resume/stylesheet-observability.ts b/packages/api/src/features/resume/stylesheet-observability.ts index bffa8c196..cb40df8c9 100644 --- a/packages/api/src/features/resume/stylesheet-observability.ts +++ b/packages/api/src/features/resume/stylesheet-observability.ts @@ -5,7 +5,6 @@ type SemanticCssEventName = | "semantic_css.compile" | "semantic_css.preflight" | "semantic_css.convert_legacy" - | "semantic_css.parity_check" | "semantic_css.activate"; export type SemanticCssEventInput = { diff --git a/packages/api/src/features/resume/stylesheet-preflight.test.ts b/packages/api/src/features/resume/stylesheet-preflight.test.ts index 5406647d7..468b38cb6 100644 --- a/packages/api/src/features/resume/stylesheet-preflight.test.ts +++ b/packages/api/src/features/resume/stylesheet-preflight.test.ts @@ -3,17 +3,7 @@ import type { ResumeData } from "@reactive-resume/schema/resume/data"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { defaultResumeData } from "@reactive-resume/schema/resume/default"; import { EMPTY_SEMANTIC_CSS_SOURCE } from "@reactive-resume/schema/resume/stylesheet"; -import { - checkLegacyStylesheetParity, - prepareImportedResumeData, - validateHistoricalStylesheet, -} from "./stylesheet-preflight"; - -const semanticPdfMocks = vi.hoisted(() => ({ - compareLegacySemanticPresentation: vi.fn(), -})); - -vi.mock("@reactive-resume/pdf/semantic", () => semanticPdfMocks); +import { prepareImportedResumeData, validateHistoricalStylesheet } from "./stylesheet-preflight"; const validSource = { languageVersion: 1, @@ -47,7 +37,6 @@ const createRunner = () => { describe("stylesheet persistence preparation", () => { beforeEach(() => { vi.restoreAllMocks(); - semanticPdfMocks.compareLegacySemanticPresentation.mockReset(); }); it("uses a valid imported source as applied only after PDF preflight", async () => { @@ -171,32 +160,4 @@ describe("stylesheet persistence preparation", () => { expect(serialized).not.toContain(validSource.text); expect(serialized).not.toContain("import-observed"); }); - - it("records a sanitized parity failure metric when the comparator throws", async () => { - const privateText = "private parity failure john.doe@example.com"; - const log = vi.spyOn(console, "info").mockImplementation(() => undefined); - semanticPdfMocks.compareLegacySemanticPresentation.mockRejectedValueOnce(new Error(privateText)); - - await expect( - checkLegacyStylesheetParity({ - data: resumeData(), - stylesheet: validSource, - resumeId: "parity-observed", - revision: 5, - }), - ).rejects.toThrow(privateText); - - expect(log.mock.calls.map(([event]) => event)).toContainEqual( - expect.objectContaining({ - name: "semantic_css.parity_check", - durationMs: expect.any(Number), - diagnosticCodes: [], - success: false, - }), - ); - const serialized = JSON.stringify(log.mock.calls); - expect(serialized).not.toContain(privateText); - expect(serialized).not.toContain(validSource.text); - expect(serialized).not.toContain("parity-observed"); - }); }); diff --git a/packages/api/src/features/resume/stylesheet-preflight.ts b/packages/api/src/features/resume/stylesheet-preflight.ts index efa513dfd..29fac27e2 100644 --- a/packages/api/src/features/resume/stylesheet-preflight.ts +++ b/packages/api/src/features/resume/stylesheet-preflight.ts @@ -6,7 +6,6 @@ import type { StylesheetSnapshot } from "./stylesheet-service"; import { ORPCError } from "@orpc/client"; import { compileStylesheet } from "@reactive-resume/resume/stylesheet"; import { EMPTY_SEMANTIC_CSS_SOURCE } from "@reactive-resume/schema/resume/stylesheet"; -import { templateSchema } from "@reactive-resume/schema/templates"; import { recordSemanticCssEvent } from "./stylesheet-observability"; type PrepareImportedResumeDataInput = { @@ -191,47 +190,3 @@ export async function convertLegacyStylesheet(snapshot: StylesheetSnapshot): Pro throw error; } } - -export async function checkLegacyStylesheetParity(input: { - data: ResumeData; - stylesheet: StylesheetSource; - resumeId: string; - revision: number; -}): Promise<{ mismatches: readonly string[] }> { - const startedAt = performance.now(); - try { - const { compareLegacySemanticPresentation } = await import("@reactive-resume/pdf/semantic"); - const result = await compareLegacySemanticPresentation({ - data: input.data, - convertedSource: input.stylesheet, - templates: templateSchema.options, - }); - recordSemanticCssEvent({ - name: "semantic_css.parity_check", - resumeId: input.resumeId, - durationMs: performance.now() - startedAt, - languageVersion: input.stylesheet.languageVersion, - sourceBytes: byteCount(input.stylesheet), - template: input.data.metadata.template, - diagnosticCodes: [], - pageCount: null, - revision: input.revision, - success: result.mismatches.length === 0, - }); - return result; - } catch (error) { - recordSemanticCssEvent({ - name: "semantic_css.parity_check", - resumeId: input.resumeId, - durationMs: performance.now() - startedAt, - languageVersion: input.stylesheet.languageVersion, - sourceBytes: byteCount(input.stylesheet), - template: input.data.metadata.template, - diagnosticCodes: [], - pageCount: null, - revision: input.revision, - success: false, - }); - throw error; - } -} diff --git a/packages/api/src/features/resume/stylesheet-route.test.ts b/packages/api/src/features/resume/stylesheet-route.test.ts index f600d8ee4..8b7c4edcf 100644 --- a/packages/api/src/features/resume/stylesheet-route.test.ts +++ b/packages/api/src/features/resume/stylesheet-route.test.ts @@ -56,14 +56,6 @@ describe("resume stylesheet route error contract", () => { }>(); }); - it("exposes strict parity mismatch data at runtime and in the inferred client error", () => { - const schema = dataSchema("STYLESHEET_PARITY_FAILED"); - - expect(schema.safeParse({ mismatches: ["onyx: page 1"] }).success).toBe(true); - expect(schema.safeParse({ mismatches: ["onyx: page 1"], source }).success).toBe(false); - expectTypeOf>().toEqualTypeOf<{ mismatches: string[] }>(); - }); - it("exposes strict canonical conflict state for type-safe client rebasing", () => { const schema = dataSchema("STYLESHEET_REVISION_CONFLICT"); diff --git a/packages/api/src/features/resume/stylesheet-service.test.ts b/packages/api/src/features/resume/stylesheet-service.test.ts index 609aa574b..0e56b3be5 100644 --- a/packages/api/src/features/resume/stylesheet-service.test.ts +++ b/packages/api/src/features/resume/stylesheet-service.test.ts @@ -88,8 +88,6 @@ type HarnessOptions = { initial?: StylesheetSnapshot; compile?: (source: StylesheetSource) => CompileStylesheetResult; preflight?: ((input: { data: ResumeData; stylesheet: StylesheetSource }) => Promise) | undefined; - parityMismatches?: readonly string[]; - parity?: () => Promise<{ mismatches: readonly string[] }>; locked?: StylesheetSnapshot; useDefaultObserver?: boolean; }; @@ -108,10 +106,6 @@ const createHarness = (options: HarnessOptions = {}) => { callOrder.push("preflight"); return Promise.resolve(successfulPreflight); }); - const parity = vi.fn(() => { - callOrder.push("parity"); - return options.parity?.() ?? Promise.resolve({ mismatches: options.parityMismatches ?? [] }); - }); const publish = vi.fn(() => { callOrder.push("publish"); return Promise.resolve(); @@ -134,7 +128,6 @@ const createHarness = (options: HarnessOptions = {}) => { callOrder.push("preflight"); return preflight(input); }, - parity, transaction: async (run) => { callOrder.push("begin"); try { @@ -171,7 +164,7 @@ const createHarness = (options: HarnessOptions = {}) => { ...(options.useDefaultObserver ? {} : { observe: (event: SemanticCssEventInput) => events.push(event) }), }); - return { callOrder, compile, events, parity, persisted: () => persisted, preflight, publish, service }; + return { callOrder, compile, events, persisted: () => persisted, preflight, publish, service }; }; describe("stylesheet service", () => { @@ -262,7 +255,7 @@ describe("stylesheet service", () => { ]); }); - it("requires parity and preflight before explicit activation", async () => { + it("activates an explicitly requested mode transition without checking legacy parity", async () => { const initial = snapshot({ ...previousStylesheet, mode: "legacy" }); const harness = createHarness({ initial }); @@ -273,7 +266,6 @@ describe("stylesheet service", () => { }); expect(result.stylesheet).toEqual({ mode: "semantic", source: validSource, applied: validSource }); - expect(harness.parity).toHaveBeenCalledOnce(); expect(harness.preflight).toHaveBeenCalledOnce(); expect(result.revision).toBe(4); expect(result.renderDataVersion).toBe(8); @@ -342,35 +334,6 @@ describe("stylesheet service", () => { expect(serialized).not.toContain("resume-1"); }); - it("records a sanitized activation failure metric when parity throws", async () => { - const privateText = "private parity failure john.doe@example.com"; - const log = vi.spyOn(console, "info").mockImplementation(() => undefined); - const harness = createHarness({ - initial: snapshot({ ...previousStylesheet, mode: "legacy" }), - parity: () => Promise.reject(new Error(privateText)), - useDefaultObserver: true, - }); - - await expect( - harness.service.mutate({ ...commonMutationInput, transition: "activate", source: validSource }), - ).rejects.toThrow(privateText); - - expect(log.mock.calls.map(([event]) => event)).toContainEqual( - expect.objectContaining({ - name: "semantic_css.activate", - durationMs: expect.any(Number), - diagnosticCodes: [], - pageCount: null, - revision: 3, - success: false, - }), - ); - const serialized = JSON.stringify(log.mock.calls); - expect(serialized).not.toContain(privateText); - expect(serialized).not.toContain(validSource.text); - expect(serialized).not.toContain("resume-1"); - }); - it("deactivates without compiling or deleting either source", async () => { const harness = createHarness(); @@ -378,7 +341,6 @@ describe("stylesheet service", () => { expect(result.stylesheet).toEqual({ ...previousStylesheet, mode: "legacy" }); expect(harness.compile).not.toHaveBeenCalled(); - expect(harness.parity).not.toHaveBeenCalled(); expect(harness.preflight).not.toHaveBeenCalled(); }); diff --git a/packages/api/src/features/resume/stylesheet-service.ts b/packages/api/src/features/resume/stylesheet-service.ts index fe8b779ca..c54b43395 100644 --- a/packages/api/src/features/resume/stylesheet-service.ts +++ b/packages/api/src/features/resume/stylesheet-service.ts @@ -12,7 +12,7 @@ import { EMPTY_SEMANTIC_CSS_SOURCE } from "@reactive-resume/schema/resume/styles import { publishResumeUpdated } from "./events"; import { parseStoredResumeData } from "./resume-data-validation"; import { recordSemanticCssEvent } from "./stylesheet-observability"; -import { checkLegacyStylesheetParity, convertLegacyStylesheet } from "./stylesheet-preflight"; +import { convertLegacyStylesheet } from "./stylesheet-preflight"; export type StylesheetSnapshot = { id: string; @@ -68,12 +68,6 @@ type StylesheetServiceDependencies = { convertLegacy(snapshot: StylesheetSnapshot): StylesheetSource | Promise; compile(source: StylesheetSource): CompileStylesheetResult; preflight?(input: { data: ResumeData; stylesheet: StylesheetSource }): Promise; - parity(input: { - data: ResumeData; - stylesheet: StylesheetSource; - resumeId: string; - revision: number; - }): Promise<{ mismatches: readonly string[] }>; transaction(run: (transaction: StylesheetTransaction) => Promise): Promise; compare(snapshot: StylesheetSnapshot, input: StylesheetMutationCommon): boolean | Promise; publish(snapshot: StylesheetSnapshot): Promise; @@ -262,20 +256,6 @@ export function createStylesheetService(dependencies: StylesheetServiceDependenc throw validationError("The stylesheet cannot be activated because it is invalid.", compiled.diagnostics); } - const parity = await dependencies.parity({ - data: snapshot.data, - stylesheet: input.source, - resumeId: snapshot.id, - revision: snapshot.stylesheetRevision, - }); - if (parity.mismatches.length > 0) { - throw new ORPCError("STYLESHEET_PARITY_FAILED", { - status: 400, - message: "The converted stylesheet does not preserve the legacy PDF presentation.", - data: { mismatches: parity.mismatches }, - }); - } - const preflight = await runPreflight(snapshot, input.source); didPreflight = true; activationPageCount = preflight.ok ? preflight.pageCount : null; @@ -397,7 +377,6 @@ export function createDatabaseStylesheetService(options: DatabaseStylesheetServi }), } : {}), - parity: checkLegacyStylesheetParity, transaction: (run) => database.transaction((transaction) => run({ diff --git a/packages/api/src/features/resume/stylesheet.ts b/packages/api/src/features/resume/stylesheet.ts index d4ad3accf..8c9fa934a 100644 --- a/packages/api/src/features/resume/stylesheet.ts +++ b/packages/api/src/features/resume/stylesheet.ts @@ -13,11 +13,6 @@ const errors = { status: 400, data: resumeDto.stylesheet.errors.validation, }, - STYLESHEET_PARITY_FAILED: { - message: "The converted stylesheet does not preserve legacy PDF presentation.", - status: 400, - data: resumeDto.stylesheet.errors.parity, - }, STYLESHEET_REVISION_CONFLICT: { message: "The resume or stylesheet changed while the candidate was being validated.", status: 409,