Skip to content

The center's Capabilities tab, and capabilities a center adds (#152, B8) - #182

Merged
Piggidragon merged 42 commits into
mainfrom
feature/center-capabilities
Nov 2, 2026
Merged

Piggidragon merged 42 commits into
mainfrom
feature/center-capabilities

Conversation

@Piggidragon

Copy link
Copy Markdown
Member

Step B8 of #152, stacked on #181 (B7). Part of #152.

A center with the settings grant can now add, change and remove capabilities of its own on a node, and the center's web UI gets a Capabilities tab to see and manage them (owner decisions Q22, Q24).

The node

  • New settings actions: settings.cap-add, settings.cap-edit and settings.cap-rm.
    • They are stored in that center's own entry of the pairing ([[center.capability]]). No other center ever sees them, in list, in cap ls rows or in the settings.
    • They need the settings grant (write) and are audited through commit as center <name>.
    • settings stays unreachable through execute.
  • The limits:
    • They always run as nerve-run; there is no account field (B5).
    • For the defaults they count as execute, whatever effect is declared.
    • Sense and actuate stay off until the owner's cap set … on --center. A center's own on is refused.
    • An off or hidden of a center's capability stays until the owner changes it.
    • A center can never add under the name of the owner's capability or a program's, and can never change or remove one of them.
  • Where they are refused: a Windows node (Windows: the desktop app is the only way to the node #176) and a node run by hand refuse with their reason, since only the installed node has nerve-run.
  • What the owner sees: cap ls shows who added each capability ("added by center X, counted as execute", or "waits for your cap set on" for sensing). cap rm NAME --center C removes one; settings.get capabilities lists a center's own.
  • Shared form check: the form check moved from the desktop app into nerve-policy (config::form), so the app, the center and the node check a capability with the same code.

The center's web UI

  • A top-level Capabilities tab (after Action checker) with a node picker.
  • It shows the node's offered units with their effect and source (built-in, the owner's node.toml, this center, program X), and the ones left out with their reason.
  • Your capabilities:
    • Add, edit and remove the center's own, removal with a confirmation.
    • The form is checked server-side as you type, by the same validator. Schemas outside the subset show where and why.
    • The node's own refusal shows in the form.
  • A node without the settings grant is shown read only (the Q24 choice): its units and sources, and a note that the center may change nothing there and how the owner gives the grant. There is no form.
  • API: GET /api/caps, POST /api/caps/check, POST /api/caps/save and POST /api/caps/remove. Each change is in the center's log.

Docs

Tests

  • Node (tests/center_caps.rs):
    • a center adds, runs and edits its own capability, and the owner removes it;
    • no account field, and the owner's capabilities are left alone;
    • execute is counted, and sensing waits for the owner;
    • another center never sees it;
    • a node run by hand takes none.
  • Conformance: two §7.4.2 checks in the node part; the e2e now runs 22 profile checks.
  • Center (tests/caps.rs), an in-process center and node:
    • the tab lists the sources;
    • broken forms are checked and refused;
    • an add through the API, then list and execute through MCP, with settings.cap-add unreachable through execute;
    • edit, the node's refusal as a 409, the log, and remove;
    • without the grant the tab is read only and a save is refused.
  • ui.rs covers the API and app.js sources.
  • Shots, light and dark at 1280×800 and 390×844: the page, the form, the picker, the add steps, read only.

