Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
66 changes: 66 additions & 0 deletions SESSION_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<pid>/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,
Expand Down
18 changes: 18 additions & 0 deletions docs/engineering/reports_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*:
Expand Down
Loading