@instruments/materialgraph 0.9.1 → 0.11.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 (64) hide show
  1. package/brands-client.d.ts +48 -0
  2. package/brands-client.js +45 -0
  3. package/browse-types.d.ts +147 -0
  4. package/browse-types.js +1 -0
  5. package/by-colour-types.d.ts +105 -5
  6. package/client-browse.d.ts +13 -3
  7. package/client-browse.js +12 -2
  8. package/client-exploration.d.ts +9 -0
  9. package/client-exploration.js +7 -0
  10. package/client-source-material-comments.d.ts +51 -0
  11. package/client-source-material-comments.js +40 -0
  12. package/client.d.ts +64 -71
  13. package/client.js +90 -78
  14. package/colour-aggregate-types.d.ts +58 -0
  15. package/colours-client.d.ts +8 -2
  16. package/colours-client.js +15 -2
  17. package/composition-client.d.ts +33 -0
  18. package/composition-client.js +27 -0
  19. package/composition-types.d.ts +46 -0
  20. package/coordinate-client.d.ts +23 -0
  21. package/coordinate-client.js +25 -0
  22. package/errors.js +24 -8
  23. package/exploration-types.d.ts +110 -0
  24. package/exploration-types.js +1 -0
  25. package/fan-deck-types.d.ts +42 -0
  26. package/fan-deck-types.js +9 -0
  27. package/http-transport.js +24 -5
  28. package/index.d.ts +7 -3
  29. package/index.js +2 -2
  30. package/match-stream-types.d.ts +1 -1
  31. package/match-stream-types.js +16 -3
  32. package/match-stream.js +14 -4
  33. package/material-types.d.ts +1 -1
  34. package/operations.d.ts +1 -1
  35. package/operations.js +187 -26
  36. package/package.json +1 -1
  37. package/palette-search-types.d.ts +31 -0
  38. package/resolve-client.d.ts +22 -3
  39. package/resolve-client.js +31 -4
  40. package/resolve-types.d.ts +70 -1
  41. package/run-events.d.ts +1 -1
  42. package/run-events.js +4 -1
  43. package/run-types.d.ts +132 -0
  44. package/runs.d.ts +35 -1
  45. package/runs.js +69 -5
  46. package/schedule-types.d.ts +128 -0
  47. package/schema-resource-types.d.ts +1 -1
  48. package/search-refinement-types.d.ts +39 -4
  49. package/search-request-types.d.ts +172 -0
  50. package/search-request-types.js +1 -0
  51. package/search-result-validation.js +21 -4
  52. package/slot-pool-types.d.ts +370 -0
  53. package/slot-pool-types.js +1 -0
  54. package/source-material-validation.d.ts +5 -1
  55. package/source-material-validation.js +114 -5
  56. package/spec-facet-types.d.ts +53 -0
  57. package/spec-facet-types.js +7 -0
  58. package/specifications.d.ts +26 -0
  59. package/specifications.js +32 -0
  60. package/sse.js +3 -1
  61. package/suggest-types.js +3 -1
  62. package/types.d.ts +176 -216
  63. package/validation.d.ts +12 -0
  64. package/validation.js +23 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @module search-request-types
