145 lines
5.3 KiB
Markdown
145 lines
5.3 KiB
Markdown
---
|
|
id: TEN-WP-0007
|
|
type: workplan
|
|
title: "Production rollout of the guardrail surface"
|
|
domain: infotech
|
|
repo: tenant-engine
|
|
status: ready
|
|
owner: claude
|
|
topic_slug: tenant-guardrails
|
|
created: "2026-08-16"
|
|
updated: "2026-08-16"
|
|
depends_on:
|
|
- TEN-WP-0006
|
|
unblocks: []
|
|
---
|
|
|
|
# TEN-WP-0007 - Guardrail production rollout
|
|
|
|
TEN-WP-0006 deliberately left production rollout out of scope. The operator has
|
|
now authorized proceeding, so this workplan carries the deploy that TEN-WP-0006
|
|
would not.
|
|
|
|
## Sequencing (agreed with flex-auth, FLEX-WP-0014)
|
|
|
|
1. **flex-auth first** — CI image from the nine-action commit, pin
|
|
`flex-auth-tenant-engine`.
|
|
2. **tenant-engine second** — pin the guardrail image.
|
|
3. **Joint live verification** against a disposable tenant.
|
|
|
|
flex-auth goes first on purpose. If we pin first, the guardrail surface is live
|
|
but every call denies with `write_denied` — a state indistinguishable from a
|
|
broken deployment to anyone reading logs. Their step landing first makes each
|
|
step independently observable.
|
|
|
|
## What is already true
|
|
|
|
- Source revision `e5c1061` on `main`, TEN-WP-0006 finished, 231 tests passing.
|
|
- flex-auth's nine-action package is verified **in source** (17/17 Rego tests,
|
|
23/23 fixtures, end-to-end against our unmodified `FlexAuthWriteAuthorizer`).
|
|
- Production flex-auth serves the **seven**-action image
|
|
`@sha256:9320df39…` — the four-action regression is resolved, but the
|
|
guardrail actions are not in that image.
|
|
- Production tenant-engine still runs the TEN-WP-0005 lifecycle image.
|
|
|
|
## T01 - Confirm the CI image and its digest
|
|
|
|
```task
|
|
id: TEN-WP-0007-T01
|
|
status: todo
|
|
priority: high
|
|
```
|
|
|
|
`src/**` is a trigger path in `.forgejo/workflows/image.yaml` and the guardrail
|
|
commits are pushed, so CI should have built an image for `main-e5c1061`.
|
|
Confirm it exists and record the immutable digest.
|
|
|
|
Blocked from this workstation: no registry credentials. Needs a shell with
|
|
forge access, or the digest relayed by whoever has one.
|
|
|
|
Done when the digest is recorded here and the image is confirmed to contain
|
|
the guardrail routes (`GET /openapi.json` on the built image lists
|
|
`/tenants/{tenant_id}/guardrails`).
|
|
|
|
## T02 - Wait for the flex-auth nine-action pin
|
|
|
|
```task
|
|
id: TEN-WP-0007-T02
|
|
status: wait
|
|
priority: high
|
|
```
|
|
|
|
Waiting on FLEX-WP-0014 step 1. Two things to collect when it lands:
|
|
|
|
- the new flex-auth image digest;
|
|
- confirmation that the **seven pre-existing actions still allow** after the
|
|
pin. The last regression surfaced as a silent rollback, and lifecycle
|
|
authority is what user-engine is already shipping against — a second silent
|
|
rollback would break a live consumer, not just this workplan.
|
|
|
|
Done when guardrail checks return allow instead of `deny unknown_action`
|
|
against the live decision surface.
|
|
|
|
## T03 - Apply the migration and pin the tenant-engine image
|
|
|
|
```task
|
|
id: TEN-WP-0007-T03
|
|
status: wait
|
|
priority: high
|
|
```
|
|
|
|
The guardrail schema change is two new tables created by the same
|
|
`CREATE TABLE IF NOT EXISTS` script fresh and existing databases both take, so
|
|
the migration is forward-only and idempotent and needs no separate step. What
|
|
it does need is evidence that the existing SQLite database on the PVC survives
|
|
it with tenants, grants, and plans untouched.
|
|
|
|
Rollback is `kubectl -n tenant-engine rollout undo`; last-known-good is the
|
|
TEN-WP-0005 lifecycle digest
|
|
`@sha256:08be0b1dcdc65575592b7be665c28e09a82316ea3d4c9b551ccb753f25360612`.
|
|
Rolling back is safe in one direction only: the new tables are additive and the
|
|
old image ignores them, but any override written while the new image is live
|
|
stops being enforced on rollback. Nothing reads guardrails yet, so today that
|
|
is inert — record it before that stops being true.
|
|
|
|
Blocked from this workstation: `kubectl` returns `Unauthorized`. This is an
|
|
operator step.
|
|
|
|
Done when the deployment is pinned to the T01 digest and the pre-existing
|
|
tenants are intact.
|
|
|
|
## T04 - Live verification and consumer confirmation
|
|
|
|
```task
|
|
id: TEN-WP-0007-T04
|
|
status: wait
|
|
priority: high
|
|
```
|
|
|
|
Against a disposable tenant, in the TEN-WP-0005-T05 evidence style:
|
|
|
|
- guardrail read returns the grouping default with provenance `grouping`;
|
|
- a `trial` tenant reads a **zero** spend ceiling — the ADR-0013 mandate, and
|
|
the one thing here that is enforceable with no meter;
|
|
- override set → read reflects it with provenance `override`;
|
|
- override cleared → falls back to the grouping default;
|
|
- `actor=ops` denied on both read and write;
|
|
- `flex-auth` allowed on read, denied on write (`action_not_granted`);
|
|
- retired tenant still reads, clamps to floor, loosening refused;
|
|
- idempotent replay returns the original result unchanged.
|
|
|
|
Then confirm to flex-auth from our side — they verified the decision surface,
|
|
not our endpoints in production, and TEN-WP-0005 established that those are
|
|
different claims.
|
|
|
|
Done when live evidence is recorded here and the confirmation is sent.
|
|
|
|
## Out of scope
|
|
|
|
- **Metering.** Still unowned by any repo. The guardrail read stays
|
|
ceiling-only until canon names a meter.
|
|
- **The plan-derived layer.** `adaptive-pricing` exposes no plan-derived
|
|
ceilings; the precedence layer exists and is tested but has no feed.
|
|
- **Grouping ceiling values.** The non-`trial` defaults are conservative
|
|
opening numbers pending product sign-off. Rolling out does not ratify them —
|
|
they are config, changeable without a deploy.
|