Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Roadmap

MOTIR-4228Implemented

(motir-core) The custom-domain entitlement — the `custom_domains` kind, `maxCustomDomains` per tier, and the org-locked cap assert in entitlementsService

Opened by Zhu Yue ·

The tier gate the story reads instead of hard-coding a plan — a new entitlement kind, its per-tier cap, and the org-locked assert the lifecycle calls before it creates a custom domain. The story's own criterion: "the capability reads the tier gate rather than hardcoding a plan, and the gate's name is cited". This card is that gate; the ADR Q8 names it.

What ships

  • lib/billing/entitlements.tsEntitlementKind gains 'custom_domains'; PmEntitlements gains maxCustomDomains: number | null; PM_ENTITLEMENTS gains the value on every PmTier row with the ADR's provisional numbers (free: 0 · scaled and the paid rows as Q8 states · enterprise / meta: null), each row commented as seed policy owned by billing-tiering.md. PM_ENTITLEMENTS is Record<PmTier, PmEntitlements>, so a missing row is a compile error — that is the totality this leans on.
  • lib/services/entitlementsService.tsassertCanAddCustomDomain(organizationId, tx): inert off-cloud (isCloudBilling() false ⇒ return, as every sibling cap does); lockOrgRowOrRefuse(organizationId, tx) FIRST — a cap that cannot serialize is not a cap (MOTIR-3710's lesson, in the file's own header); resolve the tier through pmTierForOrg (the one chokepoint — the meta org's exemption arrives for free); count the org's custom_domain rows under the lock through a new publicAddressRepository.countCustomDomainsForOrganization(organizationId, tx) (added here to the store's repository — one Prisma op, joins project.workspace.organizationId); throw EntitlementExceededError('custom_domains', …) at the cap. The caller must run it inside the same transaction as the create it guards — say so in the JSDoc exactly as the siblings do.
  • Totality downstream: every Record<EntitlementKind, …> in the tree gains the member — grep for the type and list the hits in the PR (the upgrade-prompt copy map the billing UI keys off EntitlementExceededError.entitlement is the one that matters: 8.1.7 / 8.1.8's prompt must render a sentence for custom_domains, in en and zh).
  • Tests: the new row in the table-driven PM_ENTITLEMENTS test; assertCanAddCustomDomain off-cloud is a no-op; at the cap it throws with entitlement: 'custom_domains'; a real-concurrency test — two adds at cap − 1 under a warm pool produce exactly one success (the warm-pool TOCTOU the service header describes); the meta org is never capped.

Boundary

No lifecycle logic, no route, no UI (the lifecycle calls this; part 2 of the pane renders the refusal). The NUMBERS are not this card's to argue — they are the ADR's provisional values and billing-tiering.md's to change.

Acceptance criteria

  • EntitlementKind includes 'custom_domains', PmEntitlements includes maxCustomDomains, and every PmTier row of PM_ENTITLEMENTS carries the ADR Q8 value; pnpm typecheck is what proves totality.
  • assertCanAddCustomDomain locks the org row, counts under the lock, and throws EntitlementExceededError with entitlement === 'custom_domains' at the cap; off-cloud it performs no read.
  • Two concurrent adds at cap − 1 yield one success and one EntitlementExceededError under a real connection pool.
  • Every Record<EntitlementKind, …> in the tree compiles with the new member, and the upgrade prompt has an en and a zh sentence for it.
  • No file outside motir-core is touched; the only repository method added is the count.

Context refs

  • motir-core/lib/billing/entitlements.tsPmTier, EntitlementKind, PmEntitlements, PM_ENTITLEMENTS, pmTierForOrg
  • motir-core/lib/services/entitlementsService.tslockOrgRowOrRefuse, the count-cap pattern, the MOTIR-3710 / MOTIR-3717 header
  • motir-core/lib/billing/errors.tsEntitlementExceededError; lib/dto/billing.ts — the kinds the UI maps
  • motir-core/docs/decisions/billing-tiering.md §4 — the caps and who owns their values
  • the decision — Q8; the store — the repository the count is added to

Discussion

No comments yet.

Adding to this discussion signs you in on app.motir.co and brings you back to this request.

Add a comment