Resource Validation Report — 2026-08-18¶
Superseded in part — annotated 2026-08-25. This is a point-in-time record and is kept as written. Five of its conclusions no longer hold, and one of them was wrong when it was written:
- The addendum's "T-505 observability layer — Applied" verdict is wrong about alert rules. The action group, diagnostic settings, the 0.25 GB/day daily cap and the 50/75/90/100-plus-forecast budget ladder all landed. No alert rule of any kind was ever created. The Go-Live readiness review of 2026-08-24 found
az monitor metrics alert list,scheduledQueryRules,webtestsandactivity-log alert listall empty in both subscriptions, while the workspace was simultaneouslyOverQuota. The first alert rules are declared onfix/go-live-remediationand are not applied (ADR 0022, Alerting and support).- T-506 "keyless OpenAI — Applied" is superseded by removal. The Azure OpenAI account and its resource group were retired on 2026-08-19; model calls go to external provider APIs keyed from Key Vault (Naming-Convention).
- The plan-parity finding was dispositioned the same day by ADR 0018, which superseded plan v0.1 with the as-built v0.2 and ratified or filed as debt every deviation in §2 below.
- §3 item 3 no longer stands. Purge protection is a recorded accepted risk, not a pending action (ADR 0021).
- §1's follow-up is answered. The Cloudflare WAF skip rule was built, applied and confirmed inert — Bot Fight Mode does not run on the Ruleset Engine — so synthetic validation still ends at the edge, and the smoke half of Validate Deployed Surface cannot pass from a GitHub-hosted runner. Do not rebuild the skip rule expecting a different answer; see TODO T-519.
First execution of the resource validation pass ahead of permanent platform deployment. Three layers were validated with the access available to an engineering session (no Azure control plane, no HCP Terraform token):
| Layer | Method | Verdict |
|---|---|---|
| External deployed surface | validate-deployed.yml run #32083341003 from a GitHub-hosted runner |
Live and TLS-healthy; fully shielded by Cloudflare bot challenge, which also blocks synthetic validation |
| Plan-vs-code parity | Full comparison of .azure/infrastructure-plan.json (v0.1-approved) against infra/*.tf |
~40% parity by resource count; plan and implementation have diverged into two independently-evolved documents |
| Control plane (TFC plan, az inventory) | — | Not validated — no credentials in this session. See "What still needs an operator" |
1. External surface¶
- DNS: apex
hybridcloudworks.comresolves (Cloudflare-proxied).www.andapi-azure.do NXDOMAIN — consistent with the same-origin topology (TODO.md) and withinfra/main.tfowning only the apex TXT and anapi-azureCNAME that is evidently not applied or not resolving; the plan'swwwrecord exists nowhere. - TLS: valid —
CN=hybridcloudworks.com, issuer Google Trust Services (Cloudflare-managed), valid 2026-06-29 → 2026-09-28, auto-renewing. - Frontend and API: every request from the GitHub runner —
GET /,/api/health,/api/public/content, CORS preflights — returned HTTP 403 with a Cloudflare challenge page (challenges.cloudflare.comCSP,chlraymarker). Cloudflare is challenging datacenter-IP traffic before it reaches the Static Web App or Functions origin.
What this proves: the edge is up, TLS is healthy, DNS matches the same-origin decision, and bot mitigation is aggressively on. What it cannot prove: anything about the origin. The one smoke check that "passed" (admin guards refuse anonymous callers) passed because a Cloudflare 403 is indistinguishable from an app-level refusal — treat it as unproven, not verified. The operator's 2026-08-14 smoke run from a residential IP remains the only end-to-end origin evidence.
Follow-up (human): TODO.md — decide whether to add a Cloudflare WAF
skip rule (secret header → bypass challenge) so validate-deployed.yml can
reach the origin; without it, synthetic validation ends at the edge.
2. Plan-vs-code parity¶
The approved plan (hcw-azure-foundation-001, v0.1-approved, 2026-07-22)
is resource-exact in intent; the Terraform implements roughly 40% of it
by resource count and deviates most on the items that cost money or
reduce risk. Full detail is in the repository review trail; the material
findings:
Missing from the implementation (plan says, Terraform doesn't)¶
- The three-way isolation boundary — the plan's most-repeated principle splits API / worker / labs into three Function Apps with their own plans, host storage, and App Insights. The implementation collapses all of it into one app, one plan, one host storage account, one shared App Insights — exactly the alternative the plan records rejecting.
- The entire observability control layer — no action group
(
ag-hcw-ops-prod), no diagnostic settings, no alert rules, no Log Analytics daily cap (plan: 0.25 GB/day). Budget alerts email a single address directly, at thresholds 50/80/100 instead of the approved 50/75/90/100, with no forecast alert. - Plan-side identity split — no read-only PR-plan identity; the deploy
identity is federated to the
mainbranch, not the protectedproductionenvironment the plan requires. - Storage queues (seven named queues incl. poison), SWA custom
domains in Terraform, and the
wwwDNS record.
Security-posture deviations on shared resources¶
| Deviation | Plan | Implemented |
|---|---|---|
| Cosmos network firewall | Deny public, allow Functions subnet | No network rules at all — Cosmos answers the public internet; this is the control ADR-0008/ADR-001 traded Private Link away for |
| Cosmos local (key) auth | Disabled | Enabled (ties to TODO.md's rotation question) |
| Cosmos backup | Continuous | Periodic default |
| Storage redundancy | ZRS (both accounts) | LRS (both) |
| Storage shared-key auth | Disabled | Enabled (provider default) |
| Key Vault purge protection | true |
Variable defaulting false |
| Functions zone redundancy | true |
Absent, argued against in code comments |
| Azure OpenAI | Feature-gated until capacity/filter/budget approved; keyless | Unconditionally provisioned; primary_access_key exported as a Terraform output; no RBAC data-plane grant |
Implemented but never planned¶
71 Cosmos containers (the plan explicitly gated container design), two
hardcoded OpenAI model deployments (incl. DALL·E 3, unplanned), the HCP
Terraform backend, all RBAC assignments, and a naming scheme where zero
resource names match the plan's CAF names. The plan's stated
Azure-Verified-Modules requirement is unmet — no module blocks exist
(already tracked as TODO T-502).
Disposition¶
The honest read: plan v0.1 was approved as a topology, the implementation
evolved past it with genuinely better-reasoned decisions in places (the
serverless/container-per-collection rationale, the subnet service-endpoint
fix the plan got wrong), and nobody re-versioned the plan. Continuing to
call validate-repository-structure.ps1's "plan must remain approved"
check satisfied while the artifact no longer describes the system is the
kind of drift that reads as an audit finding at ALZ absorption. TODO.md
asks for the human decision: reconcile code toward plan, or supersede the
plan with an as-built v0.2 + ADRs. Engineering-tractable security deltas
are filed as TODO T-504 (Cosmos hardening) and T-505 (observability
control layer) regardless of that decision.
3. What still needs an operator¶
- Empty-plan check: run a plan in the
hcw-azureHCP Terraform workspace. Expected: empty (the standardization changes are plan-neutral:lifecycleblocks and fmt only). Anything non-empty is drift to investigate before any deploy. - Smoke tiers 2–3 (Cosmos conditional patch; authenticated guard) —
need
az login/ a bearer token, per the script header. - ~~Purge protection: set the TFC variable
purge_protection_enabled = trueand apply before any production secret is seeded.~~ Superseded 2026-08-24: the owner accepted purge protection off as a recorded risk — enabling it is a one-way switch that removes the teardown-and-recreate path a single-environment estate depends on, and 90-day soft delete compensates. See TODO.md → Accepted risks; do not re-raise from this line. - TODO.md decisions (Cloudflare synthetic access; plan reconciliation) and the outstanding §0.2 (was a Cosmos key ever deployed?) — §0.2 gains urgency from Cosmos being network-open with key auth enabled (T-504).
Addendum — apply verification, 2026-08-18¶
The operator applied the full T-503–T-506 hardening set in HCP Terraform and ran a cold-start check. Verification state per control:
| Control | Evidence | Verdict |
|---|---|---|
| T-503 host storage default-Deny (runtime + package-pull path) | Operator cold-start invocation passed after apply — the strongest available proof the VNet path serves host state and packages | Verified |
| T-504 Cosmos firewall — app path | Cold start healthy; app-path Cosmos reads implied but not directly probed | Applied; probe a Cosmos-backed endpoint from a browser to close |
T-504 Cosmos firewall — GitHub-runner path (0.0.0.0 sentinel) |
Unverifiable yet: heal-computed-properties fails at Azure login because the CLIENT_ID / TENANT_ID / SUBSCRIPTION_ID repository variables were never set — run history shows this failure on every run predating the hardening (pre-existing provisioning gap, now CHECKLIST §7). One green heal run after setting them is the closing evidence |
Applied; pending repo variables |
| T-505 observability layer | Applied with the same TFC run; alert ladder + forecast + diagnostics live per plan | Applied |
| T-506 keyless OpenAI | Account replaced with subdomain endpoint, keys off, RBAC grant live (TFC apply evidence); no runtime consumer to probe | Applied |
| External surface regression | Post-apply validate-deployed run #32090414219: byte-identical to the pre-apply baseline — TLS/DNS green, same Cloudflare-challenge 403s (TODO.md unchanged) |
No regression |
Everything else on main |
Repository Policy, IaC Validation, CI, CodeQL all green on the latest merges | Healthy |
Remaining to close the loop: set the three OIDC repository variables (CLIENT_ID, TENANT_ID,
SUBSCRIPTION_ID — names per the variable naming standard) (values from the client_id Terraform output +
tenant + subscription) and watch one heal run go green; optionally probe a
Cosmos-backed endpoint (e.g. the public content list) from a browser; run
one more TFC plan and confirm it is empty.
Generated by the resource validation pass, 2026-08-18. The DNS, TLS and frontend-surface half of the external layer can be re-run any time via Actions → Validate Deployed Surface; its smoke job cannot pass from a GitHub-hosted runner — see the banner at the top of this page and Deployment Runbook §4.