v0.1.25.15 — canonical ScopeValidator
Bug fix release. Reported during v0.1.25.14 end-to-end testing: user created a budget with scope `tenant:acme/agentic:codex` ("agentic" is a typo for "agent") and the server 201'd. Server was doing essentially no canonical scope validation — `workspace:eng` without tenant prefix also worked.
What's new
`ScopeValidator` utility enforces the canonical grammar per `cycles-protocol-v0.yaml` SCOPE DERIVATION:
- First segment must be `tenant:`
- Each segment `:` with kind from tenant → workspace → app → workflow → agent → toolset in canonical order
- Ids non-empty, ≤128 chars, alphanumeric-bookended (rejects `.foo`, `foo-`)
- Cross-field: scope's tenant must match request `tenant_id` (admin-on-behalf-of routing integrity)
Policy `scope_pattern` also allows terminal wildcards (`tenant:acme/`, `tenant:acme/agent:`) per spec examples.
All rejections return 400 INVALID_REQUEST with a specific message pinpointing which segment broke which rule.
Affected endpoints
- `POST /v1/admin/budgets`
- `POST /v1/admin/policies`
- `PATCH /v1/admin/policies/{id}` (via forward-guard comment; `scope_pattern` is not in update body by design)
Backward compatibility
Existing non-canonical scopes in Redis keep working — read paths don't re-validate. Only new creates must be canonical; clients sending non-canonical now get 400 with a clear error message instead of silent success.
Tests
497/497 pass (was 459 at v0.1.25.14; +38 net) including 36 `ScopeValidatorTest` cases covering every rule + pre-merge review hardening (tightened regex, regression locks, equality-branch test).
Image
`ghcr.io/runcycles/cycles-server-admin:0.1.25.15` (also `:latest`).