Real machines

  • LXC, installed node (musl release build), paired with a local center that has the settings grant. Playwright drove the real page:
    • From the web UI, the center added who.am (id, read) and cam.look (sense).
    • The form flagged an argument placeholder the schema does not have.
    • A name the owner's node.toml already has was refused by the node, shown in the form.
    • execute who.am ran as uid=…(nerve-run).
    • cam.look was not offered. The center's on was refused ("only the owner can turn on what senses or actuates"). After the owner's cap set cam.look on --center lxc it ran.
    • The owner turned who.am off (the center's on was then refused) and removed it with cap rm --center.
    • The audit log has every step: the center's two adds, the calls, and the owner's on, off and rm.
    • With the grant taken away, the page was read only.
    • Found there and fixed: cap ls called a center's sensing capability "counted as execute".
    • uninstall --purge left nothing.
  • Windows VM, the CI windows recipe: 1372 passed, 0 failed.

Review and checks

  • Review rounds with the correctness, spec and security lenses. The usage limit stopped the first run after 5 rounds; the restarted run did 7 more. The last round had one low, which is fixed.
  • cargo fmt --check, clippy and cargo xwin clippy: clean.
  • cargo test --workspace: 1740 passed, 0 failed.
  • Mutation sets: 145 runs, all fail their tests as expected. Mutations made stale by later rounds were refitted to the current code.

Known limits

  • The desktop app has no remove button for a center's capability; the owner uses cap rm --center. Its switches work there.
  • Refused settings writes are not audited, as before for every refused settings write.

🤖 Generated with Claude Code

Piggidragon and others added 30 commits October 28, 2026 20:38
The cap ls overview with each unit's effect, source and switch for one
center, and the programs allowed as providers. The switches leave
Permissions and the provider allow list leaves Settings > Advanced; the
page opens with Ctrl+9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dialog checks each field as the node and the center do while the
owner types; the save runs cap add or cap edit with the schema on
stdin, and what the node refuses stays in the dialog with its reason.
Removing one asks first. Windows and a node run by hand say why they
offer none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The dialog checks a capability's time limit against the node's own
  limits.max_timeout_secs, read with the page, not the default 3600.
- A unit with no switch of its own shows its capability's (from the
  `centers` of `cap ls`), marked "like its capability".
- A save's answer goes to the dialog that sent it; a refusal after the
  dialog closed is a toast, and a second save cannot run alongside.
- Add and Save are always offered; a click shows the empty fields.
- Tests for the stale `cap ls` answer, the raised limit, the inherited
  switch, the answers of saves and the empty save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dialog checks a time limit against the default once the node's limit
is unset, its Arguments and Result boxes scroll, and an empty name or
command stays an error after the first save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scrolling text box panned on a drag and selected nothing. The wheel
and the cursor follow still scroll it. Also move the time-limit test
above the doc comment of the dialog's save test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pick and an unpairing of the shown center are tested now, and the
"In use" pill says "Usable now", as the node only says the center may
use the unit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also test the Programs row's Choose... button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A press on a line cut off at the bottom brings it into view, and how many
lines fit depends on the platform's font; on Windows the old start scrolled
the box by two pixels.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A center adds, edits and removes capabilities in its own entry of the
pairing (settings.cap-add, cap-edit, cap-rm). They are offered to that
center alone as center-added, run as nerve-run, count as execute unless
they sense or actuate, wait for the owner's on where they do, and are
audited as the center's change. The owner's capabilities and programs
come first; the owner removes one with cap rm --center, and an off of
one stays until the owner changes it. Windows and a node run by hand
take none and say why. The conformance node part checks both cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page of its own with a machine picker: each unit a machine offers the
agent with its effect and where it comes from, the ones the center left
out with the reason, and, on a machine that gave the center the settings
grant, the capabilities this center added, with a form to add and edit
them and Remove. The center checks the form with the node's own rules
while it is typed and before it sends it; the node checks it again and
its refusal shows in the form. A machine without the grant is shown read
only. The form's checks move from the desktop app to nerve-policy, so the
app, the center and the node share them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The line of a capability a center added that senses or actuates said it
counted as execute; it waits for the owner's on instead. The hint under
the form's row of effects stands on its own line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A center removes its own capability also once the owner declares the
same name; an edit of a sensing one that changes its effect or command
waits for the owner's on again; the form says an unknown effect or
platform; a summary, a command element and a center's capabilities
together have a bound; the Capabilities tab tells the center's own
units by capability and takes a node's odd answer as data; a test of
the tab for a node that takes none; docs for the pause, the log lines,
the desktop app and protocol 7.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its params decide what fills the command and its timeout how long it
runs, so the owner's on was given for those too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d say why one is not offered

The center fetches the node's description before it answers a cap-add,
cap-edit or cap-rm, so the page drawn after it is not the one from before.
A capability the center added that the node neither offers nor leaves out
is marked not offered, with what turns it on. The descriptor section of the
protocol names the providers config and center-added.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A center that added a capability could turn on or allow that capability as
a whole, and that switch or rule then also covered a sensing action of a
program the owner allowed that came later under the same name. Now:

- a center turns on or allows only its own action, by its name; the
  capability as a whole stays the owner's while nothing else offers it;
- while a center keeps one of its own under a name a program offers, the
  program's capability is not offered to that center either;
- removing what a center added (by the center or with cap rm --center)
  drops that center's on and allow rules for that action; an off stays.

cap ls says of a center's sensing capability that only the owner turns it
on, not that it waits. The Capabilities tab names the action's key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also say that an off of a center's capability lasts past removing and adding
it again, and that a removal takes its on and allow rules along.

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

A center's own sensing or actuating entries count for what only the owner
turns on in settings.set, so a switch or rule for their name set while a
program or the owner's node.toml offered it without sensing cannot cover
them later. A removal keeps the switches and rules where the owner declares
that very name, and an add refused under a program's capability no longer
tells the center to remove an entry it does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A program's events, state and streams no longer reach a center that keeps
its own under that capability; cap ls --center lists only that center's
added ones; an owner's sensing capability under a center's name waits for
the owner's on; the Capabilities tab leaves out an entry whose schema the
node would not keep; tests for these and for the grant taken back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The owner's `on` for the owner's capability no longer turns on a
  center's own sensing one of that name once the owner's goes: `cap rm`,
  `config.write` and `config accept` switch it off and `cap rm` says so.
  While a program keeps the center's own out, the owner's write that
  turns it on is refused, and `config accept` switches it off.
- A center's time limit is checked when it adds or edits one, so the
  owner may lower `limits.max_timeout_secs` later.
- The Capabilities tab keeps its picker while a machine loads, says why
  one did not load, and reads the machines again after a 404.
- Docs: the size limits of a summary and a command element, also in
  node.toml; a test of a center's capability that takes a path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A removal of a center's last capability under a name drops the owner's on
and allow rules for that name as a whole too, unless the owner declares
it; tests for the owner's other writes keeping its on; a superseded load
of the Capabilities tab ends quietly.

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

While a center keeps an entry of its own under a program's capability, it is
offered neither, so an on or allow rule the owner sets then for that
capability as a whole, or for the center's action of that name, was meant for
the program's, and would go unseen with the center's own when it is removed.
config.write refuses it and says to remove the center's first; config accept
switches it off and reports it as held.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ConfigCapability::line, which cap ls and the audit use for a command, left
DEL, the C1 controls, the line separators and the format characters (such as
direction marks) as they are, and did not quote an element with a format
character. A center's command could then read as something else in cap ls.
They are now written as \u escapes in quotes, which read back to the same
words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Piggidragon and others added 11 commits November 1, 2026 14:54
The owner turns on a center's capability that senses or acts for what it
runs, but the page showed only the center's own summary. A row of a center's
capability now shows its command, from cap ls's added, as the owner's rows
do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A center adds none under an on or allow rule set for what else offers
that name; config accept tells each kind of held switch apart; a center's
64 entries are said in its own words; the desktop shows a center's whole
command, wrapped.

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

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
config accept names a center's own sensing action once, with the note
that fits it, where the file sets an allow rule for it; a center's add
refused for a switch set for something else says the center may take it
away itself; protocol 7.4.2 and the web UI's page name that refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
config unset, config set, a config command without a running node and a
hand edit that config accept takes dropped a center's own capability but
kept its on and allow rules for it, which then turned on what a program
offers under that name later. Each of them now drops them as cap rm
--center does; config accept names what it dropped. The center's test
also reads the log lines of a change, a removal and a refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd with the node stopped

cap rm --center finds no entry once the hand edit took it out, so the
advice now says to use cap rm before, take the switches out in the same
edit, or switch them off and remove the rule afterwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The node part sends one wrong sudo password and expects it refused. The test
ran it against the machine's own sudo, which a CI runner has without a
password, so the wrong one worked. The node now uses a fake sudo that wants a
password nobody gives, as the conformance e2e and the relay test do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Piggidragon
Piggidragon changed the base branch from feature/desktop-capabilities to main November 2, 2026 08:36
@Piggidragon
Piggidragon merged commit 7b392d2 into main Nov 2, 2026
5 checks passed
Piggidragon added a commit that referenced this pull request Nov 2, 2026
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