From 3aeb3a9cc4c20d0b156c6c881f4a50b72dea33de Mon Sep 17 00:00:00 2001 From: Remylus Losius Date: Sun, 21 Jun 2026 18:05:48 -0400 Subject: [PATCH] docs: mark Reports Phase A shipped (PRs #631-#637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- BACKLOG.md | 2 +- SESSION_LOG.md | 66 ++++++++++++++++++++++++++++++ docs/engineering/reports_design.md | 18 ++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/BACKLOG.md b/BACKLOG.md index ba4e1636..c3e76906 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -78,7 +78,7 @@ |------|----------|--------|-------| | 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 | | 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) | -| 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 | +| 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 | | 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 | | Kensa Phase 5 OTA Updates | P3 | Not Started | OTA delivery of rule updates | diff --git a/SESSION_LOG.md b/SESSION_LOG.md index 62234710..1871cad7 100644 --- a/SESSION_LOG.md +++ b/SESSION_LOG.md @@ -6,6 +6,72 @@ and their provenance lives here + in the commit history. --- +## 2026-06-21 — Opus 4.8 (1M context) — Reports Phase A: scoped, coverage-honest, signed reports (PRs #629–#637) + +**Done** — the full reports build-out Phase A, 9 PRs (2 design/plan + 7 +feature slices), all merged on `main` (`9d9403dc`), verified live in-browser. + +`/reports` went from one all-hosts executive JSON kind to a real reports +system. Design + plan: `docs/engineering/reports_design.md` (§0–§11). The +thesis: 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. + +- **#629/#630** — design doc + Phase A implementation plan. +- **A1 (#631)** — scope by group/framework. `POST /reports:generate` takes + `{group_id?, framework?}`; new `group.Service.ScopeGroup`; migration 0041 + `reports.scope` JSONB; derived `scope_label`. Frontend scope picker. + `api-reports` v1.1.0. +- **A2 (#632)** — coverage caveat. `coverage` block {hosts_total/fresh/stale/ + unreachable} from `host_rule_state.last_checked_at` (24h freshness) + + `host_liveness`; frontend `CoverageCaveat` renders only when stale/unreach, + respects scope. `api-reports` v1.2.0. *(Shipped before the structural + migration — coverage had user value, the migration didn't yet.)* +- **A3a (#633)** — snapshot/faces model. Migration 0042 renames + `reports`→`report_snapshots`, adds `content_sha256` (content addressing) + + nullable `signature`/`signing_key_id`, creates `report_faces`. + `api-reports` v1.3.0. +- **A3b (#634)** — bounded pure-Go PDF face (`go-pdf/fpdf`, allowlisted; + supply-chain spec bump) + `GET /reports/{id}/export?format=pdf|json`, + cached in `report_faces`. `api-reports` v1.4.0. +- **A3b-2 (#635)** — frontend Download PDF/JSON controls (cookie-auth GET + + blob). `frontend-reports` v1.3.0. +- **A4a (#636)** — Ed25519 signing over the content address (domain + separated); `[reports].signing_key_file` config (ephemeral dev key when + unset); `GET /reports/signing-key` for offline verification; canonical + JSON face (sha256 == content_sha256). `api-reports` v1.5.0. stdlib crypto, + no new dep. +- **A4b (#637)** — frontend Signed badge + offline Verify (re-hash the JSON + face + Web-Crypto Ed25519-verify against the published key, with graceful + degradation). `frontend-reports` v1.4.0. + +**Verified live**: rebuilt + restarted `serve` to main; generated a signed +report; Verify returned "content matches and the signature is valid +(development key…). Key ed25519-c0ef4a73d0284720." Scoped + coverage demos +also confirmed (created an auto "RHEL hosts" group; RHEL scope = 5 hosts/69% +with no caveat since the stale/unreachable hosts are non-RHEL). + +**Next** — Phases B–D are a separate initiative (see `reports_design.md` +§8): B) fleet OSCAL SAR + CSV evidence extract, async (the scale-correct +bulk path; resolve the OSCAL-version + fleet-shape decisions in §10); C) +scheduled + emailed + the in-app notification bell "ready" signal (gives the +P1 bell its first producer) + Exception Register / Remediation Activity +kinds; D) POA&M + Host Evidence Pack + Drift&Trend. The other prototype +report kinds + the Templates/Scheduled tabs land in B–C. + +**Gotchas / notes**: +- Dev `serve` signs with an **ephemeral per-boot key** — signatures don't + verify across restarts (the UI says so). Production: set + `[reports].signing_key_file` to a durable 32-byte Ed25519 seed. +- The dev backend is **manually launched** (not systemd); rebuild = `go build + -o dist/openwatch ./cmd/openwatch` then SIGTERM the pid + relaunch with the + captured `/proc//environ` (DSN never printed). `serve` does NOT + auto-migrate — run `dist/openwatch migrate` separately. Graceful shutdown + is slow (~15s); the new process binds the freed port before the old fully + exits. +- "Generated by" still shows the raw user UUID (the actor-label backlog gap), + unchanged by this work. + ## 2026-06-20 — Opus 4.8 (1M context) — Scan detail host label (PR #613) **Done** (PR #613 `f07e21fc` on `fix/scan-detail-host-label`, gate green, diff --git a/docs/engineering/reports_design.md b/docs/engineering/reports_design.md index c017f129..02eae30c 100644 --- a/docs/engineering/reports_design.md +++ b/docs/engineering/reports_design.md @@ -387,6 +387,24 @@ feel instant.** Build them aware of each other. ## 11. Phase A — resolved decisions & implementation plan +> **STATUS: Phase A shipped (2026-06-21, PRs #631–#637).** The executive +> report is now scoped (group/framework, A1), coverage-honest (A2), +> content-addressed on the `report_snapshots` + `report_faces` model +> (A3a), with a bounded pure-Go PDF face + export endpoint (A3b) and a +> frontend Download control (A3b-2), Ed25519-signed with offline +> verification (A4a) and a frontend Signed badge + Verify action (A4b). +> Two adjustments to the plan below, made during implementation and noted +> here: (a) the **coverage caveat shipped before the snapshot/faces +> migration** — the migration had no user value until a second face +> existed, so A2 delivered coverage and the structural migration moved to +> **A3a**; (b) A3 and A4 were each split backend/frontend (A3b/A3b-2, +> A4a/A4b) to isolate the fpdf dependency (A3b) and the cookie-auth blob +> download / client-side Web-Crypto verification (frontend slices). The +> §10 signing-key decision resolved as a config-path key +> (`[reports].signing_key_file`) with an ephemeral per-boot dev key. +> **Remaining: Phases B–D** (OSCAL/CSV faces, async + scheduling, the +> other report kinds) — a separate initiative. + Phase A makes the **executive** report real for humans without taking on the bulk/OSCAL machinery. The §10 decisions are resolved for Phase A as follows so that *none of them blocks the start*: