ADR 009 Track Feed - digitalspace/eagle-demi GitHub Wiki
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.
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.
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.
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:
- The epictrack-api base URL in each environment.
- A service credential: a Keycloak client in the
eao-epicrealm with theviewrole and audienceepictrack-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. - Confirmation of the response contract. DEMI assumes the endpoint is unpaginated and serves
epic_guidper project. - Whether deletes are observable. Without a tombstone or an is-deleted flag, reconciliation can only infer a delete from absence.
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.
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.
- 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.
- Sync-Architecture — every sync path, including this gap
- Identity-Model — which id means what, and the Eagle-only project rule
- Connecting-an-Application-to-DEMI — how DEMI issues a service key