Skip to content

feat(admin): provision warehouses from the console via the shared provisioning API - #998

Open
benben wants to merge 1 commit into
mainfrom
ben/admin-ui-provision-warehouse
Open

feat(admin): provision warehouses from the console via the shared provisioning API#998
benben wants to merge 1 commit into
mainfrom
ben/admin-ui-provision-warehouse

Conversation

@benben

@benben benben commented Jul 28, 2026

Copy link
Copy Markdown
Member

What

Operators can provision a managed warehouse from the admin console instead of hand-rolling a curl against the control plane.

The console does not get its own provisioning implementation. The new page is a form over the public provisioning API: it posts to POST /api/v1/orgs/:id/provision — the exact route, handler, validation, transaction and analytics event the PostHog backend (Django) calls. controlplane/provisioning.RegisterAPI already mounts those routes on the audited admin /api/v1 group, so a warehouse an operator creates is identical to one a user creates by construction, not by convention.

Frontend (controlplane/admin/ui)

  • New /orgs/provision page: org id, database name, team id + optional schema override, metadata store (cnpg-shard | external RDS), data store (new per-org bucket | existing bucket).
    • Request preview renders the literal call it will send, so the "same call as the backend" claim is inspectable rather than asserted.
    • Database-name uniqueness pre-flight (GET /database-name/check), a warning when the org already has a live warehouse (the 409 case), and a type-the-org-id confirmation before creating real infrastructure.
    • The once-only root password is surfaced with copy affordances, alongside a live warehouse/status poll.
  • Body construction + the validation mirror live in lib/provision.ts so they're unit-testable — the request has to match the backend's. Optional fields are omitted, not sent empty, and ducklake.enabled is hard-coded true (the server rejects false).
  • Reset root password action on the org page — the recovery path when a provision response is lost, since only the bcrypt hash is stored.
  • Entry points from the Orgs list and from an org with no warehouse.

Backend

  • auditActionFor maps /provision, /deprovision and /reset-password to warehouse.provision / warehouse.deprovision / warehouse.reset_password. They previously fell through to the generic org.create, which made a real infrastructure action indistinguishable from an org-config write in the console's audit view.
  • Topology tripwires pin the one-implementation invariant from both sides: TestProvisioningAPIRouteTopology pins the shared route set, TestAdminAPIRegistersNoProvisioningRoutes pins the absence of an admin-side twin. (gin only panics on an exactly-matching duplicate route, so a near-miss path like /orgs/:id/provision-warehouse would fork the contract silently.)

Tests

  • ui/src/lib/provision.test.ts — validation rules + body construction (19 cases).
  • ui/src/pages/ProvisionWarehouse.test.tsx — the page posts exactly that body, blocks on the server's rules, warns on a live warehouse / taken db name, surfaces the once-only password, and is admin-gated.
  • controlplane/admin/audit_test.go — the three new action mappings.
  • The two route-topology tests above.
  • e2e (admin_provision_parity in tests/mw-dev/e2e/harness.sh): replays the console's exact body against the provisioned org and requires the warehouse-exists 409 (not a shape 400 — proving it reached the shared handler), pins the ducklake:false rejection, and asserts the warehouse.provision audit entry; the teardown lane asserts warehouse.deprovision.

Docs

CLAUDE.md (new load-bearing invariant), controlplane/admin/README.md ("Warehouse provisioning — one implementation, two callers"), docs/design/admin-ui.md, tests/mw-dev/README.md.

🤖 Generated with Claude Code

Operators can now provision a managed warehouse from the admin console
instead of hand-rolling a curl against the control plane. The console does
NOT get its own provisioning implementation: the new page is a form over
the public provisioning API, posting to POST /api/v1/orgs/:id/provision —
the exact route, handler, validation, transaction and analytics event the
PostHog backend calls. A warehouse an operator creates is therefore
identical to one a user creates by construction, not by convention.

Frontend (controlplane/admin/ui):
- New /orgs/provision page: org id, database name, team id + optional
  schema override, metadata store (cnpg-shard | external RDS), data store
  (new per-org bucket | existing bucket), with a live request preview of
  the literal call it will send, a database-name uniqueness pre-flight, a
  warning when the org already has a live warehouse (409), and a
  type-the-org-id confirmation. The once-only root password is surfaced
  with copy affordances plus a live warehouse/status poll.
- Body construction and the validation mirror live in lib/provision.ts so
  they are unit-testable: the request must match the PostHog backend's.
  Optional fields are omitted rather than sent empty, and ducklake.enabled
  is hard-coded true (the server rejects false).
- Reset-root-password action on the org page — the recovery path when a
  provision response is lost, since only the bcrypt hash is stored.
- Entry points from the Orgs list and from an org with no warehouse.

Backend:
- auditActionFor now maps /provision, /deprovision and /reset-password to
  warehouse.provision / warehouse.deprovision / warehouse.reset_password.
  They previously fell through to the generic org.create, making a real
  infrastructure action indistinguishable from an org-config write in the
  console's audit view.
- Topology tripwires pin the "one implementation" invariant from both
  sides: TestProvisioningAPIRouteTopology pins the shared route set, and
  TestAdminAPIRegistersNoProvisioningRoutes pins the absence of an
  admin-side twin (gin only panics on an exactly-matching duplicate, so a
  near-miss path would fork the contract silently).

Tests: provision.test.ts (validation + body construction),
ProvisionWarehouse.test.tsx (the page posts exactly that body, gates on
the server's rules, admin-gated), audit_test.go, the two topology tests,
and admin_provision_parity in the mw-dev e2e harness — which replays the
console's body against the provisioned org (409, not a shape 400), pins
the ducklake:false rejection, and asserts the warehouse.provision /
warehouse.deprovision audit actions.

Docs: CLAUDE.md, controlplane/admin/README.md, docs/design/admin-ui.md,
tests/mw-dev/README.md.
@github-actions

Copy link
Copy Markdown

Test Impact Plan

Deterministic summary of how this PR changes tests, CI runners, and coverage-risk signals.

Summary

Area Added Changed Deleted
Test files 2 3 0
E2E/journey files 0 0 0
Workflow files 0 0 0

Signals

  • Test cases: +2 / -0
  • Assertions: +9 / -0
  • Skips or known failures added: 0
  • Workflow continue-on-error added: 0
  • Workflow path filters added: 0
  • Test commands removed from justfile: 0
  • E2E/journey retry lines added: 0

Coverage risk: neutral or increased

No coverage-reduction warnings detected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant