← Pipeline TECHNICAL.md

CTS-IL — Technical Reference

Table of Contents

  1. Architecture overview
  2. Module layout
  3. Kafka event bus
  4. Camel route inventory
  5. REST endpoints
  6. Guarantee lifecycle
  7. Gate-out decision flow (OPUS)
  8. Security
  9. Rate limiting
  10. Durable state — the poll roster
  11. Configuration reference
  12. Observability

1. Architecture overview

┌───────────────────────────────────────────────────────────────────┐
│                          CTS-IL  (port 8090)                      │
│                                                                   │
│  ┌──────────────┐  ┌────────────────┐  ┌──────────────────────┐  │
│  │  REST layer  │  │  Camel routes  │  │   Durable state      │  │
│  │  (JAX-RS)    │──│  (integration) │  │  (Redis)             │  │
│  └──────────────┘  └───────┬────────┘  │  ActiveGuaranteeReg. │  │
│                            │           └──────────────────────┘  │
└────────────────────────────┼──────────────────────────────────────┘
                             │
          ┌──────────────────┼──────────────────┐
          │                  │                  │
    ┌─────▼──────┐   ┌───────▼──────┐   ┌───────▼──────┐
    │   Kafka    │   │  Maersk API  │   │ Evergreen API│
    │  (CTS bus) │   │  (REST/OAuth)│   │  (REST/OAuth)│
    └────────────┘   └──────────────┘   └──────────────┘
          │
    ┌─────▼──────┐
    │    CTS     │   (Container Tracking System — not in this repo)
    └────────────┘

    OPUS SmartLink ─────── POST /opus/events ──────────────▶ IL
    SL agents      ─────── POST /sl/guarantee/{id}/... ────▶ IL

The IL makes no decisions and holds no decision cache. Its only persistent state is the poll roster in Redis (see §10); all other durable state lives in CTS or in the Kafka log.


2. Module layout

cts-il/
├── pom.xml
└── src/
    └── main/
        ├── java/com/capitalpay/cgmp/il/
        │   ├── CgmpIlApplication.java          # Quarkus entry point
        │   ├── config/
        │   │   ├── KafkaTopics.java             # Topic name constants + header contract
        │   │   ├── RateLimitProperties.java     # Rate-limit config records
        │   │   ├── MaerskProperties.java
        │   │   ├── EvergreenProperties.java
        │   │   └── OpusProperties.java
        │   ├── model/
        │   │   ├── kafka/                       # Kafka message POJOs (Java records)
        │   │   ├── maersk/                      # Maersk API model objects
        │   │   └── evergreen/                   # Evergreen API model objects
        │   ├── processor/
        │   │   ├── ActiveGuaranteeRegistry.java # Per-carrier active container tracking
        │   │   ├── maersk/
        │   │   │   ├── MaerskGateEventProcessor.java
        │   │   │   ├── MaerskHmacVerifier.java
        │   │   │   └── MaerskDndProcessor.java
        │   │   ├── evergreen/
        │   │   │   ├── EvergreenGateEventProcessor.java
        │   │   │   ├── EvergreenHmacVerifier.java
        │   │   │   └── EvergreenDndProcessor.java
        │   │   └── opus/
        │   │       └── OpusHmacVerifier.java         # verifies inbound webhook HMAC
        │   ├── rest/
        │   │   ├── MaerskWebhookResource.java
        │   │   ├── EvergreenWebhookResource.java
        │   │   ├── OpusEventResource.java
        │   │   ├── SlConfirmationResource.java
        │   │   └── TosRateLimiter.java
        │   ├── routes/
        │   │   ├── maersk/
        │   │   │   ├── MaerskWebhookReceiverRoute.java
        │   │   │   ├── MaerskEventPollerRoute.java
        │   │   │   ├── MaerskDndFetcherRoute.java
        │   │   │   ├── MaerskCertPusherRoute.java
        │   │   │   └── MaerskSubscriptionManagerRoute.java (implied)
        │   │   ├── evergreen/
        │   │   │   ├── EvergreenWebhookReceiverRoute.java
        │   │   │   ├── EvergreenEventPollerRoute.java
        │   │   │   ├── EvergreenDndFetcherRoute.java
        │   │   │   ├── EvergreenCertPusherRoute.java
        │   │   │   └── EvergreenSubscriptionManagerRoute.java
        │   │   ├── opus/
        │   │   │   └── OpusEventReceiverRoute.java
        │   │   └── sl/
        │   │       └── SlReceiverRoute.java
        │   └── security/
        │       ├── MaerskOAuthClient.java
        │       ├── EvergreenOAuthClient.java
        │       └── OpusSmartLinkOAuthClient.java
        └── resources/
            └── application.properties

