fable-audit/README.md
The Fable `/live` gets main's Audit log
2026-11-12. Done in ../qcontrol/crates/qdash/ui on feat/new-pages, the Fable app behind PR qpoint-io/qcontrol#713. It is committed as d274b9306 (25 files, all under app/**/live and pages/live.vue) and pushed to feat/new-pages on 2026-11-13, so PR qpoint-io/qcontrol#713 carries it. Asked for in live-parity-plan.md under "Out of this plan": both sides of #713 changed the live files, so a merge with main would drop the Audit log unless it was carried across first.
The source. Main @ 8677b44ed. The Audit log arrived in one PR, #757. Later PRs (#765, #772, #787 and #835) changed only audit-events.ts.
What was carried
| Main | Fable home | How |
|---|---|---|
lib/live/audit-events.ts + test (46 cases) | same path | whole, with a provenance line |
composables/useLiveEvents.ts + test: two buffers, one entity index | composables/live/useLiveEvents.ts | whole: the Fable copy was unchanged from the merge-base |
lib/live/retention-settings.ts + test: per-view keys qdash.live.retention.v1 / qdash.audit.retention.v1 | same path | whole |
events.ts pluginPreview: reads data, falls back to payload | same path | the one hunk, plus main's three plugin-data specs |
EventTable.vue audit variant | EventTable.vue, fed by lib/live/event-rows.ts liveAuditRow | main read auditField / auditSummary / auditMetadata / pidForEvent in the template; here the row carries audit: { activity, summary, metadata, decision, gap } and pid (bible R8) |
EventDetail.vue categoriesOnly | lib/live/event-context.ts detailContextParts(…, { categoriesOnly }) | the Type chip is evidence, not a filter |
FilterPopover.vue categoriesOnly, right-anchored panel | same | as main |
RetentionControls.vue "Audit buffer" / "Event buffer" | same, fed by composables/live/retention.ts useLiveRetention(isAudit) | keyed by view, as main |
live.vue: third tab, note, placeholder, tableEvents, tab-switch reset | WorkspaceTabs, FilterBar, eventList.ts useLiveEventList(isAudit) (clearKinds on switch), selection.ts (tableEvents), live.vue (the note, close and jump on switch) | page script 47 lines (R9 guide is 60) |
live.vue banner: "N new audit entries", "Viewing older entries", "Jump to latest"; selecting no longer freezes the tail | EventStream.vue | as main |
live.vue .live-details fixed overlay below xl; EventDetail resize handle xl-only | SplitPane.vue (a wrapper that is display: contents at xl) | as main |
Left out. Main's mobile page height (100dvh-5.5rem) and its .live-header wrap rules. They fit main's mobile shell, and the Fable default.vue has no md: breakpoint. auto_tap also stays: main removed it in #743, before the merge-base, and the Fable app re-gained it from the gen-2 UI. That removal is a separate cleanup.
Vocabulary. The Fable app's c92 word "Harness" is kept for the column, the rail and the chips. Main says "Agent".
Checks
npm testincrates/qdash/ui: 75 files, 658 / 658, andbible-check: clean.- No
vue-tscis installed in the tree, so no typecheck ran. - Browser checks on
:3030with sentinel-audit.jsonl. The fixture was made by make-sentinel-fixture.mjs: 26 events, 16 Sentinel rows in the screenshot's mix (http 7 · llm 6 · mcp 2 · file 1), from claude-cli pid 58638, with one inference denied by "Secret in prompt". What the browser showed:- The tabs read Events 26 · Tally 26 · Audit log 16.
- Searching
activity=Inferencereproduces Mark's screenshot: four rows, one denied, with "Rules: Secret in prompt". - The Harness column hides once the detail narrows the table below 640 px.
- The Type chip is disabled on audit rows.
- Switching tabs closes the detail, clears the family filter and keeps the search.
- The Events tab now previews Sentinel's
datafields (model, method, authority, path). - Live mode shows "Event buffer" on Events and "Audit buffer" on the Audit log. The audit limit saves only under
qdash.audit.retention.v1, and Reset clears it. - At 1100 px the detail opens as an overlay.
Captures
All captures are 1440 × 900 scaled to 800 px, except the overlay, which is 1100 px.
- before-events-denied-inference.jpg: before the change, the denied inference on the Events tab. The preview shows
pluginandeventonly. - after-audit-log.jpg: the Audit log tab.
- after-audit-inference-search.jpg:
activity=Inference, the screenshot's view. - after-audit-denied-detail.jpg: the denied row's detail. The Harness column is hidden in the narrow table.
- after-audit-narrow-detail-overlay.jpg: the detail as an overlay at 1100 px.
Files in qcontrol (mine, all under crates/qdash/ui/app)
- New:
lib/live/audit-events.ts,lib/live/audit-events.test.ts. - Modified:
components/live/:EventDetail,EventStream,EventTable,FilterBar,FilterPopover,RetentionControls,SplitPane,WorkspaceTabs;composables/live/:eventList,retention,selection,stream,useLiveEvents(+ test);lib/live/:event-context(+ test),event-rows(+ test),events(+ test),retention-settings(+ test);pages/live.vue.
Other sessions' uncommitted work in the same tree is not mine and is left alone: crates/qdash/README.md, crates/qdash/ui/README.md, pages/compliance.vue, devenv.nix, crates/qdash/docs/how-it-works.md.