@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
package/run-types.d.ts CHANGED
@@ -105,6 +105,113 @@ export interface SourceImageAnalysis {
105
105
  /** ISO-8601 timestamp of the most recent analysis. */
106
106
  analysedAt: string;
107
107
  }
108
+ /** Who a comment is from, as you SUBMIT it. Not the API principal posting it. */
109
+ export interface SourceMaterialCommentAuthorInput {
110
+ /** Trimmed, 1–160 characters. Longer or blank is refused by the server. */
111
+ name: string;
112
+ /**
113
+ * Their practice or team; `null` for a note carrying no organisation.
114
+ * Trimmed, 1–160 characters when present. Nullable, not optional.
115
+ */
116
+ organisation: string | null;
117
+ }
118
+ /**
119
+ * Who a comment is from, as you READ IT BACK.
120
+ *
121
+ * `attribution` is the honesty field: nothing verified this name. Render it as
122
+ * "recorded as from X", never as a statement X signed — the identity that IS
123
+ * verified is {@link SourceMaterialCommentReply.recordedBy}.
124
+ */
125
+ export interface SourceMaterialCommentAuthor extends SourceMaterialCommentAuthorInput {
126
+ /**
127
+ * How the name got here. `reported` means a caller typed it. {@link OpenEnum}
128
+ * keeps a newer server value type-checking; treat anything unfamiliar as
129
+ * unverified.
130
+ */
131
+ attribution: OpenEnum<"reported">;
132
+ }
133
+ /** The authenticated principal that recorded a comment. */
134
+ export interface SourceMaterialCommentRecorder {
135
+ /**
136
+ * The principal that wrote the row. Comments can only be posted against a
137
+ * batch the poster owns, so on every comment you can read this is your own.
138
+ */
139
+ principalId: string;
140
+ }
141
+ /**
142
+ * One reply on a source-material file. A reply carries no replies of its own:
143
+ * threads are one level deep because the database makes a reply-to-reply
144
+ * unstorable, and this type says so rather than implying a tree.
145
+ */
146
+ export interface SourceMaterialCommentReply {
147
+ /** The stored comment id (`smc_…`). Opaque. */
148
+ commentId: string;
149
+ author: SourceMaterialCommentAuthor;
150
+ /** Who recorded it — the audit answer behind the reported name. */
151
+ recordedBy: SourceMaterialCommentRecorder;
152
+ body: string;
153
+ /** ISO-8601 timestamp of the posting. */
154
+ createdAt: string;
155
+ }
156
+ /** One root remark with the replies that answer it. */
157
+ export interface SourceMaterialComment extends SourceMaterialCommentReply {
158
+ /** ALWAYS an array. `[]` means nobody answered — never a missing field. */
159
+ replies: SourceMaterialCommentReply[];
160
+ }
161
+ /** The `200` body of `GET …/sources/{sourceId}/comments`. */
162
+ export interface SourceMaterialCommentThread {
163
+ /** The file the thread hangs off (`smf_…`). Opaque. */
164
+ sourceId: string;
165
+ /**
166
+ * The thread, oldest root first. ALWAYS an array; `[]` means the file is
167
+ * yours and nobody has commented on it.
168
+ */
169
+ comments: SourceMaterialComment[];
170
+ /**
171
+ * `true` when the file holds more comment rows than one read inlines (500),
172
+ * so `comments` is the oldest of them rather than all of them.
173
+ */
174
+ truncated: boolean;
175
+ }
176
+ /** The body of `POST …/sources/{sourceId}/comments`. */
177
+ export interface PostSourceMaterialCommentInput {
178
+ author: SourceMaterialCommentAuthorInput;
179
+ /** Trimmed, 1–4000 characters. */
180
+ body: string;
181
+ /**
182
+ * The root comment being answered, or absent for a new remark. Must be a ROOT
183
+ * comment on the same file: replying to a reply is a 400.
184
+ */
185
+ parentCommentId?: string | null;
186
+ /**
187
+ * Your own token for this one intended remark, 1–200 characters. Send it and
188
+ * a retry after a network timeout returns the comment you already posted
189
+ * rather than a permanent duplicate. Scoped to the file.
190
+ */
191
+ idempotencyKey?: string | null;
192
+ }
193
+ /** A comment the server just stored, as a root. */
194
+ export interface StoredSourceMaterialRootComment extends SourceMaterialComment {
195
+ kind: "root";
196
+ }
197
+ /** A comment the server just stored, as a reply to a root. */
198
+ export interface StoredSourceMaterialReplyComment extends SourceMaterialCommentReply {
199
+ kind: "reply";
200
+ /** The root this answers. Present because a reply always has one. */
201
+ parentCommentId: string;
202
+ }
203
+ /**
204
+ * What a POST returns, discriminated on `kind`. Switch on it: a `root` carries
205
+ * an empty `replies`, a `reply` carries `parentCommentId` and no `replies` at
206
+ * all, because it can never have any.
207
+ */
208
+ export type StoredSourceMaterialComment = StoredSourceMaterialRootComment | StoredSourceMaterialReplyComment;
209
+ /** The `201` body of `POST …/sources/{sourceId}/comments`. */
210
+ export interface PostSourceMaterialCommentResult {
211
+ /** The file the comment hangs off (`smf_…`). Opaque. */
212
+ sourceId: string;
213
+ comment: StoredSourceMaterialComment;
214
+ }
108
215
  /** One file as `GET /api/v1/source-material/{batchId}` returns it. */
