Appendix B - 14. Control↔substrate dependency (computed blast-radius)

The judgment — Declare the assumption so the blast radius is queryable before you change anything.

RoleModels-bridge
FamilySystem models
Used in stacksThe governance-of-governance stack
EnforcementHard
Related mechanismsEnabler: Deployment & tier topology; See also: Meta-model consumption discipline (read, don't hardcode); See also: Model query surface (repo-query); See also: Drift & parity gates

The Structure of Control↔substrate dependency (computed blast-radius) — its shape at a glance:

Each substrate-reading mechanism declares its stance in a typed field. A query joins those declarations against the substrate model and, given a migration target, prints exactly which mechanisms a change puts in scope — the blast radius, computed before you touch the substrate.

join on targetControl Astance: plane-XBlast-radius queryControl Bstance: agnosticSubstrate modelin-scope mechanisms
Figure B.14-1. Accessible description: two mechanisms each declare a substrate stance. A query joins those declarations against the substrate model and, for a migration target, emits the table of mechanisms that change puts in scope. The table is derived, not hand-maintained.

Full description → Control↔substrate dependency (computed blast-radius).

Intent — Make each control declare the substrate assumption it bakes in as structured metadata, so "which controls depend on which part of the substrate, and what breaks when I change it" becomes a computed query rather than a grep-and-read. Before a cross-cutting substrate change, the static-analysis blast radius is known up front.

Problem

A control — a lint, a gate, a validator — usually bakes an assumption about the substrate it checks: a service is a Deployment under this directory, the manifest carries this field. The assumption sits buried in the control's body, invisible until you change the substrate. Then the change lands and the fleet fails two silent ways at once. In a false FAIL, a migrated service is validated against manifests or fields that no longer exist; on a no-baseline deploy gate, one buried assumption blocks every release. In a false PASS, a migrated service drops its old annotation and vanishes from every totality, smoke, and disjointness check, looking clean because nothing checks it anymore — the worse of the two. The engineer planning the migration cannot see this coming: "which checks assume the old substrate?" is answerable only by grepping the importers, then reading each body.

Mechanism

The whole thing is a stable lint reading declarations: the table is derived from the fields at query time, so nothing is generated and no hand-maintained map can drift.

Engineering consequences

Every substrate-reading control gains one declaration — the intended tax, and what makes the dependency edge queryable. Grep finds the importers but not their assumption; lifting the stance into a declaration makes the importer and its posture toward the substrate one queryable fact, so the blast radius is computed before you touch the substrate.

Implementation seam

Four pieces: the closed enum of substrate stances, the structured field extending the control's existing metadata block, the join query that prints the blast-radius table, and the declaration lint the deploy gate composes. It requires the substrate already be a queryable model to join against Appendix B, and extends the read-don't-copy discipline from values to dependency edges Appendix B.

Known limitations

It pays off only at a substrate change; in a stable system the declarations sit inert, over-built if added speculatively to a substrate you will never change. A stance the closed enum cannot express forces an enum change — the honest signal that the substrate model itself grew a dimension — while an open string field would reintroduce the drift and typo class the enum exists to remove. And the existing controls must be back-filled before the lint can promote from audit-only to blocking.

© James C. Davis, 2026–present