@pipelex/sdk 0.62.0 → 0.64.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.
@@ -0,0 +1,164 @@
1
+ /**
2
+ * A method's stored layouts: the layout a producer designed for one pipe of a published version,
3
+ * which the hosted platform stores beside the method (`storeMethodLayout`,
4
+ * `POST /v1/methods/{id}/versions/{n}/layouts`) and hands back on the validate of the method by id
5
+ * that names `layouts` among its `views` (`PipelexValidationReport.layouts`).
6
+ *
7
+ * A layout is a data file: a tree of elements written in the vocabulary of a catalog of
8
+ * components, every value bound to a path in the host's form state, carrying no field
9
+ * descriptions and no data of its own. A host renders it over the input-form descriptor the same
10
+ * validate projects, and renders the plain form instead whenever it cannot. The platform stores and
11
+ * carries it and never judges it. The shapes are stated brand-neutrally, as the input-form
12
+ * descriptor they are rendered over is: snake_case members with no `pipelex_` prefix. The storage
13
+ * and the `layouts` token are the hosted plane's.
14
+ *
15
+ * The readers here check a layout member by member, as `readProblemItems` checks a verdict's
16
+ * items: a validate never loses its verdict, nor its other layouts, to one layout it cannot read.
17
+ */
18
+ /**
19
+ * How a stored layout was made, as its producer stated it when it wrote the layout. Every member
20
+ * is present on a layout the SDK hands back.
21
+ */
22
+ export interface LayoutProvenance {
23
+ /** Who produced the layout, a short kebab-case slug such as `pipelex-method`. */
24
+ producer: string;
25
+ /** The id of the model the producer ran, such as `claude-4.8-opus`. */
26
+ model: string;
27
+ /** The creative seed handed to the model with the brief, verbatim; `null` when none was. */
28
+ seed: string | null;
29
+ /**
30
+ * The id of the app style the layout was designed under, such as `editorial`; `null` when it
31
+ * was designed under none. The platform holds it to the shape `^[a-z0-9-]{1,32}$` and never to
32
+ * a list of styles, so a host reads an id it does not know as no style.
33
+ */
34
+ style: string | null;
35
+ /** How many repair rounds the producer ran before the layout validated against its catalog. */
36
+ repair_rounds: number;
37
+ }
38
+ /**
39
+ * One stored layout — what `storeMethodLayout` answers, and an entry of
40
+ * `PipelexValidationReport.layouts`.
41
+ *
42
+ * Its identity is `tree_digest`, `pipe_ref`, `catalog` and `prompt_hash` within one method: it
43
+ * belongs to the content it was designed for, never to a version number, so it is served for
44
+ * whatever selector of that method resolves the same tree, and a write of the same identity
45
+ * replaces it. It names no method and no version.
46
+ */
47
+ export interface MethodLayout {
48
+ /**
49
+ * The digest of the method tree the layout was produced against, `sha256:` and 64 lowercase
50
+ * hexadecimal characters: the MTHDS standard's digest of the tree's listing, as a published
51
+ * version's `tree_digest` states it.
52
+ */
53
+ tree_digest: string;
54
+ /**
55
+ * The namespaced pipe reference it lays out, `domain_path.pipe_code`, as the input-form
56
+ * descriptor keys its forms.
57
+ */
58
+ pipe_ref: string;
59
+ /** The id of the catalog whose vocabulary it is written in, a slug such as `composer`. */
60
+ catalog: string;
61
+ /** The hash of the prompt the producer's model read, 12 to 64 lowercase hexadecimal digits. */
62
+ prompt_hash: string;
63
+ /**
64
+ * The layout itself, as the producer's model emitted it: JSON patch lines, one per line,
65
+ * carried as one string exactly as written. The platform never parses it.
66
+ */
67
+ jsonl: string;
68
+ /** How it was made. */
69
+ provenance: LayoutProvenance;
70
+ /** The moment the write that stored it landed, ISO 8601 in UTC: the one member the platform sets. */
71
+ stored_at: string;
72
+ }
73
+ /**
74
+ * The provenance a write states — `MethodLayoutInput.provenance`. A `null` or omitted optional
75
+ * member is not sent, and the platform stores its default: no seed, no style, no repair round.
76
+ */
77
+ export interface LayoutProvenanceInput {
78
+ /** Who produced the layout, a slug: 1 to 64 lowercase letters, digits and hyphens, not starting with a hyphen. */
79
+ producer: string;
80
+ /** The id of the model the producer ran: 1 to 128 characters, none of them a control character. */
81
+ model: string;
82
+ /** The creative seed handed to the model with the brief, verbatim, at most 4,000 characters. */
83
+ seed?: string | null;
84
+ /** The id of the app style the layout was designed under, of the shape `^[a-z0-9-]{1,32}$`. */
85
+ style?: string | null;
86
+ /** How many repair rounds the producer ran before the layout validated, from 0 to 100. */
87
+ repair_rounds?: number | null;
88
+ }
89
+ /**
90
+ * The write — `storeMethodLayout`, `POST /v1/methods/{id}/versions/{n}/layouts`: the stored layout
91
+ * without `stored_at`. The wire body is closed at both levels, so the client sends these members
92
+ * and nothing else; a `MethodLayout` read back can be passed as it is, its `stored_at` left out.
93
+ * The bounds below are the platform's, which refuses a member breaking one with a `422`
94
+ * `request.invalid` naming it, `provenance.<member>` for one of the provenance's.
95
+ */
96
+ export interface MethodLayoutInput {
97
+ /**
98
+ * The digest of the tree the layout was produced against, which must be the `tree_digest` of the
99
+ * version the call names (`MethodVersionSummary.tree_digest`): `sha256:` and 64 lowercase
100
+ * hexadecimal characters.
101
+ */
102
+ tree_digest: string;
103
+ /**
104
+ * The pipe it lays out, `domain_path.pipe_code`: two or more dot-separated identifiers, at most
105
+ * 256 characters in all.
106
+ */
107
+ pipe_ref: string;
108
+ /** The catalog's id, a slug of the same shape as `provenance.producer`. */
109
+ catalog: string;
110
+ /** The hash of the prompt the producer's model read, 12 to 64 lowercase hexadecimal digits. */
111
+ prompt_hash: string;
112
+ /**
113
+ * The layout as the producer's model emitted it, not empty, at most 128 KiB of UTF-8, holding
114
+ * no raw control character but the tab, the line feed and the carriage return.
115
+ */
116
+ jsonl: string;
117
+ /** How it was made. */
118
+ provenance: LayoutProvenanceInput;
119
+ }
120
+ /**
121
+ * The members the write sends besides `provenance`, every one each time. Checked against
122
+ * `MethodLayoutInput` with `satisfies`, so a member the interface gains and this list lacks fails
123
+ * the build rather than being dropped from the body without a word.
124
+ */
125
+ export declare const LAYOUT_INPUT_MEMBERS: {
126
+ readonly tree_digest: true;
127
+ readonly pipe_ref: true;
128
+ readonly catalog: true;
129
+ readonly prompt_hash: true;
130
+ readonly jsonl: true;
131
+ };
132
+ /**
133
+ * The members of the write's provenance, `required` ones sent each time and `optional` ones only
134
+ * when given and not `null`, checked against `LayoutProvenanceInput` as `LAYOUT_INPUT_MEMBERS` is.
135
+ */
136
+ export declare const PROVENANCE_INPUT_MEMBERS: {
137
+ readonly producer: "required";
138
+ readonly model: "required";
139
+ readonly seed: "optional";
140
+ readonly style: "optional";
141
+ readonly repair_rounds: "optional";
142
+ };
143
+ /**
144
+ * What keeps `value` from reading as a stored layout, as the end of a sentence naming it ("a
145
+ * layout whose `jsonl` is not a string"), or `null` when it reads as one: an object whose
146
+ * `tree_digest`, `pipe_ref`, `catalog`, `prompt_hash`, `jsonl` and `stored_at` are strings and
147
+ * whose `provenance` is an object with a string `producer` and `model`. The provenance's optional
148
+ * members never keep a layout from reading (`readMethodLayout`).
149
+ */
150
+ export declare function methodLayoutFault(value: unknown): string | null;
151
+ /**
152
+ * `value` read as a stored layout, or `null` when it does not read as one (`methodLayoutFault`).
153
+ * The provenance's `seed` and `style` are kept only when each is a string, and read as `null`
154
+ * otherwise, and its `repair_rounds` only when it is a non-negative integer, and read as `0`
155
+ * otherwise: the platform's own defaults, so each member is what `LayoutProvenance` declares. The
156
+ * rest is returned as it came.
157
+ */
158
+ export declare function readMethodLayout(value: unknown): MethodLayout | null;
159
+ /**
160
+ * A valid report's `layouts` read entry by entry: each entry that reads as a layout
161
+ * (`readMethodLayout`), in the answer's order, newest first as the platform sends them, and
162
+ * anything else left out. A value that is not an array reads as no layout.
163
+ */
164
+ export declare function readMethodLayouts(value: unknown): MethodLayout[];
@@ -0,0 +1,115 @@
1
+ /**
2
+ * A method's stored layouts: the layout a producer designed for one pipe of a published version,
3
+ * which the hosted platform stores beside the method (`storeMethodLayout`,
4
+ * `POST /v1/methods/{id}/versions/{n}/layouts`) and hands back on the validate of the method by id
5
+ * that names `layouts` among its `views` (`PipelexValidationReport.layouts`).
6
+ *
7
+ * A layout is a data file: a tree of elements written in the vocabulary of a catalog of
8
+ * components, every value bound to a path in the host's form state, carrying no field
9
+ * descriptions and no data of its own. A host renders it over the input-form descriptor the same
10
+ * validate projects, and renders the plain form instead whenever it cannot. The platform stores and
11
+ * carries it and never judges it. The shapes are stated brand-neutrally, as the input-form
12
+ * descriptor they are rendered over is: snake_case members with no `pipelex_` prefix. The storage
13
+ * and the `layouts` token are the hosted plane's.
14
+ *
15
+ * The readers here check a layout member by member, as `readProblemItems` checks a verdict's
16
+ * items: a validate never loses its verdict, nor its other layouts, to one layout it cannot read.
17
+ */
18
+ import { isPlainObject } from "./records.js";
19
+ /**
20
+ * The members the write sends besides `provenance`, every one each time. Checked against
21
+ * `MethodLayoutInput` with `satisfies`, so a member the interface gains and this list lacks fails
22
+ * the build rather than being dropped from the body without a word.
23
+ */
24
+ export const LAYOUT_INPUT_MEMBERS = {
25
+ tree_digest: true,
26
+ pipe_ref: true,
27
+ catalog: true,
28
+ prompt_hash: true,
29
+ jsonl: true,
30
+ };
31
+ /**
32
+ * The members of the write's provenance, `required` ones sent each time and `optional` ones only
33
+ * when given and not `null`, checked against `LayoutProvenanceInput` as `LAYOUT_INPUT_MEMBERS` is.
34
+ */
35
+ export const PROVENANCE_INPUT_MEMBERS = {
36
+ producer: "required",
37
+ model: "required",
38
+ seed: "optional",
39
+ style: "optional",
40
+ repair_rounds: "optional",
41
+ };
42
+ /** The members of a layout read as strings, in the order a fault names the first one missing. */
43
+ const LAYOUT_STRING_MEMBERS = [
44
+ "tree_digest",
45
+ "pipe_ref",
46
+ "catalog",
47
+ "prompt_hash",
48
+ "jsonl",
49
+ "stored_at",
50
+ ];
51
+ /** The members of a layout's provenance read as strings. */
52
+ const PROVENANCE_STRING_MEMBERS = ["producer", "model"];
53
+ /**
54
+ * What keeps `value` from reading as a stored layout, as the end of a sentence naming it ("a
55
+ * layout whose `jsonl` is not a string"), or `null` when it reads as one: an object whose
56
+ * `tree_digest`, `pipe_ref`, `catalog`, `prompt_hash`, `jsonl` and `stored_at` are strings and
57
+ * whose `provenance` is an object with a string `producer` and `model`. The provenance's optional
58
+ * members never keep a layout from reading (`readMethodLayout`).
59
+ */
60
+ export function methodLayoutFault(value) {
61
+ if (!isPlainObject(value))
62
+ return "that is not an object";
63
+ for (const member of LAYOUT_STRING_MEMBERS) {
64
+ if (typeof value[member] !== "string")
65
+ return `whose \`${member}\` is not a string`;
66
+ }
67
+ const provenance = value.provenance;
68
+ if (!isPlainObject(provenance))
69
+ return "whose `provenance` is not an object";
70
+ for (const member of PROVENANCE_STRING_MEMBERS) {
71
+ if (typeof provenance[member] !== "string") {
72
+ return `whose \`provenance.${member}\` is not a string`;
73
+ }
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * `value` read as a stored layout, or `null` when it does not read as one (`methodLayoutFault`).
79
+ * The provenance's `seed` and `style` are kept only when each is a string, and read as `null`
80
+ * otherwise, and its `repair_rounds` only when it is a non-negative integer, and read as `0`
81
+ * otherwise: the platform's own defaults, so each member is what `LayoutProvenance` declares. The
82
+ * rest is returned as it came.
83
+ */
84
+ export function readMethodLayout(value) {
85
+ if (methodLayoutFault(value) !== null)
86
+ return null;
87
+ const layout = value;
88
+ const { seed, style, repair_rounds: rounds } = layout.provenance;
89
+ return {
90
+ ...layout,
91
+ provenance: {
92
+ ...layout.provenance,
93
+ seed: typeof seed === "string" ? seed : null,
94
+ style: typeof style === "string" ? style : null,
95
+ repair_rounds: Number.isSafeInteger(rounds) && rounds >= 0 ? rounds : 0,
96
+ },
97
+ };
98
+ }
99
+ /**
100
+ * A valid report's `layouts` read entry by entry: each entry that reads as a layout
101
+ * (`readMethodLayout`), in the answer's order, newest first as the platform sends them, and
102
+ * anything else left out. A value that is not an array reads as no layout.
103
+ */
104
+ export function readMethodLayouts(value) {
105
+ if (!Array.isArray(value))
106
+ return [];
107
+ const layouts = [];
108
+ for (const entry of value) {
109
+ const layout = readMethodLayout(entry);
110
+ if (layout !== null)
111
+ layouts.push(layout);
112
+ }
113
+ return layouts;
114
+ }
115
+ //# sourceMappingURL=method-layouts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"method-layouts.js","sourceRoot":"","sources":["../src/method-layouts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA4G7C;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,WAAW,EAAE,IAAI;IACjB,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,IAAI;IACb,WAAW,EAAE,IAAI;IACjB,KAAK,EAAE,IAAI;CACoE,CAAC;AAElF;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,UAAU;IACjB,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,UAAU;IACjB,aAAa,EAAE,UAAU;CACsD,CAAC;AAElF,iGAAiG;AACjG,MAAM,qBAAqB,GAAG;IAC5B,aAAa;IACb,UAAU;IACV,SAAS;IACT,aAAa;IACb,OAAO;IACP,WAAW;CACH,CAAC;AAEX,4DAA4D;AAC5D,MAAM,yBAAyB,GAAG,CAAC,UAAU,EAAE,OAAO,CAAU,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,uBAAuB,CAAC;IAC1D,KAAK,MAAM,MAAM,IAAI,qBAAqB,EAAE,CAAC;QAC3C,IAAI,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,QAAQ;YAAE,OAAO,WAAW,MAAM,oBAAoB,CAAC;IACtF,CAAC;IACD,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACpC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC;QAAE,OAAO,qCAAqC,CAAC;IAC7E,KAAK,MAAM,MAAM,IAAI,yBAAyB,EAAE,CAAC;QAC/C,IAAI,OAAO,UAAU,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC3C,OAAO,sBAAsB,MAAM,oBAAoB,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,iBAAiB,CAAC,KAAK,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,MAAM,GAAG,KAA0E,CAAC;IAC1F,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC;IACjE,OAAO;QACL,GAAG,MAAM;QACT,UAAU,EAAE;YACV,GAAG,MAAM,CAAC,UAAU;YACpB,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;YAC5C,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;YAC/C,aAAa,EAAE,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAK,MAAiB,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;SACpF;KACyB,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,KAAkB,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/dist/models.d.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  * `mthds/protocol` subpath.
11
11
  */
12
12
  import type { InputForm, MethodFiles, ProblemItem, MethodTreeSource, OutputForm, PipeIOContracts, RunResultExecute, RunResultStart } from "mthds/protocol";
13
+ import type { MethodLayout } from "./method-layouts.js";
13
14
  import type { MethodTreeForm, NoMethodTree } from "./method-trees.js";
14
15
  export interface DictStuff {
15
16
  concept: string;
@@ -141,6 +142,14 @@ export type PipelexValidateSource = (MethodTreeForm & {
141
142
  method_ref?: never;
142
143
  method_id?: never;
143
144
  }) | (ValidateMethodSelector & NoMethodTree);
145
+ /**
146
+ * The `views` tokens this SDK knows, each adding the same-named top-level member to the valid
147
+ * report: `input_form` and `output_form`, the runner's per-pipe form descriptors, and `layouts`, the
148
+ * hosted plane's stored layouts of a method validated by `method_id`. `ValidateOptions.views` reads
149
+ * them open: a token a later server adds is sent as its raw string, and a server ignores a token it
150
+ * does not know, never answering a `422` for one.
151
+ */
152
+ export type ValidateView = "input_form" | "output_form" | "layouts";
144
153
  /** Per-pipe dry-run verdict in `validated_pipes[]` — mirror of pipelex's `DryRunStatus`. */
145
154
  export type DryRunStatus = "SUCCESS" | "FAILURE" | "SKIPPED";
146
155
  /** One entry of `PipelexValidationReport.validated_pipes` — `{pipe_ref, status}`. */
@@ -262,6 +271,24 @@ export interface PipelexValidationReport {
262
271
  * through `views: ["output_form"]` and absent from any verdict that did not ask.
263
272
  */
264
273
  output_form?: OutputForm;
274
+ /**
275
+ * The layouts stored for the method's content, newest first by `stored_at`, at most 16: a
276
+ * Pipelex hosted extension, requested through `views: ["layouts"]` on a validate naming a stored
277
+ * method by `method_id`, and carried for every selector of that method resolving the same tree
278
+ * (the version it was written against, a later version holding the same tree, the draft holding
279
+ * it). Ask for `["input_form", "layouts"]` to receive the descriptor and the layouts over it in
280
+ * one answer.
281
+ *
282
+ * **Absent means none**: the member is left out when the validate names no `method_id`, when
283
+ * `views` does not name `layouts`, when no layout is stored for the resolved tree, and when the
284
+ * platform could not read them, so a host never reads an empty array and renders the plain form
285
+ * whenever it is absent. A platform that does not carry the view leaves it out too; its
286
+ * `GET /v1/version` does not list `method_layouts` among its `extensions`.
287
+ *
288
+ * Read entry by entry (`readMethodLayouts`): an entry that does not read as a layout is left
289
+ * out rather than failing the verdict, and a list left with none reads as absent.
290
+ */
291
+ layouts?: MethodLayout[];
265
292
  /** Pipes the runtime may skip when an optional slot resolves absent. */
266
293
  liftable_pipes: LiftablePipeEntry[];
267
294
  /**
@@ -86,7 +86,7 @@ export interface MethodVersionSummary {
86
86
  /** The method's description at publish time, as the draft held it (see `MethodData.description`). */
87
87
  description: string | null;
88
88
  /**
89
- * The method's categories at publish time, as slugs of the catalog's list (see
89
+ * The method's categories at publish time, as its manifest declared them (see
90
90
  * `MethodData.categories`); absent from a platform that predates them.
91
91
  */
92
92
  categories?: string[];
@@ -144,10 +144,11 @@ export interface MethodData {
144
144
  */
145
145
  description?: string | null;
146
146
  /**
147
- * The draft's categories, read from its manifest's `categories` under `[package]` and kept when
148
- * they are on the catalog's list, as slugs such as `human-resources`, in their declared order;
149
- * empty for a draft with no manifest or none it knows, and absent from a platform that predates
150
- * them. A person reads a category's label, which `listMethodTemplates` names beside its slug.
147
+ * The draft's categories, read from its manifest's `categories` under `[package]`: each
148
+ * well-formed slug, such as `human-resources`, once, in its declared order, whether or not it is
149
+ * on the catalog's list; empty for a draft with no manifest or none well-formed, and absent from
150
+ * a platform that predates them. The catalog shows a template under those on its list alone,
151
+ * which `listMethodCategories` reads, each category with its labels.
151
152
  */
152
153
  categories?: string[];
153
154
  created_at: string;
@@ -245,7 +246,7 @@ export interface PublicMethodDetail {
245
246
  name: string;
246
247
  /** The latest published version's description. */
247
248
  description: string | null;
248
- /** The latest published version's categories, as slugs of the catalog's list. */
249
+ /** The latest published version's categories, as its manifest declared them. */
249
250
  categories: string[];
250
251
  created_at: string;
251
252
  /** The number of the latest published version, the one a reader reads, runs and copies. */
@@ -495,12 +496,24 @@ export interface MethodVersionPage {
495
496
  items: MethodVersionSummary[];
496
497
  nextCursor: string | null;
497
498
  }
498
- /** One category of the catalog's list: the slug a manifest declares, and the English label a person reads. */
499
+ /**
500
+ * One category of the catalog's list: the slug a manifest declares, and the labels a person reads.
501
+ */
499
502
  export interface MethodCategory {
500
503
  /** The stable slug, such as `human-resources`, which a method's `categories` hold. */
501
504
  slug: string;
502
- /** The English label, such as `Human resources`; a client translating by slug falls back on it. */
505
+ /**
506
+ * The English label, such as `Human resources`: what a client that does not translate shows, and
507
+ * what one that does falls back on for a language `labels` lacks.
508
+ */
503
509
  label: string;
510
+ /**
511
+ * The label in each language the platform holds one for, by language tag, such as
512
+ * `{ en: "Human resources", fr: "Ressources humaines" }`; `labels.en` equals `label`. A category
513
+ * stored before the platform spoke a language lacks a label in it, so read `labels[locale] ??
514
+ * label`.
515
+ */
516
+ labels: Partial<Record<string, string>>;
504
517
  }
505
518
  /** One thumbnail of a template's latest published version, with a link to its bytes. */
506
519
  export interface MethodTemplateThumbnail extends MethodThumbnail {
@@ -566,10 +579,17 @@ export interface MethodTemplatePage {
566
579
  ordering: string | null;
567
580
  /**
568
581
  * Every category at least one template shows, whatever the filter, in the catalog's order, each
569
- * with its label: the tabs a client draws, none of them ever empty.
582
+ * with its labels: the tabs a client draws, none of them ever empty.
570
583
  */
571
584
  categories: MethodCategory[];
572
585
  }
586
+ /**
587
+ * The catalog's whole list of categories — `listMethodCategories`: every category on it, in the
588
+ * catalog's order, each with its labels, whether or not a template shows it.
589
+ */
590
+ export interface MethodCategoryList {
591
+ categories: MethodCategory[];
592
+ }
573
593
  /**
574
594
  * The copy — `copyMethod`, `POST /v1/methods/{id}/copy`. Its wire body is closed: the client
575
595
  * sends these members and nothing else, a `null` reading as not given.
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.62.0";
6
+ export declare const SDK_VERSION = "0.64.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.62.0";
6
+ export const SDK_VERSION = "0.64.0";
7
7
  //# sourceMappingURL=version.js.map
@@ -117,6 +117,7 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
117
117
  - `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.
118
118
  - `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`, listing their faults and warnings as the standard's `ProblemItem`, with its `SuggestedFix` and `FixOp`, see [errors.md](./errors.md#a-verdicts-problem-items)), 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.
119
119
  - `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.
120
+ - `method-layouts.ts` — a method's stored layouts: `MethodLayoutInput` and `LayoutProvenanceInput`, the write `storeMethodLayout` sends, `MethodLayout` and `LayoutProvenance`, what the platform stores, answers and carries on a validate's `layouts`, and the reader both go through, `readMethodLayout` (with `methodLayoutFault`, which names the member an unreadable layout breaks, and `readMethodLayouts`, which reads a report's list entry by entry). It imports the plain-object guard of `records.ts` and nothing else. The Python twin is `pipelex_sdk/method_layout_models.py`. See [method-layouts.md](./method-layouts.md).
120
121
  - `answer-reading.ts` — `readHostedAnswer`, the reading of an answer that is not the route's result as its problem: a problem document as received, and an answer whose body is none, the hosted edge's, a gateway's or a proxy's, by its status, `client.outcome_unknown` for a start or an execute that may have created a run; and `outcomeUnknownProblem`. `ApiResponseError` reads every answer through it. The Python twin is `pipelex_sdk/answer_reading.py`. See [errors.md](./errors.md#an-answer-that-is-no-problem-document).
121
122
  - `transport-reading.ts` — `requestMayHaveLeft`, the **pure** reading of whether a request that got no answer may have left this machine, every failure counting unless it proves nothing was sent (a failure of the connection's set-up). `ApiUnreachableError` applies it on a start or an execute, which the client marks with `mayStartRun`, to read the failure as `client.unreachable` or `client.outcome_unknown`. The Python twin is `pipelex_sdk/transport_reading.py`. See [errors.md](./errors.md#a-run-that-may-have-started--outcome_unknown).
122
123
  - `problem-items.ts` — `readProblemItems`, the reading of a verdict's `errors` and `warnings` as the standard's problem items, member by member, so one malformed member never costs the caller its fault. The Python twin is `pipelex_sdk/problem_items.py`. See [errors.md](./errors.md#a-verdicts-problem-items).
@@ -140,7 +141,7 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
140
141
  ## Client surface (current)
141
142
 
142
143
  - **Protocol execution:** `execute` (returns a `PipelexExecuteResult` with a resolved `.main_stuff`), `start`, `validate` (returns `PipelexValidationResult`), `models`, `version`.
143
- - **Two independent opt-in lists on `validate`.** `render` asks the server to render a *view format* and adds a `rendered_<format>` field; `views` asks for a *structured view* and adds a same-named top-level field — today `input_form` and `output_form`, the per-pipe form descriptors. A caller that wants the forms without the dry-run verdict reads `pipeIo` instead, which derives them with no dry run. Each resolves its own tokens against its own supported set and unknown tokens are lenient-ignored on both, never a `422`. The client always asks for `render: ["markdown"]` but sends `views` only when a caller names a token, because an opt-in view exists precisely so the default response stays byte-identical for the consumers that discard it. `pipelex-api` gates `input_form` on the token as of 0.18.0; a 0.17.0 runner resolved no token and emitted the field on every valid verdict, which is why `PipelexValidationReport.input_form` is typed optional rather than required — the one typing that reads a response from either runner correctly.
144
+ - **Two independent opt-in lists on `validate`.** `render` asks the server to render a *view format* and adds a `rendered_<format>` field; `views` asks for a *structured view* and adds a same-named top-level field — today `input_form` and `output_form`, the per-pipe form descriptors, and on a hosted validate by `method_id`, `layouts`, the layouts stored for the resolved tree, read entry by entry and absent when there are none ([method-layouts.md](./method-layouts.md)). `ValidateView` names the tokens this SDK knows, and `views` still takes any other as its raw string. A caller that wants the forms without the dry-run verdict reads `pipeIo` instead, which derives them with no dry run. Each resolves its own tokens against its own supported set and unknown tokens are lenient-ignored on both, never a `422`. The client always asks for `render: ["markdown"]` but sends `views` only when a caller names a token, because an opt-in view exists precisely so the default response stays byte-identical for the consumers that discard it. `pipelex-api` gates `input_form` on the token as of 0.18.0; a 0.17.0 runner resolved no token and emitted the field on every valid verdict, which is why `PipelexValidationReport.input_form` is typed optional rather than required — the one typing that reads a response from either runner correctly.
144
145
  - **The method's tree:** `execute` / `start` / `startAndWaitForResult` (both paths) and `validate` take the whole method as its tree — every `.mthds` bundle, its `funcs/*.py`, `structures/*.py` and `requirements.txt`, and every file a step reads, such as a `PipeDocGen` step's template — in one of four forms, `directory`, `contents`, `tree` or `files`, sent inline or by hash. `startAndWaitForResult` prepares the tree once, under its `signal`, before it starts the run, so its fallback to the blocking execute sends the same files without reading or uploading them again. A tree is self-contained, so it satisfies the "something to run" precondition on its own — `pipe_code` is not required beside it. See [run-sources](#run-sources) above and [method-trees.md](./method-trees.md).
145
146
  - **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.
146
147
  - **An idempotency key on a run start (hosted):** `start` and `startAndWaitForResult` take `idempotencyKey` (`PipelexStartRequestOptions`, intersected into `PipelexStartOptions`), sent as the `Idempotency-Key` header of `POST /v1/start` and never in the body; with no key there is no header. The hosted API binds the key to the run, answers a repeat of the same start with that run, and refuses the key with other options; a bare runner ignores the header. The client refuses a key that is not 1 to 255 visible ASCII characters with a `RequestArgumentError` before any request, the blocking `execute` fallback of `startAndWaitForResult` sends none, and the key leaves the error's problem as it is: a start that may have reached the server stays `client.outcome_unknown`, and the caller who sent a key recovers the run by sending the same start again under it. [errors.md](./errors.md#a-starts-idempotency-key--idempotencykey) holds the contract.
@@ -159,8 +160,9 @@ The hosted catalog/account routes the webapp drives. All ride the same `{base}/v
159
160
  - **User profile:** `getMe` (`GET /v1/me`).
160
161
  - **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` `pipelex.catalog.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`, and for a public method `storage_reference`, `thumbnail_missing` or `thumbnail_invalid`, read open so a later reason arrives as its raw string), a `message` and the runner's `validation` verdict, its problem items read member by member as `validate` reads them — every arm carrying the `method`; the SDK reads the nested method as `getMethod` does, and an answer whose `outcome` is none of these, or a refusal whose `validation` is not a verdict object, 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` `request.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 `500` `runtime.unexpected` 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` `pipelex.catalog.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 on every draft write from the manifest's `description`, else the primary bundle's top-level `description`, else its main pipe's), `categories` and `visibility`, 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`. Another organization's public method reads as a `PublicMethodDetail`, its latest published version and nothing of its draft, so `getMethod` is typed `MethodDetail | PublicMethodDetail`, told apart by `updated_at`; an answer without it that carries a member only the owner's read has is an answer the SDK cannot read, and so is a public read without its `latest_version` and `latest_published` ([public-methods.md](./public-methods.md)).
161
162
  - **Method examples:** `createMethodExample`, `listMethodExamples`, `iterateMethodExamples`, `getMethodExample`, `updateMethodExample`, `recordMethodExampleLatestRun`, `saveAssistantMethodExample`, `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; `saveAssistantMethodExample` is the build assistant's save, which writes into the example it names only while that example is still the assistant's (`kept_by_assistant`) at the revision it read, and otherwise creates one, answering a `MethodExampleAssistantSaveResult` discriminated on `outcome`, `kept` or `created`; one helper, `readVerdict`, reads these verdicts 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).
162
- - **Template catalog:** `listMethodTemplates`, `iterateMethodTemplates`, on `GET /v1/method-templates`. The public methods of the deployment's templates organization, in the order the platform serves for the `ordering` asked for, filtered by `category`, with the page's `categories` and each card's thumbnails behind short-lived links. `listMethodTemplates` reads the page with its own reader rather than `requestPage`, since its `ordering` and `categories` sit beside `items` and `nextCursor`, and redacts the links of an answer it cannot read; `iterateMethodTemplates` follows the cursor with the same ordering and category ([public-methods.md](./public-methods.md)).
163
+ - **Template catalog:** `listMethodTemplates`, `iterateMethodTemplates`, on `GET /v1/method-templates`, and `listMethodCategories`, on `GET /v1/method-categories`. The public methods of the deployment's templates organization, in the order the platform serves for the `ordering` asked for, filtered by `category`, with the page's `categories` and each card's thumbnails behind short-lived links. `listMethodTemplates` reads the page with its own reader rather than `requestPage`, since its `ordering` and `categories` sit beside `items` and `nextCursor`, and redacts the links of an answer it cannot read; `iterateMethodTemplates` follows the cursor with the same ordering and category. `listMethodCategories` reads the catalog's whole list of categories, which an admin of the platform keeps, in its order. Both check each category with one reader, `methodCategoriesFault`: a string `slug` and `label`, and `labels`, an object of strings by language tag ([public-methods.md](./public-methods.md)).
163
164
  - **Method copies:** `copyMethod`, on `/v1/methods/{id}/copy`. `copyMethod` makes a new, unpublished method in an organization the caller may change methods in, the method's own for a duplicate, holding the original's draft tree, its form inputs and its examples, the stored files they name copied into the destination, and answers a `MethodCopyResult` discriminated on `outcome`: `copied` with the copy as `method` and what was copied, or `refused` with the `references` of a bundle naming storage the destination cannot read or, in a duplicate, a file under the original's run files, which erasing the original would remove. The two arms carry different members, so the verdict is read by the copy itself rather than by `readVerdict`, which reads verdicts whose arms share one: an `outcome` off the union, a `copied` answer whose `method` is not an object and a `refused` one whose `references` is not an array are each an answer the SDK cannot read. Its body is closed, `destination_org_id` with `name` and `expected_draft_updated_at` when given, and its last argument takes an `idempotencyKey`, sent as the `Idempotency-Key` header through the same `requestRaw` option and the same `idempotencyKeyOf` check a start uses. Every method read carries `copied_from`, the original a copy names, `null` on a method created afresh. `MethodErrorCode` names the copy's own codes, `pipelex.org.not_found` and `pipelex.catalog.copy_too_large`, whose `limit` member is typed `MethodCopyLimit`. It takes a bare catalog id. See [method-copy.md](./method-copy.md).
165
+ - **Method layouts:** `storeMethodLayout`, on `/v1/methods/{id}/versions/{n}/layouts`, and the `layouts` view of `validate`. A layout is a data file a producer designed for one pipe of a published version, which the platform stores keyed by the content it was designed for, `tree_digest`, `pipe_ref`, `catalog` and `prompt_hash`, a write of the same identity replacing it, and carries on every validate by `method_id` that names `layouts` and resolves the same tree. The write takes a bare catalog id and a positive integer version, refuses a layout or a provenance that is not an object before sending, and sends the closed body alone, its members listed once in `method-layouts.ts` (`LAYOUT_INPUT_MEMBERS`, `PROVENANCE_INPUT_MEMBERS`) and held to the input interfaces by `satisfies`, each optional provenance member that is `null` or omitted left out; its last argument takes an `idempotencyKey` (`MethodLayoutWriteOptions`), sent as the `Idempotency-Key` header through the same `idempotencyKeyOf` check a start and a copy use, since a write sent again without one runs again, restamping the layout and applying the bound again; the platform's refusals (`422` `request.invalid` naming the member, `tree_digest` among them for a tree that is not the version's, `403` `auth.forbidden` for a runner, `404` `pipelex.catalog.not_found` naming the method or the version, `409` `pipelex.catalog.being_deleted`) are `ApiResponseError`s. One reader in `method-layouts.ts`, `readMethodLayout`, holds the write's answer and each entry of a report's `layouts` to the same rule, the six identity and payload members strings and the provenance an object with a string `producer` and `model`, its `seed` and `style` read as `null` and its `repair_rounds` as `0` when they do not fit; an answer breaking it is one the SDK cannot read, and an entry breaking it is left out of the report, a list left with none removed, so `layouts` is absent or holds at least one layout. See [method-layouts.md](./method-layouts.md).
164
166
  - **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.
165
167
  - **Organization branding:** `getBranding` (`GET /v1/branding`, any role, web session or API key) and `updateOrganizationBranding` (`PATCH /v1/organizations/{org_id}/branding`, a write by the `admin` or the `member` role, never a `runner`) answer the same `OrganizationBranding`: the organization's name, its light and dark colour sets (`BrandingColors`), its square and wide logos (`LogoDescriptor`), its app `style` and `typeface`, and its read-only `white_label` entitlement, the last three read as `null`, `null` and `false` from a platform that predates them. The write's `OrganizationBrandingUpdate` sets a key given a value, clears a key given `null` and keeps a key left out; a refused field is named by its `location.field`, a colour by its dotted key, a logo by its slot in `ApiResponseError.errors` with a `LogoRefusalCode` when the platform measured it. See [organization-branding.md](./organization-branding.md).
166
168
  - **Caller webhooks:** `listRunWebhookDeliveries`, `getRunWebhookDelivery` and `replayRunWebhookDelivery`, on `/v1/runs/{id}/webhook-deliveries`, `/v1/runs/{id}/webhook-deliveries/{delivery_id}` and its `/replay`, read a run's delivery log and send a settled delivery again, any member by web session or API key, the replay taking an `idempotencyKey` under which the platform stores its answer; `getWebhookSecret`, on `GET /v1/organizations/{org_id}/webhook-secret`, and `rotateWebhookSecret`, on `POST /v1/organizations/{org_id}/webhook-secret/rotate`, read and rotate the organization's signing secret, an admin's routes from a web session only, the rotation taking an `idempotencyKey` the platform never replays. See [webhooks.md](./webhooks.md).
package/docs/errors.md CHANGED
@@ -96,6 +96,8 @@ What the hosted API answers a start sent under a key:
96
96
 
97
97
  **A copy takes a key too.** `copyMethod` sends an `idempotencyKey` the same way, held to the same rule, and the hosted API keeps it for the same 24 hours, scoped to the caller and the method's organization. A copy binds no run, so its answers under a key differ from a start's: [method-copy.md](./method-copy.md#retrying-a-copy--idempotencykey) gives them.
98
98
 
99
+ **And a layout write.** `storeMethodLayout` sends an `idempotencyKey` the same way, held to the same rule. A write sent again without one runs again, restamping the layout and applying the bound of layouts a content keeps again, so a write whose answer was lost is sent again under the key it was first sent with, which answers it as the first write was answered: [method-layouts.md](./method-layouts.md#storing-a-layout) gives the answers.
100
+
99
101
  **So do a secret rotation and a delivery replay.** `rotateWebhookSecret` and `replayRunWebhookDelivery` send an `idempotencyKey` the same way, held to the same rule. A rotation's answer carries a secret, so the platform never replays it, while a replay's stored answer is given again: [webhooks.md](./webhooks.md) gives both.
100
102
 
101
103
  ## Each class's problem