Skip to content

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).

SetTest-user countUse case
minimal0CI fixture warm-up — no RBAC walkthrough
default5Sales-demo / eval / first-touch
full5Same five as default; denser rest of dashboard
full-plus8Adds three auth-mode + dual-admin profiles
stress8Same 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).

EmailDisplay nameOrg roleProject membershipsAPI-token scopes
alice@acme.exampleAlice (org admin)org_admin(org-wide — sees every project)artifacts:read, artifacts:write, admin:write
bob@acme.exampleBob (project admin — demo-acme-platform)demo-acme-platformproject_adminartifacts:read, artifacts:write
carol@acme.exampleCarol (read-only — demo-internal-tools)demo-internal-toolsreaderartifacts:read
dave@acme.exampleDave (developer — two projects)demo-acme-platformdeveloper, demo-bigco-mobiledeveloperartifacts:read, artifacts:write
eve@external.exampleEve (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:

Userdemo-acme-platformdemo-bigco-mobiledemo-internal-tools
alice200 (org admin)200 (org admin)200 (org admin)
bob200 (project_admin)403403
carol403403200 (reader; GET only)
dave200 (developer)200 (developer)403
eve403403403

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.

EmailDisplay nameOrg roleProject membershipsAPI-token scopesNotes
frank@acme.exampleFrank (token-only CI bot — maintainer scope)demo-acme-platformmaintainerrepo:read, repo:write (custom override)Token-only profile — no SAML / OIDC; CI-bot identity
gina@acme.exampleGina (SAML-asserted — multi-project maintainer)demo-acme-platformmaintainer, demo-bigco-mobilemaintainer, demo-internal-toolsdeveloperartifacts:read, artifacts:writeDemos a SAML-asserted multi-project posture
hank@acme.exampleHank (second org admin — break-glass)org_admin(org-wide)artifacts:read, artifacts:write, admin:writeBreak-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.

bash
# 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;
SQL

Sample output:

text
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:

bash
# 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
# → 403

Repeat 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:

  1. --reset enumerates seeded_entities WHERE set_name = $set and walks each entity_type group.
  2. The test_user group resolves each entity_id UUID to a row in users and runs DELETE FROM users WHERE id = $1.
  3. The users row drop cascades to role_bindings (FK ON DELETE CASCADE), api_tokens (FK ON DELETE CASCADE), and the seed_test_user_tokens row (FK ON DELETE CASCADE on user_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) in docs/PRODUCT-ROADMAP.md.

Released under the Apache-2.0 License.