Skip to content

Commit 010e04e

Browse files
Merge internal (#43)
## Changes <!-- Summary of your changes that are easy to understand. Add screenshots when necessary --> This PR is based on #22 and merges internal changes into the flowx repo: * Airflow support (@peterpark-db) * Fix missing notebook headers in bridge notebooks (@lorenzorubi-db ) * Emit `sync.include` in generated `databricks.yml` (@alexnastetsky ) * Fixes to documentation (@zanitarahimi) * Consolidation and conflict resolution (@matthewmoorcroft ) * Updates to GitHub actions (@matthewmoorcroft and @ghanse ) ### Linked issues <!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved. See https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword --> Resolves #18 ### Tests <!-- How is this tested? Please see the checklist below and also describe any other relevant tests --> - [x] manually tested - [x] added unit tests - [x] added integration tests --------- Co-authored-by: matthewmoorcroft <31916486+matthewmoorcroft@users.noreply.github.com>
2 parents bdf3806 + 75d64fc commit 010e04e

190 files changed

Lines changed: 26177 additions & 1673 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/push.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ jobs:
2828
version: "0.11.2"
2929
checksum: "7ac2ca0449c8d68dae9b99e635cd3bc9b22a4cb1de64b7c43716398447d42981"
3030
python-version: "3.12"
31-
- name: Scrub internal proxy URLs from uv.lock
32-
run: sed -i 's|https://pypi-proxy\.dev\.databricks\.com/simple|https://pypi.org/simple|g' uv.lock
31+
- name: Normalize uv.lock to the public package index
32+
run: sed -i -E 's#https://[a-zA-Z0-9._-]+/simple#https://pypi.org/simple#g' uv.lock
3333
- run: uv sync --frozen
3434
- run: make test
3535
- name: Verify requirements.txt is in sync with the lockfile
@@ -48,8 +48,8 @@ jobs:
4848
version: "0.11.2"
4949
checksum: "7ac2ca0449c8d68dae9b99e635cd3bc9b22a4cb1de64b7c43716398447d42981"
5050
python-version: "3.12"
51-
- name: Scrub internal proxy URLs from uv.lock
52-
run: sed -i 's|https://pypi-proxy\.dev\.databricks\.com/simple|https://pypi.org/simple|g' uv.lock
51+
- name: Normalize uv.lock to the public package index
52+
run: sed -i -E 's#https://[a-zA-Z0-9._-]+/simple#https://pypi.org/simple#g' uv.lock
5353
- run: uv sync --frozen
5454
- run: make fmt
5555
- name: Check for formatting changes

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ bootstrap a self-contained virtual environment with pip via the `setup` skill or
1717
bash scripts/bootstrap.sh # creates the venv, pip-installs requirements.txt, writes .migration-venv
1818
# then run plugin code with src/ on PYTHONPATH, using the interpreter from the marker file:
1919
PY="$(cat .migration-venv)"
20-
PYTHONPATH=src "$PY" -m flowx.adapter inputs discover
20+
PYTHONPATH=src "$PY" -m flowx.adapter inputs discover --source adf # or --source airflow
2121
```
2222

2323
`bootstrap.sh` creates the venv at `/Workspace/Users/<current user>/.migration-skills` when running

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: clean dev ci test integration fmt help docs-install docs-clean docs-build docs-serve lock-dependencies requirements precommit
1+
.PHONY: clean dev ci test integration integration-live fmt help docs-install docs-clean docs-build docs-serve lock-dependencies requirements precommit
22

33
clean:
44
rm -rf .venv .pytest_cache .ruff_cache .mypy_cache __pycache__
@@ -15,6 +15,9 @@ test:
1515
PYTHONPATH=src uv run pytest tests/unit -v
1616

1717
integration:
18+
PYTHONPATH=src uv run pytest tests/integration -v -m "not slow and not integration"
19+
20+
integration-live:
1821
PYTHONPATH=src uv run pytest tests/integration -v -m "not slow"
1922

2023
fmt:

README.md

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,42 @@
11
# flowx
22

3-
ADF to Databricks Lakeflow Jobs translator, delivered as agent skills.
3+
Orchestrator-to-Databricks Lakeflow Jobs translator, delivered as agent skills.
44

5-
flowx converts Azure Data Factory (ADF) pipeline definitions into Databricks Lakeflow Jobs packaged as Declarative Automation Bundles (DABs). It deterministically translates known activity types and falls back to agentic (LLM-assisted) translation for complex or rare types. flowx runs as a set of [agent skills](skills/) usable from Databricks Genie Code, Claude Code, or any tool that supports the Agent Skills standard.
5+
flowx converts a source orchestrator's pipelines — **Azure Data Factory (ADF)** or **Apache
6+
Airflow** — into Databricks Lakeflow Jobs packaged as Declarative Automation Bundles (DABs). It
7+
deterministically translates known activity/operator types and falls back to agentic (LLM-assisted)
8+
translation for complex or rare types. flowx runs as a set of [agent skills](skills/) usable from
9+
Databricks Genie Code, Claude Code, or any tool that supports the Agent Skills standard.
10+
11+
Both sources emit the same source-neutral Pipeline IR, so the convert-configuration and package
12+
phases are shared; only discovery and translation are source-specific. Pick the source with
13+
`--source {adf,airflow}` (required for discover/convert; package is source-independent).
614

715
## Architecture
816

917
```
1018
flowx Pipeline
1119
==================
1220
13-
ADF JSON (UC Volumes / Workspace)
14-
|
15-
v
16-
+------------------+
17-
| 1. DISCOVER | Parse ADF ARM/JSON exports
18-
| adf_loader.py | -> Typed AST -> metadata/inventory.json
19-
+------------------+
21+
ADF ARM/JSON (UC Volumes / Workspace) | Airflow DAG .py files
22+
\ | /
23+
v v v
24+
+---------------------------------------------------------------+
25+
| 1. DISCOVER sources/<adf|airflow>/ -> metadata/inventory.json
26+
| (ADF: ARM/JSON parse; Airflow: static ast parse)
27+
+---------------------------------------------------------------+
2028
|
2129
v
22-
+------------------+
23-
| 2. CONVERT | Registry dispatch + topological sort
24-
| engine.py | -> Pipeline IR (deterministic + agentic gaps)
25-
+------------------+
30+
+---------------------------------------------------------------+
31+
| 2. CONVERT sources/<adf|airflow>/ -> shared Pipeline IR
32+
| (deterministic mappings + agentic gaps)
33+
+---------------------------------------------------------------+
2634
|
2735
v
28-
+------------------+
29-
| 3. PACKAGE | IR -> DAB YAML + notebooks + setup scripts
30-
| dab_writer.py | -> Deployable DABs project
31-
+------------------+
36+
+---------------------------------------------------------------+
37+
| 3. PACKAGE bundler/dab_writer.py (source-independent)
38+
| IR -> DAB YAML + notebooks + setup scripts
39+
+---------------------------------------------------------------+
3240
|
3341
v
3442
databricks bundle validate / deploy
@@ -88,7 +96,7 @@ Run the end-to-end migration:
8896
Or run individual phases:
8997

9098
```
91-
/flowx:flowx-discover # Parse ADF JSON, produce inventory + complexity report
99+
/flowx:flowx-discover # Parse the source (ADF JSON / Airflow DAGs), produce inventory + complexity report
92100
/flowx:flowx-convert # Deterministic + agentic translation
93101
/flowx:flowx-package # Generate DABs project
94102
```
@@ -160,13 +168,45 @@ agent using LLM-assisted reasoning from the activity's ARM JSON.
160168
| Script | LLM-assisted (agentic) |
161169
| Until | LLM-assisted (agentic) |
162170

171+
## Supported Airflow Operators
172+
173+
The Airflow source parses DAG `.py` modules **statically** (via `ast`, no Airflow install or DAG
174+
execution) and maps ~35 operator/sensor families to the shared IR. Highlights:
175+
176+
- **Compute / scripts**`PythonOperator` (callable → runnable notebook with transitive deps),
177+
`BashOperator` / `SSHOperator` (incl. `spark-submit` lift), `SparkSubmitOperator`, the Databricks
178+
provider operators, and SQL operators (`DatabricksSql*`, `SQLExecuteQueryOperator`, `HiveOperator`,
179+
…) → `sql_task`.
180+
- **TaskFlow API**`@dag` / `@task`; implicit XCom data flow lowers to `dbutils.jobs.taskValues`.
181+
`@task.expand([literal])``for_each_task`; non-literal / `.partial().expand()` / `@task_group`
182+
a linked placeholder notebook that raises `NotImplementedError`.
183+
- **Sensors** — file/table/time sensors → job triggers or polling notebooks; `ExternalTaskSensor`
184+
cross-DAG wait; Http/Python/DateTime → polling tasks.
185+
- **dbt** — dbt CLI operators and astronomer-cosmos `DbtDag` / `DbtTaskGroup` → a dbt-factory job
186+
(static per-node explosion by default, or PyDABs via `--dbt-mode pydabs`).
187+
- **Scheduling & semantics** — cron → Quartz, `timedelta` → periodic, `trigger_rule``run_if`,
188+
`params={...}` → job parameters, `>>` / `<<` / `set_upstream` / TaskGroup edges.
189+
190+
Operators without a deterministic mapping become a failing placeholder and are recorded in
191+
`gaps.json` for review. Eligible leaf gaps can use the fingerprint-bound resolver backed by the pinned [`airflow-to-dabs`](https://github.com/park-peter/airflow-to-dabs/tree/main/providers/flowx-gap-resolver) provider profile; flowx retains ownership of parsing, graph identity, policy, IR, and packaging. Full matrix:
192+
[`skills/flowx-convert/sources/airflow-coverage.md`](skills/flowx-convert/sources/airflow-coverage.md).
193+
194+
Airflow discovery independently audits DAG declarations, task candidates, dependency declarations,
195+
DAG settings, mapped calls, and operator arguments before comparing them with captured IR. An
196+
included DAG is `verified` when every audited construct has a proven translation,
197+
`verified_with_gaps` when every unsupported construct is linked to a runnable-failure placeholder,
198+
or `failed` when reconciliation finds unexplained loss. Failed reconciliation exits nonzero and
199+
blocks package writes. `--exclude-dag <dag_id>` is repeatable; excluded DAGs emit no Job but remain
200+
visible with zero translated activities in inventory and coverage reporting. This guarantee applies
201+
to the supported static subset; flowx never imports or executes DAG modules.
202+
163203
## How It Works
164204

165205
### Phase 1: Discover
166-
Reads ADF JSON definitions from Unity Catalog volumes (or a `/Workspace` Git folder), normalizes ARM template format, parses into typed AST nodes, and classifies each activity as deterministic, agentic, or unsupported. Produces `metadata/inventory.json` and a per-pipeline complexity report at `metadata/profile_report.csv`.
206+
Parses the source into typed nodes and classifies each activity/operator as deterministic, agentic, or unsupported — ADF JSON from Unity Catalog volumes (or a `/Workspace` Git folder, normalizing ARM template format), or Airflow DAG `.py` modules read statically with `ast`. Airflow inventory includes audited/deterministic/agentic/failed/excluded counts, reconciliation status, stable finding fingerprints, translation-path coverage, and deterministic coverage. Produces `metadata/inventory.json` and a per-pipeline complexity report at `metadata/profile_report.csv`.
167207

168208
### Phase 2: Convert
169-
Applies deterministic translators via registry dispatch, resolves dependencies through topological sort, and threads immutable `TranslationContext` through control-flow visitors. Agentic gaps are flagged for LLM-assisted translation. Produces Pipeline IR.
209+
Applies deterministic translators (ADF activity registry / Airflow operator mapping), resolves dependencies, and records unresolved gaps. ADF supports its guided agentic translation workflow. Airflow supports a fingerprint-bound, explicitly reviewed leaf-gap workflow whose constrained provider output is replayed against an immutable deterministic baseline before packaging. Produces the shared Pipeline IR consumed unchanged by the package phase.
170210

171211
### Phase 3: Package
172212
Converts Pipeline IR into a deployable DABs project: `databricks.yml`, per-job YAML resource files, generated Python notebooks, and setup scripts for UC volumes, secrets, and connections.
@@ -180,7 +220,7 @@ flowx_output/
180220
databricks.yml # Bundle configuration (package)
181221
resources/
182222
jobs/
183-
<pipeline_name>.yml # One job per ADF pipeline
223+
<pipeline_name>.yml # One Job per included ADF pipeline or Airflow DAG
184224
src/
185225
notebooks/
186226
<pipeline_name>/
@@ -209,11 +249,12 @@ for deployment (SDK notebook or CLI script) and Genie Code registration.
209249
## Development
210250

211251
```bash
212-
make dev # Install dependencies (uses uv)
213-
make test # Run unit tests
214-
make integration # Run integration tests
215-
make fmt # Format + lint (ruff + mypy)
216-
make clean # Remove build artifacts
252+
make dev # Install dependencies (uses uv)
253+
make test # Run unit tests
254+
make integration # Run integration tests (excludes the live-Azure suite; gates CI)
255+
make integration-live # Also run tests needing live ADF access (az login + factory access)
256+
make fmt # Format + lint (ruff + mypy)
257+
make clean # Remove build artifacts
217258
```
218259

219260
### Prerequisites

app/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ operation; `parameters` is its keyword-argument dict.
2020
| `inputs` | `adapter inputs` | List a phase's input prompts/defaults |
2121
| `discover` | `adapter discover` | Parse ADF JSON, classify activities |
2222
| `convert` | `adapter convert` | ADF activities → Databricks IR |
23-
| `merge_agentic` | `adapter convert --merge-agentic` | Merge agent-produced results into the report |
23+
| `merge_agentic` | `adapter convert --merge-agentic` | Merge ADF agent-produced results into the report |
24+
| `resolve_agentic` | `adapter resolve-agentic` | Prepare, stage, and apply reviewed Airflow leaf-gap resolutions |
2425
| `inspect` | `adapter inspect` | Surface pending translation options |
2526
| `apply_answers` | `adapter modify` | Apply answers → stamped IR |
2627
| `materialize_lookup` | `adapter materialize-lookup` | CSV → lookup-values JSON |
@@ -30,7 +31,7 @@ operation; `parameters` is its keyword-argument dict.
3031
| `record_results` | `adapter record-results` | Write coverage to a UC table |
3132
| `install_dashboard` | `adapter install-dashboard` | Publish the coverage dashboard |
3233

33-
Example: `flowx(command="discover", parameters={"adf_source_path": "/Volumes/main/default/adf_export", "output_dir": "./out"})`.
34+
Example: `flowx(command="discover", parameters={"source": "adf", "adf_source_path": "/Volumes/main/default/adf_export", "output_dir": "./out"})`.
3435

3536
Each command is a thin bridge over `python -m flowx.adapter` (the same entry point the agent
3637
skills use), then reads back the JSON/CSV artifacts each phase writes — so the MCP surface stays in

docs/content/docs/architecture.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Each activity is classified with a `TranslationStrategy`:
2828
* `AGENTIC` (LLM-assisted gaps)
2929
* `UNSUPPORTED`
3030

31-
The reporting layer (`reporting/`) can write per-run coverage to a Unity Catalog table and publish an AI/BI dashboard.
31+
The reporting layer (`reporting/`) can write per-run coverage to a Unity Catalog table and publish an AI/BI dashboard. Airflow rows use independently audited candidates as the denominator and persist reconciliation status, failed/excluded counts, stable finding fingerprints, translation-path coverage, deterministic coverage, reviewed agentic outcomes, and mechanically validated code-attached coverage. Provider-authored code remains distinct from deterministic translation and requires human review.
3232

3333
## Two surfaces over one core
3434

@@ -59,7 +59,7 @@ The unified `flowx.adapter` CLI is the single contract. Both surfaces go through
5959
| `mcp/runner.py` | Subprocess bridge to `flowx.adapter` with artifact summarizers (for running translation without the `mcp` dependency) |
6060
| `mcp/__main__.py` | `python -m flowx.mcp` entry point (stdio default, `--http` for hosting) |
6161

62-
The `flowx` tool's `command` selects the adapter operation: `inputs`, `discover`, `convert`, `merge_agentic`, `inspect`, `apply_answers`, `materialize_lookup`, `workspace_paths`, `package`, `migrate`, `record_results`, and `install_dashboard` (with `parameters` carrying that command's arguments).
62+
The `flowx` tool's `command` selects the adapter operation: `inputs`, `discover`, `convert`, `merge_agentic` (ADF only), `resolve_agentic` (Airflow only), `inspect`, `apply_answers`, `materialize_lookup`, `workspace_paths`, `package`, `migrate`, `record_results`, and `install_dashboard` (with `parameters` carrying that command's arguments).
6363