3. Kafka event bus

3.1 Topic listing

Inbound — guarantee service publishes, IL consumes

Topic Constant Purpose
cgmp.il.operation-requested IL_OPERATION_REQUESTED The guarantee service requests IL operations (SNAPSHOT, SUBSCRIBE, UNSUBSCRIBE, ROSTER_ADD, ROSTER_REMOVE) for one or more containers. See docs/contracts/il-operation-contract.md.

The legacy cgmp.guarantee.{issued,claim-decision,settled,closed} topics are no longer consumed by the IL at all — the lifecycle-topic consumers were removed in PBI-047. The IL is driven exclusively by cgmp.il.operation-requested. The guarantee service may still publish those topics for its own purposes; they're simply not part of the IL's contract anymore.

Outbound — IL publishes

Topic Constant Purpose
cgmp.il.operation-completed IL_OPERATION_COMPLETED One completion event per (requestId, containerNumber) (plus batch-level rejections)
cgmp.gate-events GATE_EVENTS Normalised container gate and tracking events from all carriers and OPUS
cgmp.dnd-charges DND_CHARGES D&D charge records from all carriers
cgmp.sl.return-confirmation SL_RETURN_CONFIRMATION SL agent confirmed container returned to depot
cgmp.sl.receipt-confirmed SL_RECEIPT_CONFIRMED SL agent confirmed payment received
Topic Constant Purpose
cgmp.gate-events GATE_EVENTS Normalised container gate and tracking events from all carriers and OPUS
cgmp.dnd-charges DND_CHARGES D&D charge records from all carriers
cgmp.sl.return-confirmation SL_RETURN_CONFIRMATION SL agent confirmed container returned to depot
cgmp.sl.receipt-confirmed SL_RECEIPT_CONFIRMED SL agent confirmed payment received (triggers CLOSED in CTS)

3.2 Topic contracts

These topics form the IL ↔ CTS integration surface. Each entry documents the message type, payload fields, what triggers the publish, and the expected consumer action. All timestamps are UTC Instant (ISO-8601). Carrier identity is always duplicated in both the cgmp.slCode Kafka header and a payload field so consumers can route either before or after deserialisation.

Outbound contracts (IL → CTS)

cgmp.gate-eventsGateEventMessage

Published whenever the IL receives or polls a container equipment event from any carrier, or when OPUS reports a TOS-level gate event.

Field Type Description
source String Origin of the event: MAERSK_WEBHOOK, MAERSK_POLL, EVERGREEN_WEBHOOK, EVERGREEN_POLL, or OPUS
equipmentEventTypeCode String DCSA-standard code: GTOT (gate out full) or GTIN (gate in empty)
containerNumber String ISO 6346 container number
billOfLadingNumber String B/L reference; may be null for OPUS-sourced events
eventDateTime Instant When the physical gate event occurred at the terminal
facilityCode String UN/LOCODE or terminal code where the event occurred
carrierCode String SCAC: MAEU, EGLV, etc. Mirrors the cgmp.slCode header

Triggers: Maersk or Evergreen webhook delivery; carrier poll cycle (fallback); OPUS VESSEL_DISCHARGE_COMPLETE, CONTAINER_GATE_OUT_CONFIRMED, or FREE_TIME_EXPIRY_WARNING events.

CTS action: Update the container tracking record for the guarantee. A GTOT event starts the demurrage clock; a GTIN event stops it. VESSEL_DISCHARGE_COMPLETE creates the initial tracking record. FREE_TIME_EXPIRY_WARNING surfaces an early discrepancy flag.


cgmp.dnd-chargesDndChargeMessage

Published whenever the IL fetches D&D charges from a carrier API. The same topic is used for all carriers; CTS uses the slCode field and cgmp.slCode header to select the correct claim record type and rate card.

