ADR 009 Track Feed - digitalspace/eagle-demi GitHub Wiki

ADR-009: Track feed into DEMI

Status

Proposed 2026-08-24. Nothing here is built. The decision needs an answer from whoever owns Track; Option B needs no DEMI work at all.

Context

Track (epictrack-api) owns the master project registry, and the Track project id is the master project id across EPIC. DEMI stores it that way: a DEMI project's id is its trackProjectId verbatim, and createProject refuses a project without one.

DEMI has no live connection to Track. Its entire Track dataset is a checked-in file, src/data/track_projects_enriched.json, 382 records, snapshot 2026-07-29, read off disk by src/seed/sources.js. Every project created, renamed, re-phased or withdrawn in Track since that snapshot is invisible to DEMI, and DEMI serves eagle-public's test search. The rule is fail-closed, so the symptom is absence rather than error: a project created after the snapshot never appears, and nothing logs that it is missing.

Track runs in the Gold namespaces c8b80a-{dev,test,prod}; the API is https://epictrack-api-c8b80a-<env>.apps.gold.devops.gov.bc.ca, and /ops/healthz is the unauthenticated liveness path (every /api/v1 route answers 500, not 401, to a missing or bad token). c72cba-* holds a dormant clone whose DeploymentConfigs sit at 0 replicas; its hostnames resolve to the wildcard router and answer 503.

Decision

Ask Track for one of two directions. Option B is the recommendation: it costs Track one HTTP call, and it needs nothing built in DEMI and no credential from Track.

Option A — DEMI pulls from Track

DEMI calls GET /api/v1/projects on epictrack-api on a schedule (nightly is enough; the data changes at human speed) and reconciles. Track must supply:

  1. The epictrack-api base URL in each environment.
  2. A service credential: a Keycloak client in the eao-epic realm with the view role and audience epictrack-web, issued to DEMI as a service account rather than a person's token. This is the critical path; the rest is a morning's work.
  3. Confirmation of the response contract. DEMI assumes the endpoint is unpaginated and serves epic_guid per project.
  4. Whether deletes are observable. Without a tombstone or an is-deleted flag, reconciliation can only infer a delete from absence.

Option B — Track pushes to DEMI

Track calls PUT /api/projects/<trackProjectId> on DEMI on create, on update, and on whatever Track considers a withdrawal or delete. The path id is the Track project id because that is DEMI's own project id. The route is authenticated and write-gated (src/http/routes.js, authMiddleware + requireWrite), and DEMI mints Track a scoped, revocable, expiring key — see Connecting-an-Application-to-DEMI.

Out of scope either way

No write access to Track: nothing in DEMI writes upstream, by design. No database access: an HTTP contract in either direction is enough. No real-time guarantee: minutes or hours of lag is fine, months is not.

Consequences

  • Until one option lands, DEMI's master id space is a snapshot rather than a registry — a stale Track file is a stale primary key space, not a stale side table.
  • The gap is user-facing, not deferred to a cutover, because eagle-public's test search is served by DEMI.
  • A reconciliation job can detect a lost delete only once there is a live feed to reconcile against.
  • Option A adds a scheduled job and a Track-issued credential to rotate. Option B adds neither, and moves the failure mode to Track missing a call.

See also

⚠️ **GitHub.com Fallback** ⚠️