Seed Data Specification¶
Purpose¶
Define the minimum bootstrap data required for a greenfield launch of db_schema_v1.sql.
This is static reference data — SKU catalog, policy definitions, global policy values, the
default pricing plan, and role baseline metadata.
The seed is idempotent. Running it multiple times on the same database is safe.
How to Run¶
Or via the Makefile target:
What Gets Seeded¶
1. SKU Catalog (sku_catalog)¶
GPU model definitions used as the reference for node assignments, marketplace display, and billing rate lookup.
| SKU | Vendor | Display Name | GPUs | Rate (USD/GPU/hr) |
|---|---|---|---|---|
mi300x |
AMD | AMD MI300X | 8 | 4.00 |
h200 |
NVIDIA | NVIDIA H200 | 8 | 4.00 |
h100 |
NVIDIA | NVIDIA H100 | 8 | 2.00 |
mi210x |
AMD | AMD MI210X | 8 | 2.00 |
cs-3 |
Cerebras | Cerebras CS-3 | 1 | 5.00 |
cs-4 |
Cerebras | Cerebras CS-4 | 1 | 5.00 |
Billing calculation: total_cost = gpus_total_snapshot × gpu_hourly_price_minor × hours.
Slice SKU resource profiles:
h200-sxm-slice stores mutable lab/runtime shape choices in
sku_catalog.resource_profile until they stabilize into a first-class table.
The current seed includes named slice_vm_profiles and a
default_slice_vm_profile rather than a hidden CPU/memory constant:
| Profile | GPU count | vCPU | Memory |
|---|---|---|---|
h200_1g_12c_64g |
1 | 12 | 64 GiB |
h200_1g_24c_64g |
1 | 24 | 64 GiB |
h200_2g_48c_128g |
2 | 48 | 128 GiB |
h200_4g_96c_256g |
4 | 96 | 256 GiB |
h200_8g_192c_512g |
8 | 192 | 512 GiB |
The default is h200_1g_24c_64g, matching the latest tested prototype. Future
implementation must snapshot the selected profile name and resolved CPU/memory
values onto the allocation placement so later seed updates do not change running
allocations.
2. Policy Definitions (policy_definitions)¶
Defines every valid policy key with its type, description, and allowed bounds. Must be
present before any policy_values row can be inserted (FK constraint).
| Policy Key | Type | Default | Bounds | Purpose |
|---|---|---|---|---|
billing.low_balance_threshold_minor |
integer | 500 | min 1, max 100000 | Cents below which billing.low_balance_warning is emitted |
billing.window_seconds |
integer | 60 | min 30, max 3600 | Billing accrual interval |
billing.minimum_deposit_minor |
integer | 1000 | min 100, max 1000000 | Minimum Stripe checkout amount (cents) |
billing.maximum_deposit_minor |
integer | 100000 | min 1000, max 10000000 | Maximum Stripe checkout amount (cents) |
allocation.max_concurrent_per_user |
integer | 50 | min 1, max 100 | Max active allocations per user |
allocation.refund_window_days |
integer | 30 | min 0, max 365 | Days after credit posting for refund eligibility |
rate_limit.api_requests_per_minute |
integer | 120 | min 1, max 10000 | API rate limit per authenticated user |
rate_limit.platform_proxy_requests_per_minute |
integer | 1200 | min 1, max 10000 | Browser-facing platform proxy traffic rate limit per authenticated user |
rate_limit.terminal_token_requests_per_minute |
integer | 10 | min 1, max 1000 | Terminal token minting rate limit |
rate_limit.financial_requests_per_minute |
integer | 30 | min 1, max 1000 | Financial endpoint rate limit |
rate_limit.admin_overview_requests_per_minute |
integer | 600 | min 1, max 5000 | Admin overview polling rate limit |
terminal.session_max_ttl_seconds |
integer | 14400 | min 300, max 86400 | Maximum lifetime of an active terminal session (independent of token TTL) |
auth.service_account_token_ttl_seconds |
integer | 900 | min 60, max 3600 | Service-account access token lifetime in seconds |
notification.low_balance_enabled |
boolean | true | — | Enable low-balance warning notifications |
notification.balance_depleted_enabled |
boolean | true | — | Enable balance-depleted notifications |
allocation.isolation_model |
string | user-revoke |
user-revoke, full-reimage |
Node isolation strategy between allocations: user-revoke revokes the OS user and SSH key (fast, ~10 s); full-reimage triggers a MAAS re-deploy (full OS wipe, ~5–15 min). Requires maas.enabled=true for full-reimage. |
maas.enabled |
boolean | false | — | Enable Canonical MAAS bare-metal integration. When true, the provisioning worker uses packages/services/maas.MAASClient for machine deploy/release; cloud-init userdata is delivered via the MAAS deploy API. |
Rate-limit strategy:
- rate_limit.api_requests_per_minute is the global default.
- Middleware resolves route-class overrides (platform_proxy, terminal_token, financial, admin_overview) and falls back to global default.
3. Global Policy Values (policy_values)¶
One scope_type = 'global' row per policy key, setting the system-wide default.
Per-org or per-user overrides can be added via the Admin API post-launch without
touching the seed.
The global values match the default_value column in policy_definitions. They are
inserted conditionally — if a global value already exists for a key the row is skipped
(idempotent by design).
4. Default Pricing Plan (pricing_plans)¶
A single standard pay-as-you-go plan is seeded with a stable UUID so it can be
referenced safely in subsequent scripts. New plans can be added via the Admin API.
Seed UUID: 10000000-0000-4000-8000-000000000001
5. Dev User Tenant/Project Bootstrap (conditional)¶
For each existing user row, seed ensures: - a personal tenant exists, - a default project exists, - active tenant/project membership rows exist.
This is idempotent and only normalizes already-existing users; it does not create new users.
6. Platform Role Baseline¶
Seed inserts built-in platform role definitions and v1 role versions:
- platform_superadmin
- platform_ops
- platform_user
For existing users with users.role='admin', seed inserts active platform_role_bindings
to platform_superadmin. This preserves admin reachability when
PLATFORM_ROLE_SOURCE=bindings is enabled.
7. App Catalog Baseline¶
Seed inserts the baseline app catalog plus one active version for each seeded app.
Current seeded catalog entries:
- ollama -> runtime backend bare_metal
- meta-llama-3-8b -> runtime backend bare_metal
- mlflow -> runtime backend bare_metal
- open-webui -> runtime backend bare_metal
- nginx -> runtime backend bare_metal
- postgresql -> runtime backend bare_metal
- slurm-reference -> runtime backend slurm
- rke2-self-managed -> runtime backend rke2
The rke2-self-managed entry is the first-slice self-managed Kubernetes validation
offering. It is intentionally narrow and is not the managed-Kubernetes product
contract.
What Does NOT Get Seeded¶
| Data | Reason |
|---|---|
| Users | No user data in seed — including no admin user (see Admin Bootstrap below) |
User SSH public keys (user_ssh_public_keys) |
User-provided runtime credentials; never seeded |
User POSIX identities (user_posix_identities) |
Created lazily on first provisioning request per user; not seeded |
| Nodes | Nodes are registered post-launch via POST /api/v1/admin/nodes |
| Organizations / Projects | Not pre-created globally; created on demand and conditionally bootstrapped for existing users |
| Ledger entries / usage records | Transactional data — never seeded |
| Stripe events | Live data — never seeded |
| Audit logs | Immutable live data — never seeded |
Admin Bootstrap Procedure¶
The first admin account is not created in the seed SQL — admin credentials in SQL files are a security risk.
Development (local)¶
The Keycloak dev realm includes a dev-admin user (admin123) with the admin realm
role. On first OIDC login via the app, the auth service creates the user record in
users with role = 'admin' (mapped from Keycloak's realm_access.roles claim).
No manual DB intervention required for local dev.
Local reset convenience:
- make db-reset-fast and make dev-reset-full-web run
scripts/seed-local-dev-identities.sh after schema + seed.
- This script bootstraps local-only identities aligned to current Keycloak sub
claims for the V3 persona set: dev-user, dev-project-admin,
dev-tenant-admin, dev-platform-admin, and dev-ops.
- The legacy dev-admin smoke account remains available for scripts and broad
admin testing.
- scripts/seed.sql still does not seed users.
API-backed local bootstrap helpers¶
Use SQL seed for foundational reference data only. Use API-backed helpers for runtime/product-managed objects once the owning domain API exists, so local and kind setup exercises the same validation, idempotency, audit, and authz paths as real operators.
Current classification:
| Bootstrap data | Owner | Mechanism |
|---|---|---|
| Policy definitions/values | Platform policy | scripts/seed.sql |
| Static catalog baselines | Inventory/app platform | scripts/seed.sql until CRUD ownership is complete |
| Local identity subject alignment | Auth/dev harness | scripts/seed-local-dev-identities.sh because it must align Keycloak sub claims with DB users |
| MAAS local site/profile | Provisioning/MAAS | make seed-local-maas through admin APIs |
| Persona platform roles | IAM/platform roles | make seed-local-platform-roles-api through admin APIs |
make seed-local-platform-roles-api binds dev-admin,
dev-platform-admin, and dev-ops through
POST /api/v1/admin/users/{user_id}/platform-roles and verifies the result
through GET /api/v1/admin/users/{user_id}/platform-roles. Run it after the API
is up. It is intentionally separate from db-reset-fast, because db-reset-fast
must remain usable before API containers are started.
Production¶
- Create the first admin account in Keycloak admin console (
http://<keycloak>/admin). - Assign the
adminrealm role. - Log in via the app OIDC flow — the user record is created automatically with
role = admin. - All subsequent admin operations are available via
POST /api/v1/admin/*endpoints.
If OIDC is not yet configured, use the POST /api/v1/auth/personal/login endpoint
(requires password_hash set directly in the DB via a one-time bootstrap script — not
the seed — and must be removed after OIDC is operational).
Idempotency Guarantee¶
All inserts in seed.sql use either:
- ON CONFLICT (pk) DO NOTHING for tables with stable primary keys
- A WHERE NOT EXISTS guard for policy_values (global scope uniqueness is enforced
by a partial unique index, not the PK)
Running the seed after a partial failure or re-deployment is safe.
SSH Key Selection Policy (runtime, not seed)¶
- Users upload keys via
POST /api/v1/ssh-keys. - Exactly one active key per user may be marked default (
is_default = true). - New allocations use the user's default active key unless the request sets
ssh_key_ids. PUT /api/v1/allocations/{allocation_id}/ssh-keyssupports per-allocation multi-key override and rotation.