You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
flowx emits one DAB per ADF pipeline with no notion of ordering across them. When one pipeline calls another via ExecutePipeline, the generated run_job_task points at a job living in a sibling bundle — so bundle deploy breaks, and today's fix (FLOWX-7) only patches it with a manual ${var.X} placeholder the operator fills in by hand. That's deploy-valid but records no ordering, and is wired one-bundle-at-a-time with no global view of the call graph.
This is the "2b" follow-up #23 deferred: consume #23's deterministic lineage.control_edges in the package phase to emit real, ordered cross-pipeline references. Recommended: generate a top-level orchestration bundle (Option B) whose run_job_task DAG encodes depends_on straight from the control graph, plus deploy-ordering (Option C) so callee bundles register before callers. Depends on #23.
The gap
Verified against source — four facts:
One bundle per pipeline.dab_writer.main writes each workflow to its own dir and calls write_bundle per workflow (dab_writer.py:434-447); each emits an independent databricks.yml + resources/*.yml (:81-159). Documented in the package skill (flowx-package/SKILL.md:247). This is FLOWX-7 (KNOWN_ISSUES_RCA.md:142-160).
ExecutePipeline → a cross-bundle run_job_task.execute_pipeline.prepare lowers the call to run_job_task.job_id = ${resources.jobs.<callee>.id} (execute_pipeline.py:49-52). That reference only resolves inside this bundle, but the callee lives in a sibling bundle → bundle deploy fails with no such node "resources.jobs.X".
The FLOWX-7 fix is deploy-valid but not ordered._rewrite_cross_bundle_run_job_refs rewrites the unresolved ref to ${var.X} (dab_writer.py:1209-1248) and surfaces a manual SETUP.md row telling the operator to pass --var "X=<job_id>" (prereqs_writer.py:509-525). It records no deploy/run ordering, works one bundle at a time (_cross_bundle_variables resets per write_bundle), and leaves an exactly-knowable relationship to be reconstructed by hand.
No ordering logic exists anywhere. Nothing orders bundles, generates an orchestration job, or sequences deploys; main simply loops workflows in report order.
What #23 gives us.#23 adds a deterministic lineage block to inventory.json with control_edges (caller→callee ExecutePipeline edges: caller_pipeline, callee_pipeline, activity_name, wait_on_completion). That's the global input this issue needs — it names, before packaging runs, exactly which callee bundles each caller depends on. This issue consumes it.
Proposed solution
Consume control_edges in the package layer to replace manual ${var.X} wiring with real, ordered references. Repo conventions (AGENTS.md): @dataclass(slots=True, kw_only=True) models, deterministic bundler logic, golden-file tests.
Option B — top-level orchestration bundle (RECOMMENDED). Emit one additional generated bundle with a single orchestration job: one run_job_task per migrated pipeline, depends_on derived directly from control_edges (caller depends on callee, honoring wait_on_completion). A single deployable artifact expressing the whole call graph as a real Databricks Jobs DAG — a shape the codebase already models (RunJobActivityir.py:328-340; the parent_child_orchestration motif motifs.py:120-131). It captures ordering as data the platform enforces, needs no per-bundle rewrite gymnastics, and composes with the existing per-pipeline bundles (still the deploy unit for the child jobs).
Option C — ordered deploys (COMPANION). Topologically sort the per-pipeline bundles from control_edges so callees deploy/register before callers, and emit the ordered databricks bundle deploy sequence into SETUP.md. Cheap; ship alongside B so the numeric job IDs a caller needs exist by deploy time.
Option A — real cross-bundle run_job_task wiring (rejected). Replace ${var.X} with the callee's real reference. Weakest: DAB ${resources.jobs.X.id} only resolves within one bundle, so a genuine symbolic cross-bundle reference isn't confirmable (see open questions), and without a shared bundle it degrades to the same manual numeric ID — the same step, relocated.
Recommendation: B (primary) + C (companion). B produces the enforceable ordering artifact; C ensures the underlying per-pipeline bundles register in dependency order.
Cycle handling. ADF permits A→B and B→A; a Jobs DAG cannot be cyclic. The builder must detect cycles (deterministic DFS/SCC) and, on detection, break the back-edge, emit the tasks without the offending depends_on, and record a WARNINGS.md/SETUP.md note naming the cycle members so the operator decides the real ordering. Never emit an invalid DAG.
dab_writer.py — new write_orchestration_bundle, invoked from main (:434-459) after the per-pipeline bundles, reusing _build_databricks_yml (:576) and the run_job_task shape. Deploy-ordering (C) is computed here from the same graph.
prereqs_writer.py — when the orchestration job resolves a caller's edge, drop/downgrade the manual "Cross-bundle job references" SETUP.md row (:509-525); keep it as the fallback for unresolved callees (partial exports).
FLOWX-7 interaction. The ${var.X} placeholder stays as the backward-compatible fallback for callees absent from the current export or when the orchestration bundle is disabled. When #23's graph resolves a callee, the orchestration job's depends_on becomes the source of ordering truth and the placeholder becomes a safety net, not the primary mechanism.
Unit (extending test_bundler.py / test_prereqs_writer.py): given a synthetic control_edges list (parent→several children; a diamond; a cycle), assert the orchestration job's run_job_tasks + depends_on are correct, a cycle is broken with a recorded warning, and a resolved edge suppresses the FLOWX-7 SETUP.md row while an unresolved callee keeps it. Closes the FLOWX-7 test gap (KNOWN_ISSUES_RCA.md:160).
Integration / golden (test_golden_output.py style): a multi-pipeline fixture (a parent calling child pipelines) through load→translate→prepare→write; assert the orchestration bundle YAML is deterministic/byte-stable and the computed deploy order lists callees before callers.
DAB cross-bundle references (needs on-platform confirmation). The repo only shows run_job_task.job_id = ${resources.jobs.X.id} resolving within a single bundle, plus the numeric ${var.X} fallback across bundles. Whether DAB supports a symbolic cross-bundle job reference isn't confirmable from the codebase. Option B is chosen partly to avoid depending on this: within one orchestration bundle, targets are either its own resources or numeric IDs (both known to work). Confirm whether the orchestration job references the per-pipeline jobs by numeric deployed ID (→ child bundles deploy first, Option C) or whether a single combined bundle owning all jobs is preferable.
Cycles. The control graph may be cyclic; the Jobs DAG can't be. Needs deterministic detection + back-edge break + operator-facing note.
Backward compatibility. Existing single-pipeline migrations and the current ${var.X} + SETUP.md contract must keep working; the orchestration bundle should be additive (ideally opt-in-able) so output stays byte-stable where no cross-pipeline edges exist.
TL;DR
flowx emits one DAB per ADF pipeline with no notion of ordering across them. When one pipeline calls another via
ExecutePipeline, the generatedrun_job_taskpoints at a job living in a sibling bundle — sobundle deploybreaks, and today's fix (FLOWX-7) only patches it with a manual${var.X}placeholder the operator fills in by hand. That's deploy-valid but records no ordering, and is wired one-bundle-at-a-time with no global view of the call graph.This is the "2b" follow-up #23 deferred: consume #23's deterministic
lineage.control_edgesin the package phase to emit real, ordered cross-pipeline references. Recommended: generate a top-level orchestration bundle (Option B) whoserun_job_taskDAG encodesdepends_onstraight from the control graph, plus deploy-ordering (Option C) so callee bundles register before callers. Depends on #23.The gap
Verified against source — four facts:
dab_writer.mainwrites each workflow to its own dir and callswrite_bundleper workflow (dab_writer.py:434-447); each emits an independentdatabricks.yml+resources/*.yml(:81-159). Documented in the package skill (flowx-package/SKILL.md:247). This is FLOWX-7 (KNOWN_ISSUES_RCA.md:142-160).ExecutePipeline→ a cross-bundlerun_job_task.execute_pipeline.preparelowers the call torun_job_task.job_id = ${resources.jobs.<callee>.id}(execute_pipeline.py:49-52). That reference only resolves inside this bundle, but the callee lives in a sibling bundle →bundle deployfails withno such node "resources.jobs.X"._rewrite_cross_bundle_run_job_refsrewrites the unresolved ref to${var.X}(dab_writer.py:1209-1248) and surfaces a manual SETUP.md row telling the operator to pass--var "X=<job_id>"(prereqs_writer.py:509-525). It records no deploy/run ordering, works one bundle at a time (_cross_bundle_variablesresets perwrite_bundle), and leaves an exactly-knowable relationship to be reconstructed by hand.mainsimply loops workflows in report order.What #23 gives us. #23 adds a deterministic
lineageblock toinventory.jsonwithcontrol_edges(caller→calleeExecutePipelineedges:caller_pipeline,callee_pipeline,activity_name,wait_on_completion). That's the global input this issue needs — it names, before packaging runs, exactly which callee bundles each caller depends on. This issue consumes it.Proposed solution
Consume
control_edgesin the package layer to replace manual${var.X}wiring with real, ordered references. Repo conventions (AGENTS.md):@dataclass(slots=True, kw_only=True)models, deterministic bundler logic, golden-file tests.Option B — top-level orchestration bundle (RECOMMENDED). Emit one additional generated bundle with a single orchestration job: one
run_job_taskper migrated pipeline,depends_onderived directly fromcontrol_edges(caller depends on callee, honoringwait_on_completion). A single deployable artifact expressing the whole call graph as a real Databricks Jobs DAG — a shape the codebase already models (RunJobActivityir.py:328-340; theparent_child_orchestrationmotifmotifs.py:120-131). It captures ordering as data the platform enforces, needs no per-bundle rewrite gymnastics, and composes with the existing per-pipeline bundles (still the deploy unit for the child jobs).Option C — ordered deploys (COMPANION). Topologically sort the per-pipeline bundles from
control_edgesso callees deploy/register before callers, and emit the ordereddatabricks bundle deploysequence into SETUP.md. Cheap; ship alongside B so the numeric job IDs a caller needs exist by deploy time.Option A — real cross-bundle
run_job_taskwiring (rejected). Replace${var.X}with the callee's real reference. Weakest: DAB${resources.jobs.X.id}only resolves within one bundle, so a genuine symbolic cross-bundle reference isn't confirmable (see open questions), and without a shared bundle it degrades to the same manual numeric ID — the same step, relocated.Recommendation: B (primary) + C (companion). B produces the enforceable ordering artifact; C ensures the underlying per-pipeline bundles register in dependency order.
Cycle handling. ADF permits A→B and B→A; a Jobs DAG cannot be cyclic. The builder must detect cycles (deterministic DFS/SCC) and, on detection, break the back-edge, emit the tasks without the offending
depends_on, and record a WARNINGS.md/SETUP.md note naming the cycle members so the operator decides the real ordering. Never emit an invalid DAG.Touch points
inventory.json'slineage.control_edges([FEATURE]: Deterministic lineage & dependency tracking, surfaced in the inventory #23) and build a caller→callee graph keyed bynormalize_task_key(pipeline_name)to align with the resource keysexecute_pipeline.preparealready emits (execute_pipeline.py:49).dab_writer.py— newwrite_orchestration_bundle, invoked frommain(:434-459) after the per-pipeline bundles, reusing_build_databricks_yml(:576) and therun_job_taskshape. Deploy-ordering (C) is computed here from the same graph.prereqs_writer.py— when the orchestration job resolves a caller's edge, drop/downgrade the manual "Cross-bundle job references" SETUP.md row (:509-525); keep it as the fallback for unresolved callees (partial exports).FLOWX-7 interaction. The
${var.X}placeholder stays as the backward-compatible fallback for callees absent from the current export or when the orchestration bundle is disabled. When #23's graph resolves a callee, the orchestration job'sdepends_onbecomes the source of ordering truth and the placeholder becomes a safety net, not the primary mechanism.Testing
Repo conventions (
AGENTS.mdTesting Standards;tests/unit/,tests/integration/, golden-file):test_bundler.py/test_prereqs_writer.py): given a syntheticcontrol_edgeslist (parent→several children; a diamond; a cycle), assert the orchestration job'srun_job_tasks +depends_onare correct, a cycle is broken with a recorded warning, and a resolved edge suppresses the FLOWX-7 SETUP.md row while an unresolved callee keeps it. Closes the FLOWX-7 test gap (KNOWN_ISSUES_RCA.md:160).test_golden_output.pystyle): a multi-pipeline fixture (a parent calling child pipelines) through load→translate→prepare→write; assert the orchestration bundle YAML is deterministic/byte-stable and the computed deploy order lists callees before callers.Scope
control_edgesto emit cross-pipeline ordering — the orchestration bundle (B), deploy-ordering (C), cycle handling, and the FLOWX-7 fallback interaction.data_edges(this issue uses control edges only — data-edge ordering deferred unless a concrete need emerges); column-level lineage.Dependencies & open questions
lineage.control_edgesblock; [FEATURE]: Deterministic lineage & dependency tracking, surfaced in the inventory #23 explicitly names this follow-up "2b" and defers ordering-consumption here.run_job_task.job_id = ${resources.jobs.X.id}resolving within a single bundle, plus the numeric${var.X}fallback across bundles. Whether DAB supports a symbolic cross-bundle job reference isn't confirmable from the codebase. Option B is chosen partly to avoid depending on this: within one orchestration bundle, targets are either its own resources or numeric IDs (both known to work). Confirm whether the orchestration job references the per-pipeline jobs by numeric deployed ID (→ child bundles deploy first, Option C) or whether a single combined bundle owning all jobs is preferable.control_edgemay point at acallee_pipelinenot in the current export ([FEATURE]: Deterministic lineage & dependency tracking, surfaced in the inventory #23 records rather than drops these). Fall back to the FLOWX-7 placeholder for unresolved callees; never emit a dangling dependency.${var.X}+ SETUP.md contract must keep working; the orchestration bundle should be additive (ideally opt-in-able) so output stays byte-stable where no cross-pipeline edges exist.