656 lines
34 KiB
Markdown
656 lines
34 KiB
Markdown
# Assessment output strategy
|
||
|
||
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.
|