From c36109b0c96c47de1663e6abda0e298dcb464541 Mon Sep 17 00:00:00 2001 From: DeWitt Gibson Date: Tue, 23 Jun 2026 18:36:54 -0700 Subject: [PATCH] fix: app start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes two distinct failures that were blocking the Azure deployment pipeline, discovered by running it end-to-end against the staging environment. ### 1 · Key Vault seeding failed with `ForbiddenByRbac` Step 1 of the deploy creates the pipeline SP's **Key Vault Secrets Officer** role assignment; step 3 then writes secrets. Newly created Key Vault role assignments take several minutes to become effective on the **data plane**, so the seed ran too soon and got `ForbiddenByRbac` (Azure's error even hints at propagation time). The role assignment itself is correct — the identity matched the calling SP's object id — it just wasn't live yet. - `seed-keyvault.sh`: added `wait_for_keyvault_rbac`, a probe that retries a throwaway secret write (20s × up to 30 ≈ 10 min) until data-plane access is live, then proceeds. If it never propagates, a final unsuppressed attempt surfaces the real error. - `infra/main.bicep`: the pipeline role assignment scopes to an `existing` vault reference, so Bicep inferred no dependency on the module that *creates* the vault. Added an explicit `dependsOn: [keyVault]` so the assignment can't race ahead of vault creation on a fresh resource group (e.g. first prod deploy). ### 2 · `az acr build` failed with pnpm 401 The core and frontend image builds failed with `ERR_PNPM_FETCH_401` against the Azure Artifacts **node** feed. The `.npmrc` points pnpm at the node feed, but both Dockerfiles wrote the `_authToken` line for the **python** feed path. npm matches auth tokens by exact registry path, so the node feed received no auth header. - `packages/core/Dockerfile`, `packages/frontend/Dockerfile`: changed the injected `_authToken` path from `…/_packaging/python/npm/registry/` to `…/_packaging/node/npm/registry/` to match `.npmrc`. (The ai-service Dockerfile is unaffected — it correctly uses the python PyPI feed.) ## Testing - `az bicep build infra/main.bicep` — compiles clean. - `bash -n seed-keyvault.sh` — syntax OK. - Verified the live staging vault role assignment is held by the correct SP object id. - The Dockerfile auth change is build-time-only (requires the Azure Artifacts token); validated via the next pipeline run. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- azure-pipelines/scripts/seed-keyvault.sh | 25 ++++++++++++++++++++++++ infra/main.bicep | 4 ++++ packages/core/Dockerfile | 2 +- packages/frontend/Dockerfile | 2 +- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/azure-pipelines/scripts/seed-keyvault.sh b/azure-pipelines/scripts/seed-keyvault.sh index 3112d19..9432746 100644 --- a/azure-pipelines/scripts/seed-keyvault.sh +++ b/azure-pipelines/scripts/seed-keyvault.sh @@ -57,6 +57,31 @@ or_placeholder() { echo "Seeding secrets into Key Vault '$KEY_VAULT_NAME'..." +# The deployment step that runs just before this one (re)assigns this principal's +# 'Key Vault Secrets Officer' role. Newly created Key Vault role assignments can +# take several minutes to become effective on the *data plane*, so a write that +# happens too soon fails with ForbiddenByRbac. Probe with a throwaway secret +# until the data-plane access is live (or give up after ~10 min and surface the +# real error). +wait_for_keyvault_rbac() { + local attempt=1 max=30 + while ! az keyvault secret set --vault-name "$KEY_VAULT_NAME" \ + --name rbac-probe --value ok --output none 2>/dev/null; do + if [ "$attempt" -ge "$max" ]; then + echo "ERROR: Key Vault '$KEY_VAULT_NAME' data-plane RBAC not effective after $((max * 20))s." >&2 + # Final attempt without suppression so the real error reaches the log. + az keyvault secret set --vault-name "$KEY_VAULT_NAME" --name rbac-probe --value ok --output none + exit 1 + fi + echo " … waiting for Key Vault RBAC to propagate (attempt $attempt/$max)" + sleep 20 + attempt=$((attempt + 1)) + done + echo " ✔ Key Vault data-plane access confirmed" +} + +wait_for_keyvault_rbac + # --- Connection secrets fetched from the Azure control plane (work even when the # data plane is private) --- MONGO_URI=$(az cosmosdb keys list --name "$COSMOS_ACCOUNT" --resource-group "$RESOURCE_GROUP" \ diff --git a/infra/main.bicep b/infra/main.bicep index 901b337..dd046b2 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -426,6 +426,10 @@ resource kvPipelineRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = i principalId: pipelineSpOid principalType: 'ServicePrincipal' } + // keyVaultRes is an 'existing' reference, so Bicep infers no dependency on the + // module that creates the vault. Make it explicit so the assignment can't race + // ahead of vault creation on a fresh resource group (e.g. first prod deploy). + dependsOn: [keyVault] } resource keyVaultRes 'Microsoft.KeyVault/vaults@2023-07-01' existing = { diff --git a/packages/core/Dockerfile b/packages/core/Dockerfile index c8f670e..9abd0d6 100644 --- a/packages/core/Dockerfile +++ b/packages/core/Dockerfile @@ -19,7 +19,7 @@ RUN echo 'node-linker=hoisted' >> .npmrc # Inject Azure Artifacts auth token; strip it after install so the layer has no creds ARG AZURE_ARTIFACTS_TOKEN RUN if [ -n "${AZURE_ARTIFACTS_TOKEN}" ]; then \ - echo "//pkgs.dev.azure.com/agentaflow/agentbase/_packaging/python/npm/registry/:_authToken=${AZURE_ARTIFACTS_TOKEN}" >> .npmrc; \ + echo "//pkgs.dev.azure.com/agentaflow/agentbase/_packaging/node/npm/registry/:_authToken=${AZURE_ARTIFACTS_TOKEN}" >> .npmrc; \ fi RUN pnpm install --frozen-lockfile 2>/dev/null || pnpm install RUN sed -i '/_authToken/d' .npmrc diff --git a/packages/frontend/Dockerfile b/packages/frontend/Dockerfile index cd30d9b..16ed20c 100644 --- a/packages/frontend/Dockerfile +++ b/packages/frontend/Dockerfile @@ -12,7 +12,7 @@ COPY packages/shared/package.json packages/shared/ # Inject Azure Artifacts auth token; strip it after install so the layer has no creds ARG AZURE_ARTIFACTS_TOKEN RUN if [ -n "${AZURE_ARTIFACTS_TOKEN}" ]; then \ - echo "//pkgs.dev.azure.com/agentaflow/agentbase/_packaging/python/npm/registry/:_authToken=${AZURE_ARTIFACTS_TOKEN}" >> .npmrc; \ + echo "//pkgs.dev.azure.com/agentaflow/agentbase/_packaging/node/npm/registry/:_authToken=${AZURE_ARTIFACTS_TOKEN}" >> .npmrc; \ fi RUN pnpm install --frozen-lockfile 2>/dev/null || pnpm install RUN sed -i '/_authToken/d' .npmrc