Add an append-only ledger of entitlement set rule changes with per-pool effect rows, a preview-and-commit rule change flow, and an automatic drain that settles deferred recomputations. Rules gain a tier reduction policy, resource keys declare over-limit behavior, and the materializer now lowers limits when a rule stops applying. Add entitlement set rule change ledger and preview flow Add an append-only ledger of entitlement set rule changes with a preview-and-commit operator flow. Rule writes now go through an enclosed `core.commit_rule_change` function that files an act row and one obligation per carrying pool, with a drain workflow settling deferred recomputations. The preview dry-runs the materializer with a rule overlay and renders per-pool buckets, reduction-policy disclosures, and provider over-limit consequences. Materializing transactions take a shared advisory rendezvous that rule changes hold exclusively, enforced by a possession assertion. Add History and Entitlement changes surfaces, a rule-less warning on five product-selection surfaces, and a `tier_reduction_policy` column that gates FedWiki parking.
13 KiB
plan-downgrade
Purpose
Defines the member-facing Standard→Public downgrade contract: when a member's higher-tier plan provision ends, the pool returns to its org-type default tier (or goes off-ladder when no default is configured), tier reductions apply the clamp policy so resources already provisioned above a lowered limit are retained while new over-limit provisioning is refused, and the member is advised of any resulting over-limit state.
Requirements
Requirement: Downgrade returns the pool to its org-type default
When a member's active plan provision ends — whether via Stripe subscription cancellation (customer.subscription.deleted reconciled through the fulfillment path) or operator revocation — the system SHALL return the pool to its org-type default tier through the two-call model: entitlements.end_conferral resolved by the ending provision's source (which never re-mints the baseline itself), then the floor-guarded baseline restoration (ReapplyDefaultsForPool, invoked only when the pool is left with no live (active or suspended) plan-ladder attachment from any source on any ladder — a position held by any other source is never superseded, and the baseline is never conferred alongside a live plan position on a different ladder). When a default plan ladder is configured for the org type and the guard finds the pool without any live plan position, the pool SHALL be re-provisioned at that ladder's rank-0 product via a fresh system-authored default decree; the audit trail records an end transition for the ended position and an initiate transition for the restored default (the single-row downgrade classification belonged to the retired end-and-re-apply primitive). When no default is configured, the pool SHALL legitimately go off-ladder and only the end transition SHALL be recorded; no replacement provision is created.
Scenario: Downgrade with a configured default re-applies the rank-0 tier
- WHEN a member on a higher rung has their active provision ended and the org type has a configured default plan ladder
- THEN the system SHALL end the prior provision and every ladder rung it occupied, recording an
endtransition per rung - AND the system SHALL re-provision the pool at the default ladder's rank-0 product via the floor-guarded restoration
- AND the restored position SHALL be recorded as an
initiatetransition sourced by a fresh system-authored default grant
Scenario: Downgrade with no configured default leaves the pool off-ladder
- WHEN a member's active provision is ended and the org type has no configured default plan ladder
- THEN the system SHALL end the prior provision
- AND the system SHALL leave the pool off-ladder with no replacement provision
- AND the system SHALL record an
endtransition
Scenario: Downgrade defers to a remaining plan on another ladder
- WHEN a member's active provision is ended while the pool separately holds a live plan-ladder position from another source on a different ladder (e.g. a legacy grant from a grandfathered default)
- THEN the floor guard SHALL find the pool occupied and confer nothing
- AND only the
endtransition for the ended provision SHALL be recorded
Requirement: Tier reduction uses the clamp policy
clamp is the baseline tier-reduction policy: when any event lowers a numeric entitlement limit below the pool's current usage, resources already provisioned above the new limit SHALL be retained and remain usable, and new provisioning that would exceed the limit SHALL be refused until usage falls back below the limit. For a counted resource whose provider supports a read-only lifecycle state (set_status), the system SHALL additionally apply the force_reduce policy — parking excess instances read-only to restore compliance (see "FedWiki site downgrade parks excess sites read-only"). When force_reduce is unavailable for a resource (the provider declares no read-only state, or parking is pending or has failed), clamp SHALL hold as the safety default and SHALL NOT delete or suspend existing resources.
tier_reduction_policy is no longer unimplemented: it is a column of the entitlement set rule that funds the limit, constrained to block, defer, clamp and force_reduce, and an operator edits it on the rule form. Whether a provider's read-only parking is exercised at all SHALL be decided by that policy, not by the provider's capability alone: under clamp the surplus is the member's and parking SHALL NOT be applied even where the provider supports it; under force_reduce the provider's declared behavior SHALL be applied. force_reduce is implemented for FedWiki sites.
At an entitlement set rule commit, block and defer SHALL resolve to clamp, because a rule commit has nothing to refuse and no later boundary to defer to. That resolution SHALL be disclosed in the preview before the commit rather than discovered afterwards, and the commit SHALL NOT be refused or deferred on the strength of either policy.
Scenario: Clamp retains resources for a non-read-only-capable resource
- WHEN a pool's limit for a counted resource whose provider declares no read-only state drops below current usage
- THEN the existing over-limit resources SHALL remain present and usable
- AND the system SHALL NOT delete or suspend them
Scenario: New provisioning is refused while over the limit
- WHEN a member whose
fedwiki_sitesusage exceeds the active limit attempts to create a new site - THEN the system SHALL refuse the creation
Scenario: Provisioning is re-enabled once usage falls under the limit
- WHEN a member over the limit reduces usage until
current_usageis belowresource_limit - THEN the system SHALL again permit creation up to the limit
Scenario: A limit lowered by something other than a downgrade clamps the same way
- WHEN a committed entitlement set rule change lowers a pool's numeric limit below its current usage
- THEN the resources already provisioned above the new limit SHALL be retained and remain usable
- AND new provisioning that would exceed the limit SHALL be refused until usage falls back below it
Scenario: Block and defer resolve to clamp at a rule commit
- WHEN an operator commits a rule whose
tier_reduction_policyisblockordeferand the commit lowers a pool's limit below its usage - THEN the commit proceeds and the limit applies at once, with the usage above it retained
- AND the preview stated that resolution before the commit was taken
Scenario: Clamp withholds the provider's parking
- WHEN a pool's governing rule carries
clampand its limit drops below usage for a resource whose provider supports read-only parking - THEN no instance SHALL be parked, and the surplus SHALL remain active and usable
Requirement: Member is advised of the over-limit state
When a pool's usage exceeds its active limit and clamp holds (no force_reduce parking), the member-facing UI SHALL surface the over-limit state — displaying the over-limit count (e.g. "3 of 1 sites used") and disabling the relevant create affordance with a clear reason. When force_reduce parks excess sites read-only, usage is restored to the limit and the UI SHALL instead advise which site remains active, that the remaining sites are read-only (content retained and readable), and how to change the selection.
Scenario: Over-limit advisory under clamp
- WHEN a downgraded member with a clamped resource at usage 3 and limit 1 views their dashboard
- THEN the UI SHALL display the over-limit count (e.g. "3 of 1 sites used")
- AND the UI SHALL disable the create affordance with a clear reason
Scenario: Force-reduce advisory after sites are parked
- WHEN a downgraded member's excess sites have been parked read-only by
force_reduce - THEN the UI SHALL indicate which site is active and that the remaining sites are read-only
- AND the UI SHALL offer a way to change which site is active
Requirement: FedWiki site downgrade parks excess sites read-only
When a downgrade lowers the fedwiki_sites limit below the count of active sites, the system SHALL park the excess sites read-only via set_status(readonly) so that active usage is restored to the new limit, but only where the reduction policy governing that pool's fedwiki_sites limit is force_reduce. Where that policy is clamp, the surplus is the member's: no site SHALL be parked, no force_reduced_at SHALL be stamped, and the over-limit state SHALL be resolved by refusing new creation alone. Where the policy is block or defer, it resolves to clamp and the system SHALL behave as under clamp. Under force_reduce the limit-many most-recently-modified sites (by observed last_modified_at) SHALL be kept active by default; parked sites SHALL be stamped with force_reduced_at. Read-only parking SHALL be non-destructive; content is retained and remains readable.
The member MAY change which sites are active, subject to a per-workspace cooldown that gates rotations, which means bringing a different site into the active set in place of one already there. A transition into active SHALL be a rotation when the activated site is force-reduced (force_reduced_at IS NOT NULL, a site this downgrade parked), OR when it is performed while active usage is at the limit (a swap that parks an incumbent to promote another). Restoring a site that is not force-reduced, the member's own incumbent, whether currently active or one they archived themselves, SHALL always be allowed and SHALL NOT consult the cooldown (it returns the status quo, not a rotation). The first rotation within a cooldown window SHALL be free and subsequent rotations SHALL be refused until the window elapses for that workspace; restoring an incumbent SHALL neither be refused nor start or extend the window. When a force-reduced site is rotated back into the active set, its force_reduced_at SHALL be cleared. The cooldown window SHALL be configurable, default 30 days, and any user-facing refusal message SHALL reflect the configured window.
When the limit later rises (re-upgrade), the system SHALL auto-reactivate force_reduced_at sites, most-recently-modified first, up to the new limit, clearing force_reduced_at; each reactivation SHALL be quota-gated.
Scenario: Downgrade parks excess sites and keeps the default active
- WHEN a member with 3 active sites is downgraded so the
fedwiki_siteslimit becomes 1, under a governing rule whose policy isforce_reduce - THEN the most-recently-modified site SHALL remain active
- AND the other 2 sites SHALL be set read-only and stamped
force_reduced_at - AND active
current_usageSHALL equal 1 (the new limit)
Scenario: A clamp policy parks nothing
- WHEN a member with 3 active sites has the
fedwiki_siteslimit lowered to 1 under a governing rule whose policy isclamp - THEN no site SHALL be parked and none SHALL be stamped
force_reduced_at - AND all 3 sites SHALL remain active and usable
- AND new site creation SHALL be refused while active usage is above the limit
Scenario: Parked site content remains readable
- WHEN a site has been parked read-only by
force_reduce - THEN its pages SHALL remain served and readable
- AND edits, forks, and new pages SHALL be blocked
Scenario: Member swaps the active site, second swap refused by cooldown
- WHEN a member on a 1-site limit swaps which site is active for the first time
- THEN the swap SHALL succeed (the chosen site becomes active, the prior one read-only)
- WHEN the same member attempts a second swap before the cooldown elapses
- THEN the swap SHALL be refused
Scenario: Restoring your incumbent active site is always free
- WHEN a member archives the site that was active (not force-reduced) and then restores that same site
- THEN the restore SHALL be allowed without consulting the cooldown
- AND it SHALL NOT stamp or extend the cooldown window
Scenario: Rotating a downgrade-parked reserve in is gated
- WHEN a member whose quota was reduced restores a
force_reduced(parked) site for the first time within the window - THEN the restore SHALL succeed, clear that site's
force_reduced_at, and stamp the cooldown - WHEN the member tries to rotate in another
force_reducedsite before the window elapses - THEN that rotation SHALL be refused
- AND restoring their own (non-force-reduced) incumbent SHALL still be allowed during the window
Scenario: Cooldown window honors configuration
- WHEN the configured cooldown window is changed from its default
- THEN rotations SHALL be gated by the configured window
- AND the user-facing refusal message SHALL reflect the configured window
Scenario: Re-upgrade reactivates parked sites up to the new limit
- WHEN a member with 2
force_reducedread-only sites is upgraded so the limit becomes 3 - THEN the system SHALL reactivate the
force_reducedsites most-recently-modified first, up to the limit - AND the reactivated sites SHALL have
force_reduced_atcleared