Architecture Decision Records¶
This directory is the canonical record of HybridCloudWorks architecture decisions. The architecture describes the system as a whole; an ADR explains why a material choice was made, its purpose, its tradeoffs, and when it must be reconsidered.
Policy¶
Create an ADR before implementing a decision that changes any of the following:
- cloud service, hosting model, data store, network boundary, identity model, or trust boundary;
- availability, recovery, security, performance, observability, or cost posture;
- Terraform state, module strategy, deployment authority, or GitHub governance;
- migration, compatibility, cutover, decommission, or repository ownership;
- external integration contract or irreversible operational behavior.
ADR numbers are never reused. The register carries one historical exception: 0021 was assigned twice, first to the deferred Container Apps CI runner record and then to Key Vault purge protection, before this rule was written down; both records are kept and marked so the collision cannot be mistaken for a revision. Accepted ADRs are immutable except for spelling, links, status, and a dated addendum at the end of the record that reports what happened after acceptance (a risk that did or did not materialise, a follow-up closed, a limit found) without changing the decision; ADR 0026 carries two such addenda. A changed decision gets a new ADR that supersedes the old one, never an addendum; ADR 0025's addendum of 2026-08-30 changed its decision in place before this sentence was written and is kept as the one historical exception. Pull requests implementing architecture must link the applicable ADRs. If no ADR applies, the pull request must say why the change is not architecturally significant.
Status lifecycle¶
Proposed → Accepted → Superseded, Deprecated or Withdrawn
- Proposed: under review and not authorized for implementation.
- Accepted: approved direction; implementation may still be incomplete.
- Superseded: replaced by a newer ADR, which must be linked.
- Deprecated: retained for history but no longer applicable.
- Withdrawn: accepted and then reversed by the owner before it ever operated; retained for history with the reason and the issue that reversed it.
Required sections¶
Every ADR includes:
- status and decision date;
- context and problem;
- purpose and decision drivers;
- decision;
- consequences and accepted risks;
- alternatives considered;
- validation and revisit triggers;
- related decisions and references.
Decision register¶
Reading a
T-NNNin these records. Several ADRs cite findings asT-503,T-519,T-706and so on. These were the repository's own tracked-finding identifiers, assigned in the rootTODO.mdand used by the 2026-08 architecture review, which states each finding in full under its own heading. The scheme was retired on 2026-09-05 when open work moved to GitHub issues; aT-number in an accepted ADR is a historical citation, not a live ticket, and the sentence around it is meant to carry the meaning on its own.
| ADR | Decision | Status | Primary purpose |
|---|---|---|---|
| 0001 | Consolidate HCW into one repository | Accepted | Prevent application/platform drift |
| 0002 | Retain Cloudflare as the initial edge | Accepted | Preserve edge capability within budget |
| 0003 | Start with Cosmos DB Serverless | Accepted | Match document workload and spiky demand |
| 0004 | Separate API, worker, and labs Function Apps | Superseded by 0019 | Enforce trust and permission boundaries |
| 0005 | Use AVM Terraform and GitHub OIDC delivery | Accepted; module clause superseded by 0020; amended 2026-10-07 (the Terraform run identity's grants: ABAC condition held by the operator script, Contributor scoped to its resource groups in two steps, team tokens owner-gated) | Make infrastructure changes reviewable and credentialless |
| 0006 | Use Entra ID for administrators only | Accepted | Protect administration without requiring public accounts |
| 0007 | Preserve static-first rendering on Static Web Apps | Accepted | Keep public content fast and backend-independent |
| 0008 | Use selective Private Link | Accepted | Secure sensitive data while containing network cost |
| 0009 | Use one production workload state | Accepted | Match current operating scale without environment sprawl |
| 0010 | Centralize bounded observability | Accepted; alerting layer delivered by 0022 | Make health and change outcomes measurable |
| 0011 | Use single-region, zone-aware recovery | Accepted | Balance reliability with the USD 150 ceiling |
| 0012 | Use queues and idempotent workers for side effects | Accepted | Make publishing and integrations retry-safe |
| 0013 | Use Azure OpenAI as a feature-gated default | Accepted; provisioning gate ratified by 0018 | Remove the Vertex dependency without making AI critical path |
| 0014 | Keep source media private in ZRS Blob Storage | Accepted; ZRS amended to LRS by 0018 | Protect and recover media economically |
| 0015 | Enforce a USD 150 monthly design ceiling | Accepted | Make cost an implementation constraint |
| 0016 | Use reversible migration and explicit decommission gates | Accepted; decommission gate closed by 0023 | Prevent premature data or rollback loss |
| 0017 | Re-root the default branch after credential rotation | Accepted | Remove reachable secret-bearing Git history |
| 0018 | Supersede plan v0.1 with the as-built v0.2 plan | Accepted; two debt rows closed by 0021 and 0022 | Make the approved plan describe the real system; disposition every deviation |
| 0019 | One Function App execution boundary | Accepted | Ratify least-privilege-by-contract over process separation |
| 0020 | Flat native-provider Terraform root module (no AVM) | Accepted | Keep resource addresses stable on a live state |
| 0021 | Key Vault purge protection stays disabled | Superseded by 0031 on 2026-09-14, which turned it on | Keep teardown-and-recreate available on a single-environment estate |
| 0021 (number reused) | Container Apps self-hosted CI runner failover | Superseded — deferred 2026-08-18 before the number was reassigned to Key Vault purge protection | Kept as the record of why a self-hosted CI runner was rejected. Its own text says the gated-off resources remain in infra/ci-runner.tf; that file was deleted on 2026-08-24 and the record is now purely historical |
| 0022 | The alerting fabric, and the signal it does not cover | Accepted | Make failure visible without competing with the telemetry that explains it |
| 0023 | Retire the migration-era estate, grants and reverse path | Accepted | Close the decommission gate deliberately rather than by neglect |
| 0024 | Reachability probing from a Cloudflare Worker | Proposed (built and armed 2026-09-01 — see the note on the record) | Give the missing reachability alert a path that runs on the current Cloudflare plan, whose bot protection answers every datacenter-based monitoring client with a 403 |
| 0025 | The Cosmos datacenter-IP sentinel — kept, then removed | Accepted; recommendation reversed by its own addendum | Weigh the review finding that the Cosmos firewall admits any Azure datacenter address against the cost of closing it — then remove that allowance once a cheaper route appeared |
| 0026 | Required checks filter inside the job, not at the trigger | Accepted | Keep every merge gate while paying only for checks that can fail |
| 0027 | Documentation is a MkDocs site under docs/, published to docs.hybridcloudworks.com |
Accepted | One reviewed, validated, redaction-gated source for every page; the Wiki is retired |
| 0028 | Cosmos recovery is a weekly full export plus daily change-feed deltas, run by the Function App, kept on the RA-GRS content account | Proposed | Survive account or region loss for about $4 a month, proven by a timed restore. Four points in the design still await the owner's approval on the issue that commissioned it |
| 0029 | Podcast hosting is Podbean, the podcast page is the one audio surface, and the media route serves byte ranges | Accepted; §1 amended 2026-10-13 (§1c: the show moved from RSS.com to Podbean, and publishing with it) | Replace the retired host, put both audio systems on one seekable page, and unblock a self-hosted feed for later |
| 0030 | The newsletter provider is Resend, and the site owns the schedule | Accepted | Klaviyo's free plan cannot send a weekly newsletter past 115 subscribers; keep the cadence in the timer that already drafts it, and end the half-wired read-only integration |
| 0031 | Key Vault purge protection on, Microsoft-managed keys, no private endpoints, and storage resource logs within the log cap | Accepted | Clear the Qlty security findings with real changes where the owner chose them, and a recorded decision where not |
| 0032 | The learner labs platform: a Terraform-managed Hostinger host in its own hcw-lab workspace, Ubuntu 26.04 with Docker only, Azure Arc as the hybrid control plane, Coder as the learner identity boundary, digest-pinned images from lab-image/, and public submission locked to the site's pane |
Accepted 2026-09-27; §6 revised 2026-09-28 (public submission open only from the Landing Zone Builder's pane, by origin and Cloudflare Turnstile, bounds unchanged); amended 2026-10-07 (Arc agent guest configuration off, owner decision) | Record once the decisions the four lab epics (#656, #657, #658, #659) build on, so a lab experiment can never reach production state, secrets or sign-in |
| 0033 | ContentForge as one content operating system: a navigation registry with a sentence per item, one pipeline graphic, a content taxonomy (kind and idea origin) kept apart from publish type, Forge Studio as a workspace, per-task AI routing, image lineage from prompt set to gallery, one aggregated calendar, an audio library of books and chapters with versions, labs per provider, a new Ambassador module, and Health over every hub | Accepted 2026-10-03 | Twenty-six admin pages had grown one hub at a time; the inventory found a blocking approve bug, two identical pipeline strips, five status vocabularies and features that created things and lost them |
| 0034 | AI model selection: one global priority list, a per-task mode (recommended, global, custom chain), a model catalogue the providers populate, every modality a task, and policy locks in code | Accepted 2026-10-04 | Three overlapping controls (order, placement, routing) put the free trial tier ahead of the paid provider the owner had just chosen; model lists were typed into the seed |
| 0035 | The AddOn pane model: each independently built tool is a sandboxed pane on its own lab name, with a generic site page at /tools/<id>, a catalogue row, a Function App status proxy, a frame-src entry and a generic addons Ansible role on the lab host, digest-pinned from Docker Hub |
Accepted 2026-10-10; decisions 8 (AddOn containers beside Coder on the host) and 9 (allow-downloads) confirmed by the owner in #1044 |
Show the three App-to-AddOn program tools (migration, cloud assessment, network assessment) without taking their code, secrets or release cadence into the site, keeping connect-src closed and the secret boundary on the lab host |
| 0036 | The Code Conductor brain is a second private workspace with its own Cosmos database (hcw_code_brain) in the existing account, its own switches, owner, desktop client and code_brain_sync scope, sharing the creator Second Brain's save and sync code |
Withdrawn 2026-10-31 (#1479): the engineering brain belongs to Code Conductor, not the site; code removed, Terraform decided in Phase 2 | Reuse the proven notes-and-sync setup for engineering notes without any path between the two workspaces, and back the new notes up before they exist |
| 0037 | Koda (the owner's content agent, run by Muse) reaches the site through an Agent Content API under /api/agent-content/v1/ with one hashed key, scoped to the Drafts stage and the published reads by five switches, made, rotated and revoked on a Koda tab of the AI Engine |
Accepted 2026-10-31; built from the owner's proposal, with the deviations recorded for the owner | Let an outside agent draft and review posts without any path to publishing, settings or secrets, reusing the Drafts routes rather than copying them, and keeping revocation immediate |
| 0038 | Koda's key also reaches an allow-list of admin routes through the admin role guard: the Pipeline, Enhanced, Spotlight and Second Brain sections and the Platform health and integration reads, each behind a section switch on the Koda tab, with no DELETE ever, no publishing, scheduling or unpublishing, body rules on the three routes that could reach publishing, and the key acting as the owner only in the Second Brain | Accepted 2026-11-01; the owner's instruction set the sections and the no-deletion rule, the route list is the session's reading of it | Let Koda create and review content across the sections the owner named with one implementation per route, keep every line ADR 0037 drew where the owner kept it, and keep the owner able to narrow the reach by switch |
| 0039 | A Cost page under Platform with five tabs (Overview, Azure, AI, Services, Alerts), read from one snapshot rebuilt every few hours by the armed health timer and on Refresh; Azure read with the Function App's identity under Cost Management Reader granted by the owner (not Terraform); AI from the usage ledger, ElevenLabs credits and the image and Foundry budgets; a hand-kept ledger for subscriptions outside Azure; alerts on the page, the Decision Center, the Calendar and Telegram | Accepted 2026-11-01; owner request of the same day | Answer "what does this cost this month, and will it go over?" in one place, with alerts before the money is spent, without a new secret or a wider Terraform grant |
Template¶
Copy the ADR template for new decisions. Do not edit an accepted ADR to represent a new decision; create the next numbered record and mark the earlier ADR superseded.