@blokjs/runner 0.2.1 → 0.4.0

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 (159) hide show
  1. package/dist/Blok.js +11 -11
  2. package/dist/Blok.js.map +1 -1
  3. package/dist/Configuration.d.ts +39 -2
  4. package/dist/Configuration.js +337 -28
  5. package/dist/Configuration.js.map +1 -1
  6. package/dist/ConfigurationResolver.d.ts +9 -0
  7. package/dist/ConfigurationResolver.js +17 -1
  8. package/dist/ConfigurationResolver.js.map +1 -1
  9. package/dist/PayloadTooLargeError.d.ts +19 -0
  10. package/dist/PayloadTooLargeError.js +29 -0
  11. package/dist/PayloadTooLargeError.js.map +1 -0
  12. package/dist/RunCancelledError.d.ts +17 -0
  13. package/dist/RunCancelledError.js +25 -0
  14. package/dist/RunCancelledError.js.map +1 -0
  15. package/dist/RunnerSteps.js +363 -23
  16. package/dist/RunnerSteps.js.map +1 -1
  17. package/dist/RuntimeAdapterNode.d.ts +32 -2
  18. package/dist/RuntimeAdapterNode.js +122 -27
  19. package/dist/RuntimeAdapterNode.js.map +1 -1
  20. package/dist/SubworkflowNode.d.ts +75 -0
  21. package/dist/SubworkflowNode.js +221 -0
  22. package/dist/SubworkflowNode.js.map +1 -0
  23. package/dist/TriggerBase.d.ts +128 -0
  24. package/dist/TriggerBase.js +808 -6
  25. package/dist/TriggerBase.js.map +1 -1
  26. package/dist/WaitDispatchRequest.d.ts +38 -0
  27. package/dist/WaitDispatchRequest.js +13 -0
  28. package/dist/WaitDispatchRequest.js.map +1 -0
  29. package/dist/WaitNode.d.ts +23 -0
  30. package/dist/WaitNode.js +26 -0
  31. package/dist/WaitNode.js.map +1 -0
  32. package/dist/adapters/BunRuntimeAdapter.d.ts +1 -0
  33. package/dist/adapters/BunRuntimeAdapter.js +1 -0
  34. package/dist/adapters/BunRuntimeAdapter.js.map +1 -1
  35. package/dist/adapters/DockerRuntimeAdapter.d.ts +2 -1
  36. package/dist/adapters/DockerRuntimeAdapter.js +10 -1
  37. package/dist/adapters/DockerRuntimeAdapter.js.map +1 -1
  38. package/dist/adapters/HttpRuntimeAdapter.d.ts +26 -5
  39. package/dist/adapters/HttpRuntimeAdapter.js +97 -16
  40. package/dist/adapters/HttpRuntimeAdapter.js.map +1 -1
  41. package/dist/adapters/NodeJsRuntimeAdapter.d.ts +1 -0
  42. package/dist/adapters/NodeJsRuntimeAdapter.js +1 -0
  43. package/dist/adapters/NodeJsRuntimeAdapter.js.map +1 -1
  44. package/dist/adapters/RuntimeAdapter.d.ts +17 -0
  45. package/dist/adapters/WasmRuntimeAdapter.d.ts +1 -0
  46. package/dist/adapters/WasmRuntimeAdapter.js +1 -0
  47. package/dist/adapters/WasmRuntimeAdapter.js.map +1 -1
  48. package/dist/adapters/grpc/GrpcChannelOptions.d.ts +31 -0
  49. package/dist/adapters/grpc/GrpcChannelOptions.js +68 -0
  50. package/dist/adapters/grpc/GrpcChannelOptions.js.map +1 -0
  51. package/dist/adapters/grpc/GrpcClientPool.d.ts +43 -0
  52. package/dist/adapters/grpc/GrpcClientPool.js +89 -0
  53. package/dist/adapters/grpc/GrpcClientPool.js.map +1 -0
  54. package/dist/adapters/grpc/GrpcCodec.d.ts +226 -0
  55. package/dist/adapters/grpc/GrpcCodec.js +275 -0
  56. package/dist/adapters/grpc/GrpcCodec.js.map +1 -0
  57. package/dist/adapters/grpc/GrpcErrors.d.ts +59 -0
  58. package/dist/adapters/grpc/GrpcErrors.js +190 -0
  59. package/dist/adapters/grpc/GrpcErrors.js.map +1 -0
  60. package/dist/adapters/grpc/GrpcHealthChecker.d.ts +69 -0
  61. package/dist/adapters/grpc/GrpcHealthChecker.js +96 -0
  62. package/dist/adapters/grpc/GrpcHealthChecker.js.map +1 -0
  63. package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +98 -0
  64. package/dist/adapters/grpc/GrpcRuntimeAdapter.js +478 -0
  65. package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -0
  66. package/dist/adapters/grpc/index.d.ts +13 -0
  67. package/dist/adapters/grpc/index.js +14 -0
  68. package/dist/adapters/grpc/index.js.map +1 -0
  69. package/dist/adapters/grpc/proto/blok/runtime/v1/runtime.proto +302 -0
  70. package/dist/adapters/grpc/types.d.ts +97 -0
  71. package/dist/adapters/grpc/types.js +41 -0
  72. package/dist/adapters/grpc/types.js.map +1 -0
  73. package/dist/adapters/transport.d.ts +108 -0
  74. package/dist/adapters/transport.js +196 -0
  75. package/dist/adapters/transport.js.map +1 -0
  76. package/dist/concurrency/ConcurrencyBackend.d.ts +61 -0
  77. package/dist/concurrency/ConcurrencyBackend.js +20 -0
  78. package/dist/concurrency/ConcurrencyBackend.js.map +1 -0
  79. package/dist/concurrency/ConcurrencyLimitError.d.ts +37 -0
  80. package/dist/concurrency/ConcurrencyLimitError.js +16 -0
  81. package/dist/concurrency/ConcurrencyLimitError.js.map +1 -0
  82. package/dist/concurrency/NatsKvConcurrencyBackend.d.ts +64 -0
  83. package/dist/concurrency/NatsKvConcurrencyBackend.js +297 -0
  84. package/dist/concurrency/NatsKvConcurrencyBackend.js.map +1 -0
  85. package/dist/concurrency/QueueExpiredError.d.ts +40 -0
  86. package/dist/concurrency/QueueExpiredError.js +15 -0
  87. package/dist/concurrency/QueueExpiredError.js.map +1 -0
  88. package/dist/concurrency/createConcurrencyBackend.d.ts +23 -0
  89. package/dist/concurrency/createConcurrencyBackend.js +34 -0
  90. package/dist/concurrency/createConcurrencyBackend.js.map +1 -0
  91. package/dist/concurrency/readConcurrencyConfig.d.ts +60 -0
  92. package/dist/concurrency/readConcurrencyConfig.js +60 -0
  93. package/dist/concurrency/readConcurrencyConfig.js.map +1 -0
  94. package/dist/idempotency/resolveIdempotencyKey.d.ts +20 -0
  95. package/dist/idempotency/resolveIdempotencyKey.js +37 -0
  96. package/dist/idempotency/resolveIdempotencyKey.js.map +1 -0
  97. package/dist/index.d.ts +35 -3
  98. package/dist/index.js +61 -2
  99. package/dist/index.js.map +1 -1
  100. package/dist/monitoring/ConcurrencyMetrics.d.ts +56 -0
  101. package/dist/monitoring/ConcurrencyMetrics.js +107 -0
  102. package/dist/monitoring/ConcurrencyMetrics.js.map +1 -0
  103. package/dist/monitoring/JanitorMetrics.d.ts +27 -0
  104. package/dist/monitoring/JanitorMetrics.js +48 -0
  105. package/dist/monitoring/JanitorMetrics.js.map +1 -0
  106. package/dist/scheduling/DebounceCoordinator.d.ts +88 -0
  107. package/dist/scheduling/DebounceCoordinator.js +141 -0
  108. package/dist/scheduling/DebounceCoordinator.js.map +1 -0
  109. package/dist/scheduling/DeferredDispatchSignal.d.ts +50 -0
  110. package/dist/scheduling/DeferredDispatchSignal.js +14 -0
  111. package/dist/scheduling/DeferredDispatchSignal.js.map +1 -0
  112. package/dist/scheduling/DeferredRunScheduler.d.ts +68 -0
  113. package/dist/scheduling/DeferredRunScheduler.js +154 -0
  114. package/dist/scheduling/DeferredRunScheduler.js.map +1 -0
  115. package/dist/scheduling/readSchedulingConfig.d.ts +24 -0
  116. package/dist/scheduling/readSchedulingConfig.js +52 -0
  117. package/dist/scheduling/readSchedulingConfig.js.map +1 -0
  118. package/dist/testing/WorkflowTestRunner.js +12 -0
  119. package/dist/testing/WorkflowTestRunner.js.map +1 -1
  120. package/dist/timeouts/StepTimeoutError.d.ts +22 -0
  121. package/dist/timeouts/StepTimeoutError.js +31 -0
  122. package/dist/timeouts/StepTimeoutError.js.map +1 -0
  123. package/dist/tracing/InMemoryRunStore.d.ts +28 -1
  124. package/dist/tracing/InMemoryRunStore.js +150 -0
  125. package/dist/tracing/InMemoryRunStore.js.map +1 -1
  126. package/dist/tracing/Janitor.d.ts +70 -0
  127. package/dist/tracing/Janitor.js +150 -0
  128. package/dist/tracing/Janitor.js.map +1 -0
  129. package/dist/tracing/PostgresRunStore.d.ts +30 -0
  130. package/dist/tracing/PostgresRunStore.js +435 -3
  131. package/dist/tracing/PostgresRunStore.js.map +1 -1
  132. package/dist/tracing/RunStore.d.ts +100 -1
  133. package/dist/tracing/RunTracker.d.ts +261 -11
  134. package/dist/tracing/RunTracker.js +691 -11
  135. package/dist/tracing/RunTracker.js.map +1 -1
  136. package/dist/tracing/SqliteRunStore.d.ts +23 -1
  137. package/dist/tracing/SqliteRunStore.js +421 -6
  138. package/dist/tracing/SqliteRunStore.js.map +1 -1
  139. package/dist/tracing/TraceRouter.d.ts +20 -2
  140. package/dist/tracing/TraceRouter.js +494 -9
  141. package/dist/tracing/TraceRouter.js.map +1 -1
  142. package/dist/tracing/sanitize.d.ts +11 -0
  143. package/dist/tracing/sanitize.js +29 -0
  144. package/dist/tracing/sanitize.js.map +1 -1
  145. package/dist/tracing/types.d.ts +429 -11
  146. package/dist/types/GlobalOptions.d.ts +9 -2
  147. package/dist/utils/createChildContext.d.ts +32 -0
  148. package/dist/utils/createChildContext.js +113 -0
  149. package/dist/utils/createChildContext.js.map +1 -0
  150. package/dist/workflow/PersistenceHelper.d.ts +46 -0
  151. package/dist/workflow/PersistenceHelper.js +57 -0
  152. package/dist/workflow/PersistenceHelper.js.map +1 -0
  153. package/dist/workflow/WorkflowNormalizer.d.ts +79 -0
  154. package/dist/workflow/WorkflowNormalizer.js +486 -0
  155. package/dist/workflow/WorkflowNormalizer.js.map +1 -0
  156. package/dist/workflow/WorkflowRegistry.d.ts +64 -0
  157. package/dist/workflow/WorkflowRegistry.js +81 -0
  158. package/dist/workflow/WorkflowRegistry.js.map +1 -0
  159. package/package.json +10 -7
