Commit Graph
3 Commits
Author SHA1 Message Date
Yogesh OjhaandClaude Opus 5 724ebd42bb feat(reports): a section declares its role, its order and what it asks at generate time
Choosing what a report contains required editing a template. That is the wrong
place for a decision made per report, and the scan launch flow already
established the alternative: a saved configuration seeds the run, the run may
depart from it, and the saved configuration is never touched.

Three declarations put sections on the same footing as stages. SectionRole
separates the content a reader asked for from the furniture around it, so a
picker can offer twenty-one choices and include the cover, the contents and the
reference matter without being asked. Section.order fixes the reading order,
which until now was however the directories happened to sort — the executive
summary came after attack paths because "a" precedes "e". Section.launch_fields
names the settings worth surfacing when the report is generated rather than
only in the builder, mirroring Stage.launch_fields; the findings section
declares its severities, its detail depth, its roll-up and its asset cap.

The estimate had to become honest for any of this to be worth showing. It
counted every finding in the scan whatever the section filtered to, so dropping
three severities moved nothing. It now counts through the same severity filter
the section applies and prices each tier separately, from the real per-severity
issue counts rather than an assumed split: 28 pages against 26 rendered for a
full template, 10 against 9 for critical and high alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AW5pitnosX8LEFEYxH8gFS
2026-09-06 17:14:01 +05:30
Yogesh OjhaandClaude Fable 5.1 068f14bbfb feat(reports): the document says its answer before you open it
A design pass over the rendered documents, on the six themes, the print
stylesheet, the charts and the cover artwork. The brief was a baseline that
looked templated; the bar was a client deliverable someone would be proud to
hand over.

The structural failure was the severity chart. On a real scan, 589 informational
findings swamped 20 criticals into an invisible sliver, on the one page everyone
reads. Severity counts are now stat tiles, so the number is always legible, and
the ring and bar draw only the actionable set with the informational total
stated in the caption. That is the dataviz rule that a dominant neutral bucket
is a KPI, not a slice.

The executive page now opens with a verdict band: the gauge, the headline, the
severity tiles, then five surface figures in one row, and only then the prose at
a readable measure. Every cover carries a grade mark, so the report tells you
its answer on the front; on blueprint the reticle artwork is drawn around it.
Findings are a card with a hierarchy and a facts row where a table used to be,
control mapping is a line of text per framework rather than a wall of chips,
tables have a settled rhythm, and the section number sits in the accent.

The warning collector from the previous commit found every WeasyPrint defect
here. font-weight accepts only the hundreds, so two themes had been rendering
headings in a silent bold. content(text) drops the whitespace between an inline
span and its neighbour, so the running header read "1Executive summary" and the
bookmark with it; both now read a precomputed attribute. break-after: avoid on a
div is not honoured, so labels were orphaning from their content. A dark-paper
theme needs its background on @page as well as html, or the margins print white,
which is why noir had a white frame. word-break: break-word is invalid, and the
hostname column in the asset table was being crushed to a sliver by auto layout.

Two string edits in this pass silently missed because ruff had reformatted the
target; reading the function back after each edit is now written down.

Verified on the gov.cy scan: all 26 sections in all six themes, 57 to 60 pages
each, zero renderer warnings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017f2Mf2HGwFaTRnui4UxpRd
2026-09-06 13:59:44 +05:30
Yogesh OjhaandClaude Opus 5 424e6d6e23 feat(reports): a document engine, not an export
reNgine 2 could produce a PDF. This produces a deliverable, and it is built
the way scan engines are built: sections are plug-and-play directories under
reports/, each with a pydantic config model that IS the source of truth. That
model feeds GET /reports/catalog, the builder renders its own form from it, and
the API validates against it. Adding a section is a directory. Scan Engine is to
stages what Report Template is to sections.

WeasyPrint 69 renders it, chosen over Chromium and Typst after measuring what
each can actually do. Only real CSS Paged Media gives a table of contents with
true page numbers, running headers, a PDF outline and footnotes; SysReptor
reaches the same verdict and pipes Chromium's HTML into WeasyPrint for exactly
that reason. reNgine drops the Chromium hop because it templates in Jinja and
draws its charts as SVG in Python, so a browser buys nothing and costs 400 MB
per report. Typst was rejected because a user-uploadable theme would then have
to be Typst code rather than CSS.

A theme is data. shared/definitions/report_theme.py is the schema, six themes
ship as YAML, and a user uploads their own; there is no markup in a theme, so
there is no injection surface. Fonts are vendored so an offline instance still
renders in Latin, Greek and Cyrillic.

Everything the document claims is computed once, in reports/analysis/engine.py:
a posture score whose every deduction is named, weaknesses ranked by the signals
behind the rank rather than severity alone, attack paths that chain observations
into a route, a remediation plan ordered by risk removed, per-dimension change
against the previous run, and caveats that state what was never assessed. Two
narrators read that one brief. Without AI the report still argues its case; with
AI the prose is better and the model never sees a row, only the brief, capped and
cached per check. An AI failure falls back and the report still completes.

Generation is a job. POST /reports queues, the worker walks progress over SSE,
and four formats come out of one render: PDF, self-contained HTML, Markdown and
JSON. The estimate reads each section's own limits, so the page count it promises
is close to the one it delivers.

AI moves out of Settings into its own top-level page: connection, a model per
tier, what it may be used for, what it has cost, and the narrative cache.

Verified on a 1,379-host gov.cy scan: 124 pages in 19.5s across 19 sections,
all six themes, and a broken key still producing a complete report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017f2Mf2HGwFaTRnui4UxpRd
2026-09-06 11:45:51 +05:30