Skip to content

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:

  1. status and decision date;
  2. context and problem;
  3. purpose and decision drivers;
  4. decision;
  5. consequences and accepted risks;
  6. alternatives considered;
  7. validation and revisit triggers;
  8. related decisions and references.

Decision register

Reading a T-NNN in these records. Several ADRs cite findings as T-503, T-519, T-706 and so on. These were the repository's own tracked-finding identifiers, assigned in the root TODO.md and 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; a T- 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.