ADR 0024: Airflow executable init-fetch uses a strict versioned wire boundary¶
- Status: Accepted
- Date: 2026-07-19
- Amends: ADR 0007, ADR 0008, ADR 0009
Context¶
The Airflow deployment-index v1 contract was introduced for local, parse-safe
projection. It can describe runtime_artifact_delivery.mode: init_fetch, but it
does not carry the complete executable image, artifact, trust-policy, and
workload-pack contract required to run that mode safely. Treating an incomplete
v1 declaration as runnable would let the provider fall back to scheduler-copied
commands or inline bootstrap content.
The hardening increment needs one unambiguous executable wire while preserving the existing v1 local-preview journey. It also needs to state the actual trust boundary. Fixed pod commands prevent a workload pack from replacing provider owned execution fields; they do not make a compromised scheduler, provider, or local deployment cache harmless.
Decision¶
Freeze the wire and migration boundary¶
The provider applies this closed policy before installing any DAG:
| Index wire | Delivery mode | Result |
|---|---|---|
dpone.airflow-deployment-index.v1 |
local_preview |
Supported non-runnable preview compatibility lane. |
dpone.airflow-deployment-index.v1 |
init_fetch |
Reject with DPONE_RUNTIME_ARTIFACT_DELIVERY_MIGRATION_REQUIRED; regenerate an immutable v2 deployment. |
dpone.airflow-deployment-index.v2 |
init_fetch |
The only strict executable indexed lane. |
| v2 | Any other known mode | Reject with DPONE_RUNTIME_ARTIFACT_DELIVERY_MODE_UNSUPPORTED. |
| Either wire | Unknown mode or malformed field | Reject as an index field/schema error. |
Strict fields are not retroactively required in v1. A v1 deployment is never silently reinterpreted as v2, and operators never repair an immutable index in place.
Make the executable path explicit¶
The only indexed executable path is:
producer
-> immutable release/deployment
-> airflow-deployment-index.v2
-> trusted local cache/current snapshot
-> lightweight provider
-> KubernetesPodOperator
-> dpone airflow runtime-init-fetch
-> runtime-fetch-ready.json
-> dpone airflow runtime-pack-exec
-> verified workload argv
The v2 index carries an exact digest-pinned OCI runtime image, exact
release/deployment/workload descriptors with positive byte counts, a workload
identity, bounded ConfigMap references, and one immutable delivery context. The
provider derives a canonical plan no larger than 16 KiB, transports it as
DPONE_INIT_FETCH_PLAN_B64, and pins its digest in
DPONE_INIT_FETCH_PLAN_SHA256 and the pod annotation.
Bind hook ownership to the execution plan¶
The deployment index remains v2, while newly generated per-task runtime plans
use dpone.airflow-runtime-init-fetch-plan.v3. Plan v3 adds:
execution.scope, eitherprocessfor one exact process-plan node orworkloadfor the explicitly unselected whole-workload path;execution.process_selector, the exact selected process plan ornullfor the default process whenscope=process; it is alwaysnullforscope=workload;execution.hook_execution, eitherinlineorexternalized.
The provider derives both fields from the resolved DAG node that creates the
KPO. Runtime verifies that the selected process exists in the fetched,
fingerprint-verified workload pack before applying hook policy. inline
removes DPONE_SKIP_AIRFLOW_SEPARATE_HOOKS from the child environment;
externalized requires the skip flag because a separate Airflow hook task owns
the action. A pre_hook execution is always externalized.
Whole-workload execution is also always externalized; it cannot infer one
process node's inline ownership. Before installing a whole-workload DAG, the
provider proves that every process-local separate hook has an equivalent
top-level Airflow task. Incomplete ownership fails parse-time with
DPONE_INIT_FETCH_HOOK_OWNERSHIP_INCOMPLETE; runtime is never allowed to skip
an unowned hook.
Process-scoped runtime execution selects the exact process-keyed bootstrap
entry (<selector> or the reserved __default_process__) and verifies that
its argv carries the same selector. Process-plan metadata continues to use
__default__; command keys use a separate namespace so a valid workload ID
named __default__ cannot collide with default-process execution.
Workload-scoped execution selects the workload-keyed bootstrap entry
__workload__, whose argv has no process selector. The launcher never applies
hook policy from one process plan while running another process command.
Separately materialized hooks use
dpone.airflow-pre-hook-command.v1 inside the selected static step. This
structured command binds the display step name, canonical hook ID, process
selector, manifest path and argv without reparsing the human-readable
command field. Any mismatch fails before source I/O.
Runtime plan v1 and v2 remain readable for immutable deployment rollback. Their
wire selector identifies the workload, not a process, so runtime accepts the
bridge only when the fetched pack contains exactly one selector-coherent process
plan. Every legacy multi-process pack fails with
DPONE_RUNTIME_ARTIFACT_INTEGRITY_FAILED before source I/O. Missing or
malformed process-plan metadata also fails closed; runtime never picks the
first mapping entry. During the bounded provider/runtime bridge, a generated
structured pre-hook is accepted only when its canonical command is unique
across the pack and its human-readable legacy command tokenizes to the same
argv. Conflicting process-local hooks fail closed.
Whole-workload hook ownership compares the complete canonical descriptor: process selector, executable argv, dependencies, required and credential policies, produced evidence, and reason. Matching display labels alone never transfer execution ownership. The nested structured command schema is closed; packs with unknown fields must be rebuilt.
The runtime launcher removes the reserved skip variable from its inherited process environment before applying the verified command environment. This prevents a pod-level or image-level ambient variable from overriding the verified plan decision.
The provider replaces pack-owned executable fields with these fixed commands:
Both containers use the exact same digest-pinned image. The base launcher revalidates the ready manifest and fetched pack, selects a structured argv from that pack, and launches it without invoking a shell. The ready manifest binds the pinned trust-policy digest, the effective attestation requirement selected by init, and its attestation status. The base launcher validates that decision against the same plan and does not reread the init-only registry or trust-policy ConfigMap. Runtime commands retain their XCom outcome gate, while pre-hook and dbt commands propagate the real child exit code so Airflow cannot mark a failed prerequisite successful.
Keep pod cleanup inside the trusted provider policy¶
Strict composition fixes get_logs=false: the operator must not poll the base
container while init containers still own a PodInitializing pod. It also sets
on_finish_action=delete_succeeded_pod. Pack fields cannot override either
value.
This is provider-native, best-effort cleanup rather than a durable retention
state machine. The pinned Kubernetes provider decides cleanup from pod/base
container state. A task may still fail later during XCom handling or callbacks,
and cancellation follows provider on_kill semantics. Kubernetes deletion
errors can leave a pod behind and do not fail an already committed data load.
Production therefore requires the separate dpone runtime-Pod retention control
or an explicitly approved delete-all policy. The control inventories only
metadata in one namespace, selects provider-owned managed/contract labels plus
the provider-projected workload label, queries only Succeeded and Failed
phases, and conditionally deletes with UID/resourceVersion preconditions. Run
correlation comes from Airflow dag_id, task_id and run_id labels;
malformed correlation preserves the Pod. Infrastructure owns CronJob rollout,
ServiceAccount, durable logs, alerts and acceptance evidence.
The metadata queries for Failed and Succeeded phases consume one shared
10,000-item/32-MiB budget. An accepted Kubernetes delete request is reported as
delete_accepted; it is not promoted to observed absence. A later bounded
inventory cycle supplies convergence evidence. Apply records the selected
credential mode and identifies Kubernetes API RBAC as authorization authority;
only rendered in-cluster execution is specifically ServiceAccount-backed.
The control must not treat Pod retention as data success evidence. Airflow task
logs contain orchestration output only when get_logs=false; structured XCom,
runtime evidence and load-step audit remain the durable execution truth.
Runtime stdout requires an independently certified cluster log collector. The
metadata-only v1 janitor reports creation-time fallback explicitly and does not
claim exact time-since-terminal semantics.
Use deployment-set v2 as the runtime receipt¶
The exact fetched dpone.deployment-set.v2 is the runtime deployment receipt.
The runtime does not generate or consume a second compact receipt schema. It
verifies the fetched bytes against the plan descriptor, recomputes
deployment_id, and requires the receipt to mirror the pinned release,
runtime image, trust tier, registry reference, ConfigMap references, workload
identity, verification policy, and selected workload inventory.
This keeps one environment-specific deployment authority and avoids two
nominally equivalent receipts drifting. The fetched release contract is
release-set.v1 for legacy/non-dbt releases. The compact Airflow compatibility
transport may carry an optional bounded runtime_payloads inventory in v1 so
already-authored packs can reach the verified init-fetch boundary; that
inventory proves byte identity only and is not dbt selection or promotion
authority. The compact-v1 materializer accepts at most 64 distinct payloads,
at most 268,435,456 bytes per payload, and at most 536,870,912 actual bytes
across the distinct release-wide inventory. Equality with either byte boundary
passes. An aggregate breach produces no release directory, release identity,
or success evidence, and has no override flag. This producer budget does not
make v1 authoritative and is not a universal ceiling on release-set v2. This
v1 bridge is non-production only. Production build and runtime
receipt validation reject a v1 release whenever its release-wide
artifacts.runtime_payloads inventory is non-empty, even when the selected
workload plan does not reference those payloads. release-set.v2 remains
mandatory for dpone dbt compile, protected dbt promotion,
route-certification authority, and production dbt evidence. The selected
release, payload descriptors, and workload pack are verified separately before
the ready manifest is published.
Require explicit trust tier and one verified ConfigMap snapshot¶
trust_tier is mandatory in both the v2 index and its
runtime_artifact_delivery block, and the two values must match. Allowed values
are production and non_production; environment names do not infer trust.
Production requires a digest-pinned trust_policy_ref and
verify.attestations: required_for_prod. The plan expresses a requested
minimum. A trusted runtime policy may strengthen it and cannot be weakened by
the plan. The first stock production adapter is the offline
github_artifact_attestation_v1 verifier defined by ADR 0033. Its closed v2
policy pins repository, signer workflow and commit, predicate, issuer,
self-hosted-runner denial, trusted root, and GitHub CLI security range. Runtime
derives one immutable bundle key from the pinned release/subject digests and
performs no listing, current resolution, GitHub API call, or token-based
lookup. Production without complete verifier material still fails before
ready-state publication and never downgrades to checksum-only success.
ConfigMap names are selectors, not integrity evidence. The init process acquires one bounded snapshot of each selected file, verifies the SHA-256 of those exact bytes, and parses those same bytes. A mismatch fails before registry I/O. Changing configuration requires a new reviewed ConfigMap snapshot and v2 deployment identity; in-place mutation is not a rollout mechanism.
Reserved pod storage is fixed:
| Volume | Init mount | Base mount |
|---|---|---|
dpone-fetched-artifacts |
RW /var/lib/dpone/artifacts |
RO /var/lib/dpone/artifacts |
dpone-worktree |
RW /workspace/repo |
RO /workspace/repo |
dpone-run-output |
absent | RW /var/lib/dpone/run |
dpone-artifact-registry-config |
RO /etc/dpone/artifact-registry |
absent |
dpone-artifact-trust-policy |
RO /etc/dpone/artifact-trust when selected |
absent |
The provider rejects collisions with image, command, arguments, namespace, service account, security context, init containers, reserved volumes, and reserved mounts. Scheduling and resource fields remain the bounded extension surface.
Strict scheduler extensions are carried only by the closed
dpone.airflow-provider-execution.v1 projection inside the workload pack.
The projection contains bounded task identity/labels/environment plus
scheduling fields and the single base-container resource request. It is part
of the canonically derived whole-pack fingerprint. Producer, materializer,
provider, runtime init and base launcher recompute that identity instead of
comparing copied claims. Legacy top-level kpo_kwargs, pod_spec and
runtime_command remain available to the explicit raw/local compatibility
lane, but the indexed v2 provider never treats them as scheduler authority.
A missing projection requires an immutable pack/release/deployment rebuild;
unknown or reserved projection fields fail closed. This explicit split
replaces the earlier ambiguous behavior where the provider could silently
discard legacy executable fields.
The dependency-light provider package is the single structural authority for this projection. Its constants produce the JSON Schema consumed by the root GitOps schema catalog and constrain the executable validator; independently maintained schema copies are forbidden. Structural boundaries are covered by schema/runtime parity tests.
Logical workload identity is not a Kubernetes label contract. The complete ID remains authoritative in the pack, DAG spec, artifact identity and evidence. The shared provider contract derives a Kubernetes-safe label value: an already valid value of at most 63 characters is preserved, while every other value uses a bounded readable prefix plus a SHA-256 suffix. The complete ID is placed in a provider-owned pod annotation. Producer and provider must use this same projection, preventing admission-time failures and prefix-truncation collisions without breaking existing short IDs.
Bounded resources and explicit service-file publication (2026-09-10)¶
Workload authoring exposes airflow.resources (gitops.airflow.resources in
manifests) for CPU, memory and ephemeral-storage requests/limits. The
dependency-light provider owns their shared quantity/structural validator;
canonical core authoring policy composes it without importing an Airflow or
Kubernetes SDK. The compiler validates and projects these values before pack
identity is computed. Resource-only changes also enter authoring semantic
identity so selective builds cannot omit them. Absent settings preserve legacy
identity and Pod defaults. Existing v1 extended resource names retain bounded
structural compatibility; the new authoring surface admits only three names.
Runtime and separate-hook base containers inherit the same resources. No arbitrary Pod override is enabled; resource-capable overrides are rejected before strict rewriting can discard them. Init-fetch and sidecar resource policy remain unchanged. The resource guide specifies exact quantity bounds, comparison, precedence and upgrade behavior.
Verified execution derives XCom publication independently from the child exit policy. Runtime/dbt publish through the KPO-provided writable XCom volume; pre-hooks use the existing writable run emptyDir and never require XCom path access. Canonical runtime helpers prepare service files and report safe OS failure stage/type/errno. Workload/artifact mounts remain read-only, hook child failures still block runtime, and no security-context authority is added. See resources and startup diagnostics.
Record the current trusted computing base¶
For this increment, the trusted computing base includes:
- the producer and immutable release/deployment identity process;
- the promoted local cache snapshot and
airflow-index.json; - the Airflow scheduler/DAG processor and installed dpone provider code;
- the Kubernetes control plane and kubelet enforcing the admitted pod;
- the digest-pinned runtime image and its dpone CLI/runtime code;
- workload-identity/IAM enforcement, the selected registry adapter, and the configured attestation verifier and policy snapshot.
The scheduler, provider, and local cache are explicitly trusted. This decision does not claim resistance to their compromise. A malicious scheduler or provider can submit a different pod or plan, and a malicious trusted cache can change the input accepted by that provider. The fixed KPO contract narrows pack-owned and ordinary override authority inside this TCB.
Consequences¶
Positive consequences:
- v1 compatibility and v2 executable behavior cannot be confused;
- incomplete or unsupported delivery fails before partial DAG installation;
- the base container cannot use legacy scheduler-copied shell or inline archive fields on the strict path;
- configuration, artifacts, runtime image, and workload identity are bound to one reviewable deployment contract;
- init failure occurs before the base workload, data I/O, XCom success, checkpoint mutation, or source-state advance.
- successful pod cleanup is requested without reintroducing base-log polling; failed or orphaned pod retention remains an explicit infrastructure concern.
Costs and limitations:
- existing v1
init_fetchindexes must be regenerated, published, materialized, and promoted under a new immutable deployment identity; - producer, provider, and runtime image versions must understand the same v2 deployment-index wire; newly generated tasks additionally require provider and runtime support for plan v3;
- production remains fail-closed until a concrete verifier is configured;
- compromised-scheduler resistance would require a separate admission, signing, or policy-enforcement design outside the current TCB.
delete_succeeded_poddoes not guarantee retention for every task failure or deletion of every successful pod; cancellation, late failures and Kubernetes cleanup errors require external observation and stale-pod governance.
Evidence and certification status¶
Current unit and contract tests exercise v1/v2 mode policy, canonical plan transport, fixed pod composition, snapshot digest mismatch, ready-manifest publication, deployment/release receipt verification, post-init checksum and attestation-downgrade rejection, and shell-free argv preparation.
Live Kubernetes projected volumes, workload identity, production attestation,
Vault integration, MSSQL, and ClickHouse execution are UNVERIFIED for this
decision unless current evidence from the exact release commit and environment
is attached. Local or mocked tests are not live certification.