@glubean/sdk 0.1.39 → 0.2.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 (47) hide show
  1. package/dist/contract-core.d.ts +96 -0
  2. package/dist/contract-core.d.ts.map +1 -0
  3. package/dist/contract-core.js +747 -0
  4. package/dist/contract-core.js.map +1 -0
  5. package/dist/contract-http/adapter.d.ts +52 -0
  6. package/dist/contract-http/adapter.d.ts.map +1 -0
  7. package/dist/contract-http/adapter.js +650 -0
  8. package/dist/contract-http/adapter.js.map +1 -0
  9. package/dist/contract-http/factory.d.ts +32 -0
  10. package/dist/contract-http/factory.d.ts.map +1 -0
  11. package/dist/contract-http/factory.js +83 -0
  12. package/dist/contract-http/factory.js.map +1 -0
  13. package/dist/contract-http/flow-helpers.d.ts +12 -0
  14. package/dist/contract-http/flow-helpers.d.ts.map +1 -0
  15. package/dist/contract-http/flow-helpers.js +34 -0
  16. package/dist/contract-http/flow-helpers.js.map +1 -0
  17. package/dist/contract-http/index.d.ts +15 -0
  18. package/dist/contract-http/index.d.ts.map +1 -0
  19. package/dist/contract-http/index.js +14 -0
  20. package/dist/contract-http/index.js.map +1 -0
  21. package/dist/contract-http/markdown.d.ts +10 -0
  22. package/dist/contract-http/markdown.d.ts.map +1 -0
  23. package/dist/contract-http/markdown.js +21 -0
  24. package/dist/contract-http/markdown.js.map +1 -0
  25. package/dist/contract-http/openapi.d.ts +15 -0
  26. package/dist/contract-http/openapi.d.ts.map +1 -0
  27. package/dist/contract-http/openapi.js +38 -0
  28. package/dist/contract-http/openapi.js.map +1 -0
  29. package/dist/contract-http/types.d.ts +252 -0
  30. package/dist/contract-http/types.d.ts.map +1 -0
  31. package/dist/contract-http/types.js +13 -0
  32. package/dist/contract-http/types.js.map +1 -0
  33. package/dist/contract-types.d.ts +420 -467
  34. package/dist/contract-types.d.ts.map +1 -1
  35. package/dist/contract-types.js +16 -4
  36. package/dist/contract-types.js.map +1 -1
  37. package/dist/index.d.ts +21 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +24 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/types.d.ts +22 -10
  42. package/dist/types.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/dist/contract.d.ts +0 -64
  45. package/dist/contract.d.ts.map +0 -1
  46. package/dist/contract.js +0 -793
  47. package/dist/contract.js.map +0 -1
@@ -1,24 +1,23 @@
1
1
  /**
2
- * Types for the contract() API.
2
+ * Core types for the protocol-agnostic contract system.
3
3
  *
4
- * contract.http() is a declarative test factory for any HTTP API:
5
- * spec in, Test[] out. Users provide their own HTTP clients via
6
- * configure() the contract layer has no opinion on auth strategy.
7
- */
8
- import type { SchemaLike } from "./types.js";
9
- import type { Test, TestContext, RegisteredTestMeta, HttpClient } from "./types.js";
10
- /**
11
- * HTTP security scheme declaration for contract instances.
12
- * Maps to OpenAPI securitySchemes. Authoritative metadata, not docs-only.
4
+ * This file defines:
5
+ * - Protocol-agnostic enums (lifecycle / severity / requires / defaultRun)
6
+ * - Failure classification types
7
+ * - ContractProtocolAdapter<Spec, Rt, RtM, Sf, SfM> interface
8
+ * - ContractProjection<RuntimeSchemas, RuntimeMeta> Runtime (live objects OK)
9
+ * - ExtractedContractProjection<SafeSchemas, SafeMeta> JSON-safe (downstream)
10
+ * - ProtocolContract<Spec, PayloadSchemas, Meta> — runtime carrier
11
+ * - Flow types ContractCaseRef / FlowBuilder / FlowContract etc.
12
+ *
13
+ * Protocol-specific types (HttpContractSpec / HttpPayloadSchemas / etc.) live
14
+ * in `./contract-http/types.ts`. Core code never imports from there; other
15
+ * adapter plugins (gRPC / GraphQL / etc.) will follow the same pattern.
16
+ *
17
+ * See `internal/40-discovery/proposals/contract-generics-complete.md` v5
18
+ * and `internal/40-discovery/proposals/contract-flow.md` v9 for design.
13
19
  */
14
- export type HttpSecurityScheme = "bearer" | "basic" | {
15
- type: "apiKey";
16
- name: string;
17
- in: "header" | "query";
18
- } | {
19
- type: "oauth2";
20
- flows: Record<string, unknown>;
21
- } | null;
20
+ import type { Test, TestContext } from "./types.js";
22
21
  /**
23
22
  * Case lifecycle.
24
23
  *
@@ -35,25 +34,38 @@ export type CaseLifecycle = "active" | "deferred" | "deprecated";
35
34
  * - `"info"` — informational check, failure does not trigger alerts
36
35
  */
37
36
  export type CaseSeverity = "critical" | "warning" | "info";
37
+ /**
38
+ * Physical capability required by a case or flow.
39
+ *
40
+ * - `"headless"` — fully automated, no human in loop (default)
41
+ * - `"browser"` — needs a real browser (OAuth code flow, checkout, captcha)
42
+ * - `"out-of-band"` — needs an out-of-band channel (email, SMS, webhook tunnel)
43
+ */
44
+ export type CaseRequires = "headless" | "browser" | "out-of-band";
45
+ /**
46
+ * Default run policy for a case or flow.
47
+ *
48
+ * - `"always"` — run whenever the runner satisfies `requires` (default)
49
+ * - `"opt-in"` — skip unless explicitly requested (`--include-opt-in`)
50
+ */
51
+ export type CaseDefaultRun = "always" | "opt-in";
52
+ /**
53
+ * OpenAPI-style extensions. Keys MUST start with "x-".
54
+ * Non-x keys are rejected at the type level.
55
+ */
56
+ export type Extensions = Record<`x-${string}`, unknown>;
38
57
  /**
39
58
  * Standardized failure classification kind. Protocol-agnostic.
40
59
  *
41
60
  * Open string type — adapters can extend with custom values.
42
- * Recommended values:
43
- * - `"auth"` — authentication failure (401-class)
44
- * - `"permission"` — authorization failure (403-class)
45
- * - `"not-found"` — resource does not exist
46
- * - `"schema"` — response shape mismatch
47
- * - `"timeout"` — request or execution timeout
48
- * - `"transport"` — network/connection error
49
- * - `"rate-limit"` — throttled
50
- * - `"business-rule"` — business logic assertion failure
51
- * - `"unknown"` — cannot classify
61
+ * Recommended: "auth", "permission", "not-found", "schema", "timeout",
62
+ * "transport", "rate-limit", "business-rule", "unknown".
52
63
  */
