Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLI
Assigneesstack72

Relationships

↑ child of #622

#681 serve-auth: remote grant management via swamp access --server

Opened by stack72 · 6/18/2026· Shipped 6/18/2026

Parent

Sub-issue of #662 (serve authentication & authorization). Replaces #680 (declarative grants file).

Problem

An admin managing a remote swamp serve instance should not need to:

  • SSH into the server
  • Edit files on disk
  • Run commands locally on the server host
  • Restart the server to apply access changes

Access management must work entirely through the control plane, from any machine with network access to the server.

Solution

Add `--server` support to all `swamp access` commands (#678) so they route through the serve WebSocket protocol instead of operating on the local repo. Grants are stored in the server's data store as the system of record. Policy changes take effect immediately via the existing hot-reload mechanism (`PolicySnapshotLoader` EventBus subscription from #672) — no server restart needed.

What to build

1. `--server` flag on all `swamp access` commands

Every command from #678 gains a `--server url:string` option:

```bash

Create a grant on a remote server

swamp access grant create \ --subject idp-group:platform-eng \ --allow run \ --on 'workflow:@acme/*' \ --server wss://swamp.acme.internal:9090

List grants on a remote server

swamp access grant list --server wss://swamp.acme.internal:9090

Revoke a grant on a remote server

swamp access grant revoke 7f3a... --server wss://swamp.acme.internal:9090

Check access on a remote server

swamp access check \ --subject user:adam --action run --on workflow:@acme/deploy \ --server wss://swamp.acme.internal:9090

Group management on a remote server

swamp access group create release-managers --server wss://swamp.acme.internal:9090 swamp access group add-member release-managers user:adam --server wss://swamp.acme.internal:9090 ```

2. Route grant/group mutations through `model.method.run`

Grant create, revoke, and group operations are model method calls. The existing `model.method.run` WebSocket frame type already handles remote model method execution — the `swamp access` commands just need to construct the right payload and send it over the existing protocol. No new frame types needed.

Follow the pattern in `src/cli/commands/model_method_run.ts` (lines 175-180) where `--server` triggers an early exit to `runMethodViaServer()`, which uses `src/cli/remote_run.ts` to open a WebSocket, send the frame, and stream events back.

3. Route `access check` through a new frame type (or query)

`access check` is different — it's not a model method call, it's a policy evaluation. Two options:

(a) New frame type — add an `access.check` request/response type to the serve protocol. The server builds a `PolicySnapshot`, calls `explain()`, and returns the decisions.

(b) Read-only query — the client fetches all grants via a `data.query` mechanism, builds the snapshot locally, and evaluates. But this leaks policy data to the client and duplicates the evaluation logic.

Option (a) is cleaner — the server is the authority on access decisions. The protocol addition is small: one request type with subject/action/resource/collectives, one response with the decisions array.

4. Hot reload already works

When a grant is created or revoked (locally or via the control plane), the Grant model emits `ModelCreated`/`ModelUpdated` events. The `PolicySnapshotLoader` (#672) subscribes to these events and atomically rebuilds the in-memory policy snapshot. No restart, no explicit reload command. This is already built and tested.

5. Authorization gates the operations (layer 5 dependency)

Creating/revoking grants remotely must require `admin` on `access:*`. This enforcement is layer 5 work (wiring AccessDecisionService into serve chokepoints). This issue can land the `--server` plumbing before enforcement is wired — the same way `swamp model method run --server` works today without authorization checks.

Implementation approach

For grant/group mutations, the `swamp access` commands already invoke model methods. The `--server` path just needs to:

  1. Detect `--server` flag → early exit from local execution
  2. Construct the `model.method.run` payload (model type = `swamp/grant` or `swamp/group`, method name, inputs)
  3. Call `streamServerRun()` from `src/cli/remote_run.ts`
  4. Render the response with the existing renderer

For `grant list` and `group list`, the remote path needs a way to query data on the server. This could use a new `data.query` frame type, or piggyback on an existing mechanism.

For `access check`, add a new `access.check` frame type to the serve protocol.

Scope

  • Add `--server` option to all `swamp access` commands
  • Route grant/group mutations through existing `model.method.run` protocol
  • Add `access.check` frame type to serve protocol (request + response)
  • Add `data.query` or equivalent mechanism for remote grant/group listing
  • Server-side handlers in `src/serve/connection.ts`
  • Tests for remote routing, protocol frames, and server handlers

Out of scope

  • Authorization enforcement on grant mutations — layer 5
  • OAuth authentication for the `--server` connection — layer 4
  • The declarative grants file (#680) — closed in favor of this approach

References

  • Remote execution pattern: `src/cli/remote_run.ts`
  • Server request handling: `src/serve/connection.ts`
  • Model method run `--server` routing: `src/cli/commands/model_method_run.ts` (lines 175-180)
  • Serve protocol: `src/serve/protocol.ts`
  • Access CLI commands: `src/cli/commands/access_grant.ts`, `access_group.ts`, `access_check.ts`
  • PolicySnapshotLoader hot reload: `src/domain/access/policy_snapshot_loader.ts`
  • Grant model: `src/domain/models/access/grant_model.ts`
  • Group model: `src/domain/models/access/group_model.ts`
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 3 MOREPR_MERGED+ 1 MORENOTIFICATION_SKIPPED

Shipped

6/18/2026, 8:40:45 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack726/18/2026, 5:58:41 PM
stack72 linked parent of #6226/18/2026, 6:00:27 PM
Editable. Press Enter to edit.

stack72 commented 6/18/2026, 5:56:11 PM

Design update: explicit reload instead of hot reload

Policy snapshot rebuilds should NOT auto-fire on every grant mutation by default. A bad deny rule taking effect instantly could lock out the entire team with no recovery window.

Three reload triggers

  1. Initial load — always, at server startup
  2. Explicit reload command — `swamp access reload --server wss://...` triggers a snapshot rebuild on the server. New policy takes effect only when the admin deliberately applies it.
  3. Auto-reload (opt-in) — configurable via serve config, disabled by default. When enabled, re-enables the EventBus subscription from #672 where mutations take effect immediately.

Config

```yaml auth: grantReload: manual # manual (default) | auto ```

  • `manual` — EventBus subscription inactive. Grants are staged in the data store until `swamp access reload` is called. Safety-first default.
  • `auto` — EventBus subscription active. Mutations take effect immediately (current #672 behavior). For teams that trust their admins and want speed.

Workflow for a 4-person team

  1. Sarah (admin) creates grants: `swamp access grant create --server wss://...` — stored but not enforced
  2. Sarah reviews: `swamp access grant list --server wss://...` — sees pending state
  3. Sarah applies: `swamp access reload --server wss://...` — policy snapshot rebuilds, new grants enforced
  4. If something is wrong: revoke the bad grant, reload again

Implementation impact

  • `PolicySnapshotLoader` from #672 needs a mode flag: when `manual`, the EventBus subscription is not registered. Snapshot rebuilds only on `load()` call.
  • New `access.reload` frame type in the serve protocol (request triggers `loader.load()`, response confirms success).
  • New `swamp access reload` CLI command with `--server` support.

This is the nginx/firewall model — stage changes, then commit.

Sign in to post a ripple.