Relationships
↑ child of #662#680 serve-auth: declarative grants file with startup reconciliation
Opened by stack72 · 6/18/2026
Parent
Sub-issue of #662 (serve authentication & authorization). Layer 2.
Summary
Add support for a declarative grants file — a YAML file checked into the repo that defines authorization grants, reconciled at `swamp serve` startup. This gives infra teams the ability to manage access policy through git rather than imperative CLI commands.
Grants from the file are tagged `source: file` so the reconciler owns only its own records and never touches imperatively-created grants (`source: method`) or config-sourced admin grants (`source: config`).
File format
Location: a `grants.yaml` file (or configurable path) in the repo, e.g. at the repo root or under a config directory.
```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"'
subject: idp-group:developers effect: deny actions: [read] resource: data:@acme/pii
subject: user:adam effect: allow actions: [admin] resource: access:* ```
The schema should reserve room for a `packs:` key at the top level for phase 2 rule packs, even though it's not implemented yet:
```yaml
Phase 2 — not implemented, but the schema should accept and ignore this key
packs:
- @acme/soc2-baseline
grants:
- ... ```
Reconciliation semantics
On `swamp serve` startup (and optionally via a CLI command), the reconciler:
- Reads the grants file and parses each entry
- Loads all existing grants with `source: file` from the data store via `DataQueryService.query()`
- Diffs file entries against stored grants:
- In file but not in store → create (via Grant model `create` method with `source: file`)
- In file and in store with matching fields → no-op
- In file but stored grant has different fields → revoke old, create new (grants are immutable once created — field changes are a new grant)
- In store with `source: file` but not in file → revoke (the file no longer declares it)
- Never touches grants with other sources — `source: method`, `source: config`, or `source: extension:*` are invisible to the reconciler
Identity matching
Grants in the file don't have IDs — the file declares the desired state, not specific instances. Match file entries to stored grants by the tuple `(subject, effect, actions, resource, condition)`. If all fields match, the grant is unchanged. If any field differs, it's treated as a removal + creation.
Validation
Each grant entry is validated at reconciliation time:
- Subject, resource selector, and actions are parsed through the existing value object parsers
- CEL conditions are validated via `validateGrantCondition()` from #670
- Invalid entries fail the reconciliation with a clear error (which entry, which field, what's wrong)
- The server should refuse to start if the grants file has validation errors — bad policy is not a warning
Where it runs
At serve startup
In `src/cli/commands/serve.ts`, after `requireInitializedRepoUnlocked()` and model registry loading, before the server starts accepting connections. This ensures the policy snapshot is current before any authorization decisions.
Optionally via CLI
A `swamp access reconcile` command (or similar) that runs the same reconciliation logic outside of serve, for testing and debugging the grants file without starting the server.
Scope
- Grants file parser and schema (YAML, with `packs:` key reserved)
- Reconciliation service in `src/domain/access/` — pure domain logic, testable in isolation
- Wire reconciliation into `swamp serve` startup
- Optionally add a `swamp access reconcile` CLI command
- Tests: file parsing, reconciliation (create/no-op/revoke), validation errors, `source: file` scoping (doesn't touch other sources)
Out of scope
- `packs:` key implementation — phase 2 (#662)
- Admin materialization from config — layer 4
- Serve enforcement — layer 5
References
- Grant model: `src/domain/models/access/grant_model.ts`
- Grant source values: `src/domain/access/grant_source.ts` (`file` is already a valid source)
- CEL validation: `src/infrastructure/cel/grant_condition_environment.ts`
- Serve startup: `src/cli/commands/serve.ts` (lines 122-191)
- Extension reconciliation pattern: `src/libswamp/extensions/reconcile_from_disk_service.ts`
- Grant query pattern: `queryGrants()` in `src/cli/commands/access_grant.ts`
- Access CLI (#678): `src/cli/commands/access_grant.ts`
Closed
No activity in this phase yet.