109
216
  export interface SourceMaterialBatchFile extends SourceMaterialReceiptFile {
110
217
  /** Fetchable URL for the stored bytes. */
@@ -115,6 +222,18 @@ export interface SourceMaterialBatchFile extends SourceMaterialReceiptFile {
115
222
  * only the trigger changes and this contract does not.
116
223
  */
117
224
  analyses: SourceImageAnalysis[];
225
+ /**
226
+ * The argument about this file, oldest root first. ALWAYS an array; `[]` means
227
+ * nobody has commented, which is a state to draw rather than an absence to
228
+ * guess at.
229
+ */
230
+ comments: SourceMaterialComment[];
231
+ /**
232
+ * `true` when this file holds more comment rows than one read inlines (500),
233
+ * so `comments` is the oldest of them. `false` on a file whose whole thread
234
+ * you have, including one nobody has commented on.
235
+ */
236
+ commentsTruncated: boolean;
118
237
  }
119
238
  /**
120
239
  * The `200` body of `GET /api/v1/source-material/{batchId}` — the batch, every
@@ -139,6 +258,12 @@ export interface CapabilityInvokeLink {
139
258
  href: string;
140
259
  method: OpenEnum<"GET" | "POST">;
141
260
  }
261
+ /** One human-input checkpoint exposed by a workflow capability. */
262
+ export interface PublicCapabilitySuspension {
263
+ stepId: string;
264
+ suspend: Record<string, unknown>;
265
+ resume: Record<string, unknown>;
266
+ }
142
267
  /** One invocable capability from the governed public manifest. */
143
268
  export interface PublicCapabilityMetadata {
144
269
  publicId: string;
@@ -150,6 +275,7 @@ export interface PublicCapabilityMetadata {
150
275
  schemas: {
151
276
  input: Record<string, unknown>;
152
277
  output: Record<string, unknown>;
278
+ suspensions?: PublicCapabilitySuspension[];
153
279
  };
154
280
  sideEffectClass: CapabilitySideEffectClass;
155
281
  scopes: CapabilityScope[];
@@ -193,10 +319,16 @@ export interface PublicRun {
193
319
  usageJson: Record<string, unknown> | null;
194
320
  artifactRefs: PublicRunArtifactRef[] | null;
195
321
  failureJson: PublicRunFailure | null;
322
+ /** The current typed question payload while this Run is suspended. */
323
+ suspensionPayload: Record<string, unknown> | null;
196
324
  /**
197
325
  * The step awaiting input while `status` is `suspended`. Pass it as
198
326
  * {@link ResumeRunOptions.stepId} so a stale client cannot resume a step the
199
327
  * Run has already moved past.
328
+ *
329
+ * Read `suspensionPayload` for the current typed question. The same payload is
330
+ * also retained on the `suspended` ledger event; composition-specific events
331
+ * remain available as historical progress records.
200
332
  */
201
333
  suspensionStepId: string | null;
202
334
  eventCursor: number;
package/runs.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { HttpTransport } from "./http-transport.js";
2
+ import { type CapabilityManifestResponse, type CreatePublicRunRequest, type CreatePublicRunResponse, type DrainRunEventsOptions, type ListRunsOptions, type PostSourceMaterialCommentInput, type PostSourceMaterialCommentResult, type PublicRunListResponse, type PublicRunResponse, type ResumeRunOptions, type RunEvent, type SourceMaterialBatchDetail, type SourceMaterialBatchReceipt, type SourceMaterialCommentThread, type StreamRunEventsOptions, type UploadSourceMaterialInput } from "./run-types.js";
2
3
  import type { FinishScheduleResponse, GetRunScheduleOptions } from "./schedule-types.js";
3
- import { type CapabilityManifestResponse, type CreatePublicRunRequest, type CreatePublicRunResponse, type DrainRunEventsOptions, type ListRunsOptions, type PublicRunListResponse, type PublicRunResponse, type ResumeRunOptions, type RunEvent, type SourceMaterialBatchDetail, type SourceMaterialBatchReceipt, type StreamRunEventsOptions, type UploadSourceMaterialInput } from "./run-types.js";
4
4
  /**
5
5
  * Upload one atomic source-material batch (`POST /api/v1/source-material`).
6
6
  * Accepts the pile-of-files shape (`{ files }` → repeatable `files` field) or
@@ -21,6 +21,40 @@ export declare function uploadSourceMaterial(transport: HttpTransport, input: Up
21
21
  * the payload rather than as an undefined three frames away in caller code.
22
22
  */
23
23
  export declare function getSourceMaterialBatch(transport: HttpTransport, batchId: string, signal?: AbortSignal): Promise<SourceMaterialBatchDetail>;
24
+ /**
25
+ * Read one file's comment thread
26
+ * (`GET /api/v1/source-material/{batchId}/sources/{sourceId}/comments`).
27
+ *
28
+ * The remarks people left on one uploaded file, oldest root first, each root
29
+ * carrying the replies that answer it. `comments` is always an array — `[]`
30
+ * means the file is yours and nobody has said anything about it.
31
+ *
32
+ * Author names are REPORTED, never verified: each carries
33
+ * `author.attribution: "reported"` plus the principal that recorded it in
34
+ * `recordedBy`. Render "recorded as from X", not "X said".
35
+ *
36
+ * `truncated` is `true` when the file holds more rows than one read inlines.
37
+ *
38
+ * A file that is not in that batch, or a batch you do not own, raises the same
39
+ * 404 as a file that never existed.
40
+ */
41
+ export declare function listSourceMaterialComments(transport: HttpTransport, batchId: string, sourceId: string, signal?: AbortSignal): Promise<SourceMaterialCommentThread>;
42
+ /**
43
+ * Add one remark to a file's thread
44
+ * (`POST /api/v1/source-material/{batchId}/sources/{sourceId}/comments`).
45
+ *
46
+ * `author` is who the comment is FROM — usually the client or a colleague, not
47
+ * the key posting it — so it is stored and read back as reported attribution.
48
+ * Pass `parentCommentId` to answer a ROOT comment on the same file; anything
49
+ * deeper is a 400, because threads are one level deep.
50
+ *
51
+ * Pass `idempotencyKey` and a retry after a network timeout returns the comment
52
+ * you already posted rather than a permanent duplicate.
53
+ *
54
+ * The result is discriminated on `kind`: switch on it rather than reaching for
55
+ * `replies`, which a `reply` does not have.
56
+ */
57
+ export declare function postSourceMaterialComment(transport: HttpTransport, batchId: string, sourceId: string, input: PostSourceMaterialCommentInput, signal?: AbortSignal): Promise<PostSourceMaterialCommentResult>;
24
58
  /** List invocable agents, workflows, and tools (`GET /api/v1/capabilities`). */
25
59
  export declare function capabilities(transport: HttpTransport, signal?: AbortSignal): Promise<CapabilityManifestResponse>;
26
60
  /** Start an asynchronous capability Run (`POST /api/v1/runs`). */
package/runs.js CHANGED
@@ -7,10 +7,10 @@
7
7
  */
8
8
  import { MaterialGraphError } from "./errors.js";
9
9
  import { HttpTransport } from "./http-transport.js";
10
- import { findSdkOperation, operationPath } from "./operations.js";
11
- import { checkSourceMaterialBatchDetail } from "./source-material-validation.js";
12
- import { readSseFrames } from "./sse.js";
10
+ import { findSdkOperation, operationPath, } from "./operations.js";
13
11
  import { TERMINAL_RUN_EVENT_TYPES, } from "./run-types.js";
12
+ import { checkPostSourceMaterialCommentResult, checkSourceMaterialBatchDetail, checkSourceMaterialCommentThread, } from "./source-material-validation.js";
13
+ import { readSseFrames } from "./sse.js";
14
14
  function runOperation(operationId) {
15
15
  const match = findSdkOperation(operationId);
16
16
  if (!match)
@@ -86,6 +86,66 @@ export async function getSourceMaterialBatch(transport, batchId, signal) {
86
86
  });
87
87
  return checkSourceMaterialBatchDetail(body, batchId);
88
88
  }
89
+ /**
90
+ * Read one file's comment thread
91
+ * (`GET /api/v1/source-material/{batchId}/sources/{sourceId}/comments`).
92
+ *
93
+ * The remarks people left on one uploaded file, oldest root first, each root
94
+ * carrying the replies that answer it. `comments` is always an array — `[]`
95
+ * means the file is yours and nobody has said anything about it.
96
+ *
97
+ * Author names are REPORTED, never verified: each carries
98
+ * `author.attribution: "reported"` plus the principal that recorded it in
99
+ * `recordedBy`. Render "recorded as from X", not "X said".
100
+ *
101
+ * `truncated` is `true` when the file holds more rows than one read inlines.
102
+ *
103
+ * A file that is not in that batch, or a batch you do not own, raises the same
104
+ * 404 as a file that never existed.
105
+ */
106
+ export async function listSourceMaterialComments(transport, batchId, sourceId, signal) {
107
+ const body = await transport.requestJson(runOperation("list_source_material_comments"), {
108
+ pathParams: { batchId, sourceId },
109
+ signal,
110
+ });
111
+ return checkSourceMaterialCommentThread(body, sourceId);
112
+ }
113
+ /**
114
+ * Add one remark to a file's thread
115
+ * (`POST /api/v1/source-material/{batchId}/sources/{sourceId}/comments`).
116
+ *
117
+ * `author` is who the comment is FROM — usually the client or a colleague, not
118
+ * the key posting it — so it is stored and read back as reported attribution.
119
+ * Pass `parentCommentId` to answer a ROOT comment on the same file; anything
120
+ * deeper is a 400, because threads are one level deep.
121
+ *
122
+ * Pass `idempotencyKey` and a retry after a network timeout returns the comment
123
+ * you already posted rather than a permanent duplicate.
124
+ *
125
+ * The result is discriminated on `kind`: switch on it rather than reaching for
126
+ * `replies`, which a `reply` does not have.
127
+ */
128
+ export async function postSourceMaterialComment(transport, batchId, sourceId, input, signal) {
129
+ const body = await transport.requestJson(runOperation("post_source_material_comment"), {
130
+ // Built key by key rather than spread: `parentCommentId` and
131
+ // `idempotencyKey` are forwarded only when PRESENT, so a caller that
132
+ // omits them sends a body byte-identical to one that never knew about
133
+ // them.
134
+ body: {
135
+ author: input.author,
136
+ body: input.body,
137
+ ...(input.parentCommentId === undefined
138
+ ? {}
139
+ : { parentCommentId: input.parentCommentId }),
140
+ ...(input.idempotencyKey === undefined
141
+ ? {}
142
+ : { idempotencyKey: input.idempotencyKey }),
143
+ },
144
+ pathParams: { batchId, sourceId },
145
+ signal,
146
+ });
147
+ return checkPostSourceMaterialCommentResult(body, sourceId);
148
+ }
89
149
  /** List invocable agents, workflows, and tools (`GET /api/v1/capabilities`). */
90
150
  export function capabilities(transport, signal) {
91
151
  return transport.requestJson(runOperation("list_public_capabilities"), {
@@ -224,7 +284,9 @@ function decodeRunEvent(data) {
224
284
  export async function* streamRunEvents(transport, runId, options = {}) {
225
285
  const body = await transport.openStream(runOperation("list_public_run_events"), {
226
286
  pathParams: { runId },
227
- ...(options.after === undefined ? {} : { query: { after: options.after } }),
287
+ ...(options.after === undefined
288
+ ? {}
289
+ : { query: { after: options.after } }),
228
290
  ...(options.signal ? { signal: options.signal } : {}),
229
291
  accept: "text/event-stream",
230
292
  subject: "run-event stream",
@@ -265,7 +327,9 @@ export async function drainRunEvents(transport, runId, options) {
265
327
  }
266
328
  }
267
329
  catch (error) {
268
- const causedByDeadline = controller.signal.aborted && error instanceof MaterialGraphError && error.kind === "aborted";
330
+ const causedByDeadline = controller.signal.aborted &&
331
+ error instanceof MaterialGraphError &&
332
+ error.kind === "aborted";
269
333
  if (!causedByDeadline)
270
334
  throw error;
271
335
  }
@@ -12,6 +12,14 @@
12
12
  * ABSENT IS NOT FALSE, enforced structurally: because the column set is fixed,
13
13
  * "we do not know" has to be SAID rather than omitted. Read
14
14
  * {@link FinishScheduleCell.state} before printing `value`.
15
+ *
16
+ * MG-1351 added the schedule's organising principle: every row carries an
17
+ * {@link FinishScheduleRow.code} L-code under a numbered section, rows arrive in
18
+ * READING order, slots with nothing specified are served as
19
+ * {@link FinishSchedule.openSlots}, and a row may carry or-equal
20
+ * {@link FinishScheduleAlternate}s. Read `alternates` with
21
+ * {@link FinishSchedule.alternatesResolved}: an empty list means either "we did
22
+ * not ask" or "there is none", and those are different claims.
15
23
  */
16
24
  import type { RunId, SchemeId, SlotId, VariantId } from "./identifiers.js";
17
25
  /** One schedule column: the attribute and its header text. */
@@ -51,8 +59,106 @@ export interface FinishScheduleCell {
51
59
  * `hard_no` means a required cell failed on a real value.
52
60
  */
53
61
  export type FinishScheduleGrade = "meets_all" | "close" | "hard_no";
62
+ /** One numbered section of the schedule, and the heading it prints. */
63
+ export interface FinishScheduleSection {
64
+ /** The hundreds block, e.g. `100`. Also the base of every line number in it. */
65
+ code: number;
66
+ name: string;
67
+ }
68
+ /**
69
+ * What MaterialGraph claims about an alternate — and never more than it can
70
+ * support. None of the three means "interchangeable": accepting an alternate is
71
+ * a specification decision, and this API does not make it.
72
+ *
73
+ * `or_equal` matched the specified material's equivalence signature with every
74
+ * supporting value above its confidence floor. `or_equal_unverified` matched the
75
+ * same signature on values that did not clear it. `successor` is the brand's own
76
+ * declared replacement, from a reviewed relationship rather than a spec match —
77
+ * its verdict cells are `not_evaluated` by construction.
78
+ */
79
+ export type FinishScheduleAlternateStance = "or_equal" | "or_equal_unverified" | "successor";
80
+ /** One criterion of the equivalence signature an alternate was matched on. */
81
+ export interface FinishScheduleAlternateCriterion {
82
+ role: "primary_material" | "performance";
83
+ attributeId: string;
84
+ term: string;
85
+ }
86
+ /**
87
+ * Why an alternate is on the row. Every field is evidence, not adjective — a
88
+ * substitute presented without its basis looks like a decision when it is a
89
+ * lookup, so this is never absent from an alternate.
90
+ */
91
+ export interface FinishScheduleAlternateBasis {
92
+ /** The SPECIFIED variant this alternate was graded against. Opaque. */
93
+ specifiedVariantId: VariantId;
94
+ /** The product-type form the signature was projected in; null for a successor. */
95
+ form: string | null;
96
+ /** The criteria that matched. Empty for a successor: nothing was matched. */
97
+ signature: FinishScheduleAlternateCriterion[];
98
+ /** The seed-relative report card, projected exactly as row cells are. */
99
+ verdict: {
100
+ cells: FinishScheduleCell[];
101
+ grade: FinishScheduleGrade;
102
+ minConfidence: number | null;
103
+ };
104
+ }
105
+ /** One or-equal line sitting beneath the specified line it answers. */
106
+ export interface FinishScheduleAlternate {
107
+ stance: FinishScheduleAlternateStance;
108
+ /** Opaque — round-trip verbatim into the material lenses. */
109
+ variantId: VariantId;
110
+ brand: string;
111
+ product: string;
112
+ variant: string;
113
+ /**
114
+ * The colourway, when the catalogue names one. Carried separately because a
115
+ * substitute's `variant` is frequently the product name repeated, and a line
116
+ * that cannot name the colour has not named a material.
117
+ */
118
+ colour: string | null;
119
+ imageUrl: string | null;
120
+ hex: string | null;
121
+ recordUrl: string;
122
+ /**
123
+ * Further colourways of this same product that also cleared the signature and
124
+ * were collapsed into this line. A COUNTED collapse: `0` means this product
125
+ * offered exactly one match, never that the count is unknown.
126
+ */
127
+ otherColourways: number;
128
+ basis: FinishScheduleAlternateBasis;
129
+ }
130
+ /** A slot holding a code with nothing specified against it yet. */
131
+ export interface FinishScheduleOpenSlot {
132
+ code: string;
133
+ sectionCode: number;
134
+ sectionName: string;
135
+ line: number;
136
+ slotId: SlotId;
137
+ slotName: string;
138
+ space: string | null;
139
+ /**
140
+ * Why this line is blank, in words — or `null` when nothing measured a reason.
141
+ *
142
+ * `null` is NOT "nothing was found". It means no diagnosis was recorded for
143
+ * this slot, most often because it was never searched, and it must not be
144
+ * rendered as a search that came back empty.
145
+ */
146
+ reason: string | null;
147
+ }
54
148
  /** One row: one slot, one specified material, fully graded. */
55
149
  export interface FinishScheduleRow {
150
+ /**
151
+ * The printed line reference, e.g. `L-101` — how a drawing, an RFI or a
152
+ * substitution request names this line. Allocated from the Run's PROGRAMME
153
+ * rather than its picks, so recomposing the scheme, varying it, or accepting a
154
+ * substitution renumbers nothing.
155
+ */
156
+ code: string;
157
+ /** The hundreds block this line sits in, e.g. `100`. */
158
+ sectionCode: number;
159
+ sectionName: string;
160
+ /** The 1-based line within its (space, section) group. */
161
+ line: number;
56
162
  slotId: SlotId;
57
163
  slotName: string;
58
164
  /** The space NAME, or null for a spaceless slot. */
@@ -73,6 +179,11 @@ export interface FinishScheduleRow {
73
179
  attributes: FinishScheduleCell[];
74
180
  fitNotes: string[];
75
181
  grade: FinishScheduleGrade;
182
+ /**
183
+ * Or-equal alternates for this line. An empty array is AMBIGUOUS on its own —
184
+ * read it with {@link FinishSchedule.alternatesResolved}.
185
+ */
186
+ alternates: FinishScheduleAlternate[];
76
187
  }
77
188
  /** A whole composed scheme as an issuable finish schedule. */
78
189
  export interface FinishSchedule {
@@ -85,7 +196,24 @@ export interface FinishSchedule {
85
196
  generatedAt: string;
86
197
  /** The stable column order every row's `attributes` array follows. */
87
198
  columns: FinishScheduleColumn[];
199
+ /**
200
+ * The section table to group by, in schedule order. A section may carry no
201
+ * rows: three are reserved so their conventional numbers are held rather than
202
+ * reused, and any section can simply be unspecified in this scheme.
203
+ */
204
+ sections: FinishScheduleSection[];
205
+ /** In READING order — space, then section, then line. Not pick order. */
88
206
  rows: FinishScheduleRow[];
207
+ /**
208
+ * Slots holding a code with nothing specified against them. Served explicitly
209
+ * because a schedule that omitted them would read as complete.
210
+ */
211
+ openSlots: FinishScheduleOpenSlot[];
212
+ /**
213
+ * Whether the or-equal lookup RAN. When false, no row's empty `alternates`
214
+ * may be read as "no alternate exists".
215
+ */
216
+ alternatesResolved: boolean;
89
217
  }
90
218
  /** `GET /api/v1/runs/{runId}/schedule`. */
91
219
  export interface FinishScheduleResponse {
@@ -130,7 +130,7 @@ export interface SchemaSourceRequirementItem {
130
130
  minConfidence: number;
131
131
  reviewRequired: boolean;
132
132
  freshnessDays?: number;
133
- sourceBasis?: OpenEnum<"declared" | "derived" | "tested">;
133
+ sourceBasis?: OpenEnum<"declared" | "derived" | "declared">;
134
134
  semanticId: string;
135
135
  lifecycle: SchemaLifecycle;
136
136
  }
@@ -1,9 +1,10 @@
1
1
  /** Types for structured refinements returned by MaterialGraph search. */
2
2
  import type { SearchInput } from "./types.js";
3
3
  /**
4
- * A hard canonical-attribute filter accepted by the search engine — either a
5
- * term match (`{ attributeId, term }`) or a presence check
6
- * (`{ attributeId, hasValue: true }`). Applied as a restriction BEFORE ranking.
4
+ * A hard canonical-attribute filter accepted by the search engine — a term match
5
+ * (`{ attributeId, term }`), a presence check (`{ attributeId, hasValue: true }`)
6
+ * or a NUMERIC RANGE (`{ attributeId, min, max }`, bounds inclusive, at least one
7
+ * stated). Applied as a restriction BEFORE ranking.
7
8
  *
8
9
  * The `attributeId` may be a real registry id (`"fire_classifications"`,
9
10
  * `"environmental_product_declaration"`) OR the virtual
@@ -34,7 +35,7 @@ export interface SearchFilterTrust {
34
35
  * - `derived` — MaterialGraph inferred it. Ours, not the manufacturer's — never
35
36
  * present a `derived` value as something the brand claims.
36
37
  */
37
- sourceBasis?: "catalog_feed" | "declared" | "derived" | "tested";
38
+ sourceBasis?: "catalog_feed" | "declared" | "derived" | "declared";
38
39
  /** Fold the attribute's own registry confidence requirement in, server-side. */
39
40
  meetsRequirement?: true;
40
41
  }
@@ -52,6 +53,13 @@ export type SearchFilter = {
52
53
  attributeId: string;
53
54
  hasValue: true;
54
55
  trust?: SearchFilterTrust;
56
+ } | {
57
+ attributeId: string;
58
+ /** Inclusive lower bound. Omit for a ceiling-only range. */
59
+ min?: number;
60
+ /** Inclusive upper bound. Omit for a floor-only range. */
61
+ max?: number;
62
+ trust?: SearchFilterTrust;
55
63
  };
56
64
  /** One structured refinement returned with a search response. */
57
65
  export interface SearchSuggestion {
@@ -59,3 +67,30 @@ export interface SearchSuggestion {
59
67
  /** Complete replacement request, not a partial patch. */
60
68
  request: SearchInput;
61
69
  }
70
+ /** One of the two readings a {@link SearchDisambiguation} offers. */
71
+ export interface SearchInterpretation {
72
+ id: string;
73
+ label: string;
74
+ kind: string;
75
+ /** True for the reading that ACTUALLY RAN and produced this page. */
76
+ isDefault: boolean;
77
+ /** The colour this reading resolved to, when the ambiguity was a colour one. */
78
+ hex?: string;
79
+ /** Complete replacement request, not a partial patch. */
80
+ request: SearchInput;
81
+ }
82
+ /**
83
+ * The server saying a query had TWO defensible readings, and which one it took.
84
+ *
85
+ * `interpretations` always holds exactly two, the one that ran flagged
86
+ * `isDefault`. Each carries a complete executable request, so offering the
87
+ * other reading is a re-issue rather than a patch. Ignoring this block does not
88
+ * make the ambiguity go away — it makes it invisible to the person who asked.
89
+ */
90
+ export interface SearchDisambiguation {
91
+ /** Which ambiguity this is: "compliance-phrase", "colour-material", "paint-colour". */
92
+ kind: string;
93
+ /** The ambiguous term or phrase. */
94
+ term: string;
95
+ interpretations: SearchInterpretation[];
96
+ }