Roles and the project boundary¶
Who can see and do what, in one table. The project is the unit of separation: every build, key, blob and setting belongs to exactly one project.
| Viewer | Project admin | Global admin | |
|---|---|---|---|
| Who | signed-in user (OIDC), or anyone in open mode | member of a project's admin groups | ADMIN_EMAILS / OIDC_ADMIN_GROUPS, or the admin token in open mode |
| Sees | projects with no allowed groups, plus those whose allowed groups include one of theirs | the same, plus the projects they administer | every project, archived ones included |
| Builds, dashboards, tests, downloads, artifacts | of visible projects only (restricted in SQL; other ids are 404) | same | all |
/p/<slug>/settings: API keys, retention and blob prefix, cache endpoints, segments, the project's audit trail |
no | yes, for their projects | yes |
/settings: create and archive projects, allowed groups, admin groups, every project's keys, the global audit log |
no | no | yes |
/metrics |
with METRICS_TOKEN |
with METRICS_TOKEN |
also with a session when no token is set |
API keys are per project and carry scopes: ingest (Bazel's --bes_header), upload
(the HTTP artifact API). A key of project A cannot read, upload to or finish an invocation
of project B; the server answers "not found". Keys can carry default tags, expire, be
rotated with a grace period and revoked; each has its own stream and event-rate limits.
Every mutation on a project authorizes against the project taken from the record being
changed, never from the form alone. The guarantee is tested for every route: a user of
project A gets 404 on project B's ids and slugs (test/conveyor_web/project_boundary_test.exs).
Blobs (profiles, test outputs, cache uploads) follow the same rule: keyed by project and stored under the project's own prefix, so operators can apply bucket policies, lifecycle rules or deletion per project. See Sign-in and access for the identity provider setup and Security model for the threat model.