Files
git.stella-ops.org/docs/qa/authority-default-tenant-20260422/EVIDENCE.md
master 47665927ab feat(authority): seed default + installation tenants via migration (SPRINT_20260422_005)
Closes the bootstrap gap two parallel QA agents surfaced on 2026-04-22:
fresh Authority DBs lacked the `default` tenant row so setup-wizard admin
creation failed with users_tenant_id_fkey and /connect/token returned
invalid_grant. Fix is on the migration path per AGENTS.md §2.7; the init
script stays seeds-only as established in SPRINT_20260422_003.

- New embedded migration 003_seed_default_tenants.sql performs
  `INSERT ... ON CONFLICT (tenant_id) DO NOTHING` for `default` and
  `installation`. Numeric prefix (not S-prefix) so the migration runner's
  Startup category auto-applies it; S-prefix files route to Seed category
  which is intentionally manual-only per
  StartupMigrationHost.cs:158.
- `default` is strictly required (Authority's
  StandardPluginBootstrapper.DefaultTenantId; /internal/users bootstrap
  inserts under this FK). `installation` is not Authority-FK-referenced
  today but matches the empirical workaround both QA agents converged on
  and serves as defense for cross-service inserts that join
  authority.tenants.tenant_id.

Fresh-volume verification (docs/qa/authority-default-tenant-20260422/):
1. docker compose down -v (20 volumes removed incl. compose_postgres-data)
2. docker compose up -d — 62 containers, Authority healthy in ~15s.
3. Startup log: applying 001 (144ms) → 002 (13ms) → 003 (7ms).
   authority.tenants contains default + installation.
4. POST /api/v1/setup/sessions → 201; database/valkey/migrations prereqs
   ran; admin/execute with admin/Admin@Stella2026! → 200 "Bootstrap
   administrator 'admin' ensured successfully."
5. POST /connect/token (password, stellaops-cli, ui.admin openid) → 200
   + JWT carrying role=admin, stellaops:tenant=default.
6. docker compose restart authority → "Database is up to date for
   Authority." Clean no-op.

Docs: docs/modules/authority/architecture.md §1.1 "Seeded bootstrap
tenants (migration-owned)". Cross-link added to the archived prior
sprint's Decisions & Risks so the lineage is traceable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:41:23 +03:00

6.1 KiB

Evidence: Authority default-tenant bootstrap (AUTH-SEED-002)

  • Sprint: docs/implplan/SPRINT_20260422_005_Authority_default_tenant_bootstrap.md
  • Task: AUTH-SEED-002 (fresh-volume verification that the setup-wizard admin flow succeeds without manual SQL inserts).
  • Date (UTC): 2026-04-22
  • Host: local dev (Docker Desktop)
  • Image rebuilt: stellaops/authority:dev (SHA 01d8c359fb92) — contains new 003_seed_default_tenants.sql embedded resource.

Verification summary

# Step Status Artifact
1 docker compose -f docker-compose.stella-ops.yml down -v (destroy volumes) PASS stack stopped, 20 volumes removed incl. compose_postgres-data
2 docker compose -f docker-compose.stella-ops.yml up -d (fresh volumes) PASS 62 containers started, authority healthy in ~15s
3 Authority startup applies migration 003 automatically PASS see 06-authority-migration-logs.txt, 07-schema-migrations.txt
4 POST /api/v1/setup/sessions + .../steps/admin/execute with admin / Admin@Stella2026! PASS see 01-create-session.json, 03-pre-admin-steps.json, 04-admin-execute.json
5 POST /connect/token returns 200 + JWT (not invalid_grant) PASS see 05-connect-token.json
6 Authority restart against same volume is a clean no-op PASS log line Migration: Database is up to date for Authority.

Key log excerpts

3) Migration runner applies 003 on the fresh DB (authority container startup)

[14:32:39 INF] Migration: 3 pending startup migration(s) for Authority.
[14:32:39 INF] Migration: Applying 001_initial_schema.sql (Startup)...
[14:32:39 INF] Migration: 001_initial_schema.sql completed in 144ms.
[14:32:39 INF] Migration: Applying 002_drop_deprecated_audit_tables.sql (Startup)...
[14:32:39 INF] Migration: 002_drop_deprecated_audit_tables.sql completed in 13ms.
[14:32:39 INF] Migration: Applying 003_seed_default_tenants.sql (Startup)...
[14:32:39 INF] Migration: 003_seed_default_tenants.sql completed in 7ms.
[14:32:39 INF] Migration: Applied 3 migration(s) for Authority in 694ms.

