import { DataSurfaceActionDescriptor, DataSurfaceActionRequest, DataSurfaceActionResult, DataSurfaceDescriptor, DataSurfaceIdentity, DataSurfaceJsonObject, DataSurfaceJsonValue, DataSurfaceRowId, DataSurfaceSelectionReference } from '@happyvertical/smrt-ui/data'; import { ExecuteAsPrincipalOptions, executeAsPrincipal, PrincipalRun } from '../execute-as-principal.js'; export type DataSurfaceConfirmationPolicy = 'required' | 'none'; export type DataSurfaceActionExecution = 'foreground' | 'background'; export interface DataSurfaceActionEligibility { eligible: boolean; reason?: string; } export type DataSurfaceActionPayloadValidation = { valid: true; } | { valid: false; reason?: string; }; export interface DataSurfaceActionRowOutcome { rowId: DataSurfaceRowId; status: 'accepted' | 'skipped' | 'failed'; reason?: string; } export interface ResolvedDataSurfaceSelection { /** Fresh server-side revision of the selected surface/query. */ revision: number; /** Canonical fingerprint of the frozen query represented by the selection. */ queryFingerprint: string; /** Authoritatively resolved row ids. Browser-provided ids are only hints. */ rowIds: DataSurfaceRowId[]; } export interface DataSurfaceActionInvocation { run: PrincipalRun; request: DataSurfaceServerActionRequest; descriptor: DataSurfaceDescriptor; action: DataSurfaceServerActionDefinition; selection: ResolvedDataSurfaceSelection; } export interface DataSurfaceServerActionDefinition { descriptor: DataSurfaceActionDescriptor; /** Serializable declaration for transport/schema generators; null means no input. */ inputSchema: DataSurfaceJsonObject | null; /** Runtime enforcement for the declared schema; absence is never permissive. */ validatePayload(payload: DataSurfaceJsonValue | undefined): DataSurfaceActionPayloadValidation | Promise; /** Explicit for every action, including sensitive/public/destructive ones. */ confirmation: DataSurfaceConfirmationPolicy; execution: DataSurfaceActionExecution; /** Fail-closed persona capability checked by PrincipalRun. */ tool: string; /** Explicit RBAC catalog gate, enforced independently of callback convention. */ operation: { id: string; collection: Parameters[0]; action: string; }; /** Fresh permission/domain authorization check, run for preview and apply. */ authorize(invocation: DataSurfaceActionInvocation): boolean | Promise; /** Fresh per-row domain precondition check, repeated at apply time. */ eligible(invocation: DataSurfaceActionInvocation, rowId: DataSurfaceRowId): DataSurfaceActionEligibility | Promise; /** Foreground mutation. Background definitions are run by the injected queue. */ apply(invocation: DataSurfaceActionInvocation, rowId: DataSurfaceRowId): undefined | DataSurfaceJsonValue | Promise; } export interface ResolvedDataSurfaceActions { descriptor: DataSurfaceDescriptor; /** Current server-side revision, never trusted from the browser. */ revision: number; actions: Record; } export interface DataSurfaceServerActionRequest extends DataSurfaceActionRequest { /** Required on apply and bound into the preview token. */ expectedRevision: number; /** Required on apply. Identical retries replay the first terminal result. */ idempotencyKey?: string; } export interface DataSurfaceActionContext { principal: ExecuteAsPrincipalOptions; } export interface DataSurfaceBackgroundActionJob { idempotencyKey: string; identity: DataSurfaceIdentity; actionId: string; rowIds: DataSurfaceRowId[]; /** * The queue must call this task to perform the work. It re-enters the bound * principal and repeats descriptor, authorization, selection, and eligibility * checks before any mutation. */ run: () => Promise; } export interface DataSurfaceBackgroundQueue { enqueue(job: DataSurfaceBackgroundActionJob): Promise<{ jobId: string; details?: DataSurfaceJsonObject; }>; } export interface DataSurfacePreviewTokenRecord { expiresAt: number; actorUserId: string; tenantId: string | null; onBehalfOfUserId: string | null; actsAsProfileId: string | null; identityKey: string; actionId: string; actionFingerprint: string; revision: number; queryFingerprint: string; selectionFingerprint: string; resolvedRowsFingerprint: string; requestFingerprint: string; consumedBy?: string; } export type DataSurfaceIdempotencyRecord = { status: 'reserved'; requestFingerprint: string; ownerToken: string; reservedAt: number; } | { status: 'completed'; requestFingerprint: string; result: DataSurfaceActionResult; }; export interface DataSurfaceIdempotencyReservation { requestFingerprint: string; ownerToken: string; reservedAt: number; } export interface DataSurfaceActionStateStore { putToken(token: string, record: DataSurfacePreviewTokenRecord): Promise | void; getToken(token: string): Promise | DataSurfacePreviewTokenRecord | undefined; markTokenConsumed(token: string, idempotencyKey: string): Promise | boolean; getIdempotency(key: string): Promise | DataSurfaceIdempotencyRecord | undefined; /** Atomically create a durable reservation or return the existing record. */ reserveIdempotency(key: string, reservation: DataSurfaceIdempotencyReservation): Promise | DataSurfaceIdempotencyRecord; completeIdempotency(key: string, ownerToken: string, result: DataSurfaceActionResult): Promise | boolean; releaseIdempotency(key: string, ownerToken: string): Promise | boolean; } /** Explicit single-process/testing store; production callers inject shared state. */ export declare class InMemoryDataSurfaceActionStateStore implements DataSurfaceActionStateStore { private readonly tokens; private readonly idempotency; putToken(token: string, record: DataSurfacePreviewTokenRecord): void; getToken(token: string): DataSurfacePreviewTokenRecord | undefined; markTokenConsumed(token: string, idempotencyKey: string): boolean; getIdempotency(key: string): DataSurfaceIdempotencyRecord | undefined; reserveIdempotency(key: string, reservation: DataSurfaceIdempotencyReservation): DataSurfaceIdempotencyRecord; completeIdempotency(key: string, ownerToken: string, result: DataSurfaceActionResult): boolean; releaseIdempotency(key: string, ownerToken: string): boolean; } export interface DataSurfaceActionAdapterOptions { resolveSurface(run: PrincipalRun, identity: DataSurfaceIdentity): Promise; resolveSelection(invocation: Omit, selection: DataSurfaceSelectionReference): Promise; backgroundQueue?: DataSurfaceBackgroundQueue; /** Required durable, shared backend in production; memory storage is opt-in. */ state: DataSurfaceActionStateStore; tokenTtlMs?: number; now?: () => number; createToken?: () => string; runAsPrincipal?: typeof executeAsPrincipal; idempotencyPollIntervalMs?: number; idempotencyWaitTimeoutMs?: number; } export interface DataSurfaceActionAdapter { preview(request: DataSurfaceServerActionRequest, context: DataSurfaceActionContext): Promise; apply(request: DataSurfaceServerActionRequest, context: DataSurfaceActionContext): Promise; } /** Create a transport-neutral, principal-bound data-surface action adapter. */ export declare function createDataSurfaceActionAdapter(options: DataSurfaceActionAdapterOptions): DataSurfaceActionAdapter; //# sourceMappingURL=data-surface-actions.d.ts.map