@instruments/materialgraph 0.26.0 → 0.27.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 (113) hide show
  1. package/association-types.d.ts +18 -7
  2. package/attribution-types.d.ts +2 -2
  3. package/brand-types.d.ts +8 -0
  4. package/brands-client.d.ts +5 -36
  5. package/brands-client.js +7 -33
  6. package/by-colour-types.d.ts +40 -13
  7. package/by-spec-types.d.ts +0 -67
  8. package/by-spec-types.js +7 -5
  9. package/catalog-discovery-types.d.ts +1 -16
  10. package/catalogue-client.d.ts +12 -31
  11. package/catalogue-client.js +10 -46
  12. package/catalogue-types.d.ts +40 -0
  13. package/categories-client.d.ts +63 -0
  14. package/categories-client.js +48 -0
  15. package/category-types.d.ts +114 -0
  16. package/client-config.d.ts +2 -4
  17. package/client-config.js +2 -4
  18. package/client-vocabulary.d.ts +2 -4
  19. package/client-vocabulary.js +11 -9
  20. package/client.d.ts +103 -203
  21. package/client.js +126 -251
  22. package/codes-types.d.ts +21 -0
  23. package/colours-client.d.ts +4 -9
  24. package/colours-client.js +0 -8
  25. package/errors.d.ts +1 -1
  26. package/errors.js +1 -0
  27. package/identify-types.d.ts +49 -39
  28. package/identify-types.js +5 -3
  29. package/index.d.ts +11 -21
  30. package/index.js +4 -4
  31. package/list-variant-facets-types.d.ts +60 -0
  32. package/list-variants-types.d.ts +142 -0
  33. package/list-variants-types.js +12 -0
  34. package/match-stream-types.d.ts +13 -0
  35. package/match-stream-types.js +2 -0
  36. package/match-stream.js +12 -8
  37. package/match-types.d.ts +24 -0
  38. package/material-asset-types.d.ts +69 -49
  39. package/material-block-types.d.ts +2 -0
  40. package/material-composition-client.d.ts +21 -0
  41. package/material-composition-client.js +13 -0
  42. package/material-composition-types.d.ts +403 -0
  43. package/material-query-plan-types.d.ts +57 -141
  44. package/material-resources-client.d.ts +9 -7
  45. package/material-resources-client.js +19 -4
  46. package/material-types.d.ts +6 -14
  47. package/operations.d.ts +1 -1
  48. package/operations.js +20 -117
  49. package/package.json +5 -1
  50. package/palette-search-types.d.ts +59 -24
  51. package/query-understanding-types.d.ts +27 -5
  52. package/resolve-client.d.ts +6 -10
  53. package/resolve-client.js +5 -7
  54. package/response-schemas.d.ts +20 -0
  55. package/response-schemas.generated.d.ts +5 -0
  56. package/response-schemas.generated.js +160 -0
  57. package/response-schemas.js +113 -0
  58. package/run-types.d.ts +12 -93
  59. package/runs.d.ts +1 -37
  60. package/runs.js +2 -79
  61. package/schema-resource-types.d.ts +14 -1
  62. package/search-card-types.d.ts +22 -0
  63. package/search-index-plan-types.d.ts +70 -0
  64. package/search-refinement-types.d.ts +8 -38
  65. package/search-refinement-types.js +1 -0
  66. package/search-request-types.d.ts +117 -200
  67. package/search-response-receipt-types.d.ts +12 -268
  68. package/search-result-validation.d.ts +0 -16
  69. package/search-result-validation.js +22 -180
  70. package/spec-facet-types.d.ts +3 -8
  71. package/spec-types.d.ts +17 -10
  72. package/spec-types.js +8 -7
  73. package/specifications.d.ts +13 -55
  74. package/specifications.js +29 -73
  75. package/suggest-types.d.ts +4 -10
  76. package/suggest-types.js +1 -1
  77. package/types.d.ts +135 -338
  78. package/webmcp.d.ts +1 -1
  79. package/webmcp.generated.js +10 -94
  80. package/brand-most-sampled-types.d.ts +0 -33
  81. package/browse-types.d.ts +0 -236
  82. package/client-browse.d.ts +0 -78
  83. package/client-browse.js +0 -71
  84. package/client-exploration.d.ts +0 -9
  85. package/client-exploration.js +0 -5
  86. package/compare-types.d.ts +0 -89
  87. package/composition-client.d.ts +0 -33
  88. package/composition-client.js +0 -28
  89. package/composition-types.d.ts +0 -455
  90. package/composition-types.js +0 -10
  91. package/correction-types.d.ts +0 -88
  92. package/exploration-types.d.ts +0 -118
  93. package/exploration-types.js +0 -1
  94. package/fan-deck-types.d.ts +0 -42
  95. package/fan-deck-types.js +0 -9
  96. package/match-rejection-types.d.ts +0 -43
  97. package/match-rejection-types.js +0 -7
  98. package/palette-completion-types.d.ts +0 -115
  99. package/palette-completion-types.js +0 -1
  100. package/render-assets-types.d.ts +0 -65
  101. package/render-assets-types.js +0 -1
  102. package/search-collapse-types.d.ts +0 -69
  103. package/search-collapse-types.js +0 -1
  104. package/search-mood-types.d.ts +0 -6
  105. package/search-mood-types.js +0 -1
  106. package/search-window-types.d.ts +0 -25
  107. package/search-window-types.js +0 -1
  108. package/slot-pool-types.d.ts +0 -372
  109. package/slot-pool-types.js +0 -1
  110. /package/{brand-most-sampled-types.js → category-types.js} +0 -0
  111. /package/{browse-types.js → list-variant-facets-types.js} +0 -0
  112. /package/{compare-types.js → material-composition-types.js} +0 -0
  113. /package/{correction-types.js → search-index-plan-types.js} +0 -0
@@ -10,6 +10,7 @@
10
10
  * with {@link PairingItem}, so the two can never be concatenated into one list.
11
11
  * Present them as separate sections, and never describe a pairing as available.
12
12
  */
13
+ import type { CorpusScope } from "./corpus-scope-types.js";
13
14
  import type { Cursor, VariantId } from "./identifiers.js";
14
15
  import type { ProductTypeReceipt } from "./material-types.js";
15
16
  /**
@@ -54,13 +55,22 @@ export type DeclaredAssociation = {
54
55
  /** Review receipt; proposed types are never accepted categories. */
55
56
  productTypeReceipt: ProductTypeReceipt;
56
57
  declared: DeclaredProvenance;
58
+ /** The accepted display category, for a card's kind pill. Null when none is accepted. */
59
+ kindLabel: string | null;
60
+ /** The accepted `price_tier` term (`$` to `$$$$$`). Absent means unknown, never cheap. */
61
+ priceTier?: string;
62
+ /**
63
+ * The eligible variants of this association's product, the set `listVariants`
64
+ * pages. Absent when the product has no eligible variant.
65
+ */
66
+ colourwayCount?: number;
57
67
  };
58
68
  /**
59
69
  * One page of declared associations.
60
70
  *
61
71
  * READ `totalAssociations`, NOT `associations.length`, when rendering a count
62
72
  * like "Compatible Finishes (128)": the array is one page (up to 48), the total
63
- * is the whole set for this subject, direction and corpus. A page is not a
73
+ * is the whole set for this subject, direction and corpus scope. A page is not a
64
74
  * corpus — walk `nextCursor` before making any claim about the full set.
65
75
  */
66
76
  export type DeclaredAssociationsResponse = {
@@ -71,7 +81,7 @@ export type DeclaredAssociationsResponse = {
71
81
  */
72
82
  kind: "declared_association";
73
83
  associations: DeclaredAssociation[];
74
- /** The TRUE count for this subject, direction and corpus. Not the page size. */
84
+ /** The TRUE count for this subject, direction and corpus scope. Not the page size. */
75
85
  totalAssociations: number;
76
86
  /** How many associations this page carries. */
77
87
  returnedCount: number;
@@ -86,16 +96,17 @@ export type AssociatedOptions = {
86
96
  */
87
97
  limit?: number;
88
98
  /**
89
- * Source identity scope. `material_bank` returns associations pointing at a
90
- * variant carrying a Material Bank SKU. A SKU alone does not establish current
91
- * sampleability; use `sampleableOnly` for an accepted availability fact.
99
+ * Narrows the associations, `totalAssociations` and the cursor to one corpus
100
+ * scope. `materialBankOrderable` keeps associations pointing at a variant
101
+ * carrying a Material Bank id; absent or `all` is the whole declared set.
102
+ * Membership does not establish sampleability; use `sampleableOnly` for that.
92
103
  */
93
- corpus?: "all" | "material_bank";
104
+ corpusScope?: CorpusScope;
94
105
  /** Return only associations whose effective sampleability is true. */
95
106
  sampleableOnly?: boolean;
96
107
  /**
97
108
  * The previous response's `nextCursor`, passed back verbatim. A cursor is
98
- * bound to its subject, direction and corpus — replaying it against a
109
+ * bound to its subject, direction and corpus scope - replaying it against a
99
110
  * different one is a 400, never a silently different page.
100
111
  */
101
112
  cursor?: Cursor;
@@ -13,12 +13,12 @@
13
13
  /**
14
14
  * The ranking basis, in one vocabulary across both ranked routes.
15
15
  *
16
- * The first five values are `search()`'s `mode` verbatim, and there
16
+ * The first seven values are `search()`'s `mode` verbatim, and there
17
17
  * `attribution.basis === mode` always. `"match"` is what `match()` reports;
18
18
  * that route has no `mode`, so this is the only field its outcomes can be
19
19
  * grouped by.
20
20
  */
21
- export type AttributionBasis = "image" | "colour" | "text" | "lexical" | "entity-browse" | "agent" | "match";
21
+ export type AttributionBasis = "image" | "colour" | "text" | "lexical" | "entity-browse" | "agent" | "attribute" | "match";
22
22
  /**
23
23
  * Envelope-level attribution receipt, on every successful ranked response.
24
24
  *
package/brand-types.d.ts CHANGED
@@ -5,7 +5,13 @@
5
5
  * The SDK's one OFFSET-paginated surface — everything else that pages uses an
6
6
  * opaque cursor. `meta` is where the page/corpus distinction is settled here.
7
7
  */
8
+ import type { CorpusScope, CorpusScopeReceipt } from "./corpus-scope-types.js";
8
9
  export type BrandsOptions = {
10
+ /**
11
+ * Which corpus to list from. Narrows the brands AND the corpus their counts
12
+ * are drawn from, inside the query. Absent or `"all"` is the whole graph.
13
+ */
14
+ corpusScope?: CorpusScope;
9
15
  /** Fuzzy name filter. Omit for the whole collection. */
10
16
  q?: string;
11
17
  /** Page size. Never a bound on the collection. */
@@ -25,6 +31,8 @@ export type BrandCatalogListItem = {
25
31
  variantCount: number;
26
32
  };
27
33
  export type BrandCatalogListPage = {
34
+ /** Present only when a narrowing scope applied; absence means the whole graph. */
35
+ corpusScope?: CorpusScopeReceipt;
28
36
  /** THIS PAGE. Its length is `meta.count`, never `meta.total`. */
29
37
  items: BrandCatalogListItem[];
30
38
  /**
@@ -1,48 +1,17 @@
1
1
  /**
2
- * The BRAND lens: the catalogue collection, one brand's profile, and one
3
- * brand's whole colour range, as thin wrappers over the shared
4
- * {@link HttpTransport}. `MaterialGraphClient.brands` / `.brand` /
5
- * `.brandColourDeck` delegate here; the contract lives in this file.
2
+ * The BRAND lens: the catalogue collection, as a thin wrapper over the shared
3
+ * {@link HttpTransport}. `MaterialGraphClient.brands` delegates here; the
4
+ * contract lives in this file.
6
5
  *
7
- * A brand is a COMPANY, not a catalogue. Every count these reads return is
6
+ * A brand is a COMPANY, not a catalogue. Every count this read returns is
8
7
  * coverage — what MaterialGraph has ingested — and never a claim about how
9
8
  * many products the maker sells.
10
9
  */
11
10
  import type { BrandCatalogListPage, BrandsOptions } from "./brand-types.js";
12
- import type { BrandFanDeck } from "./fan-deck-types.js";
13
11
  import type { HttpTransport } from "./http-transport.js";
14
- import type { BrandOptions, BrandProfileResponse } from "./types.js";
15
12
  /**
16
13
  * THE QUESTION: "whose catalogs are in here, and how big is each?" The SDK's
17
14
  * one OFFSET-paginated surface: read `meta.total` for the collection size and
18
- * `meta.hasMore` to keep walking. Instead use {@link brandProfile} for one
19
- * brand's profile and trade signals.
15
+ * `meta.hasMore` to keep walking.
20
16
  */
21
17
  export declare const listBrands: (transport: HttpTransport, options?: BrandsOptions, signal?: AbortSignal) => Promise<BrandCatalogListPage>;
22
- /**
23
- * THE QUESTION: "who is this brand, and what do they claim?" — profile plus the
24
- * trade/spec signals ON RECORD. `trade` is keyed by attribute id and an ABSENT
25
- * key means no value is held — never infer `false`. Instead use `search()` to
26
- * find their materials; this describes the company, not the catalog.
27
- */
28
- export declare const brandProfile: (transport: HttpTransport, brandSlug: string, options?: BrandOptions, signal?: AbortSignal) => Promise<BrandProfileResponse>;
29
- /**
30
- * THE QUESTION: "show me this maker's whole range, the way a fan deck reads."
31
- * Colours grouped by family — creams together, then greys, then browns — and
32
- * ordered light to dark within each. No stored scalar produces that order: a
33
- * lightness sort interleaves the whites and the creams, and a reader loses the
34
- * sense that they are looking at two different families.
35
- *
36
- * Deliberately unpaginated, so there is no cursor to walk and no all-brands
37
- * form: family order is only correct over a COMPLETE range, because the greys
38
- * cannot be placed against the browns while holding one page of each. A range
39
- * past the server ceiling reports `truncatedAt` rather than being quietly cut,
40
- * and an absent `truncatedAt` means the deck is whole.
41
- *
42
- * Every swatch carries the maker's PUBLISHED hex — this read never returns a
43
- * colour derived from imagery. For "what is near this colour, across makers"
44
- * use `byColour()` instead.
45
- *
46
- * @param brandSlug The brand's slug, as `listBrands()` reports it.
47
- */
48
- export declare const brandColourDeck: (transport: HttpTransport, brandSlug: string, signal?: AbortSignal) => Promise<BrandFanDeck>;
package/brands-client.js CHANGED
@@ -2,40 +2,14 @@ import { resolveOperation } from "./operations.js";
2
2
  /**
3
3
  * THE QUESTION: "whose catalogs are in here, and how big is each?" The SDK's
4
4
  * one OFFSET-paginated surface: read `meta.total` for the collection size and
5
- * `meta.hasMore` to keep walking. Instead use {@link brandProfile} for one
6
- * brand's profile and trade signals.
5
+ * `meta.hasMore` to keep walking.
7
6
  */
8
7
  export const listBrands = async (transport, options = {}, signal) => await transport.requestJson(resolveOperation("list_brands"), {
9
- query: { limit: options.limit, offset: options.offset, q: options.q },
10
- signal,
11
- });
12
- /**
13
- * THE QUESTION: "who is this brand, and what do they claim?" — profile plus the
14
- * trade/spec signals ON RECORD. `trade` is keyed by attribute id and an ABSENT
15
- * key means no value is held — never infer `false`. Instead use `search()` to
16
- * find their materials; this describes the company, not the catalog.
17
- */
18
- export const brandProfile = async (transport, brandSlug, options, signal) => await transport.requestJson(resolveOperation("get_brand_profile"), { pathParams: { brandSlug }, query: { q: options?.q }, signal });
19
- /**
20
- * THE QUESTION: "show me this maker's whole range, the way a fan deck reads."
21
- * Colours grouped by family — creams together, then greys, then browns — and
22
- * ordered light to dark within each. No stored scalar produces that order: a
23
- * lightness sort interleaves the whites and the creams, and a reader loses the
24
- * sense that they are looking at two different families.
25
- *
26
- * Deliberately unpaginated, so there is no cursor to walk and no all-brands
27
- * form: family order is only correct over a COMPLETE range, because the greys
28
- * cannot be placed against the browns while holding one page of each. A range
29
- * past the server ceiling reports `truncatedAt` rather than being quietly cut,
30
- * and an absent `truncatedAt` means the deck is whole.
31
- *
32
- * Every swatch carries the maker's PUBLISHED hex — this read never returns a
33
- * colour derived from imagery. For "what is near this colour, across makers"
34
- * use `byColour()` instead.
35
- *
36
- * @param brandSlug The brand's slug, as `listBrands()` reports it.
37
- */
38
- export const brandColourDeck = async (transport, brandSlug, signal) => await transport.requestJson(resolveOperation("brand_colour_deck"), {
39
- pathParams: { brandSlug },
8
+ query: {
9
+ corpusScope: options.corpusScope,
10
+ limit: options.limit,
11
+ offset: options.offset,
12
+ q: options.q,
13
+ },
40
14
  signal,
41
15
  });
@@ -37,11 +37,22 @@ export type ByColourMatch = {
37
37
  color: string | null;
38
38
  variantName: string | null;
39
39
  imageUrl: string | null;
40
+ /**
41
+ * SHA-256 of the displayed image `imageUrl` points at, as a hex digest.
42
+ * ABSENT means no hash is on record, never that there is no image.
43
+ */
44
+ imageSha256?: string;
40
45
  hex: string | null;
41
46
  distance: number;
42
47
  closeness: number;
43
48
  category: MatchCategory | null;
44
49
  oklab: OklabTriple | null;
50
+ /**
51
+ * Whether the reported `hex` has a hue compatible with the query colour, by
52
+ * colorscope's `isHueCompatible` at its default thresholds. A flag, never a
53
+ * gate. ABSENT when the match has no hex or the query was a palette.
54
+ */
55
+ hueCompatible?: boolean;
45
56
  /**
46
57
  * Where `hex` came from — PER MATCH, never per response. `"declared"`: the
47
58
  * manufacturer published it (a paint's own colour system), so it may be
@@ -111,7 +122,13 @@ export type ColourFamilyGate = {
111
122
  };
112
123
  /** Receipt for the hard category gate and its treatment of unknown typing. */
113
124
  export type ColourCategoryGate = {
114
- /** Category names or keys the caller asked the gate to keep. */
125
+ /**
126
+ * Present when the caller named no category: the page then holds surface
127
+ * materials only (the `materials` product-type groups, listed in
128
+ * `requested`), so lights, taps and furniture do not answer a colour.
129
+ */
130
+ defaulted?: true;
131
+ /** Category names or keys the gate kept: the caller's, or the default groups. */
115
132
  requested: string[];
116
133
  /** Only accepted primary product types can establish membership. */
117
134
  requiresAcceptedPrimaryProductType: true;
@@ -121,8 +138,8 @@ export type ColourCategoryGate = {
121
138
  /**
122
139
  * Receipt for the colour-identity collapse that turns a variant-grain ranking
123
140
  * into a page of TILES — one per (brand, product, dominant hex), one per stored
124
- * photograph. The live endpoint always sends it; it is optional here only so
125
- * responses captured before the collapse existed still type-check.
141
+ * photograph. Only responses stored before the one search carry it: the one
142
+ * search pages one row per product, so nothing collapses now.
126
143
  */
127
144
  export type ColourCollapse = {
128
145
  /** Ranked rows the collapse examined — this page's pool, never a corpus total. */
@@ -136,17 +153,15 @@ export type ColourCollapse = {
136
153
  * Receipt naming which retrieval lane answered, and the colours it actually ran
137
154
  * on. The live endpoint always sends it.
138
155
  *
139
- * Two lanes answer two different questions. A ONE-COLOUR query is a CONTAINMENT
140
- * question — which materials hold a swatch near this colour — and is answered as
141
- * a bounded OKLab ball over stored palette swatches. A multi-colour query is a
142
- * SIMILARITY question — which palettes as a whole resemble this palette — and is
143
- * answered by nearest-neighbour over the palette embedding. The two rank on
144
- * different geometries, so `colourDistance` on a match means a different thing
145
- * in each and is NOT comparable across them.
156
+ * Every live page says `index`: the one search's colour key, the colour's
157
+ * family on the search index, nearest first by OKLab distance. A palette is
158
+ * ordered by its first colour, and `droppedColours` names the rest.
159
+ * `containment` and `palette` appear only on responses stored before the one
160
+ * search.
146
161
  */
147
162
  export type ColourLaneReceipt = {
148
163
  /** Which lane answered. Widened so a new server lane still type-checks. */
149
- lane: OpenEnum<"containment" | "palette">;
164
+ lane: OpenEnum<"index" | "containment" | "palette">;
150
165
  /** Why this lane, in one sentence, for a human reader. */
151
166
  reason: string;
152
167
  /**
@@ -247,7 +262,11 @@ export type ByColourParams = {
247
262
  l?: number;
248
263
  a?: number;
249
264
  b?: number;
250
- /** HARD category filter — display names or taxonomy ids (repeatable). */
265
+ /**
266
+ * HARD category filter — display names or taxonomy ids (repeatable). Absent,
267
+ * the page holds surface materials only (`categoryGate.defaulted`), so
268
+ * lights, taps and furniture do not answer a colour.
269
+ */
251
270
  category?: string | string[];
252
271
  /**
253
272
  * HARD brand filter (repeatable) — a canonical brand id, exact brand SLUG
@@ -283,8 +302,16 @@ export type ByColourParams = {
283
302
  * drop.
284
303
  */
285
304
  family?: string;
286
- /** Single-colour order; omitted keeps representative browsing order. */
305
+ /**
306
+ * Kept so existing callers still parse: every page is ordered nearest first
307
+ * by the one search's colour key, so both values return the same page.
308
+ */
287
309
  order?: "representative" | "nearest";
310
+ /**
311
+ * Order by the colour of an existing Variant: materials the same colour as
312
+ * this one. Used when no hex, colour or OKLab triple is given.
313
+ */
314
+ variantId?: string;
288
315
  /** Maximum OKLab distance to each match's reported hex. Single-colour only. */
289
316
  maxColourDistance?: number;
290
317
  /** Narrow retrieval inside the server query; absent means the whole graph. */
@@ -1,18 +1,5 @@
1
1
  import type { ValueResolution } from "./catalog-discovery-types.js";
2
2
  import type { VerdictCitation } from "./citation-types.js";
3
- import type { VariantId } from "./identifiers.js";
4
- /**
5
- * One criterion a match satisfied, with the trust behind it.
6
- *
7
- * `confidence` (0–1) and `sourceBasis` are the CLAIM's provenance, not decoration
8
- * — quote them wherever the value is quoted. A `null` on either means the trust
9
- * is unrecorded, which is weaker than a low number, not stronger.
10
- */
11
- export type BySpecMatchedCell = {
12
- attributeId: string;
13
- confidence: number | null;
14
- sourceBasis: string | null;
15
- };
16
3
  /**
17
4
  * The six states a verdict cell can report — and what each licenses you to say.
18
5
  *
@@ -97,19 +84,6 @@ export type BySpecVerdict = {
97
84
  * so search-surface code can name it for what it is rather than duplicating it.
98
85
  */
99
86
  export type MaterialVerdict = BySpecVerdict;
100
- export type BySpecMatch = {
101
- /** Opaque — round-trip verbatim. */
102
- variantId: VariantId;
103
- brand: string;
104
- product: string;
105
- variant: string;
106
- color: string | null;
107
- imageUrl: string | null;
108
- hex: string | null;
109
- matched: BySpecMatchedCell[];
110
- minConfidence: number | null;
111
- verdict: BySpecVerdict;
112
- };
113
87
  /**
114
88
  * One criterion the server could not resolve to canonical vocabulary, with the
115
89
  * resolver's ranked suggestions for it.
@@ -138,44 +112,3 @@ export type BySpecUnresolvedCriterion = {
138
112
  /** Ranked candidates for whichever half failed. See {@link ValueResolution}. */
139
113
  resolution: ValueResolution;
140
114
  };
141
- /**
142
- * The find-by-spec envelope — A PAGE, AND NOTHING BUT A PAGE.
143
- *
144
- * Read `totalMatches` carefully: despite the name it is the route's own words,
145
- * "Size of the returned (limited) sample, not the true corpus total" — it is
146
- * literally `matches.length` under the `limit` you asked for. This surface
147
- * states NO corpus total, so "N materials meet this specification" is not a
148
- * claim you can make from one response. Raise `limit`, or say "at least N".
149
- * `brandsSpanned` is likewise the brand count OF THIS SAMPLE.
150
- *
151
- * And check `unresolvedCriteria` BEFORE reading any of that: when it is present
152
- * the numbers beside it describe a query that never ran.
153
- */
154
- export type BySpecResponse = {
155
- /** The criteria as parsed — check them before trusting a surprising result. */
156
- criteria: {
157
- attribute: string;
158
- value: string;
159
- }[];
160
- /** `matches.length`, i.e. THIS SAMPLE under `limit`. Never a corpus total. */
161
- totalMatches: number;
162
- /** Distinct brands in the returned sample — a lower bound on cross-brand reach. */
163
- brandsSpanned: number;
164
- /** The sample, photo-first, capped by `limit`. */
165
- matches: BySpecMatch[];
166
- /**
167
- * Present ONLY when a criterion failed exact canonical-vocabulary membership —
168
- * and its presence means THE QUERY WAS NEVER RUN. A pre-flight gate short-circuits
169
- * before the search, so the rest of the envelope is a placeholder rather than
170
- * a finding: `matches` is `[]` and `totalMatches` is `0` because nothing was
171
- * searched, not because nothing qualifies. Reporting that zero as "no material
172
- * meets this specification" states a fact about the corpus on the strength of
173
- * a question nobody asked it.
174
- *
175
- * Treat these criteria as UNAPPLIED and surface them: retry with a
176
- * `resolution.candidates` id (subject to `recommendedAction` — `ambiguous`
177
- * means ask, not pick), or say the value could not be resolved. Absent means
178
- * every criterion resolved and the query did run.
179
- */
180
- unresolvedCriteria?: BySpecUnresolvedCriterion[];
181
- };
package/by-spec-types.js CHANGED
@@ -1,7 +1,9 @@
1
- // ── Catalog / spec — find-by-spec wire types ─────────────────────────────────
1
+ // ── Catalog / spec: verdict wire types ──────────────────────────────────────
2
2
  //
3
- // Hand-maintained mirror of the `CatalogBySpecResponse` OpenAPI schema (the
4
- // source of truth on the wire); the `sdk-openapi-parity` test gates this against
5
- // the generated spec. Split out of `types.ts` to keep that file under its growth
6
- // baseline; re-exported from `types.ts` so importers see no change.
3
+ // Hand-maintained mirror of the shared CandidateVerdict and of the pre-flight
4
+ // unresolved-criterion contract. The by-spec ROUTE these once described
5
+ // (`catalog_by_spec`) moved to `list_variants`'s `filters` (MG-2222); the
6
+ // verdict and unresolved-criterion shapes below are independent of that route
7
+ // and stay. Split out of `types.ts` to keep that file under its growth
8
+ // baseline; re-exported from `types.ts`.
7
9
  export {};
@@ -6,10 +6,6 @@
6
6
  * then spend on `mg.bySpec()` or `mg.search({ filters })`.
7
7
  */
8
8
  import type { OpenEnum } from "./types.js";
9
- /** Options for resolving a free-text value against one attribute's dictionary. */
10
- export type ResolveAttributeValueOptions = {
11
- limit?: number;
12
- };
13
9
  /**
14
10
  * One ranked canonical value candidate. `tier` says HOW it matched, strongest
15
11
  * first: `exact_id` > `exact_alias` > `lexical` > `semantic`. A `semantic` hit
@@ -26,8 +22,7 @@ export type ResolveValueCandidate = {
26
22
  };
27
23
  /**
28
24
  * The resolver's verdict on ONE free-text token — the single shape every
29
- * resolving surface speaks, so a caller learns to read it once. Returned whole
30
- * by {@link MaterialGraphClient.resolveAttributeValue}, and carried per bad
25
+ * resolving surface speaks, so a caller learns to read it once. Carried per bad
31
26
  * criterion by `bySpec`'s `unresolvedCriteria`.
32
27
  *
33
28
  * READ `recommendedAction`, NOT JUST `best`. `ambiguous` with a populated
@@ -52,16 +47,6 @@ export type ValueResolution = {
52
47
  candidates: ResolveValueCandidate[];
53
48
  recommendedAction: OpenEnum<"use_best" | "choose_from_candidates" | "ask_user" | "none_found">;
54
49
  };
55
- /** Response from resolving a free-text value against one attribute's dictionary. */
56
- export type ResolveAttributeValueResponse = {
57
- attribute: {
58
- id: string;
59
- name: string;
60
- dictionaryId: string;
61
- };
62
- /** The verdict on `q`. Read `recommendedAction` — see {@link ValueResolution}. */
63
- resolution: ValueResolution;
64
- };
65
50
  /** Options for searching registry attributes and value-dictionary terms together. */
66
51
  export type VocabularySearchOptions = {
67
52
  limit?: number;
@@ -1,45 +1,26 @@
1
- import type { BrandMostSampledResponse } from "./brand-most-sampled-types.js";
2
1
  import type { CatalogDocumentList, CatalogPageListResult, CatalogProductReferenceReadResult, CatalogueChannelCollectionsResponse, CatalogueCollectionsOptions, CatalogueDocumentAccessResult, CataloguePageRepresentationReadResult, CatalogueReferenceBatchResponse } from "./catalogue-types.js";
3
- import type { CompareInput, CompareResponse } from "./compare-types.js";
4
- import type { CreateCorrectionInput, CorrectionResponse, CorrectionsListResponse } from "./correction-types.js";
5
2
  import type { HttpTransport } from "./http-transport.js";
6
3
  import type { VariantId } from "./identifiers.js";
7
- import type { IdentifySelectedPageInput, IdentifySelectedPageResult } from "./identify-types.js";
8
- import type { MatchRejection, MatchRejectionList, RejectMatchResultInput } from "./match-rejection-types.js";
4
+ import type { IdentifyResponse, IdentifyVariantInput } from "./identify-types.js";
9
5
  import type { SamplizeCatalogueOptions, SamplizeCataloguePage } from "./samplize-catalogue-types.js";
10
6
  import type { SamplizeColorVecCache } from "./samplize-colorvec.js";
11
7
  /** Catalogue publication operations inherited by the public SDK client. */
12
8
  export declare class CatalogueClient {
13
9
  #private;
14
10
  protected constructor(transport: HttpTransport);
15
- /** Brand products ranked by observed Material Bank sample-order lines, with window provenance. */
16
- brandMostSampled(brandId: string, options?: {
17
- limit?: number;
18
- }, signal?: AbortSignal): Promise<BrandMostSampledResponse>;
19
- /** Compare canonical Variants without searching or ranking them. */
20
- compare(input: CompareInput, signal?: AbortSignal): Promise<CompareResponse>;
21
- /** Submit confirmed caller-authored review work; never changes accepted truth. */
22
- createCorrection(input: CreateCorrectionInput, options: {
23
- idempotencyKey: string;
24
- signal?: AbortSignal;
25
- }): Promise<CorrectionResponse>;
26
- getCorrection(correctionId: string, signal?: AbortSignal): Promise<CorrectionResponse>;
27
- listCorrections(input?: {
28
- cursor?: string;
29
- limit?: number;
30
- }, signal?: AbortSignal): Promise<CorrectionsListResponse>;
31
11
  /**
32
- * Record "not this" on one result a match showed, against the response it
33
- * came from. `responseId` is that response's `attribution.responseId`; the
34
- * call needs the same key the match was made with. Repeating a rejection
35
- * returns the original receipt. This judges the match ranking and is never a
36
- * catalogue correction - wrong product facts go through `createCorrection`.
12
+ * THE QUESTION: "what does this manufacturer page or code resolve to in the
13
+ * graph?" Give `input.url` alone, or `input.code` with `input.brandId` (from
14
+ * {@link CatalogueClient.brands} - inherited by {@link MaterialGraphClient})
15
+ * when there is no page to read.
16
+ *
17
+ * Exact and scoreless: a sibling colourway, a similar image or a brand match
18
+ * alone never identifies a variant. Read `data.outcome` - `identified` names
19
+ * an exact stored variant, `placed` an exact brand with no variant match,
20
+ * `ambiguous` carries bounded `candidates`, and `unresolved` states why,
21
+ * never a guess. The submitted URL is never persisted.
37
22
  */
38
- rejectMatchResult(responseId: string, input: RejectMatchResultInput, signal?: AbortSignal): Promise<MatchRejection>;
39
- /** Read one match response back with every rejection recorded against it. */
40
- matchRejections(responseId: string, signal?: AbortSignal): Promise<MatchRejectionList>;
41
- /** Identify the canonical graph subject for one explicitly selected page. */
42
- identify(input: IdentifySelectedPageInput, signal?: AbortSignal): Promise<IdentifySelectedPageResult>;
23
+ identify(input: IdentifyVariantInput, signal?: AbortSignal): Promise<IdentifyResponse>;
43
24
  /** Complete producer-owned vectors and classifications for portable colour matching. */
44
25
  samplizeColorVecCache(sourceVersion: string, signal?: AbortSignal): Promise<SamplizeColorVecCache>;
45
26
  /** MG-owned full-catalogue facets and directional colour refinement before page limiting. */
@@ -5,54 +5,18 @@ export class CatalogueClient {
5
5
  constructor(transport) {
6
6
  this.#transport = transport;
7
7
  }
8
- /** Brand products ranked by observed Material Bank sample-order lines, with window provenance. */
9
- async brandMostSampled(brandId, options = {}, signal) {
10
- return await this.#transport.requestJson(resolveOperation("brands.most_sampled"), { query: { brandId, ...options }, signal });
11
- }
12
- /** Compare canonical Variants without searching or ranking them. */
13
- async compare(input, signal) {
14
- return await this.#transport.requestJson(resolveOperation("retrieval.compare"), {
15
- body: input,
16
- signal,
17
- });
18
- }
19
- /** Submit confirmed caller-authored review work; never changes accepted truth. */
20
- async createCorrection(input, options) {
21
- if (!/^[\u0021-\u007E]{1,200}$/u.test(options.idempotencyKey)) {
22
- throw new Error("A valid Idempotency-Key is required for Correction submission.");
23
- }
24
- return await this.#transport.requestJson(resolveOperation("corrections.create"), {
25
- body: input,
26
- ...options,
27
- });
28
- }
29
- async getCorrection(correctionId, signal) {
30
- return await this.#transport.requestJson(resolveOperation("corrections.get"), {
31
- pathParams: { correctionId },
32
- signal,
33
- });
34
- }
35
- async listCorrections(input = {}, signal) {
36
- return await this.#transport.requestJson(resolveOperation("corrections.list"), {
37
- query: input,
38
- signal,
39
- });
40
- }
41
8
  /**
42
- * Record "not this" on one result a match showed, against the response it
43
- * came from. `responseId` is that response's `attribution.responseId`; the
44
- * call needs the same key the match was made with. Repeating a rejection
45
- * returns the original receipt. This judges the match ranking and is never a
46
- * catalogue correction - wrong product facts go through `createCorrection`.
9
+ * THE QUESTION: "what does this manufacturer page or code resolve to in the
10
+ * graph?" Give `input.url` alone, or `input.code` with `input.brandId` (from
11
+ * {@link CatalogueClient.brands} - inherited by {@link MaterialGraphClient})
12
+ * when there is no page to read.
13
+ *
14
+ * Exact and scoreless: a sibling colourway, a similar image or a brand match
15
+ * alone never identifies a variant. Read `data.outcome` - `identified` names
16
+ * an exact stored variant, `placed` an exact brand with no variant match,
17
+ * `ambiguous` carries bounded `candidates`, and `unresolved` states why,
18
+ * never a guess. The submitted URL is never persisted.
47
19
  */
48
- async rejectMatchResult(responseId, input, signal) {
49
- return await this.#transport.requestJson(resolveOperation("reject_match_result"), { body: input, pathParams: { responseId }, signal });
50
- }
51
- /** Read one match response back with every rejection recorded against it. */
52
- async matchRejections(responseId, signal) {
53
- return await this.#transport.requestJson(resolveOperation("list_match_rejections"), { pathParams: { responseId }, signal });
54
- }
55
- /** Identify the canonical graph subject for one explicitly selected page. */
56
20
  async identify(input, signal) {
57
21
  return await this.#transport.requestJson(resolveOperation("retrieval.identify"), { body: input, signal });
58
22
  }
@@ -57,6 +57,43 @@ export interface CatalogueReference {
57
57
  status: "verified";
58
58
  isPrimary: boolean;
59
59
  }
60
+ /**
61
+ * Why a known variant has no verified reference. None of these says the
62
+ * product is missing from the printed book; nobody records how many of a
63
+ * brand's products a publication contains.
64
+ */
65
+ export type CatalogueReferenceAbsenceReason =
66
+ /**
67
+ * The registry retains no publication for the variant's brand. It says
68
+ * nothing about whether the maker publishes one.
69
+ */
70
+ "no_publication_on_record"
71
+ /** The edition is known but its document is not retained. */
72
+ | "publication_source_unavailable"
73
+ /** The document is retained but no page has been reviewed. */
74
+ | "publication_not_reviewed"
75
+ /** Pages were reviewed and this product is not among the references yet. */
76
+ | "product_not_yet_referenced";
77
+ /** The brand publication a `no_verified_reference` result was judged against. */
78
+ export type CatalogueReferencePublication = {
79
+ brand: {
80
+ id: string;
81
+ name: string;
82
+ };
83
+ catalogue: {
84
+ id: string;
85
+ title: string;
86
+ };
87
+ /** Null when the edition's document is not retained. */
88
+ documentId: string | null;
89
+ edition: {
90
+ id: string;
91
+ label: string;
92
+ title?: string;
93
+ };
94
+ /** Reviewed references the publication holds; present only when its pages were reviewed. */
95
+ referencedProductCount?: number;
96
+ };
60
97
  export type CatalogueReferenceResult = {
61
98
  variantId: string;
62
99
  status: "verified";
@@ -64,6 +101,9 @@ export type CatalogueReferenceResult = {
64
101
  } | {
65
102
  variantId: string;
66
103
  status: "no_verified_reference";
104
+ reason: CatalogueReferenceAbsenceReason;
105
+ /** Absent only when `reason` is `no_publication_on_record`. */
106
+ publication?: CatalogueReferencePublication;
67
107
  } | {
68
108
  variantId: string;
69
109
  status: "unresolved_variant";