Appendix B - 12. Invariant-DAG execution policy (a typed Scheduler separates correctness from resource + cost)
The Structure of Invariant-DAG execution policy (a typed Scheduler separates correctness from resource + cost) — its shape at a glance:
Every edge declares one of three intents. Only correctness and cost-gate edges live in the graph, which is host-identical; load edges migrate to a typed Scheduler that reads a per-host profile and emits the execution plan.
flowchart LR Edge[Edge intent] --> Corr[CORRECTNESS<br/>in graph] Edge --> Cost[COST_GATE<br/>in graph] Edge --> Load[LOAD<br/>banned in graph] Load --> Sched[Scheduler] Prof[(Per-host profile)] --> Sched Sched --> Plan([Execution plan])
Accessible description: an edge declares one of three intents. Correctness and cost-gate edges stay in the host-identical graph; load edges are banned from it and move to a Scheduler, which reads a per-host profile and produces the execution plan.
Projected from the catalogue entry system-models / Invariant-DAG execution policy (a typed Scheduler separates correctness from resource + cost).
On this page: Intent · Motivation · Applicability · Structure · Sample Code · Consequences · Example use within DocAble · Related Patterns
Intent
Intent — Keep a build/deploy dependency graph a statement of correctness only, host-identical everywhere, and push every environment-specific execution concern — how much may run at once, and whether a costly step is worth running — into a typed Scheduler that reads a per-host profile and produces the execution plan. Separate three edge intents so a reader can tell a real dependency from a resource or budget accommodation (our instance: a deploy DAG whose edges carry a CORRECTNESS/COST_GATE/LOAD intent, with LOAD edges migrated out to a typed Scheduler that reads a per-host (concurrency-ceiling, budget) profile).
Motivation
A build or deploy pipeline is a dependency graph: B needs A means B runs after A. The graph is supposed to state correctness — B produces a wrong or failed result without A. In practice two other concerns leak into the same needs= syntax, and once they do the graph stops meaning one thing:
- Resource rationing dressed as a dependency. A step waits for another not because it consumes its output, but because they contend for one scarce box — a single local worker, the build machine's CPU. The wait is spelled
B needs A, identical to a correctness edge. It is not an ordering fact; it is an accommodation that vanishes the moment the host is elastic. - A cost gate dressed as a dependency. A cheap check gates an expensive one — run the free structural check before the paid GenAI check, so a red free check doesn't burn money. Again spelled
B needs A, and again not a correctness fact: B is not wrong without A, only wasteful.
The graph now lies to its reader: three unrelated intents wear one syntax, and telling them apart takes per-edge archaeology. The usual patch makes it worse — a per-environment conditional in graph construction (if host == "prod": drop this edge) that strips the rationing edges back out where the box is elastic. The graph mutates per host, the strip rule lives in control flow rather than the types, and two failure modes go uncaught: a rationing edge added without the guard silently ships to an elastic host, and a correctness edge caught by an over-broad strip silently disables a gate. One incident is the shape in miniature — a single-worker queue-drain wait modeled as a needs edge took a multi-paragraph root-cause analysis to prove it was "stale convenience, not a true dependency" before it could be removed. The graph did not encode why the edge existed, so every host difference became a prose-guarded special case.
Applicability
- A typed dependency-graph model with addressable edges. The intent axis attaches to edges; without a typed edge record there is nowhere to declare correctness-vs-cost-vs-load.
- A host taxonomy with a real cost or concurrency gradient. The Scheduler earns its keep only when hosts differ — one elastic, one single-boxed, one budget-bounded. A uniform execution environment needs no per-host policy.
- A closed set of rationed resource classes. The load lint decides "is this edge a contention accommodation?" by matching a small, enumerated set of resource classes (the worker queue, the build CPU), read at check time rather than snapshotted.
- A step selector, or at least a stable roster. The graph is a function of the selected roster; the host-invariance claim is stated over the roster two hosts share, so the roster must be addressable separately from the edges.
Structure
The Structure diagram appears at the top of this page.
Sample Code
Each edge carries a typed intent. A lint bans load edges from the graph, and a Scheduler maps a per-host (ceiling, budget) profile to a plan — so raising a host's concurrency is a one-row profile edit, never a graph edit.
from dataclasses import dataclass
import sys
DAG_RESIDENT = {"CORRECTNESS", "COST_GATE"} # the single source the load lint reads
@dataclass(frozen=True)
class Edge:
frm: str
to: str
intent: str # CORRECTNESS | COST_GATE | LOAD
def load_lint(edges: list[Edge]) -> list[str]:
"""A LOAD edge in the graph is a finding — load rationing belongs to the Scheduler."""
return [f"{e.frm}->{e.to}: intent {e.intent} not allowed in graph"
for e in edges if e.intent not in DAG_RESIDENT]
def plan_for(concurrency_ceiling: int, budget: float) -> dict:
"""Pure map from a host profile to an execution plan (permits + cost-gate honoring)."""
return {"permits": concurrency_ceiling, "run_cost_gated": budget > 0.0}
if __name__ == "__main__":
edges = load_graph() # reads the built dependency graph's typed edges
findings = load_lint(edges)
for f in findings:
print(f"LOAD-IN-GRAPH: {f}")
# A new host is a new profile row, e.g. plan_for(concurrency_ceiling=1, budget=0.0)
sys.exit(1 if findings else 0)
Consequences
- The graph gains a second axis to author. Every edge now declares an intent, not just a target. That is the cost of legibility — the intent is what makes the graph honest, but it is an annotation on each edge.
- Behavior identity across the migration must be pinned. Turning a single-worker queue-drain edge into a permits-1 semaphore is behaviorally equal at N=1 but diverges at N>1 (the semaphore permits concurrency the edge forbade). That divergence is a feature — raising the ceiling becomes a one-field profile change — but the migration must carry a check that the low-concurrency host still serializes, or a silent concurrency regression ships.
- Two rationing layers may coexist. A host may already have out-of-graph resource mediators (a test serializer, a build semaphore) that guard a different scope than the deploy Scheduler. Unifying them under one rationing authority is attractive but is its own effort; until then, two layers ration at two scopes.
- The cross-host graph diff has a cost. The invariance lint builds each host's roster and diffs the selection sets; if the builder is slow to import, the lint is expensive and belongs in a deploy-scope gate rather than every commit.
Example use within DocAble
- A
CORRECTNESS/COST_GATE/LOADEdgeIntentenum on the deploy driver, orthogonal to the existing artifact-producerEdgeKind. Only the first two are DAG-resident;LOADis forbidden in the graph and migrates to the Scheduler. A frozenDAG_RESIDENT_INTENTSset is the single source the load lint reads. - A typed
Scheduler— a pureplan_forthat maps a frozenHostLoadProfile(concurrency_ceiling, budget)to a frozenConcurrencyPlan, over three named host profiles: the local host rations to one permit (protect the shared VM), the staging host is the unbounded stress burst, the production host is a low finite smoke ceiling. Moving the stress test from one host to another is a one-cell edit to the profile table. ARationingRecordcarries per-wave ready/launched/queued counts so the plan acts on load. - The load-edge lint: it reads the deploy phase registry and the Scheduler's
DAG_RESIDENT_INTENTS, and reports anyneedsedge whose intent isLOADas a finding. Its companion superset lint calls the host-test selector and asserts the top host dominates each lower host while the two lower hosts stay incomparable — the edge-set-is-host-consistent invariant. Both landed audit-only first (they find zero by construction), pending promotion to blocking.
Related Patterns
- Sibling — journey-criticality-test-placement: a typed policy layer over the same deploy substrate, from the same effort, but a different subject and failure. That derives which tests run on which host from a journey's criticality; this derives the execution plan over a deploy graph by separating edge intents and rationing resource + cost from a per-host profile. One places tests by criticality; the other rations execution by resource and budget. Both consume the same host taxonomy; neither subsumes the other.
- Ground truth — deployment-topology-model: supplies the host taxonomy the per-host profile keys on. The Scheduler's profile is one row per host in that model's terms.
- Enabler — executable-source-of-truth: the edge intents and the per-host profiles are fields on the typed deploy model, one more consumer of that substrate; drift-parity-gates keep the model's graph matching the real deploy phases the lint reads.
- Kin — control-substrate-dependency: both attach typed metadata to edges and compute a decision from it — there a mechanism's blast radius over the substrate it guards, here an edge's intent driving whether the Scheduler honors, rations, or ignores it. Same reflex of making an edge's meaning a typed field a tool reads, applied to a different graph.