Skip to content

Approve route writes in the install review (#1717 follow-up, includes #1742) - #1757

Closed
michielbdejong wants to merge 4 commits into
claude/plugin-route-writesfrom
claude/route-write-grant-ui
Closed

michielbdejong wants to merge 4 commits into
claude/plugin-route-writesfrom
claude/route-write-grant-ui

Conversation

@michielbdejong

Copy link
Copy Markdown
Contributor

Follow-up to #1754 (route writes) for #1717. Includes #1742 (the install review's "Public endpoints" section), merged in with a merge commit, because the approval sits in that review and #1742 is based on #1732, without route writes.

The gap

A plugin installed from the browser got no route grant: installRelease sent only capability names, so every route write answered 403 route-write-not-granted. The installer also had to give the plugin's agent write rights on each target's parent by hand. Now installing from the review at read-write works end to end.

What the page writes

Grant. The Installation's grants in array form, with the targets exactly as the release declares them. This is the shape #1754's check_grants accepts:

["storage", {"route-writes": [{"id": "inbox-items", "parent": "config:inbox", "classes": ["https://atomicdata.dev/classes/PlainText"]}]}]

Rights. The installer signs one commit per resolved parent. It adds the installation's agent (pluginAgent, which the server adds when it serves the Installation) to that parent's write list and keeps the existing writers.

  • This is write, not append. Route writes: the route grant, quotas and provenance (#1717) #1754's applier calls StoreApplyHost::create → may_write(parent) → check_write, and checks write on the child for set and destroy. append alone is refused.
  • testdata/plugin-routes/inbox/page-install.json holds this page output. The browser test and the server test both use it.

Behaviour

Install review (RouteWriteApproval.tsx):

  • Shown when a release declares writeTargets and the node is at read-write. It reads "Let fixtures/inbox add items to inbox-items when other servers send them", then lists each target with its resolved parent.
  • The design (2.6, D4) has no default approval, so it starts unchecked. Left unchecked, the plugin installs without a grant: read routes serve and writes answer 403.

Resolving targets: the same rule as allowed_targets. config:<key> must be a non-empty string in the config; anything else is a URL.

  • The review starts from { "<key>": "" } for those keys, so the config editor shows up even for a release without a config schema (the inbox fixture has none).
  • While a target doesn't resolve, the review shows why and keeps Install disabled.
  • installRelease throws UnresolvedWriteTargetError before it commits anything.

Revoke / uninstall: take the rights back first, then change the status or destroy. After a revoke the server no longer reports the agent.

Upgrade review:

  • Targets the current grant doesn't cover unchanged are marked New, and the approval starts unchecked.
  • An update the grant already covers starts checked.
  • Approving replaces the grant with the new targets, gives rights on new parents and removes them from parents no longer covered. Declining drops the grant and the rights.

Installation page: the Grants list shows route-writes: inbox-items. Before, the object element rendered as [object Object].

Code: browser/lib/src/plugin-route-grant.ts (grant shape, resolving, the rights commits), installRelease / updateInstallationRelease (routeWrites), withdrawRouteWriteRights, and InstallationReviewDialog → onInstall(…, routeWrites).

Checked by hand

Server built from this branch with --features plugin-routes, run with --plugin-routes read-write. #1754's testdata/plugin-routes/inbox/ fixture was published to the catalog.

  • Installed from the review, approved: the grant and the inbox's write were as above.
  • POST /_routes/<slug>/inbox → 202.
  • Revoke: the agent is off the inbox's write, and the other writer stays.

Screenshots (desktop approved, desktop unchecked, 390px mobile) are shared with the developer in chat.

Tests

  • browser/lib/src/plugin-route-grant.test.ts:
    • the grant shape; either grants form reads back; targets widened by class or parent count as new;
    • installRelease writes exactly page-install.json's grants, and the rights commit is signed by the installer;
    • an unresolved config: target refuses the install with nothing posted; no approval means no grant and no rights;
    • revoke removes the agent and keeps other writers, and does nothing without a grant;
    • an upgrade widens the grant and gives rights on the new parent; declining drops both.
  • RouteWriteApproval.test.tsx: unchecked by default; targets passed to onInstall only once approved; Install disabled while unresolved; not offered below read-write; an update kept approved or asked again.
  • route_writes::http_tests::a_page_shaped_install_stores_a_post: installs page-install.json's grants verbatim, adds the agent to the inbox's write next to the existing writer, then POSTs /inbox → 2xx, and the item is stored.

The .po files were settled with one vite run after removing .wuchale, then checked to be the fixed point after a clean restart. The only change is six new entries plus a reuse of "New".

Not in this PR

  • Config edits on the Installation page don't move the rights. Pointing inbox at another resource leaves write on the old one, and the new one gets none. This needs its own save hook.
  • Other drives can't install a catalog listing yet. Seen while testing, and not caused by this PR: GET /releases/blake3:… answers 401 to any agent outside the publisher's drive, so their Installation commit is refused ("No read right …"). Installing into the publisher's drive works.
  • Two display glitches already on the base branch. The Installation page's Release section shows [i18n-404]: the base catalog has Pinned to <0/> {0}, a nested message inside a conditional. On mobile the review's footer buttons wrap mid-word.

🤖 Generated with Claude Code

Michiel de Jong and others added 4 commits September 24, 2026 18:29
…eview

The catalog reads `hostFeatures.pluginRoutes` and each entry's derived
`requires`: plugins that need plugin routes this build doesn't have are hidden,
with one line saying how many; plugins that need a higher level (or an unbound
listener or sidecar) are marked "Needs public endpoints" with the refusal text
of design 0.4. The install review lists every public surface in plain words,
the address that becomes the plugin's identity and the level it needs, and
disables Install with an inline banner when the node refuses, whether predicted
from the gates or thrown by the server as `host-feature-unavailable`.

The lib gains `requiresGate` and `checkGate`, so the catalog decides from
`requires` with the same rules the install uses, `parsePluginRoutesStatus`,
and `http` on `InstallationReview`.

Closes #1713

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review dialog resets its refusal in an effect that depends on
`show`. The mock returned a new `show` on every render, so the effect
cleared the server's 409 refusal right after it was shown, and
'shows the server's 409 refusal' failed whenever the suite ran under
load (it passed on its own). The real hook's functions are stable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plugin installed from the browser got no route grant, so every route
write answered 403 route-write-not-granted, and the installer had to give
the plugin's agent write rights on each target's parent by hand.

The install review now asks, unchecked by default, "Let <plugin> add items
to <targets> when other servers send them" when a release declares write
targets and the node is at read-write. Approving writes
{"route-writes": [writeTargets]} into the Installation's grants, exactly as
the release declares them, and adds the installation's agent to `write` on
each target's parent in a commit the installer signs. `write`, not
`append`: the route applier checks write on the parent before it creates.

- A `config:` target that the config doesn't name refuses the install
  before anything is committed; the review starts with those keys empty
  and keeps Install disabled until they are set.
- Revoke and uninstall take the rights back first, while the server still
  reports the agent.
- An update review shows targets the current grant doesn't cover as new
  and asks again; approving replaces the grant and moves the rights,
  declining drops both.

testdata/plugin-routes/inbox/page-install.json is the page's output; the
lib test checks the page writes it and a server test installs it and gets
a 2xx from POST /inbox.

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

Every new UI element depends on the server's hostFeatures.pluginRoutes, and the approval checkbox only shows at read-write. Without a route grant, grantsWithRouteWrites writes the same string[] as before and moves no rights.

Note: Merge before #1763, which contains this PR through a merge commit. Minor: removeRouteWriteRights removes the agent from write even if it was there before the grant.

🤖 Generated with Claude Code

joepio added a commit that referenced this pull request Oct 5, 2026
…ndpoint-health

# Conflicts:
#	TESTING_COVERAGE.md
#	browser/data-browser/src/locales/de.po
#	browser/data-browser/src/locales/en.po
#	browser/data-browser/src/locales/es.po
#	browser/data-browser/src/locales/fr.po
@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.

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.

1 participant