Skip to content

ADR 0019: One Function App execution boundary

Status: Accepted Decision date: 2026-08-18 Owners: Workload owner and architecture owner

Context

ADR 0004 required three Flex Consumption apps — API, worker, labs broker — each with its own managed identity, host storage, telemetry component, deployment artifact, and permission set. The implemented system (azurerm_function_app_flex_consumption.hcw) runs everything in one app: the 59-route API, timer/queue work behind per-timer feature flags with a master kill switch, and the labs surface as three server-side-constrained endpoints. The environment is live with production data; this ADR decides whether the divergence is a defect to fix or a decision to ratify.

Purpose and decision drivers

  • Operational cost at actual scale: three deployment pipelines, three host-storage accounts, and three telemetry components for one small workload triples the surface an operator must keep healthy.
  • Cost ceiling (ADR 0015): three plans and three storage accounts carry fixed overheads.
  • The original isolation motive — the semi-trusted labs boundary — was solved differently and better: VPS agents hold no database credential at all, authenticate with per-host Entra certificates, and can reach exactly three endpoints, each constrained server-side per caller (TODO.md, TODO T-401).

Decision

Ratify the single Function App as the execution boundary. Least privilege is enforced by contract — per-route guards, per-caller server-side constraints, per-timer feature flags — rather than by process separation. Supersedes ADR 0004.

Consequences and accepted risks

  • One managed identity holds the union of the workload's data-plane permissions; a code-execution compromise in any route reaches everything that identity can. This is the accepted trade.
  • One shared Application Insights component; per-boundary cost attribution is by operation name, not by resource.
  • A worker stampede can compete with API latency on the same plan; Flex scale-out and the schedulers' kill switch are the mitigations.
  • Splitting later is straightforward (apps are additive; identity grants shrink), so this decision is cheap to reverse — unlike its opposite.

Alternatives considered

  • Implement the three-app split now — rejected: state surgery and tripled operations for isolation the labs credential model already provides at the trust boundary that mattered.
  • Two apps (API + everything privileged) — rejected: keeps most of the overhead while still sharing the highest-value permissions in the privileged app.

Validation and revisit triggers

  • Validated by the 2026-08-14 operator smoke run (guards, anonymous surface) and the route inventory tests.
  • Revisit if: a scheduler workload measurably degrades API latency; a new integration needs a permission that would be dangerous in the shared identity; or labs job volume makes the in-app broker a noisy neighbour. The split returns worker-first.