Skip to content

feat(tangle-cli): add pipeline compile command and PipelineCompiler#1

Open
Silin144 wants to merge 23 commits into
feat/python-pipeline-dslfrom
feat/pipeline-compile
Open

feat(tangle-cli): add pipeline compile command and PipelineCompiler#1
Silin144 wants to merge 23 commits into
feat/python-pipeline-dslfrom
feat/pipeline-compile

Conversation

@Silin144

@Silin144 Silin144 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

Ports the Python-pipeline compiler from the internal tangle-deploy package into the OSS CLI, so the generic compile logic lives in OSS. Follows the existing PipelineHydrator(TangleCliHandler) architecture and the hydrate facade pattern rather than inventing new conventions.

Stacked on TangleML#20 (the python_pipeline authoring DSL) — this branch imports that package. See the stacking note below.

What's included

  • pipeline_compiler.pyPipelineCompiler(TangleCliHandler) executor plus the generic compile_pipeline() free function. The zone-root discovery seam is a module-level ZONE_ROOT_MARKERS: list[str] = [], left empty in OSS for downstream injection (e.g. tangle-deploy appends its oasis marker).
  • schema_validation.py + bundled schemas/dehydrated_pipeline_schema.json — JSON-Schema (Draft 2020-12) structural validation, semantic checks (dangling taskOutput.taskId, undeclared graphInput.inputName, outputValuesoutputs, pure componentRef), and the no-template-delimiter output contract with exempt_paths.
  • pipelines.compile_pipeline_file functional facade + a cyclopts compile subcommand under tangle sdk pipelines, mirroring hydrate.
  • jsonschema>=4.0.0 runtime dependency (uv.lock updated).
  • Tests for the compiler, the CLI command, and schema validation, with colocated python_pipeline fixtures.

Worked example

$ tangle sdk pipelines compile pipeline.py -o pipeline.yaml
Compiled pipeline.py -> pipeline.yaml (1 task(s)).

Testing

All three CI checks pass locally:

  • uv run pytest731 passed, 0 regressions (97 in the new/touched files)
  • uv lock --check — clean (exit 0)
  • git diff --check — no whitespace errors

Stacking note

⚠️ Do not merge. This PR is stacked on TangleML#20 and is targeted at the feat/python-pipeline-dsl branch so the diff shows Phase-2-only changes. Once TangleML#20 merges into master, this branch will be rebased onto master and retargeted at TangleML/tangle-cli:master.

🤖 Generated with Claude Code

Volv-G and others added 10 commits July 7, 2026 19:39
Assisted-By: devx/12bfa114-4ee7-4929-a37c-98018f6b0f41
…attempts-0.1.1

Configure submit recovery attempts
…uler-retries-fix/tangle-cli-release-workflow-inde-f259a7c

Allow tangle-cli releases with independent tangle-api version
…uler-retries-fix/release-publish-cli-only

Publish only tangle-cli artifacts in release workflow
…uler-retries-fix/release-conditional-publish

Publish release artifacts by package version
…uler-retries-fix/release-package-scoped-tags

Use package-scoped release tags
Fix generated OpenAPI client request-body handling so mixed simple and complex/object bodies remain lossless, and bump tangle-cli/tangle-api patch versions.
@Silin144
Silin144 force-pushed the feat/pipeline-compile branch from a905d75 to 02289db Compare July 10, 2026 18:41
feat(tangle-cli): add python_pipeline authoring DSL
@Silin144
Silin144 force-pushed the feat/pipeline-compile branch from 02289db to 621170b Compare July 14, 2026 16:23
Silin144 added a commit that referenced this pull request Jul 14, 2026
…er it

Second independent review of TangleML#28 found a blocking correctness bug in the
propagate_config broadcast subsystem, plus its total lack of test coverage.
Both fixed here, with two doc nits.

1. BLOCKING — broadcast hard-failed through a config-less intermediate.
   In `_process_subpipeline_children`, an active broadcast (non-empty
   `ctx.broadcast_stack`) unconditionally called `_read_raw_cfg(child_cfg_path)`
   for every child edge, and that helper raises `CompileError: config file not
   found` on a missing file. A pure composing subpipeline (no `cfg` param, no
   `config=`) has no config.yaml on disk, so the canonical shape
   `root(propagate_config=True) -> config-less parent -> config-taking child`
   failed with a spurious "config file not found" for a file the author never
   wrote. Fix: in the broadcast path, treat a MISSING child config as `{}` (the
   child declares nothing to overlay; broadcast keys flow PAST it to
   descendants). A config-DECLARING child whose file is genuinely missing is
   still caught later, with guidance, in `_load_cfg_and_raw` (verified: the
   `cfg`-param error message is preserved).

2. Coverage: the entire broadcast subsystem had zero tests
   (`grep propagate_config tests/` was empty — which is why #1 shipped). Added
   `tests/fixtures/python_pipeline/broadcast_zone/` (a 3-level tree isolated in
   its own dir so the config-less intermediate genuinely has no config.yaml —
   the shared fixtures dir has one that would mask the bug) + a regression test
   asserting the compile succeeds and the root's broadcast value reaches the
   grandchild leaf, winning over the grandchild's own config (three distinct
   values make the source unambiguous).

3. Nit: the "content-hash" wording in a subpipeline test comment is really a
   compile-key hash (source path + qualname + name + config path + overrides
   fingerprint), not a hash of emitted content — corrected.

4. Nit: genericized a docstring example name (`options_standardization` /
   "Options Standardization") that echoed an internal pipeline -> `train_model`
   / "Train Model".

746 passed (+1); ruff clean on touched files; `uv lock --check` and
`git diff --check` clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Volv-G and others added 11 commits July 14, 2026 18:48
Run the full pipelines validate authoring validator during pipeline-runs submit and --dry-run after hydration, and route PipelineRunner through the same base hook. Root graph requirements are enforced by the authoring validator, with README and tests updated. Bump tangle-cli patch version to 0.1.3. This is based on master and intentionally excludes PR TangleML#27 skip-validation CLI flag and separate submit-root check.

Assisted-By: devx/5490f7cb-c7e4-4cf5-b525-a8c4e2af55b1
Assisted-By: devx/5490f7cb-c7e4-4cf5-b525-a8c4e2af55b1
…ter-pipeline-runner-authoring-3e10657

Validate pipeline specs during submit
Move graph-shape, vendored JSON schema, and component-input validation into tangle-cli so pipeline validation, pipeline-run submit/dry-run, and PipelineRunner share one validation surface. Deduplicate validation so each path validates once, add the vendored schema/refresh helper and jsonschema dependency, preserve tangle_cli.pipelines re-exports for back-compat, and bump tangle-cli to 0.1.4.

Assisted-By: devx/5490f7cb-c7e4-4cf5-b525-a8c4e2af55b1
…onsolidate-pipeline-validation-0f07e91

feat: consolidate pipeline validation
Port the Python-pipeline compiler from the internal tangle-deploy
package into the OSS CLI so the generic compile logic lives in OSS:

- pipeline_compiler.py: PipelineCompiler(TangleCliHandler) executor plus
  the generic compile_pipeline() free function. The zone-root seam
  (ZONE_ROOT_MARKERS) is left empty here for downstream injection.
- schema_validation.py + schemas/dehydrated_pipeline_schema.json:
  Draft 2020-12 structural validation, semantic checks, and the
  no-template-delimiter output contract.
- pipelines.compile_pipeline_file facade and a cyclopts `compile`
  subcommand under `tangle sdk pipelines`, following the hydrate facade.
- jsonschema>=4.0.0 runtime dependency (uv.lock updated).
- Tests for the compiler, CLI command, and schema validation, with
  colocated python_pipeline fixtures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ead code

Follow-up to the PipelineCompiler port, addressing an independent review of
the PR:

- Add compile tests for the headline features the command is named after:
  subpipeline child-sidecar emission (parent componentRef rewrite, no
  subpipeline://pending leak), .override_config precedence on a subpipeline
  edge, and @registered gen_config resolution (omitted -> nearest ancestor;
  relative rejected when ZONE_ROOT_MARKERS is empty; relative resolved once a
  marker is appended to the seam). New hermetic fixtures under
  tests/fixtures/python_pipeline/.
- Remove the dead _parse_overrides from pipeline_compiler.py; the live copy
  lives in pipelines_cli.py and is covered by the CLI bad-override test.
- Strip remaining internal-tool references from user-facing surfaces: the
  config/output-collision CompileError message, the _fragment_for_task
  docstring, and the _coerce_override docstring in cfg.py.
- Widen the DSL no-internal-references guard to scan the sibling OSS compile
  modules (component_from_func, pipeline_compiler, schema_validation,
  pipelines, pipelines_cli), not just the DSL package.

Full suite: 745 passed. uv lock --check and git diff --check clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…surface

Second independent review of TangleML#28 surfaced two verified findings; fixing
both here per request:

1. Remove the dead `_resolve_cfg_path(pipeline_fn, module_path)` wrapper in
   pipeline_compiler.py — a thin, zero-caller shim over
   `_resolve_cfg_path_in_dir` (the actual resolver, retained).

2. Scrub internal product/infra names from OSS docstrings + examples so the
   open-source surface never names Oasis / UPI / Comet / areas-ml image paths:
   - task_env.py: example env `UPI`/`areas-ml-upi:main`/`publish_to_comet`/
     `[UPI Clustering]`/"Oasis runner" -> generic `TRAINING`/`python:3.12`/
     `train_model`/"downstream runner".
   - registered.py: relative-path resolution doc referenced the internal
     `oasis.pipeline_component_root.yaml` marker -> now points at the generic
     `ZONE_ROOT_MARKERS` seam and states the OSS build registers none by default.
   - component_from_func.py: 31 example `UPI` env-binding identifiers in the
     authoring-strip docstrings -> `TRAINING`.
   - trace.py: CompileError example `return publish_to_comet` -> `return train_model`.

Widen `TestNoInternalReferences` to enforce the scrub and prevent regression:
alongside the existing tangle_deploy/tangle-deploy substring check it now flags
`areas-ml`/`areas/ml` substrings and the standalone words oasis/upi/comet
(word-boundary, case-insensitive — so it won't false-positive on "deduping").

745 passed; `uv lock --check` and `git diff --check` clean; no new ruff findings
in touched files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er it

Second independent review of TangleML#28 found a blocking correctness bug in the
propagate_config broadcast subsystem, plus its total lack of test coverage.
Both fixed here, with two doc nits.

1. BLOCKING — broadcast hard-failed through a config-less intermediate.
   In `_process_subpipeline_children`, an active broadcast (non-empty
   `ctx.broadcast_stack`) unconditionally called `_read_raw_cfg(child_cfg_path)`
   for every child edge, and that helper raises `CompileError: config file not
   found` on a missing file. A pure composing subpipeline (no `cfg` param, no
   `config=`) has no config.yaml on disk, so the canonical shape
   `root(propagate_config=True) -> config-less parent -> config-taking child`
   failed with a spurious "config file not found" for a file the author never
   wrote. Fix: in the broadcast path, treat a MISSING child config as `{}` (the
   child declares nothing to overlay; broadcast keys flow PAST it to
   descendants). A config-DECLARING child whose file is genuinely missing is
   still caught later, with guidance, in `_load_cfg_and_raw` (verified: the
   `cfg`-param error message is preserved).

2. Coverage: the entire broadcast subsystem had zero tests
   (`grep propagate_config tests/` was empty — which is why #1 shipped). Added
   `tests/fixtures/python_pipeline/broadcast_zone/` (a 3-level tree isolated in
   its own dir so the config-less intermediate genuinely has no config.yaml —
   the shared fixtures dir has one that would mask the bug) + a regression test
   asserting the compile succeeds and the root's broadcast value reaches the
   grandchild leaf, winning over the grandchild's own config (three distinct
   values make the source unambiguous).

3. Nit: the "content-hash" wording in a subpipeline test comment is really a
   compile-key hash (source path + qualname + name + config path + overrides
   fingerprint), not a hash of emitted content — corrected.

4. Nit: genericized a docstring example name (`options_standardization` /
   "Options Standardization") that echoed an internal pipeline -> `train_model`
   / "Train Model".

746 passed (+1); ruff clean on touched files; `uv lock --check` and
`git diff --check` clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…om docstring, extract cfg-load helper

Reviewer follow-ups on the PR:

* pipeline_compiler.py — replace ~36 internal design-doc citations
  (PROPAGATE_CONFIG_DESIGN §N, Decision A–M, NM1/NM2, Fix B, Finding 2,
  P2/D4/Phase N) with the rule stated inline, so every comment stands on
  its own in the public repo with no dangling pointer to a doc that
  isn't published.
* schema_validation.py — delete the module-docstring sentence promising a
  `PipelineHydrator.validate_dehydrated_file` convenience that does not
  exist.
* pipeline_compiler.py — extract the step-1 cfg resolve/load block (plus
  its warning/override-error branches) out of `_compile_pipeline_fn` into
  `_resolve_cfg_for_pipeline`, leaving the compile function a thin
  orchestrator whose remaining steps already delegate to named passes.

Comment/docstring-only plus pure code-motion; behavior unchanged
(746 tests pass, golden byte-identical fixtures included).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… child

Under propagate_config, a broadcast key a child does not declare flows past it
as ambient pass-through context, which the parent folds into the child's
cycle-check/registry key. The child then recomputed its OWN key (ambient-less)
to push onto active_stack, so `child_key in active_stack` never matched: a real
cycle escaped precise detection and degraded to the max-depth-32 guard with a
misleading "Reduce nesting depth" message.

Thread the parent-computed key into _compile_pipeline_fn as `precomputed_key`
so the stacked, registry, and cycle-check keys are identical; the root still
builds its own (ambient-less) key. Adds the suite's first cycle-detection
tests: a direct self-reference and the propagate_config pass-through regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Silin144
Silin144 force-pushed the feat/pipeline-compile branch from d1c63c9 to 53acb14 Compare July 15, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants