import { IR, IRProvenance, IREntity, IRCommand, IRPolicy, IRExpression, ConstraintOutcome, OverrideRequest, ConcurrencyConflict, JobQueue, JobRecord } from './ir'; import { type RateLimitStore } from './runtime-rate-limit.js'; import type { IRSchedule } from './ir'; import type { EventBus } from './events/event-bus'; import { ManifestReferentialRestrictError, ManifestReferentialSetNullError } from './runtime-referential-actions.js'; import type { EvaluationStats } from './evaluation-stats.js'; export { ManifestReferentialRestrictError, ManifestReferentialSetNullError }; export type { EvaluationStats } from './evaluation-stats.js'; /** * Spec-guaranteed runtime context bindings (see docs/spec/semantics.md * § "Runtime Context Schema" and docs/spec/builtins.md § "Context Member * Access"). Every typed field is optional at the type level; per spec, * tenant-scoped commands MUST fail closed with `MISSING_TENANT_CONTEXT` * when `tenantId` is absent and `requireTenantContext` is set on * RuntimeOptions. * * The index signature is preserved for backwards compatibility — existing * callers may attach ad-hoc keys without a type-level change. */ export interface RuntimeContext { /** Active tenant identifier. Required for tenant-scoped commands. */ tenantId?: string; /** Active organization identifier (e.g. Clerk orgId). */ orgId?: string; /** Acting user identifier. */ actorId?: string; /** Caller-supplied request id; surfaces in diagnostics and emitted events. */ requestId?: string; /** Origin surface: 'route' | 'job' | 'cli' | 'test' | 'ui' | 'workflow' (or other). */ source?: string; /** If true, adapter actions throw ManifestEffectBoundaryError. options.deterministicMode wins if both set. */ deterministic?: boolean; /** Legacy actor shorthand. Prefer `actorId` for new code. */ user?: { id: string; role?: string; [key: string]: unknown; }; /** Open extension surface; legacy callers still rely on free keys. */ [key: string]: unknown; } /** * Pluggable encryption provider for field-level encryption. * When supplied via RuntimeOptions.encryptionProvider, properties with the * `encrypted` modifier are transparently encrypted on write and decrypted * on read at the store boundary. The envelope format supports key rotation: * `{"v":1,"kid":"","ct":""}`. */ export interface EncryptionProvider { encrypt(plaintext: string): Promise<{ ciphertext: string; keyId: string; }>; decrypt(ciphertext: string, keyId: string): Promise; } /** * Middleware hook types. Each corresponds to a lifecycle point in command * execution where middleware can observe, patch context, or short-circuit. */ export type MiddlewareHook = 'before-policy' | 'before-guard' | 'before-action' | 'after-emit'; /** * Context passed to middleware handlers at each lifecycle points. */ export interface MiddlewareContext { /** Which lifecycle hook triggered this middleware call */ hook: MiddlewareHook; /** The IR command being executed */ command: IRCommand; /** The current expression evaluation context (read/write via contextPatch) */ evalContext: Record; /** The original input to the command */ input: Record; /** The runtime context (user, tenantId, etc.) */ runtimeContext: RuntimeContext; /** Entity name, if applicable */ entityName?: string; /** Instance ID, if applicable */ instanceId?: string; /** Events emitted so far (populated in after-emit hook) */ emittedEvents: EmittedEvent[]; } /** * Result returned by a middleware handler. * - Empty object `{}` means "continue normally". * - `contextPatch` merges additional values into the evalContext. * - `shortCircuit` immediately returns the provided CommandResult. */ export interface MiddlewareResult { contextPatch?: Record; shortCircuit?: boolean; result?: CommandResult; } /** * A middleware instance: declares which hooks it participates in and * a handler function called at each matching lifecycle points. */ export interface Middleware { hooks: MiddlewareHook[]; handler: (ctx: MiddlewareContext) => Promise; } export interface RuntimeOptions { generateId?: () => string; now?: () => number; /** * Optional middleware pipeline. Middleware are executed in declaration order * at each matching lifecycle hook during command execution. */ middleware?: Middleware[]; /** * If true, runtime will verify IR integrity hash before execution. * When an IR hash doesn't match, the runtime will throw an error. * Set to false for development/debugging mode. * * @default * - `true` in production (NODE_ENV=production) * - `false` in development * * Explicit dev override: Set to `false` to disable verification in production for debugging. */ requireValidProvenance?: boolean; /** * Optional: expected IR hash for verification. If provided and requireValidProvenance is true, * the runtime will verify the IR's hash matches this value. * If not provided, the runtime will verify the IR's self-reported hash. */ expectedIRHash?: string; /** * Optional function to provide custom store implementations for entities. * Called with the entity name and should return a Store instance or undefined. * If undefined is returned, the runtime will use its default store initialization. * * This allows using server-side stores like PostgresStore and SupabaseStore from stores.node.ts. * * @example * ```typescript * import { PostgresStore } from './stores.node.js'; * * const runtime = new RuntimeEngine(ir, context, { * storeProvider: (entityName) => { * if (entityName === 'User' || entityName === 'Post') { * return new PostgresStore({ * connectionString: process.env.DATABASE_URL, * tableName: entityName.toLowerCase() * }); * } * return undefined; // Use default store * } * }); * ``` */ storeProvider?: (entityName: string) => Store | undefined; /** Caller-provided idempotency store for command deduplication */ idempotencyStore?: IdempotencyStore; /** * If true, adapter actions (persist/publish/effect) throw ManifestEffectBoundaryError * instead of the default no-op behavior. Use for conformance testing and replay validation. * See docs/spec/adapters.md for the normative exception. */ deterministicMode?: boolean; /** * Cap concurrent top-level `runCommand` invocations on this engine instance. * Nested reaction/saga `runCommand` calls (commandExecutionDepth > 0) do not * consume the budget. When the limit is exceeded, the call fails closed with * `PARALLEL_COMMAND_LIMIT` (does not queue). Omit or unset = unlimited. * Config G7 `runtime.concurrency.maxParallelCommands` fans into generated * factories via this option. */ maxParallelCommands?: number; /** Optional complexity limits for expression evaluation */ evaluationLimits?: EvaluationLimits; /** * If true, any `runCommand` invocation MUST fail closed with diagnostic * `MISSING_TENANT_CONTEXT` when `context.tenantId` is absent or empty. * Use to enforce tenant-scoped command semantics in multi-tenant apps. * Default: false (backwards compatible — legacy callers unaffected). */ requireTenantContext?: boolean; /** * Optional custom builtin functions from plugins or project configuration. * These are merged with core builtins; core builtins always take precedence * on name collision. Populated by the plugin loader from BuiltinFunctionPlugin * registrations. * * @see plugin-api.ts BuiltinFunctionPlugin */ customBuiltins?: Map unknown>; /** * Optional AuditSink for durable audit records. * When supplied, the runtime is contracted to call sink.emit() exactly * once per command invocation. Contract: src/manifest/audit/audit-sink.ts. * Wire-in is contract-only in this release; actual emission lands in * the audit/outbox implementation follow-on. */ auditSink?: import('./audit/audit-sink').AuditSink; /** * Optional OutboxStore for transactional event persistence. * Contract: src/manifest/outbox/outbox-store.ts. Contract-only wire-in * in this release; transactional integration lands in the follow-on. */ outboxStore?: import('./outbox/outbox-store').OutboxStore; /** * Optional durable ApprovalStore for multi-stage approval persistence. * When supplied, pending approval requests, stage grants, and denials are * read from and written to this store, so an approval created by one * engine instance is visible to a freshly-constructed engine (the normal * stateless-per-request pattern). When omitted, approval state lives in an * in-process Map (single-process / test use only). * Contract: src/manifest/approval/approval-store.ts. Memory + Postgres * adapters ship via "./approval/memory" and "./approval/postgres". */ approvalStore?: import('./approval/approval-store').ApprovalStore; /** * Optional durable RateLimitStore for command/policy rate-limit buckets. * When omitted, an in-process {@link MemoryRateLimitStore} is used (limits * reset on process restart and do not span engine instances). When set, * sliding-window state is read/written through this store so multi-instance * deployments share the same counters. * Contract: `RateLimitStore` in `runtime-rate-limit.ts`. Memory + Postgres * adapters: `./rate-limit/memory`, `./rate-limit/postgres`. */ rateLimitStore?: RateLimitStore; /** * Optional static feature-flag map. Checked by `flag(name)` when no * `flagProvider` is set (or as a fallback when the provider is absent). * Missing keys resolve to `false` (safe default — features off). * When both `flags` and `flagProvider` are set, `flagProvider` wins. */ flags?: Record; /** * Optional feature flag provider function. * Called with a flag name and returns the flag value (boolean, string, number, or object). * Enables the `flag(name)` built-in to resolve feature flags declaratively * from any provider (LaunchDarkly, Unleash, JSON file, etc.). * * When not provided, `flag(name)` returns `false` (safe default — features off) * unless a matching entry exists in {@link flags}. * * @example * ```typescript * const runtime = new RuntimeEngine(ir, context, { * flagProvider: (name) => launchDarklyClient.variation(name, false), * }); * ``` */ flagProvider?: (name: string) => unknown; /** * Optional JobQueue for async command execution. * When an async command is invoked, the runtime enqueues a job and returns * a JobId immediately. Use `drainJobs()` for deterministic testing. */ jobQueue?: JobQueue; /** * Optional TransactionProvider that gives commands an atomic write boundary. * When supplied, the runtime opens one transaction per command attempt and * threads the handle into every store, outbox, idempotency, job, and approval * write, so a command's mutations + outbox entries + idempotency record commit * or roll back together. Reactions/sagas invoked during the command join the * same transaction. Without it, behavior is unchanged (outbox enqueue is * best-effort / fail-open). Contract + semantics: docs/spec/adapters.md * § "Transaction Provider" and § "Outbox Store — Transaction Boundary". */ transactionProvider?: TransactionProvider; /** * Optional cross-instance EventBus. When supplied, the engine publishes one * message per committed command (the full parent + reaction event batch) to * the bus, and — after `connectEventBus()` — re-dispatches remote messages to * local onEvent/subscribe listeners. Enables realtime fan-out across * serverless / multi-instance deployments; without it the event stream stays * single-instance. Publish is post-commit and fail-open (a publish failure is * logged, never fails the command). Contract + semantics: * docs/spec/adapters.md § "Event Bus" and docs/spec/semantics.md * § "Cross-instance delivery". */ eventBus?: EventBus; /** * Optional encryption provider for field-level encryption. * When supplied, properties with the `encrypted` modifier are transparently * encrypted before store writes and decrypted after store reads. * No-op when omitted (plaintext stored — safe for dev/test). */ encryptionProvider?: EncryptionProvider; /** * Optional profiling configuration. When enabled, the runtime collects * per-phase timing data for each command execution. Profiles are * accessible via `engine.getProfiles()`. */ profiling?: import('./profiling').ProfilingOptions; /** Optional per-action trace hook for @angriff36/manifest/debug CommandTraceRecorder */ actionTraceHook?: (info: { index: number; kind: string; target?: string; entityName?: string; instanceId?: string; }) => void | Promise; /** Injectable sleep for deterministic retry backoff in tests/adapters */ sleep?: (ms: number) => Promise; /** Deterministic jitter override for retry delays (used when retry.jitter is true) */ retryJitter?: (delayMs: number) => number; /** * Host side-effect dispatcher for `effect` actions. Invoked (outside * deterministic mode) with the evaluated expression value and action/command * context; its resolved value becomes the action result. Absent handler ⇒ * the effect action fails closed with MISSING_EFFECT_HANDLER. */ effectHandler?: (info: { /** action.target when present (names the effect), else undefined */ name?: string; /** evaluated action expression value */ value: unknown; commandName: string; entityName?: string; instanceId?: string; context: RuntimeContext; }) => Promise | unknown; } export type { AuditSink, AuditRecord, CommandOutcome } from './audit/audit-sink'; export type { OutboxStore, OutboxEntry, OutboxEntryStatus } from './outbox/outbox-store'; export type { ApprovalStore } from './approval/approval-store'; export type { JobQueue, JobRecord } from './ir'; export type { EventBus, EventBusMessage, EventBusHandler } from './events/event-bus'; export interface EntityInstance { id: string; /** For optimistic concurrency control (optional) */ version?: number; /** Timestamp of last version change (optional) */ versionAt?: number; [key: string]: unknown; } export interface CommandResult { success: boolean; result?: unknown; instance?: EntityInstance; error?: string; deniedBy?: string; guardFailure?: GuardFailure; policyDenial?: PolicyDenial; /** Missing required command parameter (fails before rate-limit/policy/guard). */ parameterFailure?: ParameterFailure; /** All constraint evaluation outcomes (vNext) */ constraintOutcomes?: ConstraintOutcome[]; /** Pending override requests (vNext) */ overrideRequests?: OverrideRequest[]; /** Concurrency conflict details (vNext) */ concurrencyConflict?: ConcurrencyConflict; /** Approval workflow required before command can execute */ approvalRequired?: ApprovalRequiredInfo; /** Caller-supplied correlation ID grouping related events across a workflow */ correlationId?: string; /** Caller-supplied ID of the event/command that caused this command execution */ causationId?: string; emittedEvents: EmittedEvent[]; /** Retry metadata when command declares a retry policy */ retry?: { attempts: number; exhausted: boolean; lastErrorCode?: string; delaysMs: number[]; }; /** Rate limit denial when a command or policy rate limiter blocks execution */ rateLimitDenial?: { scope: 'user' | 'tenant' | 'global'; scopeKey: string; limit: number; windowMs: number; retryAfterMs: number; }; } export interface GuardFailure { index: number; expression: IRExpression; formatted: string; resolved?: GuardResolvedValue[]; } export interface ParameterFailure { /** Name of the missing required parameter. */ parameter: string; /** Declared parameter type name, when available. */ expectedType?: string; /** * Machine-readable failure code. Defaults to MISSING_REQUIRED_PARAMETER * when absent. Trusted-source injection failures use MISSING_TRUSTED_CONTEXT. */ code?: 'MISSING_REQUIRED_PARAMETER' | 'MISSING_TRUSTED_CONTEXT'; } export interface PolicyDenial { policyName: string; expression: IRExpression; formatted: string; message?: string; contextKeys: string[]; /** Resolved values from the policy expression evaluation */ resolved?: GuardResolvedValue[]; } export interface GuardResolvedValue { expression: string; value: unknown; } export interface ConstraintFailure { constraintName: string; expression: IRExpression; formatted: string; message?: string; resolved?: GuardResolvedValue[]; } export interface ApprovalGrant { stage: string; by: string; at: number; } export interface ApprovalRequestState { entity: string; instanceId: string; approvalName: string; command: string; status: 'pending' | 'granted' | 'denied' | 'expired'; /** Stages whose `when` condition evaluated true (or had no `when`) */ requiredStages: string[]; grants: ApprovalGrant[]; requestedAt: number; expiresAt?: number; deniedReason?: string; deniedBy?: string; /** Opaque author-defined escalation target (expression result). */ escalatedTo?: unknown; /** Timestamp when escalation was applied. */ escalatedAt?: number; } /** * Identity of a user approving a stage. A bare string is the legacy form * where the userId doubles as the role (kept for backward compatibility). * Prefer the object form to express real RBAC — `role`/`roles`/permissions * are made available to the stage policy as `user.*`, independent of `id`. */ export type ApprovalApprover = string | { id: string; role?: string; roles?: string[]; [key: string]: unknown; }; export interface ApprovalRequiredInfo { approvalName: string; pendingStages: string[]; requestKey: string; } /** * Canonical subject metadata identifying the originating entity, command, * and target instance for an emitted event. Populated by the runtime during * `runCommand` so downstream consumers can reliably route and correlate * events without inspecting payload internals. */ export interface EventSubject { /** The Manifest entity name associated with the command, when available. */ entity?: string; /** The Manifest command name that emitted the event. */ command: string; /** The canonical target instance id, resolved deterministically. */ id?: string; } export interface EmittedEvent { name: string; channel: string; payload: unknown; timestamp: number; /** Canonical subject metadata for the originating entity/command/instance. */ subject?: EventSubject; /** Provenance information from the IR at the time of event emission */ provenance?: { contentHash: string; compilerVersion: string; schemaVersion: string; }; /** Caller-supplied correlation ID grouping related events across a workflow */ correlationId?: string; /** Caller-supplied ID of the event/command that caused this emission */ causationId?: string; /** Zero-based index of this event within the current runCommand invocation. Per-command only. */ emitIndex?: number; } export interface SagaStepResult { step: string; command: string; /** * - `completed` — forward command succeeded * - `failed` — forward command failed (the step that triggered compensation) * - `compensated` — forward command was successfully reversed by its compensation * - `compensation_failed` — a compensation was attempted but failed its guard/policy or threw; * the step is NOT considered reversed (potential dangling state) * - `skipped` — completed step had no compensation declared (nothing to reverse) */ status: 'completed' | 'failed' | 'compensated' | 'compensation_failed' | 'skipped'; result?: CommandResult; compensation?: CommandResult; error?: string; } export interface SagaResult { saga: string; success: boolean; status: 'completed' | 'compensated' | 'aborted'; steps: SagaStepResult[]; emittedEvents: EmittedEvent[]; failedStep?: string; error?: string; } /** * Opaque handle for an open transaction. Adapters that share the provider's * underlying database understand it (e.g. a pg PoolClient); everyone else * ignores it. */ export type TransactionHandle = unknown; export interface TransactionProvider { /** Run fn inside a single transaction: begin → fn(tx) → commit. Any throw * from fn rolls back and rethrows. The engine never nests calls. */ withTransaction(fn: (tx: TransactionHandle) => Promise): Promise; } export interface Store { getAll(): Promise; getById(id: string): Promise; create(data: Partial, tx?: TransactionHandle): Promise; update(id: string, data: Partial, tx?: TransactionHandle): Promise; delete(id: string, tx?: TransactionHandle): Promise; clear(): Promise; } export interface IdempotencyStore { /** Check if a command with this key has already been executed */ has(key: string): Promise; /** Record a command result for an idempotency key. When the runtime is driving * a TransactionProvider it threads the active handle so the record is written * inside the command's transaction. */ set(key: string, result: CommandResult, tx?: TransactionHandle): Promise; /** Retrieve the cached result for an idempotency key */ get(key: string): Promise; } /** * Thrown when an adapter action (persist/publish/effect) is executed in deterministicMode. * This is a programming error, not a domain failure. * See docs/spec/adapters.md for the normative exception to default no-op behavior. */ export declare class ManifestEffectBoundaryError extends Error { readonly actionKind: string; constructor(actionKind: string); } /** * Thrown when reaction cascading exceeds the maximum depth (default: 10). * Indicates a potential infinite loop in reaction chains. * See docs/spec/semantics.md § "Reactions". */ export declare class ManifestReactionDepthError extends Error { readonly depth: number; readonly triggerEvent: string; readonly targetCommand: string; constructor(depth: number, triggerEvent: string, targetCommand: string); } /** * In-memory JobQueue implementation for async commands. * Suitable for testing and development. Production deployments should * provide a durable implementation (e.g. database-backed). */ export declare class MemoryJobQueue implements JobQueue { private jobs; enqueue(job: JobRecord): Promise; drainPending(): Promise; updateStatus(jobId: string, status: JobRecord['status'], detail?: { result?: unknown; error?: string; }): Promise; /** Test utility: get all jobs */ getAll(): JobRecord[]; } /** * Thrown when expression evaluation exceeds configured depth or step limits. * This is a domain failure (caught and converted to CommandResult), not a programming error. * See docs/spec/manifest-vnext.md § "Diagnostic Payload Bounding". */ export declare class EvaluationBudgetExceededError extends Error { readonly limitType: 'depth' | 'steps'; readonly limit: number; constructor(limitType: 'depth' | 'steps', limit: number); } /** * Optional complexity limits for expression evaluation. * Defaults are permissive — no existing programs should be affected. */ export interface EvaluationLimits { /** Maximum expression nesting depth. Default: 64 */ maxExpressionDepth?: number; /** Maximum total evaluation steps per entry point. Default: 10_000 */ maxEvaluationSteps?: number; } type EventListener = (event: EmittedEvent) => void; export interface ProvenanceVerificationResult { valid: boolean; expectedHash?: string; computedHash?: string; error?: string; } export declare class RuntimeEngine { private ir; private context; private options; private stores; private eventListeners; private eventLog; /** Current reaction nesting depth to prevent infinite loops */ private reactionDepth; private static readonly MAX_REACTION_DEPTH; /** Index of relationships for efficient lookup during expression evaluation */ private relationshipIndex; /** Memoization cache for resolved relationships to avoid repeated store queries */ private relationshipMemoCache; /** Index of roles by name for O(1) permission checks */ private roleIndex; /** Track whether version has been incremented for the current command execution */ private versionIncrementedForCommand; /** Track instances that were just created (to prevent version increment on subsequent mutate actions) */ private justCreatedInstanceIds; /** * Command-scoped write buffer. While set, mutate/compute actions apply their * changes to an in-memory working copy (`instance`) and accumulate a single * store-form `patch` instead of issuing one store read + write per action. * The buffer is flushed in one `store.update` at the end of the action loop, * then cleared — so a command that mutates N fields performs one read and one * write rather than N. Scoped to the command's target instance only; nested * (reaction/fan-out) commands save and restore the outer buffer. */ private commandBuffer; /** * Active while a command runs: entity create/update constraint overrides use the * same OverrideRequest list as command constraints (semantics.md § Override Mechanism). * OverrideApplied events from entity-level paths accumulate in `events`. */ private activeCommandOverrides; /** Last transition validation error (set by updateInstance, checked by _executeCommandInternal) */ private lastTransitionError; /** Constraint outcomes from a failed initialization persist (flush). */ private lastInitializationConstraintOutcomes; /** * Last fail-closed action error (set by executeAction for adapter actions that * cannot proceed — MISSING_OUTBOX_STORE / MISSING_EFFECT_HANDLER — checked by * _executeCommandInternal after each action so the command fails and persists * nothing, mirroring the MISSING_JOB_QUEUE / MISSING_TENANT_CONTEXT convention). */ private lastActionError; /** Last concurrency conflict (set by updateInstance, checked by _executeCommandInternal) */ private lastConcurrencyConflict; /** * Last modifier write-rejection from updateInstance (readonly change or unique * collision). Set by updateInstance, surfaced by _executeCommandInternal after * a mutate/compute action so a command reports the rejection instead of silently * persisting nothing. */ private lastWriteRejection; /** * Nesting depth of in-flight command executions (>0 while inside runCommand). * The readonly-modifier exemption for a just-created instance applies only while * a command runs (its create + mutate actions are one operation); a direct * createInstance/updateInstance pair outside a command is two operations, so * readonly blocks there. */ private commandExecutionDepth; /** * Count of top-level `runCommand` calls currently in flight (depth === 0 at * entry). Used with {@link RuntimeOptions.maxParallelCommands}. */ private topLevelInFlightCommands; /** * The transaction handle for the command attempt currently in flight, or * null when no provider transaction is open. Set by runCommand's provider-mode * wrapper and threaded into every store/outbox/idempotency/job/approval write * so nested (reaction/saga) commands join the same transaction rather than * opening a new one. Always null in non-provider mode. */ private activeTx; /** * While non-null, in-process event-listener notifications are buffered here * instead of dispatched immediately, so onEvent/subscribe listeners are only * notified after the command's transaction commits (provider mode). Null in * non-provider mode — notification stays synchronous. */ private deferredNotifications; /** * Stable per-instance id used as the EventBus `originId` so subscribers can * skip an engine's own published events. Derived lazily (first bus use) from * the deterministic id source via `instanceId()` — deriving it eagerly in the * constructor would consume a `generateId` tick and shift every user-visible * instance id, so engines without a bus never touch it. */ private _instanceId; /** * Outbound EventBus batch for the top-level command in flight, or null when * no bus is configured / no command owns a batch. The top-level runCommand * sets it to [] on entry and publishes it once on completion; nested * (reaction/saga) commands accumulate into the same array so one message * carries the full parent + reaction event set. Only allocated when * RuntimeOptions.eventBus is present — the no-bus path stays untouched. */ private busBatch; /** * Active EventBus unsubscribe from connectEventBus, or undefined when not * connected. Present so a duplicate connectEventBus is idempotent (returns the * same disconnect) rather than opening a second subscription. */ private busUnsubscribe; /** Per-engine sliding-window rate limiter (memory by default; durable via rateLimitStore) */ private rateLimiter; private readonly profilingBridge; private readonly referentialActions; private actionTraceCounter; /** * In-process approval request cache, keyed by * `${entity}:${instanceId}:${approvalName}`. Always maintained as a mirror * so the `getApprovalRequest`/`expireApprovals` accessors work. * When `options.approvalStore` is set, that store is the source of truth and * this Map is just a write-through mirror; otherwise this Map IS the store. */ private approvalRequests; /** * Load an approval request, preferring the durable store when configured. * Refreshes the in-process mirror so synchronous accessors stay coherent. */ private loadApprovalState; /** * Persist an approval request to the durable store (when configured) and * always mirror it in-process so a later synchronous read sees it. */ private saveApprovalState; /** Per-entry-point evaluation budget for bounded complexity enforcement */ private evalBudget; /** Snapshot of the last completed top-level evaluation budget (instrumentation) */ private lastEvaluationStats; /** Cache for computed property values, keyed by "entityName:instanceId:propertyName" */ private computedPropertyCache; /** Request-scoped cache for computed properties (cleared per command) */ private computedPropertyRequestCache; /** * Initialize evaluation budget if not already active (re-entrant safe). * Returns true if this call initialized the budget (caller must clear it in finally). * Returns false if budget was already active (caller should NOT clear it). */ private initEvalBudget; /** Clear evaluation budget (only call if initEvalBudget returned true) */ private clearEvalBudget; /** * Returns the set of property names marked `encrypted` for the given entity. * Cached per entity name since IR is immutable at runtime. */ private encryptedPropertyNamesCache; private encryptedPropertyNames; /** * Encrypt property values before a store write. * Returns a shallow copy with encrypted fields replaced by envelope JSON. * No-op when encryptionProvider is not configured or entity has no encrypted fields. */ private encryptProperties; /** * Decrypt property values after a store read. * Returns a shallow copy with encrypted envelope JSON replaced by plaintext. * No-op when encryptionProvider is not configured or entity has no encrypted fields. */ private decryptProperties; /** * Resolve the active tenant value from runtime context using the IR tenant * config's contextPath. Returns undefined when no tenant declaration exists * in the IR or the context lacks the value. */ private resolveTenantValue; constructor(ir: IR, context?: RuntimeContext, options?: RuntimeOptions); private initializeStores; private browserStoreUnsupported; private createConfiguredStore; /** * Build an index of all relationships for efficient lookup during expression evaluation. * Maps "EntityName.relationshipName" to relationship metadata. */ private buildRelationshipIndex; private buildRoleIndex; /** * Check if a role has a specific permission. * Uses precomputed effectivePermissions for O(1) lookup. * Unknown role → false (no permissive default, per house style). */ private roleHasPermission; /** * Clear the relationship memoization cache. * Called at the start of each command execution to ensure fresh data. */ private clearMemoCache; /** * Two-hop hasMany via join entity: source → Join rows → target instances. */ private resolveHasManyThrough; /** * Resolve a relationship for a given instance. * Uses memoization cache to avoid repeated store queries within a single command execution. * @param entityName - The source entity name * @param instance - The source instance (must have an id) * @param relationshipName - The relationship name to resolve * @returns For hasMany: array of related instances; for hasOne/belongsTo/ref: single instance or null */ private resolveRelationship; /** * Resolve only the relations the command will evaluate. This primes the * command-scoped resolver cache for both persisted instances and virtual * initialization drafts without embedding hydrated objects in either row. */ private primeRelationDependencies; private getNow; /** * Composite-key runtime identity (docs/spec/semantics.md, "Composite Keys"). * * When an entity declares `key` (an ordered list of property names), its * canonical identity is the ordered tuple of those property values, encoded * into a single deterministic string. Components percent-encode `%` and the * `|` separator so joins are unambiguous (`"a|b"` vs `["a","b"]` never * collide). When `key` is absent the identity is the `id` property, byte-for- * byte identical to the pre-composite runtime. Pure and order-stable: no * clock/random, so identical IR + instance ⇒ identical key. */ private compositeId; /** Percent-encode `%` then `|` so composite key components join unambiguously. */ private encodeKeyComponent; /** * Pair each local foreign-key column with the target column it references. * `references` is used when present and length-matched; otherwise the target * entity's declared `key` columns are paired positionally; as a last resort * the local field names are assumed to match remote column names. Generalizes * the single-column `${relName}Id`/`fields[0]` convention to N columns. */ private fkColumnPairs; /** * Generate a unique identifier for runtime-internal records (audit * records, outbox entry ids). Uses the caller-supplied generator from * RuntimeOptions when present; otherwise falls back to crypto.randomUUID. * Distinct from `getBuiltins().uuid` only by intent — keeping a named * helper avoids leaking the fallback chain across call sites. */ private nextRuntimeId; /** * Core (+ optional custom) builtin callables for this engine. * Core names always win collisions against plugins (docs/spec/builtins.md). * Public so language-metadata / Builder can introspect the live registry. */ getBuiltins(): Record unknown>; getIR(): IR; /** * Whether an IdempotencyStore is wired into this engine. Additive read-only * accessor (no semantics change): the webhook handler (src/manifest/webhooks) * must fail closed when a webhook declares an `idempotencyHeader` but the * runtime cannot honor the dedup contract, and the store lives in private * options. Runtime execution semantics are unchanged. */ hasIdempotencyStore(): boolean; /** * Last top-level evaluation step/depth counters (vNext performance guardrails). * Null until the first entry point that initializes an evaluation budget completes. */ getLastEvaluationStats(): EvaluationStats | null; /** * Get the provenance metadata from the IR */ getProvenance(): IRProvenance | undefined; /** * Log provenance information at startup * This can be called by UI code to display provenance */ logProvenance(): void; /** * Verify the IR integrity by checking that the computed hash matches the expected hash. * Returns true if verification passes, false otherwise. * * @param expectedHash - Optional expected hash. If not provided, uses the IR's self-reported irHash * @returns true if hash matches or if no hash is available to verify */ verifyIRHash(expectedHash?: string): Promise; /** * Verify IR and throw if invalid. Use this when requireValidProvenance is true. * @throws Error if IR hash verification fails */ assertValidProvenance(): Promise; getContext(): RuntimeContext; setContext(ctx: Partial): void; replaceContext(ctx: RuntimeContext): void; getEntities(): IREntity[]; getEntity(name: string): IREntity | undefined; getCommands(): IRCommand[]; getCommand(name: string, entityName?: string): IRCommand | undefined; getPolicies(): IRPolicy[]; /** Return all schedule declarations from the compiled IR. */ getSchedules(): IRSchedule[]; /** * Run a named schedule: evaluate bound params and dispatch the target command. * Sets context.source to 'schedule' and context.scheduleName for the invocation. */ runSchedule(scheduleName: string, options?: { correlationId?: string; causationId?: string; }): Promise; getStore(entityName: string): Store | undefined; /** * Get collected command profiles when profiling is enabled. * Returns an empty array when profiling is not configured. */ getProfiles(): import('./profiling').CommandProfile[]; /** * Execute middleware registered for a given hook. * Returns a short-circuit result if any middleware short-circuits, * or undefined to continue normal execution. */ private runMiddleware; /** * Public read surface: tenant filter → decrypt → mask (read-projection only). * Execution paths (guards, actions, policies, computed properties, relationship * resolution) use getAllInstancesRaw and always see real values. */ getAllInstances(entityName: string): Promise; /** Internal read path: tenant filter + decryption, NO masking. */ private getAllInstancesRaw; /** * Public read surface: tenant filter → decrypt → mask (read-projection only). * Execution paths use getInstanceRaw and always see real values. */ getInstance(entityName: string, id: string): Promise; /** Internal read path: tenant filter + decryption, NO masking. */ private getInstanceRaw; /** Cache of properties carrying maskStrategy, per entity (IR is immutable at runtime). */ private maskedPropertiesCache; private maskedProperties; /** Cache of `private`-modifier property names, per entity (IR is immutable at runtime). */ private privatePropertiesCache; private privateProperties; /** * Apply read-time masking to an instance (after decryption and tenant filtering). * - `private` wins over `masked`: the property is excluded entirely. * - `null`/`undefined` pass through unmasked. * - `unmaskWhen` falsy or throwing ⇒ value stays masked (secure by default). * An evaluation error additionally surfaces a diagnostic; it never changes * the masked outcome (diagnostics explain, never compensate). */ private applyMasking; /** Read policies applicable to an entity, in IR declaration order (cached, IR is immutable at runtime). */ private readPoliciesCache; private selectReadPolicies; /** * A read policy is context-only (instance-independent) when its expression * never references `self`/`this`. Such a policy is evaluated once per * getAllInstances call; a self-referencing policy is evaluated per row. */ private isContextOnlyExpression; /** * Evaluate a single read policy against an eval context. Fail-closed: a * rate-limit denial, a falsey expression, or a thrown expression all DENY. * A thrown expression additionally surfaces a diagnostic (never compensating, * mirroring the masking unmaskWhen contract). */ private evaluateReadPolicy; /** * Read gate for a single instance (getInstance). Returns true only if every * applicable read policy allows; the row is bound as `self`/`this`. */ private passesReadGate; /** * Read gate for a row set (getAllInstances). Context-only policies are * evaluated once (deny ⇒ empty result, no row scan); self-referencing * policies are evaluated per row and denied rows are omitted (no existence * leak, mirroring the tenant filter). */ private applyReadGateToRows; /** * Check entity constraints against instance data * Returns array of constraint failures (empty if all pass) * Useful for diagnostic purposes without mutating state */ checkConstraints(entityName: string, data: Record): Promise; /** * Evaluate all entity constraints against instance data, returning every outcome * (both passed and failed). Useful for diagnostic UIs that show full constraint status. */ evaluateAllConstraints(entityName: string, data: Record): Promise; createInstance(entityName: string, data: Partial, options?: { overrideRequests?: OverrideRequest[]; }): Promise; private prepareCreateData; /** * Build the virtual pre-persistence draft for an initialization command. * Includes ownership, declared defaults, initial lifecycle state, and * permitted initialization inputs. Declared defaults and initial lifecycle * state seed guard-time pre-state even when the command later mutates the * same field; mutations apply only when building the final document. */ private buildInitializationDraft; private reportConstraintOutcomes; private createInstanceWithOutcomes; /** Date/time primitive write-time validation (docs/spec/semantics.md, Date/Time Types). */ private validateDateTimeTypes; /** * Auto-managed field names the runtime supplies outside caller data. Mirrors the * create-null compile check so required-modifier enforcement does not flag fields * the engine fills itself (id, tenant, version, timestamps, composite key, FKs). */ private autoManagedFieldNames; /** * `required` modifier enforcement (docs/spec/semantics.md, "Modifier enforcement"). * A required property is satisfied only by a supplied value, a defaultValue, * autoNow, an auto-managed field, or a field the creating command writes — a * zero-filled type default does NOT satisfy it. Returns a blocking `E_REQUIRED` * outcome for each unsatisfied required property. */ private requiredModifierOutcomes; /** * `unique` modifier enforcement (docs/spec/semantics.md, "Modifier enforcement"). * Rejects a create/update that sets a unique property to a non-null value another * instance already holds, scanning instances in the active tenant scope. * // ponytail: O(n) scan per unique property; move to store-level uniqueness when * // the store adapter exposes a uniqueness constraint. */ private uniqueModifierOutcomes; /** * `alternateKeys` multi-column uniqueness (semantics.md § Composite Keys). * For each key group, if every column is non-null on the candidate and another * instance matches all columns, emit E_ALTERNATE_KEY. Groups with any * null/undefined column are skipped. */ private alternateKeyOutcomes; private persistPreparedCreate; updateInstance(entityName: string, id: string, data: Partial, options?: { overrideRequests?: OverrideRequest[]; }): Promise; /** * Mark cached computed properties as stale when their dependencies are mutated. * Scans the entity's computed properties for any that depend on the changed properties, * and sets their cache entries' stale flag to true. Handles transitive staleness. */ private markComputedPropertiesStale; deleteInstance(entityName: string, id: string): Promise; runCommand(commandName: string, input: Record, options?: { entityName?: string; instanceId?: string; overrideRequests?: OverrideRequest[]; /** Correlation ID for workflow event grouping */ correlationId?: string; /** Causation ID linking this command to its trigger */ causationId?: string; /** Caller-provided idempotency key for dedup. Required if idempotencyStore is configured. */ idempotencyKey?: string; }): Promise; /** * Execute a saga: run steps in declaration order, compensating completed * steps in reverse order on failure (when onFailure === 'compensate'). * Each step dispatches via `runCommand` — all policies, guards, and * constraints of the step's command still apply. */ runSaga(sagaName: string, stepInputs?: Record; instanceId?: string; }>, options?: { correlationId?: string; }): Promise; /** * Compensate completed saga steps in reverse order (best-effort). * Compensation failures are recorded but do not throw — all remaining * compensations still execute. */ private compensateSagaSteps; /** * Emit a saga lifecycle event (SagaStarted, SagaCompleted, SagaFailed, * SagaStepCompleted) only if declared in the saga's `emits` array. */ private emitSagaLifecycle; /** * Map a CommandResult and any thrown error into a CommandOutcome for the * AuditRecord. The mapping mirrors the exit paths inside runCommand and * _executeCommandInternal — keep them in lock-step when adding new * failure modes. */ private classifyOutcome; /** * Build and emit a single AuditRecord through the configured sink. * Sink errors are caught and logged — audit emission MUST NOT alter * command-execution behavior. This is the documented fail-open policy * (see docs/spec/adapters.md § "Audit Sink"). */ private emitAudit; /** * Enqueue emitted events into the configured OutboxStore as a batch. * Behavior depends on whether a command transaction is active: * * - Provider mode (this.activeTx set): the enqueue joins the command's * transaction (threading this.activeTx) and a failure is RETHROWN as an * OutboxEnqueueError so the transaction rolls back — the command then fails * with OUTBOX_ENQUEUE_FAILED rather than silently dropping a durable event. * - Non-provider mode (this.activeTx null): the enqueue is best-effort and * fail-open — a failure is logged to stderr and MUST NOT alter the * CommandResult the caller already received. */ private enqueueOutbox; /** * Provider-mode command execution. Wraps EACH attempt — command body + its * outbox enqueue + its idempotency record — in one `withTransaction` call, so * those writes commit or roll back together. A failed attempt (thrown store * error, thrown outbox failure, or a clean non-success result) rolls back * before the next attempt begins; only a committing attempt's writes survive. * In-process listener notifications are held until the transaction commits. * * Nested commands (reactions/sagas) never reach here: they run with * this.activeTx already set and take the non-transactional branch in * runCommand, joining this transaction rather than opening another. * * See docs/spec/adapters.md § "Outbox Store — Transaction Boundary". */ private _runCommandInTransaction; /** * Command parameter processing (docs/spec/semantics.md, "Commands"). * 1) Trusted-source params: strip any client-supplied value, inject from * RuntimeContext at `trustedSource` (fail closed with MISSING_TRUSTED_CONTEXT * when required and unresolved). * 2) Apply declared `defaultValue` for omitted args. * 3) Reject omitted required params with no default (MISSING_REQUIRED_PARAMETER). * An explicit `undefined` is treated as absent; `null` counts as supplied * for non-trusted params. Returns the augmented input on success. */ private processCommandParameters; /** * Resolve a trustedSource path like `context.actorId` against the active * RuntimeContext. Only `context.*` paths are supported (language grammar). */ private resolveTrustedSource; /** * Validate an async command synchronously (policies, constraints, guards) * without executing actions. Used for fail-fast before enqueuing a job. */ private _validateAsyncCommand; /** * Drain all pending jobs from the job queue and execute them. * Returns an array of CommandResults, one per drained job. * For deterministic testing: executes jobs synchronously in FIFO order. * * For each job: * - Sets context.source = 'job' to bypass the async enqueue branch * - Executes the full command body (actions + emits) * - Emits completion or failure event on the synthesized channel * - Updates job status in the queue */ drainJobs(): Promise; private _executeCommandInternal; private buildEvalContext; private checkPolicies; /** * Validate entity constraints against instance data * Returns array of constraint failures (empty if all pass) * * Constraint semantics: * - Expression evaluates to TRUE → condition is met → constraint PASSES * - Expression evaluates to FALSE → condition is not met → constraint FAILS * * Severity affects what gets reported as failures: * - severity='block': Failed constraints are returned as failures (block execution) * - severity='warn': Failed constraints are NOT returned as failures (informational only) * - severity='ok': Failed constraints are NOT returned as failures (informational only) * * CONSTRAINT SEMANTICS (vNext hybrid support): * - Positive constraints (default): Expression describes what MUST be true for validity * - When FALSE → constraint FAILS (e.g., "amount >= 0" fails when amount = -1) * - When TRUE → constraint PASSES * - Negative constraints (detected by "severity" prefix): Expression describes BAD state * - When TRUE → constraint FIRES (e.g., "status == 'cancelled'" fires when cancelled) * - When FALSE → constraint PASSES (no bad state present) */ private validateConstraints; private extractContextKeys; private formatExpression; private formatValue; private resolveExpressionValues; private notifyActionTrace; private executeAction; /** * Build a NAMED EmittedEvent for an `emit`/`publish` action, mirroring the * shape of a `command.emits` event (channel from the declared IR event, * provenance, correlation/causation, and a monotonic per-command emitIndex). * Payload is the evaluated expression value: a plain object is used directly, * a scalar is wrapped as `{ result: value }`, and null/undefined becomes `{}`. */ private buildActionEvent; /** * Flush the current command buffer's accumulated patch to the store, threading * the active transaction. Clears the patch afterward (retaining the working * copy) so a later end-of-loop flush or a subsequent explicit `persist` does * not re-write the same fields. No-op when no buffer/patch is present. Used by * both the `persist` action and the end-of-command-loop flush. */ private flushCommandBuffer; evaluateExpression(expr: IRExpression, context: Record): Promise; private evaluateBinaryOp; private evaluateUnaryOp; private irValueToJs; private getDefaultForType; evaluateComputed(entityName: string, instanceId: string, propertyName: string): Promise; /** * Evaluate a computed property and return metadata including cache status and staleness. * Returns { value, stale, cached } or undefined if the entity/property/instance doesn't exist. */ evaluateComputedWithMeta(entityName: string, instanceId: string, propertyName: string): Promise<{ value: unknown; stale: boolean; cached: boolean; } | undefined>; /** * Look up a cached computed property value based on the configured cache strategy. * Returns the cache entry if valid, or undefined if cache miss or expired. */ private getCachedComputedValue; /** * Store a computed property value in the appropriate cache based on strategy. */ private setCachedComputedValue; private evaluateComputedInternal; /** * vNext: Interpolate template placeholders with values from context * Supports {placeholder} syntax where placeholders are resolved from: * 1. details mapping (if present) * 2. resolved expression values (by expression string) * 3. evaluation context (direct property access) */ private interpolateTemplate; /** * vNext: Evaluate a single constraint and return detailed outcome */ private evaluateConstraint; /** * vNext: Evaluate command constraints with override support * Returns allowed flag, all constraint outcomes, and any OverrideApplied events. * Per spec (manifest-vnext.md § OverrideApplied Event Shape): * OverrideApplied events MUST be included in CommandResult.emittedEvents. */ private evaluateCommandConstraints; /** * Apply explicit OverrideRequest and/or auto-policy override to a failed * overrideable constraint. Mutates `outcome` when authorized. Emits * OverrideApplied to the event log. Returns the audit event when applied. */ private tryApplyConstraintOverride; /** * vNext: Validate override authorization via policy or default admin check */ private validateOverrideAuthorization; /** * vNext: Build OverrideApplied event for auditing. * Per spec (manifest-vnext.md § OverrideApplied Event Shape): * payload MUST contain: constraintCode, reason, authorizedBy, timestamp, commandName, * and optionally entityName, instanceId. * The event is a runtime-synthesized event included in CommandResult.emittedEvents. */ private buildOverrideAppliedEvent; /** * vNext: Emit ConcurrencyConflict event */ private emitConcurrencyConflictEvent; /** * vNext: Get provenance info for events */ private getProvenanceInfo; onEvent(listener: EventListener): () => void; /** * Subscribe to events for a single entity (docs/spec/semantics.md, * "Realtime Entities"). Convenience over onEvent: the listener receives * only events whose `subject.entity === entityName`. Events WITHOUT a * subject entity are NOT delivered — use onEvent for the unfiltered * firehose. Returns an unsubscribe function. Exists regardless of any * entity's `realtime` flag (the flag is a projection hint only). */ subscribe(entityName: string, listener: EventListener): () => void; /** Whether a cross-instance EventBus is wired into RuntimeOptions.eventBus. */ hasEventBus(): boolean; /** * This engine's stable EventBus `originId`, derived on first bus use (never in * the constructor — see `_instanceId`) and memoized so every publish and the * self-echo filter agree on one value for the engine's lifetime. */ private instanceId; /** * Subscribe this engine to the configured EventBus and re-dispatch REMOTE * events to local onEvent/subscribe listeners, so an SSE surface backed by * one engine observes events emitted by a command on another. Messages * published by this same engine (originId === this.instanceId) are skipped so * a local listener is never double-notified. Resolves once the subscription * is active; the returned function unsubscribes. * * Idempotent: calling it again while already connected returns the existing * unsubscribe without opening a second subscription. The constructor stays * synchronous — subscription is deferred to this awaited call. */ connectEventBus(): Promise<() => Promise>; /** * Publish one command's collected event batch to the EventBus. Post-commit * and best-effort: a publish failure is logged and never fails the command * (the events are already durable / already delivered locally). No-op without * a bus or with an empty batch. Mirrors the outbox non-provider fail-open * policy (docs/spec/adapters.md § "Event Bus — Failure Policy"). */ private publishBatchToEventBus; private notifyListeners; /** Deliver one event to every registered listener (errors swallowed). */ private dispatchToListeners; getEventLog(): EmittedEvent[]; clearEventLog(): void; serialize(): Promise<{ ir: IR; context: RuntimeContext; stores: Record; }>; restore(data: { stores: Record; }): Promise; /** * Static factory method to create a RuntimeEngine with optional provenance verification. * This is useful when you want to verify IR integrity before execution. * * In production mode (NODE_ENV=production), provenance verification is enabled by default. * Set `requireValidProvenance: false` to explicitly disable. * * @param ir - The IR to execute * @param context - Runtime context (user, etc.) * @param options - Runtime options including requireValidProvenance * @returns A tuple of [runtime, verificationResult] * * @example * ```ts * // Production: verification enabled by default * const [runtime, result] = await RuntimeEngine.create(ir, context); * if (!result.valid) { * throw new Error(`Invalid IR: ${result.error}`); * } * * // Development: explicitly disable verification * const [runtime] = await RuntimeEngine.create(ir, context, { requireValidProvenance: false }); * ``` */ /** * Build an approval-request key for the Map. */ private approvalKey; /** * Find approval declarations on an entity that gate a given command name. */ private findApprovalsForCommand; /** * Check the approval gate for a command. Returns a CommandResult (blocked) * if the command requires approval that hasn't been granted yet, or * undefined if the command may proceed. */ private checkApprovalGate; /** * Get the list of stages that still need approvals. */ private getPendingStages; /** * Request approval for a command on an entity instance. * Creates or returns the existing approval request state. */ requestApproval(entityName: string, instanceId: string, approvalName: string): Promise; /** * Grant approval for a specific stage. Evaluates the stage policy to verify * the approver is authorized. When all required stages are satisfied, marks * the approval as 'granted'. */ approveStage(entityName: string, instanceId: string, approvalName: string, stageName: string, approver: ApprovalApprover): Promise; /** * Deny an approval request. */ denyApproval(entityName: string, instanceId: string, approvalName: string, deniedBy: string, reason?: string): Promise; /** * Apply timeout actions for pending approvals past their deadline. * - `onTimeout: cancel` (or unset): status → `expired` * - `onTimeout: escalate {…}`: evaluate open `to` expression, apply author * `status` / `timeout`, record `escalatedTo` / `escalatedAt` * * Operates on the in-process request set. In durable mode * (`options.approvalStore` configured), run set-based expiry across all * stored requests via `approvalStore.expire(now)` from a cron/worker; this * accessor only sees requests this engine has touched. */ expireApprovals(now?: number): Promise; /** * Get the current approval request state for an entity instance. */ getApprovalRequest(entityName: string, instanceId: string, approvalName: string): ApprovalRequestState | undefined; static create(ir: IR, context?: RuntimeContext, options?: RuntimeOptions): Promise<[RuntimeEngine, ProvenanceVerificationResult]>; } //# sourceMappingURL=runtime-engine.d.ts.map