--- id: ADAPTIVE-WP-0010 type: workplan title: "Plan-derived guardrail spend ceilings" domain: financials repo: adaptive-pricing status: blocked flavor: planning owner: codex topic_slug: helix-forge created: "2026-08-18" updated: "2026-09-28" state_hub_workstream_id: "543e6398-d2cb-5105-977e-b90461adac3e" --- # 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::`'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 status: wait priority: high state_hub_task_id: "7533431c-a0de-5ac8-a811-14e4604ad651" ``` 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 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. ## Extend The Pricing Schema With Ceiling Terms ```task id: ADAPTIVE-WP-0010-T02 status: wait priority: medium state_hub_task_id: "7bd4dace-3505-5c68-977d-f80b30aebc77" ``` 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 state_hub_task_id: "46e49880-21ff-580f-a2ad-82c8f1401afc" ``` 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 state_hub_task_id: "a3eeffa6-57b0-5dc1-8b07-4b9b2a3d3534" ``` 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.