@glubean/sdk 0.5.1 → 0.8.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 (151) hide show
  1. package/dist/contract-artifacts.d.ts +39 -21
  2. package/dist/contract-artifacts.d.ts.map +1 -1
  3. package/dist/contract-artifacts.js +80 -24
  4. package/dist/contract-artifacts.js.map +1 -1
  5. package/dist/contract-core.d.ts +10 -21
  6. package/dist/contract-core.d.ts.map +1 -1
  7. package/dist/contract-core.js +26 -812
  8. package/dist/contract-core.js.map +1 -1
  9. package/dist/contract-http/adapter.d.ts +13 -0
  10. package/dist/contract-http/adapter.d.ts.map +1 -1
  11. package/dist/contract-http/adapter.js +191 -23
  12. package/dist/contract-http/adapter.js.map +1 -1
  13. package/dist/contract-http/factory.d.ts +2 -2
  14. package/dist/contract-http/factory.d.ts.map +1 -1
  15. package/dist/contract-http/factory.js +5 -0
  16. package/dist/contract-http/factory.js.map +1 -1
  17. package/dist/contract-http/inbound-match.d.ts +47 -0
  18. package/dist/contract-http/inbound-match.d.ts.map +1 -0
  19. package/dist/contract-http/inbound-match.js +136 -0
  20. package/dist/contract-http/inbound-match.js.map +1 -0
  21. package/dist/contract-http/inbound-verify.d.ts +46 -0
  22. package/dist/contract-http/inbound-verify.d.ts.map +1 -0
  23. package/dist/contract-http/inbound-verify.js +101 -0
  24. package/dist/contract-http/inbound-verify.js.map +1 -0
  25. package/dist/contract-http/inbound.d.ts +45 -0
  26. package/dist/contract-http/inbound.d.ts.map +1 -0
  27. package/dist/contract-http/inbound.js +89 -0
  28. package/dist/contract-http/inbound.js.map +1 -0
  29. package/dist/contract-http/index.d.ts +5 -0
  30. package/dist/contract-http/index.d.ts.map +1 -1
  31. package/dist/contract-http/index.js +3 -0
  32. package/dist/contract-http/index.js.map +1 -1
  33. package/dist/contract-http/openapi.d.ts +0 -7
  34. package/dist/contract-http/openapi.d.ts.map +1 -1
  35. package/dist/contract-http/openapi.js +20 -14
  36. package/dist/contract-http/openapi.js.map +1 -1
  37. package/dist/contract-http/types.d.ts +106 -2
  38. package/dist/contract-http/types.d.ts.map +1 -1
  39. package/dist/contract-http/types.js +33 -0
  40. package/dist/contract-http/types.js.map +1 -1
  41. package/dist/contract-types.d.ts +148 -446
  42. package/dist/contract-types.d.ts.map +1 -1
  43. package/dist/data-path.d.ts.map +1 -1
  44. package/dist/data-path.js +10 -2
  45. package/dist/data-path.js.map +1 -1
  46. package/dist/index.d.ts +10 -6
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +10 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/internal.d.ts +7 -0
  51. package/dist/internal.d.ts.map +1 -1
  52. package/dist/internal.js +14 -0
  53. package/dist/internal.js.map +1 -1
  54. package/dist/load/artifact.d.ts +376 -0
  55. package/dist/load/artifact.d.ts.map +1 -0
  56. package/dist/load/artifact.js +14 -0
  57. package/dist/load/artifact.js.map +1 -0
  58. package/dist/load/builder.d.ts +80 -0
  59. package/dist/load/builder.d.ts.map +1 -0
  60. package/dist/load/builder.js +262 -0
  61. package/dist/load/builder.js.map +1 -0
  62. package/dist/load/context.d.ts +81 -0
  63. package/dist/load/context.d.ts.map +1 -0
  64. package/dist/load/context.js +2 -0
  65. package/dist/load/context.js.map +1 -0
  66. package/dist/load/duration.d.ts +9 -0
  67. package/dist/load/duration.d.ts.map +1 -0
  68. package/dist/load/duration.js +22 -0
  69. package/dist/load/duration.js.map +1 -0
  70. package/dist/load/events.d.ts +132 -0
  71. package/dist/load/events.d.ts.map +1 -0
  72. package/dist/load/events.js +2 -0
  73. package/dist/load/events.js.map +1 -0
  74. package/dist/load/feeder.d.ts +118 -0
  75. package/dist/load/feeder.d.ts.map +1 -0
  76. package/dist/load/feeder.js +170 -0
  77. package/dist/load/feeder.js.map +1 -0
  78. package/dist/load/index.d.ts +32 -0
  79. package/dist/load/index.d.ts.map +1 -0
  80. package/dist/load/index.js +7 -0
  81. package/dist/load/index.js.map +1 -0
  82. package/dist/load/progress.d.ts +56 -0
  83. package/dist/load/progress.d.ts.map +1 -0
  84. package/dist/load/progress.js +2 -0
  85. package/dist/load/progress.js.map +1 -0
  86. package/dist/load/projection.d.ts +36 -0
  87. package/dist/load/projection.d.ts.map +1 -0
  88. package/dist/load/projection.js +47 -0
  89. package/dist/load/projection.js.map +1 -0
  90. package/dist/load/runner.d.ts +201 -0
  91. package/dist/load/runner.d.ts.map +1 -0
  92. package/dist/load/runner.js +78 -0
  93. package/dist/load/runner.js.map +1 -0
  94. package/dist/load/scenario.d.ts +130 -0
  95. package/dist/load/scenario.d.ts.map +1 -0
  96. package/dist/load/scenario.js +9 -0
  97. package/dist/load/scenario.js.map +1 -0
  98. package/dist/load/step.d.ts +42 -0
  99. package/dist/load/step.d.ts.map +1 -0
  100. package/dist/load/step.js +2 -0
  101. package/dist/load/step.js.map +1 -0
  102. package/dist/{contract-flow-poll.d.ts → poll-primitives.d.ts} +8 -81
  103. package/dist/poll-primitives.d.ts.map +1 -0
  104. package/dist/{contract-flow-poll.js → poll-primitives.js} +10 -64
  105. package/dist/poll-primitives.js.map +1 -0
  106. package/dist/{contract-flow-condition.d.ts → predicates.d.ts} +34 -71
  107. package/dist/predicates.d.ts.map +1 -0
  108. package/dist/{contract-flow-condition.js → predicates.js} +86 -80
  109. package/dist/predicates.js.map +1 -0
  110. package/dist/test/builder.js +2 -2
  111. package/dist/test/builder.js.map +1 -1
  112. package/dist/test/utils.d.ts +7 -0
  113. package/dist/test/utils.d.ts.map +1 -1
  114. package/dist/test/utils.js +22 -17
  115. package/dist/test/utils.js.map +1 -1
  116. package/dist/types.d.ts +42 -14
  117. package/dist/types.d.ts.map +1 -1
  118. package/dist/types.js.map +1 -1
  119. package/dist/workflow/builder.d.ts +386 -0
  120. package/dist/workflow/builder.d.ts.map +1 -0
  121. package/dist/workflow/builder.js +1150 -0
  122. package/dist/workflow/builder.js.map +1 -0
  123. package/dist/workflow/execute.d.ts +277 -0
  124. package/dist/workflow/execute.d.ts.map +1 -0
  125. package/dist/workflow/execute.js +1489 -0
  126. package/dist/workflow/execute.js.map +1 -0
  127. package/dist/workflow/index.d.ts +11 -0
  128. package/dist/workflow/index.d.ts.map +1 -0
  129. package/dist/workflow/index.js +15 -0
  130. package/dist/workflow/index.js.map +1 -0
  131. package/dist/workflow/project.d.ts +18 -0
  132. package/dist/workflow/project.d.ts.map +1 -0
  133. package/dist/workflow/project.js +321 -0
  134. package/dist/workflow/project.js.map +1 -0
  135. package/dist/workflow/retry.d.ts +13 -0
  136. package/dist/workflow/retry.d.ts.map +1 -0
  137. package/dist/workflow/retry.js +15 -0
  138. package/dist/workflow/retry.js.map +1 -0
  139. package/dist/workflow/types.d.ts +512 -0
  140. package/dist/workflow/types.d.ts.map +1 -0
  141. package/dist/workflow/types.js +2 -0
  142. package/dist/workflow/types.js.map +1 -0
  143. package/package.json +9 -2
  144. package/dist/contract-flow-condition.d.ts.map +0 -1
  145. package/dist/contract-flow-condition.js.map +0 -1
  146. package/dist/contract-flow-poll.d.ts.map +0 -1
  147. package/dist/contract-flow-poll.js.map +0 -1
  148. package/dist/contract-http/flow-helpers.d.ts +0 -12
  149. package/dist/contract-http/flow-helpers.d.ts.map +0 -1
  150. package/dist/contract-http/flow-helpers.js +0 -34
  151. package/dist/contract-http/flow-helpers.js.map +0 -1
@@ -17,10 +17,8 @@
17
17
  * See `internal/40-discovery/proposals/contract-generics-complete.md` v5
18
18
  * and `internal/40-discovery/proposals/contract-flow.md` v9 for design.
19
19
  */
20
- import type { SchemaLike, Test, TestContext } from "./types.js";
20
+ import type { SchemaLike, SecretsAccessor, Test, TestContext } from "./types.js";
21
21
  import type { KnownArtifacts, KnownArtifactParts, KnownArtifactOptions } from "./index.js";
22
- import type { RuntimeBranchStep, ExtractedBranchStep, ExtractedPredicate, JsonScalar, BranchPredicate, PredicateScope } from "./contract-flow-condition.js";
23
- import type { RuntimePollStep, ExtractedPollStep } from "./contract-flow-poll.js";
24
22
  /**
25
23
  * Case lifecycle.
26
24
  *
@@ -261,6 +259,20 @@ export interface CaseMeta<PayloadSchemas = unknown, Meta = unknown> {
261
259
  * the `rawBypass` execution path should appear on an overlay.
262
260
  */
263
261
  hasNeeds?: boolean;
262
+ /**
263
+ * Direction of the case relative to the system under test. Absent =
264
+ * outbound (we call them — the default since contracts began). "inbound"
265
+ * = the counterparty calls us (inbound-contract-design §9.2); such cases
266
+ * are awaited via a workflow inbound poll, never executed.
267
+ */
268
+ direction?: "inbound";
269
+ /**
270
+ * Set to `false` by adapters whose case cannot be executed as a Test
271
+ * (inbound-contract-design §9.5). The dispatcher then registers NO Test
272
+ * and NO runnable-inventory entry for it — the case exists only in
273
+ * projection/metadata. Absent = runnable (every pre-existing case).
274
+ */
275
+ runnable?: boolean;
264
276
  }
265
277
  /**
266
278
  * Runtime contract projection — adapter.project() output.
@@ -305,6 +317,20 @@ export interface ExtractedContractProjection<SafeSchemas = unknown, SafeMeta = u
305
317
  cases: Array<ExtractedCaseMeta<SafeSchemas, SafeMeta>>;
306
318
  schemas?: SafeSchemas;
307
319
  meta?: SafeMeta;
320
+ /**
321
+ * Fully-qualified logical paths of schemas that were DECLARED but could not
322
+ * be projected to JSON Schema (the live `toJSONSchema()` threw, or the value
323
+ * was an unrecognized non-plain object). Distinct from "no schema declared":
324
+ * an absent schema does not appear here. Paths are dotted, e.g.
325
+ * `"request.body"`, `"cases.login.response.body"`, `"cases.login.needsSchema"`.
326
+ *
327
+ * The schema VALUE stays `undefined` in the projection (so OpenAPI / MCP /
328
+ * descriptors degrade exactly as before); this list is the explicit record
329
+ * a snapshot consumer uses to compute `projectionComplete` (false when
330
+ * non-empty) and warn about the specific holes. Undefined/omitted when every
331
+ * declared schema projected cleanly.
332
+ */
333
+ unprojectableSchemas?: string[];
308
334
  }
