Summary
While testing permission deletion (PR #1685) end-to-end against a local server (ConnectRPC + SpiceDB), three pre-existing rough edges surfaced in the permission/resource APIs. None are caused by #1685 — they live in CreatePermission and DeleteProjectResource — but they make working with custom permission namespaces awkward and can return a 500 where a clean client error is expected. Filing together for triage.
1. DeleteProjectResource returns 500 for a custom-namespace resource that has no delete verb
A permission namespace created via CreatePermission (e.g. compute/machine) only gets the verbs you define. DeleteProjectResource runs an authorization precheck for the delete permission on the resource's type, so if the namespace never defined a delete verb, the check fails inside SpiceDB and the call 500s.
Repro
CreatePermission with key lab.sample.scan (creates namespace lab/sample with only a scan verb).
CreateProjectResource with namespace lab/sample under some project.
DeleteProjectResource for that resource.
Actual: 500 internal. Server log:
IsAuthorized.CheckAuthz operation failed ...
error: rpc error: code = FailedPrecondition desc = relation/permission `delete` not found under definition `lab/sample`
method: /raystack.frontier.v1beta1.FrontierService/DeleteProjectResource
Expected: the resource can be deleted (or a clear client error), regardless of which verbs its namespace happens to define.
Why it matters: this collides with the new permission-delete guard, which refuses to delete the last permission of a namespace while relationships of that type still exist and advises "remove the resources first." For a namespace without a delete verb, you can't remove the resource through the API at all — the only way out is direct DB/SpiceDB cleanup.
2. CreatePermission silently no-ops in the core app/* namespaces
Creating a permission whose namespace is one of the built-in app namespaces (e.g. key app.organization.somenewverb) returns 200 with an empty body and creates nothing — those namespaces are filtered out server-side.
Repro
CreatePermission with key app.organization.zzcustom.
Actual: 200 {}, no permission created, no error.
Expected: either create it, or reject with a clear error explaining that core app namespaces are reserved. A silent success that does nothing is confusing.
3. CreatePermission returns 500 (not 400) when the service or verb is shorter than 3 characters
The service and verb segments of a permission key become SpiceDB relation/definition identifiers, which must match ^[a-z][a-z0-9_]{1,62}[a-z0-9]$ (minimum 3 characters). A shorter segment passes API validation but fails at schema-compile time, returning a 500.
Repro (each 500s)
- key
foo.bar.go (verb go, 2 chars)
- key
zoo.animal.it (verb it, 2 chars)
- key
r2.widget.read (service r2, 2 chars)
For comparison, abc.widget.read and zoo.animal.use (all segments ≥3 chars) succeed.
Actual: 500 internal. Server log:
CreatePermission.AppendSchema operation failed ...
error: ... compile: failed to compile authz schema: ... invalid Relation.Name:
value does not match regex pattern "^[a-z][a-z0-9_]{1,62}[a-z0-9]$"
Expected: validate the key segments at the API and reject with a 400 InvalidArgument describing the constraint, instead of letting it reach SpiceDB and 500.
Found while manually testing PR #1685. The permission-delete behavior in that PR works correctly; these are separate, pre-existing API issues.
Summary
While testing permission deletion (PR #1685) end-to-end against a local server (ConnectRPC + SpiceDB), three pre-existing rough edges surfaced in the permission/resource APIs. None are caused by #1685 — they live in
CreatePermissionandDeleteProjectResource— but they make working with custom permission namespaces awkward and can return a500where a clean client error is expected. Filing together for triage.1.
DeleteProjectResourcereturns 500 for a custom-namespace resource that has nodeleteverbA permission namespace created via
CreatePermission(e.g.compute/machine) only gets the verbs you define.DeleteProjectResourceruns an authorization precheck for thedeletepermission on the resource's type, so if the namespace never defined adeleteverb, the check fails inside SpiceDB and the call 500s.Repro
CreatePermissionwith keylab.sample.scan(creates namespacelab/samplewith only ascanverb).CreateProjectResourcewith namespacelab/sampleunder some project.DeleteProjectResourcefor that resource.Actual:
500 internal. Server log:Expected: the resource can be deleted (or a clear client error), regardless of which verbs its namespace happens to define.
Why it matters: this collides with the new permission-delete guard, which refuses to delete the last permission of a namespace while relationships of that type still exist and advises "remove the resources first." For a namespace without a
deleteverb, you can't remove the resource through the API at all — the only way out is direct DB/SpiceDB cleanup.2.
CreatePermissionsilently no-ops in the coreapp/*namespacesCreating a permission whose namespace is one of the built-in app namespaces (e.g. key
app.organization.somenewverb) returns200with an empty body and creates nothing — those namespaces are filtered out server-side.Repro
CreatePermissionwith keyapp.organization.zzcustom.Actual:
200 {}, no permission created, no error.Expected: either create it, or reject with a clear error explaining that core app namespaces are reserved. A silent success that does nothing is confusing.
3.
CreatePermissionreturns 500 (not 400) when the service or verb is shorter than 3 charactersThe service and verb segments of a permission key become SpiceDB relation/definition identifiers, which must match
^[a-z][a-z0-9_]{1,62}[a-z0-9]$(minimum 3 characters). A shorter segment passes API validation but fails at schema-compile time, returning a 500.Repro (each 500s)
foo.bar.go(verbgo, 2 chars)zoo.animal.it(verbit, 2 chars)r2.widget.read(servicer2, 2 chars)For comparison,
abc.widget.readandzoo.animal.use(all segments ≥3 chars) succeed.Actual:
500 internal. Server log:Expected: validate the key segments at the API and reject with a
400 InvalidArgumentdescribing the constraint, instead of letting it reach SpiceDB and 500.Found while manually testing PR #1685. The permission-delete behavior in that PR works correctly; these are separate, pre-existing API issues.