53
64
  export type FailureKind = string;
54
65
  /**
55
66
  * Standardized failure classification.
56
- * Produced by protocol adapters or core. Consumed by repair loop, Cloud, runner summary.
67
+ * Produced by protocol adapters or core. Consumed by repair loop, Cloud,
68
+ * runner summary.
57
69
  */
58
70
  export interface FailureClassification {
59
71
  kind: FailureKind;
@@ -62,539 +74,480 @@ export interface FailureClassification {
62
74
  message?: string;
63
75
  }
64
76
  /**
65
- * Default values for a scoped HTTP contract instance.
66
- * Created via `contract.http.with("name", defaults)`.
67
- */
68
- /**
69
- * OpenAPI-style extensions. Keys MUST start with "x-".
70
- * Non-x keys are rejected at the type level.
71
- * Used for tool-interop metadata (e.g. "x-glubean-internal-id").
77
+ * Protocol-agnostic case metadata. `schemas` is plugin-defined payload shape,
78
+ * opaque to core.
79
+ *
80
+ * @template PayloadSchemas Plugin-defined payload shape (e.g. HttpPayloadSchemas).
81
+ * Same shape for every case in a contract; differences between cases live in
82
+ * the values, not in the type.
83
+ * @template Meta Plugin-defined free-form metadata (opaque to core).
72
84
  */
73
- export type Extensions = Record<`x-${string}`, unknown>;
74
- export interface HttpContractDefaults {
75
- /** Default HTTP client for all contracts in this instance */
76
- client?: HttpClient;
77
- /** Security scheme declaration (authoritative, maps to OpenAPI) */
78
- security?: HttpSecurityScheme;
79
- /** Tags inherited by all contracts in this instance */
85
+ export interface CaseMeta<PayloadSchemas = unknown, Meta = unknown> {
86
+ key: string;
87
+ description?: string;
88
+ lifecycle: CaseLifecycle;
89
+ severity: CaseSeverity;
90
+ deferredReason?: string;
91
+ deprecatedReason?: string;
92
+ requires?: CaseRequires;
93
+ defaultRun?: CaseDefaultRun;
80
94
  tags?: string[];
81
- /** Default feature grouping key */
82
- feature?: string;
83
- /** OpenAPI extensions (x-* keys). Inherited by all contracts in this instance. */
84
95
  extensions?: Extensions;
96
+ /** Plugin-defined payload shape. Opaque to core. */
97
+ schemas?: PayloadSchemas;
98
+ /** Plugin-defined free-form metadata. Opaque to core. */
99
+ meta?: Meta;
85
100
  }
86
101
  /**
87
- * Root contract.http entrypointonly .with() is available.
88
- * Direct contract.http("id", spec) is not supported.
102
+ * Runtime contract projectionadapter.project() output.
103
+ * Allowed to contain live objects (Zod schemas, class instances). Never crosses
104
+ * a serialization boundary; adapter.normalize() converts to Extracted.
89
105
  */
90
- export interface HttpContractRoot {
91
- with(name: string, defaults: HttpContractDefaults): HttpContractFactory;
106
+ export interface ContractProjection<PayloadSchemas = unknown, Meta = unknown> {
107
+ protocol: string;
108
+ target: string;
109
+ description?: string;
110
+ feature?: string;
111
+ instanceName?: string;
112
+ tags?: string[];
113
+ extensions?: Extensions;
114
+ deprecated?: string;
115
+ cases: Array<CaseMeta<PayloadSchemas, Meta>>;
116
+ /** Contract-level schemas (e.g. HTTP request body shared across cases). */
117
+ schemas?: PayloadSchemas;
118
+ /** Contract-level free-form metadata. */
119
+ meta?: Meta;
92
120
  }
93
121
  /**
94
- * Protocol-bound contract factory returned by `contract.http.with()`.
95
- * Callable: `factory("id", spec)` creates an HttpContract.
96
- * Chainable: `factory.with("name", defaults)` creates a nested instance.
122
+ * JSON-safe case metadata. Shape-identical to CaseMeta but `schemas` / `meta`
123
+ * must be JSON-safe (plain objects / arrays / primitives). Produced by
124
+ * adapter.normalize().
97
125
  */
98
- export interface HttpContractFactory {
99
- <Cases extends Record<string, ContractCase<any, any>>>(id: string, spec: HttpContractSpec<Cases>): HttpContract;
100
- with(name: string, defaults: HttpContractDefaults): HttpContractFactory;
101
- }
126
+ export type ExtractedCaseMeta<SafeSchemas = unknown, SafeMeta = unknown> = CaseMeta<SafeSchemas, SafeMeta>;
102
127
  /**
103
- * Physical capability required by a case or flow.
104
- *
105
- * - `"headless"` — fully automated, no human in loop (default)
106
- * - `"browser"` — needs a real browser (OAuth code flow, checkout, captcha)
107
- * - `"out-of-band"` — needs an out-of-band channel (email, SMS, push, webhook tunnel)
128
+ * JSON-safe contract projection. Downstream (scanner / MCP / CLI / Cloud)
129
+ * consume this. Includes `id` (injected by core).
108
130
  */
109
- export type CaseRequires = "headless" | "browser" | "out-of-band";
110
- /**
111
- * Default run policy for a case or flow.
112
- *
113
- * - `"always"` — run whenever the runner satisfies `requires` (default)
114
- * - `"opt-in"` — skip unless explicitly requested (e.g. `--include-opt-in`)
115
- *
116
- * Use `"opt-in"` for cases that are expensive, have real side effects,
117
- * or are slow (Twilio SMS, Stripe charges, long stress tests).
118
- */
119
- export type CaseDefaultRun = "always" | "opt-in";
120
- /**
121
- * Normalized response headers shape.
122
- * HTTP allows multi-value headers (e.g. Set-Cookie), so values can be string or string[].
123
- * Keys are always lowercase (runtime normalization).
124
- */
125
- export type NormalizedHeaders = Record<string, string | string[]>;
126
- /**
127
- * A single example entry for OpenAPI docs.
128
- */
129
- export interface ContractExample<T = unknown> {
130
- value: T;
131
- summary?: string;
131
+ export interface ExtractedContractProjection<SafeSchemas = unknown, SafeMeta = unknown> {
132
+ id: string;
133
+ protocol: string;
134
+ target: string;
132
135
  description?: string;
136
+ feature?: string;
137
+ instanceName?: string;
138
+ tags?: string[];
139
+ extensions?: Extensions;
140
+ deprecated?: string;
141
+ cases: Array<ExtractedCaseMeta<SafeSchemas, SafeMeta>>;
142
+ schemas?: SafeSchemas;
143
+ meta?: SafeMeta;
133
144
  }
134
145
  /**
135
- * Expected response for a contract case.
146
+ * Protocol adapter interface. Implement to add support for a new protocol
147
+ * (HTTP is built-in; gRPC / GraphQL etc. will be external plugins).
136
148
  *
137
- * @template T The parsed response type (inferred from schema)
149
+ * @template Spec Adapter's input spec type (e.g. HttpContractSpec).
150
+ * @template RuntimeSchemas Runtime payload shape (live objects allowed).
151
+ * @template RuntimeMeta Runtime free-form meta (live objects allowed).
152
+ * @template SafeSchemas JSON-safe payload shape (after normalize).
153
+ * @template SafeMeta JSON-safe free-form meta.
138
154
  */
139
- export interface ContractExpect<T = unknown> {
140
- /** Expected HTTP status code */
141
- status: number;
142
- /** Zod/Valibot schema to validate response body. Parsed value is passed to verify(). */
143
- schema?: SchemaLike<T>;
155
+ export interface ContractProtocolAdapter<Spec = unknown, RuntimeSchemas = unknown, RuntimeMeta = unknown, SafeSchemas = unknown, SafeMeta = unknown> {
144
156
  /**
145
- * Expected response content-type. Default: "application/json".
146
- * Used by OpenAPI generation and (future) response body deserialization dispatch.
157
+ * Execute a single case. Called by the core dispatcher for each spec case.
158
+ * Adapter does the full case lifecycle: setup request/invoke → expect →
159
+ * verify → teardown.
147
160
  */
148
- contentType?: string;
161
+ execute: (ctx: TestContext, caseSpec: unknown, contractSpec: Spec) => Promise<void>;
149
162
  /**
150
- * Schema to validate response headers. Headers are normalized to lowercase keys
151
- * before validation (HTTP spec: header names are case-insensitive).
152
- * Values are `string | string[]` to support multi-value headers (e.g. Set-Cookie).
163
+ * Project the spec to a Runtime projection (may contain live schemas).
153
164
  *
154
- * @example
155
- * headers: z.object({
156
- * "content-type": z.string().regex(/^application\/json/),
157
- * "x-request-id": z.string().uuid(),
158
- * })
159
- */
160
- headers?: SchemaLike<NormalizedHeaders>;
161
- /**
162
- * Single response example (shorthand). Equivalent to examples: { default: { value } }.
163
- * Not used at runtime — only for OpenAPI documentation.
164
- */
165
- example?: T;
166
- /**
167
- * Multiple named response examples. Mapped to OpenAPI responses[status].content.examples.
168
- * Not used at runtime — only for OpenAPI documentation.
169
- */
170
- examples?: Record<string, ContractExample<T>>;
171
- }
172
- /**
173
- * Path or query parameter value.
174
- * String shorthand: just the value. Object form: value + OpenAPI metadata.
175
- */
176
- export type ParamValue = string | {
177
- /** The actual value used for URL substitution / query string construction */
178
- value: string;
179
- /** OpenAPI parameter schema (not used at runtime, see Part 5 of proposal) */
180
- schema?: SchemaLike<unknown>;
181
- /** Parameter description for OpenAPI docs */
182
- description?: string;
183
- /** Whether the parameter is required (default: true for path, false for query) */
184
- required?: boolean;
185
- /** Whether the parameter is deprecated */
186
- deprecated?: boolean;
187
- };
188
- /**
189
- * A single spec case within a contract.
190
- *
191
- * @template T The parsed response type (inferred from expect.schema)
192
- * @template S The setup return type
193
- */
194
- export interface ContractCase<T = unknown, S = void> {
195
- /**
196
- * HTTP client to use for this case.
197
- * Each client can have different auth, base URL, headers, etc.
198
- * Created via configure() — contract doesn't care about the auth strategy.
165
+ * **Invariant**: `project().cases[].key` must 1:1 match `spec.cases` keys.
166
+ * Core validates this at registration time:
167
+ * - projected key not in spec.cases → hard error
168
+ * - spec.cases key not in projection → hard error
169
+ * - duplicate key → hard error
199
170
  *
200
- * If omitted, uses the contract-level `client`.
171
+ * The returned projection does NOT include `id` — core injects it.
201
172
  */
202
- client?: HttpClient;
203
- /** Why this case exists — business logic, boundary condition, or intent. Required. */
204
- description: string;
205
- /** Expected response */
206
- expect: ContractExpect<T>;
173
+ project: (spec: Spec) => ContractProjection<RuntimeSchemas, RuntimeMeta>;
207
174
  /**
208
- * Request body (for POST/PUT/PATCH) static value or function deriving from setup state.
209
- * For non-JSON content types, provide `FormData`, `URLSearchParams`, `Blob`, or string.
210
- * JSON is the default (plain object).
175
+ * Optional: normalize a Runtime projection to JSON-safe Extracted form.
176
+ * Called by scanner / MCP / CLI / Cloud.
177
+ *
178
+ * **Input is already `id`-injected** by core. Adapter just passes `id`
179
+ * through to the returned object.
180
+ *
181
+ * If adapter does NOT implement normalize, downstream consumers see only a
182
+ * protocol-agnostic skeleton (no `schemas` / `meta`). See `contract-flow.md`
183
+ * §3.5.3 rule 3.
211
184
  */
212
- body?: unknown | FormData | URLSearchParams | Blob | ((state: S) => unknown);
185
+ normalize?: (projection: ContractProjection<RuntimeSchemas, RuntimeMeta> & {
186
+ id: string;
187
+ }) => ExtractedContractProjection<SafeSchemas, SafeMeta>;
213
188
  /**
214
- * Request content type override for this case.
215
- * If not set, inherits from contract.request.contentType, defaults to "application/json".
216
- * Supported: "application/json", "multipart/form-data", "application/x-www-form-urlencoded",
217
- * "text/plain", "application/octet-stream".
189
+ * Optional: classify failure from error + event log.
190
+ * Consumers: repair loop, Cloud alerts, runner summary.
218
191
  */
219
- contentType?: string;
192
+ classifyFailure?: (input: {
193
+ error?: unknown;
194
+ events: Array<{
195
+ type: string;
196
+ data: Record<string, unknown>;
197
+ }>;
198
+ }) => FailureClassification | undefined;
220
199
  /**
221
- * URL params static object or function deriving from setup state.
222
- * Values can be plain strings or ParamValue objects with OpenAPI metadata.
200
+ * Optional: render contract as an OpenAPI fragment. Input is the Extracted
201
+ * projection (after normalize). Used by MCP `glubean_openapi` tool.
223
202
  */
224
- params?: Record<string, ParamValue> | ((state: S) => Record<string, string>);
203
+ toOpenApi?: (projection: ExtractedContractProjection<SafeSchemas, SafeMeta>) => Record<string, unknown> | undefined;
225
204
  /**
226
- * Query parameters static object or function deriving from setup state.
227
- * Values can be plain strings or ParamValue objects with OpenAPI metadata.
205
+ * Optional: render contract as Markdown documentation. Used by
206
+ * CLI `glubean contracts --format md-outline` and
207
+ * MCP `glubean_project_contracts`.
228
208
  */
229
- query?: Record<string, ParamValue> | ((state: S) => Record<string, string>);
230
- /** Request headers (merged with client headers) — static object or function deriving from setup state */
231
- headers?: Record<string, string> | ((state: S) => Record<string, string>);
209
+ toMarkdown?: (projection: ExtractedContractProjection<SafeSchemas, SafeMeta>) => string;
232
210
  /**
233
- * Setup function runs before the request. Return value is available
234
- * to params/query (if function) and teardown.
211
+ * Optional: render the `target` string for display. HTTP: "POST /users"
212
+ * stays as-is. gRPC: "Greeter/SayHello" might become "Greeter.SayHello()".
235
213
  */
236
- setup?: (ctx: TestContext) => Promise<S>;
214
+ renderTarget?: (target: string) => string;
237
215
  /**
238
- * Teardown function runs after verify, even if verify fails.
216
+ * Optional: produce a high-level payload summary for indexing / UI.
217
+ * Input: already-normalized SafeSchemas. Used when full schemas would be
218
+ * too heavy (e.g. index views).
239
219
  */
240
- teardown?: (ctx: TestContext, state: S) => Promise<void>;
220
+ describePayload?: (schemas: SafeSchemas) => PayloadDescriptor | undefined;
241
221
  /**
242
- * Business logic verification runs after status and schema validation.
243
- * `res` is the schema-parsed response (typed) if schema was provided,
244
- * otherwise the raw parsed JSON (unknown).
222
+ * Optional: execute a single case as a flow step.
245
223
  *
246
- * Use this for assertions that can't be expressed declaratively:
247
- * field relationships, computed checks, side-effect verification.
248
- */
249
- verify?: (ctx: TestContext, res: T) => Promise<void>;
250
- /**
251
- * Mark this case as not yet executable. Reason is shown in skip message
252
- * and coverage reports. Remove this field to activate the case.
253
- */
254
- deferred?: string;
255
- /** Additional tags for this case (merged with contract-level tags) */
256
- tags?: string[];
257
- /**
258
- * Physical capability this case requires to execute.
224
+ * Core has already:
225
+ * 1. Computed `resolvedInputs` via `step.bindings.in(state)` (may be partial)
226
+ * 2. Prepared current flow state
227
+ * 3. Passed the live contract instance (access merged scoped-factory state
228
+ * via `contract._spec`)
259
229
  *
260
- * Default: `"headless"` — fully automated, no human in loop.
230
+ * Adapter responsibilities:
231
+ * 1. Deep-merge `resolvedInputs` into the case's static input fields
232
+ * 2. Run case setup / request / expect / verify / case teardown
233
+ * (Rule 1: case teardown is step-local finally — see contract-flow §7.3)
234
+ * 3. Return adapter-specific CaseOutput shape (HTTP: { status, headers, body })
261
235
  *
262
- * When set to `"browser"` or `"out-of-band"`, the runner will skip this case
263
- * unless the corresponding `--include-browser` / `--include-out-of-band` flag
264
- * is passed. Skipped cases are reported explicitly with reason.
236
+ * Not implemented = this protocol cannot be referenced in a flow.
237
+ */
238
+ executeCaseInFlow?: (input: {
239
+ ctx: TestContext;
240
+ contract: ProtocolContract<Spec, SafeSchemas, SafeMeta>;
241
+ caseKey: string;
242
+ resolvedInputs: unknown;
243
+ }) => Promise<unknown>;
244
+ /**
245
+ * Optional: validate that a case can be referenced in a flow. Called by
246
+ * ProtocolContract.case(key). HTTP's implementation rejects cases with
247
+ * function-valued input fields (body/params/query/headers) because those
248
+ * depend on case-local setup state unavailable in flow mode. See
249
+ * contract-flow v9 §5.1.1 for rationale.
265
250
  *
266
- * Note: setting `requires` to a non-headless value automatically implies
267
- * `defaultRun: "opt-in"`.
251
+ * Throws on invalid case; returns undefined on success.
268
252
  */
269
- requires?: CaseRequires;
270
- /**
271
- * Default run policy — should this case run automatically, or only when
272
- * explicitly requested?
273
- *
274
- * Default: `"always"` — run whenever the runner satisfies `requires`.
275
- *
276
- * Set to `"opt-in"` for cases that are expensive (real Twilio SMS),
277
- * have real side effects (Stripe charges), or are slow (stress tests).
278
- * Opt-in cases require `--include-opt-in` to run, even locally.
279
- *
280
- * Note: when `requires !== "headless"`, `defaultRun` is automatically
281
- * set to `"opt-in"` if not explicitly provided.
282
- */
283
- defaultRun?: CaseDefaultRun;
253
+ validateCaseForFlow?: (spec: Spec, caseKey: string, contractId: string) => void;
254
+ }
255
+ /**
256
+ * Protocol-agnostic payload summary. Adapter-provided via describePayload.
257
+ * Free-form — common keys: "hasRequest", "hasResponse", "contentType",
258
+ * "messageCount", "streaming".
259
+ */
260
+ export interface PayloadDescriptor {
261
+ hasRequest?: boolean;
262
+ hasResponse?: boolean;
263
+ [key: string]: unknown;
264
+ }
265
+ /**
266
+ * Runtime contract object returned by `contract[protocol](id, spec)`.
267
+ * Extends Array<Test> so runner/resolve iterate it directly.
268
+ *
269
+ * @template Spec Adapter's spec type — executable info stored in `_spec`.
270
+ * @template PayloadSchemas Runtime (live) payload shape.
271
+ * @template Meta Runtime free-form meta.
272
+ */
273
+ export interface ProtocolContract<Spec = unknown, PayloadSchemas = unknown, Meta = unknown> extends Array<Test> {
284
274
  /**
285
- * Case severity. Affects Cloud alert routing.
286
- * Default: `"warning"`.
275
+ * Runtime projection with `id` injected by core. Consumers duck-type this.
287
276
  */
288
- severity?: CaseSeverity;
277
+ readonly _projection: ContractProjection<PayloadSchemas, Meta> & {
278
+ id: string;
279
+ };
289
280
  /**
290
- * Mark this case as deprecated. Value is the deprecation reason.
291
- * Deprecated cases are skipped at runtime but appear in projection output.
281
+ * Adapter-private runtime spec carrier. Holds the merged executable spec
282
+ * (scoped-factory defaults + contract spec) used by `executeCaseInFlow`
283
+ * and any adapter-internal helpers.
292
284
  *
293
- * `deprecated` takes precedence over `deferred`: if both are set,
294
- * lifecycle normalizes to `"deprecated"`.
285
+ * Core never inspects this field. Adapter writes it at construction, reads
286
+ * it during execution.
295
287
  */
296
- deprecated?: string;
288
+ readonly _spec: Spec;
297
289
  /**
298
- * OpenAPI extensions (x-* keys). Merged over contract-level extensions
299
- * (precedence: defaults < contract < case).
290
+ * Return a ContractCaseRef for use in `contract.flow(...).step(...)`.
291
+ *
292
+ * Runtime validation: adapter's `.case(key)` implementation MUST fail-fast
293
+ * if the case contains function-valued input fields (body/params/query/
294
+ * headers as functions). Function fields reference case-local setup state
295
+ * which is not available in flow mode. See contract-flow §5.1.1.
300
296
  */
301
- extensions?: Extensions;
297
+ case(key: string): ContractCaseRef<InferInputs<PayloadSchemas>, InferOutput<PayloadSchemas>>;
302
298
  }
303
299
  /**
304
- * Structured request specification.
305
- * Can be provided as a bare SchemaLike (shorthand for JSON body) or as an
306
- * object with full metadata (contentType, headers, examples).
300
+ * Adapter-defined helper: extract the "case inputs" shape from PayloadSchemas.
301
+ * Each adapter exports its own version (HTTP adapter defines InferHttpInputs).
302
+ * Used by `.case()` return type to give lens functions TS autocomplete.
303
+ *
304
+ * The contract-level `PayloadSchemas` is the same for every case; I/O shape
305
+ * differences between cases live in values, not types.
306
+ *
307
+ * This is a default fallback — adapters override via module augmentation or
308
+ * direct typing of their own ContractCaseRef.
307
309
  */
308
- export type RequestSpec = SchemaLike<unknown> | {
309
- body?: SchemaLike<unknown>;
310
- /** Request content type. Default: "application/json". */
311
- contentType?: string;
312
- /** Request headers schema (OpenAPI docs only, not runtime validated on request). */
313
- headers?: SchemaLike<Record<string, string>>;
314
- /** Single example value */
315
- example?: unknown;
316
- /** Named examples */
317
- examples?: Record<string, ContractExample<unknown>>;
318
- };
310
+ export type InferInputs<_PayloadSchemas> = unknown;
311
+ /** Adapter-defined helper: extract the "case output" shape from PayloadSchemas. */
312
+ export type InferOutput<_PayloadSchemas> = unknown;
319
313
  /**
320
- * Spec for contract.http() defines an HTTP endpoint and its cases.
314
+ * Opaque reference to a single case of a contract. Produced by
315
+ * `ProtocolContract.case(key)`. Used as input to `FlowBuilder.step(...)`.
321
316
  *
322
- * @template Cases Record of case key ContractCase
317
+ * The generic parameters carry type information for lens TS inference; at
318
+ * runtime the ref only holds identification strings + the live contract ref.
323
319
  */
324
- export interface HttpContractSpec<Cases extends Record<string, ContractCase<any, any>> = Record<string, ContractCase>> {
325
- /** HTTP method + path, e.g. "POST /users" or "GET /runs/:runId" */
326
- endpoint: string;
327
- /** Human-readable description (optional, for projection/docs) */
320
+ export interface ContractCaseRef<CaseInputs = unknown, CaseOutput = unknown> {
321
+ readonly __glubean_type: "contract-case-ref";
322
+ readonly contractId: string;
323
+ readonly caseKey: string;
324
+ readonly protocol: string;
325
+ readonly target: string;
326
+ /** Live ProtocolContract instance — flow runtime uses this, not contractId lookup. */
327
+ readonly contract: ProtocolContract<any, any, any>;
328
+ /** Phantom fields — do not populate at runtime. TS-only. */
329
+ readonly __phantom_inputs?: CaseInputs;
330
+ readonly __phantom_output?: CaseOutput;
331
+ }
332
+ /** Contract-level metadata for a flow (set via `.meta()`). */
333
+ export interface FlowMeta {
334
+ id: string;
335
+ name?: string;
328
336
  description?: string;
337
+ tags?: string[];
338
+ extensions?: Extensions;
329
339
  /**
330
- * Feature grouping key for projection output.
331
- * Contracts with the same `feature` value are grouped into one section.
332
- * If omitted, the contract is grouped by endpoint.
340
+ * Mark this flow as skipped at run time. Value is the skip reason
341
+ * displayed in reports. Useful for illustrative examples that should
342
+ * be discoverable (for scanner extraction / docs rendering) but must
343
+ * not attempt live HTTP calls.
333
344
  *
334
- * Use business language, not technical terms:
335
- * Good: "用户注册", "User Registration"
336
- * Bad: "POST /users endpoint"
345
+ * Mirrors `TestMeta.skip`.
337
346
  */
338
- feature?: string;
347
+ skip?: string;
339
348
  /**
340
- * Default HTTP client for all cases.
341
- * Individual cases can override with their own `client`.
349
+ * Mark this flow as focused. When any flows/tests in a run are `only`,
350
+ * non-focused ones may be excluded. Mirrors `TestMeta.only`.
342
351
  */
343
- client?: HttpClient;
352
+ only?: boolean;
353
+ }
354
+ /**
355
+ * Field dependency / mapping produced by Proxy dry-run of a lens function.
356
+ * Consumed by downstream (MCP/CLI/Cloud) to render flow data-flow diagrams.
357
+ */
358
+ export interface FieldMapping {
359
+ /** Destination path (within step inputs or flow state). */
360
+ target: string;
361
+ /** Source — a path in state/response, a literal, or pass-through. */
362
+ source: {
363
+ kind: "path";
364
+ path: string;
365
+ } | {
366
+ kind: "literal";
367
+ value: unknown;
368
+ } | {
369
+ kind: "pass-through";
370
+ };
371
+ }
372
+ /**
373
+ * Runtime flow step — discriminated union.
374
+ * kind "contract-call" = a ContractCaseRef with bindings (Rule 1 teardown applies).
375
+ * kind "compute" = a pure sync data-transform function (no adapter, no teardown).
376
+ */
377
+ export type RuntimeFlowStep = RuntimeContractCallStep | RuntimeComputeStep;
378
+ export interface RuntimeContractCallStep {
379
+ kind: "contract-call";
380
+ name?: string;
381
+ ref: ContractCaseRef;
382
+ caseKey: string;
383
+ /** Live contract instance (mirrors ref.contract, kept for direct access). */
384
+ contract: ProtocolContract<any, any, any>;
385
+ bindings?: {
386
+ in?: (state: any) => any;
387
+ out?: (state: any, response: any) => any;
388
+ };
389
+ }
390
+ export interface RuntimeComputeStep {
391
+ kind: "compute";
392
+ name?: string;
344
393
  /**
345
- * Request specification endpoint-level, for scanner/OpenAPI (not used at runtime).
346
- * Can be a bare SchemaLike (shorthand for JSON body) or a structured RequestSpec object.
394
+ * Synchronous pure function. NOT subject to lens Proxy purity — may use
395
+ * template literals / method calls / .map(). MUST be synchronous and
396
+ * MUST NOT return a thenable (enforced at runtime).
347
397
  */
348
- request?: RequestSpec;
349
- /** Tags inherited by all cases */
398
+ fn: (state: any) => any;
399
+ }
400
+ /**
401
+ * Runtime flow projection. Carries live callbacks + live contract refs.
402
+ * Never crosses serialization boundaries. `normalizeFlow()` converts to
403
+ * ExtractedFlowProjection for downstream consumers.
404
+ */
405
+ export interface RuntimeFlowProjection<State = unknown> {
406
+ protocol: "flow";
407
+ description?: string;
350
408
  tags?: string[];
351
- /**
352
- * Mark the entire endpoint as deprecated. Value is the deprecation reason.
353
- * Propagates to all cases: every case lifecycle becomes "deprecated" unless
354
- * the case explicitly sets its own `deprecated` reason.
355
- * Maps to OpenAPI operation `deprecated: true` + `x-deprecated-reason`.
356
- */
357
- deprecated?: string;
358
- /**
359
- * OpenAPI extensions (x-* keys). Merged over instance-level defaults.extensions.
360
- * Precedence: defaults < contract < case.
361
- */
362
409
  extensions?: Extensions;
363
- /** Named spec cases */
364
- cases: Cases;
410
+ /** Live flow-level setup callback (only I/O-capable callback in flow). */
411
+ setup?: (ctx: TestContext) => Promise<State>;
412
+ /** Live flow-level teardown callback. Rule 2: outer finally. */
413
+ teardown?: (ctx: TestContext, state: State) => Promise<void>;
414
+ steps: RuntimeFlowStep[];
365
415
  }
366
416
  /**
367
- * Return value of contract.http().
368
- *
369
- * Extends Array<Test> so runner/resolve can iterate it directly.
370
- * Adds contract-level properties and interop methods.
417
+ * Extracted flow step — discriminated union, JSON-safe.
371
418
  */
372
- export interface HttpContract extends Array<Test> {
373
- /** Contract ID */
374
- readonly id: string;
375
- /** Endpoint (e.g. "POST /users") */
376
- readonly endpoint: string;
377
- /** Contract-level description */
378
- readonly description?: string;
379
- /** Feature grouping key */
380
- readonly feature?: string;
381
- /** Instance name from contract.http.with("name", ...) */
382
- readonly instanceName?: string;
383
- /** Security scheme declaration */
384
- readonly security?: HttpSecurityScheme;
385
- /** Endpoint-level request body schema, normalized from RequestSpec */
386
- readonly request?: SchemaLike<unknown>;
387
- /** Endpoint-level request content type (default: "application/json") */
388
- readonly requestContentType?: string;
389
- /** Endpoint-level request headers schema (OpenAPI docs) */
390
- readonly requestHeaders?: SchemaLike<Record<string, string>>;
391
- /** Single request example for OpenAPI docs */
392
- readonly requestExample?: unknown;
393
- /** Named request examples for OpenAPI docs */
394
- readonly requestExamples?: Record<string, ContractExample<unknown>>;
395
- /** Contract-level deprecation reason (propagates to all cases) */
396
- readonly deprecated?: string;
397
- /**
398
- * Contract-level merged extensions (defaults < contract).
399
- * Case-level merged extensions live on _caseSchemas[key].extensions.
400
- */
401
- readonly extensions?: Extensions;
402
- /**
403
- * Per-case metadata for runtime extraction (OpenAPI generation, projection).
404
- * Maps case key → full case metadata including schema and gating fields.
405
- */
406
- readonly _caseSchemas?: Record<string, {
407
- expectStatus?: number;
408
- responseSchema?: SchemaLike<unknown>;
409
- /** Response headers schema (OpenAPI + runtime validation) */
410
- responseHeaders?: SchemaLike<NormalizedHeaders>;
411
- /** Response content-type (default: application/json) */
412
- responseContentType?: string;
413
- /** Single example (OpenAPI docs) */
414
- example?: unknown;
415
- /** Named examples (OpenAPI docs) */
416
- examples?: Record<string, ContractExample<unknown>>;
417
- /** Per-path-param metadata (schema, description, required, deprecated) */
418
- paramSchemas?: Record<string, {
419
- schema?: SchemaLike<unknown>;
420
- description?: string;
421
- required?: boolean;
422
- deprecated?: boolean;
423
- }>;
424
- /** Per-query-param metadata */
425
- querySchemas?: Record<string, {
426
- schema?: SchemaLike<unknown>;
427
- description?: string;
428
- required?: boolean;
429
- deprecated?: boolean;
430
- }>;
431
- description?: string;
432
- deferred?: string;
433
- deprecated?: string;
434
- severity?: CaseSeverity;
435
- lifecycle: CaseLifecycle;
436
- requires?: CaseRequires;
437
- defaultRun?: CaseDefaultRun;
438
- /** Fully merged extensions (defaults < contract < case) */
439
- extensions?: Extensions;
440
- }>;
441
- /**
442
- * Inject all cases as steps into a test builder.
443
- * Usage: test("e2e").use(myContract.asSteps()).step(...).build()
444
- */
445
- asSteps(): <S>(b: import("./index.js").TestBuilder<S>) => import("./index.js").TestBuilder<S>;
446
- /**
447
- * Inject a single case as a step into a test builder.
448
- * Defaults to the first non-deferred case if caseKey is omitted.
449
- */
450
- asStep(caseKey?: string): <S>(b: import("./index.js").TestBuilder<S>) => import("./index.js").TestBuilder<S>;
419
+ export type ExtractedFlowStep = ExtractedContractCallStep | ExtractedComputeStep;
420
+ export interface ExtractedContractCallStep {
421
+ kind: "contract-call";
422
+ name?: string;
423
+ contractId: string;
424
+ caseKey: string;
425
+ protocol: string;
426
+ target: string;
427
+ /** Proxy dry-run output — input mappings. */
428
+ inputs?: FieldMapping[];
429
+ /** Proxy dry-run output — state update mappings. */
430
+ outputs?: FieldMapping[];
431
+ }
432
+ export interface ExtractedComputeStep {
433
+ kind: "compute";
434
+ name?: string;
435
+ /** Top-level state paths read (from Proxy dry-run). */
436
+ reads: string[];
437
+ /** Top-level state keys written (keys of returned object). */
438
+ writes: string[];
451
439
  }
452
440
  /**
453
- * Spec for a single HTTP step in a contract.flow() chain.
454
- *
455
- * Flow steps are verification, not spec: each step has one fixed expected
456
- * outcome (expect), not multiple possible responses.
457
- *
458
- * @template T Response type (inferred from expect.schema)
459
- * @template S Incoming state type from previous step
441
+ * JSON-safe flow projection. Downstream (scanner / MCP / CLI / Cloud) consume
442
+ * this. Produced by `normalizeFlow(runtime)`.
460
443
  */
461
- export interface HttpFlowStepSpec<T = unknown, S = unknown> {
462
- /** HTTP method + path, e.g. "POST /users" or "GET /runs/:runId" */
463
- endpoint: string;
464
- /** Optional description of this step's purpose. */
444
+ export interface ExtractedFlowProjection {
445
+ id: string;
446
+ protocol: "flow";
465
447
  description?: string;
466
- /** HTTP client for this step. Falls back to flow-level default client. */
467
- client?: HttpClient;
468
- /** Fixed expected outcome for this step */
469
- expect: ContractExpect<T>;
470
- /** URL params — static or derived from previous step's state */
471
- params?: Record<string, string> | ((state: S) => Record<string, string>);
472
- /** Query parameters */
473
- query?: Record<string, string> | ((state: S) => Record<string, string>);
474
- /** Request headers — static object or derived from previous step's state */
475
- headers?: Record<string, string> | ((state: S) => Record<string, string>);
476
- /** Request body — static or derived from state */
477
- body?: unknown | ((state: S) => unknown);
478
- /**
479
- * Business logic verification — runs after status and schema validation.
480
- * Receives schema-parsed value or raw JSON.
481
- */
482
- verify?: (ctx: TestContext, res: T) => Promise<void>;
483
- /**
484
- * Extract state from response for the next step.
485
- * Receives the response and current state. Output replaces state.
486
- * If omitted, state passes through unchanged.
487
- */
488
- returns?: (res: T, state: S) => unknown;
448
+ tags?: string[];
449
+ extensions?: Extensions;
450
+ /** Present when flow has a setup callback (state source is dynamic). */
451
+ setupDynamic?: true;
452
+ steps: ExtractedFlowStep[];
489
453
  }
490
454
  /**
491
- * Protocol adapter v2 for contract.register().
492
- * Plugins implement this to add contract.grpc(), contract.ws(), etc.
493
- *
494
- * Breaking change from v1: `metadata()` replaced by `project()`.
495
- * No v1 compat — no third-party plugins exist yet.
455
+ * Builder for `contract.flow(id)`. State chain threads through `.step()` /
456
+ * `.compute()` via TypeScript generics.
496
457
  */
497
- export interface ContractProtocolAdapter<Spec = unknown> {
498
- /** Generate a Test function for a single case */
499
- execute: (ctx: TestContext, caseSpec: unknown, endpointSpec: Spec) => Promise<void>;
458
+ export interface FlowBuilder<State = unknown> {
459
+ readonly __glubean_type: "flow-builder";
460
+ meta(m: Omit<FlowMeta, "id">): FlowBuilder<State>;
500
461
  /**
501
- * Project spec into normalized contract metadata.
502
- * Scanner / CLI / MCP / Cloud consume this.
503
- *
504
- * **Invariant:** `project().cases[].key` must 1:1 match `spec.cases` keys.
505
- * `contract.register()` validates this at registration time:
506
- * - projected key not in spec.cases → hard error
507
- * - spec.cases key not in projection → hard error
508
- * - duplicate key → hard error
462
+ * Flow-level setup the ONLY I/O-capable callback in a flow. May be async,
463
+ * may read ctx, may call external services. Returns the initial state.
509
464
  */
510
- project: (spec: Spec) => ContractProjection;
465
+ setup<NewState>(fn: (ctx: TestContext) => Promise<NewState>): FlowBuilder<NewState>;
511
466
  /**
512
- * Optional: determine where schema validation mounts.
513
- * HTTP "response.body", gRPC "response.message", GraphQL "response.data"
467
+ * Add a contract-call step. `bindings.in` and `bindings.out` MUST be pure
468
+ * lens functions (select / repack only; no I/O, no method calls, no
469
+ * branching). Lens purity is enforced at Proxy dry-run time during
470
+ * projection extraction.
514
471
  */
515
- schemaMount?: (caseSpec: unknown, spec: Spec) => string | undefined;
472
+ step<CaseInputs, CaseOutput, NewState = State>(ref: ContractCaseRef<CaseInputs, CaseOutput>, bindings?: {
473
+ in?: (state: State) => CaseInputs;
474
+ out?: (state: State, response: CaseOutput) => NewState;
475
+ name?: string;
476
+ }): FlowBuilder<NewState>;
516
477
  /**
517
- * Optional: classify failure from events/error.
518
- * Consumed by repair loop and Cloud they don't need to understand protocol details.
478
+ * Add a pure synchronous data-transform step. Accepts any synchronous TS
479
+ * expression (template literals, method calls, .map()). Projection records
480
+ * only read/write dependencies, NOT the formula.
481
+ *
482
+ * Runtime enforcement: throws if `fn` is async or returns a thenable.
519
483
  */
520
- classifyFailure?: (input: {
521
- error?: unknown;
522
- events: Array<{
523
- type: string;
524
- data: Record<string, unknown>;
525
- }>;
526
- }) => FailureClassification | undefined;
527
- }
528
- /**
529
- * Return type of `ContractProtocolAdapter.project()`.
530
- * Aligns with NormalizedContractMeta in scanner.
531
- */
532
- export interface ContractProjection {
533
- protocol: string;
534
- target: string;
535
- description?: string;
536
- feature?: string;
537
- instanceName?: string;
538
- security?: unknown;
539
- schemaMount?: string;
540
- requestSchema?: unknown | null;
541
- cases: Array<{
542
- key: string;
543
- description?: string;
544
- lifecycle: CaseLifecycle;
545
- severity: CaseSeverity;
546
- deferredReason?: string;
547
- deprecatedReason?: string;
548
- requires?: CaseRequires;
549
- defaultRun?: CaseDefaultRun;
550
- schemaMount?: string;
551
- protocolExpect?: Record<string, unknown>;
552
- responseSchema?: unknown | null;
553
- protocolMeta?: Record<string, unknown>;
554
- }>;
555
- protocolMeta?: Record<string, unknown>;
484
+ compute<NewState>(fn: (state: State) => NewState): FlowBuilder<NewState>;
485
+ /**
486
+ * Flow-level teardown — runs in Rule 2 outer-finally, receives last-
487
+ * committed state. If flow.setup threw, teardown does NOT run.
488
+ */
489
+ teardown(fn: (ctx: TestContext, state: State) => Promise<void>): FlowBuilder<State>;
490
+ build(): FlowContract<State>;
556
491
  }
557
492
  /**
558
- * Return value of `contract[protocol]()` extends Test[] with projection carrier.
559
- * Mirrors HttpContract: extends Array<Test> + metadata for scanner extraction.
560
- *
561
- * `_projection` extends ContractProjection with `id` — injected by `contract.register()`
562
- * since the adapter's `project()` doesn't know the user-supplied contract id.
493
+ * Runtime flow contract. Extends Array<Test> so runner iterates directly.
494
+ * The single Test inside orchestrates setup steps → teardown via runFlow.
563
495
  */
564
- export interface ProtocolContract extends Array<Test> {
565
- /** Adapter project() output + injected id. Scanner duck-types this field for extraction. */
566
- readonly _projection: ContractProjection & {
496
+ export interface FlowContract<State = unknown> extends Array<Test> {
497
+ readonly _flow: RuntimeFlowProjection<State> & {
567
498
  id: string;
568
499
  };
500
+ /**
501
+ * Pre-computed JSON-safe extracted projection. Populated by the flow
502
+ * builder via `normalizeFlow(_flow)` so downstream consumers (scanner,
503
+ * CLI, MCP, Cloud) don't need to import the SDK to get field mappings
504
+ * for `.step()` lenses and reads/writes for `.compute()` nodes.
505
+ */
506
+ readonly _extracted: ExtractedFlowProjection;
569
507
  }
570
- /** Contract-specific metadata attached to RegisteredTestMeta. Protocol-agnostic. */
508
+ /**
509
+ * Contract registry metadata attached to tests produced by `contract[protocol]()`.
510
+ * Mirrored by `RegisteredTestMeta.contract` in types.ts.
511
+ */
571
512
  export interface ContractRegistryMeta {
572
- /** Protocol-agnostic target. HTTP: "POST /users", gRPC: "Greeter/SayHello" */
513
+ /** Protocol-agnostic target. HTTP: "POST /users", gRPC: "Greeter/SayHello". */
573
514
  target: string;
574
- /** Protocol identifier */
515
+ /** Protocol identifier. */
575
516
  protocol: string;
576
- /** Case key within the contract */
517
+ /** Case key within the contract. */
577
518
  caseKey: string;
578
- /** Case lifecycle */
579
519
  lifecycle: CaseLifecycle;
580
- /** Case severity */
581
520
  severity: CaseSeverity;
582
- /** Whether response schema is defined */
583
- hasSchema: boolean;
584
- /** Instance name from contract.http.with("name", ...) */
585
521
  instanceName?: string;
586
522
  /**
587
- * Protocol-specific metadata. Core does not read this field's contents.
588
- * HTTP: { security: "bearer", expect: { status: 200 } }
589
- * gRPC: { expect: { code: 0 } }
523
+ * Adapter.describePayload() output protocol-agnostic payload overview.
524
+ * Optional because describePayload is optional.
590
525
  */
591
- protocolMeta?: Record<string, unknown>;
526
+ payloadSummary?: PayloadDescriptor;
527
+ /** Plugin-defined free-form meta; core does not inspect. */
528
+ meta?: unknown;
592
529
  }
593
530
  /**
594
- * @deprecated Use `RegisteredTestMeta` directly it now has an optional `contract` field.
595
- * Kept as an alias for backward compatibility.
531
+ * Flow registry metadata attached to the single Test generated by
532
+ * `contract.flow()`. Mirrored by `RegisteredTestMeta.flow` in types.ts.
596
533
  */
597
- export type ContractCaseMeta = RegisteredTestMeta & {
598
- contract: ContractRegistryMeta;
599
- };
534
+ export interface FlowRegistryMeta {
535
+ id: string;
536
+ description?: string;
537
+ tags?: string[];
538
+ /** Flattened step descriptors (same shape as ExtractedFlowStep). */
539
+ steps: Array<{
540
+ kind: "contract-call" | "compute";
541
+ name?: string;
542
+ contractId?: string;
543
+ caseKey?: string;
544
+ protocol?: string;
545
+ target?: string;
546
+ inputs?: FieldMapping[];
547
+ outputs?: FieldMapping[];
548
+ reads?: string[];
549
+ writes?: string[];
550
+ }>;
551
+ setupDynamic?: true;
552
+ }
600
553
  //# sourceMappingURL=contract-types.d.ts.map