Skip to content

feat(infra) Phase 4 W1-4 — flip deployAiSearch=true + --ensure-ai-search smoke probe#103

Merged
jkeeley2073 merged 1 commit into
mainfrom
Dev-Phase4W14DeployAiSearchFlip
May 8, 2026
Merged

feat(infra) Phase 4 W1-4 — flip deployAiSearch=true + --ensure-ai-search smoke probe#103
jkeeley2073 merged 1 commit into
mainfrom
Dev-Phase4W14DeployAiSearchFlip

Conversation

@jkeeley2073
Copy link
Copy Markdown
Contributor

Summary

Phase 4 W1-4 per docs/build-spec.md § Phase 4 scope item 13. Unblocks the H1 operational hand-off (Bicep apply → AI Search Basic provisioned in dev) and the Wave 2 W2-3 embedding pipeline + index population (per ADR-0021's pinwiz-rag-v1 index schema).

This is the smallest planned PR of Wave 1 — a 1-line param flip + the post-deploy smoke probe needed for H1 verification.

What ships

  • infra/main-shared.dev.bicepparam: add param deployAiSearch = true. AI Search Basic was deferred during Phase 3 H1 fix-up via the deployAiSearch=false override in main-shared.dev.local.bicepparam (East US 2 capacity exhausted on H1 day, 2026-05-07; Phase 3 doesn't consume AI Search). Phase 4 RAG ingestion is the consumer; flipping the committed param makes the intent explicit.
  • New AiSearchOptions in Core/Configuration/: Endpoint ([Url]-validated) + IndexName (default pinwiz-rag-v1); mirrors AiFoundryOptions sectioning + EndpointKey constant for gating.
  • New IAzureAiSearchSmokeProbe + AzureAiSearchSmokeProbe in Infrastructure/Integrations/AiSearch/: connects via DefaultAzureCredential, calls SearchIndexClient.GetServiceStatisticsAsync (lightest call exercising endpoint reachability + AAD auth), returns a structured result. Idempotent, structured-result-on-failure, safe to re-run. Mirrors AzureFoundrySmokeProbe line-for-line.
  • New AddAzureAiSearchIntegration DI extension: binds options + registers the probe with ValidateOnStart. Mirrors AddAzureFoundryIntegration.
  • New --ensure-ai-search CLI flag in Program.cs: exit-code-2 + remediation message when AiSearch:Endpoint isn't configured; success message reports the endpoint + expected index name. Gated DI via CreateHost so absence is valid config in Phase 0/1/2/3 and in local dev before H1.
  • New Azure.Search.Documents 11.7.0 NuGet — the GA major covering the semantic-ranker + vector-field surface ADR-0021 locks. Wave 2 W2-3 extends consumption for index creation + upsert; Wave 3 W3-3 adds hybrid-retrieval queries.
  • 6 new unit tests mirroring AzureFoundrySmokeProbeTests shape (empty/whitespace/malformed endpoint, custom-IndexName preservation through failure result, ctor null guards).

Test Plan

  • dotnet build PinballWizard.slnx0 warnings, 0 errors
  • dotnet test PinballWizard.slnx724 / 724 passing (was 718; +6 from AzureAiSearchSmokeProbeTests)
  • Sibling-diff against Integrations/Foundry/AzureFoundrySmokeProbe — see "Local review" section
  • Post-merge H1 verification (operator hand-off, not part of this PR — see below)

Out of Scope

  • The Bicep apply itself (operator hand-off H1 — see "Operator follow-up" below).
  • Index creation + document upsert (Wave 2 W2-3 — AiSearchRagIndexer).
  • Hybrid retrieval (Wave 3 W3-3 — AiSearchRagRetriever).
  • SKU verification in the smoke probe — checking SKU requires the management-plane (Azure.ResourceManager.Search) which adds another tenant-scoped dependency for marginal value at this gate. The deploy script already reports the provisioned SKU. Tracked for revisit if H1 surfaces SKU mismatches.

Operator follow-up after merge (H1 hand-off)

Per build-spec § Phase 4 § Operational hand-offs § H1:

  1. Remove param deployAiSearch = false from main-shared.dev.local.bicepparam so the committed true takes effect.
  2. Pre-flight East US 2 AI Search Basic capacity via portal. If still constrained, edit infra/main-shared.dev.bicepparam to relocate AI Search to a sibling region (East US, Central US — Phase 1 Cosmos location stays unchanged; cross-region search-from-app latency penalty is small per Phase 3 lesson 3).
  3. Apply: pwsh ./infra/scripts/Deploy-SharedResources.ps1 -Environment dev.
  4. Verify: dotnet run --project src/PinballWizard.Cli -- --ensure-ai-search. Expected output: Azure AI Search verified: endpoint reachable at <endpoint> (expected index: pinwiz-rag-v1; index creation lands in Wave 2 W2-3).
  5. Record the apply timestamp + region in docs/decision-log.md.

Checklist

  • CI is green (build + test + coverage + CodeQL + sanitization) — pre-push verified locally
  • PR title follows the Conventional Commits format
  • If this is a new architectural decision, an ADR has been added — N/A (W1-4 implements ADR-0021's deploy-time prerequisite; no new architectural decision)
  • If user-visible behavior changes, README.md and/or docs/ are updated — N/A (CLI smoke-probe + infra param; no end-user-visible surface changes; W1-4 is the prerequisite for the W2-3 surface that will be user-visible)
  • If a memory in ~/.claude/projects/c--projects-PinballWizard/memory/ is now stale, it has been updated or removed — N/A (the wave 0 close + cleanup handoffs explicitly reference W1-4 as the next item; they remain accurate)
  • No TODO / FIXME / commented-out code committed
  • No new entries in <NoWarn> without a comment — N/A

Pre-push self-audit

Step 0 — /local-review (qualitative)

  • Ran /local-review and addressed every 🔴 finding before push
  • Local review outcome: 0 🔴 / 3 ⚠️ / 7 categories ✅ (categories 5 + 6 N/A — non-HTTP / no scraped data)

Step 1 — Mechanical checklist

  • Every new *Options property has at least one real getter call in src/Endpoint read in AzureAiSearchSmokeProbe.cs:38, 47, 51 + gated in Program.cs; IndexName read in AzureAiSearchSmokeProbe.cs:43, 52, 67, 87, 93, 98 (every code path passes it into the result record's ExpectedIndexName)
  • Sibling-diffed against AzureFoundrySmokeProbe line-for-line (see local-review category 4): identical ctor null-checks, identical Uri.TryCreate malformed-URL pattern, identical catch filter when (ex is not OperationCanceledException), identical log-message shape on success, identical Options shape (SectionName + EndpointKey + [Url] + ValidateDataAnnotations + Validate + ValidateOnStart), identical TryAddSingleton. Drift: the AI Search probe has one additional defensive test (PreservesCustomIndexNameInResult) covering the IndexName-in-failure-result round-trip — additive, not drift.
  • No bare catch { } — all catches scoped to catch (Exception ex) when (ex is not OperationCanceledException)
  • New ISourceScraper? — N/A
  • Tests assert behavior, not just structure — empty / whitespace / malformed endpoint paths all exercise specific input → specific output; round-trip test of IndexName through the failure-result record
  • Build is zero-warning — verified 0 Warning(s), 0 Error(s)
  • git log -1 --format='%an <%ae>' shows personal noreply, not work email — confirmed Jim Keeley <94459922+jkeeley2073@users.noreply.github.com>

…rch smoke probe

Phase 4 W1-4 per docs/build-spec.md § Phase 4 scope item 13. Unblocks
the H1 operational hand-off (Bicep apply → AI Search Basic provisioned
in dev) and the Wave 2 W2-3 embedding pipeline + index population
(ADR-0021 — index schema for `pinwiz-rag-v1`).

What this PR ships:

- `infra/main-shared.dev.bicepparam`: add `param deployAiSearch = true`.
  AI Search Basic was deferred during Phase 3 H1 fix-up via the
  `deployAiSearch=false` override in main-shared.dev.local.bicepparam
  (East US 2 capacity exhausted on H1 day 2026-05-07; Phase 3 doesn't
  consume AI Search). Phase 4 RAG ingestion is the consumer; flipping
  the committed param makes the intent explicit. Operator follow-up:
  remove the `param deployAiSearch = false` line from the local override
  so the committed `true` takes effect.

- New `src/PinballWizard.Core/Configuration/AiSearchOptions.cs`: sealed
  options class with `Endpoint` (the search service URL, [Url]-validated)
  and `IndexName` (default `pinwiz-rag-v1` per ADR-0021). Mirrors the
  AiFoundryOptions sectioning convention with a `EndpointKey` constant
  for presence-checking from gating code.

- New `src/PinballWizard.Infrastructure/Integrations/AiSearch/`:
  - `IAzureAiSearchSmokeProbe.cs` — the probe interface + result record.
  - `AzureAiSearchSmokeProbe.cs` — connects via DefaultAzureCredential,
    calls `SearchIndexClient.GetServiceStatisticsAsync` (lightest call
    that exercises endpoint reachability + AAD auth), returns a
    structured result. Mirrors AzureFoundrySmokeProbe — idempotent,
    structured-result-on-failure, safe to re-run.
  - `ServiceCollectionExtensions.cs` — `AddAzureAiSearchIntegration`
    binds the options + registers the probe. Mirrors
    `AddAzureFoundryIntegration` shape.

- New `--ensure-ai-search` CLI flag in `src/PinballWizard.Cli/Program.cs`:
  resolves the probe from DI; exit-code-2 + remediation message when
  AiSearch:Endpoint isn't configured; success message reports the
  endpoint + expected index name. Mirrors the --ensure-azure-foundry
  exit-code-2 pattern. The host's `CreateHost` gates
  `AddAzureAiSearchIntegration` on AiSearch:Endpoint presence so absence
  is a valid configuration in Phase 0/1/2/3 and in local dev before H1.

- New `Azure.Search.Documents` 11.7.0 NuGet (the GA major covering the
  semantic-ranker + vector-field surface ADR-0021 locks). Wave 2 W2-3
  extends consumption for index creation + document upsert; Wave 3
  W3-3 adds hybrid-retrieval queries.

- 6 new unit tests (`AzureAiSearchSmokeProbeTests.cs`) exercising the
  misconfiguration paths (empty endpoint, whitespace endpoint, malformed
  URL, custom IndexName preserved through failure result, ctor null-arg
  guards). Mirrors AzureFoundrySmokeProbeTests pattern. Test count
  718 → 724.

Local review: 0 🔴 / 3 ⚠️ / 7 ✅. Two ⚠️ addressed:
1. Test now asserts `AiSearchOptions.EndpointKey` appears in the
   malformed-URL error message (parity with the empty-endpoint test
   already-asserted).
2. bicepparam comment now explicitly names which line to remove from
   main-shared.dev.local.bicepparam (operator UX).
The third ⚠️ (the empty-endpoint code path is unreachable through DI's
ValidateOnStart but remains as defensive depth) is deferred per the
sibling-consistency principle — the Foundry sibling has the same
defensive branch.

Build: 0 warnings, 0 errors. Tests: 724 / 724.

Operator hand-off (H1) after this merges:
1. Remove `param deployAiSearch = false` from main-shared.dev.local.bicepparam.
2. Pre-flight East US 2 AI Search Basic capacity via portal; if
   constrained, edit the bicep param to relocate to East US or Central
   US (Phase 3 lesson 3 — Cosmos location stays unchanged).
3. `pwsh ./infra/scripts/Deploy-SharedResources.ps1 -Environment dev`.
4. `dotnet run --project src/PinballWizard.Cli -- --ensure-ai-search`.
5. Record apply timestamp in decision-log.md.
@jkeeley2073 jkeeley2073 added the claude-code Generated with Claude Code label May 8, 2026
@jkeeley2073 jkeeley2073 merged commit 4631b12 into main May 8, 2026
5 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