@@ -1,4 +1,4 @@
1
- import type { Dashboard, MetricsResult, NodeRun, RunEvent, RunQuery, TraceLogEntry, WorkflowRun, WorkflowSummary } from "./types";
1
+ import type { CachedStepResult, ConcurrencySlotResult, Dashboard, MetricsResult, NodeRun, RunEvent, RunQuery, ScheduledDispatchRow, TraceLogEntry, WorkflowRun, WorkflowSummary } from "./types";
2
2
  /**
3
3
  * Storage abstraction for trace data.
4
4
  *
@@ -31,8 +31,107 @@ export interface RunStore {
31
31
  listDashboards(): Dashboard[];
32
32
  deleteDashboard(dashboardId: string): boolean;
33
33
  updateDashboard(dashboardId: string, updates: Partial<Dashboard>): void;
34
+ /**
35
+ * Return every run whose `parentRunId` matches the given run id, sorted
36
+ * oldest-first. Used by Studio's "Sub-runs" list on a parent's run-detail
37
+ * page. Returns `[]` when the run has no children.
38
+ */
39
+ getRunsByParent(parentRunId: string): WorkflowRun[];
34
40
  clearAll(): number;
35
41
  deleteRunsBefore(timestamp: number): number;
36
42
  evictOldRuns(maxRuns: number): void;
43
+ /**
44
+ * Look up a previously-cached step result. Returns null on miss or
45
+ * when the entry has expired (expired entries are lazily purged on read).
46
+ */
47
+ getIdempotencyCache(workflowName: string, stepId: string, key: string): CachedStepResult | null;
48
+ /**
49
+ * Store a step result keyed by (workflowName, stepId, key). Overwrites any
50
+ * previous entry for the same triple. Pass `expiresAt: null` for no TTL.
51
+ */
52
+ setIdempotencyCache(workflowName: string, stepId: string, key: string, entry: CachedStepResult): void;
53
+ /**
54
+ * Delete every entry whose `expiresAt` is non-null and `<= now`. Returns
55
+ * the number of rows removed. Cheap to call periodically; safe under
56
+ * concurrent reads.
57
+ */
58
+ purgeExpiredIdempotencyCache(now: number): number;
59
+ /**
60
+ * Try to acquire a slot for `(workflowName, concurrencyKey)`. Lazily
61
+ * purges expired leases for the same key first, then grants the slot
62
+ * iff `currentInFlight < concurrencyLimit`.
63
+ *
64
+ * Returns:
65
+ * `{ acquired: true, currentInFlight: <new count> }` on success
66
+ * `{ acquired: false, currentInFlight: <observed count> }` on denial
67
+ *
68
+ * The lease has a hard upper bound (`leaseExpiresAt` in ms since epoch).
69
+ * Callers MUST release the slot in a `finally` block; the lease is the
70
+ * crash-safety net for processes that die before release.
71
+ */
72
+ acquireConcurrencySlot(workflowName: string, concurrencyKey: string, concurrencyLimit: number, runId: string, leaseExpiresAt: number): ConcurrencySlotResult;
73
+ /**
74
+ * Release a slot previously acquired by `runId`. Idempotent — releasing
75
+ * an unknown `runId` is a no-op (covers double-release races between
76
+ * the happy-path `finally` block and the lazy-purge fallback).
77
+ */
78
+ releaseConcurrencySlot(workflowName: string, concurrencyKey: string, runId: string): void;
79
+ /**
80
+ * Delete every lease whose `expiresAt <= now`. Returns the number of
81
+ * rows removed. Cheap to call periodically (e.g. from a janitor task);
82
+ * the gate also lazy-purges per-key on every acquire call.
83
+ */
84
+ purgeExpiredConcurrencySlots(now: number): number;
85
+ /**
86
+ * Snapshot of in-flight concurrency slots — used by the
87
+ * `/__blok/concurrency/state` endpoint and Studio's in-flight tile.
88
+ *
89
+ * Returns one entry per `(workflowName, concurrencyKey)` bucket with
90
+ * an array of currently-held leases (un-expired). Empty buckets are
91
+ * omitted. Sort order is unspecified — caller sorts as needed.
92
+ */
93
+ getConcurrencySnapshot(now: number): Array<{
94
+ workflowName: string;
95
+ concurrencyKey: string;
96
+ leases: Array<{
97
+ runId: string;
98
+ expiresAt: number;
99
+ }>;
100
+ }>;
101
+ /**
102
+ * Persist a scheduled dispatch row, or replace an existing one with
103
+ * the same `runId` (debounce reset, queue re-defer). Idempotent.
104
+ *
105
+ * Called by `DeferredRunScheduler.schedule()` when a `persist` payload
106
+ * is provided. The row carries everything the trigger needs to
107
+ * reconstruct dispatch on boot.
108
+ */
109
+ upsertScheduledDispatch(row: ScheduledDispatchRow): void;
110
+ /**
111
+ * Delete a scheduled dispatch row. Returns true when a row existed.
112
+ * Idempotent — safe to call on rows that have already been deleted
113
+ * (e.g. after the timer fires + cancel races).
114
+ */
115
+ deleteScheduledDispatch(runId: string): boolean;
116
+ /**
117
+ * List scheduled dispatches, optionally filtered by trigger type
118
+ * and/or status. Used by trigger boot recovery (HttpTrigger) to
119
+ * find rows it owns.
120
+ */
121
+ getScheduledDispatches(opts?: {
122
+ triggerType?: string;
123
+ status?: string;
124
+ }): ScheduledDispatchRow[];
125
+ /**
126
+ * Janitor sweep — delete every `scheduled_dispatches` row whose
127
+ * `expires_at` has elapsed (`expires_at IS NOT NULL AND expires_at < now`).
128
+ * Rows without a TTL are left alone (their owning runs may legitimately
129
+ * stay queued indefinitely). Returns the count of deleted rows.
130
+ *
131
+ * Forward-scheduled rows whose owning runs were deleted (orphan dispatch
132
+ * rows) get reaped on the next `HttpTrigger.recoverDispatches()` call,
133
+ * not here. This sweep only handles past-TTL rows.
134
+ */
135
+ purgeExpiredScheduledDispatches(now: number): number;
37
136
  close(): void;
