Summary
parse_activity in src/flowx/parser/adf_loader.py does not parse the activities nested inside an ADF Switch activity (typeProperties.cases[].activities and typeProperties.defaultActivities). Those children never enter the AdfActivity AST, so every downstream consumer that walks the AST is blind to them:
- Inventory (
_classify_activities) undercounts activity_count, coverage_pct, and the strategy/type mix.
- Lineage (
_walk_activities) omits every ExecutePipeline / data edge that lives inside a Switch case.
This affects main (both inventory and lineage are on main).
Why this is a bug, not a design choice (internal contradiction)
flowx's convert/translate layer already reads Switch case children:
src/flowx/translator/activity_translators/switch.py iterates type_properties.get("cases") → raw_case.get("activities") and recursively translates them, plus defaultActivities.
translator/engine.py, bundler/dab_writer.py, and bundler/inner_job_params.py consume them too.
So the tool generates output jobs from Switch-nested activities while never counting them in the inventory or drawing their edges in lineage. One half of the codebase understands the shape; the parse/inventory/lineage half doesn't. parse_activity parses ifTrueActivities/ifFalseActivities (IfCondition) and activities (ForEach/Until) but has no branch for Switch cases — and AdfActivity has no field to hold them. IfCondition and Switch are structurally identical branch containers, so there is no principled reason to parse one and not the other.
Root cause
src/flowx/parser/adf_loader.py, parse_activity (~lines 389-415): parses ifTrueActivities, ifFalseActivities, activities, but never cases[].activities or defaultActivities. AdfActivity (src/flowx/models/adf_ast.py) has if_true_activities / if_false_activities / activities fields but no field for Switch cases.
Impact — quantified on a real 327-pipeline export (a real large factory)
Measured by diffing the raw ADF export against a discover run:
| Metric |
Reported |
True |
Gap |
activity_count |
1,804 |
~2,159 |
355 activities dropped, across 38 pipelines |
| control edges |
591 |
~800 |
209 edges missing (~26% of the true graph) |
The 355 hidden activities break down as 216 ExecutePipeline, 89 SetVariable, 44 DatabricksNotebook, 6 Fail. The 216 ExecutePipeline calls are the missing control edges. Notably, app0001_a_pl_orchestrator_main — the core reusable template engine, one of the highest-fan-out pipelines — does its entire fan-out inside a Switch, so it currently appears in lineage as a near-leaf. coverage_pct (reported as 100%) is computed against the undercounted denominator and is therefore not trustworthy for Switch-bearing pipelines.
Steps to reproduce
- Run discover on any factory containing a pipeline with a populated
Switch (e.g. the existing fixture tests/resources/json/pipelines/pl_test_switch_coverage.json, whose Switch has 3 cases + a default containing DatabricksNotebook and WebActivity children).
- Inspect the emitted
inventory.json: the Switch's nested activities do not appear in that pipeline's activity list, and any ExecutePipeline inside a case is absent from lineage.control_edges.
Suggested fix
- Add a field to
AdfActivity for Switch branches (e.g. switch_cases: list[...] | None plus default_activities, or normalize all case/default children into the existing activities list at parse time).
- In
parse_activity, parse type_properties["cases"][].activities and type_properties["defaultActivities"] into that field.
- Have
_classify_activities and _walk_activities descend into it (they already recurse via activity.activities, so normalizing into that list is the smallest change).
- Regression tests: assert the inventory
activity_count for a Switch fixture includes the nested children, and that an ExecutePipeline inside a Switch case produces a control_edges entry.
Notes
- Fix should branch off
main (bug is present there).
- Independently verified against the raw ADF export and a discover run; numbers above are reproducible, not estimates.
This issue was written by Isaac.
Summary
parse_activityinsrc/flowx/parser/adf_loader.pydoes not parse the activities nested inside an ADF Switch activity (typeProperties.cases[].activitiesandtypeProperties.defaultActivities). Those children never enter theAdfActivityAST, so every downstream consumer that walks the AST is blind to them:_classify_activities) undercountsactivity_count,coverage_pct, and the strategy/type mix._walk_activities) omits everyExecutePipeline/ data edge that lives inside a Switch case.This affects
main(both inventory and lineage are onmain).Why this is a bug, not a design choice (internal contradiction)
flowx's convert/translate layer already reads Switch case children:
src/flowx/translator/activity_translators/switch.pyiteratestype_properties.get("cases")→raw_case.get("activities")and recursively translates them, plusdefaultActivities.translator/engine.py,bundler/dab_writer.py, andbundler/inner_job_params.pyconsume them too.So the tool generates output jobs from Switch-nested activities while never counting them in the inventory or drawing their edges in lineage. One half of the codebase understands the shape; the parse/inventory/lineage half doesn't.
parse_activityparsesifTrueActivities/ifFalseActivities(IfCondition) andactivities(ForEach/Until) but has no branch for Switch cases — andAdfActivityhas no field to hold them. IfCondition and Switch are structurally identical branch containers, so there is no principled reason to parse one and not the other.Root cause
src/flowx/parser/adf_loader.py,parse_activity(~lines 389-415): parsesifTrueActivities,ifFalseActivities,activities, but nevercases[].activitiesordefaultActivities.AdfActivity(src/flowx/models/adf_ast.py) hasif_true_activities/if_false_activities/activitiesfields but no field for Switch cases.Impact — quantified on a real 327-pipeline export (a real large factory)
Measured by diffing the raw ADF export against a discover run:
activity_countThe 355 hidden activities break down as 216
ExecutePipeline, 89SetVariable, 44DatabricksNotebook, 6Fail. The 216ExecutePipelinecalls are the missing control edges. Notably,app0001_a_pl_orchestrator_main— the core reusable template engine, one of the highest-fan-out pipelines — does its entire fan-out inside a Switch, so it currently appears in lineage as a near-leaf.coverage_pct(reported as 100%) is computed against the undercounted denominator and is therefore not trustworthy for Switch-bearing pipelines.Steps to reproduce
Switch(e.g. the existing fixturetests/resources/json/pipelines/pl_test_switch_coverage.json, whose Switch has 3 cases + a default containingDatabricksNotebookandWebActivitychildren).inventory.json: the Switch's nested activities do not appear in that pipeline's activity list, and anyExecutePipelineinside a case is absent fromlineage.control_edges.Suggested fix
AdfActivityfor Switch branches (e.g.switch_cases: list[...] | Noneplusdefault_activities, or normalize all case/default children into the existingactivitieslist at parse time).parse_activity, parsetype_properties["cases"][].activitiesandtype_properties["defaultActivities"]into that field._classify_activitiesand_walk_activitiesdescend into it (they already recurse viaactivity.activities, so normalizing into that list is the smallest change).activity_countfor a Switch fixture includes the nested children, and that anExecutePipelineinside a Switch case produces acontrol_edgesentry.Notes
main(bug is present there).This issue was written by Isaac.