Finish TEN-WP-0007: guardrail surface live in production

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-16 10:34:44 +02:00
parent b95b317293
commit 76196aa39f
2 changed files with 59 additions and 5 deletions

View file

@ -44,7 +44,7 @@
| task | TEN-WP-0006-T03 | done | — | workplans/TEN-WP-0006-guardrail-quota-policy.md |
| task | TEN-WP-0006-T04 | done | — | workplans/TEN-WP-0006-guardrail-quota-policy.md |
| task | TEN-WP-0006-T05 | done | — | workplans/TEN-WP-0006-guardrail-quota-policy.md |
| task | TEN-WP-0007-T01 | todo | — | workplans/TEN-WP-0007-guardrail-production-rollout.md |
| task | TEN-WP-0007-T02 | wait | — | workplans/TEN-WP-0007-guardrail-production-rollout.md |
| task | TEN-WP-0007-T01 | done | — | workplans/TEN-WP-0007-guardrail-production-rollout.md |
| task | TEN-WP-0007-T02 | done | — | workplans/TEN-WP-0007-guardrail-production-rollout.md |
| task | TEN-WP-0007-T03 | wait | — | workplans/TEN-WP-0007-guardrail-production-rollout.md |
| task | TEN-WP-0007-T04 | wait | — | workplans/TEN-WP-0007-guardrail-production-rollout.md |

View file

@ -4,7 +4,7 @@ type: workplan
title: "Production rollout of the guardrail surface"
domain: infotech
repo: tenant-engine
status: active
status: finished
owner: claude
topic_slug: tenant-guardrails
created: "2026-08-16"
@ -147,7 +147,7 @@ nominal — `flex-auth` itself is denied the write.
```task
id: TEN-WP-0007-T03
status: wait
status: done
priority: high
state_hub_task_id: "ea509a74-2030-4484-a6e0-f4543a8d394f"
```
@ -193,11 +193,36 @@ kubectl -n tenant-engine exec deploy/tenant-engine -- \
`kubectl` returns `Unauthorized` from this workstation, so I have not applied
it and cannot verify the live result.
Done 2026-08-16. flex-auth attended the apply as operator (explicitly *not* as
an ownership claim). Deployment is `1/1` on
`@sha256:44ca65f3…`, `/health` returns `0.1.0`.
**Correction to the rollback target recorded above.** The image actually
replaced was `@sha256:2249e8c6…85149207` — the **TEN-WP-0004** image — not the
TEN-WP-0005 lifecycle digest `08be0b1d…` this task assumed. Production had been
sitting on a pre-lifecycle image, so the lifecycle routes verified live on
2026-08-13 were *not* being served. That is a second silent rollback, parallel
to the flex-auth four-action one, and nobody had noticed because the two
failures looked identical from outside: user-engine's portal would have seen
`404` on the lifecycle routes rather than the `403` a policy denial produces.
This rollout fixed both surfaces at once, since the new image carries lifecycle
and guardrail routes together.
So the real last-known-good ladder is: `44ca65f3…` (current, lifecycle +
guardrails) → `08be0b1d…` (lifecycle only) → `2249e8c6…` (neither). Rolling
back past `08be0b1d…` removes lifecycle authority that user-engine depends on.
**Pre-existing tenants survived the forward-only migration:**
- `tenant:trial:portalcheck``active`, version 1, untouched
- `tenant:trial:ten-wp-0005-t05``retired`, version 5, rename and lifecycle
history intact
## T04 - Live verification and consumer confirmation
```task
id: TEN-WP-0007-T04
status: wait
status: done
priority: high
state_hub_task_id: "6e4961e2-25e4-43c4-9f39-113610306fe9"
```
@ -220,6 +245,35 @@ different claims.
Done when live evidence is recorded here and the confirmation is sent.
Done 2026-08-16. Live against production, disposable tenants
`tenant:small:ten-wp-0007-t04` (left `retired`) and
`tenant:trial:ten-wp-0007-t04` (left `active`).
| Check | Result |
| --- | --- |
| grouping default, `actor=flex-auth` | 200 — `25000 EUR P1M`, provenance `grouping` |
| **`trial` reads zero spend** | 200 — `amount 0`, provenance `grouping` |
| read `actor=ops` | 403 |
| write `actor=flex-auth` | 403 — read/write split holds live |
| write `actor=ops` | 403 |
| set override `actor=tenant-engine` | 200 — `9000`, provenance `override`, ETag `"2"` |
| idempotent replay, same key | `Idempotent-Replay: true`, version still 2, same `change_id` |
| clear override | 200 — falls back to `25000`, provenance `grouping` |
| read while `retired` | 200 — `amount 0`, provenance `lifecycle` |
| loosen while retired | 409 `guardrail_loosening_denied` |
| tighten while retired | 200 — reduce-privilege stays available |
| unregistered key | 404 `unknown_limit_key` |
**The ADR-0013 mandate is now enforced in production.** A `trial` tenant reads
a zero spend ceiling; before this rollout it had no ceiling at all. That is the
one guardrail that needs no meter to be meaningful, which is why it lands
first.
Two things this run incidentally proved beyond the guardrail surface: the
lifecycle routes are serving again (the `retire` above returned 200, which the
pre-rollout image could not have done), and the `change_id` derivation holds
across a real replay rather than only in tests.
## Out of scope
- **Metering.** Still unowned by any repo. The guardrail read stays