3
+ * The two request shapes for the `/search` surface — the POST JSON body and the
4
+ * GET query-string form. Split out of `types.ts` so neither file outgrows the
5
+ * repo's source-size gate; every name here is re-exported from `types.ts`, so
6
+ * no import path changes.
7
+ */
8
+ import type { Cursor, VariantId } from "./identifiers.js";
9
+ import type { SearchFilter } from "./search-refinement-types.js";
10
+ import type { ColourInput, OpenEnum, SearchImageInput } from "./types.js";
11
+ /**
12
+ * The POST `/search` JSON body — the `searchMaterials` contract. Every field is
13
+ * optional; supply at least one signal (`text`, `colours`, `mood`, `similarTo`,
14
+ * or `image`). Passed through to the server verbatim.
15
+ */
16
+ export interface SearchInput {
17
+ text?: string;
18
+ textModes?: Array<OpenEnum<"visual" | "lexical">>;
19
+ colours?: ColourInput[];
20
+ colourLogic?: OpenEnum<"all" | "any">;
21
+ colourTolerance?: OpenEnum<"tight" | "normal" | "loose">;
22
+ colourAppearance?: {
23
+ lightness?: {
24
+ min: number;
25
+ max: number;
26
+ };
27
+ warmth?: {
28
+ min: number;
29
+ max: number;
30
+ };
31
+ };
32
+ mood?: string;
33
+ sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
34
+ /**
35
+ * Seed a visual search from an existing variant's image (`variantId`), from a
36
+ * Material Bank SKU (`materialBankId`), or from a pre-computed image vector
37
+ * (`vector`).
38
+ *
39
+ * `materialBankId` exists so a caller already holding a SKU does not have to
40
+ * ask us to translate it before it can ask us anything useful — one call, not
41
+ * two. It resolves to the variant server-side and then behaves identically to
42
+ * the `{ variantId }` form, seed verdicts included. A SKU that names no
43
+ * variant is a 400 carrying the offending value: never an empty result set,
44
+ * never a silent fall back to unseeded search.
45
+ *
46
+ * The `{ vector }` form carries no seed variant, so it is the only one
47
+ * `verdicts` cannot diff a candidate against.
48
+ */
49
+ similarTo?: {
50
+ variantId: VariantId;
51
+ } | {
52
+ materialBankId: string;
53
+ } | {
54
+ vector: string;
55
+ };
56
+ /**
57
+ * Canonical variants that must not appear in the returned page. The server
58
+ * applies these exclusions before the page limit, so consumers can request
59
+ * unseen results without shortening an already-bounded page client-side.
60
+ * Maximum 256; ids are opaque.
61
+ */
62
+ excludeVariantIds?: VariantId[];
63
+ image?: SearchImageInput;
64
+ category?: {
65
+ boost?: string;
66
+ filter?: string[];
67
+ };
68
+ weights?: {
69
+ image?: number;
70
+ category?: number;
71
+ colour?: number;
72
+ lexical?: number;
73
+ /** Text rerank multiplier applied when a seeded search also carries `text` (default 0.4). */
74
+ text?: number;
75
+ };
76
+ filters?: SearchFilter[];
77
+ /**
78
+ * SOFT spec criteria (rank-never-exclude). Same shape as `filters`; a preference
79
+ * never removes a result — it adds a `preferred` cell to each hit's `verdict` and
80
+ * demotes a failed/missing preferred hit WITHIN its page only.
81
+ */
82
+ preferences?: SearchFilter[];
83
+ /** Page size — the size of ONE page, never a bound on the match set. */
84
+ limit?: number;
85
+ /** The previous response's `cursor`, passed back verbatim. Never synthesise one. */
86
+ cursor?: Cursor;
87
+ explain?: boolean;
88
+ collapse?: OpenEnum<"none" | "product">;
89
+ /**
90
+ * Opt-in feed total: EXACT up to 1,000, `lowerBound` beyond / for ANN pools.
91
+ * Without it the response states NO total — and a page size is not one.
92
+ */
93
+ count?: boolean;
94
+ /**
95
+ * Opt-in per-query facet counts (MG-1010): up to 10 facet ids — `"brand"`,
96
+ * `"category"`, or spec attribute ids — and the response carries a `facets`
97
+ * block counting each facet's values WITHIN this query's match set. An
98
+ * unknown id is a 400 naming it; a known but uncountable id lands in
99
+ * `facets.absent`. Absent = no `facets` in the response, no counting cost.
100
+ */
101
+ facetCounts?: string[];
102
+ /** Only return materials with at least one catalog image. Default false (full catalog). */
103
+ hasImages?: boolean;
104
+ /**
105
+ * Opt-in seed-relative verdict cells (MG-629). Only meaningful with
106
+ * `similarTo: { variantId }`: each hit's `verdict` gains `preferred` cells
107
+ * diffing its comparable attributes (numeric + a `colour` axis) against the
108
+ * seed's, with directional deviations. Inert on every other seed form.
109
+ */
110
+ verdicts?: boolean;
111
+ /** Result projection — `"full"` (default) or the lean `"card"` grid form. */
112
+ view?: OpenEnum<"full" | "card">;
113
+ /**
114
+ * Budget profile for the search primitive.
115
+ *
116
+ * `"search"` (default) is the full engine. `"typeahead"` is the as-you-type
117
+ * profile: lexical-only with prefix match on the final token, forced
118
+ * `view: "card"`, `limit` capped at 24, and no mood/colour detection tiers,
119
+ * semantic fallback, category boost, colour rerank, refinement suggestions,
120
+ * counts, or meanings. `textModes` is ignored under typeahead.
121
+ */
122
+ intent?: OpenEnum<"search" | "typeahead">;
123
+ /**
124
+ * Which corpus to answer from.
125
+ *
126
+ * **ABSENT means the WHOLE GRAPH** — every material MaterialGraph knows,
127
+ * orderable or not. Absence is never a narrower default, so a caller that
128
+ * says nothing gets materials it may not be able to buy.
129
+ * `"materialBankOrderable"` restricts retrieval to materials carrying a
130
+ * Material Bank identity. `"designShopOrderable"` restricts it to what a
131
+ * consumer can buy on DesignShop today — about 16,200 materials, with the
132
+ * products DesignShop hides from browse excluded.
133
+ *
134
+ * A scope answers what is BUYABLE. It never answers what is SUITABLE: scope
135
+ * on channel, then refine on sector. A trade-only material can be right for a
136
+ * room and still unbuyable, and most of what a consumer can buy is
137
+ * commercial-suited product.
138
+ *
139
+ * The scope is applied INSIDE the query: `limit` fills with eligible rows,
140
+ * `counts` describe the SCOPED corpus, and the response echoes the scope.
141
+ * That is what post-filtering a page cannot give you — a client-side filter
142
+ * shortens the page exactly when the query drifts toward the corpus beyond
143
+ * the Material Bank feed, which is unpredictable from outside.
144
+ */
145
+ corpusScope?: OpenEnum<"all" | "materialBankOrderable" | "designShopOrderable">;
146
+ /**
147
+ * OPS/AUDIT escape hatch — do NOT use for normal search. Default behaviour
148
+ * (absent or false) excludes sampling-artifact products: multi-swatch sample
149
+ * cards, which are orderable samples and never specifiable materials.
150
+ */
151
+ includeSamplingArtifacts?: boolean;
152
+ }
153
+ /** Query-string params for the GET `/search` form. */
154
+ export interface SearchGetParams {
155
+ q?: string;
156
+ colours?: string;
157
+ logic?: OpenEnum<"all" | "any">;
158
+ mood?: string;
159
+ sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
160
+ tol?: OpenEnum<"tight" | "normal" | "loose">;
161
+ /** Seed the search from this variant's image. Opaque — pass it through verbatim. */
162
+ variantId?: VariantId;
163
+ limit?: number;
164
+ /** "0" or "1" — include per-signal `signals` on each result. */
165
+ explain?: OpenEnum<"0" | "1">;
166
+ /** "true" — opt in to a `counts.variants` feed total. */
167
+ count?: OpenEnum<"true">;
168
+ /** "true" — only return materials with at least one catalog image. */
169
+ hasImages?: OpenEnum<"true">;
170
+ /** Result projection — `"full"` (default) or the lean `"card"` grid form. */
171
+ view?: OpenEnum<"full" | "card">;
172
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -12,17 +12,33 @@ export const VERDICT_STATES = [
12
12
  /** `attributeVerdictSchema.strength`. */
13
13
  export const VERDICT_STRENGTHS = ["required", "preferred"];
14
14
  /** `verdictBaselineSchema` discriminant — the four arms of what a cell was judged against. */
15
- export const VERDICT_BASELINE_KINDS = ["term", "presence", "seed", "image"];
15
+ export const VERDICT_BASELINE_KINDS = [
16
+ "term",
17
+ "presence",
18
+ "seed",
19
+ "image",
20
+ ];
16
21
  /** The `image` baseline arm's own `source` enum. */
17
22
  export const VERDICT_IMAGE_SOURCES = ["variant", "vector"];
18
23
  /** `verdictDeviationSchema.direction`. */
19
- export const VERDICT_DEVIATION_DIRECTIONS = ["above", "below", "other"];
24
+ export const VERDICT_DEVIATION_DIRECTIONS = [
25
+ "above",
26
+ "below",
27
+ "other",
28
+ ];
20
29
  /** `verdictTrustGapSchema` — why a real value did not clear the trust bar. */
21
- export const VERDICT_TRUST_GAPS = ["below_confidence", "basis_unmet", "proposed"];
30
+ export const VERDICT_TRUST_GAPS = [
31
+ "below_confidence",
32
+ "basis_unmet",
33
+ "proposed",
34
+ ];
22
35
  /** `verdictGradeSchema` — the rolled-up card grade. */
23
36
  export const VERDICT_GRADES = ["meets_all", "close", "hard_no"];
24
37
  /** `visualMeaningSchema.basis` — where a meaning came from. */
25
- export const VISUAL_MEANING_BASES = ["canonical_attribute", "image_colour_analysis"];
38
+ export const VISUAL_MEANING_BASES = [
39
+ "canonical_attribute",
40
+ "image_colour_analysis",
41
+ ];
26
42
  // ── Leaf checks ──────────────────────────────────────────────────────────────
27
43
  /** `palette[]` — `{ hex, proportion, name: string.nullable().optional() }`. */
28
44
  function checkPaletteSwatch(value, what, fail) {
@@ -165,6 +181,7 @@ export function checkSearchResultItem(value, what, fail) {
165
181
  checkSignals(record.signals, `${what}.signals`, fail);
166
182
  requireOptionalBoolean(record.retired, `${what}.retired`, fail);
167
183
  requireOptionalBoolean(record.belowFloor, `${what}.belowFloor`, fail);
184
+ requireOptionalBoolean(record.admittedOnColour, `${what}.admittedOnColour`, fail);
168
185
  if (record.siblingCount !== undefined) {
169
186
  requireIntegerAtLeast(record.siblingCount, `${what}.siblingCount`, fail, 1);
170
187
  }
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Hand-mirrored wire types for the slot-pool surface
3
+ * (`POST /api/v1/schemes/pools`, operation `slot_pools`, MG-1301). Like the rest
4
+ * of the SDK these interfaces carry NO runtime dependency on any
5
+ * `@materialgraph/*` package — they mirror the `SlotPoolsRequest` /
6
+ * `SlotPoolsResponse` shapes in `apps/web/public/openapi.yaml`.
7
+ *
8
+ * THE QUESTION IT ANSWERS: "for each of these slots, what should I be choosing
9
+ * between?" One call, up to 24 slots, one ranked and gated pool each. There is
10
+ * no project and no run — the request is self-contained and the ownership of the
11
+ * work stays with you.
12
+ *
13
+ * THREE THINGS TO READ CORRECTLY, because getting them wrong turns a careful
14
+ * answer into a false one:
15
+ * 1. A pool with a `shortfall` of kind `search-failed` was NOT answered. The
16
+ * other pools in the same response were. Never read that empty pool as
17
+ * "nothing matched".
18
+ * 2. `not_evaluated` and `missing` are not `failed`. The first means the engine
19
+ * did not check the criterion at the tier that ran; the second means the
20
+ * material holds no accepted value for it. Only `failed` means a value
21
+ * exists and does not satisfy the ask.
22
+ * 3. `eligible: false` does not mean "bad". It means this candidate cannot be
23
+ * placed automatically — read `flags` for which of the reasons applies.
24
+ *
25
+ * TOLERANT-PARSING CONTRACT: response enums are widened with {@link OpenEnum}
26
+ * so a new server value still type-checks; objects are read structurally.
27
+ */
28
+ import type { VariantId } from "./identifiers.js";
29
+ import type { OpenEnum } from "./types.js";
30
+ /**
31
+ * One requirement or preference. Request-side, so this is a STRICT union.
32
+ * `attribute` asks for a canonical term, `attribute-presence` merely that the
33
+ * attribute is recorded, `attribute-numeric` a one-sided bound. `unresolved`
34
+ * carries brief language nothing bound to — it is never a filter and never
35
+ * blamed for a coverage shortfall.
36
+ */
37
+ export type SlotPoolCriterion = {
38
+ kind: "attribute";
39
+ attributeId: string;
40
+ term: string;
41
+ raw: string;
42
+ } | {
43
+ kind: "attribute-presence";
44
+ attributeId: string;
45
+ raw: string;
46
+ } | {
47
+ kind: "attribute-numeric";
48
+ attributeId: string;
49
+ comparator: "at-least" | "at-most";
50
+ value: number;
51
+ unit?: string;
52
+ raw: string;
53
+ } | {
54
+ kind: "unresolved";
55
+ raw: string;
56
+ suggestion?: {
57
+ attributeId: string;
58
+ term?: string;
59
+ };
60
+ coverageNote?: string;
61
+ };
62
+ /** A colour cue for a slot. Colours rerank within the retrieved set; they never admit. */
63
+ export interface SlotPoolColourCue {
64
+ /** `#rrggbb`. */
65
+ hex: string;
66
+ name?: string;
67
+ }
68
+ /**
69
+ * What to do with a candidate the catalogue no longer lists. Neither value ever
70
+ * marks such a candidate eligible — the policy decides only whether it is
71
+ * returned at all.
72
+ */
73
+ export type SlotPoolAvailabilityPolicy = "flag-unavailable" | "exclude-unavailable";
74
+ /** One slot of work. Self-contained: nothing in it identifies a project. */
75
+ export interface SlotPoolTask {
76
+ /**
77
+ * YOUR key for this task, opaque to MaterialGraph and returned verbatim on the
78
+ * matching pool. This is how you correlate 24 results; it is never parsed,
79
+ * normalised or reused as a server id. Must be unique within a request.
80
+ */
81
+ taskKey: string;
82
+ /**
83
+ * The catalog category. OMITTING IT yields an empty pool with a `no-category`
84
+ * shortfall — category is the only cross-kind gate, and retrieval will not
85
+ * rank an ungated corpus.
86
+ */
87
+ category?: string;
88
+ /** A short VISUAL description of the material wanted — not a keyword list. */
89
+ intent?: string;
90
+ required?: SlotPoolCriterion[];
91
+ preferred?: SlotPoolCriterion[];
92
+ colours?: SlotPoolColourCue[];
93
+ /** Rank this task around an already-chosen variant. Ids are opaque tokens. */
94
+ anchorSeed?: {
95
+ variantId: VariantId;
96
+ };
97
+ exclude?: {
98
+ variantIds?: VariantId[];
99
+ brandSlugs?: string[];
100
+ };
101
+ availability?: SlotPoolAvailabilityPolicy;
102
+ /** Candidates returned for this task. Max 48, default 12. */
103
+ limit?: number;
104
+ }
105
+ /** Body for `POST /api/v1/schemes/pools` ({@link MaterialGraphClient.slotPools}). */
106
+ export interface SlotPoolsRequest {
107
+ /** Up to 24 tasks. Each is independent: one failing costs that one. */
108
+ tasks: SlotPoolTask[];
109
+ /**
110
+ * Include the co-specification evidence among the returned candidates
111
+ * (default true). One bounded read — never one request per candidate.
112
+ */
113
+ coSpecification?: boolean;
114
+ }
115
+ /** The six live verdict states, plus the two legacy tokens older rows carry. */
116
+ export type SlotPoolVerdictState = OpenEnum<"matched" | "deviated" | "failed" | "unverified" | "missing" | "not_evaluated">;
117
+ /** One cell of a candidate's report card against one criterion. */
118
+ export interface SlotPoolVerdictCell {
119
+ attributeId: string;
120
+ state: SlotPoolVerdictState;
121
+ strength: OpenEnum<"required" | "preferred">;
122
+ baseline: {
123
+ kind: "term";
124
+ term: string;
125
+ } | {
126
+ kind: "presence";
127
+ } | {
128
+ kind: "seed";
129
+ variantId: VariantId;
130
+ } | {
131
+ kind: "image";
132
+ source: OpenEnum<"variant" | "vector">;
133
+ variantId: VariantId | null;
134
+ };
135
+ /** The candidate's OWN accepted value, or null when it holds none. */
136
+ value: string | null;
137
+ deviation: {
138
+ direction: OpenEnum<"above" | "below" | "other">;
139
+ delta: number | null;
140
+ unit: string | null;
141
+ label: string;
142
+ } | null;
143
+ confidence: number | null;
144
+ /**
145
+ * WHERE the value came from — it ranks the SOURCE, not the accuracy. It says
146
+ * nothing about whether the value is right.
147
+ */
148
+ sourceBasis: string | null;
149
+ trustGap: OpenEnum<"below_confidence" | "basis_unmet" | "proposed"> | null;
150
+ }
151
+ /** Why a candidate is labelled. An eligible candidate carries none of these. */
152
+ export type SlotPoolFlag = OpenEnum<"below-floor" | "required-not-applied" | "discontinued" | "superseded" | "unverified-value">;
153
+ export type SlotPoolAvailability = OpenEnum<"available" | "discontinued" | "superseded" | "unknown">;
154
+ /** The declared successor of a withdrawn material, one hop only. */
155
+ export interface SlotPoolSuccessor {
156
+ variantId: VariantId;
157
+ brandSlug: string;
158
+ productId: string;
159
+ brand: string;
160
+ product: string;
161
+ variant: string;
162
+ color: string | null;
163
+ availability: SlotPoolAvailability;
164
+ }
165
+ /**
166
+ * The features a composer scores on. Colours are EXTRACTED FROM PRODUCT IMAGERY
167
+ * — visual likeness, never a declared colour specification. An empty `palette`
168
+ * or `attributes` means nothing is on record, never that the material has none.
169
+ */
170
+ export interface SlotPoolCandidateFeatures {
171
+ colour: {
172
+ dominantHex: string | null;
173
+ hsl: {
174
+ h: number;
175
+ s: number;
176
+ l: number;
177
+ } | null;
178
+ oklab: {
179
+ l: number;
180
+ a: number;
181
+ b: number;
182
+ } | null;
183
+ chroma: number | null;
184
+ vibrancy: number | null;
185
+ sourceBasis?: string;
186
+ };
187
+ palette: {
188
+ hex: string;
189
+ proportion: number;
190
+ name?: string | null;
191
+ oklab?: {
192
+ l: number;
193
+ a: number;
194
+ b: number;
195
+ };
196
+ }[];
197
+ attributes: {
198
+ attributeId: string;
199
+ term: string;
200
+ }[];
201
+ dimensions: Record<string, unknown>[] | null;
202
+ }
203
+ export interface SlotPoolCandidate {
204
+ variantId: VariantId;
205
+ productId: string;
206
+ brandSlug: string;
207
+ brand: string;
208
+ product: string;
209
+ variant: string;
210
+ category: string | null;
211
+ materialBankId: string | null;
212
+ imageUrl: string | null;
213
+ availability: SlotPoolAvailability;
214
+ supersededBy: SlotPoolSuccessor | null;
215
+ /**
216
+ * True only when every required criterion was applied, the catalogue still
217
+ * lists the material, and no exclusion touched it. Anything else needs a
218
+ * human decision — read `flags` for which reason.
219
+ */
220
+ eligible: boolean;
221
+ flags: SlotPoolFlag[];
222
+ score: {
223
+ /** Normalised to [0,1] against the best score on THIS pool's page. */
224
+ retrieval: number;
225
+ /** `unverified` + `not_evaluated` cells. A soft penalty, not a failure count. */
226
+ unknownCount: number;
227
+ verdictCounts: {
228
+ matched: number;
229
+ deviated: number;
230
+ failed: number;
231
+ unverified: number;
232
+ missing: number;
233
+ notEvaluated: number;
234
+ };
235
+ };
236
+ verdict: {
237
+ cells: SlotPoolVerdictCell[];
238
+ grade: OpenEnum<"meets_all" | "close" | "hard_no">;
239
+ minConfidence: number | null;
240
+ };
241
+ features: SlotPoolCandidateFeatures;
242
+ }
243
+ /**
244
+ * Why a pool is short. Four different facts, and none of them is "nothing in
245
+ * the catalogue matches your brief".
246
+ */
247
+ export type SlotPoolShortfall = {
248
+ kind: "no-category";
249
+ } | {
250
+ kind: "category-coverage";
251
+ category: string;
252
+ attributeIds: string[];
253
+ gaps?: ({
254
+ level: "attribute";
255
+ attributeId: string;
256
+ } | {
257
+ level: "value";
258
+ attributeId: string;
259
+ term: string;
260
+ })[];
261
+ } | {
262
+ kind: "search-failed";
263
+ detail: string;
264
+ } | {
265
+ kind: "no-match";
266
+ tier: string;
267
+ consideredCount: number;
268
+ };
269
+ export interface SlotPool {
270
+ /** Your own key, verbatim. */
271
+ taskKey: string;
272
+ category: string | null;
273
+ /** The relaxation rung the returned candidates were drawn at. */
274
+ retrievalTier: OpenEnum<"strict" | "presence" | "category-only">;
275
+ /**
276
+ * HOW the candidates were retrieved. DERIVED from the retrieval plan and the
277
+ * pool's receipt — MaterialGraph does not report an arm per candidate.
278
+ *
279
+ * It is constant across the relaxation rungs, so it cannot tell you how far
280
+ * the ladder went. `retrievalTier` beside it names the rung the pool settled
281
+ * on: the arm says how, the tier says how strictly.
282
+ */
283
+ arm: OpenEnum<"visual-text" | "image-similarity" | "none">;
284
+ candidates: SlotPoolCandidate[];
285
+ /** How much retrieval scored before this task's own `limit` trimmed the page. */
286
+ consideredCount: number;
287
+ returnedCount: number;
288
+ excludedCount: number;
289
+ unavailableDroppedCount: number;
290
+ /**
291
+ * The criteria nothing on this page could be graded on — a statement about
292
+ * catalogue COVERAGE, never about the candidates.
293
+ */
294
+ unknownCoverage: {
295
+ evaluatedCriteria: number;
296
+ /** Every returned candidate graded `not_evaluated` on these. */
297
+ uncheckedCriteria: string[];
298
+ /** Every returned candidate graded `missing` on these. */
299
+ unheldCriteria: string[];
300
+ };
301
+ shortfall: SlotPoolShortfall | null;
302
+ seededFrom: {
303
+ variantId: VariantId;
304
+ } | null;
305
+ warnings: string[];
306
+ }
307
+ /**
308
+ * The sparse pairing graph among the returned candidates. Each unordered pair
309
+ * appears ONCE and the relation is symmetric. An empty `edges` under a null
310
+ * `datasetSessionId` means the dataset is absent, NOT that these materials are
311
+ * never specified together.
312
+ */
313
+ export interface SlotPoolCoSpecification {
314
+ edges: {
315
+ from: VariantId;
316
+ to: VariantId;
317
+ projectCount: number;
318
+ }[];
319
+ coverage: {
320
+ candidateCount: number;
321
+ coveredCount: number;
322
+ truncated: boolean;
323
+ datasetSessionId: string | null;
324
+ window: string;
325
+ signal: string;
326
+ };
327
+ }
328
+ /** The `200` body from `POST /api/v1/schemes/pools`. */
329
+ export interface SlotPoolsResponse {
330
+ versions: {
331
+ contract: string;
332
+ retrieval: string;
333
+ evaluator: string;
334
+ dataset: {
335
+ coSpecificationSessionId: string | null;
336
+ };
337
+ };
338
+ /** One pool per task, in request order. Correlate by `taskKey`, not by index. */
339
+ pools: SlotPool[];
340
+ coSpecification: SlotPoolCoSpecification | null;
341
+ summary: {
342
+ tasks: number;
343
+ eligibleCandidates: number;
344
+ flaggedCandidates: number;
345
+ tasksWithEligibleCandidates: number;
346
+ tasksWithoutEligibleCandidates: number;
347
+ /** Non-zero means some tasks were NOT answered; their pools say so. */
348
+ searchFailed: number;
349
+ noCategory: number;
350
+ categoryCoverage: number;
351
+ noMatch: number;
352
+ };
353
+ /**
354
+ * CALL VOLUME — how many of each bounded operation the request performed.
355
+ *
356
+ * Never a cost or token figure. Slot retrieval is database and embedding work
357
+ * and does not go through the model-call path that carries token and spend
358
+ * accounting, so no such figure exists for it. Do not render these as usage
359
+ * or spend.
360
+ */
361
+ calls: {
362
+ searchesRun: number;
363
+ searchesFailed: number;
364
+ candidatesConsidered: number;
365
+ candidatesReturned: number;
366
+ hydrationBatches: number;
367
+ coSpecificationQueries: number;
368
+ };
369
+ warnings: string[];
370
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,7 @@
1
- import type { SourceMaterialBatchDetail } from "./run-types.js";
1
+ import type { PostSourceMaterialCommentResult, SourceMaterialBatchDetail, SourceMaterialCommentThread } from "./run-types.js";
2
2
  /** Validate a batch-detail body, returning it typed, or throw explaining why. */
3
3
  export declare function checkSourceMaterialBatchDetail(value: unknown, batchId: string): SourceMaterialBatchDetail;
4
+ /** Validate a comment-thread body, returning it typed, or throw explaining why. */
5
+ export declare function checkSourceMaterialCommentThread(value: unknown, sourceId: string): SourceMaterialCommentThread;
6
+ /** Validate a posted-comment body, returning it typed, or throw explaining why. */
7
+ export declare function checkPostSourceMaterialCommentResult(value: unknown, sourceId: string): PostSourceMaterialCommentResult;