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-platform → project_adminartifacts:read, artifacts:write
carol@acme.exampleCarol (read-only — demo-internal-tools)—demo-internal-tools → readerartifacts:read
dave@acme.exampleDave (developer — two projects)—demo-acme-platform → developer, demo-bigco-mobile → developerartifacts: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-platform → maintainerrepo:read, repo:write (custom override)Token-only profile — no SAML / OIDC; CI-bot identity
gina@acme.exampleGina (SAML-asserted — multi-project maintainer)—demo-acme-platform → maintainer, demo-bigco-mobile → maintainer, demo-internal-tools → developerartifacts: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.

OrbitalReg ships under the OrbitalReg EULA with a Free Forever tier.