Release v0.10.0 - #30
Merged
Merged
Conversation
…scriptor
`prepare_inputs` took the method closure as inline `files` only, and read the target
pipe's signature from the explicit inputs template. Both halves change.
It now names the method three ways — inline `files`, a `method_ref` address resolved
by the runner, or a stored `method_id` resolved by the platform — exactly one per
call, all server-resolved with nothing expanded client-side. Empty is absent, and
none or several raises `InputPreparationError` before any request leaves the process.
The signature now comes from one `POST /v1/validate` asking for `views:
["input_form"]`, and the walk is discriminated on each descriptor node's declared
kind instead of on the shape of a value. The template marked a file position by
rendering a `{"url": …}` dict, which is a side effect of a field being *named* `url`
rather than of its concept — so an optional nested file field was never enumerated
and its local path travelled to the runner as a literal string, and a text field
merely named `url` was read from disk and uploaded. Both are gone. A `Dynamic` input
is `kind: "unknown"` and is no longer entered, which is the one deliberate behaviour
flip: such a caller uploads with `upload_file` first.
`build_inputs` and its models are deleted — the wrapper existed only to be the
signature source, and nothing calls `/v1/build/*` from this SDK now. The shared crate
envelope it also held (`MthdsFileItem`, `CrateRequestBase`, `CrateInvalidReport`)
moves to `crate_models.py`, beside the routes that still use it.
`prepare_inputs` also learns the explicit `{concept, content}` input envelope, a
pre-existing parity gap: the JS SDK has accepted it since an earlier release, so the
two would not have been identical after this fix.
Mirrors `pipelex-sdk-js` 0.17.0 (PR #42, bea4632); design of record is that repo's
wip/prepare-inputs-selectors/design.md. Verified against api-dev.pipelex.com
(pipelex-hosted 0.11.1): inline files defaulting via main_pipe, a method_ref with an
explicit pipe_ref, the manifest-only main_pipe refusal, the envelope round-trip, the
bare-pipe_ref refusal, and one real bytes upload rewritten to pipelex-storage://.
Closes L-260829-8a25d5
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVMDoPnhuLyufT1ZoMmDTH
Brings in v0.9.0: the `output_form` structured view on the validate report and the `mthds` pin moving 0.11.1 -> 0.13.0. One conflict, in `CHANGELOG.md`, where both sides opened a section directly under the title: this branch's `## [Unreleased]` and dev's released `## [v0.9.0]`. They describe disjoint work, so both are kept in release order. The `mthds` bump needs no adaptation here. Its breaking change makes `json_schema` required on the closed `PipeOutputContract`, and dev already fixed the one fixture in this repo that carried an output contract. The branch's own new code walks `mthds.protocol.input_form`, which is byte-identical across the two versions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DzVXcNRcVz8DUvAzmEuxPD
The v0.9.0 release moved the model and the pin but updated no prose, so merging it made three statements in `docs/architecture.md` false: - the `views` list was described as having one token, where `VALIDATION_VIEW_OUTPUT_FORM` now sits beside it; - the report was said to add "three typed fields", enumerated — now also `output_form` and this branch's `default_pipe_ref` (the count goes, per the workspace rule against hardcoding them); - the pipe I/O contracts claimed an output carries "no schema … the payload a run produces is the run's own result". `mthds` 0.13.0 makes `json_schema` required on `PipeOutputContract`, reversing exactly that reasoning: an output's schema is the concept's content model, declared and knowable before any run. `PipelexValidationReport`'s own docstring carried the same clause and is corrected with it. `_body_with_contracts` in the validation-contract tests was weakened by the same bump. Its output block stated no `json_schema`, so under 0.13.0 every body it built failed to parse — and its two `test_artifact_drift_fails_the_parse` cases were passing on that, not on the input drift each one names. Stating the schema restores what they test; verified by parsing a body from the helper with a clean input contract, which now succeeds. Two additions rather than corrections: the removed `build_inputs` gets a real migration target, since `mthds` 0.12.0 shipped `mthds.protocol.inputs_template` and its own `InputsTemplateFormat` — the template is relocated to the standard's package and projected client-side, not lost — and `output_form` gets its own bullet in the typed-by-import section. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DzVXcNRcVz8DUvAzmEuxPD
…t as absent `_non_empty_string` coerced anything that was not a string to `None`, and it served two callers that want opposite answers for that case. Reading the opaque `bundle_blueprint` — whose schema is the runtime's, not ours — a non-string really is an absent value to fall through on. Reading a CALLER's selector it is a mistake, and calling it absent made the exactly-one check unsound: `method_ref=123` beside a real `files` passed the check and prepared against a method the caller never named, `method_id=123` alone reported that no selector was given at all, and a non-string `pipe_ref` was quietly absorbed by the pipe defaulting. Split the two rules rather than raising inside the shared helper, which would have made the defensive blueprint reads throw on a shape they exist to tolerate. `_caller_selector` refuses a non-string with an `InputPreparationError` naming the argument and the type it got; `_non_empty_string` keeps the lenient contract for payload reads. `pipe_ref` is normalized beside the method selectors now, so both refusals land on the same pre-request boundary. Also corrects `prepare_inputs`'s `Raises:` section, which named `ApiResponseError` for a no-verdict `/v1/validate` failure. `validate` is 200-diagnostic and stays on the inherited `httpx.HTTPStatusError` regime, so a caller following the docstring would have missed exactly the fetch failures and 404s it listed. Advances L-260829-8a25d5 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DzVXcNRcVz8DUvAzmEuxPD
prepare_inputs: all three selectors, signature from the input-form descriptor
PR #21 merged to `dev` as `7b1892f` and `L-260829-8a25d5` closed with it, so the campaign is over and its plan says so: `status: landed`, plus the landing record the merge is what makes knowable — the SHA, what closed, what was only advanced and why, and the one thing left for a person. Advances L-260830-f5b65e Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DzVXcNRcVz8DUvAzmEuxPD
docs: land the prepare-inputs-selectors tracker
…co-install (#25) Both packages pin `mthds` exactly, so they resolve together only when they name the same version. `pipelex` moved to `mthds==0.14.0` on its `dev` branch, which left this SDK at `0.13.0` and made the pair unresolvable from source; on PyPI the break arrives with the next `pipelex` release. Nothing in this client needed adapting. The release's substantive change is `MTHDS_STANDARD_VERSION` going from 1.0.0 to 2.0.0, which this SDK never reads — it stamps no crates and ships no manifest — and the new `is_mthds_version_satisfied` helper and the `parse_constraint` whitespace fix land in `mthds.package.manifest.schema`, which nothing here imports. `PROTOCOL_VERSION` stays at 0.6.0, so the routes and the wire contract are untouched. The inherited seam is unchanged: every protected member this client extends is still present, the four overrides still have base counterparts, and the ruff `runtime-evaluated-base-classes` dotted paths all still resolve. Claude-Session: https://claude.ai/code/session_01DuEKzDiMDWrABBibqwdFTt Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
) The skill now names the workspace release play and declares only what is this repo's own: what the push to main publishes through publish.yml and how the landing verifies it, the pyproject.toml version with the uv.lock that make li regenerates, the agent-check and agent-test gates including the version-sync test that must run again after the lock, the files the release commit carries by name, the workflows that gate a release pull request, and the particulars — the refused pre-release form, the lightweight tags, the SHA-pinned Sigstore action, the exact mthds pin, and the absence of automatic release follow-ups. Dropped the procedure the play already carries once for every repo: the git status pre-flight that offered to fold uncommitted changes or unpushed commits into the release, the release branch created in place from the current HEAD, the numbered restatement of the changelog, bump, commit and pull request steps, and the post-merge reminder that the merge is what publishes. Claude-Session: https://claude.ai/code/session_016F72qvy4QBZHe7XX24QmcT Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…e pipelex runtime (#28) Adds `write_codegen_tree`, which writes a valid `codegen()` response to disk byte for byte, so a Python project can regenerate its typed tree with nothing installed but `pipelex-sdk`. It follows `pipelex`'s own `write_stamped_projection` discipline: validate every path first, refuse an unowned file, write only what changed, prune stamped artifacts the previous lock tracked, and write the lock last. It refuses a little earlier than `pipelex` where the response arrives as two independent fields: the response's lock must parse and track exactly its artifacts, and the paths about to be pruned are checked before the first write rather than after. The lock and stamp primitives are public modules so the offline drift check can build on them, and a side-by-side run diffed the resulting trees against `pipelex`'s writer. Closes L-260907-202383 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ipelex runtime (#29) `run_codegen_check(root=…)` verifies a generated codegen tree against its `codegen.lock` by hashing alone — no engine boot, no network, no API key and no dependency on `pipelex`, which is the point: until now the only offline gate a Python project could use was the `pipelex` CLI, the whole runtime, exactly the dependency a hosted-API consumer took this SDK to avoid. It is the counterpart of `write_codegen_tree` and takes the directory that writer wrote into, and it mirrors `pipelex`'s own `run_codegen_check` down to the drift detail sentences so a consumer reads the same report from either. The stamp module gains the reader it needs, `CodegenLock` gains `hash_by_path()`, and the lock is now read in text mode so universal-newline translation folds a CRLF away as pipelex's reader does. Two divergences from the reference are deliberate, and both are named in the README and in `docs/architecture.md`. A relaxation inherited from `@pipelex/sdk`: a well-formed projection line whose axes are outside this SDK's vocabulary is accepted rather than called a hand edit, because an SDK copy of that vocabulary can lag the emitter and would otherwise redden a correctly generated tree. And a tightening of this reader's own, found by review: a Python artifact declaring a PEP 263 source encoding is refused, because that declaration chooses the codec CPython decodes the file with and so makes the stamp header's comment-prefix gate unsound — a tree can otherwise read as current to both readers while importing it executes a statement hidden in the unhashed header. Closes L-260830-4e43cd 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Release v0.10.0
Bumps version from
0.9.0to0.10.0. Promotesdev->main. The number is a minor because[Unreleased]carried breaking changes and the project is pre-1.0. The release exists now becausepipelex-starter-python#70cannot merge without it: it importswrite_codegen_treefrompipelex_sdk.codegen_writerand the request envelope frompipelex_sdk.crate_models, and neither exists in the published 0.9.0, where the envelope is stillpipelex_sdk.build_modelsand there is nocodegen_writerat all.Changelog
Added
run_codegen_check, the offline codegen drift check:pipelex_sdk.codegen_check.run_codegen_check(root=…)verifies a generated tree against itscodegen.lockby hashing alone — no engine boot, no network, no API key, and no dependency onpipelex. That last point is the reason it exists: until now the only offline gate a Python project could use was thepipelexCLI, the whole runtime, which is exactly the dependency a hosted-API consumer took this SDK to avoid, so the same integration advice left a TypeScript project with a CI drift gate and a Python one without. It is the counterpart ofwrite_codegen_treeand takes the directory that writer wrote into. The verdict rides a structuredCodegenCheckReport—lock_found,is_current, and adriftslist whose categories aremissing,modified,hand-editedandorphan, at most one per locked artifact, locked drifts first in ascending path order and then orphans — with the lock header'scrate_fingerprintandengine_versionsurfaced so a caller can ask the one question the offline check cannot, whether the tree still matches what the method resolves to. A lock that cannot be read, or a tree whose paths are not safe and canonical, raisesCodegenLockError: the absence of a verdict rather than a drift. It is a mirror ofpipelex's ownrun_codegen_checkdown to the drift sentences, so a consumer reads the same report from either, and the agreement was established by running both over scenario trees derived from realpipelex codegen typesoutput, one drift class at a time. Two divergences are deliberate and documented: a relaxation inherited from@pipelex/sdk, which accepts a well-formed projection line whose axes are outside this SDK's vocabulary rather than report a tree generated by a newer engine as entirely hand-edited; and a tightening of this reader's own, which refuses a Python artifact declaring a PEP 263 source encoding, because that declaration chooses the codec CPython decodes the file with and so makes the header's comment-prefix gate unsound — a tree can otherwise read as current while importing it executes a statement hidden in the unhashed header. Unlike the JS export, which is pure because it must stay browser-bundleable, this one owns the tree walk and the decoding, so none of that SDK's caller obligations carry over; an unreadable file or directory under the root is therefore aCodegenLockErrorrather than the reference's barePermissionError, leaving a CI caller one class to catch.pipelex_sdk.codegen_stampgains the stamp reader the check needs —parse_stamped,compute_content_hash,is_stampable_artifact_pathand theParsedStampmodel, beside the ownership predicate and stampable suffixes it already carried — andCodegenLockgainshash_by_path(). Acodegen.lockis now read in text mode, so universal-newline translation folds a CRLF away before the TOML parser sees it, aspipelex's reader does; the writer still compares raw bytes, deliberately, because translation there would make one response two different trees across platforms.write_codegen_tree, the verbatim codegen tree writer:pipelex_sdk.codegen_writer.write_codegen_tree(report, output_dir=…)writes a validcodegen()response to disk byte for byte — every artifact at itspath, the lock ascodegen.lock— so a Python project regenerates a tree identical to a localpipelex codegen typesrun without installingpipelex. Like that command it validates every path before writing, raisesCodegenErrorrather than overwrite a file codegen does not own, rewrites only what changed, and prunes stamped artifacts the previous lock tracked; unlike it, it also checks the paths it is about to prune and requires the response's lock to track exactly its artifacts before the first write; the lock format and path rules it reads are public inpipelex_sdk.codegen_lockandpipelex_sdk.codegen_stamp.prepare_inputstakes the method three ways. Beside inlinefiles, it accepts amethod_refaddress (resolved by the runner) or a storedmethod_id(resolved by the hosted platform) — exactly one per call, all three server-resolved, nothing expanded client-side. Empty is absent (files=[], a blankmethod_ref/method_id) but the wrong type is not: none, several, or a non-string selector raisesInputPreparationErrorbefore any request leaves the process — reading a mistyped selector as absent would let the exclusivity check pass and prepare against the wrong method. A non-stringpipe_refis refused on the same boundary, rather than being absorbed by the pipe defaulting. A method addressed by URL that declares a file input now has an input-preparation path; previously it had none, even though the request beneath accepted the address.PipelexValidationReport.default_pipe_ref— the qualifiedpipe_refa caller gets by omitting the pipe selector, ornullwhen the closure declares none or several. Optional and read leniently: a runner that predates the field sends nothing, andprepare_inputsfalls back to the opaquebundle_blueprint.main_pipe.Changed
prepare_inputsreads its signature from the input-form descriptor, not the inputs template. It composes onePOST /v1/validatewithviews: ["input_form"]andallow_signatures=True, and walks the standard'sInputFormartifact —document/imagemark a file position,objectrecurses throughfields,listthroughitem, everything else passes through. Source-compatible for every caller passingfiles; the SDK no longer calls/v1/build/inputsat runtime. A valid report carrying no descriptor is an error naming the pipelex-api floor, never a silent degrade to "no uploads".build_inputsand its models are removed.client.build_inputs,BuildInputsRequest,BuildInputsValidReport,BuildInputsResponse,BuildInputsResponseAdapterandInputsTemplateFormatare gone — the route wrapper existed only to be the signature sourceprepare_inputsread, and nothing calls it now. This is the Python SDK's step of the workspace program retiring/v1/build/*; a caller that still needs a fill-in template projects one from the descriptor withmthds.protocol.inputs_template(render_inputs_template/project_inputs_template, in both the compact and explicit shapes, as JSON or TOML), which is also whereInputsTemplateFormatnow lives. The wrapper is not a capability lost but one relocated to the standard's own package — and projected client-side, so a method reached bymethod_reformethod_idgets a template with no server round-trip at all.pipelex_sdk.crate_models.MthdsFileItem,CrateRequestBaseandCrateInvalidReportnow live beside the routes that use them (/v1/resolve,/v1/codegen) andpipelex_sdk/build_models.pyis deleted — a module named for the build routes could not go on holding the envelope after they left. The models themselves are unchanged; update the import path.Dynamicinput is no longer uploaded. Such an input iskind: "unknown"in the descriptor — the standard's escape hatch — and the walk does not enter it. Uploading on the strength of aurlkey is the value-shape guess this change removes; a caller with a Dynamic input uploads withupload_filefirst and passes the storage URI, whichdocs/input-preparation.mdhas always prescribed.prepare_inputsaccepts the explicit{concept, content}input envelope, not only compact values, closing a parity gap with the JS SDK. An agent that fills an explicit template — the shape the hosted console and MCP hand out — can now hand it straight back; previously every file-bearing envelope position raisedInputPreparationError: Unsupported value at a file input … got dict. The envelope'scontentis interpreted identically and preserved on output, so the concept annotation rides through to the run.docs/input-preparation.mdnow describes the three call shapes, the signature call, pipe selection and its manifest-onlymain_pipegap, the envelope, and why the template was the wrong signature source;docs/architecture.mdfollows the removal. It also catches up with v0.9.0, which shippedoutput_formand themthds0.13.0 bump without touching the docs: the views list is no longer described as having one token, the report's typed fields now includeoutput_formanddefault_pipe_ref, and the pipe I/O contracts no longer claim an output carries no schema — 0.13.0 madejson_schemarequired there, reversing the reasoning the doc still quoted.mthds0.14.0 (breaking). The pin moves from 0.13.0. Nothing in this client changed with it: the release's substantive cut isMTHDS_STANDARD_VERSIONgoing from1.0.0to2.0.0, which this SDK never reads — it stamps no crates and ships no manifest — and the newis_mthds_version_satisfiedhelper and theparse_constraintwhitespace fix sit inmthds.package.manifest.schema, which nothing here imports.PROTOCOL_VERSIONstays at0.6.0, so the routes and the wire contract are untouched. The move still matters to anyone installing this package:pipelexpinsmthdsexactly too and has already moved to 0.14.0, so two exact pins on different versions made the pair unresolvable — this is what letspipelexandpipelex-sdkco-install again.Security
required: falseand the walk enters it.urlis no longer read from disk. The template marked a file position by rendering aurl-bearing dict — a side effect of the field's name, not of its concept — so a path-shaped text value was uploaded.kind: "text"ends that.Closes L-260913-4d710c
🤖 Generated with Claude Code
Summary by cubic
Releases v0.10.0, bumping
pipelex-sdkfrom 0.9.0 to 0.10.0 and promotingdevtomain. The minor bump carries breaking changes toprepare_inputs, new offline codegen tooling, and amthdspin move; it is needed sopipelex-starter-python#70can importwrite_codegen_treeand the crate envelope, neither of which exists in 0.9.0.Breaking changes
prepare_inputsnow reads the target pipe's signature from the input-form descriptor and no longer calls/v1/build/inputsat runtime.build_inputsand its models are removed, and the crate envelope moves topipelex_sdk.crate_models; update the import path.Dynamicinput is no longer uploaded — upload withupload_filefirst.mthdsmoves from0.13.0to0.14.0sopipelexandpipelex-sdkcan co-install; the wire contract is unchanged.New features
prepare_inputstakes the method three ways — inlinefiles,method_ref, or storedmethod_id— and accepts the explicit{concept, content}envelope.write_codegen_treewrites acodegen()response to disk byte for byte, so a Python project generates its typed tree without installingpipelex.run_codegen_checkverifies a generated tree against itscodegen.lockby hashing alone — no engine boot, no network, no API key.urlis no longer read from disk.PipelexValidationReportgainsdefault_pipe_ref, and the input-preparation and architecture docs are rewritten around the descriptor.Written for commit f3401ee. Summary will update on new commits.