Repository navigation
Native List Reads
The native list endpoints /documents, /projects, /updates and /notifications read from Cosmos or the index. The mirror lists and the mirror datasets of GET /search read from Cosmos only. This page owns their store choice, paging, sort keys and filters.
GET /search routing and query building: Search-Query-Construction. Index fields: Search-Index-Reference. Keyword search inside documents: Deep-Search-Queries.
GET /documents reads one project's plain list, and an ids= read, from Cosmos. A plain list is a project or ids parameter with no q, sort, displayNameContains, isFeatured, legislation or date filter. Every other list pages in the index, cross-project lists without q included.
The Cosmos list reads by overfetch-and-slice against a repository that clamps maxItemCount to 1000. The container indexes neither datePosted nor displayName, so it cannot page or order a large project.
Index-path list metadata is stale by up to the indexer's PT5M pass. Visibility is not, because setDocumentPublished and the project cascade write ACLs straight into the index. Update-form images are left out on both paths.
Paging uses page (from 1), pageSize and count=true. The index returns the page and @odata.count in one request, and x-total-count is that count.
On the index path these are a 400:
- a
pageSizeabove 500 - a filter or contains filter with no index column, because the Cosmos re-read does not apply
CONTAINSand the filter would vanish - any flag parameter (see "Native list filters, shared by every kind")
The index returns only ids. The rows are re-read from Cosmos by id, which applies each record's own field dials, and put back in index order. A stale index makes a page short; it does not leak.
Order follows orderby, always ending in id so pages do not overlap:
| Request | Order |
|---|---|
sort named |
that key's index column (below), then id
|
q, no sort
|
relevance (search.score() desc), then id
|
no q, no sort
|
datePosted desc, then id
|
Sort keys take a - prefix for descending: datePosted, displayName (column displayNameSort), sortOrder, type, milestone, projectPhase, documentAuthorType, isPublished, legislation, isFeatured, documentAuthor (column documentAuthorSort). Most rows hold sortOrder 0, so a sortOrder sort breaks ties by datePosted desc, then displayNameSort asc, then id. Any other key is a 400.
| Filter | Takes |
|---|---|
project |
alias of projectId; several values |
typeId, milestoneId, projectPhaseId, documentAuthorTypeId
|
several values each |
legislation |
several values, each matching ^\d{1,4}$
|
isFeatured |
one value, true or false
|
datePostedStart, datePostedEnd
|
one YYYY-MM-DD day each; the end day is included |
displayNameContains |
a name match, see "Contains filters" |
These values reach the index as unquoted literals. Each is checked before any OData is built, and a bad value is a 400, because legislation=1) or (true would otherwise close the ACL clause and expose @odata.count.
The searchVis clause is sent only when q is present. Its definition: Search-Index-Reference "searchVis on documents".
Every native list parses its query with parseNativeQuery (src/helpers/native-query.js). These are a 400:
- a parameter the kind does not declare
- a control parameter (
page,pageSize,count,sort,q,ids) or single-value filter given twice - a sort key outside the kind's list,
cannot sort on: <key>
A sort or filter on a field the caller cannot see is a 400 before any read. A caller below staff who filters on a staff-only field gets [<entity>] cannot filter on a field this caller cannot see: <field>. Every sort key has an included path in the container's index policy (azure/modules/cosmos-nosql.bicep), because Cosmos cannot serve an ORDER BY on an unindexed path.
| Shape | Wire | Rule |
|---|---|---|
| exact | field=value |
A repeatable filter takes several values, at most 100, matched as in. Others take one. |
| range |
fieldStart, fieldEnd
|
One YYYY-MM-DD calendar day each, UTC. Start is from 00:00:00.000, end to 23:59:59.999, so the end day is included. A bad day is a 400. |
| contains | fieldContains |
Case-insensitive substring. Trimmed; an empty value is ignored; over 200 characters is a 400. |
| presence | hasField |
true or false only, else 400. true means the field holds a value other than null or ''; false is the exact complement. |
| flag | field |
true or false only, else 400 <field> takes only true or false. false also matches a row where the field is missing. |
Every clause honours the record's own dial on the field. A record that hides a field below the caller's level never matches a filter on it, so count=true cannot reveal that value.
On a Cosmos read the filter is CONTAINS(c.<field>, text, true), a substring match. On an index path it is the word match in Search-Query-Construction "and[nameContains] matches a fragment of the name".
Two filters use the index match. Documents displayNameContains matches displayName, documentFileName and fileNameTokens; projects nameContains with q matches name and nameTokens.
A filter the documents index has no column for is a 400, cannot match text in <field> here.
On /projects, /updates and /notifications, a list with q reads at most 250 ranked ids from the index and pages them in memory, so its total is capped at 250. Filters with an index column also narrow the index request. Every filter, range, contains and presence clause is then applied again in the Cosmos re-read of those ids, with the dials.
A list without q is read from Cosmos alone. /documents is described above.
Default order name asc. Sort keys: name, dateUpdated, proponentName, projectType, sector, region, currentPhaseName.name, eacDecisionSort. Phase sorts on the phase's name, not its id.
eacDecisionSort holds eacDecision.name. It is a separate field so that a project with no decision does not gain an eacDecision object that readers show as a decision. src/scripts/backfill-project-sort-fields.js fills it on rows written earlier.
| Filter | Takes |
|---|---|
projectType, region, projectState, proponentName, sector, eacDecision, currentPhaseName, CEAAInvolvement
|
repeatable |
regionalDistrict, municipality, electoralDistrict
|
one value |
decisionDateStart, decisionDateEnd, dateUpdatedStart, dateUpdatedEnd
|
ranges |
nameContains |
contains, on name
|
The last three repeatable filters name the List ref and match its _id. sector is an exact match. projectType matches both stored spellings (see Search-Index-Reference on PROJECT_TYPE_SPELLINGS). With q, nameContains uses the index word match and the Cosmos re-read of the ranked ids still applies the substring check. Without q it is a substring match on Cosmos.
pcp is repeatable and filters on the project's comment periods: open, upcoming, closed or pending. pending is a period that starts within the next 7 days. The result holds projects with a period in any named status that the caller may read. Another value is a 400, pcp takes open, upcoming, closed, pending. GET /search/counts does not accept pcp.
Cosmos drops a row that lacks its ORDER BY field, so upsert writes every sort field it can fill (sortFieldFills, src/repositories/projects.js):
-
proponentName,projectType,sectorandregionas null when absent -
dateUpdatedfromdateAddedas an ISO time, or null whendateAddedis absent or does not parse -
currentPhaseNameas{_id: null, name: null}
A field the row already holds, null included, is left alone.
Default order dateAdded desc. Sort keys: dateAdded, dateUpdated, publishDate, headline, type, pinned, status. The mirror stores pinned and complianceAndEnforcement as booleans (false when Eagle sets none) and status as null when there is none. src/scripts/backfill-update-sort-fields.js fills rows written earlier.
| Filter | Takes |
|---|---|
project |
alias of projectId, the Eagle project id as stored; repeatable, at most 100 ids |
type |
repeatable |
dateAddedStart, dateAddedEnd
|
one YYYY-MM-DD day each; the end day is included |
complianceAndEnforcement, hasDocumentUrl
|
true or false
|
A null documentUrl and an empty string both count as no document. Callers below staff see published rows only.
Default order notificationReceivedDate desc. Sort keys: notificationReceivedDate, name, type, region, decision, proponent, subType.
type, region and decision are repeatable. pcp takes one value, and none is a real value. A notification has no project axis, so role ACL alone decides which rows a caller sees.
GET /search accepts dataset values User, Group, Inspection, InspectionElement and InspectionItem. These are Cosmos list reads of the Eagle mirrors under the caller's access, not index queries. They take no keywords: q answers 400.
-
Userhas no project axis, so aprojectfilter answers no rows. -
and[inspection]filtersInspectionElementandInspectionItemby the parent inspection's Eagle id. -
and[element]filtersInspectionItemby its element.
dataset=Item fetches one record by Eagle id. It requires _id and _schemaName and takes nothing else. _schemaName is one of the model names above, or Comment, CommentPeriod, ProjectNotification or RecentActivity. It answers a bare array of zero or one record, not the search envelope. _schemaName on any other dataset answers 400.
and[eaoStatus] applies to dataset=Comment and takes Pending, Published, Rejected or Deferred, comma-separated for several. Any other value answers 400.
Every search response carries an x-total-count header with the measured total, the same number as meta[0].searchResultsTotal. On dataset=Item it is the length of the array. The header is exposed to CORS, and HEAD /search returns the headers with no body.
These routes take passiveAuthMiddleware.strict and credentialsMiddleware, the same as GET /documents. strict answers a rejected Bearer token with its 401 instead of serving the anonymous view. GET /search takes the lenient passiveAuthMiddleware. Each list route has a by-id route at /:id.
| Route | Filter |
|---|---|
GET /users |
none |
GET /groups |
project |
GET /inspections |
project |
GET /inspection-elements |
inspection |
GET /inspection-items |
inspection, element
|
Each sort key is in the container's index policy.
| Route | Sort keys |
|---|---|
GET /comment-periods |
dateStarted, dateCompleted, isPublished
|
GET /organizations |
name, companyType, companyLegal
|
GET /users |
displayName, orgName, email, phoneNumber
|
GET /groups |
name |
companyLegal on organizations and email and phoneNumber on users are staff-only. A caller below staff who sorts on one gets a 400, for example [users] cannot filter on a field this caller cannot see: email.
By-id reads of groups take ?project=. The other by-id reads of inspection kinds take ?inspection=, the partition key.
- Environment Reality & Gotchas
- Architecture
- Seeding — filling Cosmos from Track and Eagle
- Frontend — React shell: screens, routes, sign-in gate
- Sources and Status — every source, one state
- Search & Retrieval — overview
- Search Index Reference
- Search Query Construction
- Deep Search Queries — passages, chunk filters, degraded modes
- Native List Reads — native list endpoints and mirror list reads
- Chunk Parent Fields — parent fields copied onto chunks
- Chunk Parent-Field Backfill — roll the fields out, repair
- 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
- API Gateway — APIM, Front Door edge secret, caller IP
- 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 — rejected
- 006: AI Summarization over BM25
- 007: Service-to-Service Credentials
- 008: Audit and Usage Analytics Store
- 009: Track Feed
- 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
- Developer Brief: Digital File Library
- Developer Brief: AI Classifier Tool
- Developer Brief: EPIC System Integration
- Managing Government Information Policy 2022
- Guidelines on Documenting Government Decisions 2019
- AICT Guidelines
- Per-Document Data Format
- Per-Document Generator
- Document Types Draft
- DFL Reference Metadata
- Act and Legislation Full Text
- ORCS Filing Reference Sheet