adaptive-pricing/workplans/ADAPTIVE-WP-0010-plan-derived-guardrail-ceilings.md
codex 0ddf012f48
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Review remaining ceiling work and record contract blockers
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e76a-f84c-7fb2-8309-fdbddf620a43
2026-09-28 11:51:44 +02:00

8.5 KiB

id type title domain repo status flavor owner topic_slug created updated state_hub_workstream_id
ADAPTIVE-WP-0010 workplan Plan-derived guardrail spend ceilings financials adaptive-pricing blocked planning codex helix-forge 2026-08-18 2026-09-28 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:<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

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

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

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

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.