plan.md
c128 plan: Reports
Changed 2026-11-05 (Mark): the three reports below gave way to intents PR 6's four, as living pages: Inventory, Findings, Compliance, Executive Summary. Findings is no longer a section of Posture, the Board no longer folds into the catalog, and exports are a PDF only. The window, R1–R6 and the chassis stand. See c123's
intents-pr5-plan.md, Phase 6.
Reader: people who aren't operators: security leads reporting upward, GRC and auditors, IT. They read and don't change anything. Problems:
problem:prove-posture-over-time(recurring: Moto, Tiled), the main oneproblem:fleet-scale-compliance-stateproblem:sensor-coverage-gapproblem:unknown-agent-fleet
Prior art:
- c78 built a report chassis and four report mocks:
/pages/report-*-v1,app/components/report/,report-fixtures.ts. - The data map is in
data-inventory.md.
The shape
Reports is a catalog and a reader. Every report has the same header, and the time window lives in that header.
Reports /reports
┌ Security ───────────────────────┐ ┌ IT ──────────────────────────────────┐
│ Posture │ │ Inventory Deployment status │
│ 78 ▲4 · 3 critical · 2 past SLA │ │ 212 endpoints · 9 91% covered ▼2 │
│ Open → │ │ new harnesses 14 shadow installs│
└──────────────────────────────────┘ └──────────────────────────────────────┘
Security · Posture [Export ▾] [Print]
┌ Window ────────────────────────────────────────────────────────────────┐
│ ( Last 30 days ▾ ) Oct 4 – Nov 2, 2026 · compared with Sep 4 – Oct 3 │
└────────────────────────────────────────────────────────────────────────┘
Verdict score 78, up 4 · spark over the window · start → end
Sections each one tagged: in window · over window · as of Nov 2
Footer generated <ts> · data through <fleet_anchor> · sources
The window picker opens a single panel:
┌──────────────────────────────────────────────────────────┐
│ Rolling Last 7 days · Last 30 days · Last 90 days │
│ Calendar This month · Last month · This quarter · │
│ Last quarter · Year to date │
│ Custom [ Oct 1, 2026 ] → [ Oct 31, 2026 ] │
│ ┌ Oct 2026 ┐ ┌ Nov 2026 ┐ two-month cal. │
│ Compare with ( Previous period · Same period last yr · │
│ None ) │
│ Data available Aug 5 – Nov 2 (90 days) ░░░ hatched beyond │
└──────────────────────────────────────────────────────────┘
The three reports
Security · Posture. Is our AI risk going down, and what is left?
The report answers that question with these sections:
- The posture score over the window, with its start, end and change.
- What lowers the score.
- Critical and at-risk harnesses.
- Findings opened, resolved, still open and past SLA, by severity.
- Data protection.
- Egress decisions.
- Policies in force, with mode changes in the window.
The framework evidence summary links to c123's framework pages and isn't rebuilt here.
IT · Inventory. What AI is in use, on which machines, by whom, and what's new?
The report answers that question with these sections:
- Endpoints, people and teams.
- Harnesses, models, providers and accounts, split into personal and enterprise.
- MCP servers, skills, tools and workspaces.
- New in window (first seen).
- Spend.
IT · Deployment status. Where is the sensor, and where isn't it?
The report answers that question with these sections:
- Coverage % over the window.
- Endpoints by monitoring state.
- Shadow installs.
- Users not covered.
- Stale endpoints.
- Harness version spread.
- Sensor version and health. qdash doesn't have this data yet, so it's drawn as staged.
The source and time basis for every section is in data-inventory.md.
Rules
- R1. One window per report, in the header. Every section reads it. A section that can't follow the window says what it shows instead.
- R2. Every section states its time basis.
- in window: counts and sums.
- over window: the window's days.
- as of <date>: state at the window's end.
- Until snapshots exist, as of in the past falls back to a trend series, or says it shows current state (c108).
- R3. Same numbers as the operator pages. Reports read the same
lib/*-rollups.tsthat the pages do (c78's derivation ethic), and every section links to its page with the window carried over. - R4. Read-only. No adjudicate, stage or mode controls. A report is something you can hand to someone.
- R5. The window is in the URL. For example
?window=last-quarter&compare=previousor?from=2026-10-01&to=2026-10-31, so a link reproduces the report exactly. - R6. The data's edges are visible.
- The footer shows "data through" (
fleet_anchor), and the picker shows how far back data goes. - A window that runs past the available history hatches the missing days (as
DataHistoryGraphalready does) rather than drawing zeros.
- The footer shows "data through" (
Decisions (ruled by Mark 2026-11-03)
All ten were ruled the same day. D1–D4 and D6–D10 went with the defaults below. D5 went the other way: the window is shared (see D5 ruling after the table).
| # | Question | Default |
|---|---|---|
| D1 | Where it's built first | A design-site mock in the phase-4 lab (/pages/phase4/mock/reports, in the lab shell, which already has a Reports rail entry), on the lab's shared world plus a daily history extension. It is ported to qdash /reports after review. |
| D2 | Kinds of window | Rolling (7, 30, 90 days), calendar (this or last month, this or last quarter, year to date) and custom from–to. Auditors ask "for Q3", which a rolling window can't answer. |
| D3 | Default window | Last 30 days compared with the previous 30. The global pill defaults to 7d, which is an operator rhythm; reports run monthly and quarterly. |
| D4 | Compare | Previous period by default. Same period last year is shown disabled until there's a year of history. |
| D5 | Window vs the global when: pill | |
| D6 | Scope filter | All of the org, with an optional Team scope. Other filters (harness, provider) stay on the operator pages. |
| D7 | Export in this cycle | Print / PDF via a print stylesheet, plus CSV per table and JSON per report. Scheduled delivery and retained snapshots are recorded as follow-ons (c78's snapshot shelf). |
| D8 | c78 | c78 closes into c128, as c66 did into c105. Its chassis, fixtures and open threads (card treatment, snapshot diff, layer extraction) carry over. Its Compliance, SLA and Board reports stay as reference, and Compliance stays with c123. |
| D9 | The window component | Built design-local, then extracted to the layer as one picker (working name UxTimeWindow) once the mock is ruled. The demo covers rolling, calendar, custom, compare, out-of-range and empty states. Because of D5, this picker becomes the panel behind the global when: pill (see below). |
| D10 | Timezone | The org's timezone, shown once in the window line. The seed backend's windows are UTC from fleet_anchor; ask Tyler. |
D5 ruling: one shared window
The window is now the product's single time control, not just the reports'. The consequences are below; Phase 1 and Phase 6 grow to cover them.
- The picker is the
when:pill's panel.- The layer's
Filtertimeframe pill (timeframeOptions) and qdash'suseGlobalFilter/PERIOD_OPTIONSlearn calendar and custom windows, plus compare. ?period=grows into a window encoding that every page reads, e.g.?period=last-quarter,?from=…&to=…,&compare=previous.
- The layer's
- Every operator page must take any window. The pages that ignore the window today (posture, compliance, policies, alerts, identity, all details except users and teams) must follow it or say they don't (R2). The backend's
period_spanneedsfrom/tofor every page, not only reports. This closes data-inventory gap 5 instead of working around it. - Compare spreads too. A delta on an operator page reads against the same compare basis, so
DataMachine's baredeltaneeds its basis said once, on the window line, not on each card. - The pill stays compact. On operator pages the pill shows its label ("Last 30 days"). On a report the same window shows in full in the header ("Oct 4 – Nov 2, 2026 · compared with Sep 4 – Oct 3").
- The c113 bake. A shared window multiplies the demo crawl, so the baked demo offers named windows only (see Patterns).
- The default: 30 days everywhere (Mark, 2026-11-03). The last 30 days, compared with the previous 30, is the default for reports and operator pages alike. In qdash,
DEFAULT_PERIODmoves from7dto30dat the port (Phase 6). The default stays out of the URL, so links that carry no period open on 30 days. Operator pages that show 7d counts today will show larger numbers by default after the port.
Clarifying questions: Mark's answers (2026-11-03)
- Who opens Reports? "Non-operator" is not a role. It's a word for C-level people and others who should see the data but shouldn't have access to the controls, which can do damage. So there's no role or permission design in this cycle, and R4 is stricter: a report holds no controls, and it makes sense on its own. Section links to operator pages are a convenience. A reader who can't open those pages loses nothing they need.
- Two categories or three reports? Findings/SLA is a section of Posture, not its own report.
- c78's Board report? "Possibly, we could fold it in." The answer to "anything useful there?" is in Board report: what folds in below.
- How far back? 3–12 months, once the backend is connected. The mock's history runs for 12 months, so the picker and the reports are designed for the full range. The 90-day limit is a qdash-port problem, handled by hatching days without data (R6), not a design limit.
Board report: what folds in
c78's Board report (/pages/report-board-v1, deriveBoardReport()) has three ideas worth keeping and one to drop.
- Keep: one sentence for the period. The page leads with a headline built from the data, e.g. "3 open high findings · 87% of the agent estate monitored". Each report gets one at its top, for the C-level reader who reads only that line.
- Keep: highlights, one per report, each linking into it. The Board report is a summary of the other reports. That is what the
/reportscatalog already is. The Board folds in as the catalog's top band:- the sentence for the window across all three reports
- three highlights (posture, coverage, new in inventory)
- all of it reading the catalog's window
- Keep: the trend in words. "Improving, 84 → 90 (May to Aug)." That line is accessible, and it prints well.
- Keep: the document face. It is a white sheet of paper in both themes, and it's the starting point for the print/PDF design in Phase 4.
- Drop: the composite score. It's the mean of compliance %, SLA % and coverage %, three different units averaged into a number that means nothing. It also breaks c123's rule of counts by state, no %.
Patterns you may not be considering
- Period-closed reports. A calendar period that has ended (October, Q3) can be generated once and kept. That gives the auditor a fixed artifact instead of a live query whose numbers drift as late data arrives. It is c78's snapshot shelf, keyed by period.
- Named periods bake; custom ranges don't. The c113 demo is a crawl of every URL the UI can request. Rolling and calendar windows form a finite set and can be baked; custom from–to can't. So the demo offers custom only where the backend is live, or snaps it to days already baked.
- The landing page summarises the reports (Tyler's c78 idea). The Reports catalog cards are c78's mini-docs. Later, the same cards can sit on Home.
- The window as a cursor, not a filter (c78 ruling). Moving the window slides the whole document along, and the verdict reads start → end.
- Coverage history already exists and goes unused.
/api/trends.coverageis a 90-day daily series; only Campaigns reads it. It's the backbone of Deployment status for free. - "New in window" is the inventory's most useful line.
first_seenis everywhere, so "9 harnesses, 3 MCP servers and 1 personal account appeared this month" costs nothing. - Auditor packet = report + evidence. Ruled (Mark, 2026-11-03): downloads and reports are handled in Reports. The findings JSON/CSV, the server-side evidence pack and c123's merged export (X4) all land in Phase 4. The Compliance page's "Forensic why-trail & evidence export" panel is already gone from both the lab and qdash
/compliance. The Policies evidence packet (c126 C5) comes out at the port. - Report links as share artifacts. A copy-link button that captures the window (R5) does a lot of the work of scheduled delivery before scheduling exists.
Phases
- Spec.
data-inventory.md(done).- Mark rules on D1–D10 and the four questions.
- For each report, a section list with its source, time basis and staged/live status. This is the contract Phase 3 builds against.
- Window component. Built 2026-11-03:
/pages/phase4/time-window. See CYCLE.md.- A design-local picker with rolling, calendar and custom presets, compare, data-range hatching and URL sync.
- A full demo page with the empty, minimal, typical, overflow and error scenarios: no history, a window past the data, a custom range across a year boundary, phone width.
- Through Illustrator if Mark wants a hand pass.
- Chassis and catalog. Built 2026-11-03:
/pages/phase4/mock/reportsand/reports/<kind>. See CYCLE.md.- Rework c78's
Report*header, verdict and export bar around the new window and the R2 basis tags. - Build the
/reportscatalog with the two categories and three cards. - Extend the lab world with daily history (posture, coverage, findings, spend) and
first_seendates.
- Rework c78's
- The three reports (Posture drawn 2026-11-03; Deployment status and Inventory next), reviewed one at a time with a preview, then the build, then Mark's review (the /todos rhythm): Posture first, then Deployment status, then Inventory.
- Export.
- A print stylesheet (white paper in both themes, as with c78's document face).
- CSV per table and JSON per report with a versioned schema (
qdash.report.<kind>.v1). - Check with c123 about folding in the evidence export.
- Layer.
- Extract the window picker, plus the basis tag and report header if they're stable, then
/document-componentsand/release-layer.
- Extract the window picker, plus the basis tag and report header if they're stable, then
- Backend asks + qdash port.
- Take the data-inventory gaps to Tyler:
from/to, history beyond 90 days, snapshots, sensor version and health. - Port to qdash
/reportsonfeat/new-pages. Sections whose backend isn't ready are marked staged throughlib/projected/*.
- Take the data-inventory gaps to Tyler:
- Close.
/document-cycle: captures, story, history.- Close c78 with it if D8 holds.