ffc06e2ef5
CD - Develop (guide → bcos.dev) / build-and-deploy (push) Failing after 36m22s
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>
5.0 KiB
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}= numericatms.id, NOT theAT000xxxagent 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')(headerX-Internal-Secret= envINTERNAL_SERVICE_SECRET). - Agent path
POST /api/journal-eventsalso allowsROLE_AGENT.
Tables / DB
- Table
journal_eventsin DBhiveiq_incident. EntityJournalEvent. - Reads also touch
atms; ingest touchesatms,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 inevent_typelookup) → NOT stored; recorded as event exception; returns HTTP 202. Known → 201. createEventis@RetryableonPessimisticLockingFailureException(incident#212 deadlock guard).eventTimeparsed asLocalDateTime(no timezone); unparseable/blank →now().- Auto-registers ATM on
agentAtmIdviafindOrCreateAtm; 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 viaPOST /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 becauseresolveOpenSupervisorTimeoutdepends on it. - The mirror is best-effort (a devices outage logs a warning, never fails ingest) and fires on
afterCommit—createEventis@Retryable, so a rolled-back attempt must not mirror. devices.service.urldefaults tohttp://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
atmIdonly — they do NOT applyInstitutionContextscope (only/activityand/fleet/cassette-inventorydo).
Do NOT
- Do NOT POST agent journal events straight to incident — go through agent-proxy.
- Do NOT call
/api/internal/journal-eventswithoutX-Internal-Secret— 401/403. - Do NOT pass the
AT000xxxagent 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_propertiesto answer "is this ATM in supervisor?" — the UI readshiveiq_devices.atm_properties. The two can diverge; devices is what the customer sees.