Background
#162 + #171 give us the dashboard + security pane. Neither answers the question:
"What got installed into project X, when, by what command, with what verdict?"
The supply-chain JSONL has the raw data but no project context and no fast project-scoped query path.
Proposed scope
Schema
New `installs` table in `history.db`:
| Column |
Type |
Notes |
| id |
INTEGER |
PK |
| ts |
TEXT |
RFC-3339 |
| project_path |
TEXT |
CWD at install time (same key as `commands.project_path`) |
| ecosystem |
TEXT |
npm / PyPI / cargo / gem / etc. |
| package |
TEXT |
requested package name |
| version_spec |
TEXT |
what the user asked for (e.g. `^4.18.0`) |
| resolved_version |
TEXT |
what actually got installed (nullable) |
| verdict |
TEXT |
allow / block / skip |
| finding_ids |
TEXT |
CSV of GHSA / PYSEC IDs that fired |
| raw_command |
TEXT |
full original command for forensics |
Populator
The existing supply-chain gate hook (`supply_chain_gate.rs::log_event`) writes JSONL today. Extend it to also INSERT into `installs` so both surfaces stay in sync. JSONL remains the immutable audit log; SQL is the query surface.
Backfill
One-shot ETL: read existing `supply_chain.jsonl` (~20MB) and populate `installs` for historical rows. Idempotent (skip rows by (ts, raw_command) tuple).
Dashboard
- `GET /api/installs?project=&limit=N` — per-project install history
- New "Installs" pane with project filter dropdown (populated from distinct `commands.project_path` + `installs.project_path`)
- Card: total installs per project, blocked install rate per project
- Table: ts, package, ecosystem, version, verdict, finding IDs
Why now
Closes the traceability gap: "trackback what was installed by what and when" per the Hoff (2026-05-25).
Effort
M — 1 week.
- Schema + migration: half day
- Populator wire-up: half day
- ETL backfill: 1 day
- Endpoint + tests: 1 day
- UI pane: 2 days
- Peer review pair + doc: 1 day
Dependencies
Depends on #162 (dashboard surface) and #171 (security pane — sibling).
Background
#162 + #171 give us the dashboard + security pane. Neither answers the question:
The supply-chain JSONL has the raw data but no project context and no fast project-scoped query path.
Proposed scope
Schema
New `installs` table in `history.db`:
Populator
The existing supply-chain gate hook (`supply_chain_gate.rs::log_event`) writes JSONL today. Extend it to also INSERT into `installs` so both surfaces stay in sync. JSONL remains the immutable audit log; SQL is the query surface.
Backfill
One-shot ETL: read existing `supply_chain.jsonl` (~20MB) and populate `installs` for historical rows. Idempotent (skip rows by (ts, raw_command) tuple).
Dashboard
Why now
Closes the traceability gap: "trackback what was installed by what and when" per the Hoff (2026-05-25).
Effort
M — 1 week.
Dependencies
Depends on #162 (dashboard surface) and #171 (security pane — sibling).