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

Relationships

#1037 serve-auth: declarative grants directory with validate-and-reconcile on reload

Opened by stack72 · 7/8/2026· Shipped 7/8/2026

Parent

Sub-issue of #662 (serve authentication & authorization). Phase 2.

Summary

Add a `grants/` directory convention to the swamp repo. Admins author grant definitions as YAML files, check them into git, and apply them to a running swamp serve instance via `swamp access reload`. The reload validates all files, reconciles them against stored grants, rebuilds the policy snapshot, and returns the full active grant list.

File grants and CLI grants coexist — no merging, no precedence, no conflict resolution. Both are grants in the same store. The `source` field tells the admin where each came from. The reconciler only touches `source: file:*` grants, never CLI or config grants.

File format

```yaml

grants/platform-team.yaml

grants:

  • subject: idp-group:platform-eng effect: allow actions: [run] resource: workflow:@acme/*

  • subject: idp-group:platform-eng effect: allow actions: [run] resource: workflow:@acme/* condition: 'tags.env == "staging"' ```

```yaml

grants/compliance.yaml

grants:

  • subject: idp-group:developers effect: allow actions: [read] resource: data:*

  • subject: idp-group:developers effect: deny actions: [read] resource: data:@acme/secrets-* ```

Files live in `grants/` at the repo root, alongside `models/`, `workflows/`, `vaults/`.

How it works

At serve startup

  1. Server reads the `grants/` directory
  2. Validates every entry in every file
  3. If any validation fails → refuses to start with clear errors (which file, which entry, what's wrong)
  4. If all valid → reconciles against stored `source: file:*` grants (create new, revoke removed, skip unchanged)
  5. Loads the policy snapshot (includes file grants, CLI grants, and config grants)

On reload (user-initiated)

```bash swamp access reload --server wss://... ```

Reload now does:

  1. Read the `grants/` directory on the server

  2. Validate every entry — YAML syntax, subject format, resource selectors, actions, CEL conditions through cost-bounding (#1029), no duplicate identity tuples within a file

  3. If any validation fails → reject the entire reload, keep current policy, return clear errors: ``` Reconciliation aborted. Current policy unchanged. grants/compliance.yaml entry 2: CEL condition exceeds cost budget ```

  4. If all valid → reconcile `source: file:*` grants (create new, revoke removed, skip unchanged)

  5. Rebuild policy snapshot (picks up file + CLI + config grants)

  6. Return the full active grant list so the admin sees exactly what's in effect: ``` Reading grants/platform-team.yaml... 3 grants Reading grants/compliance.yaml... 2 grants Validating... ok Reconciling files: 2 created, 0 revoked, 3 unchanged Policy snapshot reloaded

    Active grants: ID SUBJECT EFFECT ACTIONS RESOURCE SOURCE a45619aa user:paul allow admin access:* config 7f3a1b2c idp-group:platform-eng allow run workflow:@acme/* file:platform-team.yaml b0270ca5 idp-group:developers allow read data:* file:compliance.yaml 9c2d4e6f idp-group:developers deny read data:@acme/secrets-* file:compliance.yaml 33f8c4a6 user:sarah allow run model:hello method ```

If there are no grant files, reload just rebuilds the snapshot (today's behavior unchanged).

Validation checks

Per entry:

  • Subject format valid (`user:`, `group:`, `idp-group:`)
  • Effect is `allow` or `deny`
  • Actions are valid (`run`, `read`, `write`, `admin`)
  • Resource selector valid (no non-trailing wildcards)
  • CEL conditions pass cost-bounding (AST depth, comprehension nesting, cost budget, matches() validation)

Per file:

  • Valid YAML syntax
  • No duplicate identity tuples `(subject, effect, actions, resource, condition)` within the file

Across files:

  • Duplicates across files are allowed — both get created, admin sees them in grant list

Coexistence with CLI grants

File grants (`source: file:platform-team.yaml`) and CLI grants (`source: method`) coexist in the same store. The reconciler only touches `source: file:*` grants — it never creates, modifies, or revokes CLI grants.

If duplicates exist (same subject/resource from both file and CLI), both show up in `grant list`. The evaluation engine handles them normally — deny-wins, first match. The admin can clean up duplicates manually if they want.

Same model as Kubernetes: `kubectl apply` from files and `kubectl create` imperatively both create resources, both show in `kubectl get`, the `managed-by` label tells you which tool owns it.

Identity matching for reconciliation

File entries don't have IDs. Match stored `source: file:` grants to file entries by the tuple `(subject, effect, sorted actions, resource, condition)`. Sort actions before comparing so `[read, run]` and `[run, read]` match. Trim condition whitespace.

What to build

1. Grants file parser and schema

In `src/domain/access/` — parse YAML, validate entries, detect duplicates. Returns a typed array of grant entries.

2. Grants file reconciler

In `src/domain/access/` — takes parsed entries + filename, queries stored `source: file:` grants, diffs, creates/revokes. Same direct-write pattern as the admin materializer (no `modelMethodRun`).

3. Extend reload to reconcile files first

In the serve layer — on reload, read the `grants/` directory, run the reconciler for each file, then rebuild the snapshot. Return the full grant list in the response.

4. `swamp access apply` CLI command (optional)

A local-only command for validating grant files without a server: ```bash swamp access apply --validate grants/ ```

5. `swamp repo init` creates the `grants/` directory

Add `grants/` to the directories created by `repo init`.

Out of scope

  • Rule packs (extensions shipping grants) — separate issue
  • Choosing between file and CLI mode — both coexist
  • Cross-file duplicate detection — duplicates across files are allowed

References

  • Admin materializer (reconciliation pattern): `src/domain/access/admin_materializer.ts`
  • CEL cost-bounding (validation): `src/infrastructure/cel/grant_condition_environment.ts`
  • Grant model: `src/domain/models/access/grant_model.ts`
  • Resource selector validation: `src/domain/access/resource_selector.ts`
  • Existing reload handler: `src/serve/connection.ts` (`handleAccessReload`)
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 4 MOREPR_MERGED+ 1 MORENOTIFICATION_SKIPPED

Shipped

7/8/2026, 9:43:30 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack727/8/2026, 7:54:18 PM

Sign in to post a ripple.