/**
 * Action spec types — the canonical source of truth for action contracts.
 *
 * Extracted from zzz's action system. Action specs define method, kind,
 * auth, side effects, and input/output schemas. Bridge functions in
 * `actions/action_bridge.ts` derive `RouteSpec` and `EventSpec` from them.
 *
 * @see `actions/action_rpc.ts` for the JSON-RPC dispatcher
 * @see `actions/register_action_ws.ts` for the WebSocket dispatcher
 *
 * @module
 */
import { z } from 'zod';
import { RateLimitKey } from '../http/error_schemas.js';
import { RouteAuth } from '../http/auth_shape.js';
export const ActionKind = z.enum(['request_response', 'remote_notification', 'local_call']);
export const ActionInitiator = z.enum(['frontend', 'backend', 'both']);
export const ActionSideEffects = z.boolean();
export const ActionSpec = z.strictObject({
    method: z.string(),
    kind: ActionKind,
    initiator: ActionInitiator,
    /**
     * The four-axis auth shape (canonical schema in `http/auth_shape.ts`).
     * `null` for `remote_notification` and `local_call` — those don't
     * dispatch through the request/response auth gate.
     *
     * See `http/auth_shape.ts` for the design rationale (orthogonal
     * authentication / account-resolution / actor-resolution / role-and-
     * credential authorization axes).
     */
    auth: RouteAuth.nullable(),
    side_effects: ActionSideEffects,
    input: z.custom((v) => v instanceof z.ZodType),
    output: z.custom((v) => v instanceof z.ZodType),
    async: z.boolean(),
    description: z.string(),
    /**
     * Names the notification method this action emits as request-scoped
     * progress. Forward-compatible handshake — transport-agnostic, does not
     * imply a specific delivery mechanism. Registry-time validation (e.g.,
     * ensuring the named method is a `remote_notification` spec) is a
     * consumer-side concern.
     */
    streams: z.string().optional(),
    /**
     * Error reason codes this action may surface via `error.data.reason` on
     * failure. Declarative metadata mirroring the `streams` precedent —
     * codegen, UI form-state matching, and docs read it off the spec instead
     * of scanning handler implementations. Reuses the same `as const` string
     * constants the handler throws (e.g. `ERROR_ROLE_GRANT_OFFER_*`) so call sites can
     * import either side. Optional — actions that surface only standard
     * transport errors leave it unset.
     */
    error_reasons: z.array(z.string()).readonly().optional(),
    /**
     * Rate limit key the RPC dispatcher consults for this action. Optional —
     * actions without it skip the rate-limit hook entirely.
     *
     * - `'ip'` — keyed on the resolved client IP (`get_client_ip(c)`).
     * - `'account'` — keyed on the post-auth account id (`request_context.account.id`).
     *   Registration-time error if paired with `auth.account !== 'required'`
     *   (no account to key on).
     * - `'both'` — both checks run; either can block.
     *
     * Throttle-requests semantics — every invocation records, regardless of
     * outcome (different from REST login's throttle-failures, which resets
     * on success). Suits admin mutation oracles (`invite_create` account-
     * existence probe) where the *successful* invocation is the threat.
     *
     * Today only `RequestResponseActionSpec` is consulted by the RPC
     * dispatcher. The field is on the base for shape symmetry with
     * `streams` and `error_reasons`; remote_notification / local_call
     * dispatchers don't read it.
     */
    rate_limit: RateLimitKey.optional(),
});
export const RequestResponseActionSpec = ActionSpec.extend({
    kind: z.literal('request_response').default('request_response'),
    auth: RouteAuth,
    async: z.literal(true).default(true),
});
export const RemoteNotificationActionSpec = ActionSpec.extend({
    kind: z.literal('remote_notification').default('remote_notification'),
    auth: z.null().default(null),
    side_effects: z.literal(true).default(true),
    output: z.custom((v) => v instanceof z.ZodVoid),
    async: z.literal(true).default(true),
});
/**
 * Local calls can wrap synchronous or asynchronous actions, and are the
 * escape hatch for remote APIs that do not support SAES.
 */
export const LocalCallActionSpec = ActionSpec.extend({
    kind: z.literal('local_call').default('local_call'),
    auth: z.null().default(null),
});
export const ActionSpecUnion = z.union([
    RequestResponseActionSpec,
    RemoteNotificationActionSpec,
    LocalCallActionSpec,
]);
/** Structural type guard for any `ActionSpecUnion` variant — checks `kind` is one of the three known values. */
export const is_action_spec = (value) => value !== null &&
    typeof value === 'object' &&
    'method' in value &&
    'kind' in value &&
    ActionKind.options.includes(value.kind);
export const ActionEventPhase = z.enum([
    'send_request',
    'receive_request',
    'send_response',
    'receive_response',
    'send_error',
    'receive_error',
    'send',
    'receive',
    'execute',
]);
