Skip to content

ADR 0008: Airflow parse-safe provider

Status

Accepted.

Amended by ADR 0024. The bounded loader accepts v1 only for local_preview; strict executable init_fetch requires dpone.airflow-deployment-index.v2.

Context

Airflow DAG parsing must be deterministic and fast. It must not parse dpone manifests, call external systems, read secrets, or refresh caches.

Decision

The canonical Airflow import is airflow.providers.dpone. The recommended loader is:

from airflow.providers.dpone import load_dpone_dags

load_report = load_dpone_dags(
    globals(),
    index_path="/opt/airflow/dags/.dpone-cache/current/airflow-index.json",
)
if load_report.fatal:
    error_code = (
        load_report.errors[0].get("code", "DPONE_AIRFLOW_INDEX_INVALID")
        if load_report.errors
        else "DPONE_AIRFLOW_INDEX_INVALID"
    )
    raise RuntimeError(
        f"{error_code}: dpone Airflow deployment index could not be loaded"
    )

The loader reads one supported airflow-index.json: v1 for non-runnable local_preview, or v2 for strict executable init_fetch. It resolves only cache:// artifacts inside the configured cache root, validates sizes and checksums, and returns LoadReport. The same bounded resolution surface is available through CacheResolver for escape-hatch code that needs to resolve logical cached://dags/<dag_id> / cached://workloads/<workload_id> refs or the strict pinned cached://deployments/<deployment_id>/dags/<dag_id> / cached://deployments/<deployment_id>/workloads/<workload_id> refs without scanning the repository or touching external systems. Query-form release/deployment pins remain a compatibility path, but public examples use the deployment-scoped form.

dpone init project --airflow also owns the migration path for generated dags/dpone.py files. It upgrades only an exact SHA-256 fingerprint from the allowlist of historical dpone-generated loaders. The replacement uses a compare-and-swap guard against the fingerprint observed during planning. Canonical bytes are a no-op on later runs. Unknown bytes, including comments or whitespace edits to an otherwise generated loader, remain a conflict and are never overwritten. Fresh scaffolds and successful upgrades both use the same canonical template.

Consequences

  • Airflow parse path is bounded.
  • A missing, malformed, or contract-invalid deployment index is visible as an Airflow DAG import error instead of a successful parse with zero dpone DAGs.
  • Generated-loader migration is deterministic and idempotent; maintainers rerun dpone init project --airflow instead of hand-editing the loader.
  • User-owned loader variants require manual review and remain byte-for-byte unchanged.
  • Existing dpone_airflow_pack provider facade APIs remain as compatibility exports and emit one DeprecationWarning per process when accessed from the legacy root namespace.
  • Invalid DAG specs are isolated according to provider policy.