3.5 The Physical View

The Development view mapped how the source is organized. The Physical view — Kruchten's deployment view — maps where the built software actually runs: which process lands on which host, across which network boundaries, under what per-host cost and load policy. It is the placement lens, and it earns its keep on a failure the other views cannot see: a system that is functionally correct can still be mis-deployed. Only a view that names where things run catches it. Written down as a placement model the fleet can read, that where becomes something an agent reasons over before it deploys, not a fact discovered after — the Modeling Thesis reaching the one view the others cannot see.

One general type anchors the view, and it is the honest exception in this zoo. A performance and cost model models the computation itself — what it costs to run, what it costs to move data, whether to compute locally or remotely, whether to cache or recompute. The next section takes that gap head-on: the framework supports the cost view, and one real model embodies the load-and-cost half of it, but the pure-latency slice has no dedicated instance, because no failure had justified building one until one arrived. Three real models carry the view: the invariant-DAG execution policy (the idea this chapter turns on — per-host cost and load rationing), the deployment and tier topology model (placement parity, worked in full below), and the substrate-dependency model (a computed migration blast radius).

Learn more about this governance mechanism: deployment and tier topology model.

3.5.1 The performance/cost view: mostly embodied, one honest gap

The zoo names a performance and cost model as a general type, and the framework supports it. But the catalogue holds only a partial instance, and the book would rather state that flat than invent a model to fill the slot.

The load-and-cost half of the view is embodied — by the invariant-DAG execution policy below. Its Scheduler reads a per-host profile of a concurrency ceiling and a budget, and rations both. The more elastic the host, the wider the Scheduler fans work out; the scarcer the box, the harder the plan serializes — and a cost gate is honored everywhere, a scarce-resource gate only where a box demands it. That is a real cost model in action: cost-to-run and resource-contention, driven from a declared per-host profile.

Learn more about this governance mechanism: invariant-DAG execution policy.

What has no dedicated instance is the pure-latency slice: a model of request latency, cache-hit ratios, and data-movement cost. The framework could carry it the way it carries the others — a typed profile, invariants, a drift gate. The zoo names the gap rather than padding it, on the discipline the whole harness leads with: model only where a failure lives. A model built ahead of its failure rots before it is ever checked. But that discipline cuts both ways, and the latency slice is where it drew blood.

DocAble has paid for this gap once. To cut idle cost, the fleet let its services scale to zero — a change the cost half of this model welcomed and the latency half could not weigh. Cold-start latency then surfaced in production, and the surface number hid it: a health check answered in about 150 milliseconds while the PDF-conformance service — which shelled out to a Java validator once per request — carried a warm floor over four seconds, worse on a cold boot. Chasing it cost days of telemetry and a re-architecture that kept the validator resident, cutting that floor from 4,057 to 109 milliseconds. A latency model here — request latency typed per service, the cold-start cost of scaling to zero an invariant over it — would have flagged that penalty before a user ever waited on it. The gap stays honest; this is what it costs to leave open.

3.5.2 The deployment & tier topology model

The typed statement of where things run — each service's name, its layer, and its tier — from which the deploy scripts and layering lints reason about a declared topology rather than scattered constants, and against which the real deploy table is reconciled.

(a) Quality property it helps assess. Two, both a placement fact that drifts silently the moment a service moves.

(b) Constructs and relations. A frozen record per service, keyed by name.

(c) Visual depiction. The natural diagram, Figure 3.5-1, is a deployment diagram — the build host that produces the image, and the runtime cluster it deploys into. Reused from the model's appendix Structure slot:

The build host produces the image; the runtime cluster deploys it A build host runs the test serializer and build semaphore and produces the image the runtime cluster deploys, where the web service enqueues onto a queue drained by a worker. The model declares this placement. Build host Test serializer Build semaphore produces the image Runtime cluster Web Queue Worker
Figure 3.5-1. The model declares placement — the build host that produces the image, the runtime cluster that deploys it.

(d) Invariants, and how they are checked. A bidirectional set-diff and a boundary lint, collected in Table 3.5-1:

Table 3.5-1. Invariants of the Physical view — every service deploys at the tier the model declares, held to the real deploy tables.
InvariantTemporal shapeHow it is checked
Every declared service deploys at the tier the model declares□P (safety)Parity lint, model ⊆ reality: a declared service missing from the deploy table, or at the wrong tier, is a finding.
Every deployed service appears in the model□P (safety)Parity lint, reality ⊆ model: a service in the deploy table the model never declared is a finding.
No layer imports across a boundary the model forbids□P (safety)Cross-layer-import lint over the declared layer graph.

The parity check is a set-diff run both directions against the live service-to-tier map. Read the model's declared tiers, read the real deploy table, and print each mismatch: a declared service whose tier differs from the deploy table's, and every deployed name absent from the model. Neither direction is optional — model ⊆ reality alone would miss a service that shipped without a model row, and reality ⊆ model alone would miss a tier that drifted in the code.

(e) Traceability and derivation direction. Model-from-code. The parity check re-reads the real deploy table and reconciles the declared topology against it, so the running system is the ground truth for what deploys. The join key is the service name, which indexes both the Service record and its row in the deploy table. The model is authoritative for the layer graph it holds — the import lint reads it directly — and reconciled against reality for the tier placement it checks.

Also seen in: Development (a layer boundary is a packaging fact). Rendered in full here.

That model says where the parts run. The next one says where the checks run — the same physical-placement thinking, applied to the gates themselves rather than the services.

3.5.3 The invariant-DAG execution policy

A deploy graph whose edges carry a typed intent — correctness, cost-gate, or load — kept host-identical, plus a typed Scheduler that reads a per-host profile and rations load and cost so one host fans work out while a scarce one serializes it.

(a) Quality property it helps assess. Two, both about how a host runs the work it was handed.

(b) Constructs and relations. An edge-intent axis plus a per-host Scheduler.

(c) Visual depiction. The natural diagram, Figure 3.5-2, is a data-flow — edge intents route, load leaves the graph for the Scheduler, and a per-host profile drives the plan. Reused from the model's appendix Structure slot:

Edge intents route; load leaves the graph for the Scheduler A deploy-graph edge carries one of three intents. Correctness and cost-gate edges stay in the graph, host-identical. A load edge is banned from the graph and migrated to the Scheduler, which reads a per-host profile and emits an execution plan — the concurrency and cost-gate decisions for that host. Moving the stress burden between hosts is a profile edit, never a graph edit. Edge intent CORRECTNESS in the graph COST_GATE in the graph LOAD banned from the graph Scheduler Per-host profile Execution plan
Figure 3.5-2. Profile Edit, Not Graph Edit. Correctness and cost-gate edges stay in the deploy graph; a load edge is banned and migrates to the Scheduler, which reads the per-host profile and emits the execution plan. Moving stress between hosts is a profile edit, never a graph edit.

(d) Invariants, and how they are checked. A load-edge ban and a plan derivation, collected in Table 3.5-2:

Table 3.5-2. Invariants of the host-stress model — load edges migrate to the Scheduler, never the deploy graph.
InvariantTemporal shapeHow it is checked
No deploy edge carries the LOAD intent□P (safety)Load-edge lint reads the graph and the Scheduler's graph-resident intent set; a LOAD edge is a finding.
The deploy graph is identical on every host□P (safety)Graph-parity check: the same graph is emitted for every host; a per-host fork is a finding.
The execution plan is a pure function of the host profile□P (safety)Derive-and-assert: the plan is recomputed from the profile, never hand-stored.

(e) Traceability and derivation direction. Model-from-code. The plan is derived from the per-host profile by a pure function, and a hand-stored plan or a LOAD edge is banned outright. The join key is the host name, which indexes both the HostLoadProfile and the deploy phase that runs under the emitted plan. This model is walked in full, with its Scheduler code, in the Scenarios chapter's join example — here it is the Physical-view resident that the join reaches into.

Also seen in: Process (concurrency rationing is a runtime-dynamics concern) and Scenarios (the join example). Rendered in full in the Scenarios chapter; referenced here for the placement and cost half.

3.5.4 The control↔substrate dependency model

The substrate-dependency model makes each governance mechanism declare, as typed metadata, the substrate assumption it bakes in — bound to one substrate, substrate-aware, or substrate-agnostic. It assesses migration safety: before a cross-cutting substrate change, exactly which mechanisms assume the old substrate? That declaration turns a grep into a computed blast radius. Joining the declared stances against the topology model on the migration target prints the in-scope mechanisms as a table — a real answer, not a hopeful search that misses the one whose assumption is implicit. A declaration lint requires the stance, so a mechanism that reads the substrate silently cannot hide from the join. The appendix carries its typed schema and the join code in full.

Learn more about this governance mechanism: control↔substrate dependency model.

---

The Physical view places the parts and rations their cost. That completes the four static views. Next, the +1: the scenarios that set them in motion and validate that the other four hold up.

© James C. Davis, 2026–present