Skip to content

feat(infra) Bicep shared-resources scaffold + ADR 0010 subscription guard#27

Merged
jkeeley2073 merged 1 commit into
mainfrom
Dev-BicepSharedResourcesScaffold
May 2, 2026
Merged

feat(infra) Bicep shared-resources scaffold + ADR 0010 subscription guard#27
jkeeley2073 merged 1 commit into
mainfrom
Dev-BicepSharedResourcesScaffold

Conversation

@jkeeley2073
Copy link
Copy Markdown
Contributor

Summary

Track A.3 of the parallel execution plan: Bicep IaC scaffold for the pinwiz.ai shared tier, with ADR 0010 codifying the personal-Azure-only rule and a hard az account show subscription/tenant guard in the deploy script.

This unblocks the Bicep dependency for Gate 1 (Cosmos schema), Track D (Event-driven RAG → Cosmos Change Feed), and Track E (Frontend Cosmos repos) — all of which need the Cosmos account to exist before they can deploy / integration-test against it.

What ships

infra/

  • main-shared.bicep — subscription-scoped entry. Creates rg-pinwiz-shared-{env} with project tags, invokes the shared module.
  • modules/shared.bicep — RG-scoped resources:
    • Cosmos DB Serverless (NoSQL API) — containers added by Gate 1 PR
    • Key Vault (RBAC, purge protection, soft delete)
    • Container Registry Basic
    • Azure AI Search Basic (semantic ranker free tier included)
    • Azure OpenAI account S0 (model deployments deferred — quota provisioning needed; separate PR)
    • Storage Standard LRS, Entra-only (no shared key) with pinwiz-raw / pinwiz-processed / pinwiz-photos blob containers per infra_analysis.md
    • Log Analytics (1 GB/day cap, 30-day retention) + Application Insights (workspace-based)
    • Diagnostic settings on every resource → Log Analytics
    • Optional developer RBAC (KV Secrets Officer, AcrPush, Search Index Data Contributor, Cognitive Services OpenAI User, Storage Blob Data Contributor) — gated on developerObjectId being non-empty in the parameters file
  • main-shared.dev.bicepparam — dev parameters; subscription/tenant IDs committed (identifiers, not credentials)
  • scripts/Deploy-SharedResources.ps1 — deploy orchestrator with the hard guard against EXPECTED_TENANT_ID + EXPECTED_SUBSCRIPTION_ID before any Azure call. Aborts cleanly with the fix command if the active az context is wrong. Supports -WhatIf and -SkipGuard (with unmissable warning).
  • README.md — deploy docs, prereqs, what-if usage, cost expectations (~$90–120/mo for shared tier before model deployments), and explicit "what's NOT in this scaffold" list

docs/adr/

  • 0010-personal-azure-subscription-only.md — codifies the rule alongside the existing ADR 0005. (0005 = own resource groups within the chosen subscription; 0010 = which subscription is even allowed.)
  • README.md — index updated to include 0010.

.github/workflows/

  • bicep.ymlbicep build + bicep lint on main-shared.bicep, modules/shared.bicep, and main-shared.dev.bicepparam on every PR touching infra/**. Uploads built ARM templates as artifacts. Does not authenticate to Azure (OIDC federated credentials are a separate follow-up).

Other

Subscription guard demonstrated

Deploy-SharedResources.ps1 enforces ADR 0010 with a hard guard. From infra/scripts/Deploy-SharedResources.ps1#L60-L100:

$EXPECTED_TENANT_ID       = '9793cd0f-2b27-4757-9986-1f7f1e35864a'  # Earlybird
$EXPECTED_SUBSCRIPTION_ID = '4dce9fdd-ea5f-4f67-9a00-80279e58659d'  # Earlybird personal

# ... az account show check ...

if (-not $contextOk) {
    Write-Host '  +-----------------------------------------------------------+' -ForegroundColor Red
    Write-Host '  |  SUBSCRIPTION GUARD TRIPPED (per ADR 0010)                |' -ForegroundColor Red
    Write-Host '  |  This repo deploys ONLY to the personal Earlybird tenant. |' -ForegroundColor Red
    Write-Host '  |  Refusing to proceed.                                     |' -ForegroundColor Red
    Write-Host '  +-----------------------------------------------------------+' -ForegroundColor Red
    throw 'Subscription guard tripped — aborting.'
}

This is the technical enforcement of the locked feedback memory (feedback_personal_identity_only.md).

Test Plan

  • az bicep build --file infra/main-shared.bicep — succeeds; produces valid ARM JSON
  • az bicep build --file infra/modules/shared.bicep — OK
  • az bicep build-params --file infra/main-shared.dev.bicepparam — OK
  • az bicep lint on both .bicep files — clean (no warnings or errors)
  • Deploy-SharedResources.ps1 syntax validated locally
  • CI (bicep.yml) will re-validate on this PR
  • -WhatIf deployment against the personal Earlybird subscription — pending, run before merge:
    pwsh ./infra/scripts/Deploy-SharedResources.ps1 -Environment dev -WhatIf
    Paste the what-if output as a comment on this PR before merging (per docs/parallel_execution_plan.md §6 quality discipline).

Out of Scope

  • Per-environment ACA Apps and Jobs (main-env.bicep) — ships when Track B / D / E need a deployment target
  • Azure OpenAI model deployments — separate PR; needs quota check + slow provisioning
  • OIDC federated credentials for GitHub Actions — would let the bicep.yml workflow run what-if automatically; separate PR
  • VNet + Private Endpoints / Front Door / WAF — explicitly deferred per docs/infra_analysis.md §7
  • Microsoft Entra External ID tenant provisioning — separate path (portal, not Bicep)

Cost expectations once deployed

Component Monthly
Azure AI Search Basic $74
Cosmos DB Serverless $5–25 (RU-driven; minimal at v1 traffic)
Container Registry Basic $5
Storage Standard LRS $2–5
Log Analytics + App Insights $4–8
Key Vault <$1
Azure OpenAI account (no model deployments) $0
Shared tier subtotal ~$90–120/mo

Per-environment ACA layer adds $3–35/mo when it ships. $400/mo hard cap still in force at the subscription level.

What's next after this merges

Per the parallel execution plan recommended sequence:

  1. Gate 1 — Cosmos schema + repository pattern PR (gates Tracks C / D / E)
  2. Gate 2 — PoliteScraper base class PR (gates Tracks B-mfg, NOT B-OPDB)
  3. Track B-OPDB — first new "scraper" on the Clean Architecture layout

Gate 1 and Gate 2 can run in true parallel after this scaffold lands, since they target different parts of the codebase.

…uard

Track A.3 of the parallel execution plan: Bicep IaC scaffold for the
pinwiz.ai shared tier, with ADR 0010 codifying the personal-Azure-only
rule and a hard `az account show` subscription/tenant guard in the
deploy script that aborts before any Azure call if the active az
context isn't the personal Earlybird tenant + subscription.

Files:

infra/
- main-shared.bicep         Subscription-scoped entry. Creates the shared
                            resource group (rg-pinwiz-shared-{env}) with
                            project tags and invokes the shared module.
- modules/shared.bicep      RG-scoped resources:
                              Cosmos DB Serverless (NoSQL API)
                              Key Vault (RBAC, purge protection)
                              Container Registry Basic
                              Azure AI Search Basic (semantic ranker free tier)
                              Azure OpenAI account (S0; model deployments
                                deferred to follow-up - quota provisioning)
                              Storage Standard LRS (Entra-only, no shared
                                key) with pinwiz-raw / -processed / -photos
                                blob containers per infra_analysis.md
                              Log Analytics (1 GB/day cap, 30-day retention)
                              Application Insights (workspace-based)
                              Diagnostic settings on every resource routing
                                to LAW
                              Optional developer RBAC (KV Secrets Officer,
                                AcrPush, Search Index Data Contributor,
                                Cognitive Services OpenAI User, Storage
                                Blob Data Contributor) gated on
                                developerObjectId being non-empty
- main-shared.dev.bicepparam  Dev environment parameters; subscription /
                              tenant IDs are committed (identifiers, not
                              credentials).
- scripts/Deploy-SharedResources.ps1  Deploy orchestrator. Hard guard
                              against EXPECTED_TENANT_ID +
                              EXPECTED_SUBSCRIPTION_ID before any Azure
                              call. Auto-detects local override
                              parameter file. Supports -WhatIf and
                              -SkipGuard (with unmissable warning).
- README.md                  Deploy docs, prereqs, what-if usage, cost
                              expectations, what's intentionally not in
                              this scaffold (per-env ACA, AOAI model
                              deployments, OIDC for GH Actions, etc.)

docs/adr/0010-personal-azure-subscription-only.md  Codifies the rule
                              alongside the existing ADR 0005 (own
                              resource groups). 0005 is about resource
                              isolation within the chosen subscription;
                              0010 is about which subscription is even
                              allowed.
docs/adr/README.md           Index updated to include 0010.

.github/workflows/bicep.yml  CI workflow: bicep build + lint on
                              main-shared.bicep, modules/shared.bicep,
                              main-shared.dev.bicepparam on every PR
                              touching infra/**. Does not authenticate
                              to Azure (OIDC federated credentials are
                              a separate follow-up).

.gitignore                   Add *.local.bicepparam so contributors can
                              override committed parameters locally
                              without committing the override.

CHANGELOG.md                 [Unreleased] entry for Track A.3 + the
                              ADR batch entry now that PR 26 has merged.

Local validation:
  az bicep build --file infra/main-shared.bicep         OK
  az bicep build --file infra/modules/shared.bicep      OK
  az bicep build-params --file infra/main-shared.dev.bicepparam  OK
  az bicep lint                                          clean

Per the locked feedback memory feedback_personal_identity_only.md and
ADR 0010, the deploy script will refuse to run if the active az
context is the day-job tenant. This is the enforcement of the personal
/work separation that this repo's portfolio framing depends on.
@jkeeley2073 jkeeley2073 added the claude-code Generated with Claude Code label May 2, 2026
@jkeeley2073 jkeeley2073 merged commit 8ee11b4 into main May 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude-code Generated with Claude Code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant