Skip to content

Plugin routes: move route-write rights when an Installation's config moves a target - #1775

Closed
michielbdejong wants to merge 5 commits into
claude/route-write-grant-uifrom
claude/route-write-config-rights
Closed

michielbdejong wants to merge 5 commits into
claude/route-write-grant-uifrom
claude/route-write-config-rights

Conversation

@michielbdejong

Copy link
Copy Markdown
Contributor

Follow-up to #1754 and #1757. Stacked on #1757 (claude/route-write-grant-ui).

Before this PR, editing an Installation's config so that a write target's config: key named another resource left the plugin's write on the old parent and gave the new parent nothing. Route writes to the new parent were then refused.

What changes

  • Detect: routeWriteConfigChange(grants, savedConfig, editedConfig) (lib) lists the route-grant targets whose resolved parent changes, and throws UnresolvedWriteTargetError when the edit leaves one unresolved.
  • Approve again: Save on the Installation page opens "Move incoming items". It reuses Approve route writes in the install review (#1717 follow-up, includes #1742) #1757's RouteWriteApproval with the new parent marked New, unchecked by default.
  • Move: saveInstallationConfig (lib) saves the config and rewrites route-writes in grants. When approved, it gives write on the new parent and takes it back from the old one, in commits the signed-in agent signs. When saved unchecked, it drops the route grant and every right it came with, as declining an upgrade's new targets does.
  • Refuse: a config that leaves a target unresolved can't be saved. Save is disabled, a note names the key, and the lib throws before committing anything.
  • A config change that moves nothing is saved as before.

Screenshots

Taken against a --features plugin-routes server at --plugin-routes read-write, with the #1754 inbox fixture published from a draft, installed with the route grant, then its inbox changed from an Inbox folder to an Archive folder. After approving, the server reported write on Archive for the plugin agent and no longer on Inbox. Files (local): followup-1758-move-approval-{desktop,mobile}.png, followup-1758-unresolved-{desktop,mobile}.png.

The [i18n-404] after "Pinned to" in the unresolved screenshot was already there: it is the "from …" line in a conditional with nested elements, which this PR doesn't touch.

Tests

  • plugin-route-grant.test.ts: detecting a move (including a config stored as a JSON string), approving it (rights moved, grant kept, commits signed by the installer), declining it (grant and rights dropped), an unresolved target (nothing committed, no agent fetch), and a change that moves nothing.
  • RouteWriteMoveDialog.test.tsx: New marker, unchecked default, save approved and declined.
  • cargo test -p atomic-server --lib with and without plugin-routes, clippy both ways, data-browser and lib typecheck.

Closes #1758

🤖 Generated with Claude Code

The install review gives the plugin's agent `write` on each resolved write
target's parent, and revoke or uninstall takes it back, but editing the
config on the Installation page so `inbox` names another resource moved
nothing: the old parent kept the plugin's `write` and the new one got none,
so route writes there were refused.

Saving the config now goes through `saveInstallationConfig`:
- `routeWriteConfigChange` compares the saved config with the edit. When a
  `config:` target resolves to another parent, the page asks again, like an
  upgrade review: a "Move incoming items" dialog reusing RouteWriteApproval,
  the new parent marked New, unchecked by default.
- Approving moves the rights in commits the signed-in agent signs (`write`
  given on the new parent, taken back from the old one) and keeps
  `route-writes` in `grants`. Saving unchecked drops the route grant and
  every right it came with, as declining an upgrade's new targets does.
- A target the edit leaves unresolved refuses the save: Save is disabled
  with a note naming the key, and the lib throws before committing.
- A config change that moves nothing is saved as before.

Tests: lib vitest for detecting a move, approving it, declining it, an
unresolved target committing nothing, and a change that moves nothing;
data-browser vitest for the dialog's New marker, default and both choices.

Closes #1758

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@michielbdejong michielbdejong added bug Something isn't working plugin Should probably be an Atomic Plugin browser labels Sep 24, 2026
Michiel de Jong and others added 4 commits September 25, 2026 10:32
The pre-commit format check was skipped when this branch was committed
with --no-verify. The plugin-manifest-http.ts padding and the
PublicEndpoints.test.tsx format are the same fix as on
claude/catalog-gated-ui, repeated so this branch passes the hook alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
oxlint's padding-line-between-statements and oxfmt flagged these, so the
pre-commit lint step failed on this branch before any other change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
plugin.spec "install a plugin" saves the config twice while offline. Since
the route-write change, the page cleared its edited flag only after
saveInstallationConfig resolved. Offline, the first save settles after the
next edit, so it cleared that edit's flag too: the outbox entry makes the
save state 'queued', not 'dirty', and Save stayed disabled for the newer
draft. The plugin has no write targets; the route grant was never involved.

The draft bookkeeping moves into useConfigDraft, which counts edits: a save
only clears the flag when no edit happened since it started. Its saved
config still follows the draft that was written, so the route-write rights
diff keeps comparing against the last saved config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fixes)

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

The config Save button now goes through saveInstallationConfig, which without a route grant does the same as before (set config, save()), with no agent fetch and no rights change.

🤖 Generated with Claude Code

@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

browser bug Something isn't working plugin Should probably be an Atomic Plugin

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant