Plan-wide changes to ladders and defaults run inside HTTP handlers, each with its own transaction shape #187
Closed
opened 2026-10-06 21:25:45 +00:00 by cgalo5758
·
5 comments
Labels
Clear labels
accessibility
area/billing
area/catalog
area/discourse
area/domains
area/entitlements
area/fedwiki
area/identity
area/integrations
area/licensing
area/member-ui
area/meta
area/operator-ui
area/ops
area/testing
duplicate
good-first-issue
invalid
privacy
security
upstream
wontfix
A barrier for people using assistive technology or a keyboard alone.
The Stripe mirror, checkout, subscriptions, invoices, fulfillment.
Products, prices, plan ladders, purchasability.
The Discourse integration.
The domains registry, claims, placements, the certificate ask.
Entitlement sets, rules, grants, pools, provisioning.
The Federated Wiki integration and farm sync.
Sign-in, sessions, persons, organizations, workspaces, roles.
The provider registry, outbox and webhooks in general.
Licenses, the contributor agreement, SPDX headers.
Member pages.
The repository itself, its contributing guide, CI, the tracker and the workflow.
Operator pages, forms, lists, the design system.
Deployment, configuration, migrations, workflows, instance settings.
The test stack, screens, lint, walkthroughs.
Closed because another issue already covers it.
Small, self-contained, and explained enough to be a first contribution.
Closed because it is not a ticket for this repository.
Touches what a person's data reveals.
Touches authentication, authorization, secrets or data exposure.
Waits on another repository or project before it can move.
Closed because it will not be done, with the reason in the last comment.
kind
bug
The software does something other than what it promises; closed when it again does what it promises.
kind
debt
Code, tests or tooling to clean up with nothing visible changing; closed when they are cleaner.
kind
design
A question to settle before work can be defined; closed when the decision is written down.
kind
docs
Documentation that is wrong or missing; closed when it says the right thing.
kind
enhancement
Something the software does not do yet; closed when it does.
priority
critical
Blocks the active milestone or harms members now.
priority
high
Next in line inside the active milestone.
priority
low
Inside the active milestone, when nothing else is left.
priority
medium
Inside the active milestone, after the high ones.
Milestone
No items
No Milestone
Public launch
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: wiki-cafe/member-console#187
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Grant acts live in
internal/entitlements; three acts that move many organizations stayed in handlers. Tier removal (internal/server/operator_plan_ladders.go) runs in one transaction with sorted pool locks, and any failure changes nothing. The org-type default change (operator_org_types.go) and ladder reorder save the catalog first, then move each organization in its own transaction throughenactOrgDisposition; a failure leaves that organization on the outgoing default, recorded only in the message after the commit.Done when: the three acts are functions in
internal/entitlementslocking pools through one helper; the reorder and default change run in one transaction or record the organizations they did not move where a page can show them (#78); handlers only parse, call and render; and a test runs a tier removal concurrently with a default change on a shared pool. #82 builds on these functions.This also covers tier add and tier delete. Both open their transactions in the handler, and tier delete renumbers the ladder as the reorder and the tier removal do (the race in #213). They move into
internal/entitlementswith the other three and take the same shape.Two more things this needs to cover. The five plan-wide acts now run in internal/entitlements (
AddTier,DeleteTier,RemoveTier,ChangeOrgTypeDefaultwithEnactDefault, andReorderTiers), with the same transactions they had in the handlers.Neither the default change nor the reorder can be re-run to move the organizations a failed enactment left on the outgoing default. The default-change form refuses the current default as a candidate, and dropping the committed order again finds rank 0 unchanged and enacts nothing. Both failure answers still tell the operator to re-run ("Re-run the commit.", "re-drop the same order, and commit again"), and the org-types requirement says the enactment is safely re-runnable after failures are fixed.
The default change fixes its candidate's rank-0 product once, before it saves. A reorder, delete or removal on that ladder that commits in between makes every organization classify against a product that is no longer rank 0. Two reorders of one default ladder in quick succession reach the same thing: the first one's enactment classifies against the rank-0 product it saved after the second has renumbered the ladder, and grandfathers an organization that is already on the current rank 0.
TestReorderEnactsAgainstTheRankZeroItSavedpins what that records today.The removal-versus-default-change test exists now:
TestTierRemovalAgainstDefaultChangequeues both acts on one pool in each order and runs them one after the other in each order. It pins what happens today, so the change that settles this issue flips its files on purpose.Two more defects in the plan-wide acts.
Enactment order among organizations with one name is not fixed. The default change (
ChangeOrgTypeDefault,internal/entitlements/default_change.go:192-206) and the reorder's enactment (enactReorder,internal/entitlements/ladder_acts.go:441-449) enact organizations in the orderListOrganizationsByTypereturns them. That query sorts bynamealone (internal/organization/queries/organizations.sql:87-90), and organization names are not unique. Two organizations with the same name are enacted, and listed among the failures in the answer, in either order from one run to the next. Ending the sort with the organization id fixes it.Tier add records the ladder's id in the transition reason.
AddTierwrites "tier added to ladder " (internal/entitlements/ladder_acts.go:334). The other acts record names, read when they write: the removal records the product and ladder names (line 145), the reorder the ladder's name, with the id only when the name cannot be read (lines 428-437), and the default change the org type (default_change.go:191). The Tier changes list matches the prefix and shows "Tier added to the ladder" either way (internal/server/operator_tier_changes.go:50-51), so the difference is only in the stored history: a tier add's rows name the ladder by an id, which says nothing once the ladder is deleted. Once the acts share one shape, one function should build the reason for all of them.All five plan-wide changes now run in
internal/entitlementson one shape, and a default change or a reorder keeps a record of every organization it still has to move.How each part of "Done when" is met:
internal/entitlementsthat lock pools through one helper.AddTier,DeleteTier,RemoveTier,ChangeOrgTypeDefaultandReorderTiersrun onrunPlanAct. It opens the transaction, finds the pools the act touches, locks them in ascending id throughlockPools, reads them again under the locks, then acts and commits. Every failure names the step where it happened. Each act also writes an act record (core.acts, with its plan details incore.plan_act_details): who made the change, why, and which request it came from.TestTierRemovalAgainstDefaultChangeruns both acts on one pool, in each order, each queued behind the other.The defects raised in the comments above are fixed too:
What it leaves to other issues:
core.grants.act_id.Commits:
de059233,af75641ato86c8d0a1,ff7f31e5,7382877e,0b48297b,b0df2864,a86547a4,eeded12c,9b0e2e56,93cb5545,95a863e0.Two follow-ups landed after closing:
9db9c107: the refusal of a default ladder's only tier now names the way past it: "New Personal organizations start on this tier, the only one in their default plan. Add another tier first, or choose a new default for Personal."35e14578: the ladder page decides every tier's removal with the same rule the preview and the removal use, so a tier more organizations hold than the holder limit renders Preview removal disabled with the limit sentence as its reason. It reads every tier's holder counts in one statement.