38
137
  }
@@ -1,6 +1,7 @@
1
1
  import { EventEmitter } from "node:events";
2
+ import type { ConcurrencyBackend } from "../concurrency/ConcurrencyBackend";
2
3
  import type { RunStore } from "./RunStore";
3
- import type { Dashboard, MetricsResult, NodeRun, RunEvent, StartNodeOptions, StartRunOptions, TraceLogEntry, WorkflowRun, WorkflowRunStatus, WorkflowSummary } from "./types";
4
+ import type { ConcurrencySlotResult, Dashboard, MetricsResult, NodeRun, RunEvent, RunQuery, StartNodeOptions, StartRunOptions, TraceLogEntry, WorkflowRun, WorkflowSummary } from "./types";
4
5
  /** Webhook registration for run event notifications. */
5
6
  export interface Webhook {
6
7
  id: string;
@@ -28,28 +29,277 @@ export declare class RunTracker extends EventEmitter {
28
29
  get active(): boolean;
29
30
  startRun(opts: StartRunOptions): WorkflowRun;
30
31
  completeRun(runId: string, data?: unknown): void;
31
- failRun(runId: string, error: Error): void;
32
+ failRun(runId: string, error: Error | unknown): void;
33
+ /**
34
+ * Tier 2 #6 — mark a run as throttled because the concurrency gate
35
+ * denied it before any step executed. Distinct from `failRun` because
36
+ * no step ran; nothing produced an error. Studio surfaces a Throttled
37
+ * badge and SSE subscribers see a granular `RUN_THROTTLED` event.
38
+ */
39
+ markRunThrottled(runId: string, info: {
40
+ concurrencyKey: string;
41
+ concurrencyLimit: number;
42
+ currentInFlight: number;
43
+ }): void;
44
+ /**
45
+ * Tier 2 #6 follow-up — mark a run as queued because the concurrency
46
+ * gate denied it AND the trigger is configured with `onLimit: "queue"`.
47
+ * The run will be re-attempted after `scheduledAt`; `scheduledAt` is
48
+ * persisted on the run record so Studio can render a "queued · retries
49
+ * at <time>" badge.
50
+ *
51
+ * Distinct from `markRunThrottled` because queued runs WILL eventually
52
+ * execute (or stay queued indefinitely until a slot frees), while
53
+ * throttled runs are terminal and `failRun` semantics are skipped.
54
+ *
55
+ * Caller is responsible for actually scheduling the retry via
56
+ * `DeferredRunScheduler`. This method only flips status + emits the
57
+ * `RUN_QUEUED` event. Re-marking with a later `scheduledAt` updates
58
+ * the field (used when re-defer happens after a timer-fired re-acquire
59
+ * also fails).
60
+ */
61
+ markRunQueued(runId: string, info: {
62
+ concurrencyKey: string;
63
+ concurrencyLimit: number;
64
+ currentInFlight: number;
65
+ scheduledAt: number;
66
+ }): void;
67
+ /**
68
+ * Tier 2 #5 — mark a run as `delayed`. Called immediately after
69
+ * `startRun` for runs that should be deferred. The run record carries
70
+ * `scheduledAt` (and optionally `expiresAt`) so Studio can render a
71
+ * "Delayed → fires at <time>" badge.
72
+ *
73
+ * Caller is responsible for actually scheduling the dispatch via
74
+ * `DeferredRunScheduler`. This method only flips status + emits the
75
+ * `RUN_DELAYED` event.
76
+ */
77
+ markRunDelayed(runId: string, info: {
78
+ scheduledAt: number;
79
+ delayMs: number;
80
+ expiresAt?: number;
81
+ }): void;
82
+ /**
83
+ * Tier 2 #5 — mark a run as `expired` because its TTL was exceeded
84
+ * before dispatch. Distinct from `failed` (no step ran) and
85
+ * `cancelled` (operator action — TTL is automatic).
86
+ */
87
+ markRunExpired(runId: string, info: {
88
+ expiresAt: number;
89
+ expiredAt: number;
90
+ }): void;
91
+ /**
92
+ * Tier 2 #7 — mark a run as `debounced`. In **leading** mode this is
93
+ * terminal: the ping was suppressed because a sibling fired
94
+ * immediately (`intoRunId` carries the sibling's id). In **trailing**
95
+ * mode this is transient: the same run is marked `debounced` while
96
+ * the timer is active and flips to `running` when the window closes
97
+ * (no separate transition method needed — `tracker` updates status
98
+ * directly via store before invoking the runner).
99
+ */
100
+ markRunDebounced(runId: string, info: {
101
+ debounceKey: string;
102
+ mode: "leading" | "trailing";
103
+ intoRunId?: string;
104
+ pingCount?: number;
105
+ scheduledAt?: number;
106
+ }): void;
107
+ /**
108
+ * Tier 2 quick-wins — mark a run as `crashed` (uncaught exception,
109
+ * OOM, signal). Distinct from `failRun` because the failure was
110
+ * NOT a step's `process()` throwing — it was the runner itself
111
+ * giving up. Currently manual; call from custom triggers / ops
112
+ * harnesses when uncaught failures are detected.
113
+ */
114
+ markRunCrashed(runId: string, info: {
115
+ error: Error | unknown;
116
+ }): void;
117
+ /**
118
+ * Tier 2 quick-wins follow-up — bulk-flip every run currently in
119
+ * `running` status to `crashed`. Returns the count flipped.
120
+ *
121
+ * Used by:
122
+ * - Process-level uncaught-exception handlers
123
+ * (`TriggerBase.installCrashHandlers`) — flip in-flight runs
124
+ * before the process dies.
125
+ * - Boot recovery (`TriggerBase.recoverOrphanedRuns`) — flip runs
126
+ * that were `running` from the previous (dead) process.
127
+ *
128
+ * Synchronous + safe to call from a `process.on("uncaughtException")`
129
+ * handler (which can't await). Backed by sync sqlite/in-memory
130
+ * writes that complete before the handler returns.
131
+ *
132
+ * Optional `opts.maxStartedAt` filter — only flip runs whose
133
+ * `startedAt` is at or before this timestamp. Used by boot recovery
134
+ * to avoid flipping runs from the current (live) process.
135
+ */
136
+ markAllRunningRunsAsCrashed(error: Error | unknown, opts?: {
137
+ maxStartedAt?: number;
138
+ }): number;
139
+ /**
140
+ * Tier 2 quick-wins — mark a run as `timedOut` because a step's
141
+ * final retry attempt exceeded its `maxDuration` cap. Distinct
142
+ * from `failed` so SLA dashboards can separate timeout-driven
143
+ * failures (network / capacity) from logic failures (bugs).
144
+ * Auto-called by `RunnerSteps` on final-attempt `StepTimeoutError`.
145
+ */
146
+ markRunTimedOut(runId: string, info: {
147
+ stepId: string;
148
+ maxDurationMs: number;
149
+ attemptsExhausted: number;
150
+ }): void;
151
+ /**
152
+ * Tier 2 #7 — record an additional ping into an existing trailing-mode
153
+ * debounce window. Increments `pingCount` and updates `scheduledAt`.
154
+ * Does NOT emit a new event (avoid event-stream bloat under burst).
155
+ */
156
+ recordDebouncePing(runId: string, opts: {
157
+ pingCount: number;
158
+ scheduledAt: number;
159
+ }): void;
160
+ /**
161
+ * Tier 2 #7 — transition a `delayed`/`debounced` run into `running`
162
+ * when its timer fires. Studio sees the status change via the
163
+ * existing run-update SSE stream.
164
+ */
165
+ transitionRunToRunning(runId: string): void;
166
+ /**
167
+ * Tier 2 polish — cancel a pending (delayed/debounced/queued) run.
168
+ * Idempotent. Returns true when the run existed AND was in a cancellable
169
+ * state; false when the run doesn't exist OR is already running/completed/
170
+ * failed/throttled/expired/crashed/timedOut/cancelled.
171
+ *
172
+ * **Caller responsibility**: this method only updates the run record
173
+ * (status → `"cancelled"`) and emits `RUN_CANCELLED`. The caller must
174
+ * separately clear any pending scheduler timers via
175
+ * `DeferredRunScheduler.getInstance().cancel(runId)` and (when applicable)
176
+ * `DebounceCoordinator.getInstance().cancel(workflowName, debounceKey)`.
177
+ * Done this way to avoid an import cycle from tracing → scheduling.
178
+ */
179
+ cancelRun(runId: string, options?: {
180
+ cascade?: boolean;
181
+ }): boolean;
182
+ /**
183
+ * Per-process map from runId to the AbortController owned by the
184
+ * trigger's createContext call. Populated by TriggerBase right after
185
+ * `startRun()`; cleared in TriggerBase's finally block. Used by
186
+ * `abortRunningRun` to fire the signal when an operator cancels a
187
+ * `running` run via the cancel API.
188
+ */
189
+ private abortControllers;
190
+ registerAbortController(runId: string, controller: AbortController): void;
191
+ unregisterAbortController(runId: string): void;
192
+ /**
193
+ * Tier 2 follow-up · cooperative cancellation for `running` runs.
194
+ *
195
+ * Fires the run's AbortController (so `ctx.signal.aborted` becomes
196
+ * true and any node consulting it can abort early) AND flips the run
197
+ * status to `"cancelled"` immediately via `cancelRun`. RunnerSteps'
198
+ * between-step abort check throws `RunCancelledError` shortly after,
199
+ * which TriggerBase catches without re-flipping the status.
200
+ *
201
+ * Returns true when an AbortController was registered for this run
202
+ * AND the status was successfully flipped; false otherwise (run not
203
+ * found, run not in `running` status, or no controller registered —
204
+ * e.g. controller already cleaned up).
205
+ */
206
+ abortRunningRun(runId: string): boolean;
207
+ /**
208
+ * Tier 2 #6 follow-up · cross-process concurrency backend.
209
+ *
210
+ * When set (via {@link setConcurrencyBackend}), the tracker's
211
+ * `acquireConcurrencySlot` and `releaseConcurrencySlot` methods
212
+ * delegate to the backend instead of the local sync `RunStore` impl.
213
+ * Used to coordinate across processes via NATS KV / Redis.
214
+ *
215
+ * Default `null` — preserves zero-overhead in-process behavior.
216
+ * Trigger packages install a backend in `listen()` when the operator
217
+ * sets `BLOK_CONCURRENCY_BACKEND=nats-kv`.
218
+ */
219
+ private concurrencyBackend;
220
+ setConcurrencyBackend(backend: ConcurrencyBackend | null): void;
221
+ getConcurrencyBackend(): ConcurrencyBackend | null;
222
+ /**
223
+ * Acquire a concurrency slot for `(workflowName, concurrencyKey)`.
224
+ * Delegates to the configured cross-process backend when set; falls
225
+ * back to the local sync `RunStore` impl otherwise.
226
+ *
227
+ * Async — the cross-process backend (NATS KV) is async-only. The
228
+ * sync fallback is wrapped in `Promise.resolve()` so the call site
229
+ * is uniform.
230
+ */
231
+ acquireConcurrencySlot(workflowName: string, concurrencyKey: string, concurrencyLimit: number, runId: string, leaseExpiresAt: number): Promise<ConcurrencySlotResult>;
232
+ /** Release a slot acquired via `acquireConcurrencySlot`. Idempotent. */
233
+ releaseConcurrencySlot(workflowName: string, concurrencyKey: string, runId: string): Promise<void>;
32
234
  startNode(runId: string, opts: StartNodeOptions): NodeRun;
33
235
  completeNode(nodeRunId: string, outputs?: unknown, nodeMetrics?: NodeRun["metrics"]): void;
34
- failNode(nodeRunId: string, error: Error): void;
236
+ /**
237
+ * Tier 1 idempotency cache hit. Marks the node as completed without
238
+ * having actually run, attaches the source-run/source-node lineage so
239
+ * Studio can render a CACHED badge with click-through, and emits a
240
+ * `NODE_CACHED` event so SSE subscribers see the short-circuit live.
241
+ *
242
+ * Caller is responsible for replaying the cached result through
243
+ * `PersistenceHelper.applyStepOutput` — this method only records the
244
+ * tracing side. Caching layers ABOVE persistence, never within it.
245
+ */
246
+ markNodeCached(nodeRunId: string, source: {
247
+ sourceRunId: string;
248
+ sourceNodeRunId: string;
249
+ cachedAt: number;
250
+ }, outputs?: unknown): void;
251
+ /**
252
+ * Tier 1 retry: record a single failed attempt before the next retry. The
253
+ * node stays in `running` status — `failNode` is the terminal call that
254
+ * fires only after `retry.maxAttempts` is exhausted.
255
+ *
256
+ * Per-node attempt history is capped at {@link MAX_STORED_ATTEMPTS} (10)
257
+ * to bound store growth on extreme retry counts. The cap matches the
258
+ * risk-register decision in `tier1-idempotency-replay-retry.md`.
259
+ */
260
+ recordNodeAttemptFailed(nodeRunId: string, info: {
261
+ attempt: number;
262
+ error: unknown;
263
+ }): void;
264
+ failNode(nodeRunId: string, error: Error | unknown): void;
35
265
  skipNode(runId: string, nodeName: string, stepIndex: number, reason?: string): void;
266
+ /**
267
+ * Record a streaming `Progress` frame for an in-flight node. Overwrites
268
+ * any previous progress (only the latest milestone is preserved on
269
+ * the {@link NodeRun} record). Emits a `NODE_PROGRESS` event so SSE
270
+ * subscribers (Studio) get the live update too.
271
+ *
272
+ * Master plan §17 Phase 5 follow-up — wires the proto `Progress`
273
+ * frame from `ExecuteStream` into the trace store + Studio.
274
+ *
275
+ * @param percent 0–100; values outside the range are clamped.
276
+ * @param phase optional free-form phase label (may be empty).
277
+ */
278
+ recordProgress(nodeRunId: string, percent: number, phase: string): void;
279
+ /**
280
+ * Record a streaming `PartialResult` snapshot for an in-flight node.
281
+ * Overwrites any previous snapshot. Emits a `NODE_PARTIAL_RESULT`
282
+ * event for SSE subscribers.
283
+ *
284
+ * Master plan §17 Phase 5 follow-up.
285
+ */
286
+ recordPartialResult(nodeRunId: string, snapshot: unknown): void;
36
287
  addLog(entry: Omit<TraceLogEntry, "id" | "timestamp">): void;
37
288
  trackVarsUpdate(runId: string, nodeName: string, nodeId: string | undefined, vars: Record<string, unknown>): void;
38
289
  getRun(runId: string): WorkflowRun | undefined;
39
- getRuns(opts?: {
40
- workflow?: string;
41
- status?: WorkflowRunStatus;
42
- tags?: string[];
43
- limit?: number;
44
- offset?: number;
45
- sort?: "asc" | "desc";
46
- }): {
290
+ getRuns(opts?: RunQuery): {
47
291
  runs: WorkflowRun[];
48
292
  total: number;
49
293
  };
50
294
  getNodeRuns(runId: string): NodeRun[];
51
295
  getNodeRun(nodeRunId: string): NodeRun | undefined;
52
296
  getEvents(runId: string, since?: number): RunEvent[];
297
+ /**
298
+ * Tier 2 sub-workflow lineage. Returns every run that was started by
299
+ * a `subworkflow:` step inside the given parent run. Powers Studio's
300
+ * "Sub-runs" list on a parent's run-detail page.
301
+ */
302
+ getRunsByParent(parentRunId: string): WorkflowRun[];
53
303
  getLogs(runId: string, nodeId?: string): TraceLogEntry[];
54
304
  getWorkflowSummaries(): WorkflowSummary[];
55
305
  addTag(runId: string, tag: string): boolean;