Assessor_2.0/docs/REPORT_STRATEGY.md

674 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Assessment output strategy
## Export layout decision 2026-10-02
User approval 2026-10-02: this implemented report is the binding template for future Management Reports. Its permanent contract lives inside Report Engine at [MANAGEMENT_REPORT_TEMPLATE.md](../src/reporting/MANAGEMENT_REPORT_TEMPLATE.md). Preserve the approved renderer structure and data boundaries unless explicitly revised by the user; historical HTML exports are not overwritten.
The user-supplied Huntsman report page is the layout/style reference for the Management Report only: white report sheets, ASSESSOR branding, orange headings, dark table headers, two-column category explanations/evidence, factual bar charts and repeated footers. This explicit export-only decision supersedes previous app-style layout guidance for the Management Report; application views remain unchanged. The report embeds the existing ASSESSOR logo and local Inter with no remote dependency. The opening overview and complete Context heatmap lead into one logical report sheet per populated category, followed by scope and method. Logical sheet numbering does not promise one physical PDF page per sheet. Missing time-series/score/endpoint data is never fabricated to imitate the reference. Original technical evidence and recommendations remain in the separate Technical Report.
## Management Report with published Context results (2026-10-02)
Explicit user decision supersedes the earlier Core-only Report A content below: the Management Report requires Context results for the same client, assessments, scan runs and committed repository revision. It is generated offline with fixed German text, no LLM and no Internet access. A missing Context producer or mismatched scope blocks the management run; a changing revision retries the read and ultimately fails rather than combining snapshots. Client Info refuses management generation while Context Engine is off and suppresses late output after it is switched off. Technical generation remains available independently and does not compute Context when invoked alone; its archive contains no management HTML rather than a Core-only fallback. Historical frozen projections retain the historical renderer and are not rewritten.
Context Engine publishes a versioned result object through `context-report-publication.ts`; Report Engine consumes only its declared `ReportContextPublication` contract. The Core backend composes the two at its existing pinned extension point using the canonical read-only port. Neither engine imports the other. The publication preserves catalog/vocabulary/engine/severity versions, deduplicated finding count, per-cluster scanner/GPO counts, assignment confidence, source provenance and technical Result IDs. It uses the existing Context membership and heatmap semantics, without classifying or scoring again.
The management file contains: Management Summary; all Context categories in the severity-sorted heatmap with the unique overall Finding count; populated problem fields with general catalog descriptions, evidence counts, confidence and scanner sources; fixed review guidance pointing to original recommendations in Report B; documented risk acceptance and selected work-state counts; comparison results only when supplied by the existing shared projection; environment facts, source coverage and collection times; technical Security Domain counts in the appendix; method and source-version notes. General catalog descriptions are explicitly not evidence of a common attack path. Base Severity is category severity, never a new finding verdict, CVSS, priority, exploitation probability or business loss. Unknown/unexecuted checks are not passed; neutral heatmap cells are not secure; GPO backup configuration is not effective host state. Overlapping cluster counts are never added into a total. Work tracking and risk acceptance do not prove remediation; rescans remain required.
Management disclosure stays separate: no Result IDs, source Rule IDs, evidence paths, registry keys, technical finding fields or original scanner remediation payloads are hidden in Report A. Report B receives the frozen Context counts and the Result IDs proving them alongside its original detailed evidence and recommendations. Archive projection JSON retains the published results for reproducible replay. Static HTML includes the already bundled local Inter font as embedded data and has no script or external resources. The report export is not part of the client load path.
Current versions: Report Engine 0.3.0, shared projection 1.3.0, Management renderer 3.0.0; product capability release 2.13.0. No database schema, migration or Core result semantics change. A separate historical Delta Report remains its own artifact; the new management cut does not invent comparison evidence when no delta was supplied.
Decision: 2026-09-18, explicitly approved by the user. The 2026-09-23 user request
starts the A/B HTML deliverable through Client Info (Phase 7 UI integration,
cross-cutting report projection). Artifact C and D remain separate future work.
ASSESSOR produces **three** artifacts from one assessment:
| Artifact | Audience | Nature |
| --- | --- | --- |
| A — Management Overview (HTML) | customer decision makers | frozen snapshot |
| B — Technical Report (HTML) | engineers reproducing a finding | frozen snapshot, complete |
| C — Arbeitsgrundlage (XLSX + HTML) | the people doing the work | living document, curated |
| D — Delta Report (HTML, management + technical cut) | both, after a rescan | frozen snapshot of the change |
The dividing principle: **a report states the finding and its proof; the
Arbeitsgrundlage states the measure and who owns it.**
Remediation progress is proven by rescans, never by self-reported status. See
*Progress is proven by rescan* below.
## Approved delivery decisions
| Decision | Choice |
| --- | --- |
| Delivery | Separate files per artifact |
| Design | ASSESSOR application look |
| Technical scope (B) | Deviations, diagnostics and score-neutral classes |
| Interaction | Static, print- and PDF-ready, no JavaScript |
| Language | German, all artifacts |
| Arbeitsgrundlage format | XLSX to work in, HTML as the frozen status view |
| Arbeitsgrundlage scope | Curated selection, plus the complete list on a second sheet |
| Progress evidence | Rescan only; planning fields stay but are marked unverified |
| Delta delivery | Own artifact, management and technical cut |
Separate files keep the confidentiality boundary real: the Management file can be
forwarded without carrying registry keys, SIDs, evidence paths or parser
versions in its source. A single file with a hidden section would still contain
them.
## Report language
Both reports are written in German: headings, prose, labels, table headers,
legends and every explanatory sentence.
This does not change the Client Info dashboard, which remains English-only by its
own decision. The reports are separate customer-facing artifacts; the two rules
stand side by side and neither overrides the other.
German is the carrier language. **Established English technical terminology stays
English** and is not translated, not hyphenated into a German compound and not
replaced by an invented German equivalent. This is the general rule, not a list
of exceptions. It covers at least:
- the agreed GPO verdicts `Deviation`, `Passed`, `Not configured`,
`No baseline rule`, `Not comparable`, `Conflicts`, `Duplicates`
- the security domain names from `src/features/dashboard/security-domains.ts`
- assessment vocabulary such as Finding, Baseline, Score, Scope, Severity,
Pass Rate, Review, Owner, Due Date, Remediation, Evidence
- platform vocabulary such as Group Policy, Registry, Domain Controller,
Privilege, Attack Path, Hardening
Reason: these are the terms the readers already use, and most of them appear
verbatim in the application's Work filters and in the Client Info dashboard. A
reader must be able to match a line in the report to the view it came from, and a
translated second vocabulary would weaken the documented agreements.
Where a term is not self-explanatory to a management reader, it is introduced once
with a short German explanation and then used in its canonical English form.
German prose carries the sentence; the term inside it stays English.
Original source values, registry keys, value names, principals, evidence paths and
native identifiers are never translated or localized. Numbers and dates follow
German formatting.
The shared projection stays language-neutral and carries data, not display
strings. All German wording lives in the renderers.
## Shared report projection
Implemented in `src/reporting/report-projection.ts` (moved from `src/domain` on 2026-09-30)
(`REPORT_PROJECTION_VERSION = '1.1.0'`), covered by
`tests/domain/report-projection.test.ts`.
All artifacts read **one** projection built in the platform-neutral domain layer.
The projection is the single source of truth; the renderers only choose depth,
never content. Two consequences are binding:
- A number that appears in both reports is the same number. The Management
Overview never rounds, re-derives or re-filters a value that the Technical
Report states differently.
- Renderers contain no source-specific parsing, no scoring arithmetic and no
database access. They receive the finished projection.
The projection is built on export only. It is not part of the workspace load
path and must not derive presentation fields for records no report section
consumes.
## Report A — Management Overview
**Superseding user decision 2026-09-29 (Phase 7):** The Management Report is
generated offline and without an LLM from the current, selected client's
existing report projection. It is a factual overview, not a scanner finding
list, mixed-method score or speculative risk assessment. The earlier Report A
content contract below (overall-score donut, domain spine, priority findings,
remediation status and source overview) is retained as historical context but
does not govern the current Management export.
Current Report A shows project name, report date, the captured domain and DC
count when present, counts of assessed and deviating results, a heatmap of
deviations divided by assessed results for each catalog Security Domain, and a
second stacked chart of passed and deviating results with the Pass Rate and
underlying counts. A domain with no assessed results has no percentage or heat
level. The colors and percentages describe result frequency only: they do not
express severity, business impact, compromise, protection or progress.
The GPO domain chart counts assigned result rows. When the separate policy
evaluation exists, its failing comparisons and affected settings are stated
separately and never equated to result rows. The export names its statement
limits: source observations may come from different times; affected business
services, exploitation and current remediation state are not derivable from
these facts. It does not extrapolate backup age or add invented risk scenarios,
owners, measures or deadlines. Technical results remain in Report B. The
document is standalone HTML with inline styles and no external resources.
**User decision 2026-09-30 (Report Engine v0.2):** Directly below the three key
figures, Report A shows a `Kernaussagen` block with three deterministic statements.
Each is a fixed German sentence into which only projection values are inserted:
- **Gesamtbild:** assessed and deviating results with their share.
- **Schwerpunkt:** the domain or domains with the most deviations and with the
highest deviation share. Ties name every tied domain, and the sentence carries
the note that share and frequency say nothing about severity or business risk.
- **Aussagegrenze:** not run, not applicable, failed and otherwise unassessed
checks, which are neither passed nor deviating and enter no rate.
A statement without a factual basis is omitted. The statements are
language-neutral data in the projection (`statements`, built by
`src/reporting/report-narrative.ts`). The wording lives in
`src/reporting/report-statement-text.ts`, which both renderers share. The
Technical Report proves every statement with the same sentence and the result IDs
behind each number (`Belege der Kernaussagen`). Report A contains no IDs.
### Historical Report A design (superseded 2026-09-29)
Implemented in `src/reporting/management-overview-report.ts`
(`MANAGEMENT_OVERVIEW_RENDERER_VERSION = '1.0.0'`), covered by
`tests/exports/management-overview-report.test.ts`.
Answers: how do we stand, what is most urgent, what is this based on, what do we
not know. Its reading order follows the agreed Client Info grammar.
**Named deviation from the `assessor-ui` skill:** a delivered document uses no
application shell — no sidebar, topbar, command band or context strip — and none
of the four page archetypes, because it is read and printed outside the
application. Typography roles, donut geometry, tile and segment grammar, status
colour rules and the panel inset apply unchanged. Light is the document default
because a report is printed on white; dark renders from the same semantic roles
through `prefers-color-scheme`.
The bundled Inter is referenced by family name with a system fallback. No font,
style, script or image is loaded when the file is opened.
### 1. Assessment identity
Customer, environment (domain, forest, domain controller count from the
environment inventory), assessment period, report date, ASSESSOR product version.
### 2. Overall posture
The single score as a donut using the shared donut geometry, with its method
label. Binding wording from `SCORING_MODEL.md`:
- The GPO contribution is *conformance of configured, comparable in-scope
settings*. Binding German wording: „Konformität der konfigurierten,
vergleichbaren Einstellungen im Scope". It is never labelled baseline
compliance, baseline coverage or proof that recommended settings are
implemented — in German neither „Baseline-Compliance", „Baseline-Abdeckung"
nor any phrasing implying the baseline is fully rolled out.
- PingCastle enters as `100 − native risk`; the raw risk stays secondary context.
- A mixed-method index is named as such. Security Domain values are pass rates
and are not a decomposition of the index.
### 3. Security Domains
The eight domains from `src/features/dashboard/security-domains.ts` as a
connected pass-rate spine beside the overall score. Not a table.
### 4. Priority Findings
Grouped by security domain. **There is no global cross-tool ranking.** Inside a
domain, findings are ordered by the declared priority basis of their own source,
and that basis is named at the point of display.
Per entry: title, domain, the source's own rank value with its basis, one
sentence of impact, plus owner and status when a review exists.
GPO deviations form their own equal-weight block with their count, the affected
policy settings and the scope limit. They are never assigned an invented
criticality.
No registry keys, no SIDs, no file paths, no raw values.
See *Priority without a common severity* below for why.
### 5. Remediation status
Open, in progress, accepted, overdue, and findings without an owner — derived
from the stored reviews including accepted-risk data. If no reviews are
maintained, the section is omitted rather than rendered empty.
### 6. Coverage and gaps
What was assessed, what was not, unassessed controls, and every scope limit that
bounds a statement. A GPO import covering a subset of the domain is reported as
scope-limited and never presented as a domain-wide or effective-device claim.
This section is what makes the report defensible.
### 7. Source overview
Per tool: run or file date, score contribution, and the method that produced it.
### Excluded from Report A
Per-record tables, raw values, evidence paths, diagnostics, parser, analyzer and
manifest versions.
## Report B — Technical Report
The initial static renderer is implemented in `src/reporting/technical-report.ts`.
It retains the current internal result projection, original Work fields when a
canonical occurrence matches, and the retained per-backup GPO instructions.
Missing source facts are labelled as unavailable; the renderer never invents
actual/expected values or an endpoint-effective state. A dedicated completeness
audit of diagnostics, reimport metadata and all source formats remains open and
must not be mistaken for a final acceptance of every section below.
Answers: what exactly is configured, what should it be, where does that stand,
how does ASSESSOR know, what do I do.
### 1. Scope and provenance
Imported artifacts, processing, analyzer and manifest versions, reimport status,
aggregation and evaluation run identifiers. Enough to reproduce the result.
### 2. Result inventory
Totals per status, severity, domain and tool, including the score-neutral
classes. Passed results appear here as a count.
### 3. Findings in detail
Grouped by security domain, ordered by severity inside each group. Per finding:
- technical identity and scope
- actual against expected value, with the field-specific deviation marking used
by the Finding Reader: `Operation`, `Data` and `Type code` for registry policy
differences, several fields only where each is independently proven
- principal sets compared per member: only configured principals absent from
every retained baseline variant are marked; a principal also present in the
expected baseline stays neutral
- GPO source attribution as vertically stacked sections per exact backup
identity, each with evidence reference, source-local sequence and every
original instruction immediately visible
- conflict and duplicate flags, kept separate from the policy verdict
- baseline variants where more than one exists
- the remediation step from the review
### 4. GPO policy register
All seven verdicts in full: `Deviation`, `Passed`, `Not configured`,
`No baseline rule`, `Not comparable`, `Conflicts`, `Duplicates`. The
score-neutral verdicts are labelled as score-neutral at the point of display, so
that a reader cannot mistake `Not configured` for a remediation obligation.
### 5. Diagnostics
Unresolved identities, parser gaps, scope limits, declared-against-counted
mismatches and incomplete backups. Each observation appears once with its
authoritative resolver reason and is not restated by snapshot aggregation.
### 6. Appendix
Complete result index.
## Artifact C — Arbeitsgrundlage
Decision: 2026-09-18, explicitly approved by the user.
Answers: what do we do, who owns it, by when, where does it stand.
The measured volume makes this its own artifact: the stored data holds **1,806
actual deviations** (CIS-CAT 969, Maester 350, PingCastle 277, Purple Knight 210)
plus the GPO deviations. Rendering each of those with full technical depth inside
Report B would produce a document nobody works with.
Size is not the decisive reason, though. A report is a frozen snapshot that
proves what was found on a date and is archived unchanged. An Arbeitsgrundlage is
a living document that the customer edits for weeks: owner, due date, status,
comment. Forcing both into one file makes both worse.
### Data model
The Arbeitsgrundlage is the stored review model in `src/domain/review.ts`:
`status`, `priority`, `owner`, `due`, `prerequisites`, `access`, `nextStep`,
`evidence`, `comment`, the markers `quickwin`, `blocked`, `external`, `question`
and `notRelevant`, plus `riskAcceptance`.
`include` is the curation switch. The consultant selects what reaches the
customer as a work item. Shipping 1,806 unfiltered items produces no action at
all.
### Structure
- **Sheet 1 — Maßnahmen.** Only the curated selection, with a stated selection
criterion. One row per work item.
- **Sheet 2 — Vollständige Abweichungen.** All deviations for reference, so the
customer can pull further items in without the working list overflowing.
The HTML variant renders the same content as a frozen status view for status
meetings and filing. It is generated from the same projection, never maintained
separately.
### No duplicated technical depth
A work item carries its stable finding identifier, the measure, prerequisites,
required access and the owner — not the evidence, not the source attribution, not
the baseline variants. Those stay in Report B and are reached through the
identifier.
One fact, one place. Repeating the technical detail would leave two diverging
versions after the first correction.
## Progress is proven by rescan
Decision: 2026-09-18, explicitly approved by the user: *"Den Fortschritt
dokumentiere ich durch Rescans — für mich zählt nur der technische Beweis."*
Remediation progress is established by comparing scan runs, never by a status
somebody ticked. This follows `TIMESLOT_COMPARISON_PLAN.md`, which already
replaced the conclusion *absent equals remediated*.
### Binding rules
- No artifact presents a self-reported status as a resolved finding. A work item
marked done in the Arbeitsgrundlage stays an open deviation in every report
until a rescan proves otherwise.
- A delta uses the lifecycle vocabulary of the timeslot contract — demonstrably
remediated, still open, unresolved, not comparable — never a binary
done/open. *Unresolved* is a legitimate and frequent outcome and is shown as
such.
- Absence of a finding is not proof of remediation. Remediation is claimed only
where the profile-bound negative-proof contract is satisfied for exactly that
rule and object.
- Result presence and deviation lifecycle stay separate. A rule can still be
reported while its concrete deviation is fixed.
- No effective-endpoint claim is derived from a backup-configuration comparison.
### Why the caveat is not theoretical
The stored data shows how easily absence would be misread: Maester carries **845
`not_run`** and 10 `execution_error`, Purple Knight 34 `not_applicable` and 10
`not_run`. A check that did not run would otherwise turn into a success message —
the most dangerous false statement a progress report can make, because it errs in
the comfortable direction.
### Artifact D — Delta Report
An own artifact per rescan, in the same two cuts as the reports. The initial
report stays frozen and is never rewritten; the change is a separate document
with its own reporting date.
**The comparison is against a chosen reference run, and that choice is made in
ASSESSOR — not at export time.** The export renders a comparison that already
exists; it contains no run-selection logic, no fallback to "the previous run" and
no implicit pairing of its own. Consequences:
- The chosen reference run is part of the stored comparison, together with its
contract version, engine versions and input fingerprint. The export reads it
and never re-derives it.
- The document names both compared runs explicitly — reference and current, each
with its identity and date — so it stays self-describing once it is out of the
application.
- Without a stored comparison there is no Delta Report. The export is
unavailable rather than guessing a pairing.
This keeps comparison a domain concern and presentation a separate one: the Delta
Report is a renderer, not a second comparison engine.
- **Management cut:** score movement, what is demonstrably remediated, what
persists, what is newly found, and how much of the comparison is unresolved or
not comparable. The unresolved share is stated, not hidden.
- **Technical cut:** per finding the lifecycle verdict with its evidence — which
run, which identity match, which contract qualified it — plus the
disqualifying diagnostics where a comparison was not admissible.
### Planning fields in the Arbeitsgrundlage
`status`, `due`, `owner` and the markers stay in the XLSX as the customer's own
steering aid. They are labelled as unverified self-report at the point of display.
ASSESSOR never promotes them into a report as a result.
The export therefore stays one-way by design, not by limitation: progress does not
need a return channel because it comes from the next scan.
## Priority without a common severity
Decision: 2026-09-18, explicitly approved by the user.
Severity is not a property every scanner delivers. Measured against the stored
data:
| Tool | Results | Severity coverage | Native ranking signal |
| --- | --- | --- | --- |
| CIS-CAT | 1,233 | none, all `unknown` | none |
| PingCastle | 277 | derived from Points | Points 0–50+ |
| Maester | 1,590 | 99.4 % | native severity |
| Purple Knight | 1,309 | 100 %, 187 `critical` | native severity |
| GPO / baseline | — | none | none |
A severity-ranked global list would therefore drop CIS-CAT and GPO out of the
Priority Findings entirely, let Purple Knight dominate merely because it is the
only source that emits `critical` natively, and keep PingCastle permanently below
`critical` because `pingCastleSeverityFromPoints` caps at `high` by construction.
That ordering would describe the report formats, not the customer's risk.
### Binding rules
- Priority Findings are grouped by security domain. The eight domains are
tool-neutral and already exist.
- Within a domain, findings are ordered by the declared priority basis of their
own source. The basis is visible to the reader.
- No ranking is ever produced across sources with incomparable bases. A single
ordered list that mixes them is not permitted.
- GPO deviations are equal-weight. They get their own block with count, affected
policy settings and scope limit, and never an inferred criticality.
- A source without any defensible ranking basis lists its findings unranked and
says so. Unranked is an acceptable answer; an invented rank is not.
### Profile obligation
Implemented as `ToolPriorityBasisDefinition` in
`src/domain/tool-profile-definition.ts`, required on every `ToolProfileDefinition`
and enforced by `tests/profiles/declarative-profiles.test.ts`.
Analogous to the existing `securityScoring` obligation, every tool profile
declares a **priority basis**: the exact mapped properties, scale, direction and
semantics of the value used to order its findings — or an explicit reason why no
usable basis exists. Never infer a priority from an arbitrary field named
severity, level, score or rank.
A declaration may also list `excludedProperties`: fields that resemble a basis
but are not one, each with its reason. This is what stops a later change from
quietly promoting them.
| Profile | Declared basis |
| --- | --- |
| Purple Knight | `severity` — native criticality, the only source reaching `critical` without derivation |
| Maester | `severity` — native per test |
| PingCastle | `numeric` on Points, `0-50+`, higher-is-worse |
| CIS-CAT | `unavailable` — benchmarks state no per-recommendation criticality |
| Generic | `unavailable` — unrecognized fields establish no scale or direction |
PingCastle is declared on Points rather than severity on purpose: its canonical
severity is *derived* from Points by `pingcastle-severity-from-points` and is
capped at `high` by construction. Points keeps the granularity the capped
severity loses.
`reportPriorityBasisFrom` turns an `unavailable` declaration into an explicit
`equal-weight` basis carrying the profile's own reason, so a report states why a
source is unranked instead of implying the declaration is missing.
A derived, normalized cross-tool ASSESSOR priority is **not** approved. It would
require its own versioned rule and an explicit decision of its own.
### Guarded mis-mapping risk
`src/profiles/cis-cat/cis-cat-definition.ts` declares
`severityHeaders: /^(?:severity|level)$/i`. CIS `Level 1` / `Level 2` is a
hardening profile tier, not a criticality. It falls through to `unknown`, which
is harmless, and it is now recorded as an excluded property with its reason and
covered by a test. It must not be "corrected" into a severity, because that would
manufacture criticalities the benchmark never stated.
## Report Engine boundary
Decision: 2026-09-30, explicitly approved by the user. Implementation plan: Phase 7,
*Report Engine als eigenstaendiger Nur-Lese-Baustein*.
The Report Engine is a self-contained, read-only building block inside ASSESSOR.
It produces the reports offline, without an LLM, and never changes ASSESSOR
logic or data.
- **Location.** The projection, correlation, input assembler,
`client-report-projection` and both renderers were moved unchanged to
`src/reporting/` on 2026-09-30. The shared security-domain catalog, scoring and review
hydration stay ASSESSOR components and are only imported, so that a report
number always equals the Client Info and Work number. An architecture test
enforces the boundary.
- **Read-only access.** The engine reads through a read-only repository port and
a dedicated read-only database pool. It detects a repository revision change
during assembly and repeats the read instead of mixing states.
- **Server rendering.** Since v0.1 the server renders both files once per run and
archives them. Client Info downloads exactly the archived file through
`POST /api/v1/projects/:id/report-runs`.
- **Frozen state.** Every report run is archived server-side in its own folder
and never modified afterwards. The folder holds `projection.json`, both HTML
files and a manifest with file hashes, engine versions, repository revision,
scope and time. The archive lives outside the repository and outside synced
folders (`ASSESSOR_REPORT_ARCHIVE_DIR`) and is deleted together with its
client. `projection.json` and the manifest are internal. They are never
delivered with Report A, which keeps the separate-files confidentiality
boundary.
- **Boundaries.**
- Only the shared security-domain catalog.
- Severity only per source, never summed across scanners.
- Review state only as unverified self-report, and never in Report A.
- Scenarios and cross-finding relationships only from the context engine
(Phase 13E), never from report-local rules.
## Guardrails for both reports
- Score-neutral classes never appear in Priority Findings and never enter the
score. They remain fully inspectable in Report B.
- A GPO verdict is taken only from the authoritative rolled-up policy verdict and
the explicit conflict and duplicate flags. Never from comparison prose,
historical results, baseline text, guidance or evidence.
- Unknown or unavailable facts render as an em dash, never as an inferred zero.
- Both files are self-contained: inline styles, no CDN, no external fonts,
scripts or images, no network access when opened. A report leaves the building
and must not load anything.
- The bundled Inter variable font is embedded or a local system stack is used;
no font is downloaded at view time.
- Shared typography roles, donut geometry, marking semantics and panel inset
apply as in the application. Status colour stays in accent lines, markers,
arcs and score nodes, never as a full status fill.
- Static output: no JavaScript, printable to PDF without layout loss.
## Relationship to the existing export
### Current-view export — explicit user extension, 2026-09-18
The Work toolbar's CSV/MD/HTML buttons now export the current filtered and sorted
view, including Results, with exactly two content columns: `Soll-Zustand` and
`Abweichungen`. This supersedes their old marked-only selection. The user can
still restrict the view with `My Workspace`; that restriction combines with the
other active filters. A view export is a frozen, scoped inspection snapshot,
not the curated Arbeitsgrundlage C, a replacement for reports A/B, or a delta.
`src/exports/work-view-export.ts` supplies one click-time presentation projection
for these three formats, consuming existing Work records and the shared Results
resolver without ranking, scoring, re-evaluation or database queries. The shared
`report-projection.ts` contract for artifacts A–D remains unchanged. Native
remediation, baseline variants, exact source attribution and verification gaps
stay visible. Review completion never becomes a technical resolution. Long CSV
cells are preserved in labelled continuation rows (same finding ID), keeping two
columns and avoiding Excel's cell-size truncation. HTML is self-contained,
script-free, with locally bundled Inter embedded and responsive wrapping.
The following paragraph records the earlier strategy baseline; its description
of the toolbar's marked-only behaviour has been superseded by this extension.
`src/exports/dashboard-export.ts` and `ActionlistControls` already export the
review model as CSV, Markdown and a minimal HTML page. That HTML knows nothing of
the score, the security domains, GPO verdicts or provenance and uses Segoe UI
with fixed colours.
The existing action-list export is the seed of artifact C, not of the reports.
The reports are new; the action-list HTML is replaced by the artifact C renderer,
and the CSV path stays useful for ticket-system import.
## Open decisions
- How many findings each domain block shows before it is truncated, and how a tie
inside one source's ranking basis is broken.
- Whether the Microsoft baseline packages carry any per-setting classification
that could later give GPO deviations a defensible basis. Until proven, GPO
stays equal-weight.
- The selection criterion wording for the curated sheet, and whether it is fixed
or written per engagement.
- Which profiles can satisfy a negative-proof contract at all. Profiles that
cannot will never produce a *demonstrably remediated* verdict, and the Delta
Report must say so per source rather than leave the reader guessing.
- File naming convention across all artifacts, including how a delta names its
two compared runs.
## Dependency
Artifact D depends on the timeslot comparison actually producing stored results.
`assessor_timeslot_evidence_comparisons` exists with its contract version, engine
versions and input fingerprint, but currently holds no rows. Until the comparison
is implemented per `TIMESLOT_COMPARISON_PLAN.md`, artifacts A, B and C are
deliverable and D is not.
## Change log
- 2026-09-18: Strategy established. Delivery, design, technical scope and
interaction decided by the user; content inventory per report fixed.
- 2026-09-18: Report language decided by the user — German for both reports. The
agreed GPO verdict vocabulary and the security domain names stay canonical
English inside the German text; source values are never localized. The Client
Info English-only rule is unaffected.
- 2026-09-18: User correction — established English technical terminology stays
English as a general rule, not as an enumerated exception. German carries the
prose; technical terms are never translated or turned into German compounds.
- 2026-09-18: Priority model decided by the user after the user raised that not
every tool provides severity. Verified against stored data: CIS-CAT and GPO
have no severity at all, PingCastle cannot reach `critical` by construction.
Priority Findings are grouped by security domain with a per-source, declared
and visible ranking basis; no cross-tool ranking; GPO equal-weight in its own
block. Profiles must declare a priority basis. A normalized cross-tool
priority is explicitly not approved.
- 2026-09-18: Third artifact added on the user's proposal. The per-finding
working basis becomes its own deliverable — XLSX to work in plus an HTML status
view, curated through `include` with the complete deviation list on a second
sheet. Decisive reason is the differing lifecycle, not only the measured volume
of 1,806 deviations. Work items reference findings by identifier and never
repeat the technical depth of Report B.
- 2026-09-18: Progress model fixed by the user — progress is documented by
rescans, only technical proof counts. A fourth artifact is added: the Delta
Report, in a management and a technical cut, using the timeslot lifecycle
vocabulary. Self-reported status never becomes a reported result; planning
fields remain in the Arbeitsgrundlage marked as unverified. The earlier open
question about re-importing a maintained worklist is closed: the export is
one-way by design.
- 2026-09-18: Reference run decided by the user — a delta compares against a
chosen reference run, and the choice is made in ASSESSOR. The export renders a
stored comparison and carries no run-selection logic, no previous-run fallback
and no implicit pairing. Both runs are named in the document.
- 2026-09-18: Shared projection implemented as `src/domain/report-projection.ts`.
The security-domain assignment is injected by the caller so the domain layer
never points at the feature layer. Profiles do not yet declare a priority
basis, so the projection accepts one per source and falls back to `undeclared`
with its own diagnostic rather than guessing. Declaring the basis inside
`ToolProfileDefinition` is the follow-up step.
- 2026-09-18: Profile obligation implemented. `ToolPriorityBasisDefinition` is
required on every registered profile and enforced by a contract test. Purple
Knight and Maester declare native severity, PingCastle declares Points because
its severity is derived and capped, CIS-CAT and Generic declare unavailable
with a reason. CIS `Level` is recorded as an excluded property so it cannot be
promoted into a criticality later.
- 2026-09-18: Management Overview renderer implemented as
`src/exports/management-overview-report.ts`. Static, self-contained German
HTML with no script, inline handler, external URL or `url()`. Deviation from
the `assessor-ui` shell and page archetypes named above; all token, typography,
donut and status rules kept. An out-of-range score is clamped and a missing one
draws no arc at all.
- 2026-09-30: Report Engine boundary decided by the user. The report modules move
unchanged to `src/reporting/` and access is read-only. Every run is archived
immutably server-side, outside the repository and synced folders. Domain,
severity, review-state and scenario boundaries are confirmed, with scenarios
only from the Phase 13E context engine. Documentation only; no
implementation yet.
- 2026-09-30: Report modules moved unchanged to `src/reporting/`, with their
tests in `tests/reporting/`. `tests/reporting/reporting-boundary.test.ts`
enforces the importer allow-list and the absence of writing repository calls.
Output and numbers are unchanged.
- 2026-09-30: Report Engine v0.1 (product 2.10.0). A read-only port, a PostgreSQL
reader with read-only sessions and a revision check before and after the read
(repeat, otherwise refuse) ensure a consistent read. Every run is archived
write-once on the server. The Client Info buttons now download the
server-rendered, archived file, and the browser no longer renders reports.
Report content is unchanged.
- 2026-09-30: Report Engine v0.2. The user chose the statements Gesamtbild,
Schwerpunkt and Aussagegrenze, placed below the key figures, with evidence in the
Technical Report and the archive. Projection 1.2.0 adds ID lists per domain and
per coverage group plus the statements. The narrative module is 1.0.0, the
Management renderer 2.1.0 and the Technical renderer 1.1.0, whose result index
now shows the result ID.