Release v0.1.0 - #2
Conversation
Greenfield scaffold for the Python client of the Pipelex hosted API, mirroring mthds-python's tooling so every later phase runs on a green gate. - pyproject.toml: PyPI `pipelex-sdk` / import `pipelex_sdk`, requires-python >=3.10,<3.15, hatchling, runtime deps (mthds>=0.5.0, pydantic, httpx, typing-extensions, backports.strenum) and the full ruff/pyright/mypy/ pylint/pytest config. [tool.uv.sources] resolves mthds from ../mthds-python for dev; the published wheel depends on mthds>=0.5.0 from PyPI. - Makefile: install/lock/build/test/agent-test/format/lint/pyright/mypy/ pylint and the c/cc/check/agent-check aggregates. - pipelex_sdk/_compat.py: local StrEnum/Self shim (no cross-package private import). - Boilerplate: README, CHANGELOG (Unreleased), .gitignore, CLAUDE.md (overview + standards), docs/architecture.md skeleton, smoke test. Kickoff decisions settled: package = pipelex-sdk/pipelex_sdk; client built by inheritance (PipelexAPIClient(MthdsAPIClient)); credentials Pipelex-primary with mthds fallback. Gate green: make install && make check && make agent-test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013cPeza9ezw38JFCi3uXP4m
PipelexAPIClient(MthdsAPIClient) — Pipelex-branded construction plus the transport/error layer the product and lifecycle phases build on. - errors.py: ApiResponseError (RFC 9457 `code` discriminant + problem-details) and ApiUnreachableError, both deriving from the protocol-base PipelineRequestError. - client.py: __init__ override (PIPELEX_API_KEY/URL → mthds fallback; token optional/anonymous; host-only base-URL validation; origin_url for /health), start_client override (omit the Authorization header when anonymous), and the transport helpers _send_or_unreachable (httpx transport → ApiUnreachableError), _request_product (typed ApiResponseError, empty-body tolerant, PUT/PATCH/DELETE), _request_json (plainer regime), and the _parse_error_body problem+json parser. - Tests (test-first style): construction (env precedence, optional token, base-URL validation), _parse_error_body across body shapes, _request_product (2xx/empty-body/non-2xx code mapping/transport), _request_json regime. - Config: allow SLF001/PLC2701 in tests (probe internal helpers) and a pyright executionEnvironment disabling reportPrivateUsage under tests/. Gate green: make check (ruff, pyright 0 errors, mypy, pylint 10.00/10) + make agent-test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013cPeza9ezw38JFCi3uXP4m
Lock in the repo convention (preferred over pipelex's per-line inline ignores): test-only lint/type exemptions are declared once, scoped to tests/** in config (ruff per-file-ignores + pyright executionEnvironments), never sprinkled inline. The pyproject config already follows this; this just documents it so later phases don't drift back to per-line ignores. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013cPeza9ezw38JFCi3uXP4m
Own the hosted run lifecycle in pipelex-sdk (ported from mthds-python and enhanced), independent of mthds-python's state — a transient duplication resolved by the Phase 6 strip. - pipelex_sdk/runs.py: owned lifecycle models (RunStatus with is_terminal/ is_success, RunRead, RunResults, the discriminated RunResultState union, WaitForResultOptions, PollInfo). RunResults names the Pipelex hosted artifacts (main_stuff, graph_spec, pipe_output) per the JS behavior spec. - Lifecycle methods on PipelexAPIClient: get_run_status, get_run_result (202/503 -> running, 200 -> completed, 409 -> failed), wait_for_result. Poll GETs go through _send_or_unreachable, so transport failures surface as ApiUnreachableError and missing-route 404s as RunLifecycleUnavailableError. - start override: translates a bare-runner missing-route 404 into a typed RunLifecycleUnavailableError (before any run is created), matching the JS SDK. - start_and_wait self-heals hosted<->bare: cached GET /v1/version handshake (_supports_run_lifecycle) picks durable start+poll on hosted, falls back to the blocking POST /v1/execute (_execute_blocking) on a bare runner. Closes a gap vs mthds-python (whose start_and_wait raises on a bare runner). - Lifecycle errors RunFailedError/RunTimeoutError/RunLifecycleUnavailableError owned here; RunStillRunningError re-exported from mthds (protocol 202-degrade). - Tests: lifecycle status mapping, start override, poll/timeout/cancel, plus new coverage for the version-handshake caching and the bare-runner self-heal. - Docs + CHANGELOG updated. The owned lifecycle methods transiently shadow the base's (until Phase 6 strips them from mthds-python); their owned return types read as incompatible overrides, hence a narrow # type: ignore[override] that becomes harmless after the strip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013cPeza9ezw38JFCi3uXP4m
Add the hosted product routes as typed Pydantic v2 models (pipelex_sdk/product_models.py) and client methods via _request_product: user profile, methods CRUD, organizations, billing, Pipelex API keys, gateway inference key, onboarding, storage, and run records. Documented 409-conflict behaviors surface through ApiResponseError.code (conflict; pipelex_api_key_limit_reached). Override validate to inject render: ["markdown"] (so valid + invalid verdicts both carry rendered_markdown), accept a parallel mthds_sources array, and add validate_files (synthesizes deterministic inline:// source labels when any file carries a URI). The override delegates the wire call to the inherited base validate, keeping body-building and the protocol error regime shared. Tests port pipelex-sdk-js/tests/product.test.ts (verb+path+body per route, model round-trips with complete bodies, .code branching) plus validate override coverage. Gate green: ruff, pyright, mypy, pylint 10.00/10, tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BkphvaUvNJi6G613jq1q7j
Add `PipelexAPIClient.health()` — `GET {origin}/health`, served at the
origin (NOT under the `/v1` prefix) and out-of-protocol. Rides the plainer
`_request_json` regime (`PipelineRequestError` on a non-2xx,
`ApiUnreachableError` on transport failure), not the product
`ApiResponseError`. Mirrors `pipelex-sdk-js` `client.ts` `health()`.
Tests assert the origin-level path (outside `/v1`), the return shape, the
plainer error regime, and transport-failure mapping. Docs + CHANGELOG updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkphvaUvNJi6G613jq1q7j
…sion guard, release docs Phase 5 of the pipelex-sdk-python build: the HANDOFF parity gate against the TypeScript @pipelex/sdk reference. Surface-complete with no silent gaps. Resolved the three deferred parity flags: - execute() override + PipelineExecuteTimeoutError (Phase-1 flag) — IMPLEMENTED. A blocking POST /v1/execute killed by the hosted gateway's ~30s synchronous ceiling (503/504, or a client-side request timeout, observed at/after ~28s) is translated into a clear PipelineExecuteTimeoutError pointing at start+poll, closing the one genuine JS feature gap. Other non-2xx keep the inherited httpx.HTTPStatusError regime; 202 async-degrade still raises RunStillRunningError. - health error regime (#5) — KEPT the plainer PipelineRequestError regime (already matches JS; liveness needs no code taxonomy). - validate error regime (Phase-3 flag) — DEFERRED; keeps the inherited httpx.HTTPStatusError regime, consistent with the other Python protocol routes. Also: - __version__ (pipelex_sdk/version.py) derived from installed distribution metadata via importlib.metadata, with a test asserting it matches pyproject. - Parity audit recorded in docs/architecture.md ("Parity with @pipelex/sdk"): method/model/error coverage, deliberate idiomatic ports, conscious exclusions (/v1/build/* per #8, WorkOS org-switch), and ClientAuthenticationError as a dormant non-port. - README rewritten: quickstart (validate → start_and_wait → main_stuff/native), an err.code product example, and the full no-barrel import paths. - CHANGELOG 0.1.0 entry. Tests for the gateway-timeout translation (503/504/client-timeout past ceiling → translated; fast 503 → untouched; success/202 passthrough) and the version guard. Gate green: ruff, pyright 0, mypy clean, pylint 10.00/10, all tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BkphvaUvNJi6G613jq1q7j
… floor to 0.6.0 mthds 0.6.0 is now protocol-only, so the owned lifecycle methods (get_run_status / get_run_result / wait_for_result / start_and_wait) and _raise_if_lifecycle_unavailable no longer shadow base methods — remove their @OverRide decorators and # type: ignore[override] suppressions, which were transition artifacts. @OverRide stays on start_client / execute / start / validate (permanent protocol-route overrides; validate keeps its permanent # type: ignore[override] since its signature genuinely differs). Bump the mthds floor to >=0.6.0 (the protocol-only base the override cleanup type-checks against). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BkphvaUvNJi6G613jq1q7j
Move the Pipelex-branded validation models into the SDK (pipelex_sdk/validation_models.py): PipelexValidationReport, PipelexInvalidReport, the PipelexValidationResult union + adapter, and the supporting ValidationErrorItem / ValidationErrorCategory / ValidatedPipeEntry / DryRunStatus — narrowing mthds's neutral verdict bases (mthds.protocol.models). PipelexAPIClient.validate() now parses the 200 body into PipelexValidationResult via the inherited _post_validate transport seam. Adds a local empty_list_factory_of util (pipelex_sdk/_pydantic_utils.py) and moves the validation-contract round-trip test here. Bumps the mthds floor to >=0.7.0 (the protocol-only-validate base). Completes the brand-layering follow-up #9, fully mirroring the documented pipelex-sdk-js boundary; the brand-neutral Dict* wire concretes stay in mthds and are reused by inheritance. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BkphvaUvNJi6G613jq1q7j
Mirror the mthds-python CI suite, adapted to the Pipelex org and the pipelex-sdk PyPI distribution: - PR gates across the Python 3.10-3.14 matrix: lint-check, tests-check, package-check, changelog-check, version-check, guard-branches, cla - publish.yml: build -> PyPI Trusted Publishing -> Sigstore-signed GitHub Release on push to main - Add root CLA.md (Pipelex/Evotis CLA) and docs/ci-cd.md - Align CHANGELOG.md to the `## [vX.Y.Z]` header convention the changelog/publish workflows key off Also pin the published mthds floor to >=0.6.0, dropping the local editable [tool.uv.sources] so the wheel resolves mthds from PyPI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rNPx1LVeHFCJWXPkicEWy
- client.py: honor explicit `api_token=""` as anonymous by testing `is not None` rather than truthiness, so the first present credential layer wins. Restores the documented "empty = anonymous" contract and JS SDK `??` parity (codex P2). Adds regression tests. - guard-branches.yml: check out the PR head SHA so the workflow-protection diff actually sees fork edits, and gate on a maintainer allow-list so FIRST_TIME_CONTRIBUTOR / FIRST_TIMER / NONE authors are covered (codex + greptile P1). - tests-check.yml: drop unused `id-token: write` from the untrusted PR test job (greptile P1, least privilege). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rNPx1LVeHFCJWXPkicEWy
Feature/build pipelex sdk
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 68e9cda68b
ℹ️ 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".
| # diff below would compare base-against-base and never see the fork's changes. We | ||
| # only fetch/diff/grep here — the untrusted head is never executed — so checking | ||
| # out the PR head SHA is safe. | ||
| ref: ${{ github.event.pull_request.head.sha }} |
There was a problem hiding this comment.
Avoid checking out fork heads in pull_request_target
For external PRs this job runs under pull_request_target and checks out ${{ github.event.pull_request.head.sha }}; GitHub has announced that actions/checkout will refuse this exact pattern for fork PRs in pull_request_target workflows when the protection is backported to floating major tags like actions/checkout@v4 on July 16, 2026. Because this guard job only runs for non-members, it will start failing before the diff step and block all forked external PRs, not just workflow edits; use an API/list-files approach or another safe diff mechanism instead of checking out the fork head here.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR publishes the first Python Pipelex SDK release. The main changes are:
Confidence Score: 3/5Release automation needs attention before merge because CI and publishing paths can execute unsafe installation logic or produce incorrect release artifacts. The SDK implementation is broadly covered by unit tests, but the automation issues affect release integrity and CI safety. Makefile and .github/workflows/publish.yml
What T-Rex did
Prompt To Fix All With AIFix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
Makefile:102
**Remote Installer Runs In CI**
When a PR runner does not already have `uv`, the lint path reaches `make check-uv` and executes the live `astral.sh` installer through `sh`. That gives remote, unpinned script content the workflow environment and token for the job; install `uv` through a pinned action or verified artifact instead of piping the network response into a shell.
### Issue 2 of 3
.github/workflows/publish.yml:122-124
**Changelog Delimiter Corrupts Environment**
A release note that contains a line exactly equal to `EOF` closes this fixed `$GITHUB_ENV` heredoc before the changelog is fully written. The remaining markdown is then parsed as environment-file content, so the release job can fail or publish truncated notes; use a delimiter that cannot collide with changelog text.
### Issue 3 of 3
.github/workflows/publish.yml:75
**Release Version Uses First Match**
This grep reads the first `version = ` text anywhere in `pyproject.toml`, not the `[project]` version. If tool configuration with a version-like key is added above `[project]`, the wheel still builds with the correct metadata but the GitHub tag, release title, and changelog lookup use the wrong value.
Reviews (1): Last reviewed commit: "Release v0.1.0" | Re-trigger Greptile |
|
T-Rex pricing update — T-Rex was free through June 2026. Effective July 1, 2026, T-Rex adds 2 credits on top of the standard 1-credit review (3 total). T-Rex settings |
| echo ""; \ | ||
| echo "=== [$(PROJECT_NAME)] ===== (check-uv) ====== Ensuring uv ≥ $(UV_MIN_VERSION) =========="; \ | ||
| echo "uv not found – installing latest …"; \ | ||
| curl -LsSf https://astral.sh/uv/install.sh | sh; \ |
There was a problem hiding this comment.
When a PR runner does not already have uv, the lint path reaches make check-uv and executes the live astral.sh installer through sh. That gives remote, unpinned script content the workflow environment and token for the job; install uv through a pinned action or verified artifact instead of piping the network response into a shell.
Artifacts
Repro: harness that hides uv and safely mocks curl and sh while running make check-uv and make lint
- Contains supporting evidence from the run (text/x-shellscript; charset=utf-8).
- Keeps the command output available without making the summary code-heavy.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: Makefile
Line: 102
Comment:
**Remote Installer Runs In CI**
When a PR runner does not already have `uv`, the lint path reaches `make check-uv` and executes the live `astral.sh` installer through `sh`. That gives remote, unpinned script content the workflow environment and token for the job; install `uv` through a pinned action or verified artifact instead of piping the network response into a shell.
How can I resolve this? If you propose a fix, please make it concise.| echo "CHANGELOG_NOTES<<EOF" >> $GITHUB_ENV | ||
| echo "$CHANGELOG_CONTENT" >> $GITHUB_ENV | ||
| echo "EOF" >> $GITHUB_ENV |
There was a problem hiding this comment.
Changelog Delimiter Corrupts Environment
A release note that contains a line exactly equal to EOF closes this fixed $GITHUB_ENV heredoc before the changelog is fully written. The remaining markdown is then parsed as environment-file content, so the release job can fail or publish truncated notes; use a delimiter that cannot collide with changelog text.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/publish.yml
Line: 122-124
Comment:
**Changelog Delimiter Corrupts Environment**
A release note that contains a line exactly equal to `EOF` closes this fixed `$GITHUB_ENV` heredoc before the changelog is fully written. The remaining markdown is then parsed as environment-file content, so the release job can fail or publish truncated notes; use a delimiter that cannot collide with changelog text.
How can I resolve this? If you propose a fix, please make it concise.| - name: Extract version and detect pre-release | ||
| id: get_version | ||
| run: | | ||
| VERSION=$(grep -m 1 'version = ' pyproject.toml | cut -d '"' -f 2) |
There was a problem hiding this comment.
Release Version Uses First Match
This grep reads the first version = text anywhere in pyproject.toml, not the [project] version. If tool configuration with a version-like key is added above [project], the wheel still builds with the correct metadata but the GitHub tag, release title, and changelog lookup use the wrong value.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/publish.yml
Line: 75
Comment:
**Release Version Uses First Match**
This grep reads the first `version = ` text anywhere in `pyproject.toml`, not the `[project]` version. If tool configuration with a version-like key is added above `[project]`, the wheel still builds with the correct metadata but the GitHub tag, release title, and changelog lookup use the wrong value.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
9 issues found across 41 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=".github/workflows/version-check.yml">
<violation number="1" location=".github/workflows/version-check.yml:36">
P1: `exit 0` in step "Get branch info" only exits that step's shell — it does not skip subsequent steps. Non-release PRs to `main` will always fail here because `source_release_version` is never set (empty), so the final comparison `"$PR_VERSION" != ""` evaluates to true and exits 1.</violation>
</file>
<file name=".github/workflows/cla.yml">
<violation number="1" location=".github/workflows/cla.yml:20">
P2: Pin third-party actions that receive secrets to full-length commit SHAs instead of mutable tags to prevent tag-retargeting or compromised-release takeover.</violation>
</file>
<file name=".github/workflows/publish.yml">
<violation number="1" location=".github/workflows/publish.yml:5">
P2: Publishing on every `main` push will make ordinary post-release merges attempt to republish the same package version. Gate this workflow on release tags or an explicit release trigger instead of all main pushes.</violation>
<violation number="2" location=".github/workflows/publish.yml:122">
P1: Use a unique delimiter when writing multiline changelog content to `$GITHUB_ENV`; a fixed `EOF` marker can terminate early if the notes contain a standalone `EOF` line.</violation>
</file>
<file name="pipelex_sdk/product_models.py">
<violation number="1" location="pipelex_sdk/product_models.py:61">
P2: Input payload models are not configured to forbid extras, so misspelled/unsupported request fields are silently dropped instead of failing validation. Add strict `model_config` to each request-body model.</violation>
</file>
<file name=".github/workflows/lint-check.yml">
<violation number="1" location=".github/workflows/lint-check.yml:3">
P2: Missing `push` trigger for main branch. Lint checks only run on PRs, not on pushes to main (e.g., merges). Add `push: branches: [main]` so the workflow enforces lint after merge commits too.</violation>
<violation number="2" location=".github/workflows/lint-check.yml:12">
P2: Missing least-privilege `permissions` block. A lint-only workflow needs `contents: read` at most. The inherited default grants write access to contents, issues, and pull-requests — unnecessary privilege for untrusted PR code.</violation>
</file>
<file name=".github/workflows/guard-branches.yml">
<violation number="1" location=".github/workflows/guard-branches.yml:68">
P2: Workflow-change detection uses a full tree diff instead of PR delta. This can falsely reject external PRs that did not modify workflows.</violation>
</file>
<file name="Makefile">
<violation number="1" location="Makefile:102">
P1: Avoid piping a remote installer directly into `sh` in this target. This path is used in CI, so unpinned network content executes inside the workflow environment.</violation>
</file>
You're on the cubic free plan with 19 free PR reviews remaining this month. Upgrade for unlimited reviews.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
| echo "Source is NOT a release branch - skipping version check" | ||
| echo "is_release_source=false" >> $GITHUB_OUTPUT | ||
| echo "This workflow only runs for PRs from release branches to main" | ||
| exit 0 |
There was a problem hiding this comment.
P1: exit 0 in step "Get branch info" only exits that step's shell — it does not skip subsequent steps. Non-release PRs to main will always fail here because source_release_version is never set (empty), so the final comparison "$PR_VERSION" != "" evaluates to true and exits 1.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/version-check.yml, line 36:
<comment>`exit 0` in step "Get branch info" only exits that step's shell — it does not skip subsequent steps. Non-release PRs to `main` will always fail here because `source_release_version` is never set (empty), so the final comparison `"$PR_VERSION" != ""` evaluates to true and exits 1.</comment>
<file context>
@@ -0,0 +1,74 @@
+ echo "Source is NOT a release branch - skipping version check"
+ echo "is_release_source=false" >> $GITHUB_OUTPUT
+ echo "This workflow only runs for PRs from release branches to main"
+ exit 0
+ fi
+ echo "======================================="
</file context>
| echo ""; \ | ||
| echo "=== [$(PROJECT_NAME)] ===== (check-uv) ====== Ensuring uv ≥ $(UV_MIN_VERSION) =========="; \ | ||
| echo "uv not found – installing latest …"; \ | ||
| curl -LsSf https://astral.sh/uv/install.sh | sh; \ |
There was a problem hiding this comment.
P1: Avoid piping a remote installer directly into sh in this target. This path is used in CI, so unpinned network content executes inside the workflow environment.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Makefile, line 102:
<comment>Avoid piping a remote installer directly into `sh` in this target. This path is used in CI, so unpinned network content executes inside the workflow environment.</comment>
<file context>
@@ -0,0 +1,309 @@
+ echo ""; \
+ echo "=== [$(PROJECT_NAME)] ===== (check-uv) ====== Ensuring uv ≥ $(UV_MIN_VERSION) =========="; \
+ echo "uv not found – installing latest …"; \
+ curl -LsSf https://astral.sh/uv/install.sh | sh; \
+ }
+ @uv self update >/dev/null 2>&1 || true
</file context>
| CHANGELOG_CONTENT=$(printf "%s\n\n%s" "$HEADER_LINE" "$CONTENT_LINES") | ||
|
|
||
| # Escape for GitHub Actions | ||
| echo "CHANGELOG_NOTES<<EOF" >> $GITHUB_ENV |
There was a problem hiding this comment.
P1: Use a unique delimiter when writing multiline changelog content to $GITHUB_ENV; a fixed EOF marker can terminate early if the notes contain a standalone EOF line.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/publish.yml, line 122:
<comment>Use a unique delimiter when writing multiline changelog content to `$GITHUB_ENV`; a fixed `EOF` marker can terminate early if the notes contain a standalone `EOF` line.</comment>
<file context>
@@ -0,0 +1,172 @@
+ CHANGELOG_CONTENT=$(printf "%s\n\n%s" "$HEADER_LINE" "$CONTENT_LINES")
+
+ # Escape for GitHub Actions
+ echo "CHANGELOG_NOTES<<EOF" >> $GITHUB_ENV
+ echo "$CHANGELOG_CONTENT" >> $GITHUB_ENV
+ echo "EOF" >> $GITHUB_ENV
</file context>
| @@ -0,0 +1,42 @@ | |||
| name: "CLA Assistant bot" | |||
There was a problem hiding this comment.
P2: Pin third-party actions that receive secrets to full-length commit SHAs instead of mutable tags to prevent tag-retargeting or compromised-release takeover.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/cla.yml, line 20:
<comment>Pin third-party actions that receive secrets to full-length commit SHAs instead of mutable tags to prevent tag-retargeting or compromised-release takeover.</comment>
<file context>
@@ -0,0 +1,42 @@
+ steps:
+ - name: Get GitHub App token
+ id: app-token
+ uses: actions/create-github-app-token@v3
+ with:
+ app-id: ${{ secrets.CLA_GH_APP_ID }}
</file context>
|
|
||
| on: | ||
| push: | ||
| branches: |
There was a problem hiding this comment.
P2: Publishing on every main push will make ordinary post-release merges attempt to republish the same package version. Gate this workflow on release tags or an explicit release trigger instead of all main pushes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/publish.yml, line 5:
<comment>Publishing on every `main` push will make ordinary post-release merges attempt to republish the same package version. Gate this workflow on release tags or an explicit release trigger instead of all main pushes.</comment>
<file context>
@@ -0,0 +1,172 @@
+
+on:
+ push:
+ branches:
+ - main
+
</file context>
| @@ -0,0 +1,360 @@ | |||
| """Pipelex-product wire models — the snake_case JSON shapes the hosted-product routes speak. | |||
There was a problem hiding this comment.
P2: Input payload models are not configured to forbid extras, so misspelled/unsupported request fields are silently dropped instead of failing validation. Add strict model_config to each request-body model.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pipelex_sdk/product_models.py, line 61:
<comment>Input payload models are not configured to forbid extras, so misspelled/unsupported request fields are silently dropped instead of failing validation. Add strict `model_config` to each request-body model.</comment>
<file context>
@@ -0,0 +1,360 @@
+ updated_at: str
+
+
+class MethodWriteInput(BaseModel):
+ """The create/update payload — a rename is a `PUT` with a changed `name`."""
+
</file context>
| @@ -0,0 +1,65 @@ | |||
| name: Lint check | |||
There was a problem hiding this comment.
P2: Missing least-privilege permissions block. A lint-only workflow needs contents: read at most. The inherited default grants write access to contents, issues, and pull-requests — unnecessary privilege for untrusted PR code.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/lint-check.yml, line 12:
<comment>Missing least-privilege `permissions` block. A lint-only workflow needs `contents: read` at most. The inherited default grants write access to contents, issues, and pull-requests — unnecessary privilege for untrusted PR code.</comment>
<file context>
@@ -0,0 +1,65 @@
+# --------------------------------------------------------------------------
+ lint:
+ name: Lint (${{ matrix.python-version }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
</file context>
| @@ -0,0 +1,65 @@ | |||
| name: Lint check | |||
|
|
|||
| on: | |||
There was a problem hiding this comment.
P2: Missing push trigger for main branch. Lint checks only run on PRs, not on pushes to main (e.g., merges). Add push: branches: [main] so the workflow enforces lint after merge commits too.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/lint-check.yml, line 3:
<comment>Missing `push` trigger for main branch. Lint checks only run on PRs, not on pushes to main (e.g., merges). Add `push: branches: [main]` so the workflow enforces lint after merge commits too.</comment>
<file context>
@@ -0,0 +1,65 @@
+name: Lint check
+
+on:
+ pull_request:
+
</file context>
| fetch-depth: 0 | ||
| - name: Detect workflow changes | ||
| run: | | ||
| git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=1 |
There was a problem hiding this comment.
P2: Workflow-change detection uses a full tree diff instead of PR delta. This can falsely reject external PRs that did not modify workflows.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/guard-branches.yml, line 68:
<comment>Workflow-change detection uses a full tree diff instead of PR delta. This can falsely reject external PRs that did not modify workflows.</comment>
<file context>
@@ -0,0 +1,73 @@
+ fetch-depth: 0
+ - name: Detect workflow changes
+ run: |
+ git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=1
+ CHANGED=$(git diff --name-only FETCH_HEAD HEAD | grep -E '^\.github/workflows/.*\.ya?ml$' || true)
+ if [ -n "$CHANGED" ]; then
</file context>
Gate workflow-edit trust on the author's effective repository permission (resolved via getCollaboratorPermissionLevel, only write/admin trusted, failing closed on non-404 API errors) instead of the spoofable author_association label, and drop to least-privilege contents: read. Mirrors mthds-python. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rNPx1LVeHFCJWXPkicEWy
Release v0.1.0
The initial public release of
pipelex-sdk(import packagepipelex_sdk) — the Python counterpart of@pipelex/sdk, built by inheritance on themthdsprotocol base. This is the first version; nothing was previously published to PyPI. Merging this PR intomaintriggerspublish.yml, which builds the wheel, publishes it to PyPI aspipelex-sdkvia Trusted Publishing, and cuts the Sigstore-signed GitHub release.Changelog
[v0.1.0] - 2026-07-01
The initial public surface of
pipelex-sdk— the Python counterpart of@pipelex/sdk, built by inheritance on themthdsprotocol base. Surface-complete against the TypeScript SDK (seedocs/architecture.md→ "Parity with@pipelex/sdk"); the/v1/build/*helpers and the WorkOS org-switch are consciously out of scope for this release.Added
pyproject.toml), tooling (Makefile, ruff/pyright/mypy/pylint config mirroringmthds-python), and the emptypipelex_sdkpackage.mthds-python, adapted to thePipelexorg and thepipelex-sdkPyPI distribution: PR gates (lint-check,tests-check,package-check,changelog-check,version-check,guard-branches,cla) across the full Python matrix, pluspublish.yml(build → PyPI Trusted Publishing → signed GitHub Release) on push tomain. RootCLA.mdanddocs/ci-cd.mdadded alongside.PipelexAPIClient(subclass ofmthds'sMthdsAPIClient): Pipelex-branded construction (resolvesPIPELEX_API_KEY/PIPELEX_API_URL, falling back to themthdsresolver; token optional for anonymous access; host-only base-URL validation; origin URL forhealth)._request_product,_request_json, transport-failure mapping toApiUnreachableError, and theproblem+jsonerror-body parser.ApiResponseError(with the RFC 9457codediscriminant) andApiUnreachableError, both deriving from the protocol-basePipelineRequestError.pipelex_sdk/runs.py+ client methods): owned run-lifecycle models and the polling surfaceget_run_status/get_run_result/wait_for_result.start_and_waitself-heals across hosted and bare runners (durable start+poll on the hosted API, blockingPOST /v1/executefallback on a bare runner).RunFailedError,RunTimeoutError,RunLifecycleUnavailableError;RunStillRunningErrorre-exported frommthds.pipelex_sdk/product_models.py+ client methods): user profile, methods catalog CRUD, organizations, billing, Pipelex API keys, the gateway inference key, onboarding, storage, and run records.pipelex_sdk/validation_models.py): the SDK owns the Pipelex-branded narrowing of the/v1/validate200-diagnostic union (resolves the brand-layering follow-up Scope the contract-only usage claims and fix the run-usage example #9).validateoverride +validate_files,health(),executeoverride +PipelineExecuteTimeoutError.__version__(pipelex_sdk.version), derived from installed distribution metadata.Changed
mthds>=0.6.1(protocol base floor).CHANGELOG.mdversion headers use the workspace-wide## [vX.Y.Z]convention (matchingmthds-python/pipelex-sdk-js), which the changelog/publish workflows key off.Fixed
PipelexAPIClienthonors an explicitapi_token=""as a request for anonymous access even when a credential is configured. Credential resolution testsis not Nonerather than truthiness, so the first present layer wins.guard-branches.ymlworkflow-protection job checks out the PR head (ref: pull_request.head.sha) so itsgit diffdetects fork edits to.github/workflows/*; its author-association gate is a maintainer allow-list coveringFIRST_TIME_CONTRIBUTOR/FIRST_TIMER/NONE.tests-check.ymlno longer grantsid-token: writeto the test matrix job (least privilege).🤖 Generated with Claude Code
Summary by cubic
Initial public release of
pipelex-sdk(pipelex_sdk), the Python client for the Pipelex hosted API, reaching feature parity with@pipelex/sdk. Merging tomainbuilds and publishes to PyPI via Trusted Publishing and creates a Sigstore-signed GitHub release.New Features
PipelexAPIClientonmthds: Pipelex-branded env resolution (supports anonymous withapi_token=""), host-only base URL validation, transport/error mapping (ApiResponseError,ApiUnreachableError), andhealth()at the origin.get_run_status/get_run_result/wait_for_result, plusstart_and_waitthat falls back to blockingPOST /v1/executeon bare runners. AddsRunFailedError,RunTimeoutError,RunLifecycleUnavailableError.validateinjects render of markdown and acceptsmthds_sources;validate_fileshelper for file inputs.PipelineExecuteTimeoutErrorwith guidance to use start+poll.pipelex_sdk.__version__sourced from installed metadata.mthds>=0.6.1.Bug Fixes
api_token=""as anonymous even if other credentials exist (checks for None, not truthiness).guard-branches.ymlchecks out the PR head and gates with a maintainer allow-list for fork safety.tests-check.ymldrops unnecessaryid-token: writepermission for least privilege.Written for commit 68e9cda. Summary will update on new commits.