@pipelex/sdk 0.48.0 → 0.49.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 (63) hide show
  1. package/README.md +11 -1
  2. package/dist/artifacts.d.ts +4 -3
  3. package/dist/artifacts.js +20 -14
  4. package/dist/artifacts.js.map +1 -1
  5. package/dist/cli/script.d.ts +1 -1
  6. package/dist/client.d.ts +95 -9
  7. package/dist/client.js +143 -12
  8. package/dist/client.js.map +1 -1
  9. package/dist/codegen-check.d.ts +2 -3
  10. package/dist/codegen-check.js +2 -3
  11. package/dist/codegen-check.js.map +1 -1
  12. package/dist/errors.d.ts +91 -6
  13. package/dist/errors.js +48 -6
  14. package/dist/errors.js.map +1 -1
  15. package/dist/hooks/bundle-gather.d.ts +29 -10
  16. package/dist/hooks/bundle-gather.js +91 -14
  17. package/dist/hooks/bundle-gather.js.map +1 -1
  18. package/dist/hooks/check-core.d.ts +70 -6
  19. package/dist/hooks/check-core.js +188 -29
  20. package/dist/hooks/check-core.js.map +1 -1
  21. package/dist/hooks/claude-mthds-check.d.ts +12 -0
  22. package/dist/hooks/claude-mthds-check.js +12 -0
  23. package/dist/hooks/claude-mthds-check.js.map +1 -1
  24. package/dist/hooks/secret-guard.d.ts +38 -0
  25. package/dist/hooks/secret-guard.js +134 -0
  26. package/dist/hooks/secret-guard.js.map +1 -0
  27. package/dist/hooks/validate-stage.d.ts +5 -3
  28. package/dist/hooks/validate-stage.js +16 -7
  29. package/dist/hooks/validate-stage.js.map +1 -1
  30. package/dist/index.d.ts +4 -3
  31. package/dist/index.js +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/method-trees.d.ts +19 -8
  34. package/dist/method-trees.js +37 -11
  35. package/dist/method-trees.js.map +1 -1
  36. package/dist/node-host/node.d.ts +33 -0
  37. package/dist/node-host/node.js +27 -0
  38. package/dist/node-host/node.js.map +1 -0
  39. package/dist/node-host/unavailable.d.ts +8 -0
  40. package/dist/node-host/unavailable.js +8 -0
  41. package/dist/node-host/unavailable.js.map +1 -0
  42. package/dist/node-host.d.ts +53 -0
  43. package/dist/node-host.js +72 -0
  44. package/dist/node-host.js.map +1 -0
  45. package/dist/prepare-inputs.d.ts +12 -1
  46. package/dist/prepare-inputs.js +26 -3
  47. package/dist/prepare-inputs.js.map +1 -1
  48. package/dist/product-models.d.ts +219 -2
  49. package/dist/product-models.js +2 -2
  50. package/dist/upload.d.ts +43 -20
  51. package/dist/upload.js +128 -24
  52. package/dist/upload.js.map +1 -1
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/docs/architecture.md +32 -6
  56. package/docs/crate-routes.md +1 -1
  57. package/docs/errors.md +6 -1
  58. package/docs/files.md +88 -0
  59. package/docs/hook-bundle.md +27 -1
  60. package/docs/input-preparation.md +16 -6
  61. package/docs/method-trees.md +2 -2
  62. package/docs/organization-branding.md +102 -0
  63. package/package.json +7 -1
package/dist/upload.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  * The MIME type and size are known client-side at upload time, so the record is
8
8
  * built without extending the `/v1/upload` response.
9
9
  */
10
+ import type { UploadInput, UploadKind } from "./product-models.js";
10
11
  /** A local asset `uploadFile` accepts. A path string is Node-only (see {@link readLocalPath}). */
11
12
  export type UploadableAsset = Blob | ArrayBuffer | Uint8Array | string;
12
13
  /**
@@ -24,8 +25,27 @@ export interface UploadRecord {
24
25
  /** Size of the uploaded bytes. */
25
26
  size: number;
26
27
  }
27
- /** Per-call options for {@link uploadFile}. Fill in a filename/MIME for nameless byte sources. */
28
- export interface UploadFileOptions {
28
+ /**
29
+ * What an upload is recorded as, sent with it to `POST /v1/upload`: its `kind` and, for a test
30
+ * file, the method it is for. `uploadFile` takes these among its options and `prepareInputs` as
31
+ * its `upload` member, for every file it uploads. Both are checked as the upload route checks
32
+ * them, before any file is read: `kind: "test"` needs `method_id`, a bare catalog id, and only a
33
+ * test file names a method.
34
+ */
35
+ export interface UploadKindOptions {
36
+ /** What the upload is recorded as; `uploaded` when omitted. */
37
+ kind?: UploadKind;
38
+ /**
39
+ * The bare catalog id of the method a `test` file is for, required with `kind: "test"`
40
+ * and refused without it.
41
+ */
42
+ method_id?: string;
43
+ }
44
+ /**
45
+ * Per-call options for {@link uploadFile}. Fill in a filename/MIME for nameless byte sources, and
46
+ * a `kind` with its `method_id` for a test file.
47
+ */
48
+ export interface UploadFileOptions extends UploadKindOptions {
29
49
  filename?: string;
30
50
  contentType?: string;
31
51
  /**
@@ -37,37 +57,40 @@ export interface UploadFileOptions {
37
57
  }
38
58
  /** The subset of the client `uploadFile` needs — the raw base64 `upload` wire call. */
39
59
  export interface UploadCapableClient {
40
- upload(input: {
41
- filename: string;
42
- data: string;
43
- content_type: string;
44
- }): Promise<{
60
+ upload(input: UploadInput): Promise<{
45
61
  uri: string;
46
62
  filename: string;
47
63
  }>;
48
64
  }
49
- /** True when running under Node (has `process.versions.node`). */
50
- export declare function isNodeRuntime(): boolean;
65
+ /**
66
+ * The `kind` and `method_id` an upload is sent with, checked as the upload route checks them, so
67
+ * a request the route would refuse with a `422` is refused before any file is read: a `kind` is
68
+ * `uploaded` or `test`, a `method_id` is a bare catalog id, `kind: "test"` needs one and only a
69
+ * test file names one. Returns the members to send, each only when given; `null` reads as
70
+ * absent, as it does on the route. A refusal is an `InputPreparationError` whose message opens
71
+ * with `refusal` and names each member as `field` prefixes it, so `prepareInputs` names its
72
+ * `upload.kind`.
73
+ */
74
+ export declare function uploadKindFields(given: UploadKindOptions, refusal: string, field?: string): UploadKindOptions;
51
75
  /** MIME guess from a filename extension; `application/octet-stream` when unknown. */
52
76
  export declare function guessContentType(filename: string): string;
53
77
  /**
54
78
  * Read a filesystem path into bytes — Node only. A path string in a non-Node
55
79
  * runtime cannot be read, so it fails as an invalid local source rather than
56
- * being silently misread as text or a URL. `node:fs/promises` is imported
57
- * dynamically and guarded by `isNodeRuntime()`, so it is only *loaded* in Node.
58
- * Note: the dynamic import defers execution, not bundling — a browser-targeting
59
- * bundler still statically resolves this literal specifier, so a browser consumer
60
- * of the main entry must mark `node:*` external. A page that stores a file uses the
61
- * browser-safe `@pipelex/sdk/upload` entry (`upload-grant.ts`) instead, which never
62
- * reaches this module; making the main entry browser-safe as a whole stays open.
80
+ * being silently misread as text or a URL. The file system comes from the Node
81
+ * host (`node-host.ts`), which a bundler building for the browser resolves to a
82
+ * module holding no Node built-in, so this module keeps the main entry
83
+ * bundleable for the browser.
63
84
  */
64
85
  export declare function readLocalPath(path: string): Promise<Uint8Array>;
65
86
  /** Base64-encode bytes, cross-runtime (Buffer when present, else chunked btoa). */
66
87
  export declare function bytesToBase64(bytes: Uint8Array): string;
67
88
  /**
68
- * Upload one local asset and return its {@link UploadRecord}. Maps the raw
69
- * `upload()` transport errors onto the semantic input-preparation errors: a
70
- * `413` is a rejected asset, `401`/`403` an auth failure, `404` an unsupported
71
- * upload capability, an unreachable host a transport failure.
89
+ * Upload one local asset and return its {@link UploadRecord}. A `kind` and its `method_id` in
90
+ * the options are checked before the asset is read and sent with it (see
91
+ * {@link UploadKindOptions}). Maps the raw `upload()` transport errors onto the semantic
92
+ * input-preparation errors: a `413` is a rejected asset, a test file refused for its role or its
93
+ * method a {@link TestFileRefusedError}, any other `401`/`403` an auth failure, any other `404`
94
+ * an unsupported upload capability, an unreachable host a transport failure.
72
95
  */
73
96
  export declare function uploadFile(client: UploadCapableClient, asset: UploadableAsset, options?: UploadFileOptions): Promise<UploadRecord>;
package/dist/upload.js CHANGED
@@ -7,9 +7,68 @@
7
7
  * The MIME type and size are known client-side at upload time, so the record is
8
8
  * built without extending the `/v1/upload` response.
9
9
  */
10
- import { InvalidLocalSourceError, RejectedAssetError, UploadAuthenticationError, UploadTransportError, UnsupportedUploadCapabilityError, } from "./errors.js";
10
+ import { InputPreparationError, InvalidLocalSourceError, RejectedAssetError, TestFileRefusedError, UploadAuthenticationError, UploadTransportError, UnsupportedUploadCapabilityError, } from "./errors.js";
11
11
  import { ApiResponseError, ApiUnreachableError } from "./errors.js";
12
+ import { parseMethodSelector } from "./method-selector.js";
13
+ import { absenceCause, loadNodeHost } from "./node-host.js";
12
14
  import { throwIfAborted } from "./runs.js";
15
+ /** The kinds an upload is recorded as, in the order the refusal of another one lists them. */
16
+ const UPLOAD_KINDS = ["uploaded", "test"];
17
+ /** A value an upload's kind or method was given as, described for a refusal without quoting more than an id. */
18
+ function describedArgument(value) {
19
+ if (value === null)
20
+ return "null";
21
+ if (typeof value === "string")
22
+ return JSON.stringify(value);
23
+ return typeof value;
24
+ }
25
+ /**
26
+ * The `kind` and `method_id` an upload is sent with, checked as the upload route checks them, so
27
+ * a request the route would refuse with a `422` is refused before any file is read: a `kind` is
28
+ * `uploaded` or `test`, a `method_id` is a bare catalog id, `kind: "test"` needs one and only a
29
+ * test file names one. Returns the members to send, each only when given; `null` reads as
30
+ * absent, as it does on the route. A refusal is an `InputPreparationError` whose message opens
31
+ * with `refusal` and names each member as `field` prefixes it, so `prepareInputs` names its
32
+ * `upload.kind`.
33
+ */
34
+ export function uploadKindFields(given, refusal, field = "") {
35
+ const raw = given;
36
+ const kind = raw.kind ?? undefined;
37
+ const methodId = raw.method_id ?? undefined;
38
+ if (kind !== undefined && !UPLOAD_KINDS.includes(kind)) {
39
+ throw new InputPreparationError(`${refusal}: \`${field}kind\` is "uploaded" or "test", got ${describedArgument(kind)}.`);
40
+ }
41
+ if (methodId !== undefined) {
42
+ if (typeof methodId !== "string") {
43
+ throw new InputPreparationError(`${refusal}: \`${field}method_id\` names a method by its bare catalog id, got ` +
44
+ `${describedArgument(methodId)}.`);
45
+ }
46
+ let bare;
47
+ try {
48
+ bare = parseMethodSelector(methodId).version === null;
49
+ }
50
+ catch (cause) {
51
+ throw new InputPreparationError(`${refusal}: \`${field}method_id\` ${JSON.stringify(methodId)} is not a catalog id, ` +
52
+ "which is mt_ followed by letters, digits, _ or -.", { cause });
53
+ }
54
+ if (!bare) {
55
+ throw new InputPreparationError(`${refusal}: \`${field}method_id\` names the method a test file is for by its bare ` +
56
+ `catalog id, with no version suffix, got ${JSON.stringify(methodId)}.`);
57
+ }
58
+ }
59
+ if (kind === "test" && methodId === undefined) {
60
+ throw new InputPreparationError(`${refusal}: a test file names the method it is for; give \`${field}method_id\` with ` +
61
+ `\`${field}kind: "test"\`.`);
62
+ }
63
+ if (kind !== "test" && methodId !== undefined) {
64
+ throw new InputPreparationError(`${refusal}: only a test file names a method; give \`${field}method_id\` with ` +
65
+ `\`${field}kind: "test"\` alone.`);
66
+ }
67
+ return {
68
+ ...(kind === undefined ? {} : { kind: kind }),
69
+ ...(methodId === undefined ? {} : { method_id: methodId }),
70
+ };
71
+ }
13
72
  const DEFAULT_CONTENT_TYPE = "application/octet-stream";
14
73
  const DEFAULT_FILENAME = "upload.bin";
15
74
  const EXTENSION_MIME = {
@@ -28,10 +87,6 @@ const EXTENSION_MIME = {
28
87
  csv: "text/csv",
29
88
  json: "application/json",
30
89
  };
31
- /** True when running under Node (has `process.versions.node`). */
32
- export function isNodeRuntime() {
33
- return (typeof process !== "undefined" && process.versions != null && process.versions.node != null);
34
- }
35
90
  /** Filename tail of a path, splitting on both separators (no `node:path` import needed). */
36
91
  function basename(path) {
37
92
  const parts = path.split(/[\\/]/);
@@ -48,22 +103,22 @@ export function guessContentType(filename) {
48
103
  /**
49
104
  * Read a filesystem path into bytes — Node only. A path string in a non-Node
50
105
  * runtime cannot be read, so it fails as an invalid local source rather than
51
- * being silently misread as text or a URL. `node:fs/promises` is imported
52
- * dynamically and guarded by `isNodeRuntime()`, so it is only *loaded* in Node.
53
- * Note: the dynamic import defers execution, not bundling — a browser-targeting
54
- * bundler still statically resolves this literal specifier, so a browser consumer
55
- * of the main entry must mark `node:*` external. A page that stores a file uses the
56
- * browser-safe `@pipelex/sdk/upload` entry (`upload-grant.ts`) instead, which never
57
- * reaches this module; making the main entry browser-safe as a whole stays open.
106
+ * being silently misread as text or a URL. The file system comes from the Node
107
+ * host (`node-host.ts`), which a bundler building for the browser resolves to a
108
+ * module holding no Node built-in, so this module keeps the main entry
109
+ * bundleable for the browser.
58
110
  */
59
111
  export async function readLocalPath(path) {
60
- if (!isNodeRuntime()) {
61
- throw new InvalidLocalSourceError(`Cannot read the local path "${path}" outside Node — path strings are Node-only. ` +
62
- "In the browser or an edge runtime, pass the file as bytes (Blob/File/ArrayBuffer/Uint8Array).", path);
112
+ const loaded = await loadNodeHost();
113
+ if (loaded.host === null) {
114
+ throw new InvalidLocalSourceError(loaded.reason === null
115
+ ? `Cannot read the local path "${path}" outside Node — path strings are Node-only. ` +
116
+ "In the browser or an edge runtime, pass the file as bytes (Blob/File/ArrayBuffer/Uint8Array)."
117
+ : `Cannot read the local path "${path}": ${loaded.reason}`, path, absenceCause(loaded));
63
118
  }
119
+ const host = loaded.host;
64
120
  try {
65
- const { readFile } = await import("node:fs/promises");
66
- return await readFile(path);
121
+ return await host.fs.readFile(path);
67
122
  }
68
123
  catch (cause) {
69
124
  throw new InvalidLocalSourceError(`Local file cannot be read: "${path}" (${cause.code ?? "read error"}).`, path, { cause });
@@ -106,22 +161,25 @@ function assetBytesFromBuffer(bytes, options) {
106
161
  return { bytes, filename, contentType: options.contentType ?? guessContentType(filename) };
107
162
  }
108
163
  /**
109
- * Upload one local asset and return its {@link UploadRecord}. Maps the raw
110
- * `upload()` transport errors onto the semantic input-preparation errors: a
111
- * `413` is a rejected asset, `401`/`403` an auth failure, `404` an unsupported
112
- * upload capability, an unreachable host a transport failure.
164
+ * Upload one local asset and return its {@link UploadRecord}. A `kind` and its `method_id` in
165
+ * the options are checked before the asset is read and sent with it (see
166
+ * {@link UploadKindOptions}). Maps the raw `upload()` transport errors onto the semantic
167
+ * input-preparation errors: a `413` is a rejected asset, a test file refused for its role or its
168
+ * method a {@link TestFileRefusedError}, any other `401`/`403` an auth failure, any other `404`
169
+ * an unsupported upload capability, an unreachable host a transport failure.
113
170
  */
114
171
  export async function uploadFile(client, asset, options = {}) {
172
+ const claimed = uploadKindFields(options, "Cannot upload");
115
173
  const { bytes, filename, contentType } = await toAssetBytes(asset, options);
116
174
  // A slow read, or a Blob's `arrayBuffer()`, may outlast the caller's patience.
117
175
  throwIfAborted(options.signal);
118
176
  const data = bytesToBase64(bytes);
119
177
  let uploaded;
120
178
  try {
121
- uploaded = await client.upload({ filename, data, content_type: contentType });
179
+ uploaded = await client.upload({ filename, data, content_type: contentType, ...claimed });
122
180
  }
123
181
  catch (error) {
124
- throw mapUploadError(error, filename);
182
+ throw mapUploadError(error, filename, claimed.method_id);
125
183
  }
126
184
  // The record guarantees a filename: an answer that names none keeps the one sent.
127
185
  const stored = uploaded.filename;
@@ -132,8 +190,42 @@ export async function uploadFile(client, asset, options = {}) {
132
190
  size: bytes.length,
133
191
  };
134
192
  }
193
+ /**
194
+ * Which refusal of a test file an upload's error is, or `undefined` for any other. Each is read
195
+ * off the platform's code, never the status alone: a `403` without `role_not_permitted` is a
196
+ * refused credential, and a `404` is an unknown method only when the upload named one and the
197
+ * answer is the platform's domain miss (`not_found`, `input`), not the `404` of a deployment
198
+ * without the route, which the platform answers as `config` and a bare runner without a code.
199
+ */
200
+ function testFileRefusalOf(error, methodId) {
201
+ if (error.code === "role_not_permitted")
202
+ return "role_not_permitted";
203
+ if (error.code === "method_being_deleted")
204
+ return "method_being_deleted";
205
+ if (error.status === 404 &&
206
+ error.code === "not_found" &&
207
+ error.errorDomain === "input" &&
208
+ methodId !== undefined) {
209
+ return "method_not_found";
210
+ }
211
+ return undefined;
212
+ }
213
+ /** The message of a test file's refusal, naming the file, the method and what to do. */
214
+ function testFileRefusalMessage(code, error, filename, methodId) {
215
+ const refused = `Upload of "${filename}" as a test file was refused (${error.status})`;
216
+ const method = methodId === undefined ? "the method it names" : `the method "${methodId}"`;
217
+ switch (code) {
218
+ case "role_not_permitted":
219
+ return (`${refused}: ${error.serverMessage ?? "the caller's role may not change methods"}. ` +
220
+ 'Upload it as "uploaded", or with the credential of a member who may change methods.');
221
+ case "method_not_found":
222
+ return `${refused}: ${method} is not one of the organization's methods.`;
223
+ case "method_being_deleted":
224
+ return `${refused}: ${method} is being deleted.`;
225
+ }
226
+ }
135
227
  /** Translate a raw `upload()` transport error into the matching preparation error. */
136
- function mapUploadError(error, filename) {
228
+ function mapUploadError(error, filename, methodId) {
137
229
  if (error instanceof ApiResponseError) {
138
230
  // A 2xx the client could not read: storage may hold the file, but under no reference the SDK
139
231
  // can return.
@@ -141,6 +233,18 @@ function mapUploadError(error, filename) {
141
233
  return new UploadTransportError(`Upload of "${filename}" was answered (${error.status}) with a body the SDK could not ` +
142
234
  "read, so whether and where the file was stored is unknown.", { cause: error, status: error.status, code: "unexpected", filename });
143
235
  }
236
+ // A test file refused for what it claims, its role or its method, before the status alone is
237
+ // read as a refused credential or a deployment without upload.
238
+ const refusal = testFileRefusalOf(error, methodId);
239
+ if (refusal !== undefined) {
240
+ return new TestFileRefusedError(testFileRefusalMessage(refusal, error, filename, methodId), {
241
+ cause: error,
242
+ status: error.status,
243
+ code: refusal,
244
+ filename,
245
+ ...(methodId === undefined ? {} : { methodId }),
246
+ });
247
+ }
144
248
  switch (error.status) {
145
249
  case 413:
146
250
  return new RejectedAssetError(`The server rejected "${filename}": ${error.serverMessage ?? "asset exceeds the service size limit"}.`, filename, error.status, { cause: error, code: "too_large" });
@@ -1 +1 @@
1
- {"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"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"}
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.48.0";
6
+ export declare const SDK_VERSION = "0.49.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.48.0";
6
+ export const SDK_VERSION = "0.49.0";
7
7
  //# sourceMappingURL=version.js.map
@@ -38,7 +38,28 @@ The package also declares one `bin`, `pipelex-sdk`, which is `dist/cli.js` with
38
38
 
39
39
  Condition order in `exports` matters: `types` is listed first (TS/Node resolution convention — the type-checker looks at the first matching condition), then `import`, then `default` as the CJS fallback.
40
40
 
41
- The map has a second entry, **`@pipelex/sdk/upload`** (`dist/upload-grant.js`), the browser-safe one. The main entry is Node-first — `upload.ts` names `node:fs/promises` and `artifacts.ts` names `undici`, so a browser-targeting bundler must mark them external — because the client holds an API key and belongs on a server. The subpath carries only what a page needs to store a file with an upload grant: `uploadWithGrant`, the grant types and the three error classes it raises. Its runtime import graph is `errors.ts` (with the fallback table it reads, `error-verdicts.ts`), `timers.ts` and `mthds/protocol`, nothing else, and the unit suite bundles it for the browser with esbuild, nothing marked external, so a Node-only module becoming reachable fails a test rather than a consumer's build. Both entries load the same `dist/errors.js` and `dist/upload-grant.js`, so the function and the error classes are identical whichever entry a consumer imports them through. Whether the main entry should become browser-safe as a whole is a separate question, because it touches how path strings are read.
41
+ The map has a second entry, **`@pipelex/sdk/upload`** (`dist/upload-grant.js`), the one a page that stores a file imports. The client holds an API key and belongs on a server, so a page imports the main entry for its error classes, its types and its pure helpers, never to construct a client, and the subpath carries only what a page needs to store a file with an upload grant: `uploadWithGrant`, the grant types and the three error classes it raises. Its runtime import graph is `errors.ts` (with the fallback table it reads, `error-verdicts.ts`), `timers.ts` and `mthds/protocol`, nothing else. Both entries load the same `dist/errors.js` and `dist/upload-grant.js`, so the function and the error classes are identical whichever entry a consumer imports them through.
42
+
43
+ **Both entries bundle for the browser with nothing marked external.** A Next.js client component importing an error class from `@pipelex/sdk` puts the barrel's whole graph in the browser bundle, the modules it loads by dynamic import included, since a bundler bundles a dynamic import with a literal specifier like a static one; in 0.48.0 that graph reached `node:fs` through the `mthds` package's root entry, which Turbopack's `next build` tolerated and its `next dev` did not, panicking and answering `500` for the page. `tests/browser-bundle.test.ts` is the gate the templates' builds are not: it builds the package with `tsc` into a scratch directory laid out as the published package, bundles every entry of the `exports` map for the browser with esbuild, and fails on any Node built-in, `undici` or the `mthds` root entry it reaches, naming each import and the module that made it.
44
+
45
+ ### The Node host
46
+
47
+ What only Node has, `node:fs/promises`, `node:path`, the `mthds` root entry's tree lister and `undici`'s `Agent`, sits behind one module, the Node host, which the rest of the SDK reaches through `loadNodeHost()` in `src/node-host.ts`. It imports `#node-host`, the package's own subpath import, and the `imports` map of `package.json` resolves it by condition:
48
+
49
+ - under `node`, to `dist/node-host/node.js` (`src/node-host/node.ts`), which loads the two built-ins with it, and the `mthds` root entry and `undici` on their first use, by a dynamic import;
50
+ - under every other condition set, a browser's or an edge runtime's, to `dist/node-host/unavailable.js` (`src/node-host/unavailable.ts`), which imports nothing and says the host is absent.
51
+
52
+ Node, a Next.js server bundle and the hook bundle resolve the host; a browser bundle resolves its absence, so the barrel's graph there holds no Node built-in. `loadNodeHost()` never throws: it answers the host, or its absence with the reason a refusal gives, in three cases.
53
+
54
+ - **Outside Node**, wherever `process.versions.node` is absent, it loads nothing and gives no reason, and each operation makes the refusal it always made there.
55
+ - **Inside Node, without the `node` condition**, as a bundle built for esbuild's `neutral` platform resolves the package, `#node-host` resolves to the absent variant, and the reason names the missing condition and how to bundle with it.
56
+ - **Inside Node, where the import itself fails**, as in a toolchain that does not resolve the `imports` map of `package.json` (Jest before 29.4, whose error is `ERR_PACKAGE_IMPORT_NOT_DEFINED` or "Cannot find module '#node-host'") or in a package installed in part, the reason says the host could not be loaded, names that toolchain and quotes the import's own error, which the refusal carries as its `cause`. It never claims the `node` condition was missing.
57
+
58
+ Each operation that needs Node refuses with its own class and verdict whichever the case, and the reason after its own subject: reading a path string is an `InvalidLocalSourceError`, `downloadArtifacts` an `ArtifactOperationError` (`config`), a `directory` a `RequestArgumentError` (`input`) and `writeMethodTree` a `RequestArgumentError` (`config`). A call given a `directory` asks for the host before its version handshake, so a failed or refused handshake never hides that refusal. An artifact fetch is never refused: without the host, or where `undici` cannot be loaded, it runs on the platform's default dispatcher. `tests/node-host.test.ts`, `tests/node-host-unresolved.test.ts` and `tests/node-host-import-fails.test.ts` hold each case, and `tests/browser-bundle.test.ts` runs the import failure under Node itself, against a built copy of the package whose `package.json` has no `imports` map.
59
+
60
+ The package's own conditions were chosen over marking each dynamic import for bundlers to leave alone (`/* webpackIgnore: true */`, `/* turbopackIgnore: true */`): only webpack and Turbopack read those marks, so esbuild, Vite and Rollup would still bundle Node into a page, and an import left alone is resolved at run time from wherever the bundle landed, which finds the package's own `mthds` or `undici` only when the package manager hoisted it beside the application, and a strict layout such as pnpm's does not. Conditions are read by every bundler and resolve from the package itself.
61
+
62
+ The `imports` map points into `dist/`, which is what ships, so the tools that run the source map `#node-host` to the source of its Node target: `tsconfig.json`'s `paths` for the type checker, which `tests/browser-bundle.test.ts` holds to the map, and `vitest.config.ts` and `scripts/build-hook.mjs`, which derive it from the map. `dependency-cruiser` keeps every other module of the barrel's graph off a Node built-in, `undici` and the `mthds` root entry, type-only imports apart, and nothing imports a variant by its path.
42
63
 
43
64
  ## Wire conventions
44
65
 
@@ -91,12 +112,12 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
91
112
 
92
113
  - `client.ts` — `PipelexApiClient`: the `request()` pipeline (auth, base URL, timeouts/abort, problem-details parsing) and every route. It implements the standard's `MTHDSProtocol<DictPipeOutput>` for every route but `validate`, so the protocol-execution methods stay shaped like the standard: `validate` answers Pipelex's own verdict, `PipelexValidationResult`, because the hosted plane still sends the verdict that predates the protocol's problem items, where the standard's `ValidationResult` lists them.
93
114
  - `models.ts` — Dict-serialized concretes (`DictStuff` / `DictWorkingMemory` / `DictPipeOutput` / `DictRunResultExecute`), the `/v1/validate` surface (`PipelexValidationResult`, whose two arms are declared whole rather than as narrowings of the standard's `ValidationResult`, `ValidationErrorItem`, and the `SuggestedFix` / `FixOp` repair vocabulary its items carry — the hosted plane's shapes, which predate the standard's problem items, their operation and safety vocabularies pinned to the standard's by a type-level test, see [errors.md](./errors.md#a-validation-item--validationerroritem)), the tools surface (`Diagnostic` / `LintResponse` / `FormatResponse`), and the crate-route request/response models — the shared `CrateRequestBase` / `CrateInvalidReport` vocabulary plus the `/v1/resolve`, `/v1/codegen` and `/v1/pipe-io` shapes built on it, `GeneratedArtifact` among them.
94
- - `product-models.ts` — the snake_case wire shapes for the hosted-product management routes (profile, methods catalog, organizations, billing, API keys, onboarding, storage, runs list/detail/update). Each interface models only the fields the product actually consumes — no speculative mirrors.
115
+ - `product-models.ts` — the snake_case wire shapes for the hosted-product management routes (profile, methods catalog, organizations, billing, API keys, onboarding, storage, an organization's files, runs list/detail/update). Each interface models only the fields the product actually consumes — no speculative mirrors.
95
116
  - `error-models.ts` — the error-report wire models: `RunErrorReport`, the runner's `ErrorReport` typed with every field it carries (with `UserAction`, `ProviderErrorMetadata` and `MigrationErrorBlock`), which is the one type for a failed run's report on `RunPublic.error`, `PipelineRun.error`, `RunHistoryItem.error`, the failed arm of `RunResultState` and `RunFailedError.error`; and `ProblemDetails` / `FieldError`, the typed members of a problem document that `ApiResponseError` exposes. Every field is optional and every interface keeps an index signature, because the runner owns the shape and adds fields without asking. It also holds the one function that checks a stored report, the internal `readRunErrorReport`, which every read handing a report back applies: each named field is kept only when it has its declared type, and members the SDK does not name are relayed as sent. It imports the `ValidationErrorItem` type and nothing else. See [errors.md](./errors.md#a-failed-run--runerrorreport).
96
- - `method-trees.ts` — a saved method's tree on the client side: `MethodTreeSource`, the shape a save reads a tree from, which the `mthds` package's `MethodTree` satisfies; the choice between sending a tree inline and by hash (`INLINE_TREE_MAX_BYTES`), the wire types of `POST /v1/blobs/missing` and the upload of a blob the platform lacks; `redactSignedLinks`, which every error built from an answer holding signed links reads its body through; the tree of an in-memory map; the tree limits a version handshake announces; the listing of a `directory`, which loads the `mthds` package's root entry, and with it Node's file system, by a dynamic `import("mthds")` only when a directory is given; and the Node-only write of a saved listing to disk, whose Node modules it loads when called. It is the one module that reaches past `mthds/protocol`, and only by that dynamic import, which `dependency-cruiser` holds it to. Its tree-sending half mirrors the `mthds` package's own helpers, which the `mthds/protocol` subpath does not export yet, so it holds the same rules with Web Crypto's SHA-256 and a Base64 writer of its own. See [method-trees.md](./method-trees.md).
97
- - `codegen-check.ts` — `runCodegenCheck`, the **pure** offline drift check over a committed codegen tree: the stamp grammar, the `codegen.lock` parser, the hash, and the drift taxonomy, ported from pipelex's `codegen/check.py` so a verdict computed here equals the one `pipelex codegen check` computes over the same bytes (the drift `detail` sentences included, verbatim). It touches neither the client nor `mthds/protocol` — its only runtime imports are `smol-toml` and the plain-object guard of the pure `error-models.ts` — and it hashes through WebCrypto rather than `node:crypto` so it adds no Node builtin to the barrel's import graph (`upload.ts` still names `node:fs/promises`, so a browser-targeting bundler must mark `node:*` external). It raises `CodegenLockError`, which extends `Error` rather than the protocol base and declares the error verdict itself (`input`, not retryable). No filesystem, no fetch, no API key — the caller walks its own tree and hands in the text. See [crate-routes.md](./crate-routes.md#the-offline-check--runcodegencheck).
117
+ - `method-trees.ts` — a saved method's tree on the client side: `MethodTreeSource`, the shape a save reads a tree from, which the `mthds` package's `MethodTree` satisfies; the choice between sending a tree inline and by hash (`INLINE_TREE_MAX_BYTES`), the wire types of `POST /v1/blobs/missing` and the upload of a blob the platform lacks; `redactSignedLinks`, which every error built from an answer holding signed links reads its body through; the tree of an in-memory map; the tree limits a version handshake announces; the listing of a `directory`, by the `mthds` package's lister, which it gets from the Node host only when a directory is given; and the Node-only write of a saved listing to disk, with the file system it gets from the Node host when called. Outside Node, both are refused with `RequestArgumentError`. Its tree-sending half mirrors the `mthds` package's own helpers, which the `mthds/protocol` subpath does not export yet, so it holds the same rules with Web Crypto's SHA-256 and a Base64 writer of its own. See [method-trees.md](./method-trees.md).
118
+ - `codegen-check.ts` — `runCodegenCheck`, the **pure** offline drift check over a committed codegen tree: the stamp grammar, the `codegen.lock` parser, the hash, and the drift taxonomy, ported from pipelex's `codegen/check.py` so a verdict computed here equals the one `pipelex codegen check` computes over the same bytes (the drift `detail` sentences included, verbatim). It touches neither the client nor `mthds/protocol` — its only runtime imports are `smol-toml` and the plain-object guard of the pure `error-models.ts` — and it hashes through WebCrypto rather than `node:crypto` so it adds no Node builtin to the barrel's import graph. It raises `CodegenLockError`, which extends `Error` rather than the protocol base and declares the error verdict itself (`input`, not retryable). No filesystem, no fetch, no API key — the caller walks its own tree and hands in the text. See [crate-routes.md](./crate-routes.md#the-offline-check--runcodegencheck).
98
119
  - `upload-grant.ts` — `uploadWithGrant`, the sender of an upload grant (`requestUploadGrant`, `POST /v1/upload/grant`): the file as the raw body of a `PUT` to the grant's URL with its signed headers unchanged, redirects refused, the whole exchange bounded by a time limit scaled by the file's size unless the caller passes `timeoutMs`, only the first 16 KiB of storage's error body read, and storage's refusals mapped onto the input-preparation errors (`RejectedAssetError` with a `code` for a `4xx` such as the `412` of a used grant or the `403` of a signature mismatch, `UploadTransportError` with a `code` for the time limit running out, a `5xx`, storage's `RequestTimeout`, a `409` conflict, a redirect or no response). It is the browser-safe `@pipelex/sdk/upload` entry, so it imports the error classes, the timer bound in `timers.ts` and the wire types only, never the client. See [input-preparation.md](./input-preparation.md#uploading-with-a-grant--requestuploadgrant--uploadwithgrant).
99
- - `artifacts.ts` — the artifact stack, the download twin of `prepareInputs`, in layers: `locateArtifacts` (a pure, exported walk for `pipelex-storage://` references, each with every `$`-rooted path it sits at) and `collectArtifacts` (the same walk's bare references), `resolveArtifacts` (the bulk resolve route over a whole list, chunked at its bound), `fetchArtifact` (one reference as a bounded `Response`) and `downloadArtifacts` (Node-only, a run's produced files saved under a directory as a produced verdict), plus `artifactFilename`, the rule that names a saved file after the field it fills. The three network operations are client methods over a small structural seam (`ArtifactCapableClient`: the raw `resolveStorageUrls` call and `getRunResult`); the module's static import graph stays free of Node builtins, because `node:fs/promises`, `node:path` and `undici` are loaded by dynamic import behind `isNodeRuntime()` — a browser-targeting bundler still has to mark `node:*` and `undici` external, as it already must for `upload.ts`. `undici` is a runtime dependency for one reason: the fetch runs on an `Agent` carrying `headersTimeout` and `bodyTimeout`, the per-stall bounds an `AbortSignal` budget does not give, and Node exposes its bundled undici's `fetch` but not its `Agent`. See [artifact-download.md](./artifact-download.md).
120
+ - `artifacts.ts` — the artifact stack, the download twin of `prepareInputs`, in layers: `locateArtifacts` (a pure, exported walk for `pipelex-storage://` references, each with every `$`-rooted path it sits at) and `collectArtifacts` (the same walk's bare references), `resolveArtifacts` (the bulk resolve route over a whole list, chunked at its bound), `fetchArtifact` (one reference as a bounded `Response`) and `downloadArtifacts` (Node-only, a run's produced files saved under a directory as a produced verdict), plus `artifactFilename`, the rule that names a saved file after the field it fills. The three network operations are client methods over a small structural seam (`ArtifactCapableClient`: the raw `resolveStorageUrls` call and `getRunResult`); the module's graph, static and dynamic, stays free of Node builtins, because `node:fs/promises`, `node:path` and `undici` come from the Node host. `undici` is a runtime dependency for one reason: the fetch runs on an `Agent` carrying `headersTimeout` and `bodyTimeout`, the per-stall bounds an `AbortSignal` budget does not give, and Node exposes its bundled undici's `fetch` but not its `Agent`. See [artifact-download.md](./artifact-download.md).
100
121
  - `runs.ts` — the run-lifecycle types (`RunStatus`, `RunRead`, `RunResults`, `RunResultState`) and the single poll loop (`pollUntilResult`). `RunResults.main_stuff` (the resolved main output content) is always present for a completed run: on the hosted path it is the `main_stuff.json` S3 artifact; on the bare-runner blocking path the SDK resolves it from the returned working memory via the response's `main_stuff_name`, so both paths deliver the same shape. Consumers read `main_stuff` directly. `working_memory` — every named stuff of the run, `{ root, aliases }`, typed `DictWorkingMemory` from `models.ts` — reads the same on both paths: the hosted path relays the `working_memory.json` artifact, and the blocking path lifts it off `pipe_output`, which still rides whole (blocking path only). `graph_spec` — the executed graph — also reaches the client on both paths: the hosted path relays the `graphspec.json` artifact, and the blocking path lifts it off `pipe_output` beside `graph_assembly_error`, the graph's twin of `usage_assembly_error`. That error field is declared ahead of the wire on the hosted side, where the platform's results body relays no such key and the field is therefore absent, reading `undefined` rather than `null`. A completed run that cannot deliver a main stuff throws `MissingMainStuffError` when the read asked for it. `runs.ts` also declares `RUN_RESULT_ARTIFACTS` / `RunResultArtifact`, the artifacts a results read can be narrowed to with `getRunResult`'s `artifacts` option; with a selection an unselected artifact is absent while a selected unwritten one is `null`. `runs.ts` also owns `TokensUsageRecord` — one client-facing usage record per inference call, carried by `RunResults.tokens_usages` on both paths. That is a mirror of a wire contract this SDK does not own (inference accounting is a Pipelex runtime extension rather than part of the MTHDS standard): every field is optional and the interface carries an index signature so pre-contract artifacts (relayed verbatim, never migrated) still type-check. See [`run-usage.md`](./run-usage.md) for the field reference, the cost/null semantics, and the old-artifact rules, and [`run-results.md`](./run-results.md) for the field-by-field account of `RunResults` — the durable handle, the output and how to read it, the run's working memory, the executed graph and how to render or keep it, and the produced files with their short-lived links.
101
122
  - `usage.ts` — `summarizeUsage`, the **pure** fold of a run's usage pair (`tokens_usages` / `usage_assembly_error`) into one `UsageSummary`: a `state` (`records`, `no_inference` or `unavailable`), the null-aware total cost with its `cost_partial` flag, the `input` and `output` token totals, the call count, the relayed assembly error, and a `by_pipe` rollup sorted by cost with the unattributed calls grouped under a `null` `pipe_code`. It applies the rules [`run-usage.md`](./run-usage.md#summarizing-a-run--summarizeusage) states, so that every consumer reads one computation rather than re-deriving those rules by hand. It imports types from `runs.ts` and nothing else: no client, no fetch, no `mthds/protocol`.
102
123
  - `execute-result.ts` — `PipelexExecuteResult`, a `DictRunResultExecute` subtype that adds a resolved `.main_stuff` accessor (dug out of `pipe_output`'s working memory via the response's `main_stuff_name`, throwing `MissingMainStuffError` if unlocatable). `execute()` returns it, giving blocking and durable results the **same output accessor** — `result.main_stuff` — so callers never branch on which path ran. The neutral `mthds` wire model leaves `main_stuff_name` in its extension index signature; this Pipelex-branded subtype declares it as a typed field (Pipelex owns that concept). `resultsFromExecute` (the public lift from that result onto `RunResults`, in the same module) reads that accessor too, keeping the resolution single-sourced; the `executeBlocking` fallback is its only caller inside the SDK, and a consumer driving `execute()` directly calls it by name.
@@ -106,6 +127,7 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
106
127
  - `user-agent.ts` — client identification per the spec `conformance/specs/client-identification.md`, in the `conformance` repository beside the specs' tests: the exported `AppInfo` option, its RFC 9110 token validation (a `TypeError` on a bad field, a `RangeError` past the 512-character ceiling), its rendering, the runtime detection (Node, Bun, Deno, or a browser, where no header is set) and `buildUserAgent`, which the client calls once at construction. The barrel re-exports `buildUserAgent`, `validateAppInfo`, `MAX_USER_AGENT_LENGTH` and the `RuntimeInfo` type, so a consumer's raw fetches send the same value. The client's private `requestHeaders` is the one place API request headers are built, so every route carries the value; the artifact fetch of a presigned link never goes through it. See [client-identification.md](./client-identification.md).
107
128
  - `exact-json.ts` — `parseExactJson` and `stringifyExactJson`, JSON whose integers stay exact, and `ExactJsonUnsupportedError`. The reader, which the client applies to every answer carrying a person's data (a method example, a run's record, a run's results, the blocking execute's answer), is one `JSON.parse` call whose reviver turns a number past `Number.MAX_SAFE_INTEGER` spelled as an integer into a `bigint` of its source text (`context.source`), so it reads everything else exactly as `JSON.parse` does, with no second parser to keep in step; a text holding no run of 16 digits is parsed with no reviver at all. On a runtime whose reviver gets no source text, it refuses a whole number past the safe range with `ExactJsonUnsupportedError`, which the client reports as an answer it cannot read, rather than rounding it. The writer, which `requestRaw` applies to every request body before the request is made, is one `JSON.stringify` call whose replacer turns each `bigint` into `JSON.rawJSON` text, so every getter and `toJSON` in the body runs once, and a `bigint` an inherited `toJSON` such as the `BigInt.prototype.toJSON` polyfill already turned into a string is looked up in its holder and written as its digits; a runtime without `JSON.rawJSON` refuses the `bigint`, as a `RequestArgumentError` from the client, rather than rounding it. The barrel exports all three, for a caller showing such data as JSON text; the error declares its verdict itself, `config` and not retryable, as `CodegenLockError` does. It imports nothing but the `ErrorDomain` type. See [exact-integers.md](./exact-integers.md).
108
129
  - `timers.ts` — `MAX_TIMER_DELAY_MS`, the longest delay `setTimeout` honours, and `isTimerDelay`, which every caller-supplied `timeoutMs` is checked against before it reaches a timer: a longer delay overflows and fires at once. It imports nothing, so the browser-safe upload entry uses it too.
130
+ - `node-host.ts` and `node-host/` — the Node host ([above](#the-node-host)): `loadNodeHost()` and `isNodeRuntime()` in `node-host.ts`, the Node variant `node-host/node.ts`, the only module of the barrel's graph that imports a Node built-in, `undici` or the `mthds` root entry, and `node-host/unavailable.ts`, the variant every other runtime resolves.
109
131
  - `version.ts` — `SDK_VERSION`, in its own module so the client stamps it on the `User-Agent` without importing the barrel, which re-exports it.
110
132
  - `cli.ts` and `cli/` — the `pipelex-sdk` command, `run` and `script` ([`cli.md`](cli.md)): `cli.ts` is the executable `package.json` declares as the package's one `bin`, handing the process to `cli/main.ts`'s `runCommand`, and `cli/` holds the flags, the bundle reader, the inputs, the environment, the two subcommands and what they print. It sits at the TOP of the graph, the other way round from every other module: nothing imports it, the barrel does not export it, and it reaches the SDK only through `index.ts`, which `dependency-cruiser` enforces in both directions.
111
133
  - `index.ts` — the public barrel: `export * from "mthds/protocol"` (single import source for the standard surface) plus the client, models, run types, the standalone pure helpers (`runCodegenCheck`, `summarizeUsage`, `locateArtifacts`, `collectArtifacts`, `artifactFilename`, `parseExactJson`, `stringifyExactJson`), and errors.
@@ -133,15 +155,19 @@ The hosted catalog/account routes the webapp drives. All ride the same `{base}/v
133
155
  - **Methods catalog:** `listMethods`, `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, `publishMethod`, `listMethodVersions`, `getMethodVersion`, `deleteMethod`. **A saved method has a draft and immutable published versions.** Its draft is a method tree with its `input_data`, which `writeDraft` (`PUT /v1/methods/{id}/draft`) replaces whole, without validating it; `updated_at` is the draft's token, which moves on every draft write and nothing else, and `draft_digest` the draft's tree digest (`sha256:` and 64 hex digits), `null` for an empty draft. A save takes the tree as `files`, sent as given, or as `directory`, `tree` or `contents`, sent inline or by hash after uploading the blobs the organization lacks, and a read lists the tree's files, each with a short-lived link to its bytes, which `writeMethodTree` writes to disk ([method-trees.md](./method-trees.md)). 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 method is stored as one item that keeps room for the summary a publish adds, so a draft that saves can always be published: a creation or a draft write is a `413` `payload_too_large` when it would grow the method past that room, below the store's item limit, and a rename is the same `413` 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 tree as it stood, its `tree_digest` (`null` for a version stored before methods were trees), 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}`, a `limit` from 1 to 100, 20 by default, and a `422` outside that range), and `getMethodVersion` reads one with its `files` listed as a method's are, or answers a `409` `conflict`, whose verdict is `runtime`, for a version stored before methods were trees and never rewritten as one. 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` not `null` and equal to `latest_published.tree_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** `MethodDetail`: no `files`, no `updated_at`, because neither is in the projection and putting the source back is what restored the truncation. Reach for `getMethod(id)` when you need a method's files. `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. `query.published` filters the same way, sent as `published=true` (the methods with a published version) or `published=false` (those never published), and combines with `q`. Each row carries the method's publish state, so a list tells its methods apart without reading each one: `latest_version` and `latest_published` (a `MethodVersionSummary`), both `null` when the method was never published, and `draft_digest`, `null` for an empty draft; all three are optional, absent from a platform that does not serve them. Never published is `latest_version === null`, the draft unchanged is a `draft_digest` that is not `null` and equals `latest_published.tree_digest`, and the draft ahead is two digests that differ. Both filters run after the index read, so **a filtered page can come back empty and still carry a `nextCursor`**: an empty page is not the end of the list, and only a `null` cursor is. `iterateMethods` is the async iterator that follows the cursor for callers who genuinely want everything (`for await (const m of client.iterateMethods())`); it keeps going **past empty pages**, since `{items: [], nextCursor: "…"}` means "nothing matched in the slice I just read, keep going". 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; `iterateRuns` shares the same loop. 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 (`MethodDetail`, 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, the latest version's summary and the draft's `files` on top of the write contract's `name` / `input_data`; the writes and the rename answer `MethodData`, the same method without `files`.
134
156
  - **Method examples:** `createMethodExample`, `listMethodExamples`, `iterateMethodExamples`, `getMethodExample`, `updateMethodExample`, `recordMethodExampleLatestRun`, `deleteMethodExample`, on `/v1/methods/{id}/examples`. An example is a named set of inputs saved on a method, shared across the organization, with an `input_revision` that rises each time its inputs change and the `latest_run_id` of those inputs, which an inputs edit clears. `recordMethodExampleLatestRun` records a run against the revision whose inputs it used and answers a `MethodExampleLatestRunResult` discriminated on `outcome`, `recorded` or `stale`, each arm carrying the `example`: a run that outlasted an edit is a verdict, never thrown, and an answer off the union is one the SDK cannot read; one helper, `readVerdict`, reads this verdict and `publishMethod`'s, the example checked once, by the reader every example goes through. Every answer carrying an example is read with `parseExactJson` and every request body written with `stringifyExactJson`, so an integer past `Number.MAX_SAFE_INTEGER` in the inputs reads as a `bigint` and goes back digit for digit, to an edit or as a run's inputs; `3.0` still reads as `3`, and inputs holding one sent back unchanged are an edit for the platform, which compares inputs as canonical JSON. Every example the client hands back, the page rows and the verdict's `example` included, is checked by `readMethodExample` against the fields the Python SDK's model requires, with the strict types it holds them to, an example breaking one being an answer the SDK cannot read. The list is paged oldest first by an opaque cursor, and a page may be short while `nextCursor` is set; `iterateMethodExamples` follows it through the loop `iterateMethods` and `iterateRuns` share, and refuses a method id at the call rather than at the first pull. Every method takes a bare catalog id, and the client refuses an example id that is empty, `.` or `..` or holds a lone surrogate, a create without its name or inputs or whose `run_id` is given but empty, an edit naming neither `name` nor `inputs`, and a latest-run write without a run id or a positive integer revision before sending. See [method-examples.md](./method-examples.md).
135
157
  - **Organizations:** `listMemberships`, `createOrganization`, `renameOrganization` (`PATCH`). Each membership carries the caller's `role_in_org`, an `OrgRole` (`admin`, `member` or `runner`) read open: a role a later platform adds arrives as its raw string, so a `switch` over it says what an unknown role does. The role is for display; what it may do is the platform's to decide, and a call refused to the caller's role is an `ApiResponseError`. The org "switch" is a WorkOS-session op (`/api/active-org`), not a `/v1` route — out of SDK scope.
158
+ - **Organization branding:** `getBranding` (`GET /v1/branding`, any role, web session or API key) and `updateOrganizationBranding` (`PATCH /v1/organizations/{org_id}/branding`, an admin's write) answer the same `OrganizationBranding`: the organization's name, its light and dark colour sets (`BrandingColors`) and its square and wide logos (`LogoDescriptor`). The write's `OrganizationBrandingUpdate` sets a key given a value, clears a key given `null` and keeps a key left out; a refused logo or colour is named in `ApiResponseError.errors`, by slot or by dotted key, a logo the platform measured with a `LogoRefusalCode`. See [organization-branding.md](./organization-branding.md).
136
159
  - **Billing:** `getSubscription`, `listPlans`, `listInvoices`, `createCheckout`, `changePlan` (409 `conflict` ⇒ no subscription), `getBillingPortal` (409 `conflict` ⇒ no subscription).
137
160
  - **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.
138
161
  - **Onboarding:** `submitOnboarding`.
139
162
  - **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 its tree, 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.
140
163
  - **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 SHA-256 of the sources it sent as text, 64 hex digits with no prefix and `null` for a run whose `files` held bytes as Base64 or by hash — what a history row shows. `getRunDetail` also carries the run's `tree_digest` and `crate_fingerprint`, each `null` for a run that recorded none. 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. Both also accept `createdBy: "me"`, sent as `created_by=me`, which keeps the runs the caller started (the platform resolves "me" from the authenticated identity and takes no other value), and `status`, a list of `RunStatus` sent as one repeated `status=` parameter per status, deduplicated into the platform's order, which keeps the runs in any of them: the caller's own latest unfinished run is `{ createdBy: "me", status: ["PENDING", "STARTED", "RUNNING"], limit: 1 }`. An empty `status` list would be sent as no parameter at all, which lists every status, so it is a `RequestArgumentError` before any request, as is a name that is no `RunStatus`. These two are filters applied after the index read, so **a filtered page can hold fewer runs than `limit`, or none, and still carry a `nextCursor`**: an empty page is not the end of the history, a `null` cursor is, and `iterateRuns` keeps following the cursor through one. It runs the same loop as `iterateMethods`, so it also stops on a cursor the server failed to advance and throws `PagingNotTerminatingError` past the same page ceiling. `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.
164
+ - **An organization's files:** `listFiles` (`GET /v1/files`) returns **one page** of the organization's listed files, newest first, a `FilePage` (`{items, nextCursor}`) of `FileSummary` rows, each with the `uri` a run's inputs take as any address, its name, type, size, `kind` (`uploaded`, `generated` or `test`) and `origin`. Its `ListFilesQuery` follows `ListRunsQuery`'s camelCase and is sent under the API's names: `methodId`, `runId` and `createdBy` choose the method's, the run's (in the order of its addresses), one member's or the organization's list, and `q`, `kind`, `format`, `mediaType`, `createdFrom` and `createdTo` filter it, `format` and `mediaType` as one repeated parameter per value, an empty list of either refused with `RequestArgumentError` before any request since it would be sent as no filter at all. The filters apply after the index read, so a filtered page can be short or empty and still carry a `nextCursor`, which is bound to the list and filters that issued it; `iterateFiles` follows it through the loop the other iterators share. An upload is recorded as a method's test file by `kind: "test"` with its `method_id`, the pair `UploadInput`, `uploadFile`'s options and `prepareInputs`' nested `upload` all take, checked before any file is read; the upload route's refusals of a test file, for the caller's role or for its method, are a `TestFileRefusedError`. See [files.md](./files.md).
141
165
 
142
166
  ## Hook bundle (`dist-hooks/check.mjs`)
143
167
 
144
- `src/hooks/` holds the post-edit `.mthds` hook pipeline (local lint/format via `@pipelex/tools-wasm`, then a `POST /v1/validate` verdict through this SDK), bundled into a single dependency-free ESM file by `npm run build:hook`. Its validate client is built by `src/hooks/validate-client.ts` with `appInfo: { name: "pipelex-mthds-check", version: SDK_VERSION }`, the `hook` surface's registry token, so the platform attributes its calls to the hook rather than to a plain SDK caller. The one specifier left external is `undici`, reached only by the artifact fetch path through a dynamic import the hook never executes, so the bundle does not inline an HTTP client it cannot use. **Distribution is source-checkout vendoring, not npm:** `pipelex-plugins` runs `make vendor-hook`, which builds `dist-hooks/check.mjs` in a sibling checkout of this repo and copies it in as a static hook asset — the published `@pipelex/sdk` tarball deliberately does not ship the bundle (it inlines the ~4 MB WASM engine, and no npm consumer of the SDK's library API uses it). `dist-hooks/` is gitignored; the consumer repo's own checks gate a stale vendored copy. `@pipelex/tools-wasm` is pinned to an exact version in `package.json`, because the engine carries the MTHDS schema the hook lints against: a newer schema reaches the hook only by a deliberate bump followed by a rebuild and a re-vendor, and the bundle's provenance line (`@pipelex/sdk <version> (<commit>) + @pipelex/tools-wasm <version> (npm)`) names the engine it embeds. Its tree gatherer (`src/hooks/bundle-gather.ts`) lists the edited file's directory with the `mthds` package's lister, which the bundle inlines, under the hook's own caps, and the validation sends that tree, the template files its `PipeDocGen` steps name included. The networked stage is `src/hooks/validate-stage.ts`: it holds the whole stage, the version handshake, the blob route, the uploads and the validation, to one deadline (`VALIDATE_TIMEOUT_MS`, ten seconds) whose signal it hands to `validate`, so the request or upload under way stops when it runs out and the stage reads as `unavailable`. Which file the hook checks on each platform, what it sends with it, and how it finds the file a Codex patch run through the shell wrote, is in [`hook-bundle.md`](./hook-bundle.md).
168
+ `src/hooks/` holds the post-edit `.mthds` hook pipeline (local lint/format via `@pipelex/tools-wasm`, then a `POST /v1/validate` verdict through this SDK), bundled into a single dependency-free ESM file by `npm run build:hook`. Its validate client is built by `src/hooks/validate-client.ts` with `appInfo: { name: "pipelex-mthds-check", version: SDK_VERSION }`, the `hook` surface's registry token, so the platform attributes its calls to the hook rather than to a plain SDK caller. The one specifier left external is `undici`, reached only by the artifact fetch path through the Node host's dynamic import, which the hook never executes, so the bundle does not inline an HTTP client it cannot use; the hook runs in Node, so the build resolves `#node-host` to the source of the Node host. **Distribution is source-checkout vendoring, not npm:** `pipelex-plugins` runs `make vendor-hook`, which builds `dist-hooks/check.mjs` in a sibling checkout of this repo and copies it in as a static hook asset — the published `@pipelex/sdk` tarball deliberately does not ship the bundle (it inlines the ~4 MB WASM engine, and no npm consumer of the SDK's library API uses it). `dist-hooks/` is gitignored; the consumer repo's own checks gate a stale vendored copy. `@pipelex/tools-wasm` is pinned to an exact version in `package.json`, because the engine carries the MTHDS schema the hook lints against: a newer schema reaches the hook only by a deliberate bump followed by a rebuild and a re-vendor, and the bundle's provenance line (`@pipelex/sdk <version> (<commit>) + @pipelex/tools-wasm <version> (npm)`) names the engine it embeds. Its tree gatherer (`src/hooks/bundle-gather.ts`) lists the edited file's directory with the `mthds` package's lister, which the bundle inlines, under the hook's own caps, and the validation sends that tree, the template files its `PipeDocGen` steps name included. The networked stage is `src/hooks/validate-stage.ts`: it holds the whole stage, the version handshake, the blob route, the uploads and the validation, to one deadline (`VALIDATE_TIMEOUT_MS`, ten seconds) whose signal it hands to `validate`, so the request or upload under way stops when it runs out and the stage reads as `unavailable`. Which file the hook checks on each platform, what it sends with it, and how it finds the file a Codex patch run through the shell wrote, is in [`hook-bundle.md`](./hook-bundle.md).
169
+
170
+ Before the validation sends anything, the gatherer reads every file of the tree once and screens it with `src/hooks/secret-guard.ts`, the workshop's secret guard (pipelex-mcp's `src/capabilities/secret-guard.ts`), ahead of the workshop's copy on its SSH key names and its PEM private key block, because the lister reads no `.gitignore` above the tree's root. A file named like a known secret file, or holding a well-known key format, stays home and is named in the note the verdict carries, placed after the verdict's text and named once for one tree, never quoted, and no note is shown when no verdict comes back; one that is a bundle or the manifest, which every validation reads, keeps the whole tree home, and the stage reads as `refused`. The tree sent serves the bytes screened, never read again ([`hook-bundle.md`](./hook-bundle.md#secrets-stay-home)).
145
171
 
146
172
  ## Testing
147
173
 
@@ -199,7 +199,7 @@ if (!report.isCurrent) {
199
199
 
200
200
  A `codegen()` response feeds straight in with no mapping — `GeneratedArtifact` and `CodegenTreeFile` are structurally identical on purpose, so `runCodegenCheck({ lockContent: result.lock, files: result.artifacts })` type-checks and reports `isCurrent`. That is worth doing right after a regeneration, before writing anything to disk.
201
201
 
202
- The function is `async` because it hashes through **WebCrypto** (`crypto.subtle`) rather than `node:crypto`, so the check adds no Node builtin to the barrel's import graph. (That is not the same as the barrel being browser-bundleable today: `upload.ts` still names `node:fs/promises`, so a browser-targeting bundler must mark `node:*` external.) One caveat that never bites a CI script but should not surprise anyone: `crypto.subtle` is secure-context-only, so a browser page must be served over https (or localhost).
202
+ The function is `async` because it hashes through **WebCrypto** (`crypto.subtle`) rather than `node:crypto`, so the check adds no Node builtin to the barrel's import graph, which bundles for the browser with nothing marked external ([architecture.md](./architecture.md#module-format--packaging)). One caveat that never bites a CI script but should not surprise anyone: `crypto.subtle` is secure-context-only, so a browser page must be served over https (or localhost).
203
203
 
204
204
  ### The algorithm
205
205
 
package/docs/errors.md CHANGED
@@ -50,8 +50,9 @@ On an error from a request that may have started a run, `retryable` is `false` w
50
50
  | `RunTimeoutError`, `RunStillRunningError` | `runtime` | yes | The run is still going; waiting again, by its id, can succeed. |
51
51
  | `RunLifecycleUnavailableError` | `config` | no | The base URL points at a bare runner without the run lifecycle. |
52
52
  | `MissingMainStuffError` | `runtime` | no | The API broke its own contract on a completed run. |
53
- | `PagingNotTerminatingError` | `runtime` | no | A server minting cursors forever; `pageLimit` is the ceiling `iterateMethods`, `iterateRuns` or `iterateMethodExamples` stopped at. |
53
+ | `PagingNotTerminatingError` | `runtime` | no | A server minting cursors forever; `pageLimit` is the ceiling `iterateMethods`, `iterateRuns`, `iterateMethodExamples` or `iterateFiles` stopped at. |
54
54
  | `InputPreparationError` and `InvalidLocalSourceError`, `InvalidInputValueError`, `UploadNotAllowedError`, `MethodLoadError`, `RejectedAssetError` | `input` | no | The asset, the inputs or the method selector must change. The family's base takes an optional `verdict` in its options, so a subclass of your own can declare another; `prepareInputs` declares `runtime` for a pipe I/O answer that breaks the route's contract: no boolean `is_valid`, an `input_form` that does not describe the pipe it selected, or a descriptor that is malformed or names a kind this SDK does not know. |
55
+ | `TestFileRefusedError` | the cause's, else by `code` | no | The upload route refused a test file (`kind: "test"`) for what it claims, never for its bytes. `uploadFile` and `prepareInputs` wrap the platform's `ApiResponseError` and take its verdict; without such a cause, `code` decides: `role_not_permitted`, the `403` of a role that may not change methods, is `config`, since an admin must change the role, and `method_not_found` (`404`) and `method_being_deleted` (`409`) are `input`, since the upload must name another method ([files.md](./files.md#uploading-a-test-file)). |
55
56
  | `UnsupportedUploadCapabilityError`, `UploadAuthenticationError` | `config` | no | The deployment or the credential must change. |
56
57
  | `UploadTransportError` | the cause's, else by `code` | the cause's, else by `code` | `uploadFile` wraps the `ApiResponseError` or `ApiUnreachableError` the client threw, and takes its verdict, so a wrapped `402` is `config` and not retryable. Without such a cause, which is always the case from `uploadWithGrant`: `timeout`, `storage_timeout` and `conflict` are `runtime` and retryable, `conflict` because storage documents its `409 ConditionalRequestConflict` as retryable and the grant is not spent by it (a spent grant is the `412` of a `RejectedAssetError` with code `grant_used`); `server_error` is `runtime`, and retryable when [the fallback table](#a-refused-requests-verdict) would call its `status` retryable, any `5xx` but the `501` storage answers for a request it does not implement, or when it carries no status; `unexpected` is `runtime`, and retryable when that table would call its `status` retryable, which is the `408` or `429` storage answers when it times out or throttles the request rather than refusing the file, and not retryable for any other status or none; `unreachable` is `config` and retryable; `redirected` is `config` and not retryable; `invalid_grant_url` is `runtime` and not retryable. |
57
58
  | `MethodFileTransferError` | `runtime` | by `code` | A method's file could not be moved between this machine and storage: uploaded by `createMethod` or `writeDraft`, so the method was not saved, or by a run, `validate`, `resolve`, `codegen`, `pipeIo` or `prepareInputs` sending a `directory`, `tree` or `contents` by hash, so the request was not sent, or downloaded by `writeMethodTree`. `unreachable` (no answer), `storage_error` (`408`, `429` or a `5xx` other than `501`) and `mismatch` (downloaded bytes that are not the file listed) are retryable; `refused`, any other status, an expired link among them, is not. It carries `operation`, `code`, `sha256`, `path` and `status`, and neither the link, nor storage's error body, nor a cause ([method-trees.md](./method-trees.md#when-a-transfer-fails)). |
@@ -317,6 +318,10 @@ A refusal about a saved method or one of its versions carries one of the platfor
317
318
 
318
319
  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. So is any suffix on the run routes' linkage form, a `method_id` beside an inline source, which `execute` and `start` refuse locally the same way.
319
320
 
321
+ ### The branding codes
322
+
323
+ A refused branding write (`updateOrganizationBranding`) names each logo or colour it refused in `errors`: the entry's `field` is the logo's slot, `square_logo` or `wide_logo`, or the colour's dotted key, such as `colors.action`, and its `detail` the sentence a settings screen shows under that control. A logo the platform read and refused is a `422` `validation_failed` with one entry per broken rule, both slots at once, each `code` one the exported type `LogoRefusalCode` names (`logo_too_large`, `logo_not_square`, `logo_too_small`, …); a logo URI refused before any read names its slot the same way, under `400` `invalid_storage_uri` or `403` `forbidden`. [organization-branding.md](./organization-branding.md#when-a-write-is-refused) lists every code with a sentence of each.
324
+
320
325
  ## A validation item — `ValidationErrorItem`
321
326
 
322
327
  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: