@pipelex/sdk 0.34.0 → 0.35.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.
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import type { MethodFile } from "mthds/protocol";
13
13
  import type { RunErrorReport } from "./error-models.js";
14
+ import type { PipelexValidationResult } from "./models.js";
14
15
  import type { RunStatus } from "./runs.js";
15
16
  export interface UserProfile {
16
17
  email: string;
@@ -19,33 +20,106 @@ export interface UserProfile {
19
20
  /** ISO timestamp the user completed onboarding; absent/null until they do. */
20
21
  onboarding_completed_at?: string | null;
21
22
  }
23
+ /**
24
+ * One published version without its sources — what `listMethodVersions` lists, what a
25
+ * method read carries as `latest_published`, and what a publish answers as `version`.
26
+ */
27
+ export interface MethodVersionSummary {
28
+ /** The version number, from 1, never reused and never renumbered. */
29
+ version: number;
30
+ /**
31
+ * SHA-256 of the canonical form of the version's two file sets (`.mthds` and Python), as 64
32
+ * lowercase hex characters — the same function as the method's `draft_digest`, so equal
33
+ * digests mean the same sources would run.
34
+ */
35
+ source_digest: string;
36
+ /**
37
+ * The runner's `crate.fingerprint` for the bundle (`POST /v1/resolve`), which covers its
38
+ * meaning rather than its bytes and is what generated client code is checked against.
39
+ * `null` when the runner validated the bundle but could not resolve it in memory, as for a
40
+ * bundle that depends on another method by address.
41
+ */
42
+ crate_fingerprint: string | null;
43
+ /** The version of the runner that validated the bundle and computed the fingerprint. */
44
+ runner_version: string | null;
45
+ /** The bundle's top-level `description` at publish time. */
46
+ description: string | null;
47
+ /** ISO-8601 UTC instant of the publish. */
48
+ published_at: string;
49
+ /**
50
+ * The publisher's canonical user id, or `system:publish-initial` for a version the one-time
51
+ * migration wrote for a method saved before versions existed.
52
+ */
53
+ published_by: string;
54
+ }
55
+ /**
56
+ * One published version with its sources — `getMethodVersion`. It carries no `name`, which
57
+ * belongs to the method and changes without a publish, and no `input_data`, which is the
58
+ * editor's form state and not part of what a caller runs.
59
+ */
60
+ export interface MethodVersion extends MethodVersionSummary {
61
+ method_id: string;
62
+ /** The version's `.mthds` source, in the same stored form as `MethodData.mthds`. */
63
+ mthds: string;
64
+ /**
65
+ * The version's custom-PipeFunc Python, as `MethodFile[]`, parsed from the wire string
66
+ * exactly as `MethodData.python` is. Empty array when the version has no custom Python.
67
+ */
68
+ python?: MethodFile[];
69
+ }
70
+ /**
71
+ * A saved method — `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, and the
72
+ * `method` of every publish outcome. Its content fields ARE the draft; the latest
73
+ * published version is summarized in `latest_published`.
74
+ *
75
+ * The publish state a client shows is derived, never stored: never published
76
+ * (`latest_version` is `null`), published with the draft unchanged (`draft_digest ===
77
+ * latest_published.source_digest`), or published with the draft ahead (they differ).
78
+ */
22
79
  export interface MethodData {
23
80
  method_id: string;
24
81
  org_id: string;
25
82
  created_by_user_id: string;
26
83
  name: string;
27
- /** The `.mthds` bundle source. */
84
+ /** The draft's `.mthds` bundle source. */
28
85
  mthds: string;
29
86
  /**
30
- * Custom-PipeFunc Python sources, as `MethodFile[]` (`{ name, content }`). On
87
+ * The draft's custom-PipeFunc Python sources, as `MethodFile[]` (`{ name, content }`). On
31
88
  * the wire this is the serialized `[{ name, content }]` catalog string (empty
32
89
  * `""` when the method has no custom Python); the client (de)serializes it via
33
90
  * `mthds/protocol`'s `parseMethodFiles`/`serializeMethodFiles`, so callers work
34
91
  * with the typed array. Empty array when the method has no custom Python.
35
- * Returned on both get and list.
36
92
  */
37
93
  python?: MethodFile[];
94
+ /** The editor's form inputs, saved with the draft and outside both digests. */
38
95
  input_data?: Record<string, unknown> | null;
39
96
  /** Legacy persisted output spec; optional. */
40
97
  pipe_output?: Record<string, unknown> | null;
41
98
  /**
42
- * Derived from the bundle's top-level `description`. Read-side only: the
43
- * server recomputes it from the bundle on every save, so it never appears on
99
+ * Derived from the draft's top-level `description`. Read-side only: the
100
+ * server recomputes it from the draft on every draft write, so it never appears on
44
101
  * the write contract.
45
102
  */
46
103
  description?: string | null;
47
104
  created_at: string;
105
+ /**
106
+ * **The draft's token.** It moves on every draft write and on nothing else: creation sets
107
+ * it, and neither a rename nor a publish moves it. Echo it verbatim — the platform compares
108
+ * the strings — as `expected_updated_at` on the next `writeDraft`, or as
109
+ * `expected_draft_updated_at` on `publishMethod`, so neither ever overwrites or publishes a
110
+ * draft the caller has not seen.
111
+ */
48
112
  updated_at: string;
113
+ /**
114
+ * The draft's digest: SHA-256 of the canonical form of its two file sets, 64 lowercase hex
115
+ * characters. `input_data` is outside it, so saving only the form inputs moves the token and
116
+ * leaves the digest as it was.
117
+ */
118
+ draft_digest: string;
119
+ /** The number of the latest published version; `null` when the method was never published. */
120
+ latest_version: number | null;
121
+ /** The latest published version's summary; `null` when the method was never published. */
122
+ latest_published: MethodVersionSummary | null;
49
123
  }
50
124
  /** Where a method is in the erasure cascade; absent on a normal method. */
51
125
  export type MethodDeletionState = "pending" | "in_progress" | "failed";
@@ -113,20 +187,124 @@ export interface MethodPage {
113
187
  items: MethodSummary[];
114
188
  nextCursor: string | null;
115
189
  }
116
- /** The create/update payload — a rename is a `PUT` with a changed `name`. */
190
+ /**
191
+ * The create payload — `createMethod`. A new method holds this as its draft and has no
192
+ * version yet. Its draft is then written with `writeDraft` and its name changed with
193
+ * `renameMethod`.
194
+ */
117
195
  export interface MethodWriteInput {
118
196
  name: string;
119
197
  mthds: string;
120
198
  /**
121
- * Custom-PipeFunc Python as `MethodFile[]`. Three-way on a `PUT`: **omit**
122
- * (`undefined`) preserves the stored Python (the client sends nothing, the
123
- * server treats absent as "not sent"); an **empty array** `[]` clears it (the
124
- * client serializes to `""`); a **non-empty array** replaces it. It is a
125
- * replace, not a merge — send the full set on a save that intends to change it.
199
+ * Custom-PipeFunc Python as `MethodFile[]`, serialized to the catalog string on the wire;
200
+ * omitted or an empty array, the method has none.
126
201
  */
127
202
  python?: MethodFile[];
128
203
  input_data?: Record<string, unknown> | null;
129
204
  }
205
+ /**
206
+ * The draft write — `writeDraft`, `PUT /v1/methods/{id}/draft`. It never validates: a draft
207
+ * may be invalid, and an autosave of work in progress often is.
208
+ */
209
+ export interface MethodDraftInput {
210
+ /** The draft's `.mthds` files, in the stored serialized form. Required. */
211
+ mthds: string;
212
+ /**
213
+ * Custom-PipeFunc Python as `MethodFile[]`. Three-way: **omit** (`undefined`) keeps the
214
+ * stored Python (the client sends nothing); an **empty array** `[]` clears it (the client
215
+ * serializes it to `""`); a **non-empty array** replaces it. It is a replace, not a merge —
216
+ * send the full set on a write that intends to change it.
217
+ */
218
+ python?: MethodFile[];
219
+ /**
220
+ * The editor's form inputs. Three-way too: **omit** keeps the stored inputs, an explicit
221
+ * `null` clears them, and any other value replaces them.
222
+ */
223
+ input_data?: Record<string, unknown> | null;
224
+ /**
225
+ * The draft token the caller last saw — the `updated_at` of the method it last read or
226
+ * wrote, echoed verbatim. With it the write is a compare-and-swap: a draft that moved since
227
+ * is refused with a `409` whose `code` is `method_update_conflict`, and nothing is written.
228
+ * Omitted, the write is last-writer-wins.
229
+ */
230
+ expected_updated_at?: string;
231
+ }
232
+ /** The rename — `renameMethod`, `PATCH /v1/methods/{id}`. It moves no token. */
233
+ export interface MethodRenameInput {
234
+ /** The method's new name, non-empty. */
235
+ name: string;
236
+ }
237
+ /** The publish — `publishMethod`, `POST /v1/methods/{id}/publish`. */
238
+ export interface MethodPublishInput {
239
+ /**
240
+ * The draft token the caller last saw (`MethodData.updated_at`), echoed verbatim. Required: a
241
+ * publish never takes a draft its caller has not seen, so a draft that moved since is
242
+ * refused with a `409` whose `code` is `method_update_conflict`, and nothing is published.
243
+ */
244
+ expected_draft_updated_at: string;
245
+ }
246
+ /** What a publish did with the draft — the discriminant of `MethodPublishResult`. */
247
+ export type MethodPublishOutcome = "published" | "unchanged" | "refused";
248
+ /**
249
+ * Why a draft was not published: `invalid`, it does not validate; `not_runnable`, it validates
250
+ * with pending signatures, so it is valid but does not run yet.
251
+ */
252
+ export type MethodPublishRefusalReason = "invalid" | "not_runnable";
253
+ /** A publish that wrote a new version. */
254
+ export interface MethodPublished {
255
+ outcome: "published";
256
+ /** The new version's summary. */
257
+ version: MethodVersionSummary;
258
+ method: MethodData;
259
+ }
260
+ /**
261
+ * A publish with nothing to publish: the draft's digest equals the latest version's, so no
262
+ * runner was asked. Also the answer to the loser of two concurrent publishes of the same draft.
263
+ */
264
+ export interface MethodPublishUnchanged {
265
+ outcome: "unchanged";
266
+ /** The existing latest version's summary. */
267
+ version: MethodVersionSummary;
268
+ method: MethodData;
269
+ }
270
+ /** A publish the draft's content refused: nothing was published. */
271
+ export interface MethodPublishRefused {
272
+ outcome: "refused";
273
+ reason: MethodPublishRefusalReason;
274
+ /** Says why; for `not_runnable`, that the draft "is valid but does not run yet". */
275
+ message: string;
276
+ /**
277
+ * The runner's `POST /v1/validate` answer for the draft, verbatim: an invalid arm whose
278
+ * `validation_errors` say what to fix, or, for `not_runnable`, the valid arm.
279
+ */
280
+ validation: PipelexValidationResult;
281
+ method: MethodData;
282
+ }
283
+ /**
284
+ * The answer of `publishMethod`, a `200` discriminated on `outcome` because each arm is a
285
+ * verdict about the draft's content. Branch on `outcome`, never on the status: a stale token,
286
+ * a method being deleted, a draft with no `.mthds` file or an unreachable runner are thrown as
287
+ * `ApiResponseError` instead, since they produce no verdict about the content.
288
+ */
289
+ export type MethodPublishResult = MethodPublished | MethodPublishUnchanged | MethodPublishRefused;
290
+ /** Query for one page of a method's versions. */
291
+ export interface ListMethodVersionsQuery {
292
+ /** Page size. The API defaults to 20 and caps at 100. */
293
+ limit?: number;
294
+ /** Opaque `nextCursor` from the previous page. */
295
+ cursor?: string;
296
+ }
297
+ /**
298
+ * One page of a method's published versions, newest first, without their sources.
299
+ *
300
+ * `nextCursor` is opaque — pass it straight back to `listMethodVersions` to continue; `null`
301
+ * means this was the last page. Versions are read whole on the server, so a page may be short
302
+ * while `nextCursor` is set. A method never published answers an empty page.
303
+ */
304
+ export interface MethodVersionPage {
305
+ items: MethodVersionSummary[];
306
+ nextCursor: string | null;
307
+ }
130
308
  export interface Membership {
131
309
  org_id: string;
132
310
  /** Null for the implicit personal org (no backing WorkOS organization). */
@@ -297,6 +475,20 @@ export interface PipelineRun {
297
475
  * whose completion callback carried one. This is how a consumer tells the user
298
476
  * WHY a run failed rather than showing a generic message. */
299
477
  error?: RunErrorReport | null;
478
+ /**
479
+ * Which version of its method the run ran: the version number for a run addressed by a
480
+ * bare id (the latest published version) or by `mt_…@<n>`, `"draft"` for one addressed by
481
+ * `mt_…@draft`, and `null` for a run of an inline source. Absent on a run recorded by a
482
+ * platform that did not record it yet.
483
+ */
484
+ method_version?: number | "draft" | null;
485
+ /**
486
+ * The digest of the files the run ran, in the canonical form of `MethodData.draft_digest`, so
487
+ * an inline run identical to a version records that version's `source_digest`. `null` when
488
+ * the platform never held the files, as on a `method_ref` run; absent on a run recorded by a
489
+ * platform that did not record it yet.
490
+ */
491
+ source_digest?: string | null;
300
492
  }
301
493
  /**
302
494
  * One row of a method's run history — an item of `listRuns`' page and of `iterateRuns`.
@@ -319,6 +511,14 @@ export interface RunHistoryItem {
319
511
  /** The runner's stored error report — present only on a failed run that recorded one,
320
512
  * so opening it from history shows why without another read. */
321
513
  error?: RunErrorReport | null;
514
+ /**
515
+ * Which version of the method the run ran — a number, `"draft"`, or `null` for an inline
516
+ * source (see `PipelineRun.method_version`). The history lists the runs of every version
517
+ * and of the draft together, so this is how a row says which one it was.
518
+ */
519
+ method_version?: number | "draft" | null;
520
+ /** The digest of the files the run ran (see `PipelineRun.source_digest`). */
521
+ source_digest?: string | null;
322
522
  }
323
523
  /**
324
524
  * A single run, whole — `GET /v1/runs/{id}`.
package/dist/runs.d.ts CHANGED
@@ -53,6 +53,19 @@ export interface RunPublic {
53
53
  * which is where `getRunResult`'s failed arm and `RunFailedError` take it from.
54
54
  */
55
55
  error?: RunErrorReport | null;
56
+ /**
57
+ * Which version of its stored method the run ran: the version number for a run addressed by
58
+ * a bare `method_id` (the latest published version) or by `mt_…@<n>`, `"draft"` for one
59
+ * addressed by `mt_…@draft`, and `null` for a run of an inline source. A hosted extension,
60
+ * absent on a server that does not record it.
61
+ */
62
+ method_version?: number | "draft" | null;
63
+ /**
64
+ * The digest of the files the run ran, in the canonical form of `MethodData.draft_digest`;
65
+ * `null` when the platform never held the files, as on a `method_ref` run. A hosted
66
+ * extension, absent on a server that does not record it.
67
+ */
68
+ source_digest?: string | null;
56
69
  /** Server-specific response fields (defined by the server you call). */
57
70
  [extension: string]: unknown;
58
71
  }
