Some checks failed
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CD / Build and push images (push) Failing after 27m51s
CI / Lint, typecheck, test (push) Successful in 3m11s
CI / Auth e2e pack (push) Successful in 4m0s
CI / Import/export fidelity gate (push) Failing after 36s
CI / Build container images (push) Has been skipped
Make the "structure-true best effort" fidelity contract (ADR 0009) an objective, pipeline-gated suite so "best effort" cannot erode silently. - New CI job "Import/export fidelity gate" (.gitea/workflows/ci.yml) runs the corpus suites against the pinned sidecar images the stages use (pandoc/core:3.6, gotenberg/gotenberg:8), started via docker run and reached over the host gateway. Small and separate so it stays well under five minutes; the suites self-skip in the main checks job (no sidecars). - Export fidelity: fixtures/export corpus + gen-export-fixtures.mjs + export.fidelity.test.ts — exports Markdown to docx/odt through the real pinned pandoc and reads it back, snapshotting the round trip so a writer drift (ours or a version bump) fails the gate. - PDF smoke: pdf.fidelity.test.ts renders a page through real Gotenberg and asserts the extracted text and a sane page count (pdf-parse, dev-only). - Fidelity contract doc: fixtures/README.md defines "corpus green = fidelity acceptable" and the fixture-first bug process; per-corpus READMEs updated. Because the snapshots are byte-exact and generated with the pinned tools, bumping a sidecar without regenerating shifts the output and fails the suite (AC3). The import corpus (#63) is folded into the same gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
55 lines
2.3 KiB
JavaScript
55 lines
2.3 KiB
JavaScript
#!/usr/bin/env node
|
|
// Regenerate the export fidelity corpus (issue #69, ADR 0009) from the committed
|
|
// `*.md` sources: for each source and each office format, write the Markdown you
|
|
// get back after a full export → re-read round trip through the pinned pandoc.
|
|
// That round-trip snapshot is what the fidelity gate pins — a change in pandoc's
|
|
// docx/odt writer (e.g. a version bump) shifts the output and fails the suite.
|
|
//
|
|
// docker run --rm -p 3030:3030 pandoc/core:3.6 server
|
|
// PANDOC_URL=http://localhost:3030 node scripts/gen-export-fixtures.mjs
|
|
//
|
|
// Mirrors the export path (ExportService: gfm → <ext>, standalone) and then
|
|
// reads the document back (<ext> → gfm) exactly as export.fidelity.test.ts does.
|
|
|
|
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
|
|
const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), '..', 'fixtures', 'export');
|
|
const PANDOC_URL = process.env.PANDOC_URL ?? 'http://localhost:3030';
|
|
// Match the import corpus reader so both directions normalise the same way.
|
|
const MARKDOWN_FORMAT = 'gfm-implicit_figures-raw_html';
|
|
const BINARY = new Set(['docx', 'odt']);
|
|
|
|
async function pandoc(params) {
|
|
const res = await fetch(`${PANDOC_URL}/`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(params),
|
|
});
|
|
if (!res.ok) throw new Error(`pandoc ${res.status}: ${await res.text()}`);
|
|
return BINARY.has(params.to) ? Buffer.from(await res.arrayBuffer()) : res.text();
|
|
}
|
|
|
|
/** Export markdown to an office document, then read it back to markdown. */
|
|
async function roundTrip(markdown, ext) {
|
|
const document = await pandoc({ text: markdown, from: 'gfm', to: ext, standalone: true });
|
|
return pandoc({
|
|
text: document.toString('base64'),
|
|
from: ext,
|
|
to: MARKDOWN_FORMAT,
|
|
standalone: false,
|
|
wrap: 'none',
|
|
});
|
|
}
|
|
|
|
const sources = readdirSync(FIXTURES).filter((f) => f.endsWith('.md') && !f.includes('.expected.'));
|
|
for (const source of sources) {
|
|
const name = source.replace(/\.md$/, '');
|
|
const markdown = readFileSync(join(FIXTURES, source), 'utf8');
|
|
for (const ext of ['docx', 'odt']) {
|
|
writeFileSync(join(FIXTURES, `${name}.${ext}.expected.md`), await roundTrip(markdown, ext));
|
|
console.log(`wrote ${name}.${ext}.expected.md`);
|
|
}
|
|
}
|