Repository navigation
Identity Model
This page is the authority on which id means what in DEMI. If another page disagrees, this one is right.
Track project ids are the master id for projects in DEMI.
Track (epictrack-api) is a superset of Eagle. Projects are created in Track, and business does a
manual sync into Eagle. Eagle's MongoDB is being retired.
| Field | Value | Why |
|---|---|---|
id |
Track project id, as a String | It is also the Cosmos partition key |
trackProjectId |
the same value, as a Number |
merge/project.js writes it this way |
eagleId |
the Eagle _id, an attribute |
Keeps existing Eagle URLs resolvable |
sourceSystem |
track | eagle | demi
|
Provenance |
In the Cosmos row id is a String and trackProjectId is a Number. The search response emits
trackProjectId as a String on both branches: the Cosmos branch coerces the stored Number, and the
index branch has no trackProjectId field at all, so it reuses the Edm.String key. Both legs are
in src/controllers/search.js, in the Project dataset mapper.
id is the partition key, so it cannot be changed in place. updateProject drops id and
trackProjectId from the request body rather than applying them; reassigning one is a
delete-and-reinsert, and every document under that project moves partition with it.
Retain them, and flag them. Never drop them.
This should not happen — Track is meant to be a superset. It does happen in test, because test
projects are created in Eagle exclusively. Those rows carry the literal id
eagle-<24-hex-ObjectId>, written by mergeEagleOnlyProject in src/merge/project.js, and
sourceSystem: 'eagle'.
GET /api/projects and project search list them beside the Track-sourced rows: what a caller sees
is decided by the row's read[] ACL, not by which system wrote it. GET /api/db/stats reports the
count as unlinkedProjects — the rows that carry no Track counterpart.
| Field | Value |
|---|---|
id |
the Eagle _id for mirrored documents; a UUID for DEMI-created ones |
projectId |
the DEMI project id — the Track id, not the Eagle project ObjectId |
projectId is the Cosmos partition key (src/repositories/documents.js). Reusing the Eagle
_id as the document id is what makes eagle-api's download URL
/api/public/document/{_id}/download/document resolve.
chunks partitions on /documentId, not /projectId (src/repositories/chunks.js,
azure/modules/cosmos-nosql.bicep). projectId rides along as a scope field only
(SCOPE_FIELD), because visibilityFor(access, field) uses its argument for both the partition key
and the project-scope predicate, and passing documentId there would compare project ids against
document ids and silently match nothing.
The practical consequence: re-identifying a project moves no chunks. Document ids are Eagle ObjectIds and do not change.
Chunk ids are deterministic — ${documentId}::p${pageNumber}::c${chunkIndex} — so re-extracting a
document upserts its chunks in place rather than duplicating them.
eagle-public receives the Eagle ObjectId as _id, and that is deliberate. It routes
/p/<id>/project-details and then re-fetches the project from eagle-api by that id, so a Track id
there is a link that 404s. The Project dataset mapper in src/controllers/search.js sets _id
to legacyEagleId, falling back to the DEMI id for a Track-only project.
The general rule: one field per id-space, never one derived from the other. A search result row
carries id (DEMI/Track), trackProjectId, legacyEagleId and _id (Eagle), and each has exactly
one meaning. trackProjectId is null — never the DEMI id — on a row whose id starts with eagle-.
| Principal | Direction | Credential | Grants |
|---|---|---|---|
apim:eagle-api |
eagle-api → DEMI | APIM subscription eagle-api
|
demi-service-write, no project scope; the Eagle mirror |
apim:engage |
ENGAGE → DEMI | APIM subscription engage
|
demi-service-write, no project scope; the ENGAGE ingest only |
demi-sync-out |
DEMI → eagle-api | Keycloak client in realm eao-epic, client credentials |
Realm role staff in eagle-api; secret in Key Vault as eagle-sync-out-client-secret
|
Each DEMI route family admits only its own principal. Registry rows and the ingest contract: Connecting-an-Application-to-DEMI. Why DEMI writes to eagle-api: ADR-014-Sync-Out.
trackProjectId is empty in Eagle. The field exists on Eagle's Project model as
unique, sparse, index (eagle-api/api/helpers/models/project.js:105), and eagle-api already ships
middleware that 301-redirects an ObjectId path segment to it
(eagle-api/api/middleware/projectRedirect.js:16-24). Measured 2026-08-23: it is populated on
0 of 200 sampled projects, in both test and prod.
So the Eagle↔Track mapping exists only inside DEMI (via track.epic_guid) and inside Track itself.
By decision, DEMI keeps that mapping and writes nothing into Eagle Mongo. Sync-out sends comment
periods to eagle-api's HTTP API and carries no trackProjectId (ADR-014-Sync-Out). That leaves
eagle-api's redirect middleware dormant. That is the intended state, not an oversight.
DEMI's Track data is a static file. src/data/track_projects_enriched.json — 382 records,
snapshot 2026-07-29, read off disk by src/seed/sources.js. There is no live Track feed, so the
master id space comes from a snapshot rather than from Track. ADR-009-Track-Feed states what
closing that would take, and from whom.
- Sync-Architecture — how records get into DEMI, and what happens on delete
- Architecture — the containers, the merge, the ACL model
- Environment Reality & Gotchas
- Architecture
- Seeding — filling Cosmos from Track and Eagle
- Sources and Status — every source, one state
- Search & Retrieval — overview
- Search Index Reference
- Search Query Construction
- Search Measurements
- Extraction Pipeline
- Extraction Quality — coverage, word-joining, tiling
- Identity Model — which id means what
- Attribute-Level Access — field-visibility levels, catalog
- Sync Architecture — how records get in
- Eagle Mirror Reference — mirrored kinds, refusals, drift classes
- ENGAGE Rows Reference — ENGAGE-owned comment periods, reconcile
- Public-Read Backfill — filling and repairing the mirror containers
-
Short Links —
/s/{code}redirect and/api/links -
Recent Uploads —
/api/documents/recent-uploads, eagle-public homepage panel - Reports — comment export and BCGW CSV reads
-
User Data — saved map areas, preferences,
/meroutes -
Bulk Download —
/api/bulk-downloads, zip jobs - Bulk Download: Operations — enable, poison-queue runbook
- Document Intake — upload link, malware scan, release
- Connecting an Application to DEMI
- Azure Environments
- Document Backup — archive copy of originals, run and check
- Document Backup: Revert and Drill — put documents back from the archive
- PDF Titles — public name written into stored PDFs, run, restore
- Secret Sync — Key Vault, secret-sync Function app, required names
-
Operating DEMI in Azure — deploy, verify
- Operating the DEMI Data Plane — devbox access, probes, audit and cost reads
- Edge ban detector — hourly timer, modes, kill switch, soak and promote
-
CI Workflows — every GitHub Actions workflow, trigger, identity, roles
- CI Deploy Workflows — eagle-demi staging and production deploys
- CI Admin Frontend Workflows — eagle-demi-admin checks and deploys
- Release Process — cutting, tagging, shipping and rolling back a version
- Runbook: Search Outage — index/app schema drift, diagnose, fix, roll back
- BC Gov Landing Zone
- Business Rules
- EPIC.search Business Rules — where DEMI differs from EPIC.search
- 001: Typesense for DFL Search — superseded
- 002: Async Processing (MongoDB Queue) — superseded
- 003: Rule-Based Classification
- 004: Read ACL Authorization Model
- 005: Cosmos Full-Text Search Ruled Out
- 006: AI Summarization over BM25
- 007: Service-to-Service Credentials
- 008: Audit and Usage Analytics Store
- 009: Track Feed — proposed
- 010: Attribute-Level Access Control
- 011: Public Read Surface
- 012: Public Config Surface
- 013: Stored Project Summaries — proposed
- 014: Sync-out to Eagle
- 015: Malware scanning on intake