Feat: AI Posture card + category-grouped risks + one-click authorize#5
Merged
giggsoinc merged 1 commit intoMay 14, 2026
Conversation
Shifts the dashboard from "events log" UX to "decision surface" UX.
Five mitigations (M1-M5) in one branch, closing the noise loop end-to-end:
M1. One-click authorize from dashboard (server-side + agent prep)
- NEW src/services/authorize.py — writes per-user authorized
provider list to s3://<bucket>/config/authorized/{email_safe}.json.
Idempotent merge; per-user isolated; revoke supported.
- NEW agent/install/scan_authorize_fetch.py.frag — at scan start,
fetches the per-user list via a presigned GET URL (configured in
~/.patronai/config.json) and merges into AUTH_LIST. Findings whose
provider is on the list are filtered at the agent and NEVER reach
the dashboard. Best-effort (5s timeout, never blocks a scan).
- manager_tab_actions.py v2.1.0 — new authorize_for_user() helper
called from category bulk-button.
- Once a tool is authorized, server-side findings_compact (from
PR #4) auto-resolves the open finding within stale-window cycles.
M2. AI Posture card — single aggregated headline
- NEW src/scoring/risk_score.py — weighted score 0-100 over
compacted findings. Per-severity base × per-category multiplier
× log-dampened occurrences factor, capped at 100.
Bands: CLEAN | LOW | MEDIUM | HIGH | CRITICAL.
Tuned so ONE critical process alone = 75 (CRITICAL band).
- NEW dashboard/ui/ai_posture_card.py — renders the score, band
colour, and per-category breakdown ("4 unauthorized AI tools
running → max sev HIGH"). Replaces the numeric-KPI noise as the
headline of the Inventory tab.
- manager_tab_inventory.py — calls render_ai_posture() at top.
M3. Category-grouped Risks view
- NEW dashboard/ui/category_grouped_risks.py — collapsible parent
row per category (process / mcp_server / vector_db / ...) with
count + max-severity + last-seen. Expand to see per-signature
children with first_seen / last_seen / occurrences / cleanup hint.
- manager_tab_risks.py — toggle "Grouped view (recommended)"
defaults ON. Flat alert table is one toggle-flick away — legacy
muscle memory preserved.
M4. Bulk actions per category
- Inside each expanded category: single button
"✓ Authorize all N <category> provider(s) for ravi@giggso.com"
→ fires authorize_for_user() → writes to S3 → success toast.
Next scan sees the merged AUTH_LIST and stops emitting. Compact
job auto-resolves within hours.
M5. On-device cleanup hints (warn, never execute)
- NEW src/cleanup_hints.py — per-(category, os) human-readable
cleanup suggestion. Examples:
process / darwin → "Quit the app + remove from /Applications/.
System Settings → Login Items."
mcp_server / darwin → "Edit ~/Library/Application Support/
Claude/claude_desktop_config.json — remove
the entry under `mcpServers`. Restart."
vector_db / * → "Locate via `path_safe` field and rm -rf."
- Rendered inline beside each signature in the grouped view.
- Server NEVER executes — deliberate security boundary preserved.
- Parametrised test asserts EVERY known category has a default hint
→ new agent categories forced to add a hint on introduction.
Tests added: 37 across 3 files (all under 100 LOC each):
- test_risk_score.py — 11 tests: empty/clean, resolved-skipped,
single-critical-is-red, cap-at-100, category multiplier, occurrence
dampening, band thresholds, posture_breakdown grouping.
- test_authorize_service.py — 10 tests: safe-email, per-user isolation,
idempotency, merge, revoke, garbage-input tolerance, legacy-shape
canonicalisation.
- test_cleanup_hints.py — 16 tests including parametrised coverage
of every supported category + OS-specific hint paths.
Suite: 439 passed (was 402 on PR #4 baseline) — net +37, no regressions.
Stacks on top of fix/dashboard-noise-drama-mode (PR #4) — merge order:
PR #4 → this PR. The finding_signature + compact view from #4 are
what these aggregations consume; merging this one first wouldn't break
but would render the posture card on raw events instead of compacted
ones (degrades gracefully).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacks on top of #4 — base branch is `fix/dashboard-noise-drama-mode`. Merge #4 first, then this.
TL;DR
Customer asked: "The dashboard is very heavy on numbers — I have several tools running, I don't want noise, I want aggregated warnings and an endpoint that cleans itself up."
This PR shifts the dashboard from events log UX → decision surface UX. Five mitigations in one branch, end-to-end:
439 tests pass (+37 net). Zero regressions.
What you'll see in the dashboard
Inventory tab — old vs new
Before:
```
TOTAL ASSETS DEVICES CLOUD HOSTS WITH AI EVENTS
1 1 0 1
1020 0 1020
```
After (PR #4 + this PR):
```
┌─ AI POSTURE — MacBook-Pro-154 ──────────────────── RISK SCORE: 62/100 · HIGH ─┐
│ ● 4 AI processes running max sev HIGH │
│ ● 7 Local vector DBs max sev MEDIUM │
│ ● 3 MCP servers configured max sev HIGH │
│ ● 12 IDE plugins detected max sev MEDIUM │
└─────────────────────────────────────────────────────────────────────────────────┘
```
Risks tab — old vs new
Before: 50 identical-looking HIGH rows for the same scan blob.
After (toggle ON by default):
```
▸ Process — 4 signature(s) · max sev HIGH · last seen 2026-05-11T08:02:21
▸ Vector DB — 7 signature(s) · max sev MEDIUM · last seen 2026-05-11T08:02:21
▸ MCP Server — 3 signature(s) · max sev HIGH · last seen 2026-05-11T08:02:21
▸ IDE Plugin — 12 signature(s) · max sev MEDIUM · last seen 2026-05-11T08:02:21
```
Expand any → see per-signature rows with first_seen / last_seen / occurrences + a `💡 cleanup hint` line + a single `[✓ Authorize all N providers for ravi@giggso.com]` button.
Toggle to legacy view
Flat alert table still there — flip the `Grouped view (recommended)` toggle OFF. Muscle memory preserved.
How the authorize loop closes the noise at source
One operator click → ~12 hours later the noise is gone, permanently, for that user. Other users on the same tenant unaffected.
Why M1's agent fetch is "prep", not "live"
The fetcher fragment is shipped — needs one wiring step in `setup_agent.sh.template` + URL-refresh extension to mint the presigned GET URL alongside the existing upload URL. Out of scope for this PR to keep it focused; tracked as a Roadmap item. The server-side authorize endpoint is fully live today — operators can start building the allow-list immediately; it just doesn't reach the in-flight agent until the URL-mint follow-up lands.
File list
Reviewer notes
🤖 Generated with Claude Code