@blokjs/runner 0.4.0 → 0.6.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 (163) hide show
  1. package/dist/Blok.js +32 -3
  2. package/dist/Blok.js.map +1 -1
  3. package/dist/Configuration.d.ts +41 -5
  4. package/dist/Configuration.js +215 -92
  5. package/dist/Configuration.js.map +1 -1
  6. package/dist/ForEachNode.d.ts +59 -0
  7. package/dist/ForEachNode.js +522 -0
  8. package/dist/ForEachNode.js.map +1 -0
  9. package/dist/LoopMaxIterationsError.d.ts +11 -0
  10. package/dist/LoopMaxIterationsError.js +18 -0
  11. package/dist/LoopMaxIterationsError.js.map +1 -0
  12. package/dist/LoopNode.d.ts +36 -0
  13. package/dist/LoopNode.js +182 -0
  14. package/dist/LoopNode.js.map +1 -0
  15. package/dist/Runner.d.ts +11 -1
  16. package/dist/Runner.js +9 -2
  17. package/dist/Runner.js.map +1 -1
  18. package/dist/RunnerSteps.js +419 -112
  19. package/dist/RunnerSteps.js.map +1 -1
  20. package/dist/RuntimeAdapterNode.d.ts +2 -1
  21. package/dist/RuntimeAdapterNode.js +2 -2
  22. package/dist/RuntimeAdapterNode.js.map +1 -1
  23. package/dist/RuntimeRegistry.d.ts +23 -2
  24. package/dist/RuntimeRegistry.js +31 -2
  25. package/dist/RuntimeRegistry.js.map +1 -1
  26. package/dist/SubworkflowNode.d.ts +106 -0
  27. package/dist/SubworkflowNode.js +261 -3
  28. package/dist/SubworkflowNode.js.map +1 -1
  29. package/dist/SwitchNode.d.ts +37 -0
  30. package/dist/SwitchNode.js +153 -0
  31. package/dist/SwitchNode.js.map +1 -0
  32. package/dist/TriggerBase.d.ts +50 -0
  33. package/dist/TriggerBase.js +262 -4
  34. package/dist/TriggerBase.js.map +1 -1
  35. package/dist/TryCatchNode.d.ts +32 -0
  36. package/dist/TryCatchNode.js +207 -0
  37. package/dist/TryCatchNode.js.map +1 -0
  38. package/dist/adapters/grpc/GrpcCodec.js +2 -2
  39. package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +6 -4
  40. package/dist/adapters/grpc/GrpcRuntimeAdapter.js +6 -4
  41. package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -1
  42. package/dist/adapters/grpc/types.d.ts +7 -5
  43. package/dist/adapters/grpc/types.js.map +1 -1
  44. package/dist/adapters/transport.d.ts +12 -41
  45. package/dist/adapters/transport.js +21 -70
  46. package/dist/adapters/transport.js.map +1 -1
  47. package/dist/cache/NodeResultCache.js +7 -0
  48. package/dist/cache/NodeResultCache.js.map +1 -1
  49. package/dist/concurrency/NatsKvConcurrencyBackend.js +18 -5
  50. package/dist/concurrency/NatsKvConcurrencyBackend.js.map +1 -1
  51. package/dist/concurrency/RedisConcurrencyBackend.d.ts +64 -0
  52. package/dist/concurrency/RedisConcurrencyBackend.js +374 -0
  53. package/dist/concurrency/RedisConcurrencyBackend.js.map +1 -0
  54. package/dist/concurrency/createConcurrencyBackend.d.ts +1 -0
  55. package/dist/concurrency/createConcurrencyBackend.js +5 -1
  56. package/dist/concurrency/createConcurrencyBackend.js.map +1 -1
  57. package/dist/defineNode.d.ts +8 -0
  58. package/dist/defineNode.js +25 -5
  59. package/dist/defineNode.js.map +1 -1
  60. package/dist/graphql/GraphQLSchemaGenerator.js +1 -1
  61. package/dist/graphql/GraphQLSchemaGenerator.js.map +1 -1
  62. package/dist/index.d.ts +10 -6
  63. package/dist/index.js +13 -9
  64. package/dist/index.js.map +1 -1
  65. package/dist/marketplace/RuntimeCatalog.d.ts +6 -0
  66. package/dist/marketplace/RuntimeCatalog.js.map +1 -1
  67. package/dist/marketplace/RuntimeDiscovery.d.ts +2 -2
  68. package/dist/marketplace/RuntimeDiscovery.js +18 -6
  69. package/dist/marketplace/RuntimeDiscovery.js.map +1 -1
  70. package/dist/monitoring/ConcurrencyMetrics.d.ts +26 -0
  71. package/dist/monitoring/ConcurrencyMetrics.js +36 -4
  72. package/dist/monitoring/ConcurrencyMetrics.js.map +1 -1
  73. package/dist/monitoring/ForEachWaitMetrics.d.ts +22 -0
  74. package/dist/monitoring/ForEachWaitMetrics.js +36 -0
  75. package/dist/monitoring/ForEachWaitMetrics.js.map +1 -0
  76. package/dist/openapi/OpenAPIGenerator.js +7 -2
  77. package/dist/openapi/OpenAPIGenerator.js.map +1 -1
  78. package/dist/runtime/PrimitiveStack.d.ts +64 -0
  79. package/dist/runtime/PrimitiveStack.js +92 -0
  80. package/dist/runtime/PrimitiveStack.js.map +1 -0
  81. package/dist/scheduling/DebounceBackend.d.ts +108 -0
  82. package/dist/scheduling/DebounceBackend.js +23 -0
  83. package/dist/scheduling/DebounceBackend.js.map +1 -0
  84. package/dist/scheduling/DebounceCoordinator.d.ts +65 -12
  85. package/dist/scheduling/DebounceCoordinator.js +234 -13
  86. package/dist/scheduling/DebounceCoordinator.js.map +1 -1
  87. package/dist/scheduling/DeferredRunScheduler.d.ts +28 -0
  88. package/dist/scheduling/DeferredRunScheduler.js +105 -3
  89. package/dist/scheduling/DeferredRunScheduler.js.map +1 -1
  90. package/dist/scheduling/NatsKvDebounceBackend.d.ts +53 -0
  91. package/dist/scheduling/NatsKvDebounceBackend.js +334 -0
  92. package/dist/scheduling/NatsKvDebounceBackend.js.map +1 -0
  93. package/dist/scheduling/RedisDebounceBackend.d.ts +49 -0
  94. package/dist/scheduling/RedisDebounceBackend.js +356 -0
  95. package/dist/scheduling/RedisDebounceBackend.js.map +1 -0
  96. package/dist/scheduling/createDebounceBackend.d.ts +25 -0
  97. package/dist/scheduling/createDebounceBackend.js +39 -0
  98. package/dist/scheduling/createDebounceBackend.js.map +1 -0
  99. package/dist/security/AuditLogger.js +1 -1
  100. package/dist/security/AuditLogger.js.map +1 -1
  101. package/dist/security/AuthMiddleware.d.ts +19 -20
  102. package/dist/security/AuthMiddleware.js +35 -20
  103. package/dist/security/AuthMiddleware.js.map +1 -1
  104. package/dist/security/OAuthProvider.js +2 -2
  105. package/dist/security/OAuthProvider.js.map +1 -1
  106. package/dist/security/SecretManager.js +14 -13
  107. package/dist/security/SecretManager.js.map +1 -1
  108. package/dist/security/index.d.ts +3 -1
  109. package/dist/security/index.js +3 -1
  110. package/dist/security/index.js.map +1 -1
  111. package/dist/testing/TestHarness.d.ts +27 -12
  112. package/dist/testing/TestHarness.js +19 -3
  113. package/dist/testing/TestHarness.js.map +1 -1
  114. package/dist/testing/WorkflowTestRunner.js +0 -7
  115. package/dist/testing/WorkflowTestRunner.js.map +1 -1
  116. package/dist/tracing/InMemoryRunStore.d.ts +14 -1
  117. package/dist/tracing/InMemoryRunStore.js +95 -6
  118. package/dist/tracing/InMemoryRunStore.js.map +1 -1
  119. package/dist/tracing/PostgresRunStore.d.ts +28 -2
  120. package/dist/tracing/PostgresRunStore.js +276 -3
  121. package/dist/tracing/PostgresRunStore.js.map +1 -1
  122. package/dist/tracing/RoutingDiagnostics.d.ts +55 -0
  123. package/dist/tracing/RoutingDiagnostics.js +50 -0
  124. package/dist/tracing/RoutingDiagnostics.js.map +1 -0
  125. package/dist/tracing/RunStore.d.ts +82 -1
  126. package/dist/tracing/RunTracker.d.ts +7 -1
  127. package/dist/tracing/RunTracker.js +23 -0
  128. package/dist/tracing/RunTracker.js.map +1 -1
  129. package/dist/tracing/SqliteRunStore.d.ts +57 -2
  130. package/dist/tracing/SqliteRunStore.js +408 -48
  131. package/dist/tracing/SqliteRunStore.js.map +1 -1
  132. package/dist/tracing/TraceRouter.js +380 -18
  133. package/dist/tracing/TraceRouter.js.map +1 -1
  134. package/dist/tracing/createStore.js +14 -3
  135. package/dist/tracing/createStore.js.map +1 -1
  136. package/dist/tracing/metadataFilter.d.ts +63 -0
  137. package/dist/tracing/metadataFilter.js +224 -0
  138. package/dist/tracing/metadataFilter.js.map +1 -0
  139. package/dist/tracing/types.d.ts +331 -7
  140. package/dist/utils/envAllowlist.d.ts +35 -0
  141. package/dist/utils/envAllowlist.js +113 -0
  142. package/dist/utils/envAllowlist.js.map +1 -0
  143. package/dist/version/RuntimeVersionValidator.d.ts +38 -0
  144. package/dist/version/RuntimeVersionValidator.js +121 -0
  145. package/dist/version/RuntimeVersionValidator.js.map +1 -0
  146. package/dist/visualization/WorkflowVisualizer.js +4 -4
  147. package/dist/visualization/WorkflowVisualizer.js.map +1 -1
  148. package/dist/workflow/PersistenceHelper.d.ts +18 -10
  149. package/dist/workflow/PersistenceHelper.js +35 -9
  150. package/dist/workflow/PersistenceHelper.js.map +1 -1
  151. package/dist/workflow/WorkflowNormalizer.d.ts +19 -1
  152. package/dist/workflow/WorkflowNormalizer.js +469 -19
  153. package/dist/workflow/WorkflowNormalizer.js.map +1 -1
  154. package/dist/workflow/WorkflowRegistry.d.ts +122 -0
  155. package/dist/workflow/WorkflowRegistry.js +121 -0
  156. package/dist/workflow/WorkflowRegistry.js.map +1 -1
  157. package/dist/workflow/sampleBody.d.ts +54 -0
  158. package/dist/workflow/sampleBody.js +320 -0
  159. package/dist/workflow/sampleBody.js.map +1 -0
  160. package/package.json +3 -8
  161. package/dist/adapters/HttpRuntimeAdapter.d.ts +0 -79
  162. package/dist/adapters/HttpRuntimeAdapter.js +0 -233
  163. package/dist/adapters/HttpRuntimeAdapter.js.map +0 -1
@@ -161,6 +161,39 @@ export interface WorkflowRun {
161
161
  * encountered a wait step (preserves existing semantics).
162
162
  */
163
163
  lastCompletedStepIndex?: number;
164
+ /**
165
+ * v0.6 prerequisite for wait-inside-primitives Phase 2.
166
+ *
167
+ * JSON-serialized snapshot of `ctx.state` taken immediately before
168
+ * `RunnerSteps` throws `WaitDispatchRequest`. On `dispatchDeferred`
169
+ * re-entry — including the cross-process recovery path where the
170
+ * scheduler re-builds a fresh ctx from the persisted dispatch row —
171
+ * `TriggerBase.run` rehydrates `ctx.state` (and its `ctx.vars`
172
+ * alias) from this column so subsequent steps see the same
173
+ * pre-wait state regardless of process restart.
174
+ *
175
+ * Without this column, only the in-process timer-fire path would
176
+ * preserve state (state still lives on the original ctx). The
177
+ * cross-process path would resume with empty `ctx.state`, breaking
178
+ * Phase 2's iteration-state-persistence promise (a forEach
179
+ * iteration whose body fired the wait would lose its index, the
180
+ * loop's accumulator, etc.).
181
+ *
182
+ * Capped by `BLOK_STATE_SNAPSHOT_MAX_BYTES` (default 1MB,
183
+ * matching `BLOK_DISPATCH_PAYLOAD_MAX_BYTES`) — when the
184
+ * serialized state exceeds the cap, the runner logs a warning and
185
+ * skips the snapshot (the wait still defers; resumption is
186
+ * best-effort). Authors can opt out entirely via
187
+ * `BLOK_STATE_SNAPSHOT_DISABLED=1`. Sensitive keys aren't filtered
188
+ * here — the snapshot only persists what the workflow has already
189
+ * computed into `state`, so apply your own redaction in nodes that
190
+ * write secrets there.
191
+ *
192
+ * Undefined for runs that never threw `WaitDispatchRequest`, which
193
+ * is the vast majority. Persisted column is `state_snapshot`
194
+ * (sqlite migration v11).
195
+ */
196
+ stateSnapshot?: string;
164
197
  }
165
198
  export type NodeRunStatus = "pending" | "running" | "completed" | "failed" | "skipped";
166
199
  export interface NodeRun {
@@ -251,6 +284,24 @@ export interface NodeRun {
251
284
  * visible to Studio without recomputing from outputs heuristics.
252
285
  */
253
286
  wait?: boolean;
287
+ /**
288
+ * G2 (v0.6) sub-workflow dispatch strategy — only set for
289
+ * `nodeType === "subworkflow"`. Mirrors the step config's `dispatch`
290
+ * field so Studio can distinguish in-process invocations from HTTP
291
+ * self-call dispatches at a glance.
292
+ *
293
+ * - `"in-process"` (default; also represented by `undefined` on legacy
294
+ * traces) — child runs in the same Node process via the in-process
295
+ * `Runner`.
296
+ * - `"http-self"` — child is dispatched as a fresh HTTP request to
297
+ * `BLOK_SELF_BASE_URL`, potentially landing on a different process
298
+ * in a horizontally-scaled deployment.
299
+ *
300
+ * Captured at `tracker.startNode()` time alongside `wait` so the rail
301
+ * badge can compose them (`↳ async` orange + `http` sky for an
302
+ * async http-self dispatch).
303
+ */
304
+ dispatch?: "in-process" | "http-self";
254
305
  /**
255
306
  * PR 5 E3 — sub-workflow nesting depth surfaced for Studio.
256
307
  *
@@ -263,6 +314,138 @@ export interface NodeRun {
263
314
  * keep top-level invocations un-cluttered.
264
315
  */
265
316
  subworkflowDepth?: number;
317
+ /**
318
+ * v0.5 — origin middleware name when the trigger's
319
+ * `runMiddlewareChain` produced this NodeRun. The trigger sets a
320
+ * `_blokMiddlewareName` sentinel on ctx before dispatching each
321
+ * middleware; RunnerSteps reads it and propagates here so Studio's
322
+ * StepRail can surface a `mw:<name>` badge on the inner steps a
323
+ * middleware produced (otherwise indistinguishable from regular
324
+ * depth-1 nested steps).
325
+ */
326
+ middleware?: string;
327
+ /**
328
+ * v0.5.3 — iteration index for steps inside a `forEach` or `loop`
329
+ * primitive. Set per-iteration by ForEachNode / LoopNode on the
330
+ * cloned child ctx (`_blokIterationIndex` sentinel); RunnerSteps
331
+ * reads it and propagates here. Studio's StepRail groups consecutive
332
+ * sibling rows that share an iterationIndex into a collapsible
333
+ * "iteration N" header so a 5-iteration forEach with 3 inner steps
334
+ * renders as 5 grouped sections instead of 15 flat rows with
335
+ * duplicate names. Undefined for top-level steps, for steps inside
336
+ * non-iteration primitives (`tryCatch`, `switch`), and for legacy
337
+ * traces written before v0.5.3.
338
+ */
339
+ iterationIndex?: number;
340
+ /**
341
+ * v0.6 wait-inside-primitives — iteration cursor. Set on a primitive
342
+ * iterator's NodeRun (e.g. forEach, loop) by RunnerSteps when an
343
+ * INNER step throws `WaitDispatchRequest` mid-iteration. Read by
344
+ * `TriggerBase.run` on dispatchDeferred re-entry and stamped onto
345
+ * `ctx._blokIterationResume` so the primitive can pick it up.
346
+ * Persisted column is `iteration_context` (sqlite migration v12).
347
+ *
348
+ * Two encodings via a `mode` discriminator:
349
+ *
350
+ * - `mode: "sequential"` (or omitted for back-compat): the Phase 2
351
+ * sequential forEach + wait cursor (also reused by Phase 3 loop).
352
+ * Records the in-flight iteration index, the inner step index
353
+ * where the wait fired, and an in-order `completedResults`
354
+ * accumulator covering iterations `[0..iteration-1]`. ForEachNode
355
+ * and LoopNode read this on resume to skip the completed prefix.
356
+ *
357
+ * - `mode: "parallel"` (PR follow-up to Phase 3): the parallel
358
+ * forEach + wait cursor. Records which iteration's wait fired
359
+ * (`waitFiringIteration`), where inside its body
360
+ * (`innerStepIndex`), a SPARSE `completedResults` array (slot `i`
361
+ * populated iff iteration `i` finished before the wait fired —
362
+ * `null` for "ran but returned undefined", JSON-undefined hole
363
+ * for "not present"), and the explicit `cancelledIterations`
364
+ * list (in-flight + queued indices that need re-launch on
365
+ * resume). See `docs/c/devtools/parallel-foreach-wait-spec.mdx`
366
+ * for the full contract.
367
+ *
368
+ * Undefined on regular NodeRuns and on primitives that completed
369
+ * without ever throwing a wait.
370
+ */
371
+ iterationContext?: IterationContext;
372
+ }
373
+ /**
374
+ * Discriminated union for the iteration cursor persisted into
375
+ * `node_runs.iteration_context`. See `NodeRun.iterationContext` for
376
+ * field-level semantics. The runtime selection lives in:
377
+ *
378
+ * - Write side: `core/runner/src/RunnerSteps.ts` wait-throw site
379
+ * (sequential) and `ForEachNode.run` parallel-branch error handler
380
+ * (parallel).
381
+ * - Read side: `TriggerBase.run` rehydrate stamps the cursor onto
382
+ * `ctx._blokIterationResume`; the primitive itself (ForEachNode,
383
+ * LoopNode) dispatches on `mode` to the right resume path.
384
+ */
385
+ export type IterationContext = SequentialIterationContext | ParallelIterationContext | SwitchIterationContext;
386
+ /**
387
+ * Sequential forEach + wait OR loop + wait cursor. The pre-existing
388
+ * Phase 2/3 shape — `mode` is optional and defaults to "sequential" on
389
+ * read so cursors written before the discriminator landed (i.e., main
390
+ * before this PR) keep being interpreted correctly.
391
+ */
392
+ export interface SequentialIterationContext {
393
+ mode?: "sequential";
394
+ /** Iteration index (0-based) the wait fired in. */
395
+ iteration: number;
396
+ /** Inner step index within iteration's body where the wait fired. */
397
+ innerStepIndex: number;
398
+ /** Accumulator for iterations [0..iteration-1]; pre-populates `results[]` on resume. */
399
+ completedResults: unknown[];
400
+ }
401
+ /**
402
+ * v0.6 Phase 4 — switch + wait cursor. Records which case arm matched
403
+ * (so on re-entry SwitchNode walks back into the same arm) plus the
404
+ * inner step within that arm where the wait fired.
405
+ *
406
+ * `caseIndex` semantics:
407
+ * - `>= 0` — index into the workflow's `cases[]` array.
408
+ * - `-1` — the `default` arm matched.
409
+ *
410
+ * `completedResults` is unused for switch (it doesn't iterate) but the
411
+ * field is kept on the IterationContext union for cross-schema parity.
412
+ * Always `[]` on switch.
413
+ */
414
+ export interface SwitchIterationContext {
415
+ mode: "switch";
416
+ /** Resolved case index; `-1` denotes the `default` arm. */
417
+ caseIndex: number;
418
+ /** Inner step index within the matched arm where the wait fired. */
419
+ innerStepIndex: number;
420
+ /** Unused for switch; preserved at schema level for parity. Always `[]`. */
421
+ completedResults: never[];
422
+ }
423
+ /**
424
+ * Parallel forEach + wait cursor. The headline contract: persist
425
+ * completed iteration results, retry incomplete ones on resume. See
426
+ * `docs/c/devtools/parallel-foreach-wait-spec.mdx` for the full design.
427
+ */
428
+ export interface ParallelIterationContext {
429
+ mode: "parallel";
430
+ /** Index of the iteration whose inner step threw the wait. */
431
+ waitFiringIteration: number;
432
+ /** Inner step index within the wait-firing iteration's body. */
433
+ innerStepIndex: number;
434
+ /**
435
+ * Sparse array indexed by iteration number. Slot `i` is populated
436
+ * iff iteration `i` finished before the wait fired. `null` means
437
+ * "ran but returned undefined" (distinct from the JSON-undefined
438
+ * hole = "not present in cursor, re-launch on resume").
439
+ */
440
+ completedResults: (unknown | null)[];
441
+ /**
442
+ * Iteration indices that were in-flight OR queued at wait-throw
443
+ * moment. ForEachNode re-launches these from inner step 0 on
444
+ * resume. The wait-firing iteration is NOT in this list — it
445
+ * resumes via `innerStepIndex`. Sorted ascending for trace-dump
446
+ * readability.
447
+ */
448
+ cancelledIterations: number[];
266
449
  }
267
450
  /**
268
451
  * Stored result of a previously-successful step execution, keyed by
@@ -328,6 +511,26 @@ export interface ScheduledDispatchRow {
328
511
  payload: unknown;
329
512
  /** ms since epoch when the row was first written. */
330
513
  createdAt: number;
514
+ /**
515
+ * Tier C #2 — cross-process scheduler coordination. Set when a
516
+ * process claims this dispatch during boot recovery. Unset on
517
+ * unclaimed rows.
518
+ *
519
+ * Multi-process PG deployments use the claim to prevent the same
520
+ * dispatch from being fired by two processes that both ran
521
+ * `recoverDispatches()` against the shared table. Single-process
522
+ * and per-process sqlite deployments see no observable difference
523
+ * (the same process always wins the claim).
524
+ */
525
+ claimedBy?: string;
526
+ /**
527
+ * Tier C #2 — last heartbeat timestamp (ms since epoch). The
528
+ * holder of `claimedBy` periodically refreshes `claimedAt` while
529
+ * the timer is registered. Stale claims (now > claimedAt + leaseMs)
530
+ * are eligible for takeover by another process — protects against
531
+ * crashed holders blocking recovery indefinitely.
532
+ */
533
+ claimedAt?: number;
331
534
  }
