Files
hiveops-guide/backend/src/main/resources/content/incident/journal-events/claude.md
T
johannes ffc06e2ef5
CD - Develop (guide → bcos.dev) / build-and-deploy (push) Failing after 36m22s
guide: document supervisor/service-state mirror (devices#121)
incident.journal-events (Claude tab): the state flip is a dual-write — incident
writes its own atm_properties, then mirrors to devices, and it is devices the UI
reads. Adds the best-effort/afterCommit semantics and the nginx-listener URL rule.

technical.devices: document POST /api/internal/atms/{agentAtmId}/service-state,
that devices does not ingest journal events, the now()-stamped supervisor_entered_at,
and that the status badge is derived client-side (agent-offline masks IN_SUPERVISOR).

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

5.0 KiB

module, title, tab, order, audience
module title tab order audience
incident.journal-events Journal Events Claude 40 dev

Act-without-guessing reference. Everything below is confirmed in code.

Ownership

  • Reads + store owner: hiveops-incident (port 8080, NGINX /api/incident/).
  • Ingest gateway: hiveops-agent-proxy (port 8093/host 8015). Agents NEVER hit incident directly.
  • Frontend: hiveops-incident/frontend/src/components/JournalEvents/JournalEvents.svelte (view label "Event Journal").

Endpoints (METHOD path)

Method + path Service Auth
GET /api/journal-events/atm/{atmId}/paginated incident JWT/browser role
GET /api/journal-events/atm/{atmId} incident JWT/browser role
GET /api/journal-events/atm/{atmId}/recent?hoursBack=24 incident JWT/browser role
GET /api/journal-events/atm/{atmId}/card-reader incident JWT/browser role
GET /api/journal-events/atm/{atmId}/cassette incident JWT/browser role
GET /api/journal-events/atm/{atmId}/cassette-inventory incident JWT/browser role
GET /api/journal-events/activity?hoursBack=24 incident JWT/browser role (institution-scoped)
GET /api/journal-events/fleet/cassette-inventory incident JWT/browser role (institution-scoped)
POST /api/journal-events incident controller / agent-proxy native AGENT or JWT/browser role
POST /api/internal/journal-events incident InternalJournalController ROLE_INTERNAL (X-Internal-Secret)
  • {atmId} = numeric atms.id, NOT the AT000xxx agent id.
  • Every /atm/{id}/... has an identical /device/{id}/... alias.
  • Paginated query params: page, size, sort=eventTime,desc, hoursBack, eventTypes (repeated key, e.g. eventTypes=CARD_READER_FAIL&eventTypes=...).

Roles

  • Read /api/journal-events/**: hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','BCOS_ADMIN'). NO @PreAuthorize; NOT admin-only.
  • Ingest internal: hasRole('INTERNAL') (header X-Internal-Secret = env INTERNAL_SERVICE_SECRET).
  • Agent path POST /api/journal-events also allows ROLE_AGENT.

Tables / DB

  • Table journal_events in DB hiveiq_incident. Entity JournalEvent.
  • Reads also touch atms; ingest touches atms, event_type (known-type lookup), incidents (auto-link).

Kafka

  • NONE for journal events. Ingest is synchronous HTTP. No hiveops.journal.* topic exists.

Ingest chain (exact)

agent → agent-proxy JournalEventController POST /api/journal-events → IncidentInternalService.createJournalEvent → incident POST /api/internal/journal-events → JournalEventService.createEvent

Gotchas

  • Unknown eventType (not in event_type lookup) → NOT stored; recorded as event exception; returns HTTP 202. Known → 201.
  • createEvent is @Retryable on PessimisticLockingFailureException (incident#212 deadlock guard).
  • eventTime parsed as LocalDateTime (no timezone); unparseable/blank → now().
  • Auto-registers ATM on agentAtmId via findOrCreateAtm; also updates last heartbeat + may flip service state (ATM_IN_SERVICE/ATM_OUT_OF_SERVICE/SUPERVISOR_ENTER/SUPERVISOR_EXIT) + trigger auto-incident.
  • The state flip is DUAL-WRITE (devices#121). Incident writes its own atm_properties, then mirrors to hiveops-devices via POST /api/internal/atms/{agentAtmId}/service-state (DevicesInternalService). Devices is what every status badge reads — nginx rewrites /incident/api/atms* to the devices backend — so incident's own copy is NOT what the UI shows. Before 2026-07-09 the mirror did not exist and no device ever displayed IN SUPERVISOR. Incident keeps its local write only because resolveOpenSupervisorTimeout depends on it.
  • The mirror is best-effort (a devices outage logs a warning, never fails ingest) and fires on afterCommitcreateEvent is @Retryable, so a rolled-back attempt must not mirror.
  • devices.service.url defaults to http://hiveiq-nginx:8096 (nginx internal listener), never a container name — devices runs blue/green and the active colour flips on cutover.
  • Category filter in UI is an allowlist of exact type strings (8 groups); types outside the groups are hidden when any category is ticked.
  • Per-ATM read endpoints filter by atmId only — they do NOT apply InstitutionContext scope (only /activity and /fleet/cassette-inventory do).

Do NOT

  • Do NOT POST agent journal events straight to incident — go through agent-proxy.
  • Do NOT call /api/internal/journal-events without X-Internal-Secret — 401/403.
  • Do NOT pass the AT000xxx agent id where {atmId} (numeric) is expected.
  • Do NOT expect a Kafka topic for journal ingest — there is none.
  • Do NOT assume this view is admin-gated — any authenticated user role can read it.
  • Do NOT treat a 202 on ingest as "stored" — it means the type was unknown.
  • Do NOT read hiveiq_incident.atm_properties to answer "is this ATM in supervisor?" — the UI reads hiveiq_devices.atm_properties. The two can diverge; devices is what the customer sees.