@instruments/materialgraph 0.6.0 → 0.6.1

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.
package/client.d.ts CHANGED
@@ -13,6 +13,7 @@ import type { BrandCatalogListPage, BrandsOptions } from "./brand-types.js";
13
13
  import type { MaterialAssetProjection } from "./material-asset-types.js";
14
14
  import type { MaterialBlockProjection } from "./material-block-types.js";
15
15
  import type { SchemaResource, SchemaResourceResponse } from "./schema-resource-types.js";
16
+ import type { BatchId, ProjectId, RunId, VariantId } from "./identifiers.js";
16
17
  import type { ApplicableSpecsResponse, BrandOptions, BrandProfileResponse, ByColourParams, ByColourResponse, BySpecOptions, BySpecResponse, ColourFacetsResponse, CoordinateInput, CoordinateResponse, DemandBatchInput, DemandBatchResponse, DemandParams, DemandResponse, ExtractPaletteInput, ExtractPaletteResponse, GlossaryTermResponse, MaterialResponse, MaterialsBatchDetailResponse, MaterialsBatchGetOptions, MaterialsBatchOptions, MaterialsBatchTileResponse, MatchInput, MatchResponse, PairingsOptions, PairingsResponse, ResolveAttributeOptions, ResolveAttributeResponse, ResolveAttributeValueOptions, ResolveAttributeValueResponse, ResolveColourResponse, SearchGetParams, SearchInput, SearchResponse, SiblingsResponse, SpecFacetsDistributionResponse, SpecFacetsListResponse, SuggestColourResponse, SuggestOptions, SuggestResponse, TaxonomyResponse, VocabularySearchOptions, VocabularySearchResponse, WhoamiResponse } from "./types.js";
17
18
  export declare const DEFAULT_BASE_URL = "https://beta.materialgraph.com";
18
19
  export declare const MAX_BATCH_IDS = 100;
@@ -24,24 +25,85 @@ export interface MaterialGraphClientOptions {
24
25
  /** Inject a custom `fetch` (tests, proxies). Defaults to the global `fetch`. */
25
26
  fetch?: typeof fetch;
26
27
  }
