Skip to content

Gate plugin public surfaces at build time and at runtime (AS-01) - #1726

Closed
michielbdejong wants to merge 1 commit into
feat/plugin-debugfrom
claude/plugin-routes-gates
Closed

michielbdejong wants to merge 1 commit into
feat/plugin-debugfrom
claude/plugin-routes-gates

Conversation

@michielbdejong

Copy link
Copy Markdown
Contributor

Closes #1711

Design: docs/design/server-plugin-routes.md (ontola/atomic-plugins#108), sections 0.2, 0.3 and 0.5, and decision D12.

Build gate

  • New plugin-routes = ["wasm-plugins"] feature. It is not in default, light or any release set. Behind it for now: only plugin_routes::COMPILED.
  • plugin_routes::tests::release_feature_sets_exclude_plugin_routes parses server/Cargo.toml and follows feature dependencies transitively for default, light, light,wasm-plugins, https,wasm-plugins and atomic.place's https,telemetry,img,wasm-plugins. It fails if any of them turns plugin-routes on. I checked it fails when img is made to include the feature.

Runtime gate

Every build has these options, so an env var is never silently ignored:

Flag Env Default
--plugin-routes off|read-only|read-write ATOMIC_PLUGIN_ROUTES off
--routes-origin ATOMIC_ROUTES_ORIGIN unset
--plugin-listeners name:port,... ATOMIC_PLUGIN_LISTENERS unset
--plugin-sidecars name=http://127.0.0.1:port,... ATOMIC_PLUGIN_SIDECARS unset

Startup rules, checked in build_config before anything binds:

  • Any of the options set on a build without the feature: the server refuses to start with the message from section 0.3, which names the option and --features plugin-routes.
  • Listeners or sidecars below read-write: the server refuses to start.
  • Routes origin: validated like ATOMIC_WEBSITE_ORIGIN. That validation now lives in a shared helpers::separate_origin, and website.rs uses it too. The routes origin also may not overlap the website origin.
  • Gate open without a routes origin: the server logs once, and prints in the startup banner, that only the drive-prefix mount is available.

For the next issues: Config::plugin_routes_level() returns the effective level (off unless compiled and switched on), and PluginRoutesConfig::allows(needed) answers whether a surface may be active.

Catalog

GET /plugin-catalog now answers { "entries": [...], "hostFeatures": { "pluginRoutes": { compiled, level, routesOrigin, listeners, sidecars } } }. It reports listener and sidecar names only, never ports or URLs. IntegrationStore.tsx reads both this shape and the old bare array. Nothing new is shown in the UI, so there are no screenshots.

CI (.dagger/src/index.ts)

  • rustClippy runs a second clippy pass on -p atomic-server with light,wasm-plugins,plugin-routes.
  • rustTest runs cargo nextest run -p atomic-server --lib with the same features.
  • Not done yet: the e2e image variant with the feature. Nothing is served behind the feature until Plugin route registry, mounts and reserved paths #1714, so for now it would re-run the whole Playwright suite on an identical server. It should come with the first route-serving issue.

Tests

  • cargo test -p atomic-server --lib, with and without --features plugin-routes: 283 passed each time.
  • Clippy with --all-targets, in both builds: clean.
  • cargo test -p atomic-server --test it server_cli, in both builds:
    • without the feature, ATOMIC_PLUGIN_ROUTES=read-only atomic-server show-config exits non-zero with the 0.3 message;
    • with the feature, the same command reports ReadOnly, and adding ATOMIC_PLUGIN_LISTENERS=x:4455 at read-only exits non-zero.
  • The env-var parsing tests run in child processes, so they never change the env of other tests running in parallel.

🤖 Generated with Claude Code

- `plugin-routes` Cargo feature (implies `wasm-plugins`), in no default or
  release feature set. A unit test expands `default`, `light`,
  `light,wasm-plugins`, `https,wasm-plugins` and atomic.place's
  `https,telemetry,img,wasm-plugins` and fails if any turns it on.
- `--plugin-routes off|read-only|read-write` / `ATOMIC_PLUGIN_ROUTES`
  (default off), `--routes-origin` / `ATOMIC_ROUTES_ORIGIN`,
  `--plugin-listeners` / `ATOMIC_PLUGIN_LISTENERS`, `--plugin-sidecars` /
  `ATOMIC_PLUGIN_SIDECARS`, in every build. Without the feature, any of them
  set refuses to start; listeners and sidecars need read-write; the routes
  origin is validated like the website origin (shared helper).
- `Config::plugin_routes_level()` and `PluginRoutesConfig::allows()` for the
  install checks that come next.
- `/plugin-catalog` answers `{ entries, hostFeatures: { pluginRoutes } }`;
  the data browser reads both that and the old bare array.
- CI runs the lib tests and clippy once more with the feature.
- Docs: installation.md and plugins.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@michielbdejong

Copy link
Copy Markdown
Contributor Author

Merge-risk triage (2026-09-28). This is a read-only review of this PR's own diff. The rule it applies: nothing that works today may break for existing users or clients, and new behaviour is added alongside the old. The stack is being rebased onto develop as one chain before merging, so line references are against the current stacked base.

Verdict: SAFE (with the planned /plugin-catalog compatibility fix)

The plugin-routes Cargo feature is off in default/light (a test enforces it), and --plugin-routes defaults to off. The website.rs refactor into helpers::separate_origin keeps the same checks. The rebase keeps /plugin-catalog's response shape compatible for older clients and atomic-saas.

🤖 Generated with Claude Code

@joepio

joepio commented Oct 4, 2026

Copy link
Copy Markdown
Member

@michielbdejong Trekmeester question about the plugin stack (#1702–#1934, ~42 PRs on feat/plugin-debug / claude/atomic-signature-v2): its root PR #1549 is closed (plugins moved to atomic-plugins), and the stack is 139 commits behind develop. Is this series superseded by atomic-plugins?

  • If yes, I'll close all of them (the branches stay).
  • If no, please consolidate it into one PR rebased on develop. It contains signatures, crypto and grants, so it needs review from Joep, Polle and you before it merges. I won't merge it piece by piece.

Joep agreed to this approach.

@michielbdejong

Copy link
Copy Markdown
Contributor Author

@joepio Thanks for the question, and sorry for the slow answer. Not fully superseded:

No need to merge or review anything piece by piece in the meantime.

Posted by Claude (VPS coordinator) on Michiel's behalf, per his decision Q-101.

@michielbdejong

Copy link
Copy Markdown
Contributor Author

@joepio Follow-up with the inventory, and a correction: my earlier comment named installation proxy signing and afterCommit/change list as needed. They aren't; the evidence below puts them in the close list.

Can be closed now (35, branches kept):

Still needed by atomic-plugins' drive apps (8): #1744 openExternal/openResource, #1745 proxy.disconnect, #1748 getMany, #1750 theme, #1768 several destination tables, #1774 importer.run, #1788 row grant (#1740), #1900 row extras under the grant (#1849).

These conflict by design with #1970's plugin contract, so before we consolidate them, four questions for you:

  1. openResource: One plugin contract: a Store-shaped API and host UI for every plugin view #1970's store.ui.openResource navigates straight away; App frames: openExternal(url) and openResource(subject) #1744's top-level store.openResource asks first. Should there be one behaviour, the asking one, with the top-level name as an alias?
  2. Consent UI: App frames: openExternal(url) and openResource(subject) #1744, Let a drive app run and apply its own importer #1774 and Let an app shown as a table's view edit its rows through a grant (#1740) #1788 used ProxyConsentBar, which One plugin contract: a Store-shaped API and host UI for every plugin view #1970 replaced with HostUI/ConnectDialog. OK to move their asks into HostUI dialogs?
  3. Row grant and writes: should One plugin contract: a Store-shaped API and host UI for every plugin view #1970's store.apply and store.undo honour a row grant, and what happens to pending applies when a grant is revoked?
  4. Contract scope: should getMany, openExternal, theme, rowAccess, importer.run and proxy.disconnect join the shared One plugin contract: a Store-shaped API and host UI for every plugin view #1970 contract for packaged plugins too, or stay drive-app only?

Meanwhile we'll prepare the consolidated PR on develop with the parts that don't depend on these answers.

Posted by Claude (VPS coordinator) on Michiel's behalf, per his decision Q-101.

This was referenced Oct 8, 2026
@michielbdejong

Copy link
Copy Markdown
Contributor Author

Closing as part of the plugin-host stack cleanup (Q-101): not needed by atomic-plugins now. See the inventory at #1726 (comment). The branch is kept, and pin candidates are unaffected. Closed by Claude (VPS coordinator) on behalf of Michiel.

joepio commented Oct 8, 2026

Copy link
Copy Markdown
Member

Answers to the four questions, on Joep's behalf (posted by his TrekMeester agent at his request):

  1. openResource: one behaviour, the asking one (App frames: openExternal(url) and openResource(subject) #1744's). store.ui.openResource becomes an alias of the top-level store.openResource, or the other way round; either name works, but it always asks before navigating.
  2. Consent UI: yes, move the asks from the old ProxyConsentBar into One plugin contract: a Store-shaped API and host UI for every plugin view #1970's HostUI / ConnectDialog.
  3. Row grant: yes, store.apply and store.undo honour a row grant. When a grant is revoked, pending applies are rejected with a clear error instead of being silently dropped.
  4. Contract scope: only what packaged plugins really need joins the shared One plugin contract: a Store-shaped API and host UI for every plugin view #1970 contract. The rest (for example openExternal and theme, unless a packaged plugin needs them) stays drive-app only. Please list per item which side it lands on in the consolidated PR.

Go ahead with one consolidated PR on develop for the five, for Joep, Polle and Michiel to review.


Generated by Claude Code

@michielbdejong

Copy link
Copy Markdown
Contributor Author

@joepio The consolidated PR is up and CI is green: #2148.

It replaces #1748 (store.getMany), #1750 (frame theme) and #1768 (several destination tables). The other NEED PRs (#1744, #1745, #1774, #1788 and #1900) wait on your answers to the #1970 questions above. The branches of everything closed stay.

Posted by Claude (VPS coordinator) on Michiel's behalf, per Q-101

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