feature/Python-builder-gaps · L-260913-4091b9 — python gate says the SDK has no check - #22
Merged
Merged
Conversation
pipelex-sdk 0.10.0 ships run_codegen_check, the pure-hashing mirror of `pipelex codegen check`, so a hosted-API consumer can prove its generated tree current without the pipelex runtime. pipelex-integrate still told every Python project that no such check existed and installed nothing. The skill now ships references/codegen_check.py, the Python twin of codegen-check.mjs: it runs the SDK's check over each generated directory, compares the sidecar's source hashes against the committed .mthds files, and exits 0 current, 1 drift or stale source, 2 no verdict, failing closed on the same malformed sidecars as the TypeScript script (a non-object sidecar, a non-object `sources`, a byte-order mark, a non-standard JSON constant). A missing or too-old pipelex-sdk is a no-verdict exit rather than a crash that would read as drift. Step 10 copies it to scripts/codegen_check.py and wires it into the project's existing aggregate gate, run with the project's own environment; step 8 raises the pipelex-sdk floor to 0.10.0, refresh mode installs the gate on a project integrated before it existed, and steps 6, 11 and 12, the failure table, the Python reference, pipelex-edit's staleness notice, the docs and the unreleased changelog entries no longer say a Python consumer has no gate. Tests run the script against the real SDK (added as a dev dependency) over current, drifted, stale-source, no-lock and malformed-sidecar trees, sharing one sidecar table with the TypeScript gate's run, and pyright now type-checks the shipped script. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
The design said the Python branch would gain a one-line gate once the Python SDK carried the offline check. The SDK shipped it as a function, so the branch copies a script instead, as the TypeScript branch does; the tracker and the upstream-dependencies note now say so and point at the commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
The scaffold filled PIPELEX_API_KEY from the shell and left PIPELEX_BASE_URL at the example's production URL, so a shell exporting a dev or staging pair produced an env file pairing that plane's key with production's URL. The env-file step now ships one command whose single test on the key decides both lines: when the key is copied from the environment, a non-empty PIPELEX_BASE_URL is appended with it as a later assignment; a base URL exported without a key is not copied, and a file already carrying a key is untouched. The confirmation tests the file for the URL the way it does for the key, and the report names the plane with a test instead of an echo. Branch B, the failure table, both references, the changelog entry, docs/decisions.md and the scaffold design's amendments follow. The tests pin the command in the template, the renders and the committed targets, then execute it in every POSIX shell on the machine against both example shapes with fake credentials, reading the result through a last-assignment-wins dotenv reading and Node's util.parseEnv where present. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
codegen-check.mjs promises exit 0 current, 1 drift or stale source, 2 no verdict, yet exited 1 whenever it could not run, and every gate it is wired into read that as drift. A static import of @pipelex/sdk failed with ERR_MODULE_NOT_FOUND before main ran when the SDK was not installed, and checkTree rethrew any error other than CodegenLockError as an uncaught rejection. The source check had the same hole: a sidecar nested deeper than sorting its entries can go parsed, then crashed the run with exit 1. The SDK is now imported dynamically inside main. Not resolving the SDK itself exits 2 with a message naming the cause and saying to run the script inside the project with its dependencies installed, as the Python twin does; an SDK that loads but fails (a package it imports is missing) exits 2 with its own message; one without the three exports the check needs exits 2 naming 0.13.0, the first release carrying them. Each check over each directory runs through a settle wrapper, so an error it did not expect is no verdict for that directory, the run goes on to the rest, and the 2 > 1 > 0 precedence decides the exit code. Current, drift, stale source and the malformed-sidecar verdicts and messages are unchanged. Step 10, the failure table, the TypeScript reference, the drift gate decision and the unreleased pipelex-integrate entry say how the gate behaves when it cannot run. Tests run the copied script with no SDK, a too-old stub, a stub whose own import is missing, a stub whose check throws and a too-deep sidecar, and pin that the SDK import is dynamic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
…thout a key The developer ruled that a base URL exported in the shell is the user's declared plane and is copied whenever the skill writes the env file, whether or not the key is: the Python starter's example documents a keyless self-hosted runner that the previous rule, which moved the URL only beside a key, pointed at production. The one command keeps a single guard on the file's key, adds a test that stops a shell setting neither variable from appending anything, and writes the leading newline and the comment once before whichever lines the shell supplies. The report now says when a URL copied without a key is not production's and warns that a key from app.pipelex.com will be refused there. The failure table, both references, the changelog entry, docs/decisions.md and the design amendment follow. The executed tests expect the URL-only case to copy the URL and leave the key empty, and assert the appended lines are the comment once and the supplied lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
Step 8 added @pipelex/sdk to a TypeScript project with no minimum while raising pipelex-sdk to 0.10.0 for Python, so a project already pinned older kept its pin: the gate step 10 copies in then gave no verdict, and a by-ref call site did not compile. The floor is 0.17.0, read off pipelex-sdk-js's changelog and the source at its release tags: the gate's three exports arrived in 0.13.0, method_id runs in 0.14.0, method_ref runs in 0.16.0 (typed `never` on startAndWaitForResult before it), and prepareInputs by all three sources, which the call site's docstring points callers at, in 0.17.0, the release that also stopped it calling /v1/build/inputs and reading a text field merely named `url` from disk. The client constructor, its environment fallbacks, RunResults and the error classes the reference names are all present from 0.13.0. npm has 0.17.0 published as latest. Step 8 now says "at least @pipelex/sdk 0.17.0", raises an older pin and reports it, parallel to the Python clause. Refresh mode raises a pin below step 8's floor with a line in the report instead of leaving the dependencies alone. The gate keeps testing for exports rather than a version, but SDK_MINIMUM, the version its messages ask for, follows step 8, so a user on an old SDK upgrades once. The step 10 bullet, the failure row, the TypeScript reference (its "checked against" line included) and the unreleased changelog entry name the same number, and a test fails when any of them, the committed skills of the three targets or SDK_MINIMUM drifts from step 8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
Refresh mode installs step 10's Python drift gate on a python-pydantic project integrated before the gate existed, but left its dependencies alone, so a pipelex-sdk pinned below 0.10.0 made that gate exit 2 straight after the refresh. The refresh table now raises that pin as step 8 does, beside the @pipelex/sdk half. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
codegen_check.py turned every state of the tree it knew into an outcome, but anything else the SDK raised escaped and exited 1, which a gate reads as drift, and left every later directory unchecked: a lock whose integer exceeds the integer-string limit (tomllib's ValueError), nesting deep enough for a RecursionError in the lock or in a stamp's options, a PermissionError the SDK does not wrap. Each check now runs inside `settle`, the twin of the TypeScript script's wrapper, so such an error is no verdict for that directory, with the exception's type and message, and precedence still decides the exit code. A lock behind a directory the process cannot search is named as the permission error rather than "codegen.lock — not found", which is what recent Pythons' is_file() made it say. And a line the script prints can no longer fail it: both streams escape characters the locale's encoding lacks, so a current by-ref tree under a Latin-1 locale, or a directory argument holding an undecodable byte, no longer exits 1 on UnicodeEncodeError. The failure table's row for a check that did not reach a verdict now covers the Python gate, and step 10, references/python.md and docs/decisions.md say a check that raises is exit 2. Tests run the real script over each trigger. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
Refresh mode left the gate wiring alone except to install a missing Python script. On a project whose methods A and B predate the gate, refreshing A installed the script and registered A's directory; refreshing B then found the script present, left the wiring alone, and B was never registered. And no refresh ever replaced a gate script an earlier version of the skill had copied, so a TypeScript project kept a codegen-check.mjs that exits 1 when its SDK is missing. The refresh table now re-checks the refreshed method's gate against step 10, in a column renamed "Re-derived and re-checked": a missing script is installed and wired, one that differs byte for byte from the reference is re-copied verbatim, and the refreshed method's directory is added to the gate command when it is not among its arguments, each said in the report. python-structures has no script, so only the registration applies to its `pipelex codegen check` wiring. Other methods' trees and registrations stay in "Left alone". The two language references and the unreleased changelog entry say the same, and a test pins the rules in the template and in every target's committed skill. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
…ading The import guard caught ImportError only, so an installed SDK that raised anything else at import (a pydantic-core mismatch, a corrupt file) escaped with a traceback and exit 1, which every gate reads as drift. Any other exception now exits 2 with a no-verdict message naming it and advising a reinstall of the project's dependencies, as the TypeScript twin already does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
… linters Refresh re-copies a gate script that differs byte for byte from the reference and never runs the formatter, so a project whose format check covered scripts/ had its copy reformatted once and re-copied unformatted on every refresh, leaving that check failing. Step 5 and both language references now exclude the gate script beside the generated directory, naming the exact Ruff, Black, isort, Prettier, ESLint and Biome entries, step 10 says the script is never formatted or linted, step 11 never formats it, and refresh verifies the exclusions before it re-checks the gate. The type checker's coverage is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
The offline drift gates hashed only the files `sources.json` recorded, while the call site loads every `.mthds` file under its bundle directory, so a file added there left both gates green over a bundle the types were not generated from. The sidecar now records that directory as `bundle_dir`, both gates list it the way their language's call site does and report additions as `stale-source`, refresh mode re-reads the file set from it, and the edit, design and standalone organize staleness notices cover additions. Stacked on `feature/Python-builder-gaps`, which carries the Python gate this changes. Closes L-260913-d509aa 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01PkKCLxNQGjDLNAiW4kdVpx <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes the offline drift gates so a `.mthds` file added to an integrated bundle's directory is no longer missed: the gates previously hashed only the files `sources.json` recorded, while the call site loads every `.mthds` file under the bundle directory, leaving both gates green over a bundle the types were not generated from. - The `sources.json` sidecar now records the bundle directory as `bundle_dir`. - Both the JS and Python gates compare recorded sources against every `.mthds` file under `bundle_dir`; a file added there is reported as `stale-source`, and a sidecar recording sources without `bundle_dir` fails closed. - Refresh mode re-reads the file set from `bundle_dir`, and the edit, design, and standalone organize staleness notices cover additions. - The TypeScript call-site template names the SDK's Node floor instead of suggesting a below-floor walk. Closes L-260913-d509aa. <sup>Written for commit b55c258. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/Pipelex/pipelex-plugins/pull/23?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the gaps the codegen sprint's acceptance walkthrough found in the builder path.
pipelex-integratenow installs an offline drift gate forpython-pydanticconsumers overpipelex-sdk'srun_codegen_check, names the@pipelex/sdk0.17.0 floor for TypeScript, keeps both gates current and registered on refresh, and gives no verdict rather than drift when either gate cannot run.pipelex-scaffoldnow copiesPIPELEX_BASE_URLfrom the shell into the env file, so a dev, staging or self-hosted plane is not paired with production's URL.Closes L-260913-4091b9
Closes L-260913-15af9f
Closes L-260913-db5269
Closes L-260913-3faa9e
🤖 Generated with Claude Code
https://claude.ai/code/session_01Vab12nAv2PgyWyTNn5N2Go
Summary by cubic
Closes the gaps the codegen sprint's acceptance walkthrough found in the builder path.
pipelex-integratenow installs an offline drift gate forpython-pydanticconsumers, both gates return no verdict instead of drift when they cannot run, andpipelex-scaffoldcopiesPIPELEX_BASE_URLfrom the shell so a dev, staging, or self-hosted plane is never paired with production's URL.pipelex-integratecodegen_check.py, the Python twin ofcodegen-check.mjs, runningpipelex-sdk'srun_codegen_checkand the sidecar's source hashes.bundle_dirin the sidecar, so a.mthdsfile added there is caught asstale-source, and refresh re-reads the file set from it.pipelex-sdk0.10.0 and@pipelex/sdk0.17.0.pipelex-scaffoldPIPELEX_BASE_URLinto the env file even without a key; a file already carrying a key is left untouched.Closes L-260913-4091b9, L-260913-15af9f, L-260913-db5269, L-260913-3faa9e, and L-260913-d509aa.
Written for commit e099d2b. Summary will update on new commits.