@instruments/materialgraph 0.29.0 → 0.30.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 (80) hide show
  1. package/brands-client.d.ts +1 -1
  2. package/by-colour-types.d.ts +4 -4
  3. package/catalogue-client.d.ts +3 -3
  4. package/catalogue-client.js +3 -3
  5. package/catalogue-types.d.ts +1 -1
  6. package/client-vocabulary.d.ts +3 -5
  7. package/client-vocabulary.js +9 -6
  8. package/client.d.ts +73 -174
  9. package/client.js +85 -211
  10. package/colour-region-types.d.ts +18 -0
  11. package/colours-client.d.ts +10 -83
  12. package/colours-client.js +1 -85
  13. package/find-variants-by-colour-types.d.ts +197 -0
  14. package/find-variants-by-vector-types.d.ts +67 -0
  15. package/find-variants-by-vector-types.js +5 -0
  16. package/get-variant-types.d.ts +50 -0
  17. package/get-variant-types.js +10 -0
  18. package/identifiers.d.ts +1 -1
  19. package/identifiers.js +1 -1
  20. package/image-reference-types.d.ts +18 -0
  21. package/index.d.ts +8 -7
  22. package/index.js +1 -1
  23. package/list-enum-values-types.d.ts +58 -0
  24. package/list-variants-types.d.ts +6 -2
  25. package/material-asset-types.d.ts +2 -2
  26. package/material-types.d.ts +11 -150
  27. package/operations.d.ts +1 -1
  28. package/operations.js +26 -88
  29. package/package.json +1 -1
  30. package/palette-search-types.d.ts +3 -49
  31. package/resolve-colour-types.d.ts +5 -5
  32. package/resolve-variant-types.d.ts +75 -0
  33. package/{identify-types.js → resolve-variant-types.js} +2 -2
  34. package/response-schemas.generated.d.ts +1 -1
  35. package/response-schemas.generated.js +45 -84
  36. package/run-types.d.ts +23 -216
  37. package/runs.d.ts +10 -58
  38. package/runs.js +19 -106
  39. package/schema-resource-types.d.ts +2 -139
  40. package/schema-resource-types.js +0 -8
  41. package/search-card-types.d.ts +1 -1
  42. package/search-index-plan-types.d.ts +4 -1
  43. package/search-request-types.d.ts +10 -2
  44. package/spec-types.d.ts +4 -4
  45. package/spec-types.js +2 -2
  46. package/sse.d.ts +2 -2
  47. package/sse.js +2 -2
  48. package/types.d.ts +4 -38
  49. package/webmcp.generated.js +3 -3
  50. package/workflows-client.d.ts +54 -0
  51. package/workflows-client.js +41 -0
  52. package/client-source-material-comments.d.ts +0 -51
  53. package/client-source-material-comments.js +0 -6
  54. package/codes-client.d.ts +0 -118
  55. package/codes-client.js +0 -99
  56. package/codes-types.d.ts +0 -325
  57. package/colour-aggregate-types.d.ts +0 -163
  58. package/coordinate-client.d.ts +0 -23
  59. package/coordinate-client.js +0 -13
  60. package/demand-batch-types.d.ts +0 -118
  61. package/demand-types.d.ts +0 -154
  62. package/identify-types.d.ts +0 -82
  63. package/material-composition-client.d.ts +0 -21
  64. package/material-composition-client.js +0 -13
  65. package/material-composition-types.d.ts +0 -403
  66. package/material-composition-types.js +0 -1
  67. package/source-material-validation.d.ts +0 -7
  68. package/source-material-validation.js +0 -214
  69. package/spec-facet-types.d.ts +0 -48
  70. package/spec-facet-types.js +0 -7
  71. package/substitute-types.d.ts +0 -102
  72. package/substitute-types.js +0 -1
  73. package/suggest-types.d.ts +0 -67
  74. package/suggest-types.js +0 -28
  75. package/taxonomy-coverage-types.d.ts +0 -34
  76. package/taxonomy-coverage-types.js +0 -1
  77. /package/{codes-types.js → colour-region-types.js} +0 -0
  78. /package/{colour-aggregate-types.js → find-variants-by-colour-types.js} +0 -0
  79. /package/{demand-batch-types.js → image-reference-types.js} +0 -0
  80. /package/{demand-types.js → list-enum-values-types.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  import type { OpenEnum } from "./types.js";
2
- /** The nine governed static resources. */
3
- export type SchemaResource = "blocks" | "attributes" | "verticals" | "productTypes" | "units" | "valueDictionaries" | "sourceRequirements" | "productTypeTemplates" | "mappings";
2
+ /** The governed static resources. */
3
+ export type SchemaResource = "valueDictionaries";
4
4
  /**
5
5
  * Resource → the operation that serves it. Internal to the SDK (`index.ts`
6
6
  * re-exports this module's TYPES only), and it lives beside
@@ -8,15 +8,7 @@ export type SchemaResource = "blocks" | "attributes" | "verticals" | "productTyp
8
8
  * it must cover cannot drift apart.
9
9
  */
10
10
  export declare const SCHEMA_RESOURCE_OPERATION_IDS: {
11
- readonly attributes: "list_schema_attributes";
12
- readonly blocks: "list_schema_blocks";
13
- readonly mappings: "list_schema_mappings";
14
- readonly productTypeTemplates: "list_schema_product_type_templates";
15
- readonly productTypes: "list_schema_product_types";
16
- readonly sourceRequirements: "list_schema_source_requirements";
17
- readonly units: "list_schema_units";
18
11
  readonly valueDictionaries: "list_schema_value_dictionaries";
19
- readonly verticals: "list_schema_verticals";
20
12
  };
21
13
  export type SchemaResourceRequest = SchemaResource;
22
14
  export type SchemaLifecycle = {
@@ -37,81 +29,6 @@ export type SchemaExternalMapping = {
37
29
  notes?: string;
38
30
  };
39
31
  export type SchemaMatchStrength = OpenEnum<"exact" | "close" | "broad" | "narrow" | "related">;
40
- export type SchemaBlockItem = {
41
- id: string;
42
- semanticId: string;
43
- lifecycle: SchemaLifecycle;
44
- name: string;
45
- description: string;
46
- attributeIds: string[];
47
- };
48
- export type KnownSchemaQuantityKind = "length" | "area" | "volume" | "mass" | "time" | "temperature" | "currency" | "ratio" | "density" | "coverage" | "emissions" | "force" | "thermal_conductivity" | "power" | "voltage" | "current" | "frequency" | "luminous_flux" | "color_temperature" | "count" | "unitless";
49
- export type SchemaQuantityKind = OpenEnum<KnownSchemaQuantityKind>;
50
- export type SchemaAttributeItem = {
51
- id: string;
52
- semanticId: string;
53
- lifecycle: SchemaLifecycle;
54
- name: string;
55
- description: string;
56
- valueKind: OpenEnum<"string" | "text" | "integer" | "number" | "boolean" | "enum" | "object" | "reference">;
57
- quantityKind: SchemaQuantityKind | null;
58
- canonicalUnitId: string | null;
59
- acceptedUnitIds: string[];
60
- valueDictionaryId: string | null;
61
- defaultSourceRequirementId: string | null;
62
- externalMappings: SchemaExternalMapping[];
63
- searchFacet: boolean;
64
- comparable: boolean;
65
- usedInBlocks: string[];
66
- enumValues: string[] | null;
67
- };
68
- export type SchemaVerticalItem = {
69
- id: string;
70
- displayName: string;
71
- description: string;
72
- };
73
- export type SchemaApplicability = OpenEnum<"required" | "recommended" | "optional" | "unusual">;
74
- export type SchemaProductTypeBlock = {
75
- blockId: string;
76
- applicability: SchemaApplicability;
77
- requiredAttributes?: string[];
78
- optionalAttributes?: string[];
79
- sourceRequirementId?: string;
80
- completenessWeight?: number;
81
- notes?: string;
82
- optional?: boolean;
83
- };
84
- export type SchemaProductTypeItem = {
85
- id: string;
86
- name: string;
87
- description?: string;
88
- /** Display names this type carried before a rename, kept so they still find it. */
89
- formerNames?: string[];
90
- verticalId: string;
91
- parentProductTypeGroupId?: string;
92
- blocks: SchemaProductTypeBlock[];
93
- productTypeSpecificAttributes?: string[];
94
- semanticId: string;
95
- lifecycle: SchemaLifecycle;
96
- };
97
- export type SchemaUnitItem = {
98
- id: string;
99
- name: string;
100
- symbol: string;
101
- quantityKind: SchemaQuantityKind;
102
- acceptedSourceUnits?: string[];
103
- conversionToCanonical?: {
104
- factor: number;
105
- offset?: number;
106
- };
107
- notes?: string;
108
- /** UN/CEFACT Rec 20 common code (schema.org `unitCode`); absent when Rec 20 has none. */
109
- uncefactCode?: string;
110
- /** QUDT unit IRI, where exactly one QUDT unit matches. */
111
- qudtUnit?: string;
112
- semanticId: string;
113
- lifecycle: SchemaLifecycle;
114
- };
115
32
  export type SchemaValueDictionaryTerm = {
116
33
  id: string;
117
34
  label: string;
@@ -129,62 +46,8 @@ export type SchemaValueDictionaryItem = {
129
46
  semanticId: string;
130
47
  lifecycle: SchemaLifecycle;
131
48
  };
132
- export type KnownSchemaSourceType = "manufacturer_page" | "technical_data_sheet" | "safety_data_sheet" | "test_report" | "certificate" | "image" | "commerce_page" | "catalog_pdf" | "derived_analysis";
133
- export type SchemaSourceType = OpenEnum<KnownSchemaSourceType>;
134
- export type KnownSchemaSourceMethod = "manual_review" | "agent_extract" | "pdf_extract" | "image_analysis" | "html_scrape" | "structured_feed" | "calculation";
135
- export type SchemaSourceMethod = OpenEnum<KnownSchemaSourceMethod>;
136
- export type SchemaSourceRequirementItem = {
137
- id: string;
138
- name: string;
139
- description: string;
140
- allowedSourceTypes: SchemaSourceType[];
141
- allowedMethods: SchemaSourceMethod[];
142
- minConfidence: number;
143
- reviewRequired: boolean;
144
- freshnessDays?: number;
145
- sourceBasis?: OpenEnum<"declared" | "derived">;
146
- semanticId: string;
147
- lifecycle: SchemaLifecycle;
148
- };
149
- export type SchemaProductTypeTemplateBlock = {
150
- blockId: string;
151
- applicability: SchemaApplicability;
152
- requiredAttributes?: string[];
153
- optionalAttributes?: string[];
154
- sourceRequirementId?: string;
155
- completenessWeight?: number;
156
- notes?: string;
157
- };
158
- export type SchemaProductTypeTemplateItem = {
159
- id: string;
160
- name: string;
161
- productTypeId: string;
162
- blocks: SchemaProductTypeTemplateBlock[];
163
- defaultSourceRequirementId?: string;
164
- notes?: string;
165
- semanticId: string;
166
- lifecycle: SchemaLifecycle;
167
- };
168
- export type SchemaMappingItem = {
169
- target: SchemaExternalMapping["target"];
170
- subjectKind?: OpenEnum<"attribute" | "block" | "product_type" | "template" | "unit" | "value_dictionary" | "value_dictionary_term">;
171
- subjectId?: string;
172
- canonicalPath: string;
173
- targetPath: string;
174
- mappingType: SchemaExternalMapping["mappingType"];
175
- matchStrength?: SchemaMatchStrength;
176
- notes?: string;
177
- };
178
49
  export type SchemaResourceItemMap = {
179
- blocks: SchemaBlockItem;
180
- attributes: SchemaAttributeItem;
181
- verticals: SchemaVerticalItem;
182
- productTypes: SchemaProductTypeItem;
183
- units: SchemaUnitItem;
184
50
  valueDictionaries: SchemaValueDictionaryItem;
185
- sourceRequirements: SchemaSourceRequirementItem;
186
- productTypeTemplates: SchemaProductTypeTemplateItem;
187
- mappings: SchemaMappingItem;
188
51
  };
189
52
  export type SchemaResourceResponse<K extends SchemaResource = SchemaResource> = {
190
53
  kind: K;
@@ -5,13 +5,5 @@
5
5
  * it must cover cannot drift apart.
6
6
  */
7
7
  export const SCHEMA_RESOURCE_OPERATION_IDS = {
8
- attributes: "list_schema_attributes",
9
- blocks: "list_schema_blocks",
10
- mappings: "list_schema_mappings",
11
- productTypeTemplates: "list_schema_product_type_templates",
12
- productTypes: "list_schema_product_types",
13
- sourceRequirements: "list_schema_source_requirements",
14
- units: "list_schema_units",
15
8
  valueDictionaries: "list_schema_value_dictionaries",
16
- verticals: "list_schema_verticals",
17
9
  };
@@ -16,7 +16,7 @@ import type { OpenEnum, SearchData, SearchResponse } from "./types.js";
16
16
  *
17
17
  * A PROJECTION, not a smaller answer: a field the card omits (palette, verdict,
18
18
  * signals, availability) is unread here, never known-absent. Re-read the same
19
- * `variantId` at `view: "full"` or via `material()` before claiming anything the
19
+ * `variantId` at `view: "full"` or via `getVariant()` before claiming anything the
20
20
  * card does not carry.
21
21
  */
22
22
  export type SearchCardItem = {
@@ -5,6 +5,7 @@
5
5
  * `@materialgraph/products/search-index/plan-schema`; `key-parity.test.ts`
6
6
  * holds the two to the same fields.
7
7
  */
8
+ import type { ColourRegionRange } from "./colour-region-types.js";
8
9
  import type { CorpusScope } from "./corpus-scope-types.js";
9
10
  /** One attribute the plan wants: its terms, or any value when `presence`. */
10
11
  export type SearchIndexPlanWant = {
@@ -20,7 +21,7 @@ export type SearchIndexPlanWant = {
20
21
  terms: string[];
21
22
  };
22
23
  /**
23
- * The query's plan: the category, makers, wanted and excluded attribute terms,
24
+ * The query's plan: the category, brands, wanted and excluded attribute terms,
24
25
  * the colour, the words left for text, and the caller's corpus scope.
25
26
  */
26
27
  export type SearchIndexPlan = {
@@ -40,6 +41,8 @@ export type SearchIndexPlan = {
40
41
  hex: string;
41
42
  oklab: [number, number, number];
42
43
  phrase: string;
44
+ /** The colour's region, when the plan holds one rather than a point. */
45
+ range?: ColourRegionRange;
43
46
  } | null;
44
47
  /** Present only when the caller sent a scope narrower than the whole graph. */
45
48
  corpusScope?: Exclude<CorpusScope, "all">;
@@ -5,6 +5,7 @@
5
5
  * repo's source-size gate; every name here is re-exported from `types.ts`, so
6
6
  * no import path changes.
7
7
  */
8
+ import type { ColourRegionRange } from "./colour-region-types.js";
8
9
  import type { CorpusScope } from "./corpus-scope-types.js";
9
10
  import type { Cursor, VariantId } from "./identifiers.js";
10
11
  import type { OpenEnum, SearchImageInput } from "./types.js";
@@ -93,16 +94,23 @@ export type SearchInput = {
93
94
  * existing Variant. The page is the colour's family, nearest first by OKLab
94
95
  * distance; it replaces any colour read from `text`. A Variant with no colour
95
96
  * on the index is a 400.
97
+ *
98
+ * `oklab` and `range` are additive: send them when you already resolved the
99
+ * exact OKLab point or the named colour's region (see `resolveColour()`), and
100
+ * the plan carries that colour without re-deriving it from `hex`. `hex` stays
101
+ * required.
96
102
  */
97
103
  colour?: {
98
104
  hex: string;
105
+ oklab?: [number, number, number];
106
+ range?: ColourRegionRange;
99
107
  } | {
100
108
  variantId: VariantId;
101
109
  };
102
110
  /**
103
- * Makers the page must come from: canonical brand ids, exact slugs
111
+ * Brands the page must come from: canonical brand ids, exact slugs
104
112
  * ("sherwin-williams") or exact display names ("Sherwin-Williams"), up to
105
- * 50. They replace the makers `text` was read as. An empty list is the
113
+ * 50. They replace the brands `text` was read as. An empty list is the
106
114
  * same request as omitting it — every brand. An unrecognised value is a
107
115
  * 400 naming it; a display name shared by more than one brand is a 400
108
116
  * naming the exact slugs to choose between; a brand MaterialGraph holds no
package/spec-types.d.ts CHANGED
@@ -22,8 +22,8 @@
22
22
  * object literal for anything else). Reach for it when the spec is the
23
23
  * whole question and ranking would be noise.
24
24
  *
25
- * Discover the tokens before you spend them: `client.specificationFacets()` for
26
- * what is facetable at all, `client.attributeValues()` for one attribute's
25
+ * Discover the tokens before you spend them: `client.listVariantFacets()` for
26
+ * the live values of the attributes you name, `client.attributeValues()` for one attribute's
27
27
  * values, `client.listAttributes({ query })` to turn a phrase into a canonical
28
28
  * attribute id. A hand-guessed token returns an honest empty answer, which is
29
29
  * the hardest kind of bug to see.
@@ -57,8 +57,8 @@ export type CertificationProgrammeAttributeId = typeof CERTIFICATION_PROGRAMME_A
57
57
  /**
58
58
  * A term-equality spec criterion: the attribute must carry this canonical value,
59
59
  * e.g. `{ attributeId: "fire_classifications", term: "astm_e84_class_a" }`.
60
- * Discover valid `attributeId`/`term` pairs via `client.specificationFacets()`
61
- * and `client.attributeValues()`, or find an attribute from free text with
60
+ * Discover valid `attributeId`/`term` pairs via `client.listVariantFacets()`
61
+ * and `client.listEnumValues()`, or find an attribute from free text with
62
62
  * `client.listAttributes({ query })`.
63
63
  */
64
64
  export type SpecTermFilter = {
package/spec-types.js CHANGED
@@ -22,8 +22,8 @@
22
22
  * object literal for anything else). Reach for it when the spec is the
23
23
  * whole question and ranking would be noise.
24
24
  *
25
- * Discover the tokens before you spend them: `client.specificationFacets()` for
26
- * what is facetable at all, `client.attributeValues()` for one attribute's
25
+ * Discover the tokens before you spend them: `client.listVariantFacets()` for
26
+ * the live values of the attributes you name, `client.attributeValues()` for one attribute's
27
27
  * values, `client.listAttributes({ query })` to turn a phrase into a canonical
28
28
  * attribute id. A hand-guessed token returns an honest empty answer, which is
29
29
  * the hardest kind of bug to see.
package/sse.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * A minimal, dependency-free Server-Sent Events reader — the primitive behind
3
- * {@link MaterialGraphClient.streamRunEvents}. Hand-rolled (no `eventsource`,
3
+ * {@link WorkflowsClient.streamRunEvents}. Hand-rolled (no `eventsource`,
4
4
  * no `undici`) so the SDK stays zero-runtime-dep and works unchanged inside
5
5
  * Node route handlers, where `fetch` yields a byte `ReadableStream`.
6
6
  *
7
7
  * The parser follows the WHATWG event-stream tokenizer closely enough for the
8
- * `/api/v1/runs/{runId}/events` contract: `\n`, `\r\n`, and `\r` line endings,
8
+ * `/api/v1/workflows/{workflowId}/runs/{runId}/events` contract: `\n`, `\r\n`, and `\r` line endings,
9
9
  * multi-line `data:` accumulation, comment (`:`-prefixed) lines, and a single
10
10
  * optional leading space after the field colon. Frames split across network
11
11
  * chunks are held in an internal buffer until their terminating blank line.
package/sse.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * A minimal, dependency-free Server-Sent Events reader — the primitive behind
3
- * {@link MaterialGraphClient.streamRunEvents}. Hand-rolled (no `eventsource`,
3
+ * {@link WorkflowsClient.streamRunEvents}. Hand-rolled (no `eventsource`,
4
4
  * no `undici`) so the SDK stays zero-runtime-dep and works unchanged inside
5
5
  * Node route handlers, where `fetch` yields a byte `ReadableStream`.
6
6
  *
7
7
  * The parser follows the WHATWG event-stream tokenizer closely enough for the
8
- * `/api/v1/runs/{runId}/events` contract: `\n`, `\r\n`, and `\r` line endings,
8
+ * `/api/v1/workflows/{workflowId}/runs/{runId}/events` contract: `\n`, `\r\n`, and `\r` line endings,
9
9
  * multi-line `data:` accumulation, comment (`:`-prefixed) lines, and a single
10
10
  * optional leading space after the field colon. Frames split across network
11
11
  * chunks are held in an internal buffer until their terminating blank line.
package/types.d.ts CHANGED
@@ -28,7 +28,7 @@ import type { ResponseAttribution } from "./attribution-types.js";
28
28
  import type { CorpusScope } from "./corpus-scope-types.js";
29
29
  import type { Cursor, ProductId, VariantId } from "./identifiers.js";
30
30
  import type { FilterOp } from "./list-variants-types.js";
31
- import type { MaterialDetail, MaterialTile } from "./material-types.js";
31
+ import type { MaterialTile } from "./material-types.js";
32
32
  import type { SearchMode, SearchResponseExplainReceipts } from "./search-response-receipt-types.js";
33
33
  export type { SearchResponse } from "./search-response-types.js";
34
34
  export type * from "./search-response-receipt-types.js";
@@ -77,7 +77,7 @@ export type MaterialColour = {
77
77
  };
78
78
  /** Where the served image came from. */
79
79
  export type ImageSource = OpenEnum<"r2" | "origin">;
80
- export type { DimensionEntry, MaterialAvailability, MaterialDetail, MaterialFacet, MaterialSuccessor, MaterialTile, } from "./material-types.js";
80
+ export type { MaterialAvailability, MaterialTile } from "./material-types.js";
81
81
  /**
82
82
  * Match only: the image-space cosine between the query image and this row's
83
83
  * image, in [0,1]. Null when it was not measured.
@@ -98,7 +98,7 @@ export type SearchResultItem = {
98
98
  resultId?: string;
99
99
  /** Accepted sampleability fact. Absent means unknown, never false. */
100
100
  sampleable?: boolean;
101
- /** Opaque — round-trip verbatim into `material()`, `pairings()`, `similarTo`. */
101
+ /** Opaque — round-trip verbatim into `getVariant()`, `pairings()`, `similarTo`. */
102
102
  variantId: VariantId;
103
103
  brand: string;
104
104
  product: string;
@@ -485,17 +485,6 @@ export type PairingsResponse = {
485
485
  /** Opaque continuation bound to the seed, filters, corpus, and order. */
486
486
  nextCursor?: Cursor;
487
487
  };
488
- export type MaterialResponse = {
489
- material: MaterialDetail;
490
- };
491
- export type SuggestColourResponse = {
492
- suggestions: {
493
- term: string;
494
- hex: string;
495
- libraryId?: string;
496
- kind: string;
497
- }[];
498
- };
499
488
  /**
500
489
  * How a {@link AttributeListItem} matched the query. Declared here rather than
501
490
  * imported: this package is published, so it may not reach across into
@@ -554,7 +543,6 @@ export type ListAttributesResult = {
554
543
  };
555
544
  export type * from "./catalog-discovery-types.js";
556
545
  export type * from "./resolve-colour-types.js";
557
- export type * from "./suggest-types.js";
558
546
  export type * from "./by-spec-types.js";
559
547
  export type WhoamiResponse = {
560
548
  principal: string;
@@ -578,6 +566,7 @@ export type SearchImageInput = {
578
566
  };
579
567
  export type { SearchGetParams, SearchIndexFilter, SearchInput, } from "./search-request-types.js";
580
568
  export type { SearchIndexPlan, SearchPlanReceipt, } from "./search-index-plan-types.js";
569
+ export type { ColourRegionRange } from "./colour-region-types.js";
581
570
  /** Options for a pairings read. */
582
571
  export type PairingsOptions = {
583
572
  /** Comma-separated coarse category display names; filters both arms. */
@@ -608,28 +597,6 @@ export type ListAttributesOptions = {
608
597
  /** `pageInfo.endCursor` from the previous page, with the same query and scope. */
609
598
  cursor?: string;
610
599
  };
611
- export type * from "./demand-types.js";
612
- /** One quarter's tally. Sparse across a series — an absent quarter is not a zero. */
613
- export type DemandQuarter = {
614
- quarter: string;
615
- specCount: number;
616
- projectCount: number;
617
- };
618
- /**
619
- * Which behavioural signal a demand read is scoped to — and therefore what a
620
- * number drawn from it may honestly be called.
621
- *
622
- * - `sampled` — real ORDER co-occurrence. The default everywhere. Present as
623
- * "specified together" / "specified on N projects".
624
- * - `considered` — board / moodboard CURATION. Somebody assembled them; nobody
625
- * is known to have bought them. Present as "considered alongside", never as
626
- * a specification.
627
- * - `specified` — the won tier. STATS ONLY: no co-specification edges are built
628
- * at this grain, so asking for `specified` companions is a rejected request,
629
- * not an empty result.
630
- */
631
- export type DemandSignal = OpenEnum<"sampled" | "considered" | "specified">;
632
- export type * from "./demand-batch-types.js";
633
600
  export type ExtractedPalette = {
634
601
  colors: {
635
602
  hex: string;
@@ -647,7 +614,6 @@ export type ExtractedPalette = {
647
614
  export type ExtractPaletteResponse = {
648
615
  palette: ExtractedPalette | null;
649
616
  };
650
- export type * from "./spec-facet-types.js";
651
617
  /** Body for `POST /api/v1/search/palettes/extract`. */
652
618
  export type ExtractPaletteInput = {
653
619
  /** The query image as a data URI or bare base64 string. */
@@ -1,13 +1,13 @@
1
1
  const json = [
2
- "[{\"annotations\":{\"readOnlyHint\":true,\"untrustedContentHint\":true},\"description\":\"The stable, authenticated search endpoint, POST JSON-body form: `text`, a raw `image` (`url` or `base64`, embedded server-side) or a `similarTo` seed (`variantId`, `materialBankId` or a pre-computed image-space `vector`), a `category`, `filters` and a `corpusScope`, and `limit`, `cursor`, `view` and `explain`. Every request runs the one search over the index (ADR 0031): the query's plan is read from the cache or the dictionaries (brands, categories, colours and attributes are read from the words), one query runs on the search index, and the page is hydrated in the order the index gave. No model runs while the caller waits. A word no material carries may be corrected first; the response then carries `correctedQuery: { from, to }` and its `note` says which results it shows. Results are one per product. A caller may also send a `category` (a hard gate that replaces the category the words were read as), attribute `filters` (hard token sets: a term, a term set, an exclusion or a presence check; OR within one attribute, AND across attributes) and a `corpusScope`. Each becomes a field of the query's plan and runs in the same index query; none of them changes the order. A category or filters can define a page without words. `explain.plan.plan` shows the plan with them on it, and `explain.plan.signature` covers them, so one request gives one signature. A look-alike search reads the seed's nearest materials from the image namespace; the index keeps those inside whatever the words filter to and orders them by image similarity. A field the contract does not declare is a 400 naming it, with near misses; weights, mood, colours, text modes and sorts are no longer part of the request, and brands and colours are read from `text`. `?explain=1` on the query string is honoured as a shorthand for body `explain: true`; an explicit body `explain` (true or false) always wins. The `query` echo never carries raw image bytes or a vector, only `{ type }`. Send `Accept: application/x-ndjson` for one `{ type: \\\"response\\\", status, body }` line carrying exactly what the buffered form answers. Search is dynamic, so success responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.\",\"inputSchema\":{\"$ref\":\"#/$defs/schema0\",\"$defs\":{\"schema0\":{\"type\":\"object\",\"properties\":{\"brands\":{\"description\":\"Makers the page must come from: canonical brand ids, exact slugs (\\\"sherwin-williams\\\") or exact display names (\\\"Sherwin-Williams\\\"). They replace the makers `text` was read as. An empty list is the same request as omitting it — every brand. An unrecognised value is a 400 naming it; a display name shared by more than one brand is a 400 naming the exact slugs to choose between; a brand MaterialGraph holds no materials under is a 400 naming a populated sibling when one exists.\",\"maxItems\":50,\"type\":\"array\",\"items\":{\"type\":\"string\",\"minLength\":1}},\"category\":{\"type\":\"object\",\"properties\":{\"filter\":{\"minItems\":1,\"maxItems\":50,\"type\":\"array\",\"items\":{\"type\":\"string\",\"minLength\":1},\"description\":\"Only these public categories are returned: category-group or product-type display names (case-insensitive) or taxonomy keys. A group stands for every product type under it. An unknown value is a 400 listing every valid name.\"}},\"required\":[\"filter\"],\"additionalProperties\":false,\"description\":\"A hard category gate. It replaces the category the words were read as.\"},\"colour\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"hex\":{\"type\":\"string\",\"pattern\":\"^#?[0-9a-f]{6}$\",\"description\":\"The colour to order by, e.g. \\\"#9CAF88\\\".\"},\"oklab\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":3,\"maxItems\":3},\"range\":{\"type\":\"object\",\"properties\":{\"chroma\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2},\"hue\":{\"anyOf\":[{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2},{\"type\":\"null\"}]},\"lightness\":{\"type\":\"array\"",
2
+ "[{\"annotations\":{\"readOnlyHint\":true,\"untrustedContentHint\":true},\"description\":\"The stable, authenticated search endpoint, POST JSON-body form: `text`, a raw `image` (`url` or `base64`, embedded server-side) or a `similarTo` seed (`variantId`, `materialBankId` or a pre-computed image-space `vector`), a `category`, `filters` and a `corpusScope`, and `limit`, `cursor`, `view` and `explain`. Every request runs the one search over the index (ADR 0031): the query's plan is read from the cache or the dictionaries (brands, categories, colours and attributes are read from the words), one query runs on the search index, and the page is hydrated in the order the index gave. No model runs while the caller waits. A word no material carries may be corrected first; the response then carries `correctedQuery: { from, to }` and its `note` says which results it shows. Results are one per product. A caller may also send a `category` (a hard gate that replaces the category the words were read as), attribute `filters` (hard token sets: a term, a term set, an exclusion or a presence check; OR within one attribute, AND across attributes) and a `corpusScope`. Each becomes a field of the query's plan and runs in the same index query; none of them changes the order. A category or filters can define a page without words. `explain.plan.plan` shows the plan with them on it, and `explain.plan.signature` covers them, so one request gives one signature. A look-alike search reads the seed's nearest materials from the image namespace; the index keeps those inside whatever the words filter to and orders them by image similarity. A field the contract does not declare is a 400 naming it, with near misses; weights, mood, colours, text modes and sorts are no longer part of the request, and brands and colours are read from `text`. `?explain=1` on the query string is honoured as a shorthand for body `explain: true`; an explicit body `explain` (true or false) always wins. The `query` echo never carries raw image bytes or a vector, only `{ type }`. Send `Accept: application/x-ndjson` for one `{ type: \\\"response\\\", status, body }` line carrying exactly what the buffered form answers. Search is dynamic, so success responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.\",\"inputSchema\":{\"$ref\":\"#/$defs/schema0\",\"$defs\":{\"schema0\":{\"type\":\"object\",\"properties\":{\"brands\":{\"description\":\"Brands the page must come from: canonical brand ids, exact slugs (\\\"sherwin-williams\\\") or exact display names (\\\"Sherwin-Williams\\\"). They replace the brands `text` was read as. An empty list is the same request as omitting it — every brand. An unrecognised value is a 400 naming it; a display name shared by more than one brand is a 400 naming the exact slugs to choose between; a brand MaterialGraph holds no materials under is a 400 naming a populated sibling when one exists.\",\"maxItems\":50,\"type\":\"array\",\"items\":{\"type\":\"string\",\"minLength\":1}},\"category\":{\"type\":\"object\",\"properties\":{\"filter\":{\"minItems\":1,\"maxItems\":50,\"type\":\"array\",\"items\":{\"type\":\"string\",\"minLength\":1},\"description\":\"Only these public categories are returned: category-group or product-type display names (case-insensitive) or taxonomy keys. A group stands for every product type under it. An unknown value is a 400 listing every valid name.\"}},\"required\":[\"filter\"],\"additionalProperties\":false,\"description\":\"A hard category gate. It replaces the category the words were read as.\"},\"colour\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"hex\":{\"type\":\"string\",\"pattern\":\"^#?[0-9a-f]{6}$\",\"description\":\"The colour to order by, e.g. \\\"#9CAF88\\\".\"},\"oklab\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":3,\"maxItems\":3},\"range\":{\"type\":\"object\",\"properties\":{\"chroma\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2},\"hue\":{\"anyOf\":[{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2},{\"type\":\"null\"}]},\"lightness\":{\"type\":\"array\"",
3
3
  ",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2}},\"required\":[\"chroma\",\"hue\",\"lightness\"],\"additionalProperties\":false}},\"required\":[\"hex\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"variantId\":{\"type\":\"string\",\"minLength\":1,\"description\":\"A Variant whose own colour to order by: materials the same colour as this one. A Variant with no colour on the index is a 400.\"}},\"required\":[\"variantId\"],\"additionalProperties\":false}],\"description\":\"Order the page by a colour: `hex`, or `variantId` for the colour of an existing Variant. The page is the colour's family, nearest first by OKLab distance; it replaces any colour read from `text`.\"},\"corpusScope\":{\"type\":\"string\",\"enum\":[\"all\",\"samplizeCatalogue\",\"materialBankOrderable\",\"materialBankNaOrderable\",\"materialBankNaAndCatalogue\",\"materialBankEuOrderable\",\"designShopOrderable\"],\"description\":\"Which corpus to answer from. samplizeCatalogue admits published US paint colours regardless of stock. ABSENT (or 'all') means the WHOLE GRAPH — every material MaterialGraph knows, orderable or not; absence is never a narrower default. Three transaction-oriented scopes gate on governed CHANNEL membership: 'materialBankNaOrderable' (Material Bank North America, ~277,000 materials), 'materialBankEuOrderable' (Material Bank Europe, ~65,000) and 'designShopOrderable' (DesignShop, ~34,000, with the products DesignShop hides from browse excluded). 'materialBankNaAndCatalogue' is the one-query union of Material Bank North America and the governed Catalogue editorial discovery corpus; that union does not by itself claim every member is transactable. 'materialBankOrderable' is NOT one of them: it selects materials carrying a Material Bank identity, which is a provenance signal rather than a channel one, and it admits 98.4% of the graph — use it to exclude crawl-sourced brands, not to mean orderable. A scope answers membership in its named governed corpus, never suitability: scope on channel, then refine on sector. Every scope is applied INSIDE the query: `limit` fills with eligible rows and `counts` describe the scoped corpus, which post-filtering a page cannot give you. An unknown value is a 400 naming it, never a silent fall back to the whole graph.\"},\"cursor\":{\"description\":\"Opaque cursor from a previous response's `cursor`, for the next page of the same query. A cursor from another query is a 409 telling the caller to restart from the first page.\",\"type\":\"string\",\"minLength\":1},\"explain\":{\"description\":\"Add `timings`: wall-clock milliseconds for reading the plan (`planMs`), the index query (`indexMs`) and hydrating the page (`pageMs`), and `explain.plan`: the receipt of the plan the search ran on, with the plan itself.\",\"type\":\"boolean\"},\"filters\":{\"minItems\":1,\"maxItems\":24,\"type\":\"array\",\"items\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\",\"minLength\":1,\"description\":\"An attribute the search index carries, e.g. \\\"primary_material\\\" or \\\"colour_mood\\\", or \\\"certification_programme\\\" for any certification programme.\"},\"term\":{\"type\":\"string\",\"minLength\":1,\"description\":\"The canonical term the value must carry, e.g. \\\"textile\\\".\"}},\"required\":[\"attributeId\",\"term\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\",\"minLength\":1,\"description\":\"An attribute the search index carries, e.g. \\\"primary_material\\\" or \\\"colour_mood\\\", or \\\"certification_programme\\\" for any certification programme.\"},\"terms\":{\"minItems\":1,\"maxItems\":50,\"type\":\"array\",\"items\":{\"type\":\"string\",\"minLength\":1},\"description\":\"Canonical terms, any one of which satisfies the filter.\"}},\"required\":[\"attributeId\",\"terms\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\",\"minLength\":1,\"description\":\"An attribute the search index carries, e.g. \\\"primary_material\\\" or \\\"colour_mood\\\", or \\\"certification_programme\\\" for any certification programme.\"},\"excludesTerm\":{\"type\":\"string\",\"minLength\":1,\"description\":\"A canonical term the value must not carry. A material ",
4
4
  "that states nothing for the attribute passes.\"}},\"required\":[\"attributeId\",\"excludesTerm\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\",\"minLength\":1,\"description\":\"An attribute the search index carries, e.g. \\\"primary_material\\\" or \\\"colour_mood\\\", or \\\"certification_programme\\\" for any certification programme.\"},\"hasValue\":{\"type\":\"boolean\",\"const\":true,\"description\":\"Require any stated value.\"}},\"required\":[\"attributeId\",\"hasValue\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\",\"minLength\":1,\"description\":\"A number the search index cuts into bands: a numeric attribute such as \\\"nrc_rating\\\" or \\\"lead_time_days\\\", a dimensions lens such as \\\"dimensions.thickness_mm\\\" or \\\"dimensions.short_side_mm\\\" (millimetres), or a composition share such as \\\"composition_weighted.wool\\\" (0 to 1).\"},\"max\":{\"description\":\"Inclusive upper bound. Omit for a floor.\",\"type\":\"number\"},\"min\":{\"description\":\"Inclusive lower bound. Omit for a ceiling.\",\"type\":\"number\"}},\"required\":[\"attributeId\"],\"additionalProperties\":false}]},\"description\":\"Attribute filters, every one hard, run as token sets on the search index. Each entry is a term (`attributeId` + `term`), a term set (`attributeId` + `terms`, any one satisfies it), an exclusion (`attributeId` + `excludesTerm`; a material stating nothing passes), a presence check (`attributeId` + `hasValue: true`) or a numeric range (`attributeId` + `min` and/or `max`, inclusive), read as the number bands wholly inside it, so a bound between two band edges is read inward and a value the index cannot prove in range is left out. Entries on one attribute are OR'd; entries on different attributes AND together. They are added to what the words were read as and change no ordering. An attribute or term the index does not carry is a 400 naming it.\"},\"image\":{\"description\":\"A query image to search by (URL or base64). It is embedded, and the page is its nearest materials in the image namespace; `text`, when also given, narrows them.\",\"oneOf\":[{\"type\":\"object\",\"properties\":{\"type\":{\"type\":\"string\",\"const\":\"url\"},\"url\":{\"type\":\"string\",\"format\":\"uri\",\"description\":\"HTTP(S) URL of the query image. MUST be an https URL.\"}},\"required\":[\"type\",\"url\"]},{\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"string\",\"minLength\":1,\"description\":\"Base64-encoded image bytes (or a data URI) the caller already holds — e.g. a form upload — so there is nothing to host.\"},\"type\":{\"type\":\"string\",\"const\":\"base64\"}},\"required\":[\"data\",\"type\"]}],\"type\":\"object\"},\"limit\":{\"description\":\"Products per page (max 60). Absent, the search's own page size.\",\"type\":\"integer\",\"minimum\":1,\"maximum\":60},\"returnAttributes\":{\"maxItems\":8,\"type\":\"array\",\"items\":{\"type\":\"string\",\"minLength\":1},\"description\":\"Attribute ids whose RESOLVED VALUE should ride each result, under `attributes` keyed by attribute id. Values resolve with the normal variant-over-product precedence. THREE STATES, and they are different: an attribute you did not ask for is not a key at all; an attribute you asked for that this variant carries no accepted value for is `null`; anything else is the value (a string, or an array for a multi-valued attribute). An unknown attribute id is refused naming it, never silently dropped. Pair it with `filters` to show what you filtered on.\"},\"similarTo\":{\"description\":\"Find materials that LOOK like a seed: `variantId` seeds from an existing Variant's image, `materialBankId` from a Material Bank SKU (an unknown SKU is a 400 naming it), `vector` from a pre-computed image-space halfvec literal (`[a,b,...]`). `text`, when also given, narrows the look-alikes.\",\"anyOf\":[{\"type\":\"object\",\"properties\":{\"variantId\":{\"type\":\"string\",\"minLength\":1}},\"required\":[\"variantId\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"materialBankId\":{\"type\":\"string\",\"minLength\":1}},\"required\":[\"materialBankId\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"vector\":{\"type\":\"string\",\"minLength\":1}},\"required\":[\"vector\"],\"additionalProperties\":false}]},\"text",
5
5
  "\":{\"description\":\"Free-text query (\\\"oak flooring\\\", \\\"sage green velvet\\\", \\\"Kvadrat wool\\\"). Brands, categories, colours and attributes are read from the words unless `brands`, `category`, `colour` or `filters` says so instead; a word no material carries may be corrected, and the response says so under `correctedQuery`. May be omitted when `brands`, `category` or `filters` says what to find.\",\"type\":\"string\",\"maxLength\":500},\"view\":{\"default\":\"full\",\"description\":\"Result projection. \\\"full\\\" (default) returns the complete result items; \\\"card\\\" the lean grid projection. The response echoes `view`, which discriminates the `results` union.\",\"type\":\"string\",\"enum\":[\"full\",\"card\"]}},\"additionalProperties\":false}}},\"method\":\"POST\",\"name\":\"search\",\"operationId\":\"search_materials_post\",\"outputSchema\":{\"$ref\":\"#/$defs/schema0\",\"$defs\":{\"schema0\":{\"type\":\"object\",\"properties\":{\"data\":{\"oneOf\":[{\"type\":\"object\",\"properties\":{\"results\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"resultId\":{\"type\":\"string\",\"description\":\"This exact hit's attribution key — `<responseId>:<rank>`, where rank is 1-based within THIS response. Unique across pages, because each page is its own response with its own id. Store it verbatim next to the outcome; it is opaque, and `attribution.responseId` is the field to group by.\"},\"variantId\":{\"type\":\"string\"},\"brand\":{\"type\":\"string\"},\"product\":{\"type\":\"string\"},\"brandSlug\":{\"type\":[\"string\",\"null\"]},\"productId\":{\"type\":[\"string\",\"null\"]},\"materialBankId\":{\"description\":\"The variant's Material Bank SKU — the ONE EXTERNAL identifier on a result, in contrast to the OPAQUE `variantId`: it is the id you are expected to match against Material Bank's own catalogue (carts, ordering and analytics all key on it), so a result list can be joined without a second lookup. Populated by the search and by Match alike. ABSENT means no MB identity is known for the variant, never that the SKU cannot be ordered; presence is the orderability tell, absence is silence.\",\"type\":\"string\"},\"manufacturerCode\":{\"description\":\"The governed manufacturer/source-native item code from `variants.code` (for example \\\"GADLC02\\\"). Populated when the full-result retrieval source carries that canonical field; Match always hydrates it from the variant row. ABSENT means unknown or an older external serving projection, never an invented code. Independent of materialBankId.\",\"type\":\"string\"},\"projectSaveCapability\":{\"description\":\"Explicit source-governed permission to save this exact source-coded variant to a Project. ABSENT means unknown/not allowed; it is never inferred from the brand or name.\",\"type\":\"string\",\"const\":\"allowed\"},\"sampleable\":{\"description\":\"Whether a physical sample is explicitly known to be orderable. Absent means unknown, never unavailable.\",\"type\":\"boolean\"},\"sourceStatus\":{\"description\":\"The governed data status supporting Project capture. ABSENT means unknown. Provisional data remains source-backed but is not a verified catalogue claim.\",\"type\":\"string\",\"const\":\"provisional\"},\"productType\":{\"type\":\"string\"},\"color\":{\"type\":[\"string\",\"null\"]},\"variantName\":{\"type\":[\"string\",\"null\"]},\"imageUrl\":{\"type\":[\"string\",\"null\"]},\"imageSha256\":{\"description\":\"SHA-256 of the displayed image `imageUrl` points at, as a hex digest. Lets a caller key a cache or dedupe on the asset rather than its URL. ABSENT means no hash is on record, never that there is no image.\",\"type\":\"string\"},\"hasImage\":{\"type\":\"boolean\"},\"variantCount\":{\"description\":\"How many eligible variants the row's product has, the same set `list_variants` pages, so a follow-up list of the product returns this many: the card's \\\"12 colourways\\\". ABSENT means the product is unknown or has no eligible variant.\",\"type\":\"integer\",\"exclusiveMinimum\":0,\"maximum\":9007199254740991},\"palette\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"hex\":{\"type\":\"string\",\"description\":\"`#rrggbb` of the swatch, as extracted from the product image.\"},\"name\":{\"description\":\"Stored human colour name for the swatch, when one was extracted.\",\"type\":[\"string\",\"null\"]},\"proportion\":{",
6
6
  "\"type\":\"number\",\"description\":\"The swatch's share of the analysed image, in [0,1].\"}},\"required\":[\"hex\",\"proportion\"],\"additionalProperties\":false},\"description\":\"The material's main-image palette — the FULL stored palette, uncapped and in stored order. Colours are EXTRACTED FROM PRODUCT IMAGERY (visual likeness, never a declared colour specification). A one-swatch palette means the extractor found one colour — solid-colour materials are genuinely monochrome — never that the list was trimmed. This array is always present: EMPTY means nothing is on record (unknown), not 'this material has no colours'.\"},\"colourMeta\":{\"type\":\"object\",\"properties\":{\"dominantFamily\":{\"type\":\"string\"},\"dominantHex\":{\"type\":\"string\"},\"mood\":{\"type\":\"string\"}},\"additionalProperties\":false},\"colourChip\":{\"type\":\"object\",\"properties\":{\"hex\":{\"type\":\"string\",\"description\":\"`#rrggbb` to fill the chip with. EXTRACTED FROM the product image (a visual likeness), never a manufacturer-declared colour specification — surfaces must not present it as one.\"}},\"required\":[\"hex\"],\"additionalProperties\":false,\"description\":\"Present when this material's product imagery carries nothing beyond one colour, so a surface should render that colour rather than the photograph. Set for COATINGS (paints, primers, stains, floor coatings) whose stored palette quantises to a single swatch — a paint's photo is a flat fill, and the colour we hold structured beats a JPEG of it. ABSENT means the question was not established (unknown product type, unextracted or multi-swatch palette) and the image should be rendered; it NEVER asserts 'this is not a coating'.\"},\"attributes\":{\"description\":\"Resolved attribute values for the ids sent in `returnAttributes`, keyed by attribute id. ABSENT entirely when `returnAttributes` was not sent. A key present with `null` means this variant carries no accepted value for that attribute — absent is not empty and neither is null: the three states are distinct on purpose. Values resolve with the normal variant-over-product precedence.\",\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{\"anyOf\":[{\"type\":\"string\"},{\"type\":\"array\",\"items\":{\"type\":\"string\"}},{\"type\":\"null\"}]}},\"colourBand\":{\"description\":\"`colourDistance` as a word (identical / very close / close / related / different), or null when unmeasurable. Absent when the colour stage did not run.\",\"type\":[\"string\",\"null\"]},\"colourDistance\":{\"description\":\"Match only. OKLab earth mover's distance between the query crop's palette and this material's, reading both palettes' proportions, so a colour that is a small share of the product cannot carry the match. Under 0.1 the colour AGREES, and the page lists agreeing rows first. `null` means the material carries no stored palette — UNKNOWN colour, never 'no colour' and never 'distant'. Absent when the colour stage did not run.\",\"type\":[\"number\",\"null\"]},\"confidence\":{\"description\":\"Match only (MG-1031). How close this material is to the query, as an ABSOLUTE image-space similarity in [0,1], not adjusted for colour — the same unit as `qualityFloor` and `comparison.bestRejected`, so all three can be drawn on one axis. Never normalised against the page, so the number means the same thing across runs. It is the TRUE value and may reach 1; rendering it so that no card claims '100%' is the consumer's job (round toward zero, or show '99%+'). Capping it here would put it on a different ceiling from `bestRejected`, which publishes the same unit uncapped. A Match page is ordered by colour agreement (`colourDistance` under 0.1, then unmeasured, then the rest) and then by this number, so inside each of those sets it never rises down the page. Distinct from `score`, which is the retrieval score on the `[0, maxScore]` scale and does not order a Match page. Absent outside Match.\",\"type\":\"number\",\"minimum\":0,\"maximum\":1},\"confidenceBand\":{\"description\":\"`confidence` as a word, banded on the engine's measured readings (strong >= 0.85, good >= 0.72, fair >= 0.55 = the default quality floor, weak below it and reachable only under `includeBelowFloor",
7
7
  "`). Present exactly when `confidence` is.\",\"type\":\"string\",\"enum\":[\"strong\",\"good\",\"fair\",\"weak\"]},\"score\":{\"type\":\"number\",\"description\":\"The selected mode's weighted primary score on the `[0, maxScore]` scale. On a Match page this is the retrieval score and does NOT order the page; the order is colour agreement, then `confidence`.\"},\"signals\":{\"description\":\"Match only: the measured image similarity the row carries.\",\"type\":\"object\",\"properties\":{\"image\":{\"description\":\"Image-space cosine between the query image and this row's image, in [0,1]. Null when it was not measured.\",\"type\":[\"number\",\"null\"]}},\"required\":[\"image\"],\"additionalProperties\":false},\"retired\":{\"description\":\"MB lists the SKU as not visible/retired; demoted in ranking. Omitted when false.\",\"type\":\"boolean\"},\"belowFloor\":{\"description\":\"Present (true) only on quality-floor rejects returned under includeBelowFloor — the item failed the match image-quality floor and would be dropped by default.\",\"type\":\"boolean\"},\"admittedOnColour\":{\"description\":\"Present (true) only on Match rows the colour probe admitted: this material is as close in colour as the catalogue gets, and its image similarity is BELOW the match quality floor by design. It is on the page because no image-retrieved row is nearer in colour, not because it cleared the floor — so a consumer can present it as a colour answer or suppress it knowingly, and a quality check can tell a designed admission from a genuine floor leak. Absent (never false) on rows that came through image retrieval. Distinct from `belowFloor`, which marks rejects returned only under `includeBelowFloor`.\",\"type\":\"boolean\"}},\"required\":[\"variantId\",\"brand\",\"product\",\"brandSlug\",\"productId\",\"color\",\"variantName\",\"imageUrl\",\"hasImage\",\"palette\",\"score\"],\"additionalProperties\":false}},\"view\":{\"default\":\"full\",\"description\":\"Projection echo — always 'full' on this envelope variant.\",\"type\":\"string\",\"const\":\"full\"},\"correctedQuery\":{\"description\":\"Present when a word no material carries was corrected before the search ran: `from` is the query as typed (normalised), `to` the phrasing the page shows results for.\",\"type\":\"object\",\"properties\":{\"from\":{\"type\":\"string\"},\"to\":{\"type\":\"string\"}},\"required\":[\"from\",\"to\"],\"additionalProperties\":false},\"attribution\":{\"type\":\"object\",\"properties\":{\"basis\":{\"type\":\"string\",\"enum\":[\"image\",\"colour\",\"lexical\",\"agent\",\"attribute\",\"match\"],\"description\":\"The ranking basis, groupable across both ranked routes. On /api/v1/search this is the envelope's `mode` verbatim; /api/v1/search/match reports 'match'. Group your stored outcomes by this field.\"},\"issuedAt\":{\"type\":\"string\",\"description\":\"ISO-8601 timestamp of when this response was assembled, so a stored outcome carries its own age without a second lookup.\"},\"responseId\":{\"type\":\"string\",\"description\":\"This response's stable id — the same value as the `X-Request-Id` header. Store it next to the outcome (an order, a save, a click) to join that outcome back to the ranking that produced it. Opaque: round-trip it verbatim, never parse it.\"}},\"required\":[\"basis\",\"issuedAt\",\"responseId\"],\"additionalProperties\":false,\"description\":\"Result attribution receipt. Present on every successful ranked response. Nothing on our side retains it — what you store IS the record.\"},\"suggestions\":{\"default\":[],\"description\":\"Always empty: the one search offers no refinements. Read nothing from it.\",\"maxItems\":0,\"type\":\"array\",\"items\":{\"not\":{}}},\"mode\":{\"type\":\"string\",\"enum\":[\"image\",\"colour\",\"lexical\",\"agent\",\"attribute\"],\"description\":\"What ordered the page: 'image' (a look-alike: image similarity to the seed), 'colour' (the words or the caller named a colour: colour distance inside that colour's family), 'lexical' (the query's words, ranked by text rank on the index), 'attribute' (no words left to rank on: the category and filters define the page), or 'agent' (the search agent chose these variants and their order; scores are rank-based, 1 for the first result falling linearly, and `maxScore` is 1).\"},\"maxScore\":{\"type\":\"number\"},\"note\":{\"type\":\"string\"},\"cursor\":{",
8
- "\"type\":\"string\"},\"timings\":{\"description\":\"Wall-clock milliseconds per step of the search, keyed by step id. The key set is open and depends on the path a query took, so read it by iterating rather than by naming a key. Present only when `explain` is set: diagnostic, not part of the ranking contract.\",\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{\"type\":\"number\"}},\"explain\":{\"description\":\"Present only under `explain`: the receipt of the plan the search ran on. Diagnostic, like `timings`.\",\"type\":\"object\",\"properties\":{\"plan\":{\"type\":\"object\",\"properties\":{\"cached\":{\"type\":\"boolean\"},\"plan\":{\"type\":\"object\",\"properties\":{\"brands\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"ids\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"names\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"phrase\":{\"type\":\"string\"}},\"required\":[\"ids\",\"names\",\"phrase\"],\"additionalProperties\":false},{\"type\":\"null\"}]},\"category\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"label\":{\"type\":\"string\"},\"phrase\":{\"type\":\"string\"},\"productTypeIds\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"label\",\"phrase\",\"productTypeIds\"],\"additionalProperties\":false},{\"type\":\"null\"}]},\"colour\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"family12\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"familyId\":{\"type\":\"string\"},\"hex\":{\"type\":\"string\"},\"oklab\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":3,\"maxItems\":3},\"phrase\":{\"type\":\"string\"},\"range\":{\"type\":\"object\",\"properties\":{\"chroma\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2},\"hue\":{\"anyOf\":[{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2},{\"type\":\"null\"}]},\"lightness\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2}},\"required\":[\"chroma\",\"hue\",\"lightness\"],\"additionalProperties\":false}},\"required\":[\"family12\",\"familyId\",\"hex\",\"oklab\",\"phrase\"],\"additionalProperties\":false},{\"type\":\"null\"}]},\"corpusScope\":{\"type\":\"string\",\"enum\":[\"samplizeCatalogue\",\"materialBankOrderable\",\"materialBankNaOrderable\",\"materialBankNaAndCatalogue\",\"materialBankEuOrderable\",\"designShopOrderable\"]},\"excludeBrands\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"excludes\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\"},\"phrase\":{\"type\":\"string\"},\"term\":{\"type\":\"string\"}},\"required\":[\"attributeId\",\"phrase\",\"term\"],\"additionalProperties\":false}},\"leftover\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"normalised\":{\"type\":\"string\"},\"softAlone\":{\"type\":\"boolean\"},\"vendorColourNames\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"wants\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\"},\"fill\":{\"type\":\"number\"},\"phrase\":{\"type\":\"string\"},\"presence\":{\"type\":\"boolean\",\"const\":true},\"strength\":{\"type\":\"string\",\"enum\":[\"hard\",\"soft\"]},\"terms\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"attributeId\",\"fill\",\"phrase\",\"strength\",\"terms\"],\"additionalProperties\":false}}},\"required\":[\"brands\",\"category\",\"colour\",\"excludeBrands\",\"excludes\",\"leftover\",\"normalised\",\"softAlone\",\"vendorColourNames\",\"wants\"],\"additionalProperties\":false,\"description\":\"The plan the search ran on: the category, makers, wanted and excluded attribute terms (each hard or soft, with its fill), the colour, the words left for text, and the caller's corpus scope when one was sent.\"},\"signature\":{\"type\":\"string\"},\"source\":{\"type\":\"string\",\"enum\":[\"dictionary\",\"jev\"]},\"version\":{\"type\":\"string\"}},\"required\":[\"cached\",\"plan\",\"signature\",\"source\",\"version\"],\"additionalProperties\":false}},\"required\":[\"plan\"],\"additionalProperties\":false},\"query\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}},\"seed\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}}},\"required\":[\"results\",\"view\",\"suggestions\",\"mode\",\"maxScore\",\"query\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"results\":{\"ty",
8
+ "\"type\":\"string\"},\"timings\":{\"description\":\"Wall-clock milliseconds per step of the search, keyed by step id. The key set is open and depends on the path a query took, so read it by iterating rather than by naming a key. Present only when `explain` is set: diagnostic, not part of the ranking contract.\",\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{\"type\":\"number\"}},\"explain\":{\"description\":\"Present only under `explain`: the receipt of the plan the search ran on. Diagnostic, like `timings`.\",\"type\":\"object\",\"properties\":{\"plan\":{\"type\":\"object\",\"properties\":{\"cached\":{\"type\":\"boolean\"},\"plan\":{\"type\":\"object\",\"properties\":{\"brands\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"ids\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"names\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"phrase\":{\"type\":\"string\"}},\"required\":[\"ids\",\"names\",\"phrase\"],\"additionalProperties\":false},{\"type\":\"null\"}]},\"category\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"label\":{\"type\":\"string\"},\"phrase\":{\"type\":\"string\"},\"productTypeIds\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"label\",\"phrase\",\"productTypeIds\"],\"additionalProperties\":false},{\"type\":\"null\"}]},\"colour\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"family12\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"familyId\":{\"type\":\"string\"},\"hex\":{\"type\":\"string\"},\"oklab\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":3,\"maxItems\":3},\"phrase\":{\"type\":\"string\"},\"range\":{\"type\":\"object\",\"properties\":{\"chroma\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2},\"hue\":{\"anyOf\":[{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2},{\"type\":\"null\"}]},\"lightness\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2}},\"required\":[\"chroma\",\"hue\",\"lightness\"],\"additionalProperties\":false}},\"required\":[\"family12\",\"familyId\",\"hex\",\"oklab\",\"phrase\"],\"additionalProperties\":false},{\"type\":\"null\"}]},\"corpusScope\":{\"type\":\"string\",\"enum\":[\"samplizeCatalogue\",\"materialBankOrderable\",\"materialBankNaOrderable\",\"materialBankNaAndCatalogue\",\"materialBankEuOrderable\",\"designShopOrderable\"]},\"excludeBrands\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"excludes\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\"},\"phrase\":{\"type\":\"string\"},\"term\":{\"type\":\"string\"}},\"required\":[\"attributeId\",\"phrase\",\"term\"],\"additionalProperties\":false}},\"leftover\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"normalised\":{\"type\":\"string\"},\"softAlone\":{\"type\":\"boolean\"},\"vendorColourNames\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"wants\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\"},\"fill\":{\"type\":\"number\"},\"phrase\":{\"type\":\"string\"},\"presence\":{\"type\":\"boolean\",\"const\":true},\"strength\":{\"type\":\"string\",\"enum\":[\"hard\",\"soft\"]},\"terms\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"attributeId\",\"fill\",\"phrase\",\"strength\",\"terms\"],\"additionalProperties\":false}}},\"required\":[\"brands\",\"category\",\"colour\",\"excludeBrands\",\"excludes\",\"leftover\",\"normalised\",\"softAlone\",\"vendorColourNames\",\"wants\"],\"additionalProperties\":false,\"description\":\"The plan the search ran on: the category, brands, wanted and excluded attribute terms (each hard or soft, with its fill), the colour, the words left for text, and the caller's corpus scope when one was sent.\"},\"signature\":{\"type\":\"string\"},\"source\":{\"type\":\"string\",\"enum\":[\"dictionary\",\"jev\"]},\"version\":{\"type\":\"string\"}},\"required\":[\"cached\",\"plan\",\"signature\",\"source\",\"version\"],\"additionalProperties\":false}},\"required\":[\"plan\"],\"additionalProperties\":false},\"query\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}},\"seed\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}}},\"required\":[\"results\",\"view\",\"suggestions\",\"mode\",\"maxScore\",\"query\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"results\":{\"ty",
9
9
  "pe\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"attributes\":{\"description\":\"Resolved attribute values for the ids sent in `returnAttributes`, keyed by attribute id. ABSENT entirely when `returnAttributes` was not sent. A key present with `null` means this variant carries no accepted value for that attribute — absent is not empty and neither is null: the three states are distinct on purpose. Values resolve with the normal variant-over-product precedence.\",\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{\"anyOf\":[{\"type\":\"string\"},{\"type\":\"array\",\"items\":{\"type\":\"string\"}},{\"type\":\"null\"}]}},\"brand\":{\"type\":\"string\"},\"brandSlug\":{\"type\":[\"string\",\"null\"]},\"imageSha256\":{\"description\":\"SHA-256 of the displayed image `imageUrl` points at, as a hex digest. Lets a caller key a cache or dedupe on the asset rather than its URL. ABSENT means no hash is on record, never that there is no image.\",\"type\":\"string\"},\"imageUrl\":{\"type\":[\"string\",\"null\"]},\"manufacturerCode\":{\"description\":\"The governed manufacturer/source-native item code from `variants.code` (for example \\\"K-7265-BV\\\") — the same key and the same value the full item publishes. It is what separates two cards a reader cannot otherwise tell apart: brand, product name and variant name are shared by 280,334 of 371,515 cardable variants, because `name` is the PRODUCT name and a paint product is a whole collection. ABSENT means no code is recorded for the variant, never an invented one.\",\"type\":\"string\"},\"name\":{\"type\":\"string\",\"description\":\"Product name (the full item's `product`).\"},\"productId\":{\"type\":[\"string\",\"null\"]},\"projectSaveCapability\":{\"type\":\"string\",\"const\":\"allowed\"},\"resultId\":{\"type\":\"string\",\"description\":\"This exact hit's attribution key — `<responseId>:<rank>`, where rank is 1-based within THIS response. Unique across pages, because each page is its own response with its own id. Store it verbatim next to the outcome; it is opaque, and `attribution.responseId` is the field to group by.\"},\"sampleable\":{\"description\":\"Whether a physical sample is explicitly known to be orderable. Absent means unknown, never unavailable.\",\"type\":\"boolean\"},\"score\":{\"type\":\"number\"},\"sourceStatus\":{\"type\":\"string\",\"const\":\"provisional\"},\"variantCount\":{\"description\":\"How many eligible variants the row's product has, the same set `list_variants` pages, so a follow-up list of the product returns this many: the card's \\\"12 colourways\\\". ABSENT means the product is unknown or has no eligible variant.\",\"type\":\"integer\",\"exclusiveMinimum\":0,\"maximum\":9007199254740991},\"variantId\":{\"type\":\"string\"},\"variantName\":{\"type\":[\"string\",\"null\"]}},\"required\":[\"brand\",\"brandSlug\",\"imageUrl\",\"name\",\"productId\",\"score\",\"variantId\",\"variantName\"],\"additionalProperties\":false}},\"view\":{\"type\":\"string\",\"const\":\"card\",\"description\":\"Projection echo — always 'card' on this envelope variant.\"},\"correctedQuery\":{\"description\":\"Present when a word no material carries was corrected before the search ran: `from` is the query as typed (normalised), `to` the phrasing the page shows results for.\",\"type\":\"object\",\"properties\":{\"from\":{\"type\":\"string\"},\"to\":{\"type\":\"string\"}},\"required\":[\"from\",\"to\"],\"additionalProperties\":false},\"attribution\":{\"type\":\"object\",\"properties\":{\"basis\":{\"type\":\"string\",\"enum\":[\"image\",\"colour\",\"lexical\",\"agent\",\"attribute\",\"match\"],\"description\":\"The ranking basis, groupable across both ranked routes. On /api/v1/search this is the envelope's `mode` verbatim; /api/v1/search/match reports 'match'. Group your stored outcomes by this field.\"},\"issuedAt\":{\"type\":\"string\",\"description\":\"ISO-8601 timestamp of when this response was assembled, so a stored outcome carries its own age without a second lookup.\"},\"responseId\":{\"type\":\"string\",\"description\":\"This response's stable id — the same value as the `X-Request-Id` header. Store it next to the outcome (an order, a save, a click) to join that outcome back to the ranking that produced it. Opaque: round-trip it verbatim, never parse it.\"}},\"required\":[\"basis\",\"issuedAt\",\"responseId\"],\"additionalProperties\":fals",
10
10
  "e,\"description\":\"Result attribution receipt. Present on every successful ranked response. Nothing on our side retains it — what you store IS the record.\"},\"suggestions\":{\"default\":[],\"description\":\"Always empty: the one search offers no refinements. Read nothing from it.\",\"maxItems\":0,\"type\":\"array\",\"items\":{\"not\":{}}},\"mode\":{\"type\":\"string\",\"enum\":[\"image\",\"colour\",\"lexical\",\"agent\",\"attribute\"],\"description\":\"What ordered the page: 'image' (a look-alike: image similarity to the seed), 'colour' (the words or the caller named a colour: colour distance inside that colour's family), 'lexical' (the query's words, ranked by text rank on the index), 'attribute' (no words left to rank on: the category and filters define the page), or 'agent' (the search agent chose these variants and their order; scores are rank-based, 1 for the first result falling linearly, and `maxScore` is 1).\"},\"maxScore\":{\"type\":\"number\"},\"note\":{\"type\":\"string\"},\"cursor\":{\"type\":\"string\"},\"timings\":{\"description\":\"Wall-clock milliseconds per step of the search, keyed by step id. The key set is open and depends on the path a query took, so read it by iterating rather than by naming a key. Present only when `explain` is set: diagnostic, not part of the ranking contract.\",\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{\"type\":\"number\"}},\"explain\":{\"description\":\"Present only under `explain`: the receipt of the plan the search ran on. Diagnostic, like `timings`.\",\"type\":\"object\",\"properties\":{\"plan\":{\"type\":\"object\",\"properties\":{\"cached\":{\"type\":\"boolean\"},\"plan\":{\"type\":\"object\",\"properties\":{\"brands\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"ids\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"names\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"phrase\":{\"type\":\"string\"}},\"required\":[\"ids\",\"names\",\"phrase\"],\"additionalProperties\":false},{\"type\":\"null\"}]},\"category\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"label\":{\"type\":\"string\"},\"phrase\":{\"type\":\"string\"},\"productTypeIds\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"label\",\"phrase\",\"productTypeIds\"],\"additionalProperties\":false},{\"type\":\"null\"}]},\"colour\":{\"anyOf\":[{\"type\":\"object\",\"properties\":{\"family12\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"familyId\":{\"type\":\"string\"},\"hex\":{\"type\":\"string\"},\"oklab\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":3,\"maxItems\":3},\"phrase\":{\"type\":\"string\"},\"range\":{\"type\":\"object\",\"properties\":{\"chroma\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2},\"hue\":{\"anyOf\":[{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2},{\"type\":\"null\"}]},\"lightness\":{\"type\":\"array\",\"prefixItems\":[{\"type\":\"number\"},{\"type\":\"number\"}],\"items\":false,\"minItems\":2,\"maxItems\":2}},\"required\":[\"chroma\",\"hue\",\"lightness\"],\"additionalProperties\":false}},\"required\":[\"family12\",\"familyId\",\"hex\",\"oklab\",\"phrase\"],\"additionalProperties\":false},{\"type\":\"null\"}]},\"corpusScope\":{\"type\":\"string\",\"enum\":[\"samplizeCatalogue\",\"materialBankOrderable\",\"materialBankNaOrderable\",\"materialBankNaAndCatalogue\",\"materialBankEuOrderable\",\"designShopOrderable\"]},\"excludeBrands\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"excludes\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\"},\"phrase\":{\"type\":\"string\"},\"term\":{\"type\":\"string\"}},\"required\":[\"attributeId\",\"phrase\",\"term\"],\"additionalProperties\":false}},\"leftover\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"normalised\":{\"type\":\"string\"},\"softAlone\":{\"type\":\"boolean\"},\"vendorColourNames\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"wants\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"attributeId\":{\"type\":\"string\"},\"fill\":{\"type\":\"number\"},\"phrase\":{\"type\":\"string\"},\"presence\":{\"type\":\"boolean\",\"const\":true},\"strength\":{\"type\":\"string\",\"enum\":[\"hard\",\"soft\"]},\"terms\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"attributeId\",\"fill\",\"phrase\",\"strength\",\"terms\"],\"additionalProperties\":false}}},\"req",
11
- "uired\":[\"brands\",\"category\",\"colour\",\"excludeBrands\",\"excludes\",\"leftover\",\"normalised\",\"softAlone\",\"vendorColourNames\",\"wants\"],\"additionalProperties\":false,\"description\":\"The plan the search ran on: the category, makers, wanted and excluded attribute terms (each hard or soft, with its fill), the colour, the words left for text, and the caller's corpus scope when one was sent.\"},\"signature\":{\"type\":\"string\"},\"source\":{\"type\":\"string\",\"enum\":[\"dictionary\",\"jev\"]},\"version\":{\"type\":\"string\"}},\"required\":[\"cached\",\"plan\",\"signature\",\"source\",\"version\"],\"additionalProperties\":false}},\"required\":[\"plan\"],\"additionalProperties\":false},\"query\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}},\"seed\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}}},\"required\":[\"results\",\"view\",\"suggestions\",\"mode\",\"maxScore\",\"query\"],\"additionalProperties\":false}],\"type\":\"object\"},\"links\":{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false},\"meta\":{\"type\":\"object\",\"properties\":{\"operationId\":{\"type\":\"string\",\"enum\":[\"search_materials_post\",\"search_materials_get\"]},\"requestId\":{\"type\":\"string\",\"minLength\":1}},\"required\":[\"operationId\",\"requestId\"],\"additionalProperties\":false},\"warnings\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"minLength\":1}}},\"required\":[\"data\",\"links\",\"meta\",\"warnings\"],\"additionalProperties\":false}}},\"path\":\"/api/v1/search\",\"requiredHeaders\":[],\"requiresVisibleConfirmation\":false}]",
11
+ "uired\":[\"brands\",\"category\",\"colour\",\"excludeBrands\",\"excludes\",\"leftover\",\"normalised\",\"softAlone\",\"vendorColourNames\",\"wants\"],\"additionalProperties\":false,\"description\":\"The plan the search ran on: the category, brands, wanted and excluded attribute terms (each hard or soft, with its fill), the colour, the words left for text, and the caller's corpus scope when one was sent.\"},\"signature\":{\"type\":\"string\"},\"source\":{\"type\":\"string\",\"enum\":[\"dictionary\",\"jev\"]},\"version\":{\"type\":\"string\"}},\"required\":[\"cached\",\"plan\",\"signature\",\"source\",\"version\"],\"additionalProperties\":false}},\"required\":[\"plan\"],\"additionalProperties\":false},\"query\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}},\"seed\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}}},\"required\":[\"results\",\"view\",\"suggestions\",\"mode\",\"maxScore\",\"query\"],\"additionalProperties\":false}],\"type\":\"object\"},\"links\":{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false},\"meta\":{\"type\":\"object\",\"properties\":{\"operationId\":{\"type\":\"string\",\"enum\":[\"search_materials_post\",\"search_materials_get\"]},\"requestId\":{\"type\":\"string\",\"minLength\":1}},\"required\":[\"operationId\",\"requestId\"],\"additionalProperties\":false},\"warnings\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"minLength\":1}}},\"required\":[\"data\",\"links\",\"meta\",\"warnings\"],\"additionalProperties\":false}}},\"path\":\"/api/v1/search\",\"requiredHeaders\":[],\"requiresVisibleConfirmation\":false}]",
12
12
  ].join("");
13
13
  export const WEBMCP_TOOLS = JSON.parse(json);