/** * @oshon-ai/primitives — shared RBAC + audit plumbing. * * Every interactive primitive reads permissions from `PermissionContext` * and fires `AuditEvent` through `useAudit()`. This is principle #3 * ("RBAC + audit by default") from the Oshon engineering principles. * * Shapes match ARCHITECTURE.md §10 verbatim — an action/resource * permission model (`can(action, resource, attrs)`) plus a structured * `AuditEvent` (ISO-8601 timestamp, regime, entity, details, actor, * permissionResult, denyReason). Data components in Phase 4+ inherit * these same contracts without reshaping. * * Phase 3a ships the contract + a no-op default provider. Phase 5+ wires * `PermissionProvider` up to a real auth source and `AuditSink` to a real * telemetry surface. * * Canonical action names live in `./actions.ts` — see ADR-004. */ import type { ReactNode } from 'react'; /** * Three-way fallback for how a primitive presents a denied action: * - `'disabled'` — the affordance is visible, greyed out, `aria-disabled`. * The default. Keeps the UI discoverable: users see what they can't do. * - `'hidden'` — the affordance returns `null`. Use when the action's * mere presence would leak information (e.g. an "Approve" button that * reveals a role to unprivileged users). * - `'readOnly'` — the affordance is visible and enabled but non-mutating. * Use for view-only data displays that keep their navigation intact. */ export type PermissionMode = 'disabled' | 'hidden' | 'readOnly'; /** * A permission predicate. Consumers implement the policy; primitives only * ask questions. `action` + `resource` name what's being attempted; * `attrs` is an open bag for attribute-based checks (row ID, tenant, * owner, etc.). Return `true` to allow, `false` to deny. */ export type PermissionChecker = (action: string, resource: string, attrs?: Record) => boolean; /** * Reason factory invoked when a check returned false. Returns a * human-readable string (localized by the consumer) or `null` to * suppress the tooltip / aria-description. Primitives surface this via * the `denyReason` prop on the underlying DOM element. */ export type DenyReasonFactory = (action: string, resource: string) => string | null; export interface PermissionContext { /** Policy predicate. Default (no provider) allows everything. */ can: PermissionChecker; /** How denied actions present. Defaults to `'disabled'`. */ mode?: PermissionMode; /** Optional deny-reason factory surfaced to the user. */ denyReason?: DenyReasonFactory; } export interface PermissionProviderProps { /** Partial value — merges over the allow-everything default. */ value: Partial; children: ReactNode; } /** * Scope a permission policy to a subtree. Partial values merge over the * default `{ can: () => true, mode: 'disabled' }` so a provider declaring * only `{ can: appCan }` inherits the default mode. */ export declare function PermissionProvider({ value, children, }: PermissionProviderProps): import("react/jsx-runtime").JSX.Element; /** * Read the effective permission policy for the current subtree. Primitives * use this to ask `can('view', 'dialog')`, `can('edit', 'row', { id })`, * etc. and branch on the result. */ export declare function usePermissions(): PermissionContext; /** * Resolve a primitive's effective policy given both a prop-level * `permissions` override and the context. Prop wins — consumers pinning * `permissions={{ mode: 'hidden' }}` on a single instance override the * ambient policy for that subtree only. */ export declare function resolvePermissions(override: Partial | undefined, context: PermissionContext): PermissionContext; /** * Evaluate a single check against the resolved policy. Returns a tuple * `[granted, denyReason]` so primitives can emit audit events with the * full deny rationale without duplicating the factory call. */ export declare function evaluatePermission(ctx: PermissionContext, action: string, resource: string, attrs?: Record): { granted: boolean; denyReason: string | null; }; /** * The six-regime data-density enum used by `AuditEvent.regime`. Defined * here (not imported from `@oshon-ai/data`) so `@oshon-ai/primitives` has * no upward dependency — data components consume primitives, not the * other way around. */ export type DensityRegime = 'card' | 'standard' | 'paginated' | 'virtualized' | 'server' | 'infinite'; /** * Structured audit event emitted by every interactive primitive and * (in Phase 4+) by every data component. Matches ARCHITECTURE.md §10 * verbatim. Transport is the consumer's responsibility — primitives only * emit; the `AuditSink` hands events off to Segment / Datadog / internal * bus wiring. * * PII rule: `details` may reference entity IDs, not full payloads * (principle #11). */ export interface AuditEvent { /** ISO-8601 timestamp, assigned at emission by `useAudit()`. */ timestamp: string; /** Stable primitive / component name, e.g. `'button'`, `'DataTable'`. */ component: string; /** * Namespaced verb. Primitives draw from `CANONICAL_ACTIONS` (see * `./actions.ts` + ADR-004). Data components extend with `data:*` and * `row:*` verbs per §10. */ action: string; /** Current density regime (data components only — undefined for primitives). */ regime?: DensityRegime; /** Entity acted on. `{ type: 'order', id: '123' }` shape. No PII. */ entity?: { type: string; id: string; }; /** Action-specific context. Shallow, JSON-serializable, no PII. */ details?: Record; /** Who did it. Consumer supplies; primitives never forge an actor. */ actor?: { id: string; role?: string; }; /** Whether the permission gate allowed the action. */ permissionResult?: 'granted' | 'denied'; /** Populated when `permissionResult === 'denied'`. */ denyReason?: string; } export type AuditSink = (event: AuditEvent) => void; export interface AuditProviderProps { sink: AuditSink; children: ReactNode; } /** * Route audit events from every descendant primitive into a single sink. * In tests pass a `vi.fn()`; in production route to your telemetry / * audit-log pipeline. */ export declare function AuditProvider({ sink, children }: AuditProviderProps): import("react/jsx-runtime").JSX.Element; /** * Input to `useAudit()`'s returned emitter. Caller supplies the payload; * the hook stamps `timestamp` automatically so event ordering is reliable * regardless of sink implementation. */ export type AuditEventInput = Omit; /** * Return a stable emitter bound to the nearest `AuditProvider`. If no * provider is mounted, the emitter is a no-op — primitives can emit * events unconditionally and unwired apps simply discard them. */ export declare function useAudit(): (event: AuditEventInput) => void; //# sourceMappingURL=context.d.cts.map