Repository navigation
chore: move to Python 3.14, with the sandbox fixes it needs - #12247
Merged
Merged
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 23, 2026
Fable's review of #12247: PEP 734 subinterpreters escape the audit hook. A hook added with sys.addaudithook belongs to one interpreter, and creating, entering or running code in another raises no event the guard sees; armed PlotGuard on 3.14, `concurrent.interpreters.create().exec(...)` wrote a file outside the outputs with exit 0. The validator's import denylist was the only barrier. - harness: `_interpreters.create`, `exec`, `run_string`, `run_func` and `call` join UNAUDITED_CALLS and are replaced with `refused_call` before arming (`_interpreters` is imported then if it is not loaded). `concurrent.interpreters` and `InterpreterPoolExecutor` reach them only through the module object, so both are covered. - validator: `concurrent`, `interpreters`, `InterpreterPoolExecutor`, `_interpreters`, `_interpqueues` and `_interpchannels` join ESCAPE_ATTRIBUTES. - tests: `_interpreters.exec`, `concurrent.interpreters` exec and `InterpreterPoolExecutor` routes are refused in plot mode (the last two skip below 3.14), and creating a subinterpreter fails a data run. Verified the route writes its file with the previous harness and is refused with this one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 23, 2026
Gemini Flash's first review of #12247: `load` and `from_stdin` return `cls(...)`, so `Self | None` (PEP 673) is the subclass-safe annotation instead of naming the class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 23, 2026
The anti-pattern line still named the retired default.py layout and Python 3.10-3.13 (Fable's review of #12247). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Sandbox security boundaries and production runtime changes require final human review and confirmation that CI passes on the final head.
No open issues 🎉
What changed in this PR
Moves anyplot’s services, development environment, and automation to Python 3.14, with sandbox changes needed to preserve rendering compatibility and isolation.
Changes:
- Updates the Python floor, interpreter pin, service images, workflow runtimes, and documentation.
- Narrows ctypes allowances and blocks remote debugging, subinterpreter execution, and additional annotation-evaluation routes.
- Adds sandbox regression coverage and adopts Python 3.14 exception syntax.
| File | Description |
|---|---|
| tests/unit/agents/test_image.py | Updates expected Docker stages to Python 3.14. |
| tests/unit/agents/runtime/test_render.py | Checks sandbox flags across local execution modes. |
| tests/unit/agents/runtime/test_harness_guard.py | Adds ctypes, remote-debugging, and subinterpreter regression tests. |
| tests/unit/agents/renderer/test_wire.py | Pins interpreter-flag parity between components. |
| tests/unit/agents/renderer/test_executor.py | Checks remote-debugging flags in renderer commands. |
| tests/unit/agents/evals/test_host_data.py | Checks host-data interpreter flags. |
| tests/unit/agents/data/test_warning_codes.py | Adopts Python 3.14 exception syntax. |
| tests/unit/agents/code/test_validate.py | Tests rejection of annotation-evaluation routes. |
| tests/image/convert_smoke.py | Disables remote debugging during conversion smoke tests. |
| README.md | Updates the documented Python requirement. |
| pyproject.toml | Raises Python and mypy targets; clarifies coverage behavior. |
| prompts/templates/metadata.yaml | Updates the Python-version example comment. |
| prompts/plot-generator.md | Updates Python versions in generated-header examples. |
| docs/reference/repository.md | Updates Python-version examples. |
| docs/reference/database.md | Updates a schema example comment. |
| docs/development.md | Updates prerequisites and coverage guidance. |
| docs/concepts/agent-network.md | Documents Python 3.14 and sandbox launch flags. |
| core/database/models.py | Updates runtime-version comments only. |
| core/constants.py | Updates the registered Python runtime version. |
| changelog.d/python-3.14.md | Records the runtime migration and sandbox hardening. |
| automation/scripts/sync_to_postgres.py | Adopts Python 3.14 exception syntax. |
| automation/scripts/review_retest.py | Adopts Python 3.14 exception syntax. |
| automation/scripts/review_provenance.py | Adopts Python 3.14 exception syntax. |
| automation/scripts/regen_gate.py | Adopts Python 3.14 exception syntax. |
| api/routers/insights.py | Adopts Python 3.14 exception syntax. |
| api/routers/agent.py | Adopts Python 3.14 exception syntax. |
| api/Dockerfile | Migrates build and runtime stages to Python 3.14. |
| agents/renderer/wire.py | Adopts Python 3.14 exception syntax. |
| agents/renderer/executor.py | Adds sandbox interpreter flags and updates exception syntax. |
| agents/renderer/Dockerfile | Updates Python stages, interpreter path, and pyarrow path. |
| agents/renderer/auth.py | Adopts Python 3.14 exception syntax. |
| agents/main.py | Adopts Python 3.14 exception syntax. |
| agents/evals/report.py | Adopts Python 3.14 exception syntax. |
| agents/evals/prep.py | Adopts Python 3.14 exception syntax. |
| agents/evals/matrix.py | Adopts Python 3.14 exception syntax. |
| agents/evals/host_data.py | Uses shared sandbox interpreter flags. |
| agents/Dockerfile | Migrates agents-service stages to Python 3.14. |
| agents/anyplot/tools/session.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/sub_agents/adapter.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/render/runtimes/python.py | Applies sandbox flags to plot, data, and conversion commands. |
| agents/anyplot/render/harness.py | Restricts ctypes allowances and blocks new execution routes. |
| agents/anyplot/render/gates.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/render/convert.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/render/backends/remote.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/render/backends/local.py | Updates command documentation and exception syntax. |
| agents/anyplot/plugins/ledger.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/dataset_context.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/data/prepare.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/data/parse.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/data/generate.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/code/validate.py | Bans annotation-evaluation and subinterpreter access names. |
| agents/anyplot/code/regions.py | Adopts Python 3.14 exception syntax. |
| agents/anyplot/code/data_code.py | Adopts Python 3.14 exception syntax. |
| agentic/workflows/modules/state.py | Uses Self | None for classmethod return types. |
| agentic/workflows/modules/regen/picker.py | Adopts Python 3.14 exception syntax. |
| agentic/workflows/modules/regen/metadata.py | Updates the interpreter-output example comment. |
| agentic/docs/project-guide.md | Updates runtime guidance and implementation-layout documentation. |
| agentic/commands/prime.md | Updates the documented Python baseline. |
| agentic/commands/audit/backend-auditor.md | Updates Python modernization guidance. |
| agentic/commands/audit.md | Updates Python examples in audit criteria. |
| .python-version | Pins Python 3.14.9. |
| .github/workflows/sync-postgres.yml | Selects Python 3.14 for database synchronization. |
| .github/workflows/impl-repair.yml | Selects Python 3.14 for implementation repair. |
| .github/workflows/impl-generate.yml | Updates Python selection and its metadata comment. |
| .github/workflows/daily-regen.yml | Selects Python 3.14 for daily regeneration. |
| .github/workflows/ci-tests.yml | Selects Python 3.14 for tests. |
| .github/workflows/ci-migrations.yml | Selects Python 3.14 for migration checks. |
| .github/workflows/ci-lint.yml | Selects Python 3.14 for lint checks. |
| .github/workflows/ci-image.yml | Updates sandbox smoke commands and the pyarrow inspection path. |
| .github/workflows/ci-changelog.yml | Selects Python 3.14 for changelog checks. |
| .github/copilot-instructions.md | Updates documented runtime and header examples. |
| .claude/skills/zweitmeinung/gemini_review.py | Adopts Python 3.14 exception syntax. |
| .claude/skills/verify-migrations/SKILL.md | Updates the separate-interpreter explanation. |
| .claude/skills/review-regen-week/collect_week.py | Adopts Python 3.14 exception syntax. |
Review effort: Balanced
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 23, 2026
CI's Run Tests on #12247 failed test_local_backend_kills_the_container_on_ cancellation once: after a fixed 0.5 s sleep only the light theme's container had started on the loaded runner (coverage, four workers), so only one kill was recorded. The fake docker now notes each run and the test cancels once both themes run (up to 10 s), which is what it asserts on. 12 concurrent local runs pass before and after; the race is the runner's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 23, 2026
…llback codecov/patch/backend on #12247 listed three new harness lines that only a subprocess harness run reached (coverage measures the test process): the `_interpreters` branch of `disable_unaudited_calls` and the ImportError fallback of `ctypes_helper_addresses`. Both now run in-process without arming a hook: one test makes `_ctypes` unimportable, the other imports a fresh `_interpreters` with the original's attributes registered for restore and checks all five calls are replaced and refuse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 23, 2026
The local stack test of #12247: three workflow paths ran repository Python on the runner image's own python3 without setup-python, and the code they import now uses PEP 758 `except A, B:`, which does not parse before 3.14 (reproduced as a SyntaxError under 3.12). They worked only because ubuntu-latest ships 3.14.4 today. - ci-tests: the existing setup-python step moves ahead of the spec characteristic check and runs on every event (spec_characteristics_lint imports regen_gate). - impl-merge writeback: setup-python after checkout (regen_writeback imports regen_gate). - review-retest prep, review and aggregate: setup-python after checkout (review_retest, review_provenance, regen_gate). All read `.python-version` (3.14.9, in setup-python's manifest for ubuntu-26.04). close_issue_if_complete and spec_pr_guard reach no 3.14-only module and stay as they are. Docs: agents/README.md and the changelog fragment tell developers to rebuild a local anyplot-renderer:dev image, because the local renderer mounts the checkout's harness, which no longer parses on a 3.13 image. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
3.13 receives security fixes only since October 2026; 3.14 gets bug fixes until October 2027. 3.15 waits for a lets-plot cp315 wheel. - requires-python >=3.14, mypy python_version 3.14, .python-version 3.14.9 (the newest patch current uv offers; CI's uv downloads exactly this patch) - uv.lock regenerated with the latest uv (keeps lock revision 5); the only version change is google-crc32c 1.7.1 -> 1.9.0, because 1.7.1 publishes no cp314 wheel and the images carry no compiler - python:3.14-slim and UV_PYTHON=/usr/local/bin/python3.14 in the api, agents and renderer images; the renderer's pyarrow slimming path and the CI image job's du path follow; test_image.py asserts the new stage names - python-version '3.14' in all eight setup-python steps - languages table runtime_version 3.14 (core/constants.py) - coverage comment: sysmon is coverage's default on 3.14 and measures branches there; branch stays off because it changes the reported numbers - harness docstrings re-verified on 3.14.9 (os.stat family, os.environ, __globals__, mkfifo/mknod, signal.signal still raise no event; _posixsubprocess.fork_exec now raises one and stays replaced) - agentic/workflows/modules/state.py: Optional["WorkflowState"] becomes WorkflowState | None (ruff's py314 target flags UP045; PEP 649 defers the annotation, so no quotes are needed) - docs, prompts and skill text that state the current version Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…P 758) ruff infers its target from requires-python, so with >=3.14 its formatter writes `except A, B:` instead of `except (A, B):` (PEP 758, new in 3.14). 63 lines in 32 files, nothing else. Every place this code runs is 3.14: the project venv, the three images, and the runner's system python3 (ubuntu-latest = 26.04 ships 3.14.4) that review_retest and the spec lint use before setup-python. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Python 3.14 raises the audit event `ctypes.call_function` for every call through a ctypes function pointer, ctypes' own `cast`, `memmove` and `memset` included; 3.13 raised none for them. scipy's `_lib._ccallback_c` calls `ctypes.cast` while it is imported, so PlotGuard refused every plot that imports scipy, statsmodels or scikit-learn (six harness tests and the renderer image's seaborn render failed). Every guard now lets `ctypes.call_function` through only for the three helper addresses (`CTYPES_HELPERS`, read from `_ctypes` before arming), which is 3.13's behaviour; `string_at`, `wstring_at`, `memoryview_at` and every other foreign function stay refused. A unit test per guard pins it. Two more 3.14-specific gaps closed: - PEP 768 remote debugging: the sandbox interpreter runs with `-X disable-remote-debug` (`harness.INTERPRETER_FLAGS`, mirrored in the renderer executor and pinned by test_wire), and both guards refuse `sys.remote_exec` and `cpython.remote_debugger_script` for a harness started without it. - validator: 3.14's `dataclasses` re-exports `annotationlib`, whose `get_annotations(eval_str=True)` evaluates annotation strings, and `typing.evaluate_forward_ref` is new; both passed the static validator. The five names join ESCAPE_ATTRIBUTES, the stdlib walk note records the 3.14.9 re-walk, and refused fixtures pin both routes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Fable review of the first draft found the three-address allowlist broader than needed: it let plot code call ctypes.memmove, memset and cast directly, a write primitive that 3.14's new `ctypes.call_function` event had closed, and the data and convert guards gained it for no legitimate use (data mode imports scipy before arming; neither mode imports after). - PlotGuard only: `ctypes.call_function` passes only for `_ctypes._cast_addr` (`IMPORT_CTYPES_HELPERS`) and only when the caller's nearest origin is an import (`calls_import_ctypes_helper`), the rule exec and frames follow. scipy's import-time cast is the only helper call any allowed library makes at import (Fable's audit-hook survey). - DataGuard and ConvertGuard refuse every foreign function again. - Tests: the data and convert guards refuse all helpers; the plot guard lets only an import's cast through; cast, memmove and memset from plot code and cast and memmove from a data snippet are refused end to end. - A sweep of all 650 catalogue matplotlib and seaborn files under the narrowed guard: 603 render, no ctypes refusal; the 47 failures fail identically on 3.13.12 with main's harness. - backends/local.py docstring shows the sandbox flags. - changelog fragment: a Security entry for the sandbox change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
Fable's review of #12247: PEP 734 subinterpreters escape the audit hook. A hook added with sys.addaudithook belongs to one interpreter, and creating, entering or running code in another raises no event the guard sees; armed PlotGuard on 3.14, `concurrent.interpreters.create().exec(...)` wrote a file outside the outputs with exit 0. The validator's import denylist was the only barrier. - harness: `_interpreters.create`, `exec`, `run_string`, `run_func` and `call` join UNAUDITED_CALLS and are replaced with `refused_call` before arming (`_interpreters` is imported then if it is not loaded). `concurrent.interpreters` and `InterpreterPoolExecutor` reach them only through the module object, so both are covered. - validator: `concurrent`, `interpreters`, `InterpreterPoolExecutor`, `_interpreters`, `_interpqueues` and `_interpchannels` join ESCAPE_ATTRIBUTES. - tests: `_interpreters.exec`, `concurrent.interpreters` exec and `InterpreterPoolExecutor` routes are refused in plot mode (the last two skip below 3.14), and creating a subinterpreter fails a data run. Verified the route writes its file with the previous harness and is refused with this one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
Gemini Flash's first review of #12247: `load` and `from_stdin` return `cls(...)`, so `Self | None` (PEP 673) is the subclass-safe annotation instead of naming the class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
…ragment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
The anti-pattern line still named the retired default.py layout and Python 3.10-3.13 (Fable's review of #12247). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
CI's Run Tests on #12247 failed test_local_backend_kills_the_container_on_ cancellation once: after a fixed 0.5 s sleep only the light theme's container had started on the loaded runner (coverage, four workers), so only one kill was recorded. The fake docker now notes each run and the test cancels once both themes run (up to 10 s), which is what it asserts on. 12 concurrent local runs pass before and after; the race is the runner's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
…llback codecov/patch/backend on #12247 listed three new harness lines that only a subprocess harness run reached (coverage measures the test process): the `_interpreters` branch of `disable_unaudited_calls` and the ImportError fallback of `ctypes_helper_addresses`. Both now run in-process without arming a hook: one test makes `_ctypes` unimportable, the other imports a fresh `_interpreters` with the original's attributes registered for restore and checks all five calls are replaced and refuse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
The local stack test of #12247: three workflow paths ran repository Python on the runner image's own python3 without setup-python, and the code they import now uses PEP 758 `except A, B:`, which does not parse before 3.14 (reproduced as a SyntaxError under 3.12). They worked only because ubuntu-latest ships 3.14.4 today. - ci-tests: the existing setup-python step moves ahead of the spec characteristic check and runs on every event (spec_characteristics_lint imports regen_gate). - impl-merge writeback: setup-python after checkout (regen_writeback imports regen_gate). - review-retest prep, review and aggregate: setup-python after checkout (review_retest, review_provenance, regen_gate). All read `.python-version` (3.14.9, in setup-python's manifest for ubuntu-26.04). close_issue_if_complete and spec_pr_guard reach no 3.14-only module and stay as they are. Docs: agents/README.md and the changelog fragment tell developers to rebuild a local anyplot-renderer:dev image, because the local renderer mounts the checkout's harness, which no longer parses on a 3.13 image. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
…d of uv The step now runs on setup-python's 3.14 (regen_gate's PEP 758 syntax); the test asserted the old order and failed on 658fd77. It now pins the new order and keeps the real invariant: no uv, no project install first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175hA62juZoajMcqKxWFGza
MarkusNeusinger
force-pushed
the
chore/python-3.14
branch
from
October 23, 2026 10:08
9f67e15 to
aa10dc8
Compare
This was referenced Oct 23, 2026
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 23, 2026
## Summary - PR 2 of the adaptable-code rollout ([design](https://github.com/MarkusNeusinger/anyplot/blob/main/docs/concepts/adaptable-code.md), "Rollout" item 2; PR 1 was #12244): the Data block convention in the generator and in all 15 library prompts, a Python self-check (Step 3c) in generation and repair, a record-only smoke run and predecessor render in `impl-generate.yml` and `impl-repair.yml`, a record-only adaptability check in `impl-review.yml`, and the identity-based adapt route plus the adapt regression in the regen gate. - Score-neutral: the review prompt and the rubric are unchanged, every new step is record-only (never a raw `exit 1`), and the review-free path stays off behind the repository variable `ADAPT_REVIEW_FREE`. - Checks are Python only (the 8 Python libraries). R, Julia and JavaScript get the convention in their first-generation prompts and keep today's regeneration mindset; every record carries `language`, so their checkers plug in later without format changes (the "all 15 libraries" goal). ## Plan Design: `docs/concepts/adaptable-code.md` ("The convention", "Other libraries", "The checks", "Pipeline changes", "The adapt route", "Rollout" item 2). Four parts were built against one interface contract and merged here with `--no-ff`; the merge commits keep each part's history. ### A, prompts (`eb3d074fa`) - New `prompts/adaptable-code.md` holds the convention once: the six zones, the `# Data`/`# Plot` markers and `# role:` grammar, the plot-code rules, the data shapes, the bar-grouped example, and the R, Julia and JavaScript forms. `plot-generator.md` and Step 1 of `impl-generate-claude.md` reference it. - `plot-generator.md`: the template, Structure and Data Generation Strategy follow the six zones; Data storytelling gets rule-chosen callout targets; `bbox_inches='tight'` is gone. `default-style-guide.md`: "list all eight hues and use the first n", no `bbox_inches='tight'`. - All 15 library prompts get a "Data block" section; the `[:N]` palette slices are gone from matplotlib and seaborn; Chart.js no longer recycles colours with `i % length`. - `impl-generate-claude.md`: Step 3c (`python -m core.smoke_run selfcheck`, at most three attempts, never blocking), the commit line `Adaptability self-check: pass|fail (...), attempt N`, the five regen-mindset amendments phrased "For Python (`LANGUAGE=python`)", a "Converting a Python file" bullet that aims at an identical render. `impl-repair-claude.md`: self-check lines are real defects; Step 5b reruns the self-check. ### B, generation side (`416065feb`, `3425ac00b`) - `core/smoke_run.py` (`run`, `pair`, `selfcheck`): one process per perturbed table and theme, process groups killed on timeout, errors mapped back to the original line, a stale-label hook for matplotlib and seaborn, and `pair`, which renders the new source and then the predecessor in one environment and compares the raw PNGs. - `core/perturbation.py`: `missing_number` (NaN in every `float64` column, binding) and `missing_nullable` (`<NA>` in `Int64`, `boolean` and `string` columns, advisory). - `impl-generate.yml` "Adaptability smoke run (record-only)" and the private `adapt/` staging in "Upload to GCS Staging"; `impl-repair.yml` reruns the smoke run and replaces or removes `adapt/new-smoke.json`. ### C, review side (`4b710e2c0`) - `impl-review.yml` "Adaptability check (record-only)": the workflow-ref checker mirror on the PR file and, on a regeneration, its predecessor; `automation/scripts/adapt_record.py` builds `/tmp/anyplot-adapt-{new,prev}.json`; `regen_gate.py adapt-route` before the review; the review is skipped only when `ADAPT_REVIEW_FREE` is `true` and every condition holds. - Review-free wiring, the `regen:adapt` count label, carried metadata (`review.carried`), and the staging download narrowed to `plot-*` so `adapt/` never reaches the blind reviewer. `impl-merge.yml` promotes only `plot-*`. `regen_writeback.py` removes `review.carried` on a write-back. ### D, gate (`0438a60a4`) - `adapt_assess` with the contract's 20 ordered conditions; `adapt_identity` replaces the keeps `no_visible_improvement`, `no_defect_improvement` and `below_tolerance`; `--review-skipped` merges a counted route before the score checks; `adapt_regression` turns any merge into keep. The record gains an `adapt` block (`RECORD_VERSION` stays 1), the outputs and notice gain `adapt_counted` and `adapt_why`. The gate stays stdlib-only and reads identity from impl-generate's `pair.json`. ### Integration (`db865896b`) - `impl-review.yml` gets one unconditional "Set up Python" step (`python-version-file: .python-version`) before the first regen-tools call; part C's mirror-only step is gone and every tool runs on plain `python3`. Since #12247 the gate, `review_provenance.py` and the mirror use PEP 758 `except A, B:`, and the runner image's Python is not pinned. - The Step 3c log line gains `smoke_why`, so the adapt record counts a skipped first attempt as a pass only for a shape skip, as `selfcheck`'s own exit 0 does. - Docs: the pipeline diagram and a table of the adaptability steps (`docs/workflows/overview.md`), `adapt_record.py` (`automation/scripts/README.md`), `core/smoke_run.py` (`docs/reference/repository.md`). ### Review fixes (`05ec4a82b`, `602dd40c5`, `4fd7f7c57`) - Copilot: an adapt record without valid `static` and `smoke` blocks is `record_invalid` (fail closed); the Chart.js and ECharts Data block examples run as written; the "ninth group fails loudly" rule names the idioms it holds for (lookup by position or group) in `prompts/adaptable-code.md` and `prompts/default-style-guide.md`. - Gemini and part D's own note: the route needs a `pair.json` rendered from the very sources the records checked (`code_sha256` on both sides), else the new reason token `pair_stale`. ### Second opinion (Gemini 4 Argon + Fable, `1e75be40a`..`a423e7b69`) - The gate rejects a record whose `status` does not follow from its `static` and `smoke` blocks (`record_invalid`), and `pair.json` must name its `library`. - A predecessor whose static check passed against a new source whose static check fails is an adapt regression even without a fresh predecessor smoke run. - `decide --adapt-route-held`, the record field `adapt.route_pre_review`, the output `adapt_overruled` and the label `regen:adapt-overruled` make the review's overrules of the route countable (O1). - The post-score re-dispatch also covers the review-free path, with the stored score. - `adapt/` is staged with `gsutil rsync -r` so `new/` and `prev/` keep their names. - `execution_env` drops `GITHUB_*`, `ACTIONS_*`, `RUNNER_*` and `GH_*` before running an implementation. - An empty perturbation set has its own token `no_perturbations:<shape>`, read the same way by `selfcheck` and the record. - One palette name, `IMPRINT_PALETTE`, in the convention and its example; the `IMPRINT` alias is stated once. - Known gap, unchanged: the early verdict label lands before "Update metadata and implementation header"; a metadata push failure after it is left to the watchdog (pre-existing on the normal path). ## Test plan - [x] `uv run ruff check .` and `uv run ruff format --check .`: clean. - [x] `uv run --extra typecheck --extra agents mypy api core agents`: no issues in 151 source files. - [x] `uv run pytest tests/unit tests/integration -n 4`: 13158 passed, 3 skipped (seaborn is not installed locally). That is 443 more tests than `main`: `test_smoke_run.py` 34, `test_adapt_generate_steps.py` 36, `test_adapt_review_steps.py` 85, `test_adapt_record.py` 66, plus additions to `test_regen_gate.py`, `test_perturbation.py`, `test_regen_writeback.py` and `test_prompts.py`. - [x] `uv run python -m tools.changelog check --base origin/main`: passes. - [x] actionlint 1.7.12 with shellcheck 0.11.0 on the four changed workflows: 87 findings on `main` and on this branch, the same set (only line offsets in impl-generate's upload step moved). - [x] End to end on one real pair outside the repository: bar-grouped matplotlib, a predecessor with a three-hue palette (static rule 5 fails) against the converted fixture. Part B's `run`, `pair` and `selfcheck` fed part C's mirror and `adapt_record.py`, which fed part D's `adapt-route` and `decide --review-skipped`: `route=true`, merge `adapt_identity`, identity 0.0000 in both themes, a valid gate record. The new side's smoke run passed all 7 binding tables; `missing_nullable` failed as advisory (`float() argument ... 'NAType'` in `ax.bar`). - [ ] After the merge: the first Python regeneration's `adapt_smoke`, `adapt_check`, `adapt_route`, `adapt_review` and `regen_gate ... adapt_*` notices, and the `adapt/` folder of its `regen-pair` artifact. ## Known gaps Workflow changes have no branch run: claude-code-action refuses to run on a branch whose PR changes its own workflows, so the new steps are first exercised on `main`. Locally they are covered by unit tests that run the exact step scripts against throwaway repositories with fake `gsutil` and `gh`, under `bash -eo pipefail`. - **First real runs.** The MUI X pilot of 8 forced regenerations is the first real run of the new prompts, and the lead watches it. MUI X is JavaScript and `regen:forced`, so it exercises the convention text, not the Python steps; the first Python regeneration after the merge is the first run of the smoke run, the record-only check and the adapt route. The daily regeneration stays paused until this merges ("Rollout" step 3). - **Not verified until a real run:** `gsutil rm -r` on a missing prefix, `rsync -r` of `adapt/` to staging, `cp -r` back in impl-review, and whether bucket-level IAM keeps `adapt/` private; impl-merge's non-recursive `cp "${STAGING}/plot-*"`; the runtime of the Chrome-driven libraries (bokeh, plotly) inside the 20-minute step timeout (estimate 1 to 3 minutes); the seaborn, plotly, altair, plotnine, bokeh, pygal and letsplot paths against the runner (matplotlib ran locally); `setup-python` with `.python-version` in impl-review; `vars.ADAPT_REVIEW_FREE` resolution; the review-free path end to end. - **Runner exposure.** The smoke executions run model-written code again after "Create library metadata file" has the workflow token in `.git/config`, the same exposure as the generator's own Step 3 renders. The runner is not an isolation boundary; the protections are the workflow-ref copy of `core/`, the new-then-predecessor render order, and the owner-controlled switch. - **Missing-value coverage.** The loader cast turns integer columns into `Int64`, so an integer-valued sample (the design's own example) gets no binding NaN table, only the advisory `<NA>` one. ## Open decisions for the owner - **O1, review-free switch.** It stays off. The evidence is the route-held PRs. `regen:adapt` marks a merge on which the adapt route held before and through the review. `regen:adapt-overruled` marks a PR whose route held before the review and whose review floor then turned it off, on a merge or a keep; its gate record has `adapt.route_pre_review: true`. Turn the switch on (`gh variable set ADAPT_REVIEW_FREE --body true`) only when the overruled share stays near zero over at least 50 route-held PRs: ``` gh pr list --state merged --label regen:adapt --limit 1000 --json number --jq length gh pr list --state all --label regen:adapt-overruled --limit 1000 --json number --jq length ``` The alternative is an automatic flip once 50 route-held PRs show no overruled one. - **O2, nullable integers.** `missing_nullable` stays advisory: in part A's idiom run it failed 5 of the 8 Python libraries, and it fails the design's reference example. Either the convention allows `.astype(float)` on drawn nullable-integer columns, or the loader changes. - **Review-retest arm.** This PR edits `prompts/default-style-guide.md` (two code-only sentences) and adds a "Data block" section to every library prompt, and the reviewer reads both. `CLAUDE.md` asks for a candidate `review-retest.yml` arm on such edits; the design says none is needed, because no criterion, rubric line or review-prompt line changes. No arm was dispatched; dispatch one if you want the measurement before merging. - **Local helpers.** `/regen` and `/update` list `prompts/adaptable-code.md` but have no Step 3c, so a local regeneration is not self-checked. --------- Co-authored-by: Claude Opus 5.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.
Summary
requires-python,.python-version(3.14.9), mypy, all eightsetup-pythonsteps, the three service images (python:3.14-slim) and the languages table's runtime version. 3.13 receives security fixes only since October 2026; 3.14 gets bug fixes until October 2027; 3.15 waits for a lets-plot cp315 wheel.google-crc32c1.7.1 → 1.9.0, because 1.7.1 publishes no cp314 wheel and the images carry no compiler.requires-python, so its formatter now writes PEP 758except A, B:(63 lines in 32 files, a commit of its own).Sandbox change (renderer harness and validator)
What broke. CPython 3.14 raises the audit event
ctypes.call_functionfor every call through a ctypes function pointer, ctypes' owncast,memmoveandmemsetincluded; 3.13 raised none for those three. scipy's_lib._ccallback_ccallsctypes.castwhile it is imported.PlotGuardrefuses everyctypes.*event outside threadpoolctl, so on 3.14 every plot importing scipy, statsmodels, scikit-learn or seaborn failed: six harness tests and the renderer image's own seaborn render inci-image.yml.The fix, as shipped.
PlotGuardletsctypes.call_functionthrough only for_ctypes._cast_addr(IMPORT_CTYPES_HELPERS) and only when the caller's nearest origin is an import (calls_import_ctypes_helper), the ruleexecand frames already follow.DataGuardandConvertGuardallow no foreign function at all.Fable review: "sound with changes". The first draft let
cast,memmoveandmemsetthrough in all three guards, by address alone. Fable showed it handed plot code a raw in-process write primitive (ctypes.memmove/memsetcalled directly) that 3.14's event had closed, and that the data and convert guards gained it for no legitimate use. All three narrowings were adopted:_cast_addronly: an audit-hook survey over the imports of numpy, pandas, matplotlib, scipy, scikit-learn and statsmodels foundcastas the only helper any of them calls at import, fromscipy/_lib/_ccallback.py.<frozen importlib._bootstrap>(Cython module init has no Python frame), sonearest_originis "import"; the same cast from plot code is "plot" and stays refused.PlotGuardonly: data mode imports scipy before it arms, and neither data nor convert mode imports after.Fable's open check, whether a library casts at render time from a plot-origin frame, is answered by a sweep: all 650 catalogue matplotlib and seaborn files, normalised as a session opening does, rendered under the narrowed guard with the sandbox flags. 603 render and none is refused for
ctypes. The 47 failures (refused writes andos.mkdir, network fetches, missing qrcode and adjustText, plots that divide by an emptied title) fail identically on Python 3.13.12 with main's harness.Two more 3.14-specific hardenings, in the same commits:
-X disable-remote-debug(harness.INTERPRETER_FLAGS, duplicated inagents/renderer/executor.pyand pinned bytest_wire;-IignoresPYTHON_DISABLE_REMOTE_DEBUG), and both guards refusesys.remote_execandcpython.remote_debugger_scriptfor a harness started without the flag. Verified:sys.remote_execagainst the interpreter's own pid works on plain 3.14.9 and fails with "Remote debugging is not enabled" under the flag.dataclassesre-exportsannotationlib, andfrom dataclasses import annotationlibplusget_annotations(f, eval_str=True)evaluated an arbitrary annotation string past the static validator (both runtime guards still refused it).annotationlib,get_annotations,call_evaluate_function,call_annotate_functionandevaluate_forward_refjoinedESCAPE_ATTRIBUTES; the stdlib walk note records the 3.14.9 re-walk; refused fixtures pin both routes.Fable's second review (of this PR): "sound with one change". PEP 734 subinterpreters escape the audit hook: a hook added with
sys.addaudithookbelongs to one interpreter, and creating, entering or running code in another raises no event the guard sees. WithPlotGuardarmed on 3.14,concurrent.interpreters.create().exec(...)wrote a file outside the outputs and the render exited 0; only the validator's import denylist stood in the way. Fixed in this PR:_interpreters.create,exec,run_string,run_funcandcalljoinUNAUDITED_CALLSand are replaced withrefused_callbefore arming (concurrent.interpretersandInterpreterPoolExecutorreach them only through the module object), andconcurrent,interpreters,InterpreterPoolExecutor,_interpreters,_interpqueuesand_interpchannelsjoinESCAPE_ATTRIBUTES. Tests pin the three plot routes and a data run; the same plot writes its file with the previous harness and is refused with this one. Everything else Fable checked is sound: the import-origin_cast_addrallowance cannot be smuggled (frames cannot be forged,-Ikeeps the working directory offsys.path, writes are refused, ctypes attribute names are banned), only scipy raisesctypes.call_functionat import,sys.remote_execis covered three ways, and pathlib's newPath.copy/movedecompose into audited calls.Re-verified harness claims on 3.14.9 (side-by-side probe against 3.13.12, plus the 3.14 audit-event table):
os.statand its family,os.environ/os.getenv,function.__globals__,os.mkfifo,os.mknod,signal.signaland an import of a loaded module still raise no event._posixsubprocess.fork_execnow raises one; it stays replaced. New 3.14 events:_posixsubprocess.fork_exec,cpython.PyConfig_Set,cpython.remote_debugger_script,ctypes.memoryview_at,sys.remote_exec.Reviews
WorkflowState.loadandfrom_stdinreturnSelf | Noneinstead of naming the class.requires-python = ">=3.13"ofautomation/scripts/canvas_gate_report.py. It and the one-offscripts/*.pyimport no project module (only_palette_commonfromscripts/, which the formatter does not touch) and all still parse on 3.13, so>=3.13stays true.docs/reference/api.md,mcp.mdanddatabase.md(left alone on purpose, see below).default.pylayout and Python 3.10-3.13; it now states the one-file-per-library rule on 3.14.executor.INTERPRETER_FLAGSduplicatesharness.INTERPRETER_FLAGSbecause the renderer image cannot import the agents package;test_wirealready pins them equal. The project-guide line is fixed (above).test_local_backend_kills_the_container_on_cancellationonce (only one of two containers had started 0.5 s in on the loaded runner). The test now cancels once both fake containers run instead of after a fixed sleep; 12 concurrent local runs passed before and after.Files changed, by category
.python-version,pyproject.toml(requires-python, mypy target, coverage comment),uv.lockapi/Dockerfile,agents/Dockerfile,agents/renderer/Dockerfile(base tag,UV_PYTHON, the pyarrow slimming path),tests/unit/agents/test_image.py(stage names)python-version: '3.14'in ci-changelog, ci-lint, ci-migrations, daily-regen, impl-generate, impl-repair and sync-postgres; ci-image's pyarrowdupath and harness commands (sandbox flags); the python_version comment in impl-generate. Newsetup-python(from.python-version) where repository Python ran on the runner's ownpython3: ci-tests (the existing step moves ahead of the spec check and runs on every event), impl-merge's write-back job, and review-retest's prep, review and aggregate jobsagents/anyplot/render/harness.py,agents/anyplot/render/runtimes/python.py,agents/renderer/executor.py,agents/evals/host_data.py,agents/anyplot/render/backends/local.py(docstring),agents/anyplot/code/validate.py,tests/image/convert_smoke.py, and their tests (test_harness_guard,test_validate,test_render,test_executor,test_wire,test_host_data)core/constants.pyruntime_version3.14 (sync-postgres seeds the languages table from it after merge)core/database/models.py,agentic/workflows/modules/regen/metadata.py,prompts/templates/metadata.yamlagentic/workflows/modules/state.py:Optional["WorkflowState"](UP045 under the py314 target) becomesSelf | Noneexcept (A, B):→except A, B:onlyREADME.md,docs/development.md,docs/concepts/agent-network.md,docs/reference/repository.md,docs/reference/database.md,agentic/docs/project-guide.md+.github/copilot-instructions.md(kept in sync),prompts/plot-generator.md,agentic/commands/prime.md,agentic/commands/audit.md,agentic/commands/audit/backend-auditor.md,.claude/skills/verify-migrations/SKILL.mdchangelog.d/python-3.14.md(Changed + Security)Left alone on purpose: test fixtures that carry "3.13" as sample data, the dated JSON response samples in
docs/reference/api.md,mcp.mdanddatabase.md, historical files (CHANGELOG.md,agentic/audits/, alembic revisions, the eval baseline's recordedpython), the PEP 723 headers of the one-offscripts/*.py(>=3.13still admits 3.14), and thehistory:example inprompts/templates/library-metadata.yaml.Decisions
python:3.14-slimresolves to today. CI's uv downloads exactly the.python-versionpatch (the last ci-tests run had setup-python 3.13.16 and tested on the pinned 3.13.12), so the pin is what CI tests on. A local uv 0.10.2 only knows up to 3.14.3 but finds an installed 3.14.9 (uv self updateis the clean fix).core = "sysmon"stays and is now only explicit (coverage's default on 3.14). sysmon measures branches on 3.14 (coverage 7.12, no "no-sysmon" fallback), butbranchstays off because it changes the reported numbers.ruff format --checkneeds it, andrequires-python >=3.14must stay because of it.python3any more. The local stack test found three paths that ran repository Python on it without setup-python: the ci-tests spec check (importsregen_gate), impl-merge's write-back (regen_writeback→regen_gate) and review-retest's three jobs (review_retest,review_provenance,regen_gate). They worked only because ubuntu-latest (26.04) ships 3.14.4; under 3.12 they raise SyntaxError. Each job now sets up Python from.python-version(3.14.9 is in setup-python's manifest for ubuntu-26.04).close_issue_if_complete.pyandspec_pr_guard.pyreach no 3.14-only module and stay on the runner'spython3.AGENT_RENDER_IMAGE, so ananyplot-renderer:devbuilt before this PR fails every render withSyntaxError: multiple exception types must be parenthesized.agents/README.mdand the changelog fragment say to rebuild it.Lock changes
Everything else keeps its version, plotting libraries included (the version-sources check of #12240 is unaffected). A scan of the lock for packages without a Linux x86_64 wheel usable on 3.14 finds only esprima and matplotlib-venn (pure-Python sdists,
plottingextra only, as before) and pywin32 (Windows only).Smoke renders on 3.14.9
The first five specs alphabetically for every Python library, both themes,
MPLBACKEND=Agg ANYPLOT_THEME=<theme> python -P <lib>.pyfrom a scratch directory with theplottingextra (impl-generate's command). Each cell is light / dark.80 of 80 PNGs written. Bokeh needs a working chromedriver on PATH (the local WSL one is a snap stub; CI runners ship one).
Images (local docker build)
agents/renderer/Dockerfile: builds on Debian 13 with CPython 3.14.9; pyarrow slimmed 167 MB → 99 MB; font-cache bake andcheck_fontspass. ci-image's steps: the seaborn render (3200×1800, probe written) and the multi-script render (no missing glyph) pass with the final harness; the convert smoke passes every format; no ADK;/statusreports 3.5.0; a render without a launcher is refused with 503. Before the harness fix the seaborn render failed in the image withctypes.call_function.api/Dockerfile: builds on 3.14.9; google-crc32c 1.9.0 installs from its wheel (C implementation);api.mainimports.agents/Dockerfile: not built locally; CI's image job builds it.Plan
N/A
Test plan
uv run ruff check .anduv run ruff format --check .uv run --extra typecheck mypy api core --prettyuv run python -m tools.changelog check --base origin/mainDATABASE_URL) on the final code: 12,302 passed, 0 failedcodecov/patch/backendstays red on purpose (accepted by the lead): the three new harness lines it flagged are now covered by in-process tests, and the 29 lines still missing are pre-existing error handlers that the PEP 758 formatter only rewrote (except (A, B):→except A, B:), which no test reached before this PR either.Unverified: workflows have no local verification loop. The
python-versionchanges and the new setup-python steps in impl-merge's write-back and review-retest are only observable on real pipeline runs; ci-tests exercises its own change on this PR./languageskeeps reporting Python 3.13 until sync-postgres runs on the nextplots/oralembic/push and seedsruntime_version3.14 fromcore/constants.py. The agents image was not built locally; CI's image job builds it.Noticed, not acted on: two deprecation warnings for later Python releases, google-genai's
typing._UnionGenericAlias(removed in 3.17) and uvloop 0.22.1'sasyncio.iscoroutinefunction(removed in 3.16). The local stack test (core, API parity with main on 61 routes, the agents service with four real runs, the sandbox guards, 24 catalogue renders, the frontend and all three images) passed.