feat(tenant): grant points scopes on provisioned keys

Why:
- points:read and points:write are named in ADR-0009 but were absent from
  DEFAULT_SCOPES, so a provisioned key could not reach the read paths that
  follow. The runbook documented only files:write and domains:write, which
  understated what a default key can now do.

Changes:
- Add points:read and points:write to DEFAULT_SCOPES.
- Document the full scope table in the runbook, calling out that points:read
  grants the text of every chunk of every file -- so an upload-only key gets
  files:write alone.

Impact:
- Keys issued before this change keep their existing scopes; provisioning does
  not backfill. Reissue or widen an existing key explicitly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 15:08:39 +03:30
parent 062fdd7ac1
commit 5251990444
2 changed files with 23 additions and 5 deletions

View File

@@ -80,10 +80,22 @@ Re-running with the same `--slug` reuses the tenant and any domains it already
has, and issues an **additional** key. Both keys stay valid; this adds a key, it has, and issues an **additional** key. Both keys stay valid; this adds a key, it
does not rotate one. does not rotate one.
Scopes are the security boundary between uploading and managing the allowlist. Scopes are the security boundary between uploading, reading chunks, and
Give an upload client `files:write` only. `domains:write` lets its holder create managing the allowlist. Give an upload client `files:write` only. `domains:write`
new domains, which is exactly what the allowlist exists to prevent an upload key lets its holder create new domains, which is exactly what the allowlist exists to
from doing. prevent an upload key from doing, and `points:read` lets its holder read the text
of every chunk of every file — so an upload-only key gets neither.
| Scope | Grants |
|---|---|
| `files:write` | Upload a document and read its ingestion status. |
| `points:read` | Read, list, count, and keyword-search this tenant's points, including `GET /v1/files/{file_id}/points`. |
| `points:write` | Create, edit, reorder, and soft-delete points (plan 002 Phases 3-5; no route uses it yet). |
| `domains:read` / `domains:write` | Inspect and manage the domain allowlist. |
| `admin` | Satisfies every scope check. |
The command's `--scopes` default issues all of the above except `admin`, which
suits a first operator key; narrow it explicitly for per-client keys.
### Domains after the first one ### Domains after the first one

View File

@@ -28,7 +28,13 @@ from src.infrastructure.postgres.repositories import tenants as tenants_repo
logger = structlog.get_logger(__name__) logger = structlog.get_logger(__name__)
DEFAULT_SCOPES = ("files:write", "domains:read", "domains:write") DEFAULT_SCOPES = (
"files:write",
"domains:read",
"domains:write",
"points:read",
"points:write",
)
@dataclass(frozen=True) @dataclass(frozen=True)