@mettlecast/domain-runtime 0.2.58 → 0.2.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +57 -40
  2. package/dist/primitives/action.d.ts +81 -8
  3. package/dist/primitives/action.js +68 -2
  4. package/dist/runtime/action-executor.d.ts +164 -0
  5. package/dist/runtime/action-executor.js +408 -0
  6. package/dist/runtime/action-handler.d.ts +110 -6
  7. package/dist/runtime/action-handler.js +110 -10
  8. package/dist/runtime/actions.d.ts +65 -5
  9. package/dist/runtime/actions.js +118 -13
  10. package/dist/runtime/audit.d.ts +23 -3
  11. package/dist/runtime/audit.js +20 -3
  12. package/dist/runtime/db.d.ts +23 -4
  13. package/dist/runtime/db.js +27 -6
  14. package/dist/runtime/error-formatter.d.ts +63 -0
  15. package/dist/runtime/error-formatter.js +68 -1
  16. package/dist/runtime/exposed-action-api-handler.d.ts +88 -0
  17. package/dist/runtime/exposed-action-api-handler.js +288 -0
  18. package/dist/runtime/extractors.d.ts +12 -0
  19. package/dist/runtime/extractors.js +21 -13
  20. package/dist/runtime/files.d.ts +8 -0
  21. package/dist/runtime/files.js +76 -8
  22. package/dist/runtime/hydrate.js +17 -2
  23. package/dist/runtime/index.d.ts +10 -0
  24. package/dist/runtime/index.js +18 -0
  25. package/dist/runtime/observability-bindings.d.ts +110 -0
  26. package/dist/runtime/observability-bindings.js +94 -0
  27. package/dist/runtime/store.d.ts +8 -0
  28. package/dist/runtime/store.js +29 -0
  29. package/dist/runtime/tracer.d.ts +1 -1
  30. package/dist/runtime/tracer.js +1 -1
  31. package/dist/schema/index.d.ts +1 -1
  32. package/dist/schema/index.js +1 -1
  33. package/dist/schema/rls.d.ts +31 -0
  34. package/dist/schema/rls.js +44 -0
  35. package/dist/types/tenant.d.ts +12 -0
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @mettlecast/domain-runtime
2
2
 
3
- Type-safe runtime types, factory functions, and context interface for TIB Domain Module handlers. Zero AWS dependencies in the main export — all infrastructure concerns are delegated to the CDK packer.
3
+ Type-safe runtime types, factory functions, and context interface for TIB
4
+ Domain Module handlers. Zero AWS dependencies in the main export — all
5
+ infrastructure concerns are delegated to the CDK packer.
4
6
 
5
7
  ## Install
6
8
 
@@ -10,14 +12,13 @@ npm install @mettlecast/domain-runtime
10
12
 
11
13
  ## Quick Start
12
14
 
13
- Define a domain with an API handler:
15
+ Define a domain with a public action handler:
14
16
 
15
17
  ```typescript
16
18
  import {
17
19
  defineDomain,
18
- defineApi,
19
- defineEvent,
20
- type DomainContext,
20
+ defineAction,
21
+ z,
21
22
  } from '@mettlecast/domain-runtime';
22
23
 
23
24
  const domain = defineDomain({
@@ -25,74 +26,90 @@ const domain = defineDomain({
25
26
  version: '1.0.0',
26
27
  });
27
28
 
28
- const paymentCreated = defineEvent({
29
- name: 'payment.created',
30
- schema: {
31
- type: 'object',
32
- properties: {
33
- paymentId: { type: 'string' },
34
- amount: { type: 'number' },
35
- },
29
+ // Public HTTP endpoint — equivalent of the legacy `defineApi({ tenancy: 'required' })`.
30
+ // The `exposure` block is mandatory: path/method/auth/tenancy are validated at
31
+ // construction time and at registry build, so unsafe configurations never ship.
32
+ export const chargeCard = defineAction({
33
+ id: 'charge-card',
34
+ backendAccess: 'domain',
35
+ exposure: {
36
+ type: 'api',
37
+ path: '/v1/tenants/{tenantId}/payments/charge',
38
+ method: 'POST',
39
+ auth: 'required',
40
+ tenancy: 'required',
36
41
  },
37
- });
38
-
39
- export const chargeHandler = defineApi({
40
- domain,
41
- path: '/charge',
42
- method: 'POST',
43
- handler: async (event, ctx: DomainContext) => {
44
- const amount = event.body.amount;
45
-
46
- // Use context surfaces
42
+ input: z.object({ amount: z.number().positive() }),
43
+ output: z.object({ id: z.string().uuid(), status: z.literal('charged') }),
44
+ idempotent: true,
45
+ handler: async (input, ctx) => {
47
46
  await ctx.db.query('INSERT INTO charges ...');
48
- await ctx.publish(paymentCreated, { paymentId: '123', amount });
49
- await ctx.cache.set('last_charge', amount);
50
-
51
- return { statusCode: 200, body: { success: true } };
47
+ await ctx.publish('payment.charged', { amount: input.amount });
48
+ return { id: crypto.randomUUID(), status: 'charged' as const };
52
49
  },
53
50
  });
54
51
  ```
55
52
 
53
+ The action-first contract (introduced in #4619, hardened in
54
+ `feat/4625-action-auth-hardening`):
55
+
56
+ | `exposure.type` | Reachable through | Notes |
57
+ |---|---|---|
58
+ | `'api'` | API Gateway route | Path, method, auth, tenancy all required and validated. Generates one method on the consumer SDK. |
59
+ | `'internal'` | `ctx.actions[domainId].<id>(input)` from inside the platform | Never reachable via HTTP. Use for in-process cross-domain calls. |
60
+
61
+ > **Anti-pattern.** Do NOT use the legacy `defineApi` factory for new
62
+ > public handlers. It is no longer scaffolded for public HTTP endpoints —
63
+ > every public handler must go through `defineAction({ exposure: { type: 'api', ... } })`
64
+ > so the registry can enforce the auth/tenancy contract at build time
65
+ > (see `validate-domain`).
66
+
56
67
  ## Factory Functions
57
68
 
58
- 9 factory functions help you define domain primitives with schema validation and type safety:
69
+ Nine factory functions help you define domain primitives with schema
70
+ validation and type safety. The action-first migration (#4619) folded
71
+ `defineApi` into `defineAction` so every primitive is a callable
72
+ `ActionDefinition` regardless of where it is reached from.
59
73
 
60
74
  | Function | Purpose | Produces |
61
75
  |---|---|---|
62
76
  | `defineDomain()` | Declare a domain and version | Domain metadata |
63
- | `defineApi()` | HTTP handler with route + method | API handler + EventBridge integration |
77
+ | `defineAction()` | Public API endpoint OR internal callable | Action handler + (optional) API Gateway route |
64
78
  | `defineEvent()` | Event type with schema | Typed event publisher |
65
- | `defineWebhook()` | Inbound GitHub webhook handler | Webhook + validation |
79
+ | `defineWebhook()` | Inbound webhook handler | Webhook + validation |
66
80
  | `defineSubscriber()` | Event subscriber handler | EventBridge rule + Lambda |
67
81
  | `defineSchedule()` | Cron-triggered handler | EventBridge Scheduler rule |
68
82
  | `defineJob()` | Async queue-based task | SQS queue + Lambda consumer |
69
- | `defineAction()` | Flow Designer action | Workspace-invoked handler |
70
83
  | `defineIntegration()` | External service integration | Async integration handler |
84
+ | `defineFlow()` | Multi-step orchestration flow | Step Functions state machine |
71
85
 
72
86
  ## Context Surfaces
73
87
 
74
- 14 surfaces available in `DomainContext`:
88
+ The unified `DomainContext` injected into every handler exposes 14
89
+ surfaces:
75
90
 
76
91
  | Surface | Purpose |
77
92
  |---|---|
78
- | `ctx.db` | PostgreSQL connection pool via drizzle-orm |
93
+ | `ctx.db` | PostgreSQL connection pool via drizzle-orm (auto tenant-scoped) |
79
94
  | `ctx.publish()` | Publish an event to EventBridge |
80
- | `ctx.actions` | Invoke other domain actions |
95
+ | `ctx.actions` | Invoke other domain actions (`ctx.actions[domainId].<id>(input)`) |
81
96
  | `ctx.integrations` | Call external integrations |
82
97
  | `ctx.jobs` | Enqueue async tasks to SQS |
83
- | `ctx.flows` | Trigger TIB Flow Designer flows |
84
- | `ctx.cache` | In-memory or distributed cache (Redis) |
98
+ | `ctx.flows` | Trigger multi-step flows |
99
+ | `ctx.cache` | In-memory or distributed cache |
85
100
  | `ctx.secrets` | Fetch AWS Secrets Manager values |
86
- | `ctx.fetch()` | HTTP client with request/response tracing |
101
+ | `ctx.fetch()` | HTTP client with retry + circuit breaker |
87
102
  | `ctx.idempotency` | Deduplication by request ID |
88
103
  | `ctx.logger` | Pino JSON logger |
89
104
  | `ctx.tracer` | AWS X-Ray tracing |
90
- | `ctx.request` | Original HTTP request object |
91
- | `ctx.requestId` | Unique request UUID |
105
+ | `ctx.actor` | Caller identity (sub, email, tenantId, roles, scopes) |
106
+ | `ctx.tenant` | Resolved tenant (`{ id, workspaceId, orgId }`) |
92
107
 
93
108
  ## Design Principles
94
109
 
95
- This package is **intentionally framework-agnostic**. It exports types and factory functions only — all infrastructure (Lambdas, API Gateway, EventBridge, SQS, etc.) is provisioned by `@mettlecast/domain-cdk-packer`.
110
+ This package is **intentionally framework-agnostic**. It exports types
111
+ and factory functions only — all infrastructure (Lambdas, API Gateway,
112
+ EventBridge, SQS, etc.) is provisioned by `@mettlecast/domain-cdk-packer`.
96
113
 
97
114
  **Exports by concern:**
98
115
 
@@ -1,36 +1,109 @@
1
1
  import { type ZodSchema } from 'zod';
2
2
  import type { DomainContext } from '../ctx/context.js';
3
3
  import type { OutboundAccess } from '../types/index.js';
4
- /** Who can call this action. */
5
- export type ActionVisibility = 'private' | 'domain' | 'workspace';
4
+ /**
5
+ * Backend access scope for an action.
6
+ * - `private` — callable only inside the defining domain.
7
+ * - `domain` — callable by any domain in the same workspace.
8
+ * - `platform` — callable by platform-level services (cross-workspace).
9
+ */
10
+ export type BackendAccess = 'private' | 'domain' | 'platform';
11
+ /**
12
+ * Authentication posture for an API-exposed action.
13
+ * - `required` — caller's identity must be authenticated.
14
+ * - `none` — anonymous endpoint; requires a `securityException.reason`.
15
+ * - `service` — service-to-service only (no end-user identity).
16
+ */
17
+ export type ApiAuthMode = 'required' | 'none' | 'service';
18
+ /**
19
+ * Tenancy posture for an API-exposed action. Mirrors `defineApi`'s contract
20
+ * so the same registry row can describe an action or a standalone API.
21
+ */
22
+ export type ApiTenancyMode = 'required' | 'none' | 'system';
23
+ /**
24
+ * Recorded justification for relaxing the default auth/tenancy posture.
25
+ * Required when `auth: 'none'` and when `tenancy: 'system'` is combined with
26
+ * `auth: 'required'` without role narrowing.
27
+ */
28
+ export interface SecurityException {
29
+ /** Human-readable reason captured alongside the primitive. */
30
+ reason: string;
31
+ }
32
+ /**
33
+ * Action reachable only through in-process proxies (no HTTP route).
34
+ * Carries no path, method, auth, or tenancy fields.
35
+ */
36
+ export interface InternalExposure {
37
+ type: 'internal';
38
+ }
39
+ /**
40
+ * Action exposed as an HTTP endpoint. The shape mirrors `defineApi` so the
41
+ * downstream CDK/registry pipeline can treat both primitives uniformly.
42
+ */
43
+ export interface ApiExposure {
44
+ type: 'api';
45
+ /** HTTP route path relative to the domain base. Must start with `/`. */
46
+ path: string;
47
+ /** Explicit HTTP method. Never `ANY`. */
48
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
49
+ /** Authentication posture for the HTTP route. */
50
+ auth: ApiAuthMode;
51
+ /** Tenancy posture for the HTTP route. */
52
+ tenancy: ApiTenancyMode;
53
+ /** Required role claims (only meaningful when `auth` is `required`). */
54
+ roles?: string[];
55
+ /** Required justification when the auth/tenancy posture is relaxed. */
56
+ securityException?: SecurityException;
57
+ }
58
+ /** Discriminated union of every legal exposure for an action. */
59
+ export type ActionExposure = InternalExposure | ApiExposure;
6
60
  /** Configuration for an action primitive. */
7
61
  export interface ActionConfig {
8
62
  /** Machine-readable identifier (kebab-case). */
9
63
  id: string;
10
- /** Visibility scope. 'private' = only within the same domain. */
11
- visibility: ActionVisibility;
64
+ /** Backend access scope. `private` = only within the same domain. */
65
+ backendAccess: BackendAccess;
66
+ /** How this action is reachable — internal proxy or HTTP route. */
67
+ exposure: ActionExposure;
12
68
  /** Zod schema for validated input. */
13
69
  input: ZodSchema<any>;
14
70
  /** Zod schema for return type. */
15
71
  output: ZodSchema<any>;
16
72
  /**
17
73
  * Whether to enforce idempotency for this action.
18
- * If true, the runtime checks ctx.idempotency before executing.
74
+ * If true, the runtime checks `ctx.idempotency` before executing.
19
75
  */
20
76
  idempotent?: boolean;
21
- /** Whether this handler may use NAT-backed public internet egress. Defaults to internal. */
77
+ /**
78
+ * Optional caller allowlist for sensitive `backendAccess: 'platform'`
79
+ * actions. When present and non-empty, `executeAction` denies internal
80
+ * callers whose `callerDomain` is not in the list. This narrows broad
81
+ * platform-scoped primitives — for example, an internal "rotate
82
+ * encryption key" action — without forcing every caller into the more
83
+ * restrictive `private`/`domain` scopes.
84
+ *
85
+ * - `undefined` or `[]` — fall back to the `backendAccess` policy.
86
+ * - non-empty list — callerDomain MUST appear in the list.
87
+ *
88
+ * API-exposed calls (`source.type === 'api'`) skip this check; the
89
+ * allowlist only governs the in-process / cross-Lambda proxy path,
90
+ * which is the channel a runaway backend can otherwise exploit.
91
+ */
92
+ allowedCallers?: string[];
93
+ /** Whether this handler may use NAT-backed public internet egress. Defaults to `internal`. */
22
94
  outboundAccess?: OutboundAccess;
23
95
  /** Handler implementation. */
24
96
  handler: (input: any, ctx: DomainContext) => Promise<any>;
25
97
  }
26
- /** Return type of defineAction. */
98
+ /** Return type of `defineAction`. */
27
99
  export interface ActionDefinition extends ActionConfig {
28
100
  /** Discriminant. */
29
101
  _kind: 'action';
30
102
  }
31
103
  /**
32
104
  * Register a callable domain action.
33
- * Actions with visibility 'domain' or 'workspace' are exposed via ctx.actions proxy.
105
+ * The runtime validates `backendAccess` and the discriminated `exposure`
106
+ * against the action-first contract before accepting the primitive.
34
107
  * @throws {ZodError} if config is invalid
35
108
  */
36
109
  export declare function defineAction(config: ActionConfig): ActionDefinition;
@@ -1,16 +1,82 @@
1
1
  import { z } from 'zod';
2
+ // Native enum objects — Zod v4's recommended pattern over `z.enum([...])`.
3
+ // Each `as const` object is the source of truth; `z.nativeEnum(...)` derives
4
+ // the inferred string-literal union from the keys.
5
+ const BackendAccessLiteral = { private: 'private', domain: 'domain', platform: 'platform' };
6
+ const ApiAuthModeLiteral = { required: 'required', none: 'none', service: 'service' };
7
+ const ApiTenancyModeLiteral = { required: 'required', none: 'none', system: 'system' };
8
+ const ApiMethodLiteral = {
9
+ GET: 'GET',
10
+ POST: 'POST',
11
+ PUT: 'PUT',
12
+ PATCH: 'PATCH',
13
+ DELETE: 'DELETE',
14
+ HEAD: 'HEAD',
15
+ OPTIONS: 'OPTIONS',
16
+ };
17
+ // Internal exposure — only `type` is allowed; any additional field is a bug.
18
+ const InternalExposureSchema = z
19
+ .object({
20
+ type: z.literal('internal'),
21
+ })
22
+ .strict();
23
+ // API exposure — required fields present, optional fields allowed, then
24
+ // three cross-field refinements encode the action-first contract.
25
+ const ApiExposureSchema = z
26
+ .object({
27
+ type: z.literal('api'),
28
+ path: z.string().startsWith('/'),
29
+ method: z.nativeEnum(ApiMethodLiteral),
30
+ auth: z.nativeEnum(ApiAuthModeLiteral),
31
+ tenancy: z.nativeEnum(ApiTenancyModeLiteral),
32
+ roles: z.array(z.string().min(1)).optional(),
33
+ securityException: z.object({ reason: z.string().min(1) }).optional(),
34
+ })
35
+ .strict()
36
+ // auth: 'none' requires a non-empty securityException.reason
37
+ .refine((e) => e.auth !== 'none' || (e.securityException?.reason?.length ?? 0) > 0, {
38
+ message: "exposure.auth 'none' requires exposure.securityException.reason",
39
+ path: ['securityException'],
40
+ })
41
+ // tenancy: 'required' → path must include '/v1/tenants/{tenantId}/'
42
+ .refine((e) => e.tenancy !== 'required' || e.path.includes('/v1/tenants/{tenantId}/'), {
43
+ message: "exposure.tenancy 'required' requires exposure.path to include '/v1/tenants/{tenantId}/'",
44
+ path: ['path'],
45
+ })
46
+ // tenancy: 'system' requires auth 'required' or 'service'. When auth is
47
+ // 'service' the exposure is service-only and no roles are required;
48
+ // when auth is 'required' a non-empty roles array is mandatory.
49
+ .refine((e) => {
50
+ if (e.tenancy !== 'system')
51
+ return true;
52
+ if (e.auth !== 'required' && e.auth !== 'service')
53
+ return false;
54
+ if (e.auth === 'service')
55
+ return true;
56
+ return Array.isArray(e.roles) && e.roles.length > 0;
57
+ }, {
58
+ message: "exposure.tenancy 'system' requires exposure.auth 'required' or 'service'; when auth is 'required' a non-empty roles array is mandatory",
59
+ path: ['tenancy'],
60
+ });
61
+ const ActionExposureSchema = z.discriminatedUnion('type', [
62
+ InternalExposureSchema,
63
+ ApiExposureSchema,
64
+ ]);
2
65
  const ActionConfigSchema = z.object({
3
66
  id: z.string().min(1),
4
- visibility: z.enum(['private', 'domain', 'workspace']),
67
+ backendAccess: z.nativeEnum(BackendAccessLiteral),
68
+ exposure: ActionExposureSchema,
5
69
  input: z.unknown(),
6
70
  output: z.unknown(),
7
71
  idempotent: z.boolean().optional(),
72
+ allowedCallers: z.array(z.string().min(1)).optional(),
8
73
  outboundAccess: z.enum(['internal', 'internet']).default('internal'),
9
74
  handler: z.function(),
10
75
  });
11
76
  /**
12
77
  * Register a callable domain action.
13
- * Actions with visibility 'domain' or 'workspace' are exposed via ctx.actions proxy.
78
+ * The runtime validates `backendAccess` and the discriminated `exposure`
79
+ * against the action-first contract before accepting the primitive.
14
80
  * @throws {ZodError} if config is invalid
15
81
  */
16
82
  export function defineAction(config) {
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Shared action executor.
3
+ *
4
+ * Wave 2 of #4619 — used by both API-exposed actions and internal action
5
+ * invocations. Centralises the validation, auth/tenant checks, idempotency,
6
+ * audit, handler execution, output validation, and DB cleanup steps so the
7
+ * downstream API and internal adapters can stay thin and uniform.
8
+ *
9
+ * This module does NOT format HTTP responses — transport-specific adapters
10
+ * own response shaping. The executor returns parsed output (or throws a
11
+ * `TibError`) and is responsible only for the in-process pipeline.
12
+ */
13
+ import { ZodError } from 'zod';
14
+ import type { DomainContext } from '../ctx/context.js';
15
+ import type { ActionDefinition, BackendAccess } from '../primitives/action.js';
16
+ import { TibError } from './error-formatter.js';
17
+ /** Action invocation originating from an API Gateway route. */
18
+ export interface ApiActionSource {
19
+ type: 'api';
20
+ /**
21
+ * Tenant ID extracted from the route path (e.g. `/v1/tenants/{tenantId}/...`).
22
+ * When provided, must match `ctx.tenant.id`. Optional to keep the executor
23
+ * usable when path-param extraction is delegated to the adapter.
24
+ */
25
+ pathTenantId?: string;
26
+ /**
27
+ * Role claims attached to the inbound request (e.g. parsed from JWT scopes
28
+ * or custom claims). Only consulted when the action's exposure requires
29
+ * roles (auth 'required' + tenancy 'system').
30
+ */
31
+ actorRoles?: string[];
32
+ }
33
+ /** Action invocation originating from a same-process or cross-process backend call. */
34
+ export interface InternalActionSource {
35
+ type: 'internal';
36
+ /** Domain ID of the caller. Used for backendAccess enforcement. */
37
+ callerDomain?: string;
38
+ /** Tenant ID the caller claims to operate under (must match ctx.tenant.id). */
39
+ callerTenantId?: string;
40
+ }
41
+ /** Discriminated union of every legal invocation source for an action. */
42
+ export type ActionSource = ApiActionSource | InternalActionSource;
43
+ /**
44
+ * Resolve whether a given caller domain may invoke an action based on its
45
+ * declared `backendAccess` scope and optional `allowedCallers` allowlist.
46
+ *
47
+ * Rules:
48
+ * - `allowedCallers` (if non-empty) takes precedence: callerDomain MUST be
49
+ * in the list regardless of `backendAccess`. Used to narrow broad
50
+ * platform-scoped primitives for sensitive handlers (issue #4662,
51
+ * Task A hardening).
52
+ * - `'private'` — only the defining domain may call it. When
53
+ * `definingDomain` is missing we fail CLOSED (deny) —
54
+ * the in-process proxy must always pass defining-domain
55
+ * metadata now that Wave 7 Task 7.1 has plumbed it
56
+ * through `createActionsProxy` / `invokeInProcess`.
57
+ * - `'domain'` — any caller whose domain is non-platform is allowed (the
58
+ * callerDomain is just recorded for audit; the policy here
59
+ * is "callable by other domains").
60
+ * - `'platform'` — reserved for platform-level services. We currently allow
61
+ * any non-undefined callerDomain through; finer-grained
62
+ * allowlists are out of scope for Wave 2 and will be wired
63
+ * in once the action registry exposes the defining domain.
64
+ */
65
+ export declare function isCallerAllowed(opts: {
66
+ action: Pick<ActionDefinition, 'id' | 'backendAccess' | 'allowedCallers'>;
67
+ callerDomain?: string;
68
+ definingDomain?: string;
69
+ }): boolean;
70
+ /**
71
+ * Throws `TibError` when `condition` is falsy. Used to enforce preconditions
72
+ * before any handler work begins; the resulting error carries a stable `code`
73
+ * so adapters can map it to the appropriate HTTP status without parsing
74
+ * the message.
75
+ */
76
+ export declare function assertOrThrow(condition: unknown, message: string, code: string): asserts condition;
77
+ /**
78
+ * Lightweight tenant scoping check. Centralised so the API and internal
79
+ * adapters share the same wording and codes.
80
+ *
81
+ * - `ctx.tenant.id` must be a non-empty string other than `'unknown'`.
82
+ * - When `expectedTenantId` is provided (path or caller claim), it must
83
+ * match `ctx.tenant.id`.
84
+ */
85
+ export declare function ensureTenantMatches(ctx: DomainContext, expectedTenantId?: string): void;
86
+ /**
87
+ * Check role claims against an action's required role list. Only meaningful
88
+ * when `auth === 'required'` AND `tenancy === 'system'`.
89
+ *
90
+ * Returns `true` when `requiredRoles` is empty/undefined (no narrowing).
91
+ */
92
+ export declare function actorHasRequiredRoles(actorRoles: string[] | undefined, requiredRoles: string[] | undefined): boolean;
93
+ /**
94
+ * Optional metadata that adapters may attach so audit/log lines can carry
95
+ * `traceId`, `domainId`, and the action's defining-domain identifier. The
96
+ * executor never requires these — when omitted it falls back to safe
97
+ * defaults — but supplying them produces much richer observability.
98
+ */
99
+ export interface ExecuteActionMeta {
100
+ /** Domain ID that owns the action being executed. */
101
+ definingDomain?: string;
102
+ /** Trace ID for the current request. */
103
+ traceId?: string;
104
+ }
105
+ /**
106
+ * Result envelope returned by `executeAction`. Adapters translate this into
107
+ * transport-specific responses (HTTP status, Lambda payload, etc.).
108
+ *
109
+ * - `ok: true` — handler returned a value that passed the output schema.
110
+ * - `ok: false` — the failure carried a domain-meaningful error code that
111
+ * the adapter can map to a status. The executor never returns HTTP
112
+ * response objects.
113
+ */
114
+ export type ExecuteActionResult = {
115
+ ok: true;
116
+ value: unknown;
117
+ replayed: boolean;
118
+ } | {
119
+ ok: false;
120
+ error: TibError | ZodError;
121
+ code: string;
122
+ };
123
+ /**
124
+ * Run an action through the shared pipeline.
125
+ *
126
+ * Pipeline:
127
+ * 1. Validate input with `action.input.parse(input)`.
128
+ * 2. Enforce auth/tenancy preconditions based on `source`.
129
+ * 3. Enforce `backendAccess` for internal calls.
130
+ * 4. Check `ctx.idempotency` when `action.idempotent` is true.
131
+ * 5. Invoke `action.handler(parsedInput, ctx)`.
132
+ * 6. Validate output with `action.output.parse(result)`.
133
+ * 7. Mark idempotency after successful handler execution.
134
+ * 8. Audit-log the invocation.
135
+ * 9. Release `ctx.db` (only if ownership flag is set).
136
+ *
137
+ * The executor does NOT throw for application-level failures — it returns
138
+ * a `TibError`-bearing `ExecuteActionResult` so the caller can render it
139
+ * consistently. Truly unexpected throws propagate to the caller.
140
+ */
141
+ export declare function executeAction(action: ActionDefinition, input: unknown, ctx: DomainContext, source: ActionSource, meta?: ExecuteActionMeta): Promise<ExecuteActionResult>;
142
+ /**
143
+ * Release the DB client associated with `ctx`. Safe to call multiple times —
144
+ * each `DbContext.release()` implementation is idempotent (see `createDb`).
145
+ *
146
+ * The executor does NOT auto-release by default because most adapters
147
+ * (api-handler, action-handler) already own the DB lifecycle for the
148
+ * surrounding request scope. Use `withActionExecution` when the executor
149
+ * is the top-level owner of the request (e.g. standalone scripts, tests,
150
+ * or any future one-shot action runner).
151
+ */
152
+ export declare function releaseDbSafely(ctx: DomainContext): Promise<void>;
153
+ /**
154
+ * Wrap `executeAction` so the caller can opt the executor into owning the
155
+ * DB lifecycle. When `ownDbLifecycle: true` the executor runs `ctx.db.release()`
156
+ * in a `finally` block. Existing adapters (api-handler, action-handler)
157
+ * continue to own the DB themselves and should call `executeAction` directly
158
+ * — there is no double-release because the existing `createDb`/`createMockDb`
159
+ * release paths are themselves idempotent.
160
+ */
161
+ export declare function withActionExecution(action: ActionDefinition, input: unknown, ctx: DomainContext, source: ActionSource, meta?: ExecuteActionMeta, options?: {
162
+ ownDbLifecycle?: boolean;
163
+ }): Promise<ExecuteActionResult>;
164
+ export type { BackendAccess };