/** * reportCheck.ts — a DATA-DRIVEN pre-flight check for a rendered report page. * * WHY THIS EXISTS, AND WHY IT IS NOT THE PUBLISH GATE * ─────────────────────────────────────────────────── * `backend/src/utils/artifact-validate.js` is the ONE authoritative gate on * artifact content and it stays that way. It is regex-over-source with eight * static rules (escaped markup, markdown-in-html, html-in-markdown, blocked * subresources, script syntax, structure, empty body, zero-width bar). Those * rules answer ONE question: *is this document well-formed enough to render?* * None of them is reimplemented here — that would be exactly the two-places * drift CLAUDE.md keeps getting burned by. * * This module answers the DIFFERENT question the gate's own header says it * structurally cannot: *does the rendered page agree with the DATA it was built * from?* The gate says it "cannot see that 42 should have been 45". That * sentence is this module's entire scope: * * 1. DATA ↔ RENDER — the data has 23 records, the table renders 20. * A bar drawn at 4% of full scale for the LARGEST value. * A printed number that disagrees with its own datum. * 2. GEOMETRY — a bar whose width looks fine in source but is * NULLIFIED by the cascade (a % width on an inline box). * 3. OVERFLOW — a fixed-px child inside a narrower clipping ancestor. * 4. PLACEHOLDER — `[object Object]`, a bare `NaN`, an un-substituted * `${…}` that reached the page as VISIBLE TEXT. * * THE DESIGN RULE THAT MATTERS MOST — NO PER-PAGE EXPECTATIONS * ──────────────────────────────────────────────────────────── * The failed prior attempt was for the model to hand-write "bar widths should be * 900/80/200/140/300" in the SAME pass that authored the HTML. That is not a * check: the pass that got the page wrong writes assertions that agree with it, * and the next report needs brand-new assertions. * * So the caller supplies `{ html, data }` and NOTHING ELSE. `data` is whatever * JSON the page was built from. This module DISCOVERS the collections and the * numeric series inside it, DISCOVERS the tables and bars inside the page, binds * them to each other using evidence found in the markup, and derives what must * be true. Written once; generic over any report. If a change here ever needs a * caller to describe its own page, the change is wrong. * * HOW GEOMETRY IS MEASURED — AND WHY THERE IS NO BROWSER * ────────────────────────────────────────────────────── * By PARSING, because the numbers are literally in the source: * * - An ECharts SSR chart emits each bar as ``. The * geometry is literal numbers AND the binding back to the datum is an * explicit attribute — no heuristic, no layout engine. * - An HTML bar is `style="width:45%"` — also a literal number. * * jsdom was evaluated and REJECTED: it resolves the cascade but performs NO * layout, so `getBoundingClientRect()`, `scrollWidth`, `clientWidth` and * `offsetWidth` all return 0 (measured, jsdom 29.1.1). It therefore buys nothing * this file cannot compute, at 25 MB in a package that installs into every run * container and is vendored into the Copilot Lambda image. A real browser IS the * only way to get TRUE layout — flex/grid shrink, text wrapping, a table wider * than its column — and that class is deliberately OUT OF SCOPE here rather than * faked. What IS in scope is the arithmetic subset: an explicit width nullified * by an explicit display, and an explicit px child inside an explicit px * clipping box. * * FALSE POSITIVES ARE THE ENEMY — EVERY RULE FAILS OPEN * ───────────────────────────────────────────────────── * A pre-flight that cries wolf burns the model's repair budget on nothing. So * every rule is conservative in the FLAGGING direction: a complex CSS selector * is ignored rather than guessed at, a page that renders a top-N subset of a big * collection is not "missing rows", a series with fewer than three bars gets no * proportionality verdict, and a literal `width:0` is left to the gate that * already owns it. The cost is false negatives, which is the right trade for a * check that runs before a human ever sees the page. * * ...WHICH IS WHY THE RESULT CARRIES A PROBE RECEIPT * ─────────────────────────────────────────────────── * "A NEGATIVE result means suspect your PROBE first" (CLAUDE.md). A checker that * finds nothing because its selector never matched is indistinguishable from a * clean page — unless it says what it looked at. Every result therefore carries * `checked`: how many tables, body rows, bars, collections and series it found, * and how many bars it actually managed to BIND to a datum. `bars: 5, boundBars: * 0` is a broken probe wearing a clean result's clothes, and it is visible. */ /** * The defect shape is identical to the gate's (`{ code, line, message, hint }`) * so the Copilot handles pre-flight defects and gate defects through ONE code * path. * * `zero-width-bar` is REUSED from the gate's vocabulary deliberately: it means * the same thing to the model ("this bar will render invisible") and the two * DETECTIONS are disjoint — the gate fires when a bar-ish element has no width * source anywhere or a literally broken one (`width:0`, `width:NaN%`, * `width:${pct}%`); this fires only when the element HAS a valid positive width * that the cascade then throws away. A page cannot trip both for the same * element. Every other code below is a failure kind the gate has no rule for. */ export declare const REPORT_CODES: { ZERO_WIDTH: string; ROW_COUNT: string; ROW_MISSING: string; BAR_COUNT: string; BAR_PROPORTION: string; VALUE_MISMATCH: string; PLACEHOLDER_TEXT: string; OVERFLOW: string; }; export interface ReportCheckResult { ok: boolean; defects: Array<{ code: string; line: number | null; message: string; hint: string; }>; /** * The PROBE RECEIPT — what was actually measured. A clean verdict with zeros * here is a broken probe, not a clean page. Always read it. */ checked: any; } /** * Check a rendered page against the data it was built from. * * @param html the exact HTML that would be published * @param data whatever JSON the page was rendered from — collections and * series are DISCOVERED inside it, never declared by the caller */ export declare function checkRenderedReport({ html, data }: { html: string; data: any; }): ReportCheckResult; /** Test hook — the internals are implementation detail, but a probe deserves its own unit tests. */ export declare const __reportCheckInternals: any; /** * A pure, offline, zero-credential skill: it reads two arguments and returns * JSON. No network, no filesystem, no env — so it declares no `envKeys`, is not * `callsBackend`, and runs anywhere `@zibby/skills` is installed (the Fargate * run container AND the Copilot Lambda, which vendors the same tarball). */ export declare const reportCheckSkill: any;