28
+ /**
29
+ * The MaterialGraph client — every lens on the graph, on one object.
30
+ *
31
+ * PICK YOUR LENS BY THE QUESTION, not by the noun. Most wrong answers from this
32
+ * API come from routing a question to a lens that cannot see it:
33
+ *
34
+ * | The question | The lens |
35
+ * | --- | --- |
36
+ * | "find me something like this / matching these words" | {@link MaterialGraphClient.search} |
37
+ * | "what's IN this photo?" | {@link MaterialGraphClient.match} / {@link MaterialGraphClient.matchStream} |
38
+ * | "which materials, from anyone, meet this hard spec?" | {@link MaterialGraphClient.bySpec} |
39
+ * | "what's near this colour / this palette / this gradient?" | {@link MaterialGraphClient.byColour}, `colours.palette()`, `colours.gradient()` |
40
+ * | "what goes with THIS material?" | {@link MaterialGraphClient.pairings}, {@link MaterialGraphClient.siblings}, {@link MaterialGraphClient.coordinate} |
41
+ * | "how much is this specified?" | {@link MaterialGraphClient.demand}, {@link MaterialGraphClient.demandBatch} |
42
+ * | "is this allowed in an A-2 corridor?" | `codes.*` |
43
+ * | "what can I even ask for?" | {@link MaterialGraphClient.specificationFacets}, {@link MaterialGraphClient.attributeValues}, {@link MaterialGraphClient.taxonomy}, {@link MaterialGraphClient.colourFacets}, {@link MaterialGraphClient.glossary} |
44
+ * | "do the whole job for me, async" | {@link MaterialGraphClient.createRun} + `mg:compose` |
45
+ *
46
+ * Two structural rules that fall out of that table:
47
+ * - **Colour is a separate lens.** {@link MaterialGraphClient.search} matches
48
+ * WORDS. A colour word in `text` finds materials whose text mentions it, not
49
+ * materials that look it. Route visual intent to the colour lenses.
50
+ * - **Relationships hang off a material, not off a query.** You cannot ask "what
51
+ * pairs with beige oak"; you resolve a material first, then ask what pairs
52
+ * with THAT `variantId`.
53
+ *
54
+ * And three reading rules that apply to every lens: ids are opaque (see
55
+ * `./identifiers.ts`), absent is not false, and a page is not a corpus.
56
+ */
27
57
  export declare class MaterialGraphClient {
28
58
  #private;
29
59
  readonly baseUrl: string;
60
+ /**
61
+ * THE QUESTION: "show me everything, ordered lightest / most vivid / most
62
+ * muted." Pure colour scalars — no query, no relevance. `browse({ sort })` is
63
+ * one page; `browse.pages({ sort })` walks them all, and only the walk can
64
+ * support a corpus claim.
65
+ *
66
+ * Instead use {@link MaterialGraphClient.byColour} for materials NEAR a colour,
67
+ * {@link MaterialGraphClient.search} when there is anything to rank against,
68
+ * and `colours.profiles()` for a category's palette rather than its members.
69
+ */
30
70
  readonly browse: BrowseNamespace;
31
71
  /**
32
- * The advisory building-code surface: `codes.context()` to discover the
33
- * vocabulary, `codes.requirements()` to state a space, `codes.advisory()` to
34
- * badge candidates against it.
72
+ * THE QUESTION: "is this material allowed HERE?" — a jurisdiction + occupancy +
73
+ * finish location, not a property of the material alone. `codes.context()`
74
+ * discovers the vocabulary, `codes.requirements()` states a space,
75
+ * `codes.advisory()` badges candidates.
76
+ *
77
+ * Instead use {@link MaterialGraphClient.bySpec} to find materials by a
78
+ * classification you already know you need; codes is what tells you WHICH one
79
+ * the space demands. ADVISORY ONLY: model code as adopted, never a
80
+ * determination for an authority having jurisdiction.
35
81
  */
36
82
  readonly codes: CodesClient;
37
83
  /**
38
- * The colour AGGREGATE surface: `colours.profiles()` for a category palette,
39
- * `colours.stats()` for corpus colour coverage.
84
+ * THE QUESTION: "what colours exist here?" (`profiles`, `stats`) and "what
85
+ * matches this whole SET of colours?" (`palette`, `gradient`).
86
+ *
87
+ * Instead use {@link MaterialGraphClient.byColour} for ONE colour,
88
+ * {@link MaterialGraphClient.browse} to walk by a colour scalar, and
89
+ * {@link MaterialGraphClient.colourFacets} for the colour VOCABULARY rather
90
+ * than values. Colour throughout is derived from product imagery — visual
91
+ * likeness, never a declared colour specification.
40
92
  */
41
93
  readonly colours: ColoursClient;
42
94
  constructor(options: MaterialGraphClientOptions);
43
95
  get lastRateLimit(): RateLimit | null;
44
96
  /**
97
+ * THE QUESTION: "find me materials like this" — a RANKED answer over words, a
98
+ * seed image, colours and a mood, optionally gated by hard specs.
99
+ *
100
+ * Instead use {@link MaterialGraphClient.bySpec} when the spec IS the question
101
+ * (ranking would be noise), {@link MaterialGraphClient.byColour} /
102
+ * `colours.palette()` for visual colour intent (`text` matches WORDS — "sage
103
+ * green" finds materials that SAY sage, not ones that look it),
104
+ * {@link MaterialGraphClient.match} for a photo, {@link MaterialGraphClient.pairings}
105
+ * for what goes WITH something, {@link MaterialGraphClient.suggest} for typeahead.
106
+ *
45
107
  * The full search primitive (POST `/api/v1/search`). Layer a hard
46
108
  * specification gate onto a ranked (colour / visual / lexical / seed) search
47
109
  * via `input.filters` — each entry is a typed {@link SearchFilter} criterion:
@@ -53,7 +115,26 @@ export declare class MaterialGraphClient {
53
115
  * filters with the `certificationProgramme` / `specTerm` / `hasSpec` helpers.
54
116
  */
55
117
  search(input: SearchInput, signal?: AbortSignal): Promise<SearchResponse>;
118
+ /**
119
+ * The same question as {@link MaterialGraphClient.search}; the difference is
120
+ * transport — a cacheable, link-shareable GET for queries that must survive in
121
+ * a URL. `filters`, `preferences`, `verdicts`, image bytes and `weights` have
122
+ * no query-string form, so anything using those must go through `search`.
123
+ */
56
124
  searchGet(params: SearchGetParams, signal?: AbortSignal): Promise<SearchResponse>;
125
+ /**
126
+ * THE QUESTION: "what materials are in this PHOTO?" — Material Match classifies
127
+ * the image, decomposes it into surfaces, and answers each one.
128
+ *
129
+ * Instead use {@link MaterialGraphClient.matchStream} whenever a human is
130
+ * watching, {@link MaterialGraphClient.search} with `image` for ONE ranked list
131
+ * rather than a per-surface decomposition, and
132
+ * {@link MaterialGraphClient.extractPalette} for the image's colours only.
133
+ *
134
+ * Exactly one of `imageBase64`, `imageUrl` or `vector`. Read `qualityFloor` and
135
+ * `droppedBelowFloor` before reporting an empty result: "nothing was good
136
+ * enough" and "nothing was found" are different answers.
137
+ */
57
138
  match(input: MatchInput, signal?: AbortSignal): Promise<MatchResponse>;
58
139
  /**
59
140
  * Material Match as a PROGRESSIVE stream — the same POST
@@ -79,31 +160,93 @@ export declare class MaterialGraphClient {
79
160
  * buffered-only.
80
161
  */
81
162
  matchStream(input: MatchInput, signal?: AbortSignal): AsyncGenerator<MatchStreamEvent, void, unknown>;
163
+ /**
164
+ * THE QUESTION: "what materials are near THIS colour?" — one colour (or a small
165
+ * palette), ranked by OKLab closeness to colour extracted from product imagery.
166
+ *
167
+ * Instead use `colours.palette()` for N colours at stated proportions (a
168
+ * different ranking, not a wrapper), `colours.gradient()` for a transition,
169
+ * {@link MaterialGraphClient.resolveColour} first if you hold a NAME,
170
+ * {@link MaterialGraphClient.browse} to walk by lightness/vividness, and
171
+ * {@link MaterialGraphClient.search} when colour is one signal among many.
172
+ * Report matches as visual likeness, never a stated colour specification.
173
+ */
82
174
  byColour(params: ByColourParams, signal?: AbortSignal): Promise<ByColourResponse>;
83
- /** Get a single material's detail record (GET `/api/v1/materials/{variantId}`). */
84
- material(variantId: string, options?: {
175
+ /**
176
+ * THE QUESTION: "tell me everything about THIS material" (GET
177
+ * `/api/v1/materials/{variantId}`).
178
+ *
179
+ * The hinge of the API: every relationship lens
180
+ * ({@link MaterialGraphClient.siblings}, {@link MaterialGraphClient.pairings},
181
+ * {@link MaterialGraphClient.demand}, `codes.advisory()`) hangs off a
182
+ * `variantId`. Instead use {@link MaterialGraphClient.materials} for many ids,
183
+ * {@link MaterialGraphClient.materialBlocks} to tell "no value" from "not
184
+ * checked", {@link MaterialGraphClient.search} when you have no id yet.
185
+ *
186
+ * @param variantId Opaque — pass back exactly what you were given.
187
+ */
188
+ material(variantId: VariantId, options?: {
85
189
  includeSiblings?: boolean;
86
190
  }, signal?: AbortSignal): Promise<MaterialResponse>;
87
- /** The other variants of the same product (GET `/api/v1/materials/{variantId}/siblings`). */
88
- siblings(variantId: string, signal?: AbortSignal): Promise<SiblingsResponse>;
89
- /** Canonical block completeness for a material (GET `/api/v1/materials/{variantId}/blocks`). */
90
- materialBlocks(variantId: string, signal?: AbortSignal): Promise<MaterialBlockProjection>;
91
- /** Safe images and downloads for a material (GET `/api/v1/materials/{variantId}/assets`). */
92
- materialAssets(variantId: string, signal?: AbortSignal): Promise<MaterialAssetProjection>;
93
- /** Read one static governed schema resource. */
191
+ /**
192
+ * THE QUESTION: "what other colourways and finishes does this product come in?"
193
+ * — the SAME product, different variants.
194
+ *
195
+ * Instead use {@link MaterialGraphClient.pairings} for a DIFFERENT product that
196
+ * goes with this one, and `search({ similarTo })` for one that LOOKS like it.
197
+ */
198
+ siblings(variantId: VariantId, signal?: AbortSignal): Promise<SiblingsResponse>;
199
+ /**
200
+ * THE QUESTION: "what do we actually KNOW about this material, and what have we
201
+ * simply not checked?" The only read that distinguishes a determined absence
202
+ * from an unasked question — every field carries a `status`, so this is the one
203
+ * surface entitled to back a negative ("no fire rating").
204
+ * {@link MaterialGraphClient.material} cannot: there an absence is merely unknown.
205
+ */
206
+ materialBlocks(variantId: VariantId, signal?: AbortSignal): Promise<MaterialBlockProjection>;
207
+ /**
208
+ * THE QUESTION: "what images and documents may I SHOW for this material?" — the
209
+ * redistribution-safe set (GET `/api/v1/materials/{variantId}/assets`), not
210
+ * every file on record.
211
+ */
212
+ materialAssets(variantId: VariantId, signal?: AbortSignal): Promise<MaterialAssetProjection>;
213
+ /**
214
+ * THE QUESTION: "what does the schema DEFINE?" — the governed static registry.
215
+ *
216
+ * Definitions, not data: use {@link MaterialGraphClient.specificationFacets} for
217
+ * what the corpus actually holds and {@link MaterialGraphClient.glossary} to
218
+ * explain one term.
219
+ */
94
220
  schema<K extends SchemaResource>(resource: K, signal?: AbortSignal): Promise<SchemaResourceResponse<K>>;
95
- /** The "what pairs with this material" rail (GET `/api/v1/materials/{variantId}/pairings`). */
96
- pairings(variantId: string, options?: PairingsOptions, signal?: AbortSignal): Promise<PairingsResponse>;
97
221
  /**
98
- * Batch-read materials by id (POST `/api/v1/materials/batch`, ≤ 100 ids).
99
- * Throws client-side before any request when more than {@link MAX_BATCH_IDS}
100
- * ids are supplied.
222
+ * THE QUESTION: "what goes WITH this material?" — the companionship rail, hung
223
+ * off one resolved material (GET `/api/v1/materials/{variantId}/pairings`).
224
+ *
225
+ * Instead use {@link MaterialGraphClient.coordinate} for a whole palette of
226
+ * picks, {@link MaterialGraphClient.siblings} for the same product in another
227
+ * colour, and `search({ similarTo })` for a LIKE-THIS substitute — similar and
228
+ * complementary are opposite questions.
229
+ *
230
+ * READ `item.source` BEFORE PHRASING: the rail mixes three tiers of evidence —
231
+ * `cospec` (real orders), `considered` (board activity), `colour` (palette
232
+ * look-alike). Only `cospec` supports "specified together". `cospecCount` tells
233
+ * you how much of the rail is real order evidence.
234
+ */
235
+ pairings(variantId: VariantId, options?: PairingsOptions, signal?: AbortSignal): Promise<PairingsResponse>;
236
+ /**
237
+ * THE QUESTION: "hydrate these ids I already hold" (POST
238
+ * `/api/v1/materials/batch`, ≤ 100 ids) — never a discovery call. Throws
239
+ * client-side above {@link MAX_BATCH_IDS}. The response is NOT positional:
240
+ * match on `variantId` and read `missingIds` rather than comparing lengths.
241
+ * {@link MaterialGraphClient.materialsCached} is the CDN-friendly GET form.
242
+ *
243
+ * @param ids Opaque tokens, passed back verbatim.
101
244
  */
102
- materials(ids: string[], options: {
245
+ materials(ids: VariantId[], options: {
103
246
  view: "detail";
104
247
  idKind?: MaterialsBatchOptions["idKind"];
105
248
  }, signal?: AbortSignal): Promise<MaterialsBatchDetailResponse>;
106
- materials(ids: string[], options?: {
249
+ materials(ids: VariantId[], options?: {
107
250
  view?: "tile";
108
251
  idKind?: MaterialsBatchOptions["idKind"];
109
252
  }, signal?: AbortSignal): Promise<MaterialsBatchTileResponse>;
@@ -112,23 +255,37 @@ export declare class MaterialGraphClient {
112
255
  * `Cache-Control: public, max-age=300`). Same response as {@link materials};
113
256
  * prefer this for repeatable, cache-friendly id sets. Also ≤ 100 ids.
114
257
  */
115
- materialsCached(ids: string[], options: {
258
+ materialsCached(ids: VariantId[], options: {
116
259
  view: "detail";
117
260
  idKind?: MaterialsBatchGetOptions["idKind"];
118
261
  }, signal?: AbortSignal): Promise<MaterialsBatchDetailResponse>;
119
- materialsCached(ids: string[], options?: {
262
+ materialsCached(ids: VariantId[], options?: {
120
263
  view?: "tile";
121
264
  idKind?: MaterialsBatchGetOptions["idKind"];
122
265
  }, signal?: AbortSignal): Promise<MaterialsBatchTileResponse>;
123
- /** The three canonical colour vocabularies with live counts (GET `/api/v1/colours/facets`). */
266
+ /**
267
+ * THE QUESTION: "what colour WORDS may I use?" — family, mood and undertone
268
+ * with live counts. Vocabulary, not values: instead use `colours.profiles()`
269
+ * for a category's palette, {@link MaterialGraphClient.byColour} to search by a
270
+ * colour, {@link MaterialGraphClient.resolveColour} to turn a NAME into a hex.
271
+ */
124
272
  colourFacets(signal?: AbortSignal): Promise<ColourFacetsResponse>;
125
- /** Resolve a colour name/phrase to a hex (GET `/api/v1/search/colours/resolve`). */
273
+ /**
274
+ * THE QUESTION: "what hex is 'Hague Blue'?" — the step before
275
+ * {@link MaterialGraphClient.byColour} when you hold a name rather than a hex.
276
+ * A `null` match means unrecognised, not "no such colour".
277
+ */
126
278
  resolveColour(name: string, signal?: AbortSignal): Promise<ResolveColourResponse>;
127
279
  /** Ranked colour-name typeahead (GET `/api/v1/search/colours/suggest`). */
128
280
  suggestColour(q: string, options?: {
129
281
  limit?: number;
130
282
  }, signal?: AbortSignal): Promise<SuggestColourResponse>;
131
283
  /**
284
+ * THE QUESTION: "what is the user starting to type?" It ranks NAMES, not
285
+ * materials — instead use {@link MaterialGraphClient.search} once they commit,
286
+ * {@link MaterialGraphClient.suggestColour} for colour names, and
287
+ * {@link MaterialGraphClient.searchVocabulary} for attribute/value tokens.
288
+ *
132
289
  * Entity typeahead across products, brands and categories
133
290
  * (GET `/api/v1/search/suggest`). Built for per-keystroke use: one indexed
134
291
  * lookup, no embedding call and no model. Every suggestion carries the
@@ -140,13 +297,47 @@ export declare class MaterialGraphClient {
140
297
  * const { suggestions } = await client.suggest("carr", { kind: "product" });
141
298
  */
142
299
  suggest(q: string, options?: SuggestOptions, signal?: AbortSignal): Promise<SuggestResponse>;
143
- /** Resolve a free-text spec phrase to canonical attribute ids (GET `/api/v1/catalog/attributes/resolve`). */
300
+ /**
301
+ * THE QUESTION: "the user said 'fire rating' — which canonical attribute is
302
+ * that?" Free text in, canonical `attributeId`s out; pair with
303
+ * {@link MaterialGraphClient.resolveAttributeValue} for the VALUE, then spend
304
+ * the pair on {@link MaterialGraphClient.bySpec} or `search({ filters })`.
305
+ * Instead use {@link MaterialGraphClient.specificationFacets} to browse rather
306
+ * than resolve.
307
+ */
144
308
  resolveAttribute(q: string, options?: ResolveAttributeOptions, signal?: AbortSignal): Promise<ResolveAttributeResponse>;
145
- /** Resolve a value phrase against one canonical attribute's dictionary. */
309
+ /**
310
+ * THE QUESTION: "the user said 'class A' — what canonical term is that, under
311
+ * THIS attribute?" The value half of the pair
312
+ * {@link MaterialGraphClient.resolveAttribute} starts. Read
313
+ * `resolution.recommendedAction`, not just `best`: `ambiguous` means ask.
314
+ *
315
+ * @param attributeId Canonical vocabulary, not an opaque id — discover it, do
316
+ * not invent it.
317
+ */
146
318
  resolveAttributeValue(attributeId: string, query: string, options?: ResolveAttributeValueOptions, signal?: AbortSignal): Promise<ResolveAttributeValueResponse>;
147
- /** Search canonical attributes and value-dictionary terms together. */
319
+ /**
320
+ * THE QUESTION: "is 'FloorScore' an attribute or a value, and what is it called
321
+ * here?" The catch-all when you do not know which half of an `attribute:value`
322
+ * pair a phrase belongs to. Instead use
323
+ * {@link MaterialGraphClient.resolveAttribute} /
324
+ * {@link MaterialGraphClient.resolveAttributeValue} once you do, and
325
+ * {@link MaterialGraphClient.glossary} to EXPLAIN a term rather than find it.
326
+ */
148
327
  searchVocabulary(query: string, options?: VocabularySearchOptions, signal?: AbortSignal): Promise<VocabularySearchResponse>;
149
328
  /**
329
+ * THE QUESTION: "which materials, from ANYONE, are ASTM E84 Class A AND
330
+ * GREENGUARD-certified?" — hard specifications, no ranking, every brand at once
331
+ * whatever each called the value in source.
332
+ *
333
+ * Instead use `search({ filters })` when the spec merely GATES a ranked
334
+ * question, `codes.advisory()` when the real question is what a space demands,
335
+ * and {@link MaterialGraphClient.specificationFacets} first if you do not know
336
+ * the canonical tokens — guessing one returns an honest, empty answer.
337
+ *
338
+ * A PAGE, NOT A CORPUS, unusually so: `totalMatches` is the size of the
339
+ * returned sample under `limit`, not a corpus count. See {@link BySpecResponse}.
340
+ *
150
341
  * Find materials by canonical specification across every brand — the
151
342
  * value-anchored, spec-only primitive (GET `/api/v1/search/specifications`).
152
343
  * Each `spec` is an `attribute:value` criterion; a variant must satisfy ALL of
@@ -158,22 +349,49 @@ export declare class MaterialGraphClient {
158
349
  * vocabulary with {@link specificationFacets} and {@link attributeValues}.
159
350
  */
160
351
  bySpec(spec: string | string[], options?: BySpecOptions, signal?: AbortSignal): Promise<BySpecResponse>;
161
- /** The product-category tree, flattened (GET `/api/v1/taxonomy/categories`). */
352
+ /**
353
+ * THE QUESTION: "what kinds of thing exist, and what are their canonical keys?"
354
+ * The source of every `categoryKey` / product-type id the other lenses accept
355
+ * (`search({ category })`, `colours.profiles()`,
356
+ * {@link MaterialGraphClient.applicableSpecs}). Discover, never hand-write.
357
+ */
162
358
  taxonomy(signal?: AbortSignal): Promise<TaxonomyResponse>;
163
- /** Echo the authenticated principal and scopes (GET `/api/v1/whoami`). */
359
+ /**
360
+ * THE QUESTION: "what is this key allowed to do?" Check `scopes` before
361
+ * offering a capability: the write surfaces need `mg:ingest` / `mg:compose`,
362
+ * and `mg:admin` unlocks extra evidence fields.
363
+ */
164
364
  whoami(signal?: AbortSignal): Promise<WhoamiResponse>;
165
365
  /**
366
+ * THE QUESTION: "here is my whole scheme so far — what completes it?" The
367
+ * multi-seed form of {@link MaterialGraphClient.pairings}: lift is summed across
368
+ * seeds, so a companion suiting one pick ranks below one suiting several.
369
+ * Instead use `pairings` for a single material and `colours.palette()` when the
370
+ * scheme is COLOURS rather than materials. Check `unresolved` before presenting.
371
+ *
166
372
  * Coordinate a palette of already-chosen materials (POST
167
373
  * `/api/v1/pairings/coordinate`). Returns demand-lift-ranked companions with
168
374
  * colour swatches and an annotate-only `colourFit` (never reorders in v1).
169
375
  */
170
376
  coordinate(input: CoordinateInput, signal?: AbortSignal): Promise<CoordinateResponse>;
171
377
  /**
172
- * How much a material is specified, plus what's specified alongside it (GET
173
- * `/api/v1/demand`). Provide EXACTLY ONE of `variantId` or `material`.
378
+ * THE QUESTION: "how much is this material actually specified, and what gets
379
+ * specified alongside it?" — real order behaviour, trailing 36 months.
380
+ *
381
+ * Instead use {@link MaterialGraphClient.demandBatch} for many ids (this one
382
+ * also resolves free text, which the batch deliberately does not) and
383
+ * {@link MaterialGraphClient.pairings} for a presentable rail. A null `demand`
384
+ * means UNREAD, never zero. Provide EXACTLY ONE of `variantId` or `material`.
174
385
  */
175
386
  demand(params: DemandParams, signal?: AbortSignal): Promise<DemandResponse>;
176
387
  /**
388
+ * THE QUESTION: "for each of these picks I already hold, how much momentum does
389
+ * it have?" Enrichment, never discovery or ranking. Instead use
390
+ * {@link MaterialGraphClient.demand} for one material named in free text and
391
+ * {@link MaterialGraphClient.search} to find candidates at all. Only
392
+ * `state: "available"` carries numbers; the other three mean "no number", never
393
+ * "no demand".
394
+ *
177
395
  * Enrich a bounded set of ALREADY-CHOSEN candidate ids with their real
178
396
  * specification demand (POST `/api/v1/demand/batch`, 1–48 ids). The bounded
179
397
  * batch complement to {@link demand}: no fuzzy resolution, ranking, or
@@ -195,14 +413,30 @@ export declare class MaterialGraphClient {
195
413
  */
196
414
  demandBatch(input: DemandBatchInput, signal?: AbortSignal): Promise<DemandBatchResponse>;
197
415
  /**
416
+ * THE QUESTION: "may I put these materials in an A-2 corridor in Texas?"
417
+ *
198
418
  * Badge materials against the advisory building-code registry (POST
199
419
  * `/api/v1/codes/advisory`). Identical to {@link CodesClient.advisory}, kept
200
- * flat because it shipped before the `codes` namespace existed.
420
+ * flat because it shipped before the `codes` namespace existed — prefer
421
+ * `mg.codes.advisory()` in new code, and start at `mg.codes.context()` to
422
+ * discover every accepted context value. `unverified` is not a pass.
201
423
  */
202
424
  codeAdvisory(input: CodeAdvisoryInput, signal?: AbortSignal): Promise<CodeAdvisoryResponse>;
203
- /** Extract a named colour palette from an uploaded image (POST `/api/v1/search/palettes/extract`). */
425
+ /**
426
+ * THE QUESTION: "what colours are in this image?" — the palette only, no
427
+ * materials. The bridge from an inspiration image into the colour lens: extract
428
+ * here, then feed the swatches to `colours.palette()` or
429
+ * {@link MaterialGraphClient.byColour}. Instead use
430
+ * {@link MaterialGraphClient.match} for the MATERIALS in the photo.
431
+ */
204
432
  extractPalette(input: ExtractPaletteInput, signal?: AbortSignal): Promise<ExtractPaletteResponse>;
205
433
  /**
434
+ * THE QUESTION: "what can I even filter on, and what values does the corpus
435
+ * actually hold?" The start of every spec-driven workflow. Instead use
436
+ * {@link MaterialGraphClient.attributeValues} for ONE attribute's distribution,
437
+ * {@link MaterialGraphClient.applicableSpecs} for what a product type COULD
438
+ * carry, {@link MaterialGraphClient.schema} for the definition.
439
+ *
206
440
  * Discover the live specification vocabulary (GET
207
441
  * `/api/v1/catalog/specifications/facets`) — the facetable spec attributes that
208
442
  * have real product-graph data, each with its top canonical values and the
@@ -223,13 +457,36 @@ export declare class MaterialGraphClient {
223
457
  * or every fire classification and how many variants carry each.
224
458
  */
225
459
  attributeValues(attributeId: string, signal?: AbortSignal): Promise<SpecFacetsDistributionResponse>;
226
- /** The specs a product type COULD carry (GET `/api/v1/taxonomy/categories/{key}/specs`). */
460
+ /**
461
+ * THE QUESTION: "what specs COULD a material of this type carry?" — the
462
+ * template, not the data. Use it to know which absences are meaningful; instead
463
+ * use {@link MaterialGraphClient.specificationFacets} for what the corpus holds
464
+ * and {@link MaterialGraphClient.materialBlocks} for one material.
465
+ *
466
+ * @param key A canonical taxonomy key from {@link MaterialGraphClient.taxonomy}.
467
+ */
227
468
  applicableSpecs(key: string, signal?: AbortSignal): Promise<ApplicableSpecsResponse>;
228
- /** Resolve a term to its meaning in the schema (GET `/api/v1/glossary/{term}`). */
469
+ /**
470
+ * THE QUESTION: "what does 'DCOF' MEAN?" — one term explained, for a human.
471
+ * Explanation, not retrieval: instead use
472
+ * {@link MaterialGraphClient.searchVocabulary} to FIND a token and
473
+ * {@link MaterialGraphClient.attributeValues} for the values it takes.
474
+ */
229
475
  glossary(term: string, signal?: AbortSignal): Promise<GlossaryTermResponse>;
230
- /** List registered brands (GET `/api/v1/brands`). */
476
+ /**
477
+ * THE QUESTION: "whose catalogs are in here, and how big is each?" The SDK's
478
+ * one OFFSET-paginated surface: read `meta.total` for the collection size and
479
+ * `meta.hasMore` to keep walking. Instead use {@link MaterialGraphClient.brand}
480
+ * for one brand's profile and trade signals.
481
+ */
231
482
  brands(options?: BrandsOptions, signal?: AbortSignal): Promise<BrandCatalogListPage>;
232
- /** Resolve a brand and return its profile + trade signals (GET `/api/v1/brands/{brandSlug}`). */
483
+ /**
484
+ * THE QUESTION: "who is this brand, and what do they claim?" — profile plus the
485
+ * trade/spec signals ON RECORD. `trade` is keyed by attribute id and an ABSENT
486
+ * key means no value is held — never infer `false`. Instead use
487
+ * {@link MaterialGraphClient.search} to find their materials; this describes the
488
+ * company, not the catalog.
489
+ */
233
490
  brand(brandSlug: string, options?: BrandOptions, signal?: AbortSignal): Promise<BrandProfileResponse>;
234
491
  /**
235
492
  * Upload one atomic source-material batch (POST `/api/v1/source-material`,
@@ -246,13 +503,31 @@ export declare class MaterialGraphClient {
246
503
  * immediately. A batch you do not own raises the same 404 as one that does not
247
504
  * exist. Requires the `mg:compose` scope.
248
505
  */
249
- sourceMaterialBatch(batchId: string, signal?: AbortSignal): Promise<SourceMaterialBatchDetail>;
250
- /** List invocable agents, workflows, and tools (GET `/api/v1/capabilities`). */
506
+ sourceMaterialBatch(batchId: BatchId, signal?: AbortSignal): Promise<SourceMaterialBatchDetail>;
507
+ /**
508
+ * THE QUESTION: "what can the hosted agentic surface be asked to DO?" Start
509
+ * here before {@link MaterialGraphClient.createRun}: each capability carries its
510
+ * live input/output schemas, scopes and side-effect class, so nothing about a
511
+ * Run need be hardcoded.
512
+ */
251
513
  capabilities(signal?: AbortSignal): Promise<CapabilityManifestResponse>;
252
- /** Start an asynchronous capability Run (POST `/api/v1/runs`). */
514
+ /**
515
+ * THE QUESTION: "do a whole job for me" — the ASYNC lens. Everything else here
516
+ * answers in one round trip; a Run may suspend for human input and finish
517
+ * minutes later. Follow with {@link MaterialGraphClient.streamRunEvents} (live)
518
+ * or {@link MaterialGraphClient.run} (poll); resume via
519
+ * {@link MaterialGraphClient.resumeRun}; pick the capability from
520
+ * {@link MaterialGraphClient.capabilities}. Pass `idempotencyKey` so a retry
521
+ * replays rather than duplicates.
522
+ */
253
523
  createRun(body: CreatePublicRunRequest, signal?: AbortSignal): Promise<CreatePublicRunResponse>;
254
- /** Get a Run's current state (GET `/api/v1/runs/{runId}`). */
255
- run(runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
524
+ /**
525
+ * THE QUESTION: "where has this Run got to?" — the caller-owned, redacted
526
+ * ledger record. Polling; prefer {@link MaterialGraphClient.streamRunEvents}
527
+ * when a human is waiting. `suspended` is not a failure — it means the Run is
528
+ * asking you something (see {@link MaterialGraphClient.resumeRun}).
529
+ */
530
+ run(runId: RunId, signal?: AbortSignal): Promise<PublicRunResponse>;
256
531
  /**
257
532
  * Export a completed compose Run's scheme as a finish schedule — one row per
258
533
  * slot, every attribute value carrying its source and verification state
@@ -260,7 +535,7 @@ export declare class MaterialGraphClient {
260
535
  * `missing` cells, never blanks. The `.xlsx` form is a direct-URL download:
261
536
  * `GET /api/v1/runs/{runId}/schedule.xlsx` with the same bearer key.
262
537
  */
263
- runSchedule(runId: string, options?: GetRunScheduleOptions): Promise<FinishScheduleResponse>;
538
+ runSchedule(runId: RunId, options?: GetRunScheduleOptions): Promise<FinishScheduleResponse>;
264
539
  /**
265
540
  * Resume a suspended Run (POST `/api/v1/runs/{runId}/resume`).
266
541
  *
@@ -268,9 +543,13 @@ export declare class MaterialGraphClient {
268
543
  * {@link ResumeRunOptions}, whose `stepId` guards against resuming a step the
269
544
  * run has already moved past — the API answers 409 on a mismatch (MG-608).
270
545
  */
271
- resumeRun(runId: string, resumeData: unknown, signalOrOptions?: AbortSignal | ResumeRunOptions): Promise<PublicRunResponse>;
272
- /** Request durable Run cancellation (POST `/api/v1/runs/{runId}/cancel`). */
273
- cancelRun(runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
546
+ resumeRun(runId: RunId, resumeData: unknown, signalOrOptions?: AbortSignal | ResumeRunOptions): Promise<PublicRunResponse>;
547
+ /**
548
+ * THE QUESTION: "stop this Run." Requests DURABLE cancellation — the Run
549
+ * acknowledges and unwinds; the call returning does not mean it has stopped.
550
+ * Poll {@link MaterialGraphClient.run} for the terminal state.
551
+ */
552
+ cancelRun(runId: RunId, signal?: AbortSignal): Promise<PublicRunResponse>;
274
553
  /**
275
554
  * Derive an "option B" scheme from a succeeded compose Run along one
276
555
  * constraint axis (POST `/api/v1/schemes/vary`). Deterministic and
@@ -284,25 +563,32 @@ export declare class MaterialGraphClient {
284
563
  * `/api/v1/runs/{runId}/events`, SSE). Iterates until the stream ends or the
285
564
  * signal aborts; pass `after` to resume from the last observed sequence.
286
565
  */
287
- streamRunEvents(runId: string, options?: StreamRunEventsOptions): AsyncGenerator<RunEvent>;
566
+ streamRunEvents(runId: RunId, options?: StreamRunEventsOptions): AsyncGenerator<RunEvent>;
288
567
  /**
289
568
  * Create a persistent project owned by the caller (POST `/api/v1/projects`).
290
569
  * Returns the freshly assembled state (an empty event log). Requires the
291
570
  * `mg:ingest` scope.
292
571
  */
293
572
  createProject(body?: CreateProjectRequest, signal?: AbortSignal): Promise<ProjectState>;
294
- /** Read a project's assembled state (GET `/api/v1/projects/{projectId}`). */
295
- getProject(projectId: string, signal?: AbortSignal): Promise<ProjectState>;
573
+ /**
574
+ * THE QUESTION: "what has happened on this project?" — the append-only event log
575
+ * FOLDED into a read model, plus the raw `events`. Read the folded collections;
576
+ * the log is there for audit.
577
+ */
578
+ getProject(projectId: ProjectId, signal?: AbortSignal): Promise<ProjectState>;
296
579
  /**
297
580
  * Append one event to a project's append-only process trail (POST
298
581
  * `/api/v1/projects/{projectId}/events`). Requires the `mg:ingest` scope.
299
582
  */
300
- appendProjectEvent(projectId: string, body: AppendProjectEventRequest, signal?: AbortSignal): Promise<ProjectEventReceipt>;
301
- /** List the caller's own projects, newest first (GET `/api/v1/projects`). */
583
+ appendProjectEvent(projectId: ProjectId, body: AppendProjectEventRequest, signal?: AbortSignal): Promise<ProjectEventReceipt>;
584
+ /**
585
+ * THE QUESTION: "what Boxes do I have?" Scoped to the authenticated principal —
586
+ * another key's projects are invisible, not forbidden.
587
+ */
302
588
  listProjects(signal?: AbortSignal): Promise<ProjectListResponse>;
303
589
  /**
304
590
  * Collect Run events until a terminal event, the deadline, or the stream ends
305
591
  * — the bounded events-log pattern (a single pass over {@link streamRunEvents}).
306
592
  */
307
- drainRunEvents(runId: string, options: DrainRunEventsOptions): Promise<RunEvent[]>;
593
+ drainRunEvents(runId: RunId, options: DrainRunEventsOptions): Promise<RunEvent[]>;
308
594
  }