Skip to content

Feat: AI Posture card + category-grouped risks + one-click authorize#5

Merged
giggsoinc merged 1 commit into
fix/dashboard-noise-drama-modefrom
feat/dashboard-posture-and-authorize
May 14, 2026
Merged

Feat: AI Posture card + category-grouped risks + one-click authorize#5
giggsoinc merged 1 commit into
fix/dashboard-noise-drama-modefrom
feat/dashboard-posture-and-authorize

Conversation

@giggsoinc
Copy link
Copy Markdown
Owner

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:

# Mitigation What you'll see
M1 One-click authorize writes per-user list to S3 → agent fetches at next scan → tool disappears from emission → server compactor auto-resolves the open finding "✓ Authorize all 4 vector_db providers for ravi@giggso.com" button on every category
M2 AI Posture card replaces the numeric KPI row One risk score 0-100 + band colour + per-category breakdown
M3 Category-grouped Risks view 4 collapsible parent rows instead of 50 individual alerts
M4 Bulk authorize per category Single button per group, idempotent merge
M5 On-device cleanup hints Copy-paste shell commands per category × OS (server never executes)

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

  1. Operator clicks [✓ Authorize all 7 vector_db providers]
  2. `authorize_for_user` writes `s3:///config/authorized/ravi_giggso.com.json` with the 7 provider strings merged into the existing list.
  3. Agent's next scan (within 30 min): `scan_authorize_fetch.py.frag` pulls the JSON via the presigned GET URL, merges into `AUTH_LIST`. Every `scan_*()` emitter filters via `_is_authorized()` — those 7 providers are NEVER emitted.
  4. Server's next `findings_compact` cycle (5 min): since the signatures stop reappearing, after 24 cycles (12 h) the open findings auto-resolve with `resolved_by=auto`.
  5. Dashboard re-renders. Posture score drops. Category card disappears.

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

File Type Purpose
`src/scoring/risk_score.py` NEW Weighted 0-100 score + band + breakdown
`src/services/authorize.py` NEW Per-user S3 allow-list CRUD
`src/cleanup_hints.py` NEW Per-(category, OS) cleanup suggestions
`dashboard/ui/ai_posture_card.py` NEW Aggregated headline card
`dashboard/ui/category_grouped_risks.py` NEW Collapsible category view + bulk actions
`dashboard/ui/manager_tab_inventory.py` MOD Wires posture card
`dashboard/ui/manager_tab_risks.py` MOD Wires grouped view + toggle
`dashboard/ui/manager_tab_actions.py` MOD v2.1 Adds `authorize_for_user()`
`agent/install/scan_authorize_fetch.py.frag` NEW Agent S3 fetcher (drop-in)
`tests/unit/test_risk_score.py` NEW 11 tests
`tests/unit/test_authorize_service.py` NEW 10 tests
`tests/unit/test_cleanup_hints.py` NEW 16 tests (parametrised)

Reviewer notes

🤖 Generated with Claude Code

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>
@giggsoinc giggsoinc merged commit 597b87c into fix/dashboard-noise-drama-mode May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants