Skip to content

prepare_inputs: all three selectors, signature from the input-form descriptor - #21

Merged
lchoquel merged 4 commits into
devfrom
feature/PrepareInputsSelectors
Sep 7, 2026
Merged

prepare_inputs: all three selectors, signature from the input-form descriptor#21
lchoquel merged 4 commits into
devfrom
feature/PrepareInputsSelectors

Conversation

@lchoquel

@lchoquel lchoquel commented Aug 30, 2026

Copy link
Copy Markdown
Member

The Python twin of pipelex-sdk-js 0.17.0 (PR #42, bea4632), and this repo's step of the workspace program retiring /v1/build/*. Design of record: pipelex-sdk-js/wip/prepare-inputs-selectors/design.md; this repo's tracker is wip/prepare-inputs-selectors/plan.md.

What changes

prepare_inputs names the method three ways. Inline files, a method_ref address (runner-resolved), or a stored method_id (platform-resolved) — exactly one per call, all server-resolved with nothing expanded client-side. Empty is absent; none or several raises InputPreparationError before any request leaves the process. A method addressed by URL that declares a file input previously had no preparation path at all, even though the request beneath already accepted the address.

The signature moves from the inputs template to the input-form descriptor — one POST /v1/validate with views: ["input_form"] and allow_signatures=True, walked discriminated on each node's declared kind rather than on the shape of a value.

Two security edges close (half of L-260826-ddd843, P1)

The template marked a file position by rendering a {"url": …} dict — a side effect of a field being named url, not of its concept being an Image or Document:

  • an optional nested file field was never rendered by the required-only template, so its position was invisible and the caller's local path travelled to the runner as a literal string;
  • a text field merely named url was read from disk and uploaded.

The descriptor states the resolved kind at every depth and includes optional fields.

Breaking

  • client.build_inputs and the BuildInputs* models are removed. The wrapper existed only to be the signature source prepare_inputs read; nothing calls /v1/build/* from this SDK now. (JS kept buildInputs — it has a wrapper family retiring separately. Python only ever had this one.)
  • The shared crate envelope (MthdsFileItem, CrateRequestBase, CrateInvalidReport) moves to pipelex_sdk.crate_models; build_models.py is deleted. Models unchanged — update the import path.
  • A canonical file dict nested inside a Dynamic input is no longer uploaded: that input is kind: "unknown", the standard's escape hatch, and the walk does not enter it. Such a caller uploads with upload_file first, as the docs have always prescribed.

Also in scope

prepare_inputs accepts the explicit {concept, content} input envelope. A pre-existing parity gap rather than part of the item's letter — the JS SDK has accepted it since an earlier release and Python raised Unsupported value at a file input … got dict — folded in with Louis's agreement so the two SDKs really are identical after this lands.

Adds PipelexValidationReport.default_pipe_ref, declared ahead of the server (L-260829-0208c7) exactly as the JS did.

Verification

make agent-check and make agent-test green.

Live against api-dev.pipelex.com (pipelex-hosted 0.11.1), mirroring the JS e2e:

Case Result
inline files, pipe defaulted from main_pipe image wrapped, text input untouched, nothing uploaded
method_ref + explicit qualified pipe_ref address resolved server-side, https:// URL passed through
same method_ref, no pipe_ref honest refusal naming all seven candidate pipes — the manifest-only main_pipe gap
explicit envelope round-trip concept annotation rides through
bare pipe_ref refused, naming the qualified ref
bytes at an image position uploaded, rewritten to pipelex-storage://

Notes

  • No release here. The change lands under ## [Unreleased]; /release cuts the version. L-260826-ddd843 closes only when both SDKs have shipped a release carrying the fix, and the JS half is still unreleased (its PR #43 is open).
  • Known limit, tracked separately as L-260829-60e213: a class-backed concept whose reflection cannot map a field annotation collapses to kind: "unknown", hiding a file field beneath it. Recorded in docs/input-preparation.md; deliberately not worked around here.

Closes L-260829-8a25d5
Advances L-260826-ddd843


Summary by cubic

Updates prepare_inputs to name the method three ways — inline files, a method_ref address, or a stored method_id — and to read the target pipe's signature from the input-form descriptor instead of the retired /v1/build/inputs template (the Python half of L-260829-8a25d5 retiring /v1/build/*). The old walk classified file positions by value shape, so a text field named url was read from disk and uploaded while optional nested file fields went undetected, letting local paths travel to the runner as literal strings; classifying on the descriptor's declared kind at every depth closes both security edges (L-260826-ddd843).

New Features

  • prepare_inputs accepts the explicit {concept, content} input envelope, closing a parity gap with pipelex-sdk-js.
  • Adds PipelexValidationReport.default_pipe_ref, the qualified pipe_ref a caller gets by omitting the pipe selector; older runners omit it and callers fall back.
  • A non-string method or pipe selector now raises InputPreparationError instead of being treated as absent.

Migration

  • client.build_inputs and the BuildInputs* models are removed; nothing calls /v1/build/* from this SDK now.
  • The shared crate envelope (MthdsFileItem, CrateRequestBase, CrateInvalidReport) moves to pipelex_sdk.crate_models; the models are unchanged, so just update the import path.
  • A canonical file dict nested inside a Dynamic input is no longer uploaded; upload it with upload_file first.

Written for commit a5ae0c4. Summary will update on new commits.

Review in cubic

…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
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T00:16:32.102392Z f5af18a PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@greptile-apps

greptile-apps Bot commented Aug 30, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The wrong-typed selector handling should be fixed before merging because it can prepare inputs against a method or pipe other than the one explicitly supplied.

The new normalization treats invalid selector types as absence, allowing another method selector or a default pipe to take effect silently.

Files Needing Attention: pipelex_sdk/prepare_inputs.py

Prompt To Fix All With AI
### Issue 1
pipelex_sdk/prepare_inputs.py:109-110
**Invalid selectors become absent**

When an untyped caller passes a non-string `method_ref`, `method_id`, or `pipe_ref`, `_non_empty_string` silently converts it to `None`, allowing another method selector or the default pipe to be used and preparing inputs against a method or pipe different from the caller's explicit request.

```suggestion
    if not isinstance(value, str):
        msg = f"Method and pipe selectors must be strings, received {type(value).__name__}."
        raise InputPreparationError(msg)
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "prepare_inputs: all three selectors, sig..." | Re-trigger Greptile

Comment thread pipelex_sdk/prepare_inputs.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5af18a255

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pipelex_sdk/prepare_inputs.py
Comment thread pipelex_sdk/prepare_inputs.py Outdated
lchoquel and others added 3 commits September 7, 2026 09:02
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
@lchoquel
lchoquel merged commit 7b1892f into dev Sep 7, 2026
18 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 7, 2026
@lchoquel
lchoquel deleted the feature/PrepareInputsSelectors branch September 7, 2026 07:13
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant