FE-1468: Generate pydantic models from the CLI's protocol schemas - #9262
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
PR SummaryMedium Risk Overview
Reviewed by Cursor Bugbot for commit d309df3. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
@kube looks great too - thanks! I will approve once we fix the cursor-raised issue. |
ecae501 to
3fb37f9
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3fb37f9. Configure here.
Adds libs/@local/petrinaut-python: a stdlib-only, POSIX-only package wrapping the CLI's JSON-lines protocol. PetrinautSession serves a model (from a file or over stdin) with healthz/metadata/run plus a generic request method; OptimizationSession adds describe/evaluate/objective over an opaque manifest, and evaluate returns per-seed replicates when the manifest asks for more than one seed. The code comes from petrinaut-opt's client: spawn scrubbing, bounded reads, and process-group shutdown are unchanged. petrinaut-opt consumes the package as an editable uv path dependency, mirrored as a workspace:* edge so Turbo pruning and change detection see it; its codegen inputs, the Docker python-deps/runner stages, and the deploy path filter follow. The old src/petrinaut_client.py, the PYTHON_INTEGRATION.md walkthrough, and examples/python_stdio.py are retired, and MODEL_EXAMPLES.md now uses the bindings. Two end-to-end pytests drive the real built CLI bundle, one per session type.
3fb37f9 to
deda7ec
Compare
The method sat on a class already named OptimizationSession, while its siblings evaluate() and objective() carry no prefix. describe() maps 1:1 to the protocol's optimization.describe. The previous name remains as a class-level alias. Raised in review on #9228.
Encoding failures happen before anything is written: params or a bootstrap payload that will not serialize to JSON raise TypeError, and a payload over the line cap raises ValueError. Both previously raised PetrinautClientError, whose contract says the process or transport is no longer usable — a caller treating it as terminal would discard a healthy session. The request id is now consumed only after a successful encode, so a rejected request leaves the session exactly as it was. Raised by Cursor and in review on #9228.
ruff and basedpyright are configured in each package's pyproject.toml and pinned as uv dev dependencies, so an editor and CI resolve the same versions with no editor-specific files. New lint:ruff and lint:types tasks are wired into the per-package probe in lint.yml.
The optimization protocol's response shapes become Zod schemas in petrinaut-core; the CLI publishes them as one JSON Schema document; the bindings generate pydantic models from it and validate every describe/evaluate result. Both Python packages move to typeCheckingMode strict.
deda7ec to
d309df3
Compare

🌟 What is the purpose of this PR?
The bindings returned
dict[str, Any], andapps/petrinaut-optread the study description field by field with nothing checking it; a protocol change first showed up as a failing study. This PR makes the protocol's response shapes one definition every language consumes: Zod schemas inpetrinaut-core, published by the CLI as JSON Schema, generated into pydantic models for Python.The repository already generates types across this boundary in the other direction (pydantic →
openapi.json→@local/petrinaut-optimizer-client). This is that pipeline reversed, with the same regenerate-and-diff guard.Sits on FE-1467 (#9261) in stack #9269; FE-1412 (#9229) is above.
🔗 Related links
🔍 What does this change?
petrinaut-core: the response shapes become Zod schemas (petrinautOptimizationDescribeParameterSchema,…DescribeResultSchema,…ReplicateSchema,…EvaluateResultSchema). The existing types are nowz.inferof them and are unchanged; 880 core and 39 CLI tests pass untouched. Patch changeset.petrinaut-cli: acodegentask writes the checked-inschemas/optimization-protocol.schema.json, piped through oxfmt so formatting and regeneration agree. CI regenerates and diffs it.petrinaut-python:codegenrunsdatamodel-codegenover that document intosrc/petrinaut/models.py.describe()returnsOptimizationDescribeResultandevaluate()returnsOptimizationEvaluateResult, validated in JSON-strict mode: a string where a number belongs is a contract bug and raisesPetrinautProtocolError, while enums still parse from their wire strings.py.typedis added — without it no checker trusted the package's annotations. pydantic becomes the package's first runtime dependency.petrinaut-opt:_parse_descriptionkeeps only the semantic rules a schema cannot express (bound ordering, log-scale domains, duplicates, study limits); the shape validation moves to the models. pydantic is declared directly instead of arriving through FastAPI.typeCheckingMode = "strict". After the typed rewrite, strict reported 7 errors (41 on the old code); all fixed.Review fixes. The describe response schema declares what the domain already guarantees: an int parameter's
minimum/maximum/stepare integers, andstudy.seedsPerTrialis bounded to the CLI's 1–100 range, so the generated models carry those constraints.PetrinautSession's docstring states the validation seam: model-protocol methods return the CLI's JSON verbatim as dicts, optimization methods return schema-validated models. The README example readsfull.replicatesby attribute, matching the model. The Docker image builds only the bindings' dependency closure (--filter '@local/petrinaut-python^...'): the bindings themselves have nothing to build in the image, and their codegen needs uv, which the Node-only builder stage lacks — this is what fixed the stack's Docker build.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
turbo.json's have been updated to reflect this:petrinaut-cli#codegen(outputsschemas/**) andpetrinaut-python#codegen(outputsmodels.py, depends on the CLI's codegen).🛡 What tests cover this?
Infinitythat Python's lax JSON parser admits stays a recoverablePetrinautRunError. The two e2e tests drive the built CLI through the typed methods.❓ How to test this?
turbo run codegen --filter @local/petrinaut-pythonthengit diff --exit-codepetrinaut-core/src/optimization.tsand rerun step 1: the diff is what Python consumers see.🤖 Generated with Claude Code