@pipelex/mthds-form 0.1.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.
@@ -0,0 +1,615 @@
1
+ import { R as RunField } from '../descriptor-X6MwFIWu.js';
2
+ export { B as BooleanRunField, C as ConceptCategory, D as DateRunField, E as EnumRunField, F as FileRunField, L as ListRunField, N as NumberRunField, O as ObjectRunField, P as ProseRunField, a as RunFieldCommon, b as RunFieldKind, T as TextRunField, U as UnknownRunField, c as conceptCategory } from '../descriptor-X6MwFIWu.js';
3
+
4
+ /**
5
+ * The typed `pipe_io_contracts` mirror and its gating predicates.
6
+ *
7
+ * Mirrors pipelex's `PipeIOContract` (pipelex/pipeline/pipe_io_contracts.py).
8
+ * The SDK deliberately keeps `pipe_io_contracts` opaque, so the kernel is the
9
+ * canonical TS home for this shape until the D-track specs it.
10
+ *
11
+ * The map itself is keyed by namespaced pipe ref (`<domain>.<pipe_code>`),
12
+ * NOT bare pipe code - use `getPipeIOContract` to look entries up by the bare
13
+ * codes the UI tracks.
14
+ */
15
+ interface PipeInputContract {
16
+ concept_ref: string;
17
+ json_schema: Record<string, unknown>;
18
+ /**
19
+ * `true` when the input is declared optional (`?`) - the caller may omit it
20
+ * and the pipe handles the absence itself. Plain and force (`!`) inputs
21
+ * report `false`. Absent on contracts persisted before the API carried the
22
+ * flag, so read it as "optional only when explicitly true".
23
+ */
24
+ optional?: boolean;
25
+ }
26
+ interface PipeOutputContract {
27
+ concept_ref: string;
28
+ multiplicity: 'single' | 'variable';
29
+ /** `true` when the output is declared optional (`?`) - the pipe may resolve
30
+ * it as a recorded absence instead of a value. */
31
+ optional?: boolean;
32
+ }
33
+ interface PipeIOContract {
34
+ inputs: Record<string, PipeInputContract>;
35
+ output: PipeOutputContract;
36
+ }
37
+ /** The `pipe_io_contracts` map: namespaced `pipe_ref` → IO contract. */
38
+ type PipeIOContracts = Record<string, PipeIOContract>;
39
+ /** Namespaced pipe ref (`<domain>.<pipe_code>`) - the identity convention the
40
+ * validate artifacts key on. */
41
+ declare function buildPipeRef(domain: string, pipeCode: string): string;
42
+ /**
43
+ * Look up a pipe's IO contract by pipe code, tolerant of both key conventions.
44
+ *
45
+ * The map may be keyed by namespaced `pipe_ref` (`<domain>.<code>`, the
46
+ * canonical convention) OR by bare pipe code - the current hosted `/validate`
47
+ * keys `pipe_structures` by bare code, and methods persisted before the
48
+ * pipe_ref re-keying also carry bare-code keys. Try the namespaced ref first
49
+ * (when the caller supplies a domain), then fall back to the bare code.
50
+ * Returns `undefined` (never crashes) when neither matches.
51
+ */
52
+ declare function getPipeIOContract(pipeIoContracts: PipeIOContracts | null | undefined, domain: string | null | undefined, pipeCode: string | null | undefined): PipeIOContract | undefined;
53
+ /**
54
+ * True when the input carries a plural concept (`Image[]`): its JSON Schema is
55
+ * an array wrapper. A plural slot is never "absent" in MTHDS - its empty form
56
+ * IS the empty list - so the UI must treat "nothing entered" as a real value.
57
+ */
58
+ declare function isPluralInput(input: PipeInputContract): boolean;
59
+ /**
60
+ * True when the user MUST put a value in before a run can start.
61
+ *
62
+ * Two declarations relieve an input of that duty, for different reasons:
63
+ * - **optional (`?`)** - the method itself states the caller may omit it;
64
+ * - **plural (`[]`)** - the empty list is a legitimate value and no method can
65
+ * declare "at least one", so gating on it would invent a constraint the
66
+ * method never made.
67
+ *
68
+ * Everything else (plain and force `!`) still gates: that is the whole point of
69
+ * the Run button telling you what is missing.
70
+ */
71
+ declare function inputMustBeFilled(input: PipeInputContract): boolean;
72
+
73
+ /**
74
+ * The ONE derivation function: `PipeInputContract` map → `RunField[]`.
75
+ *
76
+ * Everything heuristic stays module-private behind `buildRunFields`: the
77
+ * native-concept taxonomy, `isDocumentObject`, the depth-based prose/label
78
+ * rule, the `accept` strings, list splitting. Nothing here is exported except
79
+ * the function itself - so the M1 milestone can swap the source of the
80
+ * descriptor (server-derived) without touching consumers.
81
+ */
82
+
83
+ /**
84
+ * Convert the API's per-input contracts into the runner's field descriptors.
85
+ * The map's insertion order is preserved (required-first is the caller's job).
86
+ */
87
+ declare function buildRunFields(inputs: Record<string, PipeInputContract>): RunField[];
88
+
89
+ /**
90
+ * Value readiness - the helpers the runner uses to decide whether the Run
91
+ * button may fire and which inputs are still missing.
92
+ */
93
+
94
+ /** True when a value is meaningfully filled (not empty / blank / empty container). */
95
+ declare function isFilled(value: unknown): boolean;
96
+ /** True when every required field has a value. */
97
+ declare function fieldFilled(field: RunField, value: unknown): boolean;
98
+ interface Readiness {
99
+ total: number;
100
+ ready: number;
101
+ missing: string[];
102
+ }
103
+ /**
104
+ * True when the user must put a value in this field before the run can start.
105
+ *
106
+ * Distinct from `required`, which also drives layout (a required field always
107
+ * shows; an optional one may collapse). A **plural** field stays required - it
108
+ * is a first-class input and keeps its place in the form - but it never gates:
109
+ * a plural slot is never "absent" in MTHDS, its empty form IS the empty list,
110
+ * and no method can declare "at least one item".
111
+ *
112
+ * **For a field the app built, the answer comes from `inputMustBeFilled`**
113
+ * (`contracts.ts`) - the SAME predicate the method viewer's Run button, its ajv
114
+ * `required` list and its missing-inputs toast all read. `buildRunFields`
115
+ * stamps it onto `gating`. This used to be re-derived here as
116
+ * `required && kind !== 'list'`: the same rule expressed against the MAPPED
117
+ * field instead of the contract, and the two could disagree - `kind` is `'list'`
118
+ * for a `conceptRef` ending in `[]` OR an array schema, while the contract's
119
+ * plural test is the array schema alone, so a plural input carrying a non-array
120
+ * schema gated on one surface and not the other.
121
+ *
122
+ * The shape heuristic survives only as the fallback for a **hand-authored**
123
+ * `RunField` - story fixtures and unit tests, which have no contract to consult
124
+ * and therefore nothing to disagree with. Every field the app renders comes from
125
+ * `buildRunFields` and carries `gating`, so the app is on the contract's answer
126
+ * alone.
127
+ */
128
+ declare function mustBeFilled(field: RunField): boolean;
129
+ /** How many gating top-level inputs are satisfied by the current values. */
130
+ declare function computeReadiness(fields: RunField[], values: Record<string, unknown>): Readiness;
131
+
132
+ /**
133
+ * One validation error, in the shape the app reads (`property`, `message`,
134
+ * `name`, `stack`, `params.format`). Structurally a superset-compatible twin of
135
+ * RJSF's `RJSFValidationError`, so surfaces still holding RJSF form errors (the
136
+ * method editor's Inputs panel) can hand them to the same message renderer.
137
+ */
138
+ interface RunInputError {
139
+ /** The ajv keyword that failed (`required`, `format`, `type`, …). */
140
+ name?: string;
141
+ /** Dotted path into the form data (`.quote_date.date`); for a `required`
142
+ * error the missing property name is appended (top-level: no leading dot). */
143
+ property?: string;
144
+ message?: string;
145
+ params?: Record<string, unknown>;
146
+ /** The human line: `property message`, or `'title' message` when the parent
147
+ * schema titles the field. */
148
+ stack: string;
149
+ schemaPath?: string;
150
+ }
151
+ /**
152
+ * Validate `data` against `schema`, returning mapped errors (empty = valid).
153
+ * MUTATES `data` where ajv coerces (`"20"` → `20`) - deliberate and relied on:
154
+ * the string the user typed is stored as a number going forward.
155
+ */
156
+ declare function validateRunInputsSchema(data: unknown, schema: object): RunInputError[];
157
+
158
+ type Dict$1 = Record<string, unknown>;
159
+ /**
160
+ * Combine the per-input schemas into one object schema.
161
+ *
162
+ * Each input variable becomes a property, titled with the variable name and
163
+ * described with the concept code. Only the inputs the method actually demands
164
+ * land in `required` - an optional (`?`) or plural (`[]`) input must not block
165
+ * the form (see `inputMustBeFilled`).
166
+ */
167
+ declare function buildRunInputsSchema(inputs: Record<string, PipeInputContract>): Dict$1;
168
+ /**
169
+ * Last-gate repair, in two passes against the exact schema ajv validates.
170
+ *
171
+ * 1. **HEAL** - persisted `inputData` may still carry the legacy double-wrapped
172
+ * `{ text: { text } }` shape (or a form's state may lag a store fix).
173
+ * 2. **PRUNE** - RJSF fills every unset string with `""` on mount, which an
174
+ * optional CONSTRAINED field rejects (`DateContent.time` is `format: "time"`)
175
+ * - blocking a run on a field the user never opened. An absent optional field
176
+ * is what pipelex means by `None` anyway, so dropping it is the semantically
177
+ * correct repair, not a validation dodge.
178
+ */
179
+ declare function prepareRunInputs(data: Dict$1, schema: Dict$1): Dict$1;
180
+ interface RunInputsVerdict {
181
+ isValid: boolean;
182
+ /** Variable names the scan could pin a missing required field on. */
183
+ missingInputs: string[];
184
+ /** Raw ajv errors, for when the scan cannot name anything. */
185
+ errors: RunInputError[];
186
+ }
187
+ /**
188
+ * Validate PREPARED data against the combined schema.
189
+ *
190
+ * `missingInputs` is reported by VARIABLE NAME - the name on the node the user
191
+ * clicks - never by `concept_ref`, which used to produce "Missing required
192
+ * fields in: native.Text" and named neither the input at fault nor, when several
193
+ * inputs share a concept, which of them. Inputs the method declared omittable
194
+ * are skipped: an empty `?` input is not a missing one.
195
+ *
196
+ * The scan can come up empty even on an invalid form (a wrong value shape, a
197
+ * nested mismatch); `errors` is what the caller falls back to then, so a failure
198
+ * is never undiagnosable.
199
+ */
200
+ declare function validateRunInputs(preparedData: Dict$1, inputs: Record<string, PipeInputContract>, schema: Dict$1): RunInputsVerdict;
201
+ /**
202
+ * Build the `{ concept, content }` map a run expects, from PREPARED data.
203
+ *
204
+ * The two ways a method says "this may be nothing" land differently on the wire:
205
+ * - an **optional (`?`)** input left blank is OMITTED, so the runtime records a
206
+ * real absence the method can branch on - sending `{ text: "" }` would look
207
+ * like the caller supplied an empty string;
208
+ * - a **plural (`[]`)** input keeps its key, because a plural slot is never
209
+ * absent - its empty form IS the empty list, and the runtime requires the key.
210
+ * When that list is EMPTY it is sent BARE, without the envelope: the envelope
211
+ * is the runtime's "explicit form", which bypasses the top-down shaper and
212
+ * hands the value to the bottom-up factory, and that factory types a list from
213
+ * its first item - so an empty list there raises "Cannot create Stuff from
214
+ * empty list in content". The bare form keeps the shaper, which reads the
215
+ * DECLARED concept and builds the empty list correctly. (pipelex/#1096 makes
216
+ * the envelope agree; the bare form runs today and stays correct after.)
217
+ */
218
+ declare function apiInputsFromSchemaData(preparedData: Dict$1, inputs: Record<string, PipeInputContract>): Dict$1;
219
+
220
+ /**
221
+ * Bridges the `MethodRunner`'s field values to the rest of the app.
222
+ *
223
+ * The method-store keeps inputs in the *simplified* format and runs send the
224
+ * *full* RJSF form shape - both already handled by `inflate/deflateAllInputs`.
225
+ * The only gap is that a few field controls use a friendlier value than RJSF:
226
+ * text is a plain string (RJSF wraps it `{ text }`) and a file is `{ url,
227
+ * filename }`. `fromRjsf`/`toRjsf` translate just those, so store reads/writes
228
+ * and run payloads stay byte-for-byte compatible with the proven playground path.
229
+ *
230
+ * Pure module - no React, unit-tested.
231
+ */
232
+
233
+ type Dict = Record<string, unknown>;
234
+ type InputSchemas = Record<string, PipeInputContract>;
235
+ /** Seed the runner's form values from the method-store's simplified inputData. */
236
+ declare function runValuesFromStore(inputData: Dict | undefined, fields: RunField[], inputSchemas: InputSchemas): Dict;
237
+ /**
238
+ * The runner's form values in **schema shape** - the same shape the MethodViewer's
239
+ * RJSF form holds, and therefore the shape `gate.ts` validates
240
+ * against the per-input JSON Schemas.
241
+ *
242
+ * Deliberately stops short of `deflateAllInputs`: deflation is the STORE's
243
+ * compact form, and ajv validates the full one.
244
+ */
245
+ declare function rjsfDataFromRunValues(values: Dict, fields: RunField[]): Dict;
246
+ /** Convert the runner's form values back to the store's simplified inputData. */
247
+ declare function storeInputDataFromRunValues(values: Dict, fields: RunField[], inputSchemas: InputSchemas): Dict;
248
+ /**
249
+ * Build the `{ concept, content }` map a run expects from the form values.
250
+ *
251
+ * The two ways a method says "this may be nothing" land differently on the wire,
252
+ * and both matter:
253
+ * - an **optional (`?`)** input left blank is OMITTED, so the runtime records a
254
+ * real absence the method can branch on - sending `{ text: "" }` would look
255
+ * like the caller supplied an empty string;
256
+ * - a **plural (`[]`)** input keeps its key, because a plural slot is never
257
+ * absent - its empty form IS the empty list, and the runtime requires the key.
258
+ * When that list is EMPTY it is sent BARE, without the envelope: the envelope
259
+ * is the runtime's "explicit form", which bypasses the top-down shaper and
260
+ * hands the value to the bottom-up factory, and that factory types a list from
261
+ * its first item - so an empty list there raises "Cannot create Stuff from
262
+ * empty list in content". The bare form keeps the shaper, which reads the
263
+ * DECLARED concept and builds the empty list correctly. (pipelex/#1096 makes
264
+ * the envelope agree; the bare form runs today and stays correct after.)
265
+ */
266
+ declare function apiInputsFromRunValues(values: Dict, fields: RunField[], inputSchemas: InputSchemas): Record<string, unknown>;
267
+ /** One named output pulled out of a run's working memory, ready to render. */
268
+ interface OutputEntry {
269
+ /** The output variable key (mono). */
270
+ key: string;
271
+ conceptRef?: string;
272
+ value: unknown;
273
+ }
274
+ /**
275
+ * Split a run's pipe output into the main result and any intermediate outputs,
276
+ * excluding the inputs. The main output's identity comes from the working
277
+ * memory's `main_stuff` alias (the authoritative pointer to the output variable)
278
+ * - so its label is the REAL stuff name, not a positional guess. Falls back to
279
+ * the last produced stuff only when the alias is absent.
280
+ */
281
+ declare function outputsFromPipeOutput(pipeOutput: unknown, inputKeys: string[]): {
282
+ main: OutputEntry;
283
+ intermediates: OutputEntry[];
284
+ } | null;
285
+ /**
286
+ * Recover the inputs a run was executed with from its working memory. The run's
287
+ * `working_memory.root` holds every named stuff - inputs AND outputs - keyed by
288
+ * variable name; we pull the entries whose keys are declared inputs and deflate
289
+ * their `content` (the full form shape) back into the store's simplified
290
+ * `inputData`. Lets the runner repopulate the form for a past/shared run - the
291
+ * inputs that produced the result. Returns `null` when no inputs are present.
292
+ */
293
+ declare function inputDataFromWorkingMemory(workingMemory: unknown, inputSchemas: InputSchemas): Dict | null;
294
+ /** Field descriptors for a method's main pipe. */
295
+ declare function fieldsForContract(contract: PipeIOContract | undefined): RunField[];
296
+ /**
297
+ * Immutably set a value at a dotted/indexed path (`a.0.b`). Used to write an
298
+ * uploaded file's URL back to a field anywhere in the (possibly nested) tree.
299
+ */
300
+ declare function setValueAtPath(root: Dict, path: string[], value: unknown): Dict;
301
+
302
+ /**
303
+ * Converts between the simplified inputs.json format and the full RJSF form format.
304
+ *
305
+ * The inputs.json file uses a SIMPLIFIED format for users:
306
+ * - Text: "my text" (instead of { text: "my text" })
307
+ * - Document: "file.pdf" or { url: "..." } (instead of { url: "...", ... })
308
+ * - Image: same as Document
309
+ * - Text[]: ["t1", "t2"]
310
+ * - Document[]: ["f1.pdf"] or [{ url: "..." }]
311
+ * - Custom: { concept, content } (no simplification)
312
+ *
313
+ * The RJSF form uses the FULL concept structure from the json_schema.
314
+ *
315
+ * Two directions:
316
+ * - deflate: form (full) → inputs.json (simplified) - for writing inputs.json / API calls
317
+ * - inflate: inputs.json (simplified) → form (full) - for populating the form from inputs.json
318
+ */
319
+ /**
320
+ * Convert a single form value to the simplified inputs.json format.
321
+ * Used when writing inputs.json and when calling the run API.
322
+ *
323
+ * Rules:
324
+ * - Text { text: "hello" } → "hello"
325
+ * - Document { url: "file.pdf", ... } → "file.pdf" (if only url) or { url, ... }
326
+ * - Image: same as Document
327
+ * - Page: same as Document
328
+ * - List types → array of deflated items
329
+ * - Custom → { concept, content }
330
+ */
331
+ declare function deflateInput(value: unknown, conceptCode: string): unknown;
332
+ /**
333
+ * Convert a simplified inputs.json value to the full form structure.
334
+ * Used when populating the RJSF form from inputs.json.
335
+ *
336
+ * Rules:
337
+ * - "hello" → { text: "hello" } (for Text concepts)
338
+ * - "file.pdf" → { url: "file.pdf" } (for Document/Image/Page concepts)
339
+ * - { url: "..." } → passthrough (already full)
340
+ * - ["t1", "t2"] → inflated array of items
341
+ * - { concept, content } → content (for custom concepts)
342
+ */
343
+ declare function inflateInput(value: unknown, conceptCode: string): unknown;
344
+ interface InputSchema {
345
+ concept_ref: string;
346
+ json_schema?: Record<string, unknown>;
347
+ }
348
+ /**
349
+ * The validation API returns concept_ref WITHOUT the [] suffix for list inputs.
350
+ * The array-ness is only encoded in json_schema.type === "array".
351
+ * This helper resolves the correct concept ref by checking the schema.
352
+ */
353
+ declare function resolveConceptCode(schema: InputSchema): string;
354
+ /**
355
+ * Deflate all inputs from form format to simplified inputs.json format.
356
+ * Used by InputConfigPanel when preparing data for the run API.
357
+ */
358
+ declare function deflateAllInputs(formData: Record<string, unknown>, inputSchemas: Record<string, InputSchema>): Record<string, unknown>;
359
+ /**
360
+ * Heal values against their json_schema: the run-form bridge used to
361
+ * double-wrap plain string properties as `{ text: "..." }` (fixed in
362
+ * run-values.ts, but the corrupted shape survives in persisted inputData -
363
+ * store and server copies). Wherever the schema expects a string but the
364
+ * value is a lone `{ text: string }` wrapper, unwrap it. Walks nested
365
+ * objects and arrays, resolving `#/$defs/...` refs against the root schema;
366
+ * anything already valid passes through untouched.
367
+ */
368
+ declare function healStringWrappers(value: unknown, rawSchema: Record<string, unknown> | undefined, defs?: Record<string, Record<string, unknown>>): unknown;
369
+ /**
370
+ * Drop optional properties the user never filled, so an untouched field cannot
371
+ * fail validation or reach the API as an empty string.
372
+ *
373
+ * RJSF fills EVERY unset string property with `""` on mount. That is harmless
374
+ * for a plain string, but `prepareSchemaForRjsf` collapses pydantic's
375
+ * `anyOf: [T, null]` by dropping the null branch, so an optional *constrained*
376
+ * field - `DateContent.time`, declared `datetime.time | None` and rendered
377
+ * `{ type: "string", format: "time" }` - is left demanding a well-formed time.
378
+ * `""` is not one, and the run was blocked on a field the user never opened.
379
+ *
380
+ * Pruning is the semantically correct repair, not a validation dodge: for
381
+ * pipelex an absent optional field IS `None`, and `""` would fail pydantic at
382
+ * the runner just as it fails ajv here. Only properties absent from their
383
+ * parent's `required` are dropped - an empty REQUIRED field must still fail, so
384
+ * the user is told about it.
385
+ */
386
+ declare function pruneEmptyOptionals(value: unknown, rawSchema: Record<string, unknown> | undefined, defs?: Record<string, Record<string, unknown>>): unknown;
387
+ /**
388
+ * Inflate all inputs from simplified inputs.json format to form format.
389
+ * Used by onInputUpdate when receiving inputs.json changes from the sandbox.
390
+ */
391
+ declare function inflateAllInputs(simplifiedData: Record<string, unknown>, inputSchemas: Record<string, InputSchema>): Record<string, unknown>;
392
+
393
+ /**
394
+ * Pure conversions between a stored date value and what a native `<input
395
+ * type="date">` shows, plus the leniency rules our schema validation uses.
396
+ * Kept React-free so both the runner's `DateField` and the RJSF `DateWidget`
397
+ * share one source of truth (and it's trivially unit-tested).
398
+ *
399
+ * Two schema shapes reach us, and they are NOT interchangeable:
400
+ * - an MTHDS `type = "date"` structure field compiles to `format: date-time`,
401
+ * so its value round-trips as a full RFC 3339 timestamp;
402
+ * - `native.Date`'s own `date` field is a python `datetime.date`, i.e.
403
+ * `format: date` - a bare `YYYY-MM-DD`.
404
+ *
405
+ * ## Why these rules exist
406
+ *
407
+ * The runtime (pydantic) is LENIENT in both directions, and our client used to
408
+ * be stricter than the contract it was enforcing: `ajv`'s stock `format: "date"`
409
+ * accepts a bare `YYYY-MM-DD` and nothing else, so a hand-written
410
+ * `"2026-07-06T00:00:00Z"` in an `inputs.json` was rejected here even though
411
+ * pydantic accepts it happily and coerces it to `date(2026, 7, 6)`. The run
412
+ * never even reached the runner. These predicates mirror pydantic exactly
413
+ * (measured, not assumed):
414
+ *
415
+ * format: date "2026-07-06" -> date(2026, 7, 6)
416
+ * "2026-07-06T00:00:00Z" -> date(2026, 7, 6)
417
+ * "2026-07-06T00:00:00+02:00" -> date(2026, 7, 6) (literal date, no UTC shift)
418
+ * "2026-07-06T15:40:00Z" -> REJECTED "should have zero time"
419
+ * format: date-time "2026-07-06" -> datetime(2026, 7, 6, 0, 0)
420
+ * "2026-07-06T00:00:00Z" -> datetime(2026, 7, 6, 0, 0, tz)
421
+ *
422
+ * A midnight-padded timestamp is unambiguously "just a day", so take it. A real
423
+ * time is data - refusing it is the point (`DateContentError`: "no silent
424
+ * midnight"), because silently dropping 15:40 corrupts the value. And an
425
+ * ambiguous `06/07/2026` is never accepted by anyone: 6 July or June 7 depends
426
+ * on who's reading, and guessing wrong fails silently.
427
+ */
428
+ /**
429
+ * The calendar day a value denotes, or `null` when it doesn't denote one.
430
+ * Accepts a bare date or a midnight-padded timestamp; the day is taken
431
+ * LITERALLY from the string (never shifted through a timezone), matching
432
+ * pydantic - `2026-07-06T00:00:00+02:00` is 2026-07-06, not the 5th.
433
+ */
434
+ declare function asCalendarDate(value: string): string | null;
435
+ /**
436
+ * The calendar day inside a value that carries a REAL time - precisely the case
437
+ * `asCalendarDate` refuses. `null` when the value isn't a timestamp at all, or
438
+ * when it IS already an acceptable day (nothing to report).
439
+ *
440
+ * This exists to EXPLAIN, never to coerce. `2026-07-06T14:30:00+02:00` is
441
+ * rejected for a `format: date` field, and the rejection is right (DT3, "no
442
+ * silent midnight" - dropping 14:30 corrupts the value). But telling the user
443
+ * "pick a valid date" while the picker shows them a perfectly good 06/07/2026
444
+ * reads as a lie: `toDateInputValue` renders the day, the stored value carries
445
+ * the time, and only one of those is what gets validated. Naming both the value
446
+ * we hold and the day it would have to become is what makes the error legible.
447
+ */
448
+ declare function dayWithinDateTime(value: string): string | null;
449
+ /**
450
+ * What `format: "date"` accepts: a bare `YYYY-MM-DD`, and nothing else.
451
+ *
452
+ * DO NOT relax this to take a midnight-padded timestamp. `DateContent` in
453
+ * pipelex carries a `mode="before"` field validator (`_reject_lax_temporal`)
454
+ * that deliberately CLOSES pydantic's lax coercion for exactly this case:
455
+ *
456
+ * "A Date's `date` field takes a calendar date alone; a string carrying a
457
+ * time (e.g. '2026-07-07T00:00:00') would drop it - put the time and offset
458
+ * in `time` instead."
459
+ *
460
+ * That rule (DT3, "no silent midnight") is the concept's whole point, so a
461
+ * lenient client here does not help anyone: it just forwards the value and the
462
+ * runner answers with a 500 instead of the field saying so up front.
463
+ *
464
+ * The leniency belongs in `asCalendarDate` - normalize the pasted timestamp to
465
+ * the day it denotes, then this passes. Accept-and-fix, not accept-and-forward.
466
+ */
467
+ declare function isAcceptableDate(value: string): boolean;
468
+ /** What `format: "date-time"` should accept - a bare day is midnight. */
469
+ declare function isAcceptableDateTime(value: string): boolean;
470
+ /** The `YYYY-MM-DD` a native date input shows, from any stored date/date-time. */
471
+ declare function toDateInputValue(value: string | undefined): string;
472
+ /**
473
+ * The value to store for a picked day. A `date-time` field becomes midnight-UTC
474
+ * (`2026-07-06T00:00:00Z`); a plain `date` field keeps the bare `YYYY-MM-DD`.
475
+ * An empty day clears the value.
476
+ */
477
+ declare function toStoredDateValue(day: string, datetime: boolean): string | undefined;
478
+
479
+ /** The message keys this module renders, relative to the caller's namespace
480
+ * (the app passes its `method.editor` translator). */
481
+ type ValidationMessageKey = 'inputPanel.aDateField' | 'inputPanel.pickValidDate' | 'inputPanel.pickValidDateEmpty' | 'inputPanel.dateCarriesTime' | 'inputPanel.invalidValue' | 'inputPanel.invalidValueWithData';
482
+ /**
483
+ * The translate function the caller injects. Typed on the EXACT keys this
484
+ * module uses - not a loose `(key: string) => string` - for two reasons: a
485
+ * typo'd key still fails the build here, and next-intl's translator (whose
486
+ * `key` parameter is the namespace's real key union, a SUPERSET of these)
487
+ * remains assignable by parameter contravariance. Kernel-owned: this module
488
+ * must not import next-intl's types.
489
+ */
490
+ type Translate = (key: ValidationMessageKey, values?: Record<string, string>) => string;
491
+ /**
492
+ * The value the validator actually rejected, read back out of the submitted data.
493
+ *
494
+ * RJSF builds `property` from ajv's `instancePath` by swapping `/` for `.`
495
+ * (`.quote_date.date`), so the segments index straight into the form data.
496
+ * Array indices arrive as digit strings and index arrays natively.
497
+ */
498
+ declare function valueAtProperty(data: unknown, property: string | undefined): unknown;
499
+ /**
500
+ * The rejected value rendered for a toast, or `null` when there is nothing
501
+ * worth showing (an absent or blank value - "you left it empty" is what the
502
+ * message says on its own, and `“”` adds noise).
503
+ */
504
+ declare function displayValue(value: unknown): string | null;
505
+ /**
506
+ * One validation error as a human line naming the offending value.
507
+ *
508
+ * The date branch is special-cased because ajv's own complaint (`'Date' must
509
+ * match format "date-time"`) is opaque, and because the carries-a-time case has
510
+ * a specific fix the user cannot guess: move the time into the sibling `time`
511
+ * field, which is exactly what the runtime's own error says.
512
+ */
513
+ declare function describeValidationError(error: RunInputError, t: Translate, formData: unknown): string;
514
+
515
+ /**
516
+ * The ONE place JSON-Schema plumbing lives for the run form kernel: nullable
517
+ * `anyOf` collapsing, `$defs` collection/hoisting, `$ref` resolution, and type
518
+ * extraction. Consolidated from three drifted private copies (`field-model.ts`,
519
+ * `normalize-schema.ts`, `input-format.ts`) during the form-kernel extraction.
520
+ *
521
+ * Two collapse engines are exported ON PURPOSE - their semantics genuinely
522
+ * differ, and each caller's observable behavior is characterized and preserved
523
+ * (see `schema-utils-characterization.test.ts`):
524
+ *
525
+ * - `collapseNullable` (the field mapper's rule): collapse ONLY when exactly one
526
+ * non-null branch remains, dropping the null. A multi-branch union stays an
527
+ * `anyOf` and maps to the raw-JSON escape hatch.
528
+ * - `flattenAnyOf` (the RJSF/ajv rule): a union of simple primitives becomes a
529
+ * `type` ARRAY (null preserved as a type entry - ajv validates `type:
530
+ * ["number","null"]` natively); a 2-branch nullable non-primitive drops the
531
+ * null branch.
532
+ *
533
+ * Whether these converge (and which semantics win) is an M1 question, decided
534
+ * when the descriptor derivation moves server-side - not silently here.
535
+ */
536
+ type JsonSchema = Record<string, unknown>;
537
+ /** Pydantic emits `anyOf: [T, {type:'null'}]` for Optional[T]; collapse to T.
538
+ * Collapses ONLY when exactly one non-null branch remains; the branch merges
539
+ * OVER the outer schema (its constraints win). */
540
+ declare function collapseNullable(schema: JsonSchema): JsonSchema;
541
+ /**
542
+ * The RJSF/ajv collapse: a union of simple primitives flattens to a `type`
543
+ * array (`anyOf: [{string},{null}]` → `type: ["string","null"]`, which RJSF
544
+ * renders as a plain input instead of a sub-schema picker); an exactly-two
545
+ * `[T, {null}]` union of anything else drops the null branch and merges T over
546
+ * the outer schema. Anything else passes through unchanged.
547
+ */
548
+ declare function flattenAnyOf(schema: JsonSchema): JsonSchema;
549
+ /** The schema's non-null type: a bare `type` string, or the first non-null
550
+ * entry of a `type` array (`["number","null"]` → `"number"`). */
551
+ declare function schemaTypeOf(schema: JsonSchema): string | undefined;
552
+ /** Resolve `{ $ref: "#/$defs/Foo" }` against a collected `$defs` map. Sibling
553
+ * keys on the referencing schema override the definition's. */
554
+ declare function derefSchema(schema: JsonSchema, defs: Record<string, JsonSchema>): JsonSchema;
555
+ /**
556
+ * Resolve the indirection layers pydantic puts in front of a value schema - a
557
+ * `#/$defs/...` ref, a nullable `anyOf: [T, {null}]` wrapper, or a stack of
558
+ * both - until a plain schema remains. This is the resolver `healStringWrappers`
559
+ * and `pruneEmptyOptionals` apply before reading `type`. Unlike `derefSchema`
560
+ * it REPLACES the schema with the definition (no sibling merge) - exactly what
561
+ * the callers did inline.
562
+ */
563
+ declare function resolveSchemaIndirection(schema: JsonSchema, defs: Record<string, JsonSchema>): JsonSchema;
564
+ interface CollectDefsOptions {
565
+ /**
566
+ * Whether the walk descends into JSON arrays (`anyOf`/`allOf` branches,
567
+ * tuple `items`). The field mapper always has (so a def declared inside an
568
+ * `anyOf` branch resolves); the wire-format heal path historically has NOT -
569
+ * both behaviors are characterized, and each caller states its mode
570
+ * explicitly until M1 retires the difference.
571
+ */
572
+ traverseArrays: boolean;
573
+ }
574
+ /**
575
+ * Collect every nested `$defs` entry into one flat map so `#/$defs/...` refs
576
+ * resolve from anywhere. First definition of a name wins. The input schema is
577
+ * NOT modified (see `hoistDefsToRoot` for the stripping variant).
578
+ */
579
+ declare function collectSchemaDefs(schema: JsonSchema, into: Record<string, JsonSchema>, options: CollectDefsOptions): Record<string, JsonSchema>;
580
+ /**
581
+ * Hoist every nested `$defs` to the schema root, so a wrapper schema composed
582
+ * from several pydantic sub-schemas keeps its `#/$defs/...` refs resolvable.
583
+ * (Pydantic emits one `$defs` per model; composing models as properties of a
584
+ * wrapper object leaves those maps below the root the refs point to.)
585
+ */
586
+ declare function hoistDefsToRoot(schema: JsonSchema): JsonSchema;
587
+
588
+ /**
589
+ * Normalizes a JSON Schema for RJSF compatibility.
590
+ *
591
+ * Pydantic v2 generates `anyOf: [{type: "string"}, {type: "null"}]` for optional
592
+ * fields (`str | None`). RJSF renders this as a dropdown selector asking the user
593
+ * to pick a sub-schema, which is terrible UX. The normalizer flattens those
594
+ * patterns on every node (see `flattenAnyOf` in `schema-utils.ts` for the exact
595
+ * rules) and hoists nested `$defs` to the root so composed sub-schemas keep
596
+ * their refs resolvable.
597
+ *
598
+ * The collapse and hoist engines live in `schema-utils.ts` - the
599
+ * consolidated home for the kernel's JSON-Schema plumbing; this module owns the
600
+ * recursive application over a whole schema tree.
601
+ */
602
+
603
+ /**
604
+ * Recursively normalize a JSON Schema for RJSF.
605
+ * Walks the entire schema tree and flattens anyOf patterns on every node.
606
+ */
607
+ declare function normalizeSchemaForRjsf(schema: Record<string, unknown>): Record<string, unknown>;
608
+ /**
609
+ * Prepare a schema for RJSF: hoist all nested `$defs` to the root, then normalize
610
+ * anyOf patterns. Call this on any schema that composes multiple sub-schemas
611
+ * (each of which may ship its own `$defs`).
612
+ */
613
+ declare function prepareSchemaForRjsf(schema: Record<string, unknown>): Record<string, unknown>;
614
+
615
+ export { type CollectDefsOptions, type JsonSchema, type OutputEntry, type PipeIOContract, type PipeIOContracts, type PipeInputContract, type PipeOutputContract, type Readiness, RunField, type RunInputError, type RunInputsVerdict, type Translate, type ValidationMessageKey, apiInputsFromRunValues, apiInputsFromSchemaData, asCalendarDate, buildPipeRef, buildRunFields, buildRunInputsSchema, collapseNullable, collectSchemaDefs, computeReadiness, dayWithinDateTime, deflateAllInputs, deflateInput, derefSchema, describeValidationError, displayValue, fieldFilled, fieldsForContract, flattenAnyOf, getPipeIOContract, healStringWrappers, hoistDefsToRoot, inflateAllInputs, inflateInput, inputDataFromWorkingMemory, inputMustBeFilled, isAcceptableDate, isAcceptableDateTime, isFilled, isPluralInput, mustBeFilled, normalizeSchemaForRjsf, outputsFromPipeOutput, prepareRunInputs, prepareSchemaForRjsf, pruneEmptyOptionals, resolveConceptCode, resolveSchemaIndirection, rjsfDataFromRunValues, runValuesFromStore, schemaTypeOf, setValueAtPath, storeInputDataFromRunValues, toDateInputValue, toStoredDateValue, validateRunInputs, validateRunInputsSchema, valueAtProperty };
@@ -0,0 +1,3 @@
1
+ export { apiInputsFromRunValues, apiInputsFromSchemaData, asCalendarDate, buildPipeRef, buildRunFields, buildRunInputsSchema, collapseNullable, collectSchemaDefs, computeReadiness, conceptCategory, dayWithinDateTime, deflateAllInputs, deflateInput, derefSchema, describeValidationError, displayValue, fieldFilled, fieldsForContract, flattenAnyOf, getPipeIOContract, healStringWrappers, hoistDefsToRoot, inflateAllInputs, inflateInput, inputDataFromWorkingMemory, inputMustBeFilled, isAcceptableDate, isAcceptableDateTime, isFilled, isPluralInput, mustBeFilled, normalizeSchemaForRjsf, outputsFromPipeOutput, prepareRunInputs, prepareSchemaForRjsf, pruneEmptyOptionals, resolveConceptCode, resolveSchemaIndirection, rjsfDataFromRunValues, runValuesFromStore, schemaTypeOf, setValueAtPath, storeInputDataFromRunValues, toDateInputValue, toStoredDateValue, validateRunInputs, validateRunInputsSchema, valueAtProperty } from '../chunk-DAP47OGP.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}