Files
substrate/cmd/ateapi/internal/controlapi/functionaltest
Shruti Nair e9a31a9f51 ateapi: Implement Policy Governance CRUD Access Policy APIs (#1965)
Working on https://github.com/agent-substrate/substrate/issues/1563

Fixes https://github.com/agent-substrate/substrate/issues/1697,
https://github.com/agent-substrate/substrate/issues/1701

## Summary

Adds `AccessPolicy` singleton subresources for the Global
(`global:root`) and `Atespace` scopes, stored in PostgreSQL and enforced
through OpenFGA.

### Changes
- **API (`Control` service)**
- Global: `CreateGlobalAccessPolicy`, `GetGlobalAccessPolicy`,
`UpdateGlobalAccessPolicy` (no Delete; an update with empty bindings
clears all grants).
- Atespace: `CreateAtespaceAccessPolicy`, `GetAtespaceAccessPolicy`,
`UpdateAtespaceAccessPolicy`, `DeleteAtespaceAccessPolicy`.
  - `Binding.members` has set semantics.
- **Always-wired authz**: the OpenFGA server, `Authorizer` and
`PolicyManager` are always created. `--experimental-enable-authz` only
controls whether the interceptor enforces checks on regular RPCs. Policy
tuples therefore stay in sync with the store in every mode, and deleting
an atespace always removes its tuples.
- **Governance RPCs are always enforced**: AccessPolicy RPCs are marked
`alwaysEnforce` in the permission registry and are checked even when
enforcement is off, so nobody can grant themselves access before it is
turned on. A test fails if an AccessPolicy RPC is added without that
marker.
- **Bootstrap owners**: `--authz-bootstrap-owners` lists
server-configured owners (required when enforcing). They are never
written to the store: `Authorizer.Check` adds a contextual `owner
global:root` tuple for the calling user only, so access follows the
server config and is dropped on restart without them. A bootstrap owner
creates the global policy.
- **Storage**: migration `000003` adds `global_access_policy` and
`atespace_access_policies`. Deleting an atespace cascades to its policy
row and removes its OpenFGA tuples in the same transaction.
- **Consistency**: `PolicyManager` takes an explicit `pgx.Tx`, locks the
metadata row (`FOR UPDATE`) and reconciles OpenFGA tuples in the same
transaction. Optimistic concurrency via `uid` / `version`.
- **Hardening**: members with control characters are rejected, as are
blank, `*`, or control-character bootstrap owner entries; non-canonical
OpenFGA error codes are mapped to `Internal`.

### Testing
- Interceptor tests for both modes: with enforcement off, regular RPCs
skip the check while governance RPCs are still checked.
- Functional CRUD tests for both AccessPolicy scopes
(`functionaltest/access_policy_test.go`).
- PostgreSQL test that a policy update and its tuple writes commit or
roll back together, including under concurrent updates.
- Governance tests: creating the policy as a bootstrap owner, a
bootstrap owner also stored as owner, and a restart without a bootstrap
owner.

### Notes
- ateapi now needs OpenFGA available at startup even with enforcement
off, and atespace deletes always write OpenFGA tuples.
- TODO: move the authz settings into the hot-reloadable config proto
(#2021) once it lands.
2026-10-02 20:47:03 +00:00
..