6464
## Deployment topology
6565

@@ -78,7 +78,7 @@ The MCP server runs in whichever transport fits the calling tool. This is chosen
7878
own service principal
7979
```
8080

81-
See [Installation](/docs/installation#running-flowx-as-an-mcp-server) for the exact commands and the [app README](https://github.com/databricks-solutions/flowx/tree/main/app) for deployment details.
81+
See [Installation](/flowx/docs/installation#running-flowx-as-an-mcp-server) for the exact commands and the [app README](https://github.com/databricks-solutions/flowx/tree/main/app) for deployment details.
8282

8383
<Callout type="info" title="Inputs and outputs on a hosted app">
8484
A Databricks App can't read the user's workspace / UC Volume files (`/Volumes/...` is **not** auto-mounted). Two ways to get data in/out of the `flowx` tool:

docs/content/docs/configuration.mdx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,12 @@ phase surfaces three optional inputs — `results_table`, `results_warehouse_id`
149149

150150
- **`record-results`** writes one row **per pipeline per run** to the supplied Unity Catalog
151151
table (`catalog.schema.table`), combining the complexity columns above with the
152-
deterministic/agentic/unsupported coverage breakdown. Every row is stamped with a shared
152+
audited/deterministic/agentic/failed/excluded coverage breakdown, reconciliation and migration
153+
status, finding fingerprints, translation-path coverage, deterministic coverage, unresolved agentic count, reviewed-resolution outcomes/provider version, and code-attached coverage. The corresponding result columns are `resolved_agentic_count`, `unresolved_agentic_count`, and `code_attached_coverage_pct`. Airflow's audited count remains the denominator even for failed or excluded candidates. Code-attached coverage counts deterministic tasks plus accepted `resolved` provider candidates; it means the generated code passed mechanical contract validation, not that its semantics were certified. Every row is stamped with a shared
153154
**`run_id`** (UUID), **`run_date`** (`CURRENT_TIMESTAMP()`), and **`run_by`**
154155
(`CURRENT_USER()`), so coverage is trackable across runs and users.
155156
- **`install-dashboard`** creates and publishes an AI/BI (Lakeview) dashboard over that table —
156-
KPI counters (pipelines, coverage %, deterministic/agentic/unsupported activity totals), a
157-
pipelines-by-complexity bar chart, a coverage-over-runs line, and a per-pipeline coverage
158-
table.
157+
KPI counters (pipelines, audited activities, and mechanically validated code-attached coverage), failed/excluded totals, a pipelines-by-complexity bar chart, a code-attached-coverage trend, and a per-pipeline table that retains translation-path and deterministic coverage.
159158

160159
The SQL warehouse is auto-detected (preferring a running serverless warehouse) when
161160
`results_warehouse_id` is left blank. Both run via the Databricks SDK and degrade gracefully

docs/content/docs/guide.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ The bundle contains:
9494
Connection strings, credentials, and other protected configuration parameters are emitted as `SecretInstruction` steps that require [Databricks Secrets](https://docs.databricks.com/aws/en/security/secrets/).
9595
Run the setup scripts to add any required secret values before deploying and running pipelines in your workspace.
9696
</Callout>
97+
98+
When running with workspace auth (e.g. Genie Code), `package` can optionally persist this run's
99+
coverage to a Unity Catalog table — one row per pipeline stamped with a UUID `run_id`, `run_date`,
100+
and `run_by` (`record-results`) — and install a published AI/BI coverage dashboard over that table
101+
(`install-dashboard`). See [Configuration options](/flowx/docs/options) for details.
102+
103+
For Airflow, `activities` is the independent source-audit count rather than the number of tasks the
104+
translator happened to emit. Reporting distinguishes deterministic, agentic, failed, and excluded
105+
candidates and carries reconciliation status, translation-path coverage, deterministic coverage, unresolved agentic outcomes, and mechanically validated code-attached coverage. Code attachment is not a certification that provider-authored code is semantically correct.
97106
</Step>
98107

99108
<Step>

docs/content/docs/installation.mdx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,13 @@ The environment is created once and reused. No `uv` is required for plugin users
159159
Open Claude Code and ask *"What flowx skills do you have available?"*. You should see a list of skills (e.g. `flowx-setup`,
160160
`flowx-migrate`). You can now run `/flowx:flowx-migrate`, `/flowx:flowx-discover`, and other flowx skills.
161161

162-
<Callout type="warn" title="Troubleshooting missing dependencies">
163-
If calling a skill raises a `ModuleNotFoundError`, the virtual environment is missing or incomplete. Ensure Python is installed
164-
in your environment and that you have access to a Python package registry for installing dependencies, then re-run `/flowx:flowx-setup`.
165-
</Callout>
162+
If you hit a `ModuleNotFoundError` while running a phase, the venv is missing or incomplete — re-run `/flowx:flowx-setup`. Every Python command the skills run uses the interpreter recorded in `<plugin_dir>/.migration-venv`, with `src/` on `PYTHONPATH`:
163+
164+
```bash
165+
export PYTHONPATH="<plugin_dir>/src"
166+
PY="$(cat <plugin_dir>/.migration-venv)"
167+
"$PY" -m flowx.adapter inputs discover --source adf # or --source airflow
168+
```
166169
</Step>
167170
</Steps>
168171

0 commit comments

Comments
 (0)