fix(stylesheet): skip parity on explicit activation (#3316)

This commit is contained in:
Amruth Pillai
2026-08-13 15:53:28 +02:00
committed by GitHub
parent 5a75eda893
commit 3635b3d578
9 changed files with 4 additions and 204 deletions
-40
View File
@@ -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": {
-3
View File
@@ -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,
}),
@@ -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 = {
@@ -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");
});
});
@@ -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;
}
}
@@ -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<ErrorData<"STYLESHEET_PARITY_FAILED">>().toEqualTypeOf<{ mismatches: string[] }>();
});
it("exposes strict canonical conflict state for type-safe client rebasing", () => {
const schema = dataSchema("STYLESHEET_REVISION_CONFLICT");
@@ -88,8 +88,6 @@ type HarnessOptions = {
initial?: StylesheetSnapshot;
compile?: (source: StylesheetSource) => CompileStylesheetResult;
preflight?: ((input: { data: ResumeData; stylesheet: StylesheetSource }) => Promise<PdfPreflightResult>) | 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();
});
@@ -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<StylesheetSource>;
compile(source: StylesheetSource): CompileStylesheetResult;
preflight?(input: { data: ResumeData; stylesheet: StylesheetSource }): Promise<PdfPreflightResult>;
parity(input: {
data: ResumeData;
stylesheet: StylesheetSource;
resumeId: string;
revision: number;
}): Promise<{ mismatches: readonly string[] }>;
transaction<T>(run: (transaction: StylesheetTransaction) => Promise<T>): Promise<T>;
compare(snapshot: StylesheetSnapshot, input: StylesheetMutationCommon): boolean | Promise<boolean>;
publish(snapshot: StylesheetSnapshot): Promise<void>;
@@ -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({
@@ -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,