/** * Single source of truth for legal status transitions across the workflow * engine. Every status-write call site (engine.ts, worker.ts) must route * through `assertTransition` (via StateStore.transitionRun/Job/Step) instead * of mutating `.status` directly — this is what turns "any status can go to * any status" into a checked invariant. * * See docs/adr and the workflow status audit for the incident this closes: * a job could be marked `failed` while its step still sat in * `waiting_approval`, because nothing validated that transition was legal. */ type EntityKind = 'run' | 'job' | 'step'; declare const RUN_TRANSITIONS: Record; declare const JOB_TRANSITIONS: Record; declare const STEP_TRANSITIONS: Record; declare function isTerminal(state: string, kind: EntityKind): boolean; declare function isParked(state: string, kind: EntityKind): boolean; declare function isActive(state: string, kind: EntityKind): boolean; declare class IllegalStateTransitionError extends Error { readonly kind: EntityKind; readonly from: string; readonly to: string; readonly context?: Record | undefined; constructor(kind: EntityKind, from: string, to: string, context?: Record | undefined); } interface AssertTransitionOptions { /** * Bulk-reset escape hatch for replayRun/cleanupStaleRuns-style resets that * deliberately move a terminal entity back to `queued` (or similar) outside * the normal transition table. Must be passed explicitly and is grep-able * by design — never add a silent bypass inside the table itself. */ allowReset?: boolean; } /** * Throws IllegalStateTransitionError if `to` is not reachable from `from` * per the transition table for `kind`. No-ops (does not throw) when * `from === to` — idempotent re-writes of the same status are always legal. */ declare function assertTransition(kind: EntityKind, from: string, to: string, options?: AssertTransitionOptions): void; declare const RUN_STATES: readonly ["queued", "running", "success", "failed", "cancelled", "skipped", "dlq"]; declare const JOB_STATES: readonly ["queued", "running", "success", "failed", "cancelled", "skipped", "interrupted", "waiting_approval", "waiting_child"]; declare const STEP_STATES: readonly ["queued", "running", "success", "failed", "cancelled", "skipped", "dlq", "waiting_approval", "waiting_child"]; declare const JOB_PRIORITIES: readonly ["high", "normal", "low"]; type RunState = (typeof RUN_STATES)[number]; type JobState = (typeof JOB_STATES)[number]; type StepState = (typeof STEP_STATES)[number]; type JobPriority = (typeof JOB_PRIORITIES)[number]; declare const EVENT_NAMES: { readonly run: { readonly created: "run.created"; readonly started: "run.started"; readonly updated: "run.updated"; readonly finished: "run.finished"; readonly cancelled: "run.cancelled"; readonly failed: "run.failed"; }; readonly job: { readonly queued: "job.queued"; readonly started: "job.started"; readonly updated: "job.updated"; readonly succeeded: "job.succeeded"; readonly failed: "job.failed"; readonly cancelled: "job.cancelled"; readonly skipped: "job.skipped"; }; readonly step: { readonly queued: "step.queued"; readonly started: "step.started"; readonly updated: "step.updated"; readonly succeeded: "step.succeeded"; readonly failed: "step.failed"; readonly cancelled: "step.cancelled"; readonly skipped: "step.skipped"; readonly waitingApproval: "step.waitingApproval"; readonly waitingChild: "step.waitingChild"; }; readonly log: { readonly appended: "log.appended"; }; }; type WorkflowEventName = (typeof EVENT_NAMES)['run'][keyof (typeof EVENT_NAMES)['run']] | (typeof EVENT_NAMES)['job'][keyof (typeof EVENT_NAMES)['job']] | (typeof EVENT_NAMES)['step'][keyof (typeof EVENT_NAMES)['step']] | (typeof EVENT_NAMES)['log'][keyof (typeof EVENT_NAMES)['log']]; interface RedisKeyFactory { namespace: string; idempotency(key: string): string; concurrency(group: string): string; run(runId: string): string; artifacts(runId: string): string; jobQueue(priority?: JobPriority): string; runEvents(runId: string): string; eventChannel(): string; lock(name: string): string; } interface RedisKeyFactoryOptions { namespace?: string; } declare const DEFAULT_REDIS_NAMESPACE = "kb"; declare const WORKFLOW_REDIS_CHANNEL = "kb:wf:events"; declare function createRedisKeyFactory(options?: RedisKeyFactoryOptions): RedisKeyFactory; declare const IDEMPOTENCY_TTL_ENV = "KB_WF_IDEMP_TTL_MS"; declare const CONCURRENCY_TTL_ENV = "KB_WF_CONC_TTL_MS"; declare const REDIS_URL_ENV = "KB_REDIS_URL"; declare const REDIS_MODE_ENV = "KB_REDIS_MODE"; declare const REDIS_NAMESPACE_ENV = "KB_REDIS_NAMESPACE"; type RedisMode = 'standalone' | 'cluster' | 'sentinel'; export { type AssertTransitionOptions, CONCURRENCY_TTL_ENV, DEFAULT_REDIS_NAMESPACE, EVENT_NAMES, type EntityKind, IDEMPOTENCY_TTL_ENV, IllegalStateTransitionError, JOB_PRIORITIES, JOB_STATES, JOB_TRANSITIONS, type JobPriority, type JobState, REDIS_MODE_ENV, REDIS_NAMESPACE_ENV, REDIS_URL_ENV, RUN_STATES, RUN_TRANSITIONS, type RedisKeyFactory, type RedisKeyFactoryOptions, type RedisMode, type RunState, STEP_STATES, STEP_TRANSITIONS, type StepState, WORKFLOW_REDIS_CHANNEL, type WorkflowEventName, assertTransition, createRedisKeyFactory, isActive, isParked, isTerminal };