332
535
  export type RunEventType = "RUN_STARTED" | "RUN_COMPLETED" | "RUN_FAILED" | "NODE_STARTED" | "NODE_COMPLETED" | "NODE_FAILED" | "NODE_SKIPPED" | "VARS_UPDATED" | "LOG_ENTRY"
333
536
  /** §17 Phase 5: streaming `Progress` frame from the SDK. */
@@ -443,6 +646,26 @@ export interface WorkflowDetail extends WorkflowSummary {
443
646
  definition?: unknown;
444
647
  nodeNames: string[];
445
648
  runtimes: string[];
649
+ /**
650
+ * Sample-body for the Studio empty-state curl. `source` lets the
651
+ * UI optionally distinguish author-declared examples from the
652
+ * synthesized ones (it doesn't today, but could in the future).
653
+ * Always populated for object-shaped workflows; `body` is `{}`
654
+ * when no body references were detected.
655
+ */
656
+ examples?: {
657
+ body: unknown;
658
+ /**
659
+ * Provenance of the body. `author` = declared in the workflow's
660
+ * `trigger.http.examples.body`. `recorded` = captured from the
661
+ * first successful run when `recordSample: true` is set on the
662
+ * trigger. `inferred` = synthesized from static analysis of step
663
+ * inputs. `empty` = no body references + no recording + no
664
+ * author override (the literal `{}`). Studio surfaces this in
665
+ * the Runs-tab empty-state curl snippet label.
666
+ */
667
+ source: "author" | "recorded" | "inferred" | "empty";
668
+ };
446
669
  }