309
335
  /**
310
336
  * Protocol adapter interface. Implement to add support for a new protocol
@@ -423,7 +449,7 @@ export interface ContractProtocolAdapter<Spec = unknown, RuntimeSchemas = unknow
423
449
  * protocol-specific artifacts (OpenAPI is HTTP-only).
424
450
  */
425
451
  artifacts?: {
426
- [K in keyof KnownArtifacts]?: (projection: ExtractedContractProjection<SafeSchemas, SafeMeta>, options?: KnownArtifactOptions[K]) => KnownArtifactParts[K];
452
+ [K in keyof KnownArtifacts]?: (projection: ExtractedContractProjection<SafeSchemas, SafeMeta>, options?: KnownArtifactOptions[K]) => KnownArtifactParts[K] | null;
427
453
  };
428
454
  /**
429
455
  * Optional: render the `target` string for display. HTTP: "POST /users"
@@ -443,7 +469,7 @@ export interface ContractProtocolAdapter<Spec = unknown, RuntimeSchemas = unknow
443
469
  * 1. Computed `resolvedInputs` via `step.bindings.in(state)`
444
470
  * 2. Prepared current flow state
445
471
  * 3. **v10 (for migrated protocols)**: validated `resolvedInputs` against
446
- * the case's `needs` schema via `runFlow` → `validateNeedsOutput`, so
472
+ * the case's `needs` schema via `validateNeedsOutput`, so
447
473
  * the adapter receives already-validated logical input.
448
474
  * 4. Passed the live contract instance (access merged scoped-factory state
449
475
  * via `contract._spec`)
@@ -505,6 +531,51 @@ export interface ContractProtocolAdapter<Spec = unknown, RuntimeSchemas = unknow
505
531
  * Throws on invalid case; returns undefined on success.
506
532
  */
507
533
  validateCaseForFlow?: (spec: Spec, caseKey: string, contractId: string) => void;
534
+ /**
535
+ * Optional: classify ONE delivery against an inbound case
536
+ * (inbound-contract-design §9.4/§9.4a). Called by the workflow inbound
537
+ * poll once per unclaimed delivery, in receiver order; the FIRST terminal
538
+ * classification wins (consult #4). Authentication decides failure;
539
+ * content decides attribution: fail-class results come only from the
540
+ * channel (signature / staleness / non-JSON), mismatches are probes.
541
+ *
542
+ * Preflight errors (unknown verifier scheme, missing secret) MUST throw —
543
+ * bad config must never hide as a probe (§9.4 preflight row).
544
+ *
545
+ * The poll resolves the state side itself (it owns the state) and hands
546
+ * the matcher the concrete value + the event's EXTRACTED selector path —
547
+ * a path, not the lens, because the matcher must walk unrelated webhook
548
+ * shapes safely: `e => e.data.id` THROWS on a body without `data`, while
549
+ * a safe path walk yields `undefined` → `type-mismatch` probe (codex I3
550
+ * R1 P2).
551
+ *
552
+ * The staleness clock for timestamped signature schemes is the delivery's
553
+ * own `receivedAt` — verification must reflect RECEIPT time, not whenever
554
+ * the poll happened to scan the inbox, or a pre-existing valid delivery
555
+ * (allowed evidence, §9.4a #3) would misclassify as stale after a slow
556
+ * setup (codex I3 R2 P2).
557
+ */
558
+ matchInboundCase?: (input: {
559
+ caseSpec: unknown;
560
+ delivery: InboundDelivery;
561
+ secrets: SecretsAccessor;
562
+ correlate?: {
563
+ eventPath: readonly string[];
564
+ stateValue: unknown;
565
+ };
566
+ }) => InboundMatchResult;
567
+ /**
568
+ * Optional: validate inbound-case CONFIG without a delivery — throw on an
569
+ * unknown verifier scheme / missing secret (§9.4 preflight row). The poll
570
+ * calls this every attempt BEFORE reading the receiver: an empty inbox
571
+ * must not let bad config exhaust as a timeout (codex I3 R3 P2 — the
572
+ * matcher's own preflight only runs when a delivery exists). Adapters
573
+ * implementing matchInboundCase should implement this too.
574
+ */
575
+ preflightInboundCase?: (input: {
576
+ caseSpec: unknown;
577
+ secrets: SecretsAccessor;
578
+ }) => void;
508
579
  }
509
580
  /**
510
581
  * Protocol-agnostic payload summary. Adapter-provided via describePayload.
@@ -554,14 +625,20 @@ export interface ProtocolContract<Spec = unknown, PayloadSchemas = unknown, Meta
554
625
  */
555
626
  readonly _spec: Spec;
556
627
  /**
557
- * Return a ContractCaseRef for use in `contract.flow(...).step(...)`.
628
+ * Return a ContractCaseRef for use in `workflow().call/.poll(...)`.
558
629
  *
559
630
  * Runtime validation: adapter's `.case(key)` implementation MUST fail-fast
560
631
  * if the case contains function-valued input fields (body/params/query/
561
632
  * headers as functions). Function fields reference case-local setup state
562
633
  * which is not available in flow mode. See contract-flow §5.1.1.
563
634
  */
564
- case<K extends keyof Cases & string>(key: K): ContractCaseRef<InferCaseInput<Cases[K]>, ApplyCaseOutput<PayloadSchemas, Cases[K]>, InferAcceptKey<PayloadSchemas>, InferRawOutcome<PayloadSchemas, InferOutput<PayloadSchemas>>>;
635
+ case<K extends keyof Cases & string>(key: K): ContractCaseRef<InferCaseInput<Cases[K]>, ApplyCaseOutput<PayloadSchemas, Cases[K]>, InferAcceptKey<PayloadSchemas>, InferRawOutcome<PayloadSchemas, InferOutput<PayloadSchemas>>> & (Cases[K] extends {
636
+ direction: "inbound";
637
+ } ? {
638
+ readonly direction: "inbound";
639
+ } : {
640
+ readonly direction?: undefined;
641
+ });
565
642
  }
