{"version":3,"file":"index.cjs","names":["validatePolicy","validateRole"],"sources":["../../../src/core/builder/when.ts","../../../src/core/builder/rule.ts","../../../src/core/builder/policy.ts","../../../src/core/builder/role.ts"],"sourcesContent":["import type { AccessControl, DotPath, IamPrimitives } from '../types'\n/**\n * Fluent condition builder for duck-iam rules and role permissions.\n *\n * `When` accumulates a list of {@link AccessControl.ICondition} and nested {@link AccessControl.IConditionGroup}\n * items and then emits them as an `all` (AND), `any` (OR), or `none` (NOT) group\n * via the terminal build methods. It is used as the callback argument in\n * {@link RuleBuilder.when}, {@link RuleBuilder.whenAny}, and\n * {@link RoleBuilder.grantWhen}.\n *\n * @example\n * ```ts\n * // Inside a rule\n * defineRule('expense.approve')\n *   .allow()\n *   .on('approve').of('expense')\n *   .when(w => w\n *     .attr('department', 'eq', 'engineering')\n *     .resourceAttr('amount', 'lte', 10_000)\n *   )\n *\n * // Nested OR inside an AND\n * defineRule('post.edit')\n *   .allow()\n *   .on('update').of('post')\n *   .when(w => w\n *     .or(o => o.isOwner().role('admin'))\n *     .env('time', 'gte', 9)\n *   )\n * ```\n *\n * @template TAction         - Union of valid action strings\n * @template TResource       - Union of valid resource strings\n * @template TRole           - Union of valid role ID strings\n * @template TScope          - Union of valid scope strings\n * @template TContext        - Shape of the full evaluation context for typed dot-paths\n * @template TActiveResource - Resource narrowed by the parent `RuleBuilder.of()` (typed `resourceAttr`)\n */\nexport class When<\n  TAction extends string = string,\n  TResource extends string = string,\n  TRole extends string = string,\n  TScope extends string = string,\n  TContext extends object = DotPath.IDefaultContext,\n  TActiveResource extends string = string,\n> {\n  private _items: Array<AccessControl.ICondition | AccessControl.IConditionGroup> = []\n\n  /**\n   * Appends a raw {@link AccessControl.ICondition} to the builder with fully typed dot-path\n   * field access. The `field` parameter is constrained to valid paths within\n   * `TContext`, and `value` is inferred from the type at that path.\n   *\n   * @param field - Dot-path to the attribute being tested (e.g. `'subject.attributes.tier'`)\n   * @param op    - The {@link AccessControl.Operator} to apply\n   * @param value - The right-hand side value (omit for `exists`)\n   * @returns `this` for chaining\n   *\n   * @example\n   * ```ts\n   * w.check('subject.attributes.status', 'eq', 'banned')   // OK\n   * w.check('environment.hour', 'gte', 9)                  // OK\n   * w.check('resource.attributes.status', 'eq', 'deleted') // ERROR if 'deleted' not in type\n   * w.check('subject.attributes.age', 'eq', 30)            // ERROR if 'age' not in type\n   * ```\n   * @returns `this` for chaining.\n   */\n  check<P extends DotPath.FlexibleDotPaths<TContext>>(\n    field: P,\n    op: AccessControl.Operator,\n    value?: DotPath.FieldValue<TContext, P> | DotPath.FlexibleDollarPaths<TContext>,\n  ): this {\n    this._items.push({ field, operator: op, value })\n    return this\n  }\n\n  /**\n   * Asserts `field == value`.\n   *\n   * @param field - Typed dot-path attribute path\n   * @param value - Expected value (inferred from path type)\n   * @returns `this` for chaining\n   */\n  eq<P extends DotPath.FlexibleDotPaths<TContext>>(\n    field: P,\n    value: DotPath.FieldValue<TContext, P> | DotPath.FlexibleDollarPaths<TContext>,\n  ): this {\n    this._items.push({ field, operator: 'eq', value })\n    return this\n  }\n\n  /**\n   * Asserts `field != value`.\n   *\n   * @param field - Typed dot-path attribute path\n   * @param value - Value the field must not equal\n   * @returns `this` for chaining\n   */\n  neq<P extends DotPath.FlexibleDotPaths<TContext>>(\n    field: P,\n    value: DotPath.FieldValue<TContext, P> | DotPath.FlexibleDollarPaths<TContext>,\n  ): this {\n    this._items.push({ field, operator: 'neq', value })\n    return this\n  }\n\n  /**\n   * Asserts `field` is one of the given `values`.\n   *\n   * @param field  - Typed dot-path attribute path\n   * @param values - Array of acceptable values\n   * @returns `this` for chaining\n   */\n  in<P extends DotPath.FlexibleDotPaths<TContext>>(\n    field: P,\n    values: Array<DotPath.FieldValue<TContext, P> | DotPath.FlexibleDollarPaths<TContext>>,\n  ): this {\n    this._items.push({ field, operator: 'in', value: values as IamPrimitives.AttributeValue })\n    return this\n  }\n\n  /**\n   * Asserts that the array at `field` contains `value`.\n   *\n   * Commonly used to check role membership:\n   * `w.contains('subject.roles', 'admin')`.\n   *\n   * @param field - Typed dot-path attribute path pointing to an array\n   * @param value - The value that must be present in the array\n   * @returns `this` for chaining\n   */\n  contains<P extends DotPath.FlexibleDotPaths<TContext>>(field: P, value: string): this {\n    this._items.push({ field, operator: 'contains', value })\n    return this\n  }\n\n  /**\n   * Asserts that `field` exists (is defined and non-null).\n   *\n   * @param field - Typed dot-path attribute path to check for existence\n   * @returns `this` for chaining\n   */\n  exists<P extends DotPath.FlexibleDotPaths<TContext>>(field: P): this {\n    this._items.push({ field, operator: 'exists' })\n    return this\n  }\n\n  /**\n   * Asserts `field > value`.\n   *\n   * @param field - Typed dot-path attribute path\n   * @param value - Numeric lower bound (exclusive)\n   * @returns `this` for chaining\n   */\n  gt<P extends DotPath.FlexibleDotPaths<TContext>>(field: P, value: number): this {\n    this._items.push({ field, operator: 'gt', value })\n    return this\n  }\n\n  /**\n   * Asserts `field >= value`.\n   *\n   * @param field - Typed dot-path attribute path\n   * @param value - Numeric lower bound (inclusive)\n   * @returns `this` for chaining\n   */\n  gte<P extends DotPath.FlexibleDotPaths<TContext>>(field: P, value: number): this {\n    this._items.push({ field, operator: 'gte', value })\n    return this\n  }\n\n  /**\n   * Asserts `field < value`.\n   *\n   * @param field - Typed dot-path attribute path\n   * @param value - Numeric upper bound (exclusive)\n   * @returns `this` for chaining\n   */\n  lt<P extends DotPath.FlexibleDotPaths<TContext>>(field: P, value: number): this {\n    this._items.push({ field, operator: 'lt', value })\n    return this\n  }\n\n  /**\n   * Asserts `field <= value`.\n   *\n   * @param field - Typed dot-path attribute path\n   * @param value - Numeric upper bound (inclusive)\n   * @returns `this` for chaining\n   */\n  lte<P extends DotPath.FlexibleDotPaths<TContext>>(field: P, value: number): this {\n    this._items.push({ field, operator: 'lte', value })\n    return this\n  }\n\n  /**\n   * Asserts that `field` matches the given regular expression string.\n   *\n   * @param field - Typed dot-path attribute path\n   * @param regex - Regular expression pattern (as a string)\n   * @returns `this` for chaining\n   */\n  matches<P extends DotPath.FlexibleDotPaths<TContext>>(field: P, regex: string): this {\n    this._items.push({ field, operator: 'matches', value: regex })\n    return this\n  }\n\n  /**\n   * Asserts the subject holds the given role.\n   *\n   * Equivalent to `w.contains('subject.roles', roleId)`.\n   *\n   * @param roleId - The role ID that must be present in `subject.roles`\n   * @returns `this` for chaining\n   */\n  role(roleId: TRole): this {\n    this._items.push({ field: 'subject.roles', operator: 'contains', value: roleId })\n    return this\n  }\n\n  /**\n   * Asserts the subject holds at least one of the given roles.\n   *\n   * Equivalent to `w.check('subject.roles', 'in', roleIds)`.\n   *\n   * @param roleIds - Role IDs to test membership against\n   * @returns `this` for chaining\n   */\n  roles(...roleIds: TRole[]): this {\n    this._items.push({ field: 'subject.roles', operator: 'in', value: roleIds as string[] })\n    return this\n  }\n\n  /**\n   * Asserts the request is made within a specific scope.\n   *\n   * Equivalent to `w.check('scope', 'eq', id)`.\n   *\n   * @param id - The scope the request must be in\n   * @returns `this` for chaining\n   */\n  scope(id: TScope): this {\n    this._items.push({ field: 'scope', operator: 'eq', value: id })\n    return this\n  }\n\n  /**\n   * Asserts the request is made within one of the given scopes.\n   *\n   * Equivalent to `w.check('scope', 'in', ids)`.\n   *\n   * @param ids - Acceptable scope IDs\n   * @returns `this` for chaining\n   */\n  scopes(...ids: TScope[]): this {\n    this._items.push({ field: 'scope', operator: 'in', value: ids as string[] })\n    return this\n  }\n\n  /**\n   * Asserts the subject is the owner of the resource.\n   *\n   * Checks that `ownerField` equals the special variable `'$subject.id'`,\n   * which the engine resolves to the current subject's ID at evaluation time.\n   *\n   * @example\n   * ```ts\n   * // Using the default owner field\n   * w.isOwner()\n   *\n   * // Custom owner field\n   * w.isOwner('resource.attributes.createdBy')\n   * ```\n   *\n   * @param ownerField - Dot-path to the owner attribute on the resource.\n   *   Defaults to `'resource.attributes.ownerId'`.\n   * @returns `this` for chaining\n   */\n  isOwner(\n    ownerField: DotPath.FlexibleDotPaths<TContext> = 'resource.attributes.ownerId' as DotPath.FlexibleDotPaths<TContext>,\n  ): this {\n    this._items.push({ field: ownerField, operator: 'eq', value: '$subject.id' })\n    return this\n  }\n\n  /**\n   * Asserts the resource's type is one of the given values.\n   *\n   * Equivalent to `w.check('resource.type', 'in', types)`.\n   *\n   * @param types - Acceptable resource type strings\n   * @returns `this` for chaining\n   */\n  resourceType(...types: (TResource | '*')[]): this {\n    this._items.push({ field: 'resource.type', operator: 'in', value: types as string[] })\n    return this\n  }\n\n  /**\n   * Asserts a subject attribute at the given path.\n   *\n   * Prefixes `path` with `'subject.attributes.'` automatically.\n   *\n   * @example\n   * ```ts\n   * w.attr('department', 'eq', 'engineering')\n   * // evaluates: subject.attributes.department == 'engineering'\n   * ```\n   *\n   * @param path  - Typed attribute key under `subject.attributes`\n   * @param op    - The {@link AccessControl.Operator} to apply\n   * @param value - Right-hand side value (inferred from type)\n   * @returns `this` for chaining\n   */\n  attr<K extends DotPath.SubjectAttrs<TContext> & string>(\n    path: K,\n    op: AccessControl.Operator,\n    value?:\n      | DotPath.ConditionValue<TContext, DotPath.AttrValue<DotPath.SubjectAttrShape<TContext>, K>>\n      | DotPath.FlexibleDollarPaths<TContext>,\n  ): this {\n    this._items.push({ field: `subject.attributes.${path}`, operator: op, value })\n    return this\n  }\n\n  /**\n   * Asserts a resource attribute at the given path.\n   *\n   * Prefixes `path` with `'resource.attributes.'` automatically.\n   *\n   * @example\n   * ```ts\n   * w.resourceAttr('status', 'eq', 'published')\n   * // evaluates: resource.attributes.status == 'published'\n   * ```\n   *\n   * @param path  - Typed attribute key under `resource.attributes`\n   * @param op    - The {@link AccessControl.Operator} to apply\n   * @param value - Right-hand side value (inferred from type)\n   * @returns `this` for chaining\n   */\n  resourceAttr<K extends DotPath.ResolvedResourceAttrPaths<TContext, TActiveResource> & string>(\n    path: K,\n    op: AccessControl.Operator,\n    value?:\n      | DotPath.ConditionValue<TContext, DotPath.AttrValue<DotPath.ResolvedResourceAttrs<TContext, TActiveResource>, K>>\n      | DotPath.FlexibleDollarPaths<TContext>,\n  ): this {\n    this._items.push({ field: `resource.attributes.${path}`, operator: op, value })\n    return this\n  }\n\n  /**\n   * Asserts an environment attribute at the given path.\n   *\n   * Prefixes `path` with `'environment.'` automatically. Useful for\n   * time-based or context-based conditions.\n   *\n   * @example\n   * ```ts\n   * w.env('hour', 'gte', 9).env('hour', 'lte', 17)\n   * // evaluates: environment.hour >= 9 AND environment.hour <= 17\n   * ```\n   *\n   * @param path  - Typed attribute key under `environment`\n   * @param op    - The {@link AccessControl.Operator} to apply\n   * @param value - Right-hand side value (inferred from type)\n   * @returns `this` for chaining\n   */\n  env<K extends DotPath.EnvAttrs<TContext> & string>(\n    path: K,\n    op: AccessControl.Operator,\n    value?:\n      | DotPath.ConditionValue<TContext, DotPath.AttrValue<DotPath.EnvAttrShape<TContext>, K>>\n      | DotPath.FlexibleDollarPaths<TContext>,\n  ): this {\n    this._items.push({ field: `environment.${path}`, operator: op, value })\n    return this\n  }\n\n  /**\n   * Appends a nested ALL-of (AND) condition group.\n   *\n   * Every condition added inside the callback must hold. The nested group is\n   * treated as a single item within the outer builder's condition list.\n   *\n   * @example\n   * ```ts\n   * w.and(a => a.attr('tier', 'eq', 'premium').env('region', 'eq', 'us'))\n   * ```\n   *\n   * @param fn - Callback that receives a nested {@link When} and returns it\n   * @returns `this` for chaining\n   */\n  and(\n    fn: (\n      w: When<TAction, TResource, TRole, TScope, TContext, TActiveResource>,\n    ) => When<TAction, TResource, TRole, TScope, TContext, TActiveResource>,\n  ): this {\n    const nested = new When<TAction, TResource, TRole, TScope, TContext, TActiveResource>()\n    fn(nested)\n    this._items.push(nested.buildAll())\n    return this\n  }\n\n  /**\n   * Appends a nested ANY-of (OR) condition group.\n   *\n   * At least one condition inside the callback must hold.\n   *\n   * @example\n   * ```ts\n   * w.or(o => o.isOwner().role('admin'))\n   * // passes if subject is owner OR has the admin role\n   * ```\n   *\n   * @param fn - Callback that receives a nested {@link When} and returns it\n   * @returns `this` for chaining\n   */\n  or(\n    fn: (\n      w: When<TAction, TResource, TRole, TScope, TContext, TActiveResource>,\n    ) => When<TAction, TResource, TRole, TScope, TContext, TActiveResource>,\n  ): this {\n    const nested = new When<TAction, TResource, TRole, TScope, TContext, TActiveResource>()\n    fn(nested)\n    this._items.push(nested.buildAny())\n    return this\n  }\n\n  /**\n   * Appends a nested NONE-of (NOT) condition group.\n   *\n   * None of the conditions inside the callback may hold. Equivalent to\n   * negating an OR group.\n   *\n   * @example\n   * ```ts\n   * w.not(n => n.attr('status', 'eq', 'banned'))\n   * // passes if subject.attributes.status is NOT 'banned'\n   * ```\n   *\n   * @param fn - Callback that receives a nested {@link When} and returns it\n   * @returns `this` for chaining\n   */\n  not(\n    fn: (\n      w: When<TAction, TResource, TRole, TScope, TContext, TActiveResource>,\n    ) => When<TAction, TResource, TRole, TScope, TContext, TActiveResource>,\n  ): this {\n    const nested = new When<TAction, TResource, TRole, TScope, TContext, TActiveResource>()\n    fn(nested)\n    this._items.push(nested.buildNone())\n    return this\n  }\n\n  /**\n   * Emits the accumulated conditions as an ALL-of (`{ all: [...] }`) group.\n   *\n   * Every condition in the list must hold. This is the default used by\n   * {@link RuleBuilder.when} and {@link RoleBuilder.grantWhen}.\n   *\n   * @returns A readonly `all` condition group\n   */\n  buildAll(): { readonly all: ReadonlyArray<AccessControl.ICondition | AccessControl.IConditionGroup> } {\n    return { all: this._items }\n  }\n\n  /**\n   * Emits the accumulated conditions as an ANY-of (`{ any: [...] }`) group.\n   *\n   * At least one condition in the list must hold. Used by\n   * {@link RuleBuilder.whenAny}.\n   *\n   * @returns A readonly `any` condition group\n   */\n  buildAny(): { readonly any: ReadonlyArray<AccessControl.ICondition | AccessControl.IConditionGroup> } {\n    return { any: this._items }\n  }\n\n  /**\n   * Emits the accumulated conditions as a NONE-of (`{ none: [...] }`) group.\n   *\n   * None of the conditions in the list may hold. Produced by the {@link not}\n   * nesting helper.\n   *\n   * @returns A readonly `none` condition group\n   */\n  buildNone(): { readonly none: ReadonlyArray<AccessControl.ICondition | AccessControl.IConditionGroup> } {\n    return { none: this._items }\n  }\n}\n\n/**\n * Creates a standalone {@link When} condition builder.\n *\n * Useful when you need to construct a {@link AccessControl.IConditionGroup} outside of a\n * rule or role builder - for example, to build a reusable condition and\n * spread it across multiple rules.\n *\n * @example\n * ```ts\n * import { when } from '@gentleduck/iam'\n *\n * const ownerOrAdmin = when()\n *   .or(o => o.isOwner().role('admin'))\n *   .buildAll()\n * ```\n *\n * @returns A new {@link When} instance\n *\n * @template TAction         - Union of valid action strings\n * @template TResource       - Union of valid resource strings\n * @template TRole           - Union of valid role ID strings\n * @template TScope          - Union of valid scope strings\n * @template TContext        - Shape of the full evaluation context for typed dot-paths\n * @template TActiveResource - Resource narrowed by the parent `RuleBuilder.of()` (typed `resourceAttr`)\n */\nexport const when = <\n  TAction extends string = string,\n  TResource extends string = string,\n  TScope extends string = string,\n  TRole extends string = string,\n  TContext extends object = DotPath.IDefaultContext,\n  TActiveResource extends string = string,\n>() => new When<TAction, TResource, TRole, TScope, TContext, TActiveResource>()\n","import type { AccessControl, DotPath, IamPrimitives } from '../types'\nimport { When } from './when'\n\n/**\n * Fluent builder for constructing {@link AccessControl.IRule} objects in duck-iam.\n *\n * Rules are the atomic unit of an ABAC policy. Each rule declares an effect\n * (`allow` or `deny`), the actions and resources it covers, an optional scope\n * restriction, and an optional condition tree that must hold for the rule to\n * fire.\n *\n * Rules are collected into a {@link PolicyBuilder} and evaluated by the engine\n * using the policy's chosen conflict-resolution algorithm\n * (`allow-overrides`, `deny-overrides`, `first-match`, or `highest-priority`).\n *\n * @example\n * ```ts\n * import { defineRule } from '@gentleduck/iam'\n *\n * const rule = defineRule('post.update.owner')\n *   .allow()\n *   .desc('Authors may update their own posts')\n *   .priority(20)\n *   .on('update')\n *   .of('post')\n *   .when(w => w.isOwner())\n *   .build()\n * ```\n *\n * @template TAction         - Union of valid action strings (e.g. `'read' | 'write'`)\n * @template TResource       - Union of valid resource strings (e.g. `'post' | 'comment'`)\n * @template TScope          - Union of valid scope strings (e.g. `'org-1' | 'org-2'`)\n * @template TRole           - Union of valid role ID strings (e.g. `'viewer' | 'admin'`)\n * @template TContext        - Shape of the full evaluation context for typed dot-paths\n * @template TActiveResource - The narrowed resource selected via `.of()` (used by typed `resourceAttr`)\n */\nexport class RuleBuilder<\n  TAction extends string = string,\n  TResource extends string = string,\n  TScope extends string = string,\n  TRole extends string = string,\n  TContext extends object = DotPath.IDefaultContext,\n  TActiveResource extends string = string,\n> {\n  private _id: string\n  private _effect: AccessControl.Effect = 'allow'\n  private _description?: string\n  private _priority = 10\n  private _actions: (TAction | '*')[] = ['*']\n  private _resources: (TResource | '*')[] = ['*']\n  private _conditions: AccessControl.IConditionGroup = { all: [] }\n  private _metadata?: IamPrimitives.Attributes\n  private _scopeCondition?: AccessControl.ICondition\n\n  constructor(id: string) {\n    this._id = id\n  }\n\n  /**\n   * Sets the rule effect to `allow`.\n   *\n   * This is the default effect - you only need to call this explicitly when\n   * overriding a previous `.deny()` call on the same builder instance.\n   *\n   * @returns `this` for chaining\n   */\n  allow(): this {\n    this._effect = 'allow'\n    return this\n  }\n\n  /**\n   * Sets the rule effect to `deny`.\n   *\n   * Deny rules take precedence over allow rules when the policy algorithm is\n   * `deny-overrides`. Under `allow-overrides` a deny only wins if no allow\n   * rule matches.\n   *\n   * @returns `this` for chaining\n   */\n  deny(): this {\n    this._effect = 'deny'\n    return this\n  }\n\n  /**\n   * Attaches a human-readable description to the rule.\n   *\n   * Descriptions are stored on the {@link AccessControl.IRule} object and surfaced by the\n   * engine's explain/debug output. They have no effect on evaluation.\n   *\n   * @param d - Description text\n   * @returns `this` for chaining\n   */\n  desc(d: string): this {\n    this._description = d\n    return this\n  }\n\n  /**\n   * Sets the rule's evaluation priority.\n   *\n   * Higher numbers are evaluated first. The default priority is `10`.\n   * Priority matters when the policy algorithm is `highest-priority` - the\n   * matching rule with the highest priority number wins.\n   *\n   * @param p - Priority value (higher = evaluated earlier)\n   * @returns `this` for chaining\n   */\n  priority(p: number): this {\n    this._priority = p\n    return this\n  }\n\n  /**\n   * Declares the actions this rule applies to.\n   *\n   * Pass `'*'` to match all actions. Accepts multiple arguments.\n   *\n   * @example\n   * ```ts\n   * defineRule('post.read-write')\n   *   .on('read', 'update')\n   *   .of('post')\n   * ```\n   *\n   * @param actions - One or more action strings, or `'*'` for all actions\n   * @returns `this` for chaining\n   */\n  on(...actions: (TAction | '*')[]): this {\n    this._actions = actions\n    return this\n  }\n\n  /**\n   * Declares the resources this rule applies to.\n   *\n   * Pass `'*'` to match all resources. Accepts multiple arguments.\n   *\n   * @example\n   * ```ts\n   * defineRule('content.read')\n   *   .on('read')\n   *   .of('post', 'comment')\n   * ```\n   *\n   * @param resources - One or more resource strings, or `'*'` for all resources\n   * @returns `this` for chaining\n   */\n  of<R extends TResource | '*'>(...resources: R[]): RuleBuilder<TAction, TResource, TScope, TRole, TContext, R> {\n    this._resources = resources\n    /** : This cast to get intellisense working for the specified resource type */\n    return this as unknown as RuleBuilder<TAction, TResource, TScope, TRole, TContext, R>\n  }\n\n  /**\n   * Restricts this rule to one or more scopes.\n   *\n   * A scope typically represents a tenant, organization, or workspace.\n   * When a scope is set, the engine only fires the rule when the request's\n   * scope matches. Passing `'*'` is a no-op - use no scope restriction for\n   * global rules instead.\n   *\n   * Scope conditions compose correctly with `.when()` and `.whenAny()`.\n   *\n   * @example\n   * ```ts\n   * defineRule('org1.post.update')\n   *   .allow()\n   *   .on('update')\n   *   .of('post')\n   *   .forScope('org-1')\n   * ```\n   *\n   * @param scopes - One or more scope strings to restrict this rule to\n   * @returns `this` for chaining\n   */\n  forScope(...scopes: (TScope | '*')[]): this {\n    const nonWild = scopes.filter((s): s is TScope => s !== '*')\n    if (nonWild.length === 0) return this\n    this._scopeCondition =\n      nonWild.length === 1\n        ? { field: 'scope', operator: 'eq', value: nonWild[0] }\n        : { field: 'scope', operator: 'in', value: nonWild }\n    return this\n  }\n\n  /**\n   * Attaches an ALL-of condition group to the rule using a {@link When} builder.\n   *\n   * Every condition added inside the callback must hold (`AND` semantics) for\n   * the rule to match. Composes with `.forScope()` - the scope check is\n   * prepended to the condition list automatically at build time.\n   *\n   * @example\n   * ```ts\n   * defineRule('expense.approve')\n   *   .allow()\n   *   .on('approve')\n   *   .of('expense')\n   *   .when(w => w\n   *     .attr('department', 'eq', 'engineering')\n   *     .resourceAttr('amount', 'lte', 10000)\n   *   )\n   * ```\n   *\n   * @param fn - Callback that receives a {@link When} builder and returns it after chaining conditions\n   * @returns `this` for chaining\n   */\n  when(\n    fn: (\n      w: When<TAction, TResource, TRole, TScope, TContext, TActiveResource>,\n    ) => When<TAction, TResource, TRole, TScope, TContext, TActiveResource>,\n  ): this {\n    const w = new When<TAction, TResource, TRole, TScope, TContext, TActiveResource>()\n    fn(w)\n    this._conditions = w.buildAll()\n    return this\n  }\n\n  /**\n   * Attaches an ANY-of condition group to the rule using a {@link When} builder.\n   *\n   * At least one condition added inside the callback must hold (`OR` semantics)\n   * for the rule to match.\n   *\n   * @example\n   * ```ts\n   * defineRule('post.manage')\n   *   .allow()\n   *   .on('update', 'delete')\n   *   .of('post')\n   *   .whenAny(w => w\n   *     .isOwner()\n   *     .attr('role', 'eq', 'admin')\n   *   )\n   * ```\n   *\n   * @param fn - Callback that receives a {@link When} builder and returns it after chaining conditions\n   * @returns `this` for chaining\n   */\n  whenAny(\n    fn: (\n      w: When<TAction, TResource, TRole, TScope, TContext, TActiveResource>,\n    ) => When<TAction, TResource, TRole, TScope, TContext, TActiveResource>,\n  ): this {\n    const w = new When<TAction, TResource, TRole, TScope, TContext, TActiveResource>()\n    fn(w)\n    this._conditions = w.buildAny()\n    return this\n  }\n\n  /**\n   * Attaches arbitrary metadata to the rule.\n   *\n   * Metadata is stored on the {@link AccessControl.IRule} object but is never used during\n   * policy evaluation. Use it for audit logs, admin dashboards, or any\n   * application-level bookkeeping.\n   *\n   * @param m - Key-value map of metadata attributes\n   * @returns `this` for chaining\n   */\n  meta(m: IamPrimitives.Attributes): this {\n    this._metadata = m\n    return this\n  }\n\n  /**\n   * Finalises the builder and returns a plain {@link AccessControl.IRule} object.\n   *\n   * Any scope condition set via `.forScope()` is merged into the condition\n   * group here so that `.forScope()` and `.when()` / `.whenAny()` always\n   * compose correctly regardless of call order.\n   *\n   * @returns A fully constructed, immutable {@link AccessControl.IRule}\n   */\n  build(): AccessControl.IRule<TAction, TResource> {\n    let conditions = this._conditions\n\n    if (this._scopeCondition) {\n      conditions = {\n        all: 'all' in conditions ? [this._scopeCondition, ...conditions.all] : [this._scopeCondition, conditions],\n      }\n    }\n\n    return {\n      id: this._id,\n      effect: this._effect,\n      description: this._description,\n      priority: this._priority,\n      actions: this._actions,\n      resources: this._resources,\n      conditions,\n      metadata: this._metadata,\n    }\n  }\n}\n\n/**\n * Creates a new {@link RuleBuilder} for the given rule ID.\n *\n * Prefer this factory over instantiating `RuleBuilder` directly. When using\n * `createIam`, use `access.defineRule()` instead to get type-safe\n * action, resource, and scope constraints.\n *\n * @example\n * ```ts\n * import { defineRule } from '@gentleduck/iam'\n *\n * const rule = defineRule('post.read')\n *   .allow()\n *   .on('read')\n *   .of('post')\n *   .build()\n * ```\n *\n * @param id - Unique identifier for this rule within its policy\n * @returns A new {@link RuleBuilder} instance\n *\n * @template TAction   - Union of valid action strings\n * @template TResource - Union of valid resource strings\n * @template TScope    - Union of valid scope strings\n * @template TRole     - Union of valid role ID strings\n * @template TContext  - Shape of the full evaluation context for typed dot-paths\n */\nexport const defineRule = <\n  TAction extends string = string,\n  TResource extends string = string,\n  TScope extends string = string,\n  TRole extends string = string,\n  TContext extends object = DotPath.IDefaultContext,\n>(\n  id: string,\n) => new RuleBuilder<TAction, TResource, TScope, TRole, TContext>(id)\n","import type { AccessControl, DotPath } from '../types'\nimport { validatePolicy } from '../validate'\nimport { RuleBuilder } from './rule'\n\n/**\n * Fluent builder for constructing ABAC {@link AccessControl.IPolicy} objects.\n *\n * Policies define attribute-based access control rules that go beyond simple\n * role-permission mappings. Use them for time-based restrictions, IP/geo-fencing,\n * cross-attribute checks, dynamic deny rules, and maintenance-mode guards.\n *\n * The combining algorithm chosen via `.algorithm(...)` controls *intra*-policy\n * rule conflicts. Decisions across multiple policies are merged by the engine's\n * configured `policyCombine` (defaults to `'and'`; see {@link AccessControl.PolicyCombine}).\n *\n * @template TAction   - Union of valid action strings.\n * @template TResource - Union of valid resource strings.\n * @template TRole     - Union of valid role strings.\n * @template TScope    - Union of valid scope strings.\n * @template TContext  - Shape of the full evaluation context for typed dot-paths.\n *\n * @example\n * ```typescript\n * import { definePolicy } from '@gentleduck/iam'\n *\n * const weekendDeny = definePolicy('deny-weekends')\n *   .name('Deny on Weekends')\n *   .desc('Block all write operations on weekends')\n *   .version(1)\n *   .algorithm('deny-overrides')\n *   .rule('r-deny-weekends', r => r\n *     .deny()\n *     .on('create', 'update', 'delete')\n *     .of('*')\n *     .when(w => w.env('dayOfWeek', 'in', [0, 6]))\n *   )\n *   .build()\n * ```\n */\nexport class PolicyBuilder<\n  TAction extends string = string,\n  TResource extends string = string,\n  TRole extends string = string,\n  TScope extends string = string,\n  TContext extends object = DotPath.IDefaultContext,\n> {\n  private _id: string\n  private _name: string\n  private _description?: string\n  private _algorithm: AccessControl.CombiningAlgorithm = 'deny-overrides'\n  private _rules: AccessControl.IRule<TAction, TResource>[] = []\n  private _targets?: AccessControl.IPolicy<TAction, TResource, TRole>['targets']\n  private _version?: number\n\n  constructor(id: string) {\n    this._id = id\n    this._name = id\n  }\n\n  /**\n   * Sets a human-readable name for the policy.\n   *\n   * Defaults to the policy `id` if not called.\n   *\n   * @param n - Display name.\n   * @returns `this` for chaining.\n   */\n  name(n: string): this {\n    this._name = n\n    return this\n  }\n\n  /**\n   * Sets an optional description for the policy.\n   *\n   * @param d - Description text.\n   * @returns `this` for chaining.\n   */\n  desc(d: string): this {\n    this._description = d\n    return this\n  }\n\n  /**\n   * Sets a version number for tracking policy changes over time.\n   *\n   * @param v - Version number.\n   * @returns `this` for chaining.\n   */\n  version(v: number): this {\n    this._version = v\n    return this\n  }\n\n  /**\n   * Sets the combining algorithm used to resolve conflicts between rules\n   * within this policy.\n   *\n   * | Algorithm | Behavior |\n   * |---|---|\n   * | `deny-overrides` | Any deny wins. Default. Best for restriction policies. |\n   * | `allow-overrides` | Any allow wins. Best for RBAC / permissive rules. |\n   * | `first-match` | First matching rule wins. Best for firewall-style ordered lists. |\n   * | `highest-priority` | Highest priority number wins. Best for emergency overrides. |\n   *\n   * Defaults to `'deny-overrides'`.\n   *\n   * @param a - Combining algorithm.\n   * @returns `this` for chaining.\n   */\n  algorithm(a: AccessControl.CombiningAlgorithm): this {\n    this._algorithm = a\n    return this\n  }\n\n  /**\n   * Scopes this policy to specific actions, resources, or roles.\n   *\n   * If an incoming request does not match all specified targets, the policy is\n   * skipped entirely - its rules are not evaluated. This is useful for\n   * restriction policies that only apply to a subset of operations.\n   *\n   * @param t - Target constraints to match against.\n   *\n   * @example\n   * ```typescript\n   * definePolicy('write-restrictions')\n   *   .target({\n   *     actions: ['create', 'update', 'delete'],\n   *     resources: ['post', 'comment'],\n   *   })\n   * ```\n   * @returns `this` for chaining.\n   */\n  target(t: NonNullable<AccessControl.IPolicy<TAction, TResource, TRole>['targets']>): this {\n    this._targets = t\n    return this\n  }\n\n  /**\n   * Adds a rule to the policy using an inline {@link RuleBuilder} callback.\n   *\n   * Rules are the individual allow/deny statements inside a policy. Each rule\n   * specifies an effect, the actions and resources it applies to, an optional\n   * priority, and attribute-based conditions.\n   *\n   * @param id - Unique identifier for the rule within this policy.\n   * @param fn - Builder callback that configures and returns the rule.\n   *\n   * @example\n   * ```typescript\n   * definePolicy('ip-guard')\n   *   .rule('block-bad-ips', r => r\n   *     .deny()\n   *     .on('*')\n   *     .of('*')\n   *     .when(w => w.env('ip', 'in', ['10.0.0.99', '10.0.0.100']))\n   *   )\n   * ```\n   * @returns `this` for chaining.\n   */\n  rule(\n    id: string,\n    fn: (\n      r: RuleBuilder<TAction, TResource, TScope, TRole, TContext>,\n    ) => RuleBuilder<TAction, TResource, TScope, TRole, TContext, any>,\n  ): this {\n    const builder = new RuleBuilder<TAction, TResource, TScope, TRole, TContext>(id)\n    fn(builder)\n    this._rules.push(builder.build())\n    return this\n  }\n\n  /**\n   * Adds a pre-built {@link AccessControl.IRule} object directly to the policy.\n   *\n   * Use this when you have rules defined separately via `defineRule` and want\n   * to compose them into a policy without the inline callback form.\n   *\n   * @param rule - A fully constructed `Rule` object.\n   *\n   * @example\n   * ```typescript\n   * import { defineRule } from '@gentleduck/iam'\n   *\n   * const denyDrafts = defineRule('deny-drafts')\n   *   .deny()\n   *   .on('read')\n   *   .of('post')\n   *   .when(w => w.resourceAttr('status', 'eq', 'draft'))\n   *   .build()\n   *\n   * definePolicy('post-access').addRule(denyDrafts)\n   * ```\n   * @returns `this` for chaining.\n   */\n  addRule(rule: AccessControl.IRule<TAction, TResource>): this {\n    this._rules.push(rule)\n    return this\n  }\n\n  /**\n   * Produces the final {@link AccessControl.IPolicy} object.\n   *\n   * Call this after all builder methods have been chained. The resulting object\n   * can be passed to an adapter or registered with the engine directly.\n   *\n   * @returns The constructed `Policy`.\n   */\n  build(): AccessControl.IPolicy<TAction, TResource, TRole> {\n    const policy: AccessControl.IPolicy<TAction, TResource, TRole> = {\n      id: this._id,\n      name: this._name,\n      description: this._description,\n      version: this._version,\n      algorithm: this._algorithm,\n      rules: this._rules,\n      targets: this._targets,\n    }\n    // IamValidate at build time so callers wiring the adapter directly\n    // (bypassing engine.admin.savePolicy's validator) still see the\n    // failure where the bug was introduced.\n    const result = validatePolicy(policy)\n    if (!result.valid) {\n      const errs = result.issues\n        .filter((i) => i.type === 'error')\n        .map((i) => (i.path ? `${i.code} at \"${i.path}\"` : i.code))\n      throw new Error(\n        `[@gentleduck/iam:builder] PolicyBuilder.build(): policy rejected by validator - ${errs.join('; ')}`,\n      )\n    }\n    return policy\n  }\n}\n\n/**\n * Creates a new {@link PolicyBuilder} for the given policy ID.\n *\n * This is the primary entry point for defining ABAC policies. Prefer this\n * factory over constructing `PolicyBuilder` directly. When using\n * `createIam`, use `access.definePolicy()` instead to get type-safe\n * action, resource, and role constraints.\n *\n * @template TAction   - Union of valid action strings.\n * @template TResource - Union of valid resource strings.\n * @template TRole     - Union of valid role strings.\n * @template TScope    - Union of valid scope strings.\n * @template TContext  - Shape of the full evaluation context for typed dot-paths.\n *\n * @param id - Unique identifier for the policy. Also used as the default name.\n * @returns A new `PolicyBuilder` instance.\n *\n * @example\n * ```typescript\n * import { definePolicy } from '@gentleduck/iam'\n *\n * const maintenanceMode = definePolicy('maintenance-mode')\n *   .name('Maintenance Mode')\n *   .desc('Deny all writes when the maintenance flag is active')\n *   .algorithm('deny-overrides')\n *   .rule('deny-writes', r => r\n *     .deny()\n *     .on('create', 'update', 'delete')\n *     .of('*')\n *     .when(w => w.env('maintenanceMode', 'eq', true))\n *   )\n *   .build()\n * ```\n */\nexport const definePolicy = <\n  TAction extends string = string,\n  TResource extends string = string,\n  TRole extends string = string,\n  TScope extends string = string,\n  TContext extends object = DotPath.IDefaultContext,\n>(\n  id: string,\n) => new PolicyBuilder<TAction, TResource, TRole, TScope, TContext>(id)\n","import type { AccessControl, DotPath, IamPrimitives } from '../types'\nimport { validateRole } from '../validate'\nimport { When } from './when'\n\n/**\n * Fluent builder for constructing {@link AccessControl.IRole} objects in duck-iam.\n *\n * Roles are the RBAC side of duck-iam. Each role holds a set of\n * action/resource permissions and an optional inheritance chain. At evaluation\n * time, `rolesToPolicy()` converts every role into ABAC rules that flow through\n * the same engine as hand-written policies, so RBAC and ABAC compose.\n *\n * Prefer the {@link defineRole} factory (or `access.defineRole()` for type-safe\n * variants) over instantiating `RoleBuilder` directly.\n *\n * @example\n * ```ts\n * import { defineRole } from '@gentleduck/iam'\n *\n * const editor = defineRole('editor')\n *   .name('Editor')\n *   .desc('Full write access to posts and comments')\n *   .inherits('viewer')\n *   .grant('create', 'post')\n *   .grant('update', 'post')\n *   .grant('delete', 'post')\n *   .grantCRUD('comment')\n *   .build()\n * ```\n *\n * @template TAction   - Union of valid action strings (e.g. `'read' | 'write'`)\n * @template TResource - Union of valid resource strings (e.g. `'post' | 'comment'`)\n * @template TRole       - Literal string type of the role ID (inferred by {@link defineRole})\n * @template TScope    - Union of valid scope strings (e.g. `'org-1' | 'org-2'`)\n * @template TContext  - Shape of the full evaluation context for typed dot-paths\n */\nexport class RoleBuilder<\n  TAction extends string = string,\n  TResource extends string = string,\n  TRole extends string = string,\n  TScope extends string = string,\n  TContext extends object = DotPath.IDefaultContext,\n> {\n  private _id: TRole\n  private _name: string\n  private _description?: string\n  private _permissions: AccessControl.IPermission<TAction, TResource, TScope>[] = []\n  private _inherits: (TRole | (string & {}))[] = []\n  private _scope?: TScope\n  private _metadata?: IamPrimitives.Attributes\n\n  constructor(id: TRole) {\n    this._id = id\n    this._name = id\n  }\n\n  /**\n   * Sets a human-readable display name for the role.\n   *\n   * Defaults to the role ID if not called. Used in admin dashboards,\n   * audit logs, and the engine's explain output.\n   *\n   * @param n - Display name (e.g. `'Content Editor'`)\n   * @returns `this` for chaining\n   */\n  name(n: string): this {\n    this._name = n\n    return this\n  }\n\n  /**\n   * Attaches a human-readable description to the role.\n   *\n   * Stored on the {@link AccessControl.IRole} object for documentation purposes.\n   * Not used during policy evaluation.\n   *\n   * @param d - Description text\n   * @returns `this` for chaining\n   */\n  desc(d: string): this {\n    this._description = d\n    return this\n  }\n\n  /**\n   * Declares parent roles this role inherits from.\n   *\n   * The role receives all permissions from every listed parent, resolved\n   * recursively. Multiple parents are supported. Inheritance cycles are\n   * handled safely via a visited set - cycles are skipped rather than\n   * causing infinite recursion.\n   *\n   * Note: inherited permissions cannot be selectively removed. To restrict\n   * access below what a parent grants, use an ABAC deny policy instead.\n   *\n   * @example\n   * ```ts\n   * // Single parent\n   * defineRole('editor').inherits('viewer')\n   *\n   * // Multiple parents\n   * defineRole('moderator').inherits('viewer', 'commenter')\n   * ```\n   *\n   * @param roleIds - IDs of the parent roles to inherit from\n   * @returns `this` for chaining\n   */\n  inherits(...roleIds: (TRole | (string & {}))[]): this {\n    this._inherits = roleIds\n    return this\n  }\n\n  /**\n   * Sets a default scope that applies to every permission in this role.\n   *\n   * When `rolesToPolicy()` converts this role, each generated rule gets an\n   * additional condition `scope eq \"<s>\"`. The permission only fires when the\n   * request's scope matches.\n   *\n   * To scope individual permissions rather than the entire role, use\n   * {@link grantScoped} instead.\n   *\n   * @example\n   * ```ts\n   * const orgEditor = defineRole('org-editor')\n   *   .scope('org-1')\n   *   .grant('create', 'post')\n   *   .grant('update', 'post')\n   *   .build()\n   * ```\n   *\n   * @param s - The scope string to restrict all permissions to\n   * @returns `this` for chaining\n   */\n  scope(s: TScope): this {\n    this._scope = s\n    return this\n  }\n\n  /**\n   * Grants a single unconditional permission on an action/resource pair.\n   *\n   * Pass `'*'` for either argument to match all actions or all resources.\n   * Pass an optional `scope` to restrict this permission to a specific scope\n   * (e.g. a tenant or workspace). Without a scope the permission is global.\n   *\n   * @example\n   * ```ts\n   * defineRole('viewer')\n   *   .grant('read', 'post')\n   *   .grant('read', 'comment')\n   *\n   * // With permission-level scope\n   * defineRole('hybrid')\n   *   .grant('read', 'post')                     // global\n   *   .grant('update', 'post', 'org-1')           // org-1 only\n   *   .grant('create', 'comment', 'org-2')        // org-2 only\n   * ```\n   *\n   * @param action   - The action to permit, or `'*'` for all actions\n   * @param resource - The resource to permit, or `'*'` for all resources\n   * @param scope    - Optional scope to restrict this permission to\n   * @returns `this` for chaining\n   */\n  grant(action: TAction | '*', resource: TResource | '*', scope?: TScope): this {\n    this._permissions.push(scope ? { action, resource, scope } : { action, resource })\n    return this\n  }\n\n  /**\n   * Grants a single permission restricted to a specific scope.\n   *\n   * Unlike {@link scope}, which scopes the entire role, `grantScoped` lets\n   * you mix global and scoped permissions within the same role.\n   *\n   * @example\n   * ```ts\n   * defineRole('hybrid')\n   *   .grant('read', 'post')                    // global\n   *   .grantScoped('org-1', 'update', 'post')   // org-1 only\n   *   .grantScoped('org-2', 'create', 'comment') // org-2 only\n   * ```\n   *\n   * @param scope    - The scope this permission is restricted to\n   * @param action   - The action to permit, or `'*'` for all actions\n   * @param resource - The resource to permit, or `'*'` for all resources\n   * @returns `this` for chaining\n   */\n  grantScoped(scope: TScope, action: TAction | '*', resource: TResource | '*'): this {\n    this._permissions.push({ action, resource, scope })\n    return this\n  }\n\n  /**\n   * Grants a permission that only applies when a condition holds.\n   *\n   * The callback receives a {@link When} builder. All conditions added inside\n   * the callback must hold simultaneously (`AND` semantics). Use\n   * `w.isOwner()` as a shorthand for checking `resource.attributes.ownerId eq $subject.id`.\n   *\n   * @example\n   * ```ts\n   * defineRole('author')\n   *   .grant('read', 'post')\n   *   .grantWhen('update', 'post', w => w.isOwner())\n   *   .grantWhen('delete', 'post', w => w.isOwner())\n   *\n   * // Complex condition\n   * defineRole('team-lead')\n   *   .grantWhen('approve', 'expense', w => w\n   *     .attr('department', 'eq', 'engineering')\n   *     .resourceAttr('amount', 'lte', 10000)\n   *   )\n   * ```\n   *\n   * @param action   - The action to permit conditionally\n   * @param resource - The resource to permit conditionally\n   * @param fn       - Callback that builds the condition using a {@link When} builder\n   * @returns `this` for chaining\n   */\n  grantWhen<R extends TResource | '*'>(\n    action: TAction | '*',\n    resource: R,\n    fn: (\n      w: When<TAction, TResource, TRole, TScope, TContext, R>,\n    ) => When<TAction, TResource, TRole, TScope, TContext, R>,\n  ): this {\n    const w = new When<TAction, TResource, TRole, TScope, TContext, R>()\n    fn(w)\n    this._permissions.push({ action, resource, conditions: w.buildAll() })\n    return this\n  }\n\n  /**\n   * Grants all actions (`'*'`) on a resource.\n   *\n   * Use `grantAll('*')` to grant unrestricted access to everything (typical\n   * for a super-admin role). For a more explicit alternative that only covers\n   * standard CRUD, see {@link grantCRUD}.\n   *\n   * @example\n   * ```ts\n   * defineRole('super-admin').grantAll('*')  // all actions, all resources\n   * defineRole('post-admin').grantAll('post') // all actions on posts only\n   * ```\n   *\n   * @param resource - The resource to grant all actions on, or `'*'` for all resources\n   * @returns `this` for chaining\n   */\n  grantAll(resource: TResource | '*'): this {\n    return this.grant('*', resource)\n  }\n\n  /**\n   * Grants `read` access to one or more resources.\n   *\n   * Accepts multiple resource arguments. Equivalent to calling\n   * `.grant('read', resource)` for each.\n   *\n   * @example\n   * ```ts\n   * defineRole('auditor')\n   *   .grantRead('post', 'comment', 'user', 'audit-log')\n   * ```\n   *\n   * @param resources - One or more resource strings to grant read access on\n   * @returns `this` for chaining\n   */\n  grantRead(...resources: (TResource | '*')[]): this {\n    for (const r of resources) this.grant('read' as TAction | '*', r)\n    return this\n  }\n\n  /**\n   * Grants `create`, `read`, `update`, and `delete` on a resource.\n   *\n   * More explicit than {@link grantAll} - does not include custom actions\n   * like `publish` or `archive`. Equivalent to four separate `.grant()` calls.\n   *\n   * @example\n   * ```ts\n   * defineRole('content-manager')\n   *   .grantCRUD('post')\n   *   .grantCRUD('comment')\n   * ```\n   *\n   * @param resource - The resource to grant CRUD access on\n   * @returns `this` for chaining\n   */\n  grantCRUD(resource: TResource | '*'): this {\n    for (const a of ['create', 'read', 'update', 'delete'] as (TAction | '*')[]) {\n      this.grant(a, resource)\n    }\n    return this\n  }\n\n  /**\n   * Attaches arbitrary metadata to the role.\n   *\n   * Metadata is stored on the {@link AccessControl.IRole} object but is never consulted\n   * during policy evaluation. Use it for admin dashboards, audit logs,\n   * UI labels, or any other application-level bookkeeping.\n   *\n   * @example\n   * ```ts\n   * defineRole('beta-tester')\n   *   .meta({ createdBy: 'system', tier: 'beta', maxSeats: 10 })\n   *   .grant('read', 'beta-feature')\n   * ```\n   *\n   * @param m - Key-value map of metadata attributes\n   * @returns `this` for chaining\n   */\n  meta(m: IamPrimitives.Attributes): this {\n    this._metadata = m\n    return this\n  }\n\n  /**\n   * Finalises the builder and returns a plain {@link AccessControl.IRole} object.\n   *\n   * The returned object is a plain data record with no builder methods.\n   * Pass it to `engine.admin.saveRole()` or `access.()`.\n   *\n   * @returns A fully constructed {@link AccessControl.IRole}\n   */\n  build(): AccessControl.IRole<TAction, TResource, TRole, TScope> {\n    const role: AccessControl.IRole<TAction, TResource, TRole, TScope> = {\n      id: this._id,\n      name: this._name,\n      description: this._description,\n      permissions: this._permissions,\n      inherits: this._inherits.length > 0 ? this._inherits : undefined,\n      scope: this._scope,\n      metadata: this._metadata,\n    }\n    // IamValidate at build time so callers wiring the adapter directly\n    // still see the failure where the bug was introduced.\n    const result = validateRole(role)\n    if (!result.valid) {\n      const errs = result.issues\n        .filter((i) => i.type === 'error')\n        .map((i) => (i.path ? `${i.code} at \"${i.path}\"` : i.code))\n      throw new Error(`[@gentleduck/iam:builder] RoleBuilder.build(): role rejected by validator - ${errs.join('; ')}`)\n    }\n    return role\n  }\n}\n\n/**\n * Creates a new {@link RoleBuilder} for the given role ID.\n *\n * The role ID is preserved as a literal type (`TId`) so that references to\n * it in `.inherits()` calls and adapter lookups remain type-safe when using\n * `createIam`.\n *\n * For type-safe action, resource, and scope constraints, use\n * `access.defineRole()` returned by `createIam()` instead.\n *\n * @example\n * ```ts\n * import { defineRole } from '@gentleduck/iam'\n *\n * const viewer = defineRole('viewer')\n *   .name('Viewer')\n *   .desc('Read-only access to published content')\n *   .grant('read', 'post')\n *   .grant('read', 'comment')\n *   .build()\n * ```\n *\n * @param id - Unique identifier for this role\n * @returns A new {@link RoleBuilder} instance typed to the given ID\n *\n * @template TId       - Inferred literal type of the role ID\n * @template TAction   - Union of valid action strings (defaults to `string`)\n * @template TResource - Union of valid resource strings (defaults to `string`)\n * @template TScope    - Union of valid scope strings (defaults to `string`)\n * @template TContext  - Shape of the full evaluation context for typed dot-paths\n */\nexport const defineRole = <\n  const TRole extends string,\n  const TAction extends string = string,\n  const TResource extends string = string,\n  const TScope extends string = string,\n  TContext extends object = DotPath.IDefaultContext,\n>(\n  id: TRole,\n) => new RoleBuilder<TAction, TResource, TRole, TScope, TContext>(id)\n"],"mappings":"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"}