447
670
  export interface PaginatedResult<T> {
448
671
  data: T[];
@@ -509,6 +732,13 @@ export interface StartNodeOptions {
509
732
  * Persisted onto `NodeRun.wait` so Studio can render `↳ async` vs `↳ sub`.
510
733
  */
511
734
  wait?: boolean;
735
+ /**
736
+ * G2 (v0.6) sub-workflow dispatch strategy. Only meaningful when
737
+ * `nodeType === "subworkflow"`. Persisted onto `NodeRun.dispatch` so
738
+ * Studio can render an extra `http` badge alongside `↳ async`/`↳ sub`
739
+ * for HTTP self-call dispatches.
740
+ */
741
+ dispatch?: "in-process" | "http-self";
512
742
  /**
513
743
  * PR 5 E3 — sub-workflow nesting depth. Only meaningful when
514
744
  * `nodeType === "subworkflow"`. The runner computes
@@ -516,6 +746,23 @@ export interface StartNodeOptions {
516
746
  * passes here so Studio can render `↳ sub (N)` for N >= 2.
517
747
  */
518
748
  subworkflowDepth?: number;
749
+ /**
750
+ * v0.5 — origin middleware name. Set when the parent ctx carries
751
+ * `_blokMiddlewareName` (HttpTrigger.runMiddlewareChain stash).
752
+ * Propagates onto `NodeRun.middleware` so Studio's StepRail can
753
+ * surface a `mw:<name>` badge.
754
+ */
755
+ middleware?: string;
756
+ /**
757
+ * v0.5.3 — iteration index for steps inside a `forEach` or `loop`
758
+ * primitive. Set when the parent ctx carries `_blokIterationIndex`
759
+ * (ForEachNode + LoopNode stash on each per-iteration child ctx).
760
+ * Studio's StepRail uses this to group consecutive sibling rows
761
+ * into "iteration N" sections instead of showing them flat with
762
+ * duplicated step names. Undefined for top-level steps and for
763
+ * inner steps of non-iteration primitives (`tryCatch`, `switch`).
764
+ */
765
+ iterationIndex?: number;
519
766
  }
520
767
  export type WidgetType = "stat-card" | "timeline" | "error-rate" | "duration-distribution" | "workflow-breakdown" | "node-performance" | "recent-runs" | "heatmap";
521
768
  export interface DashboardWidget {
@@ -545,19 +792,96 @@ export interface Dashboard {
545
792
  updatedAt: number;
546
793
  widgets: DashboardWidget[];
547
794
  }
795
+ /**
796
+ * E2 — server-side saved filter for the runs list. Mirrors the
797
+ * earlier localStorage shape (`apps/studio/src/lib/savedFilters.ts`)
798
+ * with `id` + timestamps added. Filter names are UNIQUE across the
799
+ * deployment — re-saving overwrites the existing row. Studio applies
800
+ * them by setting the run-list filter UI from the stored values.
801
+ */
802
+ export interface SavedFilter {
803
+ id: string;
804
+ name: string;
805
+ /** Workflow run status (`""` for "all"). */
806
+ status: string;
807
+ /**
808
+ * Free-form tag input as the user typed it ("env:prod,team:billing").
809
+ * Server stores it verbatim; the run-list URL parser interprets it.
810
+ */
811
+ tagsInput: string;
812
+ /**
813
+ * Free-form metadata input as the user typed it
814
+ * ("tier=premium,region__in=us,eu"). Same parsing contract as the
815
+ * runs-page URL — `MetadataFilter[]` once parsed.
816
+ */
817
+ metadataInput: string;
818
+ createdAt: number;
819
+ updatedAt: number;
820
+ }
821
+ /**
822
+ * Sample-body recording (v0.6 follow-up to #100). When a workflow's
823
+ * HTTP trigger declares `recordSample: true`, the trigger captures
824
+ * the request body of the FIRST successful run and persists it
825
+ * here. Studio's `examples.body` resolution prefers a recorded
826
+ * sample over static inference but always defers to an author-
827
+ * declared `trigger.http.examples.body`.
828
+ *
829
+ * One row per workflow — re-recording is intentionally skipped
830
+ * after the first capture so storage stays bounded + the
831
+ * operator-visible example stays stable. Authors can re-trigger a
832
+ * recording by deleting the row (no Studio surface for this yet;
833
+ * call `tracker.deleteWorkflowSample(name)` from a script).
834
+ */
835
+ export interface WorkflowSample {
836
+ workflowName: string;
837
+ body: unknown;
838
+ /** Run id whose request body was captured. */
839
+ sourceRunId: string;
840
+ /** Wall-clock when the sample was recorded. */
841
+ recordedAt: number;
842
+ }
843
+ /**
844
+ * F2 (v0.5) — operators accepted on metadata filters. `eq` is the
845
+ * default (and the only one available before v0.5). Each operator
846
+ * coerces values the way an operator typically would: numerical
847
+ * comparisons cast to number, `like` is SQL `LIKE` (with `%` /
848
+ * `_` wildcards), `in`/`nin` accept array values.
849
+ */
850
+ export type MetadataOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "like" | "in" | "nin";
851
+ /**
852
+ * F2 (v0.5) — operator-aware metadata filter. Combines with other
853
+ * filters in the same `RunQuery.metadata` array via AND semantics.
854
+ *
855
+ * `value` is `string` for scalar operators (`eq` / `ne` / `gt` / `lt` /
856
+ * `gte` / `lte` / `like`), `string[]` for set operators (`in` / `nin`).
857
+ * Numeric comparisons treat the stored metadata value as a number when
858
+ * possible (the underlying JSON store preserves the native type).
859
+ */
860
+ export interface MetadataFilter {
861
+ key: string;
862
+ op: MetadataOp;
863
+ value: string | string[];
864
+ }
548
865
  export interface RunQuery {
549
866
  workflow?: string;
550
867
  status?: WorkflowRunStatus;
551
868
  tags?: string[];
552
869
  /**
553
- * Tier 2 quick-wins — filter by metadata key=value pairs. Multiple
554
- * pairs combine with AND semantics (a run matches only when all
555
- * declared keys match the requested values, compared via stringified
556
- * equality). Backed by `json_extract` on SQLite + `Object` lookup
557
- * on InMemory. Sequential scan (no index); acceptable given the
558
- * `evictOldRuns` size cap on the runs table.
870
+ * Filter by metadata key/value pairs. Multiple entries combine with
871
+ * AND semantics (a run matches only when every declared filter is
872
+ * satisfied).
873
+ *
874
+ * **Two accepted shapes** `Record<string, string>` is back-compat
875
+ * sugar for an array of `{key, op: "eq", value}` filters. Pass an
876
+ * array to use operators beyond `=` (F2: `ne` / `gt` / `gte` / `lt`
877
+ * / `lte` / `like` / `in` / `nin`). Both shapes route through the
878
+ * same evaluator after normalisation.
879
+ *
880
+ * **Performance** — sequential JSON-extract scan by default;
881
+ * declare hot keys via `BLOK_INDEXED_METADATA_KEYS=tier,region` to
882
+ * get an indexed column + index on the SqliteRunStore side (F1).
559
883
  */
560
- metadata?: Record<string, string>;
884
+ metadata?: Record<string, string> | MetadataFilter[];
561
885
  limit?: number;
562
886
  offset?: number;
563
887
  sort?: "asc" | "desc";
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Operator-controlled allowlist for `ctx.env` exposed to user nodes.
3
+ *
4
+ * By default `ctx.env` mirrors `process.env` — every node sees every
5
+ * env var. Operators harden against accidental secret leakage by
6
+ * setting one or both of:
7
+ *
8
+ * - `BLOK_NODE_ENV_ALLOW` — comma-separated exact names. Example:
9
+ * `BLOK_NODE_ENV_ALLOW=DATABASE_URL,STRIPE_KEY`
10
+ * - `BLOK_NODE_ENV_ALLOW_PREFIX` — comma-separated prefixes. Example:
11
+ * `BLOK_NODE_ENV_ALLOW_PREFIX=PUBLIC_,SAFE_`
12
+ *
13
+ * When EITHER is set, `ctx.env` becomes a Proxy that returns
14
+ * `undefined` for keys not in the allowlist and excludes them from
15
+ * `Object.keys`/`for...in`/`hasOwnProperty`. When NEITHER is set,
16
+ * `ctx.env` is `process.env` directly (preserves v0.4.x semantics —
17
+ * no breaking change).
18
+ *
19
+ * Set `BLOK_SUPPRESS_ENV_ALLOW_WARNING=1` to silence the boot warning
20
+ * that fires when `BLOK_ENV=production` and no allowlist is configured.
21
+ *
22
+ * The allowlist is parsed on every `getEnvForCtx()` call (creating a
23
+ * Context). Cost is sub-microsecond — strings are small and the
24
+ * Proxy is allocated once per request. Tests can mutate
25
+ * `process.env.BLOK_NODE_ENV_ALLOW` between calls and see the change
26
+ * immediately (no module-load caching).
27
+ */
28
+ /**
29
+ * Returns the object to assign to `ctx.env`. When no allowlist is
30
+ * configured, returns `process.env` (default-allow). When configured,
31
+ * returns a filtering Proxy. Called by `TriggerBase.createContext`.
32
+ */
33
+ export declare function getEnvForCtx(): NodeJS.ProcessEnv;
34
+ /** Test-only — clears the once-per-process production warning flag. */
35
+ export declare function _resetEnvAllowlistForTests(): void;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Operator-controlled allowlist for `ctx.env` exposed to user nodes.
3
+ *
4
+ * By default `ctx.env` mirrors `process.env` — every node sees every
5
+ * env var. Operators harden against accidental secret leakage by
6
+ * setting one or both of:
7
+ *
8
+ * - `BLOK_NODE_ENV_ALLOW` — comma-separated exact names. Example:
9
+ * `BLOK_NODE_ENV_ALLOW=DATABASE_URL,STRIPE_KEY`
10
+ * - `BLOK_NODE_ENV_ALLOW_PREFIX` — comma-separated prefixes. Example:
11
+ * `BLOK_NODE_ENV_ALLOW_PREFIX=PUBLIC_,SAFE_`
12
+ *
13
+ * When EITHER is set, `ctx.env` becomes a Proxy that returns
14
+ * `undefined` for keys not in the allowlist and excludes them from
15
+ * `Object.keys`/`for...in`/`hasOwnProperty`. When NEITHER is set,
16
+ * `ctx.env` is `process.env` directly (preserves v0.4.x semantics —
17
+ * no breaking change).
18
+ *
19
+ * Set `BLOK_SUPPRESS_ENV_ALLOW_WARNING=1` to silence the boot warning
20
+ * that fires when `BLOK_ENV=production` and no allowlist is configured.
21
+ *
22
+ * The allowlist is parsed on every `getEnvForCtx()` call (creating a
23
+ * Context). Cost is sub-microsecond — strings are small and the
24
+ * Proxy is allocated once per request. Tests can mutate
25
+ * `process.env.BLOK_NODE_ENV_ALLOW` between calls and see the change
26
+ * immediately (no module-load caching).
27
+ */
28
+ function parseList(raw) {
29
+ if (!raw)
30
+ return [];
31
+ return raw
32
+ .split(",")
33
+ .map((s) => s.trim())
34
+ .filter((s) => s.length > 0);
35
+ }
36
+ function loadConfig() {
37
+ const allow = parseList(process.env.BLOK_NODE_ENV_ALLOW);
38
+ const prefixes = parseList(process.env.BLOK_NODE_ENV_ALLOW_PREFIX);
39
+ if (allow.length === 0 && prefixes.length === 0)
40
+ return null;
41
+ return { allow, prefixes };
42
+ }
43
+ function isAllowed(key, config) {
44
+ if (config.allow.includes(key))
45
+ return true;
46
+ for (const p of config.prefixes) {
47
+ if (key.startsWith(p))
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+ /**
53
+ * Build a Proxy around `process.env` that hides keys not in the
54
+ * allowlist. Reads of denied keys return `undefined`; iteration via
55
+ * `Object.keys` / `for...in` / `Object.entries` excludes them; `key
56
+ * in env` returns `false`. Writes pass through (operators don't lose
57
+ * the ability for nodes to mutate process.env if they want to — the
58
+ * filter is a read gate, not a sandbox).
59
+ */
60
+ function buildProxy(config) {
61
+ return new Proxy(process.env, {
62
+ get(target, key) {
63
+ if (typeof key !== "string")
64
+ return undefined;
65
+ return isAllowed(key, config) ? target[key] : undefined;
66
+ },
67
+ has(target, key) {
68
+ if (typeof key !== "string")
69
+ return false;
70
+ return isAllowed(key, config) && key in target;
71
+ },
72
+ ownKeys(target) {
73
+ return Object.keys(target).filter((k) => isAllowed(k, config));
74
+ },
75
+ getOwnPropertyDescriptor(target, key) {
76
+ if (typeof key !== "string" || !isAllowed(key, config))
77
+ return undefined;
78
+ return Object.getOwnPropertyDescriptor(target, key);
79
+ },
80
+ });
81
+ }
82
+ let productionWarningEmitted = false;
83
+ function emitProductionWarning() {
84
+ if (productionWarningEmitted)
85
+ return;
86
+ productionWarningEmitted = true;
87
+ if (process.env.BLOK_SUPPRESS_ENV_ALLOW_WARNING === "1")
88
+ return;
89
+ if (process.env.BLOK_ENV !== "production")
90
+ return;
91
+ console.warn("[blok] BLOK_ENV=production but neither BLOK_NODE_ENV_ALLOW nor BLOK_NODE_ENV_ALLOW_PREFIX is set. " +
92
+ "Every loaded node sees every env var, including secrets. " +
93
+ "Configure an allowlist to harden the surface, or set " +
94
+ "BLOK_SUPPRESS_ENV_ALLOW_WARNING=1 to silence.");
95
+ }
96
+ /**
97
+ * Returns the object to assign to `ctx.env`. When no allowlist is
98
+ * configured, returns `process.env` (default-allow). When configured,
99
+ * returns a filtering Proxy. Called by `TriggerBase.createContext`.
100
+ */
101
+ export function getEnvForCtx() {
102
+ const config = loadConfig();
103
+ if (!config) {
104
+ emitProductionWarning();
105
+ return process.env;
106
+ }
107
+ return buildProxy(config);
108
+ }
109
+ /** Test-only — clears the once-per-process production warning flag. */
110
+ export function _resetEnvAllowlistForTests() {
111
+ productionWarningEmitted = false;
112
+ }
113
+ //# sourceMappingURL=envAllowlist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envAllowlist.js","sourceRoot":"","sources":["../../src/utils/envAllowlist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * RuntimeVersionValidator — Validates node and workflow runtime version requirements.
3
+ *
4
+ * Used by the runner to check that nodes' `runtimeRequirements` are satisfied
5
+ * by the currently running runtime versions before executing workflows.
6
+ */
7
+ export interface VersionValidationResult {
8
+ valid: boolean;
9
+ node: string;
10
+ runtime: string;
11
+ required: string;
12
+ actual: string | undefined;
13
+ message: string;
14
+ }
15
+ export declare class RuntimeVersionValidator {
16
+ private runtimeVersions;
17
+ constructor(runtimeVersions?: Record<string, string>);
18
+ setRuntimeVersion(kind: string, version: string): void;
19
+ getRuntimeVersion(kind: string): string | undefined;
20
+ /**
21
+ * Validate a single node's runtime requirements against known runtime versions.
22
+ */
23
+ validateNode(node: {
24
+ name: string;
25
+ runtimeRequirements?: Partial<Record<string, string>>;
26
+ }): VersionValidationResult[];
27
+ /**
28
+ * Validate all nodes in a workflow against known runtime versions.
29
+ */
30
+ validateWorkflow(nodes: Array<{
31
+ name: string;
32
+ runtimeRequirements?: Partial<Record<string, string>>;
33
+ }>): VersionValidationResult[];
34
+ /**
35
+ * Format validation errors into a user-friendly multi-line string.
36
+ */
37
+ static formatErrors(results: VersionValidationResult[]): string;
38
+ }