Skip to content

feat(agents): serve plotly and bokeh in the agent chat (PB-2a) - #12490

Merged
MarkusNeusinger merged 8 commits into
mainfrom
feat/pb2-plotly-bokeh
Nov 1, 2026
Merged

MarkusNeusinger merged 8 commits into
mainfrom
feat/pb2-plotly-bokeh

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

Summary

  • plotly and bokeh are served in "Use with my data" for the admin. They render through PB-1's two-step figure mode: the plot writes its figure as JSON under the plot guard, and the JavaScript harness draws it in Chromium at the catalogue's canvas. All 15 catalogue libraries are now served.

    • The normaliser turns the catalogue's save block into one pinned JSON save.
    • The export swaps that save back for the library's normal PNG and HTML save (question 10, decided by the owner).
    • The adapter reads a measured section for each library: theme tokens, UTC dates, plot-language marks, no WebGL.
    • The eval fixtures cover both libraries: 120 cases plus 2 smoke cases.
  • Validator profiles for both libraries, with the owner's decisions on the surface survey:

    • no private names;
    • anything but None where a URL is expected;
    • every profile's URL rule also refuses //host;
    • bokeh tooltip templates only as the catalogue writes them, never {safe};
    • bokeh ** only of literal options;
    • no link in plotly text;
    • import-by-name, callback, CSS, tile and map models banned.
  • The renderer checks the figure JSON before the page draws it. render.mjs (figureMarkupProblem) refuses, as figure UnsafeMarkup:

    • bokeh: JavaScript, HTML, CSS and URL models, and any model outside the 97 the eligible catalogue uses;
    • bokeh: JavaScript callbacks set as properties, CSS that loads, MathJax's HTML commands, and tooltips outside the template rule;
    • plotly: any link, styles that load, and image or geojson URLs.

    The export is the same code on the same data, so what the render refuses never reaches the downloaded HTML. A plotly WebGL trace fails as draw NoWebGL before it draws.

Plan

agentic/specs/261024-agent-all-libraries.md, step 4: "What PB-2a built (2026-10-31)". That section has the table of deviations, the measurements and "Found for later". Question 10 is marked decided. The section "What JS-4c built" gains the MUI X eval matrix run.

Measurements

  • 13 served libraries: normalised code, SECURITY findings and readiness are identical to origin/main on all 4,213 catalogue files (0 changed).
  • Readiness:
    • plotly: 273 of 325 eligible (12 clean, 261 coupled, 52 blocked);
    • bokeh: 252 of 325 eligible (11 clean, 241 coupled, 73 blocked).
  • Catalogue sweep through the figure mode, both themes, image rebuilt from this branch:
    • plotly: 254 of 273 eligible files render on target;
    • bokeh: 245 of 252 render on target;
    • the remaining failures are all ZeroDivisionError (code that divides by the length of the emptied catalogue title; a follow-up is filed);
    • canvas is always 3200 x 1800 or 2400 x 2400;
    • the markup check refuses no eligible file.
  • Wall time per theme (container start included, under the sweep's load): plotly p50 3.6 s / p95 5.1 s, bokeh p50 3.4 s / p95 5.2 s.
  • End to end without a model: a 60-row semicolon/decimal-comma paste with gaps, a +01:00 timestamp and an integer past 2^53. It passes R1 to R3 in both themes at 3200 x 1800 for both libraries, and the export ends on the normal save.

Reviews

  • Fable, round 1 (on e210b0e). HIGH, measured end to end: tooltips built at run time (**{"tool" + "tips": s}, a dict filled by subscript, chr(60), SetValue on a button) ran script in the exported bokeh HTML. Fixed in two layers:
    • static: bokeh ** only of literal options, callback, CSS and MathML models banned;
    • runtime: the figure-JSON markup check, which is the barrier.
    • MEDIUM: URL contexts admitted built strings. Now only None or a bool, plus flat update keys like "images[0].source".
    • LOW: CSS models and the save's file handle, both fixed.
  • Fable, round 2 (on 4871f1c). Sound, no new escape. Follow-ups:
    • MEDIUM: the image gate. Fixed by rebuilding the image, adding a CI step and an image test with the image's own harness; confirmed the deploy-renderer trigger covers automation/js-render/**.
    • LOW: a bokeh model allowlist under the denylist (97 names).
    • LOW: no plotly links at all, since no eligible catalogue file writes one.
  • Gemini 3.8 Flash.
    • WebGL detection: now guards a non-array data and compares case-insensitively; fixed.
    • An entity-encoded javascript: link in a plotly annotation: plotly.js 4.1.1 drops it itself, measured in Chromium; the markup check refuses it anyway.
    • FuncTickFormatter: not in bokeh 3.10.1, a false positive.
    • The dynamic-name finding was superseded by Fable's URL-context fix.

Test plan

  • uv run ruff check ., uv run ruff format --check ., mypy api core agents (193 files)
  • pytest tests/unit tests/integration -n auto: 25,526 passed, 135 skipped
  • tests/unit/automation/test_js_render_figure.py with the lock's node_modules: 67 passed, 1 skipped (the opt-in test below)
  • tests/unit/agents/code/test_profile_python_figure_image.py against an image built from this branch: 11 passed. The markup cases run with and without the checkout's render.mjs, and a stale image fails them.
  • ANYPLOT_CATALOGUE_MODELS=1 recollection of the bokeh model allowlist from every eligible catalogue file: passed (35 s on 32 CPUs). It is opt-in and does not run per PR (about 8 CPU minutes).
  • python -m agents.evals.make_fixtures --check, python -m tools.changelog check --base origin/main
  • CI's "Build renderer image and render through the harness" job: the new step "A plotly and a bokeh figure with markup fail as figure UnsafeMarkup" only shows on this PR's own run (a .github/workflows change). I ran its script locally against the rebuilt image and both were refused.
  • After the merge: the renderer and agents deploys; then the eval matrix for plotly and bokeh (not run here, no model calls).

https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8

MarkusNeusinger and others added 4 commits October 31, 2026 15:58
plotly and bokeh join PYTHON_LIBRARIES and the admin deploy's _LIBRARIES,
rendered by PB-1's two-step figure mode: all 15 catalogue libraries are
served for the admin's live tests.

- code/figure.py: the pinned figure save the run form ends on
  (plotly: fig.update_layout(width=, height=).write_json(f"plot-{THEME}.json");
  bokeh: with open(...) as figure_file: json.dump(json_item(p), figure_file)),
  the figure mode's device scale, and the export's swap back to the
  library's normal save (question 10, decided by the owner).
- Normaliser: plotly's write_image/write_html block and bokeh's Selenium
  screenshot block become the figure save; render_mode="svg" for plotly
  express; "...".format(...) to an f-string for both. The 13 served
  libraries' normalised code, findings and readiness are unchanged on all
  4,213 catalogue files.
- Validator: plotly and bokeh profiles (subpackages only, JavaScript,
  markup, URL fetchers and writers banned, HTML tooltip templates refused),
  the pinned save lifted out of the scan in exactly its shape.
- Readiness: a plotly WebGL trace blocks as `webgl`; plotly and bokeh
  coupling hints. render.mjs fails a WebGL trace as draw NoWebGL before
  drawing (a 3D scene drew a blank plot that the host gates passed).
- Runtime: figure-mode argv and error grammar, export, local Docker mount
  of render.mjs; adapter sections adapter_plotly.md and adapter_bokeh.md
  after the unchanged Python prompt.
- 120 fixtures + 2 smoke cases, a plotly figure gate in the renderer's
  candidate smoke, the agents image's CI scan, docs and plan.

Measured in the renderer image: 254 of 273 eligible plotly and 246 of 253
eligible bokeh catalogue files render on target in both themes (the rest
divide by the emptied title's length); model-free end to end passes R1-R3
in both themes for both libraries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
The owner's decisions on the survey's open questions, and the routes the
surface walk found:

- No private names in the plotly and bokeh profiles, as an attribute on
  any receiver, a keyword or an unpacked dict key (`_validate=False`,
  `fig._layout`); `bokeh.core.properties` leaves the surface.
- plotly: `plotly_relayout`, `plotly_update`, `plotly_restyle`, the geo
  and map traces and `update_geos` banned; `trendline_options` banned as
  a keyword (a getattr by string).
- bokeh: import by name and every route to a property object or a model
  class by name (`instance_type`, `lookup`, `.property`, `descriptors`,
  `parameters`, `model_class_reverse_map`, ...), `set_from_json`,
  `apply_theme`, `Document`, and the `icon` properties banned.
- A string at a URL keyword (`URL_CONTEXTS`, plotly's image `source`) is
  a `url-literal` whatever its scheme; the Python, R and Julia URL rules
  refuse `//host`, `ws`, `wss`, `blob:` and `javascript:` as the
  JavaScript one does. 0 of 4,213 served catalogue files change.
- bokeh tooltip templates pass as the catalogue writes them: every text
  a tooltips value can take is read, text tags with quoted style or
  class only, never `{safe}`; a pair's value is HTML too, so a Python
  variable's text there is refused (2 catalogue files), and bokeh is
  254 of 325 eligible.
- The CSP handler test asserts what ran and the directive, not
  Chromium's wording.
- The plan records the MUI X eval matrix run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
Fable's review of PB-2a built bokeh tooltips the static rules did not
see (a key built at run time, a dict filled by subscript, `chr(60)`, a
SetValue callback) and had them run script in the exported HTML.

- render.mjs reads the figure the plot wrote before a page opens and
  refuses what a browser would run, load or link, whatever built it, as
  `figure UnsafeMarkup`: bokeh's JavaScript, HTML, CSS and URL models,
  js_*_callbacks, CSS that loads or escapes, MathJax's HTML commands, a
  tooltip outside the template rule; plotly links to anything but
  http(s) (entities decoded), styles that load, image and geojson URLs.
  The export is the same code on the same data, so a refused figure is
  never downloaded.
- Static bokeh rules: `**` only of literal options (a dict literal or a
  module-level dict read only by `**`), js_event_callbacks,
  js_property_callbacks, styles and stylesheets refused as keywords and
  dict keys, every callback model, set_select, stylesheet models, Styles
  and MathML banned, MathJax HTML commands refused in strings, the save's
  file handle named nowhere else.
- URL contexts take only None or a bool (geojson a dict literal); flat
  update keys such as "images[0].source" are checked too.
- The WebGL check guards a non-list `data` and compares without case.
- plotly.js 4.1.1 drops entity-encoded javascript: links itself
  (measured); the check refuses them and relative links anyway.

0 of 4,213 served catalogue files change. bokeh: 252 of 325 eligible,
245 render on target; plotly unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
Fable's second review of PB-2a:

- The renderer's markup check refuses a bokeh model outside the 97 the
  eligible catalogue's figures hold (BOKEH_CATALOGUE_MODELS, under the
  denylist), with a message that points the adapter to the catalogue's
  glyphs; tests/unit/automation/bokeh_catalogue_models.json holds the
  names, a test keeps the set equal to it, and an opt-in test
  (ANYPLOT_CATALOGUE_MODELS=1) recollects it from the catalogue.
- No link at all in plotly text (an <a> tag or an href, https too), at
  run time and in the plotly profile (`url-literal`): no eligible
  catalogue file writes one.
- The bokeh profile bans Tooltip, Pane, Panel, Drawer and Examiner, as
  the renderer refuses them.
- The renderer image's CI asserts that a plotly link and a bokeh Div built
  with chr(60) fail as `figure UnsafeMarkup` through the image's own
  harness; the figure image test runs the markup cases with and without
  the checkout's render.mjs, so a stale image fails it.

0 eligible catalogue files are refused; 0 of 4,213 served catalogue files
change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
Copilot AI lite review requested due to automatic review settings October 31, 2026 20:19
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Critical validator gaps still allow protocol-relative URLs without dotted TLDs, and a Julia test fixture still incorrectly treats one such URL as valid.

4 open issues
What changed in this PR

This PR extends the agent chat’s Python runtime to serve Plotly and Bokeh through the renderer’s two-step figure mode. Catalogue code writes a guarded JSON figure, Chromium renders it at the canonical canvas size, and exported code restores each library’s normal PNG/HTML save workflow.

Changes:

  • Adds Plotly and Bokeh normalisation, validation, readiness, export, runtime, prompt, and renderer support.
  • Adds figure JSON markup/WebGL checks and deploy-time smoke coverage.
  • Adds 120 generated evaluation fixtures plus associated counts, documentation, and tests.
File Description
tests/​unit/​agents/​test_settings.py Updated as part of this pull request.
tests/​unit/​agents/​test_image.py Updated as part of this pull request.
tests/​unit/​agents/​runtime/​test_render.py Updated as part of this pull request.
tests/​unit/​agents/​runtime/​test_assess.py Updated as part of this pull request.
tests/​unit/​agents/​evals/​test_cases.py Updated as part of this pull request.
tests/​unit/​agents/​code/​test_readiness.py Updated as part of this pull request.
tests/​unit/​agents/​code/​test_profile_r_validate.py Updated as part of this pull request.
tests/​unit/​agents/​code/​test_profile_julia_validate.py Updated as part of this pull request.
tests/​unit/​agents/​code/​test_normalise.py Updated as part of this pull request.
agents/​renderer/​cloudbuild.yaml Updated as part of this pull request.
agents/​evals/​matrix.py Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-plotly-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-plotly-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-plotly-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-plotly-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-plotly-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-plotly-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-plotly-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-bokeh-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-bokeh-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-bokeh-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-bokeh-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-bokeh-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-bokeh-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​violin-basic-bokeh-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-plotly-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-plotly-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-plotly-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-plotly-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-plotly-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-plotly-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-plotly-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-bokeh-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-bokeh-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-bokeh-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-bokeh-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-bokeh-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-bokeh-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​scatter-basic-bokeh-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-x10/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-renamed/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-decimal-comma/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-date/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-plotly-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-x10/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-renamed/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-decimal-comma/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-date/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​pie-basic-bokeh-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-plotly-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-plotly-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-plotly-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-plotly-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-plotly-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-plotly-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-plotly-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-bokeh-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-bokeh-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-bokeh-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-bokeh-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-bokeh-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-bokeh-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-timeseries-bokeh-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-plotly-x10/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-plotly-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-plotly-renamed/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-plotly-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-plotly-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-plotly-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-plotly-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-plotly-decimal-comma/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-plotly-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-plotly-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-bokeh-x10/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-bokeh-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-bokeh-renamed/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-bokeh-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-bokeh-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-bokeh-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-bokeh-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-bokeh-decimal-comma/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-bokeh-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​line-basic-bokeh-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-plotly-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-plotly-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-plotly-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-plotly-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-plotly-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-plotly-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-plotly-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-bokeh-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-bokeh-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-bokeh-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-bokeh-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-bokeh-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-bokeh-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​histogram-basic-bokeh-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-plotly-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-plotly-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-plotly-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-plotly-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-plotly-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-plotly-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-plotly-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-bokeh-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-bokeh-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-bokeh-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-bokeh-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-bokeh-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-bokeh-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​heatmap-basic-bokeh-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-plotly-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-plotly-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-plotly-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-plotly-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-plotly-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-plotly-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-plotly-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-bokeh-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-bokeh-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-bokeh-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-bokeh-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-bokeh-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-bokeh-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​box-basic-bokeh-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-x10/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-renamed/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-decimal-comma/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-date/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-plotly-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-x10/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-renamed/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-decimal-comma/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-date/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​bar-grouped-bokeh-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-plotly-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-plotly-renamed/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-plotly-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-plotly-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-plotly-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-plotly-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-plotly-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-plotly-date/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-bokeh-x10/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-bokeh-renamed/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-bokeh-renamed/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-bokeh-n5000/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-bokeh-n12/​data.csv Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-bokeh-n12/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-bokeh-decimal-comma/​case.json Updated as part of this pull request.
agents/​evals/​fixtures/​cases/​area-basic-bokeh-date/​case.json Updated as part of this pull request.
agents/​evals/​eligibility.py Updated as part of this pull request.
agents/​evals/​cases.py Updated as part of this pull request.
agents/​Dockerfile Updated as part of this pull request.
agents/​cloudbuild.yaml Updated as part of this pull request.
agents/​anyplot/​settings.py Updated as part of this pull request.
agents/​anyplot/​render/​runtimes/​__init__.py Updated as part of this pull request.
agents/​anyplot/​render/​contract.py Updated as part of this pull request.
agents/​anyplot/​render/​backends/​local.py Updated as part of this pull request.
agents/​anyplot/​prompts/​root.md Updated as part of this pull request.
agents/​anyplot/​code/​profiles/​r/​validate.py Updated as part of this pull request.
agents/​anyplot/​code/​profiles/​julia/​validate.py Updated as part of this pull request.

Review effort: Lite

Comment thread agents/anyplot/code/validate.py Resolved
MarkusNeusinger and others added 2 commits November 1, 2026 00:29
…e once

Copilot's review of #12490: the protocol-relative branch of the URL rule
matched only dotted names with an alphabetic TLD, so "//localhost/x",
"//127.0.0.1/x" and "//host/x" passed. The Python, R, Julia and
JavaScript profiles now share one rule: two or more slashes after the
start, a space, a quote, `(`, `=` or `,`, then the first character of any
host (a name, an IPv4 address, `[::1]`, a percent-encoded name,
"///example.org"). A matplotlib hatch ("///"), "a // b" and Julia's 1//2
stay no URL; 0 of 4,863 catalogue files of the 15 libraries change.

test_catalogue_summary ran over its CPU bound after plotly and bokeh
joined the corpus. The scan now walks each tree once (it listed every
node's children twice), `of()` returns its per-type list without a
filter, and `_module_allowed` does one set lookup per dotted prefix: the
six older libraries validate 17 % faster than on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
test_catalogue_summary bounded the whole corpus at 20 s of CPU, which
plotly's and bokeh's 650 files pushed over in CI (26.1 s). The agent
validates one file per request, so the bound is now main's own per-file
budget, 20 s over its 1,946 files (CATALOGUE_CPU_PER_FILE_S), and the
failure message reads in ms per file. With the single tree walk the
branch takes 1.8 ms per file locally against the budget's 10.3 ms (CI
runs about 4 times slower).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
MarkusNeusinger added a commit that referenced this pull request Nov 1, 2026
## Summary
- Main is red since #12493: it added the `.claude/guardrails.md` section
"Long-running work reports progress proactively" without registering it
in `tests/unit/test_agent_instructions.py`, so
`test_every_companion_section_is_registered` fails (seen on #12490's CI
run).
- #12493's CI never ran that test: "Run Tests" only starts for Python,
config, workflow and prompt changes, and the PR changed only instruction
files. `ci-tests.yml` now also runs the tests for `CLAUDE.md`,
`AGENTS.md`, `.github/copilot-instructions.md`, `.claude/guardrails.md`,
`.claude/skills/` and `agentic/commands/`, which
`tests/unit/test_agent_instructions.py` and `tests/unit/skills` read.
- Registers the new section with three CLAUDE.md anchors (never let the
user ask, the 30-minute stall rule, the post-compaction re-check) and
the classifier-denial rule under the external-writes section.

## Plan
N/A (follow-up to #12493)

## Test plan
- [x] `uv run --extra dev --extra test pytest
tests/unit/test_agent_instructions.py tests/unit/skills
tests/unit/workflows -n auto`: 1,319 passed
- [x] The new filter matches `CLAUDE.md`, `.claude/guardrails.md`, a
skill and a command, and not `docs/index.md` or `app/` (dry run of the
grep)
- [x] `tools.changelog check --base origin/main` passes
- [ ] CI: this PR changes a workflow and a test, so "Run Tests" runs and
must be green

https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.40288% with 35 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
agents/anyplot/code/validate.py 94.32% 19 Missing ⚠️
agents/anyplot/code/normalise.py 96.62% 11 Missing ⚠️
agents/anyplot/code/figure.py 98.81% 2 Missing ⚠️
agents/anyplot/code/readiness.py 97.36% 2 Missing ⚠️
agents/anyplot/policy.py 91.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@MarkusNeusinger
MarkusNeusinger merged commit c6cef41 into main Nov 1, 2026
16 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the feat/pb2-plotly-bokeh branch November 1, 2026 09:27
MarkusNeusinger added a commit that referenced this pull request Nov 1, 2026
## Summary
- Cloud Build refused the renderer deploy for PB-2a (#12490) before its
first step ran: `invalid build: invalid .steps field: build step 7 arg 1
too long (max: 10000)` (build `dc1cf3ae`, 2026-11-01). The inline
`smoke` step had reached 11,347 characters with the plotly probe; the
agents service deployed (it now offers plotly and bokeh to the admin)
while the renderer kept its PB-1 revision, without PB-2a's figure-markup
checks.
- The render probes (matplotlib, Makie, Chart.js, plotly, data run) move
unchanged into `agents/renderer/deploy_smoke.sh`; only Cloud Build's
`$$` escapes become `$` (a normalised line diff against the removed
inline text is identical). The step keeps the candidate double read, the
anonymous-403 check, the token mint and `/status`, and calls the script
with the token in the environment. The step's argument is now 3,606
characters.
- `tests/unit/test_cloudbuild_configs.py` keeps every step argument of
every Cloud Build config at or under 10,000 characters (it fails on
main's renderer config), and `ci-tests.yml` runs the unit tests when a
Cloud Build config or the script changes.

## Plan
N/A (incident fix after the PB-2a merge).

## Test plan
- [x] `pytest tests/unit tests/integration -n 4`: 25,800 passed, 141
skipped.
- [x] New tests: the step calls the script once, after `/status`, with
the token through the environment; the script runs end to end against a
fake `curl` (all five probes pass and send the token; a plotly render
that fails twice exits non-zero and skips the data run); the plotly
probe writes a valid `RenderRequest`.
- [x] The limit test reports `step 7 (smoke) arg 1: 11347 characters` on
main's config.
- [x] Gemini 3.8 Flash pre-PR check: sound with changes, 3 low findings.
Applied: the fake `curl` rejects unknown flags; the script is
executable. Not applied: a 9,000-character margin, because
`app/cloudbuild.yaml` already has a 9,895-character argument; moving
that one is a separate follow-up.
- [ ] After the merge: `deploy-renderer` passes its smoke on Cloud Run
(Makie, Chart.js and plotly gates) and promotes; then the site smoke.

No live loop exists for Cloud Build configs; the deploy after the merge
is the first real run of the script.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants