2026-08-18 13:31:43 +02:00
---
id: ADAPTIVE-WP-0010
type: workplan
title: "Plan-derived guardrail spend ceilings"
domain: financials
repo: adaptive-pricing
2026-09-28 11:51:44 +02:00
status: blocked
2026-09-14 15:50:41 +02:00
flavor: planning
2026-08-18 13:31:43 +02:00
owner: codex
topic_slug: helix-forge
created: "2026-08-18"
2026-09-28 11:51:44 +02:00
updated: "2026-09-28"
2026-08-25 20:06:58 +02:00
state_hub_workstream_id: "543e6398-d2cb-5105-977e-b90461adac3e"
2026-08-18 13:31:43 +02:00
---
# Plan-derived guardrail spend ceilings
Give `tenant-engine` 's guardrail resolver a commercial feed, so a tenant's
monthly spend ceiling derives from the plan it holds rather than from a
headcount band that nobody signed off on.
Offered by `tenant-engine` on 2026-08-17 alongside the TEN-WP-0010 grouping
change (`tenant-engine/docs/tenant-guardrail-policy.md` , TEN-WP-0006). The
precedence layer already exists in their resolver — a plan-derived limit
outranks a grouping default — and it has no feed today.
## Position
**The problem is ownership, not mechanism.** Ceilings currently resolve from
`tenant:<grouping>:<name>` 's middle segment. Reclassifying a tenant from `small`
to `enterprise` moves its ceiling from EUR 250 to EUR 20,000. That is a
commercial fact wearing operational clothing: `tenant-engine` states plainly
that every non-trial value is a conservative opener they chose so no grouping
started unbounded, and that nobody has approved them commercially. Only
`trial = 0` is canon, from ADR-0013.
This is the same shape the tenancy framework was written to stop — a
customer-affecting commercial commitment resolved by an operational default
with no recorded owner. `adaptive-pricing` owns plan terms, so this is ours to
feed.
**A plan is a better basis than a band.** The grouping is a proxy for size; the
plan is the actual agreement the customer entered. Deriving a blast-radius limit
from what someone bought is more defensible than deriving it from what bracket
their headcount fell into at onboarding — particularly now that grouping is
mutable (TEN-WP-0010) and the identifier's middle segment is historical rather
than authoritative.
**Two constraints, stated up front because they shape the design.**
*A ceiling is a guardrail, not a price.* The moment a limit derives from plan
terms there is pressure to present it as an entitlement the customer purchased.
That is a different product with different obligations — an entitlement implies
we will serve up to it, a guardrail only says we will stop beyond it. The design
must keep them distinguishable, and the customer-facing wording must not
promise capacity.
*A ceiling change is a commercial event.* It goes through the tier-terms
approval path rather than resolving silently, consistent with `SCOPE.md` on
customer-visible pricing changes and with the `ADAPTIVE-WP-0009` T04 gate.
**Not yet agreed with `tenant-engine` .** They offered the conversation; we said
yes in principle and flagged it as new scope. Nothing here is committed until
T01 produces a shape both repos accept.
## Establish The Interface Contract With tenant-engine
```task
id: ADAPTIVE-WP-0010-T01
2026-09-28 11:51:44 +02:00
status: wait
2026-08-18 13:31:43 +02:00
priority: high
2026-08-25 20:06:58 +02:00
state_hub_task_id: "7533431c-a0de-5ac8-a811-14e4604ad651"
2026-08-18 13:31:43 +02:00
```
Agree the shape of the feed jointly with `tenant-engine` : what
`adaptive-pricing` publishes, what their resolver consumes, and what happens on
stale or missing input.
Open questions to settle, not to assume:
- Is the ceiling a field on the pricing model, or a separate derivation from it?
A tier's ceiling is not obviously one of its charge components.
- What resolves for a tenant whose plan carries no ceiling — the grouping
default, or a refusal? Falling back silently reintroduces the problem.
- Which side holds the number when they disagree? `ADAPTIVE-WP-0009` 's answer
for assurance claims was that the tier definition is authoritative and other
records derive from it; the same logic likely applies, but say so explicitly.
- Does a plan change mid-period move the ceiling immediately or at renewal?
This is a commercial answer, not a technical one.
Record the outcome as a decision, since it binds two repos.
2026-09-28 11:51:44 +02:00
### 2026-09-28 interface review
Reviewed tenant-engine at commit
`4ce89969c0cc0ab4ba111f1b4009aa137c94a68b` , specifically
`docs/tenant-guardrail-policy.md` , `src/tenant_engine/guardrail/resolution.py` ,
`src/tenant_engine/guardrail/model.py` , and the guardrail read in
`src/tenant_engine/app.py` . The existing consumer establishes these facts:
| Surface | Verified behavior |
| --- | --- |
| Resolver input | Optional `plan_limits: Mapping[str, LimitValue]` , already resolved for the tenant; no transport or plan lookup is defined here. |
| Monthly spend | Key `spend.monthly` ; spend values carry integer minor units, currency and period. |
| Precedence | Per key: override, plan, grouping/reserved profile, fail-closed floor; lifecycle clamping follows resolution. |
| Missing plan value | Falls through to grouping; the resolver cannot distinguish a deliberately silent tier from an unavailable feed. |
| Unlimited | Explicit sentinel, never inferred from absence or parsing failure; assignment requires auditing. |
| Currency | Highest-precedence value wins; tenant-engine performs no exchange-rate conversion. |
| HTTP integration | The read endpoint calls `resolve_limits` without `plan_limits` ; the documented feed remains unwired. |
This completes the local interface investigation, not the joint agreement.
T01 remains `wait` : the following proposal needs acceptance by tenant-engine
and commercial decisions by Bernd Worsch before T02 can define schema terms.
| Decision needed | Proposed basis for agreement (not adopted) |
| --- | --- |
| Representation and authority | Optional typed guardrail terms separate from charge components on the canonical tier; adaptive-pricing owns terms and revision, tenant-engine owns assignment. |
| Silent versus unavailable | Preserve grouping fallback for deliberately unmigrated tiers, with visible provenance. For migrated tiers, distinguish missing/stale/invalid terms from silence and return an explicit unavailable result rather than silently increasing the ceiling. Agree freshness and failure handling before wiring the feed. |
| Effective time | Carry an explicit approved effective time and revision. Bernd Worsch must choose immediate versus renewal application, including mid-period reductions and already-consumed spend. Do not infer this from an assignment timestamp. |
| Consumer handoff | Agree a versioned artifact or API and validation of plan identity, revision, amount, currency, period and freshness before converting to `LimitValue` ; preserve overrides and lifecycle clamping. |
| Coherence basis | Define expected monthly spend in the same currency and period as the guardrail, with an evidence reference. Current charge prices alone do not establish expected spend or capacity. |
Unblock evidence is a recorded joint contract with those decisions resolved;
then implement T02, T03 and T04 under their existing task IDs. No new workplan
or task is required. No ceiling values, customer terms, or resolver behavior
were changed by this review.
2026-08-18 13:31:43 +02:00
## Extend The Pricing Schema With Ceiling Terms
```task
id: ADAPTIVE-WP-0010-T02
status: wait
priority: medium
2026-08-25 20:06:58 +02:00
state_hub_task_id: "7bd4dace-3505-5c68-977d-f80b30aebc77"
2026-08-18 13:31:43 +02:00
```
Blocked on T01.
Add the agreed representation to the canonical schema. Follow the
`assurance_claims` precedent from `ADAPTIVE-WP-0009-T02` : optional, typed,
defaulted, so every existing model stays valid and silent tiers keep resolving
from the grouping default until deliberately migrated.
Keep the guardrail/entitlement distinction structural rather than a comment —
if the field is named or shaped like an allowance, it will be read as one.
## Validate Ceiling Coherence
```task
id: ADAPTIVE-WP-0010-T03
status: wait
priority: medium
2026-08-25 20:06:58 +02:00
state_hub_task_id: "46e49880-21ff-580f-a2ad-82c8f1401afc"
2026-08-18 13:31:43 +02:00
```
Blocked on T02.
Boundary-engine rules, explainable in the existing style:
- A ceiling below the tier's own expected usage is incoherent — the tier would
stop the customer doing what they paid for.
- A ceiling must not be presented in customer-facing wording as capacity.
- A tier with usage-based components and no ceiling is a declared choice, not an
oversight; require it to be explicit.
## Route Ceiling Changes Through Approval
```task
id: ADAPTIVE-WP-0010-T04
status: wait
priority: medium
2026-08-25 20:06:58 +02:00
state_hub_task_id: "a3eeffa6-57b0-5dc1-8b07-4b9b2a3d3534"
2026-08-18 13:31:43 +02:00
```
Blocked on T02.
Extend the `ADAPTIVE-WP-0009-T04` governance gate so a tier acquiring or
changing a ceiling raises a blocking approval requirement at tier definition,
carrying the prior and proposed values. Reuse the existing comparison
machinery rather than adding a second gate; as with assurance claims, it must
stay a definition-time review and not become a per-campaign one.