The control plane (provisioning)
The control plane is the SaaS registry ABOVE the engine: a separate
Worker (ribose-status-control, wrangler.control.toml) that owns
the org + page + binding rows in its own KV namespace and turns a
registration into a reviewable instance YAML through the same
emission engine the admin's provisioning form uses. It never
deploys anything — deploys stay deliberate (a tag or a manual
dispatch), and the binding row only records where an emission went.
The live plane
Up since 2026-09-10: https://ribose-status-control.crimson-ribose.workers.dev
(namespace CONTROL_KV id 81da03cb-..., the Bearer secret
CONTROL_TOKEN). Without the token the whole surface answers 404 —
the fail-closed doctrine; the local token copy lives at
~/.ribose-secrets/status-control.token (rotate with
wrangler secret put CONTROL_TOKEN --config wrangler.control.toml).
The org lifecycle
An org retires only when no active pages remain (POST /v1/orgs/:id with {"retire": true}; the refusal names the live
pages). A retired org refuses new pages; its roster rows stay as
bookkeeping.
The registry's backup + restore
The weekly workflow exports the two listings (the plane's ENTIRE state) as a 90-day artifact beside the page-store backups. The restore is re-registration through the API — the emitted instance YAML rides in the pages document — and the registry's restore note in manual 07's worked example is the runbook. The one-liner:
npx wrangler kv key list --namespace-id 81da03cb7a9143bf844f84a504bbabc1 | jq -r '.[] | .name' > control-keys.txt # the roster of rows; the
# values follow with key get
The lifecycle
- Register the org —
POST /v1/orgswith the kebab-case id, the name, and at least one bootstrap owner email (the member rows use the same grammar the engine's org surface uses). - Register the page —
POST /v1/pageswith the org id, the slug, the title, the base URL, a description, and the services asid|name|url|needlelines. The plane emits the instance YAML (GET /v1/pages/<slug>/instance.yaml) and the manifest naming the explicit secret/KV steps. The YAML must parse through the same strict loader the build gate uses — the API returns 400 otherwise, and the leg proves the round trip. - Deploy the emission — the deliberate act (review the YAML, then deploy it as its own worker or register it into a shared deployment, TODO 41).
- Record the binding —
POST /v1/pages/<slug>with the host the page answers on (and the deployment URL/worker name): the state movesemitted → deployed.{"retire": true}retires it. - Register the page's OP client —
POST /v1/pages/<slug>/op-client: the plane performs RFC 7591 dynamic client registration at the plane's identity issuer and re-emits the YAML withadmin.oidcreal. See the next section.
The page's OP client (TODO 71)
Until this act runs, the emitted YAML carries admin.oidc commented
(the operator would register the client at the OP by hand and
uncomment). The plane can do that registration itself:
- Posture: the plane knows exactly two OP facts —
OP_ISSUER(a var, the issuer) andOP_DCR_TOKEN(a secret, the OP's enrollment token). Without either, the route answers 404 — the route does not exist, the plane's fail-closed door again. - ONE issuer per plane: every page registered here gets its
identity from that issuer. An estate running its own OP (the
Conormis posture,
id.conormis.com) runs its own plane. - The client is the public-PKCE posture the RP side speaks: the
registration asks
token_endpoint_auth_method: none. Redirect URIs: the base URL's callback, plus (when a host is bound) the host's callback and the shared-deployment path mount (/p/<slug>/admin/oidc/callback). - Any
client_secretor registration material the OP answers with is dropped at the door — never stored, never returned; the row keeps only the fact that a discard happened. - Idempotent: a page with a registration row returns it; a re-register never creates a second OP client.
- Honest refusals: a retired page takes no client; a row that
predates the kept registration input is named (register the page
again); an OP without a
registration_endpointin discovery (or answering an error) is a 502 with the reason, and the row is unchanged. - What it does NOT do: the OP still decides WHO administers. The
first admin is granted by adding their subject to
admin.oidc.subjects(or settingadmins_claim), andorgstays an operator uncomment with that subject as the bootstrap owner. There is no client deletion: the OP advertises no RFC 7592 management endpoint (docs/manual/09).
The plane and the live console (the two-way street)
The PLANE is the source of truth for NEW pages: registration emits
the YAML, the deployment's /api/pages takes it, and the plane's
instance.yaml endpoint regenerates from its row on demand. A
deployment's console can also edit a LIVE page's services (the
Services register, docs/manual/03) — an out-of-band edit lands in the
deployment's registry and is surfaced on the plane when its emission
is regenerated. The precedence, stated plainly: new pages are the
plane's; live service edits are the deployment operator's, audited
like every other act.
Org-wide membership
The plane is the source of truth for WHO belongs to an org across
its whole page fleet: POST /v1/orgs/:id/members (email + role
owner|operator — the same role grammar the engine's org surface
resolves), GET /v1/orgs/:id/members, DELETE by email. Emails
normalize exactly as the engine's invites do.
The bridge, stated honestly: adding a member on the plane does not write any page's local org rows. A deployment redeems membership through its own invite flow (the operator sends an invite, the member's verified email claim redeems it) — the plane is the roster, the deployment is the gate.
The API (Bearer CONTROL_TOKEN)
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/orgs |
List orgs |
| POST | /v1/orgs |
Register an org (id, name, ownerEmails) |
| GET | /v1/orgs/:id |
The org + its bootstrap members |
| GET | /v1/orgs/:id/members |
The org-wide roster |
| POST | /v1/orgs/:id/members |
Add a member (email, role) |
| DELETE | /v1/orgs/:id/members |
Remove a member (email) |
| GET | /v1/pages?org= |
List page summaries |
| POST | /v1/pages |
Register a page; returns the emitted YAML |
| GET | /v1/pages/:slug |
The page row + the YAML |
| GET | /v1/pages/:slug/instance.yaml |
The emitted instance (text/yaml) |
| POST | /v1/pages/:slug |
Record a binding, or retire |
| POST | /v1/pages/:slug/op-client |
RFC 7591 registration at the plane's issuer; re-emits the YAML with admin.oidc real |