3) authority.schema_migrations ledger after fresh bring-up

            migration_name            | category |          applied_at           | duration_ms
--------------------------------------+----------+-------------------------------+-------------
 001_initial_schema.sql               | startup  | 2026-04-22 14:32:39.066637+00 |         132
 002_drop_deprecated_audit_tables.sql | startup  | 2026-04-22 14:32:39.199035+00 |           4
 003_seed_default_tenants.sql         | startup  | 2026-04-22 14:32:39.21278+00  |           3

3) authority.tenants rows present after auto-migrate (no manual inserts)

  tenant_id   |     name     |    display_name     | status
--------------+--------------+---------------------+--------
 default      | Default      | Default Tenant      | active
 installation | Installation | Installation Tenant | active
(2 rows)

4) Admin bootstrap succeeds via the public setup-wizard HTTP surface

Prerequisite steps (database, valkey, migrations) completed cleanly, then:

POST /api/v1/setup/sessions/setup-installation-20260422143427/steps/admin/execute
→ HTTP 200
  "stepId":"admin","status":"completed",
  "message":"Step applied successfully.",
  validationResults:[{
    "checkId":"check.auth.admin.exists","status":"passed",
    "message":"Bootstrap administrator 'admin' ensured successfully."
  }]

Admin user persisted under the seeded default tenant (the FK the setup wizard requires):

                  id                  | tenant_id | username | status | enabled
--------------------------------------+-----------+----------+--------+---------
 07b6e4c4-2a3a-42f0-ae1f-6b3ed4b5ca97 | default   | admin    | active | t

5) /connect/token password grant returns a live admin JWT

POST https://stella-ops.local/connect/token
  grant_type=password
  client_id=stellaops-cli
  username=admin
  password=Admin@Stella2026!
  scope=ui.admin openid
→ HTTP 200
  {
    "access_token": "eyJ...",
    "token_type": "Bearer",
    "expires_in": 1800,
    ...
  }

Decoded claims show sub, preferred_username=admin, role=admin, stellaops:tenant=default, client_id=stellaops-cli.

6) Authority restart is a pure no-op

[14:35:44 INF] Migration: Starting migration check for Authority...
[14:35:45 INF] Migration: 1 optional seed migration(s) are pending for Authority. They remain manual-only and will not run at startup.
[14:35:45 INF] Run manually when needed: stella system migrations-run --module Authority --category seed
[14:35:45 INF] Migration: Database is up to date for Authority.

Confirms migration 003 is idempotent (checksum matched, ledger accepted, zero rows changed on replay — the INSERT ... ON CONFLICT DO NOTHING protects the seed).

Artifacts

  • 01-create-session.jsonPOST /api/v1/setup/sessions response
  • 02-admin-execute.json — first admin-step attempt before prereqs (documents the "Blocked by incomplete dependencies: Migrations" gate that forces the wizard to run the DB/valkey/migrations steps first)
  • 03-pre-admin-steps.json — database / valkey / migrations step responses
  • 04-admin-execute.json — successful admin-step apply
  • 05-connect-token.json — admin token issuance
  • 06-authority-migration-logs.txt — full Migration: log timeline
  • 07-schema-migrations.txtauthority.schema_migrations ledger
  • 08-tenants.txtauthority.tenants contents after fresh bring-up
  • 09-admin-user.txt — admin row with tenant_id='default' FK reference

Conclusion

The fresh-volume setup flow now completes without any manual SQL insert into authority.tenants. The regression that forced two FE-QA agents (fe-qa-006-relsec, fe-qa-007-evidops) to hand-seed default / installation during SPRINT_20260421_006/007 is resolved by migration 003_seed_default_tenants.sql, applied by the Authority persistence assembly's startup migration runner (wired in SPRINT_20260422_003 / AUTH-MIGRATE-002, so no new runtime wiring was required — only the additional embedded SQL resource).