[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-astronomer-migrating-dagster-to-airflow":3,"mdc-mvkbwq-key":55,"related-org-astronomer-migrating-dagster-to-airflow":1420,"related-repo-astronomer-migrating-dagster-to-airflow":1588},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":25,"repoUrl":26,"updatedAt":27,"license":28,"forks":29,"topics":30,"repo":50,"sourceUrl":53,"mdContent":54},"migrating-dagster-to-airflow","migrate Dagster projects to Apache Airflow","Guide for migrating Dagster projects to Apache Airflow 3 on Astro. Use when the user mentions migrating, converting, or porting Dagster (or Dagster+) code to Airflow or Astro, wants to plan or assess such a migration, or asks what a Dagster construct maps to in Airflow. Covers assets, partitions, schedules, sensors, declarative automation, resources, IO managers, ops\u002Fjobs, dbt, Pipes, Components, and Dagster+ platform config. Always load this skill as the first step for any Dagster-to-Airflow request.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},"astronomer","Astronomer","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fastronomer.png",[12,16,19,22],{"name":13,"slug":14,"type":15},"Airflow","airflow","tag",{"name":17,"slug":18,"type":15},"Data Pipeline","data-pipeline",{"name":20,"slug":21,"type":15},"Dagster","dagster",{"name":23,"slug":24,"type":15},"Migration","migration",412,"https:\u002F\u002Fgithub.com\u002Fastronomer\u002Fagents","2026-07-14T05:48:39.921802",null,55,[31,32,33,34,14,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49],"agentic-workflow","agents","ai","ai-agents","apache-airflow","claude","cursor","dag","data-engineering","data-pipelines","dbt","llm","mcp","orchestrator","skills","workflow-automation","workflow-management","workflow-orchestration","workflows",{"repoUrl":26,"stars":25,"forks":29,"topics":51,"description":52},[31,32,33,34,14,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49],"AI agent tooling for data engineering workflows.","https:\u002F\u002Fgithub.com\u002Fastronomer\u002Fagents\u002Ftree\u002FHEAD\u002Fskills\u002Fmigrating-dagster-to-airflow","---\nname: migrating-dagster-to-airflow\ndescription: Guide for migrating Dagster projects to Apache Airflow 3 on Astro. Use when the user mentions migrating, converting, or porting Dagster (or Dagster+) code to Airflow or Astro, wants to plan or assess such a migration, or asks what a Dagster construct maps to in Airflow. Covers assets, partitions, schedules, sensors, declarative automation, resources, IO managers, ops\u002Fjobs, dbt, Pipes, Components, and Dagster+ platform config. Always load this skill as the first step for any Dagster-to-Airflow request.\nhooks:\n  PostToolUse:\n    - matcher: \"Edit\"\n      hooks:\n        - type: command\n          command: \"echo 'Migrated DAG edited: consider re-running gates 1-3: python3 scripts\u002Fvalidate_dag.py \u003Castro_project> --manifest manifest.json'\"\n---\n\n# Dagster → Airflow 3 (Astro) migration\n\nMigrate a Dagster project to Airflow 3 on Astro Runtime, honestly. The migration is asset-first (Dagster asset graphs translate to Airflow assets and asset-aware schedules, not flattened DAGs), incremental (domain by domain, Dagster stays authoritative until parity), and honest (every definition gets an explicit disposition; semantic deltas are documented, never papered over).\n\nFirst time driving this? Read `reference\u002Fquickstart.md` first: hour-one commands, the glossary, and what can and cannot break.\n\n## Migration at a glance\n\n1. Baseline the source project's tests, then inventory it read-only (`scripts\u002Finventory.py` → manifest).\n2. Review classifications (MECH\u002FJUDG\u002FREDESIGN\u002FNONE per `reference\u002Fmapping.md`); make the go\u002Fno-go call (three outcomes; migrate-with-conditions is the common case, stay is the narrow one); plan DAG boundaries, per-edge IO decisions, and Gate 3 expectations into the manifest.\n3. Trial-migrate 2-3 representative units end-to-end through every validation gate.\n4. Migrate domain by domain through the six-gate ladder (`reference\u002Fvalidation.md`), tracking per-unit state (`scripts\u002Fstatus.py`); fix failure classes via `reference\u002Ftroubleshooting.md`, never stub.\n5. Map the platform layer (secrets, alerts, CI\u002FCD, Deployments) per `reference\u002Fastro-deployment.md`.\n6. Run side by side, then cut over per domain (consumers unpause first; see the checklist), keeping rollback one step away.\n7. Deliver the migration report: every definition dispositioned, an equivalence row per trigger, losses stated plainly.\n\n## Version drift\n\nVerified against Airflow 3.3.0 \u002F Astro Runtime 3.3-2 \u002F astronomer-cosmos 1.15 \u002F Dagster 1.13 (2026-07). Version-sensitive rows in the references carry their floor (notably the 3.2-vs-3.3 partition surface). Before relying on a version-gated claim: check the target (`airflow version`, `astro deployment inspect`), probe imports for sdk surface (`python3 -c \"from airflow.sdk import X\"`), and prefer `--help` \u002F API spec discovery over assuming verbatim CLI\u002FREST contracts on newer versions. Playbook entries are version-scoped per entry.\n\n## Requirements\n\n- Target **Astro Runtime 3.3+** (Airflow 3.3+); the native asset-partition surface requires it. Below 3.2 the mapping degrades badly; say so and recommend upgrading before migrating.\n- The Dagster repo, and ideally a running Dagster instance (its materialization metadata provides parity-test fixtures).\n- `astro` CLI for the target project.\n\n## Hard rules\n\n1. **Never stub.** A translated unit either works through its validation gate or is deferred with a written reason. Fake-success bodies and workaround code with long justifying comments are failures.\n2. **No silent omissions.** Every record in the inventory manifest ends `complete` or `deferred (reason)`. `scripts\u002Fstatus.py summary` exits nonzero otherwise; run it before claiming done.\n3. **Equivalence rows for every trigger.** Each schedule\u002Fsensor\u002Fautomation condition gets a report row: source spelling, target spelling, delta in one sentence. Semantic deltas exist (catchup, on_cron inversion, eager guarantees); the sin is not the delta, it is the undocumented delta.\n4. **Fix classes, not instances.** When a translation pattern fails validation, fix the pattern (and record it in `reference\u002Ftroubleshooting.md`), then re-apply; do not hand-patch one unit.\n5. **Do not invent APIs.** The references contain verified names only. Anything not covered there gets verified against official docs before use.\n\n## Workflow\n\n### Phase 0: Preflight\n\nConfirm target Runtime version, `astro` CLI presence, and repo access. Detect the project layout: classic (`@repository`\u002F`workspace.yaml`), modern (`Definitions`), or Components (`pyproject.toml [tool.dg]`, `defs.yaml` files); all three occur, sometimes together. Baseline the source project's test suite now: pre-existing failures are recorded and excluded from migration blame.\n\n### Phase 1: Inventory (read-only)\n\n```\npython3 scripts\u002Finventory.py \u003Cdagster_repo> --out manifest.json          # static scan\npython3 scripts\u002Finventory.py \u003Cdagster_repo> --runtime --out manifest.json # + runtime introspection when the project imports\n```\n\nThe manifest lists every definition with file:line, captured params, current-vs-deprecated spelling, and dependency edges with their IO manager; every record starts `classification: \"pending\"`. Classifying is YOUR first judgment task: assign each record MECH \u002F JUDG \u002F REDESIGN \u002F NONE from its row in `reference\u002Fmapping.md` and write it into the manifest. The scanner enumerates (deterministic completeness); the agent classifies (judgment). A record you cannot map to a mapping.md row is itself a finding: record it, do not guess. Also grep for `DAGSTER_CLOUD_` and `EnvVar(` (platform layer, Phase 5).\n\nManifest conventions: the canonical manifest lives in the migration run directory. Once the Astro project exists (Phase 2 scaffold), copy the manifest to its `include\u002Finventory\u002Fmanifest.json` so the Gate 3 pytest and `status.py` defaults find it; until then it just stays in the run dir (keep the two in sync afterward, the run-dir copy wins). Static records are the canonical migration units; runtime-mode enrichment merges into them, and only genuinely runtime-only definitions (factory-generated) become new units.\n\nEmit the migration report skeleton now: one section per manifest record, plus the secrets\u002Fenv naming map from `reference\u002Fastro-deployment.md`. Scale the skeleton to the project: a secretless local project gets a one-line \"no secrets\u002Fplatform layer\" note, not empty boilerplate sections.\n\n### Phase 1.5: Go\u002Fno-go (the honest gate)\n\nBefore translating anything, answer the project-level question the inventory makes answerable: **what does this team give up by migrating, and does each loss have an acceptable answer?** Assess the NONE and REDESIGN rows against what is load-bearing for THIS team, evaluating the mitigation, not just the loss:\n\n| If load-bearing | The Airflow-world answer | Stay-signal only if |\n|---|---|---|\n| dbt rebuild-on-code-change (`code_version_changed()`) | State-aware dbt builds on a cron (Fusion \u002F dbt State skip unchanged models per run, so the post-deploy tick rebuilds exactly what changed), and\u002For CI-triggered `dbt build` on merge (PR-gated, often an upgrade) | The team can neither run a state-aware dbt stack nor dbt from CI |\n| Freshness driving materialization | Astro Observe freshness SLAs \u002F Timeliness alerts + scheduled runs sized to the SLA | Freshness-triggered compute is genuinely irreplaceable by schedule+alerting |\n| Per-asset cost accounting (Insights) | Astro Observe pipeline-level warehouse cost management; per-asset granularity is lost | Per-ASSET chargeback is a contractual\u002Forganizational requirement |\n| Asset catalog \u002F column-level lineage as daily tools | Airflow 3 asset views + OpenLineage\u002FAstro lineage (asset-level) | Column-level lineage is embedded in daily workflows with no external catalog |\n| Deep AutomationCondition compositions, `can_subset`, selective per-partition materialization | Most decompose to cron\u002Fasset schedules (see `reference\u002Fautomation.md`); the residue is redesigned per domain | Multiple domains depend on compositions that decompose to nothing |\n| Sensor cursor transactionality, run-scoped teardown | Idempotent consumers + `max_active_runs`; context managers in task bodies | Exactly-once event coalescing is a correctness requirement that idempotency cannot absorb |\n\nOne rule the table implies, stated plainly: **no dbt-only condition reaches \"stay.\"** Between Cosmos, state-aware dbt builds, and CI-triggered builds, every dbt-workflow loss has an accepted-practice mitigation (execution-proven in this skill's eval program, including on a real warehouse); dbt items are conditions to record, never blockers. The observability rows (per-asset cost, column-level lineage) are separate conditions and are evaluated on their own, even for dbt-heavy teams.\n\nThe gate's outcome is three-valued, and the middle one is the common case:\n\n- **Migrate**: no stay-signals; proceed to Phase 2.\n- **Migrate with conditions** (most real projects): losses exist, mitigations are named and accepted in writing in the report's first section, specific domains may carry REDESIGN work; proceed to Phase 2 with those conditions recorded.\n- **Stay on Dagster, today**: reserved for the case where MULTIPLE stay-signal conditions in the right column genuinely hold at once and the migration is not externally mandated. Then the honest deliverable is that recommendation, in writing, with the specific unmitigated losses named, and the run stops there. A migration guide that cannot say \"don't\" cannot be trusted when it says \"do\", but \"don't\" is earned by unmitigatable losses, not by the mere existence of deltas.\n\n### Phase 2: Plan\n\n- **DAG boundaries**: decide which asset-dependency edges become asset-aware schedules (cross-DAG) vs task ordering (intra-DAG). Group by domain\u002Fschedule cadence\u002Fteam ownership; `define_asset_job` selections usually name the natural domains.\n- **Per-edge IO decisions** via the tree in `reference\u002Fio-and-data-passing.md` (fuse \u002F explicit storage \u002F XCom).\n- **Order**: leaf domains first, dependency order after; the platform layer last.\n- **Fill each planned unit's target expectations into the manifest**: `dag_id`, `task_count`, `edges`, `schedule`, `asset_outlets` per unit. Gate 3 asserts against exactly these fields; a unit without them is skipped by validation, so an unenriched manifest means Gate 3 checks nothing (validate_dag reports skipped counts loudly, do not ignore them).\n- Scaffold the target: `astro dev init`, shared helpers under `include\u002F`. House conventions the scaffold imposes (e.g. a test demanding `retries >= 2`) do NOT override source fidelity: source behavior wins; convention adoption is a post-cutover improvement listed in the report, and the scaffold test gets skipped with an explicit reason.\n\n### Phase 3: Trial\n\nMigrate 2-3 representative units end-to-end through every gate before fanning out. Pick one MECH asset, one partitioned asset, one JUDG case, or the nearest available mix (small projects may have no partitioned or no MECH assets; pick one full path through a real DAG instead). What the trial teaches goes into `reference\u002Ftroubleshooting.md` before scaling; if the trial fails structurally, stop and rework the plan, not the units.\n\n### Phase 4: Migrate, domain by domain\n\nPer unit, the state machine (tracked in the manifest):\n\n```\npending → translate → fix-import → fix-lint → fix-tests → verify-parity → complete\n                                    ↘ deferred (reason required)\n```\n\n- Translate using the reference file for the construct (routing table below). Rich context beats cleverness: read the source unit, its mapping rows, and a nearby already-migrated example.\n- Validate through the gates: `python3 scripts\u002Fvalidate_dag.py \u003Castro_project> --manifest manifest.json` (gates 1-3), then execution and parity per `reference\u002Fvalidation.md`.\n- On gate failure, retry with the latest validator output in context (cap ~10 attempts, then defer with the failure class).\n- Advance state only on gate pass: `python3 scripts\u002Fstatus.py advance \u003Cunit-id> ...`. A wrong disposition is corrected with `status.py reopen \u003Cunit-id> --reason ...`. The no-hand-editing rule applies to the STATE field (`status`) only; the PLAN fields (`dag_id`, `task_count`, `edges`, `schedule`, `asset_outlets`, `target`) are the planner's to write in Phase 2.\n- Units that deliberately translate to NO DAG of their own (helpers absorbed into tasks, policies that became alerts, resources that became connections) are dispositioned `complete` with `target: \"none\"` and evidence naming where they went; Gate 3 skips them by design.\n- Commit per unit, atomically.\n\n### Phase 5: Platform layer\n\n`reference\u002Fastro-deployment.md`: Deployments topology, secrets\u002Fconnection naming map, CI\u002FCD and preview Deployments, alert-policy mapping, Observe\u002Flineage expectations, the `DAGSTER_CLOUD_*` in-code rewrite checklist.\n\n### Phase 6: Side-by-side and cutover\n\nDagster remains authoritative. Run migrated DAGs shadowed\u002Fpaused; compare outputs over the same logical window (row counts + checksums; recompute expected values from the Dagster-produced output itself, using recorded materialization metadata only opportunistically, per `reference\u002Fvalidation.md` Gate 5). Flip schedules one domain per change window: pause the Dagster schedule, unpause the Airflow DAG; rollback is the reverse. Keep Dagster readable after cutover (run history does not migrate).\n\n### Phase 7: Final report\n\n`scripts\u002Fstatus.py summary` must pass. The report contains: the go\u002Fno-go assessment (Phase 1.5) and its rationale, disposition table for every definition, all equivalence rows, the NONE\u002FREDESIGN losses stated plainly (lineage depth, code_version triggers, Insights cost accounting, sensor cursor transactionality), the secrets map, and the deferred list with reasons. Spot-check ten `complete` claims before delivering it.\n\n## Reference routing\n\n| Construct encountered | Read |\n|---|---|\n| First hour, glossary, what can break | `reference\u002Fquickstart.md` |\n| Anything (first stop: one row per construct) | `reference\u002Fmapping.md` |\n| Asset-key → URI convention, translation granularity, external\u002Fobservable assets | `reference\u002Fassets.md` |\n| Asset deps, IO managers, XCom, storage decisions | `reference\u002Fio-and-data-passing.md` |\n| Any `partitions_def`, partition mappings, backfills | `reference\u002Fpartitions.md` |\n| Schedules, sensors, AutomationCondition, freshness | `reference\u002Fautomation.md` |\n| `@dbt_assets`, translators, dbt Cloud | `reference\u002Fdbt.md` |\n| Components (defs.yaml), custom Component subclasses, dynamic generation | `reference\u002Fcomponents.md` |\n| dagster_cloud.yaml, secrets, alerts, CI\u002FCD, cutover | `reference\u002Fastro-deployment.md` |\n| Gates, parity testing, state machine | `reference\u002Fvalidation.md` |\n| Failure classes seen before | `reference\u002Ftroubleshooting.md` |\n\n## Scripts\n\n| Script | Purpose |\n|---|---|\n| `scripts\u002Finventory.py` | Scan the Dagster repo → JSON manifest (static + optional runtime mode) |\n| `scripts\u002Fvalidate_dag.py` | Gates 1-3 against the generated Astro project |\n| `scripts\u002Fstatus.py` | Per-unit state machine + completeness gate |\n",{"data":56,"body":65},{"name":4,"description":6,"hooks":57},{"PostToolUse":58},[59],{"matcher":60,"hooks":61},"Edit",[62],{"type":63,"command":64},"command","echo 'Migrated DAG edited: consider re-running gates 1-3: python3 scripts\u002Fvalidate_dag.py \u003Castro_project> --manifest manifest.json'",{"type":66,"children":67},"root",[68,77,83,97,104,192,198,235,241,274,280,364,370,377,428,434,446,482,503,515,521,533,714,726,731,764,770,894,900,912,918,923,932,1056,1062,1080,1086,1098,1104,1121,1127,1343,1349],{"type":69,"tag":70,"props":71,"children":73},"element","h1",{"id":72},"dagster-airflow-3-astro-migration",[74],{"type":75,"value":76},"text","Dagster → Airflow 3 (Astro) migration",{"type":69,"tag":78,"props":79,"children":80},"p",{},[81],{"type":75,"value":82},"Migrate a Dagster project to Airflow 3 on Astro Runtime, honestly. The migration is asset-first (Dagster asset graphs translate to Airflow assets and asset-aware schedules, not flattened DAGs), incremental (domain by domain, Dagster stays authoritative until parity), and honest (every definition gets an explicit disposition; semantic deltas are documented, never papered over).",{"type":69,"tag":78,"props":84,"children":85},{},[86,88,95],{"type":75,"value":87},"First time driving this? Read ",{"type":69,"tag":89,"props":90,"children":92},"code",{"className":91},[],[93],{"type":75,"value":94},"reference\u002Fquickstart.md",{"type":75,"value":96}," first: hour-one commands, the glossary, and what can and cannot break.",{"type":69,"tag":98,"props":99,"children":101},"h2",{"id":100},"migration-at-a-glance",[102],{"type":75,"value":103},"Migration at a glance",{"type":69,"tag":105,"props":106,"children":107},"ol",{},[108,122,135,140,169,182,187],{"type":69,"tag":109,"props":110,"children":111},"li",{},[112,114,120],{"type":75,"value":113},"Baseline the source project's tests, then inventory it read-only (",{"type":69,"tag":89,"props":115,"children":117},{"className":116},[],[118],{"type":75,"value":119},"scripts\u002Finventory.py",{"type":75,"value":121}," → manifest).",{"type":69,"tag":109,"props":123,"children":124},{},[125,127,133],{"type":75,"value":126},"Review classifications (MECH\u002FJUDG\u002FREDESIGN\u002FNONE per ",{"type":69,"tag":89,"props":128,"children":130},{"className":129},[],[131],{"type":75,"value":132},"reference\u002Fmapping.md",{"type":75,"value":134},"); make the go\u002Fno-go call (three outcomes; migrate-with-conditions is the common case, stay is the narrow one); plan DAG boundaries, per-edge IO decisions, and Gate 3 expectations into the manifest.",{"type":69,"tag":109,"props":136,"children":137},{},[138],{"type":75,"value":139},"Trial-migrate 2-3 representative units end-to-end through every validation gate.",{"type":69,"tag":109,"props":141,"children":142},{},[143,145,151,153,159,161,167],{"type":75,"value":144},"Migrate domain by domain through the six-gate ladder (",{"type":69,"tag":89,"props":146,"children":148},{"className":147},[],[149],{"type":75,"value":150},"reference\u002Fvalidation.md",{"type":75,"value":152},"), tracking per-unit state (",{"type":69,"tag":89,"props":154,"children":156},{"className":155},[],[157],{"type":75,"value":158},"scripts\u002Fstatus.py",{"type":75,"value":160},"); fix failure classes via ",{"type":69,"tag":89,"props":162,"children":164},{"className":163},[],[165],{"type":75,"value":166},"reference\u002Ftroubleshooting.md",{"type":75,"value":168},", never stub.",{"type":69,"tag":109,"props":170,"children":171},{},[172,174,180],{"type":75,"value":173},"Map the platform layer (secrets, alerts, CI\u002FCD, Deployments) per ",{"type":69,"tag":89,"props":175,"children":177},{"className":176},[],[178],{"type":75,"value":179},"reference\u002Fastro-deployment.md",{"type":75,"value":181},".",{"type":69,"tag":109,"props":183,"children":184},{},[185],{"type":75,"value":186},"Run side by side, then cut over per domain (consumers unpause first; see the checklist), keeping rollback one step away.",{"type":69,"tag":109,"props":188,"children":189},{},[190],{"type":75,"value":191},"Deliver the migration report: every definition dispositioned, an equivalence row per trigger, losses stated plainly.",{"type":69,"tag":98,"props":193,"children":195},{"id":194},"version-drift",[196],{"type":75,"value":197},"Version drift",{"type":69,"tag":78,"props":199,"children":200},{},[201,203,209,211,217,219,225,227,233],{"type":75,"value":202},"Verified against Airflow 3.3.0 \u002F Astro Runtime 3.3-2 \u002F astronomer-cosmos 1.15 \u002F Dagster 1.13 (2026-07). Version-sensitive rows in the references carry their floor (notably the 3.2-vs-3.3 partition surface). Before relying on a version-gated claim: check the target (",{"type":69,"tag":89,"props":204,"children":206},{"className":205},[],[207],{"type":75,"value":208},"airflow version",{"type":75,"value":210},", ",{"type":69,"tag":89,"props":212,"children":214},{"className":213},[],[215],{"type":75,"value":216},"astro deployment inspect",{"type":75,"value":218},"), probe imports for sdk surface (",{"type":69,"tag":89,"props":220,"children":222},{"className":221},[],[223],{"type":75,"value":224},"python3 -c \"from airflow.sdk import X\"",{"type":75,"value":226},"), and prefer ",{"type":69,"tag":89,"props":228,"children":230},{"className":229},[],[231],{"type":75,"value":232},"--help",{"type":75,"value":234}," \u002F API spec discovery over assuming verbatim CLI\u002FREST contracts on newer versions. Playbook entries are version-scoped per entry.",{"type":69,"tag":98,"props":236,"children":238},{"id":237},"requirements",[239],{"type":75,"value":240},"Requirements",{"type":69,"tag":242,"props":243,"children":244},"ul",{},[245,258,263],{"type":69,"tag":109,"props":246,"children":247},{},[248,250,256],{"type":75,"value":249},"Target ",{"type":69,"tag":251,"props":252,"children":253},"strong",{},[254],{"type":75,"value":255},"Astro Runtime 3.3+",{"type":75,"value":257}," (Airflow 3.3+); the native asset-partition surface requires it. Below 3.2 the mapping degrades badly; say so and recommend upgrading before migrating.",{"type":69,"tag":109,"props":259,"children":260},{},[261],{"type":75,"value":262},"The Dagster repo, and ideally a running Dagster instance (its materialization metadata provides parity-test fixtures).",{"type":69,"tag":109,"props":264,"children":265},{},[266,272],{"type":69,"tag":89,"props":267,"children":269},{"className":268},[],[270],{"type":75,"value":271},"astro",{"type":75,"value":273}," CLI for the target project.",{"type":69,"tag":98,"props":275,"children":277},{"id":276},"hard-rules",[278],{"type":75,"value":279},"Hard rules",{"type":69,"tag":105,"props":281,"children":282},{},[283,293,327,337,354],{"type":69,"tag":109,"props":284,"children":285},{},[286,291],{"type":69,"tag":251,"props":287,"children":288},{},[289],{"type":75,"value":290},"Never stub.",{"type":75,"value":292}," A translated unit either works through its validation gate or is deferred with a written reason. Fake-success bodies and workaround code with long justifying comments are failures.",{"type":69,"tag":109,"props":294,"children":295},{},[296,301,303,309,311,317,319,325],{"type":69,"tag":251,"props":297,"children":298},{},[299],{"type":75,"value":300},"No silent omissions.",{"type":75,"value":302}," Every record in the inventory manifest ends ",{"type":69,"tag":89,"props":304,"children":306},{"className":305},[],[307],{"type":75,"value":308},"complete",{"type":75,"value":310}," or ",{"type":69,"tag":89,"props":312,"children":314},{"className":313},[],[315],{"type":75,"value":316},"deferred (reason)",{"type":75,"value":318},". ",{"type":69,"tag":89,"props":320,"children":322},{"className":321},[],[323],{"type":75,"value":324},"scripts\u002Fstatus.py summary",{"type":75,"value":326}," exits nonzero otherwise; run it before claiming done.",{"type":69,"tag":109,"props":328,"children":329},{},[330,335],{"type":69,"tag":251,"props":331,"children":332},{},[333],{"type":75,"value":334},"Equivalence rows for every trigger.",{"type":75,"value":336}," Each schedule\u002Fsensor\u002Fautomation condition gets a report row: source spelling, target spelling, delta in one sentence. Semantic deltas exist (catchup, on_cron inversion, eager guarantees); the sin is not the delta, it is the undocumented delta.",{"type":69,"tag":109,"props":338,"children":339},{},[340,345,347,352],{"type":69,"tag":251,"props":341,"children":342},{},[343],{"type":75,"value":344},"Fix classes, not instances.",{"type":75,"value":346}," When a translation pattern fails validation, fix the pattern (and record it in ",{"type":69,"tag":89,"props":348,"children":350},{"className":349},[],[351],{"type":75,"value":166},{"type":75,"value":353},"), then re-apply; do not hand-patch one unit.",{"type":69,"tag":109,"props":355,"children":356},{},[357,362],{"type":69,"tag":251,"props":358,"children":359},{},[360],{"type":75,"value":361},"Do not invent APIs.",{"type":75,"value":363}," The references contain verified names only. Anything not covered there gets verified against official docs before use.",{"type":69,"tag":98,"props":365,"children":367},{"id":366},"workflow",[368],{"type":75,"value":369},"Workflow",{"type":69,"tag":371,"props":372,"children":374},"h3",{"id":373},"phase-0-preflight",[375],{"type":75,"value":376},"Phase 0: Preflight",{"type":69,"tag":78,"props":378,"children":379},{},[380,382,387,389,395,397,403,405,411,413,419,420,426],{"type":75,"value":381},"Confirm target Runtime version, ",{"type":69,"tag":89,"props":383,"children":385},{"className":384},[],[386],{"type":75,"value":271},{"type":75,"value":388}," CLI presence, and repo access. Detect the project layout: classic (",{"type":69,"tag":89,"props":390,"children":392},{"className":391},[],[393],{"type":75,"value":394},"@repository",{"type":75,"value":396},"\u002F",{"type":69,"tag":89,"props":398,"children":400},{"className":399},[],[401],{"type":75,"value":402},"workspace.yaml",{"type":75,"value":404},"), modern (",{"type":69,"tag":89,"props":406,"children":408},{"className":407},[],[409],{"type":75,"value":410},"Definitions",{"type":75,"value":412},"), or Components (",{"type":69,"tag":89,"props":414,"children":416},{"className":415},[],[417],{"type":75,"value":418},"pyproject.toml [tool.dg]",{"type":75,"value":210},{"type":69,"tag":89,"props":421,"children":423},{"className":422},[],[424],{"type":75,"value":425},"defs.yaml",{"type":75,"value":427}," files); all three occur, sometimes together. Baseline the source project's test suite now: pre-existing failures are recorded and excluded from migration blame.",{"type":69,"tag":371,"props":429,"children":431},{"id":430},"phase-1-inventory-read-only",[432],{"type":75,"value":433},"Phase 1: Inventory (read-only)",{"type":69,"tag":435,"props":436,"children":440},"pre",{"className":437,"code":439,"language":75},[438],"language-text","python3 scripts\u002Finventory.py \u003Cdagster_repo> --out manifest.json          # static scan\npython3 scripts\u002Finventory.py \u003Cdagster_repo> --runtime --out manifest.json # + runtime introspection when the project imports\n",[441],{"type":69,"tag":89,"props":442,"children":444},{"__ignoreMap":443},"",[445],{"type":75,"value":439},{"type":69,"tag":78,"props":447,"children":448},{},[449,451,457,459,464,466,472,474,480],{"type":75,"value":450},"The manifest lists every definition with file:line, captured params, current-vs-deprecated spelling, and dependency edges with their IO manager; every record starts ",{"type":69,"tag":89,"props":452,"children":454},{"className":453},[],[455],{"type":75,"value":456},"classification: \"pending\"",{"type":75,"value":458},". Classifying is YOUR first judgment task: assign each record MECH \u002F JUDG \u002F REDESIGN \u002F NONE from its row in ",{"type":69,"tag":89,"props":460,"children":462},{"className":461},[],[463],{"type":75,"value":132},{"type":75,"value":465}," and write it into the manifest. The scanner enumerates (deterministic completeness); the agent classifies (judgment). A record you cannot map to a mapping.md row is itself a finding: record it, do not guess. Also grep for ",{"type":69,"tag":89,"props":467,"children":469},{"className":468},[],[470],{"type":75,"value":471},"DAGSTER_CLOUD_",{"type":75,"value":473}," and ",{"type":69,"tag":89,"props":475,"children":477},{"className":476},[],[478],{"type":75,"value":479},"EnvVar(",{"type":75,"value":481}," (platform layer, Phase 5).",{"type":69,"tag":78,"props":483,"children":484},{},[485,487,493,495,501],{"type":75,"value":486},"Manifest conventions: the canonical manifest lives in the migration run directory. Once the Astro project exists (Phase 2 scaffold), copy the manifest to its ",{"type":69,"tag":89,"props":488,"children":490},{"className":489},[],[491],{"type":75,"value":492},"include\u002Finventory\u002Fmanifest.json",{"type":75,"value":494}," so the Gate 3 pytest and ",{"type":69,"tag":89,"props":496,"children":498},{"className":497},[],[499],{"type":75,"value":500},"status.py",{"type":75,"value":502}," defaults find it; until then it just stays in the run dir (keep the two in sync afterward, the run-dir copy wins). Static records are the canonical migration units; runtime-mode enrichment merges into them, and only genuinely runtime-only definitions (factory-generated) become new units.",{"type":69,"tag":78,"props":504,"children":505},{},[506,508,513],{"type":75,"value":507},"Emit the migration report skeleton now: one section per manifest record, plus the secrets\u002Fenv naming map from ",{"type":69,"tag":89,"props":509,"children":511},{"className":510},[],[512],{"type":75,"value":179},{"type":75,"value":514},". Scale the skeleton to the project: a secretless local project gets a one-line \"no secrets\u002Fplatform layer\" note, not empty boilerplate sections.",{"type":69,"tag":371,"props":516,"children":518},{"id":517},"phase-15-gono-go-the-honest-gate",[519],{"type":75,"value":520},"Phase 1.5: Go\u002Fno-go (the honest gate)",{"type":69,"tag":78,"props":522,"children":523},{},[524,526,531],{"type":75,"value":525},"Before translating anything, answer the project-level question the inventory makes answerable: ",{"type":69,"tag":251,"props":527,"children":528},{},[529],{"type":75,"value":530},"what does this team give up by migrating, and does each loss have an acceptable answer?",{"type":75,"value":532}," Assess the NONE and REDESIGN rows against what is load-bearing for THIS team, evaluating the mitigation, not just the loss:",{"type":69,"tag":534,"props":535,"children":536},"table",{},[537,561],{"type":69,"tag":538,"props":539,"children":540},"thead",{},[541],{"type":69,"tag":542,"props":543,"children":544},"tr",{},[545,551,556],{"type":69,"tag":546,"props":547,"children":548},"th",{},[549],{"type":75,"value":550},"If load-bearing",{"type":69,"tag":546,"props":552,"children":553},{},[554],{"type":75,"value":555},"The Airflow-world answer",{"type":69,"tag":546,"props":557,"children":558},{},[559],{"type":75,"value":560},"Stay-signal only if",{"type":69,"tag":562,"props":563,"children":564},"tbody",{},[565,600,618,636,654,688],{"type":69,"tag":542,"props":566,"children":567},{},[568,582,595],{"type":69,"tag":569,"props":570,"children":571},"td",{},[572,574,580],{"type":75,"value":573},"dbt rebuild-on-code-change (",{"type":69,"tag":89,"props":575,"children":577},{"className":576},[],[578],{"type":75,"value":579},"code_version_changed()",{"type":75,"value":581},")",{"type":69,"tag":569,"props":583,"children":584},{},[585,587,593],{"type":75,"value":586},"State-aware dbt builds on a cron (Fusion \u002F dbt State skip unchanged models per run, so the post-deploy tick rebuilds exactly what changed), and\u002For CI-triggered ",{"type":69,"tag":89,"props":588,"children":590},{"className":589},[],[591],{"type":75,"value":592},"dbt build",{"type":75,"value":594}," on merge (PR-gated, often an upgrade)",{"type":69,"tag":569,"props":596,"children":597},{},[598],{"type":75,"value":599},"The team can neither run a state-aware dbt stack nor dbt from CI",{"type":69,"tag":542,"props":601,"children":602},{},[603,608,613],{"type":69,"tag":569,"props":604,"children":605},{},[606],{"type":75,"value":607},"Freshness driving materialization",{"type":69,"tag":569,"props":609,"children":610},{},[611],{"type":75,"value":612},"Astro Observe freshness SLAs \u002F Timeliness alerts + scheduled runs sized to the SLA",{"type":69,"tag":569,"props":614,"children":615},{},[616],{"type":75,"value":617},"Freshness-triggered compute is genuinely irreplaceable by schedule+alerting",{"type":69,"tag":542,"props":619,"children":620},{},[621,626,631],{"type":69,"tag":569,"props":622,"children":623},{},[624],{"type":75,"value":625},"Per-asset cost accounting (Insights)",{"type":69,"tag":569,"props":627,"children":628},{},[629],{"type":75,"value":630},"Astro Observe pipeline-level warehouse cost management; per-asset granularity is lost",{"type":69,"tag":569,"props":632,"children":633},{},[634],{"type":75,"value":635},"Per-ASSET chargeback is a contractual\u002Forganizational requirement",{"type":69,"tag":542,"props":637,"children":638},{},[639,644,649],{"type":69,"tag":569,"props":640,"children":641},{},[642],{"type":75,"value":643},"Asset catalog \u002F column-level lineage as daily tools",{"type":69,"tag":569,"props":645,"children":646},{},[647],{"type":75,"value":648},"Airflow 3 asset views + OpenLineage\u002FAstro lineage (asset-level)",{"type":69,"tag":569,"props":650,"children":651},{},[652],{"type":75,"value":653},"Column-level lineage is embedded in daily workflows with no external catalog",{"type":69,"tag":542,"props":655,"children":656},{},[657,670,683],{"type":69,"tag":569,"props":658,"children":659},{},[660,662,668],{"type":75,"value":661},"Deep AutomationCondition compositions, ",{"type":69,"tag":89,"props":663,"children":665},{"className":664},[],[666],{"type":75,"value":667},"can_subset",{"type":75,"value":669},", selective per-partition materialization",{"type":69,"tag":569,"props":671,"children":672},{},[673,675,681],{"type":75,"value":674},"Most decompose to cron\u002Fasset schedules (see ",{"type":69,"tag":89,"props":676,"children":678},{"className":677},[],[679],{"type":75,"value":680},"reference\u002Fautomation.md",{"type":75,"value":682},"); the residue is redesigned per domain",{"type":69,"tag":569,"props":684,"children":685},{},[686],{"type":75,"value":687},"Multiple domains depend on compositions that decompose to nothing",{"type":69,"tag":542,"props":689,"children":690},{},[691,696,709],{"type":69,"tag":569,"props":692,"children":693},{},[694],{"type":75,"value":695},"Sensor cursor transactionality, run-scoped teardown",{"type":69,"tag":569,"props":697,"children":698},{},[699,701,707],{"type":75,"value":700},"Idempotent consumers + ",{"type":69,"tag":89,"props":702,"children":704},{"className":703},[],[705],{"type":75,"value":706},"max_active_runs",{"type":75,"value":708},"; context managers in task bodies",{"type":69,"tag":569,"props":710,"children":711},{},[712],{"type":75,"value":713},"Exactly-once event coalescing is a correctness requirement that idempotency cannot absorb",{"type":69,"tag":78,"props":715,"children":716},{},[717,719,724],{"type":75,"value":718},"One rule the table implies, stated plainly: ",{"type":69,"tag":251,"props":720,"children":721},{},[722],{"type":75,"value":723},"no dbt-only condition reaches \"stay.\"",{"type":75,"value":725}," Between Cosmos, state-aware dbt builds, and CI-triggered builds, every dbt-workflow loss has an accepted-practice mitigation (execution-proven in this skill's eval program, including on a real warehouse); dbt items are conditions to record, never blockers. The observability rows (per-asset cost, column-level lineage) are separate conditions and are evaluated on their own, even for dbt-heavy teams.",{"type":69,"tag":78,"props":727,"children":728},{},[729],{"type":75,"value":730},"The gate's outcome is three-valued, and the middle one is the common case:",{"type":69,"tag":242,"props":732,"children":733},{},[734,744,754],{"type":69,"tag":109,"props":735,"children":736},{},[737,742],{"type":69,"tag":251,"props":738,"children":739},{},[740],{"type":75,"value":741},"Migrate",{"type":75,"value":743},": no stay-signals; proceed to Phase 2.",{"type":69,"tag":109,"props":745,"children":746},{},[747,752],{"type":69,"tag":251,"props":748,"children":749},{},[750],{"type":75,"value":751},"Migrate with conditions",{"type":75,"value":753}," (most real projects): losses exist, mitigations are named and accepted in writing in the report's first section, specific domains may carry REDESIGN work; proceed to Phase 2 with those conditions recorded.",{"type":69,"tag":109,"props":755,"children":756},{},[757,762],{"type":69,"tag":251,"props":758,"children":759},{},[760],{"type":75,"value":761},"Stay on Dagster, today",{"type":75,"value":763},": reserved for the case where MULTIPLE stay-signal conditions in the right column genuinely hold at once and the migration is not externally mandated. Then the honest deliverable is that recommendation, in writing, with the specific unmitigated losses named, and the run stops there. A migration guide that cannot say \"don't\" cannot be trusted when it says \"do\", but \"don't\" is earned by unmitigatable losses, not by the mere existence of deltas.",{"type":69,"tag":371,"props":765,"children":767},{"id":766},"phase-2-plan",[768],{"type":75,"value":769},"Phase 2: Plan",{"type":69,"tag":242,"props":771,"children":772},{},[773,791,809,819,865],{"type":69,"tag":109,"props":774,"children":775},{},[776,781,783,789],{"type":69,"tag":251,"props":777,"children":778},{},[779],{"type":75,"value":780},"DAG boundaries",{"type":75,"value":782},": decide which asset-dependency edges become asset-aware schedules (cross-DAG) vs task ordering (intra-DAG). Group by domain\u002Fschedule cadence\u002Fteam ownership; ",{"type":69,"tag":89,"props":784,"children":786},{"className":785},[],[787],{"type":75,"value":788},"define_asset_job",{"type":75,"value":790}," selections usually name the natural domains.",{"type":69,"tag":109,"props":792,"children":793},{},[794,799,801,807],{"type":69,"tag":251,"props":795,"children":796},{},[797],{"type":75,"value":798},"Per-edge IO decisions",{"type":75,"value":800}," via the tree in ",{"type":69,"tag":89,"props":802,"children":804},{"className":803},[],[805],{"type":75,"value":806},"reference\u002Fio-and-data-passing.md",{"type":75,"value":808}," (fuse \u002F explicit storage \u002F XCom).",{"type":69,"tag":109,"props":810,"children":811},{},[812,817],{"type":69,"tag":251,"props":813,"children":814},{},[815],{"type":75,"value":816},"Order",{"type":75,"value":818},": leaf domains first, dependency order after; the platform layer last.",{"type":69,"tag":109,"props":820,"children":821},{},[822,827,829,835,836,842,843,849,850,856,857,863],{"type":69,"tag":251,"props":823,"children":824},{},[825],{"type":75,"value":826},"Fill each planned unit's target expectations into the manifest",{"type":75,"value":828},": ",{"type":69,"tag":89,"props":830,"children":832},{"className":831},[],[833],{"type":75,"value":834},"dag_id",{"type":75,"value":210},{"type":69,"tag":89,"props":837,"children":839},{"className":838},[],[840],{"type":75,"value":841},"task_count",{"type":75,"value":210},{"type":69,"tag":89,"props":844,"children":846},{"className":845},[],[847],{"type":75,"value":848},"edges",{"type":75,"value":210},{"type":69,"tag":89,"props":851,"children":853},{"className":852},[],[854],{"type":75,"value":855},"schedule",{"type":75,"value":210},{"type":69,"tag":89,"props":858,"children":860},{"className":859},[],[861],{"type":75,"value":862},"asset_outlets",{"type":75,"value":864}," per unit. Gate 3 asserts against exactly these fields; a unit without them is skipped by validation, so an unenriched manifest means Gate 3 checks nothing (validate_dag reports skipped counts loudly, do not ignore them).",{"type":69,"tag":109,"props":866,"children":867},{},[868,870,876,878,884,886,892],{"type":75,"value":869},"Scaffold the target: ",{"type":69,"tag":89,"props":871,"children":873},{"className":872},[],[874],{"type":75,"value":875},"astro dev init",{"type":75,"value":877},", shared helpers under ",{"type":69,"tag":89,"props":879,"children":881},{"className":880},[],[882],{"type":75,"value":883},"include\u002F",{"type":75,"value":885},". House conventions the scaffold imposes (e.g. a test demanding ",{"type":69,"tag":89,"props":887,"children":889},{"className":888},[],[890],{"type":75,"value":891},"retries >= 2",{"type":75,"value":893},") do NOT override source fidelity: source behavior wins; convention adoption is a post-cutover improvement listed in the report, and the scaffold test gets skipped with an explicit reason.",{"type":69,"tag":371,"props":895,"children":897},{"id":896},"phase-3-trial",[898],{"type":75,"value":899},"Phase 3: Trial",{"type":69,"tag":78,"props":901,"children":902},{},[903,905,910],{"type":75,"value":904},"Migrate 2-3 representative units end-to-end through every gate before fanning out. Pick one MECH asset, one partitioned asset, one JUDG case, or the nearest available mix (small projects may have no partitioned or no MECH assets; pick one full path through a real DAG instead). What the trial teaches goes into ",{"type":69,"tag":89,"props":906,"children":908},{"className":907},[],[909],{"type":75,"value":166},{"type":75,"value":911}," before scaling; if the trial fails structurally, stop and rework the plan, not the units.",{"type":69,"tag":371,"props":913,"children":915},{"id":914},"phase-4-migrate-domain-by-domain",[916],{"type":75,"value":917},"Phase 4: Migrate, domain by domain",{"type":69,"tag":78,"props":919,"children":920},{},[921],{"type":75,"value":922},"Per unit, the state machine (tracked in the manifest):",{"type":69,"tag":435,"props":924,"children":927},{"className":925,"code":926,"language":75},[438],"pending → translate → fix-import → fix-lint → fix-tests → verify-parity → complete\n                                    ↘ deferred (reason required)\n",[928],{"type":69,"tag":89,"props":929,"children":930},{"__ignoreMap":443},[931],{"type":75,"value":926},{"type":69,"tag":242,"props":933,"children":934},{},[935,940,959,964,1031,1051],{"type":69,"tag":109,"props":936,"children":937},{},[938],{"type":75,"value":939},"Translate using the reference file for the construct (routing table below). Rich context beats cleverness: read the source unit, its mapping rows, and a nearby already-migrated example.",{"type":69,"tag":109,"props":941,"children":942},{},[943,945,951,953,958],{"type":75,"value":944},"Validate through the gates: ",{"type":69,"tag":89,"props":946,"children":948},{"className":947},[],[949],{"type":75,"value":950},"python3 scripts\u002Fvalidate_dag.py \u003Castro_project> --manifest manifest.json",{"type":75,"value":952}," (gates 1-3), then execution and parity per ",{"type":69,"tag":89,"props":954,"children":956},{"className":955},[],[957],{"type":75,"value":150},{"type":75,"value":181},{"type":69,"tag":109,"props":960,"children":961},{},[962],{"type":75,"value":963},"On gate failure, retry with the latest validator output in context (cap ~10 attempts, then defer with the failure class).",{"type":69,"tag":109,"props":965,"children":966},{},[967,969,975,977,983,985,991,993,998,999,1004,1005,1010,1011,1016,1017,1022,1023,1029],{"type":75,"value":968},"Advance state only on gate pass: ",{"type":69,"tag":89,"props":970,"children":972},{"className":971},[],[973],{"type":75,"value":974},"python3 scripts\u002Fstatus.py advance \u003Cunit-id> ...",{"type":75,"value":976},". A wrong disposition is corrected with ",{"type":69,"tag":89,"props":978,"children":980},{"className":979},[],[981],{"type":75,"value":982},"status.py reopen \u003Cunit-id> --reason ...",{"type":75,"value":984},". The no-hand-editing rule applies to the STATE field (",{"type":69,"tag":89,"props":986,"children":988},{"className":987},[],[989],{"type":75,"value":990},"status",{"type":75,"value":992},") only; the PLAN fields (",{"type":69,"tag":89,"props":994,"children":996},{"className":995},[],[997],{"type":75,"value":834},{"type":75,"value":210},{"type":69,"tag":89,"props":1000,"children":1002},{"className":1001},[],[1003],{"type":75,"value":841},{"type":75,"value":210},{"type":69,"tag":89,"props":1006,"children":1008},{"className":1007},[],[1009],{"type":75,"value":848},{"type":75,"value":210},{"type":69,"tag":89,"props":1012,"children":1014},{"className":1013},[],[1015],{"type":75,"value":855},{"type":75,"value":210},{"type":69,"tag":89,"props":1018,"children":1020},{"className":1019},[],[1021],{"type":75,"value":862},{"type":75,"value":210},{"type":69,"tag":89,"props":1024,"children":1026},{"className":1025},[],[1027],{"type":75,"value":1028},"target",{"type":75,"value":1030},") are the planner's to write in Phase 2.",{"type":69,"tag":109,"props":1032,"children":1033},{},[1034,1036,1041,1043,1049],{"type":75,"value":1035},"Units that deliberately translate to NO DAG of their own (helpers absorbed into tasks, policies that became alerts, resources that became connections) are dispositioned ",{"type":69,"tag":89,"props":1037,"children":1039},{"className":1038},[],[1040],{"type":75,"value":308},{"type":75,"value":1042}," with ",{"type":69,"tag":89,"props":1044,"children":1046},{"className":1045},[],[1047],{"type":75,"value":1048},"target: \"none\"",{"type":75,"value":1050}," and evidence naming where they went; Gate 3 skips them by design.",{"type":69,"tag":109,"props":1052,"children":1053},{},[1054],{"type":75,"value":1055},"Commit per unit, atomically.",{"type":69,"tag":371,"props":1057,"children":1059},{"id":1058},"phase-5-platform-layer",[1060],{"type":75,"value":1061},"Phase 5: Platform layer",{"type":69,"tag":78,"props":1063,"children":1064},{},[1065,1070,1072,1078],{"type":69,"tag":89,"props":1066,"children":1068},{"className":1067},[],[1069],{"type":75,"value":179},{"type":75,"value":1071},": Deployments topology, secrets\u002Fconnection naming map, CI\u002FCD and preview Deployments, alert-policy mapping, Observe\u002Flineage expectations, the ",{"type":69,"tag":89,"props":1073,"children":1075},{"className":1074},[],[1076],{"type":75,"value":1077},"DAGSTER_CLOUD_*",{"type":75,"value":1079}," in-code rewrite checklist.",{"type":69,"tag":371,"props":1081,"children":1083},{"id":1082},"phase-6-side-by-side-and-cutover",[1084],{"type":75,"value":1085},"Phase 6: Side-by-side and cutover",{"type":69,"tag":78,"props":1087,"children":1088},{},[1089,1091,1096],{"type":75,"value":1090},"Dagster remains authoritative. Run migrated DAGs shadowed\u002Fpaused; compare outputs over the same logical window (row counts + checksums; recompute expected values from the Dagster-produced output itself, using recorded materialization metadata only opportunistically, per ",{"type":69,"tag":89,"props":1092,"children":1094},{"className":1093},[],[1095],{"type":75,"value":150},{"type":75,"value":1097}," Gate 5). Flip schedules one domain per change window: pause the Dagster schedule, unpause the Airflow DAG; rollback is the reverse. Keep Dagster readable after cutover (run history does not migrate).",{"type":69,"tag":371,"props":1099,"children":1101},{"id":1100},"phase-7-final-report",[1102],{"type":75,"value":1103},"Phase 7: Final report",{"type":69,"tag":78,"props":1105,"children":1106},{},[1107,1112,1114,1119],{"type":69,"tag":89,"props":1108,"children":1110},{"className":1109},[],[1111],{"type":75,"value":324},{"type":75,"value":1113}," must pass. The report contains: the go\u002Fno-go assessment (Phase 1.5) and its rationale, disposition table for every definition, all equivalence rows, the NONE\u002FREDESIGN losses stated plainly (lineage depth, code_version triggers, Insights cost accounting, sensor cursor transactionality), the secrets map, and the deferred list with reasons. Spot-check ten ",{"type":69,"tag":89,"props":1115,"children":1117},{"className":1116},[],[1118],{"type":75,"value":308},{"type":75,"value":1120}," claims before delivering it.",{"type":69,"tag":98,"props":1122,"children":1124},{"id":1123},"reference-routing",[1125],{"type":75,"value":1126},"Reference routing",{"type":69,"tag":534,"props":1128,"children":1129},{},[1130,1146],{"type":69,"tag":538,"props":1131,"children":1132},{},[1133],{"type":69,"tag":542,"props":1134,"children":1135},{},[1136,1141],{"type":69,"tag":546,"props":1137,"children":1138},{},[1139],{"type":75,"value":1140},"Construct encountered",{"type":69,"tag":546,"props":1142,"children":1143},{},[1144],{"type":75,"value":1145},"Read",{"type":69,"tag":562,"props":1147,"children":1148},{},[1149,1165,1181,1198,1214,1239,1255,1278,1295,1311,1327],{"type":69,"tag":542,"props":1150,"children":1151},{},[1152,1157],{"type":69,"tag":569,"props":1153,"children":1154},{},[1155],{"type":75,"value":1156},"First hour, glossary, what can break",{"type":69,"tag":569,"props":1158,"children":1159},{},[1160],{"type":69,"tag":89,"props":1161,"children":1163},{"className":1162},[],[1164],{"type":75,"value":94},{"type":69,"tag":542,"props":1166,"children":1167},{},[1168,1173],{"type":69,"tag":569,"props":1169,"children":1170},{},[1171],{"type":75,"value":1172},"Anything (first stop: one row per construct)",{"type":69,"tag":569,"props":1174,"children":1175},{},[1176],{"type":69,"tag":89,"props":1177,"children":1179},{"className":1178},[],[1180],{"type":75,"value":132},{"type":69,"tag":542,"props":1182,"children":1183},{},[1184,1189],{"type":69,"tag":569,"props":1185,"children":1186},{},[1187],{"type":75,"value":1188},"Asset-key → URI convention, translation granularity, external\u002Fobservable assets",{"type":69,"tag":569,"props":1190,"children":1191},{},[1192],{"type":69,"tag":89,"props":1193,"children":1195},{"className":1194},[],[1196],{"type":75,"value":1197},"reference\u002Fassets.md",{"type":69,"tag":542,"props":1199,"children":1200},{},[1201,1206],{"type":69,"tag":569,"props":1202,"children":1203},{},[1204],{"type":75,"value":1205},"Asset deps, IO managers, XCom, storage decisions",{"type":69,"tag":569,"props":1207,"children":1208},{},[1209],{"type":69,"tag":89,"props":1210,"children":1212},{"className":1211},[],[1213],{"type":75,"value":806},{"type":69,"tag":542,"props":1215,"children":1216},{},[1217,1230],{"type":69,"tag":569,"props":1218,"children":1219},{},[1220,1222,1228],{"type":75,"value":1221},"Any ",{"type":69,"tag":89,"props":1223,"children":1225},{"className":1224},[],[1226],{"type":75,"value":1227},"partitions_def",{"type":75,"value":1229},", partition mappings, backfills",{"type":69,"tag":569,"props":1231,"children":1232},{},[1233],{"type":69,"tag":89,"props":1234,"children":1236},{"className":1235},[],[1237],{"type":75,"value":1238},"reference\u002Fpartitions.md",{"type":69,"tag":542,"props":1240,"children":1241},{},[1242,1247],{"type":69,"tag":569,"props":1243,"children":1244},{},[1245],{"type":75,"value":1246},"Schedules, sensors, AutomationCondition, freshness",{"type":69,"tag":569,"props":1248,"children":1249},{},[1250],{"type":69,"tag":89,"props":1251,"children":1253},{"className":1252},[],[1254],{"type":75,"value":680},{"type":69,"tag":542,"props":1256,"children":1257},{},[1258,1269],{"type":69,"tag":569,"props":1259,"children":1260},{},[1261,1267],{"type":69,"tag":89,"props":1262,"children":1264},{"className":1263},[],[1265],{"type":75,"value":1266},"@dbt_assets",{"type":75,"value":1268},", translators, dbt Cloud",{"type":69,"tag":569,"props":1270,"children":1271},{},[1272],{"type":69,"tag":89,"props":1273,"children":1275},{"className":1274},[],[1276],{"type":75,"value":1277},"reference\u002Fdbt.md",{"type":69,"tag":542,"props":1279,"children":1280},{},[1281,1286],{"type":69,"tag":569,"props":1282,"children":1283},{},[1284],{"type":75,"value":1285},"Components (defs.yaml), custom Component subclasses, dynamic generation",{"type":69,"tag":569,"props":1287,"children":1288},{},[1289],{"type":69,"tag":89,"props":1290,"children":1292},{"className":1291},[],[1293],{"type":75,"value":1294},"reference\u002Fcomponents.md",{"type":69,"tag":542,"props":1296,"children":1297},{},[1298,1303],{"type":69,"tag":569,"props":1299,"children":1300},{},[1301],{"type":75,"value":1302},"dagster_cloud.yaml, secrets, alerts, CI\u002FCD, cutover",{"type":69,"tag":569,"props":1304,"children":1305},{},[1306],{"type":69,"tag":89,"props":1307,"children":1309},{"className":1308},[],[1310],{"type":75,"value":179},{"type":69,"tag":542,"props":1312,"children":1313},{},[1314,1319],{"type":69,"tag":569,"props":1315,"children":1316},{},[1317],{"type":75,"value":1318},"Gates, parity testing, state machine",{"type":69,"tag":569,"props":1320,"children":1321},{},[1322],{"type":69,"tag":89,"props":1323,"children":1325},{"className":1324},[],[1326],{"type":75,"value":150},{"type":69,"tag":542,"props":1328,"children":1329},{},[1330,1335],{"type":69,"tag":569,"props":1331,"children":1332},{},[1333],{"type":75,"value":1334},"Failure classes seen before",{"type":69,"tag":569,"props":1336,"children":1337},{},[1338],{"type":69,"tag":89,"props":1339,"children":1341},{"className":1340},[],[1342],{"type":75,"value":166},{"type":69,"tag":98,"props":1344,"children":1346},{"id":1345},"scripts",[1347],{"type":75,"value":1348},"Scripts",{"type":69,"tag":534,"props":1350,"children":1351},{},[1352,1368],{"type":69,"tag":538,"props":1353,"children":1354},{},[1355],{"type":69,"tag":542,"props":1356,"children":1357},{},[1358,1363],{"type":69,"tag":546,"props":1359,"children":1360},{},[1361],{"type":75,"value":1362},"Script",{"type":69,"tag":546,"props":1364,"children":1365},{},[1366],{"type":75,"value":1367},"Purpose",{"type":69,"tag":562,"props":1369,"children":1370},{},[1371,1387,1404],{"type":69,"tag":542,"props":1372,"children":1373},{},[1374,1382],{"type":69,"tag":569,"props":1375,"children":1376},{},[1377],{"type":69,"tag":89,"props":1378,"children":1380},{"className":1379},[],[1381],{"type":75,"value":119},{"type":69,"tag":569,"props":1383,"children":1384},{},[1385],{"type":75,"value":1386},"Scan the Dagster repo → JSON manifest (static + optional runtime mode)",{"type":69,"tag":542,"props":1388,"children":1389},{},[1390,1399],{"type":69,"tag":569,"props":1391,"children":1392},{},[1393],{"type":69,"tag":89,"props":1394,"children":1396},{"className":1395},[],[1397],{"type":75,"value":1398},"scripts\u002Fvalidate_dag.py",{"type":69,"tag":569,"props":1400,"children":1401},{},[1402],{"type":75,"value":1403},"Gates 1-3 against the generated Astro project",{"type":69,"tag":542,"props":1405,"children":1406},{},[1407,1415],{"type":69,"tag":569,"props":1408,"children":1409},{},[1410],{"type":69,"tag":89,"props":1411,"children":1413},{"className":1412},[],[1414],{"type":75,"value":158},{"type":69,"tag":569,"props":1416,"children":1417},{},[1418],{"type":75,"value":1419},"Per-unit state machine + completeness gate",{"items":1421,"total":1587},[1422,1436,1448,1465,1479,1496,1509,1522,1537,1551,1561,1574],{"slug":14,"name":14,"fn":1423,"description":1424,"org":1425,"tags":1426,"stars":25,"repoUrl":26,"updatedAt":1435},"manage and troubleshoot Airflow via CLI","Queries, manages, and troubleshoots Apache Airflow using the `af` CLI. Use when working with anything related to Airflow - a DAG, a DAG run, a task log, an import or parse error, a broken DAG, or any Airflow operation. Covers listing and triggering DAGs, retrying runs, reading task logs, diagnosing failures, debugging import and parse errors, checking connections, variables and pools, exploring the REST API, and monitoring health (for example \"trigger a pipeline\", \"retry a run\", \"list connections\", \"check Airflow health\", \"why did my DAG fail\"). This is the entrypoint that routes to sibling skills for authoring, testing, deploying, and migrating Airflow 2 to 3. Not for warehouse\u002FSQL analytics on Airflow metadata tables (use analyzing-data); for deep root-cause reports use debugging-dags or airflow-investigation.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1427,1428,1431,1432],{"name":13,"slug":14,"type":15},{"name":1429,"slug":1430,"type":15},"CLI","cli",{"name":17,"slug":18,"type":15},{"name":1433,"slug":1434,"type":15},"Debugging","debugging","2026-04-06T18:01:43.992997",{"slug":1437,"name":1437,"fn":1438,"description":1439,"org":1440,"tags":1441,"stars":25,"repoUrl":26,"updatedAt":1447},"airflow-hitl","add human-in-the-loop steps to Airflow DAGs","Builds human-in-the-loop (HITL) Airflow workflows - approval gates, form input, and human-driven branching. Use when a DAG needs a human in the loop - an approval or reject step, sign-off before a task runs, a decision or approval UI, branching on a human choice, or collecting form input mid-run; also on mentions of ApprovalOperator, HITLOperator, HITLBranchOperator, HITLEntryOperator, or HITLTrigger. Requires Airflow 3.1+. Not for AI\u002FLLM task calls (see migrating-ai-sdk-to-common-ai).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1442,1443,1446],{"name":13,"slug":14,"type":15},{"name":1444,"slug":1445,"type":15},"Approvals","approvals",{"name":17,"slug":18,"type":15},"2026-04-06T18:01:46.758548",{"slug":1449,"name":1449,"fn":1450,"description":1451,"org":1452,"tags":1453,"stars":25,"repoUrl":26,"updatedAt":1464},"airflow-plugins","build Airflow UI plugins","Builds Airflow 3.1+ plugins that embed FastAPI apps, custom UI pages, React components, middleware, macros, and operator links directly into the Airflow UI. Use when building anything custom inside Airflow 3.1+ that involves Python and a browser-facing interface - creating an Airflow plugin, adding a custom UI page or nav entry, building FastAPI-backed endpoints inside Airflow, serving static assets from a plugin, embedding a React app, adding middleware to the API server, creating custom operator extra links, or calling the Airflow REST API from inside a plugin; also when AirflowPlugin, fastapi_apps, external_views, react_apps, or plugin registration come up.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1454,1455,1458,1461],{"name":13,"slug":14,"type":15},{"name":1456,"slug":1457,"type":15},"Plugin Development","plugin-development",{"name":1459,"slug":1460,"type":15},"Python","python",{"name":1462,"slug":1463,"type":15},"UI Components","ui-components","2026-04-06T18:01:56.827891",{"slug":1466,"name":1466,"fn":1467,"description":1468,"org":1469,"tags":1470,"stars":25,"repoUrl":26,"updatedAt":1478},"airflow-state-store","persist Airflow task and asset state","Persists task and asset state across retries and DAG runs using Airflow 3.3's AIP-103 key\u002Fvalue stores (`task_state_store`, `asset_state_store`) and the crash-safe `ResumableJobMixin`. Use when the user asks about task state store, checkpointing in tasks, persisting state across retries, job IDs surviving worker crashes, watermarks, asset metadata, resumable tasks, crash-safe operators, or \"what's new in Airflow 3.3\". Also use proactively when reading a DAG that uses Variables or XCom for intra-task coordination state — flag the anti-pattern and recommend task_state_store or asset_state_store instead. Requires Airflow 3.3+.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1471,1472,1474,1475],{"name":13,"slug":14,"type":15},{"name":1473,"slug":39,"type":15},"Data Engineering",{"name":17,"slug":18,"type":15},{"name":1476,"slug":1477,"type":15},"Operations","operations","2026-07-07T06:43:11.160671",{"slug":1480,"name":1480,"fn":1481,"description":1482,"org":1483,"tags":1484,"stars":25,"repoUrl":26,"updatedAt":1495},"analyzing-data","query data warehouses for business questions","Queries the data warehouse with SQL and answers business questions about data. Use when answering anything that needs warehouse data - counts, metrics, trends, aggregations, joins across tables, data lookups, or ad-hoc SQL analysis (for example \"who uses X\", \"how many Y\", \"show me Z\", \"find customers\", \"what is the count\").",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1485,1488,1491,1492],{"name":1486,"slug":1487,"type":15},"Analytics","analytics",{"name":1489,"slug":1490,"type":15},"Data Analysis","data-analysis",{"name":1473,"slug":39,"type":15},{"name":1493,"slug":1494,"type":15},"SQL","sql","2026-04-06T18:01:49.599775",{"slug":1497,"name":1497,"fn":1498,"description":1499,"org":1500,"tags":1501,"stars":25,"repoUrl":26,"updatedAt":1508},"annotating-task-lineage","annotate Airflow tasks with data lineage","Annotate Airflow tasks with data lineage using inlets and outlets. Use when the user wants to add lineage metadata to tasks, specify input\u002Foutput datasets, or enable lineage tracking for operators without built-in OpenLineage extraction.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1502,1503,1504,1505],{"name":13,"slug":14,"type":15},{"name":1473,"slug":39,"type":15},{"name":17,"slug":18,"type":15},{"name":1506,"slug":1507,"type":15},"Observability","observability","2026-04-06T18:02:03.487365",{"slug":1510,"name":1510,"fn":1511,"description":1512,"org":1513,"tags":1514,"stars":25,"repoUrl":26,"updatedAt":1521},"authoring-dags","author Airflow DAGs","Workflow and best practices for writing Apache Airflow DAGs. Use when creating a new DAG, write pipeline code, handling questions about DAG patterns and conventions or extending an existing DAG with a follow-up\u002Fdownstream task. ANY request shaped like 'add a DAG named X', 'write a pipeline', 'add a task that runs after Y', or 'extend the DAG'. For testing and debugging DAGs, see the testing-dags skill.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1515,1516,1517,1520],{"name":13,"slug":14,"type":15},{"name":17,"slug":18,"type":15},{"name":1518,"slug":1519,"type":15},"ETL","etl",{"name":1459,"slug":1460,"type":15},"2026-04-06T18:01:52.679888",{"slug":1523,"name":1523,"fn":1524,"description":1525,"org":1526,"tags":1527,"stars":25,"repoUrl":26,"updatedAt":1536},"authoring-go-sdk-tasks","implement Airflow tasks in Go","Writes Airflow task logic in Go using the Airflow Go SDK. Use when the user wants to implement Airflow tasks in Go, asks about `BundleProvider`\u002F`RegisterDags`, the `bundlev1` Registry\u002FDag interfaces, registering Go tasks (`AddTask`\u002F`AddTaskWithName`), dependency injection by parameter type (`context.Context`, `sdk.TIRunContext`, `*slog.Logger`, `sdk.Client`), or reading connections\u002Fvariables\u002FXComs from Go. This skill covers the Go-specific native API; the shared Python-stub pattern and conceptual model live in authoring-language-sdk-tasks. For building\u002Fpacking\u002Fshipping the bundle see deploying-go-sdk-bundles; for coordinator config see configuring-airflow-language-sdks.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1528,1529,1532,1533],{"name":13,"slug":14,"type":15},{"name":1530,"slug":1531,"type":15},"API Development","api-development",{"name":17,"slug":18,"type":15},{"name":1534,"slug":1535,"type":15},"Go","go","2026-07-11T05:39:13.552213",{"slug":1538,"name":1538,"fn":1539,"description":1540,"org":1541,"tags":1542,"stars":25,"repoUrl":26,"updatedAt":1550},"authoring-java-sdk-tasks","implement Airflow tasks in Java","Writes Airflow task logic in Java, Kotlin, or any JVM language using the Airflow Java SDK. Use when the user wants to implement Airflow tasks in Java\u002FJVM, asks about `@Builder.Dag`\u002F`@Builder.Task`\u002F`@Builder.XCom`, the `Task`\u002F`BundleBuilder` interfaces, reading connections\u002Fvariables\u002FXComs from Java, the JSON-to-Java type mapping, or logging from Java tasks. This skill covers the Java-specific native API; the shared Python-stub pattern and conceptual model live in authoring-language-sdk-tasks. For building\u002Fshipping the bundle see deploying-java-sdk-bundles; for coordinator config see configuring-airflow-language-sdks.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1543,1544,1547],{"name":17,"slug":18,"type":15},{"name":1545,"slug":1546,"type":15},"Engineering","engineering",{"name":1548,"slug":1549,"type":15},"Java","java","2026-07-18T05:48:13.374003",{"slug":1552,"name":1552,"fn":1553,"description":1554,"org":1555,"tags":1556,"stars":25,"repoUrl":26,"updatedAt":1560},"authoring-language-sdk-tasks","implement non-Python Airflow tasks","The language-neutral foundation for Airflow language SDKs — implement task logic in a non-Python language while the DAG stays in Python. Use when the user wants to run an Airflow task in another language (Java, Kotlin, Go, or other JVM\u002Fnative languages), asks how the Python `@task.stub` pairs with native task code, how task\u002FDAG IDs must match across the two sides, how data passes via XCom as JSON, or which language SDKs exist. This skill owns the shared Python-stub pattern and conceptual model; for a specific language's native API, build, and runtime, use that language's skill (e.g. authoring-java-sdk-tasks, authoring-go-sdk-tasks).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1557,1558,1559],{"name":13,"slug":14,"type":15},{"name":17,"slug":18,"type":15},{"name":1545,"slug":1546,"type":15},"2026-07-18T05:11:54.496539",{"slug":1562,"name":1562,"fn":1563,"description":1564,"org":1565,"tags":1566,"stars":25,"repoUrl":26,"updatedAt":1573},"blueprint","build reusable Airflow task group templates","Define reusable Airflow task group templates with Pydantic validation and compose DAGs from YAML. Use when creating blueprint templates, composing DAGs from YAML, validating configurations, or enabling no-code DAG authoring for non-engineers.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1567,1568,1569,1570],{"name":13,"slug":14,"type":15},{"name":17,"slug":18,"type":15},{"name":1518,"slug":1519,"type":15},{"name":1571,"slug":1572,"type":15},"Templates","templates","2026-04-06T18:01:45.361425",{"slug":1575,"name":1575,"fn":1576,"description":1577,"org":1578,"tags":1579,"stars":25,"repoUrl":26,"updatedAt":1586},"checking-freshness","check data freshness in warehouses","Quick data freshness check. Use when the user asks if data is up to date, when a table was last updated, if data is stale, or needs to verify data currency before using it.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1580,1581,1582,1585],{"name":13,"slug":14,"type":15},{"name":1473,"slug":39,"type":15},{"name":1583,"slug":1584,"type":15},"Data Quality","data-quality",{"name":1518,"slug":1519,"type":15},"2026-04-06T18:02:02.138565",34,{"items":1589,"total":1587},[1590,1597,1603,1610,1617,1624,1631],{"slug":14,"name":14,"fn":1423,"description":1424,"org":1591,"tags":1592,"stars":25,"repoUrl":26,"updatedAt":1435},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1593,1594,1595,1596],{"name":13,"slug":14,"type":15},{"name":1429,"slug":1430,"type":15},{"name":17,"slug":18,"type":15},{"name":1433,"slug":1434,"type":15},{"slug":1437,"name":1437,"fn":1438,"description":1439,"org":1598,"tags":1599,"stars":25,"repoUrl":26,"updatedAt":1447},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1600,1601,1602],{"name":13,"slug":14,"type":15},{"name":1444,"slug":1445,"type":15},{"name":17,"slug":18,"type":15},{"slug":1449,"name":1449,"fn":1450,"description":1451,"org":1604,"tags":1605,"stars":25,"repoUrl":26,"updatedAt":1464},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1606,1607,1608,1609],{"name":13,"slug":14,"type":15},{"name":1456,"slug":1457,"type":15},{"name":1459,"slug":1460,"type":15},{"name":1462,"slug":1463,"type":15},{"slug":1466,"name":1466,"fn":1467,"description":1468,"org":1611,"tags":1612,"stars":25,"repoUrl":26,"updatedAt":1478},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1613,1614,1615,1616],{"name":13,"slug":14,"type":15},{"name":1473,"slug":39,"type":15},{"name":17,"slug":18,"type":15},{"name":1476,"slug":1477,"type":15},{"slug":1480,"name":1480,"fn":1481,"description":1482,"org":1618,"tags":1619,"stars":25,"repoUrl":26,"updatedAt":1495},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1620,1621,1622,1623],{"name":1486,"slug":1487,"type":15},{"name":1489,"slug":1490,"type":15},{"name":1473,"slug":39,"type":15},{"name":1493,"slug":1494,"type":15},{"slug":1497,"name":1497,"fn":1498,"description":1499,"org":1625,"tags":1626,"stars":25,"repoUrl":26,"updatedAt":1508},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1627,1628,1629,1630],{"name":13,"slug":14,"type":15},{"name":1473,"slug":39,"type":15},{"name":17,"slug":18,"type":15},{"name":1506,"slug":1507,"type":15},{"slug":1510,"name":1510,"fn":1511,"description":1512,"org":1632,"tags":1633,"stars":25,"repoUrl":26,"updatedAt":1521},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1634,1635,1636,1637],{"name":13,"slug":14,"type":15},{"name":17,"slug":18,"type":15},{"name":1518,"slug":1519,"type":15},{"name":1459,"slug":1460,"type":15}]