@aglyn/aglyn 1.0.0-beta.229 → 1.0.0-beta.231
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -11
- package/src/lib/app-utils/analytics-events.d.ts +18 -0
- package/src/lib/app-utils/analytics-events.js +2 -0
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +14 -1
- package/src/lib/app-utils/crm.js +19 -2
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +111 -9
- package/src/lib/app-utils/docs-help.generated.js +272 -3
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +810 -61
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/host-status.d.ts +85 -0
- package/src/lib/app-utils/host-status.js +115 -0
- package/src/lib/app-utils/host-status.js.map +1 -0
- package/src/lib/app-utils/lockdown.js +1 -1
- package/src/lib/app-utils/lockdown.js.map +1 -1
- package/src/lib/app-utils/media-filter.d.ts +136 -0
- package/src/lib/app-utils/media-filter.js +400 -0
- package/src/lib/app-utils/media-filter.js.map +1 -0
- package/src/lib/app-utils/mobile-push.d.ts +98 -0
- package/src/lib/app-utils/mobile-push.js +97 -0
- package/src/lib/app-utils/mobile-push.js.map +1 -0
- package/src/lib/app-utils/notification-push.d.ts +38 -0
- package/src/lib/app-utils/notification-push.js +54 -0
- package/src/lib/app-utils/notification-push.js.map +1 -0
- package/src/lib/app-utils/notifications.d.ts +7 -0
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/organizations.js +5 -2
- package/src/lib/app-utils/organizations.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.js +20 -0
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.d.ts +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.js +164 -0
- package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -1
- package/src/lib/app-utils/plugin-release-flags.generated.d.ts +1 -1
- package/src/lib/app-utils/plugin-release-flags.generated.js +7 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -1
- package/src/lib/app-utils/realm-host-surface.generated.js +3 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.js +6 -2
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/scope-tokens.d.ts +16 -1
- package/src/lib/app-utils/scope-tokens.js +15 -1
- package/src/lib/app-utils/scope-tokens.js.map +1 -1
- package/src/lib/app-utils/site-journey.d.ts +143 -0
- package/src/lib/app-utils/site-journey.js +282 -0
- package/src/lib/app-utils/site-journey.js.map +1 -0
- package/src/lib/app-utils/site-list-query.d.ts +47 -0
- package/src/lib/app-utils/site-list-query.js +142 -0
- package/src/lib/app-utils/site-list-query.js.map +1 -0
- package/src/lib/app-utils/site-wide-outbox.d.ts +95 -0
- package/src/lib/app-utils/site-wide-outbox.js +117 -0
- package/src/lib/app-utils/site-wide-outbox.js.map +1 -0
- package/src/lib/app-utils/transfer-launcher-context.d.ts +6 -0
- package/src/lib/app-utils/transfer-launcher-context.js.map +1 -1
- package/src/lib/app-utils/upload-inspection.js +7 -0
- package/src/lib/app-utils/upload-inspection.js.map +1 -1
- package/src/lib/app-utils/webhook-delivery.js +4 -1
- package/src/lib/app-utils/webhook-delivery.js.map +1 -1
- package/src/lib/foundation/definitions/org-billing.types.d.ts +24 -0
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +18 -7
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +4 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.js +12 -2
- package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
- package/src/lib/plugin-manager/enabled-plugins.js +4 -2
- package/src/lib/plugin-manager/enabled-plugins.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +173 -0
- package/src/lib/plugin-manager/feature-plugins.js +62 -1
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +251 -2
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-ai-capabilities.d.ts +192 -0
- package/src/lib/plugin-manager/plugin-ai-capabilities.js +157 -0
- package/src/lib/plugin-manager/plugin-ai-capabilities.js.map +1 -0
- package/src/lib/plugin-manager/plugin-checkout-extras.d.ts +168 -0
- package/src/lib/plugin-manager/plugin-checkout-extras.js +172 -0
- package/src/lib/plugin-manager/plugin-checkout-extras.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contributions.d.ts +7 -0
- package/src/lib/plugin-manager/plugin-contributions.js +1 -1
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-domain-events.d.ts +138 -0
- package/src/lib/plugin-manager/plugin-domain-events.js +148 -0
- package/src/lib/plugin-manager/plugin-domain-events.js.map +1 -0
- package/src/lib/plugin-manager/plugin-events.d.ts +51 -0
- package/src/lib/plugin-manager/plugin-events.js +4 -0
- package/src/lib/plugin-manager/plugin-events.js.map +1 -1
- package/src/lib/plugin-manager/plugin-fulfillment-providers.d.ts +101 -0
- package/src/lib/plugin-manager/plugin-fulfillment-providers.js +83 -0
- package/src/lib/plugin-manager/plugin-fulfillment-providers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-permissions.js +21 -5
- package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-records.d.ts +90 -0
- package/src/lib/plugin-manager/plugin-person-records.js +26 -0
- package/src/lib/plugin-manager/plugin-person-records.js.map +1 -1
- package/src/lib/plugin-manager/plugin-product-catalog.d.ts +204 -0
- package/src/lib/plugin-manager/plugin-product-catalog.js +43 -0
- package/src/lib/plugin-manager/plugin-product-catalog.js.map +1 -0
- package/src/lib/plugin-manager/plugin-shipment-records.d.ts +210 -0
- package/src/lib/plugin-manager/plugin-shipment-records.js +63 -0
- package/src/lib/plugin-manager/plugin-shipment-records.js.map +1 -0
- package/src/lib/plugin-manager/plugin-shipping-rates.d.ts +151 -0
- package/src/lib/plugin-manager/plugin-shipping-rates.js +62 -0
- package/src/lib/plugin-manager/plugin-shipping-rates.js.map +1 -0
- package/src/lib/plugin-manager/plugin-sms-messaging.d.ts +103 -0
- package/src/lib/plugin-manager/plugin-sms-messaging.js +39 -0
- package/src/lib/plugin-manager/plugin-sms-messaging.js.map +1 -0
- package/src/lib/plugin-manager/plugin-stock-levels.d.ts +81 -0
- package/src/lib/plugin-manager/plugin-stock-levels.js +32 -0
- package/src/lib/plugin-manager/plugin-stock-levels.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tax-profile.d.ts +154 -0
- package/src/lib/plugin-manager/plugin-tax-profile.js +56 -0
- package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
- package/src/lib/plugin-manager/plugin-theme-font-catalog.d.ts +59 -0
- package/src/lib/plugin-manager/plugin-theme-font-catalog.js +40 -0
- package/src/lib/plugin-manager/plugin-theme-font-catalog.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tracking-pages.d.ts +55 -0
- package/src/lib/plugin-manager/plugin-tracking-pages.js +76 -0
- package/src/lib/plugin-manager/plugin-tracking-pages.js.map +1 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +3 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/plan-entitlements.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n AglynOrgBilling,\n CoreOrgEntitlements,\n CoreOrgFeatureFlags,\n OrgBrandingProfile,\n OrgDiscount,\n OrgEntitlements,\n OrgFeatureFlags,\n OrgPlan,\n OrgSeatAddons,\n} from '../foundation'\nimport type {\n PluginOrgFeatures,\n PluginOrgQuotas,\n} from '../plugin-manager/plugin-entitlement-keys'\nimport {\n isOrgOverrideReasonCode,\n type OrgOverrideReasonCode,\n} from './org-override-reason'\nimport type { ResolvedBrandingProfile } from './platform-brand'\nimport {\n PLATFORM_BRAND_NAME,\n PLATFORM_BRANDING_PROFILE,\n} from './platform-brand'\nimport {\n listPluginFeatures,\n listPluginSeatAddons,\n pluginSeatAddon,\n} from '../plugin-manager/plugin-entitlements'\nimport { pluginOrgCapacity } from '../plugin-manager/plugin-org-capacity'\nimport {\n declaredMeterReading,\n pluginCostAxes,\n pluginCostAxisFields,\n} from '../plugin-manager/plugin-usage-axes'\nimport {\n planQuotaOf,\n pluginPlanFeatureRow,\n pluginPlanQuotaRow,\n pluginPlanQuotas,\n} from '../plugin-manager/plugin-plan-entitlements'\n\n/** Sentinel for quotas a plan does not cap; `checkQuota` always allows. */\nexport const UNLIMITED = Number.POSITIVE_INFINITY\n\n/**\n * How many saved form definitions one site may hold on the plans at the top\n * of the ladder: Scale, Advanced, Agency and Enterprise.\n *\n * Since 2026-10-06 the saved-form catalog IS a tier lever (AGL-3597): Free 1,\n * Starter 5, Pro 25, Business 100, and this ceiling above them. Before that\n * date every paid plan carried this number and Free carried none, which made\n * a Starter catalog of 500 forms a give-away no pricing page listed. The\n * ladder sits close to the published form-count ladders in the field (Wix\n * 4/10/25/75, Jotform 5/25/50/100), and the allowance is listed on the\n * pricing pages as \"Saved forms per site\".\n *\n * A form is no longer gated by `reusableComponents`: a Free site can save its\n * one form, and only components stay Starter-and-above. The count is refused\n * at the CREATE and nowhere else, so a site already holding more than its new\n * allowance keeps every form it has; it only cannot add another.\n *\n * The value stays STRICTLY below `FORMS_MAX_PER_HOST` — the page size two\n * listing reads use. The gap is deliberate: a catalog can sit above this\n * ceiling when a per-org override is withdrawn, and the two numbers must stay\n * tellable apart so a surface reading the window where it means the ceiling\n * is never accidentally right. `forms.spec.ts` pins both halves.\n *\n * A per-org `entitlements.formsPerHost` override still resolves ahead of the\n * plan's number, so a contract can raise or lower it for one org without\n * moving the allowance everyone else on the plan is measured against.\n */\nexport const FORMS_PER_HOST_CEILING = 500\n\n/**\n * Is this quota value \"no cap\"? (AGL-2482; AGL-2223 is the same class.)\n *\n * WHY A PREDICATE AND NOT `x === UNLIMITED`. `UNLIMITED` is\n * `Number.POSITIVE_INFINITY`, and `JSON.stringify(Infinity)` is **`null`** —\n * so the moment an entitlement crosses a route boundary the sentinel is gone\n * and `=== UNLIMITED` is false for a plan that really is uncapped. Worse, the\n * client then reads `Number(null)`, which is `0`, and `Number.isFinite(0)` is\n * TRUE: the value sails through every guard written to reject a payload that\n * cannot state the terms, and the console renders a cap of zero for the most\n * expensive plan on the price list.\n *\n * So: `Infinity`, `null`, `undefined` and `NaN` all read as unlimited here.\n *\n * THIS IS A BACKSTOP, NOT THE FIX. `null` on the wire genuinely cannot\n * distinguish \"unlimited\" from \"the field is missing\", and answering\n * \"unlimited\" for a missing field is the permissive direction — the same\n * shape as `checkQuota(undefined)` resolving to Free. A route that serialises\n * a quota must therefore send a FINITE number plus an explicit boolean flag\n * (see `/api/billing/storage-overage`), and the client must rebuild the\n * sentinel from the flag with `restoreQuotaLimit` before it does arithmetic.\n * This predicate exists so that a surface which has not been given the flag\n * still renders \"Unlimited\" instead of `0`, `null` or `Infinity`.\n */\nexport function isUnlimitedQuota(limit: number | null | undefined): boolean {\n return limit == null || !Number.isFinite(limit)\n}\n\n/**\n * A quota cap, written the way every surface in the console must write it:\n * `Unlimited` for an uncapped plan, the number otherwise.\n *\n * ONE implementation on purpose. Six surfaces had grown their own copy of the\n * `limit === UNLIMITED ? 'Unlimited' : String(limit)` line and a seventh\n * category — the ones that interpolated the raw number — shipped\n * \"Every site can run null registers on your current plan\" and\n * \"Screen limit reached (Infinity)\" to customers.\n *\n * @param limit The cap. Non-finite, `null` and `undefined` are unlimited.\n * @param unit Appended to a finite number only — `MB`, `registers`. Never\n * appended to `Unlimited`, because \"Unlimited MB\" reads as a typo.\n */\nexport function formatQuotaLimit(\n limit: number | null | undefined,\n unit?: string,\n): string {\n if (isUnlimitedQuota(limit)) return 'Unlimited'\n return unit ? `${limit} ${unit}` : String(limit)\n}\n\n/**\n * Rebuild the `UNLIMITED` sentinel a JSON round-trip flattened, from the\n * explicit flag the route sent alongside the number.\n *\n * The client half of the wire contract. Once this has run, every comparison\n * downstream is ordinary arithmetic that happens to be correct: `used >\n * Infinity` is false, `Math.max(0, used - Infinity)` is `0`, and nothing has\n * to remember that this particular number might be a `null` in disguise.\n *\n * That mattered more than the display did. `1 > null` is **true**, so a site\n * on an unlimited plan was reported \"1 over the limit\" while the readout\n * beside it said `1/∞` — a false limit warning on a plan that has no limit.\n *\n * @param value The finite number the route sent (or the `null` it sent for a\n * pre-fix payload).\n * @param unlimited The route's explicit flag. `undefined` means the route has\n * not been taught to send one, and then `value` decides — non-finite reads\n * as unlimited, which is the `isUnlimitedQuota` backstop.\n */\nexport function restoreQuotaLimit(\n value: number | null | undefined,\n unlimited?: boolean,\n): number {\n if (unlimited === true) return UNLIMITED\n if (unlimited === false) return Number(value)\n return isUnlimitedQuota(value) ? UNLIMITED : Number(value)\n}\n\n/**\n * Campaign emails an Enterprise agreement includes per month, before the\n * contract says otherwise.\n *\n * ## Why the one Enterprise quota that is a number\n *\n * Every other Enterprise band is `UNLIMITED` because the thing behind it is\n * infrastructure we already meter and price into the deal — storage, page\n * views, seats. Email is not that. It is a per-message charge from a third\n * party, so an unbounded allowance is an unbounded liability with no meter\n * underneath it and no ceiling a negotiation ever had to name.\n *\n * `UNLIMITED` was also unrepresentable off-process. It is\n * `Number.POSITIVE_INFINITY`, `JSON.stringify(Infinity)` is `null`, and\n * `Number(null)` is `0` — so the most expensive plan on the price list\n * serialised to a cap of ZERO through any route that did not also send the\n * explicit flag `restoreQuotaLimit` rebuilds from. A finite number crosses\n * the wire as itself.\n *\n * ## Why this number\n *\n * Twice Agency's band — the rule every Enterprise fallback follows since the\n * 2026-09-07 pricing decision (see the `enterprise` row of\n * `PLAN_ENTITLEMENTS`) — and inside what the sending platform can actually\n * deliver to one workspace with room to spare. At the shipped constants a\n * workspace's share is 500 messages an hour and a projected month is 720\n * hours, so 360,000 is the ceiling nothing can exceed however it is sold.\n * 260,000 spends 520 of those 720 hours, leaving the rest for bursts, retries\n * and domain warm-up — `email-ceiling-dimensioning.spec.ts` holds that\n * relation, and holds the platform rate still so the gap cannot be closed by\n * quietly raising it.\n *\n * Selling more than that would be selling mail that cannot leave the\n * building: the hourly ceiling DEFERS rather than refuses, so the excess does\n * not bounce, it simply never sends.\n *\n * ## It is a DEFAULT, and a contract raises it\n *\n * `resolveOrgEntitlements` applies a per-org `entitlements.emailSendsPerMonth`\n * override ahead of this, so a deal that buys more email buys it on that org\n * without moving the figure every other agreement is measured against — the\n * same mechanism every numeric entitlement uses.\n */\nexport const ENTERPRISE_EMAIL_SENDS_PER_MONTH = 260_000\n\n/**\n * Plan → default entitlements. Versioned with the app so pricing changes are\n * code-reviewed; per-org overrides live on `org.entitlements` and win\n * key-by-key. Tier table aligned to the Tenant Billing & SaaS Plans proposal\n * (AGL-67, 2026-07-07): storage-per-host is media storage; the published\n * total-site-size cap it used to exceed by design was retired in AGL-2133.\n * Metered costs are passed through\n * from Firebase/Vercel at cost × 1.30 separately (AGL-41).\n */\n/** Legacy host-keyed dataset overrides resolved into org keys (AGL-240). */\ntype LegacyEntitlementKeys = 'datasetsPerHost' | 'maxDatasetsPerHost'\n\n/**\n * Keys `OrgEntitlements` still carries so stored documents type-check, but\n * that no plan declares and nothing resolves (AGL-2133). Kept as a named type\n * rather than folded into `LegacyEntitlementKeys`: legacy keys are resolved\n * INTO their replacements, retired ones are dropped, and conflating the two\n * would eventually resolve a retired value into something.\n *\n * `RETIRED_ENTITLEMENT_KEYS` is the runtime half and must list the same keys.\n */\ntype RetiredEntitlementKeys = 'totalSiteSizeMb'\n\n/**\n * Keys stored under `entitlements` that are not quotas at all (AGL-3034): the\n * staff plan comp rides the same staff-owned map, and is read through\n * `readOrgPlanComp` rather than resolved into a limit.\n */\ntype NonQuotaEntitlementKeys = 'planComp'\n\n/**\n * Fully-resolved entitlements: every LIVE CORE quota present, the core\n * features complete, and whatever the plugins declared beside them.\n *\n * `Required` is over the CORE halves on purpose (AGL-3124). It is what keeps\n * a plan table honest — a plan that forgets a platform quota or a platform\n * gate does not compile — and that reasoning does not carry to a plugin's\n * key: a plugin's figure per plan comes from its own `plan-entitlements`\n * declaration (AGL-3080), whose rows are typed per plan and so cannot forget\n * one either, and requiring it here would mean every plan row in the core\n * naming every installed plugin's keys, which is the arrangement the seam\n * retires. So the plugin halves join as declared: present once a plugin\n * declares them, and optional, because core compiles without the plugin's\n * type and a workspace without the plugin has no value for them.\n *\n * They join in their MAPPED form (`PluginOrgQuotas`, `PluginOrgFeatures`)\n * because an intersection with a bare interface has no implicit string index\n * signature, and readers that hand these objects to a `Record<string, …>`\n * parameter — the quota line, the digest gates — would stop compiling for a\n * plugin half that is empty.\n */\nexport type ResolvedOrgEntitlements = Required<\n Omit<\n CoreOrgEntitlements,\n | 'features'\n | LegacyEntitlementKeys\n | RetiredEntitlementKeys\n | NonQuotaEntitlementKeys\n >\n> &\n PluginOrgQuotas & {\n features: Required<CoreOrgFeatureFlags> & PluginOrgFeatures\n }\n\nexport const PLAN_ENTITLEMENTS: Record<OrgPlan, ResolvedOrgEntitlements> = {\n free: {\n hostLimit: 1,\n screensPerHost: 5,\n sharedLayoutsPerHost: 1,\n templatesPerHost: 10,\n storagePerHostMb: 250,\n membersPerHost: 1,\n managersPerOrg: 1,\n maxManagersPerOrg: 1,\n maxMembersPerHost: 1,\n bandwidthGb: 2,\n // One saved form per Free site (AGL-3597). The saved form does not ride\n // `reusableComponents` — components stay Starter-and-above, a form does\n // not — so `/api/hosts/resources` refuses a second form on this number,\n // with the upgrade path, rather than refusing the first on a feature.\n //\n // A `Form` node placed on a page still needs no definition to collect,\n // and the 20 submissions above are the band those replies spend.\n //\n // The ladder: Free 1, Starter 5, Pro 25, Business 100, and\n // `FORMS_PER_HOST_CEILING` on Scale and above.\n formsPerHost: 1,\n variablesPerHost: 3,\n functionsPerHost: 1,\n workflowsPerHost: 0,\n servicesPerHost: 0,\n redirectsPerHost: 0,\n contactsPerHost: 100,\n emailSendsPerMonth: 0,\n actionRunsPerMonth: 0,\n apiRequestsPerMonth: 0,\n datasetsPerOrg: 0,\n maxDatasetsPerOrg: 0,\n recordsPerDataset: 0,\n dataStorageMbPerOrg: 0,\n productsPerHost: 0,\n inventoryLocations: 1,\n posRegisters: 0,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 0,\n features: {\n abTesting: false,\n versioning: false,\n reusableComponents: false,\n customDomain: false,\n // Sending as a domain the customer owns starts at Pro, with campaign\n // email. Free sends no marketing at all — `emailSendsPerMonth` is 0 —\n // and its transactional mail leaves on the shared pool, which needs no\n // DNS from anybody.\n customSendingDomain: false,\n // The expensive shape, and the one Free must never reach by default: a\n // provider slot, three records in the platform's own zone and a place\n // in the re-verification sweep, spent per site on a tier that pays\n // nothing. The pool carries this tier's mail at no per-site cost.\n dedicatedSendingDomain: false,\n removeBranding: false,\n scheduledPublishing: false,\n // The two AI flags point opposite ways on Free, on purpose (AGL-2925).\n // `aiAssist` is the guided rung of the console assistant — page-aware\n // level-2 answers and the copy assistant — and starts at Starter; Free\n // keeps the docs-grounded level-1 chat it always had. `aiGenerative`\n // is the generative door the taste is FOR: a Free workspace may\n // generate against its 300-credit band, behind the wall and every\n // precaution the constant lists, so the landing page's \"generate your\n // first page free\" is a thing the product does.\n aiAssist: false,\n aiGenerative: true,\n workflows: false,\n dataStore: false,\n videoMedia: false,\n bookings: false,\n // The CRM starts at Starter (AGL-2611), whole, and Free has none of it\n // (AGL-2851): no Leads, Contacts, Companies, Deals, Tasks, Reports,\n // Fields or Settings, no CRM automation step and no CRM REST resource.\n // What Free's sites capture still counts against its 100-record band,\n // and exporting and erasing those people stay on every plan.\n // The Drive pricing decision of 2026-09-05 records why Starter and\n // not Pro: the field prices a CRM seat at $14–25 a month, so Starter\n // with the suite included is the competitive entry, and gating a\n // tier higher hands the small-business buyer to a free CRM elsewhere.\n crm: false,\n // Sequences (AGL-2974) is on NO plan, this one through Enterprise: which\n // tiers carry sequences and connected mailboxes, and at what caps, is\n // an owner decision still to be made. Until then an organization gets\n // it only through the per-org `entitlements.features.outreach`\n // override, and the Firestore rules read that override alone.\n outreach: false,\n interactions: true,\n actions: false,\n webhooks: false,\n apiAccess: false,\n siteExport: false,\n multilingual: false,\n eventCalendar: false,\n redirects: false,\n screenAnalytics: false,\n /**\n * CDN delivery and responsive image variants, on every plan (AGL-1152).\n *\n * Without this, media is addressed by its absolute\n * `firebasestorage.googleapis.com` URL: a visitor fetches the full-size\n * original from Storage egress on every request, with no shared edge\n * cache in front of it. With it, media is served over the CDN path,\n * which is edge-cacheable and returns a resized WebP variant.\n *\n * The ungated path therefore costs more to run, on both bytes per\n * request and origin requests per byte. Gating this would raise the cost\n * of the tier that pays nothing.\n */\n mediaCdn: true,\n marketingOverlays: false,\n commerce: false,\n pos: false,\n contentGating: false,\n productReviews: false,\n abandonedCart: false,\n dropshipRouting: false,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n starter: {\n hostLimit: 1,\n screensPerHost: 25,\n sharedLayoutsPerHost: 3,\n templatesPerHost: 50,\n storagePerHostMb: 2048,\n membersPerHost: 2,\n managersPerOrg: 2,\n maxManagersPerOrg: 5,\n maxMembersPerHost: 10,\n // Sized by the same annual-price invariant as Pro's — see that band.\n bandwidthGb: 15,\n formsPerHost: 5,\n variablesPerHost: 25,\n functionsPerHost: 10,\n workflowsPerHost: 3,\n servicesPerHost: 1,\n redirectsPerHost: 25,\n contactsPerHost: 1000,\n // Campaign email starts at Pro. A site that may send campaigns needs its\n // own verified provider sending domain, and provisioning one is a real\n // per-site operational cost that a campaign allowance commits the platform\n // to the moment the tier includes it. Holding the allowance at 0 here\n // keeps that cost attached to the tiers that carry it.\n //\n // Zero is a REFUSAL, not a formality: `reserveCampaignEmailSends` compares\n // `used + count > limit` inside its transaction, so any campaign of one or\n // more recipients is refused before a message leaves.\n //\n // It does NOT stop transactional mail. Receipts, invites, booking\n // reminders, workflow notifications and password resets are refused by no\n // quota at any tier (see `emailSendsPerMonth` on `OrgEntitlements`), so a\n // Starter store still confirms its orders. Those sends still count on the\n // `emailSends` cost meter; they are simply not billable here, because\n // `emailSendsOverage` yields 0 against a non-positive band.\n //\n // The contacts band above is untouched: holding an audience is not sending\n // to it, and the CRM is what a Starter site builds before it upgrades.\n emailSendsPerMonth: 0,\n actionRunsPerMonth: 0,\n apiRequestsPerMonth: 0,\n datasetsPerOrg: 2,\n maxDatasetsPerOrg: 10,\n recordsPerDataset: 1000,\n dataStorageMbPerOrg: 512,\n productsPerHost: 100,\n inventoryLocations: 1,\n posRegisters: 0,\n transactionFeePhysicalPct: 2,\n // Pricing v3 (2026-07): softened 7→5 — a 7% digital fee stacked on the\n // ~2.9%+30¢ processor was ~10% all-in, far above the website-builder\n // norm (Wix/BigCommerce/Squarespace-Core = 0% platform fee). See the\n // Pricing Decision Log + Competitive Benchmark.\n transactionFeeDigitalPct: 5,\n features: {\n abTesting: false,\n versioning: false,\n reusableComponents: true,\n customDomain: true,\n // Starter's allowance is 0 campaign sends, so a verified domain of the\n // customer's own would carry receipts and nothing else — the one class\n // of mail the shared pool already delivers with no DNS work at all. The\n // capability arrives with the sends that need it.\n customSendingDomain: false,\n // Same tier as the customer-owned shape and for the stronger reason:\n // this one draws on a provider allowance that cannot be bought past a\n // certain size, so it is rationed above the tier that carries no\n // campaign sends at all.\n dedicatedSendingDomain: false,\n removeBranding: true,\n scheduledPublishing: false,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: false,\n bookings: true,\n // The whole CRM, from the first paid tier (AGL-2611). At 100% of\n // its band and its one-to-one email cap this costs $1.73 a month\n // against a $16 annual price — the Drive pricing decision of\n // 2026-09-05 has the arithmetic — and it is the upgrade motive from\n // Free, not a line on top of a plan.\n crm: true,\n outreach: false,\n interactions: true,\n actions: false,\n webhooks: false,\n apiAccess: false,\n siteExport: false,\n multilingual: false,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: false,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: false,\n contentGating: false,\n productReviews: false,\n abandonedCart: false,\n dropshipRouting: false,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n pro: {\n hostLimit: 3,\n screensPerHost: 100,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 5120,\n membersPerHost: 3,\n managersPerOrg: 3,\n maxManagersPerOrg: 20,\n maxMembersPerHost: 25,\n // Page views are the largest term of this tier's modeled COGS. One GB is\n // 1,035 views at `ESTIMATED_PAGE_TRANSFER_BYTES`, priced where Vercel is\n // dearest because it bills in the region that serves a visitor: $0.413 of\n // weight (`perPageView` — transfer at $0.35 a decimal GB, reads at nam5)\n // and $0.224 of requests once the hosting plan's monthly allowance is\n // spent (`PAGE_VIEW_CDN_REQUEST_COST_USD` — 57 CDN requests at $3.20 per\n // million and the analytics beacon's function). So a GB of included\n // bandwidth costs $0.637, and the bandwidth band is what decides whether\n // the tier survives a customer spending the whole allowance it was sold.\n //\n // The invariant the band is sized against is the ANNUAL price ($39 a\n // month) net of Stripe's fee, with every band at 100% and the CRM seat\n // and one-to-one email terms counted — the 2026-09-07 pricing decision —\n // at that all-in gigabyte (AGL-3444), and it must cover that cost plus\n // 30%, the same markup the metered overages carry (AGL-3469). 25 GB is\n // what it carries beside the storage and campaign bands it now sits\n // with; every paid band on the ladder is sized the same way.\n // `tier-margin-floor.spec.ts` holds the model and pins the figure.\n //\n // `meteredInfraPassThrough` is true here, so traffic past the band BILLS\n // at the page-view pass-through rather than being refused or absorbed.\n bandwidthGb: 25,\n formsPerHost: 25,\n variablesPerHost: 100,\n functionsPerHost: 50,\n workflowsPerHost: 25,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: 100,\n contactsPerHost: 10000,\n emailSendsPerMonth: 2500,\n actionRunsPerMonth: 5000,\n apiRequestsPerMonth: 0,\n datasetsPerOrg: 5,\n maxDatasetsPerOrg: 50,\n recordsPerDataset: 10000,\n dataStorageMbPerOrg: 2048,\n productsPerHost: 2500,\n inventoryLocations: 2,\n posRegisters: 1,\n transactionFeePhysicalPct: 0,\n // Pricing v3 (2026-07): softened 5→3 to smooth the digital fee ladder\n // (Starter 5 → Pro 3 → Business 2 → Scale 1 → Advanced/Agency 0).\n transactionFeeDigitalPct: 3,\n features: {\n abTesting: false,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n // The tier campaign email starts at, and therefore the tier a domain\n // whose reputation belongs to the merchant starts at: marketing may not\n // leave on the shared pool, because one merchant's complaint rate would\n // be charged against every other site's password resets.\n //\n // It costs the platform nothing in our own zone — the customer\n // publishes the records — which is what keeps the platform's domain\n // count flat as the customer base grows. The platform subdomain a site\n // is issued is the expensive half and stays an optimization on top of\n // the pool.\n customSendingDomain: true,\n // The floor for the platform subdomain too, and the tier this one is\n // sized against: a merchant who may send campaigns is a merchant whose\n // mail needs a name of its own, and Pro is where the revenue starts\n // covering a provider slot and three zone records.\n //\n // A flag rather than a tier comparison, so a single org can be granted\n // one through `entitlements.features` without being moved up a plan —\n // the support case this capability meets most often, answered without\n // repricing an account. What the grant does not do is provision: it\n // admits a REQUEST, and the provider ceiling still decides whether the\n // request is filled.\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: false,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: false,\n apiAccess: false,\n siteExport: true,\n multilingual: false,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: false,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n business: {\n hostLimit: 10,\n screensPerHost: UNLIMITED,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 10240,\n membersPerHost: 5,\n managersPerOrg: 5,\n maxManagersPerOrg: 100,\n maxMembersPerHost: 100,\n // Sized by the same annual-price invariant as Pro's — see that band.\n bandwidthGb: 45,\n formsPerHost: 100,\n variablesPerHost: 1000,\n functionsPerHost: 250,\n workflowsPerHost: 100,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: UNLIMITED,\n contactsPerHost: 30000,\n emailSendsPerMonth: 15000,\n actionRunsPerMonth: 50000,\n apiRequestsPerMonth: 1_500,\n datasetsPerOrg: 10,\n maxDatasetsPerOrg: 250,\n recordsPerDataset: 100000,\n dataStorageMbPerOrg: 10240,\n productsPerHost: 10000,\n inventoryLocations: 4,\n posRegisters: 2,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 2,\n features: {\n abTesting: true,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n customSendingDomain: true,\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: true,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: true,\n apiAccess: true,\n siteExport: true,\n multilingual: true,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: true,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n // Scale (Pricing v3, 2026-07): fills the $139→$399 gap. Feature set equals\n // Business/Advanced (all commerce features); limits interpolate between\n // Business and Advanced; digital platform fee drops to 1%.\n scale: {\n hostLimit: 15,\n screensPerHost: UNLIMITED,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 15360,\n membersPerHost: 5,\n managersPerOrg: 5,\n maxManagersPerOrg: 150,\n maxMembersPerHost: 150,\n // Sized by the same annual-price invariant as Pro's — see that band.\n bandwidthGb: 70,\n formsPerHost: FORMS_PER_HOST_CEILING,\n variablesPerHost: 5000,\n functionsPerHost: 500,\n workflowsPerHost: 250,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: UNLIMITED,\n contactsPerHost: 100000,\n emailSendsPerMonth: 25000,\n actionRunsPerMonth: 100000,\n apiRequestsPerMonth: 5_000,\n datasetsPerOrg: 15,\n maxDatasetsPerOrg: 500,\n recordsPerDataset: 500000,\n dataStorageMbPerOrg: 25600,\n productsPerHost: 25000,\n inventoryLocations: 6,\n posRegisters: 3,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 1,\n features: {\n abTesting: true,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n customSendingDomain: true,\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: true,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: true,\n apiAccess: true,\n siteExport: true,\n multilingual: true,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: true,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n advanced: {\n hostLimit: 25,\n screensPerHost: UNLIMITED,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 20480,\n membersPerHost: 5,\n managersPerOrg: 10,\n maxManagersPerOrg: 250,\n maxMembersPerHost: 250,\n // Sized by the same annual-price invariant as Pro's — see that band.\n bandwidthGb: 80,\n formsPerHost: FORMS_PER_HOST_CEILING,\n variablesPerHost: UNLIMITED,\n functionsPerHost: 1000,\n workflowsPerHost: 500,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: UNLIMITED,\n contactsPerHost: 150000,\n emailSendsPerMonth: 65000,\n actionRunsPerMonth: 250000,\n apiRequestsPerMonth: 17_000,\n datasetsPerOrg: 25,\n maxDatasetsPerOrg: 1000,\n recordsPerDataset: 1000000,\n dataStorageMbPerOrg: 51200,\n productsPerHost: UNLIMITED,\n inventoryLocations: 10,\n posRegisters: 5,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 0,\n features: {\n abTesting: true,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n customSendingDomain: true,\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: true,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: true,\n apiAccess: true,\n siteExport: true,\n multilingual: true,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: true,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n // Agency (Pricing v3, 2026-07): high-volume multi-site tier above Advanced.\n // 100 sites, 0% fees, top-of-ladder capacity. Positioned for agencies /\n // resellers — white-label, SSO, and an SLA are the follow-on product work\n // that will further differentiate it (see the Pricing Decision Log).\n agency: {\n hostLimit: 100,\n screensPerHost: UNLIMITED,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 20480,\n membersPerHost: 5,\n managersPerOrg: 10,\n maxManagersPerOrg: 500,\n maxMembersPerHost: 1000,\n // Sized by the same annual-price invariant as Pro's — see that band.\n bandwidthGb: 395,\n formsPerHost: FORMS_PER_HOST_CEILING,\n variablesPerHost: UNLIMITED,\n functionsPerHost: UNLIMITED,\n workflowsPerHost: UNLIMITED,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: UNLIMITED,\n contactsPerHost: 500000,\n emailSendsPerMonth: 130000,\n actionRunsPerMonth: 1000000,\n apiRequestsPerMonth: 85_000,\n datasetsPerOrg: 50,\n maxDatasetsPerOrg: 5000,\n recordsPerDataset: UNLIMITED,\n dataStorageMbPerOrg: 204800,\n productsPerHost: UNLIMITED,\n inventoryLocations: 50,\n posRegisters: 20,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 0,\n features: {\n abTesting: true,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n customSendingDomain: true,\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: true,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: true,\n apiAccess: true,\n siteExport: true,\n multilingual: true,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: true,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n // White-Label Phase 1: the Agency-tier ($1,299) differentiator. Only\n // this tier and Enterprise ship white-label by default.\n whiteLabel: true,\n ssoEnabled: false,\n },\n },\n // Enterprise (AGL-1118): the top of the ladder and the ONLY custom-priced\n // tier — staff-provisioned per deal (AGL-1110), never sold self-serve, so it\n // is excluded from `SELF_SERVE_PLANS` and carries no list price in\n // `PLAN_PRICING`. White-label AND SSO ship on the plan itself rather than\n // through the per-org `entitlements` override that Enterprise-as-a-label\n // needed.\n //\n // ## Every band here is a FALLBACK, and it is FINITE (2026-09-07)\n //\n // The row used to read UNLIMITED on every axis that costs infrastructure,\n // on the theory that the deal prices the usage. It does not, unless the\n // deal says so: `meteredInfraPassThrough` is false here and every overage\n // rate is the \"not for sale\" sentinel, so an Enterprise org provisioned\n // without per-org figures was the one org on the platform whose cost had no\n // bound at all — the pricing soundness audit of 2026-09-07 found its worst\n // case unbounded on every axis. A default that a contract must OVERRIDE to\n // become finite is the wrong way round.\n //\n // So the rule is Agency's band × 2 on every axis Agency bounds, and\n // UNLIMITED only where Agency is already UNLIMITED (screens, layouts,\n // templates, variables, functions, workflows, services, redirects, records\n // per dataset, products). `plan-entitlements.spec.ts` holds that relation\n // key by key, so moving an Agency band without moving this one goes red.\n //\n // A contracted per-org `entitlements.*` value still wins on every one of\n // these — `resolveOrgEntitlements` honors a numeric override on every\n // axis, which is the instrument a deal that needs more is written with. A\n // fallback is what an agreement gets before anybody has written it down.\n //\n // These are CAPS, not prices: with no rate and no pass-through the quota\n // gates refuse at the line (`checkContactQuota`, `checkApiRequestQuota`,\n // `checkFormSubmissionQuota`, `mediaStorageGate` …), exactly as Free's do.\n // At twice the largest self-serve tier's bands that line is one a deal\n // reaches only by being written without its numbers. The bandwidth abuse\n // ceiling still flags and pages staff at 3× the band, and does NOT degrade\n // the render on this plan — see `bandwidthCeilingDegradesRender`.\n //\n // `formsPerHost` is `FORMS_PER_HOST_CEILING`, the top of the saved-form\n // ladder (AGL-3597), shared with Scale, Advanced and Agency.\n enterprise: {\n hostLimit: 200,\n screensPerHost: UNLIMITED,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 40_960,\n membersPerHost: 10,\n managersPerOrg: 20,\n maxManagersPerOrg: 1_000,\n maxMembersPerHost: 2_000,\n bandwidthGb: 790,\n formsPerHost: FORMS_PER_HOST_CEILING,\n variablesPerHost: UNLIMITED,\n functionsPerHost: UNLIMITED,\n workflowsPerHost: UNLIMITED,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: UNLIMITED,\n contactsPerHost: 1_000_000,\n emailSendsPerMonth: ENTERPRISE_EMAIL_SENDS_PER_MONTH,\n actionRunsPerMonth: 2_000_000,\n apiRequestsPerMonth: 170_000,\n datasetsPerOrg: 100,\n maxDatasetsPerOrg: 10_000,\n recordsPerDataset: UNLIMITED,\n dataStorageMbPerOrg: 409_600,\n productsPerHost: UNLIMITED,\n inventoryLocations: 100,\n posRegisters: 40,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 0,\n features: {\n abTesting: true,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n customSendingDomain: true,\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: true,\n aiAssist: true,\n aiGenerative: true,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: true,\n apiAccess: true,\n siteExport: true,\n multilingual: true,\n // Event Calendar stays an add-on purchase, as on every other tier.\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: true,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n whiteLabel: true,\n // SSO (AGL-1101) is the Enterprise differentiator — the only plan that\n // carries it by default. Lower tiers still need a per-org override.\n ssoEnabled: true,\n },\n },\n}\n\n/**\n * Each plan row gains what the plugins declared for that plan (AGL-3080):\n * a plugin's own band or gate — the AI credits, the CRM's one-to-one email\n * pace, the marketplace's take rate — is written beside the reasoning that\n * sized it, in that plugin's `plan-entitlements` module, and compiled into\n * `first-party-plugins.generated.ts`. Filled here, once, as the module\n * loads, so every reader of `PLAN_ENTITLEMENTS` — the pricing tables, the\n * plan comparison, `resolveOrgEntitlements` — reads one table and cannot tell\n * which half of a row a figure came from.\n *\n * Core's keys keep their order and a plugin's follow them. A key both declare\n * keeps CORE's figure, so a declaration can never shadow what the platform\n * already charges; `plan-entitlements.spec.ts` refuses the collision outright.\n */\nfunction fillPluginPlanEntitlements(\n rows: Record<OrgPlan, ResolvedOrgEntitlements>,\n): void {\n for (const plan of Object.keys(rows) as OrgPlan[]) {\n const quotas = rows[plan] as unknown as Record<string, unknown>\n for (const [key, value] of Object.entries(pluginPlanQuotaRow(plan))) {\n if (!(key in quotas)) quotas[key] = value\n }\n const features = rows[plan].features as unknown as Record<string, boolean>\n for (const [key, value] of Object.entries(pluginPlanFeatureRow(plan))) {\n if (!(key in features)) features[key] = value\n }\n }\n}\n\nfillPluginPlanEntitlements(PLAN_ENTITLEMENTS)\n\n/**\n * The tiers a customer can buy themselves — every plan EXCEPT `enterprise`\n * (AGL-1118), which is priced per deal and provisioned by staff (AGL-1110).\n * Any surface that offers plans for sale (the console plan-cards grid, the\n * marketing pricing table, the plan-change dialog) MUST iterate this rather\n * than `Object.keys(PLAN_ENTITLEMENTS)`, so a custom-priced tier can never\n * show a self-serve Upgrade button or a $0 headline price.\n */\nexport const SELF_SERVE_PLANS: OrgPlan[] = [\n 'free',\n 'starter',\n 'pro',\n 'business',\n 'scale',\n 'advanced',\n 'agency',\n]\n\n/**\n * Whether the plan has no list price and is quoted per deal (AGL-1118).\n * `PLAN_PRICING` still carries a row for it — the record is exhaustive by\n * type — but every figure there is 0/null and means \"no list price\", NOT\n * \"free\". Revenue for such an org comes from `subscription.customMonthlyUsd`.\n */\nexport function isCustomPricedPlan(plan: OrgPlan | undefined | null): boolean {\n return plan === 'enterprise'\n}\n\n/** Human plan names — the ONE source every plan badge/label reads. */\nexport const PLAN_LABELS: Record<OrgPlan, string> = {\n free: 'Free',\n starter: 'Starter',\n pro: 'Pro',\n business: 'Business',\n scale: 'Scale',\n advanced: 'Advanced',\n agency: 'Agency',\n enterprise: 'Enterprise',\n}\n\n/**\n * Event Calendar add-on (AGL-145): first-party, Aglyn-supported, $9/mo\n * **per organization** — cost×1.3 floor honored (mostly Firestore reads).\n * Enabled via the `eventCalendar` entitlement override at purchase.\n *\n * NOT per host, despite what this comment said until AGL-1279. AGL-145\n * shipped it per host; AGL-526 then made add-ons self-serve and capped this\n * one at `return 1` in `app/api/billing/addons/route.ts`, and\n * `resolveOrgEntitlements` flips a single org-level `features.eventCalendar`\n * boolean from any quantity >= 1. One purchase covers every host in the org.\n * The marketing copy on /pricing is written against that behaviour.\n */\nexport const EVENT_CALENDAR_ADDON_MONTHLY_USD = 9\n\n/**\n * POS Pro register add-on (AGL-329): $89/mo per extra register/location\n * (Shopify POS Pro parity). Purchased add-ons land as a per-org\n * `posRegisters` entitlement override, which\n * `resolveOrgEntitlements` already applies over the plan default.\n */\nexport const POS_REGISTER_ADDON_MONTHLY_USD = 89\n\n/**\n * The Aglyn AI add-on's credit band (AGL-2896): the credits a month the\n * add-on ADDS to the plan's `assistCreditsPerMonth` when\n * `seatAddons.aiAddon` is set. One pool and one meter — the add-on widens\n * the band the assist meter already draws on rather than opening a second\n * one, so `resolveAssistCreditBudget`, the overage price and the hard-cap\n * switch all see one number.\n *\n * ## Sized against the add-on's own price\n *\n * A credit is `ASSIST_CREDIT_COST_USD` of provider spend, so each band is a\n * cost figure: 1,000 credits are $1.00. Every band here costs at most 50% of\n * `PlanPricing.aiAddonMonthlyUsd` at full consumption — Starter $4 of $9,\n * Pro $9 of $19, Business $19 of $39, Scale $34 of $69, Advanced $49 of\n * $99, Agency $149 of $299 — which is the same line-margin floor\n * (`ASSIST_CREDIT_MIN_MARGIN_PCT`) every retail assist rate clears.\n * `tier-margin-floor.spec.ts` holds the whole tier non-negative with the\n * add-on band at 100% and the add-on's revenue counted, and pins each band's\n * share of its price.\n *\n * ## Free and Enterprise\n *\n * Free is 0: the plan sells no add-on (`aiAddonMonthlyUsd` is null), so a\n * quantity written onto a Free org adds nothing. Enterprise is finite at\n * twice Agency's, the rule every Enterprise fallback follows since the\n * 2026-09-07 decision (AGL-2654) — never `UNLIMITED`, for the reason\n * `ENTERPRISE_ASSIST_CREDITS_PER_MONTH` gives: an unbounded band reads back\n * as zero off the wire. Enterprise carries `aiGenerative` in the agreement\n * and its price is null too, so this figure is what a contract that names\n * the add-on starts from before its own number is written.\n */\nexport const AI_ADDON_CREDITS_PER_MONTH: Record<OrgPlan, number> = {\n free: 0,\n starter: 4_000,\n pro: 9_000,\n business: 19_000,\n scale: 34_000,\n advanced: 49_000,\n agency: 149_000,\n enterprise: 298_000,\n}\n\n/**\n * The generative add-on's name as a person sees it (AGL-2896): the brand,\n * then \"AI\". Read from the platform brand by default and from the org's\n * resolved `productName` where the surface has one, for the reason every\n * rendered product name is: a self-host operator renames the product by\n * configuration, and a white-label org must not see ours.\n */\nexport const aiAddonName = (brand: string = PLATFORM_BRAND_NAME): string =>\n `${brand} AI`\n\n/**\n * Purchase ceilings for the two add-on kinds that have NO per-plan hard max\n * (AGL-1738). They live here, beside the bands and the resolver they bound,\n * because they ARE the bound — and until now they were two private literals\n * in `app/api/billing/addons/route.ts`, seventeen hundred lines and one\n * project away from the `+` they cap.\n *\n * That distance is why AGL-1738 read `resolveOrgEntitlements` as unbounded\n * and proposed a `maxPosRegistersPerHost` clamp. Do not add one. Seats and\n * datasets clamp at `checkSeatQuota` / `checkDatasetQuota` because their\n * plans genuinely refuse to sell past a band (`upgradeRequired` flips and\n * the answer is a plan change). Registers and extra sites are sold flat\n * instead: `addonMax()` will sell an org up to these quantities on ANY plan\n * carrying the feature, so a band-shaped clamp in the resolver would take\n * back registers the merchant is being invoiced $89/mo each for — the\n * expensive direction of a wrong clamp, and the reason the add stays open.\n *\n * The self-serve route is the only path that writes `seatAddons` from a\n * purchase, so these are the real ceilings: a plan's band plus at most\n * `POS_REGISTERS_ADDON_MAX` registers, or plus `EXTRA_HOSTS_ADDON_MAX` sites.\n * Any console surface that windows a head-count must clear the SUM, not the\n * band (`registers-card.component.tsx` still windows at 25; AGL-1738).\n */\nexport const EXTRA_HOSTS_ADDON_MAX = 100\n\n/** @see EXTRA_HOSTS_ADDON_MAX — the flat register ceiling, per org. */\nexport const POS_REGISTERS_ADDON_MAX = 50\n\nexport interface PlanPricing {\n /** Flat monthly base price in USD (month-to-month billing). */\n basePriceMonthlyUsd: number\n /**\n * Effective per-month price when billed annually (AGL-278): the\n * Squarespace/Shopify-parity headline number. Charged as ×12 up front.\n */\n basePriceAnnualMonthlyUsd: number\n /**\n * Monthly price per host beyond `hostLimit` (AGL-68); null when the plan\n * cannot buy extra hosts.\n *\n * ## It is not a discount ladder, because what it GRANTS is not constant\n *\n * `storagePerHostMb` and `formSubmissionsPerMonth` are per-HOST\n * entitlements, and `meteredIncludedAllowance` expands both by `hostLimit`\n * — so an extra host does not add a slot, it adds that tier's per-host\n * bands to the org's included allowance. A tier that includes more per host\n * therefore hands over more capacity for the same purchase.\n *\n * A ladder that stepped down with the tier inverted that: the tiers\n * granting the most charged the least, and the two largest ran at 24% and\n * 6% margin on a line sold as an add-on.\n *\n * ## It sits above every rung's step rate, or the rungs stop meaning anything\n *\n * The shape is a flat $20 on every plan, Starter included. A flat $8 above\n * Starter, with no cap on how many hosts a plan may add, priced the\n * marginal host below every step of the base ladder, which costs $8.57 to\n * $16 per included host on annual prices and $12 to $22 on monthly: ten\n * Business sites plus fifteen extras came to $219 against Advanced's $299,\n * and plus ninety extras to $819 against Agency's $1,049, so on host count\n * alone no plan was ever worth upgrading to. $20 clears the steepest step on\n * both intervals: extra hosts are how a plan takes a few more sites, and\n * the next plan is how it takes many more (2026-09-20).\n */\n extraHostMonthlyUsd: number | null\n /**\n * Monthly price per org-manager seat beyond `managersPerOrg`\n * (AGL-112); null when the plan cannot buy extra seats.\n */\n extraSeatMonthlyUsd: number | null\n /**\n * Monthly price per site-collaborator seat beyond `membersPerHost`\n * (AGL-112, renamed AGL-888): per-site console collaborators\n * (`hosts/{id}/members`, viewer/editor/admin) — never end-user member\n * accounts, which are unlimited on every plan. Null when the plan sells\n * no extra seats. Persisted/external keys keep the legacy \"member\"\n * spelling (`seatAddons.members`, `STRIPE_PRICE_*_EXTRA_MEMBER`).\n */\n extraCollaboratorMonthlyUsd: number | null\n /**\n * Monthly price per org dataset beyond `datasetsPerOrg` (AGL-132/240);\n * null when the plan cannot buy extra datasets.\n */\n extraDatasetMonthlyUsd: number | null\n /**\n * Retail overage per GB-month of dataset storage beyond\n * `dataStorageMbPerOrg` (AGL-240); null when the plan hard-blocks at the\n * included size instead of metering.\n *\n * ⛔ **NOT the metered storage pass-through.** `/pricing` publishes two\n * per-GB-month figures and they are different products: the pass-through\n * is `METERED_BILLED_RATES_USD.storagePerGbMonth` at $0.0338, media bytes\n * on GCS at cost + 30%; this is Firestore-backed DATASET bytes, an order\n * of magnitude pricier to store, sold as a retail line. Confusing the two\n * is the easiest way to corrupt a correct price, in either direction.\n *\n * Priced against `ORG_COGS_UNIT_RATES_USD.dataStoragePerGbMonth` ($0.18)\n * at the 50% retail floor. It sat at $0.25 — a 28% line margin, close\n * enough to the pass-through's 23% that the two looked like the same kind\n * of number while being sold as opposites.\n */\n extraDataGbMonthlyUsd: number | null\n /**\n * Metered overage per 1,000 customer REST API requests beyond\n * `apiRequestsPerMonth` (AGL-634). API access starts at Business and every\n * tier above it carries it, so Free, Starter and Pro are null (no API to\n * meter).\n */\n extraApiRequestsUsdPer1k: number | null\n /**\n * Retail overage per 1,000 Aglyn Assist credits beyond\n * `assistCreditsPerMonth`.\n *\n * ## The rate is a multiplier on our own cost, and reads as one\n *\n * A credit is defined as a fixed quantity of provider spend\n * (`ASSIST_CREDIT_COST_USD`), so 1,000 credits cost us exactly $1.00 and\n * this figure IS the multiplier: $3.00 per 1,000 is cost x3, $2.00 is\n * cost x2. That is the property worth having, because the alternative —\n * a rate per message — prices a question and a generated screen the same\n * when they differ by two orders of magnitude.\n *\n * ## The floor, and why the ladder stops descending\n *\n * Every retail line carries at least a 50% margin\n * (`ASSIST_CREDIT_MIN_MARGIN_PCT`), which at $1.00 of cost per 1,000 is a\n * floor of $2.00 per 1,000 — cost x2. The ladder steps down with the tier\n * the way contacts and API requests do, and since AGL-3203 it begins on\n * Starter: $3.00 on Starter and Pro together, then $2.75 -> $2.50 ->\n * $2.25, stopping at the floor on Agency rather than running past it.\n * Starter joins at Pro's figure rather than stepping above it, because the\n * ladder descends with the tier and Starter is the rung below Pro.\n *\n * ## Where it is null, and why that is not \"free overage\"\n *\n * Null on Free because its band is a WALL by decision (AGL-2925): the\n * 300-credit taste refuses at 100% with nothing sold past it, and a rate\n * here would be a charge on a plan that must never produce one. Null on\n * Enterprise, where every rate is the \"not for sale\" sentinel and the\n * terms are contractual.\n *\n * Those two are the only nulls. Every self-serve PAID plan now includes a\n * positive assist band and therefore carries a rate, Starter included —\n * a finite band with no rate beside it is usage past a bound that is\n * silently free, which `plan-entitlements.spec.ts` forbids.\n *\n * ## What the rate decides at the gate (AGL-2653)\n *\n * A plan WITH a rate sells past its band by default: `reserveAssistMessage`\n * keeps reserving and `report-usage` bills the excess here, unless the org\n * has switched on `assistOverage.hardCap` to be refused at the band\n * instead. A plan with NO rate has nothing to sell the excess at, so its\n * band stays the wall it always was, switch or no switch — see\n * `assistBandRefuses`. Unlike email, a null here strands nothing: there is\n * no unrefusable assist traffic to absorb.\n */\n extraAssistCreditsUsdPer1k: number | null\n /**\n * The Aglyn AI add-on, flat per organization per month (AGL-2896); null\n * when the plan does not sell it.\n *\n * ## What it buys\n *\n * `features.aiGenerative` — generative building and automation, the rung\n * no self-serve tier includes — plus `AI_ADDON_CREDITS_PER_MONTH[plan]`\n * added to the plan's assist band. It also switches `aiAssist` on, so on\n * Starter it brings the two rungs the tier does not carry — the copy\n * assist and the generative one — on top of the 750 credits the plan\n * already includes (AGL-3203). It ADDS to that band; it does not replace\n * it, on Starter or on any tier.\n *\n * ## Where the ladder sits\n *\n * Priced as an uplift on the plan rather than on its own cost: the field\n * charges roughly 30% of the base price for an AI tier (Zylo's 2025\n * survey puts the AI uplift at 20–37% across SaaS; aissist.io's benchmark\n * and the Framer, Webflow and HubSpot credit add-ons sit in the same\n * band), and the share DESCENDS with the tier — 36% of Starter's $25, 34%\n * of Pro's, 28% of Business's, 28% of Scale's, 25% of Advanced's, 23% of\n * Agency's — because the larger the plan the more of its price is already\n * carrying the bands the assistant builds against.\n *\n * Annual is x12 of this with no discount, as every other add-on is\n * (`orgListPriceMonthlyUsd`). The band it funds costs at most 50% of it in\n * provider spend, the `ASSIST_CREDIT_MIN_MARGIN_PCT` floor every retail\n * assist figure holds.\n *\n * Null on Free, which sells no add-on of any kind, and on Enterprise,\n * where every figure is the \"not for sale\" sentinel and generative\n * building is in the agreement.\n */\n aiAddonMonthlyUsd: number | null\n /**\n * Metered overage per 1,000 CRM records beyond `contactsPerHost` (AGL-890,\n * widened to contacts + companies + deals in AGL-2611): audience bands,\n * Ghost-style. Paid plans meter (a growing audience is never dropped);\n * free is null and hard-bands at the included count. The key keeps its\n * name because it is a persisted Stripe-side vocabulary and a locked\n * price; the band it prices was widened, the rate was not.\n */\n extraContactsUsdPer1k: number | null\n /**\n * Retail overage per 1,000 emails beyond `emailSendsPerMonth`.\n *\n * ## What it prices, which is not what the cap refuses\n *\n * The cap is enforced against CAMPAIGN sends alone and refuses them at the\n * band — a campaign is discretionary, so the plan is allowed to say no.\n * Transactional mail is never refused at any tier, so an org finishes the\n * month above its band whenever receipts, invites, booking reminders and\n * workflow notifications carry it there. That excess is real provider spend\n * that no gate could have stopped, and this is the rate it bills at.\n *\n * ## Retail, NOT the infrastructure pass-through\n *\n * `meteredInfraPassThrough` covers exactly three meters — storage, page\n * views, form submissions — and the published term for those is \"at cost +\n * 30%\", which makes the figure in `METERED_BILLED_RATES_USD` the claim\n * itself. Email is not in that set and this rate is not derived from\n * `METERED_MARKUP`: it is a price, set beside `extraContactsUsdPer1k` and\n * `extraApiRequestsUsdPer1k`, and it descends with the tier the way both of\n * those do. Describing it as \"at cost\" anywhere would make the pass-through\n * sentence false about three meters that really are.\n *\n * Our own per-email cost is `ORG_COGS_UNIT_RATES_USD.perEmailSend`, and it\n * is a cost-model input rather than anything a customer is quoted.\n *\n * ## The floor, and why the ladder stops descending\n *\n * Every retail line must carry at least a 50% margin, and at a cost of\n * $0.90 per 1,000 that floor is $1.80. The ladder still steps down with\n * volume the way contacts and API requests do — a larger plan pays less per\n * message — it simply stops at the floor instead of running past it, which\n * is what the contacts ladder did when it reached $0.25 against a $0.20\n * cost.\n *\n * Null wherever the plan has no email band to be over, and null on\n * enterprise, where every rate is the \"not for sale\" sentinel and the terms\n * are contractual. Free and Starter are the two banded at 0: campaign email\n * begins at Pro, and `emailSendsOverage` yields 0 against a non-positive\n * band, so a rate on either would advertise a fee that can never be\n * computed — the same defect as a rate beside an UNLIMITED band.\n *\n * Every tier whose band is POSITIVE carries one, Pro upward. A finite band\n * with no rate is silently free past the band, and since transactional mail\n * cannot be refused at any tier that would be unbounded absorbed spend\n * rather than \"no overage\".\n *\n * The Starter null therefore leaves that tier's transactional mail — the\n * only mail it can send — unbilled at any volume. Bounded in practice by\n * `hostLimit: 1` and a 1,000-contact CRM, and the alternative prices a\n * password reset, but it is the one place this axis absorbs cost by design.\n */\n extraEmailSendsUsdPer1k: number | null\n /**\n * Whether the plan carries the metered infrastructure pass-through\n * (AGL-41, turned on in AGL-1280): media storage, bandwidth (page views)\n * and form submissions BEYOND the plan's included bands, billed at\n * cost × `METERED_MARKUP`. The rates are platform-wide, not per-plan —\n * this flag only says whether a plan is subject to them at all, which is\n * why it is a boolean rather than another `extra*` rate.\n *\n * Free is `false` because there is no subscription to hang a metered item\n * on; its bands stay hard caps, which is also what stops \"metered means\n * you're never cut off\" from becoming unlimited free capacity.\n *\n * Enterprise is `false` because every band it has is `UNLIMITED` and its\n * price is negotiated (`isCustomPricedPlan`) — there is no published\n * pass-through to apply, and an overage can never arise anyway.\n */\n meteredInfraPassThrough: boolean\n}\n\n/**\n * Flat subscription pricing per tier (AGL-68). Kept beside the entitlements\n * so price changes ride the same review path; Stripe price ids map to plans\n * via `STRIPE_PRICE_*` env vars on the billing API routes.\n */\nexport const PLAN_PRICING: Record<OrgPlan, PlanPricing> = {\n free: {\n basePriceMonthlyUsd: 0,\n basePriceAnnualMonthlyUsd: 0,\n extraHostMonthlyUsd: null,\n extraSeatMonthlyUsd: null,\n extraCollaboratorMonthlyUsd: null,\n extraDatasetMonthlyUsd: null,\n extraDataGbMonthlyUsd: null,\n extraApiRequestsUsdPer1k: null,\n extraAssistCreditsUsdPer1k: null,\n aiAddonMonthlyUsd: null,\n extraContactsUsdPer1k: null,\n extraEmailSendsUsdPer1k: null,\n meteredInfraPassThrough: false,\n },\n starter: {\n basePriceMonthlyUsd: 25,\n basePriceAnnualMonthlyUsd: 16,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 5,\n extraCollaboratorMonthlyUsd: 3,\n extraDatasetMonthlyUsd: 2,\n extraDataGbMonthlyUsd: 0.36,\n extraApiRequestsUsdPer1k: null,\n // The top of the retail ladder, which Starter JOINS rather than steps\n // above (AGL-3203): the ladder descends with the tier, and Starter is the\n // rung below Pro, not a new one over it. Moves in lockstep with the 750\n // credit band on the Starter entitlements — the band without this rate is\n // the silent free overage, and this rate without the band is a fee\n // advertised on a quantity the plan never sold.\n extraAssistCreditsUsdPer1k: 3,\n aiAddonMonthlyUsd: 9,\n extraContactsUsdPer1k: 1,\n // No email band to be \"over\" — see `emailSendsPerMonth` on the Starter\n // entitlements. `emailSendsOverage` returns 0 for any non-positive band,\n // so a rate here would price volume that can never be computed, which is\n // the same defect as a rate beside an UNLIMITED band.\n extraEmailSendsUsdPer1k: null,\n meteredInfraPassThrough: true,\n },\n pro: {\n basePriceMonthlyUsd: 56,\n basePriceAnnualMonthlyUsd: 39,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 4,\n extraCollaboratorMonthlyUsd: 2,\n extraDatasetMonthlyUsd: 2,\n extraDataGbMonthlyUsd: 0.36,\n extraApiRequestsUsdPer1k: null,\n extraAssistCreditsUsdPer1k: 3,\n aiAddonMonthlyUsd: 19,\n extraContactsUsdPer1k: 0.75,\n extraEmailSendsUsdPer1k: 2.25,\n meteredInfraPassThrough: true,\n },\n business: {\n basePriceMonthlyUsd: 139,\n basePriceAnnualMonthlyUsd: 99,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 3,\n extraCollaboratorMonthlyUsd: 1,\n extraDatasetMonthlyUsd: 1,\n extraDataGbMonthlyUsd: 0.36,\n extraApiRequestsUsdPer1k: 0.5,\n extraAssistCreditsUsdPer1k: 2.75,\n aiAddonMonthlyUsd: 39,\n extraContactsUsdPer1k: 0.5,\n extraEmailSendsUsdPer1k: 2,\n meteredInfraPassThrough: true,\n },\n scale: {\n basePriceMonthlyUsd: 249,\n basePriceAnnualMonthlyUsd: 179,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 2,\n extraCollaboratorMonthlyUsd: 1,\n extraDatasetMonthlyUsd: 1,\n extraDataGbMonthlyUsd: 0.36,\n extraApiRequestsUsdPer1k: 0.35,\n extraAssistCreditsUsdPer1k: 2.5,\n aiAddonMonthlyUsd: 69,\n extraContactsUsdPer1k: 0.4,\n extraEmailSendsUsdPer1k: 1.9,\n meteredInfraPassThrough: true,\n },\n advanced: {\n basePriceMonthlyUsd: 399,\n basePriceAnnualMonthlyUsd: 299,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 2,\n extraCollaboratorMonthlyUsd: 1,\n extraDatasetMonthlyUsd: 1,\n extraDataGbMonthlyUsd: 0.36,\n // FLOORED at the 50% retail margin, with Agency's: a `/v1` request costs\n // $0.117 per 1,000 at the dearest region, so $0.25 is the round figure at\n // or above $0.234 (AGL-3444).\n extraApiRequestsUsdPer1k: 0.25,\n extraAssistCreditsUsdPer1k: 2.25,\n aiAddonMonthlyUsd: 99,\n // FLOORED at the 50% retail margin, not stepped down again. The ladder\n // above it descends $1.00 → $0.75 → $0.50 → $0.40, and one more step\n // would have reached $0.25 against a `perContactMonth` cost of $0.20 per\n // 1,000 — a 20% line margin on a retail price, thinner than the 23% the\n // infrastructure pass-through earns while being sold as cost recovery.\n extraContactsUsdPer1k: 0.4,\n extraEmailSendsUsdPer1k: 1.85,\n meteredInfraPassThrough: true,\n },\n // Agency is the one tier whose PRICE moved. It included 100 hosts, 20 TB of\n // bandwidth and an unbounded form-submission band against $799, which made\n // it both the worst margin on the ladder and the most underpriced against\n // the field: Duda charges ~$1,396-1,493/mo for 100 sites, BigCommerce\n // Enterprise starts at $1,499 and Shopify Plus at $2,300. $1,299 still\n // undercuts all three for the buyer this architecture is built for.\n agency: {\n basePriceMonthlyUsd: 1299,\n // $1,049/mo is $12,588 a year, which is what the live Stripe yearly price\n // charges. Stripe prices are immutable, so the constant every surface\n // quotes has to match the one a customer is actually billed by — a\n // ratio-derived figure that no price object backs would quote one number\n // and charge another. It lands within a dollar of holding the previous\n // 18.8% annual discount across the rise, which is what stops the annual\n // interval becoming the cheap way past a repricing.\n basePriceAnnualMonthlyUsd: 1049,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 2,\n extraCollaboratorMonthlyUsd: 1,\n extraDatasetMonthlyUsd: 1,\n extraDataGbMonthlyUsd: 0.36,\n // FLOORED at the 50% retail margin — see Advanced's.\n extraApiRequestsUsdPer1k: 0.25,\n extraAssistCreditsUsdPer1k: 2,\n aiAddonMonthlyUsd: 299,\n // $0.40, because the band it meters is now FINITE.\n //\n // This row has been wrong in both directions. It shipped $0.20 against an\n // UNLIMITED band, advertising a fee that could not be charged —\n // `checkContactQuota` computes `Math.max(0, used - Infinity)`, which is 0\n // at every usage level (AGL-2439) — and was corrected to `null` to stop\n // publishing a claim the code could not honour. Bounding the band inverts\n // the problem: a finite band with no rate is usage past a bound that is\n // silently free, and the bound achieves nothing.\n //\n // $0.40 rather than a further step down the ladder because that is the\n // 50% retail floor against a `perContactMonth` cost of $0.20 per 1,000 —\n // the same figure Scale and Advanced carry, and the same floor that moved\n // Advanced off $0.25.\n //\n // It METERS rather than walls: a rate is what makes `checkContactQuota`\n // return `allowed: true` past the band, so a growing audience is billed\n // and never dropped. That is what makes bounding this band safe here and\n // NOT safe on enterprise, whose every rate is the \"not for sale\"\n // sentinel.\n extraContactsUsdPer1k: 0.4,\n extraEmailSendsUsdPer1k: 1.8,\n meteredInfraPassThrough: true,\n },\n // Enterprise (AGL-1118) has NO list price — every figure here is the\n // \"not for sale\" sentinel, not a $0 offer (`isCustomPricedPlan`). The org's\n // real price is the negotiated `subscription.customMonthlyUsd` the\n // enterprise-billing flow writes (AGL-1110), which `orgListPriceMonthlyUsd`\n // already prefers over the plan default; a comped enterprise org genuinely\n // bills $0, and that is what this row then reports. Nulls also mean the\n // plan sells no self-serve add-on seats — enterprise capacity is unlimited,\n // so there is nothing to buy.\n enterprise: {\n basePriceMonthlyUsd: 0,\n basePriceAnnualMonthlyUsd: 0,\n extraHostMonthlyUsd: null,\n extraSeatMonthlyUsd: null,\n extraCollaboratorMonthlyUsd: null,\n extraDatasetMonthlyUsd: null,\n extraDataGbMonthlyUsd: null,\n extraApiRequestsUsdPer1k: null,\n extraAssistCreditsUsdPer1k: null,\n aiAddonMonthlyUsd: null,\n extraContactsUsdPer1k: null,\n extraEmailSendsUsdPer1k: null,\n meteredInfraPassThrough: false,\n },\n}\n\n/**\n * The price list a plan's row becomes when a staff COMP is what grants it\n * (AGL-3034): the list prices kept, and nothing for sale.\n *\n * Every other figure on a row prices something sold on top of a subscription —\n * an add-on item, or usage past a band billed onto that subscription's next\n * invoice. A comp has no subscription to add an item to or to invoice, so\n * every one of them is `null` and the pass-through is off. That is `free`'s\n * reasoning (`meteredInfraPassThrough`: \"there is no subscription to hang a\n * metered item on\"), and it turns each band into a wall rather than a line\n * past which usage is sold to nobody.\n *\n * It is also what keeps a comp away from Stripe without a check anyone could\n * forget: the AI overage charge claims only what `assistMonthOverage` prices,\n * the usage sweep bills only what these rates price, and a `null` rate prices\n * zero. A comp that needs more of something gets a quota override beside it,\n * or is uncapped outright (AGL-3049) — and an uncapped comp reads THIS row\n * too, so lifting every band sells nothing past any of them.\n *\n * Derived from the row rather than written out, so a rate added to\n * `PlanPricing` later is withheld from a comp on the day it ships.\n */\nfunction compPricing(pricing: PlanPricing): PlanPricing {\n const withheld: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(pricing)) {\n if (key === 'basePriceMonthlyUsd' || key === 'basePriceAnnualMonthlyUsd') {\n withheld[key] = value\n } else if (typeof value === 'boolean') {\n withheld[key] = false\n } else {\n withheld[key] = null\n }\n }\n return withheld as unknown as PlanPricing\n}\n\nconst PLAN_COMP_PRICING = Object.fromEntries(\n Object.entries(PLAN_PRICING).map(([plan, pricing]) => [plan, compPricing(pricing)]),\n) as Record<OrgPlan, PlanPricing>\n\n/**\n * The prices the org's usage and add-ons are sold at (AGL-3034): its\n * effective plan's row, or that row with nothing for sale when a staff comp\n * is what grants the plan — see `compPricing`.\n *\n * EVERY reader that decides whether something past a band is sold, or what a\n * purchasable seat costs, reads this rather than indexing `PLAN_PRICING` by\n * the effective plan. Indexing it directly is how a comp would be quoted a\n * rate nobody can be invoiced at.\n */\nexport function resolvePlanPricing(\n org: Partial<AglynOrgBilling> | null | undefined,\n): PlanPricing {\n const plan = resolveEffectivePlan(org)\n return resolvePlanComp(org) ? PLAN_COMP_PRICING[plan] : PLAN_PRICING[plan]\n}\n\n/**\n * Subscription states that stop paying for the plan (AGL-247). `past_due`\n * keeps working as a dunning grace period; these do not.\n *\n * `incomplete_expired` is the state a signup reaches when its first payment\n * never completes — an abandoned SCA challenge, a card that never\n * authenticated — and Stripe stamps it about a day later. It has charged\n * nothing and never will, so it belongs here beside `incomplete`, which is the\n * same subscription an hour earlier. `LIVE_SUBSCRIPTION_STATUSES` in\n * `org-billing-doc.ts` already excludes it and `DEAD_STATUSES` in\n * `utils/subscription-period-notice.ts` already names it; this set is the one\n * that decides both what a workspace GETS and whether we count it as revenue,\n * so it is the one place the omission cost money in both directions at once.\n *\n * THE DENYLIST FORM IS THE HAZARD, and it is why the omission was silent: an\n * allowlist of live statuses cannot rot when Stripe adds a status, whereas a\n * denylist of dead ones silently grants the plan — and books the MRR — for\n * every status nobody has enumerated yet. The three readers below keep the\n * denylist anyway, because the direction they fail in is not the same as the\n * live-side question's: an unrecognized status here must not revoke a paying\n * workspace's features, while `isLiveSubscriptionStatus` refusing to call an\n * unrecognized status live only ever declines to sell a second subscription.\n * A new Stripe status therefore has to be added HERE by hand, which is a\n * standing obligation rather than a property of the code.\n */\nconst DEAD_SUBSCRIPTION_STATUSES = new Set([\n 'canceled',\n 'unpaid',\n 'incomplete',\n 'incomplete_expired',\n])\n\n/**\n * The subscription status entitlement resolution runs on (AGL-1028).\n *\n * `subscription` moved to `orgs/{orgId}/billing/stripe`, behind\n * `canManageOrg()` — but the status word is an ENTITLEMENT input, not a\n * commercial secret: `resolveEffectivePlan` downgrades a paid plan to free on a\n * dead subscription, and `resolvePurchasedAddons` stops counting add-ons on\n * one. Both resolve in the tenant runtime and in console components belonging\n * to members who cannot read a manager-gated doc, so the webhook mirrors the\n * bare status string back onto the org doc as `billingStatus`.\n *\n * Reads the mirror first, then the inline `subscription` — the fallback is what\n * keeps entitlements correct for orgs the backfill has not reached yet, and for\n * any caller still passing a whole billing doc rather than an org doc.\n */\nfunction subscriptionStatusOf(\n org: Partial<AglynOrgBilling> | null | undefined,\n): string | undefined {\n const mirrored = (org as { billingStatus?: unknown } | null | undefined)\n ?.billingStatus\n if (typeof mirrored === 'string' && mirrored) return mirrored\n return org?.subscription?.status\n}\n\n/**\n * What the org's subscription contributes to deciding its plan (AGL-3034).\n *\n * - `live` — a status is mirrored and it is not one this module reads as\n * dead. The subscription decides the plan. `past_due` is live (dunning\n * grace), and so is a status this build does not recognize, for the reason\n * `DEAD_SUBSCRIPTION_STATUSES` gives: an unrecognized status must not take\n * away what a paying workspace bought, and handing the plan to a comp would\n * do exactly that.\n * - `dead` — the status is in `DEAD_SUBSCRIPTION_STATUSES`. A stored paid\n * plan grants nothing (AGL-247).\n * - `none` — no status at all: the workspace has never been subscribed, or\n * predates the mirror.\n *\n * `dead` and `none` are the only states a staff comp applies in.\n */\nexport type OrgSubscriptionState = 'live' | 'dead' | 'none'\n\nexport function orgSubscriptionState(\n org: Partial<AglynOrgBilling> | null | undefined,\n): OrgSubscriptionState {\n const status = subscriptionStatusOf(org)\n if (!status) return 'none'\n return DEAD_SUBSCRIPTION_STATUSES.has(status) ? 'dead' : 'live'\n}\n\n/** A staff plan comp, as every reader receives it (AGL-3034). */\nexport interface ResolvedOrgPlanComp {\n /** The plan granted — always a paid plan this build knows. */\n plan: OrgPlan\n /**\n * Whether the comp lifts every cap (AGL-3049) — see `isUncappedPlanComp`.\n * `false` for anything but a stored `true`.\n */\n uncapped: boolean\n /** `null` when the stored code is not one of the override set's. */\n reason: OrgOverrideReasonCode | null\n note: string | null\n grantedBy: string | null\n /** ISO instant, or `null` when the stored stamp does not read back. */\n grantedAt: string | null\n}\n\n/**\n * An instant off a stored value in whichever form it reached this reader: a\n * Firestore `Timestamp` from either SDK, the `{ seconds }`/`{ _seconds }`\n * shape a timestamp takes after crossing JSON, a `Date`, or an ISO string.\n */\nfunction instantOf(value: unknown): string | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString()\n }\n if (typeof value === 'string') {\n const millis = Date.parse(value)\n return Number.isFinite(millis) ? new Date(millis).toISOString() : null\n }\n if (!value || typeof value !== 'object') return null\n const toDate = (value as { toDate?: unknown }).toDate\n if (typeof toDate === 'function') {\n const date = toDate.call(value)\n return date instanceof Date && !Number.isNaN(date.getTime())\n ? date.toISOString()\n : null\n }\n const record = value as { seconds?: unknown; _seconds?: unknown }\n const seconds = Number(record.seconds ?? record._seconds)\n if (!Number.isFinite(seconds)) return null\n // Out of `Date`'s range is Invalid Date, whose `toISOString` throws.\n const date = new Date(seconds * 1000)\n return Number.isNaN(date.getTime()) ? null : date.toISOString()\n}\n\nfunction trimmedOrNull(value: unknown): string | null {\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\n/**\n * The org's STORED comp (`entitlements.planComp`), whether or not it is in\n * force, or `null` when there is none (AGL-3034).\n *\n * Strict about the one field that grants anything: `plan` must be a paid plan\n * this build knows. A comp of `free` grants nothing and an unknown plan could\n * grant anything, so both read as no comp at all — the org then resolves\n * exactly as it would without one, which is the direction a malformed grant\n * must fail in. The descriptive fields are narrowed and never block the plan:\n * a comp whose note or stamp did not read back is still the comp staff wrote.\n *\n * `uncapped` is strict in the same direction (AGL-3049): it lifts every cap,\n * so only a literal `true` does. A `\"true\"`, a `1` or a map written by hand\n * reads as a capped comp — the plan still stands, and its bands stay walls.\n */\nexport function readOrgPlanComp(\n org: Partial<AglynOrgBilling> | null | undefined,\n): ResolvedOrgPlanComp | null {\n const entitlements = org?.entitlements as { planComp?: unknown } | null | undefined\n const raw =\n entitlements && typeof entitlements === 'object' ? entitlements.planComp : undefined\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null\n const record = raw as Record<string, unknown>\n const plan = record['plan']\n if (\n typeof plan !== 'string' ||\n plan === 'free' ||\n !Object.prototype.hasOwnProperty.call(PLAN_ENTITLEMENTS, plan)\n ) {\n return null\n }\n const reason = record['reason']\n return {\n plan: plan as OrgPlan,\n uncapped: record['uncapped'] === true,\n reason: isOrgOverrideReasonCode(reason) ? reason : null,\n note: trimmedOrNull(record['note']),\n grantedBy: trimmedOrNull(record['grantedBy']),\n grantedAt: instantOf(record['grantedAt']),\n }\n}\n\n/**\n * The comp IN FORCE: the stored comp while no subscription decides the plan,\n * and `null` otherwise (AGL-3034).\n *\n * A live subscription always wins. The comp is not removed by one starting —\n * nothing but the staff override writes it — so it waits, dormant, and\n * applies again if that subscription ends. Staff views say so.\n */\nexport function resolvePlanComp(\n org: Partial<AglynOrgBilling> | null | undefined,\n): ResolvedOrgPlanComp | null {\n if (orgSubscriptionState(org) === 'live') return null\n return readOrgPlanComp(org)\n}\n\n/**\n * Whether an UNCAPPED comp is in force (AGL-3049): every band and quota the\n * org resolves reads as `UNLIMITED`, whatever its plan row and its per-org\n * overrides say — see `withEveryCapLifted`.\n *\n * In force only, like the comp itself. A live subscription decides the plan\n * and its bands, so a dormant uncapped comp lifts nothing, and a canceled one\n * lifts every cap again the moment the subscription ends.\n *\n * What it does NOT lift is anything that is not a plan band: the fee\n * percentages (`PRICE_ENTITLEMENT_KEYS`) are prices, and the platform's own\n * guards live outside the entitlements map and are not the plan's to give\n * away — the form-submission and bandwidth abuse ceilings (which read an\n * unlimited band as `FORM_ABUSE_CEILING_UNLIMITED` and\n * `BANDWIDTH_ABUSE_CEILING_UNLIMITED`), the flat per-site caps on webhooks,\n * actions and collection entries, the email send pacing, the assist runaway\n * message cap, an operator's explicit spend ceiling, and staff pauses and\n * lockdowns. It never sells anything either: the pricing stays the comp's,\n * with every rate withheld, so an unbounded band is still billed nothing.\n *\n * The lifted value is `UNLIMITED` in process and nowhere else. Every route\n * that sends a band off-process sends a finite number with an explicit flag,\n * or `null` with a documented meaning, because `JSON.stringify(Infinity)` is\n * `null` — and the comp itself is stored and sent as a boolean.\n */\nexport function isUncappedPlanComp(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n return resolvePlanComp(org)?.uncapped === true\n}\n\n/**\n * What decided the org's effective plan (AGL-3034):\n *\n * - `subscription` — a live subscription; the stored plan is its mirror.\n * - `comp` — a staff comp, on a dead or absent subscription.\n * - `lapsed` — a dead subscription and no comp: Free.\n * - `stored-plan` — no subscription and no comp, and a paid plan stored\n * directly, the way staff granted plans before comps.\n * - `no-plan` — no subscription, no comp, no paid plan: Free.\n */\nexport type OrgPlanDecidedBy =\n | 'subscription'\n | 'comp'\n | 'lapsed'\n | 'stored-plan'\n | 'no-plan'\n\n/** Everything a staff surface needs to say how an org's plan resolves. */\nexport interface OrgPlanDescription {\n effectivePlan: OrgPlan\n /** `org.plan` when it names a plan this build knows, else `null`. */\n storedPlan: OrgPlan | null\n subscriptionStatus: string | null\n subscription: OrgSubscriptionState\n /** The STORED comp — in force or dormant; see `compInForce`. */\n comp: ResolvedOrgPlanComp | null\n compInForce: boolean\n /**\n * An uncapped comp is IN FORCE, so every band reads as unlimited\n * (AGL-3049). A dormant uncapped comp is `false` here and `comp.uncapped`.\n */\n uncapped: boolean\n decidedBy: OrgPlanDecidedBy\n}\n\n/**\n * How a staff chip names a stored comp (AGL-3049): the plan, then whether it\n * is uncapped and whether a live subscription holds it dormant —\n * `Enterprise (uncapped)`, `Pro (dormant)`, `Agency (uncapped, dormant)`.\n *\n * One reading of the terms, so the orgs list, the org summary and the\n * override dialog cannot describe the same grant differently. `name` is the\n * plan as the surface already writes plans: the label by default, the stored\n * key on the surfaces whose other chips show keys.\n */\nexport function planCompLabel(\n comp: Pick<ResolvedOrgPlanComp, 'plan' | 'uncapped'>,\n inForce: boolean,\n name: string = PLAN_LABELS[comp.plan],\n): string {\n const terms = [comp.uncapped ? 'uncapped' : null, inForce ? null : 'dormant']\n .filter(Boolean)\n .join(', ')\n return terms ? `${name} (${terms})` : name\n}\n\n/**\n * A stored comp as a sentence names it (AGL-3049): `uncapped Enterprise\n * comp`, `Pro comp`. Shared by the plan description and the override route's\n * account of what it changed.\n */\nexport function planCompPhrase(\n comp: Pick<ResolvedOrgPlanComp, 'plan' | 'uncapped'>,\n): string {\n return `${comp.uncapped ? 'uncapped ' : ''}${PLAN_LABELS[comp.plan]} comp`\n}\n\n/** \"A Pro comp\", \"An uncapped Enterprise comp\": the article a phrase takes. */\nfunction withArticle(phrase: string): string {\n return `${/^[aeiou]/i.test(phrase) ? 'An' : 'A'} ${phrase}`\n}\n\n/**\n * The org's plan, described rather than only resolved (AGL-3034) — the ONE\n * reading the staff override route answers with and the staff override\n * dialog renders, so the response and the dialog cannot tell staff two\n * different stories about the same document.\n */\nexport function describeOrgPlan(\n org: Partial<AglynOrgBilling> | null | undefined,\n): OrgPlanDescription {\n const subscription = orgSubscriptionState(org)\n const status = subscriptionStatusOf(org)\n const stored = org?.plan\n const storedPlan =\n typeof stored === 'string' &&\n Object.prototype.hasOwnProperty.call(PLAN_ENTITLEMENTS, stored)\n ? (stored as OrgPlan)\n : null\n const comp = readOrgPlanComp(org)\n const compInForce = comp !== null && subscription !== 'live'\n const decidedBy: OrgPlanDecidedBy =\n subscription === 'live'\n ? 'subscription'\n : compInForce\n ? 'comp'\n : subscription === 'dead'\n ? 'lapsed'\n : storedPlan && storedPlan !== 'free'\n ? 'stored-plan'\n : 'no-plan'\n return {\n effectivePlan: resolveEffectivePlan(org),\n storedPlan,\n subscriptionStatus: typeof status === 'string' && status ? status : null,\n subscription,\n comp,\n compInForce,\n uncapped: compInForce && comp?.uncapped === true,\n decidedBy,\n }\n}\n\n/**\n * One sentence for staff saying what decides the plan and what a plan set in\n * the override dialog would do (AGL-3034). Shared by the route's response and\n * the dialog, for the reason `describeOrgPlan` is.\n *\n * A comp in force also says whether its bands are walls or lifted\n * (AGL-3049), because that is the question a comped workspace refused at a\n * band brings to staff.\n */\nexport function orgPlanDescriptionSentence(\n description: OrgPlanDescription,\n): string {\n const label = (plan: OrgPlan | null) => (plan ? PLAN_LABELS[plan] : 'no plan')\n const status = description.subscriptionStatus ?? 'none'\n const dormant =\n description.comp && !description.compInForce\n ? ` ${withArticle(planCompPhrase(description.comp))} is stored and ` +\n 'dormant: it takes effect if the subscription ends.'\n : ''\n switch (description.decidedBy) {\n case 'subscription':\n return (\n `The live subscription (${status}) decides the plan: ` +\n `${label(description.effectivePlan)}. Stripe's next subscription ` +\n `event rewrites a plan set here.${dormant}`\n )\n case 'comp':\n return (\n `${label(description.effectivePlan)} is in force as ` +\n `${description.uncapped ? 'an uncapped' : 'a'} staff comp, ` +\n (description.subscription === 'dead'\n ? `because the subscription is ${status}. `\n : 'because no subscription pays for this workspace. ') +\n (description.uncapped\n ? 'It bills nothing, every band and quota reads as unlimited, '\n : 'It bills nothing, every band is a hard limit that a quota ' +\n 'override can raise, ') +\n 'and it lasts until it is removed or a live subscription starts.'\n )\n case 'lapsed':\n return (\n `The subscription is ${status}, so this workspace resolves as ` +\n `${label('free')}` +\n (description.storedPlan && description.storedPlan !== 'free'\n ? ` — the stored ${label(description.storedPlan)} grants nothing`\n : '') +\n '. A plan set here is written as a staff comp.'\n )\n case 'stored-plan':\n return (\n `No subscription: the plan stored directly, ` +\n `${label(description.storedPlan)}, decides it. A plan set here is ` +\n 'written as a staff comp.'\n )\n default:\n return (\n `No subscription and no paid plan, so this workspace resolves as ` +\n `${label('free')}. A plan set here is written as a staff comp.`\n )\n }\n}\n\n/**\n * Whether the org is actually being charged for its plan right now.\n *\n * This is deliberately stricter than `resolveEffectivePlan`: entitlement\n * asks \"what does this org get\", revenue asks \"who is paying us\". A staff\n * plan override (`/admin/orgs`) never writes `subscription` — before AGL-3034\n * it wrote `plan`, and now it writes a comp — so a comped or dark-launched\n * org resolves to a paid plan while billing nothing, and counting it inflates\n * MRR (AGL-925). No subscription mirror means no Stripe subscription means no\n * revenue.\n *\n * A comp never reaches this answer: it reads the stored `plan` and the\n * status, never `entitlements.planComp`, and a comp is only ever in force\n * where the status is dead or absent — both of which answer false here.\n *\n * `past_due` counts: Stripe is still retrying and the plan is still owed.\n */\nexport function isBillingSubscription(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n const plan = org?.plan\n if (!plan || plan === 'free' || !(plan in PLAN_ENTITLEMENTS)) return false\n const status = subscriptionStatusOf(org)\n if (!status) return false\n return !DEAD_SUBSCRIPTION_STATUSES.has(status)\n}\n\n/**\n * The org's monthly LIST price in USD — plan base plus every purchased add-on\n * (manager/collaborator seats, datasets, extra hosts, POS registers, Event\n * Calendar), BEFORE any applied discount, and 0 for anyone not actually\n * billing. This is the sticker price the plan + add-ons would bill at.\n *\n * Annual subscriptions contribute their per-month equivalent\n * (`basePriceAnnualMonthlyUsd`), not the month-to-month price, so a mixed\n * book does not read high by the size of the annual discount. Add-ons bill\n * per-month (annual variants are ×12 with no discount), so each contributes\n * its monthly price on either interval.\n *\n * Pricing-v3 fix (2026-07): previously omitted the host, POS-register, and\n * Event-Calendar add-ons, understating the total for every org carrying them.\n *\n * AGL-1105: split out of `orgMonthlyRevenueUsd`, which now subtracts an\n * applied per-org discount on top of this. Callers wanting the pre-discount\n * sticker (the margin guardrail, a \"what would this bill at\" quote) use this;\n * callers wanting the actual money collected use `orgMonthlyRevenueUsd`.\n */\nexport function orgListPriceMonthlyUsd(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number {\n if (!isBillingSubscription(org)) return 0\n // Custom enterprise price (AGL-1110): a negotiated amount is the list price\n // outright — it already reflects the whole deal, so it overrides the plan\n // default AND the seat/host add-on math (those are folded into the quote).\n // Stored monthly-normalized, so it is used as-is.\n const custom = org?.subscription?.customMonthlyUsd\n if (typeof custom === 'number' && custom > 0) {\n return Math.round(custom * 100) / 100\n }\n const pricing = PLAN_PRICING[org?.plan as OrgPlan]\n if (!pricing) return 0\n const annual = org?.subscription?.interval === 'year'\n const addons = org?.seatAddons ?? {}\n const seats = (quantity: number | undefined, price: number | null) =>\n Math.max(0, quantity ?? 0) * (price ?? 0)\n return (\n (annual ? pricing.basePriceAnnualMonthlyUsd : pricing.basePriceMonthlyUsd) +\n seats(addons.managers, pricing.extraSeatMonthlyUsd) +\n seats(addons.members, pricing.extraCollaboratorMonthlyUsd) +\n seats(addons.datasets, pricing.extraDatasetMonthlyUsd) +\n seats(addons.hosts, pricing.extraHostMonthlyUsd) +\n seats(addons.posRegisters, POS_REGISTER_ADDON_MONTHLY_USD) +\n seats(addons.eventCalendar, EVENT_CALENDAR_ADDON_MONTHLY_USD) +\n // A toggle, not a quantity: one purchase covers the org, so a stored\n // quantity above one bills once.\n seats(aiAddonUnits(addons), pricing.aiAddonMonthlyUsd)\n )\n}\n\n/**\n * How many Aglyn AI add-ons a purchase map counts as: 0 or 1 (AGL-2896).\n * The add-on is org-wide like Event Calendar, so a quantity above one — a\n * hand edit, a doubled webhook item — is one purchase, and a non-number,\n * `NaN` or a negative is none.\n */\nexport function aiAddonUnits(addons: OrgSeatAddons | null | undefined): 0 | 1 {\n const quantity = Number(addons?.aiAddon ?? 0)\n return Number.isFinite(quantity) && quantity >= 1 ? 1 : 0\n}\n\n/**\n * A list price net of a discount (AGL-1105). Percentage discounts scale the\n * whole bill; fixed discounts subtract a flat USD amount. Never below 0, and\n * a percentage is clamped to 0–100. Prefers `percentOff` when both are\n * somehow set (Stripe coupons carry only one). Rounds to whole cents.\n */\nexport function applyDiscountUsd(\n listPriceUsd: number,\n discount:\n | Pick<OrgDiscount, 'percentOff' | 'amountOffUsd'>\n | null\n | undefined,\n): number {\n if (!(listPriceUsd > 0) || !discount) return Math.max(0, listPriceUsd)\n if (typeof discount.percentOff === 'number') {\n const pct = Math.min(100, Math.max(0, discount.percentOff))\n return Math.round(listPriceUsd * (1 - pct / 100) * 100) / 100\n }\n if (typeof discount.amountOffUsd === 'number') {\n return Math.max(0, Math.round((listPriceUsd - discount.amountOffUsd) * 100) / 100)\n }\n return listPriceUsd\n}\n\n/**\n * Whether an org should read as **\"Enterprise\"** everywhere the plan is shown.\n * Three ways to qualify:\n * - **`plan === 'enterprise'`** — the real plan (AGL-1118). This is the path\n * every new enterprise org takes, and the only one that also grants\n * Enterprise *entitlements*; the two below are display-only overlays on a\n * lower base plan;\n * - a billing org on a **negotiated custom price** (`customMonthlyUsd > 0`) —\n * a paying enterprise deal still parked on its pre-AGL-1118 base plan; or\n * - an explicit **`org.enterprise`** marker — a *comped* enterprise account,\n * 100%-discounted so it collects $0 while infra cost is still metered.\n *\n * ⚠️ This function answers ONE question — \"does this org read as Enterprise\" —\n * and it is not the same question as \"what does this org get\". Only the first\n * bullet grants anything: `resolveEffectivePlan` reads `org.plan` and nothing\n * else, so `org.enterprise` and `customMonthlyUsd` are read NOWHERE but here.\n * A comped or custom-priced org resolves its BASE plan's entitlements, and\n * gets Enterprise capability through a per-org `entitlements` override — see\n * `ssoEnabled`, whose docblock calls that \"how enterprise orgs provisioned\n * before that plan existed still get it\".\n *\n * This paragraph replaces a parenthetical claiming the comped marker carried\n * \"full Enterprise capability + SSO\" (AGL-2297). It never did, and the billing\n * page believed it: the Enterprise card ticked five entitlements against any\n * org this function marked, so a comped org whose overrides granted SSO but\n * not white-label was shown a green tick for full white-label while the\n * branding card correctly refused it.\n *\n * The last two are kept for orgs provisioned before `enterprise` was a plan;\n * migrating such an org to `plan: 'enterprise'` is the clean end state and\n * changes nothing about how it reads here. List-priced orgs with none of the\n * three are not enterprise.\n *\n * A staff comp in force names the plan instead of the stored one (AGL-3034),\n * exactly as it does for entitlements: a workspace comped to Enterprise reads\n * as Enterprise, and one comped to a lower tier does not read as Enterprise\n * because of a plan stored before the comp. The explicit marker still counts.\n */\nexport function isEnterpriseOrg(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n const comp = resolvePlanComp(org)\n if (comp) return comp.plan === 'enterprise' || org?.enterprise === true\n if (org?.plan === 'enterprise') return true\n if (org?.enterprise === true) return true\n return (\n isBillingSubscription(org) &&\n (org?.subscription?.customMonthlyUsd ?? 0) > 0\n )\n}\n\n/**\n * Human label shown for `enterprise` in plan badges. Kept as a named export\n * for the surfaces that label a custom-priced org whose stored `plan` is still\n * a lower tier; a real `plan: 'enterprise'` org gets the same string from\n * `PLAN_LABELS`.\n */\nexport const ENTERPRISE_PLAN_LABEL = PLAN_LABELS.enterprise\n\n/**\n * The org's monthly recurring revenue in USD — its LIST price\n * (`orgListPriceMonthlyUsd`) net of any applied per-org discount\n * (`org.discount`, AGL-1105), and 0 for anyone who is not actually billing. A\n * comped enterprise deal genuinely collects less, so the discount belongs in\n * MRR; the pre-discount sticker stays available as `orgListPriceMonthlyUsd`.\n */\nexport function orgMonthlyRevenueUsd(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number {\n const listPrice = orgListPriceMonthlyUsd(org)\n if (listPrice <= 0) return 0\n return applyDiscountUsd(listPrice, org?.discount)\n}\n\n/**\n * Stripe's standard processing fee on Aglyn's OWN subscription charges\n * (AGL-1108) — 2.9% + $0.30 per charge. This is the fee Aglyn pays Stripe to\n * collect a subscription; it is DISTINCT from the storefront platform fee\n * (`transactionFee*Pct`, the Connect `application_fee`) that a seller's own\n * Stripe pays on their storefront sales. Revenue/margin should be reasoned on\n * the NET figure, not gross.\n */\nexport const STRIPE_PROCESSOR_FEE_PCT = 0.029\nexport const STRIPE_PROCESSOR_FEE_FIXED_USD = 0.3\n\n/**\n * A gross monthly USD amount net of Stripe's processing fee. Annual\n * subscriptions are charged once per year, so the fixed 30¢ amortizes to\n * $0.30/12 per month; monthly subscriptions pay it every month. Never\n * returns below 0.\n */\nexport function netOfProcessorFee(\n grossMonthlyUsd: number,\n annual = false,\n): number {\n if (!(grossMonthlyUsd > 0)) return 0\n const fixedPerMonth = annual\n ? STRIPE_PROCESSOR_FEE_FIXED_USD / 12\n : STRIPE_PROCESSOR_FEE_FIXED_USD\n const net = grossMonthlyUsd * (1 - STRIPE_PROCESSOR_FEE_PCT) - fixedPerMonth\n return Math.max(0, Math.round(net * 100) / 100)\n}\n\n/**\n * The org's monthly recurring revenue NET of Stripe's processing fee\n * (AGL-1108) — what Aglyn actually keeps before infra COGS, and the correct\n * base for margin/discount reasoning. Gross is `orgMonthlyRevenueUsd`; this\n * subtracts ~2.9%+30¢, interval-aware. 0 for anyone not actually billing.\n */\nexport function orgNetMonthlyRevenueUsd(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number {\n const gross = orgMonthlyRevenueUsd(org)\n if (gross <= 0) return 0\n return netOfProcessorFee(gross, org?.subscription?.interval === 'year')\n}\n\n/**\n * Net-margin floor for discount underwriting (AGL-1105). Mirrors the\n * Enterprise-Pricing-Calculator's ~75% gross-margin target: a discounted\n * subscription should still keep ≥75% of its net (post-processor-fee) revenue\n * after infrastructure COGS. Below the floor a discount is warned; well below\n * it is blocked without explicit sign-off.\n *\n * WHAT THIS IS A FLOOR ON (AGL-1930): net revenue less INFRASTRUCTURE COGS,\n * and nothing else. It is a contribution margin — see `MARGIN_SCOPE_NOTE`,\n * which is the sentence every staff surface quoting this number has to print\n * beside it.\n *\n * Read as a dollar guard it says: net revenue must be at least\n * `cogs / (1 − floor)` = **4 × COGS**, and blocks below\n * `cogs / (1 − floor + band)` ≈ **2.86 × COGS**. That restatement is the point\n * of AGL-1930 — the percentage is scale-invariant, so the only thing that\n * moves the real guard line in dollars is the COGS figure fed into it, and on\n * production today that figure is `INFRA_COGS_PER_SITE_USD × sites` for\n * **every** org (14 of 14 usage rollups across all 6 orgs, measured\n * 2026-08-24). Re-tuning this constant therefore re-tunes a multiplier on the\n * flat floor, not on anything measured. Calibrate the floor first.\n */\nexport const NET_MARGIN_FLOOR_PCT = 0.75\n\n/**\n * How far under the floor a discount may sit and still only WARN (AGL-1105).\n * Within `[floor − band, floor)` staff see a caution; below `floor − band`\n * the guardrail blocks unless overridden. 10 points, matching the\n * calculator's soft/hard split.\n */\nexport const NET_MARGIN_WARN_BAND_PCT = 0.1\n\n/**\n * What every \"net margin\" figure in the staff console actually measures\n * (AGL-1930).\n *\n * `checkDiscountMargin`, the enterprise quote card and the coupon badge all\n * compute `(net revenue − infrastructure COGS) / net revenue`. Support,\n * acquisition and overhead are not in the model anywhere — no meter records\n * them and no constant estimates them — so \"net margin 78%\" is a CONTRIBUTION\n * margin, and a staff member signing a deal on it is reading a ceiling, not a\n * profit.\n *\n * One exported string rather than three literals: the three surfaces that\n * print a margin already drifted apart once (AGL-1134), and a caveat that is\n * true on one screen and absent on the next is worse than none.\n */\nexport const MARGIN_SCOPE_NOTE =\n 'Contribution margin: net revenue less infrastructure COGS only. ' +\n 'Support, customer acquisition (CAC) and overhead are not in this figure.'\n\n/**\n * The `ok` / `warn` / `block` band a cost-recovery margin falls in (AGL-1930).\n *\n * Extracted so `NET_MARGIN_FLOOR_PCT` and `NET_MARGIN_WARN_BAND_PCT` have ONE\n * reader. The staff org page's enterprise quote card had `>= 0.75` and\n * `>= 0.65` written into its JSX, so re-tuning either constant — the whole\n * subject of AGL-1930 — would have moved the discount guardrail and left that\n * card rating deals against the old numbers, silently and with money on the\n * line.\n */\nexport function netMarginRating(marginPct: number): DiscountMarginRating {\n return marginPct >= NET_MARGIN_FLOOR_PCT\n ? 'ok'\n : marginPct >= NET_MARGIN_FLOOR_PCT - NET_MARGIN_WARN_BAND_PCT\n ? 'warn'\n : 'block'\n}\n\n/**\n * Per-site monthly infrastructure COGS in USD (AGL-1105). The\n * Enterprise-Pricing-Calculator's ~$2/site figure — Firebase + Vercel at\n * cost — charged against every host the org runs. The margin guardrail\n * subtracts this from net revenue before rating a discount.\n */\nexport const INFRA_COGS_PER_SITE_USD = 2\n\n/**\n * Platform markup on passed-through infrastructure costs (AGL-41).\n *\n * DEFINED HERE, not in `apps/console/utils/usage-metering.ts`, and re-exported\n * from there — the same move AGL-2155 made for `pageViewsFromBandwidthGb`, and\n * for the same reason (AGL-2194). The published `/pricing` pass-through table\n * states both a cost column and a \"you pay (+30%)\" column, and\n * `tools/marketing/build-pricing-tables.mts` must generate both from code so\n * `npm run check:pricing-tables` can fail on drift. That generator resolves no\n * `@aglyn/*` path alias, so it can import `plan-entitlements.ts` and nothing\n * under `apps/console`. A constant the published page depends on must live\n * where the page's generator can read it.\n *\n * The console keeps importing it from `usage-metering` unchanged.\n *\n * It is the margin KEPT: cost + 30% after Stripe's processor fee, not before\n * it (AGL-3476). A price of cost × 1.3 kept cost × 1.26 once 2.9% came off,\n * so the published rates are set by {@link METERED_PRICE_MULTIPLE}, and this\n * stays the figure every full-utilization rule is held to.\n */\nexport const METERED_MARKUP = 1.3\n\n/**\n * What a passed-through cost is multiplied by to reach its price, so that\n * cost + 30% survives Stripe's percentage fee: 1.3 ÷ 0.971 ≈ 1.3388\n * (AGL-3476). The fixed 30¢ is paid by the subscription charge the overage\n * rides on, so only the percentage is grossed up.\n */\nexport const METERED_PRICE_MULTIPLE = METERED_MARKUP / (1 - STRIPE_PROCESSOR_FEE_PCT)\n\n/**\n * A published overage price: `costPerQuotedUnitUsd` × {@link\n * METERED_PRICE_MULTIPLE}, rounded UP at the `decimals` it is quoted in, so\n * the price the page prints and the invoice bills is the same round figure\n * and never less than the rule. The product is settled to six places before\n * the ceiling so a figure that is exact in decimal — or a pinned cost carried\n * to eleven places, like the page-view pair — is not lifted a step by its\n * tail.\n */\nexport function publishedMeteredPrice(costPerQuotedUnitUsd: number, decimals: number): number {\n const scale = 10 ** decimals\n return (\n Math.ceil(Number((costPerQuotedUnitUsd * METERED_PRICE_MULTIPLE * scale).toFixed(6))) / scale\n )\n}\n\n/**\n * What the REQUESTS behind ONE page view cost once the platform is past its\n * hosting plan's included request allowance, at the DEAREST region the CDN\n * and functions bill, in USD per view (AGL-1879, AGL-3444): the CDN requests\n * a cold load makes, and the one of them that reaches a function — the\n * analytics beacon. A page view billed past a plan's band is priced at\n * `(perPageView + this) × METERED_MARKUP` — $0.80 per 1,000 — and every\n * `bandwidthGb` band is sized with it counted.\n *\n * ## Why a page view needs a second cost term\n *\n * `perPageView` prices what a view WEIGHS: the bytes it transfers, plus the\n * Firestore reads behind the render. A view also costs per REQUEST, which is\n * not proportional to weight: Vercel Pro includes 10,000,000 CDN requests a\n * month, then bills $2.00 to $3.20 per million by region, and a cold load of\n * `aglyn.com/` makes 57 first-party requests to settle\n * (`tools/tenant-page-budget.json`, `wireCalibration.measuredAgainst`) — so\n * the allowance is about 175,000 views a month, platform-wide, and runs out\n * long before the transfer allowance. One of those requests is the analytics\n * beacon (`apps/tenant/app/api/analytics/collect`), which runs a function on\n * every view and writes two counter documents.\n *\n * ## Why the dearest region\n *\n * The CDN bills a request, and the bytes it transfers, in the region that\n * SERVES it — the one nearest the visitor — so a site whose audience is in\n * São Paulo or Cape Town costs what those regions cost, and nothing about a\n * plan can choose otherwise. The platform's rule is that no plan is under\n * water at any utilization and that \"at cost + 30%\" is true of every billed\n * unit, so every Vercel-billed term of a page view is priced where Vercel is\n * dearest.\n *\n * ## The figure\n *\n * Per view, past the allowance, at the dearest region:\n *\n * | | |\n * |--------------------------------------------------------|--------------|\n * | 57 CDN requests at $3.20 per million | $0.00018240 |\n * | the beacon's function: 0.4 s billed as active CPU at | $0.00002922 |\n * | $0.221/hr, 2 GB at $0.0183/GB-hr, $0.60 per million | |\n * | the beacon's two counter writes at nam5's $0.18/100k | $0.00000360 |\n * | the beacon's ~1 KB through the function at $0.43/GB | $0.00000044 |\n * | **requests, measured** | $0.00021566 |\n *\n * ($0.399028 weight + $0.215662) × 1.3 is $0.7991 per 1,000, rounded up to\n * $0.80, and this term is pinned so ($0.399028 + $0.216357) × 1.3 is exactly\n * $0.80 — \"Our cost $0.615385 / You pay $0.80\" — with the basis set ABOVE the\n * measurement as the weight basis is. \"At cost + 30%\" holds of the cost a\n * billed view incurs in the dearest region, and so in every other. The 0.4 s\n * is the one function duration on record (the form-submission route), billed\n * whole as CPU because nothing measured splits it from I/O wait.\n *\n * ## ⛔ Not in {@link ORG_COGS_UNIT_RATES_USD}, deliberately\n *\n * `perPageView` is half of a pair with `ESTIMATED_PAGE_TRANSFER_BYTES` — one\n * page weight in dollars and in bytes — and `npm run check:page-view-rate`\n * recovers that weight from the rate. A per-request charge is not\n * proportional to weight, so folding it into `perPageView` would make the\n * gate read a page nobody serves.\n *\n * ## The INCLUDED bands carry it too\n *\n * A view inside a plan's band makes the same requests as one past it, and\n * once the platform is past the allowance they cost the same either way, so\n * the `bandwidthGb` bands are sized with this term counted: $0.224 per\n * included GB on top of $0.413 of weight. `tier-margin-floor.spec.ts`\n * multiplies it in and pins every tier at 100% of every band.\n *\n * Lives HERE, beside the markup, for the reason the markup does: the\n * `/pricing` generator publishes the cost column from it and cannot import\n * anything under `apps/console`.\n */\nexport const PAGE_VIEW_CDN_REQUEST_COST_USD = 0.00021635711\n\n/**\n * Monthly unit costs for the meters the rollup already records (AGL-1134).\n *\n * These are OUR costs, not prices — the org is billed at cost × 1.30\n * (`METERED_MARKUP`, AGL-41). Storage/page-view/form rates match\n * `METERED_UNIT_RATES_USD` in the metering module on purpose: two files\n * disagreeing about what a page view costs is exactly the drift this function\n * exists to remove.\n *\n * The three added here are operator-tuned estimates of the same quality as\n * the existing three — Firestore-backed dataset storage is priced well above\n * object storage because it is, and API requests and contacts are priced off\n * the reads behind them. Treat every operator-tuned figure here as\n * provisional until validated against a real Firebase + Vercel invoice month;\n * AGL-1134 says so out loud rather than implying the numbers are derived.\n * `perEmailSend` is the exception and carries its own basis below.\n *\n * ## Three of these are shared with the billed table. The rest are NOT.\n *\n * `storagePerGbMonth`, `perPageView` and `perFormSubmission` are the metered\n * infrastructure pass-through, and the customer is charged those figures\n * times `METERED_MARKUP`. They therefore appear in `METERED_UNIT_RATES_USD`\n * as well, with identical values, and must be changed in both places at once.\n * A page view billed past its band also carries\n * {@link PAGE_VIEW_CDN_REQUEST_COST_USD}, which neither table holds: it is\n * the per-request CDN charge past the hosting plan's allowance, and this\n * table's `perPageView` is the weight-proportional half of what a band is\n * sized against — the request term is the other.\n *\n * ## Every Vercel-billed input is priced at Vercel's dearest region\n *\n * The CDN and functions bill by region, from $0.15 to $0.35 per GB of\n * transfer (a decimal GB), $2.00 to $3.20 per million requests, and $0.128 to\n * $0.221 per hour of active CPU (memory $0.0106 to $0.0183 per GB-hour), and\n * a plan cannot choose where its visitors are. So the Vercel share of every\n * rate here is priced at the dearest region (AGL-3444): the transfer inside\n * `perPageView`, the function, CDN request and transfer inside\n * `perFormSubmission` and `perApiRequest`, and the compute inside `perRun`.\n *\n * The GCP shares are priced at the database's own location. Production's\n * Firestore is `nam5` (read from the Admin API 2026-10-01), at $0.06 per\n * 100,000 reads, $0.18 per 100,000 writes and $0.18 per GiB-month — twice\n * the single-region figures — and every Firestore operation in this table is\n * priced there. Object storage is GCS US multi-region, `storagePerGbMonth`.\n *\n * Everything else here is a cost-model input ONLY. `dataStoragePerGbMonth`,\n * `perApiRequest`, `perContactMonth` and `perEmailSend` price what an org\n * costs us; what an org is CHARGED on those axes is a retail rate on\n * `PLAN_PRICING` that descends with the tier and is not derived from cost.\n * Adding one of them to `METERED_UNIT_RATES_USD` would put it inside the\n * \"at cost + 30%\" claim, which is true of exactly three meters.\n *\n * The first three were corrected 2026-08-09 (AGL-1280) — storage $0.03 →\n * $0.026 and form submissions $0.0005 → $0.00005 — `perPageView` was\n * re-pegged 2026-09-09 (AGL-2711), from $0.0001 to $0.00016153846, and the\n * last two were re-priced at Vercel's dearest region and Firestore's `nam5`\n * on 2026-10-01 (AGL-3444): page views $0.00039902751, form submissions\n * $0.000061538462.\n * All three MUST be changed here and in `METERED_UNIT_RATES_USD` together. See that\n * table for each figure's basis; it is the one a customer is billed against,\n * so it is the one that carries the working.\n *\n * The page-view basis is no longer an open gap. The rate priced a 627 KB page\n * against one that measured far more for weeks; the weight reduction that was\n * preferred over a re-peg has now shipped, the page was re-measured at\n * 976.1 KB, and the rate is pegged to a 1012.8 KB basis that sits ABOVE it.\n * `npm run check:page-view-rate` holds that peg, and now fails if the basis\n * ever drops back below what a published page measures.\n *\n * Raising the two here raises measured COGS, which changes no guardrail\n * verdict: `INFRA_COGS_PER_SITE_USD × sites` is the floor, and it still\n * outruns measured cost by orders of magnitude at real traffic.\n */\nexport const ORG_COGS_UNIT_RATES_USD = {\n storagePerGbMonth: 0.026,\n /**\n * ⛔ HALF OF A PAIR — see ESTIMATED_PAGE_TRANSFER_BYTES, which states the\n * same page in bytes as this states it in dollars. Both are calibrated\n * against a 1012.8 KB page and neither may move alone: their quotient is\n * the cost of a gigabyte's weight, and that — with the request term — is\n * what every bandwidthGb band on the ladder is sized against.\n */\n perPageView: 0.00039902751,\n perFormSubmission: 0.000061538462,\n /**\n * Firestore-backed dataset bytes — nam5's $0.18 per GiB-month, an order\n * pricier than object storage.\n */\n dataStoragePerGbMonth: 0.18,\n /**\n * One `/v1` request — $117 per million (AGL-3444).\n *\n * Every request is a function invocation behind a CDN request, and it reads\n * before it answers (`apps/console/utils/api-v1.ts`). The ledger for a\n * default list page — 25 records, read as 26 — at Vercel's dearest region\n * and nam5's list prices:\n *\n * - reads: the key, the rate-limit window, the org, the month's quota\n * counter, the site the resource sits under, and the page — 31, at\n * $0.06 per 100,000 = $0.0000186\n * - writes: the rate-limit window and the usage counter — 2, at $0.18 per\n * 100,000 = $0.0000036\n * - the function, 0.4 s billed as active CPU at $0.221/hr, 2 GB at\n * $0.0183/GB-hr, $0.60 per million invocations = $0.0000292\n * - the CDN request: $3.20 per million = $0.0000032\n * - the response, 25 records at 3 KB — the largest mean document size\n * measured on production across the collections `/v1` lists (leads,\n * 3.0 KB; records, contacts, media and the rest 1.0–1.5 KB) — plus 2 KB\n * of request and envelope, through Fast Data Transfer ($0.35 a GB) and\n * Fast Origin Transfer ($0.43 a GB), decimal GB = $0.0000615\n *\n * $0.0001161, carried up to $0.000117. A page of 100 — the most a client\n * may ask for — costs about three times that, so the figure is the default\n * request rather than the ceiling; see the 2026-10-01 decision entry.\n */\n perApiRequest: 0.000117,\n /**\n * One CRM record held for a month. An operator-tuned estimate (AGL-1134),\n * not built from a per-operation price: at `nam5`'s $0.06 per 100,000 it\n * covers about 333 reads a record a month, plus the record's storage at\n * $0.18 per GiB-month — well beyond what a record is read.\n */\n perContactMonth: 0.0002,\n /**\n * One delivered email, whatever produced it — $0.90 per 1,000.\n *\n * Unlike its five neighbours this is not an operator-tuned estimate. It is\n * the provider's own blended rate at the tier above the account's current\n * one ($90 / 100,000) and also the marginal overage rate on both that tier\n * and the current one, so it does not fall as volume grows and is the\n * conservative figure at any usage the platform can reach.\n *\n * It prices EVERY send on the cost meter — campaigns, receipts, invites,\n * password resets — because every one of them is a message the provider\n * charges for. The band on the plan and the retail overage rate beside it\n * both concern a smaller quantity; this is the whole bill.\n */\n perEmailSend: 0.0009,\n /**\n * One workflow or action run — $13 per million.\n *\n * Derived from the run paths at list prices (2026-09-07), because nothing\n * measurable exists yet: runs execute inside the Vercel request that fired\n * the event, not in a function of their own, so Cloud Logging carries no\n * per-run entry to count, and Firestore's operation counts are platform-wide\n * and cannot be attributed to a run. Over the 14 days to 2026-09-07 the\n * project logged no run at all.\n *\n * The ledger, for an event that executes one workflow on a site holding a\n * handful of functions, variables and workflows (`runEventWorkflows`,\n * `runEventActions` and the inbound-webhook run are the same shape):\n *\n * - reads: the triggered query, the workflow map, the host → org lookup,\n * the run counter, the functions and the variables — about 12, at\n * nam5's $0.06 per 100,000 = $0.0000072\n * - writes: the activity row and the counter increment — 2, at $0.18 per\n * 100,000 = $0.0000036\n * - compute: a few milliseconds of active CPU and memory on Fluid compute\n * for the expression evaluation, about $0.000001 in the cheapest\n * region and $0.0000017 in the dearest ($0.221 against $0.128 an\n * hour of active CPU), which is where it is priced\n *\n * $0.0000125, carried as $0.000013. It scales with the catalog a site holds\n * — every `limit(100)` read above is a query that costs one read per\n * document returned — and a step that writes a dataset record or calls a\n * webhook adds to it, so this is the typical figure, not the ceiling.\n *\n * It is a COST, not a price: no plan bills runs, and none refuses past the\n * band on a metered plan. It exists so a tier's run bands reach\n * `orgMonthlyCogsUsd` and `tier-margin-floor.spec.ts` — Agency's 3,000,000\n * runs are $36 a month that read as nothing while this was absent.\n */\n perRun: 0.000013,\n}\n\n/**\n * The rollup fields `orgMonthlyCogsUsd` prices. All optional and all\n * absent-safe.\n *\n * The named fields are the platform's own meters. A plugin's meter rides the\n * index signature under the rollup field its declaration names\n * (`plugin-usage-axes.ts`): the AI plugin's provider spend, the CRM's\n * records, the forms plugin's submissions, the workflows plugin's runs.\n */\nexport interface OrgUsageRollupInput {\n hostCount?: number | null\n storageGb?: number | null\n pageViews?: number | null\n dataStorageMb?: number | null\n apiRequests?: number | null\n /**\n * Every email the org sent this month, campaigns and transactional alike —\n * the `emailSends` cost meter, not the campaign meter.\n *\n * The cost meter is the right input precisely because the campaign meter is\n * the one with a cap on it. What this org cost us is every message the\n * provider charged for, and most of them are messages no quota could have\n * refused.\n */\n emailSends?: number | null\n /**\n * A plugin's meter, by the rollup field its declaration names. `unknown`\n * because a rollup document carries its month and its audit fields beside\n * the meters, and a caller may hand the document over whole; every meter\n * is read through `declaredMeterReading`, which prices only a positive\n * finite number.\n */\n [field: string]: unknown\n}\n\nexport interface OrgCogsResult {\n /** What the guardrail should charge against revenue. */\n cogsUsd: number\n /** Which arm produced it — `floor` means the meters came in under the flat estimate. */\n basis: 'measured' | 'floor'\n /** Cost from the meters alone, before the floor is applied. */\n measuredUsd: number\n /** `INFRA_COGS_PER_SITE_USD` × sites. */\n floorUsd: number\n /** Per-meter contributions, for showing the working in staff UI. */\n breakdown: Record<string, number>\n}\n\n/**\n * The unit rate a declared cost axis names, or 1 where it names none (the\n * rollup already records dollars).\n *\n * ⚑ A rate key core does not carry THROWS. Pricing an unknown axis at zero\n * would make an org cost less than it does, which is the direction that\n * approves a discount; `plugin-usage-axes.spec.ts` holds every declared key\n * to a rate that exists, so this is reached only by a tree that skipped it.\n */\nfunction cogsRateOf(axis: { id: string; rate?: string }): number {\n if (axis.rate === undefined) return 1\n const rate = (ORG_COGS_UNIT_RATES_USD as Record<string, number>)[axis.rate]\n if (typeof rate !== 'number' || !Number.isFinite(rate)) {\n throw new Error(\n `cost axis \"${axis.id}\" names the rate \"${axis.rate}\", which ORG_COGS_UNIT_RATES_USD does not carry`,\n )\n }\n return rate\n}\n\n/**\n * One org's monthly infrastructure COGS — the single cost model (AGL-1134).\n *\n * Replaces two things that had drifted apart: the discount guardrail read the\n * rollup's `costUsd`, and the staff MRR views read the same field separately,\n * so neither could be changed without silently changing the other's meaning.\n *\n * `costUsd` was also incomplete. The rollup records `dataStorageMb`,\n * `apiRequests` and `contactsCount`, and `estimateMonthlyUsageCost` prices\n * none of them — it sums storage, page views and form submissions only. So\n * the \"measured\" cost omitted three meters the platform was already at the\n * trouble of recording.\n *\n * The flat per-site figure stays a FLOOR rather than a fallback: measured\n * cost wins only when it exceeds the estimate. A rollup that has not run, or\n * an org with genuinely tiny usage, must not make the guardrail more generous\n * — that is the one direction that costs money.\n *\n * Measured against production 2026-07-30, that floor is doing all the work:\n * the largest real org's `costUsd` was **$0.0000054** against a $4.00 floor\n * for two sites. Five orders of magnitude. So this function changes no verdict\n * today, and will not until there is real traffic — which is the honest\n * reason to build it now, while nothing depends on the answer.\n */\nexport function orgMonthlyCogsUsd(\n rollup: OrgUsageRollupInput | null | undefined,\n siteCount: number,\n): OrgCogsResult {\n const num = (value: unknown) => {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 0\n }\n const rates = ORG_COGS_UNIT_RATES_USD\n // The platform's own axes, at the positions the plugins' declared axes\n // interleave with (`order`, a multiple of 10 here). The generator refuses a\n // declaration that takes one of these ids or positions.\n const core: Array<{ id: string; order: number; usd: number }> = [\n { id: 'storage', order: 10, usd: num(rollup?.storageGb) * rates.storagePerGbMonth },\n { id: 'pageViews', order: 20, usd: num(rollup?.pageViews) * rates.perPageView },\n // Megabytes on the doc, gigabytes in the rate — the unit mismatch is in\n // the stored field name, so convert here rather than in each caller.\n {\n id: 'dataStorage',\n order: 40,\n usd: (num(rollup?.dataStorageMb) / 1024) * rates.dataStoragePerGbMonth,\n },\n { id: 'apiRequests', order: 50, usd: num(rollup?.apiRequests) * rates.perApiRequest },\n // Every send, not the overage — the provider bills the first message of\n // the month as much as the last, so a cost model that started counting\n // at the plan's included band would report an org that stayed inside its\n // allowance as free.\n { id: 'emailSends', order: 70, usd: num(rollup?.emailSends) * rates.perEmailSend },\n ]\n // Each plugin's meter, priced at the core rate its declaration names — or\n // at ×1 where the rollup already records dollars, as the AI plugin's\n // provider spend does.\n const declared = pluginCostAxes().map((axis) => ({\n id: axis.id,\n order: axis.order,\n usd: declaredMeterReading(rollup, axis) * cogsRateOf(axis),\n }))\n const breakdown: Record<string, number> = Object.fromEntries(\n [...core, ...declared]\n .sort((a, b) => a.order - b.order)\n .map((axis) => [axis.id, axis.usd]),\n )\n const measuredUsd = Object.values(breakdown).reduce((sum, x) => sum + x, 0)\n const floorUsd = Math.max(0, siteCount) * INFRA_COGS_PER_SITE_USD\n return {\n cogsUsd: Math.max(measuredUsd, floorUsd),\n basis: measuredUsd > floorUsd ? 'measured' : 'floor',\n measuredUsd,\n floorUsd,\n breakdown,\n }\n}\n\n/**\n * Pluck the fields `orgMonthlyCogsUsd` prices out of a raw usage rollup\n * (AGL-1134) — a Firestore `snapshot.data()`, or the JSON a staff endpoint\n * served from one. ONE list of field names, in one place.\n *\n * The hazard this closes is silent. Every caller previously hand-listed the\n * six fields at its own call site — the discount route, the anomaly detector,\n * the staff org page — and `/api/admin/org-usage` projected only three of\n * them. A projection that drops a priced field does not error; the model just\n * returns a smaller number, and a smaller COGS is the direction that APPROVES\n * a discount. Same shape as the API projection that starved a predicate in\n * AGL-1355, and the two-units usage figure that read 20-45% wrong for years\n * in AGL-1402.\n *\n * UNITS, because the field names do not all say: `storageGb` and\n * `dataStorageMb` are gigabytes and MEGABYTES respectively (the conversion\n * lives in `orgMonthlyCogsUsd`, with its own test); `pageViews`,\n * `apiRequests` and `emailSends` are counts FOR THE MONTH. A plugin's fields\n * carry the unit its cost-axis declaration states — a monthly count, a\n * point-in-time level, or dollars.\n */\nexport function orgCogsInputFrom(\n source: Record<string, unknown> | null | undefined,\n): OrgUsageRollupInput {\n const read = (field: string) => {\n const value = source?.[field]\n return value == null ? undefined : (value as number)\n }\n const input: OrgUsageRollupInput = {\n hostCount: read('hostCount'),\n storageGb: read('storageGb'),\n pageViews: read('pageViews'),\n dataStorageMb: read('dataStorageMb'),\n apiRequests: read('apiRequests'),\n emailSends: read('emailSends'),\n }\n // Every field a plugin's cost axis reads or records — forwarded for the\n // reason the six above are: a projection that drops a priced field prices\n // the meter at nothing, which is the direction that approves a discount.\n for (const field of pluginCostAxisFields()) input[field] = read(field)\n return input\n}\n\n/**\n * A cost figure, or the explicit admission that the rollup has not loaded\n * (AGL-1134).\n *\n * `pending` is not \"no usage\" and not \"$0\" — it is \"do not answer yet\".\n */\nexport type OrgCogsPreview =\n | { status: 'pending' }\n | { status: 'ready'; cogs: OrgCogsResult }\n\n/**\n * `orgMonthlyCogsUsd` behind an explicit readiness flag (AGL-1134).\n *\n * The staff org page loads its rollup asynchronously, and an unfinished read\n * and an absent document are the same `undefined`. Priced directly, both\n * yield `basis: 'floor'` — so the enterprise pricing card printed \"per-site\n * floor — no usage recorded yet\" during the loading window, a\n * measurement-shaped sentence about an org whose measurements had simply not\n * arrived. That is the AGL-1380 / AGL-1422 defect exactly: a question\n * answered before its data is ready, and answered in the cheap direction.\n *\n * Returning a discriminated union rather than a nullable number is what makes\n * the window unrepresentable — a caller cannot reach a cost without first\n * handling `pending`, so the next call site cannot re-introduce this by\n * forgetting a flag it was handed.\n */\nexport function orgCogsPreview(\n ready: boolean,\n rollup: OrgUsageRollupInput | null | undefined,\n siteCount: number,\n): OrgCogsPreview {\n // Gated on the caller's flag alone. Deriving readiness from `rollup` being\n // truthy would re-create the bug: the org with no rollup yet is precisely\n // the one whose value stays falsy after the read completes.\n if (!ready) return { status: 'pending' }\n return { status: 'ready', cogs: orgMonthlyCogsUsd(rollup, siteCount) }\n}\n\n/**\n * Where a cost figure came from, in a sentence a staff member can act on\n * (AGL-1930).\n *\n * The staff quote card said `per-site floor — no usage recorded yet` for\n * EVERY `basis: 'floor'` org. Measured against production 2026-08-24 that\n * sentence is false on most of them: all 14 usage rollups across all 6 orgs\n * price under the floor, and 9 of the 14 carry genuinely non-zero usage — the\n * largest real org measured **$0.22** against its $2.00 floor. \"No usage\n * recorded\" and \"usage recorded, and it came in under the floor\" are opposite\n * facts about the cost model, and only the second one is evidence the meters\n * are working at all.\n *\n * Same failure family as AGL-1380 / AGL-1422 — a measurement-shaped sentence\n * printed over a measurement that was never consulted — one layer further in:\n * here the measurement exists and the copy denies it.\n */\nexport function orgCogsBasisSummary(\n cogs: OrgCogsResult,\n month?: string | null,\n): string {\n const when = month ?? 'the latest'\n if (cogs.basis === 'measured') return `measured from ${when} usage`\n // Absent and zero are NOT the same claim, and with `strictNullChecks` off an\n // absent meter reads as 0 all the way down — so this leans on the caller's\n // `orgCogsPreview` readiness gate for \"has a rollup arrived\", and reports\n // only what the priced total says.\n if (!(cogs.measuredUsd > 0)) return 'per-site floor — no usage recorded yet'\n // Cents are the wrong precision for these figures and rounding to them\n // prints `$0.00`, which reads as the \"no usage\" claim this function exists\n // to stop making. Every real org's measured month is under a cent.\n const measured =\n cogs.measuredUsd < 0.01\n ? cogs.measuredUsd.toFixed(4)\n : cogs.measuredUsd.toFixed(2)\n return (\n `per-site floor — ${when} usage measured ` +\n `$${measured}, under the $${cogs.floorUsd.toFixed(2)} floor`\n )\n}\n\n/**\n * Percent-off at or above which creating a coupon needs explicit staff\n * sign-off (AGL-1105). A ≥40%-off coupon is a real revenue commitment, so the\n * creation route rejects it unless the caller passes a confirm flag; the UI\n * surfaces a checkbox at this threshold.\n */\nexport const DISCOUNT_APPROVAL_THRESHOLD_PCT = 40\n\n/**\n * Discount DEPTH bands (AGL-1120) — how much of list price is being given\n * away, which is a different question from whether the remainder covers\n * infrastructure.\n *\n * The original guardrail asked only the second question, so a **93% off**\n * coupon rated \"OK — net margin 78.1% vs a 75% floor\": $9.15 left against $2\n * of infra really is a 78% contribution margin. The ratio is scale-invariant,\n * so as long as the leftover dwarfs $2 the verdict stays green no matter how\n * little is left. Depth never entered the verdict at all.\n *\n * Warn at the threshold that already requires sign-off, so the badge and the\n * approval checkbox stop disagreeing with each other; block at 70%, where a\n * deal is being given away rather than discounted.\n */\nexport const DISCOUNT_DEPTH_WARN_PCT = DISCOUNT_APPROVAL_THRESHOLD_PCT\nexport const DISCOUNT_DEPTH_BLOCK_PCT = 70\n\n/** How many sites (hosts) the org runs — at least 1 for a billing org. */\nexport function orgSiteCount(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number {\n return Math.max(1, Object.keys(org?.hosts ?? {}).length)\n}\n\n/** The margin-guardrail verdict for a proposed or applied discount. */\nexport type DiscountMarginRating = 'ok' | 'warn' | 'block'\n\nexport interface DiscountMarginResult {\n /** Pre-discount list price (`orgListPriceMonthlyUsd`). */\n grossUsd: number\n /** List price net of the proposed discount. */\n discountedUsd: number\n /** Discounted price net of Stripe's processing fee — what Aglyn keeps. */\n netUsd: number\n /**\n * Monthly COGS charged against this org: the org's MEASURED cost when the\n * caller supplies one, else the flat `$/site × sites` estimate. The flat\n * figure is a floor, never a ceiling — a measured cost below it is treated\n * as the estimate, since a rollup that has not caught up must not make a\n * deal look cheaper than the placeholder did.\n */\n infraCogsUsd: number\n /** True when `infraCogsUsd` came from measured usage, not the flat rate. */\n cogsMeasured: boolean\n /**\n * Cost recovery = (net − COGS) / net, as a fraction. 1 = COGS-free; -1 is\n * the clamp for a fully-underwater discount (net ≤ 0), which always blocks.\n *\n * NOT a deal-quality number on its own — see `depthPct`. Kept under its\n * original name because the staff UI reports it verbatim.\n */\n marginPct: number\n /** Share of list price given away, as a fraction (0.93 = 93% off). */\n depthPct: number\n /** The worse of the depth and cost-recovery verdicts. */\n rating: DiscountMarginRating\n /**\n * Which test produced `rating`, so the UI can say why rather than showing a\n * margin number that looks fine next to a red badge.\n */\n reason: 'none' | 'depth' | 'cogs' | 'underwater'\n /** The cost-recovery floor this was rated against (`NET_MARGIN_FLOOR_PCT`). */\n floorPct: number\n}\n\nexport interface DiscountMarginOptions {\n /**\n * The org's measured monthly cost, when the caller has it — the `costUsd`\n * rollup at `orgs/{id}/usage/{month}`. Server callers and the staff org\n * page can supply it; coupon creation cannot, because no org is chosen yet.\n */\n measuredCogsUsd?: number | null\n}\n\n/**\n * Rate a proposed discount (AGL-1105, re-shaped in AGL-1120) — the guardrail\n * behind staff coupon creation and per-org application.\n *\n * Two independent questions, and the verdict is the WORSE of them:\n *\n * 1. **How deep is it?** `depthPct` against `DISCOUNT_DEPTH_WARN_PCT` /\n * `DISCOUNT_DEPTH_BLOCK_PCT`. This is the one that was missing. Cost\n * recovery alone is scale-invariant — 93% off $139 leaves $9.15 against\n * $2 of infra, a genuine 78% contribution margin — so the old verdict\n * rated a give-away \"OK\" and only warned at 94%.\n * 2. **Does the remainder cover the cost of serving them?** `marginPct`\n * against `NET_MARGIN_FLOOR_PCT`, with `NET_MARGIN_WARN_BAND_PCT` below\n * it, and an automatic block once the discount drives net ≤ 0.\n *\n * Neither subsumes the other: a shallow discount on an org running twenty\n * sites can still fail cost recovery, and a deep one on a cheap org can still\n * clear it. `reason` reports which test bound, so the UI never shows a\n * healthy-looking margin beside a red badge without explanation.\n *\n * `discount` is the PROPOSED change, independent of any discount already on\n * the org — `grossUsd` is always the list price (`orgListPriceMonthlyUsd`),\n * so this never double-counts an existing `org.discount`. A non-billing org\n * (list price 0) has no revenue to protect and rates `ok`.\n */\nexport function checkDiscountMargin(\n org: Partial<AglynOrgBilling> | null | undefined,\n discount: Pick<OrgDiscount, 'percentOff' | 'amountOffUsd'>,\n options: DiscountMarginOptions = {},\n): DiscountMarginResult {\n const floorPct = NET_MARGIN_FLOOR_PCT\n const grossUsd = orgListPriceMonthlyUsd(org)\n const flatCogsUsd =\n Math.round(INFRA_COGS_PER_SITE_USD * orgSiteCount(org) * 100) / 100\n // Measured cost wins only when it EXCEEDS the flat estimate. A rollup that\n // has not run yet reads as a low number, and a guardrail that got looser\n // because the meter was behind would fail in the one direction that costs\n // money.\n const measured = Number(options.measuredCogsUsd ?? 0)\n const cogsMeasured = Number.isFinite(measured) && measured > flatCogsUsd\n const infraCogsUsd = cogsMeasured\n ? Math.round(measured * 100) / 100\n : flatCogsUsd\n if (grossUsd <= 0) {\n return {\n grossUsd: 0,\n discountedUsd: 0,\n netUsd: 0,\n infraCogsUsd,\n cogsMeasured,\n marginPct: 1,\n depthPct: 0,\n rating: 'ok',\n reason: 'none',\n floorPct,\n }\n }\n const discountedUsd = applyDiscountUsd(grossUsd, discount)\n const annual = org?.subscription?.interval === 'year'\n const netUsd = netOfProcessorFee(discountedUsd, annual)\n const depthPct =\n Math.round(((grossUsd - discountedUsd) / grossUsd) * 10000) / 10000\n const marginPct =\n netUsd > 0\n ? Math.round(((netUsd - infraCogsUsd) / netUsd) * 10000) / 10000\n : -1\n\n const depthRating: DiscountMarginRating =\n depthPct * 100 >= DISCOUNT_DEPTH_BLOCK_PCT\n ? 'block'\n : depthPct * 100 >= DISCOUNT_DEPTH_WARN_PCT\n ? 'warn'\n : 'ok'\n // Same bands the staff quote card renders — one reader for the two\n // constants (AGL-1930), so a re-tune cannot reach the guardrail and miss\n // the UI.\n const cogsRating: DiscountMarginRating = netMarginRating(marginPct)\n\n const severity = { ok: 0, warn: 1, block: 2 } as const\n const rating =\n severity[depthRating] >= severity[cogsRating] ? depthRating : cogsRating\n const reason: DiscountMarginResult['reason'] =\n rating === 'ok'\n ? 'none'\n : netUsd <= 0\n ? 'underwater'\n : severity[depthRating] >= severity[cogsRating]\n ? 'depth'\n : 'cogs'\n\n return {\n grossUsd,\n discountedUsd,\n netUsd,\n infraCogsUsd,\n cogsMeasured,\n marginPct,\n depthPct,\n rating,\n reason,\n floorPct,\n }\n}\n\n/**\n * The plan the org actually gets (AGL-247): missing/unknown plans resolve\n * as `free`, and a paid plan whose subscription is canceled/unpaid/\n * incomplete downgrades to `free` until the webhook restores it — plan\n * fields alone are not entitlement.\n *\n * A STAFF COMP comes first (AGL-3034), and only where no subscription decides\n * the plan: `resolvePlanComp` answers `null` while one is live, so a paying\n * workspace's plan is always its subscription's. The comp is its own marker,\n * never the stored `plan` — a canceled customer whose `plan` still names what\n * they paid for resolves as Free exactly as before, because nothing about\n * their document is a comp.\n */\nexport function resolveEffectivePlan(\n org: Partial<AglynOrgBilling> | null | undefined,\n): OrgPlan {\n const comp = resolvePlanComp(org)\n if (comp) return comp.plan\n const plan = org?.plan\n if (!plan || !(plan in PLAN_ENTITLEMENTS)) return 'free'\n const status = subscriptionStatusOf(org)\n if (plan !== 'free' && status && DEAD_SUBSCRIPTION_STATUSES.has(status)) {\n return 'free'\n }\n return plan\n}\n\nfunction resolvePlan(org: Partial<AglynOrgBilling> | null | undefined) {\n return resolveEffectivePlan(org)\n}\n\n/**\n * Purchased add-on quantities that currently apply (AGL-524): add-ons\n * bill as items on the org's Stripe subscription, so a dead subscription\n * (the `resolveEffectivePlan` set) stops them counting until the webhook\n * restores it. Orgs with no subscription state keep staff-set quantities\n * (comped add-ons predating self-serve billing).\n */\nfunction resolvePurchasedAddons(\n org: Partial<AglynOrgBilling> | null | undefined,\n): OrgSeatAddons {\n const status = subscriptionStatusOf(org)\n if (status && DEAD_SUBSCRIPTION_STATUSES.has(status)) return {}\n return org?.seatAddons ?? {}\n}\n\n/**\n * Whether the org's Aglyn AI add-on currently applies (AGL-2896): bought,\n * and on a subscription that is still paying for it. The ONE reading of\n * `seatAddons.aiAddon` — the entitlement fold and the assist overage rate\n * both ask this, so a dead subscription drops the band and the rate\n * together rather than leaving a rate on a band that has gone.\n */\nexport function hasAiAddon(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n return aiAddonUnits(resolvePurchasedAddons(org)) === 1\n}\n\n/**\n * Numeric entitlement keys that were REMOVED from `PLAN_ENTITLEMENTS` and\n * must never be resolved again from a stored override (AGL-2133).\n *\n * `totalSiteSizeMb` is the first. It carried a value on all 8 plans and was\n * enforced by nothing — no gate, no meter, no alert. AGL-1107 added it and\n * AGL-1370 deleted its meter and its alert on measurement rather than taste:\n * `measure-node-map.ts` refuses any node map over 900 KB (AGL-678), so the\n * measurable org total tops out at 2.3–20.9% of the cap depending on plan.\n * What survived was a WRITABLE staff override field for a number nothing\n * read, so a support engineer resolving a \"this site is too big\" ticket\n * raised it, got a success and an audit row, and changed nothing.\n *\n * WIRING IT WAS THE OTHER OPTION AND IT IS WORSE. A cap the measurement can\n * reach a fifth of is a gate that refuses nobody — the same \"reports success,\n * does nothing\" shape, moved from the staff dialog to the enforcement point,\n * plus a new hard wall introduced days before launch. The per-site size\n * ceiling that actually binds is AGL-678's 900 KB node map, and it is real.\n *\n * The rollup's `siteSizeMb` MEASUREMENT stays — host-usage and the usage\n * audit read it, and it is a genuine internal signal. Only the entitlement\n * goes.\n */\nexport const RETIRED_ENTITLEMENT_KEYS: ReadonlySet<string> = new Set([\n 'totalSiteSizeMb',\n])\n\n/** Each seat band as `[included, max]`: the max purchases may reach. */\nconst SEAT_BANDS = [\n ['managersPerOrg', 'maxManagersPerOrg'],\n ['membersPerHost', 'maxMembersPerHost'],\n ['datasetsPerOrg', 'maxDatasetsPerOrg'],\n] as const satisfies ReadonlyArray<\n readonly [\n keyof Omit<ResolvedOrgEntitlements, 'features'>,\n keyof Omit<ResolvedOrgEntitlements, 'features'>,\n ]\n>\n\n/**\n * The numeric entitlements that are PRICES rather than caps (AGL-3049): the\n * platform's percentage of a storefront or marketplace sale.\n *\n * Every other numeric key is a band — a count, a size, a monthly or daily\n * allowance — and an uncapped comp lifts it to `UNLIMITED`. These three it\n * must never touch. `transactionFee*Pct` becomes Stripe's\n * `application_fee_amount` at checkout, so an unlimited one is a fee larger\n * than the charge and every sale on the storefront fails; and \"no cap\" has\n * no meaning for a price in the first place. An uncapped workspace pays its\n * plan's percentages, or whatever a per-org override sets them to.\n *\n * Named rather than derived from the `Pct` suffix, so a new numeric key is a\n * band unless someone decides here that it is a price. `plan-comp.spec.ts`\n * pins these three by name and proves every other numeric key is lifted.\n */\nexport const PRICE_ENTITLEMENT_KEYS: ReadonlySet<string> = new Set([\n 'transactionFeePhysicalPct',\n 'transactionFeeDigitalPct',\n // A plugin's percentage of a sale says so in its declaration.\n ...pluginPlanQuotas()\n .filter((declared) => declared.price)\n .map((declared) => declared.key),\n])\n\n/**\n * The entitlements with every band lifted (AGL-3049): `UNLIMITED` on each\n * numeric key but the prices, features untouched. What an uncapped comp in\n * force resolves to — see `isUncappedPlanComp`.\n *\n * Applied LAST, over the plan row, the per-org overrides and the folded\n * add-ons alike: \"every band reads as unlimited\" means an override written\n * before the comp was uncapped does not quietly cap it again. The overrides\n * stay stored, and apply once more if the comp is capped or removed.\n */\nfunction withEveryCapLifted(\n entitlements: ResolvedOrgEntitlements,\n): ResolvedOrgEntitlements {\n const lifted: Record<string, unknown> = { ...entitlements }\n for (const [key, value] of Object.entries(entitlements)) {\n if (typeof value === 'number' && !PRICE_ENTITLEMENT_KEYS.has(key)) {\n lifted[key] = UNLIMITED\n }\n }\n return lifted as unknown as ResolvedOrgEntitlements\n}\n\n/**\n * Effective entitlements for an org: plan defaults with the org doc's\n * per-key overrides applied (features merge key-by-key too), then\n * purchased add-ons stacked on top (AGL-524): `seatAddons.hosts` raises\n * `hostLimit`, `seatAddons.eventCalendar` switches the `eventCalendar`\n * feature on, and every seat add-on a plugin declared through\n * `registerPluginEntitlements` (AGL-2940) widens the quota it names by its\n * per-plan figure per unit and switches its features on — the Aglyn AI\n * add-on (AGL-2896) is the first such declaration. (Seat/dataset add-ons\n * instead fold in at `checkSeatQuota` / `checkDatasetQuota`, where the\n * per-plan hard max clamps them.)\n * Missing or unknown plans resolve as `free`.\n *\n * EVERY NUMERIC KEY of `OrgEntitlements` is an override axis, not a chosen\n * few: the loop below copies any number it finds under the org's\n * `entitlements`, so a contracted `bandwidthGb`, `apiRequestsPerMonth` or\n * `workflowRunsPerMonth` resolves ahead of the plan default exactly as the\n * long-documented `emailSendsPerMonth`, `formsPerHost` and\n * `assistCreditsPerMonth` overrides do. Since 2026-09-07 this is what makes\n * Enterprise's finite fallbacks safe to hold: the row in `PLAN_ENTITLEMENTS`\n * is what an agreement gets before its numbers are written down, and the\n * override is where they are written. `plan-entitlements.spec.ts` proves\n * the precedence on every axis, so a resolver that started honoring a list\n * would fail by the name of the axis it dropped.\n *\n * `hostLimit` is DELIBERATELY unclamped here (AGL-1738): there is no\n * `maxHostsPerOrg` to clamp against, because extra sites are sold flat rather\n * than up to a band. Their bound is the purchase ceiling,\n * `EXTRA_HOSTS_ADDON_MAX`, and a band-shaped `Math.min` added here would\n * silently disable sites the org is invoiced for. See that constant before\n * \"fixing\" this add.\n *\n * `posRegisters` IS NOT ADDED HERE, and this is the fix, not an omission\n * (AGL-1775). The register add-on is priced \"per\n * extra register/location\" and enforced PER SITE, so folding the org-wide\n * purchase into an org-level value every site inherits sold one register and\n * delivered `hostLimit` of them. `seatAddons.posRegisters` is now a POOL and\n * `org.registerAllocations` says which site holds each seat. The number this\n * function returns for `posRegisters` is therefore **the plan's cap alone** —\n * exactly what an unallocated site must resolve to. Per-site capacity comes\n * from `resolveHostRegisterCap(org, hostId)`; nothing else may add the pool\n * back in.\n *\n * (`eventCalendar` is still org-wide on purpose: it is documented and priced\n * as \"one purchase covers every host in the org\". The register add-on never\n * was, which is the whole difference.)\n *\n * AN UNCAPPED COMP IN FORCE (AGL-3049) resolves every band as `UNLIMITED`\n * on top of all of the above — `withEveryCapLifted` — and leaves the\n * features and the fee percentages exactly as resolved.\n */\nexport function resolveOrgEntitlements(\n org: Partial<AglynOrgBilling> | null | undefined,\n): ResolvedOrgEntitlements {\n const resolved = resolveCappedOrgEntitlements(org)\n return isUncappedPlanComp(org) ? withEveryCapLifted(resolved) : resolved\n}\n\n/** `resolveOrgEntitlements` before an uncapped comp lifts anything. */\nfunction resolveCappedOrgEntitlements(\n org: Partial<AglynOrgBilling> | null | undefined,\n): ResolvedOrgEntitlements {\n const defaults = withPluginFeatureDefaults(\n PLAN_ENTITLEMENTS[resolvePlan(org)],\n resolvePlan(org),\n )\n const overrides = org?.entitlements\n let resolved = defaults\n if (overrides) {\n const {\n features: featureOverrides,\n datasetsPerHost: legacyDatasets,\n maxDatasetsPerHost: legacyMaxDatasets,\n ...quotaOverrides\n } = overrides\n const merged = { ...defaults }\n for (const [key, value] of Object.entries(quotaOverrides)) {\n // A RETIRED quota's stored override is dropped, not merged (AGL-2133).\n // This loop copies any numeric key it finds, so removing a key from\n // `PLAN_ENTITLEMENTS` alone would leave the value that staff already\n // wrote onto live org documents still appearing on the resolved\n // entitlements — a number with no default behind it and no reader in\n // front of it, which is a worse artefact than the field that produced\n // it. `tools/scripts/strip-retired-entitlements.mjs` clears them from\n // Firestore; this makes the resolver correct before it has run, and\n // stays correct if a document is restored from an old backup.\n if (RETIRED_ENTITLEMENT_KEYS.has(key)) continue\n if (typeof value === 'number') (merged as any)[key] = value\n }\n // Pre-AGL-240 override docs keyed datasets per host; resolve them into\n // the org keys unless an org-keyed override is present.\n if (typeof legacyDatasets === 'number' && overrides.datasetsPerOrg == null) {\n merged.datasetsPerOrg = legacyDatasets\n }\n if (\n typeof legacyMaxDatasets === 'number' &&\n overrides.maxDatasetsPerOrg == null\n ) {\n merged.maxDatasetsPerOrg = legacyMaxDatasets\n }\n // A seat band's max never sits below its included count. The max is the\n // ceiling PURCHASES stop at, and `checkSeatQuota` clamps the limit to it —\n // so a staff override of Team seats to 2 on Free (max 1) resolved back to\n // 1 everywhere: the members card, the invite gate and the acceptance\n // transaction all refused the second manager the override granted. An\n // override of the included count is a grant; the band moves with it.\n for (const [included, max] of SEAT_BANDS) {\n merged[max] = Math.max(merged[max], merged[included])\n }\n resolved = {\n ...merged,\n features: { ...defaults.features, ...featureOverrides },\n }\n }\n const purchased = resolvePurchasedAddons(org)\n const extraHosts = Math.max(0, purchased.hosts ?? 0)\n const eventCalendar = (purchased.eventCalendar ?? 0) >= 1\n // A declared add-on's quota is added AFTER the per-org override, so a\n // contracted figure and a purchased add-on stack the way a raised\n // `hostLimit` and purchased sites do. The per-unit figure is the PLAN's,\n // read off the effective plan rather than the stored one, so a dead\n // subscription that resolved to free adds free's figure.\n const folded = foldPluginSeatAddons(resolved, purchased, resolvePlan(org))\n if (!extraHosts && !eventCalendar && folded === resolved) return resolved\n return {\n ...folded,\n hostLimit: folded.hostLimit + extraHosts,\n features: {\n ...folded.features,\n ...(eventCalendar ? { eventCalendar: true } : {}),\n },\n }\n}\n\n/**\n * How many units of a plugin-declared seat add-on a purchase map counts as\n * (AGL-2940): the stored quantity, capped at the declaration's `maxUnits`\n * — 1 for an org-wide add-on, where a doubled webhook item or a hand edit\n * above one is still one purchase. A non-number, `NaN`, a negative, or a\n * key nothing declared is none.\n */\nexport function pluginSeatAddonUnits(\n addons: OrgSeatAddons | null | undefined,\n key: string,\n): number {\n const declared = pluginSeatAddon(key)\n if (!declared) return 0\n const quantity = Number((addons as Record<string, unknown> | null)?.[key] ?? 0)\n if (!Number.isFinite(quantity) || quantity < 1) return 0\n const units = Math.floor(quantity)\n return declared.maxUnits != null ? Math.min(units, declared.maxUnits) : units\n}\n\n/**\n * Whether a plugin-declared seat add-on currently applies to the org: bought,\n * and on a subscription that is still paying for it — the one reading every\n * surface that shows or charges for the add-on goes through, so a dead\n * subscription drops the quota and the feature together.\n */\nexport function hasPluginSeatAddon(\n org: Partial<AglynOrgBilling> | null | undefined,\n key: string,\n): boolean {\n return pluginSeatAddonUnits(resolvePurchasedAddons(org), key) > 0\n}\n\n/**\n * Every declared seat add-on the org holds, folded in catalog order: the\n * quota each names gains its per-plan figure per unit, and its features\n * switch on. Returns the input object untouched when nothing applies, so a\n * caller can tell \"nothing folded\" from \"folded to the same numbers\".\n */\nfunction foldPluginSeatAddons(\n resolved: ResolvedOrgEntitlements,\n purchased: OrgSeatAddons,\n plan: OrgPlan,\n): ResolvedOrgEntitlements {\n let out = resolved\n for (const addon of listPluginSeatAddons()) {\n const units = pluginSeatAddonUnits(purchased, addon.key)\n if (!units) continue\n const next: ResolvedOrgEntitlements = { ...out, features: { ...out.features } }\n if (addon.quota) {\n const perUnit = addon.quota.perUnitByPlan[plan] ?? 0\n const bag = next as unknown as Record<string, unknown>\n const current = Number(bag[addon.quota.key] ?? 0)\n bag[addon.quota.key] = (Number.isFinite(current) ? current : 0) + perUnit * units\n }\n const features = next.features as unknown as Record<string, boolean>\n for (const feature of addon.features ?? []) features[feature] = true\n out = next\n }\n return out\n}\n\n/**\n * The plan row with every plugin-declared feature's default filled in\n * (AGL-2940). A feature the plan tables already carry keeps the table's\n * answer; a feature only a declaration knows reads its `defaultByPlan`, or\n * `false`. Returns the row itself when no declaration adds anything.\n */\nfunction withPluginFeatureDefaults(\n row: ResolvedOrgEntitlements,\n plan: OrgPlan,\n): ResolvedOrgEntitlements {\n const features = row.features as unknown as Record<string, boolean | undefined>\n let out: Record<string, boolean | undefined> | undefined\n for (const declared of listPluginFeatures()) {\n if (features[declared.key] !== undefined) continue\n out = out ?? { ...features }\n out[declared.key] = declared.defaultByPlan?.[plan] ?? false\n }\n return out\n ? { ...row, features: out as unknown as ResolvedOrgEntitlements['features'] }\n : row\n}\n\n/**\n * The org's POS register pool (AGL-1775): how many seats were purchased, how\n * many are assigned to a site, and how many are left to assign.\n *\n * `purchased` runs through `resolvePurchasedAddons`, so a dead subscription\n * empties the pool exactly as it empties every other add-on — the seats stop\n * counting when they stop billing.\n *\n * `byHost` is the SANITISED allocation, and it never sums past `purchased`.\n * The write path validates before it stores, so an over-allocated map means\n * the data is stale or corrupt (a purchase reduced without the allocation\n * being trimmed, a partial write, a hand edit). Both directions of getting\n * that wrong cost money, so it is resolved rather than trusted: hosts are\n * walked in sorted-id order and granted what remains of the pool, which is\n * deterministic — the same host gets the same answer on every reader — and\n * cannot hand out capacity nobody paid for.\n *\n * Every value is coerced: a non-number, `NaN`, `Infinity` or a negative reads\n * as zero seats. An `Infinity` in this map would otherwise become an\n * unbounded per-site register cap, which is the one outcome the guard in\n * `resolveHostRegisterCap` exists to make impossible.\n */\nexport function resolveRegisterSeatPool(\n org: Partial<AglynOrgBilling> | null | undefined,\n): {\n purchased: number\n allocated: number\n available: number\n byHost: Record<string, number>\n} {\n const rawPurchased = resolvePurchasedAddons(org).posRegisters\n const purchased =\n Number.isFinite(rawPurchased) && (rawPurchased as number) > 0\n ? Math.floor(rawPurchased as number)\n : 0\n const raw = org?.registerAllocations\n const byHost: Record<string, number> = {}\n let allocated = 0\n if (raw && typeof raw === 'object') {\n for (const hostId of Object.keys(raw).sort()) {\n if (allocated >= purchased) break\n const value = Number((raw as Record<string, unknown>)[hostId])\n if (!Number.isFinite(value) || value <= 0) continue\n const seats = Math.min(Math.floor(value), purchased - allocated)\n if (seats <= 0) continue\n byHost[hostId] = seats\n allocated += seats\n }\n }\n return { purchased, allocated, available: purchased - allocated, byHost }\n}\n\n/**\n * How many POS registers one SITE may run (AGL-1775): the plan's per-site cap\n * plus whatever the org has assigned to that site out of the pool.\n *\n * THE GUARD, and the reason this is a function rather than a lookup. An\n * unallocated host — no entry in `registerAllocations`, a missing map, a\n * missing org, an org still loading — resolves to `resolveOrgEntitlements(org)\n * .posRegisters`, which since AGL-1775 is the PLAN's cap and nothing else.\n * Never the pooled total, and never `Infinity` unless the plan itself is\n * `UNLIMITED` (enterprise, where an unbounded cap is what was sold).\n *\n * `checkQuota(undefined)` resolving to the Free tier is the lesson this\n * inverts: an absent input there quietly answered a question in the\n * PERMISSIVE direction on the day it mattered. Here the absent input answers\n * in the direction of the plan, which is the conservative one — a site that\n * has been assigned seats and whose allocation failed to load runs fewer\n * registers than it paid for, which the console can say out loud, rather than\n * more than it paid for, which nothing would ever notice.\n *\n * There is deliberately no separate read: the allocation lives on the org doc\n * beside the entitlements, so a caller holding one holds the other.\n */\nexport function resolveHostRegisterCap(\n org: Partial<AglynOrgBilling> | null | undefined,\n hostId: string | null | undefined,\n): number {\n const planCap = resolveOrgEntitlements(org).posRegisters\n if (!hostId) return planCap\n // An UNLIMITED plan cap plus anything is still UNLIMITED; short-circuit so\n // `Infinity + n` never has to be reasoned about downstream.\n if (planCap === UNLIMITED) return UNLIMITED\n return planCap + (resolveRegisterSeatPool(org).byHost[hostId] ?? 0)\n}\n\n/**\n * The org's COLLABORATOR seat pool (AGL-2439): how many per-site collaborator\n * seats were purchased, how many are assigned to a site, and how many are\n * left to assign.\n *\n * The AGL-1775 register mechanism, applied to the key it was never applied to.\n * `seatAddons.members` is bought once, org-wide; `membersPerHost` is enforced\n * PER SITE. Adding one to the other handed the whole purchase to every site,\n * so an org with 20 sites that bought ONE extra collaborator seat received 20.\n * The quantity is now the size of an org-level pool and\n * `org.collaboratorAllocations` says which site holds each seat.\n *\n * Everything about the resolution is deliberately identical to\n * `resolveRegisterSeatPool` — the same clamp, the same sorted-id determinism,\n * the same coercion of a non-number / `NaN` / `Infinity` / negative to zero.\n * This is ONE mechanism used twice, not a second mechanism that happens to\n * rhyme: a reader who has understood the register pool has understood this,\n * and a fix to one is a fix to both.\n *\n * `purchased` runs through `resolvePurchasedAddons`, so a dead subscription\n * empties the pool exactly as it empties every other add-on.\n */\nexport function resolveCollaboratorSeatPool(\n org: Partial<AglynOrgBilling> | null | undefined,\n): {\n purchased: number\n allocated: number\n available: number\n byHost: Record<string, number>\n} {\n const rawPurchased = resolvePurchasedAddons(org).members\n const purchased =\n Number.isFinite(rawPurchased) && (rawPurchased as number) > 0\n ? Math.floor(rawPurchased as number)\n : 0\n const raw = org?.collaboratorAllocations\n const byHost: Record<string, number> = {}\n let allocated = 0\n if (raw && typeof raw === 'object') {\n for (const hostId of Object.keys(raw).sort()) {\n if (allocated >= purchased) break\n const value = Number((raw as Record<string, unknown>)[hostId])\n if (!Number.isFinite(value) || value <= 0) continue\n const seats = Math.min(Math.floor(value), purchased - allocated)\n if (seats <= 0) continue\n byHost[hostId] = seats\n allocated += seats\n }\n }\n return { purchased, allocated, available: purchased - allocated, byHost }\n}\n\n/**\n * How many console COLLABORATORS one SITE may hold (AGL-2439): the plan's\n * per-site allowance plus whatever the org has assigned to that site out of\n * the pool, clamped to the plan's hard `maxMembersPerHost`.\n *\n * THE GUARD, and the reason this is a function rather than a lookup. An\n * unallocated host — no entry in `collaboratorAllocations`, a missing map, a\n * missing org, an org still loading — resolves to the PLAN's `membersPerHost`\n * and nothing else. Never the pooled total, and never `Infinity` unless the\n * plan itself is `UNLIMITED` (enterprise, where an unbounded cap is what was\n * sold). `checkQuota(undefined)` resolving to the Free tier is the lesson\n * this inverts: the absent input answers in the direction of the plan, which\n * is the conservative one.\n *\n * The clamp is what `checkSeatQuota` always did and is kept: collaborator\n * seats are sold up to a BAND (`maxMembersPerHost`), unlike registers, which\n * are sold flat. Assigning more pool seats to a site than the band allows\n * cannot lift it past the band — the org must upgrade the plan.\n *\n * There is deliberately no separate read: the allocation lives on the org doc\n * beside the entitlements, so a caller holding one holds the other.\n */\nexport function resolveHostCollaboratorCap(\n org: Partial<AglynOrgBilling> | null | undefined,\n hostId: string | null | undefined,\n): number {\n const entitlements = resolveOrgEntitlements(org)\n const included = entitlements.membersPerHost\n const maxSeats = entitlements.maxMembersPerHost\n if (!hostId) return Math.min(included, maxSeats)\n // An UNLIMITED plan cap plus anything is still UNLIMITED; short-circuit so\n // `Infinity + n` never has to be reasoned about downstream.\n if (included === UNLIMITED) return UNLIMITED\n const assigned = resolveCollaboratorSeatPool(org).byHost[hostId] ?? 0\n return Math.min(included + assigned, maxSeats)\n}\n\n/**\n * Quota answer for a site's console COLLABORATORS (AGL-2439) — the\n * host-scoped `checkSeatQuota`, in the same `SeatQuotaResult` shape so the\n * existing call sites and refusal copy read identically.\n *\n * `checkSeatQuota(org, 'members', …)` is WRONG for a site now and this exists\n * so nobody has to remember why: since AGL-2439 that helper no longer folds\n * the org-wide purchase into a per-site number, so it answers the PLAN's cap\n * and would refuse a site the seats it holds.\n *\n * ## THE GRANDFATHER BOUNDARY — explicit, and this field is it\n *\n * The cap is corrected, and no org already above it is evicted or locked\n * out.\n *\n * The boundary is drawn between two different questions, and they are\n * different lines of code rather than one number used for two purposes:\n *\n * `allowed` — may this site take on ANOTHER collaborator? Bound\n * by `limit`, the corrected cap. This is the ONLY\n * thing the cap decides.\n * `retainedOverCap` — how many seats this site holds ABOVE `limit`. They\n * are RETAINED. Nothing in this repo revokes a\n * collaborator for being over a cap, and nothing may\n * be added that does: the cap binds ALLOCATION, never\n * ACCESS.\n *\n * So an over-cap site keeps every collaborator it has, cannot add another,\n * and returns to normal by attrition, by buying pool seats and assigning them\n * here, or by upgrading — at which point `limit` recomputes from the new plan\n * or allocation and the retention lapses on its own. That is what \"they keep\n * what they have until they next change plan or seat count\" means, resolved\n * rather than stored: a stored floor would need a backfill over live orgs and\n * would be a defaulted field on a `merge`-written document, which is the\n * converter hazard that destroys the real value it was meant to protect.\n *\n * `retainedOverCap` is non-zero ONLY when the site is already over. It is not\n * headroom and cannot be spent: it is a count the console says out loud so\n * the customer sees why the next Add is refused while nobody loses access.\n */\nexport function checkHostCollaboratorQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n hostId: string | null | undefined,\n currentUsage: number,\n): SeatQuotaResult & {\n /** Seats held ABOVE `limit`, retained and never revoked (the grandfather). */\n retainedOverCap: number\n /** Pool seats the org has assigned to THIS site. */\n assignedSeats: number\n} {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const included = entitlements.membersPerHost\n const maxSeats = entitlements.maxMembersPerHost\n const addonPriceUsd = pricing.extraCollaboratorMonthlyUsd\n const assignedSeats = resolveCollaboratorSeatPool(org).byHost[hostId ?? ''] ?? 0\n const limit = resolveHostCollaboratorCap(org, hostId)\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n included,\n purchased: assignedSeats,\n maxSeats,\n upgradeRequired: addonPriceUsd === null || limit >= maxSeats,\n addonPriceUsd,\n // THE GRANDFATHER. `Math.max(0, …)` so a site under its cap reports zero\n // rather than a negative that a caller could add and turn into headroom.\n retainedOverCap: Math.max(0, currentUsage - limit),\n assignedSeats,\n }\n}\n\n/**\n * Quota answer for a site's POS registers (AGL-1775) — the register-shaped\n * `checkQuota`, in the same `{ allowed, limit, remaining }` shape so callers\n * read identically.\n *\n * `checkQuota(org, 'posRegisters', used)` is WRONG for registers now and this\n * exists so nobody has to remember why: that helper resolves the org-level\n * value, which is the plan cap with no pool in it, so it would refuse a site\n * the seats it holds. Everything that gates a register creation calls this.\n */\nexport function checkHostRegisterQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n hostId: string | null | undefined,\n currentUsage: number,\n): { allowed: boolean; limit: number; remaining: number } {\n const limit = resolveHostRegisterCap(org, hostId)\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n }\n}\n\n/**\n * Platform transaction fee % for a storefront sale (AGL-278): resolved\n * from the effective plan (with per-org overrides) by product type.\n * Digital and service sales use the digital rate; AGL-307 turns this into\n * the Stripe Connect `application_fee_amount` at charge time.\n */\nexport function resolveTransactionFeePct(\n org: Partial<AglynOrgBilling> | null | undefined,\n productType: 'physical' | 'digital' | 'service',\n): number {\n const entitlements = resolveOrgEntitlements(org)\n const key =\n productType === 'physical'\n ? 'transactionFeePhysicalPct'\n : 'transactionFeeDigitalPct'\n // A MALFORMED value falls back to the PLAN'S OWN rate, never to zero\n // (AGL-2114). `resolveOrgEntitlements` merges any numeric override\n // indistinguishably from the default, and `strictNullChecks` is off\n // repo-wide, so a `NaN`, an `Infinity`, a stringly-typed number or a\n // negative can reach here from a hand-edited or partially-written org doc.\n // This used to answer 0 for every one of them — silently zeroing the\n // platform's cut on a PAYING merchant's storefront, which on a destination\n // charge is not \"no take rate\" but a loss (see below).\n //\n // The sibling `resolveMarketplaceFeePct` has forbidden exactly this since\n // AGL-1543, in as many words: \"A malformed override must never zero the\n // platform's cut.\" This is the storefront half of the same rule. The plan\n // table is a literal and cannot itself be malformed, so it is the honest\n // floor — and it preserves the tiers whose 0% is a DELIBERATE, advertised\n // rate rather than a data fault.\n const raw = entitlements[key]\n const pct =\n typeof raw === 'number' && Number.isFinite(raw) && raw >= 0\n ? raw\n : PLAN_ENTITLEMENTS[resolvePlan(org)][key]\n if (Number.isFinite(pct) && pct > 0) return pct\n // The free plan's 0% prices a plan that CANNOT SELL: `features.commerce` is\n // false there, so on an ordinary free org this rate is never consulted. It\n // IS consulted for the one org shape that can reach a storefront on the free\n // entitlement set — a per-org `features.commerce` override — and 0% on a\n // DESTINATION charge is not \"no take rate\", it is a loss: Stripe's\n // processing fee (2.9% + 30¢) and any $15 dispute fee are debited from the\n // PLATFORM's balance, so every sale costs Aglyn money (AGL-2071).\n //\n // `marketplaceFeePct: 30` on the free row is the same decision already made\n // for marketplace selling, and its comment says so in as many words: the\n // rate exists to price \"an org GRANTED selling via a per-org feature\n // override\". This is the missing equivalent for storefront sales, expressed\n // here rather than in the table because the plan CARDS render the table —\n // `billing-plan-cards.component.tsx:444` — and a Free card advertising a\n // transaction fee for selling it does not permit would be a pricing lie.\n //\n // A staff fee override wins even at 0: `resolveOrgEntitlements` merges any\n // numeric override indistinguishably from the default, so the raw doc is\n // what separates \"nobody set a rate\" from a deliberately comped one. That\n // decision has a name and a reason attached (`org-override-reason.ts`), and\n // this floor must not silently overrule it.\n if (\n resolvePlan(org) === 'free' &&\n entitlements.features.commerce &&\n typeof org?.entitlements?.[key] !== 'number'\n ) {\n return PLAN_ENTITLEMENTS.starter[key]\n }\n return 0\n}\n\n/**\n * Stripe's processing cost on ONE destination-charge sale, in cents\n * (AGL-2152) — a storefront order, a booking deposit — the dearest enabled\n * method's percentage plus the fixed 30¢, rounded UP on the percentage so the\n * answer is never a cent short of what Stripe actually debits.\n *\n * WHO PAYS THIS. Every storefront charge is a DESTINATION charge:\n * `payment_intent_data[transfer_data][destination]` names the merchant's\n * connected account and no `transfer_data[amount]` is sent, so Stripe moves\n * the whole charge to the merchant and debits its processing fee from the\n * PLATFORM's balance. Aglyn's net on an order is therefore\n *\n * application_fee_amount − (processing% × charge + 30¢)\n *\n * and with `application_fee_amount` at 0 — which is what\n * `transactionFeePhysicalPct: 0` produced on Pro, Business, Scale, Advanced,\n * Agency and Enterprise — that net is NEGATIVE on every single sale. A flat\n * percentage cannot repair it either: Starter's 2% is below even the cheapest\n * processing rate, so Starter lost money at every order size too, and any rate\n * above it still loses until the order clears `30¢ ÷ (take − processing)`. The\n * fixed 30¢ is why a percentage alone can never reach break-even on small\n * orders, and why this returns a CENTS amount rather than a rate.\n *\n * WHY THE DEAREST METHOD AND NOT 2.9%. The shopper picks how to pay AFTER the\n * session is created, and `application_fee_amount` is a fixed number Stripe\n * will not let us revise once the charge settles. No storefront path pins\n * `payment_method_types`, so the platform's payment method configuration\n * decides the set — and per AGL-2343 that configuration has `klarna` and\n * `affirm` on beside the card family. Pricing this at the card rate would be\n * wrong exactly on the orders where it cost the most, which is the same\n * reasoning the marketplace listing floor applies; both are defined from the\n * SAME processing constants so the two cannot drift.\n *\n * THE ONE-LINE LEVER. On a card-family order this over-recovers by the spread\n * between the two rates. Pinning `payment_method_types` on the storefront\n * sessions to the card family would make 2.9% the true binding rate, and\n * `SALE_PROCESSING_PERCENT` below is then the single identifier to\n * repoint. That is a product decision about which payment methods a storefront\n * offers, not this one; it is recorded on AGL-2152 and in the Pricing Decision\n * Log so it can be taken deliberately.\n */\n// `SALE_PROCESSING_PERCENT` and `SALE_PROCESSING_FIXED_CENTS` are declared\n// beside the processing constants they are defined from, further down this\n// file — a `const` cannot be read before its declaration is evaluated, and\n// this function is only ever CALLED, never evaluated at import.\nexport function saleProcessingCostCents(chargeCents: number): number {\n if (!Number.isFinite(chargeCents) || chargeCents <= 0) return 0\n return (\n Math.ceil((chargeCents * SALE_PROCESSING_PERCENT) / 100) +\n SALE_PROCESSING_FIXED_CENTS\n )\n}\n\n/**\n * The Stripe Connect `application_fee_amount` for ONE storefront sale\n * (AGL-2152) — the platform's advertised take PLUS Stripe's processing cost,\n * passed through at cost.\n *\n * fee = take%(feeBaseCents) + processing%(chargeCents) + 30¢\n *\n * so Aglyn's net per order is AT LEAST the advertised take, at every order\n * size down to Stripe's own 50¢ charge minimum, and never negative. The\n * September-1 price lock was lifted for this one change; see the Pricing\n * Decision Log entry of 2026-08-19. No advertised PLATFORM rate moved — a 0% tier is still 0%\n * platform take — what changed is that the card cost stops being absorbed\n * silently by Aglyn.\n *\n * `feeBaseCents` is the GOODS the take is a cut of (discounts already applied,\n * tax and shipping excluded — that has always been the basis, AGL-2317).\n * `chargeCents` is what Stripe will actually run the card for, which is goods\n * + tax + shipping, and defaults to `feeBaseCents` for the paths where they\n * are the same number. Passing the goods figure for a charge that also carries\n * tax would under-recover by the processing rate on the difference.\n *\n * Clamped to `chargeCents` because Stripe rejects an application fee larger\n * than the charge. That clamp can only bind below ~32¢, under Stripe's own 50¢\n * charge minimum, so it never trims a real order's fee.\n */\nexport function resolveTransactionFeeCents(\n org: Partial<AglynOrgBilling> | null | undefined,\n productType: 'physical' | 'digital' | 'service',\n feeBaseCents: number,\n chargeCents: number = feeBaseCents,\n): number {\n const charge =\n Number.isFinite(chargeCents) && chargeCents > 0 ? Math.round(chargeCents) : 0\n if (charge <= 0) return 0\n const base =\n Number.isFinite(feeBaseCents) && feeBaseCents > 0\n ? Math.round(feeBaseCents)\n : 0\n const pct = resolveTransactionFeePct(org, productType)\n // The `Math.max(1, …)` every door already applied: a rate above zero on\n // goods the shopper pays for is a fee however small it rounds to.\n const take = pct > 0 && base > 0 ? Math.max(1, Math.round((base * pct) / 100)) : 0\n return Math.min(charge, take + saleProcessingCostCents(charge))\n}\n\n/**\n * Stripe's processing cost on a RECURRING charge of `amountCents`, as the\n * percent a Subscription's `application_fee_percent` can carry (AGL-2655).\n *\n * A Stripe Subscription accepts no `application_fee_amount` — only\n * `application_fee_percent`, a rate with at most two decimal places — so the\n * fixed 30¢ that `saleProcessingCostCents` adds in cents has to be\n * folded into the rate. `(rate × amount + fixed) ÷ amount` is, with the\n * amount cancelled,\n *\n * percent = rate + fixed ÷ amount\n *\n * which is why a smaller recurring price carries a HIGHER percent: the 30¢\n * is a bigger share of it. At 6% + 30¢ a $10 price carries 9%, $25 carries\n * 7.2% and $100 carries 6.3%.\n *\n * Computed in hundredths of a percent on integers and rounded UP to the next\n * hundredth. Up, for the reason the one-time helper rounds its percentage\n * up: rounding to nearest would leave the recovery a fraction of a cent\n * short of what Stripe debits half the time, and Stripe rejects a third\n * decimal outright, so the only honest direction is over. Integers, because\n * `0.09 * 100` is not `9` in floating point and a percent one ulp above a\n * whole hundredth would round up to the next one. The same two constants as\n * the one-time path, so repointing `SALE_PROCESSING_PERCENT` moves\n * both surfaces together. Zero for a charge that is not a charge.\n */\nexport function saleProcessingPassThroughPercent(\n amountCents: number,\n): number {\n const amount =\n Number.isFinite(amountCents) && amountCents > 0 ? Math.round(amountCents) : 0\n if (amount <= 0) return 0\n const rateHundredths = Math.round(SALE_PROCESSING_PERCENT * 100)\n // 30¢ ÷ amount is a fraction; × 100 makes it a percent, × 100 again makes\n // it hundredths of one.\n const fixedHundredths = (SALE_PROCESSING_FIXED_CENTS * 10_000) / amount\n return Math.min(100, Math.ceil(rateHundredths + fixedHundredths) / 100)\n}\n\n/**\n * The `application_fee_percent` for ONE storefront subscription (AGL-2655):\n * the plan's advertised take PLUS Stripe's processing cost as a percent —\n * the recurring twin of `resolveTransactionFeeCents`, under the same rule.\n * Every tier carries the pass-through, a 0% tier included, because the\n * tier's 0% is a platform take and the pass-through is not one; it is what\n * Stripe charges to take the payment, which on a destination charge is\n * debited from the platform's balance whatever the tier.\n *\n * `amountCents` is the recurring items the percent will be applied to —\n * the goods after any discount, tax and shipping excluded, the AGL-2317\n * basis. Stripe applies the percent to the whole invoice and the paid-invoice\n * correction scales it back to items, so sizing on items is what makes the\n * recovery come out at cost rather than under.\n *\n * Composed in hundredths so two decimals stay two decimals (`2 + 6.34` is\n * not always `8.34` in floating point), and capped at 100, Stripe's ceiling\n * for the parameter — the recurring twin of the clamp to the charge.\n */\nexport function resolveSubscriptionFeePercent(\n org: Partial<AglynOrgBilling> | null | undefined,\n productType: 'physical' | 'digital' | 'service',\n amountCents: number,\n): number {\n const amount =\n Number.isFinite(amountCents) && amountCents > 0 ? Math.round(amountCents) : 0\n if (amount <= 0) return 0\n const take = resolveTransactionFeePct(org, productType)\n const passThrough = saleProcessingPassThroughPercent(amount)\n return Math.min(\n 100,\n (Math.round(take * 100) + Math.round(passThrough * 100)) / 100,\n )\n}\n\n/**\n * Platform take rate % for a MARKETPLACE listing sale (AGL-1543), priced\n * off the SELLER org. Resolved through `resolveOrgEntitlements` — so a\n * dead subscription prices as free-plan (30%) even while the stale `plan`\n * field still names a paid tier, and a negotiated per-org\n * `entitlements.marketplaceFeePct` override wins over the plan default.\n * Resolve per request from the org doc; never cache the answer.\n *\n * An out-of-range or missing value falls back to the FREE-plan rate — the\n * conservative direction. A malformed override must never zero the\n * platform's cut.\n */\nexport function resolveMarketplaceFeePct(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number {\n const pct = takeRatePctOf(resolveOrgEntitlements(org))\n if (pct !== null) return pct\n const fallback = takeRatePctOf(PLAN_ENTITLEMENTS.free)\n if (fallback !== null) return fallback\n // No plan carries a take rate at all: the marketplace plugin's declaration\n // did not compile in. Pricing a sale at 0% would hand the whole charge to\n // the seller, so the sale is refused instead.\n throw new Error(\n `no plan declares \"${TAKE_RATE_KEY}\" — the marketplace take rate ` +\n 'is declared by the marketplace plugin (plugins.config.json)',\n )\n}\n\n/**\n * The marketplace take rate's key. Declared by the marketplace plugin\n * (AGL-3080); read here by key because the storefront and the revenue report\n * price a marketplace sale from core.\n */\nconst TAKE_RATE_KEY = 'marketplaceFeePct'\n\n/** A row's take rate when it is a usable percentage, else `null`. */\nfunction takeRatePctOf(entitlements: object): number | null {\n const pct = (entitlements as Record<string, unknown>)[TAKE_RATE_KEY]\n return typeof pct === 'number' && Number.isFinite(pct) && pct >= 0 && pct <= 100\n ? pct\n : null\n}\n\n/**\n * The marketplace take rate that breaks even LATEST, derived from the plan\n * table rather than copied from it (AGL-2343).\n *\n * The lowest rate is the binding one: a smaller cut of the same price against\n * the same processing cost needs a dearer listing to cover it, so advice quoted\n * at this band holds for every publisher on every plan. Derived on each call\n * because a hand-written second copy of a rate is exactly the artifact that\n * decays into wrong money copy shown to a publisher — the same reason\n * `planGrantingFeature` reads the table instead of a map.\n */\nexport function bindingMarketplaceFeePct(): number {\n return Math.min(\n ...Object.values(PLAN_ENTITLEMENTS)\n .map((plan) => takeRatePctOf(plan) ?? 0)\n .filter((pct) => Number.isFinite(pct) && pct > 0),\n )\n}\n\n/**\n * What one destination-charge sale costs the PLATFORM to process (AGL-2343):\n * a storefront order, a booking deposit, a marketplace listing. On a\n * destination charge Stripe debits its fee from the PLATFORM's balance, so\n * every surface that sells through the platform's account prices its take\n * against these.\n *\n * NONE OF THESE IS AN AGLYN PRICE. They are Stripe's published US rates, and\n * they exist only as inputs to a platform take and to figures a seller reads;\n * changing one never changes a price list.\n *\n * THE BNPL RATE IS THE ONE THAT BINDS, and it is not hypothetical: the live\n * default payment method configuration has `klarna` and `affirm` enabled\n * alongside `card`, `cashapp`, `link`, `amazon_pay` and `apple_pay`, and no\n * checkout session pins `payment_method_types`, so a buyer may pay by either.\n * A cost computed from the cheapest method would be wrong exactly when it\n * mattered.\n */\nexport const CARD_PROCESSING_PERCENT = 2.9\n/** Klarna/Affirm, roughly. See the note above — this is the binding one. */\nexport const BNPL_PROCESSING_PERCENT = 6\n/** Stripe's per-transaction fixed component, in cents. */\nexport const PROCESSING_FIXED_CENTS = 30\n\n/**\n * The processing rate a storefront or booking destination charge is priced\n * against (AGL-2152), and the one identifier to repoint if those sessions'\n * payment method set changes.\n *\n * Deliberately its OWN name rather than a direct use of the binding rate,\n * while being defined as it: every seller shares Stripe's published rates and\n * today shares the platform's payment method configuration, but each surface\n * is a separate product decision. A future choice to pin\n * `payment_method_types` on storefront sessions to the card family — and so\n * to recover at `CARD_PROCESSING_PERCENT` — must not silently lower the\n * marketplace listing floor, which is derived from the binding rate for a\n * different flow.\n */\nexport const SALE_PROCESSING_PERCENT = BNPL_PROCESSING_PERCENT\n/** Stripe's per-transaction fixed component, in cents. Not rate-dependent. */\nexport const SALE_PROCESSING_FIXED_CENTS = PROCESSING_FIXED_CENTS\n\n/**\n * The cheapest plan whose BASE features include `feature`, in ladder order —\n * the answer to \"which plan do I need for this?\", which is the one thing an\n * upsell has to say and the one thing no gate can hard-code without drifting\n * from the table it is quoting.\n *\n * Returns `undefined` when NO plan carries it on the base tier. That is not\n * an error: `eventCalendar` is an add-on purchased through\n * `resolveOrgEntitlements`' override path and is false on all eight tiers, so\n * \"upgrade to X\" is the wrong sentence for it. Callers must handle undefined\n * rather than defaulting to a tier name.\n *\n * Derived from PLAN_ENTITLEMENTS on every call rather than from a\n * hand-written map: a table of \"feature → plan\" is exactly the artifact that\n * decays silently when a tier's flags are re-cut, and it would decay into\n * pricing copy shown to a customer.\n *\n * Ladder order is `SELF_SERVE_PLANS` then `enterprise` — the same order the\n * plan grid renders — so an enterprise-only feature (`ssoEnabled`) reports\n * Enterprise rather than nothing.\n */\nexport function planGrantingFeature(\n feature: keyof OrgFeatureFlags,\n): OrgPlan | undefined {\n return [...SELF_SERVE_PLANS, 'enterprise' as OrgPlan].find((plan) =>\n Boolean(PLAN_ENTITLEMENTS[plan].features[feature]),\n )\n}\n\n/**\n * The plan name an upsell should name, ready to interpolate — \"Business\",\n * or undefined when {@link planGrantingFeature} has no answer.\n */\nexport function planLabelGrantingFeature(\n feature: keyof OrgFeatureFlags,\n): string | undefined {\n const plan = planGrantingFeature(feature)\n return plan ? PLAN_LABELS[plan] : undefined\n}\n\n/**\n * True when the org's plan (or overrides) enables the boolean feature. A\n * key outside `OrgFeatureFlags` is one a plugin declared (AGL-2940) and is\n * answered the same way.\n */\nexport function checkEntitlement(\n org: Partial<AglynOrgBilling> | null | undefined,\n feature: keyof OrgFeatureFlags | (string & {}),\n): boolean {\n const features = resolveOrgEntitlements(org).features as unknown as Record<\n string,\n boolean | undefined\n >\n return Boolean(features[feature])\n}\n\n/**\n * The branding profile shape and the platform's own fallback, re-exported\n * for every caller that reads them alongside the entitlement gate.\n *\n * They are DEFINED in `platform-brand.ts`, beside the environment-driven\n * literals they are assembled from. A published tenant page renders the\n * attribution badge from `PLATFORM_BRANDING_PROFILE` and needs nothing else\n * in this module; importing it from here would put the whole plan, quota and\n * entitlement table into every anonymous visitor's first load.\n */\nexport type { ResolvedBrandingProfile } from './platform-brand'\nexport { PLATFORM_BRANDING_PROFILE } from './platform-brand'\n\n/** A non-empty trimmed string, else undefined — blanks never override a default. */\nfunction cleanBrandString(value: string | undefined): string | undefined {\n const trimmed = typeof value === 'string' ? value.trim() : ''\n return trimmed.length ? trimmed : undefined\n}\n\n/**\n * The effective brand for an org (White-Label Phase 1). EVERY branded\n * surface — published-site branding, transactional email, console chrome —\n * MUST read the brand through this one resolver so a white-label org can\n * never partly-render as Aglyn (the multi-surface drift that dogged\n * `removeBranding`, which each surface re-derived on its own).\n *\n * When the org carries the `whiteLabel` entitlement (Agency tier, or an\n * Enterprise per-org override), the org's `brandingProfile` values win and\n * any field it leaves blank falls back to the Aglyn default. Without the\n * entitlement the stored profile is ignored entirely and the full Aglyn\n * brand is returned — so an org that white-labeled and then downgraded\n * reverts cleanly.\n *\n * An org WITH the entitlement that has stored no brand value at all gets the\n * full Aglyn brand too (AGL-3440). The entitlement is the right to a brand,\n * not a brand: until the org sets one, every name it shows is the platform's\n * anyway, so the white-label profile's blanks — no logo, no support link, no\n * home — conceal nothing and only strip the platform's own header and footer\n * from mail whose every line already names the platform. Every Enterprise\n * org carries the entitlement, Aglyn's own included. One field set, and the\n * rules below apply.\n */\nexport function resolveBrandingProfile(\n org: Partial<AglynOrgBilling> | null | undefined,\n): ResolvedBrandingProfile {\n if (!checkEntitlement(org, 'whiteLabel')) return PLATFORM_BRANDING_PROFILE\n const profile = (org?.brandingProfile ?? {}) as OrgBrandingProfile\n if (\n !Object.values(profile).some(\n (value) => cleanBrandString(value as string | undefined) !== undefined,\n )\n ) {\n return PLATFORM_BRANDING_PROFILE\n }\n return {\n productName:\n cleanBrandString(profile.productName) ??\n PLATFORM_BRANDING_PROFILE.productName,\n // NO FALLBACK — and this became load-bearing the moment\n // `PLATFORM_BRANDING_PROFILE.logoUrl` stopped being null. Inheriting it\n // would stamp the PLATFORM's mark on a white-label org's published sites,\n // which is precisely the disclosure white-label is sold to prevent. A\n // white-label org that set no logo shows none.\n logoUrl: cleanBrandString(profile.logoUrl) ?? null,\n // The same no-fallback rule, and no fallback to the light logo either:\n // a reader wanting \"the logo for a dark ground\" asks for\n // `logoDarkUrl ?? logoUrl` itself, so null here keeps \"the org set a\n // dark variant\" answerable.\n logoDarkUrl: cleanBrandString(profile.logoDarkUrl) ?? null,\n faviconUrl:\n cleanBrandString(profile.faviconUrl) ?? PLATFORM_BRANDING_PROFILE.faviconUrl,\n faviconDarkUrl: cleanBrandString(profile.faviconDarkUrl) ?? null,\n primaryColor:\n cleanBrandString(profile.primaryColor) ??\n PLATFORM_BRANDING_PROFILE.primaryColor,\n // NO FALLBACK, and this is the one field that gets none (AGL-2428).\n //\n // Only a white-label org reaches this line — the guard above hands every\n // other org the platform profile whole. So a blank here belongs to an\n // organization whose transactional mail reads as *theirs* to a recipient\n // who is *their* customer, and filling the gap with Aglyn's desk sends\n // that person to a support team that cannot help them, while disclosing\n // a vendor they were never told about. `platform-brand.ts` calls the\n // same substitution in the self-host case \"a trap dressed as a default\";\n // this is that trap with a different victim.\n //\n // Blank therefore means NO LINK, exactly as `emailLogoUrl` one field\n // down already means no logo. An email with a gap where a link should be\n // reads as broken; an email with no support line reads as plain, which\n // is the right appearance for an organization that has not set one.\n supportUrl: cleanBrandString(profile.supportUrl) ?? null,\n // A white-label org has no separate \"home\" field to store, and the same\n // no-fallback rule applies for the same reason: `PLATFORM_HOME_URL` here\n // would put a link to the platform's marketing site on the badge of an\n // org that pays to conceal the platform exists. Their support URL is the\n // only destination they have told us about, which is exactly what this\n // badge already linked to before the split — so white-label badges are\n // unchanged, and only the platform's own badge stops opening a mail\n // client.\n homeUrl: cleanBrandString(profile.supportUrl) ?? null,\n fromName:\n cleanBrandString(profile.fromName) ?? PLATFORM_BRANDING_PROFILE.fromName,\n emailLogoUrl:\n cleanBrandString(profile.emailLogoUrl) ??\n PLATFORM_BRANDING_PROFILE.emailLogoUrl,\n customConsoleDomain:\n cleanBrandString(profile.customConsoleDomain) ??\n PLATFORM_BRANDING_PROFILE.customConsoleDomain,\n }\n}\n\n/**\n * The brand as MERGE TOKENS, for a staff-designed system-email template\n * (AGL-2139).\n *\n * White-label inverted precisely when staff published a template, which is\n * that feature's normal steady state. Every org-context sender has the shape\n * `subject: designed?.subject ?? <branded fallback>` — the DESIGNED template\n * wins — and the catalog copy hard-coded \"Aglyn\" throughout. There was no\n * brand token to design against either: the merge maps carried `org.name`,\n * `invite.role`, `signInUrl` and friends and nothing about the brand, and\n * `blankUnresolvedTokens` BLANKS any token the caller did not supply. So a\n * designer who reached for `{{brand.productName}}` would have shipped an\n * email with a hole in the sentence.\n *\n * Token bodies are namespaced `brand.*` so a designer can tell them from the\n * per-send values, and they resolve for EVERY org — Aglyn's own included,\n * since `PLATFORM_BRANDING_PROFILE.productName` is `'Aglyn'`. That is what makes\n * a single template correct for both populations instead of one hard-coded\n * for the majority.\n *\n * The email LOGO is deliberately absent. It is structural — `renderEmailHtml`\n * emits it as a header row above the designed body, or emits nothing — so it\n * travels as an option rather than as a token a template could forget to\n * place, and there is no sample URL a staff test-send could render without\n * showing a broken image.\n */\nexport function brandMergeTokens(\n branding: ResolvedBrandingProfile,\n): Record<string, string> {\n return {\n 'brand.productName': branding.productName,\n 'brand.fromName': branding.fromName,\n // EMPTY, never omitted, when the org has no Support URL (AGL-2428).\n //\n // `renderLoadedSystemEmail` merges these OVER `DEFAULT_BRAND_TOKENS`,\n // which carries the platform's own support URL for the genuinely\n // org-less senders. Dropping the key here would let that default show\n // through and mail a white-label org's customer a link to Aglyn — the\n // exact leak this closes — so the key must be present and empty in order\n // to overwrite it. A designed template that interpolates the token\n // renders nothing where it sits.\n 'brand.supportUrl': branding.supportUrl ?? '',\n }\n}\n\n/**\n * The value of the `<meta name=\"generator\">` tag and of the `x-powered-by`\n * header on published sites (AGL-2088) — the canonical CMS signal WordPress,\n * Squarespace, Drupal and Ghost all ship, and the thing tech-stack detectors\n * key on.\n *\n * Deliberately VERSIONLESS. The accidental headers this replaced carried\n * `1.0.0-alpha.0` and a Node version, which dated every deployment and named\n * our runtime to no consumer. A detector needs the product name and nothing\n * else.\n */\nexport const PLATFORM_GENERATOR_NAME = PLATFORM_BRAND_NAME\n\n/**\n * May this org's published pages say they were built with Aglyn? (AGL-2088)\n *\n * The gate for the `<meta name=\"generator\">` tag and the `x-powered-by`\n * header. It reads the SAME entitlement `resolveBrandingProfile` reads —\n * `whiteLabel` — rather than inventing a parallel one, so an agency's\n * concealment promise has exactly one definition.\n *\n * WHY `whiteLabel` AND NOT `showBranding`. The \"Made with Aglyn\" badge is\n * gated on `removeBranding`, which every plan above free grants, so reusing\n * that gate would confine the signal to free-tier sites — the corpus this\n * exists to build would be almost empty on day one. The two promises are also\n * different in kind: `removeBranding` buys a page with no Aglyn credit on it,\n * an aesthetic commitment about what a VISITOR sees; `whiteLabel` buys\n * concealment of who built the site, which is the promise a machine-readable\n * fingerprint actually breaks. A paying customer on a mid plan has bought a\n * clean page, not anonymity.\n *\n * ⚠️ AN UNRESOLVED ORG SUPPRESSES, and this is the whole reason the function\n * exists instead of a bare `!checkEntitlement(org, 'whiteLabel')` at each call\n * site. `resolveOrgEntitlements(null)` resolves to the FREE plan — a loading\n * default answering a question it was never asked — so a null org reads as\n * \"no white-label\" and would EMIT. That null is not hypothetical: the tenant's\n * `getOrgBilling` fails open with `org: null` on a Firestore error, so a\n * transient read failure on an Agency site would have stamped the generator\n * tag onto it. The asymmetry decides the direction: suppressing wrongly costs\n * us one detection sample, emitting wrongly breaks a paid promise on a\n * customer's own domain. So the org must be PRESENT and lack the entitlement;\n * absent is not the same as free here.\n */\nexport function showsPlatformAttribution(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n // `{}` is treated as unresolved alongside `null`, and that is not\n // pedantry: no org doc that was actually READ is empty — the converter\n // yields `$id` at minimum — so an empty object only ever arrives from a\n // placeholder or a loading default, the same shape `useBranding` hands back\n // before its read lands. It resolves to the free plan like every other\n // absent org, which is the right default for a quota and the wrong one for\n // a disclosure.\n if (!org || Object.keys(org).length === 0) return false\n return !checkEntitlement(org, 'whiteLabel')\n}\n\n/**\n * Whether the org has actually **configured** a white-label brand (AGL-1110) —\n * a non-empty product name or logo. White-label chrome should activate only\n * when this is true, not merely because the plan grants the `whiteLabel`\n * entitlement: an Agency org (or a comped Enterprise org) that never set up a\n * brand keeps the plain Aglyn wordmark — no product-name-text render, no\n * flash — until it opts in on the branding page.\n */\nexport function hasBrandingProfile(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n const profile = org?.brandingProfile\n if (!profile) return false\n return !!(\n cleanBrandString(profile.productName) || cleanBrandString(profile.logoUrl)\n )\n}\n\n/**\n * Quota check: `allowed` is false once `currentUsage` meets the limit —\n * call before creating the next resource (e.g. usage=hostCount before\n * creating another host). `remaining` never goes negative.\n */\n/**\n * `members` = per-site collaborator seats (legacy key, AGL-888): the key\n * matches persisted `seatAddons.members` and must not be renamed.\n */\nexport type SeatKind = 'managers' | 'members'\n\nexport interface SeatQuotaResult {\n /** False once usage meets the effective limit (included + purchased). */\n allowed: boolean\n /** Effective seat limit: included + purchased addons, clamped to the max. */\n limit: number\n remaining: number\n /** Included seats on the plan before addons. */\n included: number\n /** Purchased addon seats currently applied. */\n purchased: number\n /** Hard cap incl. addons; reaching it requires a plan upgrade. */\n maxSeats: number\n /**\n * True when buying more addon seats cannot raise the limit — either the\n * plan sells no addons or the hard cap is reached. UI should prompt an\n * upgrade instead of an addon purchase.\n */\n upgradeRequired: boolean\n /** Monthly price per addon seat; null when the plan sells none. */\n addonPriceUsd: number | null\n}\n\n/**\n * Seat quota check (AGL-112): seats differ from plain quotas because orgs\n * can buy addon seats (`org.seatAddons`) up to a per-plan hard max —\n * beyond the max the only path is upgrading the plan. `managers` counts\n * org-manager seats org-wide; `members` counts per-site COLLABORATORS\n * (`hosts/{id}/members`, viewer/editor/admin — legacy key name, AGL-888).\n * End-user member accounts (`siteMembers`) are unlimited and never pass\n * through here.\n *\n * SINCE AGL-2439 THIS IS THE PLAN'S CAP FOR `members`, with no purchased\n * seats in it — see the comment at the add below. A per-site answer must come\n * from `checkHostCollaboratorQuota(org, hostId, used)`, which is the only\n * thing that knows which site holds which pool seat. Every caller that has a\n * `hostId` must use that one; this stays correct for a plan-level readout\n * (what the plan includes per site, before anything is assigned).\n */\nexport function checkSeatQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n kind: SeatKind,\n currentUsage: number,\n): SeatQuotaResult {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const included =\n kind === 'managers'\n ? entitlements.managersPerOrg\n : entitlements.membersPerHost\n const maxSeats =\n kind === 'managers'\n ? entitlements.maxManagersPerOrg\n : entitlements.maxMembersPerHost\n const addonPriceUsd =\n kind === 'managers'\n ? pricing.extraSeatMonthlyUsd\n : pricing.extraCollaboratorMonthlyUsd\n // `members` IS NOT ADDED HERE, and this is the fix, not an omission\n // (AGL-2439) — the AGL-1775 shape, applied to\n // the key that never got it. `seatAddons.members` is an ORG-LEVEL purchased\n // quantity and `membersPerHost` is enforced PER SITE, so folding one into\n // the other handed every site the whole purchase: an org with 20 sites that\n // bought ONE extra collaborator seat received 20 seats for one seat's price.\n // The quantity is now a POOL and `org.collaboratorAllocations` says which\n // site holds each seat. What this function returns for `members` is\n // therefore **the plan's cap alone** — exactly what an unallocated site must\n // resolve to. Per-site capacity comes from `checkHostCollaboratorQuota`;\n // nothing else may add the pool back in.\n //\n // `managers` is unaffected and still adds: `managersPerOrg` really is\n // org-level, so an org-level purchase raising an org-level cap is correct\n // there. That difference is the whole reason this is a `kind` switch rather\n // than one rule.\n const purchased =\n kind === 'managers'\n ? Math.max(0, resolvePurchasedAddons(org)[kind] ?? 0)\n : 0\n const limit = Math.min(included + purchased, maxSeats)\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n included,\n purchased,\n maxSeats,\n upgradeRequired: addonPriceUsd === null || limit >= maxSeats,\n addonPriceUsd,\n }\n}\n\nexport function checkQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n quota: keyof Omit<ResolvedOrgEntitlements, 'features'>,\n currentUsage: number,\n): { allowed: boolean; limit: number; remaining: number } {\n const limit = resolveOrgEntitlements(org)[quota]\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n }\n}\n\nexport interface DatasetQuotaResult {\n /** False once usage meets the effective limit (included + purchased). */\n allowed: boolean\n /** Effective dataset limit: included + purchased, clamped to the max. */\n limit: number\n remaining: number\n included: number\n purchased: number\n maxDatasets: number\n /** True when addons cannot raise the limit — upgrade instead. */\n upgradeRequired: boolean\n /** Monthly price per extra dataset; null when the plan sells none. */\n addonPriceUsd: number | null\n}\n\n/**\n * Dataset quota check (AGL-132/240), mirroring `checkSeatQuota`: orgs can\n * buy addon datasets (`org.seatAddons.datasets`, org-wide) up to the\n * plan's hard max; beyond the max the only path is upgrading.\n */\nexport function checkDatasetQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n currentUsage: number,\n): DatasetQuotaResult {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const included = entitlements.datasetsPerOrg\n const maxDatasets = entitlements.maxDatasetsPerOrg\n const addonPriceUsd = pricing.extraDatasetMonthlyUsd\n const purchased = Math.max(0, resolvePurchasedAddons(org).datasets ?? 0)\n const limit = Math.min(included + purchased, maxDatasets)\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n included,\n purchased,\n maxDatasets,\n upgradeRequired: addonPriceUsd === null || limit >= maxDatasets,\n addonPriceUsd,\n }\n}\n\nexport interface PluginOrgCapacityQuotaResult {\n /** False once usage meets the effective limit. */\n allowed: boolean\n /** Included + purchased, clamped to the plan's ceiling for this capacity. */\n limit: number\n remaining: number\n /** What the plan includes — the figure a downgrade warning quotes. */\n included: number\n /** What the org bought on top, 0 on a dead subscription. */\n purchased: number\n /** How far buying can raise it on this plan. */\n ceiling: number\n}\n\n/**\n * The quota for a capacity a PLUGIN backs (AGL-3080), from its declaration.\n *\n * `checkDatasetQuota` is this arithmetic written out for the one capacity\n * core happened to know the name of. It has a dozen callers on money paths\n * and keeps its own shape; what moves here is the ability to ask the same\n * question about a capacity core does not name, which is what a console meter\n * needs — it draws a row per declared capacity and cannot switch on `datasets`.\n *\n * The two must agree, and `plan-entitlements-plugin-capacity.spec.ts` is what\n * holds that: for every plan, this answers exactly what `checkDatasetQuota`\n * answers for the declared dataset capacity. A second implementation of a\n * limit is worth having only while something refuses to let the two drift.\n *\n * ⚑ An undeclared kind, or a declaration naming a field no plan carries, is\n * ZERO — included, ceiling and limit all — which reads as \"you are over\" and\n * shows a meter full. That is the same direction `overLimitRows` and\n * `includedCapacity` already fail in, and for the same reason: the reassuring\n * answer here would be a meter promising room the create is refused at.\n */\nexport function checkPluginOrgCapacityQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n kind: string,\n currentUsage: number,\n): PluginOrgCapacityQuotaResult {\n const capacity = pluginOrgCapacity(kind)\n const entitlements = resolveOrgEntitlements(org) as unknown as Record<\n string,\n unknown\n >\n const included = capacity\n ? Number(entitlements[capacity.includedEntitlement] ?? 0)\n : 0\n const ceiling = capacity\n ? Number(entitlements[capacity.purchaseCeilingEntitlement] ?? 0)\n : 0\n // The add-on kind IS the `seatAddons` key it is stored under — the\n // generator refuses two capacities on one add-on kind, so one purchased\n // quantity can only ever raise the capacity that sells it.\n const purchased = capacity\n ? Math.max(\n 0,\n Number(\n (resolvePurchasedAddons(org) as unknown as Record<string, unknown>)[\n capacity.addonKind\n ] ?? 0,\n ),\n )\n : 0\n const limit = Math.min(included + purchased, ceiling)\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n included,\n purchased,\n ceiling,\n }\n}\n\nexport interface DataStorageQuotaResult {\n /**\n * False only when the plan hard-blocks (no overage pricing) and usage\n * meets the included size; metered plans always allow and bill overage.\n */\n allowed: boolean\n /** Included dataset storage on the plan, MB. */\n includedMb: number\n usedMb: number\n /** Remaining included storage, MB; 0 once into overage. */\n remainingMb: number\n /** Usage beyond the included size, GB (0 when within the plan). */\n overageGb: number\n /** Estimated overage this month at the plan's per-GB rate. */\n overageMonthlyUsd: number\n /** Per-GB-month overage rate; null when the plan meters nothing. */\n overageRateUsd: number | null\n}\n\n/**\n * Org dataset-storage meter (AGL-240): aggregate stored document bytes\n * across `orgs/{orgId}/datasets`. Plans with an `extraDataGbMonthlyUsd`\n * rate meter the overage onto the monthly invoice (cost-plus, AGL-41);\n * plans without one (free) hard-block at the included size.\n */\nexport function checkDataStorageQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedMb: number,\n): DataStorageQuotaResult {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const includedMb = entitlements.dataStorageMbPerOrg\n const overageRateUsd = pricing.extraDataGbMonthlyUsd\n const used = Math.max(0, usedMb)\n const overageMb = Math.max(0, used - includedMb)\n const overageGb = overageMb / 1024\n return {\n allowed: overageRateUsd !== null ? true : used < includedMb,\n includedMb,\n usedMb: used,\n remainingMb: Math.max(0, includedMb - used),\n overageGb,\n overageMonthlyUsd:\n overageRateUsd === null\n ? 0\n : Math.round(overageGb * overageRateUsd * 100) / 100,\n overageRateUsd,\n }\n}\n\nexport interface ApiRequestQuotaResult {\n /** Metered plans always allow; plans without API access block. */\n allowed: boolean\n /** Included requests this month at the plan. */\n included: number\n used: number\n /** Remaining included requests; 0 once into overage. */\n remaining: number\n /** Requests beyond the included quota (0 within the plan). */\n overageRequests: number\n /** Estimated overage this month at the plan's per-1,000 rate. */\n overageMonthlyUsd: number\n /** Per-1,000 overage rate; null when the plan has no API. */\n overageRateUsd: number | null\n}\n\n/**\n * Customer REST API request meter (AGL-634): the monthly request count for an\n * org. Business and every tier above it carry `apiAccess` and an\n * `extraApiRequestsUsdPer1k` rate, so requests past the included quota meter\n * onto the monthly invoice\n * (cost-plus, like storage overage — never a hard wall mid-integration); plans\n * without API access have `included: 0` and always block.\n */\nexport function checkApiRequestQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedRequests: number,\n): ApiRequestQuotaResult {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const included = entitlements.apiRequestsPerMonth\n const overageRateUsd = pricing.extraApiRequestsUsdPer1k\n const used = Math.max(0, usedRequests)\n const overageRequests = Math.max(0, used - included)\n return {\n allowed: overageRateUsd !== null ? true : used < included,\n included,\n used,\n remaining: Math.max(0, included - used),\n overageRequests,\n overageMonthlyUsd:\n overageRateUsd === null\n ? 0\n : Math.round((overageRequests / 1000) * overageRateUsd * 100) / 100,\n overageRateUsd,\n }\n}\n\nexport interface CrmRecordsQuotaResult {\n /** Metered plans always allow; free hard-bands at the included count. */\n allowed: boolean\n /** Included CRM records on the plan — the `contactsPerHost` band. */\n included: number\n /** Contacts + companies + deals, as the caller counted them. */\n used: number\n /** Remaining included records; 0 once into overage. */\n remaining: number\n /** Records beyond the included band (0 within the plan). */\n overageRecords: number\n /** Estimated overage this month at the plan's per-1,000 rate. */\n overageMonthlyUsd: number\n /** Per-1,000 overage rate; null when the plan hard-bands (free). */\n overageRateUsd: number | null\n}\n\n/**\n * The name this result carried while the band counted contacts alone.\n * @deprecated Read `CrmRecordsQuotaResult`; the shape is the same.\n */\nexport type ContactQuotaResult = CrmRecordsQuotaResult\n\n/**\n * CRM records band meter (AGL-890, widened in AGL-2611): contacts, companies\n * and deals, counted together across the org and measured against the ONE\n * band `contactsPerHost` names. Paid plans carry an `extraContactsUsdPer1k`\n * rate, so records past the included band meter onto the monthly invoice\n * (cost-plus, like storage and API overage — a growing audience is never\n * dropped); free has no rate and hard-bands at the included count, refusing\n * the next record of any of the three kinds.\n *\n * ONE band for three collections, and not three bands, because a company or\n * a deal costs less to hold than a contact — a ~2 KB document with few index\n * entries and no dynamic-list scan — so counting them at the contacts rate\n * over-covers them, and a second and third number to explain would buy\n * nothing a customer could act on. The caller passes the SUM; the server\n * helper that takes the three aggregate reads is `countCrmRecords`, and the\n * billing meter shows the three components in its caption so the sum is\n * legible rather than opaque. Tasks and activities are not counted: they\n * are bounded by human effort, and the activity log is capped per record by\n * `CRM_ACTIVITIES_PER_RECORD_CEILING` instead.\n *\n * End-user member ACCOUNTS are unlimited on every plan (AGL-889) — this\n * meters only the CRM record.\n */\nexport function checkCrmRecordsQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedRecords: number,\n): CrmRecordsQuotaResult {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const included = entitlements.contactsPerHost\n const overageRateUsd = pricing.extraContactsUsdPer1k\n const used = Math.max(0, usedRecords)\n const overageRecords = Math.max(0, used - included)\n return {\n allowed: overageRateUsd !== null ? true : used < included,\n included,\n used,\n remaining: Math.max(0, included - used),\n overageRecords,\n overageMonthlyUsd:\n overageRateUsd === null\n ? 0\n : Math.round((overageRecords / 1000) * overageRateUsd * 100) / 100,\n overageRateUsd,\n }\n}\n\n/**\n * The name the band's gate carried while it counted contacts alone. The SAME\n * function — every caller that still says \"contact\" is measuring the records\n * band and must hand it the summed count, not the contacts aggregate.\n * @deprecated Call `checkCrmRecordsQuota`.\n */\nexport const checkContactQuota = checkCrmRecordsQuota\n\n/**\n * `orgs/{orgId}/crmEmailUsage/{YYYY-MM-DD}` — the durable per-day counter of\n * one-to-one CRM emails, `{ count, day, updatedAt }`, server-written only and\n * member-readable, exactly as `apiUsage/{month}` is for API requests.\n */\nexport const CRM_EMAIL_USAGE_COLLECTION = 'crmEmailUsage'\n\n/**\n * The document id of today's one-to-one email counter, `YYYY-MM-DD` in UTC.\n *\n * UTC rather than the workspace's zone, for the reason `apiUsageMonth` keys\n * the API counter in UTC: the cap is enforced on a server that has no zone\n * of the customer's to read, and a boundary that moved with a browser's\n * clock would let one day's sends be counted against two.\n */\nexport function crmEmailUsageDayKey(now: Date = new Date()): string {\n return now.toISOString().slice(0, 10)\n}\n\nexport interface CrmEmailQuotaResult {\n /** Whether ONE MORE send is inside today's cap. Enterprise always allows. */\n allowed: boolean\n /** The plan's daily cap; 0 where the tier has no CRM. */\n included: number\n /** Sends already counted today. */\n used: number\n /** Sends left today; 0 at the cap. */\n remaining: number\n /** When the counter rolls over — the next UTC midnight. */\n resetsAt: Date\n}\n\n/**\n * One-to-one CRM email cap (AGL-2611): how many messages a workspace may\n * still send from CRM records today, mirroring `checkApiRequestQuota`.\n *\n * A HARD cap with no overage rate, on every tier — the one respect in which\n * it differs from the API meter it is shaped after. `allowed` is therefore\n * `used < included` outright. Every paid tier's cap is finite, Enterprise's\n * 2,000 included, and 0 on Free makes it false at zero, so a per-org\n * `features.crm` grant does not send until `crmEmailsPerDay` is raised\n * beside it.\n *\n * ## The contract for the send route\n *\n * The route that sends a one-to-one email takes the send as a SLOT on\n * `orgs/{orgId}/crmEmailUsage/{crmEmailUsageDayKey()}` inside one Firestore\n * transaction — `reserveCrmEmailSend` in `@aglyn/tenant-data-admin` reads\n * `count`, calls this with that figure, REFUSES with the plan reason when\n * `allowed` is false and otherwise writes `used + 1` in the same transaction\n * (AGL-2645). A read-then-write cap let two reps at the last slot both pass\n * one reading of it; the transaction re-runs the second on the raised\n * figure. The slot is taken before the message leaves and handed back by\n * `releaseCrmEmailSend` when the provider refuses it. A delivered send is\n * then recorded on the org's `emailSends` cost meter through\n * `recordEmailSends` as `transactional`, because every message the provider\n * charged for is priced into COGS and the campaign cap does not govern it.\n * The count enforced is the count billed, from one document, the way the\n * API meter works.\n *\n * Read the counter with `used` from the server: the console meter on the\n * billing page reads the same document, member-readable, so a rep is never\n * told their limit by being refused.\n */\nexport function checkCrmEmailQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedToday: number,\n now: Date = new Date(),\n): CrmEmailQuotaResult {\n const included = planQuotaOf(resolveOrgEntitlements(org), 'crmEmailsPerDay')\n const used = Math.max(0, Math.floor(Number(usedToday) || 0))\n const resetsAt = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1),\n )\n return {\n allowed: used < included,\n included,\n used,\n remaining: Math.max(0, included - used),\n resetsAt,\n }\n}\n\n/**\n * Volume above the plan's included band, in emails.\n *\n * Transactional mail is never refused, so an org CAN and will finish a month\n * over its included allowance. That is not an error and not a failed send —\n * it is the overage the cap chose not to enforce, and recording it is what\n * keeps it from being a surprise at invoicing. `UNLIMITED` (and any\n * non-positive limit, which is how \"no included allowance\" is expressed)\n * yields 0 rather than the whole month's volume.\n *\n * DEFINED HERE and re-exported from `email-metering.ts` (AGL-2155's move):\n * the billing page renders this figure before anything is invoiced, and it\n * is a client component that cannot pull in the Admin SDK. One subtraction,\n * so the readout and the invoice cannot disagree.\n */\nexport function emailSendsOverage(total: number, limit: number): number {\n const sends = Number(total)\n const included = Number(limit)\n if (!Number.isFinite(sends) || sends <= 0) return 0\n if (!Number.isFinite(included) || included <= 0) return 0\n return Math.max(0, sends - included)\n}\n\nexport interface EmailSendOveragePrice {\n /** Emails past the plan's included band, as handed in. */\n overageSends: number\n /** Estimated charge this month at the plan's per-1,000 rate. */\n overageMonthlyUsd: number\n /** Per-1,000 rate; null when the plan carries no email overage price. */\n overageRateUsd: number | null\n}\n\n/**\n * Prices email volume past a plan's included band.\n *\n * ## No `allowed`, deliberately\n *\n * Every other meter's helper here answers \"may this proceed\" alongside \"what\n * does it cost\", because on those axes one gate decides both. Email has two\n * gates that are not the same gate: `reserveCampaignEmailSends` refuses a\n * CAMPAIGN at the band inside a transaction, and nothing at all refuses\n * transactional mail at any tier. A field named `allowed` on this result\n * would be a plausible-looking thing for a sender to consult, and a\n * transactional sender consulting it is how a password reset stops going out.\n * So the result carries money and nothing else.\n *\n * ## Takes the overage, does not recompute it\n *\n * `emailSendsOverage` already derives the excess from the cost meter and the\n * resolved band, on the path that records it. Re-deriving it here from a raw\n * total would be a second overage model to drift from the first — which is\n * the shape AGL-1402 documented, where two figures that should have been one\n * disagreed by 20-45% for years.\n *\n * A null rate yields zero, structurally rather than by a check, which is what\n * keeps free at zero on this axis for the same reason it is zero on the other\n * three.\n */\nexport function priceEmailSendOverage(\n org: Partial<AglynOrgBilling> | null | undefined,\n overageSends: number,\n): EmailSendOveragePrice {\n const overageRateUsd = resolvePlanPricing(org).extraEmailSendsUsdPer1k\n const sends = Number(overageSends)\n const over = Number.isFinite(sends) && sends > 0 ? sends : 0\n return {\n overageSends: over,\n overageMonthlyUsd:\n overageRateUsd === null\n ? 0\n : Math.round((over / 1000) * overageRateUsd * 100) / 100,\n overageRateUsd,\n }\n}\n\n/**\n * Is this org's plan subject to the metered infrastructure pass-through\n * (AGL-1280)? The single answer behind three otherwise-unrelated behaviours:\n * whether storage/bandwidth/form usage past the included band is BILLED, and\n * whether the form-submission cap is a wall or a meter.\n *\n * Deliberately a plan-level question, not an org-level one — an org with no\n * plan resolves as free, so an unknown org meters nothing. Billing usage to\n * someone with no subscription is the one error direction with no recovery.\n * A staff comp is that someone too (AGL-3034): it grants a paid plan with no\n * subscription behind it, so `resolvePlanPricing` answers `false` for it and\n * its bands are walls.\n */\nexport function planMetersInfraOverage(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n return resolvePlanPricing(org).meteredInfraPassThrough\n}\n\nexport interface FormSubmissionQuotaResult {\n /**\n * False only when the plan hard-caps (no metered pass-through) and usage\n * has reached the included count; metered plans always accept and bill\n * the excess.\n */\n allowed: boolean\n /** Included submissions this month for this site. */\n included: number\n used: number\n /** Remaining included submissions; 0 once into overage. */\n remaining: number\n /** Submissions beyond the included count (0 within the plan). */\n overageSubmissions: number\n /** True when overage meters instead of blocking. */\n metered: boolean\n}\n\n/**\n * Monthly form-submission meter (AGL-76, made coherent in AGL-1280).\n *\n * Submissions were BOTH hard-capped at `formSubmissionsPerMonth` (429) and\n * metered — which made the published promise that usage past the\n * included band is billed rather than cut off false for the one meter where\n * being cut off costs the customer a lead. Now it mirrors the other three\n * overage meters exactly: a plan with the metered pass-through always accepts\n * and prices the excess (`estimateMonthlyUsageCost`); free has no\n * subscription to bill, so its included count stays a hard wall.\n *\n * Priced per ORG at cost-plus, but capped per SITE, because that is where the\n * counter lives (`hosts/{id}/counters/formSubmissions`). Callers gating a\n * single submission pass that site's month count.\n */\nexport function checkFormSubmissionQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedThisMonth: number,\n): FormSubmissionQuotaResult {\n const included = planQuotaOf(\n resolveOrgEntitlements(org),\n 'formSubmissionsPerMonth',\n )\n const metered = planMetersInfraOverage(org)\n const used = Math.max(0, usedThisMonth)\n return {\n allowed: metered ? true : used < included,\n included,\n used,\n remaining: Math.max(0, included - used),\n overageSubmissions: Math.max(0, used - included),\n metered,\n }\n}\n\n/**\n * How far past a plan's own included band the abuse ceiling sits (AGL-1655).\n * Ten times what the customer bought is not growth — a site that legitimately\n * outgrows its band by an order of magnitude has an upgrade conversation\n * waiting, not a silent invoice.\n */\nexport const FORM_ABUSE_CEILING_MULTIPLE = 10\n\n/**\n * Absolute floor for the abuse ceiling, so the small plans get real headroom\n * rather than a second, tighter plan limit wearing a different name. Starter\n * includes 200/month; 10× would be 2,000, which a genuinely busy site could\n * reach. 5,000 is ~167 submissions a day on ONE site — no legitimate site on\n * a small plan reaches that, and no small customer can be billed past\n * 5,000 × $0.000065 ≈ $0.33 of abuse.\n */\nexport const FORM_ABUSE_CEILING_FLOOR = 5_000\n\n/**\n * Ceiling for plans whose included count is UNLIMITED (Agency, Enterprise),\n * where a multiple has nothing to multiply. Kept at or above the ceiling of\n * every finite plan below it (Advanced includes 100,000 → 1,000,000) so the\n * ladder never inverts and a bigger plan never gets the smaller ceiling.\n */\nexport const FORM_ABUSE_CEILING_UNLIMITED = 1_000_000\n\nexport interface FormAbuseCeilingResult {\n /** True once this month's count for the SITE has reached the ceiling. */\n exceeded: boolean\n /** The ceiling this site's plan resolves to. Always finite. */\n ceiling: number\n used: number\n}\n\n/**\n * Per-site monthly abuse ceiling for form submissions (AGL-1655).\n *\n * Deliberately NOT part of `checkFormSubmissionQuota`. That function answers\n * a plan question — \"did the customer buy this?\" — and AGL-1280 correctly\n * made its answer on a metered plan always yes, because usage past the band\n * bills rather than blocks. This one answers a different question: \"is this\n * still a customer's traffic at all?\" Conflating them is how the plan gate\n * ended up as the anti-abuse control it was never designed to be, and how\n * `allowed: metered ? true : …` came to mean an unbounded bill.\n *\n * So the ceiling is containment, not capacity. It is far above anything a\n * legitimate site reaches, it is the same shape on every plan, and crossing\n * it is an INCIDENT: the caller is expected to refuse the write, leave the\n * billable counter alone, and make the trip visible to the site's managers\n * and to staff. A ceiling that silently absorbed submissions would only move\n * the free plan's lost-lead failure up to a bigger number.\n *\n * Evaluated against the same month counter the plan gate reads — which is\n * Admin-SDK-only (`counters` is excluded from every client write in\n * `cloud/firebase-firestore.rules`), so the count behind the ceiling cannot\n * be lowered to launder past it.\n */\nexport function checkFormSubmissionAbuseCeiling(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedThisMonth: number,\n): FormAbuseCeilingResult {\n const included = planQuotaOf(\n resolveOrgEntitlements(org),\n 'formSubmissionsPerMonth',\n )\n const ceiling = Number.isFinite(included)\n ? Math.max(FORM_ABUSE_CEILING_FLOOR, included * FORM_ABUSE_CEILING_MULTIPLE)\n : FORM_ABUSE_CEILING_UNLIMITED\n const used = Math.max(0, usedThisMonth)\n return { exceeded: used >= ceiling, ceiling, used }\n}\n\n/**\n * Average transfer per page view (HTML + JS + a few images), the constant\n * that turns the analytics view counter into a bandwidth number and back.\n *\n * ## ⛔ HALF OF A PAIR. It moves with `perPageView`, or neither moves.\n *\n * This is the BYTES one page view weighs;\n * {@link ORG_COGS_UNIT_RATES_USD}`.perPageView` is the DOLLARS the same page\n * view costs. They are one physical measurement written in two units, and\n * what every `bandwidthGb` band is actually sized against is neither of them\n * alone but their quotient — `(1 GB ÷ this) × perPageView`, the cost of a\n * gigabyte's WEIGHT, to which the band sizing adds the CDN requests the same\n * views make ({@link PAGE_VIEW_CDN_REQUEST_COST_USD}). That quotient is\n * stable even while the page's weight is not,\n * which is precisely why moving one half re-prices every band on the ladder\n * without touching a band: a heavier page costs more per view and buys fewer\n * views per gigabyte, and the two cancel.\n *\n * Both are therefore stated against the SAME weight — **1012.8 KB**, the\n * basis recorded in `tools/tenant-page-budget.json` and recoverable from the\n * rate itself — and `npm run check:page-view-rate` compares the two and\n * refuses a mismatch. There is no version of this constant that is correct\n * against a `perPageView` calibrated for a different page.\n *\n * The basis is FIRST-PARTY encoded bytes at settle, because that is what an\n * egress bill is denominated in: third-party tags leave someone else's\n * network and cost the platform nothing, so counting them here would inflate\n * a customer's measured bandwidth with bytes nobody pays us for.\n *\n * Lives HERE rather than beside the metering rates (AGL-2155). It used to sit\n * in `apps/console/utils/usage-metering.ts`, which the console imports and the\n * tenant app cannot — and the bandwidth ceiling below has to be evaluated in\n * the tenant app, at the beacon that writes the counter. Re-deriving the\n * conversion there would have been a fourth hand-rolled copy of\n * `× 1024³ / page weight`, which is the exact drift AGL-1371 collapsed into\n * one function. `usage-metering` now re-exports these three, so every existing\n * importer is unchanged and there is still one definition.\n */\nexport const ESTIMATED_PAGE_TRANSFER_BYTES = 1012.8 * 1024\n\n/**\n * Bandwidth ⇄ page views: the one conversion three surfaces used to each\n * write out by hand (AGL-1371).\n *\n * `bandwidthGb` is not a second cap next to metered page views — it IS the\n * included band of the page-view meter, expressed in the unit customers\n * understand. It is therefore used in both directions: forward by\n * `meteredIncludedAllowance` to size the band the invoice subtracts, backward\n * by the usage-alerts cron and the console meter to render live page views as\n * GB.\n */\nexport function pageViewsFromBandwidthGb(bandwidthGb: number): number {\n return (bandwidthGb * 1024 * 1024 * 1024) / ESTIMATED_PAGE_TRANSFER_BYTES\n}\n\n/** @see pageViewsFromBandwidthGb — the same constant, the other way. */\nexport function bandwidthGbFromPageViews(pageViews: number): number {\n return (\n (Math.max(0, Number(pageViews) || 0) * ESTIMATED_PAGE_TRANSFER_BYTES) /\n (1024 * 1024 * 1024)\n )\n}\n\n/** The decimal GB Vercel bills transfer in (AGL-3444). */\nconst DECIMAL_GB_BYTES = 1_000_000_000\n\n/** The binary GB `bandwidthGb` and GCS egress are counted in. */\nconst BINARY_GB_BYTES = 1024 * 1024 * 1024\n\n/**\n * How long one decimal GB keeps a streaming function open: a viewer draws a\n * 720p film at about 5 Mbit/s, and the media route reads Storage only as fast\n * as the viewer takes the bytes (AGL-2810), so the function lives for the\n * film, not for the transfer — 1,600 seconds a GB.\n */\nconst ORIGIN_MEDIA_STREAM_SECONDS_PER_GB = (DECIMAL_GB_BYTES * 8) / 5_000_000\n\n/**\n * The range requests one decimal GB arrives in: a player asks for a film a\n * slice at a time, and a slice of about a megabyte is the small end of what\n * players ask for, so per-request costs are counted a thousand times a GB.\n */\nconst ORIGIN_MEDIA_REQUESTS_PER_GB = 1_000\n\n/**\n * What ONE DECIMAL GB of video, audio or a file costs to SERVE FROM ORIGIN, by\n * term, at the dearest region every Vercel term is billed in (AGL-3474).\n *\n * The media CDN serves every type the edge must never hold (AGL-1515) on\n * every request, from origin, so a GB of it pays both of Vercel's transfer\n * meters and Storage's egress — where a page's GB is mostly answered by the\n * edge. This is the basis {@link ORIGIN_MEDIA_BANDWIDTH_WEIGHT} is derived\n * from, and the figure the published \"at cost + 30%\" has to be true of.\n *\n * | term | basis | per decimal GB |\n * |---|---|---|\n * | `fastDataTransfer` | Vercel Fast Data Transfer, dearest region, decimal GB | $0.35 |\n * | `fastOriginTransfer` | Vercel Fast Origin Transfer, dearest region, decimal GB | $0.43 |\n * | `storageEgress` | GCS internet egress at list, $0.12 per GiB (0–1 TiB) | $0.111759 |\n * | `streamingMemory` | 2 GB (the function default) at $0.0183/GB-hour, held 1,600 s | $0.016267 |\n * | `streamingCpu` | 60 s of active CPU at $0.221/hour — an allowance, see below | $0.003683 |\n * | `requests` | 1,000 range requests × $0.0000070 | $0.007 |\n * | **total** | | **$0.918709** |\n *\n * Each request is a CDN request ($3.20 per million), a function invocation\n * ($0.60 per million), the media document's read ($0.0000006 at `nam5`), the\n * day document's counter write ($0.0000018) and two Storage Class B\n * operations, the metadata read and the object read ($0.0000004 each). The\n * lockdown, quarantine and rate-limit reads are cached across requests and\n * cost nothing a request.\n *\n * `streamingCpu` is the one unmeasured term. The bytes are copied, not\n * computed — piping a GB through Node is a few seconds of CPU — and the\n * minute allowed here is chosen to err high, as every other basis in this\n * file does.\n *\n * ## Storing the bytes is NOT here\n *\n * A file in the library is already metered as storage (`storagePerGbMonth`)\n * and billed past the plan's storage at cost + 30% after card fees; serving\n * it does not store it again. Adding storage here would charge for the same\n * bytes twice.\n */\nexport const ORIGIN_MEDIA_SERVE_COST_USD_PER_GB = {\n fastDataTransfer: 0.35,\n fastOriginTransfer: 0.43,\n storageEgress: (0.12 * DECIMAL_GB_BYTES) / BINARY_GB_BYTES,\n streamingMemory: (2 * 0.0183 * ORIGIN_MEDIA_STREAM_SECONDS_PER_GB) / 3600,\n streamingCpu: (60 * 0.221) / 3600,\n requests:\n ORIGIN_MEDIA_REQUESTS_PER_GB *\n (3.2 / 1_000_000 + 0.6 / 1_000_000 + 0.0000006 + 0.0000018 + 2 * 0.0000004),\n} as const\n\n/** {@link ORIGIN_MEDIA_SERVE_COST_USD_PER_GB}, summed: about $0.9187 a decimal GB. */\nexport const ORIGIN_MEDIA_COST_USD_PER_GB = Object.values(\n ORIGIN_MEDIA_SERVE_COST_USD_PER_GB,\n).reduce((sum, term) => sum + term, 0)\n\n/**\n * The published page-view overage, per view: what a billed view costs us\n * (`perPageView` and the request term) at {@link METERED_PRICE_MULTIPLE},\n * rounded up to the cent per 1,000 it is quoted in — $0.83 per 1,000. The\n * same expression as the console's `METERED_BILLED_RATES_USD.perPageView`,\n * computed here because the weight below is, and a spec holds the two equal.\n */\nexport const PAGE_VIEW_PUBLISHED_PRICE_USD =\n publishedMeteredPrice(\n (ORG_COGS_UNIT_RATES_USD.perPageView + PAGE_VIEW_CDN_REQUEST_COST_USD) * 1000,\n 2,\n ) / 1000\n\n/**\n * (a) THE OVERAGE: does counting origin media at `weight` bill a GB of it past\n * the band at cost + 30%, kept after Stripe's percentage fee?\n *\n * A counted GB is `weight` GB of page views at {@link PAGE_VIEW_PUBLISHED_PRICE_USD}.\n */\nexport function originMediaOverageHolds(weight: number): boolean {\n const billedPerGbUsd =\n weight * PAGE_VIEW_PUBLISHED_PRICE_USD * (DECIMAL_GB_BYTES / ESTIMATED_PAGE_TRANSFER_BYTES)\n return (\n billedPerGbUsd * (1 - STRIPE_PROCESSOR_FEE_PCT) >=\n METERED_MARKUP * ORIGIN_MEDIA_COST_USD_PER_GB\n )\n}\n\n/**\n * (b) THE BAND: does counting origin media at `weight` keep a plan that spends\n * its whole bandwidth band on it inside what the band was sized on?\n *\n * Every `bandwidthGb` band is sized at the cost of the views one GiB buys —\n * their weight (`perPageView`) and their requests\n * (`PAGE_VIEW_CDN_REQUEST_COST_USD`), $0.63712 a GiB — and\n * `tier-margin-floor.spec.ts` holds every tier at 1.3× its full-use cost on\n * that figure. A GiB of origin media spends `weight` GiB of the band, so the\n * band holds while `weight` GiB of band costs at least one GiB of media.\n */\nexport function originMediaBandHolds(weight: number): boolean {\n const bandCostPerGibUsd =\n (ORG_COGS_UNIT_RATES_USD.perPageView + PAGE_VIEW_CDN_REQUEST_COST_USD) *\n pageViewsFromBandwidthGb(1)\n const originCostPerGibUsd =\n (ORIGIN_MEDIA_COST_USD_PER_GB * BINARY_GB_BYTES) / DECIMAL_GB_BYTES\n return weight * bandCostPerGibUsd >= originCostPerGibUsd\n}\n\n/**\n * How many GB of bandwidth one GB of video, audio or a file served from origin\n * counts as (AGL-3474, decided by the account owner 2026-10-02): the smallest\n * weight, in tenths and never below 1, that satisfies BOTH\n * {@link originMediaOverageHolds} and {@link originMediaBandHolds}.\n *\n * One weight rather than a second meter or a second rate, because it moves\n * the band and the overage together: the Free wall, the abuse ceiling, the\n * meters, the alerts and the invoice all read media through\n * `pageViewsFromMediaBytes`, which applies it. At today's bases the overage\n * needs 1.54 and the band 1.55, so it is 1.6 — the published sentence names\n * it, and the docs and `/pricing` have to change before it does.\n *\n * Derived, never written down: a rate or a band basis that moves re-derives\n * it, and `media-bandwidth.spec.ts` pins the figure so that a move is a\n * decision with a red test attached rather than a silent re-price.\n */\nexport const ORIGIN_MEDIA_BANDWIDTH_WEIGHT = ((): number => {\n for (let tenths = 10; tenths <= 1000; tenths += 1) {\n const weight = tenths / 10\n if (originMediaOverageHolds(weight) && originMediaBandHolds(weight)) return weight\n }\n throw new Error('no origin-media weight up to 100 covers its cost')\n})()\n\n/**\n * The sentence every customer surface states the weight in — `/pricing`\n * beside the metered rates (`tools/marketing/build-pricing-tables.mts`), and\n * the bandwidth, billing and media docs, which\n * `media-bandwidth-weight-copy.spec.ts` holds to this exact text. Built from\n * the derived figure, so a weight that moves turns those red before it can\n * bill anyone.\n */\nexport const ORIGIN_MEDIA_BANDWIDTH_SENTENCE =\n 'Video, audio, file downloads and other media served from our servers count ' +\n `${ORIGIN_MEDIA_BANDWIDTH_WEIGHT}× toward bandwidth, because serving them ` +\n 'costs more than serving pages.'\n\n/**\n * How far past a plan's own included bandwidth the abuse ceiling sits\n * (AGL-2155). Three times the bandwidth the customer bought is not growth; it\n * is an upgrade conversation, or it is not the customer's traffic at all.\n *\n * It is a CAP, not a price, and it is lower than {@link\n * FORM_ABUSE_CEILING_MULTIPLE} because bandwidth is the largest cost line the\n * platform carries and the one a stranger can spend on the customer's behalf.\n * Past the band every 1,000 views bills $0.80 against at most $0.62 of real\n * cost — the dearest region's — once the CDN's request allowance is spent\n * ({@link PAGE_VIEW_CDN_REQUEST_COST_USD}), so the tail is not a loss — but a\n * scraper, a hotlinked asset or a botnet still bills the account holder for\n * traffic they did not ask for, and the ceiling is what bounds that before\n * staff look at it. At 10× the band that tail was open-ended on Agency; at 3×\n * it is bounded.\n */\nexport const BANDWIDTH_ABUSE_CEILING_MULTIPLE = 3\n\n/**\n * Absolute floor for the bandwidth ceiling, in PAGE VIEWS per month, so the\n * small plans get real headroom instead of a second, tighter plan limit\n * wearing a different name.\n *\n * Free includes 2 GB ≈ 2,071 views; 3× would be ~6,212, which a genuinely\n * successful hobby site (a post that lands on Hacker News) reaches in an\n * afternoon and would be a miserable first experience of the platform.\n * 100,000 views/month ≈ 96.6 GB ≈ **$62 of real COGS** at the dearest\n * region (`METERED_UNIT_RATES_USD.perPageView` plus\n * `PAGE_VIEW_CDN_REQUEST_COST_USD`) — well above the free band, and an order\n * of magnitude below the $615 a million views costs. It is the number\n * that makes \"free stays free\" true without making it stingy.\n */\nexport const BANDWIDTH_ABUSE_CEILING_FLOOR = 100_000\n\n/**\n * Ceiling for an org whose bandwidth band resolves to UNLIMITED — a\n * contracted per-org override, since no plan row carries the sentinel any\n * more — where a multiple has nothing to multiply. Kept at or above the\n * ceiling of every finite band below it so the ladder never inverts and a\n * bigger plan never inherits a smaller ceiling.\n */\nexport const BANDWIDTH_ABUSE_CEILING_UNLIMITED = 500_000_000\n\n/**\n * Machine-readable marker for a bandwidth containment, so a capped response\n * is distinguishable from the maintenance/lockdown notices it shares a shell\n * with. Deliberately NOT a {@link LockdownReasonCode}: a lockdown is a staff\n * takedown, this is a billing containment that clears itself at the month\n * boundary with nobody doing anything.\n */\nexport const BANDWIDTH_CEILING_CODE = 'bandwidth_ceiling'\n\nexport interface BandwidthAbuseCeilingResult {\n /** True once this month's page views for the SITE reached the ceiling. */\n exceeded: boolean\n /** The ceiling this site's plan resolves to, in page views. Finite. */\n ceiling: number\n used: number\n}\n\n/**\n * Per-site monthly bandwidth abuse ceiling (AGL-2155).\n *\n * ## The hole this closes\n *\n * Every other free dimension had a runtime brace — `mediaStorageGate`,\n * `checkFormSubmissionQuota`, a zero band for dataset storage and API, a\n * hard band for contacts. Bandwidth had **none**: nothing anywhere refused a\n * page view, so a free site that went viral served a million views — roughly\n * **$162 of real COGS** — with no wall, no throttle and no alert. It was\n * protected only by the structural zero on the billing side (free carries no\n * rate, so the invoice stays $0), which protects the *bill* and not the\n * *bleeding*. `free-tier-never-billed.spec.ts` said so in its own table.\n *\n * ## Why an abuse ceiling and not a plan gate\n *\n * Exactly the distinction {@link checkFormSubmissionAbuseCeiling} draws, and\n * for the same reason. A plan gate answers \"did the customer buy this?\" — and\n * for a metered plan the answer past the band is *yes, and we bill it*. This\n * answers \"is this still a customer's traffic at all?\", and crossing it is an\n * INCIDENT rather than a quota: the site is flagged, staff are told, and the\n * render path degrades to a static notice instead of paying ~40 Firestore\n * reads and ~1,013 KB of egress per view.\n *\n * ## Where it is evaluated\n *\n * NOT in the render path. The page-view counter is written *after* the render\n * by `/api/analytics/collect`; that route evaluates this on a sampled cadence\n * and stamps `bandwidthCeiling` onto the host document. The render path reads\n * that field off a host doc **it already loads** (`load-page-data.ts` reads\n * `hostRes.host` for the lockdown branch), so the containment costs the happy\n * path exactly zero extra reads.\n *\n * An edge/CDN rule would be the other natural home and is deliberately not\n * the answer here: it is not a repo change, nothing in this suite could\n * verify it, and it cannot see which plan a host is on.\n */\nexport function checkBandwidthAbuseCeiling(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedPageViewsThisMonth: number,\n): BandwidthAbuseCeilingResult {\n const includedGb = resolveOrgEntitlements(org).bandwidthGb ?? 0\n const includedViews = pageViewsFromBandwidthGb(includedGb)\n const ceiling = Number.isFinite(includedViews)\n ? Math.max(\n BANDWIDTH_ABUSE_CEILING_FLOOR,\n Math.round(includedViews * BANDWIDTH_ABUSE_CEILING_MULTIPLE),\n )\n : BANDWIDTH_ABUSE_CEILING_UNLIMITED\n const used = Math.max(0, Number(usedPageViewsThisMonth) || 0)\n return { exceeded: used >= ceiling, ceiling, used }\n}\n\n/**\n * Does crossing the ceiling **degrade what visitors see**, or only raise the\n * incident? (AGL-2155)\n *\n * Only on a plan that does NOT meter the infra pass-through — which today is\n * free/hobby, where the requirement is that free always actually stays free.\n * On free the traffic is uncompensated, so serving it is a\n * straight loss and the least destructive way to stop the bleeding is to stop\n * paying for the expensive render.\n *\n * On a metered plan the same traffic is *invoiced*, so taking a paying\n * customer's site off the air would trade a bill they agreed to for an outage\n * they did not. Their ceiling still trips, still flags the host and still\n * escalates to staff — it just does not change what a visitor gets. Whether a\n * paying host past its OWN ceiling should also be degraded is a product call\n * and is left open on purpose; flipping it is this one function.\n *\n * Enterprise does not meter either, and is NOT degraded, for the same reason\n * a metered plan is not: its traffic is paid for, by agreement rather than by\n * invoice. Since 2026-09-07 its bandwidth band is a finite fallback (twice\n * Agency's), so without this exclusion the ceiling an agreement never named\n * would have taken a contracted customer's site off the air three times past\n * a default nobody chose for them. It still trips, flags and pages.\n */\nexport function bandwidthCeilingDegradesRender(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n return !planMetersInfraOverage(org) && !isCustomPricedPlan(resolvePlan(org))\n}\n\n/**\n * `hosts/{id}.bandwidthCeiling` — the flag the beacon stamps and the render\n * path reads. Month-scoped so it **self-clears at the month boundary** with\n * no staff action and no write, the same property `LockdownState.untilMs`\n * has: a site contained in August serves normally on September 1.\n *\n * Admin-SDK-only. It is frozen against client writes in\n * `cloud/firebase-firestore.rules` alongside the `suspendedAt` family, for\n * the identical reason: a site that could clear its own containment does not\n * have one.\n */\nexport interface HostBandwidthCeiling {\n /** `YYYY-MM` (UTC) the trip belongs to. */\n month: string\n /** The ceiling that was crossed, in page views. */\n ceiling: number\n /** The month count observed when it tripped. */\n used: number\n trippedAtMs: number\n /** False when the plan meters the overage — flagged, not degraded. */\n degraded: boolean\n}\n\n/**\n * Read the host document's containment flag, tolerantly. Returns null unless\n * the flag is present, well-formed AND belongs to `month` — an August trip\n * must not silence a September visitor.\n */\nexport function normalizeHostBandwidthCeiling(\n host: Record<string, any> | null | undefined,\n month: string,\n): HostBandwidthCeiling | null {\n const raw = host?.['bandwidthCeiling']\n if (!raw || typeof raw !== 'object') return null\n if (String(raw.month ?? '') !== month) return null\n const ceiling = Number(raw.ceiling ?? 0)\n if (!Number.isFinite(ceiling) || ceiling <= 0) return null\n return {\n month,\n ceiling,\n used: Math.max(0, Number(raw.used ?? 0) || 0),\n trippedAtMs: Math.max(0, Number(raw.trippedAtMs ?? 0) || 0),\n degraded: raw.degraded === true,\n }\n}\n\n/**\n * Is the render path required to serve the capped notice instead of the page?\n *\n * Three things must hold: the flag is for THIS month, the trip was recorded\n * as one that degrades, and the org's CURRENT plan still degrades.\n *\n * The stored `degraded` alone cannot answer the last one. It is written once,\n * at trip time, and nothing rewrites it: the beacon stops evaluating a host\n * once it has tripped this month, and a contained site serves no pages to\n * beacon from. So a host that upgraded mid-month stayed on the notice until\n * the 1st, while the owner notice and the visitor notice both promised an\n * upgrade would bring it back. Re-deriving from `org` on every read releases\n * it on the next org-doc TTL with no write, the same asymmetry\n * `bandwidthCapEngaged` keeps for the plan band.\n *\n * Both halves still hold in the other direction: a flag written for a paying\n * host (`degraded: false`) can never take that host down, whatever its plan\n * becomes later. An unknown org (`null`) resolves as unmetered, so a lookup\n * failure keeps the containment rather than lifting it.\n */\nexport function bandwidthCeilingDegradesHost(\n host: Record<string, any> | null | undefined,\n month: string,\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n return (\n normalizeHostBandwidthCeiling(host, month)?.degraded === true &&\n bandwidthCeilingDegradesRender(org)\n )\n}\n\n/** UTC `YYYY-MM`, the key both the beacon and the render path agree on. */\nexport function bandwidthCeilingMonthKey(now: Date = new Date()): string {\n return now.toISOString().slice(0, 7)\n}\n\n/**\n * Visitor-facing copy for a contained site. Says what happened and that it\n * ends, and names nothing about the plan, the count or the owner — this is\n * read by a stranger to the site, exactly like the form ceiling's refusal\n * body (AGL-1666).\n */\nexport function bandwidthCeilingNotice(): { title: string; body: string } {\n return {\n title: 'This site is temporarily unavailable',\n body: 'It has served more traffic this month than its plan includes. It will be back at the start of next month, or sooner if the owner upgrades.',\n }\n}\n\n/**\n * What a quota check costs a caller that intends to ENFORCE it (AGL-2163).\n *\n * `checkDataStorageQuota` and `checkApiRequestQuota` each carry an `allowed`\n * field, and their docblocks promise it refuses — \"plans without one (free)\n * hard-block at the included size\", \"plans without API access have\n * `included: 0` and always block\". Nothing read it. Both functions appeared\n * only in `/api/billing/report-usage`, which reads `overageMonthlyUsd` and\n * ignores `allowed`, so the promise was documentation and the free tier's\n * \"runtime braces\" on those two dimensions did not exist.\n *\n * Enforcing them naively means measuring first, and for dataset storage the\n * measurement is O(datasets) reads — far too much for a per-record write.\n * This makes the cheap answer available: for every plan shipping today the\n * verdict does not depend on the measurement at all, because a plan either\n * has an overage RATE (then it always allows and meters) or a band of zero\n * (then it always refuses). Measuring is required only for the shape a staff\n * `entitlementOverrides` can create — a finite, non-zero band on a plan with\n * no rate — and only then does the caller pay a read.\n *\n * - `'always-blocks'` — refuse now; no measurement, no read.\n * - `'never-blocks'` — proceed; no measurement, no read. **This is every\n * metered/paid plan**, so enforcement costs a paying customer nothing and\n * can never refuse them.\n * - `'measure'` — the verdict depends on usage; measure and call the check.\n */\nexport type QuotaEnforcementShape =\n | 'always-blocks'\n | 'never-blocks'\n | 'measure'\n\nfunction enforcementShape(\n atZero: boolean,\n atCeiling: boolean,\n): QuotaEnforcementShape {\n if (!atZero && !atCeiling) return 'always-blocks'\n if (atZero && atCeiling) return 'never-blocks'\n return 'measure'\n}\n\n/**\n * Enforcement shape of {@link checkDataStorageQuota} for this org, before\n * anything has been measured. @see QuotaEnforcementShape\n */\nexport function dataStorageEnforcementShape(\n org: Partial<AglynOrgBilling> | null | undefined,\n): QuotaEnforcementShape {\n return enforcementShape(\n checkDataStorageQuota(org, 0).allowed,\n checkDataStorageQuota(org, Number.MAX_SAFE_INTEGER).allowed,\n )\n}\n\n/**\n * Enforcement shape of {@link checkApiRequestQuota} for this org, before\n * anything has been measured. @see QuotaEnforcementShape\n */\nexport function apiRequestEnforcementShape(\n org: Partial<AglynOrgBilling> | null | undefined,\n): QuotaEnforcementShape {\n return enforcementShape(\n checkApiRequestQuota(org, 0).allowed,\n checkApiRequestQuota(org, Number.MAX_SAFE_INTEGER).allowed,\n )\n}\n"],"names":["isOrgOverrideReasonCode","PLATFORM_BRAND_NAME","PLATFORM_BRANDING_PROFILE","listPluginFeatures","listPluginSeatAddons","pluginSeatAddon","pluginOrgCapacity","declaredMeterReading","pluginCostAxes","pluginCostAxisFields","planQuotaOf","pluginPlanFeatureRow","pluginPlanQuotaRow","pluginPlanQuotas","UNLIMITED","Number","POSITIVE_INFINITY","FORMS_PER_HOST_CEILING","isUnlimitedQuota","limit","isFinite","formatQuotaLimit","unit","String","restoreQuotaLimit","value","unlimited","ENTERPRISE_EMAIL_SENDS_PER_MONTH","PLAN_ENTITLEMENTS","free","hostLimit","screensPerHost","sharedLayoutsPerHost","templatesPerHost","storagePerHostMb","membersPerHost","managersPerOrg","maxManagersPerOrg","maxMembersPerHost","bandwidthGb","formsPerHost","variablesPerHost","functionsPerHost","workflowsPerHost","servicesPerHost","redirectsPerHost","contactsPerHost","emailSendsPerMonth","actionRunsPerMonth","apiRequestsPerMonth","datasetsPerOrg","maxDatasetsPerOrg","recordsPerDataset","dataStorageMbPerOrg","productsPerHost","inventoryLocations","posRegisters","transactionFeePhysicalPct","transactionFeeDigitalPct","features","abTesting","versioning","reusableComponents","customDomain","customSendingDomain","dedicatedSendingDomain","removeBranding","scheduledPublishing","aiAssist","aiGenerative","workflows","dataStore","videoMedia","bookings","crm","outreach","interactions","actions","webhooks","apiAccess","siteExport","multilingual","eventCalendar","redirects","screenAnalytics","mediaCdn","marketingOverlays","commerce","pos","contentGating","productReviews","abandonedCart","dropshipRouting","whiteLabel","ssoEnabled","starter","pro","business","scale","advanced","agency","enterprise","fillPluginPlanEntitlements","rows","plan","Object","keys","quotas","key","entries","SELF_SERVE_PLANS","isCustomPricedPlan","PLAN_LABELS","EVENT_CALENDAR_ADDON_MONTHLY_USD","POS_REGISTER_ADDON_MONTHLY_USD","AI_ADDON_CREDITS_PER_MONTH","aiAddonName","brand","EXTRA_HOSTS_ADDON_MAX","POS_REGISTERS_ADDON_MAX","PLAN_PRICING","basePriceMonthlyUsd","basePriceAnnualMonthlyUsd","extraHostMonthlyUsd","extraSeatMonthlyUsd","extraCollaboratorMonthlyUsd","extraDatasetMonthlyUsd","extraDataGbMonthlyUsd","extraApiRequestsUsdPer1k","extraAssistCreditsUsdPer1k","aiAddonMonthlyUsd","extraContactsUsdPer1k","extraEmailSendsUsdPer1k","meteredInfraPassThrough","compPricing","pricing","withheld","PLAN_COMP_PRICING","fromEntries","map","resolvePlanPricing","org","resolveEffectivePlan","resolvePlanComp","DEAD_SUBSCRIPTION_STATUSES","Set","subscriptionStatusOf","mirrored","billingStatus","subscription","status","orgSubscriptionState","has","instantOf","record","Date","isNaN","getTime","toISOString","millis","parse","toDate","date","call","seconds","_seconds","trimmedOrNull","trim","readOrgPlanComp","entitlements","raw","planComp","undefined","Array","isArray","prototype","hasOwnProperty","reason","uncapped","note","grantedBy","grantedAt","isUncappedPlanComp","planCompLabel","comp","inForce","name","terms","filter","Boolean","join","planCompPhrase","withArticle","phrase","test","describeOrgPlan","stored","storedPlan","compInForce","decidedBy","effectivePlan","subscriptionStatus","orgPlanDescriptionSentence","description","label","dormant","isBillingSubscription","orgListPriceMonthlyUsd","custom","customMonthlyUsd","Math","round","annual","interval","addons","seatAddons","seats","quantity","price","max","managers","members","datasets","hosts","aiAddonUnits","aiAddon","applyDiscountUsd","listPriceUsd","discount","percentOff","pct","min","amountOffUsd","isEnterpriseOrg","ENTERPRISE_PLAN_LABEL","orgMonthlyRevenueUsd","listPrice","STRIPE_PROCESSOR_FEE_PCT","STRIPE_PROCESSOR_FEE_FIXED_USD","netOfProcessorFee","grossMonthlyUsd","fixedPerMonth","net","orgNetMonthlyRevenueUsd","gross","NET_MARGIN_FLOOR_PCT","NET_MARGIN_WARN_BAND_PCT","MARGIN_SCOPE_NOTE","netMarginRating","marginPct","INFRA_COGS_PER_SITE_USD","METERED_MARKUP","METERED_PRICE_MULTIPLE","publishedMeteredPrice","costPerQuotedUnitUsd","decimals","ceil","toFixed","PAGE_VIEW_CDN_REQUEST_COST_USD","ORG_COGS_UNIT_RATES_USD","storagePerGbMonth","perPageView","perFormSubmission","dataStoragePerGbMonth","perApiRequest","perContactMonth","perEmailSend","perRun","cogsRateOf","axis","rate","Error","id","orgMonthlyCogsUsd","rollup","siteCount","num","parsed","rates","core","order","usd","storageGb","pageViews","dataStorageMb","apiRequests","emailSends","declared","breakdown","sort","a","b","measuredUsd","values","reduce","sum","x","floorUsd","cogsUsd","basis","orgCogsInputFrom","source","read","field","input","hostCount","orgCogsPreview","ready","cogs","orgCogsBasisSummary","month","when","measured","DISCOUNT_APPROVAL_THRESHOLD_PCT","DISCOUNT_DEPTH_WARN_PCT","DISCOUNT_DEPTH_BLOCK_PCT","orgSiteCount","length","checkDiscountMargin","options","floorPct","grossUsd","flatCogsUsd","measuredCogsUsd","cogsMeasured","infraCogsUsd","discountedUsd","netUsd","depthPct","rating","depthRating","cogsRating","severity","ok","warn","block","resolvePlan","resolvePurchasedAddons","hasAiAddon","RETIRED_ENTITLEMENT_KEYS","SEAT_BANDS","PRICE_ENTITLEMENT_KEYS","withEveryCapLifted","lifted","resolveOrgEntitlements","resolved","resolveCappedOrgEntitlements","purchased","defaults","withPluginFeatureDefaults","overrides","featureOverrides","datasetsPerHost","legacyDatasets","maxDatasetsPerHost","legacyMaxDatasets","quotaOverrides","merged","included","extraHosts","folded","foldPluginSeatAddons","pluginSeatAddonUnits","units","floor","maxUnits","hasPluginSeatAddon","out","addon","next","quota","bag","perUnit","perUnitByPlan","current","feature","row","defaultByPlan","resolveRegisterSeatPool","rawPurchased","registerAllocations","byHost","allocated","hostId","available","resolveHostRegisterCap","planCap","resolveCollaboratorSeatPool","collaboratorAllocations","resolveHostCollaboratorCap","maxSeats","assigned","checkHostCollaboratorQuota","currentUsage","addonPriceUsd","assignedSeats","allowed","remaining","upgradeRequired","retainedOverCap","checkHostRegisterQuota","resolveTransactionFeePct","productType","saleProcessingCostCents","chargeCents","SALE_PROCESSING_PERCENT","SALE_PROCESSING_FIXED_CENTS","resolveTransactionFeeCents","feeBaseCents","charge","base","take","saleProcessingPassThroughPercent","amountCents","amount","rateHundredths","fixedHundredths","resolveSubscriptionFeePercent","passThrough","resolveMarketplaceFeePct","takeRatePctOf","fallback","TAKE_RATE_KEY","bindingMarketplaceFeePct","CARD_PROCESSING_PERCENT","BNPL_PROCESSING_PERCENT","PROCESSING_FIXED_CENTS","planGrantingFeature","find","planLabelGrantingFeature","checkEntitlement","cleanBrandString","trimmed","resolveBrandingProfile","profile","brandingProfile","some","productName","logoUrl","logoDarkUrl","faviconUrl","faviconDarkUrl","primaryColor","supportUrl","homeUrl","fromName","emailLogoUrl","customConsoleDomain","brandMergeTokens","branding","PLATFORM_GENERATOR_NAME","showsPlatformAttribution","hasBrandingProfile","checkSeatQuota","kind","checkQuota","checkDatasetQuota","maxDatasets","checkPluginOrgCapacityQuota","capacity","includedEntitlement","ceiling","purchaseCeilingEntitlement","addonKind","checkDataStorageQuota","usedMb","includedMb","overageRateUsd","used","overageMb","overageGb","remainingMb","overageMonthlyUsd","checkApiRequestQuota","usedRequests","overageRequests","checkCrmRecordsQuota","usedRecords","overageRecords","checkContactQuota","CRM_EMAIL_USAGE_COLLECTION","crmEmailUsageDayKey","now","slice","checkCrmEmailQuota","usedToday","resetsAt","UTC","getUTCFullYear","getUTCMonth","getUTCDate","emailSendsOverage","total","sends","priceEmailSendOverage","overageSends","over","planMetersInfraOverage","checkFormSubmissionQuota","usedThisMonth","metered","overageSubmissions","FORM_ABUSE_CEILING_MULTIPLE","FORM_ABUSE_CEILING_FLOOR","FORM_ABUSE_CEILING_UNLIMITED","checkFormSubmissionAbuseCeiling","exceeded","ESTIMATED_PAGE_TRANSFER_BYTES","pageViewsFromBandwidthGb","bandwidthGbFromPageViews","DECIMAL_GB_BYTES","BINARY_GB_BYTES","ORIGIN_MEDIA_STREAM_SECONDS_PER_GB","ORIGIN_MEDIA_REQUESTS_PER_GB","ORIGIN_MEDIA_SERVE_COST_USD_PER_GB","fastDataTransfer","fastOriginTransfer","storageEgress","streamingMemory","streamingCpu","requests","ORIGIN_MEDIA_COST_USD_PER_GB","term","PAGE_VIEW_PUBLISHED_PRICE_USD","originMediaOverageHolds","weight","billedPerGbUsd","originMediaBandHolds","bandCostPerGibUsd","originCostPerGibUsd","ORIGIN_MEDIA_BANDWIDTH_WEIGHT","tenths","ORIGIN_MEDIA_BANDWIDTH_SENTENCE","BANDWIDTH_ABUSE_CEILING_MULTIPLE","BANDWIDTH_ABUSE_CEILING_FLOOR","BANDWIDTH_ABUSE_CEILING_UNLIMITED","BANDWIDTH_CEILING_CODE","checkBandwidthAbuseCeiling","usedPageViewsThisMonth","includedGb","includedViews","bandwidthCeilingDegradesRender","normalizeHostBandwidthCeiling","host","trippedAtMs","degraded","bandwidthCeilingDegradesHost","bandwidthCeilingMonthKey","bandwidthCeilingNotice","title","body","enforcementShape","atZero","atCeiling","dataStorageEnforcementShape","MAX_SAFE_INTEGER","apiRequestEnforcementShape"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/plan-entitlements.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n AglynOrgBilling,\n CoreOrgEntitlements,\n CoreOrgFeatureFlags,\n OrgBrandingProfile,\n OrgDiscount,\n OrgEntitlements,\n OrgFeatureFlags,\n OrgPlan,\n OrgSeatAddons,\n} from '../foundation'\nimport type {\n PluginOrgFeatures,\n PluginOrgQuotas,\n} from '../plugin-manager/plugin-entitlement-keys'\nimport {\n isOrgOverrideReasonCode,\n type OrgOverrideReasonCode,\n} from './org-override-reason'\nimport type { ResolvedBrandingProfile } from './platform-brand'\nimport {\n PLATFORM_BRAND_NAME,\n PLATFORM_BRANDING_PROFILE,\n} from './platform-brand'\nimport {\n listPluginFeatures,\n listPluginSeatAddons,\n pluginSeatAddon,\n} from '../plugin-manager/plugin-entitlements'\nimport { pluginOrgCapacity } from '../plugin-manager/plugin-org-capacity'\nimport {\n declaredMeterReading,\n pluginCostAxes,\n pluginCostAxisFields,\n} from '../plugin-manager/plugin-usage-axes'\nimport {\n planQuotaOf,\n pluginPlanFeatureRow,\n pluginPlanQuotaRow,\n pluginPlanQuotas,\n} from '../plugin-manager/plugin-plan-entitlements'\n\n/** Sentinel for quotas a plan does not cap; `checkQuota` always allows. */\nexport const UNLIMITED = Number.POSITIVE_INFINITY\n\n/**\n * How many saved form definitions one site may hold on the plans at the top\n * of the ladder: Scale, Advanced, Agency and Enterprise.\n *\n * Since 2026-10-06 the saved-form catalog IS a tier lever (AGL-3597): Free 1,\n * Starter 5, Pro 25, Business 100, and this ceiling above them. Before that\n * date every paid plan carried this number and Free carried none, which made\n * a Starter catalog of 500 forms a give-away no pricing page listed. The\n * ladder sits close to the published form-count ladders in the field (Wix\n * 4/10/25/75, Jotform 5/25/50/100), and the allowance is listed on the\n * pricing pages as \"Saved forms per site\".\n *\n * A form is no longer gated by `reusableComponents`: a Free site can save its\n * one form, and only components stay Starter-and-above. The count is refused\n * at the CREATE and nowhere else, so a site already holding more than its new\n * allowance keeps every form it has; it only cannot add another.\n *\n * The value stays STRICTLY below `FORMS_MAX_PER_HOST` — the page size two\n * listing reads use. The gap is deliberate: a catalog can sit above this\n * ceiling when a per-org override is withdrawn, and the two numbers must stay\n * tellable apart so a surface reading the window where it means the ceiling\n * is never accidentally right. `forms.spec.ts` pins both halves.\n *\n * A per-org `entitlements.formsPerHost` override still resolves ahead of the\n * plan's number, so a contract can raise or lower it for one org without\n * moving the allowance everyone else on the plan is measured against.\n */\nexport const FORMS_PER_HOST_CEILING = 500\n\n/**\n * Is this quota value \"no cap\"? (AGL-2482; AGL-2223 is the same class.)\n *\n * WHY A PREDICATE AND NOT `x === UNLIMITED`. `UNLIMITED` is\n * `Number.POSITIVE_INFINITY`, and `JSON.stringify(Infinity)` is **`null`** —\n * so the moment an entitlement crosses a route boundary the sentinel is gone\n * and `=== UNLIMITED` is false for a plan that really is uncapped. Worse, the\n * client then reads `Number(null)`, which is `0`, and `Number.isFinite(0)` is\n * TRUE: the value sails through every guard written to reject a payload that\n * cannot state the terms, and the console renders a cap of zero for the most\n * expensive plan on the price list.\n *\n * So: `Infinity`, `null`, `undefined` and `NaN` all read as unlimited here.\n *\n * THIS IS A BACKSTOP, NOT THE FIX. `null` on the wire genuinely cannot\n * distinguish \"unlimited\" from \"the field is missing\", and answering\n * \"unlimited\" for a missing field is the permissive direction — the same\n * shape as `checkQuota(undefined)` resolving to Free. A route that serialises\n * a quota must therefore send a FINITE number plus an explicit boolean flag\n * (see `/api/billing/storage-overage`), and the client must rebuild the\n * sentinel from the flag with `restoreQuotaLimit` before it does arithmetic.\n * This predicate exists so that a surface which has not been given the flag\n * still renders \"Unlimited\" instead of `0`, `null` or `Infinity`.\n */\nexport function isUnlimitedQuota(limit: number | null | undefined): boolean {\n return limit == null || !Number.isFinite(limit)\n}\n\n/**\n * A quota cap, written the way every surface in the console must write it:\n * `Unlimited` for an uncapped plan, the number otherwise.\n *\n * ONE implementation on purpose. Six surfaces had grown their own copy of the\n * `limit === UNLIMITED ? 'Unlimited' : String(limit)` line and a seventh\n * category — the ones that interpolated the raw number — shipped\n * \"Every site can run null registers on your current plan\" and\n * \"Screen limit reached (Infinity)\" to customers.\n *\n * @param limit The cap. Non-finite, `null` and `undefined` are unlimited.\n * @param unit Appended to a finite number only — `MB`, `registers`. Never\n * appended to `Unlimited`, because \"Unlimited MB\" reads as a typo.\n */\nexport function formatQuotaLimit(\n limit: number | null | undefined,\n unit?: string,\n): string {\n if (isUnlimitedQuota(limit)) return 'Unlimited'\n return unit ? `${limit} ${unit}` : String(limit)\n}\n\n/**\n * Rebuild the `UNLIMITED` sentinel a JSON round-trip flattened, from the\n * explicit flag the route sent alongside the number.\n *\n * The client half of the wire contract. Once this has run, every comparison\n * downstream is ordinary arithmetic that happens to be correct: `used >\n * Infinity` is false, `Math.max(0, used - Infinity)` is `0`, and nothing has\n * to remember that this particular number might be a `null` in disguise.\n *\n * That mattered more than the display did. `1 > null` is **true**, so a site\n * on an unlimited plan was reported \"1 over the limit\" while the readout\n * beside it said `1/∞` — a false limit warning on a plan that has no limit.\n *\n * @param value The finite number the route sent (or the `null` it sent for a\n * pre-fix payload).\n * @param unlimited The route's explicit flag. `undefined` means the route has\n * not been taught to send one, and then `value` decides — non-finite reads\n * as unlimited, which is the `isUnlimitedQuota` backstop.\n */\nexport function restoreQuotaLimit(\n value: number | null | undefined,\n unlimited?: boolean,\n): number {\n if (unlimited === true) return UNLIMITED\n if (unlimited === false) return Number(value)\n return isUnlimitedQuota(value) ? UNLIMITED : Number(value)\n}\n\n/**\n * Campaign emails an Enterprise agreement includes per month, before the\n * contract says otherwise.\n *\n * ## Why the one Enterprise quota that is a number\n *\n * Every other Enterprise band is `UNLIMITED` because the thing behind it is\n * infrastructure we already meter and price into the deal — storage, page\n * views, seats. Email is not that. It is a per-message charge from a third\n * party, so an unbounded allowance is an unbounded liability with no meter\n * underneath it and no ceiling a negotiation ever had to name.\n *\n * `UNLIMITED` was also unrepresentable off-process. It is\n * `Number.POSITIVE_INFINITY`, `JSON.stringify(Infinity)` is `null`, and\n * `Number(null)` is `0` — so the most expensive plan on the price list\n * serialised to a cap of ZERO through any route that did not also send the\n * explicit flag `restoreQuotaLimit` rebuilds from. A finite number crosses\n * the wire as itself.\n *\n * ## Why this number\n *\n * Twice Agency's band — the rule every Enterprise fallback follows since the\n * 2026-09-07 pricing decision (see the `enterprise` row of\n * `PLAN_ENTITLEMENTS`) — and inside what the sending platform can actually\n * deliver to one workspace with room to spare. At the shipped constants a\n * workspace's share is 500 messages an hour and a projected month is 720\n * hours, so 360,000 is the ceiling nothing can exceed however it is sold.\n * 260,000 spends 520 of those 720 hours, leaving the rest for bursts, retries\n * and domain warm-up — `email-ceiling-dimensioning.spec.ts` holds that\n * relation, and holds the platform rate still so the gap cannot be closed by\n * quietly raising it.\n *\n * Selling more than that would be selling mail that cannot leave the\n * building: the hourly ceiling DEFERS rather than refuses, so the excess does\n * not bounce, it simply never sends.\n *\n * ## It is a DEFAULT, and a contract raises it\n *\n * `resolveOrgEntitlements` applies a per-org `entitlements.emailSendsPerMonth`\n * override ahead of this, so a deal that buys more email buys it on that org\n * without moving the figure every other agreement is measured against — the\n * same mechanism every numeric entitlement uses.\n */\nexport const ENTERPRISE_EMAIL_SENDS_PER_MONTH = 260_000\n\n/**\n * Plan → default entitlements. Versioned with the app so pricing changes are\n * code-reviewed; per-org overrides live on `org.entitlements` and win\n * key-by-key. Tier table aligned to the Tenant Billing & SaaS Plans proposal\n * (AGL-67, 2026-07-07): storage-per-host is media storage; the published\n * total-site-size cap it used to exceed by design was retired in AGL-2133.\n * Metered costs are passed through\n * from Firebase/Vercel at cost × 1.30 separately (AGL-41).\n */\n/** Legacy host-keyed dataset overrides resolved into org keys (AGL-240). */\ntype LegacyEntitlementKeys = 'datasetsPerHost' | 'maxDatasetsPerHost'\n\n/**\n * Keys `OrgEntitlements` still carries so stored documents type-check, but\n * that no plan declares and nothing resolves (AGL-2133). Kept as a named type\n * rather than folded into `LegacyEntitlementKeys`: legacy keys are resolved\n * INTO their replacements, retired ones are dropped, and conflating the two\n * would eventually resolve a retired value into something.\n *\n * `RETIRED_ENTITLEMENT_KEYS` is the runtime half and must list the same keys.\n */\ntype RetiredEntitlementKeys = 'totalSiteSizeMb'\n\n/**\n * Keys stored under `entitlements` that are not quotas at all (AGL-3034): the\n * staff plan comp rides the same staff-owned map, and is read through\n * `readOrgPlanComp` rather than resolved into a limit.\n */\ntype NonQuotaEntitlementKeys = 'planComp'\n\n/**\n * Fully-resolved entitlements: every LIVE CORE quota present, the core\n * features complete, and whatever the plugins declared beside them.\n *\n * `Required` is over the CORE halves on purpose (AGL-3124). It is what keeps\n * a plan table honest — a plan that forgets a platform quota or a platform\n * gate does not compile — and that reasoning does not carry to a plugin's\n * key: a plugin's figure per plan comes from its own `plan-entitlements`\n * declaration (AGL-3080), whose rows are typed per plan and so cannot forget\n * one either, and requiring it here would mean every plan row in the core\n * naming every installed plugin's keys, which is the arrangement the seam\n * retires. So the plugin halves join as declared: present once a plugin\n * declares them, and optional, because core compiles without the plugin's\n * type and a workspace without the plugin has no value for them.\n *\n * They join in their MAPPED form (`PluginOrgQuotas`, `PluginOrgFeatures`)\n * because an intersection with a bare interface has no implicit string index\n * signature, and readers that hand these objects to a `Record<string, …>`\n * parameter — the quota line, the digest gates — would stop compiling for a\n * plugin half that is empty.\n */\nexport type ResolvedOrgEntitlements = Required<\n Omit<\n CoreOrgEntitlements,\n | 'features'\n | LegacyEntitlementKeys\n | RetiredEntitlementKeys\n | NonQuotaEntitlementKeys\n >\n> &\n PluginOrgQuotas & {\n features: Required<CoreOrgFeatureFlags> & PluginOrgFeatures\n }\n\nexport const PLAN_ENTITLEMENTS: Record<OrgPlan, ResolvedOrgEntitlements> = {\n free: {\n hostLimit: 1,\n screensPerHost: 5,\n sharedLayoutsPerHost: 1,\n templatesPerHost: 10,\n storagePerHostMb: 250,\n membersPerHost: 1,\n managersPerOrg: 1,\n maxManagersPerOrg: 1,\n maxMembersPerHost: 1,\n bandwidthGb: 2,\n // One saved form per Free site (AGL-3597). The saved form does not ride\n // `reusableComponents` — components stay Starter-and-above, a form does\n // not — so `/api/hosts/resources` refuses a second form on this number,\n // with the upgrade path, rather than refusing the first on a feature.\n //\n // A `Form` node placed on a page still needs no definition to collect,\n // and the 20 submissions above are the band those replies spend.\n //\n // The ladder: Free 1, Starter 5, Pro 25, Business 100, and\n // `FORMS_PER_HOST_CEILING` on Scale and above.\n formsPerHost: 1,\n // One reusable component per Free site (AGL-3615), counted the way the\n // one saved form is: the create is refused on this number, with the\n // upgrade path, and no longer on `reusableComponents`, which stays off\n // here because Free's allowance has a count. Every paid plan is\n // `UNLIMITED`, the allowance it had when components were only a feature.\n componentsPerHost: 1,\n variablesPerHost: 3,\n functionsPerHost: 1,\n workflowsPerHost: 0,\n servicesPerHost: 0,\n redirectsPerHost: 0,\n contactsPerHost: 100,\n emailSendsPerMonth: 0,\n actionRunsPerMonth: 0,\n apiRequestsPerMonth: 0,\n datasetsPerOrg: 0,\n maxDatasetsPerOrg: 0,\n recordsPerDataset: 0,\n dataStorageMbPerOrg: 0,\n productsPerHost: 0,\n inventoryLocations: 1,\n posRegisters: 0,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 0,\n features: {\n abTesting: false,\n versioning: false,\n reusableComponents: false,\n customDomain: false,\n // Sending as a domain the customer owns starts at Pro, with campaign\n // email. Free sends no marketing at all — `emailSendsPerMonth` is 0 —\n // and its transactional mail leaves on the shared pool, which needs no\n // DNS from anybody.\n customSendingDomain: false,\n // The expensive shape, and the one Free must never reach by default: a\n // provider slot, three records in the platform's own zone and a place\n // in the re-verification sweep, spent per site on a tier that pays\n // nothing. The pool carries this tier's mail at no per-site cost.\n dedicatedSendingDomain: false,\n removeBranding: false,\n scheduledPublishing: false,\n // The two AI flags point opposite ways on Free, on purpose (AGL-2925).\n // `aiAssist` is the guided rung of the console assistant — page-aware\n // level-2 answers and the copy assistant — and starts at Starter; Free\n // keeps the docs-grounded level-1 chat it always had. `aiGenerative`\n // is the generative door the taste is FOR: a Free workspace may\n // generate against its 300-credit band, behind the wall and every\n // precaution the constant lists, so the landing page's \"generate your\n // first page free\" is a thing the product does.\n aiAssist: false,\n aiGenerative: true,\n workflows: false,\n dataStore: false,\n videoMedia: false,\n bookings: false,\n // The CRM starts at Starter (AGL-2611), whole, and Free has none of it\n // (AGL-2851): no Leads, Contacts, Companies, Deals, Tasks, Reports,\n // Fields or Settings, no CRM automation step and no CRM REST resource.\n // What Free's sites capture still counts against its 100-record band,\n // and exporting and erasing those people stay on every plan.\n // The Drive pricing decision of 2026-09-05 records why Starter and\n // not Pro: the field prices a CRM seat at $14–25 a month, so Starter\n // with the suite included is the competitive entry, and gating a\n // tier higher hands the small-business buyer to a free CRM elsewhere.\n crm: false,\n // Sequences (AGL-2974) is on NO plan, this one through Enterprise: which\n // tiers carry sequences and connected mailboxes, and at what caps, is\n // an owner decision still to be made. Until then an organization gets\n // it only through the per-org `entitlements.features.outreach`\n // override, and the Firestore rules read that override alone.\n outreach: false,\n interactions: true,\n actions: false,\n webhooks: false,\n apiAccess: false,\n siteExport: false,\n multilingual: false,\n eventCalendar: false,\n redirects: false,\n screenAnalytics: false,\n /**\n * CDN delivery and responsive image variants, on every plan (AGL-1152).\n *\n * Without this, media is addressed by its absolute\n * `firebasestorage.googleapis.com` URL: a visitor fetches the full-size\n * original from Storage egress on every request, with no shared edge\n * cache in front of it. With it, media is served over the CDN path,\n * which is edge-cacheable and returns a resized WebP variant.\n *\n * The ungated path therefore costs more to run, on both bytes per\n * request and origin requests per byte. Gating this would raise the cost\n * of the tier that pays nothing.\n */\n mediaCdn: true,\n marketingOverlays: false,\n commerce: false,\n pos: false,\n contentGating: false,\n productReviews: false,\n abandonedCart: false,\n dropshipRouting: false,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n starter: {\n hostLimit: 1,\n screensPerHost: 25,\n sharedLayoutsPerHost: 3,\n templatesPerHost: 50,\n storagePerHostMb: 2048,\n membersPerHost: 2,\n managersPerOrg: 2,\n maxManagersPerOrg: 5,\n maxMembersPerHost: 10,\n // Sized by the same annual-price invariant as Pro's — see that band.\n bandwidthGb: 15,\n formsPerHost: 5,\n componentsPerHost: UNLIMITED,\n variablesPerHost: 25,\n functionsPerHost: 10,\n workflowsPerHost: 3,\n servicesPerHost: 1,\n redirectsPerHost: 25,\n contactsPerHost: 1000,\n // Campaign email starts at Pro. A site that may send campaigns needs its\n // own verified provider sending domain, and provisioning one is a real\n // per-site operational cost that a campaign allowance commits the platform\n // to the moment the tier includes it. Holding the allowance at 0 here\n // keeps that cost attached to the tiers that carry it.\n //\n // Zero is a REFUSAL, not a formality: `reserveCampaignEmailSends` compares\n // `used + count > limit` inside its transaction, so any campaign of one or\n // more recipients is refused before a message leaves.\n //\n // It does NOT stop transactional mail. Receipts, invites, booking\n // reminders, workflow notifications and password resets are refused by no\n // quota at any tier (see `emailSendsPerMonth` on `OrgEntitlements`), so a\n // Starter store still confirms its orders. Those sends still count on the\n // `emailSends` cost meter; they are simply not billable here, because\n // `emailSendsOverage` yields 0 against a non-positive band.\n //\n // The contacts band above is untouched: holding an audience is not sending\n // to it, and the CRM is what a Starter site builds before it upgrades.\n emailSendsPerMonth: 0,\n actionRunsPerMonth: 0,\n apiRequestsPerMonth: 0,\n datasetsPerOrg: 2,\n maxDatasetsPerOrg: 10,\n recordsPerDataset: 1000,\n dataStorageMbPerOrg: 512,\n productsPerHost: 100,\n inventoryLocations: 1,\n posRegisters: 0,\n transactionFeePhysicalPct: 2,\n // Pricing v3 (2026-07): softened 7→5 — a 7% digital fee stacked on the\n // ~2.9%+30¢ processor was ~10% all-in, far above the website-builder\n // norm (Wix/BigCommerce/Squarespace-Core = 0% platform fee). See the\n // Pricing Decision Log + Competitive Benchmark.\n transactionFeeDigitalPct: 5,\n features: {\n abTesting: false,\n versioning: false,\n reusableComponents: true,\n customDomain: true,\n // Starter's allowance is 0 campaign sends, so a verified domain of the\n // customer's own would carry receipts and nothing else — the one class\n // of mail the shared pool already delivers with no DNS work at all. The\n // capability arrives with the sends that need it.\n customSendingDomain: false,\n // Same tier as the customer-owned shape and for the stronger reason:\n // this one draws on a provider allowance that cannot be bought past a\n // certain size, so it is rationed above the tier that carries no\n // campaign sends at all.\n dedicatedSendingDomain: false,\n removeBranding: true,\n scheduledPublishing: false,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: false,\n bookings: true,\n // The whole CRM, from the first paid tier (AGL-2611). At 100% of\n // its band and its one-to-one email cap this costs $1.73 a month\n // against a $16 annual price — the Drive pricing decision of\n // 2026-09-05 has the arithmetic — and it is the upgrade motive from\n // Free, not a line on top of a plan.\n crm: true,\n outreach: false,\n interactions: true,\n actions: false,\n webhooks: false,\n apiAccess: false,\n siteExport: false,\n multilingual: false,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: false,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: false,\n contentGating: false,\n productReviews: false,\n abandonedCart: false,\n dropshipRouting: false,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n pro: {\n hostLimit: 3,\n screensPerHost: 100,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 5120,\n membersPerHost: 3,\n managersPerOrg: 3,\n maxManagersPerOrg: 20,\n maxMembersPerHost: 25,\n // Page views are the largest term of this tier's modeled COGS. One GB is\n // 1,035 views at `ESTIMATED_PAGE_TRANSFER_BYTES`, priced where Vercel is\n // dearest because it bills in the region that serves a visitor: $0.413 of\n // weight (`perPageView` — transfer at $0.35 a decimal GB, reads at nam5)\n // and $0.224 of requests once the hosting plan's monthly allowance is\n // spent (`PAGE_VIEW_CDN_REQUEST_COST_USD` — 57 CDN requests at $3.20 per\n // million and the analytics beacon's function). So a GB of included\n // bandwidth costs $0.637, and the bandwidth band is what decides whether\n // the tier survives a customer spending the whole allowance it was sold.\n //\n // The invariant the band is sized against is the ANNUAL price ($39 a\n // month) net of Stripe's fee, with every band at 100% and the CRM seat\n // and one-to-one email terms counted — the 2026-09-07 pricing decision —\n // at that all-in gigabyte (AGL-3444), and it must cover that cost plus\n // 30%, the same markup the metered overages carry (AGL-3469). 25 GB is\n // what it carries beside the storage and campaign bands it now sits\n // with; every paid band on the ladder is sized the same way.\n // `tier-margin-floor.spec.ts` holds the model and pins the figure.\n //\n // `meteredInfraPassThrough` is true here, so traffic past the band BILLS\n // at the page-view pass-through rather than being refused or absorbed.\n bandwidthGb: 25,\n formsPerHost: 25,\n componentsPerHost: UNLIMITED,\n variablesPerHost: 100,\n functionsPerHost: 50,\n workflowsPerHost: 25,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: 100,\n contactsPerHost: 10000,\n emailSendsPerMonth: 2500,\n actionRunsPerMonth: 5000,\n apiRequestsPerMonth: 0,\n datasetsPerOrg: 5,\n maxDatasetsPerOrg: 50,\n recordsPerDataset: 10000,\n dataStorageMbPerOrg: 2048,\n productsPerHost: 2500,\n inventoryLocations: 2,\n posRegisters: 1,\n transactionFeePhysicalPct: 0,\n // Pricing v3 (2026-07): softened 5→3 to smooth the digital fee ladder\n // (Starter 5 → Pro 3 → Business 2 → Scale 1 → Advanced/Agency 0).\n transactionFeeDigitalPct: 3,\n features: {\n abTesting: false,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n // The tier campaign email starts at, and therefore the tier a domain\n // whose reputation belongs to the merchant starts at: marketing may not\n // leave on the shared pool, because one merchant's complaint rate would\n // be charged against every other site's password resets.\n //\n // It costs the platform nothing in our own zone — the customer\n // publishes the records — which is what keeps the platform's domain\n // count flat as the customer base grows. The platform subdomain a site\n // is issued is the expensive half and stays an optimization on top of\n // the pool.\n customSendingDomain: true,\n // The floor for the platform subdomain too, and the tier this one is\n // sized against: a merchant who may send campaigns is a merchant whose\n // mail needs a name of its own, and Pro is where the revenue starts\n // covering a provider slot and three zone records.\n //\n // A flag rather than a tier comparison, so a single org can be granted\n // one through `entitlements.features` without being moved up a plan —\n // the support case this capability meets most often, answered without\n // repricing an account. What the grant does not do is provision: it\n // admits a REQUEST, and the provider ceiling still decides whether the\n // request is filled.\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: false,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: false,\n apiAccess: false,\n siteExport: true,\n multilingual: false,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: false,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n business: {\n hostLimit: 10,\n screensPerHost: UNLIMITED,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 10240,\n membersPerHost: 5,\n managersPerOrg: 5,\n maxManagersPerOrg: 100,\n maxMembersPerHost: 100,\n // Sized by the same annual-price invariant as Pro's — see that band.\n bandwidthGb: 45,\n formsPerHost: 100,\n componentsPerHost: UNLIMITED,\n variablesPerHost: 1000,\n functionsPerHost: 250,\n workflowsPerHost: 100,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: UNLIMITED,\n contactsPerHost: 30000,\n emailSendsPerMonth: 15000,\n actionRunsPerMonth: 50000,\n apiRequestsPerMonth: 1_500,\n datasetsPerOrg: 10,\n maxDatasetsPerOrg: 250,\n recordsPerDataset: 100000,\n dataStorageMbPerOrg: 10240,\n productsPerHost: 10000,\n inventoryLocations: 4,\n posRegisters: 2,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 2,\n features: {\n abTesting: true,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n customSendingDomain: true,\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: true,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: true,\n apiAccess: true,\n siteExport: true,\n multilingual: true,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: true,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n // Scale (Pricing v3, 2026-07): fills the $139→$399 gap. Feature set equals\n // Business/Advanced (all commerce features); limits interpolate between\n // Business and Advanced; digital platform fee drops to 1%.\n scale: {\n hostLimit: 15,\n screensPerHost: UNLIMITED,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 15360,\n membersPerHost: 5,\n managersPerOrg: 5,\n maxManagersPerOrg: 150,\n maxMembersPerHost: 150,\n // Sized by the same annual-price invariant as Pro's — see that band.\n bandwidthGb: 70,\n formsPerHost: FORMS_PER_HOST_CEILING,\n componentsPerHost: UNLIMITED,\n variablesPerHost: 5000,\n functionsPerHost: 500,\n workflowsPerHost: 250,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: UNLIMITED,\n contactsPerHost: 100000,\n emailSendsPerMonth: 25000,\n actionRunsPerMonth: 100000,\n apiRequestsPerMonth: 5_000,\n datasetsPerOrg: 15,\n maxDatasetsPerOrg: 500,\n recordsPerDataset: 500000,\n dataStorageMbPerOrg: 25600,\n productsPerHost: 25000,\n inventoryLocations: 6,\n posRegisters: 3,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 1,\n features: {\n abTesting: true,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n customSendingDomain: true,\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: true,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: true,\n apiAccess: true,\n siteExport: true,\n multilingual: true,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: true,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n advanced: {\n hostLimit: 25,\n screensPerHost: UNLIMITED,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 20480,\n membersPerHost: 5,\n managersPerOrg: 10,\n maxManagersPerOrg: 250,\n maxMembersPerHost: 250,\n // Sized by the same annual-price invariant as Pro's — see that band.\n bandwidthGb: 80,\n formsPerHost: FORMS_PER_HOST_CEILING,\n componentsPerHost: UNLIMITED,\n variablesPerHost: UNLIMITED,\n functionsPerHost: 1000,\n workflowsPerHost: 500,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: UNLIMITED,\n contactsPerHost: 150000,\n emailSendsPerMonth: 65000,\n actionRunsPerMonth: 250000,\n apiRequestsPerMonth: 17_000,\n datasetsPerOrg: 25,\n maxDatasetsPerOrg: 1000,\n recordsPerDataset: 1000000,\n dataStorageMbPerOrg: 51200,\n productsPerHost: UNLIMITED,\n inventoryLocations: 10,\n posRegisters: 5,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 0,\n features: {\n abTesting: true,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n customSendingDomain: true,\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: true,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: true,\n apiAccess: true,\n siteExport: true,\n multilingual: true,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: true,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n whiteLabel: false,\n ssoEnabled: false,\n },\n },\n // Agency (Pricing v3, 2026-07): high-volume multi-site tier above Advanced.\n // 100 sites, 0% fees, top-of-ladder capacity. Positioned for agencies /\n // resellers — white-label, SSO, and an SLA are the follow-on product work\n // that will further differentiate it (see the Pricing Decision Log).\n agency: {\n hostLimit: 100,\n screensPerHost: UNLIMITED,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 20480,\n membersPerHost: 5,\n managersPerOrg: 10,\n maxManagersPerOrg: 500,\n maxMembersPerHost: 1000,\n // Sized by the same annual-price invariant as Pro's — see that band.\n bandwidthGb: 395,\n formsPerHost: FORMS_PER_HOST_CEILING,\n componentsPerHost: UNLIMITED,\n variablesPerHost: UNLIMITED,\n functionsPerHost: UNLIMITED,\n workflowsPerHost: UNLIMITED,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: UNLIMITED,\n contactsPerHost: 500000,\n emailSendsPerMonth: 130000,\n actionRunsPerMonth: 1000000,\n apiRequestsPerMonth: 85_000,\n datasetsPerOrg: 50,\n maxDatasetsPerOrg: 5000,\n recordsPerDataset: UNLIMITED,\n dataStorageMbPerOrg: 204800,\n productsPerHost: UNLIMITED,\n inventoryLocations: 50,\n posRegisters: 20,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 0,\n features: {\n abTesting: true,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n customSendingDomain: true,\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: true,\n aiAssist: true,\n aiGenerative: false,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: true,\n apiAccess: true,\n siteExport: true,\n multilingual: true,\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: true,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n // White-Label Phase 1: the Agency-tier ($1,299) differentiator. Only\n // this tier and Enterprise ship white-label by default.\n whiteLabel: true,\n ssoEnabled: false,\n },\n },\n // Enterprise (AGL-1118): the top of the ladder and the ONLY custom-priced\n // tier — staff-provisioned per deal (AGL-1110), never sold self-serve, so it\n // is excluded from `SELF_SERVE_PLANS` and carries no list price in\n // `PLAN_PRICING`. White-label AND SSO ship on the plan itself rather than\n // through the per-org `entitlements` override that Enterprise-as-a-label\n // needed.\n //\n // ## Every band here is a FALLBACK, and it is FINITE (2026-09-07)\n //\n // The row used to read UNLIMITED on every axis that costs infrastructure,\n // on the theory that the deal prices the usage. It does not, unless the\n // deal says so: `meteredInfraPassThrough` is false here and every overage\n // rate is the \"not for sale\" sentinel, so an Enterprise org provisioned\n // without per-org figures was the one org on the platform whose cost had no\n // bound at all — the pricing soundness audit of 2026-09-07 found its worst\n // case unbounded on every axis. A default that a contract must OVERRIDE to\n // become finite is the wrong way round.\n //\n // So the rule is Agency's band × 2 on every axis Agency bounds, and\n // UNLIMITED only where Agency is already UNLIMITED (screens, layouts,\n // templates, variables, functions, workflows, services, redirects, records\n // per dataset, products). `plan-entitlements.spec.ts` holds that relation\n // key by key, so moving an Agency band without moving this one goes red.\n //\n // A contracted per-org `entitlements.*` value still wins on every one of\n // these — `resolveOrgEntitlements` honors a numeric override on every\n // axis, which is the instrument a deal that needs more is written with. A\n // fallback is what an agreement gets before anybody has written it down.\n //\n // These are CAPS, not prices: with no rate and no pass-through the quota\n // gates refuse at the line (`checkContactQuota`, `checkApiRequestQuota`,\n // `checkFormSubmissionQuota`, `mediaStorageGate` …), exactly as Free's do.\n // At twice the largest self-serve tier's bands that line is one a deal\n // reaches only by being written without its numbers. The bandwidth abuse\n // ceiling still flags and pages staff at 3× the band, and does NOT degrade\n // the render on this plan — see `bandwidthCeilingDegradesRender`.\n //\n // `formsPerHost` is `FORMS_PER_HOST_CEILING`, the top of the saved-form\n // ladder (AGL-3597), shared with Scale, Advanced and Agency.\n enterprise: {\n hostLimit: 200,\n screensPerHost: UNLIMITED,\n sharedLayoutsPerHost: UNLIMITED,\n templatesPerHost: UNLIMITED,\n storagePerHostMb: 40_960,\n membersPerHost: 10,\n managersPerOrg: 20,\n maxManagersPerOrg: 1_000,\n maxMembersPerHost: 2_000,\n bandwidthGb: 790,\n formsPerHost: FORMS_PER_HOST_CEILING,\n componentsPerHost: UNLIMITED,\n variablesPerHost: UNLIMITED,\n functionsPerHost: UNLIMITED,\n workflowsPerHost: UNLIMITED,\n servicesPerHost: UNLIMITED,\n redirectsPerHost: UNLIMITED,\n contactsPerHost: 1_000_000,\n emailSendsPerMonth: ENTERPRISE_EMAIL_SENDS_PER_MONTH,\n actionRunsPerMonth: 2_000_000,\n apiRequestsPerMonth: 170_000,\n datasetsPerOrg: 100,\n maxDatasetsPerOrg: 10_000,\n recordsPerDataset: UNLIMITED,\n dataStorageMbPerOrg: 409_600,\n productsPerHost: UNLIMITED,\n inventoryLocations: 100,\n posRegisters: 40,\n transactionFeePhysicalPct: 0,\n transactionFeeDigitalPct: 0,\n features: {\n abTesting: true,\n versioning: true,\n reusableComponents: true,\n customDomain: true,\n customSendingDomain: true,\n dedicatedSendingDomain: true,\n removeBranding: true,\n scheduledPublishing: true,\n aiAssist: true,\n aiGenerative: true,\n workflows: true,\n dataStore: true,\n videoMedia: true,\n bookings: true,\n crm: true,\n outreach: false,\n interactions: true,\n actions: true,\n webhooks: true,\n apiAccess: true,\n siteExport: true,\n multilingual: true,\n // Event Calendar stays an add-on purchase, as on every other tier.\n eventCalendar: false,\n redirects: true,\n screenAnalytics: true,\n mediaCdn: true,\n marketingOverlays: true,\n commerce: true,\n pos: true,\n contentGating: true,\n productReviews: true,\n abandonedCart: true,\n dropshipRouting: true,\n whiteLabel: true,\n // SSO (AGL-1101) is the Enterprise differentiator — the only plan that\n // carries it by default. Lower tiers still need a per-org override.\n ssoEnabled: true,\n },\n },\n}\n\n/**\n * Each plan row gains what the plugins declared for that plan (AGL-3080):\n * a plugin's own band or gate — the AI credits, the CRM's one-to-one email\n * pace, the marketplace's take rate — is written beside the reasoning that\n * sized it, in that plugin's `plan-entitlements` module, and compiled into\n * `first-party-plugins.generated.ts`. Filled here, once, as the module\n * loads, so every reader of `PLAN_ENTITLEMENTS` — the pricing tables, the\n * plan comparison, `resolveOrgEntitlements` — reads one table and cannot tell\n * which half of a row a figure came from.\n *\n * Core's keys keep their order and a plugin's follow them. A key both declare\n * keeps CORE's figure, so a declaration can never shadow what the platform\n * already charges; `plan-entitlements.spec.ts` refuses the collision outright.\n */\nfunction fillPluginPlanEntitlements(\n rows: Record<OrgPlan, ResolvedOrgEntitlements>,\n): void {\n for (const plan of Object.keys(rows) as OrgPlan[]) {\n const quotas = rows[plan] as unknown as Record<string, unknown>\n for (const [key, value] of Object.entries(pluginPlanQuotaRow(plan))) {\n if (!(key in quotas)) quotas[key] = value\n }\n const features = rows[plan].features as unknown as Record<string, boolean>\n for (const [key, value] of Object.entries(pluginPlanFeatureRow(plan))) {\n if (!(key in features)) features[key] = value\n }\n }\n}\n\nfillPluginPlanEntitlements(PLAN_ENTITLEMENTS)\n\n/**\n * The tiers a customer can buy themselves — every plan EXCEPT `enterprise`\n * (AGL-1118), which is priced per deal and provisioned by staff (AGL-1110).\n * Any surface that offers plans for sale (the console plan-cards grid, the\n * marketing pricing table, the plan-change dialog) MUST iterate this rather\n * than `Object.keys(PLAN_ENTITLEMENTS)`, so a custom-priced tier can never\n * show a self-serve Upgrade button or a $0 headline price.\n */\nexport const SELF_SERVE_PLANS: OrgPlan[] = [\n 'free',\n 'starter',\n 'pro',\n 'business',\n 'scale',\n 'advanced',\n 'agency',\n]\n\n/**\n * Whether the plan has no list price and is quoted per deal (AGL-1118).\n * `PLAN_PRICING` still carries a row for it — the record is exhaustive by\n * type — but every figure there is 0/null and means \"no list price\", NOT\n * \"free\". Revenue for such an org comes from `subscription.customMonthlyUsd`.\n */\nexport function isCustomPricedPlan(plan: OrgPlan | undefined | null): boolean {\n return plan === 'enterprise'\n}\n\n/** Human plan names — the ONE source every plan badge/label reads. */\nexport const PLAN_LABELS: Record<OrgPlan, string> = {\n free: 'Free',\n starter: 'Starter',\n pro: 'Pro',\n business: 'Business',\n scale: 'Scale',\n advanced: 'Advanced',\n agency: 'Agency',\n enterprise: 'Enterprise',\n}\n\n/**\n * Event Calendar add-on (AGL-145): first-party, Aglyn-supported, $9/mo\n * **per organization** — cost×1.3 floor honored (mostly Firestore reads).\n * Enabled via the `eventCalendar` entitlement override at purchase.\n *\n * NOT per host, despite what this comment said until AGL-1279. AGL-145\n * shipped it per host; AGL-526 then made add-ons self-serve and capped this\n * one at `return 1` in `app/api/billing/addons/route.ts`, and\n * `resolveOrgEntitlements` flips a single org-level `features.eventCalendar`\n * boolean from any quantity >= 1. One purchase covers every host in the org.\n * The marketing copy on /pricing is written against that behaviour.\n */\nexport const EVENT_CALENDAR_ADDON_MONTHLY_USD = 9\n\n/**\n * POS Pro register add-on (AGL-329): $89/mo per extra register/location\n * (Shopify POS Pro parity). Purchased add-ons land as a per-org\n * `posRegisters` entitlement override, which\n * `resolveOrgEntitlements` already applies over the plan default.\n */\nexport const POS_REGISTER_ADDON_MONTHLY_USD = 89\n\n/**\n * The Aglyn AI add-on's credit band (AGL-2896): the credits a month the\n * add-on ADDS to the plan's `assistCreditsPerMonth` when\n * `seatAddons.aiAddon` is set. One pool and one meter — the add-on widens\n * the band the assist meter already draws on rather than opening a second\n * one, so `resolveAssistCreditBudget`, the overage price and the hard-cap\n * switch all see one number.\n *\n * ## Sized against the add-on's own price\n *\n * A credit is `ASSIST_CREDIT_COST_USD` of provider spend, so each band is a\n * cost figure: 1,000 credits are $1.00. Every band here costs at most 50% of\n * `PlanPricing.aiAddonMonthlyUsd` at full consumption — Starter $4 of $9,\n * Pro $9 of $19, Business $19 of $39, Scale $34 of $69, Advanced $49 of\n * $99, Agency $149 of $299 — which is the same line-margin floor\n * (`ASSIST_CREDIT_MIN_MARGIN_PCT`) every retail assist rate clears.\n * `tier-margin-floor.spec.ts` holds the whole tier non-negative with the\n * add-on band at 100% and the add-on's revenue counted, and pins each band's\n * share of its price.\n *\n * ## Free and Enterprise\n *\n * Free is 0: the plan sells no add-on (`aiAddonMonthlyUsd` is null), so a\n * quantity written onto a Free org adds nothing. Enterprise is finite at\n * twice Agency's, the rule every Enterprise fallback follows since the\n * 2026-09-07 decision (AGL-2654) — never `UNLIMITED`, for the reason\n * `ENTERPRISE_ASSIST_CREDITS_PER_MONTH` gives: an unbounded band reads back\n * as zero off the wire. Enterprise carries `aiGenerative` in the agreement\n * and its price is null too, so this figure is what a contract that names\n * the add-on starts from before its own number is written.\n */\nexport const AI_ADDON_CREDITS_PER_MONTH: Record<OrgPlan, number> = {\n free: 0,\n starter: 4_000,\n pro: 9_000,\n business: 19_000,\n scale: 34_000,\n advanced: 49_000,\n agency: 149_000,\n enterprise: 298_000,\n}\n\n/**\n * The generative add-on's name as a person sees it (AGL-2896): the brand,\n * then \"AI\". Read from the platform brand by default and from the org's\n * resolved `productName` where the surface has one, for the reason every\n * rendered product name is: a self-host operator renames the product by\n * configuration, and a white-label org must not see ours.\n */\nexport const aiAddonName = (brand: string = PLATFORM_BRAND_NAME): string =>\n `${brand} AI`\n\n/**\n * Purchase ceilings for the two add-on kinds that have NO per-plan hard max\n * (AGL-1738). They live here, beside the bands and the resolver they bound,\n * because they ARE the bound — and until now they were two private literals\n * in `app/api/billing/addons/route.ts`, seventeen hundred lines and one\n * project away from the `+` they cap.\n *\n * That distance is why AGL-1738 read `resolveOrgEntitlements` as unbounded\n * and proposed a `maxPosRegistersPerHost` clamp. Do not add one. Seats and\n * datasets clamp at `checkSeatQuota` / `checkDatasetQuota` because their\n * plans genuinely refuse to sell past a band (`upgradeRequired` flips and\n * the answer is a plan change). Registers and extra sites are sold flat\n * instead: `addonMax()` will sell an org up to these quantities on ANY plan\n * carrying the feature, so a band-shaped clamp in the resolver would take\n * back registers the merchant is being invoiced $89/mo each for — the\n * expensive direction of a wrong clamp, and the reason the add stays open.\n *\n * The self-serve route is the only path that writes `seatAddons` from a\n * purchase, so these are the real ceilings: a plan's band plus at most\n * `POS_REGISTERS_ADDON_MAX` registers, or plus `EXTRA_HOSTS_ADDON_MAX` sites.\n * Any console surface that windows a head-count must clear the SUM, not the\n * band (`registers-card.component.tsx` still windows at 25; AGL-1738).\n */\nexport const EXTRA_HOSTS_ADDON_MAX = 100\n\n/** @see EXTRA_HOSTS_ADDON_MAX — the flat register ceiling, per org. */\nexport const POS_REGISTERS_ADDON_MAX = 50\n\nexport interface PlanPricing {\n /** Flat monthly base price in USD (month-to-month billing). */\n basePriceMonthlyUsd: number\n /**\n * Effective per-month price when billed annually (AGL-278): the\n * Squarespace/Shopify-parity headline number. Charged as ×12 up front.\n */\n basePriceAnnualMonthlyUsd: number\n /**\n * Monthly price per host beyond `hostLimit` (AGL-68); null when the plan\n * cannot buy extra hosts.\n *\n * ## It is not a discount ladder, because what it GRANTS is not constant\n *\n * `storagePerHostMb` and `formSubmissionsPerMonth` are per-HOST\n * entitlements, and `meteredIncludedAllowance` expands both by `hostLimit`\n * — so an extra host does not add a slot, it adds that tier's per-host\n * bands to the org's included allowance. A tier that includes more per host\n * therefore hands over more capacity for the same purchase.\n *\n * A ladder that stepped down with the tier inverted that: the tiers\n * granting the most charged the least, and the two largest ran at 24% and\n * 6% margin on a line sold as an add-on.\n *\n * ## It sits above every rung's step rate, or the rungs stop meaning anything\n *\n * The shape is a flat $20 on every plan, Starter included. A flat $8 above\n * Starter, with no cap on how many hosts a plan may add, priced the\n * marginal host below every step of the base ladder, which costs $8.57 to\n * $16 per included host on annual prices and $12 to $22 on monthly: ten\n * Business sites plus fifteen extras came to $219 against Advanced's $299,\n * and plus ninety extras to $819 against Agency's $1,049, so on host count\n * alone no plan was ever worth upgrading to. $20 clears the steepest step on\n * both intervals: extra hosts are how a plan takes a few more sites, and\n * the next plan is how it takes many more (2026-09-20).\n */\n extraHostMonthlyUsd: number | null\n /**\n * Monthly price per org-manager seat beyond `managersPerOrg`\n * (AGL-112); null when the plan cannot buy extra seats.\n */\n extraSeatMonthlyUsd: number | null\n /**\n * Monthly price per site-collaborator seat beyond `membersPerHost`\n * (AGL-112, renamed AGL-888): per-site console collaborators\n * (`hosts/{id}/members`, viewer/editor/admin) — never end-user member\n * accounts, which are unlimited on every plan. Null when the plan sells\n * no extra seats. Persisted/external keys keep the legacy \"member\"\n * spelling (`seatAddons.members`, `STRIPE_PRICE_*_EXTRA_MEMBER`).\n */\n extraCollaboratorMonthlyUsd: number | null\n /**\n * Monthly price per org dataset beyond `datasetsPerOrg` (AGL-132/240);\n * null when the plan cannot buy extra datasets.\n */\n extraDatasetMonthlyUsd: number | null\n /**\n * Retail overage per GB-month of dataset storage beyond\n * `dataStorageMbPerOrg` (AGL-240); null when the plan hard-blocks at the\n * included size instead of metering.\n *\n * ⛔ **NOT the metered storage pass-through.** `/pricing` publishes two\n * per-GB-month figures and they are different products: the pass-through\n * is `METERED_BILLED_RATES_USD.storagePerGbMonth` at $0.0338, media bytes\n * on GCS at cost + 30%; this is Firestore-backed DATASET bytes, an order\n * of magnitude pricier to store, sold as a retail line. Confusing the two\n * is the easiest way to corrupt a correct price, in either direction.\n *\n * Priced against `ORG_COGS_UNIT_RATES_USD.dataStoragePerGbMonth` ($0.18)\n * at the 50% retail floor. It sat at $0.25 — a 28% line margin, close\n * enough to the pass-through's 23% that the two looked like the same kind\n * of number while being sold as opposites.\n */\n extraDataGbMonthlyUsd: number | null\n /**\n * Metered overage per 1,000 customer REST API requests beyond\n * `apiRequestsPerMonth` (AGL-634). API access starts at Business and every\n * tier above it carries it, so Free, Starter and Pro are null (no API to\n * meter).\n */\n extraApiRequestsUsdPer1k: number | null\n /**\n * Retail overage per 1,000 Aglyn Assist credits beyond\n * `assistCreditsPerMonth`.\n *\n * ## The rate is a multiplier on our own cost, and reads as one\n *\n * A credit is defined as a fixed quantity of provider spend\n * (`ASSIST_CREDIT_COST_USD`), so 1,000 credits cost us exactly $1.00 and\n * this figure IS the multiplier: $3.00 per 1,000 is cost x3, $2.00 is\n * cost x2. That is the property worth having, because the alternative —\n * a rate per message — prices a question and a generated screen the same\n * when they differ by two orders of magnitude.\n *\n * ## The floor, and why the ladder stops descending\n *\n * Every retail line carries at least a 50% margin\n * (`ASSIST_CREDIT_MIN_MARGIN_PCT`), which at $1.00 of cost per 1,000 is a\n * floor of $2.00 per 1,000 — cost x2. The ladder steps down with the tier\n * the way contacts and API requests do, and since AGL-3203 it begins on\n * Starter: $3.00 on Starter and Pro together, then $2.75 -> $2.50 ->\n * $2.25, stopping at the floor on Agency rather than running past it.\n * Starter joins at Pro's figure rather than stepping above it, because the\n * ladder descends with the tier and Starter is the rung below Pro.\n *\n * ## Where it is null, and why that is not \"free overage\"\n *\n * Null on Free because its band is a WALL by decision (AGL-2925): the\n * 300-credit taste refuses at 100% with nothing sold past it, and a rate\n * here would be a charge on a plan that must never produce one. Null on\n * Enterprise, where every rate is the \"not for sale\" sentinel and the\n * terms are contractual.\n *\n * Those two are the only nulls. Every self-serve PAID plan now includes a\n * positive assist band and therefore carries a rate, Starter included —\n * a finite band with no rate beside it is usage past a bound that is\n * silently free, which `plan-entitlements.spec.ts` forbids.\n *\n * ## What the rate decides at the gate (AGL-2653)\n *\n * A plan WITH a rate sells past its band by default: `reserveAssistMessage`\n * keeps reserving and `report-usage` bills the excess here, unless the org\n * has switched on `assistOverage.hardCap` to be refused at the band\n * instead. A plan with NO rate has nothing to sell the excess at, so its\n * band stays the wall it always was, switch or no switch — see\n * `assistBandRefuses`. Unlike email, a null here strands nothing: there is\n * no unrefusable assist traffic to absorb.\n */\n extraAssistCreditsUsdPer1k: number | null\n /**\n * The Aglyn AI add-on, flat per organization per month (AGL-2896); null\n * when the plan does not sell it.\n *\n * ## What it buys\n *\n * `features.aiGenerative` — generative building and automation, the rung\n * no self-serve tier includes — plus `AI_ADDON_CREDITS_PER_MONTH[plan]`\n * added to the plan's assist band. It also switches `aiAssist` on, so on\n * Starter it brings the two rungs the tier does not carry — the copy\n * assist and the generative one — on top of the 750 credits the plan\n * already includes (AGL-3203). It ADDS to that band; it does not replace\n * it, on Starter or on any tier.\n *\n * ## Where the ladder sits\n *\n * Priced as an uplift on the plan rather than on its own cost: the field\n * charges roughly 30% of the base price for an AI tier (Zylo's 2025\n * survey puts the AI uplift at 20–37% across SaaS; aissist.io's benchmark\n * and the Framer, Webflow and HubSpot credit add-ons sit in the same\n * band), and the share DESCENDS with the tier — 36% of Starter's $25, 34%\n * of Pro's, 28% of Business's, 28% of Scale's, 25% of Advanced's, 23% of\n * Agency's — because the larger the plan the more of its price is already\n * carrying the bands the assistant builds against.\n *\n * Annual is x12 of this with no discount, as every other add-on is\n * (`orgListPriceMonthlyUsd`). The band it funds costs at most 50% of it in\n * provider spend, the `ASSIST_CREDIT_MIN_MARGIN_PCT` floor every retail\n * assist figure holds.\n *\n * Null on Free, which sells no add-on of any kind, and on Enterprise,\n * where every figure is the \"not for sale\" sentinel and generative\n * building is in the agreement.\n */\n aiAddonMonthlyUsd: number | null\n /**\n * Metered overage per 1,000 CRM records beyond `contactsPerHost` (AGL-890,\n * widened to contacts + companies + deals in AGL-2611): audience bands,\n * Ghost-style. Paid plans meter (a growing audience is never dropped);\n * free is null and hard-bands at the included count. The key keeps its\n * name because it is a persisted Stripe-side vocabulary and a locked\n * price; the band it prices was widened, the rate was not.\n */\n extraContactsUsdPer1k: number | null\n /**\n * Retail overage per 1,000 emails beyond `emailSendsPerMonth`.\n *\n * ## What it prices, which is not what the cap refuses\n *\n * The cap is enforced against CAMPAIGN sends alone and refuses them at the\n * band — a campaign is discretionary, so the plan is allowed to say no.\n * Transactional mail is never refused at any tier, so an org finishes the\n * month above its band whenever receipts, invites, booking reminders and\n * workflow notifications carry it there. That excess is real provider spend\n * that no gate could have stopped, and this is the rate it bills at.\n *\n * ## Retail, NOT the infrastructure pass-through\n *\n * `meteredInfraPassThrough` covers exactly three meters — storage, page\n * views, form submissions — and the published term for those is \"at cost +\n * 30%\", which makes the figure in `METERED_BILLED_RATES_USD` the claim\n * itself. Email is not in that set and this rate is not derived from\n * `METERED_MARKUP`: it is a price, set beside `extraContactsUsdPer1k` and\n * `extraApiRequestsUsdPer1k`, and it descends with the tier the way both of\n * those do. Describing it as \"at cost\" anywhere would make the pass-through\n * sentence false about three meters that really are.\n *\n * Our own per-email cost is `ORG_COGS_UNIT_RATES_USD.perEmailSend`, and it\n * is a cost-model input rather than anything a customer is quoted.\n *\n * ## The floor, and why the ladder stops descending\n *\n * Every retail line must carry at least a 50% margin, and at a cost of\n * $0.90 per 1,000 that floor is $1.80. The ladder still steps down with\n * volume the way contacts and API requests do — a larger plan pays less per\n * message — it simply stops at the floor instead of running past it, which\n * is what the contacts ladder did when it reached $0.25 against a $0.20\n * cost.\n *\n * Null wherever the plan has no email band to be over, and null on\n * enterprise, where every rate is the \"not for sale\" sentinel and the terms\n * are contractual. Free and Starter are the two banded at 0: campaign email\n * begins at Pro, and `emailSendsOverage` yields 0 against a non-positive\n * band, so a rate on either would advertise a fee that can never be\n * computed — the same defect as a rate beside an UNLIMITED band.\n *\n * Every tier whose band is POSITIVE carries one, Pro upward. A finite band\n * with no rate is silently free past the band, and since transactional mail\n * cannot be refused at any tier that would be unbounded absorbed spend\n * rather than \"no overage\".\n *\n * The Starter null therefore leaves that tier's transactional mail — the\n * only mail it can send — unbilled at any volume. Bounded in practice by\n * `hostLimit: 1` and a 1,000-contact CRM, and the alternative prices a\n * password reset, but it is the one place this axis absorbs cost by design.\n */\n extraEmailSendsUsdPer1k: number | null\n /**\n * Whether the plan carries the metered infrastructure pass-through\n * (AGL-41, turned on in AGL-1280): media storage, bandwidth (page views)\n * and form submissions BEYOND the plan's included bands, billed at\n * cost × `METERED_MARKUP`. The rates are platform-wide, not per-plan —\n * this flag only says whether a plan is subject to them at all, which is\n * why it is a boolean rather than another `extra*` rate.\n *\n * Free is `false` because there is no subscription to hang a metered item\n * on; its bands stay hard caps, which is also what stops \"metered means\n * you're never cut off\" from becoming unlimited free capacity.\n *\n * Enterprise is `false` because every band it has is `UNLIMITED` and its\n * price is negotiated (`isCustomPricedPlan`) — there is no published\n * pass-through to apply, and an overage can never arise anyway.\n */\n meteredInfraPassThrough: boolean\n}\n\n/**\n * Flat subscription pricing per tier (AGL-68). Kept beside the entitlements\n * so price changes ride the same review path; Stripe price ids map to plans\n * via `STRIPE_PRICE_*` env vars on the billing API routes.\n */\nexport const PLAN_PRICING: Record<OrgPlan, PlanPricing> = {\n free: {\n basePriceMonthlyUsd: 0,\n basePriceAnnualMonthlyUsd: 0,\n extraHostMonthlyUsd: null,\n extraSeatMonthlyUsd: null,\n extraCollaboratorMonthlyUsd: null,\n extraDatasetMonthlyUsd: null,\n extraDataGbMonthlyUsd: null,\n extraApiRequestsUsdPer1k: null,\n extraAssistCreditsUsdPer1k: null,\n aiAddonMonthlyUsd: null,\n extraContactsUsdPer1k: null,\n extraEmailSendsUsdPer1k: null,\n meteredInfraPassThrough: false,\n },\n starter: {\n basePriceMonthlyUsd: 25,\n basePriceAnnualMonthlyUsd: 16,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 5,\n extraCollaboratorMonthlyUsd: 3,\n extraDatasetMonthlyUsd: 2,\n extraDataGbMonthlyUsd: 0.36,\n extraApiRequestsUsdPer1k: null,\n // The top of the retail ladder, which Starter JOINS rather than steps\n // above (AGL-3203): the ladder descends with the tier, and Starter is the\n // rung below Pro, not a new one over it. Moves in lockstep with the 750\n // credit band on the Starter entitlements — the band without this rate is\n // the silent free overage, and this rate without the band is a fee\n // advertised on a quantity the plan never sold.\n extraAssistCreditsUsdPer1k: 3,\n aiAddonMonthlyUsd: 9,\n extraContactsUsdPer1k: 1,\n // No email band to be \"over\" — see `emailSendsPerMonth` on the Starter\n // entitlements. `emailSendsOverage` returns 0 for any non-positive band,\n // so a rate here would price volume that can never be computed, which is\n // the same defect as a rate beside an UNLIMITED band.\n extraEmailSendsUsdPer1k: null,\n meteredInfraPassThrough: true,\n },\n pro: {\n basePriceMonthlyUsd: 56,\n basePriceAnnualMonthlyUsd: 39,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 4,\n extraCollaboratorMonthlyUsd: 2,\n extraDatasetMonthlyUsd: 2,\n extraDataGbMonthlyUsd: 0.36,\n extraApiRequestsUsdPer1k: null,\n extraAssistCreditsUsdPer1k: 3,\n aiAddonMonthlyUsd: 19,\n extraContactsUsdPer1k: 0.75,\n extraEmailSendsUsdPer1k: 2.25,\n meteredInfraPassThrough: true,\n },\n business: {\n basePriceMonthlyUsd: 139,\n basePriceAnnualMonthlyUsd: 99,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 3,\n extraCollaboratorMonthlyUsd: 1,\n extraDatasetMonthlyUsd: 1,\n extraDataGbMonthlyUsd: 0.36,\n extraApiRequestsUsdPer1k: 0.5,\n extraAssistCreditsUsdPer1k: 2.75,\n aiAddonMonthlyUsd: 39,\n extraContactsUsdPer1k: 0.5,\n extraEmailSendsUsdPer1k: 2,\n meteredInfraPassThrough: true,\n },\n scale: {\n basePriceMonthlyUsd: 249,\n basePriceAnnualMonthlyUsd: 179,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 2,\n extraCollaboratorMonthlyUsd: 1,\n extraDatasetMonthlyUsd: 1,\n extraDataGbMonthlyUsd: 0.36,\n extraApiRequestsUsdPer1k: 0.35,\n extraAssistCreditsUsdPer1k: 2.5,\n aiAddonMonthlyUsd: 69,\n extraContactsUsdPer1k: 0.4,\n extraEmailSendsUsdPer1k: 1.9,\n meteredInfraPassThrough: true,\n },\n advanced: {\n basePriceMonthlyUsd: 399,\n basePriceAnnualMonthlyUsd: 299,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 2,\n extraCollaboratorMonthlyUsd: 1,\n extraDatasetMonthlyUsd: 1,\n extraDataGbMonthlyUsd: 0.36,\n // FLOORED at the 50% retail margin, with Agency's: a `/v1` request costs\n // $0.117 per 1,000 at the dearest region, so $0.25 is the round figure at\n // or above $0.234 (AGL-3444).\n extraApiRequestsUsdPer1k: 0.25,\n extraAssistCreditsUsdPer1k: 2.25,\n aiAddonMonthlyUsd: 99,\n // FLOORED at the 50% retail margin, not stepped down again. The ladder\n // above it descends $1.00 → $0.75 → $0.50 → $0.40, and one more step\n // would have reached $0.25 against a `perContactMonth` cost of $0.20 per\n // 1,000 — a 20% line margin on a retail price, thinner than the 23% the\n // infrastructure pass-through earns while being sold as cost recovery.\n extraContactsUsdPer1k: 0.4,\n extraEmailSendsUsdPer1k: 1.85,\n meteredInfraPassThrough: true,\n },\n // Agency is the one tier whose PRICE moved. It included 100 hosts, 20 TB of\n // bandwidth and an unbounded form-submission band against $799, which made\n // it both the worst margin on the ladder and the most underpriced against\n // the field: Duda charges ~$1,396-1,493/mo for 100 sites, BigCommerce\n // Enterprise starts at $1,499 and Shopify Plus at $2,300. $1,299 still\n // undercuts all three for the buyer this architecture is built for.\n agency: {\n basePriceMonthlyUsd: 1299,\n // $1,049/mo is $12,588 a year, which is what the live Stripe yearly price\n // charges. Stripe prices are immutable, so the constant every surface\n // quotes has to match the one a customer is actually billed by — a\n // ratio-derived figure that no price object backs would quote one number\n // and charge another. It lands within a dollar of holding the previous\n // 18.8% annual discount across the rise, which is what stops the annual\n // interval becoming the cheap way past a repricing.\n basePriceAnnualMonthlyUsd: 1049,\n extraHostMonthlyUsd: 20,\n extraSeatMonthlyUsd: 2,\n extraCollaboratorMonthlyUsd: 1,\n extraDatasetMonthlyUsd: 1,\n extraDataGbMonthlyUsd: 0.36,\n // FLOORED at the 50% retail margin — see Advanced's.\n extraApiRequestsUsdPer1k: 0.25,\n extraAssistCreditsUsdPer1k: 2,\n aiAddonMonthlyUsd: 299,\n // $0.40, because the band it meters is now FINITE.\n //\n // This row has been wrong in both directions. It shipped $0.20 against an\n // UNLIMITED band, advertising a fee that could not be charged —\n // `checkContactQuota` computes `Math.max(0, used - Infinity)`, which is 0\n // at every usage level (AGL-2439) — and was corrected to `null` to stop\n // publishing a claim the code could not honour. Bounding the band inverts\n // the problem: a finite band with no rate is usage past a bound that is\n // silently free, and the bound achieves nothing.\n //\n // $0.40 rather than a further step down the ladder because that is the\n // 50% retail floor against a `perContactMonth` cost of $0.20 per 1,000 —\n // the same figure Scale and Advanced carry, and the same floor that moved\n // Advanced off $0.25.\n //\n // It METERS rather than walls: a rate is what makes `checkContactQuota`\n // return `allowed: true` past the band, so a growing audience is billed\n // and never dropped. That is what makes bounding this band safe here and\n // NOT safe on enterprise, whose every rate is the \"not for sale\"\n // sentinel.\n extraContactsUsdPer1k: 0.4,\n extraEmailSendsUsdPer1k: 1.8,\n meteredInfraPassThrough: true,\n },\n // Enterprise (AGL-1118) has NO list price — every figure here is the\n // \"not for sale\" sentinel, not a $0 offer (`isCustomPricedPlan`). The org's\n // real price is the negotiated `subscription.customMonthlyUsd` the\n // enterprise-billing flow writes (AGL-1110), which `orgListPriceMonthlyUsd`\n // already prefers over the plan default; a comped enterprise org genuinely\n // bills $0, and that is what this row then reports. Nulls also mean the\n // plan sells no self-serve add-on seats — enterprise capacity is unlimited,\n // so there is nothing to buy.\n enterprise: {\n basePriceMonthlyUsd: 0,\n basePriceAnnualMonthlyUsd: 0,\n extraHostMonthlyUsd: null,\n extraSeatMonthlyUsd: null,\n extraCollaboratorMonthlyUsd: null,\n extraDatasetMonthlyUsd: null,\n extraDataGbMonthlyUsd: null,\n extraApiRequestsUsdPer1k: null,\n extraAssistCreditsUsdPer1k: null,\n aiAddonMonthlyUsd: null,\n extraContactsUsdPer1k: null,\n extraEmailSendsUsdPer1k: null,\n meteredInfraPassThrough: false,\n },\n}\n\n/**\n * The price list a plan's row becomes when a staff COMP is what grants it\n * (AGL-3034): the list prices kept, and nothing for sale.\n *\n * Every other figure on a row prices something sold on top of a subscription —\n * an add-on item, or usage past a band billed onto that subscription's next\n * invoice. A comp has no subscription to add an item to or to invoice, so\n * every one of them is `null` and the pass-through is off. That is `free`'s\n * reasoning (`meteredInfraPassThrough`: \"there is no subscription to hang a\n * metered item on\"), and it turns each band into a wall rather than a line\n * past which usage is sold to nobody.\n *\n * It is also what keeps a comp away from Stripe without a check anyone could\n * forget: the AI overage charge claims only what `assistMonthOverage` prices,\n * the usage sweep bills only what these rates price, and a `null` rate prices\n * zero. A comp that needs more of something gets a quota override beside it,\n * or is uncapped outright (AGL-3049) — and an uncapped comp reads THIS row\n * too, so lifting every band sells nothing past any of them.\n *\n * Derived from the row rather than written out, so a rate added to\n * `PlanPricing` later is withheld from a comp on the day it ships.\n */\nfunction compPricing(pricing: PlanPricing): PlanPricing {\n const withheld: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(pricing)) {\n if (key === 'basePriceMonthlyUsd' || key === 'basePriceAnnualMonthlyUsd') {\n withheld[key] = value\n } else if (typeof value === 'boolean') {\n withheld[key] = false\n } else {\n withheld[key] = null\n }\n }\n return withheld as unknown as PlanPricing\n}\n\nconst PLAN_COMP_PRICING = Object.fromEntries(\n Object.entries(PLAN_PRICING).map(([plan, pricing]) => [plan, compPricing(pricing)]),\n) as Record<OrgPlan, PlanPricing>\n\n/**\n * The prices the org's usage and add-ons are sold at (AGL-3034): its\n * effective plan's row, or that row with nothing for sale when a staff comp\n * is what grants the plan — see `compPricing`.\n *\n * EVERY reader that decides whether something past a band is sold, or what a\n * purchasable seat costs, reads this rather than indexing `PLAN_PRICING` by\n * the effective plan. Indexing it directly is how a comp would be quoted a\n * rate nobody can be invoiced at.\n */\nexport function resolvePlanPricing(\n org: Partial<AglynOrgBilling> | null | undefined,\n): PlanPricing {\n const plan = resolveEffectivePlan(org)\n return resolvePlanComp(org) ? PLAN_COMP_PRICING[plan] : PLAN_PRICING[plan]\n}\n\n/**\n * Subscription states that stop paying for the plan (AGL-247). `past_due`\n * keeps working as a dunning grace period; these do not.\n *\n * `incomplete_expired` is the state a signup reaches when its first payment\n * never completes — an abandoned SCA challenge, a card that never\n * authenticated — and Stripe stamps it about a day later. It has charged\n * nothing and never will, so it belongs here beside `incomplete`, which is the\n * same subscription an hour earlier. `LIVE_SUBSCRIPTION_STATUSES` in\n * `org-billing-doc.ts` already excludes it and `DEAD_STATUSES` in\n * `utils/subscription-period-notice.ts` already names it; this set is the one\n * that decides both what a workspace GETS and whether we count it as revenue,\n * so it is the one place the omission cost money in both directions at once.\n *\n * THE DENYLIST FORM IS THE HAZARD, and it is why the omission was silent: an\n * allowlist of live statuses cannot rot when Stripe adds a status, whereas a\n * denylist of dead ones silently grants the plan — and books the MRR — for\n * every status nobody has enumerated yet. The three readers below keep the\n * denylist anyway, because the direction they fail in is not the same as the\n * live-side question's: an unrecognized status here must not revoke a paying\n * workspace's features, while `isLiveSubscriptionStatus` refusing to call an\n * unrecognized status live only ever declines to sell a second subscription.\n * A new Stripe status therefore has to be added HERE by hand, which is a\n * standing obligation rather than a property of the code.\n */\nconst DEAD_SUBSCRIPTION_STATUSES = new Set([\n 'canceled',\n 'unpaid',\n 'incomplete',\n 'incomplete_expired',\n])\n\n/**\n * The subscription status entitlement resolution runs on (AGL-1028).\n *\n * `subscription` moved to `orgs/{orgId}/billing/stripe`, behind\n * `canManageOrg()` — but the status word is an ENTITLEMENT input, not a\n * commercial secret: `resolveEffectivePlan` downgrades a paid plan to free on a\n * dead subscription, and `resolvePurchasedAddons` stops counting add-ons on\n * one. Both resolve in the tenant runtime and in console components belonging\n * to members who cannot read a manager-gated doc, so the webhook mirrors the\n * bare status string back onto the org doc as `billingStatus`.\n *\n * Reads the mirror first, then the inline `subscription` — the fallback is what\n * keeps entitlements correct for orgs the backfill has not reached yet, and for\n * any caller still passing a whole billing doc rather than an org doc.\n */\nfunction subscriptionStatusOf(\n org: Partial<AglynOrgBilling> | null | undefined,\n): string | undefined {\n const mirrored = (org as { billingStatus?: unknown } | null | undefined)\n ?.billingStatus\n if (typeof mirrored === 'string' && mirrored) return mirrored\n return org?.subscription?.status\n}\n\n/**\n * What the org's subscription contributes to deciding its plan (AGL-3034).\n *\n * - `live` — a status is mirrored and it is not one this module reads as\n * dead. The subscription decides the plan. `past_due` is live (dunning\n * grace), and so is a status this build does not recognize, for the reason\n * `DEAD_SUBSCRIPTION_STATUSES` gives: an unrecognized status must not take\n * away what a paying workspace bought, and handing the plan to a comp would\n * do exactly that.\n * - `dead` — the status is in `DEAD_SUBSCRIPTION_STATUSES`. A stored paid\n * plan grants nothing (AGL-247).\n * - `none` — no status at all: the workspace has never been subscribed, or\n * predates the mirror.\n *\n * `dead` and `none` are the only states a staff comp applies in.\n */\nexport type OrgSubscriptionState = 'live' | 'dead' | 'none'\n\nexport function orgSubscriptionState(\n org: Partial<AglynOrgBilling> | null | undefined,\n): OrgSubscriptionState {\n const status = subscriptionStatusOf(org)\n if (!status) return 'none'\n return DEAD_SUBSCRIPTION_STATUSES.has(status) ? 'dead' : 'live'\n}\n\n/** A staff plan comp, as every reader receives it (AGL-3034). */\nexport interface ResolvedOrgPlanComp {\n /** The plan granted — always a paid plan this build knows. */\n plan: OrgPlan\n /**\n * Whether the comp lifts every cap (AGL-3049) — see `isUncappedPlanComp`.\n * `false` for anything but a stored `true`.\n */\n uncapped: boolean\n /** `null` when the stored code is not one of the override set's. */\n reason: OrgOverrideReasonCode | null\n note: string | null\n grantedBy: string | null\n /** ISO instant, or `null` when the stored stamp does not read back. */\n grantedAt: string | null\n}\n\n/**\n * An instant off a stored value in whichever form it reached this reader: a\n * Firestore `Timestamp` from either SDK, the `{ seconds }`/`{ _seconds }`\n * shape a timestamp takes after crossing JSON, a `Date`, or an ISO string.\n */\nfunction instantOf(value: unknown): string | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString()\n }\n if (typeof value === 'string') {\n const millis = Date.parse(value)\n return Number.isFinite(millis) ? new Date(millis).toISOString() : null\n }\n if (!value || typeof value !== 'object') return null\n const toDate = (value as { toDate?: unknown }).toDate\n if (typeof toDate === 'function') {\n const date = toDate.call(value)\n return date instanceof Date && !Number.isNaN(date.getTime())\n ? date.toISOString()\n : null\n }\n const record = value as { seconds?: unknown; _seconds?: unknown }\n const seconds = Number(record.seconds ?? record._seconds)\n if (!Number.isFinite(seconds)) return null\n // Out of `Date`'s range is Invalid Date, whose `toISOString` throws.\n const date = new Date(seconds * 1000)\n return Number.isNaN(date.getTime()) ? null : date.toISOString()\n}\n\nfunction trimmedOrNull(value: unknown): string | null {\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\n/**\n * The org's STORED comp (`entitlements.planComp`), whether or not it is in\n * force, or `null` when there is none (AGL-3034).\n *\n * Strict about the one field that grants anything: `plan` must be a paid plan\n * this build knows. A comp of `free` grants nothing and an unknown plan could\n * grant anything, so both read as no comp at all — the org then resolves\n * exactly as it would without one, which is the direction a malformed grant\n * must fail in. The descriptive fields are narrowed and never block the plan:\n * a comp whose note or stamp did not read back is still the comp staff wrote.\n *\n * `uncapped` is strict in the same direction (AGL-3049): it lifts every cap,\n * so only a literal `true` does. A `\"true\"`, a `1` or a map written by hand\n * reads as a capped comp — the plan still stands, and its bands stay walls.\n */\nexport function readOrgPlanComp(\n org: Partial<AglynOrgBilling> | null | undefined,\n): ResolvedOrgPlanComp | null {\n const entitlements = org?.entitlements as { planComp?: unknown } | null | undefined\n const raw =\n entitlements && typeof entitlements === 'object' ? entitlements.planComp : undefined\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null\n const record = raw as Record<string, unknown>\n const plan = record['plan']\n if (\n typeof plan !== 'string' ||\n plan === 'free' ||\n !Object.prototype.hasOwnProperty.call(PLAN_ENTITLEMENTS, plan)\n ) {\n return null\n }\n const reason = record['reason']\n return {\n plan: plan as OrgPlan,\n uncapped: record['uncapped'] === true,\n reason: isOrgOverrideReasonCode(reason) ? reason : null,\n note: trimmedOrNull(record['note']),\n grantedBy: trimmedOrNull(record['grantedBy']),\n grantedAt: instantOf(record['grantedAt']),\n }\n}\n\n/**\n * The comp IN FORCE: the stored comp while no subscription decides the plan,\n * and `null` otherwise (AGL-3034).\n *\n * A live subscription always wins. The comp is not removed by one starting —\n * nothing but the staff override writes it — so it waits, dormant, and\n * applies again if that subscription ends. Staff views say so.\n */\nexport function resolvePlanComp(\n org: Partial<AglynOrgBilling> | null | undefined,\n): ResolvedOrgPlanComp | null {\n if (orgSubscriptionState(org) === 'live') return null\n return readOrgPlanComp(org)\n}\n\n/**\n * Whether an UNCAPPED comp is in force (AGL-3049): every band and quota the\n * org resolves reads as `UNLIMITED`, whatever its plan row and its per-org\n * overrides say — see `withEveryCapLifted`.\n *\n * In force only, like the comp itself. A live subscription decides the plan\n * and its bands, so a dormant uncapped comp lifts nothing, and a canceled one\n * lifts every cap again the moment the subscription ends.\n *\n * What it does NOT lift is anything that is not a plan band: the fee\n * percentages (`PRICE_ENTITLEMENT_KEYS`) are prices, and the platform's own\n * guards live outside the entitlements map and are not the plan's to give\n * away — the form-submission and bandwidth abuse ceilings (which read an\n * unlimited band as `FORM_ABUSE_CEILING_UNLIMITED` and\n * `BANDWIDTH_ABUSE_CEILING_UNLIMITED`), the flat per-site caps on webhooks,\n * actions and collection entries, the email send pacing, the assist runaway\n * message cap, an operator's explicit spend ceiling, and staff pauses and\n * lockdowns. It never sells anything either: the pricing stays the comp's,\n * with every rate withheld, so an unbounded band is still billed nothing.\n *\n * The lifted value is `UNLIMITED` in process and nowhere else. Every route\n * that sends a band off-process sends a finite number with an explicit flag,\n * or `null` with a documented meaning, because `JSON.stringify(Infinity)` is\n * `null` — and the comp itself is stored and sent as a boolean.\n */\nexport function isUncappedPlanComp(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n return resolvePlanComp(org)?.uncapped === true\n}\n\n/**\n * What decided the org's effective plan (AGL-3034):\n *\n * - `subscription` — a live subscription; the stored plan is its mirror.\n * - `comp` — a staff comp, on a dead or absent subscription.\n * - `lapsed` — a dead subscription and no comp: Free.\n * - `stored-plan` — no subscription and no comp, and a paid plan stored\n * directly, the way staff granted plans before comps.\n * - `no-plan` — no subscription, no comp, no paid plan: Free.\n */\nexport type OrgPlanDecidedBy =\n | 'subscription'\n | 'comp'\n | 'lapsed'\n | 'stored-plan'\n | 'no-plan'\n\n/** Everything a staff surface needs to say how an org's plan resolves. */\nexport interface OrgPlanDescription {\n effectivePlan: OrgPlan\n /** `org.plan` when it names a plan this build knows, else `null`. */\n storedPlan: OrgPlan | null\n subscriptionStatus: string | null\n subscription: OrgSubscriptionState\n /** The STORED comp — in force or dormant; see `compInForce`. */\n comp: ResolvedOrgPlanComp | null\n compInForce: boolean\n /**\n * An uncapped comp is IN FORCE, so every band reads as unlimited\n * (AGL-3049). A dormant uncapped comp is `false` here and `comp.uncapped`.\n */\n uncapped: boolean\n decidedBy: OrgPlanDecidedBy\n}\n\n/**\n * How a staff chip names a stored comp (AGL-3049): the plan, then whether it\n * is uncapped and whether a live subscription holds it dormant —\n * `Enterprise (uncapped)`, `Pro (dormant)`, `Agency (uncapped, dormant)`.\n *\n * One reading of the terms, so the orgs list, the org summary and the\n * override dialog cannot describe the same grant differently. `name` is the\n * plan as the surface already writes plans: the label by default, the stored\n * key on the surfaces whose other chips show keys.\n */\nexport function planCompLabel(\n comp: Pick<ResolvedOrgPlanComp, 'plan' | 'uncapped'>,\n inForce: boolean,\n name: string = PLAN_LABELS[comp.plan],\n): string {\n const terms = [comp.uncapped ? 'uncapped' : null, inForce ? null : 'dormant']\n .filter(Boolean)\n .join(', ')\n return terms ? `${name} (${terms})` : name\n}\n\n/**\n * A stored comp as a sentence names it (AGL-3049): `uncapped Enterprise\n * comp`, `Pro comp`. Shared by the plan description and the override route's\n * account of what it changed.\n */\nexport function planCompPhrase(\n comp: Pick<ResolvedOrgPlanComp, 'plan' | 'uncapped'>,\n): string {\n return `${comp.uncapped ? 'uncapped ' : ''}${PLAN_LABELS[comp.plan]} comp`\n}\n\n/** \"A Pro comp\", \"An uncapped Enterprise comp\": the article a phrase takes. */\nfunction withArticle(phrase: string): string {\n return `${/^[aeiou]/i.test(phrase) ? 'An' : 'A'} ${phrase}`\n}\n\n/**\n * The org's plan, described rather than only resolved (AGL-3034) — the ONE\n * reading the staff override route answers with and the staff override\n * dialog renders, so the response and the dialog cannot tell staff two\n * different stories about the same document.\n */\nexport function describeOrgPlan(\n org: Partial<AglynOrgBilling> | null | undefined,\n): OrgPlanDescription {\n const subscription = orgSubscriptionState(org)\n const status = subscriptionStatusOf(org)\n const stored = org?.plan\n const storedPlan =\n typeof stored === 'string' &&\n Object.prototype.hasOwnProperty.call(PLAN_ENTITLEMENTS, stored)\n ? (stored as OrgPlan)\n : null\n const comp = readOrgPlanComp(org)\n const compInForce = comp !== null && subscription !== 'live'\n const decidedBy: OrgPlanDecidedBy =\n subscription === 'live'\n ? 'subscription'\n : compInForce\n ? 'comp'\n : subscription === 'dead'\n ? 'lapsed'\n : storedPlan && storedPlan !== 'free'\n ? 'stored-plan'\n : 'no-plan'\n return {\n effectivePlan: resolveEffectivePlan(org),\n storedPlan,\n subscriptionStatus: typeof status === 'string' && status ? status : null,\n subscription,\n comp,\n compInForce,\n uncapped: compInForce && comp?.uncapped === true,\n decidedBy,\n }\n}\n\n/**\n * One sentence for staff saying what decides the plan and what a plan set in\n * the override dialog would do (AGL-3034). Shared by the route's response and\n * the dialog, for the reason `describeOrgPlan` is.\n *\n * A comp in force also says whether its bands are walls or lifted\n * (AGL-3049), because that is the question a comped workspace refused at a\n * band brings to staff.\n */\nexport function orgPlanDescriptionSentence(\n description: OrgPlanDescription,\n): string {\n const label = (plan: OrgPlan | null) => (plan ? PLAN_LABELS[plan] : 'no plan')\n const status = description.subscriptionStatus ?? 'none'\n const dormant =\n description.comp && !description.compInForce\n ? ` ${withArticle(planCompPhrase(description.comp))} is stored and ` +\n 'dormant: it takes effect if the subscription ends.'\n : ''\n switch (description.decidedBy) {\n case 'subscription':\n return (\n `The live subscription (${status}) decides the plan: ` +\n `${label(description.effectivePlan)}. Stripe's next subscription ` +\n `event rewrites a plan set here.${dormant}`\n )\n case 'comp':\n return (\n `${label(description.effectivePlan)} is in force as ` +\n `${description.uncapped ? 'an uncapped' : 'a'} staff comp, ` +\n (description.subscription === 'dead'\n ? `because the subscription is ${status}. `\n : 'because no subscription pays for this workspace. ') +\n (description.uncapped\n ? 'It bills nothing, every band and quota reads as unlimited, '\n : 'It bills nothing, every band is a hard limit that a quota ' +\n 'override can raise, ') +\n 'and it lasts until it is removed or a live subscription starts.'\n )\n case 'lapsed':\n return (\n `The subscription is ${status}, so this workspace resolves as ` +\n `${label('free')}` +\n (description.storedPlan && description.storedPlan !== 'free'\n ? ` — the stored ${label(description.storedPlan)} grants nothing`\n : '') +\n '. A plan set here is written as a staff comp.'\n )\n case 'stored-plan':\n return (\n `No subscription: the plan stored directly, ` +\n `${label(description.storedPlan)}, decides it. A plan set here is ` +\n 'written as a staff comp.'\n )\n default:\n return (\n `No subscription and no paid plan, so this workspace resolves as ` +\n `${label('free')}. A plan set here is written as a staff comp.`\n )\n }\n}\n\n/**\n * Whether the org is actually being charged for its plan right now.\n *\n * This is deliberately stricter than `resolveEffectivePlan`: entitlement\n * asks \"what does this org get\", revenue asks \"who is paying us\". A staff\n * plan override (`/admin/orgs`) never writes `subscription` — before AGL-3034\n * it wrote `plan`, and now it writes a comp — so a comped or dark-launched\n * org resolves to a paid plan while billing nothing, and counting it inflates\n * MRR (AGL-925). No subscription mirror means no Stripe subscription means no\n * revenue.\n *\n * A comp never reaches this answer: it reads the stored `plan` and the\n * status, never `entitlements.planComp`, and a comp is only ever in force\n * where the status is dead or absent — both of which answer false here.\n *\n * `past_due` counts: Stripe is still retrying and the plan is still owed.\n */\nexport function isBillingSubscription(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n const plan = org?.plan\n if (!plan || plan === 'free' || !(plan in PLAN_ENTITLEMENTS)) return false\n const status = subscriptionStatusOf(org)\n if (!status) return false\n return !DEAD_SUBSCRIPTION_STATUSES.has(status)\n}\n\n/**\n * The org's monthly LIST price in USD — plan base plus every purchased add-on\n * (manager/collaborator seats, datasets, extra hosts, POS registers, Event\n * Calendar), BEFORE any applied discount, and 0 for anyone not actually\n * billing. This is the sticker price the plan + add-ons would bill at.\n *\n * Annual subscriptions contribute their per-month equivalent\n * (`basePriceAnnualMonthlyUsd`), not the month-to-month price, so a mixed\n * book does not read high by the size of the annual discount. Add-ons bill\n * per-month (annual variants are ×12 with no discount), so each contributes\n * its monthly price on either interval.\n *\n * Pricing-v3 fix (2026-07): previously omitted the host, POS-register, and\n * Event-Calendar add-ons, understating the total for every org carrying them.\n *\n * AGL-1105: split out of `orgMonthlyRevenueUsd`, which now subtracts an\n * applied per-org discount on top of this. Callers wanting the pre-discount\n * sticker (the margin guardrail, a \"what would this bill at\" quote) use this;\n * callers wanting the actual money collected use `orgMonthlyRevenueUsd`.\n */\nexport function orgListPriceMonthlyUsd(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number {\n if (!isBillingSubscription(org)) return 0\n // Custom enterprise price (AGL-1110): a negotiated amount is the list price\n // outright — it already reflects the whole deal, so it overrides the plan\n // default AND the seat/host add-on math (those are folded into the quote).\n // Stored monthly-normalized, so it is used as-is.\n const custom = org?.subscription?.customMonthlyUsd\n if (typeof custom === 'number' && custom > 0) {\n return Math.round(custom * 100) / 100\n }\n const pricing = PLAN_PRICING[org?.plan as OrgPlan]\n if (!pricing) return 0\n const annual = org?.subscription?.interval === 'year'\n const addons = org?.seatAddons ?? {}\n const seats = (quantity: number | undefined, price: number | null) =>\n Math.max(0, quantity ?? 0) * (price ?? 0)\n return (\n (annual ? pricing.basePriceAnnualMonthlyUsd : pricing.basePriceMonthlyUsd) +\n seats(addons.managers, pricing.extraSeatMonthlyUsd) +\n seats(addons.members, pricing.extraCollaboratorMonthlyUsd) +\n seats(addons.datasets, pricing.extraDatasetMonthlyUsd) +\n seats(addons.hosts, pricing.extraHostMonthlyUsd) +\n seats(addons.posRegisters, POS_REGISTER_ADDON_MONTHLY_USD) +\n seats(addons.eventCalendar, EVENT_CALENDAR_ADDON_MONTHLY_USD) +\n // A toggle, not a quantity: one purchase covers the org, so a stored\n // quantity above one bills once.\n seats(aiAddonUnits(addons), pricing.aiAddonMonthlyUsd)\n )\n}\n\n/**\n * How many Aglyn AI add-ons a purchase map counts as: 0 or 1 (AGL-2896).\n * The add-on is org-wide like Event Calendar, so a quantity above one — a\n * hand edit, a doubled webhook item — is one purchase, and a non-number,\n * `NaN` or a negative is none.\n */\nexport function aiAddonUnits(addons: OrgSeatAddons | null | undefined): 0 | 1 {\n const quantity = Number(addons?.aiAddon ?? 0)\n return Number.isFinite(quantity) && quantity >= 1 ? 1 : 0\n}\n\n/**\n * A list price net of a discount (AGL-1105). Percentage discounts scale the\n * whole bill; fixed discounts subtract a flat USD amount. Never below 0, and\n * a percentage is clamped to 0–100. Prefers `percentOff` when both are\n * somehow set (Stripe coupons carry only one). Rounds to whole cents.\n */\nexport function applyDiscountUsd(\n listPriceUsd: number,\n discount:\n | Pick<OrgDiscount, 'percentOff' | 'amountOffUsd'>\n | null\n | undefined,\n): number {\n if (!(listPriceUsd > 0) || !discount) return Math.max(0, listPriceUsd)\n if (typeof discount.percentOff === 'number') {\n const pct = Math.min(100, Math.max(0, discount.percentOff))\n return Math.round(listPriceUsd * (1 - pct / 100) * 100) / 100\n }\n if (typeof discount.amountOffUsd === 'number') {\n return Math.max(0, Math.round((listPriceUsd - discount.amountOffUsd) * 100) / 100)\n }\n return listPriceUsd\n}\n\n/**\n * Whether an org should read as **\"Enterprise\"** everywhere the plan is shown.\n * Three ways to qualify:\n * - **`plan === 'enterprise'`** — the real plan (AGL-1118). This is the path\n * every new enterprise org takes, and the only one that also grants\n * Enterprise *entitlements*; the two below are display-only overlays on a\n * lower base plan;\n * - a billing org on a **negotiated custom price** (`customMonthlyUsd > 0`) —\n * a paying enterprise deal still parked on its pre-AGL-1118 base plan; or\n * - an explicit **`org.enterprise`** marker — a *comped* enterprise account,\n * 100%-discounted so it collects $0 while infra cost is still metered.\n *\n * ⚠️ This function answers ONE question — \"does this org read as Enterprise\" —\n * and it is not the same question as \"what does this org get\". Only the first\n * bullet grants anything: `resolveEffectivePlan` reads `org.plan` and nothing\n * else, so `org.enterprise` and `customMonthlyUsd` are read NOWHERE but here.\n * A comped or custom-priced org resolves its BASE plan's entitlements, and\n * gets Enterprise capability through a per-org `entitlements` override — see\n * `ssoEnabled`, whose docblock calls that \"how enterprise orgs provisioned\n * before that plan existed still get it\".\n *\n * This paragraph replaces a parenthetical claiming the comped marker carried\n * \"full Enterprise capability + SSO\" (AGL-2297). It never did, and the billing\n * page believed it: the Enterprise card ticked five entitlements against any\n * org this function marked, so a comped org whose overrides granted SSO but\n * not white-label was shown a green tick for full white-label while the\n * branding card correctly refused it.\n *\n * The last two are kept for orgs provisioned before `enterprise` was a plan;\n * migrating such an org to `plan: 'enterprise'` is the clean end state and\n * changes nothing about how it reads here. List-priced orgs with none of the\n * three are not enterprise.\n *\n * A staff comp in force names the plan instead of the stored one (AGL-3034),\n * exactly as it does for entitlements: a workspace comped to Enterprise reads\n * as Enterprise, and one comped to a lower tier does not read as Enterprise\n * because of a plan stored before the comp. The explicit marker still counts.\n */\nexport function isEnterpriseOrg(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n const comp = resolvePlanComp(org)\n if (comp) return comp.plan === 'enterprise' || org?.enterprise === true\n if (org?.plan === 'enterprise') return true\n if (org?.enterprise === true) return true\n return (\n isBillingSubscription(org) &&\n (org?.subscription?.customMonthlyUsd ?? 0) > 0\n )\n}\n\n/**\n * Human label shown for `enterprise` in plan badges. Kept as a named export\n * for the surfaces that label a custom-priced org whose stored `plan` is still\n * a lower tier; a real `plan: 'enterprise'` org gets the same string from\n * `PLAN_LABELS`.\n */\nexport const ENTERPRISE_PLAN_LABEL = PLAN_LABELS.enterprise\n\n/**\n * The org's monthly recurring revenue in USD — its LIST price\n * (`orgListPriceMonthlyUsd`) net of any applied per-org discount\n * (`org.discount`, AGL-1105), and 0 for anyone who is not actually billing. A\n * comped enterprise deal genuinely collects less, so the discount belongs in\n * MRR; the pre-discount sticker stays available as `orgListPriceMonthlyUsd`.\n */\nexport function orgMonthlyRevenueUsd(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number {\n const listPrice = orgListPriceMonthlyUsd(org)\n if (listPrice <= 0) return 0\n return applyDiscountUsd(listPrice, org?.discount)\n}\n\n/**\n * Stripe's standard processing fee on Aglyn's OWN subscription charges\n * (AGL-1108) — 2.9% + $0.30 per charge. This is the fee Aglyn pays Stripe to\n * collect a subscription; it is DISTINCT from the storefront platform fee\n * (`transactionFee*Pct`, the Connect `application_fee`) that a seller's own\n * Stripe pays on their storefront sales. Revenue/margin should be reasoned on\n * the NET figure, not gross.\n */\nexport const STRIPE_PROCESSOR_FEE_PCT = 0.029\nexport const STRIPE_PROCESSOR_FEE_FIXED_USD = 0.3\n\n/**\n * A gross monthly USD amount net of Stripe's processing fee. Annual\n * subscriptions are charged once per year, so the fixed 30¢ amortizes to\n * $0.30/12 per month; monthly subscriptions pay it every month. Never\n * returns below 0.\n */\nexport function netOfProcessorFee(\n grossMonthlyUsd: number,\n annual = false,\n): number {\n if (!(grossMonthlyUsd > 0)) return 0\n const fixedPerMonth = annual\n ? STRIPE_PROCESSOR_FEE_FIXED_USD / 12\n : STRIPE_PROCESSOR_FEE_FIXED_USD\n const net = grossMonthlyUsd * (1 - STRIPE_PROCESSOR_FEE_PCT) - fixedPerMonth\n return Math.max(0, Math.round(net * 100) / 100)\n}\n\n/**\n * The org's monthly recurring revenue NET of Stripe's processing fee\n * (AGL-1108) — what Aglyn actually keeps before infra COGS, and the correct\n * base for margin/discount reasoning. Gross is `orgMonthlyRevenueUsd`; this\n * subtracts ~2.9%+30¢, interval-aware. 0 for anyone not actually billing.\n */\nexport function orgNetMonthlyRevenueUsd(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number {\n const gross = orgMonthlyRevenueUsd(org)\n if (gross <= 0) return 0\n return netOfProcessorFee(gross, org?.subscription?.interval === 'year')\n}\n\n/**\n * Net-margin floor for discount underwriting (AGL-1105). Mirrors the\n * Enterprise-Pricing-Calculator's ~75% gross-margin target: a discounted\n * subscription should still keep ≥75% of its net (post-processor-fee) revenue\n * after infrastructure COGS. Below the floor a discount is warned; well below\n * it is blocked without explicit sign-off.\n *\n * WHAT THIS IS A FLOOR ON (AGL-1930): net revenue less INFRASTRUCTURE COGS,\n * and nothing else. It is a contribution margin — see `MARGIN_SCOPE_NOTE`,\n * which is the sentence every staff surface quoting this number has to print\n * beside it.\n *\n * Read as a dollar guard it says: net revenue must be at least\n * `cogs / (1 − floor)` = **4 × COGS**, and blocks below\n * `cogs / (1 − floor + band)` ≈ **2.86 × COGS**. That restatement is the point\n * of AGL-1930 — the percentage is scale-invariant, so the only thing that\n * moves the real guard line in dollars is the COGS figure fed into it, and on\n * production today that figure is `INFRA_COGS_PER_SITE_USD × sites` for\n * **every** org (14 of 14 usage rollups across all 6 orgs, measured\n * 2026-08-24). Re-tuning this constant therefore re-tunes a multiplier on the\n * flat floor, not on anything measured. Calibrate the floor first.\n */\nexport const NET_MARGIN_FLOOR_PCT = 0.75\n\n/**\n * How far under the floor a discount may sit and still only WARN (AGL-1105).\n * Within `[floor − band, floor)` staff see a caution; below `floor − band`\n * the guardrail blocks unless overridden. 10 points, matching the\n * calculator's soft/hard split.\n */\nexport const NET_MARGIN_WARN_BAND_PCT = 0.1\n\n/**\n * What every \"net margin\" figure in the staff console actually measures\n * (AGL-1930).\n *\n * `checkDiscountMargin`, the enterprise quote card and the coupon badge all\n * compute `(net revenue − infrastructure COGS) / net revenue`. Support,\n * acquisition and overhead are not in the model anywhere — no meter records\n * them and no constant estimates them — so \"net margin 78%\" is a CONTRIBUTION\n * margin, and a staff member signing a deal on it is reading a ceiling, not a\n * profit.\n *\n * One exported string rather than three literals: the three surfaces that\n * print a margin already drifted apart once (AGL-1134), and a caveat that is\n * true on one screen and absent on the next is worse than none.\n */\nexport const MARGIN_SCOPE_NOTE =\n 'Contribution margin: net revenue less infrastructure COGS only. ' +\n 'Support, customer acquisition (CAC) and overhead are not in this figure.'\n\n/**\n * The `ok` / `warn` / `block` band a cost-recovery margin falls in (AGL-1930).\n *\n * Extracted so `NET_MARGIN_FLOOR_PCT` and `NET_MARGIN_WARN_BAND_PCT` have ONE\n * reader. The staff org page's enterprise quote card had `>= 0.75` and\n * `>= 0.65` written into its JSX, so re-tuning either constant — the whole\n * subject of AGL-1930 — would have moved the discount guardrail and left that\n * card rating deals against the old numbers, silently and with money on the\n * line.\n */\nexport function netMarginRating(marginPct: number): DiscountMarginRating {\n return marginPct >= NET_MARGIN_FLOOR_PCT\n ? 'ok'\n : marginPct >= NET_MARGIN_FLOOR_PCT - NET_MARGIN_WARN_BAND_PCT\n ? 'warn'\n : 'block'\n}\n\n/**\n * Per-site monthly infrastructure COGS in USD (AGL-1105). The\n * Enterprise-Pricing-Calculator's ~$2/site figure — Firebase + Vercel at\n * cost — charged against every host the org runs. The margin guardrail\n * subtracts this from net revenue before rating a discount.\n */\nexport const INFRA_COGS_PER_SITE_USD = 2\n\n/**\n * Platform markup on passed-through infrastructure costs (AGL-41).\n *\n * DEFINED HERE, not in `apps/console/utils/usage-metering.ts`, and re-exported\n * from there — the same move AGL-2155 made for `pageViewsFromBandwidthGb`, and\n * for the same reason (AGL-2194). The published `/pricing` pass-through table\n * states both a cost column and a \"you pay (+30%)\" column, and\n * `tools/marketing/build-pricing-tables.mts` must generate both from code so\n * `npm run check:pricing-tables` can fail on drift. That generator resolves no\n * `@aglyn/*` path alias, so it can import `plan-entitlements.ts` and nothing\n * under `apps/console`. A constant the published page depends on must live\n * where the page's generator can read it.\n *\n * The console keeps importing it from `usage-metering` unchanged.\n *\n * It is the margin KEPT: cost + 30% after Stripe's processor fee, not before\n * it (AGL-3476). A price of cost × 1.3 kept cost × 1.26 once 2.9% came off,\n * so the published rates are set by {@link METERED_PRICE_MULTIPLE}, and this\n * stays the figure every full-utilization rule is held to.\n */\nexport const METERED_MARKUP = 1.3\n\n/**\n * What a passed-through cost is multiplied by to reach its price, so that\n * cost + 30% survives Stripe's percentage fee: 1.3 ÷ 0.971 ≈ 1.3388\n * (AGL-3476). The fixed 30¢ is paid by the subscription charge the overage\n * rides on, so only the percentage is grossed up.\n */\nexport const METERED_PRICE_MULTIPLE = METERED_MARKUP / (1 - STRIPE_PROCESSOR_FEE_PCT)\n\n/**\n * A published overage price: `costPerQuotedUnitUsd` × {@link\n * METERED_PRICE_MULTIPLE}, rounded UP at the `decimals` it is quoted in, so\n * the price the page prints and the invoice bills is the same round figure\n * and never less than the rule. The product is settled to six places before\n * the ceiling so a figure that is exact in decimal — or a pinned cost carried\n * to eleven places, like the page-view pair — is not lifted a step by its\n * tail.\n */\nexport function publishedMeteredPrice(costPerQuotedUnitUsd: number, decimals: number): number {\n const scale = 10 ** decimals\n return (\n Math.ceil(Number((costPerQuotedUnitUsd * METERED_PRICE_MULTIPLE * scale).toFixed(6))) / scale\n )\n}\n\n/**\n * What the REQUESTS behind ONE page view cost once the platform is past its\n * hosting plan's included request allowance, at the DEAREST region the CDN\n * and functions bill, in USD per view (AGL-1879, AGL-3444): the CDN requests\n * a cold load makes, and the one of them that reaches a function — the\n * analytics beacon. A page view billed past a plan's band is priced at\n * `(perPageView + this) × METERED_MARKUP` — $0.80 per 1,000 — and every\n * `bandwidthGb` band is sized with it counted.\n *\n * ## Why a page view needs a second cost term\n *\n * `perPageView` prices what a view WEIGHS: the bytes it transfers, plus the\n * Firestore reads behind the render. A view also costs per REQUEST, which is\n * not proportional to weight: Vercel Pro includes 10,000,000 CDN requests a\n * month, then bills $2.00 to $3.20 per million by region, and a cold load of\n * `aglyn.com/` makes 57 first-party requests to settle\n * (`tools/tenant-page-budget.json`, `wireCalibration.measuredAgainst`) — so\n * the allowance is about 175,000 views a month, platform-wide, and runs out\n * long before the transfer allowance. One of those requests is the analytics\n * beacon (`apps/tenant/app/api/analytics/collect`), which runs a function on\n * every view and writes two counter documents.\n *\n * ## Why the dearest region\n *\n * The CDN bills a request, and the bytes it transfers, in the region that\n * SERVES it — the one nearest the visitor — so a site whose audience is in\n * São Paulo or Cape Town costs what those regions cost, and nothing about a\n * plan can choose otherwise. The platform's rule is that no plan is under\n * water at any utilization and that \"at cost + 30%\" is true of every billed\n * unit, so every Vercel-billed term of a page view is priced where Vercel is\n * dearest.\n *\n * ## The figure\n *\n * Per view, past the allowance, at the dearest region:\n *\n * | | |\n * |--------------------------------------------------------|--------------|\n * | 57 CDN requests at $3.20 per million | $0.00018240 |\n * | the beacon's function: 0.4 s billed as active CPU at | $0.00002922 |\n * | $0.221/hr, 2 GB at $0.0183/GB-hr, $0.60 per million | |\n * | the beacon's two counter writes at nam5's $0.18/100k | $0.00000360 |\n * | the beacon's ~1 KB through the function at $0.43/GB | $0.00000044 |\n * | **requests, measured** | $0.00021566 |\n *\n * ($0.399028 weight + $0.215662) × 1.3 is $0.7991 per 1,000, rounded up to\n * $0.80, and this term is pinned so ($0.399028 + $0.216357) × 1.3 is exactly\n * $0.80 — \"Our cost $0.615385 / You pay $0.80\" — with the basis set ABOVE the\n * measurement as the weight basis is. \"At cost + 30%\" holds of the cost a\n * billed view incurs in the dearest region, and so in every other. The 0.4 s\n * is the one function duration on record (the form-submission route), billed\n * whole as CPU because nothing measured splits it from I/O wait.\n *\n * ## ⛔ Not in {@link ORG_COGS_UNIT_RATES_USD}, deliberately\n *\n * `perPageView` is half of a pair with `ESTIMATED_PAGE_TRANSFER_BYTES` — one\n * page weight in dollars and in bytes — and `npm run check:page-view-rate`\n * recovers that weight from the rate. A per-request charge is not\n * proportional to weight, so folding it into `perPageView` would make the\n * gate read a page nobody serves.\n *\n * ## The INCLUDED bands carry it too\n *\n * A view inside a plan's band makes the same requests as one past it, and\n * once the platform is past the allowance they cost the same either way, so\n * the `bandwidthGb` bands are sized with this term counted: $0.224 per\n * included GB on top of $0.413 of weight. `tier-margin-floor.spec.ts`\n * multiplies it in and pins every tier at 100% of every band.\n *\n * Lives HERE, beside the markup, for the reason the markup does: the\n * `/pricing` generator publishes the cost column from it and cannot import\n * anything under `apps/console`.\n */\nexport const PAGE_VIEW_CDN_REQUEST_COST_USD = 0.00021635711\n\n/**\n * Monthly unit costs for the meters the rollup already records (AGL-1134).\n *\n * These are OUR costs, not prices — the org is billed at cost × 1.30\n * (`METERED_MARKUP`, AGL-41). Storage/page-view/form rates match\n * `METERED_UNIT_RATES_USD` in the metering module on purpose: two files\n * disagreeing about what a page view costs is exactly the drift this function\n * exists to remove.\n *\n * The three added here are operator-tuned estimates of the same quality as\n * the existing three — Firestore-backed dataset storage is priced well above\n * object storage because it is, and API requests and contacts are priced off\n * the reads behind them. Treat every operator-tuned figure here as\n * provisional until validated against a real Firebase + Vercel invoice month;\n * AGL-1134 says so out loud rather than implying the numbers are derived.\n * `perEmailSend` is the exception and carries its own basis below.\n *\n * ## Three of these are shared with the billed table. The rest are NOT.\n *\n * `storagePerGbMonth`, `perPageView` and `perFormSubmission` are the metered\n * infrastructure pass-through, and the customer is charged those figures\n * times `METERED_MARKUP`. They therefore appear in `METERED_UNIT_RATES_USD`\n * as well, with identical values, and must be changed in both places at once.\n * A page view billed past its band also carries\n * {@link PAGE_VIEW_CDN_REQUEST_COST_USD}, which neither table holds: it is\n * the per-request CDN charge past the hosting plan's allowance, and this\n * table's `perPageView` is the weight-proportional half of what a band is\n * sized against — the request term is the other.\n *\n * ## Every Vercel-billed input is priced at Vercel's dearest region\n *\n * The CDN and functions bill by region, from $0.15 to $0.35 per GB of\n * transfer (a decimal GB), $2.00 to $3.20 per million requests, and $0.128 to\n * $0.221 per hour of active CPU (memory $0.0106 to $0.0183 per GB-hour), and\n * a plan cannot choose where its visitors are. So the Vercel share of every\n * rate here is priced at the dearest region (AGL-3444): the transfer inside\n * `perPageView`, the function, CDN request and transfer inside\n * `perFormSubmission` and `perApiRequest`, and the compute inside `perRun`.\n *\n * The GCP shares are priced at the database's own location. Production's\n * Firestore is `nam5` (read from the Admin API 2026-10-01), at $0.06 per\n * 100,000 reads, $0.18 per 100,000 writes and $0.18 per GiB-month — twice\n * the single-region figures — and every Firestore operation in this table is\n * priced there. Object storage is GCS US multi-region, `storagePerGbMonth`.\n *\n * Everything else here is a cost-model input ONLY. `dataStoragePerGbMonth`,\n * `perApiRequest`, `perContactMonth` and `perEmailSend` price what an org\n * costs us; what an org is CHARGED on those axes is a retail rate on\n * `PLAN_PRICING` that descends with the tier and is not derived from cost.\n * Adding one of them to `METERED_UNIT_RATES_USD` would put it inside the\n * \"at cost + 30%\" claim, which is true of exactly three meters.\n *\n * The first three were corrected 2026-08-09 (AGL-1280) — storage $0.03 →\n * $0.026 and form submissions $0.0005 → $0.00005 — `perPageView` was\n * re-pegged 2026-09-09 (AGL-2711), from $0.0001 to $0.00016153846, and the\n * last two were re-priced at Vercel's dearest region and Firestore's `nam5`\n * on 2026-10-01 (AGL-3444): page views $0.00039902751, form submissions\n * $0.000061538462.\n * All three MUST be changed here and in `METERED_UNIT_RATES_USD` together. See that\n * table for each figure's basis; it is the one a customer is billed against,\n * so it is the one that carries the working.\n *\n * The page-view basis is no longer an open gap. The rate priced a 627 KB page\n * against one that measured far more for weeks; the weight reduction that was\n * preferred over a re-peg has now shipped, the page was re-measured at\n * 976.1 KB, and the rate is pegged to a 1012.8 KB basis that sits ABOVE it.\n * `npm run check:page-view-rate` holds that peg, and now fails if the basis\n * ever drops back below what a published page measures.\n *\n * Raising the two here raises measured COGS, which changes no guardrail\n * verdict: `INFRA_COGS_PER_SITE_USD × sites` is the floor, and it still\n * outruns measured cost by orders of magnitude at real traffic.\n */\nexport const ORG_COGS_UNIT_RATES_USD = {\n storagePerGbMonth: 0.026,\n /**\n * ⛔ HALF OF A PAIR — see ESTIMATED_PAGE_TRANSFER_BYTES, which states the\n * same page in bytes as this states it in dollars. Both are calibrated\n * against a 1012.8 KB page and neither may move alone: their quotient is\n * the cost of a gigabyte's weight, and that — with the request term — is\n * what every bandwidthGb band on the ladder is sized against.\n */\n perPageView: 0.00039902751,\n perFormSubmission: 0.000061538462,\n /**\n * Firestore-backed dataset bytes — nam5's $0.18 per GiB-month, an order\n * pricier than object storage.\n */\n dataStoragePerGbMonth: 0.18,\n /**\n * One `/v1` request — $117 per million (AGL-3444).\n *\n * Every request is a function invocation behind a CDN request, and it reads\n * before it answers (`apps/console/utils/api-v1.ts`). The ledger for a\n * default list page — 25 records, read as 26 — at Vercel's dearest region\n * and nam5's list prices:\n *\n * - reads: the key, the rate-limit window, the org, the month's quota\n * counter, the site the resource sits under, and the page — 31, at\n * $0.06 per 100,000 = $0.0000186\n * - writes: the rate-limit window and the usage counter — 2, at $0.18 per\n * 100,000 = $0.0000036\n * - the function, 0.4 s billed as active CPU at $0.221/hr, 2 GB at\n * $0.0183/GB-hr, $0.60 per million invocations = $0.0000292\n * - the CDN request: $3.20 per million = $0.0000032\n * - the response, 25 records at 3 KB — the largest mean document size\n * measured on production across the collections `/v1` lists (leads,\n * 3.0 KB; records, contacts, media and the rest 1.0–1.5 KB) — plus 2 KB\n * of request and envelope, through Fast Data Transfer ($0.35 a GB) and\n * Fast Origin Transfer ($0.43 a GB), decimal GB = $0.0000615\n *\n * $0.0001161, carried up to $0.000117. A page of 100 — the most a client\n * may ask for — costs about three times that, so the figure is the default\n * request rather than the ceiling; see the 2026-10-01 decision entry.\n */\n perApiRequest: 0.000117,\n /**\n * One CRM record held for a month. An operator-tuned estimate (AGL-1134),\n * not built from a per-operation price: at `nam5`'s $0.06 per 100,000 it\n * covers about 333 reads a record a month, plus the record's storage at\n * $0.18 per GiB-month — well beyond what a record is read.\n */\n perContactMonth: 0.0002,\n /**\n * One delivered email, whatever produced it — $0.90 per 1,000.\n *\n * Unlike its five neighbours this is not an operator-tuned estimate. It is\n * the provider's own blended rate at the tier above the account's current\n * one ($90 / 100,000) and also the marginal overage rate on both that tier\n * and the current one, so it does not fall as volume grows and is the\n * conservative figure at any usage the platform can reach.\n *\n * It prices EVERY send on the cost meter — campaigns, receipts, invites,\n * password resets — because every one of them is a message the provider\n * charges for. The band on the plan and the retail overage rate beside it\n * both concern a smaller quantity; this is the whole bill.\n */\n perEmailSend: 0.0009,\n /**\n * One workflow or action run — $13 per million.\n *\n * Derived from the run paths at list prices (2026-09-07), because nothing\n * measurable exists yet: runs execute inside the Vercel request that fired\n * the event, not in a function of their own, so Cloud Logging carries no\n * per-run entry to count, and Firestore's operation counts are platform-wide\n * and cannot be attributed to a run. Over the 14 days to 2026-09-07 the\n * project logged no run at all.\n *\n * The ledger, for an event that executes one workflow on a site holding a\n * handful of functions, variables and workflows (`runEventWorkflows`,\n * `runEventActions` and the inbound-webhook run are the same shape):\n *\n * - reads: the triggered query, the workflow map, the host → org lookup,\n * the run counter, the functions and the variables — about 12, at\n * nam5's $0.06 per 100,000 = $0.0000072\n * - writes: the activity row and the counter increment — 2, at $0.18 per\n * 100,000 = $0.0000036\n * - compute: a few milliseconds of active CPU and memory on Fluid compute\n * for the expression evaluation, about $0.000001 in the cheapest\n * region and $0.0000017 in the dearest ($0.221 against $0.128 an\n * hour of active CPU), which is where it is priced\n *\n * $0.0000125, carried as $0.000013. It scales with the catalog a site holds\n * — every `limit(100)` read above is a query that costs one read per\n * document returned — and a step that writes a dataset record or calls a\n * webhook adds to it, so this is the typical figure, not the ceiling.\n *\n * It is a COST, not a price: no plan bills runs, and none refuses past the\n * band on a metered plan. It exists so a tier's run bands reach\n * `orgMonthlyCogsUsd` and `tier-margin-floor.spec.ts` — Agency's 3,000,000\n * runs are $36 a month that read as nothing while this was absent.\n */\n perRun: 0.000013,\n}\n\n/**\n * The rollup fields `orgMonthlyCogsUsd` prices. All optional and all\n * absent-safe.\n *\n * The named fields are the platform's own meters. A plugin's meter rides the\n * index signature under the rollup field its declaration names\n * (`plugin-usage-axes.ts`): the AI plugin's provider spend, the CRM's\n * records, the forms plugin's submissions, the workflows plugin's runs.\n */\nexport interface OrgUsageRollupInput {\n hostCount?: number | null\n storageGb?: number | null\n pageViews?: number | null\n dataStorageMb?: number | null\n apiRequests?: number | null\n /**\n * Every email the org sent this month, campaigns and transactional alike —\n * the `emailSends` cost meter, not the campaign meter.\n *\n * The cost meter is the right input precisely because the campaign meter is\n * the one with a cap on it. What this org cost us is every message the\n * provider charged for, and most of them are messages no quota could have\n * refused.\n */\n emailSends?: number | null\n /**\n * A plugin's meter, by the rollup field its declaration names. `unknown`\n * because a rollup document carries its month and its audit fields beside\n * the meters, and a caller may hand the document over whole; every meter\n * is read through `declaredMeterReading`, which prices only a positive\n * finite number.\n */\n [field: string]: unknown\n}\n\nexport interface OrgCogsResult {\n /** What the guardrail should charge against revenue. */\n cogsUsd: number\n /** Which arm produced it — `floor` means the meters came in under the flat estimate. */\n basis: 'measured' | 'floor'\n /** Cost from the meters alone, before the floor is applied. */\n measuredUsd: number\n /** `INFRA_COGS_PER_SITE_USD` × sites. */\n floorUsd: number\n /** Per-meter contributions, for showing the working in staff UI. */\n breakdown: Record<string, number>\n}\n\n/**\n * The unit rate a declared cost axis names, or 1 where it names none (the\n * rollup already records dollars).\n *\n * ⚑ A rate key core does not carry THROWS. Pricing an unknown axis at zero\n * would make an org cost less than it does, which is the direction that\n * approves a discount; `plugin-usage-axes.spec.ts` holds every declared key\n * to a rate that exists, so this is reached only by a tree that skipped it.\n */\nfunction cogsRateOf(axis: { id: string; rate?: string }): number {\n if (axis.rate === undefined) return 1\n const rate = (ORG_COGS_UNIT_RATES_USD as Record<string, number>)[axis.rate]\n if (typeof rate !== 'number' || !Number.isFinite(rate)) {\n throw new Error(\n `cost axis \"${axis.id}\" names the rate \"${axis.rate}\", which ORG_COGS_UNIT_RATES_USD does not carry`,\n )\n }\n return rate\n}\n\n/**\n * One org's monthly infrastructure COGS — the single cost model (AGL-1134).\n *\n * Replaces two things that had drifted apart: the discount guardrail read the\n * rollup's `costUsd`, and the staff MRR views read the same field separately,\n * so neither could be changed without silently changing the other's meaning.\n *\n * `costUsd` was also incomplete. The rollup records `dataStorageMb`,\n * `apiRequests` and `contactsCount`, and `estimateMonthlyUsageCost` prices\n * none of them — it sums storage, page views and form submissions only. So\n * the \"measured\" cost omitted three meters the platform was already at the\n * trouble of recording.\n *\n * The flat per-site figure stays a FLOOR rather than a fallback: measured\n * cost wins only when it exceeds the estimate. A rollup that has not run, or\n * an org with genuinely tiny usage, must not make the guardrail more generous\n * — that is the one direction that costs money.\n *\n * Measured against production 2026-07-30, that floor is doing all the work:\n * the largest real org's `costUsd` was **$0.0000054** against a $4.00 floor\n * for two sites. Five orders of magnitude. So this function changes no verdict\n * today, and will not until there is real traffic — which is the honest\n * reason to build it now, while nothing depends on the answer.\n */\nexport function orgMonthlyCogsUsd(\n rollup: OrgUsageRollupInput | null | undefined,\n siteCount: number,\n): OrgCogsResult {\n const num = (value: unknown) => {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 0\n }\n const rates = ORG_COGS_UNIT_RATES_USD\n // The platform's own axes, at the positions the plugins' declared axes\n // interleave with (`order`, a multiple of 10 here). The generator refuses a\n // declaration that takes one of these ids or positions.\n const core: Array<{ id: string; order: number; usd: number }> = [\n { id: 'storage', order: 10, usd: num(rollup?.storageGb) * rates.storagePerGbMonth },\n { id: 'pageViews', order: 20, usd: num(rollup?.pageViews) * rates.perPageView },\n // Megabytes on the doc, gigabytes in the rate — the unit mismatch is in\n // the stored field name, so convert here rather than in each caller.\n {\n id: 'dataStorage',\n order: 40,\n usd: (num(rollup?.dataStorageMb) / 1024) * rates.dataStoragePerGbMonth,\n },\n { id: 'apiRequests', order: 50, usd: num(rollup?.apiRequests) * rates.perApiRequest },\n // Every send, not the overage — the provider bills the first message of\n // the month as much as the last, so a cost model that started counting\n // at the plan's included band would report an org that stayed inside its\n // allowance as free.\n { id: 'emailSends', order: 70, usd: num(rollup?.emailSends) * rates.perEmailSend },\n ]\n // Each plugin's meter, priced at the core rate its declaration names — or\n // at ×1 where the rollup already records dollars, as the AI plugin's\n // provider spend does.\n const declared = pluginCostAxes().map((axis) => ({\n id: axis.id,\n order: axis.order,\n usd: declaredMeterReading(rollup, axis) * cogsRateOf(axis),\n }))\n const breakdown: Record<string, number> = Object.fromEntries(\n [...core, ...declared]\n .sort((a, b) => a.order - b.order)\n .map((axis) => [axis.id, axis.usd]),\n )\n const measuredUsd = Object.values(breakdown).reduce((sum, x) => sum + x, 0)\n const floorUsd = Math.max(0, siteCount) * INFRA_COGS_PER_SITE_USD\n return {\n cogsUsd: Math.max(measuredUsd, floorUsd),\n basis: measuredUsd > floorUsd ? 'measured' : 'floor',\n measuredUsd,\n floorUsd,\n breakdown,\n }\n}\n\n/**\n * Pluck the fields `orgMonthlyCogsUsd` prices out of a raw usage rollup\n * (AGL-1134) — a Firestore `snapshot.data()`, or the JSON a staff endpoint\n * served from one. ONE list of field names, in one place.\n *\n * The hazard this closes is silent. Every caller previously hand-listed the\n * six fields at its own call site — the discount route, the anomaly detector,\n * the staff org page — and `/api/admin/org-usage` projected only three of\n * them. A projection that drops a priced field does not error; the model just\n * returns a smaller number, and a smaller COGS is the direction that APPROVES\n * a discount. Same shape as the API projection that starved a predicate in\n * AGL-1355, and the two-units usage figure that read 20-45% wrong for years\n * in AGL-1402.\n *\n * UNITS, because the field names do not all say: `storageGb` and\n * `dataStorageMb` are gigabytes and MEGABYTES respectively (the conversion\n * lives in `orgMonthlyCogsUsd`, with its own test); `pageViews`,\n * `apiRequests` and `emailSends` are counts FOR THE MONTH. A plugin's fields\n * carry the unit its cost-axis declaration states — a monthly count, a\n * point-in-time level, or dollars.\n */\nexport function orgCogsInputFrom(\n source: Record<string, unknown> | null | undefined,\n): OrgUsageRollupInput {\n const read = (field: string) => {\n const value = source?.[field]\n return value == null ? undefined : (value as number)\n }\n const input: OrgUsageRollupInput = {\n hostCount: read('hostCount'),\n storageGb: read('storageGb'),\n pageViews: read('pageViews'),\n dataStorageMb: read('dataStorageMb'),\n apiRequests: read('apiRequests'),\n emailSends: read('emailSends'),\n }\n // Every field a plugin's cost axis reads or records — forwarded for the\n // reason the six above are: a projection that drops a priced field prices\n // the meter at nothing, which is the direction that approves a discount.\n for (const field of pluginCostAxisFields()) input[field] = read(field)\n return input\n}\n\n/**\n * A cost figure, or the explicit admission that the rollup has not loaded\n * (AGL-1134).\n *\n * `pending` is not \"no usage\" and not \"$0\" — it is \"do not answer yet\".\n */\nexport type OrgCogsPreview =\n | { status: 'pending' }\n | { status: 'ready'; cogs: OrgCogsResult }\n\n/**\n * `orgMonthlyCogsUsd` behind an explicit readiness flag (AGL-1134).\n *\n * The staff org page loads its rollup asynchronously, and an unfinished read\n * and an absent document are the same `undefined`. Priced directly, both\n * yield `basis: 'floor'` — so the enterprise pricing card printed \"per-site\n * floor — no usage recorded yet\" during the loading window, a\n * measurement-shaped sentence about an org whose measurements had simply not\n * arrived. That is the AGL-1380 / AGL-1422 defect exactly: a question\n * answered before its data is ready, and answered in the cheap direction.\n *\n * Returning a discriminated union rather than a nullable number is what makes\n * the window unrepresentable — a caller cannot reach a cost without first\n * handling `pending`, so the next call site cannot re-introduce this by\n * forgetting a flag it was handed.\n */\nexport function orgCogsPreview(\n ready: boolean,\n rollup: OrgUsageRollupInput | null | undefined,\n siteCount: number,\n): OrgCogsPreview {\n // Gated on the caller's flag alone. Deriving readiness from `rollup` being\n // truthy would re-create the bug: the org with no rollup yet is precisely\n // the one whose value stays falsy after the read completes.\n if (!ready) return { status: 'pending' }\n return { status: 'ready', cogs: orgMonthlyCogsUsd(rollup, siteCount) }\n}\n\n/**\n * Where a cost figure came from, in a sentence a staff member can act on\n * (AGL-1930).\n *\n * The staff quote card said `per-site floor — no usage recorded yet` for\n * EVERY `basis: 'floor'` org. Measured against production 2026-08-24 that\n * sentence is false on most of them: all 14 usage rollups across all 6 orgs\n * price under the floor, and 9 of the 14 carry genuinely non-zero usage — the\n * largest real org measured **$0.22** against its $2.00 floor. \"No usage\n * recorded\" and \"usage recorded, and it came in under the floor\" are opposite\n * facts about the cost model, and only the second one is evidence the meters\n * are working at all.\n *\n * Same failure family as AGL-1380 / AGL-1422 — a measurement-shaped sentence\n * printed over a measurement that was never consulted — one layer further in:\n * here the measurement exists and the copy denies it.\n */\nexport function orgCogsBasisSummary(\n cogs: OrgCogsResult,\n month?: string | null,\n): string {\n const when = month ?? 'the latest'\n if (cogs.basis === 'measured') return `measured from ${when} usage`\n // Absent and zero are NOT the same claim, and with `strictNullChecks` off an\n // absent meter reads as 0 all the way down — so this leans on the caller's\n // `orgCogsPreview` readiness gate for \"has a rollup arrived\", and reports\n // only what the priced total says.\n if (!(cogs.measuredUsd > 0)) return 'per-site floor — no usage recorded yet'\n // Cents are the wrong precision for these figures and rounding to them\n // prints `$0.00`, which reads as the \"no usage\" claim this function exists\n // to stop making. Every real org's measured month is under a cent.\n const measured =\n cogs.measuredUsd < 0.01\n ? cogs.measuredUsd.toFixed(4)\n : cogs.measuredUsd.toFixed(2)\n return (\n `per-site floor — ${when} usage measured ` +\n `$${measured}, under the $${cogs.floorUsd.toFixed(2)} floor`\n )\n}\n\n/**\n * Percent-off at or above which creating a coupon needs explicit staff\n * sign-off (AGL-1105). A ≥40%-off coupon is a real revenue commitment, so the\n * creation route rejects it unless the caller passes a confirm flag; the UI\n * surfaces a checkbox at this threshold.\n */\nexport const DISCOUNT_APPROVAL_THRESHOLD_PCT = 40\n\n/**\n * Discount DEPTH bands (AGL-1120) — how much of list price is being given\n * away, which is a different question from whether the remainder covers\n * infrastructure.\n *\n * The original guardrail asked only the second question, so a **93% off**\n * coupon rated \"OK — net margin 78.1% vs a 75% floor\": $9.15 left against $2\n * of infra really is a 78% contribution margin. The ratio is scale-invariant,\n * so as long as the leftover dwarfs $2 the verdict stays green no matter how\n * little is left. Depth never entered the verdict at all.\n *\n * Warn at the threshold that already requires sign-off, so the badge and the\n * approval checkbox stop disagreeing with each other; block at 70%, where a\n * deal is being given away rather than discounted.\n */\nexport const DISCOUNT_DEPTH_WARN_PCT = DISCOUNT_APPROVAL_THRESHOLD_PCT\nexport const DISCOUNT_DEPTH_BLOCK_PCT = 70\n\n/** How many sites (hosts) the org runs — at least 1 for a billing org. */\nexport function orgSiteCount(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number {\n return Math.max(1, Object.keys(org?.hosts ?? {}).length)\n}\n\n/** The margin-guardrail verdict for a proposed or applied discount. */\nexport type DiscountMarginRating = 'ok' | 'warn' | 'block'\n\nexport interface DiscountMarginResult {\n /** Pre-discount list price (`orgListPriceMonthlyUsd`). */\n grossUsd: number\n /** List price net of the proposed discount. */\n discountedUsd: number\n /** Discounted price net of Stripe's processing fee — what Aglyn keeps. */\n netUsd: number\n /**\n * Monthly COGS charged against this org: the org's MEASURED cost when the\n * caller supplies one, else the flat `$/site × sites` estimate. The flat\n * figure is a floor, never a ceiling — a measured cost below it is treated\n * as the estimate, since a rollup that has not caught up must not make a\n * deal look cheaper than the placeholder did.\n */\n infraCogsUsd: number\n /** True when `infraCogsUsd` came from measured usage, not the flat rate. */\n cogsMeasured: boolean\n /**\n * Cost recovery = (net − COGS) / net, as a fraction. 1 = COGS-free; -1 is\n * the clamp for a fully-underwater discount (net ≤ 0), which always blocks.\n *\n * NOT a deal-quality number on its own — see `depthPct`. Kept under its\n * original name because the staff UI reports it verbatim.\n */\n marginPct: number\n /** Share of list price given away, as a fraction (0.93 = 93% off). */\n depthPct: number\n /** The worse of the depth and cost-recovery verdicts. */\n rating: DiscountMarginRating\n /**\n * Which test produced `rating`, so the UI can say why rather than showing a\n * margin number that looks fine next to a red badge.\n */\n reason: 'none' | 'depth' | 'cogs' | 'underwater'\n /** The cost-recovery floor this was rated against (`NET_MARGIN_FLOOR_PCT`). */\n floorPct: number\n}\n\nexport interface DiscountMarginOptions {\n /**\n * The org's measured monthly cost, when the caller has it — the `costUsd`\n * rollup at `orgs/{id}/usage/{month}`. Server callers and the staff org\n * page can supply it; coupon creation cannot, because no org is chosen yet.\n */\n measuredCogsUsd?: number | null\n}\n\n/**\n * Rate a proposed discount (AGL-1105, re-shaped in AGL-1120) — the guardrail\n * behind staff coupon creation and per-org application.\n *\n * Two independent questions, and the verdict is the WORSE of them:\n *\n * 1. **How deep is it?** `depthPct` against `DISCOUNT_DEPTH_WARN_PCT` /\n * `DISCOUNT_DEPTH_BLOCK_PCT`. This is the one that was missing. Cost\n * recovery alone is scale-invariant — 93% off $139 leaves $9.15 against\n * $2 of infra, a genuine 78% contribution margin — so the old verdict\n * rated a give-away \"OK\" and only warned at 94%.\n * 2. **Does the remainder cover the cost of serving them?** `marginPct`\n * against `NET_MARGIN_FLOOR_PCT`, with `NET_MARGIN_WARN_BAND_PCT` below\n * it, and an automatic block once the discount drives net ≤ 0.\n *\n * Neither subsumes the other: a shallow discount on an org running twenty\n * sites can still fail cost recovery, and a deep one on a cheap org can still\n * clear it. `reason` reports which test bound, so the UI never shows a\n * healthy-looking margin beside a red badge without explanation.\n *\n * `discount` is the PROPOSED change, independent of any discount already on\n * the org — `grossUsd` is always the list price (`orgListPriceMonthlyUsd`),\n * so this never double-counts an existing `org.discount`. A non-billing org\n * (list price 0) has no revenue to protect and rates `ok`.\n */\nexport function checkDiscountMargin(\n org: Partial<AglynOrgBilling> | null | undefined,\n discount: Pick<OrgDiscount, 'percentOff' | 'amountOffUsd'>,\n options: DiscountMarginOptions = {},\n): DiscountMarginResult {\n const floorPct = NET_MARGIN_FLOOR_PCT\n const grossUsd = orgListPriceMonthlyUsd(org)\n const flatCogsUsd =\n Math.round(INFRA_COGS_PER_SITE_USD * orgSiteCount(org) * 100) / 100\n // Measured cost wins only when it EXCEEDS the flat estimate. A rollup that\n // has not run yet reads as a low number, and a guardrail that got looser\n // because the meter was behind would fail in the one direction that costs\n // money.\n const measured = Number(options.measuredCogsUsd ?? 0)\n const cogsMeasured = Number.isFinite(measured) && measured > flatCogsUsd\n const infraCogsUsd = cogsMeasured\n ? Math.round(measured * 100) / 100\n : flatCogsUsd\n if (grossUsd <= 0) {\n return {\n grossUsd: 0,\n discountedUsd: 0,\n netUsd: 0,\n infraCogsUsd,\n cogsMeasured,\n marginPct: 1,\n depthPct: 0,\n rating: 'ok',\n reason: 'none',\n floorPct,\n }\n }\n const discountedUsd = applyDiscountUsd(grossUsd, discount)\n const annual = org?.subscription?.interval === 'year'\n const netUsd = netOfProcessorFee(discountedUsd, annual)\n const depthPct =\n Math.round(((grossUsd - discountedUsd) / grossUsd) * 10000) / 10000\n const marginPct =\n netUsd > 0\n ? Math.round(((netUsd - infraCogsUsd) / netUsd) * 10000) / 10000\n : -1\n\n const depthRating: DiscountMarginRating =\n depthPct * 100 >= DISCOUNT_DEPTH_BLOCK_PCT\n ? 'block'\n : depthPct * 100 >= DISCOUNT_DEPTH_WARN_PCT\n ? 'warn'\n : 'ok'\n // Same bands the staff quote card renders — one reader for the two\n // constants (AGL-1930), so a re-tune cannot reach the guardrail and miss\n // the UI.\n const cogsRating: DiscountMarginRating = netMarginRating(marginPct)\n\n const severity = { ok: 0, warn: 1, block: 2 } as const\n const rating =\n severity[depthRating] >= severity[cogsRating] ? depthRating : cogsRating\n const reason: DiscountMarginResult['reason'] =\n rating === 'ok'\n ? 'none'\n : netUsd <= 0\n ? 'underwater'\n : severity[depthRating] >= severity[cogsRating]\n ? 'depth'\n : 'cogs'\n\n return {\n grossUsd,\n discountedUsd,\n netUsd,\n infraCogsUsd,\n cogsMeasured,\n marginPct,\n depthPct,\n rating,\n reason,\n floorPct,\n }\n}\n\n/**\n * The plan the org actually gets (AGL-247): missing/unknown plans resolve\n * as `free`, and a paid plan whose subscription is canceled/unpaid/\n * incomplete downgrades to `free` until the webhook restores it — plan\n * fields alone are not entitlement.\n *\n * A STAFF COMP comes first (AGL-3034), and only where no subscription decides\n * the plan: `resolvePlanComp` answers `null` while one is live, so a paying\n * workspace's plan is always its subscription's. The comp is its own marker,\n * never the stored `plan` — a canceled customer whose `plan` still names what\n * they paid for resolves as Free exactly as before, because nothing about\n * their document is a comp.\n */\nexport function resolveEffectivePlan(\n org: Partial<AglynOrgBilling> | null | undefined,\n): OrgPlan {\n const comp = resolvePlanComp(org)\n if (comp) return comp.plan\n const plan = org?.plan\n if (!plan || !(plan in PLAN_ENTITLEMENTS)) return 'free'\n const status = subscriptionStatusOf(org)\n if (plan !== 'free' && status && DEAD_SUBSCRIPTION_STATUSES.has(status)) {\n return 'free'\n }\n return plan\n}\n\nfunction resolvePlan(org: Partial<AglynOrgBilling> | null | undefined) {\n return resolveEffectivePlan(org)\n}\n\n/**\n * Purchased add-on quantities that currently apply (AGL-524): add-ons\n * bill as items on the org's Stripe subscription, so a dead subscription\n * (the `resolveEffectivePlan` set) stops them counting until the webhook\n * restores it. Orgs with no subscription state keep staff-set quantities\n * (comped add-ons predating self-serve billing).\n */\nfunction resolvePurchasedAddons(\n org: Partial<AglynOrgBilling> | null | undefined,\n): OrgSeatAddons {\n const status = subscriptionStatusOf(org)\n if (status && DEAD_SUBSCRIPTION_STATUSES.has(status)) return {}\n return org?.seatAddons ?? {}\n}\n\n/**\n * Whether the org's Aglyn AI add-on currently applies (AGL-2896): bought,\n * and on a subscription that is still paying for it. The ONE reading of\n * `seatAddons.aiAddon` — the entitlement fold and the assist overage rate\n * both ask this, so a dead subscription drops the band and the rate\n * together rather than leaving a rate on a band that has gone.\n */\nexport function hasAiAddon(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n return aiAddonUnits(resolvePurchasedAddons(org)) === 1\n}\n\n/**\n * Numeric entitlement keys that were REMOVED from `PLAN_ENTITLEMENTS` and\n * must never be resolved again from a stored override (AGL-2133).\n *\n * `totalSiteSizeMb` is the first. It carried a value on all 8 plans and was\n * enforced by nothing — no gate, no meter, no alert. AGL-1107 added it and\n * AGL-1370 deleted its meter and its alert on measurement rather than taste:\n * `measure-node-map.ts` refuses any node map over 900 KB (AGL-678), so the\n * measurable org total tops out at 2.3–20.9% of the cap depending on plan.\n * What survived was a WRITABLE staff override field for a number nothing\n * read, so a support engineer resolving a \"this site is too big\" ticket\n * raised it, got a success and an audit row, and changed nothing.\n *\n * WIRING IT WAS THE OTHER OPTION AND IT IS WORSE. A cap the measurement can\n * reach a fifth of is a gate that refuses nobody — the same \"reports success,\n * does nothing\" shape, moved from the staff dialog to the enforcement point,\n * plus a new hard wall introduced days before launch. The per-site size\n * ceiling that actually binds is AGL-678's 900 KB node map, and it is real.\n *\n * The rollup's `siteSizeMb` MEASUREMENT stays — host-usage and the usage\n * audit read it, and it is a genuine internal signal. Only the entitlement\n * goes.\n */\nexport const RETIRED_ENTITLEMENT_KEYS: ReadonlySet<string> = new Set([\n 'totalSiteSizeMb',\n])\n\n/** Each seat band as `[included, max]`: the max purchases may reach. */\nconst SEAT_BANDS = [\n ['managersPerOrg', 'maxManagersPerOrg'],\n ['membersPerHost', 'maxMembersPerHost'],\n ['datasetsPerOrg', 'maxDatasetsPerOrg'],\n] as const satisfies ReadonlyArray<\n readonly [\n keyof Omit<ResolvedOrgEntitlements, 'features'>,\n keyof Omit<ResolvedOrgEntitlements, 'features'>,\n ]\n>\n\n/**\n * The numeric entitlements that are PRICES rather than caps (AGL-3049): the\n * platform's percentage of a storefront or marketplace sale.\n *\n * Every other numeric key is a band — a count, a size, a monthly or daily\n * allowance — and an uncapped comp lifts it to `UNLIMITED`. These three it\n * must never touch. `transactionFee*Pct` becomes Stripe's\n * `application_fee_amount` at checkout, so an unlimited one is a fee larger\n * than the charge and every sale on the storefront fails; and \"no cap\" has\n * no meaning for a price in the first place. An uncapped workspace pays its\n * plan's percentages, or whatever a per-org override sets them to.\n *\n * Named rather than derived from the `Pct` suffix, so a new numeric key is a\n * band unless someone decides here that it is a price. `plan-comp.spec.ts`\n * pins these three by name and proves every other numeric key is lifted.\n */\nexport const PRICE_ENTITLEMENT_KEYS: ReadonlySet<string> = new Set([\n 'transactionFeePhysicalPct',\n 'transactionFeeDigitalPct',\n // A plugin's percentage of a sale says so in its declaration.\n ...pluginPlanQuotas()\n .filter((declared) => declared.price)\n .map((declared) => declared.key),\n])\n\n/**\n * The entitlements with every band lifted (AGL-3049): `UNLIMITED` on each\n * numeric key but the prices, features untouched. What an uncapped comp in\n * force resolves to — see `isUncappedPlanComp`.\n *\n * Applied LAST, over the plan row, the per-org overrides and the folded\n * add-ons alike: \"every band reads as unlimited\" means an override written\n * before the comp was uncapped does not quietly cap it again. The overrides\n * stay stored, and apply once more if the comp is capped or removed.\n */\nfunction withEveryCapLifted(\n entitlements: ResolvedOrgEntitlements,\n): ResolvedOrgEntitlements {\n const lifted: Record<string, unknown> = { ...entitlements }\n for (const [key, value] of Object.entries(entitlements)) {\n if (typeof value === 'number' && !PRICE_ENTITLEMENT_KEYS.has(key)) {\n lifted[key] = UNLIMITED\n }\n }\n return lifted as unknown as ResolvedOrgEntitlements\n}\n\n/**\n * Effective entitlements for an org: plan defaults with the org doc's\n * per-key overrides applied (features merge key-by-key too), then\n * purchased add-ons stacked on top (AGL-524): `seatAddons.hosts` raises\n * `hostLimit`, `seatAddons.eventCalendar` switches the `eventCalendar`\n * feature on, and every seat add-on a plugin declared through\n * `registerPluginEntitlements` (AGL-2940) widens the quota it names by its\n * per-plan figure per unit and switches its features on — the Aglyn AI\n * add-on (AGL-2896) is the first such declaration. (Seat/dataset add-ons\n * instead fold in at `checkSeatQuota` / `checkDatasetQuota`, where the\n * per-plan hard max clamps them.)\n * Missing or unknown plans resolve as `free`.\n *\n * EVERY NUMERIC KEY of `OrgEntitlements` is an override axis, not a chosen\n * few: the loop below copies any number it finds under the org's\n * `entitlements`, so a contracted `bandwidthGb`, `apiRequestsPerMonth` or\n * `workflowRunsPerMonth` resolves ahead of the plan default exactly as the\n * long-documented `emailSendsPerMonth`, `formsPerHost` and\n * `assistCreditsPerMonth` overrides do. Since 2026-09-07 this is what makes\n * Enterprise's finite fallbacks safe to hold: the row in `PLAN_ENTITLEMENTS`\n * is what an agreement gets before its numbers are written down, and the\n * override is where they are written. `plan-entitlements.spec.ts` proves\n * the precedence on every axis, so a resolver that started honoring a list\n * would fail by the name of the axis it dropped.\n *\n * `hostLimit` is DELIBERATELY unclamped here (AGL-1738): there is no\n * `maxHostsPerOrg` to clamp against, because extra sites are sold flat rather\n * than up to a band. Their bound is the purchase ceiling,\n * `EXTRA_HOSTS_ADDON_MAX`, and a band-shaped `Math.min` added here would\n * silently disable sites the org is invoiced for. See that constant before\n * \"fixing\" this add.\n *\n * `posRegisters` IS NOT ADDED HERE, and this is the fix, not an omission\n * (AGL-1775). The register add-on is priced \"per\n * extra register/location\" and enforced PER SITE, so folding the org-wide\n * purchase into an org-level value every site inherits sold one register and\n * delivered `hostLimit` of them. `seatAddons.posRegisters` is now a POOL and\n * `org.registerAllocations` says which site holds each seat. The number this\n * function returns for `posRegisters` is therefore **the plan's cap alone** —\n * exactly what an unallocated site must resolve to. Per-site capacity comes\n * from `resolveHostRegisterCap(org, hostId)`; nothing else may add the pool\n * back in.\n *\n * (`eventCalendar` is still org-wide on purpose: it is documented and priced\n * as \"one purchase covers every host in the org\". The register add-on never\n * was, which is the whole difference.)\n *\n * AN UNCAPPED COMP IN FORCE (AGL-3049) resolves every band as `UNLIMITED`\n * on top of all of the above — `withEveryCapLifted` — and leaves the\n * features and the fee percentages exactly as resolved.\n */\nexport function resolveOrgEntitlements(\n org: Partial<AglynOrgBilling> | null | undefined,\n): ResolvedOrgEntitlements {\n const resolved = resolveCappedOrgEntitlements(org)\n return isUncappedPlanComp(org) ? withEveryCapLifted(resolved) : resolved\n}\n\n/** `resolveOrgEntitlements` before an uncapped comp lifts anything. */\nfunction resolveCappedOrgEntitlements(\n org: Partial<AglynOrgBilling> | null | undefined,\n): ResolvedOrgEntitlements {\n const defaults = withPluginFeatureDefaults(\n PLAN_ENTITLEMENTS[resolvePlan(org)],\n resolvePlan(org),\n )\n const overrides = org?.entitlements\n let resolved = defaults\n if (overrides) {\n const {\n features: featureOverrides,\n datasetsPerHost: legacyDatasets,\n maxDatasetsPerHost: legacyMaxDatasets,\n ...quotaOverrides\n } = overrides\n const merged = { ...defaults }\n for (const [key, value] of Object.entries(quotaOverrides)) {\n // A RETIRED quota's stored override is dropped, not merged (AGL-2133).\n // This loop copies any numeric key it finds, so removing a key from\n // `PLAN_ENTITLEMENTS` alone would leave the value that staff already\n // wrote onto live org documents still appearing on the resolved\n // entitlements — a number with no default behind it and no reader in\n // front of it, which is a worse artefact than the field that produced\n // it. `tools/scripts/strip-retired-entitlements.mjs` clears them from\n // Firestore; this makes the resolver correct before it has run, and\n // stays correct if a document is restored from an old backup.\n if (RETIRED_ENTITLEMENT_KEYS.has(key)) continue\n if (typeof value === 'number') (merged as any)[key] = value\n }\n // Pre-AGL-240 override docs keyed datasets per host; resolve them into\n // the org keys unless an org-keyed override is present.\n if (typeof legacyDatasets === 'number' && overrides.datasetsPerOrg == null) {\n merged.datasetsPerOrg = legacyDatasets\n }\n if (\n typeof legacyMaxDatasets === 'number' &&\n overrides.maxDatasetsPerOrg == null\n ) {\n merged.maxDatasetsPerOrg = legacyMaxDatasets\n }\n // A seat band's max never sits below its included count. The max is the\n // ceiling PURCHASES stop at, and `checkSeatQuota` clamps the limit to it —\n // so a staff override of Team seats to 2 on Free (max 1) resolved back to\n // 1 everywhere: the members card, the invite gate and the acceptance\n // transaction all refused the second manager the override granted. An\n // override of the included count is a grant; the band moves with it.\n for (const [included, max] of SEAT_BANDS) {\n merged[max] = Math.max(merged[max], merged[included])\n }\n // An org granted `reusableComponents` by override was granted components\n // without a count, which is what the flag meant before\n // `componentsPerHost` existed (AGL-3615). The grant keeps meaning that\n // unless the same override also names a count, which then decides.\n if (\n featureOverrides?.reusableComponents === true &&\n typeof quotaOverrides.componentsPerHost !== 'number'\n ) {\n merged.componentsPerHost = UNLIMITED\n }\n resolved = {\n ...merged,\n features: { ...defaults.features, ...featureOverrides },\n }\n }\n const purchased = resolvePurchasedAddons(org)\n const extraHosts = Math.max(0, purchased.hosts ?? 0)\n const eventCalendar = (purchased.eventCalendar ?? 0) >= 1\n // A declared add-on's quota is added AFTER the per-org override, so a\n // contracted figure and a purchased add-on stack the way a raised\n // `hostLimit` and purchased sites do. The per-unit figure is the PLAN's,\n // read off the effective plan rather than the stored one, so a dead\n // subscription that resolved to free adds free's figure.\n const folded = foldPluginSeatAddons(resolved, purchased, resolvePlan(org))\n if (!extraHosts && !eventCalendar && folded === resolved) return resolved\n return {\n ...folded,\n hostLimit: folded.hostLimit + extraHosts,\n features: {\n ...folded.features,\n ...(eventCalendar ? { eventCalendar: true } : {}),\n },\n }\n}\n\n/**\n * How many units of a plugin-declared seat add-on a purchase map counts as\n * (AGL-2940): the stored quantity, capped at the declaration's `maxUnits`\n * — 1 for an org-wide add-on, where a doubled webhook item or a hand edit\n * above one is still one purchase. A non-number, `NaN`, a negative, or a\n * key nothing declared is none.\n */\nexport function pluginSeatAddonUnits(\n addons: OrgSeatAddons | null | undefined,\n key: string,\n): number {\n const declared = pluginSeatAddon(key)\n if (!declared) return 0\n const quantity = Number((addons as Record<string, unknown> | null)?.[key] ?? 0)\n if (!Number.isFinite(quantity) || quantity < 1) return 0\n const units = Math.floor(quantity)\n return declared.maxUnits != null ? Math.min(units, declared.maxUnits) : units\n}\n\n/**\n * Whether a plugin-declared seat add-on currently applies to the org: bought,\n * and on a subscription that is still paying for it — the one reading every\n * surface that shows or charges for the add-on goes through, so a dead\n * subscription drops the quota and the feature together.\n */\nexport function hasPluginSeatAddon(\n org: Partial<AglynOrgBilling> | null | undefined,\n key: string,\n): boolean {\n return pluginSeatAddonUnits(resolvePurchasedAddons(org), key) > 0\n}\n\n/**\n * Every declared seat add-on the org holds, folded in catalog order: the\n * quota each names gains its per-plan figure per unit, and its features\n * switch on. Returns the input object untouched when nothing applies, so a\n * caller can tell \"nothing folded\" from \"folded to the same numbers\".\n */\nfunction foldPluginSeatAddons(\n resolved: ResolvedOrgEntitlements,\n purchased: OrgSeatAddons,\n plan: OrgPlan,\n): ResolvedOrgEntitlements {\n let out = resolved\n for (const addon of listPluginSeatAddons()) {\n const units = pluginSeatAddonUnits(purchased, addon.key)\n if (!units) continue\n const next: ResolvedOrgEntitlements = { ...out, features: { ...out.features } }\n if (addon.quota) {\n const perUnit = addon.quota.perUnitByPlan[plan] ?? 0\n const bag = next as unknown as Record<string, unknown>\n const current = Number(bag[addon.quota.key] ?? 0)\n bag[addon.quota.key] = (Number.isFinite(current) ? current : 0) + perUnit * units\n }\n const features = next.features as unknown as Record<string, boolean>\n for (const feature of addon.features ?? []) features[feature] = true\n out = next\n }\n return out\n}\n\n/**\n * The plan row with every plugin-declared feature's default filled in\n * (AGL-2940). A feature the plan tables already carry keeps the table's\n * answer; a feature only a declaration knows reads its `defaultByPlan`, or\n * `false`. Returns the row itself when no declaration adds anything.\n */\nfunction withPluginFeatureDefaults(\n row: ResolvedOrgEntitlements,\n plan: OrgPlan,\n): ResolvedOrgEntitlements {\n const features = row.features as unknown as Record<string, boolean | undefined>\n let out: Record<string, boolean | undefined> | undefined\n for (const declared of listPluginFeatures()) {\n if (features[declared.key] !== undefined) continue\n out = out ?? { ...features }\n out[declared.key] = declared.defaultByPlan?.[plan] ?? false\n }\n return out\n ? { ...row, features: out as unknown as ResolvedOrgEntitlements['features'] }\n : row\n}\n\n/**\n * The org's POS register pool (AGL-1775): how many seats were purchased, how\n * many are assigned to a site, and how many are left to assign.\n *\n * `purchased` runs through `resolvePurchasedAddons`, so a dead subscription\n * empties the pool exactly as it empties every other add-on — the seats stop\n * counting when they stop billing.\n *\n * `byHost` is the SANITISED allocation, and it never sums past `purchased`.\n * The write path validates before it stores, so an over-allocated map means\n * the data is stale or corrupt (a purchase reduced without the allocation\n * being trimmed, a partial write, a hand edit). Both directions of getting\n * that wrong cost money, so it is resolved rather than trusted: hosts are\n * walked in sorted-id order and granted what remains of the pool, which is\n * deterministic — the same host gets the same answer on every reader — and\n * cannot hand out capacity nobody paid for.\n *\n * Every value is coerced: a non-number, `NaN`, `Infinity` or a negative reads\n * as zero seats. An `Infinity` in this map would otherwise become an\n * unbounded per-site register cap, which is the one outcome the guard in\n * `resolveHostRegisterCap` exists to make impossible.\n */\nexport function resolveRegisterSeatPool(\n org: Partial<AglynOrgBilling> | null | undefined,\n): {\n purchased: number\n allocated: number\n available: number\n byHost: Record<string, number>\n} {\n const rawPurchased = resolvePurchasedAddons(org).posRegisters\n const purchased =\n Number.isFinite(rawPurchased) && (rawPurchased as number) > 0\n ? Math.floor(rawPurchased as number)\n : 0\n const raw = org?.registerAllocations\n const byHost: Record<string, number> = {}\n let allocated = 0\n if (raw && typeof raw === 'object') {\n for (const hostId of Object.keys(raw).sort()) {\n if (allocated >= purchased) break\n const value = Number((raw as Record<string, unknown>)[hostId])\n if (!Number.isFinite(value) || value <= 0) continue\n const seats = Math.min(Math.floor(value), purchased - allocated)\n if (seats <= 0) continue\n byHost[hostId] = seats\n allocated += seats\n }\n }\n return { purchased, allocated, available: purchased - allocated, byHost }\n}\n\n/**\n * How many POS registers one SITE may run (AGL-1775): the plan's per-site cap\n * plus whatever the org has assigned to that site out of the pool.\n *\n * THE GUARD, and the reason this is a function rather than a lookup. An\n * unallocated host — no entry in `registerAllocations`, a missing map, a\n * missing org, an org still loading — resolves to `resolveOrgEntitlements(org)\n * .posRegisters`, which since AGL-1775 is the PLAN's cap and nothing else.\n * Never the pooled total, and never `Infinity` unless the plan itself is\n * `UNLIMITED` (enterprise, where an unbounded cap is what was sold).\n *\n * `checkQuota(undefined)` resolving to the Free tier is the lesson this\n * inverts: an absent input there quietly answered a question in the\n * PERMISSIVE direction on the day it mattered. Here the absent input answers\n * in the direction of the plan, which is the conservative one — a site that\n * has been assigned seats and whose allocation failed to load runs fewer\n * registers than it paid for, which the console can say out loud, rather than\n * more than it paid for, which nothing would ever notice.\n *\n * There is deliberately no separate read: the allocation lives on the org doc\n * beside the entitlements, so a caller holding one holds the other.\n */\nexport function resolveHostRegisterCap(\n org: Partial<AglynOrgBilling> | null | undefined,\n hostId: string | null | undefined,\n): number {\n const planCap = resolveOrgEntitlements(org).posRegisters\n if (!hostId) return planCap\n // An UNLIMITED plan cap plus anything is still UNLIMITED; short-circuit so\n // `Infinity + n` never has to be reasoned about downstream.\n if (planCap === UNLIMITED) return UNLIMITED\n return planCap + (resolveRegisterSeatPool(org).byHost[hostId] ?? 0)\n}\n\n/**\n * The org's COLLABORATOR seat pool (AGL-2439): how many per-site collaborator\n * seats were purchased, how many are assigned to a site, and how many are\n * left to assign.\n *\n * The AGL-1775 register mechanism, applied to the key it was never applied to.\n * `seatAddons.members` is bought once, org-wide; `membersPerHost` is enforced\n * PER SITE. Adding one to the other handed the whole purchase to every site,\n * so an org with 20 sites that bought ONE extra collaborator seat received 20.\n * The quantity is now the size of an org-level pool and\n * `org.collaboratorAllocations` says which site holds each seat.\n *\n * Everything about the resolution is deliberately identical to\n * `resolveRegisterSeatPool` — the same clamp, the same sorted-id determinism,\n * the same coercion of a non-number / `NaN` / `Infinity` / negative to zero.\n * This is ONE mechanism used twice, not a second mechanism that happens to\n * rhyme: a reader who has understood the register pool has understood this,\n * and a fix to one is a fix to both.\n *\n * `purchased` runs through `resolvePurchasedAddons`, so a dead subscription\n * empties the pool exactly as it empties every other add-on.\n */\nexport function resolveCollaboratorSeatPool(\n org: Partial<AglynOrgBilling> | null | undefined,\n): {\n purchased: number\n allocated: number\n available: number\n byHost: Record<string, number>\n} {\n const rawPurchased = resolvePurchasedAddons(org).members\n const purchased =\n Number.isFinite(rawPurchased) && (rawPurchased as number) > 0\n ? Math.floor(rawPurchased as number)\n : 0\n const raw = org?.collaboratorAllocations\n const byHost: Record<string, number> = {}\n let allocated = 0\n if (raw && typeof raw === 'object') {\n for (const hostId of Object.keys(raw).sort()) {\n if (allocated >= purchased) break\n const value = Number((raw as Record<string, unknown>)[hostId])\n if (!Number.isFinite(value) || value <= 0) continue\n const seats = Math.min(Math.floor(value), purchased - allocated)\n if (seats <= 0) continue\n byHost[hostId] = seats\n allocated += seats\n }\n }\n return { purchased, allocated, available: purchased - allocated, byHost }\n}\n\n/**\n * How many console COLLABORATORS one SITE may hold (AGL-2439): the plan's\n * per-site allowance plus whatever the org has assigned to that site out of\n * the pool, clamped to the plan's hard `maxMembersPerHost`.\n *\n * THE GUARD, and the reason this is a function rather than a lookup. An\n * unallocated host — no entry in `collaboratorAllocations`, a missing map, a\n * missing org, an org still loading — resolves to the PLAN's `membersPerHost`\n * and nothing else. Never the pooled total, and never `Infinity` unless the\n * plan itself is `UNLIMITED` (enterprise, where an unbounded cap is what was\n * sold). `checkQuota(undefined)` resolving to the Free tier is the lesson\n * this inverts: the absent input answers in the direction of the plan, which\n * is the conservative one.\n *\n * The clamp is what `checkSeatQuota` always did and is kept: collaborator\n * seats are sold up to a BAND (`maxMembersPerHost`), unlike registers, which\n * are sold flat. Assigning more pool seats to a site than the band allows\n * cannot lift it past the band — the org must upgrade the plan.\n *\n * There is deliberately no separate read: the allocation lives on the org doc\n * beside the entitlements, so a caller holding one holds the other.\n */\nexport function resolveHostCollaboratorCap(\n org: Partial<AglynOrgBilling> | null | undefined,\n hostId: string | null | undefined,\n): number {\n const entitlements = resolveOrgEntitlements(org)\n const included = entitlements.membersPerHost\n const maxSeats = entitlements.maxMembersPerHost\n if (!hostId) return Math.min(included, maxSeats)\n // An UNLIMITED plan cap plus anything is still UNLIMITED; short-circuit so\n // `Infinity + n` never has to be reasoned about downstream.\n if (included === UNLIMITED) return UNLIMITED\n const assigned = resolveCollaboratorSeatPool(org).byHost[hostId] ?? 0\n return Math.min(included + assigned, maxSeats)\n}\n\n/**\n * Quota answer for a site's console COLLABORATORS (AGL-2439) — the\n * host-scoped `checkSeatQuota`, in the same `SeatQuotaResult` shape so the\n * existing call sites and refusal copy read identically.\n *\n * `checkSeatQuota(org, 'members', …)` is WRONG for a site now and this exists\n * so nobody has to remember why: since AGL-2439 that helper no longer folds\n * the org-wide purchase into a per-site number, so it answers the PLAN's cap\n * and would refuse a site the seats it holds.\n *\n * ## THE GRANDFATHER BOUNDARY — explicit, and this field is it\n *\n * The cap is corrected, and no org already above it is evicted or locked\n * out.\n *\n * The boundary is drawn between two different questions, and they are\n * different lines of code rather than one number used for two purposes:\n *\n * `allowed` — may this site take on ANOTHER collaborator? Bound\n * by `limit`, the corrected cap. This is the ONLY\n * thing the cap decides.\n * `retainedOverCap` — how many seats this site holds ABOVE `limit`. They\n * are RETAINED. Nothing in this repo revokes a\n * collaborator for being over a cap, and nothing may\n * be added that does: the cap binds ALLOCATION, never\n * ACCESS.\n *\n * So an over-cap site keeps every collaborator it has, cannot add another,\n * and returns to normal by attrition, by buying pool seats and assigning them\n * here, or by upgrading — at which point `limit` recomputes from the new plan\n * or allocation and the retention lapses on its own. That is what \"they keep\n * what they have until they next change plan or seat count\" means, resolved\n * rather than stored: a stored floor would need a backfill over live orgs and\n * would be a defaulted field on a `merge`-written document, which is the\n * converter hazard that destroys the real value it was meant to protect.\n *\n * `retainedOverCap` is non-zero ONLY when the site is already over. It is not\n * headroom and cannot be spent: it is a count the console says out loud so\n * the customer sees why the next Add is refused while nobody loses access.\n */\nexport function checkHostCollaboratorQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n hostId: string | null | undefined,\n currentUsage: number,\n): SeatQuotaResult & {\n /** Seats held ABOVE `limit`, retained and never revoked (the grandfather). */\n retainedOverCap: number\n /** Pool seats the org has assigned to THIS site. */\n assignedSeats: number\n} {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const included = entitlements.membersPerHost\n const maxSeats = entitlements.maxMembersPerHost\n const addonPriceUsd = pricing.extraCollaboratorMonthlyUsd\n const assignedSeats = resolveCollaboratorSeatPool(org).byHost[hostId ?? ''] ?? 0\n const limit = resolveHostCollaboratorCap(org, hostId)\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n included,\n purchased: assignedSeats,\n maxSeats,\n upgradeRequired: addonPriceUsd === null || limit >= maxSeats,\n addonPriceUsd,\n // THE GRANDFATHER. `Math.max(0, …)` so a site under its cap reports zero\n // rather than a negative that a caller could add and turn into headroom.\n retainedOverCap: Math.max(0, currentUsage - limit),\n assignedSeats,\n }\n}\n\n/**\n * Quota answer for a site's POS registers (AGL-1775) — the register-shaped\n * `checkQuota`, in the same `{ allowed, limit, remaining }` shape so callers\n * read identically.\n *\n * `checkQuota(org, 'posRegisters', used)` is WRONG for registers now and this\n * exists so nobody has to remember why: that helper resolves the org-level\n * value, which is the plan cap with no pool in it, so it would refuse a site\n * the seats it holds. Everything that gates a register creation calls this.\n */\nexport function checkHostRegisterQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n hostId: string | null | undefined,\n currentUsage: number,\n): { allowed: boolean; limit: number; remaining: number } {\n const limit = resolveHostRegisterCap(org, hostId)\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n }\n}\n\n/**\n * Platform transaction fee % for a storefront sale (AGL-278): resolved\n * from the effective plan (with per-org overrides) by product type.\n * Digital and service sales use the digital rate; AGL-307 turns this into\n * the Stripe Connect `application_fee_amount` at charge time.\n */\nexport function resolveTransactionFeePct(\n org: Partial<AglynOrgBilling> | null | undefined,\n productType: 'physical' | 'digital' | 'service',\n): number {\n const entitlements = resolveOrgEntitlements(org)\n const key =\n productType === 'physical'\n ? 'transactionFeePhysicalPct'\n : 'transactionFeeDigitalPct'\n // A MALFORMED value falls back to the PLAN'S OWN rate, never to zero\n // (AGL-2114). `resolveOrgEntitlements` merges any numeric override\n // indistinguishably from the default, and `strictNullChecks` is off\n // repo-wide, so a `NaN`, an `Infinity`, a stringly-typed number or a\n // negative can reach here from a hand-edited or partially-written org doc.\n // This used to answer 0 for every one of them — silently zeroing the\n // platform's cut on a PAYING merchant's storefront, which on a destination\n // charge is not \"no take rate\" but a loss (see below).\n //\n // The sibling `resolveMarketplaceFeePct` has forbidden exactly this since\n // AGL-1543, in as many words: \"A malformed override must never zero the\n // platform's cut.\" This is the storefront half of the same rule. The plan\n // table is a literal and cannot itself be malformed, so it is the honest\n // floor — and it preserves the tiers whose 0% is a DELIBERATE, advertised\n // rate rather than a data fault.\n const raw = entitlements[key]\n const pct =\n typeof raw === 'number' && Number.isFinite(raw) && raw >= 0\n ? raw\n : PLAN_ENTITLEMENTS[resolvePlan(org)][key]\n if (Number.isFinite(pct) && pct > 0) return pct\n // The free plan's 0% prices a plan that CANNOT SELL: `features.commerce` is\n // false there, so on an ordinary free org this rate is never consulted. It\n // IS consulted for the one org shape that can reach a storefront on the free\n // entitlement set — a per-org `features.commerce` override — and 0% on a\n // DESTINATION charge is not \"no take rate\", it is a loss: Stripe's\n // processing fee (2.9% + 30¢) and any $15 dispute fee are debited from the\n // PLATFORM's balance, so every sale costs Aglyn money (AGL-2071).\n //\n // `marketplaceFeePct: 30` on the free row is the same decision already made\n // for marketplace selling, and its comment says so in as many words: the\n // rate exists to price \"an org GRANTED selling via a per-org feature\n // override\". This is the missing equivalent for storefront sales, expressed\n // here rather than in the table because the plan CARDS render the table —\n // `billing-plan-cards.component.tsx:444` — and a Free card advertising a\n // transaction fee for selling it does not permit would be a pricing lie.\n //\n // A staff fee override wins even at 0: `resolveOrgEntitlements` merges any\n // numeric override indistinguishably from the default, so the raw doc is\n // what separates \"nobody set a rate\" from a deliberately comped one. That\n // decision has a name and a reason attached (`org-override-reason.ts`), and\n // this floor must not silently overrule it.\n if (\n resolvePlan(org) === 'free' &&\n entitlements.features.commerce &&\n typeof org?.entitlements?.[key] !== 'number'\n ) {\n return PLAN_ENTITLEMENTS.starter[key]\n }\n return 0\n}\n\n/**\n * Stripe's processing cost on ONE destination-charge sale, in cents\n * (AGL-2152) — a storefront order, a booking deposit — the dearest enabled\n * method's percentage plus the fixed 30¢, rounded UP on the percentage so the\n * answer is never a cent short of what Stripe actually debits.\n *\n * WHO PAYS THIS. Every storefront charge is a DESTINATION charge:\n * `payment_intent_data[transfer_data][destination]` names the merchant's\n * connected account and no `transfer_data[amount]` is sent, so Stripe moves\n * the whole charge to the merchant and debits its processing fee from the\n * PLATFORM's balance. Aglyn's net on an order is therefore\n *\n * application_fee_amount − (processing% × charge + 30¢)\n *\n * and with `application_fee_amount` at 0 — which is what\n * `transactionFeePhysicalPct: 0` produced on Pro, Business, Scale, Advanced,\n * Agency and Enterprise — that net is NEGATIVE on every single sale. A flat\n * percentage cannot repair it either: Starter's 2% is below even the cheapest\n * processing rate, so Starter lost money at every order size too, and any rate\n * above it still loses until the order clears `30¢ ÷ (take − processing)`. The\n * fixed 30¢ is why a percentage alone can never reach break-even on small\n * orders, and why this returns a CENTS amount rather than a rate.\n *\n * WHY THE DEAREST METHOD AND NOT 2.9%. The shopper picks how to pay AFTER the\n * session is created, and `application_fee_amount` is a fixed number Stripe\n * will not let us revise once the charge settles. No storefront path pins\n * `payment_method_types`, so the platform's payment method configuration\n * decides the set — and per AGL-2343 that configuration has `klarna` and\n * `affirm` on beside the card family. Pricing this at the card rate would be\n * wrong exactly on the orders where it cost the most, which is the same\n * reasoning the marketplace listing floor applies; both are defined from the\n * SAME processing constants so the two cannot drift.\n *\n * THE ONE-LINE LEVER. On a card-family order this over-recovers by the spread\n * between the two rates. Pinning `payment_method_types` on the storefront\n * sessions to the card family would make 2.9% the true binding rate, and\n * `SALE_PROCESSING_PERCENT` below is then the single identifier to\n * repoint. That is a product decision about which payment methods a storefront\n * offers, not this one; it is recorded on AGL-2152 and in the Pricing Decision\n * Log so it can be taken deliberately.\n */\n// `SALE_PROCESSING_PERCENT` and `SALE_PROCESSING_FIXED_CENTS` are declared\n// beside the processing constants they are defined from, further down this\n// file — a `const` cannot be read before its declaration is evaluated, and\n// this function is only ever CALLED, never evaluated at import.\nexport function saleProcessingCostCents(chargeCents: number): number {\n if (!Number.isFinite(chargeCents) || chargeCents <= 0) return 0\n return (\n Math.ceil((chargeCents * SALE_PROCESSING_PERCENT) / 100) +\n SALE_PROCESSING_FIXED_CENTS\n )\n}\n\n/**\n * The Stripe Connect `application_fee_amount` for ONE storefront sale\n * (AGL-2152) — the platform's advertised take PLUS Stripe's processing cost,\n * passed through at cost.\n *\n * fee = take%(feeBaseCents) + processing%(chargeCents) + 30¢\n *\n * so Aglyn's net per order is AT LEAST the advertised take, at every order\n * size down to Stripe's own 50¢ charge minimum, and never negative. The\n * September-1 price lock was lifted for this one change; see the Pricing\n * Decision Log entry of 2026-08-19. No advertised PLATFORM rate moved — a 0% tier is still 0%\n * platform take — what changed is that the card cost stops being absorbed\n * silently by Aglyn.\n *\n * `feeBaseCents` is the GOODS the take is a cut of (discounts already applied,\n * tax and shipping excluded — that has always been the basis, AGL-2317).\n * `chargeCents` is what Stripe will actually run the card for, which is goods\n * + tax + shipping, and defaults to `feeBaseCents` for the paths where they\n * are the same number. Passing the goods figure for a charge that also carries\n * tax would under-recover by the processing rate on the difference.\n *\n * Clamped to `chargeCents` because Stripe rejects an application fee larger\n * than the charge. That clamp can only bind below ~32¢, under Stripe's own 50¢\n * charge minimum, so it never trims a real order's fee.\n */\nexport function resolveTransactionFeeCents(\n org: Partial<AglynOrgBilling> | null | undefined,\n productType: 'physical' | 'digital' | 'service',\n feeBaseCents: number,\n chargeCents: number = feeBaseCents,\n): number {\n const charge =\n Number.isFinite(chargeCents) && chargeCents > 0 ? Math.round(chargeCents) : 0\n if (charge <= 0) return 0\n const base =\n Number.isFinite(feeBaseCents) && feeBaseCents > 0\n ? Math.round(feeBaseCents)\n : 0\n const pct = resolveTransactionFeePct(org, productType)\n // The `Math.max(1, …)` every door already applied: a rate above zero on\n // goods the shopper pays for is a fee however small it rounds to.\n const take = pct > 0 && base > 0 ? Math.max(1, Math.round((base * pct) / 100)) : 0\n return Math.min(charge, take + saleProcessingCostCents(charge))\n}\n\n/**\n * Stripe's processing cost on a RECURRING charge of `amountCents`, as the\n * percent a Subscription's `application_fee_percent` can carry (AGL-2655).\n *\n * A Stripe Subscription accepts no `application_fee_amount` — only\n * `application_fee_percent`, a rate with at most two decimal places — so the\n * fixed 30¢ that `saleProcessingCostCents` adds in cents has to be\n * folded into the rate. `(rate × amount + fixed) ÷ amount` is, with the\n * amount cancelled,\n *\n * percent = rate + fixed ÷ amount\n *\n * which is why a smaller recurring price carries a HIGHER percent: the 30¢\n * is a bigger share of it. At 6% + 30¢ a $10 price carries 9%, $25 carries\n * 7.2% and $100 carries 6.3%.\n *\n * Computed in hundredths of a percent on integers and rounded UP to the next\n * hundredth. Up, for the reason the one-time helper rounds its percentage\n * up: rounding to nearest would leave the recovery a fraction of a cent\n * short of what Stripe debits half the time, and Stripe rejects a third\n * decimal outright, so the only honest direction is over. Integers, because\n * `0.09 * 100` is not `9` in floating point and a percent one ulp above a\n * whole hundredth would round up to the next one. The same two constants as\n * the one-time path, so repointing `SALE_PROCESSING_PERCENT` moves\n * both surfaces together. Zero for a charge that is not a charge.\n */\nexport function saleProcessingPassThroughPercent(\n amountCents: number,\n): number {\n const amount =\n Number.isFinite(amountCents) && amountCents > 0 ? Math.round(amountCents) : 0\n if (amount <= 0) return 0\n const rateHundredths = Math.round(SALE_PROCESSING_PERCENT * 100)\n // 30¢ ÷ amount is a fraction; × 100 makes it a percent, × 100 again makes\n // it hundredths of one.\n const fixedHundredths = (SALE_PROCESSING_FIXED_CENTS * 10_000) / amount\n return Math.min(100, Math.ceil(rateHundredths + fixedHundredths) / 100)\n}\n\n/**\n * The `application_fee_percent` for ONE storefront subscription (AGL-2655):\n * the plan's advertised take PLUS Stripe's processing cost as a percent —\n * the recurring twin of `resolveTransactionFeeCents`, under the same rule.\n * Every tier carries the pass-through, a 0% tier included, because the\n * tier's 0% is a platform take and the pass-through is not one; it is what\n * Stripe charges to take the payment, which on a destination charge is\n * debited from the platform's balance whatever the tier.\n *\n * `amountCents` is the recurring items the percent will be applied to —\n * the goods after any discount, tax and shipping excluded, the AGL-2317\n * basis. Stripe applies the percent to the whole invoice and the paid-invoice\n * correction scales it back to items, so sizing on items is what makes the\n * recovery come out at cost rather than under.\n *\n * Composed in hundredths so two decimals stay two decimals (`2 + 6.34` is\n * not always `8.34` in floating point), and capped at 100, Stripe's ceiling\n * for the parameter — the recurring twin of the clamp to the charge.\n */\nexport function resolveSubscriptionFeePercent(\n org: Partial<AglynOrgBilling> | null | undefined,\n productType: 'physical' | 'digital' | 'service',\n amountCents: number,\n): number {\n const amount =\n Number.isFinite(amountCents) && amountCents > 0 ? Math.round(amountCents) : 0\n if (amount <= 0) return 0\n const take = resolveTransactionFeePct(org, productType)\n const passThrough = saleProcessingPassThroughPercent(amount)\n return Math.min(\n 100,\n (Math.round(take * 100) + Math.round(passThrough * 100)) / 100,\n )\n}\n\n/**\n * Platform take rate % for a MARKETPLACE listing sale (AGL-1543), priced\n * off the SELLER org. Resolved through `resolveOrgEntitlements` — so a\n * dead subscription prices as free-plan (30%) even while the stale `plan`\n * field still names a paid tier, and a negotiated per-org\n * `entitlements.marketplaceFeePct` override wins over the plan default.\n * Resolve per request from the org doc; never cache the answer.\n *\n * An out-of-range or missing value falls back to the FREE-plan rate — the\n * conservative direction. A malformed override must never zero the\n * platform's cut.\n */\nexport function resolveMarketplaceFeePct(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number {\n const pct = takeRatePctOf(resolveOrgEntitlements(org))\n if (pct !== null) return pct\n const fallback = takeRatePctOf(PLAN_ENTITLEMENTS.free)\n if (fallback !== null) return fallback\n // No plan carries a take rate at all: the marketplace plugin's declaration\n // did not compile in. Pricing a sale at 0% would hand the whole charge to\n // the seller, so the sale is refused instead.\n throw new Error(\n `no plan declares \"${TAKE_RATE_KEY}\" — the marketplace take rate ` +\n 'is declared by the marketplace plugin (plugins.config.json)',\n )\n}\n\n/**\n * The marketplace take rate's key. Declared by the marketplace plugin\n * (AGL-3080); read here by key because the storefront and the revenue report\n * price a marketplace sale from core.\n */\nconst TAKE_RATE_KEY = 'marketplaceFeePct'\n\n/** A row's take rate when it is a usable percentage, else `null`. */\nfunction takeRatePctOf(entitlements: object): number | null {\n const pct = (entitlements as Record<string, unknown>)[TAKE_RATE_KEY]\n return typeof pct === 'number' && Number.isFinite(pct) && pct >= 0 && pct <= 100\n ? pct\n : null\n}\n\n/**\n * The marketplace take rate that breaks even LATEST, derived from the plan\n * table rather than copied from it (AGL-2343).\n *\n * The lowest rate is the binding one: a smaller cut of the same price against\n * the same processing cost needs a dearer listing to cover it, so advice quoted\n * at this band holds for every publisher on every plan. Derived on each call\n * because a hand-written second copy of a rate is exactly the artifact that\n * decays into wrong money copy shown to a publisher — the same reason\n * `planGrantingFeature` reads the table instead of a map.\n */\nexport function bindingMarketplaceFeePct(): number {\n return Math.min(\n ...Object.values(PLAN_ENTITLEMENTS)\n .map((plan) => takeRatePctOf(plan) ?? 0)\n .filter((pct) => Number.isFinite(pct) && pct > 0),\n )\n}\n\n/**\n * What one destination-charge sale costs the PLATFORM to process (AGL-2343):\n * a storefront order, a booking deposit, a marketplace listing. On a\n * destination charge Stripe debits its fee from the PLATFORM's balance, so\n * every surface that sells through the platform's account prices its take\n * against these.\n *\n * NONE OF THESE IS AN AGLYN PRICE. They are Stripe's published US rates, and\n * they exist only as inputs to a platform take and to figures a seller reads;\n * changing one never changes a price list.\n *\n * THE BNPL RATE IS THE ONE THAT BINDS, and it is not hypothetical: the live\n * default payment method configuration has `klarna` and `affirm` enabled\n * alongside `card`, `cashapp`, `link`, `amazon_pay` and `apple_pay`, and no\n * checkout session pins `payment_method_types`, so a buyer may pay by either.\n * A cost computed from the cheapest method would be wrong exactly when it\n * mattered.\n */\nexport const CARD_PROCESSING_PERCENT = 2.9\n/** Klarna/Affirm, roughly. See the note above — this is the binding one. */\nexport const BNPL_PROCESSING_PERCENT = 6\n/** Stripe's per-transaction fixed component, in cents. */\nexport const PROCESSING_FIXED_CENTS = 30\n\n/**\n * The processing rate a storefront or booking destination charge is priced\n * against (AGL-2152), and the one identifier to repoint if those sessions'\n * payment method set changes.\n *\n * Deliberately its OWN name rather than a direct use of the binding rate,\n * while being defined as it: every seller shares Stripe's published rates and\n * today shares the platform's payment method configuration, but each surface\n * is a separate product decision. A future choice to pin\n * `payment_method_types` on storefront sessions to the card family — and so\n * to recover at `CARD_PROCESSING_PERCENT` — must not silently lower the\n * marketplace listing floor, which is derived from the binding rate for a\n * different flow.\n */\nexport const SALE_PROCESSING_PERCENT = BNPL_PROCESSING_PERCENT\n/** Stripe's per-transaction fixed component, in cents. Not rate-dependent. */\nexport const SALE_PROCESSING_FIXED_CENTS = PROCESSING_FIXED_CENTS\n\n/**\n * The cheapest plan whose BASE features include `feature`, in ladder order —\n * the answer to \"which plan do I need for this?\", which is the one thing an\n * upsell has to say and the one thing no gate can hard-code without drifting\n * from the table it is quoting.\n *\n * Returns `undefined` when NO plan carries it on the base tier. That is not\n * an error: `eventCalendar` is an add-on purchased through\n * `resolveOrgEntitlements`' override path and is false on all eight tiers, so\n * \"upgrade to X\" is the wrong sentence for it. Callers must handle undefined\n * rather than defaulting to a tier name.\n *\n * Derived from PLAN_ENTITLEMENTS on every call rather than from a\n * hand-written map: a table of \"feature → plan\" is exactly the artifact that\n * decays silently when a tier's flags are re-cut, and it would decay into\n * pricing copy shown to a customer.\n *\n * Ladder order is `SELF_SERVE_PLANS` then `enterprise` — the same order the\n * plan grid renders — so an enterprise-only feature (`ssoEnabled`) reports\n * Enterprise rather than nothing.\n */\nexport function planGrantingFeature(\n feature: keyof OrgFeatureFlags,\n): OrgPlan | undefined {\n return [...SELF_SERVE_PLANS, 'enterprise' as OrgPlan].find((plan) =>\n Boolean(PLAN_ENTITLEMENTS[plan].features[feature]),\n )\n}\n\n/**\n * The plan name an upsell should name, ready to interpolate — \"Business\",\n * or undefined when {@link planGrantingFeature} has no answer.\n */\nexport function planLabelGrantingFeature(\n feature: keyof OrgFeatureFlags,\n): string | undefined {\n const plan = planGrantingFeature(feature)\n return plan ? PLAN_LABELS[plan] : undefined\n}\n\n/**\n * True when the org's plan (or overrides) enables the boolean feature. A\n * key outside `OrgFeatureFlags` is one a plugin declared (AGL-2940) and is\n * answered the same way.\n */\nexport function checkEntitlement(\n org: Partial<AglynOrgBilling> | null | undefined,\n feature: keyof OrgFeatureFlags | (string & {}),\n): boolean {\n const features = resolveOrgEntitlements(org).features as unknown as Record<\n string,\n boolean | undefined\n >\n return Boolean(features[feature])\n}\n\n/**\n * The branding profile shape and the platform's own fallback, re-exported\n * for every caller that reads them alongside the entitlement gate.\n *\n * They are DEFINED in `platform-brand.ts`, beside the environment-driven\n * literals they are assembled from. A published tenant page renders the\n * attribution badge from `PLATFORM_BRANDING_PROFILE` and needs nothing else\n * in this module; importing it from here would put the whole plan, quota and\n * entitlement table into every anonymous visitor's first load.\n */\nexport type { ResolvedBrandingProfile } from './platform-brand'\nexport { PLATFORM_BRANDING_PROFILE } from './platform-brand'\n\n/** A non-empty trimmed string, else undefined — blanks never override a default. */\nfunction cleanBrandString(value: string | undefined): string | undefined {\n const trimmed = typeof value === 'string' ? value.trim() : ''\n return trimmed.length ? trimmed : undefined\n}\n\n/**\n * The effective brand for an org (White-Label Phase 1). EVERY branded\n * surface — published-site branding, transactional email, console chrome —\n * MUST read the brand through this one resolver so a white-label org can\n * never partly-render as Aglyn (the multi-surface drift that dogged\n * `removeBranding`, which each surface re-derived on its own).\n *\n * When the org carries the `whiteLabel` entitlement (Agency tier, or an\n * Enterprise per-org override), the org's `brandingProfile` values win and\n * any field it leaves blank falls back to the Aglyn default. Without the\n * entitlement the stored profile is ignored entirely and the full Aglyn\n * brand is returned — so an org that white-labeled and then downgraded\n * reverts cleanly.\n *\n * An org WITH the entitlement that has stored no brand value at all gets the\n * full Aglyn brand too (AGL-3440). The entitlement is the right to a brand,\n * not a brand: until the org sets one, every name it shows is the platform's\n * anyway, so the white-label profile's blanks — no logo, no support link, no\n * home — conceal nothing and only strip the platform's own header and footer\n * from mail whose every line already names the platform. Every Enterprise\n * org carries the entitlement, Aglyn's own included. One field set, and the\n * rules below apply.\n */\nexport function resolveBrandingProfile(\n org: Partial<AglynOrgBilling> | null | undefined,\n): ResolvedBrandingProfile {\n if (!checkEntitlement(org, 'whiteLabel')) return PLATFORM_BRANDING_PROFILE\n const profile = (org?.brandingProfile ?? {}) as OrgBrandingProfile\n if (\n !Object.values(profile).some(\n (value) => cleanBrandString(value as string | undefined) !== undefined,\n )\n ) {\n return PLATFORM_BRANDING_PROFILE\n }\n return {\n productName:\n cleanBrandString(profile.productName) ??\n PLATFORM_BRANDING_PROFILE.productName,\n // NO FALLBACK — and this became load-bearing the moment\n // `PLATFORM_BRANDING_PROFILE.logoUrl` stopped being null. Inheriting it\n // would stamp the PLATFORM's mark on a white-label org's published sites,\n // which is precisely the disclosure white-label is sold to prevent. A\n // white-label org that set no logo shows none.\n logoUrl: cleanBrandString(profile.logoUrl) ?? null,\n // The same no-fallback rule, and no fallback to the light logo either:\n // a reader wanting \"the logo for a dark ground\" asks for\n // `logoDarkUrl ?? logoUrl` itself, so null here keeps \"the org set a\n // dark variant\" answerable.\n logoDarkUrl: cleanBrandString(profile.logoDarkUrl) ?? null,\n faviconUrl:\n cleanBrandString(profile.faviconUrl) ?? PLATFORM_BRANDING_PROFILE.faviconUrl,\n faviconDarkUrl: cleanBrandString(profile.faviconDarkUrl) ?? null,\n primaryColor:\n cleanBrandString(profile.primaryColor) ??\n PLATFORM_BRANDING_PROFILE.primaryColor,\n // NO FALLBACK, and this is the one field that gets none (AGL-2428).\n //\n // Only a white-label org reaches this line — the guard above hands every\n // other org the platform profile whole. So a blank here belongs to an\n // organization whose transactional mail reads as *theirs* to a recipient\n // who is *their* customer, and filling the gap with Aglyn's desk sends\n // that person to a support team that cannot help them, while disclosing\n // a vendor they were never told about. `platform-brand.ts` calls the\n // same substitution in the self-host case \"a trap dressed as a default\";\n // this is that trap with a different victim.\n //\n // Blank therefore means NO LINK, exactly as `emailLogoUrl` one field\n // down already means no logo. An email with a gap where a link should be\n // reads as broken; an email with no support line reads as plain, which\n // is the right appearance for an organization that has not set one.\n supportUrl: cleanBrandString(profile.supportUrl) ?? null,\n // A white-label org has no separate \"home\" field to store, and the same\n // no-fallback rule applies for the same reason: `PLATFORM_HOME_URL` here\n // would put a link to the platform's marketing site on the badge of an\n // org that pays to conceal the platform exists. Their support URL is the\n // only destination they have told us about, which is exactly what this\n // badge already linked to before the split — so white-label badges are\n // unchanged, and only the platform's own badge stops opening a mail\n // client.\n homeUrl: cleanBrandString(profile.supportUrl) ?? null,\n fromName:\n cleanBrandString(profile.fromName) ?? PLATFORM_BRANDING_PROFILE.fromName,\n emailLogoUrl:\n cleanBrandString(profile.emailLogoUrl) ??\n PLATFORM_BRANDING_PROFILE.emailLogoUrl,\n customConsoleDomain:\n cleanBrandString(profile.customConsoleDomain) ??\n PLATFORM_BRANDING_PROFILE.customConsoleDomain,\n }\n}\n\n/**\n * The brand as MERGE TOKENS, for a staff-designed system-email template\n * (AGL-2139).\n *\n * White-label inverted precisely when staff published a template, which is\n * that feature's normal steady state. Every org-context sender has the shape\n * `subject: designed?.subject ?? <branded fallback>` — the DESIGNED template\n * wins — and the catalog copy hard-coded \"Aglyn\" throughout. There was no\n * brand token to design against either: the merge maps carried `org.name`,\n * `invite.role`, `signInUrl` and friends and nothing about the brand, and\n * `blankUnresolvedTokens` BLANKS any token the caller did not supply. So a\n * designer who reached for `{{brand.productName}}` would have shipped an\n * email with a hole in the sentence.\n *\n * Token bodies are namespaced `brand.*` so a designer can tell them from the\n * per-send values, and they resolve for EVERY org — Aglyn's own included,\n * since `PLATFORM_BRANDING_PROFILE.productName` is `'Aglyn'`. That is what makes\n * a single template correct for both populations instead of one hard-coded\n * for the majority.\n *\n * The email LOGO is deliberately absent. It is structural — `renderEmailHtml`\n * emits it as a header row above the designed body, or emits nothing — so it\n * travels as an option rather than as a token a template could forget to\n * place, and there is no sample URL a staff test-send could render without\n * showing a broken image.\n */\nexport function brandMergeTokens(\n branding: ResolvedBrandingProfile,\n): Record<string, string> {\n return {\n 'brand.productName': branding.productName,\n 'brand.fromName': branding.fromName,\n // EMPTY, never omitted, when the org has no Support URL (AGL-2428).\n //\n // `renderLoadedSystemEmail` merges these OVER `DEFAULT_BRAND_TOKENS`,\n // which carries the platform's own support URL for the genuinely\n // org-less senders. Dropping the key here would let that default show\n // through and mail a white-label org's customer a link to Aglyn — the\n // exact leak this closes — so the key must be present and empty in order\n // to overwrite it. A designed template that interpolates the token\n // renders nothing where it sits.\n 'brand.supportUrl': branding.supportUrl ?? '',\n }\n}\n\n/**\n * The value of the `<meta name=\"generator\">` tag and of the `x-powered-by`\n * header on published sites (AGL-2088) — the canonical CMS signal WordPress,\n * Squarespace, Drupal and Ghost all ship, and the thing tech-stack detectors\n * key on.\n *\n * Deliberately VERSIONLESS. The accidental headers this replaced carried\n * `1.0.0-alpha.0` and a Node version, which dated every deployment and named\n * our runtime to no consumer. A detector needs the product name and nothing\n * else.\n */\nexport const PLATFORM_GENERATOR_NAME = PLATFORM_BRAND_NAME\n\n/**\n * May this org's published pages say they were built with Aglyn? (AGL-2088)\n *\n * The gate for the `<meta name=\"generator\">` tag and the `x-powered-by`\n * header. It reads the SAME entitlement `resolveBrandingProfile` reads —\n * `whiteLabel` — rather than inventing a parallel one, so an agency's\n * concealment promise has exactly one definition.\n *\n * WHY `whiteLabel` AND NOT `showBranding`. The \"Made with Aglyn\" badge is\n * gated on `removeBranding`, which every plan above free grants, so reusing\n * that gate would confine the signal to free-tier sites — the corpus this\n * exists to build would be almost empty on day one. The two promises are also\n * different in kind: `removeBranding` buys a page with no Aglyn credit on it,\n * an aesthetic commitment about what a VISITOR sees; `whiteLabel` buys\n * concealment of who built the site, which is the promise a machine-readable\n * fingerprint actually breaks. A paying customer on a mid plan has bought a\n * clean page, not anonymity.\n *\n * ⚠️ AN UNRESOLVED ORG SUPPRESSES, and this is the whole reason the function\n * exists instead of a bare `!checkEntitlement(org, 'whiteLabel')` at each call\n * site. `resolveOrgEntitlements(null)` resolves to the FREE plan — a loading\n * default answering a question it was never asked — so a null org reads as\n * \"no white-label\" and would EMIT. That null is not hypothetical: the tenant's\n * `getOrgBilling` fails open with `org: null` on a Firestore error, so a\n * transient read failure on an Agency site would have stamped the generator\n * tag onto it. The asymmetry decides the direction: suppressing wrongly costs\n * us one detection sample, emitting wrongly breaks a paid promise on a\n * customer's own domain. So the org must be PRESENT and lack the entitlement;\n * absent is not the same as free here.\n */\nexport function showsPlatformAttribution(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n // `{}` is treated as unresolved alongside `null`, and that is not\n // pedantry: no org doc that was actually READ is empty — the converter\n // yields `$id` at minimum — so an empty object only ever arrives from a\n // placeholder or a loading default, the same shape `useBranding` hands back\n // before its read lands. It resolves to the free plan like every other\n // absent org, which is the right default for a quota and the wrong one for\n // a disclosure.\n if (!org || Object.keys(org).length === 0) return false\n return !checkEntitlement(org, 'whiteLabel')\n}\n\n/**\n * Whether the org has actually **configured** a white-label brand (AGL-1110) —\n * a non-empty product name or logo. White-label chrome should activate only\n * when this is true, not merely because the plan grants the `whiteLabel`\n * entitlement: an Agency org (or a comped Enterprise org) that never set up a\n * brand keeps the plain Aglyn wordmark — no product-name-text render, no\n * flash — until it opts in on the branding page.\n */\nexport function hasBrandingProfile(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n const profile = org?.brandingProfile\n if (!profile) return false\n return !!(\n cleanBrandString(profile.productName) || cleanBrandString(profile.logoUrl)\n )\n}\n\n/**\n * Quota check: `allowed` is false once `currentUsage` meets the limit —\n * call before creating the next resource (e.g. usage=hostCount before\n * creating another host). `remaining` never goes negative.\n */\n/**\n * `members` = per-site collaborator seats (legacy key, AGL-888): the key\n * matches persisted `seatAddons.members` and must not be renamed.\n */\nexport type SeatKind = 'managers' | 'members'\n\nexport interface SeatQuotaResult {\n /** False once usage meets the effective limit (included + purchased). */\n allowed: boolean\n /** Effective seat limit: included + purchased addons, clamped to the max. */\n limit: number\n remaining: number\n /** Included seats on the plan before addons. */\n included: number\n /** Purchased addon seats currently applied. */\n purchased: number\n /** Hard cap incl. addons; reaching it requires a plan upgrade. */\n maxSeats: number\n /**\n * True when buying more addon seats cannot raise the limit — either the\n * plan sells no addons or the hard cap is reached. UI should prompt an\n * upgrade instead of an addon purchase.\n */\n upgradeRequired: boolean\n /** Monthly price per addon seat; null when the plan sells none. */\n addonPriceUsd: number | null\n}\n\n/**\n * Seat quota check (AGL-112): seats differ from plain quotas because orgs\n * can buy addon seats (`org.seatAddons`) up to a per-plan hard max —\n * beyond the max the only path is upgrading the plan. `managers` counts\n * org-manager seats org-wide; `members` counts per-site COLLABORATORS\n * (`hosts/{id}/members`, viewer/editor/admin — legacy key name, AGL-888).\n * End-user member accounts (`siteMembers`) are unlimited and never pass\n * through here.\n *\n * SINCE AGL-2439 THIS IS THE PLAN'S CAP FOR `members`, with no purchased\n * seats in it — see the comment at the add below. A per-site answer must come\n * from `checkHostCollaboratorQuota(org, hostId, used)`, which is the only\n * thing that knows which site holds which pool seat. Every caller that has a\n * `hostId` must use that one; this stays correct for a plan-level readout\n * (what the plan includes per site, before anything is assigned).\n */\nexport function checkSeatQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n kind: SeatKind,\n currentUsage: number,\n): SeatQuotaResult {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const included =\n kind === 'managers'\n ? entitlements.managersPerOrg\n : entitlements.membersPerHost\n const maxSeats =\n kind === 'managers'\n ? entitlements.maxManagersPerOrg\n : entitlements.maxMembersPerHost\n const addonPriceUsd =\n kind === 'managers'\n ? pricing.extraSeatMonthlyUsd\n : pricing.extraCollaboratorMonthlyUsd\n // `members` IS NOT ADDED HERE, and this is the fix, not an omission\n // (AGL-2439) — the AGL-1775 shape, applied to\n // the key that never got it. `seatAddons.members` is an ORG-LEVEL purchased\n // quantity and `membersPerHost` is enforced PER SITE, so folding one into\n // the other handed every site the whole purchase: an org with 20 sites that\n // bought ONE extra collaborator seat received 20 seats for one seat's price.\n // The quantity is now a POOL and `org.collaboratorAllocations` says which\n // site holds each seat. What this function returns for `members` is\n // therefore **the plan's cap alone** — exactly what an unallocated site must\n // resolve to. Per-site capacity comes from `checkHostCollaboratorQuota`;\n // nothing else may add the pool back in.\n //\n // `managers` is unaffected and still adds: `managersPerOrg` really is\n // org-level, so an org-level purchase raising an org-level cap is correct\n // there. That difference is the whole reason this is a `kind` switch rather\n // than one rule.\n const purchased =\n kind === 'managers'\n ? Math.max(0, resolvePurchasedAddons(org)[kind] ?? 0)\n : 0\n const limit = Math.min(included + purchased, maxSeats)\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n included,\n purchased,\n maxSeats,\n upgradeRequired: addonPriceUsd === null || limit >= maxSeats,\n addonPriceUsd,\n }\n}\n\nexport function checkQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n quota: keyof Omit<ResolvedOrgEntitlements, 'features'>,\n currentUsage: number,\n): { allowed: boolean; limit: number; remaining: number } {\n const limit = resolveOrgEntitlements(org)[quota]\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n }\n}\n\nexport interface DatasetQuotaResult {\n /** False once usage meets the effective limit (included + purchased). */\n allowed: boolean\n /** Effective dataset limit: included + purchased, clamped to the max. */\n limit: number\n remaining: number\n included: number\n purchased: number\n maxDatasets: number\n /** True when addons cannot raise the limit — upgrade instead. */\n upgradeRequired: boolean\n /** Monthly price per extra dataset; null when the plan sells none. */\n addonPriceUsd: number | null\n}\n\n/**\n * Dataset quota check (AGL-132/240), mirroring `checkSeatQuota`: orgs can\n * buy addon datasets (`org.seatAddons.datasets`, org-wide) up to the\n * plan's hard max; beyond the max the only path is upgrading.\n */\nexport function checkDatasetQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n currentUsage: number,\n): DatasetQuotaResult {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const included = entitlements.datasetsPerOrg\n const maxDatasets = entitlements.maxDatasetsPerOrg\n const addonPriceUsd = pricing.extraDatasetMonthlyUsd\n const purchased = Math.max(0, resolvePurchasedAddons(org).datasets ?? 0)\n const limit = Math.min(included + purchased, maxDatasets)\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n included,\n purchased,\n maxDatasets,\n upgradeRequired: addonPriceUsd === null || limit >= maxDatasets,\n addonPriceUsd,\n }\n}\n\nexport interface PluginOrgCapacityQuotaResult {\n /** False once usage meets the effective limit. */\n allowed: boolean\n /** Included + purchased, clamped to the plan's ceiling for this capacity. */\n limit: number\n remaining: number\n /** What the plan includes — the figure a downgrade warning quotes. */\n included: number\n /** What the org bought on top, 0 on a dead subscription. */\n purchased: number\n /** How far buying can raise it on this plan. */\n ceiling: number\n}\n\n/**\n * The quota for a capacity a PLUGIN backs (AGL-3080), from its declaration.\n *\n * `checkDatasetQuota` is this arithmetic written out for the one capacity\n * core happened to know the name of. It has a dozen callers on money paths\n * and keeps its own shape; what moves here is the ability to ask the same\n * question about a capacity core does not name, which is what a console meter\n * needs — it draws a row per declared capacity and cannot switch on `datasets`.\n *\n * The two must agree, and `plan-entitlements-plugin-capacity.spec.ts` is what\n * holds that: for every plan, this answers exactly what `checkDatasetQuota`\n * answers for the declared dataset capacity. A second implementation of a\n * limit is worth having only while something refuses to let the two drift.\n *\n * ⚑ An undeclared kind, or a declaration naming a field no plan carries, is\n * ZERO — included, ceiling and limit all — which reads as \"you are over\" and\n * shows a meter full. That is the same direction `overLimitRows` and\n * `includedCapacity` already fail in, and for the same reason: the reassuring\n * answer here would be a meter promising room the create is refused at.\n */\nexport function checkPluginOrgCapacityQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n kind: string,\n currentUsage: number,\n): PluginOrgCapacityQuotaResult {\n const capacity = pluginOrgCapacity(kind)\n const entitlements = resolveOrgEntitlements(org) as unknown as Record<\n string,\n unknown\n >\n const included = capacity\n ? Number(entitlements[capacity.includedEntitlement] ?? 0)\n : 0\n const ceiling = capacity\n ? Number(entitlements[capacity.purchaseCeilingEntitlement] ?? 0)\n : 0\n // The add-on kind IS the `seatAddons` key it is stored under — the\n // generator refuses two capacities on one add-on kind, so one purchased\n // quantity can only ever raise the capacity that sells it.\n const purchased = capacity\n ? Math.max(\n 0,\n Number(\n (resolvePurchasedAddons(org) as unknown as Record<string, unknown>)[\n capacity.addonKind\n ] ?? 0,\n ),\n )\n : 0\n const limit = Math.min(included + purchased, ceiling)\n return {\n allowed: currentUsage < limit,\n limit,\n remaining: Math.max(0, limit - currentUsage),\n included,\n purchased,\n ceiling,\n }\n}\n\nexport interface DataStorageQuotaResult {\n /**\n * False only when the plan hard-blocks (no overage pricing) and usage\n * meets the included size; metered plans always allow and bill overage.\n */\n allowed: boolean\n /** Included dataset storage on the plan, MB. */\n includedMb: number\n usedMb: number\n /** Remaining included storage, MB; 0 once into overage. */\n remainingMb: number\n /** Usage beyond the included size, GB (0 when within the plan). */\n overageGb: number\n /** Estimated overage this month at the plan's per-GB rate. */\n overageMonthlyUsd: number\n /** Per-GB-month overage rate; null when the plan meters nothing. */\n overageRateUsd: number | null\n}\n\n/**\n * Org dataset-storage meter (AGL-240): aggregate stored document bytes\n * across `orgs/{orgId}/datasets`. Plans with an `extraDataGbMonthlyUsd`\n * rate meter the overage onto the monthly invoice (cost-plus, AGL-41);\n * plans without one (free) hard-block at the included size.\n */\nexport function checkDataStorageQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedMb: number,\n): DataStorageQuotaResult {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const includedMb = entitlements.dataStorageMbPerOrg\n const overageRateUsd = pricing.extraDataGbMonthlyUsd\n const used = Math.max(0, usedMb)\n const overageMb = Math.max(0, used - includedMb)\n const overageGb = overageMb / 1024\n return {\n allowed: overageRateUsd !== null ? true : used < includedMb,\n includedMb,\n usedMb: used,\n remainingMb: Math.max(0, includedMb - used),\n overageGb,\n overageMonthlyUsd:\n overageRateUsd === null\n ? 0\n : Math.round(overageGb * overageRateUsd * 100) / 100,\n overageRateUsd,\n }\n}\n\nexport interface ApiRequestQuotaResult {\n /** Metered plans always allow; plans without API access block. */\n allowed: boolean\n /** Included requests this month at the plan. */\n included: number\n used: number\n /** Remaining included requests; 0 once into overage. */\n remaining: number\n /** Requests beyond the included quota (0 within the plan). */\n overageRequests: number\n /** Estimated overage this month at the plan's per-1,000 rate. */\n overageMonthlyUsd: number\n /** Per-1,000 overage rate; null when the plan has no API. */\n overageRateUsd: number | null\n}\n\n/**\n * Customer REST API request meter (AGL-634): the monthly request count for an\n * org. Business and every tier above it carry `apiAccess` and an\n * `extraApiRequestsUsdPer1k` rate, so requests past the included quota meter\n * onto the monthly invoice\n * (cost-plus, like storage overage — never a hard wall mid-integration); plans\n * without API access have `included: 0` and always block.\n */\nexport function checkApiRequestQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedRequests: number,\n): ApiRequestQuotaResult {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const included = entitlements.apiRequestsPerMonth\n const overageRateUsd = pricing.extraApiRequestsUsdPer1k\n const used = Math.max(0, usedRequests)\n const overageRequests = Math.max(0, used - included)\n return {\n allowed: overageRateUsd !== null ? true : used < included,\n included,\n used,\n remaining: Math.max(0, included - used),\n overageRequests,\n overageMonthlyUsd:\n overageRateUsd === null\n ? 0\n : Math.round((overageRequests / 1000) * overageRateUsd * 100) / 100,\n overageRateUsd,\n }\n}\n\nexport interface CrmRecordsQuotaResult {\n /** Metered plans always allow; free hard-bands at the included count. */\n allowed: boolean\n /** Included CRM records on the plan — the `contactsPerHost` band. */\n included: number\n /** Contacts + companies + deals, as the caller counted them. */\n used: number\n /** Remaining included records; 0 once into overage. */\n remaining: number\n /** Records beyond the included band (0 within the plan). */\n overageRecords: number\n /** Estimated overage this month at the plan's per-1,000 rate. */\n overageMonthlyUsd: number\n /** Per-1,000 overage rate; null when the plan hard-bands (free). */\n overageRateUsd: number | null\n}\n\n/**\n * The name this result carried while the band counted contacts alone.\n * @deprecated Read `CrmRecordsQuotaResult`; the shape is the same.\n */\nexport type ContactQuotaResult = CrmRecordsQuotaResult\n\n/**\n * CRM records band meter (AGL-890, widened in AGL-2611): contacts, companies\n * and deals, counted together across the org and measured against the ONE\n * band `contactsPerHost` names. Paid plans carry an `extraContactsUsdPer1k`\n * rate, so records past the included band meter onto the monthly invoice\n * (cost-plus, like storage and API overage — a growing audience is never\n * dropped); free has no rate and hard-bands at the included count, refusing\n * the next record of any of the three kinds.\n *\n * ONE band for three collections, and not three bands, because a company or\n * a deal costs less to hold than a contact — a ~2 KB document with few index\n * entries and no dynamic-list scan — so counting them at the contacts rate\n * over-covers them, and a second and third number to explain would buy\n * nothing a customer could act on. The caller passes the SUM; the server\n * helper that takes the three aggregate reads is `countCrmRecords`, and the\n * billing meter shows the three components in its caption so the sum is\n * legible rather than opaque. Tasks and activities are not counted: they\n * are bounded by human effort, and the activity log is capped per record by\n * `CRM_ACTIVITIES_PER_RECORD_CEILING` instead.\n *\n * End-user member ACCOUNTS are unlimited on every plan (AGL-889) — this\n * meters only the CRM record.\n */\nexport function checkCrmRecordsQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedRecords: number,\n): CrmRecordsQuotaResult {\n const entitlements = resolveOrgEntitlements(org)\n const pricing = resolvePlanPricing(org)\n const included = entitlements.contactsPerHost\n const overageRateUsd = pricing.extraContactsUsdPer1k\n const used = Math.max(0, usedRecords)\n const overageRecords = Math.max(0, used - included)\n return {\n allowed: overageRateUsd !== null ? true : used < included,\n included,\n used,\n remaining: Math.max(0, included - used),\n overageRecords,\n overageMonthlyUsd:\n overageRateUsd === null\n ? 0\n : Math.round((overageRecords / 1000) * overageRateUsd * 100) / 100,\n overageRateUsd,\n }\n}\n\n/**\n * The name the band's gate carried while it counted contacts alone. The SAME\n * function — every caller that still says \"contact\" is measuring the records\n * band and must hand it the summed count, not the contacts aggregate.\n * @deprecated Call `checkCrmRecordsQuota`.\n */\nexport const checkContactQuota = checkCrmRecordsQuota\n\n/**\n * `orgs/{orgId}/crmEmailUsage/{YYYY-MM-DD}` — the durable per-day counter of\n * one-to-one CRM emails, `{ count, day, updatedAt }`, server-written only and\n * member-readable, exactly as `apiUsage/{month}` is for API requests.\n */\nexport const CRM_EMAIL_USAGE_COLLECTION = 'crmEmailUsage'\n\n/**\n * The document id of today's one-to-one email counter, `YYYY-MM-DD` in UTC.\n *\n * UTC rather than the workspace's zone, for the reason `apiUsageMonth` keys\n * the API counter in UTC: the cap is enforced on a server that has no zone\n * of the customer's to read, and a boundary that moved with a browser's\n * clock would let one day's sends be counted against two.\n */\nexport function crmEmailUsageDayKey(now: Date = new Date()): string {\n return now.toISOString().slice(0, 10)\n}\n\nexport interface CrmEmailQuotaResult {\n /** Whether ONE MORE send is inside today's cap. Enterprise always allows. */\n allowed: boolean\n /** The plan's daily cap; 0 where the tier has no CRM. */\n included: number\n /** Sends already counted today. */\n used: number\n /** Sends left today; 0 at the cap. */\n remaining: number\n /** When the counter rolls over — the next UTC midnight. */\n resetsAt: Date\n}\n\n/**\n * One-to-one CRM email cap (AGL-2611): how many messages a workspace may\n * still send from CRM records today, mirroring `checkApiRequestQuota`.\n *\n * A HARD cap with no overage rate, on every tier — the one respect in which\n * it differs from the API meter it is shaped after. `allowed` is therefore\n * `used < included` outright. Every paid tier's cap is finite, Enterprise's\n * 2,000 included, and 0 on Free makes it false at zero, so a per-org\n * `features.crm` grant does not send until `crmEmailsPerDay` is raised\n * beside it.\n *\n * ## The contract for the send route\n *\n * The route that sends a one-to-one email takes the send as a SLOT on\n * `orgs/{orgId}/crmEmailUsage/{crmEmailUsageDayKey()}` inside one Firestore\n * transaction — `reserveCrmEmailSend` in `@aglyn/tenant-data-admin` reads\n * `count`, calls this with that figure, REFUSES with the plan reason when\n * `allowed` is false and otherwise writes `used + 1` in the same transaction\n * (AGL-2645). A read-then-write cap let two reps at the last slot both pass\n * one reading of it; the transaction re-runs the second on the raised\n * figure. The slot is taken before the message leaves and handed back by\n * `releaseCrmEmailSend` when the provider refuses it. A delivered send is\n * then recorded on the org's `emailSends` cost meter through\n * `recordEmailSends` as `transactional`, because every message the provider\n * charged for is priced into COGS and the campaign cap does not govern it.\n * The count enforced is the count billed, from one document, the way the\n * API meter works.\n *\n * Read the counter with `used` from the server: the console meter on the\n * billing page reads the same document, member-readable, so a rep is never\n * told their limit by being refused.\n */\nexport function checkCrmEmailQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedToday: number,\n now: Date = new Date(),\n): CrmEmailQuotaResult {\n const included = planQuotaOf(resolveOrgEntitlements(org), 'crmEmailsPerDay')\n const used = Math.max(0, Math.floor(Number(usedToday) || 0))\n const resetsAt = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1),\n )\n return {\n allowed: used < included,\n included,\n used,\n remaining: Math.max(0, included - used),\n resetsAt,\n }\n}\n\n/**\n * Volume above the plan's included band, in emails.\n *\n * Transactional mail is never refused, so an org CAN and will finish a month\n * over its included allowance. That is not an error and not a failed send —\n * it is the overage the cap chose not to enforce, and recording it is what\n * keeps it from being a surprise at invoicing. `UNLIMITED` (and any\n * non-positive limit, which is how \"no included allowance\" is expressed)\n * yields 0 rather than the whole month's volume.\n *\n * DEFINED HERE and re-exported from `email-metering.ts` (AGL-2155's move):\n * the billing page renders this figure before anything is invoiced, and it\n * is a client component that cannot pull in the Admin SDK. One subtraction,\n * so the readout and the invoice cannot disagree.\n */\nexport function emailSendsOverage(total: number, limit: number): number {\n const sends = Number(total)\n const included = Number(limit)\n if (!Number.isFinite(sends) || sends <= 0) return 0\n if (!Number.isFinite(included) || included <= 0) return 0\n return Math.max(0, sends - included)\n}\n\nexport interface EmailSendOveragePrice {\n /** Emails past the plan's included band, as handed in. */\n overageSends: number\n /** Estimated charge this month at the plan's per-1,000 rate. */\n overageMonthlyUsd: number\n /** Per-1,000 rate; null when the plan carries no email overage price. */\n overageRateUsd: number | null\n}\n\n/**\n * Prices email volume past a plan's included band.\n *\n * ## No `allowed`, deliberately\n *\n * Every other meter's helper here answers \"may this proceed\" alongside \"what\n * does it cost\", because on those axes one gate decides both. Email has two\n * gates that are not the same gate: `reserveCampaignEmailSends` refuses a\n * CAMPAIGN at the band inside a transaction, and nothing at all refuses\n * transactional mail at any tier. A field named `allowed` on this result\n * would be a plausible-looking thing for a sender to consult, and a\n * transactional sender consulting it is how a password reset stops going out.\n * So the result carries money and nothing else.\n *\n * ## Takes the overage, does not recompute it\n *\n * `emailSendsOverage` already derives the excess from the cost meter and the\n * resolved band, on the path that records it. Re-deriving it here from a raw\n * total would be a second overage model to drift from the first — which is\n * the shape AGL-1402 documented, where two figures that should have been one\n * disagreed by 20-45% for years.\n *\n * A null rate yields zero, structurally rather than by a check, which is what\n * keeps free at zero on this axis for the same reason it is zero on the other\n * three.\n */\nexport function priceEmailSendOverage(\n org: Partial<AglynOrgBilling> | null | undefined,\n overageSends: number,\n): EmailSendOveragePrice {\n const overageRateUsd = resolvePlanPricing(org).extraEmailSendsUsdPer1k\n const sends = Number(overageSends)\n const over = Number.isFinite(sends) && sends > 0 ? sends : 0\n return {\n overageSends: over,\n overageMonthlyUsd:\n overageRateUsd === null\n ? 0\n : Math.round((over / 1000) * overageRateUsd * 100) / 100,\n overageRateUsd,\n }\n}\n\n/**\n * Is this org's plan subject to the metered infrastructure pass-through\n * (AGL-1280)? The single answer behind three otherwise-unrelated behaviours:\n * whether storage/bandwidth/form usage past the included band is BILLED, and\n * whether the form-submission cap is a wall or a meter.\n *\n * Deliberately a plan-level question, not an org-level one — an org with no\n * plan resolves as free, so an unknown org meters nothing. Billing usage to\n * someone with no subscription is the one error direction with no recovery.\n * A staff comp is that someone too (AGL-3034): it grants a paid plan with no\n * subscription behind it, so `resolvePlanPricing` answers `false` for it and\n * its bands are walls.\n */\nexport function planMetersInfraOverage(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n return resolvePlanPricing(org).meteredInfraPassThrough\n}\n\nexport interface FormSubmissionQuotaResult {\n /**\n * False only when the plan hard-caps (no metered pass-through) and usage\n * has reached the included count; metered plans always accept and bill\n * the excess.\n */\n allowed: boolean\n /** Included submissions this month for this site. */\n included: number\n used: number\n /** Remaining included submissions; 0 once into overage. */\n remaining: number\n /** Submissions beyond the included count (0 within the plan). */\n overageSubmissions: number\n /** True when overage meters instead of blocking. */\n metered: boolean\n}\n\n/**\n * Monthly form-submission meter (AGL-76, made coherent in AGL-1280).\n *\n * Submissions were BOTH hard-capped at `formSubmissionsPerMonth` (429) and\n * metered — which made the published promise that usage past the\n * included band is billed rather than cut off false for the one meter where\n * being cut off costs the customer a lead. Now it mirrors the other three\n * overage meters exactly: a plan with the metered pass-through always accepts\n * and prices the excess (`estimateMonthlyUsageCost`); free has no\n * subscription to bill, so its included count stays a hard wall.\n *\n * Priced per ORG at cost-plus, but capped per SITE, because that is where the\n * counter lives (`hosts/{id}/counters/formSubmissions`). Callers gating a\n * single submission pass that site's month count.\n */\nexport function checkFormSubmissionQuota(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedThisMonth: number,\n): FormSubmissionQuotaResult {\n const included = planQuotaOf(\n resolveOrgEntitlements(org),\n 'formSubmissionsPerMonth',\n )\n const metered = planMetersInfraOverage(org)\n const used = Math.max(0, usedThisMonth)\n return {\n allowed: metered ? true : used < included,\n included,\n used,\n remaining: Math.max(0, included - used),\n overageSubmissions: Math.max(0, used - included),\n metered,\n }\n}\n\n/**\n * How far past a plan's own included band the abuse ceiling sits (AGL-1655).\n * Ten times what the customer bought is not growth — a site that legitimately\n * outgrows its band by an order of magnitude has an upgrade conversation\n * waiting, not a silent invoice.\n */\nexport const FORM_ABUSE_CEILING_MULTIPLE = 10\n\n/**\n * Absolute floor for the abuse ceiling, so the small plans get real headroom\n * rather than a second, tighter plan limit wearing a different name. Starter\n * includes 200/month; 10× would be 2,000, which a genuinely busy site could\n * reach. 5,000 is ~167 submissions a day on ONE site — no legitimate site on\n * a small plan reaches that, and no small customer can be billed past\n * 5,000 × $0.000065 ≈ $0.33 of abuse.\n */\nexport const FORM_ABUSE_CEILING_FLOOR = 5_000\n\n/**\n * Ceiling for plans whose included count is UNLIMITED (Agency, Enterprise),\n * where a multiple has nothing to multiply. Kept at or above the ceiling of\n * every finite plan below it (Advanced includes 100,000 → 1,000,000) so the\n * ladder never inverts and a bigger plan never gets the smaller ceiling.\n */\nexport const FORM_ABUSE_CEILING_UNLIMITED = 1_000_000\n\nexport interface FormAbuseCeilingResult {\n /** True once this month's count for the SITE has reached the ceiling. */\n exceeded: boolean\n /** The ceiling this site's plan resolves to. Always finite. */\n ceiling: number\n used: number\n}\n\n/**\n * Per-site monthly abuse ceiling for form submissions (AGL-1655).\n *\n * Deliberately NOT part of `checkFormSubmissionQuota`. That function answers\n * a plan question — \"did the customer buy this?\" — and AGL-1280 correctly\n * made its answer on a metered plan always yes, because usage past the band\n * bills rather than blocks. This one answers a different question: \"is this\n * still a customer's traffic at all?\" Conflating them is how the plan gate\n * ended up as the anti-abuse control it was never designed to be, and how\n * `allowed: metered ? true : …` came to mean an unbounded bill.\n *\n * So the ceiling is containment, not capacity. It is far above anything a\n * legitimate site reaches, it is the same shape on every plan, and crossing\n * it is an INCIDENT: the caller is expected to refuse the write, leave the\n * billable counter alone, and make the trip visible to the site's managers\n * and to staff. A ceiling that silently absorbed submissions would only move\n * the free plan's lost-lead failure up to a bigger number.\n *\n * Evaluated against the same month counter the plan gate reads — which is\n * Admin-SDK-only (`counters` is excluded from every client write in\n * `cloud/firebase-firestore.rules`), so the count behind the ceiling cannot\n * be lowered to launder past it.\n */\nexport function checkFormSubmissionAbuseCeiling(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedThisMonth: number,\n): FormAbuseCeilingResult {\n const included = planQuotaOf(\n resolveOrgEntitlements(org),\n 'formSubmissionsPerMonth',\n )\n const ceiling = Number.isFinite(included)\n ? Math.max(FORM_ABUSE_CEILING_FLOOR, included * FORM_ABUSE_CEILING_MULTIPLE)\n : FORM_ABUSE_CEILING_UNLIMITED\n const used = Math.max(0, usedThisMonth)\n return { exceeded: used >= ceiling, ceiling, used }\n}\n\n/**\n * Average transfer per page view (HTML + JS + a few images), the constant\n * that turns the analytics view counter into a bandwidth number and back.\n *\n * ## ⛔ HALF OF A PAIR. It moves with `perPageView`, or neither moves.\n *\n * This is the BYTES one page view weighs;\n * {@link ORG_COGS_UNIT_RATES_USD}`.perPageView` is the DOLLARS the same page\n * view costs. They are one physical measurement written in two units, and\n * what every `bandwidthGb` band is actually sized against is neither of them\n * alone but their quotient — `(1 GB ÷ this) × perPageView`, the cost of a\n * gigabyte's WEIGHT, to which the band sizing adds the CDN requests the same\n * views make ({@link PAGE_VIEW_CDN_REQUEST_COST_USD}). That quotient is\n * stable even while the page's weight is not,\n * which is precisely why moving one half re-prices every band on the ladder\n * without touching a band: a heavier page costs more per view and buys fewer\n * views per gigabyte, and the two cancel.\n *\n * Both are therefore stated against the SAME weight — **1012.8 KB**, the\n * basis recorded in `tools/tenant-page-budget.json` and recoverable from the\n * rate itself — and `npm run check:page-view-rate` compares the two and\n * refuses a mismatch. There is no version of this constant that is correct\n * against a `perPageView` calibrated for a different page.\n *\n * The basis is FIRST-PARTY encoded bytes at settle, because that is what an\n * egress bill is denominated in: third-party tags leave someone else's\n * network and cost the platform nothing, so counting them here would inflate\n * a customer's measured bandwidth with bytes nobody pays us for.\n *\n * Lives HERE rather than beside the metering rates (AGL-2155). It used to sit\n * in `apps/console/utils/usage-metering.ts`, which the console imports and the\n * tenant app cannot — and the bandwidth ceiling below has to be evaluated in\n * the tenant app, at the beacon that writes the counter. Re-deriving the\n * conversion there would have been a fourth hand-rolled copy of\n * `× 1024³ / page weight`, which is the exact drift AGL-1371 collapsed into\n * one function. `usage-metering` now re-exports these three, so every existing\n * importer is unchanged and there is still one definition.\n */\nexport const ESTIMATED_PAGE_TRANSFER_BYTES = 1012.8 * 1024\n\n/**\n * Bandwidth ⇄ page views: the one conversion three surfaces used to each\n * write out by hand (AGL-1371).\n *\n * `bandwidthGb` is not a second cap next to metered page views — it IS the\n * included band of the page-view meter, expressed in the unit customers\n * understand. It is therefore used in both directions: forward by\n * `meteredIncludedAllowance` to size the band the invoice subtracts, backward\n * by the usage-alerts cron and the console meter to render live page views as\n * GB.\n */\nexport function pageViewsFromBandwidthGb(bandwidthGb: number): number {\n return (bandwidthGb * 1024 * 1024 * 1024) / ESTIMATED_PAGE_TRANSFER_BYTES\n}\n\n/** @see pageViewsFromBandwidthGb — the same constant, the other way. */\nexport function bandwidthGbFromPageViews(pageViews: number): number {\n return (\n (Math.max(0, Number(pageViews) || 0) * ESTIMATED_PAGE_TRANSFER_BYTES) /\n (1024 * 1024 * 1024)\n )\n}\n\n/** The decimal GB Vercel bills transfer in (AGL-3444). */\nconst DECIMAL_GB_BYTES = 1_000_000_000\n\n/** The binary GB `bandwidthGb` and GCS egress are counted in. */\nconst BINARY_GB_BYTES = 1024 * 1024 * 1024\n\n/**\n * How long one decimal GB keeps a streaming function open: a viewer draws a\n * 720p film at about 5 Mbit/s, and the media route reads Storage only as fast\n * as the viewer takes the bytes (AGL-2810), so the function lives for the\n * film, not for the transfer — 1,600 seconds a GB.\n */\nconst ORIGIN_MEDIA_STREAM_SECONDS_PER_GB = (DECIMAL_GB_BYTES * 8) / 5_000_000\n\n/**\n * The range requests one decimal GB arrives in: a player asks for a film a\n * slice at a time, and a slice of about a megabyte is the small end of what\n * players ask for, so per-request costs are counted a thousand times a GB.\n */\nconst ORIGIN_MEDIA_REQUESTS_PER_GB = 1_000\n\n/**\n * What ONE DECIMAL GB of video, audio or a file costs to SERVE FROM ORIGIN, by\n * term, at the dearest region every Vercel term is billed in (AGL-3474).\n *\n * The media CDN serves every type the edge must never hold (AGL-1515) on\n * every request, from origin, so a GB of it pays both of Vercel's transfer\n * meters and Storage's egress — where a page's GB is mostly answered by the\n * edge. This is the basis {@link ORIGIN_MEDIA_BANDWIDTH_WEIGHT} is derived\n * from, and the figure the published \"at cost + 30%\" has to be true of.\n *\n * | term | basis | per decimal GB |\n * |---|---|---|\n * | `fastDataTransfer` | Vercel Fast Data Transfer, dearest region, decimal GB | $0.35 |\n * | `fastOriginTransfer` | Vercel Fast Origin Transfer, dearest region, decimal GB | $0.43 |\n * | `storageEgress` | GCS internet egress at list, $0.12 per GiB (0–1 TiB) | $0.111759 |\n * | `streamingMemory` | 2 GB (the function default) at $0.0183/GB-hour, held 1,600 s | $0.016267 |\n * | `streamingCpu` | 60 s of active CPU at $0.221/hour — an allowance, see below | $0.003683 |\n * | `requests` | 1,000 range requests × $0.0000070 | $0.007 |\n * | **total** | | **$0.918709** |\n *\n * Each request is a CDN request ($3.20 per million), a function invocation\n * ($0.60 per million), the media document's read ($0.0000006 at `nam5`), the\n * day document's counter write ($0.0000018) and two Storage Class B\n * operations, the metadata read and the object read ($0.0000004 each). The\n * lockdown, quarantine and rate-limit reads are cached across requests and\n * cost nothing a request.\n *\n * `streamingCpu` is the one unmeasured term. The bytes are copied, not\n * computed — piping a GB through Node is a few seconds of CPU — and the\n * minute allowed here is chosen to err high, as every other basis in this\n * file does.\n *\n * ## Storing the bytes is NOT here\n *\n * A file in the library is already metered as storage (`storagePerGbMonth`)\n * and billed past the plan's storage at cost + 30% after card fees; serving\n * it does not store it again. Adding storage here would charge for the same\n * bytes twice.\n */\nexport const ORIGIN_MEDIA_SERVE_COST_USD_PER_GB = {\n fastDataTransfer: 0.35,\n fastOriginTransfer: 0.43,\n storageEgress: (0.12 * DECIMAL_GB_BYTES) / BINARY_GB_BYTES,\n streamingMemory: (2 * 0.0183 * ORIGIN_MEDIA_STREAM_SECONDS_PER_GB) / 3600,\n streamingCpu: (60 * 0.221) / 3600,\n requests:\n ORIGIN_MEDIA_REQUESTS_PER_GB *\n (3.2 / 1_000_000 + 0.6 / 1_000_000 + 0.0000006 + 0.0000018 + 2 * 0.0000004),\n} as const\n\n/** {@link ORIGIN_MEDIA_SERVE_COST_USD_PER_GB}, summed: about $0.9187 a decimal GB. */\nexport const ORIGIN_MEDIA_COST_USD_PER_GB = Object.values(\n ORIGIN_MEDIA_SERVE_COST_USD_PER_GB,\n).reduce((sum, term) => sum + term, 0)\n\n/**\n * The published page-view overage, per view: what a billed view costs us\n * (`perPageView` and the request term) at {@link METERED_PRICE_MULTIPLE},\n * rounded up to the cent per 1,000 it is quoted in — $0.83 per 1,000. The\n * same expression as the console's `METERED_BILLED_RATES_USD.perPageView`,\n * computed here because the weight below is, and a spec holds the two equal.\n */\nexport const PAGE_VIEW_PUBLISHED_PRICE_USD =\n publishedMeteredPrice(\n (ORG_COGS_UNIT_RATES_USD.perPageView + PAGE_VIEW_CDN_REQUEST_COST_USD) * 1000,\n 2,\n ) / 1000\n\n/**\n * (a) THE OVERAGE: does counting origin media at `weight` bill a GB of it past\n * the band at cost + 30%, kept after Stripe's percentage fee?\n *\n * A counted GB is `weight` GB of page views at {@link PAGE_VIEW_PUBLISHED_PRICE_USD}.\n */\nexport function originMediaOverageHolds(weight: number): boolean {\n const billedPerGbUsd =\n weight * PAGE_VIEW_PUBLISHED_PRICE_USD * (DECIMAL_GB_BYTES / ESTIMATED_PAGE_TRANSFER_BYTES)\n return (\n billedPerGbUsd * (1 - STRIPE_PROCESSOR_FEE_PCT) >=\n METERED_MARKUP * ORIGIN_MEDIA_COST_USD_PER_GB\n )\n}\n\n/**\n * (b) THE BAND: does counting origin media at `weight` keep a plan that spends\n * its whole bandwidth band on it inside what the band was sized on?\n *\n * Every `bandwidthGb` band is sized at the cost of the views one GiB buys —\n * their weight (`perPageView`) and their requests\n * (`PAGE_VIEW_CDN_REQUEST_COST_USD`), $0.63712 a GiB — and\n * `tier-margin-floor.spec.ts` holds every tier at 1.3× its full-use cost on\n * that figure. A GiB of origin media spends `weight` GiB of the band, so the\n * band holds while `weight` GiB of band costs at least one GiB of media.\n */\nexport function originMediaBandHolds(weight: number): boolean {\n const bandCostPerGibUsd =\n (ORG_COGS_UNIT_RATES_USD.perPageView + PAGE_VIEW_CDN_REQUEST_COST_USD) *\n pageViewsFromBandwidthGb(1)\n const originCostPerGibUsd =\n (ORIGIN_MEDIA_COST_USD_PER_GB * BINARY_GB_BYTES) / DECIMAL_GB_BYTES\n return weight * bandCostPerGibUsd >= originCostPerGibUsd\n}\n\n/**\n * How many GB of bandwidth one GB of video, audio or a file served from origin\n * counts as (AGL-3474, decided by the account owner 2026-10-02): the smallest\n * weight, in tenths and never below 1, that satisfies BOTH\n * {@link originMediaOverageHolds} and {@link originMediaBandHolds}.\n *\n * One weight rather than a second meter or a second rate, because it moves\n * the band and the overage together: the Free wall, the abuse ceiling, the\n * meters, the alerts and the invoice all read media through\n * `pageViewsFromMediaBytes`, which applies it. At today's bases the overage\n * needs 1.54 and the band 1.55, so it is 1.6 — the published sentence names\n * it, and the docs and `/pricing` have to change before it does.\n *\n * Derived, never written down: a rate or a band basis that moves re-derives\n * it, and `media-bandwidth.spec.ts` pins the figure so that a move is a\n * decision with a red test attached rather than a silent re-price.\n */\nexport const ORIGIN_MEDIA_BANDWIDTH_WEIGHT = ((): number => {\n for (let tenths = 10; tenths <= 1000; tenths += 1) {\n const weight = tenths / 10\n if (originMediaOverageHolds(weight) && originMediaBandHolds(weight)) return weight\n }\n throw new Error('no origin-media weight up to 100 covers its cost')\n})()\n\n/**\n * The sentence every customer surface states the weight in — `/pricing`\n * beside the metered rates (`tools/marketing/build-pricing-tables.mts`), and\n * the bandwidth, billing and media docs, which\n * `media-bandwidth-weight-copy.spec.ts` holds to this exact text. Built from\n * the derived figure, so a weight that moves turns those red before it can\n * bill anyone.\n */\nexport const ORIGIN_MEDIA_BANDWIDTH_SENTENCE =\n 'Video, audio, file downloads and other media served from our servers count ' +\n `${ORIGIN_MEDIA_BANDWIDTH_WEIGHT}× toward bandwidth, because serving them ` +\n 'costs more than serving pages.'\n\n/**\n * How far past a plan's own included bandwidth the abuse ceiling sits\n * (AGL-2155). Three times the bandwidth the customer bought is not growth; it\n * is an upgrade conversation, or it is not the customer's traffic at all.\n *\n * It is a CAP, not a price, and it is lower than {@link\n * FORM_ABUSE_CEILING_MULTIPLE} because bandwidth is the largest cost line the\n * platform carries and the one a stranger can spend on the customer's behalf.\n * Past the band every 1,000 views bills $0.80 against at most $0.62 of real\n * cost — the dearest region's — once the CDN's request allowance is spent\n * ({@link PAGE_VIEW_CDN_REQUEST_COST_USD}), so the tail is not a loss — but a\n * scraper, a hotlinked asset or a botnet still bills the account holder for\n * traffic they did not ask for, and the ceiling is what bounds that before\n * staff look at it. At 10× the band that tail was open-ended on Agency; at 3×\n * it is bounded.\n */\nexport const BANDWIDTH_ABUSE_CEILING_MULTIPLE = 3\n\n/**\n * Absolute floor for the bandwidth ceiling, in PAGE VIEWS per month, so the\n * small plans get real headroom instead of a second, tighter plan limit\n * wearing a different name.\n *\n * Free includes 2 GB ≈ 2,071 views; 3× would be ~6,212, which a genuinely\n * successful hobby site (a post that lands on Hacker News) reaches in an\n * afternoon and would be a miserable first experience of the platform.\n * 100,000 views/month ≈ 96.6 GB ≈ **$62 of real COGS** at the dearest\n * region (`METERED_UNIT_RATES_USD.perPageView` plus\n * `PAGE_VIEW_CDN_REQUEST_COST_USD`) — well above the free band, and an order\n * of magnitude below the $615 a million views costs. It is the number\n * that makes \"free stays free\" true without making it stingy.\n */\nexport const BANDWIDTH_ABUSE_CEILING_FLOOR = 100_000\n\n/**\n * Ceiling for an org whose bandwidth band resolves to UNLIMITED — a\n * contracted per-org override, since no plan row carries the sentinel any\n * more — where a multiple has nothing to multiply. Kept at or above the\n * ceiling of every finite band below it so the ladder never inverts and a\n * bigger plan never inherits a smaller ceiling.\n */\nexport const BANDWIDTH_ABUSE_CEILING_UNLIMITED = 500_000_000\n\n/**\n * Machine-readable marker for a bandwidth containment, so a capped response\n * is distinguishable from the maintenance/lockdown notices it shares a shell\n * with. Deliberately NOT a {@link LockdownReasonCode}: a lockdown is a staff\n * takedown, this is a billing containment that clears itself at the month\n * boundary with nobody doing anything.\n */\nexport const BANDWIDTH_CEILING_CODE = 'bandwidth_ceiling'\n\nexport interface BandwidthAbuseCeilingResult {\n /** True once this month's page views for the SITE reached the ceiling. */\n exceeded: boolean\n /** The ceiling this site's plan resolves to, in page views. Finite. */\n ceiling: number\n used: number\n}\n\n/**\n * Per-site monthly bandwidth abuse ceiling (AGL-2155).\n *\n * ## The hole this closes\n *\n * Every other free dimension had a runtime brace — `mediaStorageGate`,\n * `checkFormSubmissionQuota`, a zero band for dataset storage and API, a\n * hard band for contacts. Bandwidth had **none**: nothing anywhere refused a\n * page view, so a free site that went viral served a million views — roughly\n * **$162 of real COGS** — with no wall, no throttle and no alert. It was\n * protected only by the structural zero on the billing side (free carries no\n * rate, so the invoice stays $0), which protects the *bill* and not the\n * *bleeding*. `free-tier-never-billed.spec.ts` said so in its own table.\n *\n * ## Why an abuse ceiling and not a plan gate\n *\n * Exactly the distinction {@link checkFormSubmissionAbuseCeiling} draws, and\n * for the same reason. A plan gate answers \"did the customer buy this?\" — and\n * for a metered plan the answer past the band is *yes, and we bill it*. This\n * answers \"is this still a customer's traffic at all?\", and crossing it is an\n * INCIDENT rather than a quota: the site is flagged, staff are told, and the\n * render path degrades to a static notice instead of paying ~40 Firestore\n * reads and ~1,013 KB of egress per view.\n *\n * ## Where it is evaluated\n *\n * NOT in the render path. The page-view counter is written *after* the render\n * by `/api/analytics/collect`; that route evaluates this on a sampled cadence\n * and stamps `bandwidthCeiling` onto the host document. The render path reads\n * that field off a host doc **it already loads** (`load-page-data.ts` reads\n * `hostRes.host` for the lockdown branch), so the containment costs the happy\n * path exactly zero extra reads.\n *\n * An edge/CDN rule would be the other natural home and is deliberately not\n * the answer here: it is not a repo change, nothing in this suite could\n * verify it, and it cannot see which plan a host is on.\n */\nexport function checkBandwidthAbuseCeiling(\n org: Partial<AglynOrgBilling> | null | undefined,\n usedPageViewsThisMonth: number,\n): BandwidthAbuseCeilingResult {\n const includedGb = resolveOrgEntitlements(org).bandwidthGb ?? 0\n const includedViews = pageViewsFromBandwidthGb(includedGb)\n const ceiling = Number.isFinite(includedViews)\n ? Math.max(\n BANDWIDTH_ABUSE_CEILING_FLOOR,\n Math.round(includedViews * BANDWIDTH_ABUSE_CEILING_MULTIPLE),\n )\n : BANDWIDTH_ABUSE_CEILING_UNLIMITED\n const used = Math.max(0, Number(usedPageViewsThisMonth) || 0)\n return { exceeded: used >= ceiling, ceiling, used }\n}\n\n/**\n * Does crossing the ceiling **degrade what visitors see**, or only raise the\n * incident? (AGL-2155)\n *\n * Only on a plan that does NOT meter the infra pass-through — which today is\n * free/hobby, where the requirement is that free always actually stays free.\n * On free the traffic is uncompensated, so serving it is a\n * straight loss and the least destructive way to stop the bleeding is to stop\n * paying for the expensive render.\n *\n * On a metered plan the same traffic is *invoiced*, so taking a paying\n * customer's site off the air would trade a bill they agreed to for an outage\n * they did not. Their ceiling still trips, still flags the host and still\n * escalates to staff — it just does not change what a visitor gets. Whether a\n * paying host past its OWN ceiling should also be degraded is a product call\n * and is left open on purpose; flipping it is this one function.\n *\n * Enterprise does not meter either, and is NOT degraded, for the same reason\n * a metered plan is not: its traffic is paid for, by agreement rather than by\n * invoice. Since 2026-09-07 its bandwidth band is a finite fallback (twice\n * Agency's), so without this exclusion the ceiling an agreement never named\n * would have taken a contracted customer's site off the air three times past\n * a default nobody chose for them. It still trips, flags and pages.\n */\nexport function bandwidthCeilingDegradesRender(\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n return !planMetersInfraOverage(org) && !isCustomPricedPlan(resolvePlan(org))\n}\n\n/**\n * `hosts/{id}.bandwidthCeiling` — the flag the beacon stamps and the render\n * path reads. Month-scoped so it **self-clears at the month boundary** with\n * no staff action and no write, the same property `LockdownState.untilMs`\n * has: a site contained in August serves normally on September 1.\n *\n * Admin-SDK-only. It is frozen against client writes in\n * `cloud/firebase-firestore.rules` alongside the `suspendedAt` family, for\n * the identical reason: a site that could clear its own containment does not\n * have one.\n */\nexport interface HostBandwidthCeiling {\n /** `YYYY-MM` (UTC) the trip belongs to. */\n month: string\n /** The ceiling that was crossed, in page views. */\n ceiling: number\n /** The month count observed when it tripped. */\n used: number\n trippedAtMs: number\n /** False when the plan meters the overage — flagged, not degraded. */\n degraded: boolean\n}\n\n/**\n * Read the host document's containment flag, tolerantly. Returns null unless\n * the flag is present, well-formed AND belongs to `month` — an August trip\n * must not silence a September visitor.\n */\nexport function normalizeHostBandwidthCeiling(\n host: Record<string, any> | null | undefined,\n month: string,\n): HostBandwidthCeiling | null {\n const raw = host?.['bandwidthCeiling']\n if (!raw || typeof raw !== 'object') return null\n if (String(raw.month ?? '') !== month) return null\n const ceiling = Number(raw.ceiling ?? 0)\n if (!Number.isFinite(ceiling) || ceiling <= 0) return null\n return {\n month,\n ceiling,\n used: Math.max(0, Number(raw.used ?? 0) || 0),\n trippedAtMs: Math.max(0, Number(raw.trippedAtMs ?? 0) || 0),\n degraded: raw.degraded === true,\n }\n}\n\n/**\n * Is the render path required to serve the capped notice instead of the page?\n *\n * Three things must hold: the flag is for THIS month, the trip was recorded\n * as one that degrades, and the org's CURRENT plan still degrades.\n *\n * The stored `degraded` alone cannot answer the last one. It is written once,\n * at trip time, and nothing rewrites it: the beacon stops evaluating a host\n * once it has tripped this month, and a contained site serves no pages to\n * beacon from. So a host that upgraded mid-month stayed on the notice until\n * the 1st, while the owner notice and the visitor notice both promised an\n * upgrade would bring it back. Re-deriving from `org` on every read releases\n * it on the next org-doc TTL with no write, the same asymmetry\n * `bandwidthCapEngaged` keeps for the plan band.\n *\n * Both halves still hold in the other direction: a flag written for a paying\n * host (`degraded: false`) can never take that host down, whatever its plan\n * becomes later. An unknown org (`null`) resolves as unmetered, so a lookup\n * failure keeps the containment rather than lifting it.\n */\nexport function bandwidthCeilingDegradesHost(\n host: Record<string, any> | null | undefined,\n month: string,\n org: Partial<AglynOrgBilling> | null | undefined,\n): boolean {\n return (\n normalizeHostBandwidthCeiling(host, month)?.degraded === true &&\n bandwidthCeilingDegradesRender(org)\n )\n}\n\n/** UTC `YYYY-MM`, the key both the beacon and the render path agree on. */\nexport function bandwidthCeilingMonthKey(now: Date = new Date()): string {\n return now.toISOString().slice(0, 7)\n}\n\n/**\n * Visitor-facing copy for a contained site. Says what happened and that it\n * ends, and names nothing about the plan, the count or the owner — this is\n * read by a stranger to the site, exactly like the form ceiling's refusal\n * body (AGL-1666).\n */\nexport function bandwidthCeilingNotice(): { title: string; body: string } {\n return {\n title: 'This site is temporarily unavailable',\n body: 'It has served more traffic this month than its plan includes. It will be back at the start of next month, or sooner if the owner upgrades.',\n }\n}\n\n/**\n * What a quota check costs a caller that intends to ENFORCE it (AGL-2163).\n *\n * `checkDataStorageQuota` and `checkApiRequestQuota` each carry an `allowed`\n * field, and their docblocks promise it refuses — \"plans without one (free)\n * hard-block at the included size\", \"plans without API access have\n * `included: 0` and always block\". Nothing read it. Both functions appeared\n * only in `/api/billing/report-usage`, which reads `overageMonthlyUsd` and\n * ignores `allowed`, so the promise was documentation and the free tier's\n * \"runtime braces\" on those two dimensions did not exist.\n *\n * Enforcing them naively means measuring first, and for dataset storage the\n * measurement is O(datasets) reads — far too much for a per-record write.\n * This makes the cheap answer available: for every plan shipping today the\n * verdict does not depend on the measurement at all, because a plan either\n * has an overage RATE (then it always allows and meters) or a band of zero\n * (then it always refuses). Measuring is required only for the shape a staff\n * `entitlementOverrides` can create — a finite, non-zero band on a plan with\n * no rate — and only then does the caller pay a read.\n *\n * - `'always-blocks'` — refuse now; no measurement, no read.\n * - `'never-blocks'` — proceed; no measurement, no read. **This is every\n * metered/paid plan**, so enforcement costs a paying customer nothing and\n * can never refuse them.\n * - `'measure'` — the verdict depends on usage; measure and call the check.\n */\nexport type QuotaEnforcementShape =\n | 'always-blocks'\n | 'never-blocks'\n | 'measure'\n\nfunction enforcementShape(\n atZero: boolean,\n atCeiling: boolean,\n): QuotaEnforcementShape {\n if (!atZero && !atCeiling) return 'always-blocks'\n if (atZero && atCeiling) return 'never-blocks'\n return 'measure'\n}\n\n/**\n * Enforcement shape of {@link checkDataStorageQuota} for this org, before\n * anything has been measured. @see QuotaEnforcementShape\n */\nexport function dataStorageEnforcementShape(\n org: Partial<AglynOrgBilling> | null | undefined,\n): QuotaEnforcementShape {\n return enforcementShape(\n checkDataStorageQuota(org, 0).allowed,\n checkDataStorageQuota(org, Number.MAX_SAFE_INTEGER).allowed,\n )\n}\n\n/**\n * Enforcement shape of {@link checkApiRequestQuota} for this org, before\n * anything has been measured. @see QuotaEnforcementShape\n */\nexport function apiRequestEnforcementShape(\n org: Partial<AglynOrgBilling> | null | undefined,\n): QuotaEnforcementShape {\n return enforcementShape(\n checkApiRequestQuota(org, 0).allowed,\n checkApiRequestQuota(org, Number.MAX_SAFE_INTEGER).allowed,\n )\n}\n"],"names":["isOrgOverrideReasonCode","PLATFORM_BRAND_NAME","PLATFORM_BRANDING_PROFILE","listPluginFeatures","listPluginSeatAddons","pluginSeatAddon","pluginOrgCapacity","declaredMeterReading","pluginCostAxes","pluginCostAxisFields","planQuotaOf","pluginPlanFeatureRow","pluginPlanQuotaRow","pluginPlanQuotas","UNLIMITED","Number","POSITIVE_INFINITY","FORMS_PER_HOST_CEILING","isUnlimitedQuota","limit","isFinite","formatQuotaLimit","unit","String","restoreQuotaLimit","value","unlimited","ENTERPRISE_EMAIL_SENDS_PER_MONTH","PLAN_ENTITLEMENTS","free","hostLimit","screensPerHost","sharedLayoutsPerHost","templatesPerHost","storagePerHostMb","membersPerHost","managersPerOrg","maxManagersPerOrg","maxMembersPerHost","bandwidthGb","formsPerHost","componentsPerHost","variablesPerHost","functionsPerHost","workflowsPerHost","servicesPerHost","redirectsPerHost","contactsPerHost","emailSendsPerMonth","actionRunsPerMonth","apiRequestsPerMonth","datasetsPerOrg","maxDatasetsPerOrg","recordsPerDataset","dataStorageMbPerOrg","productsPerHost","inventoryLocations","posRegisters","transactionFeePhysicalPct","transactionFeeDigitalPct","features","abTesting","versioning","reusableComponents","customDomain","customSendingDomain","dedicatedSendingDomain","removeBranding","scheduledPublishing","aiAssist","aiGenerative","workflows","dataStore","videoMedia","bookings","crm","outreach","interactions","actions","webhooks","apiAccess","siteExport","multilingual","eventCalendar","redirects","screenAnalytics","mediaCdn","marketingOverlays","commerce","pos","contentGating","productReviews","abandonedCart","dropshipRouting","whiteLabel","ssoEnabled","starter","pro","business","scale","advanced","agency","enterprise","fillPluginPlanEntitlements","rows","plan","Object","keys","quotas","key","entries","SELF_SERVE_PLANS","isCustomPricedPlan","PLAN_LABELS","EVENT_CALENDAR_ADDON_MONTHLY_USD","POS_REGISTER_ADDON_MONTHLY_USD","AI_ADDON_CREDITS_PER_MONTH","aiAddonName","brand","EXTRA_HOSTS_ADDON_MAX","POS_REGISTERS_ADDON_MAX","PLAN_PRICING","basePriceMonthlyUsd","basePriceAnnualMonthlyUsd","extraHostMonthlyUsd","extraSeatMonthlyUsd","extraCollaboratorMonthlyUsd","extraDatasetMonthlyUsd","extraDataGbMonthlyUsd","extraApiRequestsUsdPer1k","extraAssistCreditsUsdPer1k","aiAddonMonthlyUsd","extraContactsUsdPer1k","extraEmailSendsUsdPer1k","meteredInfraPassThrough","compPricing","pricing","withheld","PLAN_COMP_PRICING","fromEntries","map","resolvePlanPricing","org","resolveEffectivePlan","resolvePlanComp","DEAD_SUBSCRIPTION_STATUSES","Set","subscriptionStatusOf","mirrored","billingStatus","subscription","status","orgSubscriptionState","has","instantOf","record","Date","isNaN","getTime","toISOString","millis","parse","toDate","date","call","seconds","_seconds","trimmedOrNull","trim","readOrgPlanComp","entitlements","raw","planComp","undefined","Array","isArray","prototype","hasOwnProperty","reason","uncapped","note","grantedBy","grantedAt","isUncappedPlanComp","planCompLabel","comp","inForce","name","terms","filter","Boolean","join","planCompPhrase","withArticle","phrase","test","describeOrgPlan","stored","storedPlan","compInForce","decidedBy","effectivePlan","subscriptionStatus","orgPlanDescriptionSentence","description","label","dormant","isBillingSubscription","orgListPriceMonthlyUsd","custom","customMonthlyUsd","Math","round","annual","interval","addons","seatAddons","seats","quantity","price","max","managers","members","datasets","hosts","aiAddonUnits","aiAddon","applyDiscountUsd","listPriceUsd","discount","percentOff","pct","min","amountOffUsd","isEnterpriseOrg","ENTERPRISE_PLAN_LABEL","orgMonthlyRevenueUsd","listPrice","STRIPE_PROCESSOR_FEE_PCT","STRIPE_PROCESSOR_FEE_FIXED_USD","netOfProcessorFee","grossMonthlyUsd","fixedPerMonth","net","orgNetMonthlyRevenueUsd","gross","NET_MARGIN_FLOOR_PCT","NET_MARGIN_WARN_BAND_PCT","MARGIN_SCOPE_NOTE","netMarginRating","marginPct","INFRA_COGS_PER_SITE_USD","METERED_MARKUP","METERED_PRICE_MULTIPLE","publishedMeteredPrice","costPerQuotedUnitUsd","decimals","ceil","toFixed","PAGE_VIEW_CDN_REQUEST_COST_USD","ORG_COGS_UNIT_RATES_USD","storagePerGbMonth","perPageView","perFormSubmission","dataStoragePerGbMonth","perApiRequest","perContactMonth","perEmailSend","perRun","cogsRateOf","axis","rate","Error","id","orgMonthlyCogsUsd","rollup","siteCount","num","parsed","rates","core","order","usd","storageGb","pageViews","dataStorageMb","apiRequests","emailSends","declared","breakdown","sort","a","b","measuredUsd","values","reduce","sum","x","floorUsd","cogsUsd","basis","orgCogsInputFrom","source","read","field","input","hostCount","orgCogsPreview","ready","cogs","orgCogsBasisSummary","month","when","measured","DISCOUNT_APPROVAL_THRESHOLD_PCT","DISCOUNT_DEPTH_WARN_PCT","DISCOUNT_DEPTH_BLOCK_PCT","orgSiteCount","length","checkDiscountMargin","options","floorPct","grossUsd","flatCogsUsd","measuredCogsUsd","cogsMeasured","infraCogsUsd","discountedUsd","netUsd","depthPct","rating","depthRating","cogsRating","severity","ok","warn","block","resolvePlan","resolvePurchasedAddons","hasAiAddon","RETIRED_ENTITLEMENT_KEYS","SEAT_BANDS","PRICE_ENTITLEMENT_KEYS","withEveryCapLifted","lifted","resolveOrgEntitlements","resolved","resolveCappedOrgEntitlements","purchased","defaults","withPluginFeatureDefaults","overrides","featureOverrides","datasetsPerHost","legacyDatasets","maxDatasetsPerHost","legacyMaxDatasets","quotaOverrides","merged","included","extraHosts","folded","foldPluginSeatAddons","pluginSeatAddonUnits","units","floor","maxUnits","hasPluginSeatAddon","out","addon","next","quota","bag","perUnit","perUnitByPlan","current","feature","row","defaultByPlan","resolveRegisterSeatPool","rawPurchased","registerAllocations","byHost","allocated","hostId","available","resolveHostRegisterCap","planCap","resolveCollaboratorSeatPool","collaboratorAllocations","resolveHostCollaboratorCap","maxSeats","assigned","checkHostCollaboratorQuota","currentUsage","addonPriceUsd","assignedSeats","allowed","remaining","upgradeRequired","retainedOverCap","checkHostRegisterQuota","resolveTransactionFeePct","productType","saleProcessingCostCents","chargeCents","SALE_PROCESSING_PERCENT","SALE_PROCESSING_FIXED_CENTS","resolveTransactionFeeCents","feeBaseCents","charge","base","take","saleProcessingPassThroughPercent","amountCents","amount","rateHundredths","fixedHundredths","resolveSubscriptionFeePercent","passThrough","resolveMarketplaceFeePct","takeRatePctOf","fallback","TAKE_RATE_KEY","bindingMarketplaceFeePct","CARD_PROCESSING_PERCENT","BNPL_PROCESSING_PERCENT","PROCESSING_FIXED_CENTS","planGrantingFeature","find","planLabelGrantingFeature","checkEntitlement","cleanBrandString","trimmed","resolveBrandingProfile","profile","brandingProfile","some","productName","logoUrl","logoDarkUrl","faviconUrl","faviconDarkUrl","primaryColor","supportUrl","homeUrl","fromName","emailLogoUrl","customConsoleDomain","brandMergeTokens","branding","PLATFORM_GENERATOR_NAME","showsPlatformAttribution","hasBrandingProfile","checkSeatQuota","kind","checkQuota","checkDatasetQuota","maxDatasets","checkPluginOrgCapacityQuota","capacity","includedEntitlement","ceiling","purchaseCeilingEntitlement","addonKind","checkDataStorageQuota","usedMb","includedMb","overageRateUsd","used","overageMb","overageGb","remainingMb","overageMonthlyUsd","checkApiRequestQuota","usedRequests","overageRequests","checkCrmRecordsQuota","usedRecords","overageRecords","checkContactQuota","CRM_EMAIL_USAGE_COLLECTION","crmEmailUsageDayKey","now","slice","checkCrmEmailQuota","usedToday","resetsAt","UTC","getUTCFullYear","getUTCMonth","getUTCDate","emailSendsOverage","total","sends","priceEmailSendOverage","overageSends","over","planMetersInfraOverage","checkFormSubmissionQuota","usedThisMonth","metered","overageSubmissions","FORM_ABUSE_CEILING_MULTIPLE","FORM_ABUSE_CEILING_FLOOR","FORM_ABUSE_CEILING_UNLIMITED","checkFormSubmissionAbuseCeiling","exceeded","ESTIMATED_PAGE_TRANSFER_BYTES","pageViewsFromBandwidthGb","bandwidthGbFromPageViews","DECIMAL_GB_BYTES","BINARY_GB_BYTES","ORIGIN_MEDIA_STREAM_SECONDS_PER_GB","ORIGIN_MEDIA_REQUESTS_PER_GB","ORIGIN_MEDIA_SERVE_COST_USD_PER_GB","fastDataTransfer","fastOriginTransfer","storageEgress","streamingMemory","streamingCpu","requests","ORIGIN_MEDIA_COST_USD_PER_GB","term","PAGE_VIEW_PUBLISHED_PRICE_USD","originMediaOverageHolds","weight","billedPerGbUsd","originMediaBandHolds","bandCostPerGibUsd","originCostPerGibUsd","ORIGIN_MEDIA_BANDWIDTH_WEIGHT","tenths","ORIGIN_MEDIA_BANDWIDTH_SENTENCE","BANDWIDTH_ABUSE_CEILING_MULTIPLE","BANDWIDTH_ABUSE_CEILING_FLOOR","BANDWIDTH_ABUSE_CEILING_UNLIMITED","BANDWIDTH_CEILING_CODE","checkBandwidthAbuseCeiling","usedPageViewsThisMonth","includedGb","includedViews","bandwidthCeilingDegradesRender","normalizeHostBandwidthCeiling","host","trippedAtMs","degraded","bandwidthCeilingDegradesHost","bandwidthCeilingMonthKey","bandwidthCeilingNotice","title","body","enforcementShape","atZero","atCeiling","dataStorageEnforcementShape","MAX_SAFE_INTEGER","apiRequestEnforcementShape"],"mappings":"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"}
|