Field Type Description
containerNumber String ISO 6346 container number
guaranteeId String UUID of the guarantee in CTS
slCode String SCAC of the carrier whose D&D API produced these charges
fetchTrigger String What triggered this fetch: SNAPSHOT (operation-driven), SCHEDULED (24 h poll), MANUAL
fetchedAt Instant When the IL retrieved the charges from the carrier API
charges List<ChargeItem> One entry per charge line; see below

Each ChargeItem contains: chargeType (DEMURRAGE / DETENTION / STORAGE), status (ACCRUING / INVOICED), freeDaysGranted, freeDaysUsed, dailyRate, currency, accruedAmount, invoiced, invoiceNumber.

Triggers: SNAPSHOT — operation-driven via cgmp.il.operation-requested. SCHEDULED — 24-hour timer poll for all rostered containers. MANUAL — admin-driven.

CTS action: Create or update a D&D claim record (MAERSK_DND or EVERGREEN_DND based on slCode). Run the CTS vs SL cross-check against the applicable rate card. Raise a discrepancy flag if amounts diverge beyond the configured threshold.


cgmp.sl.return-confirmationReturnConfirmationMessage

Published when an SL agent confirms the container has been physically returned to the depot, or when OPUS reports a CONTAINER_GATE_IN_RETURN event.

Field Type Description
guaranteeId String UUID of the guarantee in CTS
containerNumber String ISO 6346 container number
slCode String SCAC of the carrier that owns the guarantee
returnedAt Instant Timestamp of the return confirmation
depotCode String Terminal/depot where the container was returned
confirmedByAgent String Identity of the SL agent who submitted the confirmation
shippingLineId String UUID of the ShippingLine entity in CTS — used to verify the agent's authority before processing

Triggers: POST /sl/guarantee/{id}/confirm-return (SL agent REST call) or OPUS CONTAINER_GATE_IN_RETURN event.

