@instruments/materialgraph 0.24.0 → 0.25.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.
@@ -77,6 +77,13 @@ export interface CandidateReadReceipt {
77
77
  dedupe: {
78
78
  retrieved: number;
79
79
  removed: number;
80
+ /**
81
+ * How many of `removed` went because an earlier row showed the SAME
82
+ * PICTURE, counted apart from the id rules (MG-1917). Distinct products
83
+ * routinely share one photograph, so an id-only collapse returned the same
84
+ * image several times on one page.
85
+ */
86
+ removedBySharedImage: number;
80
87
  truncated: number;
81
88
  projectionMisses: number;
82
89
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materialgraph",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -17,7 +17,7 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
- "@instruments/colorscope": "7.6.0"
20
+ "@instruments/colorscope": "9.0.1"
21
21
  },
22
22
  "license": "Proprietary",
23
23
  "engines": {
@@ -1,3 +1,5 @@
1
+ import type { MatchTarget } from "@instruments/colorscope/match";
2
+ import type { ColorResolution } from "@instruments/colorscope/naming";
1
3
  import type { SearchFilter } from "./search-refinement-types.js";
2
4
  import type { OpenEnum } from "./types.js";
3
5
  export type QuerySenseKind = OpenEnum<"brand" | "product" | "paint_identity" | "category" | "material" | "colour" | "attribute" | "attribute_value" | "certification">;
@@ -34,6 +36,8 @@ export interface CanonicalLexicalAuthorityReceipt {
34
36
  digest?: string;
35
37
  }
36
38
  export interface CanonicalLexicalReading {
39
+ matchTarget?: MatchTarget;
40
+ colorResolution?: ColorResolution;
37
41
  authority: OpenEnum<"taxonomy" | "colorscope">;
38
42
  identity: string;
39
43
  outcome: OpenEnum<"equivalent" | "ambiguous" | "declined">;
@@ -181,7 +185,7 @@ export interface QueryAmbiguityReceipt {
181
185
  }
182
186
  export interface QueryCategoryScopeReceipt {
183
187
  source: OpenEnum<"caller" | "anchor">;
184
- mechanism: OpenEnum<"sql-predicate" | "allowlist" | "prune">;
188
+ mechanism: OpenEnum<"sql-predicate" | "provider-filter" | "allowlist" | "prune">;
185
189
  variantCount: number | null;
186
190
  ceiling: number;
187
191
  specScanBounded?: boolean;
@@ -1,7 +1,9 @@
1
+ import type { MatchTarget } from "@instruments/colorscope/match";
1
2
  /**
2
3
  * Colour-name resolution (GET `/api/v1/search/colours/resolve`): the resolved
3
4
  * colour, and whether the named paint is a variant in the catalogue.
4
5
  */
6
+ import type { ColorResolution } from "@instruments/colorscope/naming";
5
7
  import type { CorpusScope } from "./corpus-scope-types.js";
6
8
  import type { VariantId } from "./identifiers.js";
7
9
  import type { OpenEnum } from "./types.js";
@@ -49,7 +51,21 @@ export type ResolveColourVariant = ({
49
51
  reason: OpenEnum<"reference-colour" | "descriptive-colour" | "no-paint-identity" | "generic-colour" | "several-paints">;
50
52
  };
51
53
  export interface ResolveColourResponse {
54
+ /** Examples within the governed range. They do not replace its representative or bounds. */
55
+ rangeSamples?: string[];
56
+ /** Explicit manufacturer choices for an ambiguous paint name. Never selected automatically. */
57
+ alternatives?: Array<{
58
+ query: string;
59
+ name: string;
60
+ hex: string;
61
+ libraryId: string;
62
+ libraryName: string;
63
+ code?: string;
64
+ }>;
65
+ /** Generic meaning; maker-specific paint identity is carried by match. */
66
+ genericResolution: ColorResolution;
52
67
  match: {
68
+ matchTarget: MatchTarget;
53
69
  hex: string;
54
70
  name: string;
55
71
  code?: string;
@@ -11,7 +11,8 @@
11
11
  import type { ProductId, VariantId } from "./identifiers.js";
12
12
  import type { OpenEnum, SearchData, SearchResponse } from "./types.js";
13
13
  /**
14
- * The lean `view:"card"` result item — identity + name + thumbnail + score.
14
+ * The lean `view:"card"` result item — identity + name + code + thumbnail +
15
+ * score.
15
16
  *
16
17
  * A PROJECTION, not a smaller answer: a field the card omits (palette, verdict,
17
18
  * signals, availability) is unread here, never known-absent. Re-read the same
@@ -30,6 +31,14 @@ export interface SearchCardItem {
30
31
  name: string;
31
32
  variantName: string | null;
32
33
  imageUrl: string | null;
34
+ /**
35
+ * The manufacturer's own item code, the same key the full item carries.
36
+ * Reach for it when two cards read alike: `name` is the PRODUCT name, and a
37
+ * paint product is a whole collection, so brand, product name and variant
38
+ * name together are shared by most of the catalogue. ABSENT means no code is
39
+ * recorded, never that the variant cannot be ordered.
40
+ */
41
+ manufacturerCode?: string;
33
42
  projectSaveCapability?: "allowed";
34
43
  sourceStatus?: "provisional";
35
44
  score: number;
@@ -122,7 +122,7 @@ export interface SearchInput {
122
122
  * demotes a failed/missing preferred hit WITHIN its page only.
123
123
  */
124
124
  preferences?: SearchFilter[];
125
- /** Page size — the size of ONE page, never a bound on the match set. */
125
+ /** Results per page (default 60, max 60), never a bound on the match set. */
126
126
  limit?: number;
127
127
  /** The previous response's `cursor`, passed back verbatim. Never synthesise one. */
128
128
  cursor?: Cursor;
@@ -24,16 +24,42 @@ export interface SearchRerankReport {
24
24
  colourWeight?: number;
25
25
  colourLabel?: string;
26
26
  }
27
- /** Stage counts retained when an explain-enabled search returns no results. */
27
+ /**
28
+ * A part of the search that can fail without stopping it.
29
+ *
30
+ * Coarse on purpose: a caller needs to know that something did not answer, not
31
+ * which provider it was. OPEN, and it will grow — read it with a default
32
+ * branch.
33
+ */
34
+ export type SearchDegradedPart = OpenEnum<"filter-coverage" | "term-coverage">;
35
+ /**
36
+ * Which parts of the search did not answer, on EVERY response (MG-1913).
37
+ *
38
+ * `parts: []` says no part reported a failure. It does NOT promise every check
39
+ * ran: a check the request never needed is skipped rather than failed. ABSENT
40
+ * means the response came from a server that does not report this, never that
41
+ * nothing was degraded.
42
+ */
43
+ export interface SearchDegraded {
44
+ parts: SearchDegradedPart[];
45
+ }
46
+ /** Stage counts retained when a search returns no results. */
28
47
  export interface SearchEmptyOutcomeCounts {
29
48
  retrieved: number;
30
49
  afterPrune: number;
31
50
  final: number;
32
51
  }
33
52
  /**
34
- * Explain-only evidence for why a completed search returned no results.
35
- * `proven-corpus-empty` is exhaustive; every other empty remains explicitly
36
- * unproven rather than being presented as catalogue absence.
53
+ * Why a search returned no results, on EVERY empty response (MG-1911).
54
+ *
55
+ * `proven-corpus-empty` is exhaustive and is the only member that supports
56
+ * telling someone the catalogue holds nothing. `unproven-empty` is an honest
57
+ * end to a search that ran without establishing that. `search-degraded` means
58
+ * part of the search did not answer, so the empty page describes the runtime
59
+ * rather than the catalogue, and the right response is to retry.
60
+ *
61
+ * Read it by `kind` with a default branch: this union has grown once and the
62
+ * reason and basis enums are open.
37
63
  */
38
64
  export type SearchEmptyOutcome = {
39
65
  kind: "proven-corpus-empty";
@@ -46,6 +72,12 @@ export type SearchEmptyOutcome = {
46
72
  reason: OpenEnum<"allowlist-truncated" | "retrieval-window-not-exhaustive" | "source-empty" | "post-retrieval-rejected" | "pagination-empty" | "no-authoritative-empty-proof">;
47
73
  sourceEmpty?: boolean | null;
48
74
  counts?: SearchEmptyOutcomeCounts;
75
+ } | {
76
+ kind: "search-degraded";
77
+ /** Which parts did not answer. Coarse: never a provider's name. */
78
+ parts: SearchDegradedPart[];
79
+ sourceEmpty?: boolean | null;
80
+ counts?: SearchEmptyOutcomeCounts;
49
81
  };
50
82
  /** Explain-only coverage of substantive query terms in the lexical projection. */
51
83
  export interface SearchTermCoverageReceipt {
@@ -53,10 +85,63 @@ export interface SearchTermCoverageReceipt {
53
85
  terms: string[];
54
86
  note?: string;
55
87
  }
56
- /** Explain-only outcome receipts shared by full and card search responses. */
88
+ /** The Search workflow run a ledger receipt belongs to. */
89
+ export interface SearchRunRef {
90
+ contextId: string;
91
+ runId: string;
92
+ schemaVersion: 1;
93
+ }
94
+ /** One artifact a Search workflow step read or wrote. */
95
+ export interface SearchRunArtifactRef {
96
+ artifactId: string;
97
+ kind: string;
98
+ producedBy: string;
99
+ runRef: SearchRunRef;
100
+ schemaVersion: 1;
101
+ }
102
+ /** What one Search workflow step did: its reads, its write and its time. */
103
+ export interface SearchStageReceipt {
104
+ stageId: string;
105
+ status: "succeeded";
106
+ /** The step's own work, excluding reading and writing its artifacts. */
107
+ durationMs: number;
108
+ inputArtifacts: SearchRunArtifactRef[];
109
+ outputArtifact: SearchRunArtifactRef;
110
+ inputCount: number | null;
111
+ outputCount: number | null;
112
+ runRef: SearchRunRef;
113
+ /** The outcome the step chose, when it chooses one. */
114
+ outcome?: string;
115
+ /** The arm a fork took and why. */
116
+ branch?: {
117
+ decision: string;
118
+ reason: string;
119
+ };
120
+ }
121
+ /** One ledger line: a step's receipt and its span since the run opened. */
122
+ export interface SearchRunLedgerEntry {
123
+ receipt: SearchStageReceipt;
124
+ /** The nested workflows the step ran under, outermost first. */
125
+ workflowPath: string[];
126
+ startedAtMs: number;
127
+ endedAtMs: number;
128
+ }
129
+ /**
130
+ * Outcome receipts shared by full and card search responses. `degraded` and
131
+ * `emptyOutcome` are on every response; `termCoverage` and `explain` are
132
+ * explain-only.
133
+ */
57
134
  export interface SearchResponseExplainReceipts {
135
+ degraded?: SearchDegraded;
58
136
  emptyOutcome?: SearchEmptyOutcome;
59
137
  termCoverage?: SearchTermCoverageReceipt;
138
+ /**
139
+ * Present under `explain` when the Search workflow answered the request:
140
+ * every step receipt, in completion order. Absent when the engine answered.
141
+ */
142
+ explain?: {
143
+ ledger: readonly SearchRunLedgerEntry[];
144
+ };
60
145
  }
61
146
  /** One stage in the explain-only ledger of transformations that actually ran. */
62
147
  export type SearchQueryTransformStage = {