Demo seeder — orbital seed
orbital seed populates a fresh OrbitalReg install with a self- consistent demo universe — projects, repositories, artifacts, trust policies, banners, webhooks, and a small set of test users with gestaffelte project access. The test-user matrix is what this page documents: who can see what, what each role grants, and how to verify RBAC isolation against your live API surface from a curl shell or an integration test.
The data definitions live in api/internal/seed/bundles_test_users.go (canonical source-of-truth); the apply path is api/internal/seed/apply_phase_users.go. This page mirrors them as a human-readable reference. If you change the matrix in Go, update this page in the same commit so the documented expectations don't drift from what the seeder writes.
Sets and tiers
Each set is a strict superset of the lighter tiers — full extends default, full-plus extends full, stress extends full-plus. The minimal set ships zero test users (it's the fastest tier and is intended for CI fixture warm-up).
| Set | Test-user count | Use case |
|---|---|---|
minimal | 0 | CI fixture warm-up — no RBAC walkthrough |
default | 5 | Sales-demo / eval / first-touch |
full | 5 | Same five as default; denser rest of dashboard |
full-plus | 8 | Adds three auth-mode + dual-admin profiles |
stress | 8 | Same eight; large repo fan-out for profiling |
The default-tier matrix (5 users)
The five profiles below land on every default / full / full-plus / stress install. Roles use the canonical role_name enum (reader / developer / maintainer / project_admin / org_admin).
| Display name | Org role | Project memberships | API-token scopes | |
|---|---|---|---|---|
alice@acme.example | Alice (org admin) | org_admin | (org-wide — sees every project) | artifacts:read, artifacts:write, admin:write |
bob@acme.example | Bob (project admin — demo-acme-platform) | — | demo-acme-platform → project_admin | artifacts:read, artifacts:write |
carol@acme.example | Carol (read-only — demo-internal-tools) | — | demo-internal-tools → reader | artifacts:read |
dave@acme.example | Dave (developer — two projects) | — | demo-acme-platform → developer, demo-bigco-mobile → developer | artifacts:read, artifacts:write |
eve@external.example | Eve (external — no memberships) | — | (none — deliberate-negative case) | artifacts:read |
Why eve still gets a token. The deliberate-negative profile is load-bearing. Without it, a regression that returns 200 across the board would still pass an "all-positive" matrix trivially. eve has a real bearer token so the RBAC layer answers her requests with 403 Forbidden, not 401 Unauthorized from a missing-credential layer — that's the precise signal the integration test asserts.
Per-project access matrix
Reading the documented memberships row-by-row gives the canonical RBAC expectation against the three default-tier projects:
| User | demo-acme-platform | demo-bigco-mobile | demo-internal-tools |
|---|---|---|---|
| alice | 200 (org admin) | 200 (org admin) | 200 (org admin) |
| bob | 200 (project_admin) | 403 | 403 |
| carol | 403 | 403 | 200 (reader; GET only) |
| dave | 200 (developer) | 200 (developer) | 403 |
| eve | 403 | 403 | 403 |
Write-side semantics layer on top of read access: carol's reader role accepts GET against demo-internal-tools and rejects POST / DELETE with 403; dave's developer role accepts both read and write against the two projects he's a member of.
The full-plus tier extras (3 additional users)
full-plus and stress keep the five default profiles and add three richer profiles for sales walkthroughs that need to demo auth-mode variants and a dual-admin posture.
| Display name | Org role | Project memberships | API-token scopes | Notes | |
|---|---|---|---|---|---|
frank@acme.example | Frank (token-only CI bot — maintainer scope) | — | demo-acme-platform → maintainer | repo:read, repo:write (custom override) | Token-only profile — no SAML / OIDC; CI-bot identity |
gina@acme.example | Gina (SAML-asserted — multi-project maintainer) | — | demo-acme-platform → maintainer, demo-bigco-mobile → maintainer, demo-internal-tools → developer | artifacts:read, artifacts:write | Demos a SAML-asserted multi-project posture |
hank@acme.example | Hank (second org admin — break-glass) | org_admin | (org-wide) | artifacts:read, artifacts:write, admin:write | Break-glass dual-admin profile — useful for demoing admin:* MFA story |
Reading the plaintext tokens
orbital seed mints a fresh orbr_<48-char-base62> API token for each test user, hashes the plaintext into api_tokens.token_hash, and stashes the plaintext in the side-table seed_test_user_tokens(user_id, set_name) so an integration test (or a curious operator) can authenticate as each profile without an auth roundtrip. Production token mints never persist plaintext — the side-table only carries seeded demo tokens.
Because the side-table holds plaintext, it's keyed by both user_id and set_name and the rows live behind psql access only. There is no admin UI surface for them, by design.
# Read the alice token from the demo install (set=default)
psql -d orbitalreg -At <<'SQL'
SELECT u.email, t.plaintext_token
FROM seed_test_user_tokens t
JOIN users u ON u.id = t.user_id
WHERE t.set_name = 'default'
ORDER BY u.email;
SQLSample output:
alice@acme.example|orbr_<48 chars>
bob@acme.example|orbr_<48 chars>
carol@acme.example|orbr_<48 chars>
dave@acme.example|orbr_<48 chars>
eve@external.example|orbr_<48 chars>The plaintext is stable for the lifetime of one seeded install; a re-run with orbital seed --reset && orbital seed --set=default --force rotates every token (the users row is dropped, which cascades the row in seed_test_user_tokens, so the next apply mints a fresh plaintext).
Verifying the matrix from curl
The documented 200/403 matrix above is what the Phase D integration test (TestSeedAccessMatrix, lands in tests/integration/seed/access_matrix_test.go) pins as a regression guard. You can spot-check the same matrix from a curl shell against your local install:
# Pick a token out of the side-table
BOB_TOKEN=$(psql -d orbitalreg -At -c "
SELECT t.plaintext_token
FROM seed_test_user_tokens t
JOIN users u ON u.id = t.user_id
WHERE u.email = 'bob@acme.example' AND t.set_name = 'default'
")
# Bob is project_admin on demo-acme-platform → expect 200
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Authorization: Bearer $BOB_TOKEN" \
http://localhost:8080/api/v1/projects/demo-acme-platform
# → 200
# Bob has no membership on demo-internal-tools → expect 403
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Authorization: Bearer $BOB_TOKEN" \
http://localhost:8080/api/v1/projects/demo-internal-tools
# → 403Repeat for each row of the per-project matrix above. A single 200 in a row that should answer 403 means the RBAC layer is broken; a 401 means the bearer-token layer is broken (token rotated, revoked, or the side-table got out of sync with api_tokens — likely cause is a hand-edit, not a seeder bug).
The eve case is the deliberate canary: every project URL must answer 403. If eve gets a 200, RBAC has gone wide-open.
Resetting between demo runs
The side-table cleans up automatically on orbital seed --reset. The chain is:
--resetenumeratesseeded_entities WHERE set_name = $setand walks eachentity_typegroup.- The
test_usergroup resolves eachentity_idUUID to a row inusersand runsDELETE FROM users WHERE id = $1. - The
usersrow drop cascades torole_bindings(FK ON DELETE CASCADE),api_tokens(FK ON DELETE CASCADE), and theseed_test_user_tokensrow (FK ON DELETE CASCADE onuser_id).
After --reset the side-table is empty for the cleared set. A second orbital seed --set=default --force re-mints fresh plaintexts; the ON CONFLICT (user_id, set_name) DO UPDATE on the side-table only fires when the same user already has a row for the same set, which only happens on a re-apply without an intervening reset.
What's not in scope
- Real auth-flow validation (SAML / OIDC). Verifying that the identity-provider integration mints the right roles requires an external IdP and is its own item. The seed test users authenticate via static bearer tokens, not via the SAML / OIDC handshake.
- Cross-tenant access. OrbitalReg is single-tenant by architecture decision (see roadmap item 24); a multi-tenant cross-tenant access matrix would need a separate seed-set design.
- RBAC under load. Performance-testing the RBAC layer is the remit of the load-test harness (item 32), not the demo seeder.
Cross-references
- Source of truth — Go data:
api/internal/seed/bundles_test_users.go - Apply path:
api/internal/seed/apply_phase_users.go - Side-table migration:
api/internal/db/migrations/104_seed_test_user_tokens.up.sql - Default-bundle context:
api/internal/seed/bundles.go(defaultBundle) - Roadmap item: see item 98 (
orbital seed— Test-User mit gestaffelter Project-Access-Matrix) indocs/PRODUCT-ROADMAP.md.