Files
hiveops-guide/backend/src/main/resources/content/aria/precursor-alerts/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.1 KiB

module, title, tab, order, audience
module title tab order audience
aria.precursor-alerts Precursor Alerts Architect 30 bcos

How the Precursor Alerts feature is built. Everything lives in hiveops-aria (port 8095, DB hiveiq_aria). ARIA owns detection end-to-end — it does not read another service's tables to build alerts. See [platform.service-ownership].

Services involved

  • hiveops-aria — the only service in the write path. Ingests threat events, classifies them, detects multi-phase sequences, persists alerts, serves this screen, and emits a Kafka event downstream.
  • Agents / internal callers — POST threat events into ARIA ingestion (out of scope of this screen but the source of all data).
  • Downstream consumers (potential) — anything subscribing to hiveops.aria.sequences. ARIA itself has no Kafka consumers for this feature; the topic is fire-and-forget.

Data flow

agent/internal → POST /api/aria/ingest/events
   → ThreatEventIngestionService.ingest()  [@Transactional]
       → ThreatEventClassifier.classify()   (severity + attack_phase + IOC match)
       → save ThreatEvent                    (threat_event)
       → if severity HIGH/CRITICAL: AriaThreatProducer → topic hiveops.aria.threats
       → SequenceDetectionService.detectAndAlert(event)
              → query recent events for device within window
              → detect JACKPOTTING / SKIMMING / (null)
              → dedup guard (one active alert per device)
              → stamp events with sequence UUID, save
              → save PrecursorSequenceAlert  (precursor_sequence_alert)
              → AriaSequenceProducer → topic hiveops.aria.sequences

This is the ingest → executor(detect) → persist → Kafka pattern: the ingestion service is the executor, detection persists the record, then publishes an event to the record-store topic for downstream fan-out. See [platform.kafka], [platform.data-architecture].

Detection logic (SequenceDetectionService)

  • Window: aria.sequence.window.minutes (ARIA_SEQUENCE_WINDOW_MINUTES, default 15). Pulls all threat_event rows for the device with detected_at after now - window.
  • Requires ≥2 recent events and a PHYSICAL_ACCESS attack phase, else returns null (no alert).
  • JACKPOTTING if MALWARE_STAGING or MALWARE_EXECUTION phase present. Confidence = weighted sum of phases (PHYSICAL_ACCESS .25, MALWARE_STAGING .30, MALWARE_EXECUTION .25, PERSISTENCE .10, CLEANUP .05) + .10 if any CRITICAL event + .05 if any IOC match, capped at 1.0, 3-dp.
  • SKIMMING if signal CARD_READER_TAMPER or USB_INSERTION present. Base .35 + .40 (tamper) + .20 (USB) + .05 (critical), capped 1.0.
  • Dedup: existsByDeviceAgentIdAndResolvedAtIsNull — a device with an unresolved alert gets no new alert until the existing one is resolved.
  • On detect: all correlated events get the same sequence_id (UUID) written back, then the alert row is saved with phasesDetected[], confidence, institutionKey, sequenceId.

Kafka

Topic Direction Producer Key When
hiveops.aria.sequences produced AriaSequenceProducer.publishSequenceAlert deviceAgentId on every raised sequence alert
hiveops.aria.threats produced AriaThreatProducer.publishThreatEvent deviceAgentId per HIGH/CRITICAL ingested event (feeds context, not alerts)

No @KafkaListener consumes these in ARIA. Payloads are JSON (AriaSequenceEvent, AriaThreatEvent). AriaSequenceEvent.alertId is a freshly generated UUID (not the DB row id). See [platform.kafka].

Database (hiveiq_aria, Flyway-managed, ddl-auto=validate)

Table Role in this feature Read Write
threat_event source signals; also linked to a sequence via sequence_id detection query; events panel sequence_id stamped on correlate
precursor_sequence_alert the alert record shown on this screen list / resolve created on detect; resolved_at/resolved_by on resolve

precursor_sequence_alert columns: device_id, device_agent_id, sequence_type, phases_detected[], confidence (precision 4 scale 3), triggered_at, auto_response_taken, resolved_at, resolved_by, institution_key, sequence_id. See [platform.data-architecture].

API endpoints (PrecursorSequenceAlertController, all hasRole('BCOS_ADMIN'))

Method Path Purpose
GET /api/aria/sequences paged list; params resolved, sequenceType, deviceAgentId, page, size (default size 25); sorted triggeredAt desc
GET /api/aria/sequences/{id}/events threat events sharing the alert's sequence_id
POST /api/aria/sequences/{id}/resolve idempotent resolve; stamps resolved_at + resolved_by (JWT email)

Note the repository query precedence in getSequences: a non-blank deviceAgentId overrides the resolved and sequenceType filters (device query returns all statuses for that device). Only when no device filter is set do the resolved/type branches apply.