566
643
  /**
567
644
  * Adapter-defined helper: extract the "case inputs" shape from PayloadSchemas.
@@ -642,6 +719,66 @@ export type InferAcceptKey<P> = P extends {
642
719
  export type InferRawOutcome<P, Fallback = unknown> = P extends {
643
720
  readonly __rawOutcome?: infer R;
644
721
  } ? [unknown] extends [R] ? Fallback : NonNullable<R> : Fallback;
722
+ /**
723
+ * One delivery as received — RAW. `bodyBytes` is the exact byte sequence the
724
+ * counterparty sent: HMAC-style signatures are computed over raw bytes, so
725
+ * any parsing or decoding before verification would destroy the evidence
726
+ * (design §9.1, blind spot 3). Protocol-agnostic: core's inbound-poll
727
+ * machinery and adapter `matchInboundCase` hooks both consume it (the
728
+ * zero-dependency local implementation lives in `contract-http/inbound.ts`).
729
+ */
730
+ export interface InboundDelivery {
731
+ /** Receiver-assigned, unique per delivery (claim key). */
732
+ id: string;
733
+ /** EXACT body bytes as received — THE signature input. */
734
+ bodyBytes: Uint8Array;
735
+ /**
736
+ * UTF-8 decoded view of `bodyBytes`, for JSON parsing and display. LOSSY
737
+ * for non-UTF-8 payloads — never feed this to a signature verifier.
738
+ */
739
+ rawBody: string;
740
+ /** Header names lowercased. */
741
+ headers: Record<string, string>;
742
+ method: string;
743
+ path: string;
744
+ /** Epoch ms at receipt — the measured side of `within` evidence. */
745
+ receivedAt: number;
746
+ }
747
+ /**
748
+ * The receiver protocol an inbound poll (`.poll(ref, { via })`) consumes
749
+ * (design §9.1). NON-DESTRUCTIVE by construction: matching inspects
750
+ * `deliveries()` snapshots and `claim()`s only the matched delivery — other
751
+ * consumers' events are never swallowed by a failed match.
752
+ *
753
+ * ONE handle corresponds to ONE endpoint/secret domain (design §9.4):
754
+ * the matcher never checks method/path — full grade means "received through
755
+ * THIS handle" (consult #7), so multi-source channels must be split into
756
+ * per-domain handles at the receiver layer.
757
+ */
758
+ export interface ReceiverHandle {
759
+ /** Snapshot of UNCLAIMED deliveries, oldest first. */
760
+ deliveries(): readonly InboundDelivery[];
761
+ /** Mark one delivery consumed (idempotent; unknown ids are a no-op). */
762
+ claim(id: string): void;
763
+ /** The URL the counterparty should be pointed at. */
764
+ url: string;
765
+ close(): Promise<void>;
766
+ }
767
+ /**
768
+ * Result of an adapter's `matchInboundCase` over ONE delivery (§9.4/§9.4a).
769
+ * Fail-class results (signature-invalid / stale / unparseable) FAIL the poll
770
+ * node; mismatch results are probes whose labels exist for diagnosis;
771
+ * `matched` claims the delivery and satisfies the poll.
772
+ */
773
+ export type InboundMatchResult = {
774
+ kind: "matched";
775
+ parsed: unknown;
776
+ } | {
777
+ kind: "type-mismatch" | "correlation-mismatch" | "schema-mismatch";
778
+ } | {
779
+ kind: "signature-invalid" | "stale" | "unparseable";
780
+ detail?: string;
781
+ };
645
782
  /**
646
783
  * Opaque reference to a single case of a contract. Produced by
647
784
  * `ProtocolContract.case(key)`. Used as input to `FlowBuilder.step(...)`.
@@ -665,28 +802,6 @@ export interface ContractCaseRef<CaseInputs = unknown, CaseOutput = unknown, Acc
665
802
  /** Phantom — `out`'s `res` type when `accept` is present (adapter raw outcome). */
666
803
  readonly __phantom_raw_outcome?: RawOutcome;
667
804
  }
