Skip to content

Fix/dry run#932

Draft
thomashebrard wants to merge 4 commits into
devfrom
fix/dry-run
Draft

Fix/dry run#932
thomashebrard wants to merge 4 commits into
devfrom
fix/dry-run

Conversation

@thomashebrard
Copy link
Copy Markdown
Member

@thomashebrard thomashebrard commented May 22, 2026

Summary by cubic

Routes DRY mode through the standard runner/orchestrator and deletes legacy dry-run paths. Restores dry-on-load validation in validate_bundle, updates CLI, builder, and graph rendering to the new API, and adds a keep_library_loaded option. Developer notes documenting the refactor were added under .notes/dry-run-refactor/*.

  • Refactors

    • Remove pipelex/pipe_run/{dry_run.py,dry_run_pipeline.py,dry_run_with_graph.py,dry_pipe_router.py}; DRY now uses PipelexRunner + PipeRunMode.DRY.
    • Add dry_run_loaded_pipes_or_raise in validate_bundle; migrate CLI and builder to use it; restore dry-on-load with a new integration test.
    • Update graph_rendering to dry-run bundles via PipelexRunner and emit GraphSpec.
    • Wire PipelexRunner to PipeRun/PipeRouter for consistent LIVE/DRY execution; DRY runs locally for graphing/validation.
    • Extend WorkingMemoryFactory with convert_input_specs_to_typed; replace old convert_to_working_memory_format calls.
    • Add keep_library_loaded to pipeline_run_setup/runner to control library teardown.
    • Add refactor notes: taxonomy, load profile, parity gate, implementation map, and validation checklist in .notes/dry-run-refactor/*.
  • Migration

    • Replace dry_run_pipe, dry_run_pipes, dry_run_pipeline, dry_run_with_graph with dry_run_loaded_pipes_or_raise or run via PipelexRunner + PipeRunMode.DRY.
    • Remove any DryPipeRouter and DryRunStatus references.
    • Drop allowed_to_fail_pipes from pipelex.toml and DryRunConfig.
    • Update imports/tests and use WorkingMemoryFactory.convert_input_specs_to_typed.

Written for commit 94938d5. Summary will update on new commits. Review in cubic

thomashebrard and others added 3 commits May 22, 2026 08:38
D-plan-for-codex.md is the decided 8-point plan that drove the refactor
(deletion of dry_run.py / dry_run_pipeline.py / dry_run_with_graph.py /
dry_pipe_router.py, PipelexRunner DRY-routes-local, validator migration,
validate_bundle dry-on-load restoration). A/B/C cover the upstream
taxonomy, load-profile audit, and synthesis the plan was built on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thomashebrard thomashebrard marked this pull request as draft May 22, 2026 06:59
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR routes dry-run validation through the main runner path. The main changes are:

  • Deletes the standalone dry-run helpers and dry router.
  • Forces PipelexRunner dry runs onto a local PipeRun.
  • Restores bundle dry-run validation via validate_bundle helpers.
  • Moves mock-input type conversion into WorkingMemoryFactory.
  • Updates graph generation and validation callers to use the new dry-run flow.

Confidence Score: 2/5

This should not merge until the dry-run routing and validation regressions are fixed.

  • Dry runs bypass configured observer hooks.
  • Validation can leave the current library context set to a temporary library.
  • Single-pipe bundle validation now fails on unrelated broken pipes.
  • Existing configs using the documented dry-run allowlist can fail at startup.

Focus on pipelex/pipeline/runner.py, pipelex/pipeline/validate_bundle.py, pipelex/builder/operations/validate_ops.py, and pipelex/system/configuration/configs.py.

Important Files Changed

Filename Overview
pipelex/pipeline/runner.py Adds dry-run pipe-run resolution and keep-library cleanup behavior.
pipelex/pipeline/validate_bundle.py Adds runner-based dry-run helpers and restores dry-on-load validation.
pipelex/builder/operations/validate_ops.py Migrates validation operations to the new dry-run helper path.
pipelex/system/configuration/configs.py Removes the dry-run allowlist field from strict configuration.

Comments Outside Diff (1)

  1. pipelex/system/configuration/configs.py, line 63-69 (link)

    P1 Config key removed

    Removing allowed_to_fail_pipes makes existing configs with pipelex.dry_run_config.allowed_to_fail_pipes invalid because ConfigModel forbids extra fields. This key is still documented and was present in the default TOML before this PR, so projects that already use the allowlist will fail during config loading before validation can run. Keep the field as a deprecated no-op or migrate the config path before removing it.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: pipelex/system/configuration/configs.py
    Line: 63-69
    
    Comment:
    **Config key removed**
    
    Removing `allowed_to_fail_pipes` makes existing configs with `pipelex.dry_run_config.allowed_to_fail_pipes` invalid because `ConfigModel` forbids extra fields. This key is still documented and was present in the default TOML before this PR, so projects that already use the allowlist will fail during config loading before validation can run. Keep the field as a deprecated no-op or migrate the config path before removing it.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
pipelex/pipeline/runner.py:89-90
**Dry runs skip observers**

This branch builds a fresh local router with `ObserverNoOp`, so every dry run executed through `PipelexRunner` skips the observer configured during `Pipelex.setup()`. The normal direct path wires the router with the `MultiObserver`, which includes telemetry and any custom observer hooks. A hosted process that records validation, audit, or metrics events through observers will see live runs emit those hooks, while dry runs silently bypass them.

### Issue 2 of 4
pipelex/pipeline/runner.py:228-231
**Library context leaks**

When `keep_library_loaded=True`, this cleanup skips both tearing down the library and clearing the current-library context. `pipeline_run_setup()` has already called `set_current_library(library_id)`, so helpers such as `validate_bundle()` leave the async context pointing at the temporary validation library after they return. Later code that calls `get_current_library()` can then read the just-validated bundle's library instead of the caller's previous library, causing cross-call contamination.

### Issue 3 of 4
pipelex/builder/operations/validate_ops.py:117-119
**Single-pipe validation widens**

`validate_pipe_in_bundle()` now calls `validate_bundle()`, and this PR changed `validate_bundle()` to dry-run every pipe in the bundle. That means a request to validate `good_pipe` inside a bundle fails if any unrelated pipe in the same bundle is broken, even though the command contract is to validate only the selected pipe. This breaks partial validation workflows for bundles that contain experimental or currently broken pipes.

### Issue 4 of 4
pipelex/system/configuration/configs.py:63-69
**Config key removed**

Removing `allowed_to_fail_pipes` makes existing configs with `pipelex.dry_run_config.allowed_to_fail_pipes` invalid because `ConfigModel` forbids extra fields. This key is still documented and was present in the default TOML before this PR, so projects that already use the allowlist will fail during config loading before validation can run. Keep the field as a deprecated no-op or migrate the config path before removing it.

Reviews (1): Last reviewed commit: "docs: add dry-run refactor plan and supp..." | Re-trigger Greptile

Comment on lines +89 to +90
if self.pipe_run_mode is PipeRunMode.DRY:
return PipeRun(pipe_router=PipeRouter(observer=ObserverNoOp()))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Dry runs skip observers

This branch builds a fresh local router with ObserverNoOp, so every dry run executed through PipelexRunner skips the observer configured during Pipelex.setup(). The normal direct path wires the router with the MultiObserver, which includes telemetry and any custom observer hooks. A hosted process that records validation, audit, or metrics events through observers will see live runs emit those hooks, while dry runs silently bypass them.

Prompt To Fix With AI
This is a comment left during a code review.
Path: pipelex/pipeline/runner.py
Line: 89-90

Comment:
**Dry runs skip observers**

This branch builds a fresh local router with `ObserverNoOp`, so every dry run executed through `PipelexRunner` skips the observer configured during `Pipelex.setup()`. The normal direct path wires the router with the `MultiObserver`, which includes telemetry and any custom observer hooks. A hosted process that records validation, audit, or metrics events through observers will see live runs emit those hooks, while dry runs silently bypass them.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +228 to 231
# Only teardown library if it was successfully created and the runner owns it
if library_id_resolved is not None and not self.keep_library_loaded:
get_library_manager().teardown(library_id=library_id_resolved)
teardown_current_library()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Library context leaks

When keep_library_loaded=True, this cleanup skips both tearing down the library and clearing the current-library context. pipeline_run_setup() has already called set_current_library(library_id), so helpers such as validate_bundle() leave the async context pointing at the temporary validation library after they return. Later code that calls get_current_library() can then read the just-validated bundle's library instead of the caller's previous library, causing cross-call contamination.

Prompt To Fix With AI
This is a comment left during a code review.
Path: pipelex/pipeline/runner.py
Line: 228-231

Comment:
**Library context leaks**

When `keep_library_loaded=True`, this cleanup skips both tearing down the library and clearing the current-library context. `pipeline_run_setup()` has already called `set_current_library(library_id)`, so helpers such as `validate_bundle()` leave the async context pointing at the temporary validation library after they return. Later code that calls `get_current_library()` can then read the just-validated bundle's library instead of the caller's previous library, causing cross-call contamination.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +117 to +119
result = await validate_bundle(mthds_file_path=bundle_path, library_dirs=library_dirs)
if not any(pipe_code in {the_pipe.pipe_ref, the_pipe.code} for the_pipe in result.pipes):
get_required_pipe(pipe_code=pipe_code)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Single-pipe validation widens

validate_pipe_in_bundle() now calls validate_bundle(), and this PR changed validate_bundle() to dry-run every pipe in the bundle. That means a request to validate good_pipe inside a bundle fails if any unrelated pipe in the same bundle is broken, even though the command contract is to validate only the selected pipe. This breaks partial validation workflows for bundles that contain experimental or currently broken pipes.

Prompt To Fix With AI
This is a comment left during a code review.
Path: pipelex/builder/operations/validate_ops.py
Line: 117-119

Comment:
**Single-pipe validation widens**

`validate_pipe_in_bundle()` now calls `validate_bundle()`, and this PR changed `validate_bundle()` to dry-run every pipe in the bundle. That means a request to validate `good_pipe` inside a bundle fails if any unrelated pipe in the same bundle is broken, even though the command contract is to validate only the selected pipe. This breaks partial validation workflows for bundles that contain experimental or currently broken pipes.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 24 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".notes/dry-run-refactor/A-taxonomy.md">

<violation number="1" location=".notes/dry-run-refactor/A-taxonomy.md:56">
P2: The document claims `DryPipeRouter` exists at `pipelex/pipe_run/dry_pipe_router.py:9` and describes it as dead code, but the file doesn't exist in the codebase. Update the document to reflect the current state: either remove the `DryPipeRouter` references or update them to note that the file/class has already been removed.</violation>
</file>

<file name="pipelex/cli/agent_cli/commands/validate/_validate_core.py">

<violation number="1" location="pipelex/cli/agent_cli/commands/validate/_validate_core.py:102">
P2: When the pipe is missing from the bundle, the fallback `get_required_pipe` can still resolve a library pipe and incorrectly return success. Raise immediately on failed membership instead of doing a global lookup.</violation>
</file>

<file name="pipelex/builder/operations/validate_ops.py">

<violation number="1" location="pipelex/builder/operations/validate_ops.py:117">
P1: This call widens single-pipe validation into full-bundle dry-run, so validating one pipe can fail due to unrelated broken pipes in the same bundle.</violation>

<violation number="2" location="pipelex/builder/operations/validate_ops.py:119">
P2: `validate_pipe_in_bundle` can incorrectly succeed for a pipe that is not in the target bundle because the fallback lookup searches the whole loaded library.</violation>
</file>

<file name="pipelex/pipeline/runner.py">

<violation number="1" location="pipelex/pipeline/runner.py:172">
P1: PipeRun selection uses constructor mode instead of the resolved per-job run mode, so env/default-resolved DRY runs can still be dispatched through `get_pipe_run()`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

keep_library_loaded=self.keep_library_loaded,
)
effective_pipe_run = self._pipe_run or get_pipe_run()
effective_pipe_run = self._resolve_pipe_run()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: PipeRun selection uses constructor mode instead of the resolved per-job run mode, so env/default-resolved DRY runs can still be dispatched through get_pipe_run().

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pipelex/pipeline/runner.py, line 172:

<comment>PipeRun selection uses constructor mode instead of the resolved per-job run mode, so env/default-resolved DRY runs can still be dispatched through `get_pipe_run()`.</comment>

<file context>
@@ -148,8 +167,9 @@ async def execute_pipeline(
+                keep_library_loaded=self.keep_library_loaded,
             )
-            effective_pipe_run = self._pipe_run or get_pipe_run()
+            effective_pipe_run = self._resolve_pipe_run()
             pipe_output = await effective_pipe_run.run(pipe_job, delivery_assignment=delivery_assignment)
         except PipeRouterError as exc:
</file context>
Suggested change
effective_pipe_run = self._resolve_pipe_run()
effective_pipe_run = self._pipe_run
if effective_pipe_run is None:
if pipe_job.pipe_run_params.run_mode is PipeRunMode.DRY:
effective_pipe_run = PipeRun(pipe_router=PipeRouter(observer=ObserverNoOp()))
else:
effective_pipe_run = get_pipe_run()

# Now get the specific pipe and dry-run only that one
the_pipe = get_required_pipe(pipe_code=pipe_code)
dry_run_output = await dry_run_pipe(the_pipe, raise_on_failure=True)
result = await validate_bundle(mthds_file_path=bundle_path, library_dirs=library_dirs)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This call widens single-pipe validation into full-bundle dry-run, so validating one pipe can fail due to unrelated broken pipes in the same bundle.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pipelex/builder/operations/validate_ops.py, line 117:

<comment>This call widens single-pipe validation into full-bundle dry-run, so validating one pipe can fail due to unrelated broken pipes in the same bundle.</comment>

<file context>
@@ -163,30 +111,16 @@ async def validate_pipe_in_bundle(
-    # Now get the specific pipe and dry-run only that one
-    the_pipe = get_required_pipe(pipe_code=pipe_code)
-    dry_run_output = await dry_run_pipe(the_pipe, raise_on_failure=True)
+    result = await validate_bundle(mthds_file_path=bundle_path, library_dirs=library_dirs)
+    if not any(pipe_code in {the_pipe.pipe_ref, the_pipe.code} for the_pipe in result.pipes):
+        get_required_pipe(pipe_code=pipe_code)
</file context>


---

### DryPipeRouter (`pipelex/pipe_run/dry_pipe_router.py:9`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The document claims DryPipeRouter exists at pipelex/pipe_run/dry_pipe_router.py:9 and describes it as dead code, but the file doesn't exist in the codebase. Update the document to reflect the current state: either remove the DryPipeRouter references or update them to note that the file/class has already been removed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .notes/dry-run-refactor/A-taxonomy.md, line 56:

<comment>The document claims `DryPipeRouter` exists at `pipelex/pipe_run/dry_pipe_router.py:9` and describes it as dead code, but the file doesn't exist in the codebase. Update the document to reflect the current state: either remove the `DryPipeRouter` references or update them to note that the file/class has already been removed.</comment>

<file context>
@@ -0,0 +1,350 @@
+
+---
+
+### DryPipeRouter (`pipelex/pipe_run/dry_pipe_router.py:9`)
+**What it does:** Dead code; calls `pipe.dry_run_pipe()` at line 18. No production usage.
+
</file context>

result = await validate_bundle(mthds_file_path=bundle_path, library_dirs=library_dirs)
if not any(pipe_code in {the_pipe.pipe_ref, the_pipe.code} for the_pipe in result.pipes):
# Use the runtime lookup to surface the same error path as before
get_required_pipe(pipe_code=pipe_code)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When the pipe is missing from the bundle, the fallback get_required_pipe can still resolve a library pipe and incorrectly return success. Raise immediately on failed membership instead of doing a global lookup.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pipelex/cli/agent_cli/commands/validate/_validate_core.py, line 102:

<comment>When the pipe is missing from the bundle, the fallback `get_required_pipe` can still resolve a library pipe and incorrectly return success. Raise immediately on failed membership instead of doing a global lookup.</comment>

<file context>
@@ -127,27 +93,13 @@ async def validate_pipe_in_bundle_core(
+    result = await validate_bundle(mthds_file_path=bundle_path, library_dirs=library_dirs)
+    if not any(pipe_code in {the_pipe.pipe_ref, the_pipe.code} for the_pipe in result.pipes):
+        # Use the runtime lookup to surface the same error path as before
+        get_required_pipe(pipe_code=pipe_code)
 
     return {
</file context>

dry_run_output = await dry_run_pipe(the_pipe, raise_on_failure=True)
result = await validate_bundle(mthds_file_path=bundle_path, library_dirs=library_dirs)
if not any(pipe_code in {the_pipe.pipe_ref, the_pipe.code} for the_pipe in result.pipes):
get_required_pipe(pipe_code=pipe_code)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: validate_pipe_in_bundle can incorrectly succeed for a pipe that is not in the target bundle because the fallback lookup searches the whole loaded library.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pipelex/builder/operations/validate_ops.py, line 119:

<comment>`validate_pipe_in_bundle` can incorrectly succeed for a pipe that is not in the target bundle because the fallback lookup searches the whole loaded library.</comment>

<file context>
@@ -163,30 +111,16 @@ async def validate_pipe_in_bundle(
-    dry_run_output = await dry_run_pipe(the_pipe, raise_on_failure=True)
+    result = await validate_bundle(mthds_file_path=bundle_path, library_dirs=library_dirs)
+    if not any(pipe_code in {the_pipe.pipe_ref, the_pipe.code} for the_pipe in result.pipes):
+        get_required_pipe(pipe_code=pipe_code)
 
     return {
</file context>
Suggested change
get_required_pipe(pipe_code=pipe_code)
raise ValueError(f"Pipe '{pipe_code}' is not part of bundle '{bundle_path}'.")

…ion + validation)

Completes the .notes/dry-run-refactor/ bundle so a reader landing on the PR
has end-to-end context without needing the original session.

- README.md         index, read order, TL;DR
- E-parity-gate.md  API/worker library preload parity verification that
                    gated "DRY -> always local" routing
- F-implementation.md  file-by-file map of the diff, the keep_library_loaded
                    flag added beyond the plan, behavioral changes worth
                    flagging in review (DryRunStatus.SKIPPED gone,
                    ValidateBundleResult schema change, etc.)
- G-validation.md   commands run, results, known flakes, pre-merge checklist

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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