Repository navigation
Chunk Parent Fields
The chunks index repeats five fields of each chunk's parent document row. A chunk filter then runs on the chunk alone, with no join across indexes.
Rolling the fields into an environment: Chunk-Parent-Field-Backfill. Index definitions: Search-Index-Reference. How queries use the copies: Search-Query-Construction.
| Field | Type | Flags | Wire key |
|---|---|---|---|
typeId |
Edm.String |
filterable, facetable | and[type] |
milestoneId |
Edm.String |
filterable, facetable | and[milestone] |
projectPhaseId |
Edm.String |
filterable, facetable | and[projectPhase] |
documentAuthorTypeId |
Edm.String |
filterable, facetable | and[documentAuthorType] |
projectId |
Edm.String |
filterable, not facetable | none |
parentFieldsVersion |
Edm.Int32 |
filterable, retrievable, not facetable, not sortable | none |
- Each copy is a straight copy of the same-named column on the parent document row.
-
CHUNK_PARENT_FIELDSinsrc/repositories/chunks.jsis the one list every writer reads;CHUNK_PARENT_LIST_REFSis its four List-ref subset. -
DOCUMENT_FACETSinsrc/search/eagle-query.jsmaps the four wire keys onto the List refs for bothdataset=Documentanddataset=DocumentChunk. The same key means the same field on either dataset. -
projectIdscopes access on every chunk read. Chunks are partitioned bydocumentId, so a document pushed under a new project leaves its chunks on the old scope until they are re-stamped. -
parentFieldsVersionis a stamp, not a copy.parentFieldsOf()writes it on every stamp fromCHUNK_PARENT_FIELDS_VERSION. -
parentStampedAtorders writers and answers no query.chunks.jsondoes not declare it anddemi-chunks-dsdoes not project it, so it costs no index PUT.
The version exists because four null List refs cannot tell "stamped, parent has no List refs" from "never stamped". Many prod documents have no List refs, so a null count never reaches zero.
-
chunkMatchesParent()treats a chunk whose version is absent or below the constant as a mismatch, whatever its values hold. - The search-side completeness probe counts
parentFieldsVersion lt CHUNK_PARENT_FIELDS_VERSION or parentFieldsVersion eq null. -
projectIdis not versioned: every ingest path writes it, so a stale value fails the plain value comparison.
The copy is a snapshot, so a writer must re-stamp it when the parent changes.
| Event | What stamps the chunks |
|---|---|
| Ingest | The chunk build copies the values off the document it is chunking. |
| Ingest end | The document is re-read and compared with the snapshot; any change enqueues a re-stamp. |
Parent write that moves a value (eagle-api push, staff PUT /documents/:id) |
A re-stamp job on the CHUNK_RESTAMP_QUEUE storage queue. |
| Missed or lost re-stamp |
backfill-chunk-parent-fields.js --pending --live. |
- The ingest-end check catches a re-type that lands halfway through a long stream. The NDJSON path refreshes its values every 25 batches (5,000 chunks), which narrows the window but does not close it.
- The stream latches every change it sees, so a value that moves and moves back is raised too. A failed ingest-end check logs a line and never returns a 500, because the chunks have already landed.
- The re-stamp is one bulk PATCH per 100 chunks with 429 backoff, riding the indexer's
_tshigh-water mark. A 6,000-chunk document takes minutes, longer than eagle-api's 10-second push timeout, so it runs on the queue (src/jobs/restamp-chunks.js). - The
read[]propagation stays on the request, because it is a visibility boundary and a queue would leave a window where the index disagrees with Cosmos. - The message carries the document and project ids only. The handler re-reads the row, so a redelivered message stamps the later state and the handler can throw on a failed patch.
- With no
CHUNK_RESTAMP_QUEUE, the write logs one warning and skips the re-stamp.CHUNK_RESTAMP_INLINE=1restores the inline walk forfunc starton a box with no storage account. - Failure handling for the queue: Bulk-Download-Operations "Poison-queue runbook".
A document row owes a re-stamp from the moment the change is durable. The flag is an outbox.
- Every write that moves a value sets
parentFieldsPending: trueandparentFieldsPendingAton the same upsert that changes the fields, before anything is enqueued. - The re-stamp is dispatched after that write lands, and a landed walk is the only thing that clears the flag. A failed enqueue only logs, because the row already carries the flag.
- Without the flag, a lost re-stamp is invisible: the request answers 200 and a chunk filter matches fewer rows than it should.
- The nightly reconcile counts flagged rows as
parentFieldsPending=Non the linedemi-reconcile-drift-<env>alerts on. -
backfill-chunk-parent-fields.js --pending --livewalks exactly those documents, re-stamps them and clears the flag. - An eagle-api push replaces the whole row and carries the flag across.
- The
documentscontainer's indexing policy inazure/modules/cosmos-nosql.bicepincludes/parentFieldsPending/?. CI never deploys infra; an index-only change can usescripts/apply-cosmos-index.sh, see Operating-DEMI-in-Azure.
-
clearPending()insrc/jobs/restamp-chunks.jsclears it after the queue handler's walk and after the inline walk. - The clear is conditional on the
parentFieldsPendingAttoken, not on the row's_etag. An unrelated write, such as an extraction patch, moves the etag and would refuse the clear for a document whose chunks are already correct. -
documents.freshPendingAt()mints each token strictly later than the one on the row, so two raises in the same millisecond cannot share one. - A refused clear returns
conflict: a newer parent-field change owns the flag and its own re-stamp clears it. The handler logs one info line and completes the message. - A vanished row returns
missing, logged at warn, and also completes. A row raised before tokens existed carries none and clears unconditionally. -
setParentFieldsPending()insrc/repositories/documents.jspatches the row directly, so it needs the row's partition key,projectId. A document with no project holds a JSON-nullprojectId. The writer reads such a row cross-partition and passes its ownprojectIdthrough; an empty string is a real partition and is used as is. - A row purged between that read and the write is left unpatched.
restamp-chunks.jsreadsbody.projectId ?? undefined, so only null or undefined falls back to a cross-partition read.
A write that moves a value rebuilds the whole row from the row it read, and a Cosmos upsert replaces the item. Both paths in src/controllers/nosql/document.js therefore carry IfMatch on the _etag they read.
- A 412 means another writer landed first. The write re-reads the row, rebuilds the change on top of it, mints the token off the stored one and retries, up to three times.
- The guard keeps a flag another walk cleared from being written back. It also keeps a value another writer moved from being reverted with no flag raised.
- After three losses the staff route answers 409 and the push answers 503, the only answer eagle-api's push client retries.
- An ingest stamps
parentStampedAton every chunk it writes: the row'sparentFieldsPendingAtwhere the row owes a re-stamp, the ingest's start instant otherwise. - A walk patches a chunk only if the chunk's token is older than the walk's. An unstamped chunk is older than every walk.
- A chunk stamped by a newer walk keeps its values and counts as
skippedNewer, an outcome and not a shortfall. The token stops a re-stamp that began before an ingest from overwriting the chunks that ingest wrote.
Adding a field touches five places:
-
CHUNK_PARENT_FIELDSinsrc/repositories/chunks.js. - The field definition in
azure/search/indexes/chunks.json. - The
SELECTinazure/search/datasources/demi-chunks-ds.json. - The vis catalog entry in
src/vis/catalog/chunks.js. -
CHUNK_PARENT_FIELDS_VERSION, bumped.
Tests pin the list against chunks.json, demi-chunks-ds.json and the vis catalog (test/azure/search-datasource-columns.test.js), so leaving out one of the first four fails a test.
- The bump is what puts every stamped chunk back in scope.
backfill-chunk-parent-fields.jsre-stamps a chunk only when its stamped version is below the constant. - Skip the bump and the backfill still runs, exits 0 and reports success, but re-stamps nothing, so the new field stays null on every row.
- A bump with no change to what
parentFieldsOf()stamps has nothing to backfill. - Skip the bump only for a field every ingest path already writes, as with
projectId: the value comparison catches a wrong value, and a bump would force a corpus-wide re-stamp.
- 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