Skip to content

Eagle Search Archive

Daniel Truong edited this page Oct 3, 2026 · 5 revisions

Eagle Search Archive

digitalspace/eagle-search is archived and read-only. This page keeps the facts that lived only in that repository. How search runs is on Eagle Search; test search and extraction are on Eagle DEMI.

Estate

Nothing runs. Deleted from rg-eagle-search-prod (subscription c4b0a8-prod, be5924ac-1083-4a1b-be92-7b444882cfd9): eagle-search-prod (the AI Search service) and its private endpoint pe-eagle-search-prod, eagle-search-api-prod, eagle-search-insights-prod, eagle-search-logs-prod, and its smart-detector alert rule and action group. Also deleted: the eagle-chunks, eagle-documents and eagle-projects indexes on demi-search-prod (rg-demi-prod), and, in 6cdc9e-prod, deploy/eagle-search-sync and cronjob/eagle-search-reindex. rproxy's /eagle-search/ location still exists in eao-nginx/helm/rproxy/values-prod.yaml but proxies to a localhost:9999 sentinel, so it returns 502.

Two resources remain in rg-eagle-search-prod, both reused by DEMI: plan-eagle-search-prod, the App Service plan that now hosts demi-api-prod, and eagle-search-identity-prod, the identity demi-search-prod runs its indexers as (holds Cosmos Data Reader and Cosmos DB Account Reader on demi-cosmos-prod).

Gone (test). eagle-search-api-test, the eagle-search-test AI Search service, pe-eagle-search-test, the extractor Function App and its queue, and the sync worker release in 6cdc9e-test. Left in c4b0a8-test-rg: eagle-search-identity-test, eagle-search-logs-test, eagle-search-insights-test.

There is no rollback to eagle-search: the index, the service and the sync workers that kept it current are all deleted. A revival means unarchiving the repo and rebuilding the estate from scratch.

Index schema decisions

Bicep creates the search service but cannot create definitions inside it; the JSON in azure/search/indexes/ is the only record.

  • read is the ACL field and the name is load-bearing. Collection(Edm.String), filterable, never sortable. Naming it read rather than allowed_roles let eagle-demi/src/helpers/access-odata.js be copied byte-identical.
  • Every resolved display name carries an *Id companion; filters use the id. Name fields are filterable: false, because filtering an id against a name field matches nothing and renders as an empty table, not an error.
  • Dates are Edm.DateTimeOffset, not epoch seconds. An omitted date sorts last on desc.
  • centroid is Edm.GeographyPoint, sortable: false, facetable: false (creation 400s otherwise). Takes GeoJSON [lng, lat], what MongoDB stores.
  • documentFileName has a custom filename analyzer: PatternTokenizer on [^A-Za-z0-9]+ plus lowercase, because en.microsoft does not split 52_KsiLisimsLNG_25_Conclusions_Final.pdf.
  • content is retrievable: false, not stored: false. stored: false on a string field is rejected on api-version 2024-07-01 and 2026-04-01; it exists for vector fields only. The value is stored; highlighting via @search.highlights.content works.
  • Plain BM25: no scoring profile, no semantic config, no vector fields. semantic is explicitly null so a later edit has to be deliberate.
  • Chunk keys are ${documentId}_p${pageNumber}_c${chunkIndex}. AI Search keys allow only letters, digits, -, _ and =, so DEMI's ${id}::p${n}::c${i} is illegal here. chunkIndex is document-global. Never re-derive a key to delete by it; read it back.

Operational lessons

INGEST_KEY and whole-collection PUTs. siteConfig.appSettings is a whole-collection PUT: an infra deploy that does not supply INGEST_KEY blanks it, and one that mints a fresh key rotates the live credential while the OpenShift worker still holds the old one. Either way the worker 401s, the index goes stale, and nothing alerts; what-if masks @secure() values on both sides of the diff. Read the current key back from the OpenShift secret before redeploying, and guard the readback with : "${VAR:?readback returned nothing}", because an empty readback sets the variable to empty, not unset. The admin header is x-ingest-key; x-api-key returns 401 {"error":"unauthorised"}, which reads like a bad key.

Config propagation lags that look like a failed deploy. App Service CORS and Front Door rule sets keep enforcing the previous config for up to about three minutes after ARM reports Succeeded. A preflight in the window returns 400 with no Access-Control-Allow-Origin; a deep link returns index.html with a 404 status. Poll; purge fixes neither.

Cross-service grants answer 404, not 403. An App Service authenticating to a search service owned by another identity fails first on the missing index, not on auth. A 404 for The index 'eagle-projects' for service '…' was not found. means auth worked.

Private DNS for a freshly created private endpoint arrives by policy, minutes after the deployment returns. Until then the endpoint exists and its name does not resolve. Check with az network private-endpoint dns-zone-group list -g <rg> --endpoint-name <pe>.

Two counts that are not drift. The raw index total (/admin/index-stats) includes non-public rows; the anonymous total through the API is what survives the read[] filter. Label which one a number is.

Where things went

  • Search itself: DEMI (eagle-demi). Production search is served by demi-api-prod (SEARCH_API_PATH: /demi-search). eagle-public has no eagle-api fallback. DEMI derives chunk visibility from the parent document at query time, so resync.js, stamp.js and awaitParents were not ported.
  • The ACL gate: eagle-demi/src/scripts/probe-acl.js, replacing scripts/check-acl.js. The browser gate (scripts/check-frontend.py) has no successor script: check the deployed app in a real browser and watch where the search calls go, because curl cannot answer that question.
  • Shared Front Door, UAMI and observability Bicep: digitalspace/eagle-edge, including scripts/deploy-infra.sh, which reads INGEST_KEY back instead of blanking it.
  • Extraction: DEMI. The in-repo extractor (POST /ingest/markdown, extract.py) was never finished. The eagle-chunks corpus came out of DEMI's Cosmos.

Clone this wiki locally