tasks
Phase 0 tickets — enablers for the KaaG persona dashboards
Section titled “Phase 0 tickets — enablers for the KaaG persona dashboards”Tracker-ready. Each ticket: repo · title · description · acceptance criteria.
Epics reference proposal.md §3. Branch/PR conventions per each repo’s
.claude/skills/backend-pr-conventions.
E12 · RADR personas RBAC
Section titled “E12 · RADR personas RBAC”T1 · [mocx-contracts] Add RADR App + three KaaG persona AppRoles
Section titled “T1 · [mocx-contracts] Add RADR App + three KaaG persona AppRoles”Add RADR to the App enum and three AppRoles: RADR_VP_PRODUCT (read-only),
RADR_PRODUCT_MANAGER, RADR_TECHNICAL_SPECIALIST. Version-bump the contract and publish
to the local maven repo consumers (core-api, hermes-java-api).
Accept: contracts artifact consumed by both services; AppRoleCatalogController lists
the three roles under the RADR app; no existing role IDs shifted.
T2 · [hermes-java-api] Per-persona scoping in RadrAccessPolicy
Section titled “T2 · [hermes-java-api] Per-persona scoping in RadrAccessPolicy”Extend core/configs/security/RadrAccessPolicy: map RADR_VP_PRODUCT → BOARD (read-only,
no COMMAND), RADR_PRODUCT_MANAGER → FLEET_VIEW + a limited command set
(KAAG_ON, KAAG_OFF, AUTO only), RADR_TECHNICAL_SPECIALIST → full COMMAND +
firmware scope backed by Authority.CAN_MANAGE_VCU_FIRMWARES. Add per-VcuCommandType
authorization to VcuCommandServiceImpl (PM attempting IMMOBILIZE ⇒ 403).
Accept: integration tests per persona × command type; VP receives 403 on any POST command.
T3 · [rechaj-backend-api] Lift the admins-only RADR gate
Section titled “T3 · [rechaj-backend-api] Lift the admins-only RADR gate”RequestSource.RADR_APP currently allows ADMIN/SUPER_ADMIN only. Allow the three new
AppRoles through login/token issuance for the RADR surfaces while keeping fine-grained
policy in radr-api.
Accept: a user holding only RADR_VP_PRODUCT can authenticate into RADR surfaces;
non-RADR roles still rejected.
E13 · Firmware-side prerequisites (→ Telpo change requests)
Section titled “E13 · Firmware-side prerequisites (→ Telpo change requests)”Ownership note (2026-07-13): Telpo writes the VCU firmware. The local
rechaj-vehicle-controllerrepo is stale — a wire-contract reference, not a ship vehicle (itsmaindoesn’t compile; changes there never reach devices). T4–T7 are therefore change requests to Telpo. The reference implementation in rechaj-vehicle-controller#3 doubles as the vendor spec: exact payload keys (fault registerhpd…bcf/icf, packedindindicator bitmask with documented bit layout,spaSOC-push age, the{"ack","result","ts"}command-ack frame) and thefvrJSON-quoting fix. Acceptance criteria below still apply — verified on a bench unit running Telpo’s build.
T4 · Publish the MCU fault register + MOSFET/motor temps (RUNBOOK §4.4)
Section titled “T4 · Publish the MCU fault register + MOSFET/motor temps (RUNBOOK §4.4)”Uncomment the publish lines in updateData() (main.cpp ~670-681). vcu-consumer columns and
parser already exist.
Accept: fault flags + tp1/tp2 visible in vcu_telemetry_data for a bench unit; a forced
bms_comm_failure appears end-to-end within one cadence.
T5 · Publish display-link health (vehicle-lights frame + SOC-push freshness)
Section titled “T5 · Publish display-link health (vehicle-lights frame + SOC-push freshness)”Add the parsed MCU_VEHICLE_LIGHTS_DATA_T states (frame 0x18F86590) and
last_soc_send_time age to the payload; extend vcu-consumer normalization.
Accept: indicator states + soc_push_age_s land in Timescale; DU display-link health is
derivable server-side.
T6 · Resolve the pub/ vs vcu/pub/ topic-namespace mismatch
Section titled “T6 · Resolve the pub/ vs vcu/pub/ topic-namespace mismatch”Decide the canonical namespace; during transition the consumer subscribes both
(pub/# and vcu/pub/#) with dedup by (device_id, ts). Update
LoggingVcuCommandPublisher successor + docs.
Accept: zero frame loss during cutover (compare ingest counts); RUNBOOK updated;
diagnostics “namespace mismatch” case closable.
T7 · Emit a command-ack frame
Section titled “T7 · Emit a command-ack frame”On executing any sub/<device_id> command (MECHON/MECHOFF/INVON/INVOFF/AUTO), publish an
ack frame {ack:<token>, cmd, result, ts} on the telemetry topic.
Accept: ack arrives within one cadence on bench; malformed/unknown commands ack with
result:"rejected".
E1 · Real command execution + durable audit (hermes-java-api)
Section titled “E1 · Real command execution + durable audit (hermes-java-api)”T8 · Real MQTT command publisher
Section titled “T8 · Real MQTT command publisher”Replace LoggingVcuCommandPublisher with an MQTT publisher on sub/<device_id> (reuse the
Spring-Integration mqttOutputChannel proven by battery unlock). Return SENT when the
broker confirms publish to a connected client, QUEUED otherwise (retained or broker queue —
match firmware semantics).
Accept: bench KAAG_OFF opens the relay; SENT/QUEUED distinction verified against a
disconnected unit.
T9 · Durable vcu_command table + idempotency
Section titled “T9 · Durable vcu_command table + idempotency”Flyway migration: vcu_command (uid, vcu_serial, type, firmware_token, actor, reason,
confirm_token_used, idempotency_key UNIQUE, status SENT/QUEUED/ACKED/EXPIRED/SUPPRESSED/
REJECTED, requested_at, resolved_at, ack_payload). Replace the in-memory idempotency Set.
Accept: duplicate idempotency key returns SUPPRESSED across service restarts/replicas;
every POST writes exactly one row.
T10 · Ack consumption, queued replay, 72 h expiry
Section titled “T10 · Ack consumption, queued replay, 72 h expiry”Consume T7 ack frames → transition QUEUED/SENT → ACKED. On device reconnect (first frame after gap), replay unexpired QUEUED commands. Scheduled job expires QUEUED > 72 h → EXPIRED (never fire stale — the audit-trail invariant the dashboards promise). Accept: command sent to an offline unit lands after reconnect within one cadence; a 72 h-old queued command never fires and shows EXPIRED.
T11 · Command history endpoints
Section titled “T11 · Command history endpoints”GET /api/v1/vcu/{serial}/commands and GET /api/v1/vcu/commands (filters: type, status,
actor, date range; paged). Feeds the PM/TS audit tables and the per-unit event timeline (E6).
Accept: OpenAPI documented; RBAC — VP read allowed, command POST still scoped per T2.
T12 · Stage-6 bench review → enable the kill-switch
Section titled “T12 · Stage-6 bench review → enable the kill-switch”Run the bench checklist (T4–T10 verified on hardware), then flip radr.commands.enabled=true
in qa → uat → prod with the rollout note.
Accept: sign-off recorded; prod config change audit-logged.
E11 · Fleet-table API hardening (hermes-java-api)
Section titled “E11 · Fleet-table API hardening (hermes-java-api)”T13 · Paginated, filterable fleet endpoint + honesty buckets
Section titled “T13 · Paginated, filterable fleet endpoint + honesty buckets”New GET /api/v1/radr/fleet?page&size&sort&q&band&kaag&fw returning the dashboard row DTO
(identity, last-seen band + minutes, SOC/SOH, fw, hasKaag/hasTelpo, latest flags). Companion
GET /api/v1/radr/fleet/buckets returning the five last-seen band counts (GREEN<10m …
BLACK≥24h) summing to deployed.
Accept: p95 < 300 ms at fleet scale (uses findLatestForAllDevices / Redis cache);
band math identical to VcuActivityStatus.
Sequencing
Section titled “Sequencing”T1→T2→T3 (RBAC chain) can run parallel to T4–T7 (firmware). T8 depends on T6 (namespace decision); T9–T11 depend on T8; T10 depends on T7. T12 last. T13 independent — start anytime.