Finish TEN-WP-0006-T02: implement guardrail domain model
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
0d1435c2d2
commit
33ceb882ee
6 changed files with 868 additions and 1 deletions
|
|
@ -116,7 +116,7 @@ default lands immediately.
|
|||
|
||||
```task
|
||||
id: TEN-WP-0006-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "1b28d836-11d9-4ef3-967b-05bfa74c304a"
|
||||
```
|
||||
|
|
@ -134,6 +134,36 @@ Done when effective-guardrail resolution is a total, side-effect-free function
|
|||
with unit tests covering every grouping, the reserved identifiers, missing
|
||||
plans, and conflicting overrides.
|
||||
|
||||
Done 2026-08-16: `src/tenant_engine/guardrail/` — `model.py` (frozen
|
||||
dataclasses, typed errors), `registry.py` (the key registry and grouping
|
||||
defaults), `resolution.py` (the pure resolution function). 42 new tests, 166
|
||||
total, all passing.
|
||||
|
||||
Design notes worth carrying:
|
||||
|
||||
- **Registry totality is enforced at construction.** `LimitDefinition` raises
|
||||
`GuardrailRegistryInvalidError` if any grouping in `GROUPINGS` lacks a
|
||||
default, so an unmapped grouping cannot reach production. The `fail_closed`
|
||||
branch in `resolve_limit` is the second line of defence, not the first — its
|
||||
test has to bypass the constructor to reach it.
|
||||
- **A floor may never be `unlimited`,** also enforced at construction. A floor
|
||||
that fails open is not a floor.
|
||||
- **Currency is deliberately excluded from `compatible_with`.** Precedence
|
||||
resolves per key and the winning layer takes the value whole, so two
|
||||
currencies never combine — which is why there is no exchange rate anywhere
|
||||
in this service, and why there must not be one.
|
||||
- **The lifecycle clamp only relabels when it actually reduces.** A retired
|
||||
`trial` tenant already at zero keeps provenance `grouping`; a retired
|
||||
`large` tenant becomes `lifecycle`. Relabelling unconditionally would hide
|
||||
where the value came from.
|
||||
- **`bool` is rejected as an amount** — `True` is an `int` in Python and would
|
||||
otherwise pass the non-negative check as a ceiling of 1.
|
||||
|
||||
Lint: the new files are clean. `make lint` still fails on **19 pre-existing
|
||||
E501s** in files this workplan does not touch (`tests/test_store.py` and
|
||||
friends) — that baseline predates TEN-WP-0006 and is left alone rather than
|
||||
folded into this diff.
|
||||
|
||||
## T03 - Persist guardrails in both stores
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue