Skip to content

Add pipelines decompile: YAML -> Python pipeline source (v0.1.31) - #76

Merged
Volv-G merged 2 commits into
masterfrom
piforge/tangle-pipeline-crud/yaml-to-python-decompiler-on-mer-fb70506
Oct 3, 2026
Merged

Volv-G merged 2 commits into
masterfrom
piforge/tangle-pipeline-crud/yaml-to-python-decompiler-on-mer-fb70506

Conversation

@Volv-G

@Volv-G Volv-G commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

(AI-assisted)

Part 2 of 2 in a stack. Part 1, #75 (the extract_subgraphs seam, v0.1.30), is merged as 1faa8a4; this PR is restacked onto it and targets master.

What

A new tangle sdk pipelines decompile PIPELINE.yaml -o gen.py command, plus decompile_pipeline_file() / decompile_pipeline(). It turns hydrated or dehydrated pipeline YAML into deterministic python_pipeline source. Version 0.1.31.

  • Every graph becomes Python. The root goes in gen.py, and every nested graph becomes a @pipeline in gen_subgraphs.py, called through subpipeline(fn). Names are allocated one-to-one and collision-free.
  • Leaf components:
    • A component the library verified becomes ref(digest=…) by default.
    • An unpublished component becomes ref(url="resolve://./gen.leaves.yaml#…"), with its only local copy in gen.leaves/.
    • --local-fallbacks keeps the portable form instead: the digest first, with a local copy behind it.
    • The name is deliberately not gen.components.yaml, which compile writes for @task.
  • Python components become @task source under gen_tasks/ (opt-out: --no-python-tasks). This covers:
    • plain generator-made functions, wrapped once;
    • literal @task(...) sources, re-exported verbatim;
    • image_id, kept as written, so it is resolved at compile time;
    • unwrap call sites, regrouped back into dicts.
  • Layout sugar:
    • Task positions become .with_position(x, y[, width, height]).
    • Graph inputs and outputs use position=(x, y).
    • Both apply only when upstream rendering reproduces the stored string exactly. Anything else stays verbatim.

Evidence: exactness and safety

  • Two gates must pass before anything is written:
    • (a) the pinned document resolves back to the same specs, both online and with the library forced unreachable;
    • (b) each generated @pipeline recompiles to its graph's semantic digest.
  • Python tasks and digest refs are substituted afterwards. A static AST check proves the final code differs from the verified code only at those call sites.
  • Decompile is fully static. It never imports, runs, compiles, renders or hydrates recovered or input code.
    • Only digest, name, http(s) URL and plain-file resolution is allowed, pinned to the built-in resolvers and readers.
    • Refused with "hydrate first" guidance: local_from_python, docker/container and downstream resolver kinds, template_file configs, and downstream URI readers.
  • Transactional: output is staged and published by renames only, with rollback across all five products.
  • Privacy: refusal messages never echo document values.

How

  • New file pipeline_decompiler.py, with ast-only code generation and a node allowlist.
  • CLI wiring in pipelines_cli.py.
  • pipeline_hydrator.py: an explicit logger is forwarded only when one is passed.
  • schema_validation.py: location= on validation errors.
  • README section.

Failure modes

  • Unsupported constructs are refused with stable codes, for example unsupported-output-name, unsupported-executable-component-source and verification-digest-mismatch. Nothing is half-written.
  • Default output needs the component library to hydrate leaves that are referenced by digest. Use --local-fallbacks for offline-resilient output.
  • Recovered Python components: their rebuilt specs (types, the generator-version program, provenance) are produced later by Tangle and are not proven here. This is disclosed in the output.

Testing

  • 3.10 and 3.12 full non-packaging suites: 2349 passed each.
  • Packaging: 10/10. Locally this needed the uv index config bypassed.
  • uv build: produces the 0.1.31 sdist and wheel.
  • ruff: clean on the touched files. The repo-wide count is 36, the same as master.
  • git diff --check: clean.
  • Test-only follow-up commit 2f03208: tests that pass no client get an unreachable library instead of a real API client, which retried for about a minute per lookup in CI. With the network blocked, the decompiler and CLI tests run in 30s.
  • Corpus of 116 real pipeline docs: 107 decompile and verify. The other 9 are 8 unsupported-document and 1 unsupported-edge-fields.
  • Iterative independent local review: the final verdict is no blocking findings, with zero open findings on review page v18 (https://piforge-preview.quick.shopify.io/?s=tangle-pipeline-crud&p=yaml-python-decompiler-pi136-review). All reviewed functional files are byte-identical after restacking onto Add PipelineDehydrator(extract_subgraphs=False) seam (v0.1.30) #75 / master.

Review focus

  • The static-resolution boundary (_StaticResolution, _static_hydrator).
  • Gate structure and the post-verification substitutions (_assert_only_task_substitutions).
  • Python-task candidate rules (_python_task, _task_decorator, _interface_matches).

Volv-G added 2 commits October 2, 2026 19:11
A test that passes no client fell back to a real component-library client,
which retries for about a minute per lookup where the network is blocked (CI)
and silently answers where it is not. Client creation now yields an
unreachable library in those tests; explicit clients are unaffected. Test-only.
@Volv-G
Volv-G force-pushed the piforge/tangle-pipeline-crud/yaml-to-python-decompiler-on-mer-fb70506 branch from 481be8b to 2f03208 Compare October 3, 2026 02:45
@Volv-G
Volv-G changed the base branch from piforge/tangle-pipeline-crud/dehydrator-extract-subgraphs-opt-b7f418e to master October 3, 2026 02:45
@Volv-G
Volv-G merged commit 37910cb into master Oct 3, 2026
9 checks passed
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.

1 participant