Skip to content

Commit 9863a3c

Browse files
docs: mark Reports Phase A shipped (PRs #631-#637) (#638)
Reconciles the docs with the merged reports build-out. - BACKLOG.md: the Reports entry flips Planned -> Phase A shipped (P1->P2), summarizing A1-A4b and the production signing-key op note; remaining work scoped to Phases B-D (OSCAL/CSV, async+scheduling, other kinds). - reports_design.md: a STATUS banner on §11 records Phase A as shipped and the two in-flight plan adjustments (coverage shipped before the structural migration; A3/A4 split backend/frontend). - SESSION_LOG.md: a Phase A handoff entry (per-PR summary, the live verification result, Phases B-D next, and the dev-serve / ephemeral-key gotchas).
1 parent 9d9403d commit 9863a3c

3 files changed

Lines changed: 85 additions & 1 deletion

File tree

BACKLOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
|------|----------|--------|-------|
7979
| Per-user alert-type preferences | P2 | Partial | **Dispatch-on-fire is now LIVE**`notification.NewDispatchChannel` is registered with the alert router (`cmd/openwatch/main.go:326`) and `DispatchChannel.Send` (`internal/notification/delivery.go:204`) fans fired alerts to enabled Slack/email/webhook channels, filtered by the channel's `tag_filter.severity`. **Remaining:** per-USER alert-type preferences (which alert types each user wants), RBAC-gated — only channel-level severity tags exist today. The general `users.preferences` JSONB + `internal/userpref` + `/api/v1/users/me/preferences` (system-user-preferences, PR #611) is the natural home — extend the typed `UserPreferences` contract rather than a new table |
8080
| In-app notifications | P1 | Planned | Bell icon with unread count, drawer, mark-as-read. Sources: alerts, scan completions, exception approvals, system events. RBAC-filtered. WebSocket or SSE delivery (the existing SSE bus can carry it). **Coupled to Reports below** — async report "ready" is the bell's first concrete producer (see `reports_design.md` §7) |
81-
| Reports — full build-out (snapshot-with-faces) | P1 | Planned | Today `/reports` is one `executive` kind, JSON only, synchronous, no export/sign/schedule (Templates + Scheduled tabs are `ComingSoon`). Design doc: **`docs/engineering/reports_design.md`** — a report is one immutable signed point-in-time **snapshot** with multiple rendered **faces** (PDF/CSV/OSCAL/JSON/in-app); format follows audience × cardinality so the 1000-page PDF is structurally impossible (PDF bounded by construction; bulk evidence is CSV/OSCAL, async + content-addressed). Serves operator/CISO/auditor/GRC. All inputs already exist (fleetrollup, posture_snapshots, scan_results+OSCAL, exceptions, remediation transactions, queue, notification dispatch). **Phasing:** A) signed exec PDF + scope picker + coverage caveat + migrate `reports`→`report_snapshots`/`report_faces`; B) fleet OSCAL SAR + CSV async (the scale-correct bulk path); C) scheduled + emailed + bell "ready" signal + Exception Register/Remediation kinds; D) POA&M + Host Evidence Pack + Drift&Trend. Recommend starting Phase A |
81+
| Reports — full build-out (snapshot-with-faces) | P2 | **Phase A shipped** | Design doc: **`docs/engineering/reports_design.md`** — a report is one immutable signed point-in-time **snapshot** with multiple rendered **faces**; format follows audience × cardinality so the 1000-page PDF is structurally impossible. **Phase A complete (PRs #631-#637, 2026-06-21):** the `executive` kind is now scoped (group/framework, A1), coverage-honest (staleness caveat that respects scope, A2), content-addressed on the renamed `report_snapshots` + `report_faces` model (A3a), with a bounded pure-Go **PDF face** + `GET /reports/{id}/export` (A3b) and a frontend Download control (A3b-2), **Ed25519-signed** with offline verification via `GET /reports/signing-key` (A4a) and a frontend **Signed badge + Verify** action (A4b). Production op: set `[reports].signing_key_file` for a durable signing key (dev uses an ephemeral per-boot key). **Remaining (Phases B-D, separate initiative):** B) fleet OSCAL SAR + CSV evidence extract, async (the scale-correct bulk path for auditors/GRC); C) scheduled + emailed + the in-app bell "ready" signal + Exception Register / Remediation Activity kinds; D) POA&M + Host Evidence Pack + Drift&Trend. The other prototype report kinds + the Templates/Scheduled tabs land here |
8282
| Dashboard layout customization (drag/drop) | P2 | Planned | 3 tiers per spec AC-12: full (admins), limited (analysts), none (auditor). Preset structure ready, needs `@dnd-kit/core` + persistence |
8383
| Kensa Phase 5 OTA Updates | P3 | Not Started | OTA delivery of rule updates |
8484

SESSION_LOG.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,72 @@ and their provenance lives here + in the commit history.
66

77
---
88

9+
## 2026-06-21 — Opus 4.8 (1M context) — Reports Phase A: scoped, coverage-honest, signed reports (PRs #629#637)
10+
11+
**Done** — the full reports build-out Phase A, 9 PRs (2 design/plan + 7
12+
feature slices), all merged on `main` (`9d9403dc`), verified live in-browser.
13+
14+
`/reports` went from one all-hosts executive JSON kind to a real reports
15+
system. Design + plan: `docs/engineering/reports_design.md` (§0–§11). The
16+
thesis: a report is one immutable signed point-in-time **snapshot** with
17+
multiple rendered **faces**; format follows audience × cardinality so the
18+
1000-page PDF is structurally impossible.
19+
20+
- **#629/#630** — design doc + Phase A implementation plan.
21+
- **A1 (#631)** — scope by group/framework. `POST /reports:generate` takes
22+
`{group_id?, framework?}`; new `group.Service.ScopeGroup`; migration 0041
23+
`reports.scope` JSONB; derived `scope_label`. Frontend scope picker.
24+
`api-reports` v1.1.0.
25+
- **A2 (#632)** — coverage caveat. `coverage` block {hosts_total/fresh/stale/
26+
unreachable} from `host_rule_state.last_checked_at` (24h freshness) +
27+
`host_liveness`; frontend `CoverageCaveat` renders only when stale/unreach,
28+
respects scope. `api-reports` v1.2.0. *(Shipped before the structural
29+
migration — coverage had user value, the migration didn't yet.)*
30+
- **A3a (#633)** — snapshot/faces model. Migration 0042 renames
31+
`reports``report_snapshots`, adds `content_sha256` (content addressing) +
32+
nullable `signature`/`signing_key_id`, creates `report_faces`.
33+
`api-reports` v1.3.0.
34+
- **A3b (#634)** — bounded pure-Go PDF face (`go-pdf/fpdf`, allowlisted;
35+
supply-chain spec bump) + `GET /reports/{id}/export?format=pdf|json`,
36+
cached in `report_faces`. `api-reports` v1.4.0.
37+
- **A3b-2 (#635)** — frontend Download PDF/JSON controls (cookie-auth GET +
38+
blob). `frontend-reports` v1.3.0.
39+
- **A4a (#636)** — Ed25519 signing over the content address (domain
40+
separated); `[reports].signing_key_file` config (ephemeral dev key when
41+
unset); `GET /reports/signing-key` for offline verification; canonical
42+
JSON face (sha256 == content_sha256). `api-reports` v1.5.0. stdlib crypto,
43+
no new dep.
44+
- **A4b (#637)** — frontend Signed badge + offline Verify (re-hash the JSON
45+
face + Web-Crypto Ed25519-verify against the published key, with graceful
46+
degradation). `frontend-reports` v1.4.0.
47+
48+
**Verified live**: rebuilt + restarted `serve` to main; generated a signed
49+
report; Verify returned "content matches and the signature is valid
50+
(development key…). Key ed25519-c0ef4a73d0284720." Scoped + coverage demos
51+
also confirmed (created an auto "RHEL hosts" group; RHEL scope = 5 hosts/69%
52+
with no caveat since the stale/unreachable hosts are non-RHEL).
53+
54+
**Next** — Phases B–D are a separate initiative (see `reports_design.md`
55+
§8): B) fleet OSCAL SAR + CSV evidence extract, async (the scale-correct
56+
bulk path; resolve the OSCAL-version + fleet-shape decisions in §10); C)
57+
scheduled + emailed + the in-app notification bell "ready" signal (gives the
58+
P1 bell its first producer) + Exception Register / Remediation Activity
59+
kinds; D) POA&M + Host Evidence Pack + Drift&Trend. The other prototype
60+
report kinds + the Templates/Scheduled tabs land in B–C.
61+
62+
**Gotchas / notes**:
63+
- Dev `serve` signs with an **ephemeral per-boot key** — signatures don't
64+
verify across restarts (the UI says so). Production: set
65+
`[reports].signing_key_file` to a durable 32-byte Ed25519 seed.
66+
- The dev backend is **manually launched** (not systemd); rebuild = `go build
67+
-o dist/openwatch ./cmd/openwatch` then SIGTERM the pid + relaunch with the
68+
captured `/proc/<pid>/environ` (DSN never printed). `serve` does NOT
69+
auto-migrate — run `dist/openwatch migrate` separately. Graceful shutdown
70+
is slow (~15s); the new process binds the freed port before the old fully
71+
exits.
72+
- "Generated by" still shows the raw user UUID (the actor-label backlog gap),
73+
unchanged by this work.
74+
975
## 2026-06-20 — Opus 4.8 (1M context) — Scan detail host label (PR #613)
1076

1177
**Done** (PR #613 `f07e21fc` on `fix/scan-detail-host-label`, gate green,

docs/engineering/reports_design.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,24 @@ feel instant.** Build them aware of each other.
387387

388388
## 11. Phase A — resolved decisions & implementation plan
389389

390+
> **STATUS: Phase A shipped (2026-06-21, PRs #631#637).** The executive
391+
> report is now scoped (group/framework, A1), coverage-honest (A2),
392+
> content-addressed on the `report_snapshots` + `report_faces` model
393+
> (A3a), with a bounded pure-Go PDF face + export endpoint (A3b) and a
394+
> frontend Download control (A3b-2), Ed25519-signed with offline
395+
> verification (A4a) and a frontend Signed badge + Verify action (A4b).
396+
> Two adjustments to the plan below, made during implementation and noted
397+
> here: (a) the **coverage caveat shipped before the snapshot/faces
398+
> migration** — the migration had no user value until a second face
399+
> existed, so A2 delivered coverage and the structural migration moved to
400+
> **A3a**; (b) A3 and A4 were each split backend/frontend (A3b/A3b-2,
401+
> A4a/A4b) to isolate the fpdf dependency (A3b) and the cookie-auth blob
402+
> download / client-side Web-Crypto verification (frontend slices). The
403+
> §10 signing-key decision resolved as a config-path key
404+
> (`[reports].signing_key_file`) with an ephemeral per-boot dev key.
405+
> **Remaining: Phases B–D** (OSCAL/CSV faces, async + scheduling, the
406+
> other report kinds) — a separate initiative.
407+
390408
Phase A makes the **executive** report real for humans without taking on
391409
the bulk/OSCAL machinery. The §10 decisions are resolved for Phase A as
392410
follows so that *none of them blocks the start*:

0 commit comments

Comments
 (0)