Downstream action (not the IL's concern): the guarantee service is the authoritative consumer. The IL doesn't model what state-machine transitions it triggers.


cgmp.sl.receipt-confirmedReceiptConfirmedMessage

Published when an SL agent records their payment receipt reference, confirming they have received the guarantee payout.

Field Type Description
guaranteeId String UUID of the guarantee in CTS
containerNumber String ISO 6346 container number
slCode String SCAC of the carrier that owns the guarantee
slReceiptRef String The SL's internal payment receipt reference
confirmedAt Instant Timestamp of the confirmation
confirmedBy String UUID of the SL agent user who submitted the receipt (from authenticated identity)

Triggers: POST /sl/guarantee/{id}/confirm-receipt (SL agent REST call).

CTS action: Verify the confirming agent belongs to the guarantee's SL account. Transition guarantee to CLOSED. Issue the teardown operations to the IL on cgmp.il.operation-requested (UNSUBSCRIBE + ROSTER_REMOVE).


Inbound contract (guarantee service → IL)

The IL's sole inbound command channel is cgmp.il.operation-requested (IlOperationRequestedMessage). The guarantee service requests operations — SNAPSHOT, SUBSCRIBE, UNSUBSCRIBE, ROSTER_ADD, ROSTER_REMOVE — for one or more containers; the IL executes them and reports back on cgmp.il.operation-completed. The full schema, per-operation field requirements, fan-out/rate-limit behaviour, and error codes live in docs/contracts/il-operation-contract.md.

The legacy cgmp.guarantee.{issued,claim-decision,settled,closed} lifecycle topics and their GuaranteeIssuedMessage / GuaranteeClosedMessage payloads were removed from the IL in PBI-047. The IL no longer consumes them; the guarantee service translates its own state transitions into the operations above (see the state-machine mapping in the contract doc).


3.3 Kafka header contract

Every message on the CGMP bus, in both directions, must carry the following Kafka record headers:

Header key Constant Format Required
cgmp.slCode HEADER_SL_CODE SCAC code: MAEU or EGLV Always
cgmp.containerNo HEADER_CONTAINER_NO ISO 6346, e.g. MSCU1234567 On container-scoped messages
cgmp.guaranteeId HEADER_GUARANTEE_ID UUID Where guarantee is known

The cgmp.slCode header is the primary routing discriminator. IL routes branch on this header before deserialising the message body. This avoids unnecessary JSON parsing on the hot path and ensures routing is deterministic even as body schemas evolve.

CTS responsibility: CTS must set cgmp.slCode on every message it publishes to inbound IL topics.

3.3 Partition key convention

All Kafka partition keys use the format slCode:containerNumber (e.g., MAEU:MSCU1234567). This makes the shipping line visible in the key itself — consumers and monitoring tools can identify the carrier without deserialising the body. Per-container ordering is preserved because slCode is stable for the lifetime of a guarantee.

KafkaTopics.partitionKey(slCode, containerNumber) is the canonical builder.


4. Camel route inventory

4.1 Maersk routes

Route ID Entry point Description
maersk-webhook-receiver direct:maersk-webhook-event Verifies HMAC, normalises event to GateEventMessage, publishes to cgmp.gate-events
maersk-event-poller Timer (5 min) Walks a roster slice; per the track batch mode either fetches per container (maersk-fetch-track-events) or groups the slice by B/L and fetches per B/L (maersk-fetch-track-events-by-bl)
maersk-fetch-track-events direct:maersk-fetch-track-events Per-container T&T fetch (equipmentReference); publishes to cgmp.gate-events; rate-limited to 150 req/min
maersk-fetch-track-events-by-bl direct:maersk-fetch-track-events-by-bl GROUP_BY_BL T&T fetch (transportDocumentReference — all containers on a B/L in one call); same 150 req/min bucket
maersk-dnd-scheduled-poller Timer (24 h) Groups the roster slice by (billOfLadingNumber, carrierCustomerCode) and fans out one maersk-dnd-fetcher call per group
maersk-dnd-fetcher direct:maersk-fetch-dnd B/L-keyed D&D fetch (/import/{chargeType}?billOfLadingNumber=, one call per DEM/DET); demuxes equipmentCharges[] to per-container cgmp.dnd-charges; 60 req/min. Maersk D&D has no per-container query, so this is always GROUP_BY_BL
maersk-subscription-create direct:maersk-subscribe Registers a Maersk webhook subscription (SUBSCRIBE op)
maersk-subscription-delete direct:maersk-unsubscribe Deletes the Maersk webhook subscription (UNSUBSCRIBE op)

4.2 Evergreen routes

Route ID Entry point Description
evergreen-webhook-receiver direct:evergreen-webhook-event Verifies HMAC, translates Evergreen event codes to DCSA, publishes to cgmp.gate-events
evergreen-event-poller Timer (5 min) Iterates the roster and delegates per container to evergreen-fetch-track-events
evergreen-fetch-track-events direct:evergreen-fetch-track-events Per-container T&T fetch (poller + SNAPSHOT op); publishes to cgmp.gate-events; rate-limited to 100 req/min
evergreen-dnd-scheduled-poller Timer (24 h) Fans out to evergreen-dnd-fetcher
evergreen-dnd-fetcher direct:evergreen-fetch-dnd On-demand D&D fetch; publishes to cgmp.dnd-charges; rate-limited to 60 req/min
evergreen-subscription-create direct:evergreen-subscribe Registers an Evergreen webhook subscription (SUBSCRIBE op)
evergreen-subscription-delete direct:evergreen-unsubscribe Deletes the Evergreen webhook subscription (UNSUBSCRIBE op)

4.3 OPUS / SmartLink routes

Route ID Entry point Description
opus-event-receiver direct:opus-tos-event Dispatches by eventType (see §7); auth is performed at the resource layer before this route is invoked

4.4 SL Receiver routes (SL agent → IL)

Route ID Entry point Description
sl-receiver-return direct:sl-return-confirmation Publishes ReturnConfirmationMessage to cgmp.sl.return-confirmation
sl-receiver-receipt direct:sl-receipt-confirmed Publishes ReceiptConfirmedMessage to cgmp.sl.receipt-confirmed; triggers CTS CLOSED

5. REST endpoints

All endpoints listen on port 8090. Webhook paths are called by external carrier systems; SL confirmation paths are called by authenticated SL agents.

Webhook receivers

POST /webhooks/maersk/events

Receives Maersk equipment event notifications.

POST /webhooks/evergreen/events

Equivalent endpoint for Evergreen.

POST /tos/events

Receives TOS events from OPUS Terminal via SmartLink.

SL agent confirmations

POST /sl/guarantee/{id}/confirm-return

SL agent confirms the container has physically returned to the depot.

POST /sl/guarantee/{id}/confirm-receipt

SL agent confirms all payments were received and provides their accounts-receivable reference. Triggers CTS CLOSED.


6. Guarantee lifecycle

The state machine lives in CTS. IL participates as an event gateway:

                     ┌──────────────┐
                     │   PENDING    │  (CTS internal)
                     └──────┬───────┘
                            │ SNAPSHOT + SUBSCRIBE + ROSTER_ADD ops issued to IL
                     ┌──────▼───────┐
                     │    ARMED     │  container on the IL roster, webhook subscribed
                     └──────┬───────┘
                            │ container gate-out events from IL
                     ┌──────▼───────┐
                     │   ACTIVE     │  (CTS internal — demurrage clock running)
                     └──────┬───────┘
                            │ claim-decision (CTS-internal; SL notified out-of-band)
                     ┌──────▼───────┐
                     │   SETTLED    │  (SL agent verifies wallet via their own channel)
                     └──────┬───────┘
                            │ /confirm-return (DISARM)
                     ┌──────▼───────┐
                     │  DISARMED    │  D&D clock frozen, final charges computed
                     └──────┬───────┘
                            │ /confirm-receipt → UNSUBSCRIBE + ROSTER_REMOVE ops issued to IL
                     ┌──────▼───────┐
                     │   CLOSED     │  webhook subscription deleted, container off the roster
                     └──────────────┘

Key IL responsibilities per transition:

Transition IL action
SNAPSHOT / SUBSCRIBE / ROSTER_ADD ops received Fetch a D&D/T&T baseline, register a carrier webhook subscription, and add the container to ActiveGuaranteeRegistry — one operation each, issued by the guarantee service on cgmp.il.operation-requested
Gate/tracking events received Normalise to GateEventMessage; publish to cgmp.gate-events
Claim decision / settlement (CTS-internal) No IL involvement — SL communication is out-of-band; the IL is never told about these transitions
POST /sl/guarantee/{id}/confirm-return Publish ReturnConfirmationMessagecgmp.sl.return-confirmation
POST /sl/guarantee/{id}/confirm-receipt Publish ReceiptConfirmedMessagecgmp.sl.receipt-confirmed
UNSUBSCRIBE / ROSTER_REMOVE ops received Delete the carrier webhook subscription and remove the container from ActiveGuaranteeRegistry — issued by the guarantee service on cgmp.il.operation-requested

7. OPUS gate-event relay

OPUS is treated like any shipping line under the ingress-only rule: it is an inbound webhook event source only. There is no OPUS API the IL queries, and the IL makes no gating decision — it does not answer truck-arrival requests with ALLOW/HOLD. (The earlier synchronous CONTAINER_GATE_OUT_REQUEST → ALLOW/HOLD machinery and its GuaranteeCache were removed; gating is the terminal operator's concern, driven out-of-band by the guarantee state the operator already holds.)

OPUS POSTs events to POST /tos/events. The resource authenticates each event (HMAC-SHA256 over the raw body and a shared bearer token), enforces the inbound rate cap, then hands the raw bytes to direct:opus-tos-event. The opus-event-receiver route relays the two gate events the IL cares about and discards everything else.

Relay path

OPUS → POST /tos/events
         │
         ▼
  OpusHmacVerifier + OpusBearerTokenVerifier   (1) verify HMAC + bearer token
         │
         ▼
  OpusEventResource → direct:opus-tos-event     (2) rate-limit, then dispatch
         │
         ▼
  opus-event-receiver  .choice() on eventType
         ├─ CONTAINER_GATE_OUT_CONFIRMED ──▶ publish to cgmp.gate-events
         ├─ CONTAINER_GATE_IN_RETURN     ──▶ publish to cgmp.gate-events
         └─ otherwise                    ──▶ discard with warning log

Both relayed events are published to cgmp.gate-events with the Kafka key set to the container number. CTS consumes cgmp.gate-events and applies its own demurrage-clock semantics — the IL attaches no interpretation.

OPUS event types handled

eventType Action
CONTAINER_GATE_OUT_CONFIRMED Publish to cgmp.gate-events (container left the terminal gate)
CONTAINER_GATE_IN_RETURN Publish to cgmp.gate-events (container returned through the gate)
All other Discarded with warning log

8. Security

HMAC-SHA256 webhook verification

All inbound webhooks (Maersk, Evergreen, OPUS) are verified before any processing occurs. The verifier is the first processor in each route. An invalid signature throws IllegalArgumentException, which the route's onException handler traps, logs, and converts to HTTP 400.

Verifier class Header inspected Secret config key
MaerskHmacVerifier X-Maersk-Signature MAERSK_WEBHOOK_SECRET
EvergreenHmacVerifier X-Evergreen-Signature EVERGREEN_WEBHOOK_SECRET
OpusHmacVerifier OPUS-specific OPUS_HMAC_SECRET

OAuth 2.0 client credentials

Outbound calls to carrier APIs use tokens obtained via the OIDC client credentials flow. Quarkus manages token acquisition and refresh automatically.

Named OIDC client Target Scopes
maersk Maersk API track:read, dnd:read, notifications:write
evergreen Evergreen API (carrier-defined)
opus OPUS SmartLink (carrier-defined)

Token endpoints are configurable via environment variables (see §11).

SL agent authentication

SL agent REST calls (/sl/guarantee/...) require a valid Bearer token. The confirmedBy field in confirmation messages is populated from the authenticated identity at the REST layer — it is not trusted from the request body.


9. Rate limiting

IL enforces rate limits in both directions to comply with carrier API quotas and protect OPUS.

Outbound — carrier API calls

Rate limits are applied using Camel throttle() with a 60-second window.

API Operation Default limit Config property
Maersk Track & Trace 150 req/min MAERSK_RATE_LIMIT_TRACK
Maersk D&D 60 req/min MAERSK_RATE_LIMIT_DND
Maersk Notifications 30 req/min MAERSK_RATE_LIMIT_NOTIFICATIONS
Evergreen Track & Trace 100 req/min EVERGREEN_RATE_LIMIT_TRACK
Evergreen D&D 60 req/min EVERGREEN_RATE_LIMIT_DND
Evergreen Notifications 30 req/min EVERGREEN_RATE_LIMIT_NOTIFICATIONS

Throttle behaviour: Camel throttle applies back-pressure — messages queue, they do not drop. Queued messages will be processed once the rate window clears.

Inbound — OPUS TOS events

OPUS inbound events are capped at 200 req/min (TosRateLimiter). Requests beyond the cap receive HTTP 429. SmartLink is expected to back off and retry (per CGMP×OPUS Integration Spec §8).

SL agent confirmation endpoints (/sl/guarantee/...) are not rate-limited because they are human-initiated, low-frequency events bounded by the number of active guarantees.


10. Durable state — the poll roster

The IL holds no decision-making cache. The only state it persists is the poll roster: the set of containers each SL's pollers must cover. (The earlier GuaranteeCache used for synchronous OPUS gate decisions was removed — see §7.)

ActiveGuaranteeRegistry

A Redis-backed roster, one hash per SL: cgmp:il:roster:{slCode}, with the container number as the hash field and a small RosterEntry (billOfLadingNumber, carrierCustomerCode) JSON as the value. It is read by the polling routes (MaerskEventPollerRoute, EvergreenEventPollerRoute, MaerskDndFetcherRoute, EvergreenDndFetcherRoute) to know which containers to include in each poll cycle when webhooks are unavailable.

Operation Redis command Driven by
addContainer(slCode, containerNo, bl, custCode) HSET cgmp:il:roster:{slCode} ROSTER_ADD operation
removeContainer(slCode, containerNo) HDEL (null slCode removes from both SL hashes) ROSTER_REMOVE operation
nextSlice(slCode, api, countHint) HSCAN + persisted cursor (fail-soft: empty slice + WARN on Redis error) poller ticks
getMaerskContainers() / getEvergreenContainers() HKEYS (fail-soft) whole-roster callers
lookup(slCode, containerNo) HGET enrichment

The roster is driven exclusively by ROSTER_ADD / ROSTER_REMOVE operations on cgmp.il.operation-requested (the legacy cgmp.guarantee.* @Incoming consumers were removed in PBI-047).

Bounded polling (PBI-055): the timer pollers do not re-read the whole roster each tick — at production roster sizes (≈4,000 containers) a full sweep can't drain within one period, so re-submitting it every tick would compound the throttle backlog without bound. Instead each tick processes a bounded slice via nextSlice, which walks the roster with HSCAN and persists a per-{slCode}:{api} cursor in cgmp:il:sweep-cursor:{slCode}:{api} so successive ticks cover the rest. Slice size = rate-limit × (period / 1 min), sized to drain within the period; full-roster coverage latency is therefore roster / rate (~27 min for Maersk T&T). A per-{slCode}:{api} non-overlap guard (PollSweepGuard) additionally skips a tick whose predecessor is still running. Webhooks (SUBSCRIBE) remain the primary real-time path; the poller is reconciliation. See docs/pbi/PBI-055-cursored-reconciliation-sweep.md.

Durability: because the roster lives in persistent, replicated Redis (AOF in dev via docker-compose; managed Redis in prod via REDIS_URL), it survives IL restarts. A freshly restarted IL reads the existing roster from Redis and resumes polling immediately — no replay or re-arming from the guarantee service is required. See docs/pbi/PBI-054-durable-roster.md.


11. Configuration reference

All sensitive values are injected via environment variables. Non-sensitive defaults are defined in application.properties.

Core

Property Env var Default Description
quarkus.http.port 8090 HTTP server port
kafka.bootstrap.servers KAFKA_BOOTSTRAP_SERVERS localhost:9092 Kafka broker list

Maersk

Property Env var Default Description
maersk.api.base-url MAERSK_API_BASE_URL https://api.maersk.com API base URL
maersk.api.consumer-key MAERSK_CONSUMER_KEY OAuth2 client ID
maersk.api.consumer-secret MAERSK_CONSUMER_SECRET OAuth2 client secret
maersk.api.oauth-token-url MAERSK_OAUTH_TOKEN_URL https://api.maersk.com/oauth2/access_token Token endpoint
maersk.api.webhook.secret MAERSK_WEBHOOK_SECRET HMAC verification secret
maersk.api.dnd.poll-interval-ms MAERSK_DND_POLL_INTERVAL_MS 3600000 (1 h) D&D polling interval

Evergreen

Property Env var Default Description
evergreen.api.base-url EVERGREEN_API_BASE_URL https://api.evergreen-line.com API base URL
evergreen.api.client-id EVERGREEN_CLIENT_ID OAuth2 client ID
evergreen.api.client-secret EVERGREEN_CLIENT_SECRET OAuth2 client secret
evergreen.api.oauth-token-url EVERGREEN_OAUTH_TOKEN_URL https://api.evergreen-line.com/oauth/token Token endpoint
evergreen.api.webhook.secret EVERGREEN_WEBHOOK_SECRET HMAC verification secret
evergreen.api.dnd.poll-interval-ms EVERGREEN_DND_POLL_INTERVAL_MS 3600000 (1 h) D&D polling interval

OPUS / SmartLink

Property Env var Default Description
opus.smartlink.hmac-secret OPUS_HMAC_SECRET HMAC-SHA256 secret for inbound POST /tos/events
opus.smartlink.inbound-bearer-token OPUS_INBOUND_BEARER_TOKEN Bearer token CGMP requires on inbound POST /tos/events (second factor)

OPUS has no queryable API, so there is no base-URL / OAuth / outbound config — only the two secrets used to authenticate the inbound webhook.

Rate limits

Property Env var Default Description
cgmp.rate-limit.maersk.track-api-per-min MAERSK_RATE_LIMIT_TRACK 150
cgmp.rate-limit.maersk.dnd-api-per-min MAERSK_RATE_LIMIT_DND 60
cgmp.rate-limit.maersk.notifications-api-per-min MAERSK_RATE_LIMIT_NOTIFICATIONS 30
cgmp.rate-limit.evergreen.track-api-per-min EVERGREEN_RATE_LIMIT_TRACK 100
cgmp.rate-limit.evergreen.dnd-api-per-min EVERGREEN_RATE_LIMIT_DND 60
cgmp.rate-limit.evergreen.notifications-api-per-min EVERGREEN_RATE_LIMIT_NOTIFICATIONS 30
cgmp.rate-limit.opus.inbound-events-per-min OPUS_RATE_LIMIT_INBOUND 200

12. Observability

Health

GET /health — SmallRye Health composite endpoint.

Individual probes:

Metrics

GET /metrics — Prometheus text format via Micrometer.

Key metrics to watch:

Logging

Default log levels (application.properties):

Logger Level
Root INFO
com.capitalpay.cgmp.il DEBUG
org.apache.camel INFO

All route log statements include the container number and/or Kafka record key to allow correlation with CTS logs and carrier API audit trails.