Capability group 01

Relational SQL

The default claim: a distributed, Postgres-wire OLTP database (Yugabyte) or a MySQL-wire / HTAP system (TiDB, Beta). Where a relational tenant lands — and how it is isolated — is a recorded policy (ADR-0071), not an operator's ad-hoc choice. Postgres tenants get database-enforced Row-Level Security; MySQL/TiDB tenants get documented application-level filtering with a mechanical query guard.

Postgres · MySQL · TiDB HTAP

≤10 ms
P99 single-row query (Yugabyte)
≤30 s
leader-election failover
RLS
database-enforced on shared Postgres

Sub-capabilities

Distributed SQL with database-enforced tenant isolation.

Distributed Postgres-wire SQL

Yugabyte is the GA OLTP claim — a distributed, Postgres-wire database. Point any Postgres driver at the binding's DSN; the password is drawn from a Vault lease, never written into the connection string.

MySQL-wire & TiDB HTAP (Beta)

TiDB is admitted as a Beta engine (ADR-0069): MySQL wire-compatible and horizontally scalable, it absorbs multi-instance MySQL requirements as one scaled system with a nearline HTAP profile — operational isolation, not cross-product analytics (that stays in Prism).

Tenant→database placement policy

A tenant declares a requirement — dialect (postgres | mysql) and topology (single | multi) — and the placement policy (a pure function) decides the target instance and isolation mode. The caller never names the target; postgres+multi is refused with guidance to Yugabyte.

Row-Level Security, served as DDL

For shared-Postgres tenants, provisioning emits the RLS setup — per-tenant schema, a NOLOGIN role with app.tenant_id pinned, and ENABLE + FORCE ROW LEVEL SECURITY per table — served on GET …/placement?tables=… so you apply exactly the isolation the platform enforces.

Shared→TiDB migration state machine

A shared-model tenant that outgrows one instance is migrated, not re-created: placement walks shared → migrating → tidb. Completing the move re-binds onto TiDB and revokes every outstanding lease — shared-instance credentials die with the move.

Endpoints

9 real operations on the /api/v1 contract.

Method + path straight from the SS-02 OpenAPI spec. Governed routes take an SS-01 bearer token and, where marked, an X-Tenant-Id; the S3 wire gateway authenticates with AWS SigV4 instead.

POST/api/v1/claimsbearertenantidempotency-key

Declare an OLTP SQL claim (engine yugabyte or tidb); provisions the resource, pins a version, mints a Vault credential.

GET/api/v1/claimsbearertenant

List every engine claim owned by the tenant (never the raw credential).

GET/api/v1/claims/{name}bearertenant

Fetch one claim by name, tenant-scoped.

GET/api/v1/claims/{name}/bindingbearertenant

DSN + endpoint + Vault secret_ref — the credential-free connect info.

POST/api/v1/claims/{name}/rotatebearertenantidempotency-key

Rotate the credential (new Vault version); DSN unchanged.

POST/api/v1/claims/{name}/placementbearertenant

Declare placement requirement (dialect + topology); decides target + isolation, enters migrating when moving to TiDB.

GET/api/v1/claims/{name}/placementbearertenant

Read the placement block; ?tables=… serves the Postgres RLS DDL / MySQL discriminator DDL.

POST/api/v1/claims/{name}/placement/completebearertenant

Operator seam: re-bind onto TiDB and revoke every outstanding lease.

DELETE/api/v1/claims/{name}bearertenant

Deprovision the resource and remove the claim.

Worked example

Place a MySQL tenant that outgrew one instance onto TiDB.

Place a MySQL tenant that outgrew one instance onto TiDB · bash
# 1. Re-declare the requirement: mysql + multi → TiDB. The caller
#    never names the target; the policy decides it.
curl -X POST "$BASE/claims/orders/placement" \
  -H "Authorization: Bearer $TOKEN" -H "X-Tenant-Id: acme" \
  -H "Content-Type: application/json" \
  -d '{"dialect":"mysql","topology":"multi"}'
#   → 200 {"target":"tidb","isolation":"app-filter","phase":"migrating"}
#     The binding stays on the shared instance; the data copy is
#     executed by operator tooling outside SS-02.

# 2. Operators finish the copy, then complete the move. This re-binds
#    onto TiDB and revokes every outstanding credential lease.
curl -X POST "$BASE/claims/orders/placement/complete" \
  -H "Authorization: Bearer $TOKEN" -H "X-Tenant-Id: acme"
#   → 200 {"target":"tidb","phase":"tidb","leases_revoked":3}
#     One-way: there is no tidb→shared, and the dialect never changes.

Placement is deterministic and table-tested; the transition is one-way and the audit chain records placement.update and placement.migrate.complete.

In the architecture

Where it sits on the data plane.

Relational claims sit at the front of the data plane. The placement policy (services/dbaas/internal/core/placement.go) is a pure function: postgres+single lands on a shared Postgres instance with database-enforced RLS; mysql+single shares a Postgres-free MySQL instance with an application-enforced tenant_id discriminator; mysql+multi lands on TiDB. The shared instances join the fleet as data-* bundles; SS-05 Vault holds every credential.

ADR-0071Tenant→database placement policyADR-0069Polyglot engine tiers (TiDB Beta)ADR-0019Tenant-id as a first-class request concern