Feature design: Airflow S3 desired-state pull control plane¶
- Status: APPROVED
- Owner: dpone maintainers
- Issue: Canonical Airflow Pack Recovery And Deployment
- Target release: 0.73.24
- Approval authority: maintainer-approved pull activation direction, 2026-07-28 Last verified: 2026-07-28
Retry-boundary hardening through the canonical cross-job preparation artifact
targets 0.73.25 and remains within this approved control-plane contract.
Executive summary¶
Airflow must recover generated dpone DAGs after a pod restart and receive new
immutable deployments without a GitLab pipeline mutating Airflow
infrastructure on every release. The current deployment uses pod-local
emptyDir cache and two competing synchronization paths. A restart can remove
one cache while the DAG loader silently observes no generated DAGs.
This feature introduces a connector-neutral desired-state control plane:
DAG repository protected branch
-> build and exact immutable publication
-> compare-and-swap desired-state object
-> fail-open Airflow init/watcher
-> verified local materialization
-> atomic current activation
-> network-free DAG parse
-> loader acknowledgement and Airflow REST convergence
Infrastructure installs one generic watcher. It does not participate in each
release. The watcher is analogous to git-sync, but it synchronizes exact,
precompiled, content-addressed dpone deployments from object storage rather
than Git source code.
The measurable outcome is:
- an Airflow cache restart restores the exact desired deployment without a repository-specific infrastructure pipeline;
- DAG parsing performs no object-storage or credential I/O;
- a corrupt, partial, stale, or concurrently replaced desired state never
becomes
current; - an object-storage outage preserves the last known good deployment;
- activation and rollback are bound to immutable IDs, checksums, a unique activation occurrence, loader acknowledgement, and REST convergence.
This belongs in dpone because desired-state identity, publication evidence, cache materialization, loader acknowledgement, and Airflow deployment convergence are framework contracts shared by every dpone Airflow consumer.
Personas and customer journey¶
| Persona | Goal | Current pain | Success signal |
|---|---|---|---|
| Data engineer | Merge a dpone workload and see its DAG | Deployment depends on hidden cache state | MR merge produces one traceable deployment |
| Airflow operator | Survive restarts and S3 incidents | Empty cache can hide generated DAGs | Last known good remains active or a visible diagnostic explains an empty cache |
| Platform engineer | Operate dev and production consistently | Per-release infra pipelines and tokens couple repositories | Infra installs one environment-neutral watcher |
| Release manager | Promote and roll back exact content | Mutable latest does not identify runnable bytes |
Promotion selects exact release and deployment IDs |
| Security engineer | Bound write and read authority | Cross-project tokens broaden mutation rights | CI writes one allowlisted control key; watcher is read-only |
End-to-end journey¶
- An engineer changes a workload in the DAG repository.
- CI validates manifests, packs, DAG parsing, ownership, and policy.
- The protected branch pipeline builds one immutable release and deployment.
- Exact publication uploads content, reads it back, and verifies size and SHA-256 before emitting publication evidence.
- The environment promotion job checks protected-branch freshness and writes one desired-state envelope with conditional object-storage semantics.
- The Airflow watcher reads the envelope on its next bounded cycle.
- If the desired deployment differs, the watcher stages and verifies the deployment, then delegates local activation to the existing cache materializer.
- The DAG processor parses only the local
current/airflow-index.json. - The provider writes a local loader acknowledgement.
- CI or an operator verifies exact Airflow REST convergence.
- Rollback writes a new desired-state occurrence that points to an older immutable deployment. It never mutates that deployment.
Common recovery:
- object storage unavailable with an active cache: continue with last known good and publish a warning;
- object storage unavailable with an empty cache: Airflow starts, ordinary DAGs remain available, and the dpone loader emits a deterministic import diagnostic;
- invalid desired state: reject it, retain current, and publish a blocker;
- stale writer: conditional write loses with no hidden overwrite;
- uncertain write timeout: read back and reconcile exact bytes before deciding success, conflict, or uncertain state.
Scope¶
In scope¶
- One versioned desired-state envelope for one Airflow environment.
- Connector-neutral read and conditional-write ports.
- First certified adapter for S3-compatible object storage.
- Conditional create and replace using opaque object-store revisions.
- Immutable release/deployment references and exact publication evidence.
- Bounded local fetch, validation, atomic projection, and activation.
- Fail-open init/watcher process behavior and fail-visible empty-cache loader.
- Airflow 2.10 and 3.2 component topology.
- Forward design for Airflow 3.3 custom versioned DAG bundles.
- Dev automatic promotion and production protected manual promotion.
- Loader acknowledgement and Airflow REST convergence.
- Cache and remote retention protection for active control state.
Non-goals¶
- General Kubernetes operator or arbitrary filesystem synchronization.
- Mutable
latestas a runnable deployment identity. - Parsing or executing DAG source directly from S3.
- Installing a watcher in every KubernetesExecutor worker.
- Object-storage calls in DAG top-level code.
- Distributed consensus across multiple desired-state keys.
- Automatic production promotion without an explicit protected gate.
- Claiming live certification from fake S3, MinIO, rendered Helm, or mocked Airflow API evidence.
Assumptions and constraints¶
- Release and deployment objects are immutable and content-addressed.
- The desired-state object is the only mutable control object.
- The writer can update only an allowlisted environment key.
- Airflow receives read-only access to the required artifact and control roots.
- Cache activation uses a local POSIX filesystem with an atomic relative symlink switch.
- Cache budget remains bounded; the default is 512 MiB and three generations.
- Scheduler and DAG processor topology differs between Airflow 2.10 and 3.2.
- Every unsupported object-store conditional-write capability fails before a desired-state mutation.
Public contract¶
CLI¶
The final command names may be reconciled with the existing dpone airflow
group during implementation. Required behavior is:
dpone airflow desired-state prepare \
--promotion-evidence .ci/out/dpone_deployment_promotion.json \
--expected-revision "${OBSERVED_REVISION:-absent}" \
--output .ci/out/dpone_desired_state_publish_preparation.json \
--status-output .ci/out/dpone_desired_state_prepare_error.json
dpone airflow desired-state publish \
--connection-type airflow \
--connection-id s3_dpone_artifacts_writer \
--promotion-evidence .ci/out/dpone_deployment_promotion.json \
--preparation .ci/out/dpone_desired_state_publish_preparation.json \
--output .ci/out/dpone_desired_state_publish.json \
--status-output .ci/out/dpone_desired_state_publish_error.json
dpone airflow desired-state fetch \
--connection-type airflow \
--connection-id s3_dpone_artifacts_reader \
--output /var/lib/dpone/desired-state/snapshot.json \
--status-output /var/lib/dpone/cache/status/desired-state.json
dpone airflow desired-state reconcile \
--connection-type airflow \
--connection-id s3_dpone_artifacts_reader \
--artifact-connection-type airflow \
--artifact-connection-id s3_dpone_artifacts_reader \
--cache-root /opt/airflow/.dpone-cache
Environment, fixed desired-state key, certified S3 endpoint, registry root/ref,
watcher identity, source project, and protected ref come only from the
protected DPONE_AIRFLOW_DESIRED_STATE_AUTHORITY_FILE. They are deliberately
not CLI parameters.
publish:
- exit
0: created, replaced, or byte-identical idempotent replay; - exit
2: usage or input-contract failure; - exit
4: conditional-write conflict, stale source, unsupported capability, integrity failure, or uncertain remote state; - never performs an unconditional overwrite;
- writes evidence atomically and never emits credentials, signed URLs, or raw connection payloads.
fetch:
- performs one bounded read;
- validates UTF-8, duplicate JSON keys, schema, environment, identifiers, promotion digest, and size;
- writes one complete local snapshot atomically;
- never activates a cache directly;
- reports unchanged revisions as a no-op.
reconcile is the canonical single-cycle watcher command:
- obtains a single-writer cache-root lock;
- performs conditional fetch from the last committed checkpoint revision;
- delegates schema and canonical-byte validation to the same OSS contracts as
publishandfetch; - materializes the exact immutable deployment through the existing registry service;
- rechecks the remote revision inside the local cache promotion lock;
- activates only while both remote desired state and local current CAS guards still match;
- commits the revision checkpoint only after successful activation;
- emits bounded credential-free evidence;
- returns non-zero for the wrapper to classify, while the infra init wrapper
still exits
0and preserves last-known-good.
Infrastructure must not reimplement the desired-state schema, accept an arbitrary reader command, or parse vendor SDK payloads.
Python API¶
Canonical models and ports:
from dpone.contracts.airflow_desired_state import (
AirflowDesiredDeployment,
DesiredStatePublishEvidence,
DesiredStateRevision,
)
from dpone.ports.airflow_desired_state import (
AirflowDesiredStateReader,
AirflowDesiredStateWriter,
)
Application services receive ports, clocks, identity generators, size policy, and retry policy through dependency injection. Vendor SDK clients are constructed only in adapters/composition roots.
Desired-state schema¶
Stable schema:
The envelope contains:
schema: dpone.airflow-desired-deployment.v1
environment: dev
source:
project: group/repository
pipeline_id: "123"
job_id: "456" # job that durably created the promotion occurrence
occurrence_id: "uuid"
git_sha: "<40 or 64 lowercase hex>"
promotion:
release_id: "sha256:..."
deployment_id: "sha256:..."
airflow_index_sha256: "sha256:..."
runtime_image_digest: "sha256:..."
expected_dag_ids: []
publication_evidence_sha256: "sha256:..."
previous:
revision: "<opaque revision or null>"
deployment_id: "sha256:... or null"
promoted_at: "RFC3339 UTC"
The schema is strict and bounded to 72 KiB. Unknown, duplicate, missing, or
secret-like fields are rejected. As superseded by ADR-0032, the UUIDv4
occurrence ID identifies one trusted desired-state activation and is persisted
as activation_id by every pod-local switch for that occurrence. A standalone
local promotion still generates a local activation UUID because it has no
trusted desired-state occurrence.
Revision and checksum semantics¶
DesiredStateRevision.valueis an opaque bounded token supplied by the object store.- An S3 ETag is never interpreted as MD5 or SHA-256.
- SHA-256 identifies canonical desired-state bytes and immutable artifacts.
- Initial creation uses
If-None-Match: *. - Replacement uses
If-Match: <exact observed revision>. - No adapter may fall back to an unconditional write.
Artifacts and evidence¶
New schemas:
dpone.airflow-desired-deployment.v1
dpone.airflow-desired-state-publish-intent.v1
dpone.airflow-desired-state-publish-preparation.v1
dpone.airflow-desired-state-publish.v2
dpone.airflow-desired-state-fetch.v1
dpone.airflow-desired-state-reconcile.v1
Publish evidence v2 records both preparation_job_id and
publisher_job_id. The desired deployment remains byte-stable across GitLab
job retries: its source.job_id identifies the job that created the durable
occurrence, while publication evidence identifies the job that actually
performed or reconciled the conditional mutation. Publish evidence v1 remains
registered for consumers of the 0.73.24 contract.
Existing schemas remain distinct:
- publication evidence proves immutable remote bytes;
- desired-state evidence proves an ordered environment selection;
- cache activation evidence proves a local
currentswitch; - loader acknowledgement proves one parse occurrence;
- convergence evidence proves the Airflow control plane exposes the expected deployment.
No one artifact substitutes for another.
Compatibility and migration¶
- Existing immutable release/deployment and provider contracts remain valid.
- Existing local
cache-materializeandcache-syncremain the activation implementation. - ConfigMap desired-state projection remains readable only during migration.
- The GitLab cross-project activation path becomes inactive after pull certification; it is not a fallback that can silently race the S3 writer.
- Local committed packs may remain a documented rollback during dev cutover,
but provenance must identify
local_fallback. - Airflow 2.10 and 3.2 use the same desired-state schema.
- Airflow 3.3 may later implement the reader/materializer as a custom versioned DAG Bundle without changing release, desired state, checksums, or evidence.
- The desired deployment, authority, publish-intent, checkpoint, receipt, and reconcile v1 contracts were first released in 0.73.24. The preparation and publish-evidence v2 contracts are additive in 0.73.25; publish evidence v1 remains registered for compatibility.
Detailed algorithm¶
Publish and promote¶
- Validate the protected branch, exact source SHA, environment, promotion evidence schema, and immutable publication commitment.
- Read the authoritative desired-state key with a bounded response.
- If absent, require an expected-absent guard.
- If present, validate it and preserve its opaque revision and deployment ID.
- In a non-mutating preparation job, persist one bounded canonical preparation artifact containing the complete publish candidate, observed predecessor, occurrence ID, and timestamp. GitLab retains that artifact independently of the mutating job container. The candidate includes a credential-free SHA-256 identity of the complete write authority: environment, desired-state key, certified endpoint, registry scope, watcher, repository, and protected ref.
- Refuse a source pipeline whose SHA is no longer the protected branch head.
- The mutating job consumes the exact preparation, revalidates it against the trusted authority and immutable promotion evidence, and never creates a new occurrence. Retrying only this job therefore reuses the same bytes.
- If the same occurrence already exists:
- equal canonical bytes: idempotent success;
- different bytes: corruption blocker.
- Create with
If-None-Match: *or replace with exactIf-Match. - On
412, return a terminal non-mutating conflict. - On
409, perform bounded read/reconcile and retry only while the observed predecessor has not changed. - On any unknown outcome after dispatch, including a missing revision in an
otherwise successful response, read back:
- equal bytes: idempotent success;
- different valid bytes: another writer won;
- absent, unavailable, or otherwise inconclusive read: blocked with
state_may_have_changed=trueand no second write dispatch.
- Emit publish evidence only after the remote state is reconciled. Evidence records the preparation job and the actual publishing job separately.
The preparation schema is
dpone.airflow-desired-state-publish-preparation.v1. Its canonical bytes are
the retry boundary. Repeating preparation at the same artifact path with the
same candidate returns the existing bytes; a different candidate is blocked.
Retrying only the mutating job consumes that retained artifact. Re-running the
whole pipeline uses a new artifact path and is a new promotion attempt. The
legacy single-job --intent path remains compatible for local/operator use,
but it is not the canonical GitLab retry contract.
Watcher fetch and activation¶
- Sleep for configured interval plus bounded jitter.
- Read desired state conditionally using the last observed revision.
- On unchanged state, validate the complete active projection and only then validate its immutable activation receipt and update heartbeat/status without materialization.
- On changed state, enforce response size before JSON decoding.
- Validate canonical envelope, endpoint-bound registry authority, protected source ref/SHA, IDs, digest, and the full materialized projection.
- Classify the local transition as
bootstrap,continuous,recovered, orskipped. A missed intermediate occurrence does not block convergence to the latest CAS-protected authority; it is explicit durable evidence. When a verified active projection exists but its receipt/checkpoint is incomplete, validate and reuse its immutable activation receipt, or reconstruct the missing activation fact, then emit separatestatus=recoveredcycle evidence without claiming a second activation. - Durably stage the exact desired bytes and observed opaque revision in the bounded pre-activation recovery record.
- Write, fsync, and atomically switch the local desired-state snapshot.
- Materialize the exact immutable release/deployment into a candidate cache.
- Verify every declared file size and SHA-256.
- Invoke local cache activation with:
- current deployment CAS;
- desired-state bytes digest as precommit guard;
- environment and allowed actor policy.
- Generate a new UUIDv4
activation_idonly for a real activation. - Leave the previous
currentunchanged on any failure before the atomic switch. - Before mutation, replace the latest status with a bounded non-passing in-progress record.
- If a previous activation switched D2 but receipt/checkpoint still name D1, recover D2 from the staged record before reading or applying newer D3.
- Under the same promotion lock, commit an immutable activation receipt, checkpoint, and latest bounded status in that order.
- Continue the sidecar loop regardless of one failed cycle.
Init recovery¶
- At pod startup, execute one bounded watcher cycle with a hard timeout.
- Exit
0regardless of object-storage availability or desired-state validity, so the Airflow main container can start. - If a last known good cache exists, leave it active.
- If
emptyDiris empty and the fetch succeeds, activate the exact desired deployment. - If
emptyDiris empty and fetch fails, create no syntheticcurrent. - The provider then emits an explicit bounded import error for generated dpone DAGs; ordinary DAGs continue to load.
- The long-running watcher retries later and can recover without pod restart.
Rollback and roll-forward¶
- Operator selects a previously verified immutable deployment.
- Promotion creates a new desired-state occurrence with the current revision as predecessor.
- Conditional replacement serializes rollback against other promotions.
- Watcher materializes and activates the selected deployment.
- Loader acknowledgement and REST convergence must pass again.
- Roll-forward repeats the same algorithm with another new occurrence and activation ID.
Pseudocode¶
function promote(candidate, expected_revision):
current = store.read_bounded()
validate_predecessor(current, expected_revision)
validate_source_is_current_protected_head(candidate.source)
body = canonical_json(build_envelope(candidate, current))
if current.occurrence_id == candidate.occurrence_id:
return identical(current.bytes, body) ? IDEMPOTENT : CORRUPTION
result = current.absent
? store.create_if_absent(body)
: store.replace_if_revision(current.revision, body)
return reconcile_conditional_result(result, body, current.revision)
function watch_cycle(last_revision):
remote = store.read_if_changed(last_revision)
if remote.unchanged:
write_heartbeat(NOOP)
return
desired = validate_bounded_envelope(remote.bytes)
snapshot = atomic_local_snapshot(desired, remote.revision)
candidate = materialize_exact(snapshot.promotion)
verify_all_bytes(candidate)
cache_sync(
candidate,
expected_current=current_deployment(),
desired_guard_sha256=sha256(snapshot.bytes),
)
write_status(SUCCESS)
State machine¶
stateDiagram-v2
[*] --> PublishedImmutable
PublishedImmutable --> DesiredWritePending
DesiredWritePending --> DesiredCommitted: CAS winner
DesiredWritePending --> DesiredConflict: stale revision
DesiredWritePending --> DesiredUncertain: timeout and unreadable state
DesiredCommitted --> Observed
Observed --> CandidateMaterialized
CandidateMaterialized --> CandidateVerified
CandidateVerified --> LocallyActivated
LocallyActivated --> LoaderAcknowledged
LoaderAcknowledged --> Converged
Observed --> LastKnownGood: fetch or validation failure
CandidateMaterialized --> LastKnownGood: integrity failure
CandidateVerified --> LastKnownGood: local CAS loss
LastKnownGood --> Observed: later watcher cycle
Concurrency and ordering¶
- CI uses an environment-specific resource lock to reduce avoidable races.
- Object-storage CAS remains authoritative even when CI serialization fails.
- Stale writers never retry against a newly observed predecessor without rebuilding and re-authorizing their desired state.
- Watcher cycles are single-writer per cache root.
- Multiple Airflow parse-authority pods may independently converge on the same immutable deployment; REST convergence and loader ACK prove the cluster view.
- Jitter prevents all pods polling at the same instant.
Resource limits¶
- desired-state object: 72 KiB;
- desired snapshot: 72 KiB, matching the remote desired-state contract;
- pre-activation recovery record, checkpoint, immutable activation receipt, and latest reconcile status: 96 KiB each, enforced symmetrically on read and write;
- bounded status and log tails;
- default poll interval: 60 seconds, configurable;
- maximum conditional-write reconciliation attempts: four;
- cache: 512 MiB, current plus two previous verified generations;
- no download when the desired revision is unchanged;
- remote retention must protect the current desired state and all referenced active immutable objects.
Failure classification¶
| Failure | Runtime action | User action |
|---|---|---|
| S3 read timeout with current | Preserve current, warning | Check S3/network if stale beyond SLO |
| S3 read timeout with empty cache | Airflow starts, dpone import diagnostic | Restore reader access; watcher retries |
| Desired schema/hash mismatch | Preserve current, blocker | Re-publish from protected CI |
Conditional write 412 |
No mutation, conflict | Re-run promotion from current head |
Conditional write 409 |
Bounded reconcile | Investigate only if retries exhausted |
| Ambiguous write timeout | Read-back reconcile | Manual recovery only when state remains unknown |
| Immutable artifact mismatch | Preserve current, blocker | Quarantine release and republish |
| Local cache CAS loss | Preserve winner, no forced retry | Observe next desired cycle |
| Loader ACK stale/missing | Not converged | Diagnose DAG processor parse |
| 7/8 expected DAGs visible | Not converged | Inspect load report and missing DAG |
Architecture¶
Components and responsibilities¶
| Component | Existing/new | Responsibility | Dependencies |
|---|---|---|---|
AirflowDesiredDeployment |
New | Strict immutable value model | Standard library/contracts only |
DesiredStateRevision |
New | Opaque conditional-write revision | Contracts only |
AirflowDesiredStateReader |
New port | Bounded read and conditional read | No vendor types |
AirflowDesiredStateWriter |
New port | Conditional create/replace | No vendor types |
| Desired-state publisher | New service | Validate, order, CAS, reconcile, evidence | Ports, clock, ID provider |
| Desired-state fetcher | New service | Bounded validation and atomic local snapshot | Reader port, filesystem policy |
| S3 desired-state adapter | New adapter | Map port to S3 conditional APIs | Lazy SDK import |
| Cache materializer/sync | Existing | Verify immutable bytes and switch local current | Local filesystem |
| Airflow loader/ACK | Existing | Network-free parse and local evidence | Lightweight package |
| Desired-state reconciler | New service | One locked fetch/materialize/remote-recheck/local-CAS cycle | Ports and existing cache services |
| Infra init/watcher | New composition | Schedule bounded reconcile cycles and classify exit evidence | CLI only |
Ports, adapters, and composition root¶
Contracts and ports never import Airflow, GitLab, Kubernetes, boto3, or cloud
SDKs. The S3 adapter translates If-None-Match, If-Match, 304, 409,
412, timeouts, and opaque ETags. CLI composition resolves the object-storage
connection and injects the adapter. The watcher is deployment composition, not
domain policy.
Data and control flow¶
flowchart LR
Git["Protected DAG repository"] --> CI["Shared CI"]
CI --> Immutable["Immutable S3 release/deployment"]
Immutable --> Desired["CAS desired-state object"]
Desired --> Watcher["Fail-open init/watcher"]
Watcher --> Candidate["Verified local generation"]
Candidate --> Current["Atomic current"]
Current --> Processor["dagProcessor or scheduler"]
Processor --> Serialized["Serialized DAG metadata"]
Processor --> Ack["Loader ACK"]
Serialized --> API["Airflow REST convergence"]
Airflow component matrix¶
| Version | Parse authority | Watcher placement | API/UI | Worker behavior |
|---|---|---|---|---|
| Airflow 2.10 | Scheduler-managed parser or standalone DAG processor | Beside the configured parse authority | Webserver reads serialized DAGs | Exact runtime init_fetch; no scheduler cache mount |
| Airflow 3.2 | Dedicated dagProcessor |
dagProcessor pod only |
apiServer reads serialized DAGs |
Exact runtime init_fetch; no scheduler cache mount |
| Airflow 3.3+ | DAG Bundle capable processor | Initial implementation remains watcher; future custom versioned bundle | Serialized DAGs | Version-specific bundle/runtime retrieval |
Airflow 3.3 includes official S3 and GCS DAG bundles, but the official S3/GCS bundles currently do not support bundle versioning. A future custom versioned dpone bundle may absorb watcher behavior only after it preserves exact release, desired-state CAS, checksums, last-known-good, activation occurrence, ACK, and convergence contracts.
Alternatives and tradeoffs¶
| Alternative | Advantages | Disadvantages | Decision |
|---|---|---|---|
| Cosmos-style direct remote manifest read | Simple configuration | Network and object-store availability enter every parse | Reject for scheduler packs |
| Airflow Variable cache | Fast metadata read | Metadata DB bloat and cache lifecycle coupling | Reject as artifact store |
| Shared PVC mounted everywhere | One visible cache | RWX dependency, stale mounts, cross-pod mutation risk | Reject as default |
| Per-release infra pipeline | Explicit infra mutation | Cross-project tokens and tight repository coupling | Replace |
Mutable latest object |
Simple lookup | No exact identity or stale-writer protection | Reject |
| S3 desired state plus local watcher | Parse-safe, recoverable, exact | Additional sidecar lifecycle | Adopt |
| Airflow 3.3 S3DagBundle | Native refresh integration | No bundle versioning; no Airflow 2.10/3.2 parity | Revisit |
ADR requirement¶
ADR 0009 is amended by this implementation because external desired state changes from projected ConfigMap input to a connector-neutral conditional object-store control plane. ADR 0033 owns the detailed remote state machine; local cache activation ordering remains unchanged.
Quality-budget impact¶
New production modules are split by stable responsibility:
- contracts;
- reader/writer ports;
- publish application service;
- fetch application service;
- S3 adapter;
- CLI composition and rendering.
Every changed Python module must remain within
docs/benchmarks/quality_budgets.yml; no module may exceed the global SLOC or
clustering budgets. Vendor imports remain lazy and outside base/help paths.
Market comparison¶
Facts were checked against official primary documentation on 2026-07-28.
| System/version | Relevant capability | Observed design | Strength | Limitation | Adopt/reject | Source |
|---|---|---|---|---|---|---|
| Apache Airflow 3.3 | DAG Bundles | Processor periodically refreshes external bundles; Git bundle is versioned; S3/GCS bundles are not | Native lifecycle and serialized version context | Current object-store bundles do not provide exact versioned deployment | Adopt bundle concepts; retain exact dpone contract | DAG Bundles |
| Astronomer Cosmos (docs reviewed 2026-07-28) | Remote dbt manifest and cache | Remote manifest is read through Airflow Object Storage during DAG construction; remote cache trades portability for parse-time network latency | Simple compiled-manifest UX and hash invalidation | Remote cache can add roughly 2-4 seconds per parse; no exact deployment activation protocol | Adopt compiled artifacts, cache cleanup and hash invalidation; reject parse-time remote I/O | Parsing, Caching |
| gusty | DAG authoring | N/A for remote scheduler artifact activation | N/A | No equivalent exact S3 activation contract | N/A | Capability not provided |
| dlt | Data loading runtime | N/A for Airflow scheduler artifact activation | N/A | Different control plane | N/A | Capability not provided |
| Airbyte | Managed connector runtime | N/A for Airflow scheduler artifact activation | N/A | Different control plane | N/A | Capability not provided |
| Fivetran | Managed ELT service | N/A for Airflow scheduler artifact activation | N/A | Different control plane | N/A | Capability not provided |
| Informatica | Enterprise integration runtime | N/A for Airflow scheduler artifact activation | N/A | Different control plane | N/A | Capability not provided |
| Pentaho | ETL runtime/repository | N/A for Airflow scheduler artifact activation | N/A | Different control plane | N/A | Capability not provided |
| Microsoft SSIS | Package execution/deployment | N/A for Airflow scheduler artifact activation | N/A | Different control plane | N/A | Capability not provided |
| Apache Beam | Data processing SDK | N/A for Airflow scheduler artifact activation | N/A | Different control plane | N/A | Capability not provided |
The design also follows Airflow's requirement to avoid networking and database access in top-level DAG code: Airflow best practices. S3 conditional-write behavior must be certified against the actual provider: Yandex Object Storage PutObject.
Measurable differentiation¶
axis: scheduler parse independence from remote object storage
scenario: object storage is unavailable for 30 minutes after one valid deployment
baseline: Cosmos remote manifest path requires remote access during repeated parsing
metric: successful parses of the last accepted deployment and remote calls per parse
target: 100% last-known-good dpone parses; 0 object-storage calls in DAG parse
procedure: block object-storage endpoint after activation and run repeated DagBag parses
artifact: test_artifacts/airflow-s3-desired-state/live-fault-report.json
limitations: compares scheduler artifact delivery, not dbt execution features
axis: concurrent promotion safety
scenario: two protected pipelines promote different deployments from one predecessor
baseline: unconditional mutable-object replacement is last-writer-wins
metric: committed winners and hidden overwrites
target: exactly one winner; zero unconditional writes; loser reports a CAS conflict
procedure: barrier-started conditional writes against Yandex Object Storage
artifact: test_artifacts/airflow-s3-desired-state/live-s3-cas-report.json
limitations: object-store CAS is not distributed multi-key consensus
Security, privacy, and operations¶
- CI writer and Airflow reader credentials are distinct.
- Environment determines a fixed control key; callers cannot choose arbitrary buckets or paths.
- Production writer access is protected and manual.
- Signed URLs, authorization headers, access keys, secret keys, connection payloads, Vault paths, and raw SDK exceptions are redacted from all evidence.
- The watcher filesystem and container run with least privilege and a read-only root filesystem where supported.
- Status exposes desired/current divergence, cache age, last success, error code, and next retry without credentials.
- Alerts fire after two missed cycles, stale cache beyond SLO, fatal load report, hash mismatch, recovery latch, or expected DAG mismatch.
- Retention never deletes the desired-state key, active deployment, current release, or retained rollback generations.
Test and certification plan¶
| Layer | Scenario | Environment | Expected artifact |
|---|---|---|---|
| Unit | Strict deterministic envelope, duplicate keys, limits, redaction | Local | pytest report |
| Unit | Conditional create/replace, opaque revision, 409/412/timeout reconciliation | Fake port | pytest report |
| Contract | No unconditional write and no parse-time network I/O | Local | architecture checks |
| Integration | Concurrent writers produce one winner | Fake S3/MinIO | desired-state evidence |
| Integration | Atomic local snapshot and last-known-good preservation | POSIX temp cache | cache status evidence |
| Compatibility | Airflow 2.10 and 3.2 DagBag/serialization | Exact CI images | parse reports |
| Live certification | Yandex conditional create/replace/race/timeout | Approved scratch key | live-s3-cas-report.json |
| Live certification | Empty-cache restart and S3 outage recovery | Dev Airflow 3.2 | live-fault-report.json |
| Live certification | D1 -> D2 -> D1 -> D2 | Dev Airflow 3.2 | rollback/roll-forward report |
| Live acceptance | Exact expected DAG set and loader ACK | Dev Airflow REST | convergence evidence |
| Future compatibility | Airflow 2.10 disposable environment | Approved namespace | Airflow 2 report |
Live checks are UNVERIFIED until executed against the exact released commit,
image, object store, and Airflow environment. A mock or skipped check is never
reported as a pass.
Documentation plan¶
OSS documentation:
- this normative feature specification;
- ADR 0009 amendment;
- architecture overview with desired-state and cache boundaries;
airflow-cache-sync.mdoperator runbook;airflow-pack-provider.mdparse and component matrix;- configuration and CLI references;
- troubleshooting for stale desired state, CAS conflict, empty cache, checksum mismatch, S3 outage, and rollback;
- Cosmos comparison with adopted and rejected patterns;
- Airflow 2.10/3.2/3.3 migration roadmap.
Deployment repositories:
- one-page overview linked from README;
- dev promotion and production approval CJM;
- watcher status and recovery runbook;
- restart, rollback, and roll-forward procedures;
- evidence locations and API-only diagnosis without Kubernetes access.
Every document separates:
- what the watcher is;
- where it runs;
- what it reads and writes;
- what happens on failure;
- what is never deleted;
- what proves production readiness.
Rollout and rollback¶
- Release desired-state contracts and CLI without activating the new transport.
- Release shared CI support and produce dry-run desired-state evidence.
- Deploy watcher beside the existing dev cache path.
- Materialize and validate the canonical cache without switching the loader.
- Promote one exact dev deployment and prove expected DAG convergence.
- Switch the loader to canonical
current. - Run restart, outage, corruption, rollback, and roll-forward certification.
- Observe seven days of stable dev operation.
- Remove the competing
smoke-v2path and cross-project activation variables. - Enable production only through a protected manual environment gate.
Rollback changes the desired state to the previous exact deployment. If the
new watcher binary itself is defective, infra rolls back the image/Helm release
while the existing current remains available. The old per-release infra
pipeline is not re-enabled automatically because two mutation authorities
would violate the single-writer contract.
Agent execution plan¶
| Agent/role | Owned paths | Read-only paths | Forbidden paths | Dependency |
|---|---|---|---|---|
| Integrator | contracts, schemas, shared registries, CLI, ADR, changelog | whole repository | unrelated user changes | approved spec |
| Desired-state service writer | dedicated contracts/ports/services/tests | existing publication/cache code | shared CLI/registries | task contract |
| Adapter writer | dedicated object-store adapter/tests | storage adapters and ports | domain policy/CLI | port contract |
| Docs/UX reviewer | docs and examples assigned by contract | implementation and tests | shared navigation unless delegated | stable behavior |
| Fresh-context reviewer | read-only whole diff | whole repository | all writes | integrated candidate |
The integrator owns all shared semantic files and resolves cross-repository contracts.
Approval checklist¶
- User problem and CJM are clear.
- Algorithm and failure semantics are implementable without guessing.
- Public contracts and compatibility are explicit.
- Architecture and alternatives are justified.
- Relevant market research uses current official sources.
- Claimed differentiation is measurable.
- Tests, evidence, docs, rollout, and rollback are complete.
- Path ownership and integration plan are conflict-safe.
- Maintainer approved the pull-based desired-state direction.