Files
hiveops-guide/backend/src/main/resources/content/aria/dashboard/architect.md
T
johannes f21c783d8c fix(guide): gate Architect + Claude tabs to BCOS_ADMIN only (#16)
The 162 Architect/Claude docs were tagged 'audience: dev'. The 'dev' tier
resolves to isInternal() = ROLE_MSP_ADMIN || ROLE_BCOS_ADMIN, so every
MSP_ADMIN could read full internal architecture: source paths, DB tables,
service ports, NGINX routes. Verified live on bcos.dev as msp_a@msp1.bcos.dev.

Retag them to 'audience: bcos' (isBcos, BCOS_ADMIN only). Content-only change;
the 'dev' tier mapping is deliberately left alone so the 15 Internal, 35
Overview and 66 Testing docs stay MSP-visible.

Also add platform.guide-access documenting the audience tiers and the
per-role visibility matrix, so the access model is queryable from the Guide
instead of re-derived from source each time.

Verified: MSP module count unchanged (116 across 17 apps); no module loses
all its tabs. Guide is bcos.dev only, not deployed to production.

Closes #16

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:55:07 -04:00

5.2 KiB
Raw Blame History

module, title, tab, order, audience
module title tab order audience
aria.dashboard Threat Dashboard Architect 30 bcos

How the ARIA Threat Dashboard is built. The dashboard is a thin read-only aggregation view — it does not own data, run detection, or consume Kafka. It renders one snapshot from hiveops-aria's tables. All the interesting machinery (ingestion, IOC matching, sequence detection) runs upstream and only populates the tables this view reads.

Services involved

  • hiveops-aria (Spring Boot, spring.application.name=hiveops-aria, port 8095, DB hiveiq_aria) — owns everything on this screen. Serves the stats, owns all four tables. See [platform.service-ownership].
  • hiveops-auth — only for the header's GET /auth/api/users/me name/role lookup; not part of the stats path.
  • No other service participates in the dashboard read path. (Ingestion into the tables comes from agents via the ARIA ingest endpoints; auto-response — disabled by default — would reach out to hiveops-incident.)

Data flow for THIS feature

Browser (Dashboard.svelte, onMount)
  → GET /api/aria/stats            [JWT Bearer, ROLE_BCOS_ADMIN]
    → ThreatEventController.getStats()
       ├─ eventRepository.count()                                   → threat_event
       ├─ eventRepository.countBySeverityAndDetectedAtAfter(CRIT,-24h) → threat_event
       ├─ eventRepository.countBySeverityAndDetectedAtAfter(HIGH,-24h) → threat_event
       ├─ iocRepository.findByActiveTrue().size()                  → threat_ioc
       ├─ sequenceAlertRepository.countByResolvedAtIsNull()        → precursor_sequence_alert
       ├─ postureRepository.countByPostureScoreLessThan(50)        → device_security_posture
       └─ postureRepository.countByOsEolStatus(EOL)                → device_security_posture
    ← Map<String,Object> of 7 counts
  → renders 8 cards (Non-Critical Events computed client-side)

Clicking a card is pure client-side navigation (Svelte createEventDispatcherApp.svelte handleDashboardNav) to the Events / Sequences / IOC / Posture views. No stats-specific endpoint backs the click.

Tables read (never written by this view)

Table Read for Written by (upstream)
threat_event Total / Critical / High counts event ingestion service (POST /api/aria/ingest/events)
threat_ioc Active IOC count Flyway seed (FBI FLASH) + IOC feed refresh + manual CRUD
precursor_sequence_alert Active (unresolved) sequence count sequence-detection service
device_security_posture Low-posture + EOL device counts posture report ingestion (POST /api/aria/posture/report)

The dashboard issues zero writes. See [platform.data-architecture].

Kafka role (indirect only)

The dashboard has no Kafka consumer and no producer — it is a synchronous DB read. Kafka appears one hop upstream, populating the tables it later reads:

Topic Direction (in aria) Producer Note
hiveops.aria.threats produced AriaThreatProducer from ThreatEventIngestionService fan-out of ingested threat events; not consumed within aria
hiveops.aria.sequences produced AriaSequenceProducer from SequenceDetectionService fan-out when a precursor sequence trips

There are no @KafkaListeners in hiveops-aria. This means the executor→Kafka→record-store pattern used elsewhere on the platform does not drive this dashboard: writes land directly in Postgres via the ingest/detection services, and the dashboard reads Postgres. Kafka here is purely an outbound notification channel for other consumers. See [platform.kafka].

Key API endpoint

Method Path Auth Returns
GET /api/aria/stats hasRole('BCOS_ADMIN') { totalEvents, criticalCount, highCount, activeIocCount, activeSequenceCount, lowPostureCount, eolDeviceCount }

Auth chain: SecurityConfig (stateless, @EnableMethodSecurity) → JwtAuthenticationFilter validates the Bearer JWT via shared JwtTokenValidator, maps the single role claim to ROLE_<role>. /stats is not in the permitAll list, so it requires an authenticated BCOS_ADMIN.

Design notes / semantics

  • Windowing is mixed on purpose (or by oversight): criticalCount/highCount are 24h rolling counts (...DetectedAtAfter(now-24h)), while totalEvents and everything else are point-in-time totals. The client's "Non-Critical Events = Total Critical High" therefore subtracts a 24h slice from an all-time total. Worth confirming this is intended before building anything else on those numbers.
  • No caching / no auto-poll: the stat map is recomputed on every GET /stats, and the SPA calls it exactly once on mount. Live dashboards would need a poll loop or push.
  • Counting shape: activeIocCount materializes the active-IOC list and takes .size() rather than a count(*) query — fine at current scale, a candidate for a dedicated count query later.

Related: [aria.dashboard.internal], [technical.aria], [platform.data-architecture], [platform.kafka], [platform.service-ownership].