@instruments/materialgraph 0.16.0 → 0.17.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.
@@ -1,4 +1,4 @@
1
- import type { CatalogueDocumentAccessResult, CatalogueReferenceBatchResponse } from "./catalogue-types.js";
1
+ import type { CatalogDocumentList, CatalogProductReferenceList, CatalogueDocumentAccessResult, CatalogueReferenceBatchResponse } from "./catalogue-types.js";
2
2
  import type { HttpTransport } from "./http-transport.js";
3
3
  import type { VariantId } from "./identifiers.js";
4
4
  /** Catalogue publication operations inherited by the public SDK client. */
@@ -13,6 +13,10 @@ export declare class CatalogueClient {
13
13
  catalogueReferences(input: {
14
14
  variantIds: VariantId[];
15
15
  }, signal?: AbortSignal): Promise<CatalogueReferenceBatchResponse>;
16
+ /** List immutable catalog documents retained from verified source material. */
17
+ catalogs(signal?: AbortSignal): Promise<CatalogDocumentList>;
18
+ /** Read every verified product/page reference for one retained catalog. */
19
+ catalogReferences(catalogId: string, signal?: AbortSignal): Promise<CatalogProductReferenceList>;
16
20
  /** Resolve browser-safe access to one immutable catalogue document. */
17
21
  catalogueDocumentAccess(documentId: string, signal?: AbortSignal): Promise<CatalogueDocumentAccessResult>;
18
22
  }
@@ -13,6 +13,14 @@ export class CatalogueClient {
13
13
  catalogueReferences(input, signal) {
14
14
  return this.#transport.requestJson(resolveOperation("get_catalogue_references_batch"), { body: input, signal });
15
15
  }
16
+ /** List immutable catalog documents retained from verified source material. */
17
+ catalogs(signal) {
18
+ return this.#transport.requestJson(resolveOperation("list_catalog_documents"), { signal });
19
+ }
20
+ /** Read every verified product/page reference for one retained catalog. */
21
+ catalogReferences(catalogId, signal) {
22
+ return this.#transport.requestJson(resolveOperation("list_catalog_product_references"), { pathParams: { catalogId }, signal });
23
+ }
16
24
  /** Resolve browser-safe access to one immutable catalogue document. */
17
25
  catalogueDocumentAccess(documentId, signal) {
18
26
  return this.#transport.requestJson(resolveOperation("get_catalogue_document_access"), { pathParams: { documentId }, signal });
@@ -1,5 +1,13 @@
1
1
  /** A verified product location in a specific immutable brand publication. */
2
2
  export interface CatalogueReference {
3
+ anchor: {
4
+ coordinateSpace: "normalized_0_1000";
5
+ height: number;
6
+ kind: "printed_code";
7
+ width: number;
8
+ x: number;
9
+ y: number;
10
+ };
3
11
  variantId: string;
4
12
  brand: {
5
13
  id: string;
@@ -63,3 +71,32 @@ export type CatalogueDocumentAccessResult = {
63
71
  documentId: string;
64
72
  status: "unresolved_document" | "unavailable" | "forbidden" | "upstream_failure";
65
73
  };
74
+ export interface CatalogDocument {
75
+ brand: {
76
+ id: string;
77
+ name: string;
78
+ };
79
+ document: {
80
+ contentScope: "full_document" | "cover_only";
81
+ id: string;
82
+ mediaType: "application/pdf";
83
+ pageCount: number;
84
+ };
85
+ id: string;
86
+ kind: "document";
87
+ productStatus: "partial" | "not_processed";
88
+ title: string;
89
+ }
90
+ export interface CatalogDocumentList {
91
+ catalogs: CatalogDocument[];
92
+ source: "verified-source-material-registry";
93
+ }
94
+ export interface CatalogProductReferenceList {
95
+ catalog: CatalogDocument;
96
+ coverage: {
97
+ pagesWithReferences: number;
98
+ references: number;
99
+ };
100
+ references: CatalogueReference[];
101
+ source: "verified-source-material-registry";
102
+ }
package/index.d.ts CHANGED
@@ -17,6 +17,7 @@ export type * from "./run-events.js";
17
17
  export { MATERIALGRAPH_PROBLEM_EXTENSIONS, MaterialGraphError, } from "./errors.js";
18
18
  export type { MaterialGraphErrorInit, MaterialGraphErrorKind, MaterialGraphRequestMetadata, ProblemDetail, RateLimit, SanitizedErrorValue, SanitizedIssue, SimpleErrorBody, } from "./errors.js";
19
19
  export type * from "./types.js";
20
+ export type * from "./search-collapse-types.js";
20
21
  export type * from "./match-types.js";
21
22
  export type * from "./browse-types.js";
22
23
  export type * from "./catalogue-types.js";
package/operations.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export declare const MATERIALGRAPH_API_VERSION = "0.7.0";
2
- declare const OPERATION_DEFINITIONS: readonly [readonly ["search", "POST", "/api/v1/search", "search_materials_post"], readonly ["planQuery", "POST", "/api/v1/search/plan", "plan_material_query"], readonly ["searchGet", "GET", "/api/v1/search", "search_materials_get"], readonly ["match", "POST", "/api/v1/search/match", "match_material"], readonly ["byColour", "GET", "/api/v1/search/colours", "search_by_colour"], readonly ["material", "GET", "/api/v1/materials/{variantId}", "get_material"], readonly ["catalogueReferences", "POST", "/api/v1/catalogues/references", "get_catalogue_references_batch"], readonly ["catalogueDocumentAccess", "GET", "/api/v1/catalogues/documents/{documentId}", "get_catalogue_document_access"], readonly ["siblings", "GET", "/api/v1/materials/{variantId}/siblings", "get_material_siblings"], readonly ["materialBlocks", "GET", "/api/v1/materials/{variantId}/blocks", "list_material_blocks"], readonly ["materialAssets", "GET", "/api/v1/materials/{variantId}/assets", "list_material_assets"], readonly ["exploration.operators", "GET", "/api/v1/materials/{variantId}/operators", "list_material_operators"], readonly ["exploration.candidates", "POST", "/api/v1/materials/{variantId}/candidates", "read_material_candidates"], readonly ["schema", "GET", "/api/v1/schema/blocks", "list_schema_blocks"], readonly ["schema", "GET", "/api/v1/schema/attributes", "list_schema_attributes"], readonly ["schema", "GET", "/api/v1/schema/verticals", "list_schema_verticals"], readonly ["schema", "GET", "/api/v1/schema/product-types", "list_schema_product_types"], readonly ["schema", "GET", "/api/v1/schema/units", "list_schema_units"], readonly ["schema", "GET", "/api/v1/schema/value-dictionaries", "list_schema_value_dictionaries"], readonly ["schema", "GET", "/api/v1/schema/source-requirements", "list_schema_source_requirements"], readonly ["schema", "GET", "/api/v1/schema/product-type-templates", "list_schema_product_type_templates"], readonly ["schema", "GET", "/api/v1/schema/mappings", "list_schema_mappings"], readonly ["pairings", "GET", "/api/v1/materials/{variantId}/pairings", "get_material_pairings"], readonly ["associatedFinishes", "GET", "/api/v1/associated/finishes", "list_associated_finishes"], readonly ["associatedProducts", "GET", "/api/v1/associated/products", "list_associated_products"], readonly ["materials", "POST", "/api/v1/materials/batch", "get_materials_batch_post"], readonly ["materialsCached", "GET", "/api/v1/materials/batch", "get_materials_batch_get"], readonly ["browse", "GET", "/api/v1/search/browse", "search_browse_by_colour"], readonly ["colourFacets", "GET", "/api/v1/colours/facets", "get_colour_facets"], readonly ["colours.profiles", "GET", "/api/v1/colours/profiles", "get_category_colour_profiles"], readonly ["colours.stats", "GET", "/api/v1/colours/stats", "get_colour_stats"], readonly ["resolveColour", "GET", "/api/v1/search/colours/resolve", "resolve_colour_name"], readonly ["suggestColour", "GET", "/api/v1/search/colours/suggest", "suggest_colour_names"], readonly ["suggest", "GET", "/api/v1/search/suggest", "search_suggest"], readonly ["resolveAttribute", "GET", "/api/v1/catalog/attributes/resolve", "catalog_resolve_attribute"], readonly ["resolveAttributeValue", "GET", "/api/v1/catalog/attributes/{attributeId}/values/resolve", "catalog_resolve_value"], readonly ["searchVocabulary", "GET", "/api/v1/catalog/vocabulary/search", "catalog_vocabulary_search"], readonly ["resolve", "POST", "/api/v1/resolve", "resolve_phrases"], readonly ["bySpec", "GET", "/api/v1/search/specifications", "catalog_by_spec"], readonly ["taxonomy", "GET", "/api/v1/taxonomy/categories", "get_category_taxonomy"], readonly ["whoami", "GET", "/api/v1/whoami", "whoami"], readonly ["coordinate", "POST", "/api/v1/pairings/coordinate", "coordinate_materials"], readonly ["demand", "GET", "/api/v1/demand", "get_material_demand"], readonly ["colours.palette", "POST", "/api/v1/search/palettes", "search_by_palette"], readonly ["extractPalette", "POST", "/api/v1/search/palettes/extract", "extract_image_palette"], readonly ["colours.gradient", "POST", "/api/v1/search/gradients", "search_by_gradient"], readonly ["specificationFacets", "GET", "/api/v1/catalog/specifications/facets", "list_spec_facets"], readonly ["attributeValues", "GET", "/api/v1/catalog/attributes/{attributeId}/values", "list_attribute_values"], readonly ["applicableSpecs", "GET", "/api/v1/taxonomy/categories/{key}/specs", "get_applicable_specs"], readonly ["glossary", "GET", "/api/v1/glossary/{term}", "explain_material_term"], readonly ["brands", "GET", "/api/v1/brands", "list_brands"], readonly ["brand", "GET", "/api/v1/brands/{brandSlug}", "get_brand_profile"], readonly ["brandColourDeck", "GET", "/api/v1/brands/{brandSlug}/colours", "brand_colour_deck"], readonly ["uploadSourceMaterial", "POST", "/api/v1/source-material", "upload_source_material"], readonly ["sourceMaterialBatch", "GET", "/api/v1/source-material/{batchId}", "get_source_material_batch"], readonly ["sourceMaterialComments", "GET", "/api/v1/source-material/{batchId}/sources/{sourceId}/comments", "list_source_material_comments"], readonly ["sourceMaterialComments", "POST", "/api/v1/source-material/{batchId}/sources/{sourceId}/comments", "post_source_material_comment"], readonly ["capabilities", "GET", "/api/v1/capabilities", "list_public_capabilities"], readonly ["createRun", "POST", "/api/v1/runs", "create_public_run"], readonly ["listRuns", "GET", "/api/v1/runs", "list_public_runs"], readonly ["run", "GET", "/api/v1/runs/{runId}", "get_public_run"], readonly ["runSchedule", "GET", "/api/v1/runs/{runId}/schedule", "get_run_finish_schedule"], readonly ["streamRunEvents", "GET", "/api/v1/runs/{runId}/events", "list_public_run_events"], readonly ["resumeRun", "POST", "/api/v1/runs/{runId}/resume", "resume_public_run"], readonly ["cancelRun", "POST", "/api/v1/runs/{runId}/cancel", "cancel_public_run"], readonly ["varyScheme", "POST", "/api/v1/schemes/vary", "vary_schemes"], readonly ["slotPools", "POST", "/api/v1/schemes/pools", "slot_pools"], readonly ["createProject", "POST", "/api/v1/projects", "create_box_project"], readonly ["listProjects", "GET", "/api/v1/projects", "list_box_projects"], readonly ["getProject", "GET", "/api/v1/projects/{projectId}", "get_box_project"], readonly ["appendProjectEvent", "POST", "/api/v1/projects/{projectId}/events", "append_box_project_event"], readonly ["demandBatch", "POST", "/api/v1/demand/batch", "get_demand_batch"], readonly ["codeAdvisory", "POST", "/api/v1/codes/advisory", "resolveCodeAdvisory"], readonly ["codes.context", "GET", "/api/v1/codes/context", "list_code_context"], readonly ["codes.requirements", "GET", "/api/v1/codes/requirements", "list_code_requirements"]];
2
+ declare const OPERATION_DEFINITIONS: readonly [readonly ["catalogs", "GET", "/api/v1/catalogs", "list_catalog_documents"], readonly ["catalogReferences", "GET", "/api/v1/catalogs/{catalogId}/references", "list_catalog_product_references"], readonly ["search", "POST", "/api/v1/search", "search_materials_post"], readonly ["planQuery", "POST", "/api/v1/search/plan", "plan_material_query"], readonly ["searchGet", "GET", "/api/v1/search", "search_materials_get"], readonly ["match", "POST", "/api/v1/search/match", "match_material"], readonly ["byColour", "GET", "/api/v1/search/colours", "search_by_colour"], readonly ["material", "GET", "/api/v1/materials/{variantId}", "get_material"], readonly ["catalogueReferences", "POST", "/api/v1/catalogues/references", "get_catalogue_references_batch"], readonly ["catalogueDocumentAccess", "GET", "/api/v1/catalogues/documents/{documentId}", "get_catalogue_document_access"], readonly ["siblings", "GET", "/api/v1/materials/{variantId}/siblings", "get_material_siblings"], readonly ["materialBlocks", "GET", "/api/v1/materials/{variantId}/blocks", "list_material_blocks"], readonly ["materialAssets", "GET", "/api/v1/materials/{variantId}/assets", "list_material_assets"], readonly ["exploration.operators", "GET", "/api/v1/materials/{variantId}/operators", "list_material_operators"], readonly ["exploration.candidates", "POST", "/api/v1/materials/{variantId}/candidates", "read_material_candidates"], readonly ["schema", "GET", "/api/v1/schema/blocks", "list_schema_blocks"], readonly ["schema", "GET", "/api/v1/schema/attributes", "list_schema_attributes"], readonly ["schema", "GET", "/api/v1/schema/verticals", "list_schema_verticals"], readonly ["schema", "GET", "/api/v1/schema/product-types", "list_schema_product_types"], readonly ["schema", "GET", "/api/v1/schema/units", "list_schema_units"], readonly ["schema", "GET", "/api/v1/schema/value-dictionaries", "list_schema_value_dictionaries"], readonly ["schema", "GET", "/api/v1/schema/source-requirements", "list_schema_source_requirements"], readonly ["schema", "GET", "/api/v1/schema/product-type-templates", "list_schema_product_type_templates"], readonly ["schema", "GET", "/api/v1/schema/mappings", "list_schema_mappings"], readonly ["pairings", "GET", "/api/v1/materials/{variantId}/pairings", "get_material_pairings"], readonly ["associatedFinishes", "GET", "/api/v1/associated/finishes", "list_associated_finishes"], readonly ["associatedProducts", "GET", "/api/v1/associated/products", "list_associated_products"], readonly ["materials", "POST", "/api/v1/materials/batch", "get_materials_batch_post"], readonly ["materialsCached", "GET", "/api/v1/materials/batch", "get_materials_batch_get"], readonly ["browse", "GET", "/api/v1/search/browse", "search_browse_by_colour"], readonly ["colourFacets", "GET", "/api/v1/colours/facets", "get_colour_facets"], readonly ["colours.profiles", "GET", "/api/v1/colours/profiles", "get_category_colour_profiles"], readonly ["colours.stats", "GET", "/api/v1/colours/stats", "get_colour_stats"], readonly ["resolveColour", "GET", "/api/v1/search/colours/resolve", "resolve_colour_name"], readonly ["suggestColour", "GET", "/api/v1/search/colours/suggest", "suggest_colour_names"], readonly ["suggest", "GET", "/api/v1/search/suggest", "search_suggest"], readonly ["resolveAttribute", "GET", "/api/v1/catalog/attributes/resolve", "catalog_resolve_attribute"], readonly ["resolveAttributeValue", "GET", "/api/v1/catalog/attributes/{attributeId}/values/resolve", "catalog_resolve_value"], readonly ["searchVocabulary", "GET", "/api/v1/catalog/vocabulary/search", "catalog_vocabulary_search"], readonly ["resolve", "POST", "/api/v1/resolve", "resolve_phrases"], readonly ["bySpec", "GET", "/api/v1/search/specifications", "catalog_by_spec"], readonly ["taxonomy", "GET", "/api/v1/taxonomy/categories", "get_category_taxonomy"], readonly ["whoami", "GET", "/api/v1/whoami", "whoami"], readonly ["coordinate", "POST", "/api/v1/pairings/coordinate", "coordinate_materials"], readonly ["demand", "GET", "/api/v1/demand", "get_material_demand"], readonly ["colours.palette", "POST", "/api/v1/search/palettes", "search_by_palette"], readonly ["extractPalette", "POST", "/api/v1/search/palettes/extract", "extract_image_palette"], readonly ["colours.gradient", "POST", "/api/v1/search/gradients", "search_by_gradient"], readonly ["specificationFacets", "GET", "/api/v1/catalog/specifications/facets", "list_spec_facets"], readonly ["attributeValues", "GET", "/api/v1/catalog/attributes/{attributeId}/values", "list_attribute_values"], readonly ["applicableSpecs", "GET", "/api/v1/taxonomy/categories/{key}/specs", "get_applicable_specs"], readonly ["glossary", "GET", "/api/v1/glossary/{term}", "explain_material_term"], readonly ["brands", "GET", "/api/v1/brands", "list_brands"], readonly ["brand", "GET", "/api/v1/brands/{brandSlug}", "get_brand_profile"], readonly ["brandColourDeck", "GET", "/api/v1/brands/{brandSlug}/colours", "brand_colour_deck"], readonly ["uploadSourceMaterial", "POST", "/api/v1/source-material", "upload_source_material"], readonly ["sourceMaterialBatch", "GET", "/api/v1/source-material/{batchId}", "get_source_material_batch"], readonly ["sourceMaterialComments", "GET", "/api/v1/source-material/{batchId}/sources/{sourceId}/comments", "list_source_material_comments"], readonly ["sourceMaterialComments", "POST", "/api/v1/source-material/{batchId}/sources/{sourceId}/comments", "post_source_material_comment"], readonly ["capabilities", "GET", "/api/v1/capabilities", "list_public_capabilities"], readonly ["createRun", "POST", "/api/v1/runs", "create_public_run"], readonly ["listRuns", "GET", "/api/v1/runs", "list_public_runs"], readonly ["run", "GET", "/api/v1/runs/{runId}", "get_public_run"], readonly ["runSchedule", "GET", "/api/v1/runs/{runId}/schedule", "get_run_finish_schedule"], readonly ["streamRunEvents", "GET", "/api/v1/runs/{runId}/events", "list_public_run_events"], readonly ["resumeRun", "POST", "/api/v1/runs/{runId}/resume", "resume_public_run"], readonly ["cancelRun", "POST", "/api/v1/runs/{runId}/cancel", "cancel_public_run"], readonly ["varyScheme", "POST", "/api/v1/schemes/vary", "vary_schemes"], readonly ["slotPools", "POST", "/api/v1/schemes/pools", "slot_pools"], readonly ["createProject", "POST", "/api/v1/projects", "create_box_project"], readonly ["listProjects", "GET", "/api/v1/projects", "list_box_projects"], readonly ["getProject", "GET", "/api/v1/projects/{projectId}", "get_box_project"], readonly ["appendProjectEvent", "POST", "/api/v1/projects/{projectId}/events", "append_box_project_event"], readonly ["demandBatch", "POST", "/api/v1/demand/batch", "get_demand_batch"], readonly ["codeAdvisory", "POST", "/api/v1/codes/advisory", "resolveCodeAdvisory"], readonly ["codes.context", "GET", "/api/v1/codes/context", "list_code_context"], readonly ["codes.requirements", "GET", "/api/v1/codes/requirements", "list_code_requirements"]];
3
3
  export type SdkOperationId = (typeof OPERATION_DEFINITIONS)[number][3];
4
4
  export type SdkOperation = Readonly<{
5
5
  clientMethod: (typeof OPERATION_DEFINITIONS)[number][0];
package/operations.js CHANGED
@@ -1,6 +1,13 @@
1
1
  import { MaterialGraphError } from "./errors.js";
2
2
  export const MATERIALGRAPH_API_VERSION = "0.7.0";
3
3
  const OPERATION_DEFINITIONS = [
4
+ ["catalogs", "GET", "/api/v1/catalogs", "list_catalog_documents"],
5
+ [
6
+ "catalogReferences",
7
+ "GET",
8
+ "/api/v1/catalogs/{catalogId}/references",
9
+ "list_catalog_product_references",
10
+ ],
4
11
  ["search", "POST", "/api/v1/search", "search_materials_post"],
5
12
  ["planQuery", "POST", "/api/v1/search/plan", "plan_material_query"],
6
13
  ["searchGet", "GET", "/api/v1/search", "search_materials_get"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materialgraph",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,3 +1,4 @@
1
+ import type { SearchFilter } from "./search-refinement-types.js";
1
2
  import type { OpenEnum } from "./types.js";
2
3
  export type QuerySenseKind = OpenEnum<"brand" | "product" | "paint_identity" | "category" | "material" | "colour" | "attribute" | "attribute_value" | "certification">;
3
4
  export interface QuerySense {
@@ -26,6 +27,181 @@ export interface QuerySense {
26
27
  };
27
28
  reason?: string;
28
29
  }
30
+ export interface CanonicalLexicalAuthorityReceipt {
31
+ authority: OpenEnum<"taxonomy" | "colorscope">;
32
+ version: string;
33
+ revision: string;
34
+ digest?: string;
35
+ }
36
+ export interface CanonicalLexicalReading {
37
+ authority: OpenEnum<"taxonomy" | "colorscope">;
38
+ identity: string;
39
+ outcome: OpenEnum<"equivalent" | "ambiguous" | "declined">;
40
+ executionStatus: OpenEnum<"applied" | "suppressed" | "executable" | "recognized-but-unexecutable" | "declined">;
41
+ role: OpenEnum<"primary" | "secondary"> | null;
42
+ kind?: OpenEnum<"domain" | "core-colour" | "paint">;
43
+ label?: string;
44
+ libraryId?: string;
45
+ code?: string;
46
+ aliasCount?: number;
47
+ matchForm?: OpenEnum<"name" | "code" | "authoritative-term">;
48
+ collisionCount?: number;
49
+ familyId?: string;
50
+ hex?: string;
51
+ level?: OpenEnum<"family" | "tone-band" | "branch" | "concept">;
52
+ lifecycle?: {
53
+ introducedAt: string;
54
+ reviewedAt?: string;
55
+ status: string;
56
+ };
57
+ provenance?: {
58
+ basis: string;
59
+ snapshot?: string;
60
+ source: string;
61
+ };
62
+ }
63
+ export interface CanonicalLexicalMatch {
64
+ original: string;
65
+ canonical: string;
66
+ rule: OpenEnum<"exact" | "plural-to-singular">;
67
+ span: {
68
+ start: number;
69
+ end: number;
70
+ };
71
+ coverage: number;
72
+ readings: CanonicalLexicalReading[];
73
+ receipt: {
74
+ original: string;
75
+ canonical: string;
76
+ rule: OpenEnum<"exact" | "plural-to-singular">;
77
+ authorities: CanonicalLexicalAuthorityReceipt[];
78
+ };
79
+ }
80
+ export interface CanonicalLexicalReceipt {
81
+ original: string;
82
+ outcome: OpenEnum<"resolved" | "ambiguous" | "declined" | "partial" | "unresolved">;
83
+ matches: CanonicalLexicalMatch[];
84
+ unresolved: CanonicalLexicalMatch[];
85
+ }
86
+ export interface QueryResidualReceipt {
87
+ kind: OpenEnum<"compound" | "descriptive" | "ambiguous" | "empty">;
88
+ tokens: string[];
89
+ resolvedTokens: string[];
90
+ unresolvedTokens: string[];
91
+ resolvedRatio: number;
92
+ comparisonFrame: boolean;
93
+ }
94
+ /** Deterministic criteria compiled from the query before retrieval. */
95
+ export interface QueryCriteriaReceipt {
96
+ applied: Array<{
97
+ criterion: SearchFilter;
98
+ lane: OpenEnum<"filter" | "preference">;
99
+ lexicalSignal?: {
100
+ attributeId: string;
101
+ canonical: string;
102
+ identity: string;
103
+ original: string;
104
+ relation: OpenEnum<"equivalent" | "broader-storage-term">;
105
+ span: {
106
+ start: number;
107
+ end: number;
108
+ };
109
+ storedTerm: string;
110
+ };
111
+ source: OpenEnum<"compiled" | "taxonomy-adapter">;
112
+ span: string;
113
+ }>;
114
+ conflicts: Array<{
115
+ attributeId: string;
116
+ caller: SearchFilter;
117
+ compiled: SearchFilter;
118
+ reason: string;
119
+ }>;
120
+ exclusions: Array<{
121
+ criterion: SearchFilter;
122
+ execution: OpenEnum<"blocked-positive-only-contract">;
123
+ lane: OpenEnum<"hard" | "soft">;
124
+ missingValuePolicy: OpenEnum<"retain-unknown">;
125
+ span: string;
126
+ }>;
127
+ preferences: SearchFilter[];
128
+ residualText: string | null;
129
+ unresolved: Array<{
130
+ code: OpenEnum<"missing-searchable-attribute" | "missing-dimension-pair-contract" | "missing-explicit-comparator" | "negative-criterion-contract-blocked" | "registry-contract-drift" | "unsupported-numeric-format" | "unsupported-unit" | "unsupported-strict-comparator">;
131
+ reason: string;
132
+ span: string;
133
+ }>;
134
+ }
135
+ /** How the query named or inferred a product type before retrieval. */
136
+ export interface QueryProductTypeReceipt {
137
+ kind: OpenEnum<"leaf" | "group" | "vector" | "ambiguous" | "none">;
138
+ gate: OpenEnum<"hard" | "constrain" | "none">;
139
+ pass: OpenEnum<"taxonomy" | "synonym" | "display-name" | "group" | "head-noun" | "vector" | "none">;
140
+ reason: string;
141
+ displayName: string | null;
142
+ productTypeId: string | null;
143
+ groupKey: string | null;
144
+ productTypeIds: string[];
145
+ similarity: number | null;
146
+ margin: number | null;
147
+ residualText: string;
148
+ vectorPassRan: boolean;
149
+ registryVersion: string;
150
+ durationMs: number;
151
+ candidates: Array<{
152
+ productTypeId: string;
153
+ displayName: string;
154
+ similarity: number;
155
+ }>;
156
+ }
157
+ /** The selected retrieval arm; null when the path is browse or criteria-only. */
158
+ export interface QueryArmReceipt {
159
+ arm: OpenEnum<"dense" | "lexical" | "colour"> | null;
160
+ reason: string;
161
+ outcome: OpenEnum<"dense" | "lexical" | "colour" | "browse" | "criteria-only" | "ambiguous">;
162
+ decidedBy: OpenEnum<"deterministic" | "model">;
163
+ productTypeGate: OpenEnum<"hard" | "constrain" | "none">;
164
+ productTypeIds: string[];
165
+ residual: QueryResidualReceipt;
166
+ registryVersion: string;
167
+ }
168
+ export interface QueryAmbiguityReceipt {
169
+ outcome: OpenEnum<"applied" | "abstained" | "refused" | "timeout">;
170
+ modelId: string | null;
171
+ providerRan: boolean;
172
+ durationMs: number;
173
+ timeoutMs: number;
174
+ validation: OpenEnum<"valid" | "invalid" | "not-run">;
175
+ refusalCode: string | null;
176
+ confidence: number | null;
177
+ routing: OpenEnum<"compound" | "descriptive"> | null;
178
+ productTypeIds: string[];
179
+ changedArm: boolean;
180
+ }
181
+ export interface QueryCategoryScopeReceipt {
182
+ source: OpenEnum<"caller" | "anchor">;
183
+ mechanism: OpenEnum<"sql-predicate" | "allowlist" | "prune">;
184
+ variantCount: number | null;
185
+ ceiling: number;
186
+ specScanBounded?: boolean;
187
+ [key: string]: unknown;
188
+ }
189
+ export interface QuerySpecAllowlistReceipt {
190
+ limit: number;
191
+ returned: number;
192
+ truncated: boolean;
193
+ emptyMeansScarcity: boolean;
194
+ }
195
+ export interface QueryUnsatisfiableNarrowing {
196
+ narrowing: OpenEnum<"product-type">;
197
+ gate: OpenEnum<"hard">;
198
+ productTypeId: string;
199
+ displayName: string;
200
+ productTypeIds: string[];
201
+ pass: string;
202
+ evidence: OpenEnum<"zero-accepted-variants">;
203
+ relaxedTo: OpenEnum<"none">;
204
+ }
29
205
  /** Inspectable interpretation of the raw query by ordinary MaterialGraph search. */
30
206
  export interface QueryUnderstanding {
31
207
  version: OpenEnum<"1">;
@@ -35,4 +211,12 @@ export interface QueryUnderstanding {
35
211
  status: OpenEnum<"resolved" | "ambiguous" | "partial" | "unresolved">;
36
212
  mode: OpenEnum<"image" | "colour" | "text" | "lexical" | "entity-browse">;
37
213
  senses: QuerySense[];
214
+ canonicalLexical?: CanonicalLexicalReceipt;
215
+ criteria?: QueryCriteriaReceipt;
216
+ productType?: QueryProductTypeReceipt;
217
+ arm?: QueryArmReceipt;
218
+ ambiguity?: QueryAmbiguityReceipt;
219
+ categoryScope?: QueryCategoryScopeReceipt;
220
+ specAllowlist?: QuerySpecAllowlistReceipt;
221
+ unsatisfiableNarrowing?: QueryUnsatisfiableNarrowing;
38
222
  }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The product-collapsed view of a search result — what `collapse: "product"`
3
+ * hands back beside each card. Kept out of `types.ts` for the same reason
4
+ * `palette-search-types.ts` and `by-colour-types.ts` are: that file is at its
5
+ * source-size ceiling, and this is a self-contained corner of the contract.
6
+ *
7
+ * READ THIS FIRST, because the default is the opposite of what "duplicates"
8
+ * intuition suggests. MaterialGraph search is VARIANT-based: without `collapse`,
9
+ * every matching variant comes back as its own result, and that is correct —
10
+ * twelve SKUs of one tile are twelve separately orderable products, not twelve
11
+ * copies of one. Product grouping is an optional VIEW over that, not a fix for
12
+ * it.
13
+ *
14
+ * When you do ask for the view, the product arrives CARRYING its variants
15
+ * (`SearchResultItem.variants`) rather than a bare count, so a colourway strip
16
+ * renders from the same response as the card. That array is CAPPED at 12, while
17
+ * `SearchResultItem.siblingCount` stays the complete tally — so a truncated list
18
+ * is decidable from the response alone: `(siblingCount ?? 0) + 1 >
19
+ * variants.length`. A full-length array is never on its own evidence that the
20
+ * product holds no further variants.
21
+ */
22
+ import type { VariantId } from "./identifiers.js";
23
+ import type { SearchResultSignals } from "./types.js";
24
+ /**
25
+ * One variant of a product-collapsed result.
26
+ *
27
+ * A slim shape, deliberately: the fields a colourway strip renders, projected
28
+ * from the row the engine already held in memory, so the array costs no extra
29
+ * query. It is NOT a nested search result — no `attributes`, `palette`,
30
+ * `verdict` or `productType` — because those come from per-result reads the
31
+ * engine issues for the card only. Fetch the variant by `variantId` when you
32
+ * need its full record.
33
+ */
34
+ export interface CollapsedVariant {
35
+ /** Opaque — round-trip verbatim. */
36
+ variantId: VariantId;
37
+ /** The variant's own label; for paint, the colourway name. Null when unnamed. */
38
+ variantName: string | null;
39
+ /** The variant's coarse colour family. */
40
+ color: string | null;
41
+ /**
42
+ * `#rrggbb` dominant colour EXTRACTED FROM the variant's image — a visual
43
+ * likeness, never a manufacturer-declared colour specification. ABSENT means
44
+ * no palette is on record: unknown, never "this variant has no colour".
45
+ */
46
+ hex?: string;
47
+ imageUrl: string | null;
48
+ /**
49
+ * The variant's Material Bank SKU, on the same contract as the result item's:
50
+ * ABSENT means no MB identity is on record, never that it cannot be ordered.
51
+ */
52
+ materialBankId?: string;
53
+ /** This variant's own fused score — the value the array is ordered by. */
54
+ score: number;
55
+ }
56
+ /**
57
+ * One member of a collapsed result's `SearchResultItem.anatomy` — the
58
+ * EXPLAIN-only readout of the same grouping {@link CollapsedVariant} publishes.
59
+ * Where a collapsed variant is what a surface renders, this is what an operator
60
+ * inspects: it carries the per-arm `signals` that made the variant a candidate.
61
+ */
62
+ export interface SearchAnatomyMember {
63
+ /** Opaque — round-trip verbatim. */
64
+ variantId: VariantId;
65
+ variantName: string | null;
66
+ color: string | null;
67
+ score: number;
68
+ signals: SearchResultSignals;
69
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -76,6 +76,13 @@ export type SearchFilter = {
76
76
  attributeId: string;
77
77
  hasValue: true;
78
78
  trust?: SearchFilterTrust;
79
+ }
80
+ /** Date-sensitive presence is currently executable only for EPD expiration. */
81
+ | {
82
+ attributeId: "epd_expiration_date";
83
+ hasValue: true;
84
+ asOf: string;
85
+ trust?: SearchFilterTrust;
79
86
  } | {
80
87
  attributeId: string;
81
88
  /** Inclusive lower bound. Omit for a ceiling-only range. */
@@ -90,29 +97,31 @@ export interface SearchSuggestion {
90
97
  /** Complete replacement request, not a partial patch. */
91
98
  request: SearchInput;
92
99
  }
93
- /** One of the two readings a {@link SearchDisambiguation} offers. */
100
+ /** One reading a {@link SearchDisambiguation} offers. */
94
101
  export interface SearchInterpretation {
95
102
  id: string;
96
103
  label: string;
97
104
  kind: string;
98
- /** True for the reading that ACTUALLY RAN and produced this page. */
105
+ /** True for the reading that ran; all false means the engine chose none. */
99
106
  isDefault: boolean;
100
107
  /** The colour this reading resolved to, when the ambiguity was a colour one. */
101
108
  hex?: string;
102
109
  /** Complete replacement request, not a partial patch. */
103
110
  request: SearchInput;
104
111
  }
112
+ /** The governed ambiguity classes emitted by MaterialGraph search. */
113
+ export type SearchDisambiguationKind = "compliance-phrase" | "colour-material" | "paint-colour" | "paint-identity";
105
114
  /**
106
- * The server saying a query had TWO defensible readings, and which one it took.
115
+ * The server saying a query had multiple defensible readings.
107
116
  *
108
- * `interpretations` always holds exactly two, the one that ran flagged
109
- * `isDefault`. Each carries a complete executable request, so offering the
110
- * other reading is a re-issue rather than a patch. Ignoring this block does not
117
+ * The reading that ran is flagged `isDefault`; every reading is false when the
118
+ * engine deliberately chose none. Each carries a complete executable request,
119
+ * so choosing a reading is a re-issue rather than a patch. Ignoring this block does not
111
120
  * make the ambiguity go away — it makes it invisible to the person who asked.
112
121
  */
113
122
  export interface SearchDisambiguation {
114
- /** Which ambiguity this is: "compliance-phrase", "colour-material", "paint-colour". */
115
- kind: string;
123
+ /** Compliance, colour-material, paint-colour, or paint-identity ambiguity. */
124
+ kind: SearchDisambiguationKind;
116
125
  /** The ambiguous term or phrase. */
117
126
  term: string;
118
127
  interpretations: SearchInterpretation[];
@@ -4,7 +4,7 @@
4
4
  * emitted by ordinary Search responses.
5
5
  */
6
6
  import type { OpenEnum } from "./types.js";
7
- /** The single candidate source a query resolved to. */
7
+ /** The primary ranking source a query resolved to and reports as its mode. */
8
8
  export type SearchMode = OpenEnum<"image" | "colour" | "text" | "lexical" | "entity-browse">;
9
9
  /** Rerank-term report (absent in colour mode — there colour IS the source). */
10
10
  export interface SearchRerankReport {
@@ -23,11 +23,22 @@ export type SearchQueryTransformStage = {
23
23
  };
24
24
  /** Explain-only category gate selected by query understanding. */
25
25
  export interface SearchCategoryAnchor {
26
+ /**
27
+ * A leaf type id; the group key when `source` is `"group"`; or the canonical
28
+ * category noun when `source` is `"head-noun"`. Gate on `productTypeIds`.
29
+ */
26
30
  productTypeId: string;
31
+ /**
32
+ * The leaf type ids a `"group"` or `"head-noun"` anchor expanded to; absent
33
+ * for leaf sources.
34
+ */
35
+ productTypeIds?: readonly string[];
27
36
  displayName: string;
28
37
  similarity: number;
29
38
  margin: number | null;
30
- source: OpenEnum<"synonym" | "display-name" | "vector">;
39
+ source: OpenEnum<"taxonomy" | "synonym" | "display-name" | "group" | "head-noun" | "vector">;
40
+ /** Hard category eligibility is applied inside lexical candidate selection. */
41
+ candidateSource?: OpenEnum<"lexical">;
31
42
  applied: boolean;
32
43
  }
33
44
  /** Explain-only brand gate selected by query understanding. */
@@ -78,7 +89,7 @@ export interface CompoundColourAbundanceReceipt {
78
89
  * text query. Counts describe the bounded candidate pool, not the whole corpus.
79
90
  */
80
91
  export interface CompoundColourConstraintReceipt {
81
- provenance: OpenEnum<"explicit" | "detected-strong">;
92
+ basis: OpenEnum<"explicit" | "detected-strong">;
82
93
  family: string;
83
94
  queryHexes: string[];
84
95
  considered: number;
@@ -88,3 +99,25 @@ export interface CompoundColourConstraintReceipt {
88
99
  adjacent: number;
89
100
  abundance?: CompoundColourAbundanceReceipt;
90
101
  }
102
+ /** Why a hard form constraint returned a full or short page. */
103
+ export interface CompoundFormAbundanceReceipt {
104
+ grain: OpenEnum<"variant" | "product">;
105
+ requested: number;
106
+ eligible: number;
107
+ candidateWindow: number;
108
+ sourceExhausted: boolean;
109
+ state: OpenEnum<"filled" | "corpus_scarcity" | "evidence_unavailable" | "retrieval_window_exhausted">;
110
+ }
111
+ /**
112
+ * Hard form eligibility for a compound text query. Product and variant names
113
+ * may retrieve a candidate but never prove that it satisfies the form.
114
+ */
115
+ export interface CompoundFormConstraintReceipt {
116
+ instruction: OpenEnum<"subway" | "plank" | "square" | "hexagon" | "penny-round">;
117
+ considered: number;
118
+ kept: number;
119
+ dropped: number;
120
+ contradicted: number;
121
+ unknown: number;
122
+ abundance: CompoundFormAbundanceReceipt;
123
+ }
@@ -49,6 +49,23 @@ function checkPaletteSwatch(value, what, fail) {
49
49
  if (record.name !== undefined)
50
50
  requireNullableString(record.name, `${what}.name`, fail);
51
51
  }
52
+ /**
53
+ * `variants[]` — one entry of a product-collapsed result's ranked variant list
54
+ * (`collapsedVariantSchema`). A SLIM projection, not a nested result item: the
55
+ * checks here are the whole shape.
56
+ */
57
+ function checkCollapsedVariant(value, what, fail) {
58
+ const record = requireRecord(value, what, fail);
59
+ requireString(record.variantId, `${what}.variantId`, fail);
60
+ requireNullableString(record.variantName, `${what}.variantName`, fail);
61
+ requireNullableString(record.color, `${what}.color`, fail);
62
+ // `.optional()`, NOT nullable: the emit path omits the key when no palette is
63
+ // on record rather than sending null.
64
+ requireOptionalString(record.hex, `${what}.hex`, fail);
65
+ requireNullableString(record.imageUrl, `${what}.imageUrl`, fail);
66
+ requireOptionalString(record.materialBankId, `${what}.materialBankId`, fail);
67
+ requireNumber(record.score, `${what}.score`, fail);
68
+ }
52
69
  /** `colourMeta` — three independently optional strings. */
53
70
  function checkColourMeta(value, what, fail) {
54
71
  const record = requireRecord(value, what, fail);
@@ -192,6 +209,10 @@ export function checkSearchResultItem(value, what, fail) {
192
209
  if (record.siblingCount !== undefined) {
193
210
  requireIntegerAtLeast(record.siblingCount, `${what}.siblingCount`, fail, 1);
194
211
  }
212
+ if (record.variants !== undefined) {
213
+ const variants = requireArray(record.variants, `${what}.variants`, fail);
214
+ variants.forEach((variant, index) => checkCollapsedVariant(variant, `${what}.variants[${index}]`, fail));
215
+ }
195
216
  if (record.visualMeanings !== undefined) {
196
217
  const meanings = requireArray(record.visualMeanings, `${what}.visualMeanings`, fail);
197
218
  meanings.forEach((meaning, index) => checkVisualMeaning(meaning, `${what}.visualMeanings[${index}]`, fail));
package/spec-types.d.ts CHANGED
@@ -70,10 +70,16 @@ export interface SpecTermFilter {
70
70
  * {@link CERTIFICATION_PROGRAMME_ATTRIBUTE_ID} (a programme is matched by name,
71
71
  * never by bare presence).
72
72
  */
73
- export interface SpecPresenceFilter {
73
+ export type SpecPresenceFilter = {
74
74
  attributeId: string;
75
75
  hasValue: true;
76
76
  }
77
+ /** Date-sensitive presence is currently executable only for EPD expiration. */
78
+ | {
79
+ attributeId: "epd_expiration_date";
80
+ hasValue: true;
81
+ asOf: string;
82
+ };
77
83
  /**
78
84
  * A certification-programme criterion — a {@link SpecTermFilter} pinned to the
79
85
  * virtual {@link CERTIFICATION_PROGRAMME_ATTRIBUTE_ID}. Structurally assignable
package/types.d.ts CHANGED
@@ -30,8 +30,9 @@ import type { CorpusScopeReceipt } from "./corpus-scope-types.js";
30
30
  import type { Cursor, ProductId, VariantId } from "./identifiers.js";
31
31
  import type { MaterialDetail, MaterialTile } from "./material-types.js";
32
32
  import type { QueryUnderstanding } from "./query-understanding-types.js";
33
+ import type { CollapsedVariant, SearchAnatomyMember } from "./search-collapse-types.js";
33
34
  import type { SearchDisambiguation, SearchSuggestion } from "./search-refinement-types.js";
34
- import type { CompoundColourConstraintReceipt, SearchExecutionPlan, SearchMode, SearchRerankReport } from "./search-response-receipt-types.js";
35
+ import type { CompoundColourConstraintReceipt, CompoundFormConstraintReceipt, SearchExecutionPlan, SearchMode, SearchRerankReport } from "./search-response-receipt-types.js";
35
36
  import type { SearchWindowReceipt } from "./search-window-types.js";
36
37
  export type * from "./search-response-receipt-types.js";
37
38
  export type OpenEnum<T extends string> = T | (string & {});
@@ -107,15 +108,6 @@ export interface VisualMeaning {
107
108
  version?: string;
108
109
  appliedAt?: string;
109
110
  }
110
- /** One member of a collapsed result's {@link SearchResultItem.anatomy}. */
111
- export interface SearchAnatomyMember {
112
- /** Opaque — round-trip verbatim. */
113
- variantId: VariantId;
114
- variantName: string | null;
115
- color: string | null;
116
- score: number;
117
- signals: SearchResultSignals;
118
- }
119
111
  /**
120
112
  * One material in a search / match result list.
121
113
  *
@@ -248,7 +240,10 @@ export interface SearchResultItem {
248
240
  * `includeBelowFloor`.
249
241
  */
250
242
  admittedOnColour?: boolean;
243
+ /** Variants HIDDEN behind this card by `collapse: "product"` — the COMPLETE tally, even when {@link variants} is capped. */
251
244
  siblingCount?: number;
245
+ /** The product's matching variants, MOST RELEVANT FIRST — present exactly under `collapse: "product"`, capped, and led by the group's RANKING representative, which is NOT always this item. {@link CollapsedVariant} states the full contract. */
246
+ variants?: CollapsedVariant[];
252
247
  visualMeanings?: VisualMeaning[];
253
248
  /**
254
249
  * The shared report card for this hit against the request's `filters` (required
@@ -410,13 +405,12 @@ export interface SearchResponse {
410
405
  suggestions: SearchSuggestion[];
411
406
  mode: SearchMode;
412
407
  maxScore: number;
413
- /** Opaque keyset for the next page. Absent = end of the walk, not an error. */
414
408
  cursor?: Cursor;
415
- /** Boundedness and retrieval-cost receipt on current Search responses. */
416
409
  window?: SearchWindowReceipt;
417
410
  rerank?: SearchRerankReport;
418
- /** Hard compound-colour eligibility and abundance receipt, when applied. */
411
+ /** Hard compound eligibility and abundance receipts, when applied. */
419
412
  colourConstraint?: CompoundColourConstraintReceipt;
413
+ formConstraint?: CompoundFormConstraintReceipt;
420
414
  /** Explain-only receipt of the query shape and transform ledger that ran. */
421
415
  plan?: SearchExecutionPlan;
422
416
  counts?: SearchCounts;
@@ -443,8 +437,9 @@ export interface SearchResponse {
443
437
  /** A detected mood term overridden by a stronger colour reading. */
444
438
  supersededMoodId?: string;
445
439
  /**
446
- * Present when the query had TWO defensible readings. Says which one ran and
447
- * offers the other as a complete executable request.
440
+ * Present when the query had multiple defensible readings. Says which one
441
+ * ran, or leaves every reading non-default when the engine chose none. Every
442
+ * reading carries a complete executable replacement request.
448
443
  */
449
444
  disambiguation?: SearchDisambiguation;
450
445
  /** Structured receipt of the standard query-understanding decisions. */