/** Reserved permission namespaces; rejected by definePermissions/publishCatalogAtBoot * unless `bypassReservedNamespaceCheck: true` is set (only for the platform_ops * module hosted by nodii-tenant-service per 07-rbac § 2). * * `"substrate"` joined this list in 0.11.0 (D655): it is the RESERVED SENTINEL * VALUE of {@link SUBSTRATE_ENTITLEMENT}, so nothing may publish a `substrate.*` * domain and alias it. `validateDomainKey` rejects it as a publish namespace; * `validateEntitlementKey` accepts it ONLY as the exact sentinel. */ export declare const RESERVED_NAMESPACES: readonly ["platform", "system", "pii", "audit", "dsar", "substrate"]; export type ReservedNamespace = (typeof RESERVED_NAMESPACES)[number]; /** The explicit "this permission has NO commerce gate" marker (D655 / D649 § 3). * * D649 kills the first-segment default and makes * `module_permission_catalog.entitlement_key` NOT NULL, so "substrate" has to * be a STORED literal rather than an absence: *"'this is substrate' is stated, * never implied"*. This exact string is that literal — self-documenting in a * psql/WG audit and in the god-seed-retirement invariant check. * * It is a RESERVED_NAMESPACES member, so it can never collide with a real * module or add-on key, and {@link RESERVED_NAMESPACES} makes that structural * rather than conventional. A key whose entitlementKey is this sentinel is * ALWAYS effective (see resolveEffectivePermissions). */ export declare const SUBSTRATE_ENTITLEMENT: "substrate"; /** Reserved SUBSTRATE domain_keys (07-rbac § 5.5, ratified per D513). These are * NOT §2 publish-forbidden reserved namespaces (that list is RESERVED_NAMESPACES * above) — they are PUBLISH ENVELOPES for never-subscribable INFRA SUBSTRATE * services that own permission keys across MULTIPLE namespaces. When a catalog * is published under a substrate domain_key WITH a declared `ownedNamespaces` * set, the validator accepts each key iff its first-segment ∈ {declared owned * namespaces} — relaxing the strict first-segment == domain_key rule ONLY for * these keys. Every other publish keeps the strict rule. `observability` is * obs-interface-service's envelope for its telemetry.* / obs.* / platform_ops.* * keys; keys KEEP their canonical namespaces (platform_ops.* is NOT re-pointed * — global-uniqueness + shared grants stand). See D513 + 07-rbac § 5.5.2. */ export declare const SUBSTRATE_DOMAIN_KEYS: readonly ["observability"]; export type SubstrateDomainKey = (typeof SUBSTRATE_DOMAIN_KEYS)[number]; /** Sanity caps per spec § 5.9 errors table. * MAX_PERMISSIONS_PER_CATALOG raised 50 -> 500 to unblock large legitimate * catalogs (nodii-hr-service 170 keys, nodii-task-tracking 91 keys) per reqs * 69a04c69 + fc006383 / drift c170e4f4 (07-rbac doctrine imposes no count cap; * 500 is a generous anti-abuse bound, not a feature limit). */ export declare const MAX_PERMISSIONS_PER_CATALOG = 500; export declare const MAX_STARTER_ROLES_PER_CATALOG = 50; export declare const MAX_SEGMENT_LENGTH = 50; /** Assignment policy for a permission (RBAC drive §2.2). `'tenant'` keys are * freely composable into tenant roles; `'seat_locked'` keys are excluded from * the role-builder listing and grantable ONLY via the seat-role machinery. * Default `'tenant'`. This does NOT participate in effective-set math (it * filters at the catalog-listing layer) — see resolveEffectivePermissions. */ export type AssignmentPolicy = "tenant" | "seat_locked"; /** Role semantics carried on the publish wire so predefined_roles can store * them (RBAC drive §5 / D576). Default `'standard'`; `'seat'` roles are the * locked, roster-driven seat roles and MUST declare a `seatDimensionKey`. */ export type RoleClass = "standard" | "admin" | "seat"; /** ⚠️ THE `entitlementKey` / `isCrossTenant` ASYMMETRY IS DELIBERATE (0.11.0). * * `entitlementKey` is **hard-required with no typed escape** (D655): omission * is a compile error, and the compile error IS the adoption checklist. An * optional-with-sentinel escape would let an adoption PR go green with the * fill half-done, recreating the exact NULL drift D649 exists to kill. * * `isCrossTenant` is **optional** (D656): omitted means "not declared", NOT * "declared false". The D633 § 3 forcing rule owns the default and the flag * can only ever ADD marking — `false` must never be able to unmark a * namespace-forced key. Requiring it would turn every unfilled call site into * an explicit `false`, which is precisely the assertion we do not want made. */ /** Input shape passed to definePermissions per § 5.1. */ export interface PermissionDefInput { resource: string; verb: string; qualifier?: string; isCritical?: boolean; description?: string; deprecatedInVersion?: string; metadata?: Record; /** REQUIRED per D649 § 3 / D655. The sellable COMMERCE module key (or add-on * key) that UNLOCKS this permission — this one really is the commerce * catalog's `modules.key`, NOT an RBAC namespace, which is exactly the * collision the 0.7.0 module_key -> domain_key rename exists to make * legible. * * Either 1–2 dot segments (`hr`, `hr.people_management`) or the exact * {@link SUBSTRATE_ENTITLEMENT} sentinel for a permission with no commerce * gate. RBAC drive §2.1: a key only enters the effective set when its * entitlement_key ∈ active keys ∪ ALWAYS_ON ∪ {sentinel}. */ entitlementKey: string; /** Assignment policy (RBAC drive §2.2). Default `'tenant'`. */ assignmentPolicy?: AssignmentPolicy; /** D633 § 3 / D656 — declare this key cross-tenant. OPTIONAL by design: * omitted means "not declared", and `false` can never UNMARK a key the * server's namespace rule forces true. See the asymmetry note above. */ isCrossTenant?: boolean; } /** Resolved permission definition with `.key` attached. */ export interface PermissionDef { key: string; resource: string; verb: string; qualifier?: string; isCritical: boolean; description: string; deprecatedInVersion?: string; metadata?: Record; /** REQUIRED per D649 § 3 / D655 — a sellable COMMERCE module/add-on key * (1–2 dot segments) or the exact {@link SUBSTRATE_ENTITLEMENT} sentinel. * The "omitted ⇒ default to the permission's own domain_key" contract is * DEAD as of 0.11.0. RBAC drive §2.1. */ entitlementKey: string; /** Assignment policy (RBAC drive §2.2). Omitted ⇒ treated as `'tenant'`. */ assignmentPolicy?: AssignmentPolicy; /** D633 § 3 / D656 — see {@link PermissionDefInput.isCrossTenant}. */ isCrossTenant?: boolean; } /** Starter role definition per § 5.1. */ export interface StarterRoleDef { /** REQUIRED per D654 — the MACHINE IDENTITY of this starter role, and the * only identity axis. Slug shape `^[a-z][a-z0-9_]*$`, ≤ 50 chars. * * Before 0.11.0 identity was derived from the display name * (`slugifyRoleKey(name)` server-side), which made every rename an identity * change — drop the template, re-create it under a new key. That is why the * operator's Title-Case standing rule kept getting lost: obeying it broke * identity. `key` decouples the two. */ key: string; /** DISPLAY-ONLY, Title Case, NEVER an identity (D654). Matching and * role_class stamping key on {@link StarterRoleDef.key}. A slug-shaped or * dotted value is rejected at authoring time with a targeted error telling * you to put the slug in `key`. */ name: string; description: string; permissionKeys: string[]; isDefaultForNewTenants?: boolean; isRecommended?: boolean; /** Role class (RBAC drive §5 / D576). Omitted ⇒ `'standard'`. */ roleClass?: RoleClass; /** Pricing/capacity dimension a seat role is bound to. REQUIRED iff * `roleClass === 'seat'`; MUST be absent otherwise (validated). */ seatDimensionKey?: string; } /** Type-level key construction so callers get string literal narrowing. */ export type ConstructKey = Q extends string ? `${K}.${R}.${V}.${Q}` : `${K}.${R}.${V}`; /** Output shape of definePermissions — typed const map with `.key` field. */ export type DefinedPermissionMap> = { readonly [Name in keyof T]: { readonly key: ConstructKey; readonly resource: T[Name]["resource"]; readonly verb: T[Name]["verb"]; readonly qualifier: T[Name]["qualifier"]; readonly isCritical: boolean; readonly description: string; readonly deprecatedInVersion?: string; readonly metadata?: Record; readonly entitlementKey: string; readonly assignmentPolicy?: AssignmentPolicy; readonly isCrossTenant?: boolean; }; }; /** Response from RoleCatalogService.PublishCatalog. */ export interface PublishCatalogResponse { accepted: boolean; /** DERIVED "this publish changed nothing" signal, NOT an idempotency * short-circuit — the server synthesises it from a zero-diff after running * the full pipeline. There is no version-keyed cache (drift DR-5). */ cachedResponse: boolean; publishedAt: Date; newPermissionCount: number; unchangedPermissionCount: number; deprecatedPermissionCount: number; affectedTenantsCount: number; catalogId: string; /** Wire field 20 (D657). Keys whose definition CHANGED in this publish. */ updatedPermissionCount: number; /** Wire field 21 (D657). Non-empty ⇒ the peer silently dropped keys for * grammar/namespace violations while still answering `accepted: true`. * {@link RoleCatalogClient.publishCatalog} converts a non-empty list into a * thrown {@link CatalogPublishNamespaceRejected}, so this array is `[]` on * every response a caller actually receives. It is decoded and surfaced so * the failure is legible, never so it can be ignored. */ namespaceValidationErrors: string[]; } /** What a catalog-publish FAILURE does to the booting process. * * `"degrade"` is the DEFAULT and the D410(3)-mandated posture: "all * module-services wire publishCatalogAtBoot (best-effort, non-boot-blocking)". * `"throw"` is the explicit opt-in for a caller that genuinely wants the * pre-0.9.0 crash-on-boot behaviour. */ export type CatalogPublishFailurePolicy = "degrade" | "throw"; /** Lifecycle of one domain's catalog publish within THIS process. */ export type CatalogPublishStatus = /** No attempt has completed yet. */ "pending" /** The catalog is live in tenant-service. */ | "published" /** At least one attempt FAILED. The domain's permission keys do NOT resolve; * guarded routes 403 (fail-CLOSED). Retry may still be in flight. */ | "degraded"; /** Durable, queryable publish state for one domain key. * * EXISTS SO A DEGRADED CATALOG IS NOT INVISIBLE. Degrading a failure into a * log line and nothing else would make a service with no catalog look * identical to a healthy one. Surface this from `/health` (or equivalent) so * the condition is observable without scraping boot logs. */ export interface CatalogPublishState { status: CatalogPublishStatus; domainKey: string; catalogVersion: string; /** Completed attempts, boot + background. */ attempts: number; lastAttemptAt: string | null; publishedAt: string | null; /** `Name: message` of the most recent failure. Never carries the payload or * any credential — only the error's own name + message. */ lastError: string | null; /** True once the failure is known to be unfixable WITHOUT a redeploy (a local * catalog-authoring error). Background retry is NOT scheduled for these, * because re-sending an identical malformed catalog cannot ever succeed. */ terminal: boolean; /** True while a background retry ladder or refresh timer is still armed. */ retrying: boolean; /** The successful response, when `status === "published"`. */ response: PublishCatalogResponse | null; } /** Outcome label set used by the telemetry counter. */ export type PublishOutcome = "success" | "failed_precondition" | "unavailable" | "timed_out" | "validation_error" | "other"; /** ⚠️ TELEMETRY LABEL SET — these strings are emitted verbatim as the label on * `TelemetrySink.recordValidationFailure`, i.e. they land in metrics and are * what dashboards / alerts key on. * * 0.7.0 renamed `"invalid_module_key"` -> `"invalid_domain_key"`. A panel or * alert filtering on the OLD literal goes SILENTLY DEAD — it keeps rendering, * it just matches nothing, which reads identically to "no failures". There is * no error, no log line, no CI signal for this. It must be migrated by hand. * * The old literal is RETAINED in this union (deprecated, never emitted by * default) so a consumer's exhaustive `switch (kind)` keeps compiling instead * of hard-failing on a removed union member. Services with live dashboards can * set `emitLegacyValidationFailureKind: true` on publishCatalogAtBoot to emit * BOTH labels while the panels are migrated — see PublishCatalogAtBootOpts. */ export type ValidationFailureKind = "reserved_namespace" | "invalid_segment" | "invalid_segment_count" | "invalid_semver" | "invalid_domain_key" /** @deprecated 0.7.0 renamed this label to `"invalid_domain_key"`. Never * emitted unless `emitLegacyValidationFailureKind` is set. Removed in 1.0.0. */ | "invalid_module_key" | "starter_role_unknown_permission" | "starter_role_wildcard_mismatch" | "starter_role_wildcard_not_allowed" | "starter_role_class_invalid" | "duplicate_permission_key" | "catalog_too_large" | "substrate_namespace" | "invalid_entitlement_key" | "starter_role_invalid_key" | "starter_role_name_not_title_case" | "starter_role_duplicate_key" /** The peer answered `accepted: true` but returned a non-empty * `namespace_validation_errors` (wire field 21) — a partial rejection that * read as success before 0.11.0 (D657). */ | "namespace_validation_rejected" /** 0.14.0 (drift `55b2e60f`) — ADDITIVE, like the 0.11.0 block above. A * starter role in the PUBLISH payload carried a permission key from another * module's namespace. Distinct from `starter_role_wildcard_mismatch`, which * is the AUTHORING-layer label for the same condition, so a dashboard can * tell "an author was stopped by `defineStarterRoles`" apart from "a * hand-built payload was stopped at the wire". */ | "starter_role_namespace_violation"; //# sourceMappingURL=types.d.ts.map