OpenAPI Storage convergence and Direct progress¶
Motivation¶
A live Azure Container Apps fast deployment preserved an empty STORAGE_ACCOUNT_NAME on the API, worker, beat, and terminal sidecars. The next automatic AKS OpenAPI deployment propagated that empty value as ELB_STORAGE_ACCOUNT, so every metadata read and query upload addressed https://.blob.core.windows.net and timed out. The same live run showed the database manager rendering the previous completed S3 copy count (731 / 731 files) while a new NCBI Direct generation was actually downloading 0 / 85 archives.
User-facing change¶
- Fast deployments restore the non-secret Storage account coordinate on every Bicep-owned runtime sidecar.
- OpenAPI automatic, manual, task, and manifest boundaries refuse to deploy with an empty Storage account instead of creating a broken workload.
- Manual OpenAPI deploys recover older UI payloads from the platform Storage environment.
- Active NCBI Direct updates render pending-generation archive progress rather than stale progress from the currently active S3 generation.
- After a worker revision replacement, the orphan reconciler refreshes active Direct archive counters from the surviving indexed AKS Job, so progress remains live instead of freezing until promotion.
- The confirmation distinguishes NCBI's database-volume count from the additional pinned taxonomy bundle, explaining why
core_ntshows 84 database archives but the indexed Job tracks 85 total archives. - The target deployment enables Auto oracle execution with current-caller RBAC revalidation. The existing enabled preference is scoped to
core_ntonelb-cluster-01only. - External/Service Bus result downloads combine the captured relative blob path with the job's durable flat or date-tiered results prefix. Missing blobs fall back to the live OpenAPI stream instead of escaping the response as HTTP 500.
- Background result manifests include only parseable BLAST output files; Storage directory markers, metadata, and runtime logs no longer appear in the user result list.
- Pod-log persistence preserves the external job's trusted canonical columns while merging nested progress payloads. It no longer clears the Storage account or degrades
blastn - core_nttoblast, so the same finalizer pass can build result artifacts. - Redis drain-lock disconnects during a sidecar revision replacement still fail closed and rely on the lease TTL, but log the exception type without traceback telemetry so expected coordination recovery no longer appears as an App Insights exception.
- Failed Auto warm retries use Foreground deletion and wait up to 120 seconds for current Jobs/pods and legacy DaemonSets/pods to disappear before recreating same-name resources. A classified delete, verification, or timeout failure returns
partialand blocks recreation, preventing terminating old pods from overlapping a retry on the same node-local path. Manual release remains non-blocking for compatibility. - Warmup cache reuse and completion now require a full
blastdbcmd -entry allrecord-stream probe in addition toblastdbcmd -info. A stale-generation file that leaves metadata readable but corrupts record access invalidates the marker and fails the shard instead of allowing Auto oracle or BLAST to discover it later. - Oracle terminal failure captures the first failed Job's final 20 log lines before cleanup, sanitises them, and applies the existing 300-character durable error cap. Diagnostic retrieval is best-effort and bounded to one Job, so an unavailable log endpoint cannot prevent cleanup or terminal state.
- Monitor snapshot refreshes classify the repository's open AKS circuit-breaker signal as transient. Repeated
ClusterApiUnreachablestale-cache fallbacks retain one diagnostic stack per dedup window and a counter for every failure without creating an App Insights exception row every five-second poll.
API, deployment, and UI summary¶
scripts/dev/quick-deploy.shupsertsSTORAGE_ACCOUNT_NAMEfrom the validated azd target on API, worker, beat, and terminal exact-container patches. An empty local value remains a no-op and cannot erase a live coordinate.build_auto_openapi_payloadskips enqueue when no Storage account can be resolved.- The manual deploy route resolves Storage account and resource group fallbacks and returns
missing_storage_accountbefore enqueue when no target exists. deploy_openapi_serviceandbuild_manifestsfail before Azure or Kubernetes mutation when the Storage account is empty.BlastDbRowpreferspending_generation.succeeded_archives / archive_countfor an activencbi-directgeneration; existing server-copy and AKS-fanout progress contracts are unchanged.- The orphan reconciler updates only active Direct counters after revalidating operation owner, generation ID, and Job name under Blob ETag CAS. A concurrent replacement operation wins without being overwritten.
- The offline result streamer validates the resolved job-owned path before opening Storage, avoids double-prefixing already container-relative paths, and converts only
BlobNotFoundinto the existing OpenAPI fallback signal. Authorization and network failures remain visible. - No Storage network rule, SAS flow, Azure resource, role assignment, Service Bus target, or dependency changed.
Validation evidence¶
- Focused Direct update, ownership, recovery, readiness, Auto warm, and Auto oracle baseline:
169 passed. - Quick-deploy coordinate regression:
30 passedbefore the wider boundary fix. - OpenAPI Storage boundary regression suite:
117 passed. - Direct progress and confirmation component regression:
10 passed. - Live OpenAPI repair task completed in 39 seconds with one ready replica and
ELB_STORAGE_ACCOUNT=stelbdashboardcyutlgcnv3; subsequent Service Bus submission returned OpenAPI HTTP 202. - Live
core_ntDirect update started at2026-08-29T15:41:38.840Z: generationncbi-direct-20260819-cab30d18c360, task03fb194f-c3fc-4801-b1ce-5e6422f4e848, 85 indexed completions, parallelism 4, and zero initial failures. - The indexed download completed 85/85 archives with zero failures at
2026-08-29T17:36:36Z, taking 6,889 seconds (1 hour 54 minutes 49 seconds). The post-grace reconciler validated and promoted 766 files / 295,616,990,673 bytes at2026-08-29T17:43:21.770793Z; click-to-promotion was 2 hours 1 minute 43 seconds. The previous generation stayed active until that atomic commit, and the Direct Job, ConfigMap, and pods were then removed. - The promoted generation exposes all shard layouts
[1, 2, 3, 4, 5, 6, 8, 10],shard_source_versionequals the new generation,shards_stale=false, and the old oracle is explicitlystalerather than silently reused. - A uniquely identified Service Bus probe was enqueued at
2026-08-29T15:42:51Zand accepted as OpenAPI jobc1b89b30473fat2026-08-29T15:43:18Zwhile the Direct update was active (27 seconds enqueue-to-acceptance). Its ConfigMap pinned the previous active database version. It ran from2026-08-29T16:21:12.747364Zto2026-08-29T16:24:24.386169Z(about 3 minutes 12 seconds runtime; about 41 minutes 33 seconds enqueue-to-completion under the existing backlog) and completed all 10/10 BLAST pods. - The same Service Bus probe exposed a date-layout result download HTTP 500. After repair, both external and dashboard file routes returned HTTP 200 with the expected 396-byte gzip file. Rebuilding its idempotent artifacts reduced the dashboard result list from 69 mixed Storage entries (33 zero-byte markers) to one merged BLAST output with no metadata/log/directory entries.
- Service Bus health after the backlog drained reported active
0, scheduled0, request DLQ0, completion pending/DLQ0, and outbox pending0. - Auto warm task
c522f574-33ee-4228-b652-c4c14d60bb78started 51 seconds after promotion. All ten node Jobs targetncbi-direct-20260819-cab30d18c360; at the 11-minute checkpoint all were active with zero failures/restarts, node CPU was 38-44%, and node memory was 17-18%. Auto oracle was correctly blocked aswarmup_generation_stalewith zero retry-budget consumption. - The forced Auto warm retry completed 10/10 Jobs with zero failed Jobs in 23 minutes 48 seconds by Kubernetes Job conditions. The first Auto oracle run and its automatic retry both isolated shard
02:blastdbcmdfailed withFrame type=eFrameClassMember, Member name=titlewhile the warmup's metadata-only-infoprobe had passed. - A temporary node-pinned diagnostic proved only
core_nt.21.nhrwas corrupt. The official 2,792,299,029-byte archive matched MD5bcabcef7f8895f0541b082481fbe8397and enumerated successfully. The official member and active immutable Storage blob both had 310,982,213 bytes and SHA-2560440277357658dc063cd24da3377681a5752125c621544fc09928dd1bfbb894a; the node-local file had 315,191,936 bytes and SHA-2569ce542274025faccbe31f7541155faceb406ead3a453f173df9d8975c5974cf3, exactly matching the previous legacy Storage blob. Kubernetes events showed the old warmup pod receiving termination at18:20:24Zand its replacement starting at18:20:36Z, confirming cross-generation write overlap during background deletion. blastdbcheckwas rejected as the warmup gate after it reported one error for each healthy standalone volume and eight errors for the healthy eight-volume shard alias. After atomically replacing the stale node-local file from the verified active-generation blob, fullblastdbcmdenumeration succeeded in 38 seconds; the same enumeration had failed in about 30 seconds before repair. The record-stream probe therefore adds a measured integrity gate without relying on the false-positive checker.- The repaired automatic oracle run
20260829192214-0757dc7bwas dispatched on the first eligible reconcile at19:22:14Zand published all 10/10 parts forncbi-direct-20260819-cab30d18c360at19:23:24Z. The task completed in 68.61 seconds with zero cleanup errors, and automation reset from two failures tofailure_count=0,status=ready, and no pending retry. - Click-to-final-oracle elapsed time was about 3 hours 41 minutes 46 seconds, from the Direct update request at
15:41:38.840Zthrough oracle publication at19:23:24.948Z. This includes the 2 hour 1 minute 43 second atomic update/promotion, the initial Auto warm failure and 23 minute 48 second successful warm retry, two failed oracle attempts, diagnosis, repair, and the 68.61 second successful automatic retry. - One Service Bus health sample at
19:23:17Zfailed closed withopenapi_pod_check_failedduring concurrent OpenAPI traffic. The Deployment, pod, Service, internal LoadBalancer, and endpoint remained Ready; repeated sibling probes before and after were HTTP 200. A direct in-pod/v1/readycheck at19:25:22Zreturned HTTP 200 withk8s_api=ok,openapi_pod=ok, andworkload_pool=okfor all 10 nodes, confirming automatic recovery rather than a persistent admission fault. - The four-hour App Insights review found no request failure after the two pre-fix result-download errors at
16:41Z, and no application exception after18:44Z. It also exposed 22 duplicate monitor-refresh exception rows in 106 seconds while the AKS circuit breaker was open; the transient classifier did not recognise its localClusterApiUnreachabletype. The classifier fix preserves graceful stale responses and suppresses only repeated traceback telemetry. Ten create-before-apply oracle Job 404 dependencies and Storage/Table not-found probes were expected idempotent control flow. - Final full backend suite after the warmup integrity and oracle diagnostic fixes:
5545 passed, 4 skipped;uv run ruff check apipassed. Focused warmup cleanup/integrity sweep:126 passed. Complete oracle-focused sweep:140 passed. Full frontend suite: 109 files / 988 tests and production build passed. Persona/deployment contract sweep:138 passed. The focused result pipeline sweep passed 196 tests, and the complete Service Bus task suite passed 134 tests. Documentation frontmatter and strict MkDocs builds passed. - Commit
7d4bdb25was pushed toorigin/main. GitHub Tests, Build Images, and Publish Docs completed successfully; the isolated pre-push mirror also passed5545backend tests with four expected skips, Ruff, the documentation frontmatter guard, and strict MkDocs. - GitHub deployment variables target a different environment (
ambitiousisland/acrelbdashboard3abp67bppe), so the automatic deploy correctly remained skipped and was not repurposed. The validateddelightfulpebbleenvironment was explicitly targeted through its local azd coordinates and deployed from the pushed commit with immutable tagdeploy-7d4bdb2on2026-08-30. - The full deployment built
elb-api,elb-prepare-db,elb-frontend, andelb-terminalin the target ACR, then sequentially converged API, worker, beat, frontend, and terminal. Active revisionca-elb-dashboard--env-terminal-1788088435-1502owns 100% traffic. API/worker/beat use digestsha256:8be5a624…, frontendsha256:b712c78d…, and terminalsha256:000562bb…. - Post-deploy
/api/healthand/api/health/readyreturned HTTP 200 with Redis, managed identity, terminal, and Storage ready. The SPA bundle contains commit stamps7d4bdb25/7d4bdb2; root and deep-link routes returned HTTP 200. Authenticated DB state retained the promoted generation and ready 10/10 oracle, while unauthenticated/api/mereturned HTTP 401. - All six containers were Ready/Running with zero restarts.
STORAGE_ACCOUNT_NAMEandPLATFORM_ACR_NAMEconverged on API, worker, beat, and terminal; the API points Direct work atelb-prepare-db:deploy-7d4bdb2. Workload Storage remainedpublicNetworkAccess=Disabled,defaultAction=Deny, with no IP rules. - Service Bus remained effectively enabled with zero active, scheduled, request-DLQ, completion, or completion-DLQ messages. Because AKS was intentionally Stopped, drain and Auto warm/oracle ticks failed closed as
cluster_context_unavailable,cluster_not_ready, andaks_unavailablewithout consuming work or retry budget. After the rolling revision's expected Redis reconnect, the stable App Insights window had zero 5xx requests, exceptions, or severity-3+ traces; its only unsuccessful request was the deliberate unauthenticated/api/meprobe returning HTTP 401.