content(guide): role-tabs (Internal/Architect/Claude) for all 63 customer modules (DRAFT, Refs #1 #3)

Full role-tab rollout: every customer module now has Customer/Internal/
Architect/Claude tabs, grounded per-module in real code. Backend serves 296
docs / 96 modules. Grounding surfaced ~20 real bugs across services (filed
separately after verification).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 12:14:54 -04:00
parent 916095f43b
commit 2f94d62769
189 changed files with 12123 additions and 0 deletions
@@ -0,0 +1,71 @@
---
module: aria.precursor-alerts
title: Precursor Alerts
tab: Architect
order: 30
audience: internal
---
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.