668
- /** Contract-level metadata for a flow (set via `.meta()`). */
669
- export interface FlowMeta {
670
- id: string;
671
- name?: string;
672
- description?: string;
673
- tags?: string[];
674
- extensions?: Extensions;
675
- /**
676
- * Mark this flow as skipped at run time. Value is the skip reason
677
- * displayed in reports. Useful for illustrative examples that should
678
- * be discoverable (for scanner extraction / docs rendering) but must
679
- * not attempt live HTTP calls.
680
- *
681
- * Mirrors `TestMeta.skip`.
682
- */
683
- skip?: string;
684
- /**
685
- * Mark this flow as focused. When any flows/tests in a run are `only`,
686
- * non-focused ones may be excluded. Mirrors `TestMeta.only`.
687
- */
688
- only?: boolean;
689
- }
690
805
  /**
691
806
  * Field dependency / mapping produced by Proxy dry-run of a lens function.
692
807
  * Consumed by downstream (MCP/CLI/Cloud) to render flow data-flow diagrams.
@@ -706,371 +821,14 @@ export interface FieldMapping {
706
821
  };
707
822
  }
708
823
  /**
709
- * Runtime flow stepdiscriminated union.
710
- * kind "contract-call" = a ContractCaseRef with bindings (Rule 1 teardown applies).
711
- * kind "compute" = a pure sync data-transform function (no adapter, no teardown).
712
- */
713
- export type RuntimeFlowStep = RuntimeContractCallStep | RuntimeComputeStep | RuntimeBranchStep | RuntimePollStep;
714
- export interface RuntimeContractCallStep {
715
- kind: "contract-call";
716
- name?: string;
717
- ref: ContractCaseRef<any, any, any, any>;
718
- caseKey: string;
719
- /** Live contract instance (mirrors ref.contract, kept for direct access). */
720
- contract: ProtocolContract<any, any, any>;
721
- bindings?: {
722
- in?: (state: any) => any;
723
- out?: (state: any, response: any) => any;
724
- /**
725
- * Accepted alternate outcome keys (HTTP: status numbers). When the actual
726
- * outcome key is in this list AND differs from the case's primary expected
727
- * outcome, the adapter treats it as a legal outcome: it skips the case's
728
- * primary result validation (schema / headers / verify) and hands the RAW
729
- * outcome to `out`, so a condition can branch on it. Protocol-agnostic in
730
- * core; the adapter interprets the element type.
731
- */
732
- accept?: readonly unknown[];
733
- };
734
- }
735
- export interface RuntimeComputeStep {
736
- kind: "compute";
737
- name?: string;
738
- /**
739
- * Synchronous pure function. NOT subject to lens Proxy purity — may use
740
- * template literals / method calls / .map(). MUST be synchronous and
741
- * MUST NOT return a thenable (enforced at runtime).
742
- */
743
- fn: (state: any) => any;
744
- }
745
- /**
746
- * Runtime flow projection. Carries live callbacks + live contract refs.
747
- * Never crosses serialization boundaries. `normalizeFlow()` converts to
748
- * ExtractedFlowProjection for downstream consumers.
749
- */
750
- export interface RuntimeFlowProjection<State = unknown> {
751
- protocol: "flow";
752
- description?: string;
753
- tags?: string[];
754
- extensions?: Extensions;
755
- /** Mirrors `FlowMeta.skip` — skip reason for discoverable-but-not-runnable flows. */
756
- skip?: string;
757
- /** Mirrors `FlowMeta.only` — focus filter. */
758
- only?: boolean;
759
- /** Live flow-level setup callback (only I/O-capable callback in flow). */
760
- setup?: (ctx: TestContext) => Promise<State>;
761
- /** Live flow-level teardown callback. Rule 2: outer finally. */
762
- teardown?: (ctx: TestContext, state: State) => Promise<void>;
763
- steps: RuntimeFlowStep[];
764
- }
765
- /**
766
- * Extracted flow step — discriminated union, JSON-safe.
767
- */
768
- export type ExtractedFlowStep = ExtractedContractCallStep | ExtractedComputeStep | ExtractedBranchStep | ExtractedPollStep;
769
- export interface ExtractedContractCallStep {
770
- kind: "contract-call";
771
- name?: string;
772
- contractId: string;
773
- caseKey: string;
774
- protocol: string;
775
- target: string;
776
- /** Proxy dry-run output — input mappings. */
777
- inputs?: FieldMapping[];
778
- /** Proxy dry-run output — state update mappings. */
779
- outputs?: FieldMapping[];
780
- /**
781
- * Accepted alternate outcome keys (HTTP: status numbers) — present when the
782
- * step declared `accept`. Surfaces the multi-status contract to downstream
783
- * consumers (scanner / MCP / CLI / Cloud) so a status-gated step is not
784
- * indistinguishable from a single-primary-status one.
785
- */
786
- accept?: ReadonlyArray<string | number>;
787
- }
788
- export interface ExtractedComputeStep {
789
- kind: "compute";
790
- name?: string;
791
- /** Top-level state paths read (from Proxy dry-run). */
792
- reads: string[];
793
- /** Top-level state keys written (keys of returned object). */
794
- writes: string[];
795
- }
796
- /**
797
- * JSON-safe flow projection. Downstream (scanner / MCP / CLI / Cloud) consume
798
- * this. Produced by `normalizeFlow(runtime)`.
799
- */
800
- export interface ExtractedFlowProjection {
801
- id: string;
802
- protocol: "flow";
803
- description?: string;
804
- tags?: string[];
805
- extensions?: Extensions;
806
- /** Mirrors `FlowMeta.skip`. */
807
- skip?: string;
808
- /** Mirrors `FlowMeta.only`. */
809
- only?: boolean;
810
- /** Present when flow has a setup callback (state source is dynamic). */
811
- setupDynamic?: true;
812
- steps: ExtractedFlowStep[];
813
- }
814
- /**
815
- * Base options shared by `poll` / `pollFn` / `pollAsync`. `Res` switches on
816
- * `accept` exactly like `step` (no accept → CaseOutput; accept → RawOutcome).
817
- * `accept?: Accept` is what drives the `Accept` inference. Bound semantics +
818
- * the build-time rules live in `validatePollBounds`. (Spike P.)
819
- */
820
- export interface PollOptsBase<State, Res, NewState, Accept extends readonly unknown[]> {
821
- /** The exit (satisfying) response written into state. */
822
- out?: (state: State, response: Res) => NewState;
823
- /** Accepted alternate outcome keys (HTTP: status numbers) — drives `Accept` inference. */
824
- accept?: Accept;
825
- name?: string;
826
- /** Total wall-clock bound (ms). */
827
- timeout?: number;
828
- /** Max attempts (incl. the first). */
829
- maxAttempts?: number;
830
- /** Per-attempt budget (ms) — required when `timeout` is absent. */
831
- perAttemptTimeout?: number;
832
- /** Interval between attempts (ms, default 1000). */
833
- every?: number;
834
- /** Backoff multiplier applied to `every` after each retry (default 1, capped). */
835
- backoff?: number;
836
- }
837
- /**
838
- * Poll argument tuple. Mirrors `step`'s conditional tuple: `in` is REQUIRED for
839
- * a needs-input case and FORBIDDEN for a void-input case. `Exit` is each tier's
840
- * `{ until, message }` fragment (L2 declarative vs opaque). (Spike P.)
841
- */
842
- export type PollArgs<State, CaseInputs, Res, NewState, Accept extends readonly unknown[], Exit> = [
843
- CaseInputs
844
- ] extends [void] ? [opts: PollOptsBase<State, Res, NewState, Accept> & Exit] : [opts: PollOptsBase<State, Res, NewState, Accept> & Exit & {
845
- in: (state: State) => CaseInputs;
846
- }];
847
- /**
848
- * Builder for `contract.flow(id)`. State chain threads through `.step()` /
849
- * `.compute()` via TypeScript generics.
850
- */
851
- export interface FlowBuilder<State = unknown> {
852
- readonly __glubean_type: "flow-builder";
853
- meta(m: Omit<FlowMeta, "id">): FlowBuilder<State>;
854
- /**
855
- * Flow-level setup — the ONLY I/O-capable callback in a flow. May be async,
856
- * may read ctx, may call external services. Returns the initial state.
857
- */
858
- setup<NewState>(fn: (ctx: TestContext) => Promise<NewState>): FlowBuilder<NewState>;
859
- /**
860
- * Add a contract-call step. `bindings.in` and `bindings.out` MUST be pure
861
- * lens functions (select / repack only; no I/O, no method calls, no
862
- * branching). Lens purity is enforced at Proxy dry-run time during
863
- * projection extraction.
864
- *
865
- * Single signature with rest-parameter conditional tuple (Spike 0 Finding 3).
866
- * Two-overload form (historically tried) has a subtle hole: overload 1
867
- * (void-input, no `in`) excess-property-fails when `in` is passed → TS
868
- * falls through to overload 2, which accepts because `() => X` is
869
- * bivariant-assignable to `() => void`. Conditional tuple is a single
870
- * signature so TS doesn't get a second chance:
871
- * - void-input case → `bindings` optional; `in` not present in the
872
- * allowed shape
873
- * - typed-input case → `bindings.in` REQUIRED
874
- *
875
- * In v10, `in` returns LOGICAL case input (matches the case's `needs`),
876
- * NOT an adapter patch. HTTP adapter's `executeCaseInFlow` calls
877
- * function-valued body/headers/params/query with this logical input
878
- * (same mechanism as standalone `executeStandaloneCase`).
879
- */
880
- step<CaseInputs, CaseOutput, AcceptKey, RawOutcome, Accept extends readonly AcceptKey[] = never, NewState = State>(ref: ContractCaseRef<CaseInputs, CaseOutput, AcceptKey, RawOutcome>, ...args: [CaseInputs] extends [void] ? [
881
- bindings?: {
882
- accept?: Accept;
883
- out?: (state: State, response: [Accept] extends [never] ? CaseOutput : RawOutcome) => NewState;
884
- name?: string;
885
- }
886
- ] : [
887
- bindings: {
888
- in: (state: State) => CaseInputs;
889
- accept?: Accept;
890
- out?: (state: State, response: [Accept] extends [never] ? CaseOutput : RawOutcome) => NewState;
891
- name?: string;
892
- }
893
- ]): FlowBuilder<NewState>;
894
- /**
895
- * Add a pure synchronous data-transform step. Accepts any synchronous TS
896
- * expression (template literals, method calls, .map()). Projection records
897
- * only read/write dependencies, NOT the formula.
898
- *
899
- * Runtime enforcement: throws if `fn` is async or returns a thenable.
900
- */
901
- compute<NewState>(fn: (state: State) => NewState): FlowBuilder<NewState>;
902
- /**
903
- * Flow-level teardown — runs in Rule 2 outer-finally, receives last-
904
- * committed state. If flow.setup threw, teardown does NOT run.
905
- */
906
- teardown(fn: (ctx: TestContext, state: State) => Promise<void>): FlowBuilder<State>;
907
- /** L2 — declarative predicate (precisely projectable). No else → then keeps State's shape (values may narrow). */
908
- condition<R extends State = State>(spec: {
909
- predicate: (w: PredicateScope<State>) => BranchPredicate<State>;
910
- message?: string;
911
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<R & NoExtraKeys<R, State>>): FlowBuilder<State>;
912
- /** L2 with else — `T` is inferred from `then`; `else` is checked against it (`NoInfer`). */
913
- condition<T>(spec: {
914
- predicate: (w: PredicateScope<State>) => BranchPredicate<State>;
915
- message?: string;
916
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<T>, elseBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<NoInfer<T>>): FlowBuilder<T>;
917
- /** L1 — opaque sync predicate. `message` is required (projection marks ⚠ opaque-gate). */
918
- conditionFn<R extends State = State>(spec: {
919
- predicate: (ctx: TestContext, s: State) => boolean;
920
- message: string;
921
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<R & NoExtraKeys<R, State>>): FlowBuilder<State>;
922
- conditionFn<T>(spec: {
923
- predicate: (ctx: TestContext, s: State) => boolean;
924
- message: string;
925
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<T>, elseBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<NoInfer<T>>): FlowBuilder<T>;
926
- /** L0 — opaque async predicate (may do out-of-contract I/O). `message` required (⚠ dynamic-gate). */
927
- conditionAsync<R extends State = State>(spec: {
928
- predicate: (ctx: TestContext, s: State) => Promise<boolean>;
929
- message: string;
930
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<R & NoExtraKeys<R, State>>): FlowBuilder<State>;
931
- conditionAsync<T>(spec: {
932
- predicate: (ctx: TestContext, s: State) => Promise<boolean>;
933
- message: string;
934
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<T>, elseBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<NoInfer<T>>): FlowBuilder<T>;
935
- /**
936
- * value-switch (single lens × N scalar values). Curried so `V` infers from
937
- * the lens alone (Spike 0): first call fixes `V`, second checks each `value`
938
- * against it. `default` is required (anchors `T`; the no-match path). switch
939
- * is always L2 — a clean decision table.
940
- */
941
- switchOn<V>(lens: (s: State) => V): <T>(cases: ReadonlyArray<{
942
- value: [Exclude<V, undefined>] extends [JsonScalar] ? Exclude<V, undefined> : never;
943
- then: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<NoInfer<T>>;
944
- }>, deflt: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<T>) => FlowBuilder<T>;
945
- /** cond-switch (ordered, possibly-overlapping declarative predicates; first-match). */
946
- switchCond<T>(cases: ReadonlyArray<{
947
- when: (w: PredicateScope<State>) => BranchPredicate<State>;
948
- then: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<NoInfer<T>>;
949
- }>, deflt: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<T>): FlowBuilder<T>;
950
- /** L2 — declarative exit predicate over the response (precisely projectable). poll-on-status needs `accept`. */
951
- poll<CaseInputs, CaseOutput, AcceptKey, RawOutcome, Accept extends readonly AcceptKey[] = never, NewState = State>(ref: ContractCaseRef<CaseInputs, CaseOutput, AcceptKey, RawOutcome>, ...args: PollArgs<State, CaseInputs, [Accept] extends [never] ? CaseOutput : RawOutcome, NewState, Accept, {
952
- until: (w: PredicateScope<[Accept] extends [never] ? CaseOutput : RawOutcome>) => BranchPredicate<[Accept] extends [never] ? CaseOutput : RawOutcome>;
953
- message?: string;
954
- }>): FlowBuilder<NewState>;
955
- /** L1 — opaque sync exit predicate (gets ctx, res, state). `message` required (marked gate). */
956
- pollFn<CaseInputs, CaseOutput, AcceptKey, RawOutcome, Accept extends readonly AcceptKey[] = never, NewState = State>(ref: ContractCaseRef<CaseInputs, CaseOutput, AcceptKey, RawOutcome>, ...args: PollArgs<State, CaseInputs, [Accept] extends [never] ? CaseOutput : RawOutcome, NewState, Accept, {
957
- until: (ctx: TestContext, res: [Accept] extends [never] ? CaseOutput : RawOutcome, state: State) => boolean;
958
- message: string;
959
- }>): FlowBuilder<NewState>;
960
- /** L0 — opaque async exit predicate (may do contract-external I/O). `message` required (marked gate). */
961
- pollAsync<CaseInputs, CaseOutput, AcceptKey, RawOutcome, Accept extends readonly AcceptKey[] = never, NewState = State>(ref: ContractCaseRef<CaseInputs, CaseOutput, AcceptKey, RawOutcome>, ...args: PollArgs<State, CaseInputs, [Accept] extends [never] ? CaseOutput : RawOutcome, NewState, Accept, {
962
- until: (ctx: TestContext, res: [Accept] extends [never] ? CaseOutput : RawOutcome, state: State) => Promise<boolean>;
963
- message: string;
964
- }>): FlowBuilder<NewState>;
965
- build(): FlowContract<State>;
966
- }
967
- /**
968
- * Restricted builder for a branch body (condition then/else, switch case/default).
969
- * Accumulates step / compute / nested branch into the branch node's
970
- * `cases[].steps` / `default`; it deliberately does NOT expose flow-level
971
- * setup / teardown / meta / build.
972
- *
973
- * INVARIANT BRAND (Spike 0 / §8.1): `State` appears in both covariant (return)
974
- * and contravariant (parameter) position via the phantom `[FRAGMENT_STATE]`
975
- * field, making `FlowFragmentBuilder` *invariant* in `State`. Without this,
976
- * TS structural width-subtyping would let `FlowFragmentBuilder<{...State, x}>`
977
- * be assignable to `FlowFragmentBuilder<State>`, so a no-else branch could
978
- * silently add fields (or an else could return a `T` supertype) and bypass
979
- * convergence. The phantom is type-only — never read at runtime.
980
- */
981
- export interface FlowFragmentBuilder<State = unknown> {
982
- readonly [FRAGMENT_STATE]: (s: State) => State;
983
- step<CaseInputs, CaseOutput, AcceptKey, RawOutcome, Accept extends readonly AcceptKey[] = never, NewState = State>(ref: ContractCaseRef<CaseInputs, CaseOutput, AcceptKey, RawOutcome>, ...args: [CaseInputs] extends [void] ? [
984
- bindings?: {
985
- accept?: Accept;
986
- out?: (state: State, response: [Accept] extends [never] ? CaseOutput : RawOutcome) => NewState;
987
- name?: string;
988
- }
989
- ] : [
990
- bindings: {
991
- in: (state: State) => CaseInputs;
992
- accept?: Accept;
993
- out?: (state: State, response: [Accept] extends [never] ? CaseOutput : RawOutcome) => NewState;
994
- name?: string;
995
- }
996
- ]): FlowFragmentBuilder<NewState>;
997
- compute<NewState>(fn: (state: State) => NewState): FlowFragmentBuilder<NewState>;
998
- condition<R extends State = State>(spec: {
999
- predicate: (w: PredicateScope<State>) => BranchPredicate<State>;
1000
- message?: string;
1001
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<R & NoExtraKeys<R, State>>): FlowFragmentBuilder<State>;
1002
- condition<T>(spec: {
1003
- predicate: (w: PredicateScope<State>) => BranchPredicate<State>;
1004
- message?: string;
1005
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<T>, elseBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<NoInfer<T>>): FlowFragmentBuilder<T>;
1006
- conditionFn<R extends State = State>(spec: {
1007
- predicate: (ctx: TestContext, s: State) => boolean;
1008
- message: string;
1009
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<R & NoExtraKeys<R, State>>): FlowFragmentBuilder<State>;
1010
- conditionFn<T>(spec: {
1011
- predicate: (ctx: TestContext, s: State) => boolean;
1012
- message: string;
1013
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<T>, elseBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<NoInfer<T>>): FlowFragmentBuilder<T>;
1014
- conditionAsync<R extends State = State>(spec: {
1015
- predicate: (ctx: TestContext, s: State) => Promise<boolean>;
1016
- message: string;
1017
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<R & NoExtraKeys<R, State>>): FlowFragmentBuilder<State>;
1018
- conditionAsync<T>(spec: {
1019
- predicate: (ctx: TestContext, s: State) => Promise<boolean>;
1020
- message: string;
1021
- }, thenBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<T>, elseBranch: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<NoInfer<T>>): FlowFragmentBuilder<T>;
1022
- switchOn<V>(lens: (s: State) => V): <T>(cases: ReadonlyArray<{
1023
- value: [Exclude<V, undefined>] extends [JsonScalar] ? Exclude<V, undefined> : never;
1024
- then: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<NoInfer<T>>;
1025
- }>, deflt: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<T>) => FlowFragmentBuilder<T>;
1026
- switchCond<T>(cases: ReadonlyArray<{
1027
- when: (w: PredicateScope<State>) => BranchPredicate<State>;
1028
- then: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<NoInfer<T>>;
1029
- }>, deflt: (b: FlowFragmentBuilder<State>) => FlowFragmentBuilder<T>): FlowFragmentBuilder<T>;
1030
- poll<CaseInputs, CaseOutput, AcceptKey, RawOutcome, Accept extends readonly AcceptKey[] = never, NewState = State>(ref: ContractCaseRef<CaseInputs, CaseOutput, AcceptKey, RawOutcome>, ...args: PollArgs<State, CaseInputs, [Accept] extends [never] ? CaseOutput : RawOutcome, NewState, Accept, {
1031
- until: (w: PredicateScope<[Accept] extends [never] ? CaseOutput : RawOutcome>) => BranchPredicate<[Accept] extends [never] ? CaseOutput : RawOutcome>;
1032
- message?: string;
1033
- }>): FlowFragmentBuilder<NewState>;
1034
- pollFn<CaseInputs, CaseOutput, AcceptKey, RawOutcome, Accept extends readonly AcceptKey[] = never, NewState = State>(ref: ContractCaseRef<CaseInputs, CaseOutput, AcceptKey, RawOutcome>, ...args: PollArgs<State, CaseInputs, [Accept] extends [never] ? CaseOutput : RawOutcome, NewState, Accept, {
1035
- until: (ctx: TestContext, res: [Accept] extends [never] ? CaseOutput : RawOutcome, state: State) => boolean;
1036
- message: string;
1037
- }>): FlowFragmentBuilder<NewState>;
1038
- pollAsync<CaseInputs, CaseOutput, AcceptKey, RawOutcome, Accept extends readonly AcceptKey[] = never, NewState = State>(ref: ContractCaseRef<CaseInputs, CaseOutput, AcceptKey, RawOutcome>, ...args: PollArgs<State, CaseInputs, [Accept] extends [never] ? CaseOutput : RawOutcome, NewState, Accept, {
1039
- until: (ctx: TestContext, res: [Accept] extends [never] ? CaseOutput : RawOutcome, state: State) => Promise<boolean>;
1040
- message: string;
1041
- }>): FlowFragmentBuilder<NewState>;
1042
- }
1043
- /** Module-private phantom brand making FlowFragmentBuilder invariant in State. */
1044
- declare const FRAGMENT_STATE: unique symbol;
1045
- /**
1046
- * No-else branch shape guard. A no-else `then` must NOT add/remove state keys
1047
- * (when the predicate is false the branch is skipped, so downstream state stays
1048
- * `State`), but it MAY narrow an existing key's value (e.g. flip `ok: boolean`
1049
- * to `ok: true`). Encoded as `R extends State` (no removed keys, values only
1050
- * narrow) intersected with `NoExtraKeys<R, State>` (any extra key collapses to
1051
- * `never`, so adding a field fails to type-check). This is the ergonomic
1052
- * counterpart to the invariant brand: it blocks shape drift without rejecting
1053
- * safe literal narrowing.
824
+ * "No extra keys" structural guard maps any key of R that is NOT in State to
825
+ * `never`, so a branch/fragment returning extra keys fails to type-check.
826
+ * (Shared type utility; survived the legacy-flow deletion because the vNext
827
+ * builder's strict-S sides use the same trick.)
1054
828
  */
1055
829
  export type NoExtraKeys<R, State> = {
1056
830
  [K in Exclude<keyof R, keyof State>]: never;
1057
831
  };
1058
- /**
1059
- * Runtime flow contract. Extends Array<Test> so runner iterates directly.
1060
- * The single Test inside orchestrates setup → steps → teardown via runFlow.
1061
- */
1062
- export interface FlowContract<State = unknown> extends Array<Test> {
1063
- readonly _flow: RuntimeFlowProjection<State> & {
1064
- id: string;
1065
- };
1066
- /**
1067
- * Pre-computed JSON-safe extracted projection. Populated by the flow
1068
- * builder via `normalizeFlow(_flow)` so downstream consumers (scanner,
1069
- * CLI, MCP, Cloud) don't need to import the SDK to get field mappings
1070
- * for `.step()` lenses and reads/writes for `.compute()` nodes.
1071
- */
1072
- readonly _extracted: ExtractedFlowProjection;
1073
- }
1074
832
  /**
1075
833
  * Contract registry metadata attached to tests produced by `contract[protocol]()`.
1076
834
  * Mirrored by `RegisteredTestMeta.contract` in types.ts.
@@ -1093,60 +851,4 @@ export interface ContractRegistryMeta {
1093
851
  /** Plugin-defined free-form meta; core does not inspect. */
1094
852
  meta?: unknown;
1095
853
  }
1096
- /**
1097
- * Flow registry metadata attached to the single Test generated by
1098
- * `contract.flow()`. Mirrored by `RegisteredTestMeta.flow` in types.ts.
1099
- */
1100
- /** Registry step descriptor — same shape family as ExtractedFlowStep (recursive for branch). */
1101
- export type FlowRegistryStep = {
1102
- kind: "contract-call" | "compute";
1103
- name?: string;
1104
- contractId?: string;
1105
- caseKey?: string;
1106
- protocol?: string;
1107
- target?: string;
1108
- inputs?: FieldMapping[];
1109
- outputs?: FieldMapping[];
1110
- reads?: string[];
1111
- writes?: string[];
1112
- accept?: ReadonlyArray<string | number>;
1113
- } | {
1114
- kind: "branch";
1115
- mode: "value" | "predicate";
1116
- name?: string;
1117
- subjectPath?: string[];
1118
- cases: Array<{
1119
- value?: JsonScalar;
1120
- message?: string;
1121
- predicate?: ExtractedPredicate;
1122
- steps: FlowRegistryStep[];
1123
- }>;
1124
- default: FlowRegistryStep[];
1125
- } | {
1126
- kind: "poll";
1127
- name?: string;
1128
- contractId?: string;
1129
- caseKey?: string;
1130
- protocol?: string;
1131
- target?: string;
1132
- inputs?: FieldMapping[];
1133
- outputs?: FieldMapping[];
1134
- accept?: ReadonlyArray<string | number>;
1135
- until?: ExtractedPredicate;
1136
- message?: string;
1137
- every?: number;
1138
- backoff?: number;
1139
- timeoutMs?: number;
1140
- perAttemptTimeoutMs?: number;
1141
- maxAttempts?: number;
1142
- };
1143
- export interface FlowRegistryMeta {
1144
- id: string;
1145
- description?: string;
1146
- tags?: string[];
1147
- /** Step descriptors (same shape family as ExtractedFlowStep; branch nests recursively). */
1148
- steps: FlowRegistryStep[];
1149
- setupDynamic?: true;
1150
- }
1151
- export {};
1152
854
  //# sourceMappingURL=contract-types.d.ts.map