/** * The D609-locked ALWAYS_ON namespaces — the true non-sellable platform * substrate every tenant always holds regardless of subscription. A held key * whose entitlement_key resolves into this set is ALWAYS effective. Order is * not significant (membership is a Set); the list is frozen as the canonical * source of truth. * * ⚠️ `"hr"` WAS IN THIS LIST AND WAS REMOVED IN 0.11.0. It never belonged: hr * is a SELLABLE module in the D620 taxonomy, not substrate. Its presence made * every `hr.*` key entitled for every tenant regardless of subscription, which * would have swallowed D649's add-on gating whole — an hr add-on could not * restrict anything if the base namespace was already granted to everybody. * Removing it is a real gating change, which is why hr's own backfill + lib * bump must land in the SAME tenant deploy. * * ⚠️ `"my_tools"` WAS IN THIS LIST AND WAS REMOVED IN 0.13.0 — RELEASE B OF THE * TWO-RELEASE SWAP (D662). `"work"` IS ITS REPLACEMENT AND MUST STAY. * DO NOT RE-ADD `my_tools`: it is a retired key, and a list carrying both again * would silently re-entitle rows the rename is supposed to have converged. * * D662 renames the commerce module key `my_tools` → `work` and amends this * list to carry `work`. It also rules the ORDERING, in its own words: "the * always-on list moves first, and the fill follows. Never the reverse." * * Release A (0.12.0) carried BOTH keys for exactly one release, and the reason * was measured, not stylistic. On dev (2026-07-31): 91 `tasks.*` catalog rows * carried `entitlement_key = 'my_tools'`, **0 of 72 tenants** held `work` in * `tenant_entitlements.active_module_keys`, 2 of 72 still held `my_tools`, and * only 11 tenants had an entitlement row at all. For essentially the whole * fleet this list is the ONLY thing keeping `tasks.*` effective, so a * single-release swap would have left a window in which the rows said one key * and the deployed reader honoured the other — every `tasks.*` key stripped * from every effective set for the duration, with no error and no log line. * Holding both keys made that window structurally impossible rather than * merely short: whichever value the rows carried at any instant was honoured, * so no ordering mistake between the DML, the publisher republish and the * consumer redeploy could strip anything. * * ⚠️ RELEASE B IS THE FAIL-CLOSED HALF AND IS GATED ON DATA, NOT ON CODE. * Deleting the entry is the one action in the whole rename that can actually * revoke permissions: from this version on, a held key whose `entitlement_key` * is still the literal string `'my_tools'` is NO LONGER entitled for a tenant * that does not separately hold that module — it drops out of the effective set * and its routes 403. Publishing/adopting 0.13.0 is therefore conditional on a * live query proving convergence — zero rows remaining, in BOTH places: * * SELECT count(*) FROM permission_catalog WHERE entitlement_key = 'my_tools'; * SELECT count(*) FROM tenant_entitlements * WHERE 'my_tools' = ANY(active_module_keys); * * Both must be 0 before a consumer takes this version. Adopting it while either * is non-zero reproduces exactly the outage release A was built to prevent. * * `tasks` stays for an independent reason: 4 deprecated `tasks.task.*` catalog * rows carry a NULL `entitlement_key` and therefore fall back to their own * first segment. They are not covered by either commerce key, and release B * does not touch them. */ export declare const ALWAYS_ON_NAMESPACES: readonly ["tenant", "commerce", "platform", "platform_ops", "pii", "audit", "dsar", "compliance", "tasks", "work"]; export type AlwaysOnNamespace = (typeof ALWAYS_ON_NAMESPACES)[number]; /** * Minimal catalog entry the filter needs. Structurally a subset of * PermissionDef, so a `Map` (or a plain object of * PermissionDef) can be passed directly. */ export interface EntitlementCatalogEntry { /** The sellable module/add-on key that unlocks this permission. When absent, * the key's own domain_key (first segment) is used. */ entitlementKey?: string; } type CatalogMap = ReadonlyMap | Readonly>; /** * Resolve the entitlement_key of a permission key: the explicit value on the * catalog entry, else the permission's own domain_key (the first `.`-segment). * * ⚠️ The first-segment fallback is TRANSITIONAL DEAD WEIGHT as of 0.11.0. * `entitlementKey` is now REQUIRED on every published permission (D649 § 3 / * D655), so the only rows that can still hit it are ones absent from * `catalogMap` entirely — i.e. pre-backfill data. Once the server-side backfill * lands and every publisher has filled its catalog, this branch is unreachable * and its removal is a 1.0.0 item. It is deliberately NOT removed here: doing so * before the backfill would silently drop every un-migrated key out of the * effective set, which is a fail-CLOSED outage rather than a clean error. */ export declare function entitlementKeyOf(key: string, entry?: EntitlementCatalogEntry): string; /** * "Subscribing to ANY of `whenAnyOf` entitles `grants`." * * `whenAnyOf` are exact MODULE keys as they appear in the tenant's active set. * `grants` are the synthetic entitlement keys the substrate's permissions * resolve to (their domain_key, per {@link entitlementKeyOf}'s default rule). */ export interface SubstrateEntitlementRule { /** Synthetic entitlement keys to add when the rule fires. */ grants: readonly string[]; /** Exact module keys; the rule fires if the active set contains ANY of them. */ whenAnyOf: readonly string[]; } /** * Expand an active entitlement-key set with the substrate keys its members * imply. Pure; returns a NEW de-duplicated array and never mutates the input. * * Rules are independent and additive, evaluated against the ORIGINAL active set * — a rule cannot fire off a key another rule just granted, so rule ORDER cannot * change the result and no rule pair can cascade into an unintended grant. A * tenant with no matching subscription is unchanged (a bare tenant stays bare; * substrate keys are not ALWAYS_ON). */ export declare function expandEntitlementKeys(activeKeys: Iterable, rules: Iterable): string[]; export interface ResolveEffectivePermissionsOptions { /** * ANY-OF substrate rules applied to `activeKeys` BEFORE the filter runs. * See {@link SubstrateEntitlementRule}. Omitted → no expansion, byte-identical * to the pre-0.7.0 behaviour. */ substrateRules?: Iterable; } /** * effective = held ∩ { k : entitlement_key(k) ∈ expand(activeKeys) ∪ ALWAYS_ON }. * * Pure. Returns the effective keys as a de-duplicated array in first-seen order * of `held` (deterministic — the callers build `permissions[]` / catalog rows * from it). `catalogMap` is optional; when a key is absent from it, the key's * domain_key is used as its entitlement_key (the default rule). * * `opts.substrateRules` closes the any-of gap described above. Absent → the * function behaves exactly as before. */ export declare function resolveEffectivePermissions(held: Iterable, activeKeys: Iterable, catalogMap?: CatalogMap, opts?: ResolveEffectivePermissionsOptions): string[]; export {}; //# sourceMappingURL=effective.d.ts.map