package/dist/runs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"runs.js","sourceRoot":"","sources":["../src/runs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runs.js","sourceRoot":"","sources":["../src/runs.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAyCjD,MAAM,qBAAqB,GAA2B,IAAI,GAAG,CAAY;IACvE,WAAW;IACX,QAAQ;IACR,WAAW;IACX,YAAY;IACZ,WAAW;CACZ,CAAC,CAAC;AAEH,6EAA6E;AAC7E,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,OAAO,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,kBAAkB,CAAC,MAAiB;IAClD,OAAO,MAAM,KAAK,WAAW,CAAC;AAChC,CAAC;AAgHD,oEAAoE;AAEpE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,YAAY;IACZ,mBAAmB;IACnB,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,gBAAgB;IAChB,eAAe;CACP,CAAC;AAKX;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAAwC;IAC9E,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO;IACpC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,UAAU,CAClB,wCAAwC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB;YACzF,iBAAiB,CACpB,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAsB,oBAAoB,CAAC;IACtD,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAClE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,UAAU,CAClB,8BAA8B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB;YACvE,GAAG,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACxC,CAAC;IACJ,CAAC;AACH,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,0BAA0B,CACxC,SAAmD;IAEnD,OAAO,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AACrE,CAAC;AAsND,uEAAuE;AAEvE,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAC9C,MAAM,CAAC,MAAM,uBAAuB,GAAG,SAAS,CAAC,CAAC,0DAA0D;AAQ5G;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAgC,EAAE;IAClE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAC1C,IAAI,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,UAAU,CAClB,qDAAqD,MAAM,CAAC,UAAU,CAAC,OAAO;YAC5E,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,CAC1B,CAAC;IACJ,CAAC;IACD,uBAAuB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,SAA0B,EAC1B,KAAa,EACb,UAAgC,EAAE;IAElC,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC3B,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,wBAAwB,CAAC;IAClE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,uBAAuB,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,SAAS,CAAC;QACR,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAE/B,8EAA8E;QAC9E,0EAA0E;QAC1E,qDAAqD;QACrD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACzC,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,eAAe,CACvB,OAAO,KAAK,0CAA0C,SAAS,KAAK,EACpE,KAAK,EACL,SAAS,CACV,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,SAAS,CAC3B,KAAK,EACL,OAAO,CAAC,SAAS,KAAK,SAAS;YAC7B,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;YAC5B,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAC7D,CAAC;QAEF,IAAI,KAAK,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC,MAAM,CAAC;QACtB,CAAC;QACD,IAAI,KAAK,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QACvF,CAAC;QAED,OAAO,IAAI,CAAC,CAAC;QACb,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QAEzC,MAAM,OAAO,GAAG,KAAK,CAAC,mBAAmB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACzF,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC,CAAC;QAC9E,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,IAAI,MAAM,EAAE,OAAO;QAAE,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,UAAU,CAAC,MAAoB;IACtC,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,CAAC;IAC9B,IAAI,MAAM,YAAY,KAAK;QAAE,OAAO,MAAM,CAAC;IAC3C,OAAO,IAAI,YAAY,CAAC,2BAA2B,EAAE,YAAY,CAAC,CAAC;AACrE,CAAC;AAED,iFAAiF;AACjF,SAAS,KAAK,CAAC,EAAU,EAAE,MAAoB;IAC7C,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QACZ,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7B,CAAC,CAAC;QACF,oFAAoF;QACpF,iEAAiE;QACjE,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE;YACH,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACZ,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,kBAAkB,CAAC,CACjC,CAAC;QACF,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;gBAC3B,OAAO;YACT,CAAC;YACD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/version.d.ts CHANGED
@@ -3,4 +3,4 @@
3
3
  * `User-Agent` without importing the public barrel. Kept in sync with
4
4
  * `package.json` — enforced by `tests/index.test.ts`.
5
5
  */
6
- export declare const SDK_VERSION = "0.34.0";
6
+ export declare const SDK_VERSION = "0.35.0";
package/dist/version.js CHANGED
@@ -3,5 +3,5 @@
3
3
  * `User-Agent` without importing the public barrel. Kept in sync with
4
4
  * `package.json` — enforced by `tests/index.test.ts`.
5
5
  */
6
- export const SDK_VERSION = "0.34.0";
6
+ export const SDK_VERSION = "0.35.0";
7
7
  //# sourceMappingURL=version.js.map
@@ -6,11 +6,13 @@
6
6
 
7
7
  - run methods against the hosted runner (`execute` / `start`, run lifecycle, results),
8
8
  - validate `.mthds` content (`validate`, returning the Pipelex `PipelexValidationResult` envelope),
9
- - read the model deck and version handshake (`models` / `version`),
9
+ - read the model deck and version handshake (`models` / `version`) and check one model reference against the deck (`checkModelReference`),
10
10
  - and reach the Pipelex product surface (user profile, methods catalog, organizations, billing, API keys, onboarding, storage).
11
11
 
12
12
  `models` returns the deck as the runner sent it, with no runtime check, and types each entry's `type` as `ModelCategory | (string & {}) | null`: a category `mthds` defines, `judgment` among them, or the raw value of one this version does not define, which the MTHDS Protocol requires a client reading a model list to keep rather than fail on.
13
13
 
14
+ `checkModelReference` asks the runner whether one reference, written as a method's `model` field writes it, resolves, as what kind and to which model (`GET /v1/models/check`). The runner answers from the parser and the deck lookups a validation runs, so the SDK keeps no parser of references of its own and checks nothing before sending: the reference travels percent-encoded, and a reference the runner cannot read is its `422`.
15
+
14
16
  ## Dependency direction (one-way)
15
17
 
16
18
  ```
@@ -80,6 +82,7 @@ A run request carries its method inline exactly one of three ways: `mthds_conten
80
82
  - **Alone it is a run source; alongside an inline source it is linkage.** With no inline source the platform resolves and runs the stored method. With one, the inline source is what RUNS (precedence) and the id is recorded as run-history linkage on the Run row — the index key `GET /v1/runs?method_id=` queries. Rejecting the combination is not an option: it once orphaned every unsaved-bundle run from its method. The client therefore counts `method_id` towards its "something to run" precondition, and never treats it as an exclusivity violation.
81
83
  - **It rides the blocking fallback too.** `startAndWaitForResult` degrades to `POST /v1/execute` against a runner with no run store, and the selector goes with it — so a bare `pipelex-api` answers a `422` that names the key (its Rule 4 diagnosis: "a hosted-only selector must be sent to the hosted API") instead of the client silently dropping it and running something else.
82
84
  - **An empty string is treated as absent.** `method_id: ""` selects no method and links nothing, so it is not sent and does not satisfy the precondition.
85
+ - **It may name a version.** A bare `mt_…` runs the method's latest published version, `mt_…@<n>` the fixed version `n` and `mt_…@draft` its draft; the suffix is part of the string and passes through untouched, on the run routes and the tooling routes alike, since the platform parses it at its edge. On the linkage form, beside an inline source, the id must be bare: the inline source is what runs, so a suffix there would claim a version that did not run, and the platform refuses it with a `422`. The start ack and the execute response carry `method_version`, the version a `method_id` run runs, so a caller learns which version a bare id ran from the answer that started it. `parseMethodSelector` splits a selector into its bare id and version locally, for a caller that reads the method by its bare id.
83
86
 
84
87
  ## Module layout
85
88
 
@@ -112,6 +115,7 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
112
115
  - **Method selectors on the run methods:** `execute` / `start` / `startAndWaitForResult` take the Pipelex API's `method_ref` (an address the runner resolves; provenance comes back typed as `method_provenance`) and the platform's `method_id` as **typed named options** (`PipelexRunOptions` / `PipelexStartOptions`), never `extra` entries. See [Pipelex API run extensions](#pipelex-api-run-extensions-method_ref) and [hosted run extensions](#hosted-run-extensions-method_id) above.
113
116
  - **Method selectors on `validate`:** the first argument is inline contents (`string[]`) or a `ValidateMethodSelector` — `{ method_ref }` (runner-resolved by address) or `{ method_id }` (hosted, platform-resolved) — under the strict tooling XOR: exactly one selector, no linkage exception, `mthds_sources` legal only beside inline contents. A selector-resolution failure is a non-2xx `ApiResponseError`, never an `is_valid: false` verdict.
114
117
  - **Crate routes (`/v1/resolve`, `/v1/codegen`, `/v1/pipe-io`):** `resolve` emits the **normalized library crate** (the MTHDS standard's Library Crate Format, fingerprint riding inside the payload); `codegen` projects that crate through two explicit axes — `kind` (`types`) × `target` (`ts-zod` / `python-pydantic` / `python-structures`) — into stamped artifacts plus their `codegen.lock`. All three share one closure envelope (`files: [{content, source?}]` XOR a `method_ref`, the address form server-resolved and the registry form a `501`) and return a **discriminated 200 verdict** (`is_valid`, with `CrateInvalidReport` as the shared invalid arm) rather than a bare payload, the same discipline as `validate`. They also take the hosted `method_id` (`PipelexHostedToolingExtensions`) as a third closure form under the strict three-way XOR — a pass-through the platform resolves, meaningless against a bare runner. Writing the artifacts and lock verbatim reproduces a local `pipelex codegen types` run byte-for-byte, so the offline `pipelex codegen check` passes — the SDK stays transport-only and does not write files. Verifying such a tree afterwards is `runCodegenCheck`, a standalone pure helper rather than a client method, because it needs no server at all. `pipeIo` derives a method's three I/O artifacts — the standard's `PipeIOContracts`, `InputForm` and `OutputForm`, imported from `mthds/protocol` — for the pipe the route selects (the request's qualified `pipe_ref`, else the method's own entry pipe) or for every pipe under `all_pipes`, with the runnability facts beside them and the closure's files echoed on `include_files`, and it runs no dry run; a selection it refuses is a `422` typed `EntryPipeNotFoundError` or `EntryPipeAmbiguousError`. See [crate-routes.md](./crate-routes.md).
118
+ - **Model reference check (`/v1/models/check`):** `checkModelReference(reference, category?)`, a Pipelex API extension served by any `pipelex-api` runner from pipelex 0.78.0 and on the hosted API, whose platform relays it byte-identically. It is a **diagnostic endpoint** like `validate`: a reference that resolves nowhere is a `200` verdict with `resolution: "not_found"` and the names the caller may have meant (`suggestions`, `other_kinds`, `other_categories`), never a thrown error. Only a request that cannot produce a verdict throws `ApiResponseError`, a `422` typed by its `errorType`: `InvalidModelReference` for a reference that is blank, a sigil or a namespace alone, or past the runner's length limit, `InvalidModelCategory` for an unknown `type`, `ValidationError` for a missing or repeated parameter. The SDK validates none of it, so the runner's rule is the only one. `ModelReferenceVerdict` is a union of one arm per reference kind, discriminated on the wire's own `kind` (`PresetReferenceVerdict`, `AliasReferenceVerdict`, `WaterfallReferenceVerdict`, `HandleReferenceVerdict`): a `matches` entry carries no kind of its own, but every entry of one verdict shares the verdict's, so narrowing on `kind` types `matches` as `PresetMatch[]`, `AliasMatch[]`, `WaterfallMatch[]` or `HandleMatch[]` without a field the wire does not carry. `ModelCheckCategory` is the protocol's model categories plus `doc_gen`, the family of `PipeDocGen`, which the protocol and so `models` leave out but a method's `model` field can name. `resolution` is typed open, `ModelReferenceResolution | (string & {})`, under the spec's reader rule that a client reading a value it does not know treats the reference as unresolved, and so is every category a verdict names, `ModelCheckCategory | (string & {})` as `mthds` types `ModelInfo.type`, since a runner of a later version may add one; the `category` a request names stays closed. A match's `resolves_to` is `null` when the reference resolves by name but a run through it would reach no model the runner can call, which a consumer shows as a warning rather than as an unknown name.
115
119
  - **Tools (`/v1/lint`, `/v1/format`):** `lint`, `format` — single-file static diagnostics and canonical formatting, served by any pipelex-api runner (no inference, no bundle load). Both are **diagnostic endpoints** like `validate`: a malformed `.mthds` file is a produced verdict on a **200** carrying `diagnostics[]`, never a thrown error — `format` additionally echoes the content back unchanged with `changed: false`. Only a no-verdict condition is non-2xx: a malformed body, **malformed formatter `options`** (e.g. a non-numeric `column_width` ⇒ 422), auth, or a server fault, all mapped to the typed `ApiResponseError`. `lint` is the cheap static check (syntax / schema / semantic); it does not replace `validate`, which loads the bundle, resolves across files, and dry-runs the pipes.
116
120
  - **Durable run lifecycle (hosted extension — NOT protocol):** `getRunStatus`, `getRunResult` (with an optional `artifacts` selection sent as `?artifacts=`, see [run-results.md](./run-results.md#reading-only-some-artifacts--artifacts)), `waitForResult`, `startAndWaitForResult` (handshakes `/v1/version`, takes the durable start+poll path on a hosted deployment, and self-heals to the blocking `execute` against a bare runner). The handshake's outcome is cached for the client's lifetime, under the rule the Python SDK follows too: an answer that is not a usable version assumes hosted and lets the start surface the real error, while no answer at all throws its `ApiUnreachableError` at once, caches nothing and sends no start. An answer is anything the server sent back: an error status, a body that is not JSON, not UTF-8 or no version, and a body that arrived but could not be decoded, such as a broken gzip stream, which fetch fails with the decompressor's code (`Z_DATA_ERROR` and the other zlib codes, brotli's `ERR__ERROR_…`, zstd's `ZSTD_error_…`). A run that ended without completing answers the results read with a `409` whose `run_status` and `error` members carry its status and its stored error report; `getRunResult` turns it into the `failed` arm (`status`, `message` from `detail`, `error`) and the waiting calls on the durable path into a `RunFailedError` carrying the same (the blocking fallback's failure is the runner's `ApiResponseError`), the status read from the member, or from the `detail` sentence on a platform that does not send it yet. The status read serves the same report as `RunRead.error`. See [errors.md](./errors.md#a-failed-run--runerrorreport). Reading what a completed run consumed is `summarizeUsage(results)`, a standalone pure helper rather than a client method, because it folds the usage pair the results already carry and needs no server. See [run-usage.md](./run-usage.md#summarizing-a-run--summarizeusage).
117
121
  - **Health:** `health` (origin-level `/health`), on the same transport as every route: an unreachable origin is an `ApiUnreachableError` and a non-2xx answer an `ApiResponseError` naming `/health`, whose verdict the fallback reads from the status. A 2xx whose body is not a JSON object is an `ApiResponseError` too, as on every protocol, extension and run-lifecycle route.
@@ -121,13 +125,13 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
121
125
  The hosted catalog/account routes the webapp drives. All ride the same `{base}/v1/*` + `Authorization: Bearer` + org-from-JWT contract as the protocol routes, go through the shared `requestProduct<T>` helper (30s management timeout, empty-body tolerant), and map a non-2xx RFC 9457 `problem+json` to a typed `ApiResponseError`. Every route parses a 2xx body through one private helper, so an answer that is not JSON (a gateway's HTML page, an empty body where one was due) is an `ApiResponseError` carrying its status and raw text, `runtime` and not retryable, rather than a bare `SyntaxError`. A product route whose answer the SDK or its caller reads before anything else (`listMethods`, `listRuns`, `getRunDetail`, `upload`, `requestUploadGrant`, `resolveStorageUrls`, and the methods routes answering one stored method) goes through `requestProductObject` or a helper built on the same read instead, which also refuses an empty body and JSON that is not an object; the two paged routes then refuse, the same way, through one page reader, a page whose `items` is not an array or whose `next_cursor` is neither a string nor `null`, `upload` an answer with no non-empty string `uri`, and the methods routes a stored `mthds` field that is neither a string nor absent and a stored `python` field that is not the serialized file list. Consumers branch on **`type` and `errorDomain`**, as the hosted-envelope spec (`conformance/specs/pipelex-hosted-envelope.md`, in the `conformance` repository) says, never on the HTTP status. The platform's `type` is `https://pipelex.com/errors/<code>`, one-to-one with its native `code` (`conflict`, `not_found`, `pipelex_api_key_limit_reached`, …), which stays on the error as the finer surface-specific code. The platform does not emit `error_domain` or `retryable` on its own refusals yet, so their `errorDomain` and `retryable` are the SDK's fallback, read from the status; every error carries both, decided. See [errors.md](./errors.md).
122
126
 
123
127
  - **User profile:** `getMe` (`GET /v1/me`).
124
- - **Methods catalog:** `listMethods`, `getMethod`, `createMethod`, `updateMethod` (rename = changed `name`), `deleteMethod`. **`deleteMethod` is asynchronous and its return type says so:** `DELETE /v1/methods/{id}` answers `202` with a `MethodDeletionAccepted` (`method_id`, `deletion_state`, `deletion_job_id`) the moment the platform has claimed the method and terminated its in-flight workflows — the rest of the cascade (runs, events, S3 objects) is enqueued. A resolved promise means "accepted", never "gone"; completion is the row disappearing from `listMethods`, not any field of that body. The claim is a conditional write, so a double-clicked delete is a `409 conflict` rather than a second cascade over the same runs, and an unknown or foreign-org id is a `404`. (This doc previously stated the SDK had no `delete` because the platform had no such route — the route has always existed; the claim was simply false.) `listMethods` returns **one page**, not the whole catalog — a `MethodPage` (`{items, nextCursor}`) served from a narrow index projection, so its cost is the page size rather than the size of the catalog. It used to return every method with its full `.mthds` bundle attached, which hit DynamoDB's 1 MB page cap at a couple hundred methods and came back truncated with no error and no flag, so methods silently vanished from the UI. Rows are therefore `MethodSummary`, **not** `MethodData`: no `mthds`, no `python`, no `updated_at`, because none of them is in the projection and putting `mthds` back is what restored the truncation. Reach for `getMethod(id)` when you need a method's source. `query.q` is applied server-side across the whole catalog, not over the page — filtering one page client-side would be searching 50 rows of 10,000 and calling it a search. `iterateMethods` is the async iterator that follows the cursor for callers who genuinely want everything (`for await (const m of client.iterateMethods())`); unlike `iterateRuns` it must keep going **past empty pages**, because `q` is a post-read filter over a bounded index slice per request, so `{items: [], nextCursor: "…"}` means "nothing matched in the slice I just read, keep going" — the difference is the server, not the client. A server that keeps minting fresh cursors forever stops it at a page ceiling far past any real catalog with a `PagingNotTerminatingError` carrying `pageLimit`, never a silently truncated stream. It gives up only when the server stops advancing its cursor, and **throws** rather than returning a partial answer; like `iterateRuns` it is deliberately not a `listAllMethods(): Promise<MethodSummary[]>`, because an all-at-once helper needs a page cap and a cap means silently returning a truncated list. `MethodSummary.deletion_state` rides the list rows: a method mid-erasure stays listed so the UI can render it as "Deleting…", while `getMethod` refuses it with a 409. Full read responses (`MethodData`, from `getMethod`) carry `org_id`, `created_by_user_id`, and a server-derived `description` (parsed read-side from the bundle's top-level `description`) on top of the write contract's `name` / `mthds` / `input_data`. `getMethodClosure` is a client-side semantic layer over `getMethod` (not a route) that parses the stored method's polymorphic `mthds` source into a runnable, provenance-labelled closure — see [input-preparation.md](./input-preparation.md#getmethodclosure--the-explicit-expansion-utility).
128
+ - **Methods catalog:** `listMethods`, `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, `publishMethod`, `listMethodVersions`, `getMethodVersion`, `deleteMethod`. **A saved method has a draft and immutable published versions.** Its content fields (`mthds`, `python`, `input_data`) are its draft, which `writeDraft` (`PUT /v1/methods/{id}/draft`) replaces without validating it; `updated_at` is the draft's token, which moves on every draft write and nothing else, and `draft_digest` the SHA-256 of its two file sets. A draft write may carry `expected_updated_at`, the token the caller last saw, and is then a compare-and-swap: a draft that moved since is a `409` `method_update_conflict` and nothing is written. `renameMethod` (`PATCH /v1/methods/{id}`) changes the name alone and moves no token. `publishMethod` (`POST /v1/methods/{id}/publish`) takes the required `expected_draft_updated_at` and answers a `MethodPublishResult` discriminated on `outcome` — `published` with the new version, `unchanged` with the existing latest version when the draft's digest equals it, `refused` with a `reason` (`invalid` or `not_runnable`), a `message` and the runner's `validation` verdict — every arm carrying the `method`; the SDK reads the nested method as `getMethod` does, and an answer whose `outcome` is none of these is an answer it cannot read. A publish that produced no verdict is thrown rather than answered: only a draft that differs from the latest version reaches the runner, and a runner that cannot be reached, answers unusably or does not finish within the platform's deadline is a `502` or `503` with nothing written, so a retry is safe, and a retry of a publish that landed while its answer was lost answers `unchanged` with the version it wrote. A rename can be a `413` `payload_too_large` for a name so long it would leave the method too large to publish. A version is numbered from 1, never reused, and holds the draft's files as they stood, its `source_digest`, its `crate_fingerprint` (`null` when the runner could not resolve the bundle in memory), the runner's version, and when and by whom it was published; `listMethodVersions` pages the summaries newest first (`MethodVersionPage`, `{items, nextCursor}`), and `getMethodVersion` reads one with its sources, its `python` parsed as a method's is. A method read carries `latest_version` and `latest_published`, both `null` until the first publish, so the publish state is derived: never published, draft unchanged (`draft_digest === latest_published.source_digest`), or draft ahead. The method routes take a bare id and address the method itself, never one of its versions; the platform does not parse a suffix there, so `mt_x@3` would answer a `404` that reads as a method that does not exist, and each route refuses a suffixed id with `RequestArgumentError` before anything is sent, naming `parseMethodSelector` and `getMethodVersion`. The whole-method `updateMethod` is gone, replaced by the draft write and the rename. **`deleteMethod` is asynchronous and its return type says so:** `DELETE /v1/methods/{id}` answers `202` with a `MethodDeletionAccepted` (`method_id`, `deletion_state`, `deletion_job_id`) the moment the platform has claimed the method and terminated its in-flight workflows — the rest of the cascade (runs, events, S3 objects) is enqueued. A resolved promise means "accepted", never "gone"; completion is the row disappearing from `listMethods`, not any field of that body. The claim is a conditional write, so a double-clicked delete is a `409 method_being_deleted` rather than a second cascade over the same runs, and an unknown or foreign-org id is a `404`. The erasure deletes the method's versions with the rest. (This doc previously stated the SDK had no `delete` because the platform had no such route — the route has always existed; the claim was simply false.) `listMethods` returns **one page**, not the whole catalog — a `MethodPage` (`{items, nextCursor}`) served from a narrow index projection, so its cost is the page size rather than the size of the catalog. It used to return every method with its full `.mthds` bundle attached, which hit DynamoDB's 1 MB page cap at a couple hundred methods and came back truncated with no error and no flag, so methods silently vanished from the UI. Rows are therefore `MethodSummary`, **not** `MethodData`: no `mthds`, no `python`, no `updated_at`, because none of them is in the projection and putting `mthds` back is what restored the truncation. Reach for `getMethod(id)` when you need a method's source. `query.q` is applied server-side across the whole catalog, not over the page — filtering one page client-side would be searching 50 rows of 10,000 and calling it a search. `iterateMethods` is the async iterator that follows the cursor for callers who genuinely want everything (`for await (const m of client.iterateMethods())`); unlike `iterateRuns` it must keep going **past empty pages**, because `q` is a post-read filter over a bounded index slice per request, so `{items: [], nextCursor: "…"}` means "nothing matched in the slice I just read, keep going" — the difference is the server, not the client. A server that keeps minting fresh cursors forever stops it at a page ceiling far past any real catalog with a `PagingNotTerminatingError` carrying `pageLimit`, never a silently truncated stream. It gives up only when the server stops advancing its cursor, and **throws** rather than returning a partial answer; like `iterateRuns` it is deliberately not a `listAllMethods(): Promise<MethodSummary[]>`, because an all-at-once helper needs a page cap and a cap means silently returning a truncated list. `MethodSummary.deletion_state` rides the list rows: a method mid-erasure stays listed so the UI can render it as "Deleting…", while `getMethod` refuses it with a 409. Full read responses (`MethodData`, from `getMethod`) carry `org_id`, `created_by_user_id`, a server-derived `description` (recomputed from the draft's top-level `description` on every draft write), the draft token and digest and the latest version's summary on top of the write contract's `name` / `mthds` / `input_data`. `getMethodClosure` is a client-side semantic layer over `getMethod` (not a route) that parses the draft's polymorphic `mthds` source into a runnable, provenance-labelled closure — the draft, not what a bare id runs — see [input-preparation.md](./input-preparation.md#getmethodclosure--the-explicit-expansion-utility).
125
129
  - **Organizations:** `listMemberships`, `createOrganization`, `renameOrganization` (`PATCH`). The org "switch" is a WorkOS-session op (`/api/active-org`), not a `/v1` route — out of SDK scope.
126
130
  - **Billing:** `getSubscription`, `listPlans`, `listInvoices`, `createCheckout`, `changePlan` (409 `conflict` ⇒ no subscription), `getBillingPortal` (409 `conflict` ⇒ no subscription).
127
131
  - **Pipelex API keys (`plx_sk_…`):** `listPipelexApiKeys`, `createPipelexApiKey` (409 `pipelex_api_key_limit_reached`), `revokePipelexApiKey`, `rotatePipelexApiKey`. The plaintext `api_key` is returned once, on create/rotate.
128
132
  - **Onboarding:** `submitOnboarding`.
129
133
  - **Storage:** `resolveStorageUrl`, `resolveStorageUrls` (`POST /v1/resolve-storage-url/bulk` — the single route applied to a list of at most 100 references, one verdict per reference in request order, a refused reference a value on its item; a deployment without the route answers a `404` `ApiResponseError`), `upload` (JSON base64 — the multipart hop is browser→BFF only), `requestUploadGrant` (`POST /v1/upload/grant` — a presigned, create-only `PUT` for one new object, which the holder of the bytes sends with the standalone `uploadWithGrant`, also exported from the browser-safe `@pipelex/sdk/upload` entry; a declared size over the cap is a `413` `ApiResponseError`). The higher-level `uploadFile` / `prepareInputs` preparation surface is built on top of `upload`; `prepareInputs` additionally composes one `pipeIo` call, which selects the pipe and answers with the input-form descriptor it walks, and takes the method as inline `files`, a `method_ref` or a `method_id` like every other method-taking operation — see [input-preparation.md](./input-preparation.md). The download twin — `locateArtifacts`, `collectArtifacts`, `resolveArtifacts`, `fetchArtifact`, `downloadArtifacts` — is built on top of `resolveStorageUrls` and `getRunResult`, never on the embedded `public_url`, and is documented on [artifact-download.md](./artifact-download.md); downloading is a separate explicit gesture, and no run call takes a `download` option.
130
- - **Runs list/detail/update:** `listRuns` (by `method_id`) returns **one page**, not the whole history — a `RunPage` (`{items, nextCursor}`) served from a time-ordered index, so its cost is the page size rather than the size of the history. Its rows are `RunHistoryItem`, **not** `PipelineRun`: the id, `status`, `created_at`, `finished_at`, `pipe_code` and, for a failed run, `error` — what a history row shows. The run's organization, creator, method, workflow id and result location are not on the list; `getRunDetail` returns them. `iterateRuns` is the async iterator that follows the cursor for callers who genuinely want everything (`for await (const run of client.iterateRuns(id))`); it is deliberately not a `listAllRuns(): Promise<RunHistoryItem[]>`, because an all-at-once helper needs a page cap and a cap means silently returning a truncated list. Both accept `createdFrom` / `createdTo`, applied server-side as index key conditions — these are **instants, not days** (ISO-8601 with a UTC offset; a naive timestamp is a 400), because only the caller knows which timezone's day it means. `getRunDetail` (`GET /v1/runs/{id}`) is the only call returning `mthds_contents` — what the run actually executed — and `inputs`; it is kept off the status read that pollers hit every few seconds and off the list, where the bundle would be multiplied by the page size. `updateRun` is the admin/manual status patch. The status/results/start lifecycle routes live in the run-lifecycle section above.
134
+ - **Runs list/detail/update:** `listRuns` (by `method_id`) returns **one page**, not the whole history — a `RunPage` (`{items, nextCursor}`) served from a time-ordered index, so its cost is the page size rather than the size of the history. Its rows are `RunHistoryItem`, **not** `PipelineRun`: the id, `status`, `created_at`, `finished_at`, `pipe_code`, for a failed run `error`, and `method_version` and `source_digest`, which version of the method the run ran (a number, `"draft"`, or `null` for an inline source) and the digest of what it ran — what a history row shows. The history lists the runs of every version and of the draft together, by the bare id. `method_version` and `source_digest` are optional on every run record (`RunPublic`, `RunRead`, `PipelineRun`, `RunDetail`, `RunHistoryItem`), since a run recorded before the platform recorded them carries neither. The run's organization, creator, method, workflow id and result location are not on the list; `getRunDetail` returns them. `iterateRuns` is the async iterator that follows the cursor for callers who genuinely want everything (`for await (const run of client.iterateRuns(id))`); it is deliberately not a `listAllRuns(): Promise<RunHistoryItem[]>`, because an all-at-once helper needs a page cap and a cap means silently returning a truncated list. Both accept `createdFrom` / `createdTo`, applied server-side as index key conditions — these are **instants, not days** (ISO-8601 with a UTC offset; a naive timestamp is a 400), because only the caller knows which timezone's day it means. `getRunDetail` (`GET /v1/runs/{id}`) is the only call returning `mthds_contents` — what the run actually executed — and `inputs`; it is kept off the status read that pollers hit every few seconds and off the list, where the bundle would be multiplied by the page size. `updateRun` is the admin/manual status patch. The status/results/start lifecycle routes live in the run-lifecycle section above.
131
135
 
132
136
  ## Hook bundle (`dist-hooks/check.mjs`)
133
137
 
package/docs/cli.md CHANGED
@@ -19,7 +19,7 @@ The command lives in `src/cli.ts`, the executable, and `src/cli/`. It sits at th
19
19
  pipelex-sdk run --method <method> [--pipe <domain.pipe_code>] [--inputs <file> | --inputs-template]
20
20
  ```
21
21
 
22
- - **`--method`**, required, names the method in one of the forms below, told apart by shape alone. A value starting with `mt_` is a catalog id, sent as `method_id`. A value starting with `github.com/` is a published method's address, `github.com/<owner>/<repo>[/<package>][@<tag>]`, sent as `method_ref`. Anything else is a path to a `.mthds` file or a bundle directory, which must exist and is read from disk and sent inline (see "Reading a bundle" below). A file or directory that happens to be named `mt_…` is reached as `./mt_…`, so what a value names never depends on what the current directory holds, and an address is written without its scheme. An `mt_` value holding a character no catalog id holds, anything but letters, digits, `_` and `-` (the ids are minted as `mt_` and a UUID, and the run route takes no other character), is a usage error made before any request, which says to write `./` to name a file: `mt_review.mthds` is refused, `./mt_review.mthds` is a path.
22
+ - **`--method`**, required, names the method in one of the forms below, told apart by shape alone. A value starting with `mt_` is a catalog id, sent as `method_id`. A value starting with `github.com/` is a published method's address, `github.com/<owner>/<repo>[/<package>][@<tag>]`, sent as `method_ref`. Anything else is a path to a `.mthds` file or a bundle directory, which must exist and is read from disk and sent inline (see "Reading a bundle" below). A file or directory that happens to be named `mt_…` is reached as `./mt_…`, so what a value names never depends on what the current directory holds, and an address is written without its scheme. An `mt_` value holding a character no catalog id holds, anything but letters, digits, `_` and `-` (the ids are minted as `mt_` and a UUID, and the run route takes no other character), is a usage error made before any request, which says to write `./` to name a file: `mt_review.mthds` is refused, `./mt_review.mthds` is a path. A catalog id may end in a version suffix, which is sent whole: a bare `mt_…` runs the method's latest published version, `mt_…@<n>` the fixed version `n` and `mt_…@draft` its draft. A suffix that is neither a positive number without a leading zero nor `draft` (`@0`, `@03`, `@Draft`, two suffixes) is a usage error made before any request, as `parseMethodSelector` reads it.
23
23
  - **`--pipe`** names the pipe by its qualified ref, `domain.pipe_code`. It is sent as `pipe_ref` to the input preparation and as `pipe_code` to the run. A bare code, and an `alias->domain.pipe_code` ref naming a dependency's pipe, are usage errors. Without it, the run takes the method's main pipe, as the API resolves it.
24
24
  - **`--inputs`** names a JSON file holding one object, one entry per input, the shape `mthds run --inputs` and `cli-python`'s `--inputs` take; `-` reads it from stdin. The text is UTF-8, and a byte-order mark at its start is dropped, as RFC 8259 lets a JSON reader do, since Windows PowerShell 5.1 writes one (`Out-File -Encoding utf8`); a bundle's mark, by contrast, is sent as it is on disk. A relative path resolves against the current directory. A file or stdin that cannot be read is a usage error, `cannot read stdin.` with the system's reason under it (`Reason: EIO`), and a stdin closed when the command starts reads as empty, so it is not valid JSON: Node opens the null device on a standard stream a process starts without, and the Python command reads the missing stream the same way. A `process.stdin` destroyed before the command reads it is unreadable too, with the reason `ERR_STREAM_PREMATURE_CLOSE`. Without it the run gets no inputs. When the object has entries, they first go through the SDK's input preparation (`prepareInputs`, one `POST /v1/pipe-io` call): a local path or a `data:` URL at a file input is uploaded and replaced by its `pipelex-storage://` reference, each upload is reported on stderr, and a URL passes through. An empty object or no `--inputs` makes no preparation request. A value at a file input that is no file, a `data:` URL that does not decode or a value of another type such as a number, is a usage error, as is a local path that cannot be read; a method that does not load, met during the preparation, is reported as `--inputs-template` reports it ("Exit codes, errors and Ctrl-C" below). The command tells the two apart by the SDK's error classes, `InvalidLocalSourceError` and `InvalidInputValueError` for the inputs' mistakes and `MethodLoadError` for the method's ([`input-preparation.md`](input-preparation.md)).
25
25
  - **`--inputs-template`** prints the pipe's inputs template, the object `--inputs` takes with a placeholder for every input, and runs nothing. The template is rendered by `mthds`' `renderInputsTemplate`, in its compact JSON shape, from the input-form descriptor one `POST /v1/pipe-io` call returns, which spends no inference. It takes no `--inputs`.
@@ -41,13 +41,13 @@ pipelex-sdk script --method <address | catalog id> [--pipe <domain.pipe_code>] [
41
41
  # receipt-review: runs github.com/acme/methods/receipt-review@v1.0.0 on the Pipelex API.
42
42
  # Written by @pipelex/sdk X.Y.Z. Needs Node 22.12 or later, and PIPELEX_API_KEY in the environment.
43
43
  # Usage: ./receipt-review --inputs inputs.json, or ./receipt-review --inputs-template to see what to fill in.
44
- exec npx --yes @pipelex/sdk@X.Y.Z run --method 'github.com/acme/methods/receipt-review@v1.0.0' --pipe 'receipts.review_receipt' "$@"
44
+ exec npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@X.Y.Z run --method 'github.com/acme/methods/receipt-review@v1.0.0' --pipe 'receipts.review_receipt' "$@"
45
45
  ```
46
46
 
47
47
  - **The method is an address or a catalog id.** A local bundle is refused, since a script naming a path breaks as soon as the script or the bundle moves; the refusal names both ways to get one, publishing the method or saving it to the catalog. An address without a tag is accepted, with a warning that the script follows the repository's default branch.
48
- - **The name** defaults to the address's last segment without its tag, or, for a catalog id, to the method's name read from its catalog entry (`GET /v1/methods/{id}`) and kebab-cased: decomposed (NFKD), stripped of combining marks, lowercased, every run of characters outside `a-z0-9` turned into one `-`, trimmed of `-`. A name that comes out empty asks for `--name`, and so does a catalog entry whose name is missing or not a string. A name is a file name: it may not be empty, `.` or `..`, or hold a `/`, a `\` or a control character. The `\` is refused on every platform, since Windows reads it as a `/` and `..\outside` would leave `--dir`.
48
+ - **The name** defaults to the address's last segment without its tag, or, for a catalog id, to the method's name read from its catalog entry (`GET /v1/methods/{id}`, by the bare id, since the name belongs to the method and not to a version; the script keeps the suffix) and kebab-cased: decomposed (NFKD), stripped of combining marks, lowercased, every run of characters outside `a-z0-9` turned into one `-`, trimmed of `-`. A name that comes out empty asks for `--name`, and so does a catalog entry whose name is missing or not a string. A name is a file name: it may not be empty, `.` or `..`, or hold a `/`, a `\` or a control character. The `\` is refused on every platform, since Windows reads it as a `/` and `..\outside` would leave `--dir`.
49
49
  - **The file** is written in `--dir`, by default the current directory, which must exist. It is created with the permissions of an executable (`0755`, less the umask) and never over an existing file, a dangling link included. A write that fails once the file exists removes it, so a script is whole or absent, never a truncated one that would run half a command and make the next attempt say it already exists; a Ctrl-C waits for the file to be whole ("Exit codes, errors and Ctrl-C" below). The path printed is `--dir` as given without its trailing `/`, or `.`, then `/` and the name.
50
- - **The script's one line** is `exec npx --yes @pipelex/sdk@<version> run --method '<method>' [--pipe '<pipe>'] "$@"`: every value single-quoted, an inner `'` spelled `'\''`, and no value may hold a control character. The `"$@"` passes everything else on, `--inputs`, `--inputs-template` and stdin included, and since `run` refuses a flag given twice, a second `--method` cannot quietly run another method. The version is the one of the SDK that wrote the script, so the script keeps running the command it was written with; `npx` caches the package, so after the first run only the API needs the network. There is no option to write the Python SDK's line, since this SDK cannot know which version of the other package exists and was tested.
50
+ - **The script's one line** is `exec npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@<version> run --method '<method>' [--pipe '<pipe>'] "$@"`: every value single-quoted, an inner `'` spelled `'\''`, and no value may hold a control character. The `"$@"` passes everything else on, `--inputs`, `--inputs-template` and stdin included, and since `run` refuses a flag given twice, a second `--method` cannot quietly run another method. The version is the one of the SDK that wrote the script, so the script keeps running the command it was written with; `npx` caches the package, so after the first run only the API needs the network. The two flags keep npm's own notices off the script's stderr, which carries the command's lines: `--no-update-notifier` removes the update notice npm 10 and 11 print wherever their cache starts empty, as in a fresh container, and `--loglevel=warn` removes the `npm notice run …` lines npm 12 prints at every run, while npm's warnings and errors, such as a version that cannot be fetched or a Node older than the package needs, still print. They act on `npx` alone, which hands everything after the package to the command. There is no option to write the Python SDK's line, since this SDK cannot know which version of the other package exists and was tested.
51
51
  - **On Windows** the script needs a POSIX shell, such as Git Bash or WSL; otherwise type the `npx` line directly.
52
52
 
53
53
  ## The environment
@@ -66,7 +66,7 @@ The exit code is presentation, following the workspace's surface-output rule: a
66
66
  | --- | --- |
67
67
  | `0` | The result was printed, or the script written, or the help or version shown |
68
68
  | `1` | The run failed, the API refused a request, could not be reached, or answered what the command cannot read |
69
- | `2` | A usage error: a bad flag, an unreadable or invalid inputs file, a selector of no known form, a path that names no bundle, a missing key, a refused base URL, an `mt_` value that cannot be a catalog id, a local file the inputs name that cannot be read, a value at a file input that is no file, a script that would overwrite a file |
69
+ | `2` | A usage error: a bad flag, an unreadable or invalid inputs file, a selector of no known form, a path that names no bundle, a missing key, a refused base URL, an `mt_` value that cannot be a catalog id or names no version, a local file the inputs name that cannot be read, a value at a file input that is no file, a script that would overwrite a file |
70
70
  | `130` | Interrupted: on Linux and macOS the process ends by the interrupt signal, which a shell reports as `130` |
71
71
 
72
72
  Every error goes to stderr, its first line `Error: <sentence>` and the lines under it giving the details. The command words a failure from the error's typed fields rather than from an SDK message, wherever the fields carry what a person needs, so that the JavaScript and Python commands print the same lines for the same answer:
@@ -108,7 +108,7 @@ The table holds:
108
108
 
109
109
  - **`base_url`**, the origin every recorded route is served on, and **`env`**, the environment every case starts from: a key, that base URL and the poll interval `0`.
110
110
  - **`answers`**, named answers a route's exchange can refer to rather than repeat.
111
- - **`placeholders`**, each with its JavaScript and Python value: `WRITER` (`@pipelex/sdk`), `NEEDS` (`Node 22.12 or later`), `RUNNER` (`npx --yes @pipelex/sdk@{{SDK_VERSION}}`), and `SDK_VERSION`, which each suite fills with its own SDK's version. They appear as `{{NAME}}` in a case's expected outputs only, and each suite substitutes the language values first, then `{{SDK_VERSION}}`.
111
+ - **`placeholders`**, each with its JavaScript and Python value: `WRITER` (`@pipelex/sdk`), `NEEDS` (`Node 22.12 or later`), `RUNNER` (`npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@{{SDK_VERSION}}`), and `SDK_VERSION`, which each suite fills with its own SDK's version. They appear as `{{NAME}}` in a case's expected outputs only, and each suite substitutes the language values first, then `{{SDK_VERSION}}`.
112
112
  - **`cases`**, each with these fields:
113
113
  - `name`, unique, and `summary`, one sentence saying what the case holds.
114
114
  - `argv`, the arguments after the command's name.
@@ -33,7 +33,7 @@ Treat that attribution as best-effort, not a guarantee — which is why `Validat
33
33
 
34
34
  An **address-form** `method_ref` (`github.com/<owner>/<repo>[/<selector>][@<tag>]`) is resolved by the server through the same fetch path as a `method_ref` run: the repository fetched at the tag, the package located by manifest identity, the package's real relative paths feeding the per-file `source` labels. Any non-address reference stays reserved and answers `501`.
35
35
 
36
- `method_id` is the hosted platform's selector, and it is a **pass-through**: nothing is expanded client-side — the platform resolves the id against the org's catalog and injects the stored source before the runner sees the request. It is meaningless against a bare runner (no catalog), and on `api.pipelex.com` its availability follows the platform deploy that adds the tooling-route transform. An unknown or foreign-org id is a `404` (indistinguishable by design); a stored method with no MTHDS source is a `422`.
36
+ `method_id` is the hosted platform's selector, and it is a **pass-through**: nothing is expanded client-side — the platform resolves the id against the org's catalog and injects the stored source before the runner sees the request. A bare `mt_…` names the method's latest published version, `mt_…@<n>` the fixed version `n` and `mt_…@draft` its draft, and the suffix rides the string untouched. It is meaningless against a bare runner (no catalog), and on `api.pipelex.com` its availability follows the platform deploy that adds the tooling-route transform. An unknown or foreign-org id is a `404` (indistinguishable by design); a bare id of a method never published is a `409` `method_not_published`, whose detail names `mt_…@draft`; a version the method never published is a `404` `method_version_not_found`; a malformed suffix is a `422`; a stored method with no MTHDS source is a `422`.
37
37
 
38
38
  Supplying **no** selector or **more than one** is a request-shape `422` — the tooling routes are stateless, so there is no linkage exception; a second selector could only be ignored, which is the worst contract of the three. The SDK does not model the XOR in the type system — the union would force the overwhelmingly common `{ files }` call site to pick a branch for no gain, and the server's answer is a typed `ApiResponseError` either way.
39
39
 
@@ -134,9 +134,11 @@ Only a **no-verdict** condition, as the typed `ApiResponseError` — branch on i
134
134
 
135
135
  | Status | Cause |
136
136
  | ------------- | --------------------------------------------------------------------------------------------------------- |
137
- | `422` | Request shape: no closure selector or more than one, an over-limit file, an unknown `kind`/`target`, a `pipe_ref` on `kind: "types"`, a fetched package with no `.mthds` file, a stored method with no MTHDS source. |
137
+ | `422` | Request shape: no closure selector or more than one, an over-limit file, an unknown `kind`/`target`, a `pipe_ref` on `kind: "types"`, a fetched package with no `.mthds` file, a stored method with no MTHDS source, a `method_id` whose version suffix is malformed. |
138
138
  | `422` | On `pipeIo`, a pipe selection the route refuses: `errorType` `EntryPipeNotFoundError` for a `pipe_ref` that names no pipe or a method with no entry pipe, `EntryPipeAmbiguousError` for a code that matches pipes in several domains or several `main_pipe` declarations. The `serverMessage` names the candidates where there are some. |
139
139
  | `404` | Unknown or foreign-org `method_id` (indistinguishable by design); no package at a `method_ref` address. |
140
+ | `404` | `code` `method_version_not_found`: a `mt_…@<n>` naming a version the method never published. |
141
+ | `409` | `code` `method_not_published`: a bare `method_id` of a method never published; `mt_…@draft` reaches its draft. |
140
142
  | `501` | Registry-form `method_ref` — reserved, not implemented (the address form resolves). |
141
143
  | `401` / `403` | Auth, or on a hosted origin a route the gateway does not list yet (`403 {"message":"Forbidden"}`). |
142
144
  | `502` | On a hosted origin, a cold `method_ref` clone that outran the gateway's 30-second cap; a retry clears it. |
package/docs/errors.md CHANGED
@@ -41,7 +41,7 @@ try {
41
41
  | Class | `errorDomain` | `retryable` | Why |
42
42
  |---|---|---|---|
43
43
  | `ApiResponseError` | the server's, else the fallback | the server's, else no for a sent `input` or `config` domain, else the fallback | See [below](#a-refused-requests-verdict). |
44
- | `RequestArgumentError` | `input` | no | The client refused a call's arguments before sending any request: no run source given to `execute` or `start`, run sources or method selectors that exclude each other, a selector rule of `validate`, an empty `validateFiles`, a reserved key in `extra`. Its options take an optional `verdict`, and the client declares `config` for a base URL that is not host-only, since it typically comes from `PIPELEX_BASE_URL`. That refusal shows the base URL's scheme and host alone and names the other parts it carried (credentials, a path, a query, a fragment) without their text, since those are where a secret travels in a URL; a value that is not an http or https URL is not shown at all. It is a `PipelineRequestError`, as the bare refusal it replaced was, and a refusal the standard's own run-source check raises keeps that original as `cause`. |
44
+ | `RequestArgumentError` | `input` | no | The client refused a call's arguments before sending any request: no run source given to `execute` or `start`, run sources or method selectors that exclude each other, a selector rule of `validate`, an empty `validateFiles`, a reserved key in `extra`, a `publishMethod` without its token, a `getMethodVersion` version that is not a positive integer, or a selector `parseMethodSelector` refuses. Its options take an optional `verdict`, and the client declares `config` for a base URL that is not host-only, since it typically comes from `PIPELEX_BASE_URL`. That refusal shows the base URL's scheme and host alone and names the other parts it carried (credentials, a path, a query, a fragment) without their text, since those are where a secret travels in a URL; a value that is not an http or https URL is not shown at all. It is a `PipelineRequestError`, as the bare refusal it replaced was, and a refusal the standard's own run-source check raises keeps that original as `cause`. |
45
45
  | `ApiUnreachableError` | `config` | yes | The address or the network must be checked, and a later attempt can get through. With the code `ABORT_TIMEOUT`, the SDK's own request timeout, it is `runtime`: the API took the request and did not answer in time. |
46
46
  | `PipelineExecuteTimeoutError` | `input` | no | The blocking `execute` cannot outlast the gateway's limit; start the run and poll instead. |
47
47
  | `RunFailedError` | the report's | the report's | See [a failed run](#a-failed-run--runerrorreport). |
@@ -71,7 +71,7 @@ The fallback reads the HTTP status, the platform's `code`, and whether the body
71
71
  | `404`, bare | `config` | no | The base URL points at a deployment without the route. |
72
72
  | `405` | `config` | no | The deployment does not serve this method on the route. |
73
73
  | `408`, `429` | `runtime` | yes | Refused for its timing, not its content. |
74
- | `409` | `input` | no | The request conflicts with the stored state: a method being deleted, a stale update, a taken label. |
74
+ | `409` | `input` | no | The request conflicts with the stored state: a method being deleted, a stale draft token, a method never published addressed by its bare id, a taken label. |
75
75
  | `409` with code `pipelex_api_key_limit_reached` | `config` | no | The organization holds as many keys as it may; someone removes one. |
76
76
  | `501` | `config` | no | The deployment does not implement it, and asking again will not change that. |
77
77
  | `500`, `502`, `503`, `504`, and any `5xx` not listed here | `runtime` | yes | A fault in the service, which may pass. |
@@ -155,7 +155,7 @@ A field a newer runner adds is reachable through the interface's index signature
155
155
 
156
156
  Every non-2xx answer from a `/v1` route is a problem document, and the SDK throws an `ApiResponseError` whose fields are its members. Each field is `undefined` when the document did not carry it, and a member of the wrong type (a numeric `type`, a `retryable` of `"no"`, a `user_action` without a `detail`) reads as absent rather than as a wrong value. The nested members are checked one level down, exactly as a [stored report's](#a-failed-run--runerrorreport) are: `providerMetadata` and `migration` keep each named field only when it has its type, `validationErrors` keeps only the items that are objects with a string `category` and `message`, and each item of `errors` keeps its `field`, `code` and `detail` only when they are strings; members the SDK does not name are relayed as sent. The verdict is the exception: `errorDomain` and `retryable` are always decided, the server's value when it sent a valid one and otherwise [as the verdict section says](#a-refused-requests-verdict).
157
157
 
158
- **An answer the SDK could not read is an `ApiResponseError` too.** A `2xx` whose body is not JSON, such as a gateway's HTML page or an empty body where the route answers one, is no result the SDK can hand back, so every route throws an `ApiResponseError` for it instead of letting the runtime's `SyntaxError` escape. So is a `2xx` whose body is JSON but not the object the route answers — `null`, a number, a string, an array — on every protocol, extension and run-lifecycle route, on `health()`, and on the product routes whose answer the SDK or its caller reads before anything else (`listMethods`, `getMethod`, `createMethod`, `updateMethod`, `listRuns`, `getRunDetail`, `upload`, `requestUploadGrant`, `resolveStorageUrls`), instead of the `TypeError` that reading it would raise. So is an object missing the member those reads depend on: a page of `listMethods` or `listRuns` whose `items` is not an array or whose `next_cursor` is neither a string nor `null`, which `iterateMethods` and `iterateRuns` could not walk or would follow forever; an `upload` answer with no non-empty string `uri`, which names no stored file; and a stored method whose `mthds` field is neither a string nor absent (a `null` one is still no source, which `getMethodClosure` reports as `EmptyMethodSourceError`) or whose `python` field is not the serialized `[{ name, content }]` list, as `getMethod`, `createMethod` and `updateMethod` answer it: that is server data no change to the call fixes, not an argument the caller passed. Its `status` is the `2xx`, `responseBody` the raw text, `cause` what made the answer unreadable (the parse failure, or `mthds`'s refusal of the `python` field; nothing for a body that parsed to something the route does not answer), `problemDocument` and the problem members are `undefined`, and its verdict is the fallback's for a status no refusal carries: `runtime`, not retryable. A product route that answers a `2xx` with no body at all, such as `revokePipelexApiKey`, still resolves to `undefined`, and the other product routes hand their decoded body back as it came.
158
+ **An answer the SDK could not read is an `ApiResponseError` too.** A `2xx` whose body is not JSON, such as a gateway's HTML page or an empty body where the route answers one, is no result the SDK can hand back, so every route throws an `ApiResponseError` for it instead of letting the runtime's `SyntaxError` escape. So is a `2xx` whose body is JSON but not the object the route answers — `null`, a number, a string, an array — on every protocol, extension and run-lifecycle route, on `health()`, and on the product routes whose answer the SDK or its caller reads before anything else (`listMethods`, `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, `publishMethod`, `listMethodVersions`, `getMethodVersion`, `listRuns`, `getRunDetail`, `upload`, `requestUploadGrant`, `resolveStorageUrls`), instead of the `TypeError` that reading it would raise. So is an object missing the member those reads depend on: a page of `listMethods`, `listMethodVersions` or `listRuns` whose `items` is not an array or whose `next_cursor` is neither a string nor `null`, which `iterateMethods` and `iterateRuns` could not walk or would follow forever; a publish result whose `outcome` is not `published`, `unchanged` or `refused`, or whose `method` is not an object, which a caller branching on `outcome` could not read; an `upload` answer with no non-empty string `uri`, which names no stored file; and a stored method or a published version whose `mthds` field is neither a string nor absent (a `null` one is still no source, which `getMethodClosure` reports as `EmptyMethodSourceError`) or whose `python` field is not the serialized `[{ name, content }]` list, as `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, `publishMethod` and `getMethodVersion` answer it: that is server data no change to the call fixes, not an argument the caller passed. Its `status` is the `2xx`, `responseBody` the raw text, `cause` what made the answer unreadable (the parse failure, or `mthds`'s refusal of the `python` field; nothing for a body that parsed to something the route does not answer), `problemDocument` and the problem members are `undefined`, and its verdict is the fallback's for a status no refusal carries: `runtime`, not retryable. A product route that answers a `2xx` with no body at all, such as `revokePipelexApiKey`, still resolves to `undefined`, and the other product routes hand their decoded body back as it came.
159
159
 
160
160
  | Field | Wire member | Meaning |
161
161
  |---|---|---|
@@ -167,7 +167,7 @@ Every non-2xx answer from a `/v1` route is a problem document, and the SDK throw
167
167
  | `title` | `title` | The stable human label of the error class. |
168
168
  | `serverMessage` | `detail` (or `message`) | The per-occurrence message. |
169
169
  | `instance` | `instance` | The occurrence: a request URN (`urn:pipelex:request:<id>`) or a request path. |
170
- | `code` | `code` | The platform's native code, a closed set (`conflict`, `not_found`, `validation_failed`, `pipelex_api_key_limit_reached`, …), one-to-one with `type`. |
170
+ | `code` | `code` | The platform's native code, a closed set (`conflict`, `not_found`, `validation_failed`, `pipelex_api_key_limit_reached`, …), one-to-one with `type`; `MethodErrorCode` names the method ones (see [The method codes](#the-method-codes)). |
171
171
  | `errorType` | `error_type` | The runner's native code, its open exception class name. |
172
172
  | `errorCategory`, `model`, `provider`, `providerMetadata` | `error_category`, `model`, `provider`, `provider_metadata` | An inference failure's class and origin, as on the run report. |
173
173
  | `migration` | `migration` | A pending configuration migration that explains the failure. |
@@ -197,6 +197,19 @@ try {
197
197
 
198
198
  The members `mthds`'s own `ApiResponseError` carries (`type`, `title`, `instance`, `requestId`, `errorDomain`, `retryable`, `userAction`) have the same names here, and the same types except the verdict: the standard's client leaves `errorDomain` an open string and `retryable` optional, where this SDK decides both. A type-level test pins `ProblemDetails`, the members as parsed, to the standard client's declaration, so a consumer reads one vocabulary whichever client raised the error. The rest are the Pipelex members the standard's client leaves to this SDK.
199
199
 
200
+ ### The method codes
201
+
202
+ A refusal about a saved method or one of its versions carries one of the platform codes the exported type `MethodErrorCode` names. `code` itself stays a `string`, since the platform adds codes; every one of these is `input` and not retryable by the fallback, since asking again unchanged meets the same answer.
203
+
204
+ | `code` | Status | When | What the caller does |
205
+ |---|---|---|---|
206
+ | `method_update_conflict` | `409` | The draft moved since the token a `writeDraft` (`expected_updated_at`) or a `publishMethod` (`expected_draft_updated_at`) sent; nothing was written or published. `errors[0].field` names the request field that carried the token. | Read the method again, and either load its draft or overwrite it by writing again with the fresh token. |
207
+ | `method_being_deleted` | `409` | The method's erasure has started, on every route that addresses it; the detail tells an erasure in flight from a failed one. | Stop offering the method; retry the delete when the detail says it failed. |
208
+ | `method_not_published` | `409` | A bare `method_id`, which names the latest published version, on a run or tooling route, for a method never published. The detail names `mt_…@draft` and publishing. | Publish the method, or address its draft as `mt_…@draft`. |
209
+ | `method_version_not_found` | `404` | `mt_…@<n>`, or `getMethodVersion`, names a version the method never published. An unknown method is `not_found` instead, so the two are told apart by the code. | Pick a version `listMethodVersions` lists. |
210
+
211
+ A malformed version suffix (`@0`, `@03`, `@Draft`, two suffixes) is a `422` `validation_failed` whose `errors` name the field `method_id`; `parseMethodSelector` refuses the same selectors locally with a `RequestArgumentError`, before anything is sent.
212
+
200
213
  ## A validation item — `ValidationErrorItem`
201
214
 
202
215
  A method refused for what it says, rather than for how its run went, comes back as a list of validation items. They ride the `/v1/validate` verdict (its invalid arm's `validation_errors[]`, and the advisory `warnings[]` of its valid arm) and the crate routes' invalid verdict (`CrateInvalidReport.validation_errors[]`), each a `200` because an invalid bundle is a produced verdict; the `422` a run route answers when the runner refuses the method for its validation errors, as `ApiResponseError.validationErrors`; and a failed run's report, as `validation_errors`. Each item has a `category` and a `message`; the other members locate the error and say what to do next, and each is present only when the runner knows it: