@pipelex/mthds-form 0.3.0 → 0.5.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 (81) hide show
  1. package/README.md +21 -7
  2. package/dist/chunk-25I6JDIK.js +28 -0
  3. package/dist/chunk-25I6JDIK.js.map +1 -0
  4. package/dist/chunk-5Q2W6HBA.js +201 -0
  5. package/dist/chunk-5Q2W6HBA.js.map +1 -0
  6. package/dist/chunk-GCXV76WU.js +213 -0
  7. package/dist/chunk-GCXV76WU.js.map +1 -0
  8. package/dist/chunk-GKV77RAS.js +46 -0
  9. package/dist/chunk-GKV77RAS.js.map +1 -0
  10. package/dist/chunk-IIJRJGZQ.js +128 -0
  11. package/dist/chunk-IIJRJGZQ.js.map +1 -0
  12. package/dist/chunk-IM2AHWBR.js +99 -0
  13. package/dist/chunk-IM2AHWBR.js.map +1 -0
  14. package/dist/chunk-LBFZXYCY.js +44 -0
  15. package/dist/chunk-LBFZXYCY.js.map +1 -0
  16. package/dist/chunk-OD3VYXUY.js +156 -0
  17. package/dist/chunk-OD3VYXUY.js.map +1 -0
  18. package/dist/chunk-PM55AHRO.js +52 -0
  19. package/dist/chunk-PM55AHRO.js.map +1 -0
  20. package/dist/chunk-RPA7DNSK.js +41 -0
  21. package/dist/chunk-RPA7DNSK.js.map +1 -0
  22. package/dist/chunk-V3OV2XQT.js +12 -0
  23. package/dist/chunk-V3OV2XQT.js.map +1 -0
  24. package/dist/chunk-VTPUDBSJ.js +61 -0
  25. package/dist/chunk-VTPUDBSJ.js.map +1 -0
  26. package/dist/chunk-XYQ7NXQZ.js +31 -0
  27. package/dist/chunk-XYQ7NXQZ.js.map +1 -0
  28. package/dist/chunk-YEVVR6FE.js +66 -0
  29. package/dist/chunk-YEVVR6FE.js.map +1 -0
  30. package/dist/core/contracts.d.ts +114 -0
  31. package/dist/core/contracts.js +4 -0
  32. package/dist/core/contracts.js.map +1 -0
  33. package/dist/core/date-format.d.ts +87 -0
  34. package/dist/core/date-format.js +3 -0
  35. package/dist/core/date-format.js.map +1 -0
  36. package/dist/core/derive.d.ts +55 -0
  37. package/dist/core/derive.js +6 -0
  38. package/dist/core/derive.js.map +1 -0
  39. package/dist/core/descriptor.d.ts +172 -0
  40. package/dist/core/descriptor.js +3 -0
  41. package/dist/core/descriptor.js.map +1 -0
  42. package/dist/core/gate-validator.d.ts +27 -0
  43. package/dist/core/gate-validator.js +4 -0
  44. package/dist/core/gate-validator.js.map +1 -0
  45. package/dist/core/gate.d.ts +144 -0
  46. package/dist/core/gate.js +12 -0
  47. package/dist/core/gate.js.map +1 -0
  48. package/dist/core/gating-fields.d.ts +38 -0
  49. package/dist/core/gating-fields.js +6 -0
  50. package/dist/core/gating-fields.js.map +1 -0
  51. package/dist/core/index.d.ts +13 -623
  52. package/dist/core/index.js +14 -1
  53. package/dist/core/normalize-schema.d.ts +30 -0
  54. package/dist/core/normalize-schema.js +4 -0
  55. package/dist/core/normalize-schema.js.map +1 -0
  56. package/dist/core/own-property.d.ts +46 -0
  57. package/dist/core/own-property.js +3 -0
  58. package/dist/core/own-property.js.map +1 -0
  59. package/dist/core/readiness.d.ts +91 -0
  60. package/dist/core/readiness.js +4 -0
  61. package/dist/core/readiness.js.map +1 -0
  62. package/dist/core/schema-utils.d.ts +86 -0
  63. package/dist/core/schema-utils.js +3 -0
  64. package/dist/core/schema-utils.js.map +1 -0
  65. package/dist/core/validation-message.d.ts +39 -0
  66. package/dist/core/validation-message.js +4 -0
  67. package/dist/core/validation-message.js.map +1 -0
  68. package/dist/core/values.d.ts +98 -0
  69. package/dist/core/values.js +10 -0
  70. package/dist/core/values.js.map +1 -0
  71. package/dist/core/wire-format.d.ts +95 -0
  72. package/dist/core/wire-format.js +6 -0
  73. package/dist/core/wire-format.js.map +1 -0
  74. package/dist/react/index.d.ts +43 -2
  75. package/dist/react/index.js +166 -64
  76. package/dist/react/index.js.map +1 -1
  77. package/dist/styles.css +1 -1
  78. package/package.json +7 -1
  79. package/dist/chunk-S6KOTTGD.js +0 -990
  80. package/dist/chunk-S6KOTTGD.js.map +0 -1
  81. package/dist/descriptor-DPrrfAjL.d.ts +0 -101
@@ -0,0 +1,114 @@
1
+ import { PresenceMarker, PipeIOContracts, PipeIOContract, PipeInputContract } from 'mthds/protocol';
2
+ export { IOMultiplicity, PipeIOContract, PipeIOContracts, PipeInputContract, PipeOutputContract } from 'mthds/protocol';
3
+
4
+ /**
5
+ * The `pipe_io_contracts` types and the kernel's gating predicates over them.
6
+ *
7
+ * The types are the STANDARD'S, imported from `mthds/protocol` and re-exported
8
+ * under the names this package has always exported. `pipe_io_contracts` is an
9
+ * MTHDS artifact - the standard specifies it (the "Pipe I/O Contracts" page)
10
+ * and its TypeScript client declares it - so this module states no shape of its
11
+ * own any more. What it keeps is the half that is genuinely the kernel's: the
12
+ * predicates that turn a declared slot into a gating answer, and the lookup
13
+ * that tolerates both key conventions a real payload arrives with.
14
+ *
15
+ * `mthds` is a TYPES-ONLY peer dependency: every import here is `import type`,
16
+ * so nothing survives into `dist/` and no consumer ships the standard's CLI.
17
+ * `scripts/assert-bundle.mjs` holds that on the built graph and lint holds it
18
+ * on the source. See docs/dependency-budget.md.
19
+ *
20
+ * The map itself is keyed by namespaced pipe ref (`<domain>.<pipe_code>`),
21
+ * NOT bare pipe code - use `getPipeIOContract` to look entries up by the bare
22
+ * codes the UI tracks.
23
+ */
24
+
25
+ /**
26
+ * The authored presence marker on an input slot, verbatim - the standard's
27
+ * `PresenceMarker` under the name this package exports.
28
+ *
29
+ * - `plain` - no marker: the caller must supply the input.
30
+ * - `optional` (`?`) - the caller may omit it and the pipe handles the absence
31
+ * itself.
32
+ * - `force` (`!`) - a use-site assertion that the value IS present. It must be
33
+ * supplied, exactly like `plain`; the distinction is the authored assertion,
34
+ * which lint and graph surfaces read and a form does not.
35
+ *
36
+ * Kept as an alias rather than renamed because a consumer imports it by this
37
+ * name; a host that would rather read the standard's spelling has `mthds`
38
+ * installed through this package's peer and can import `PresenceMarker`
39
+ * straight from `mthds/protocol`.
40
+ */
41
+ type InputPresence = PresenceMarker;
42
+ /** Namespaced pipe ref (`<domain>.<pipe_code>`) - the identity convention the
43
+ * validate artifacts key on. */
44
+ declare function buildPipeRef(domain: string, pipeCode: string): string;
45
+ /**
46
+ * Look up a pipe's IO contract by pipe code, tolerant of both key conventions.
47
+ *
48
+ * The map may be keyed by namespaced `pipe_ref` (`<domain>.<code>`, the
49
+ * canonical convention) OR by bare pipe code - the current hosted `/validate`
50
+ * keys `pipe_structures` by bare code, and methods persisted before the
51
+ * pipe_ref re-keying also carry bare-code keys. Try the namespaced ref first
52
+ * (when the caller supplies a domain), then fall back to the bare code.
53
+ * Returns `undefined` (never crashes) when neither matches.
54
+ *
55
+ * Both lookups read OWN properties only. A bare index returned the inherited
56
+ * `Object` constructor for a pipe code named `constructor` (or `toString`, …) -
57
+ * a truthy non-contract that sailed straight through the `if (!contract)` guard
58
+ * hosts are shown writing, so a pipe that does not exist rendered as one taking
59
+ * no inputs instead of reaching the host's not-found path. See `ownProp`.
60
+ */
61
+ declare function getPipeIOContract(pipeIoContracts: PipeIOContracts | null | undefined, domain: string | null | undefined, pipeCode: string | null | undefined): PipeIOContract | undefined;
62
+ /**
63
+ * True when the method declared the input omittable (`?`).
64
+ *
65
+ * The ONE reader of `presence` in the package: everything that asks "may this
66
+ * be absent?" - the descriptor's `required`, the gate's `required` list, the
67
+ * payload builders that omit an unfilled input - goes through here, so a
68
+ * marker the wire adds later cannot mean one thing on one surface and another
69
+ * elsewhere. `force` (`!`) reads as NOT optional, like `plain`.
70
+ */
71
+ declare function isOptionalInput(input: PipeInputContract): boolean;
72
+ /**
73
+ * True when the input carries a plural concept (`Image[]`, `Image[3]`): its
74
+ * JSON Schema is an array wrapper.
75
+ *
76
+ * Deliberately a SCHEMA test rather than a read of the declared `multiplicity`,
77
+ * even though the wire now states both. The schema is what `buildRunFields`
78
+ * maps and therefore what the user is shown; a predicate reading the other
79
+ * field could disagree with the rendered control, which is the exact failure
80
+ * mode `mustBeFilled` (readiness.ts) exists to prevent. The two agree by
81
+ * construction upstream - the engine wraps the schema in an array exactly when
82
+ * it reports `variable` or `fixed`.
83
+ */
84
+ declare function isPluralInput(input: PipeInputContract): boolean;
85
+ /**
86
+ * True when the slot declares an EXACT number of items (`Concept[N]`), whose
87
+ * count is `item_count`.
88
+ *
89
+ * The distinction that matters to the gate: a variable list can legitimately be
90
+ * empty, a fixed one cannot.
91
+ */
92
+ declare function isFixedCountInput(input: PipeInputContract): boolean;
93
+ /**
94
+ * True when the user MUST put a value in before a run can start.
95
+ *
96
+ * Two declarations relieve an input of that duty, for different reasons:
97
+ * - **optional (`?`)** - the method itself states the caller may omit it;
98
+ * - **variable-plural (`[]`)** - the empty list is a legitimate value and the
99
+ * language cannot declare "at least one", so gating on it would invent a
100
+ * constraint the method never made.
101
+ *
102
+ * Everything else gates: `plain`, `force` (`!`), and a **fixed-count** list
103
+ * (`[N]`), which is a list whose empty form the method has explicitly ruled out.
104
+ * This is the derivation the input-form descriptor spec states for `gating`.
105
+ *
106
+ * Emptiness is all this predicate answers for - WHETHER a slot gates, never how
107
+ * much it needs. A fixed-count list that holds fewer items than it declares
108
+ * gates here and is refused by `fieldFilled`, which reads the declared count off
109
+ * the descriptor's `itemCount`, and by the gate's ajv pass on the schema's
110
+ * `minItems`. All three read the one number the method stated.
111
+ */
112
+ declare function inputMustBeFilled(input: PipeInputContract): boolean;
113
+
114
+ export { type InputPresence, buildPipeRef, getPipeIOContract, inputMustBeFilled, isFixedCountInput, isOptionalInput, isPluralInput };
@@ -0,0 +1,4 @@
1
+ export { buildPipeRef, getPipeIOContract, inputMustBeFilled, isFixedCountInput, isOptionalInput, isPluralInput } from '../chunk-XYQ7NXQZ.js';
2
+ import '../chunk-V3OV2XQT.js';
3
+ //# sourceMappingURL=contracts.js.map
4
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"contracts.js"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Pure conversions between a stored date value and what a native `<input
3
+ * type="date">` shows, plus the leniency rules our schema validation uses.
4
+ * Kept React-free so both the runner's `DateField` and the RJSF `DateWidget`
5
+ * share one source of truth (and it's trivially unit-tested).
6
+ *
7
+ * Two schema shapes reach us, and they are NOT interchangeable:
8
+ * - an MTHDS `type = "date"` structure field compiles to `format: date-time`,
9
+ * so its value round-trips as a full RFC 3339 timestamp;
10
+ * - `native.Date`'s own `date` field is a python `datetime.date`, i.e.
11
+ * `format: date` - a bare `YYYY-MM-DD`.
12
+ *
13
+ * ## Why these rules exist
14
+ *
15
+ * The runtime (pydantic) is LENIENT in both directions, and our client used to
16
+ * be stricter than the contract it was enforcing: `ajv`'s stock `format: "date"`
17
+ * accepts a bare `YYYY-MM-DD` and nothing else, so a hand-written
18
+ * `"2026-07-06T00:00:00Z"` in an `inputs.json` was rejected here even though
19
+ * pydantic accepts it happily and coerces it to `date(2026, 7, 6)`. The run
20
+ * never even reached the runner. These predicates mirror pydantic exactly
21
+ * (measured, not assumed):
22
+ *
23
+ * format: date "2026-07-06" -> date(2026, 7, 6)
24
+ * "2026-07-06T00:00:00Z" -> date(2026, 7, 6)
25
+ * "2026-07-06T00:00:00+02:00" -> date(2026, 7, 6) (literal date, no UTC shift)
26
+ * "2026-07-06T15:40:00Z" -> REJECTED "should have zero time"
27
+ * format: date-time "2026-07-06" -> datetime(2026, 7, 6, 0, 0)
28
+ * "2026-07-06T00:00:00Z" -> datetime(2026, 7, 6, 0, 0, tz)
29
+ *
30
+ * A midnight-padded timestamp is unambiguously "just a day", so take it. A real
31
+ * time is data - refusing it is the point (`DateContentError`: "no silent
32
+ * midnight"), because silently dropping 15:40 corrupts the value. And an
33
+ * ambiguous `06/07/2026` is never accepted by anyone: 6 July or June 7 depends
34
+ * on who's reading, and guessing wrong fails silently.
35
+ */
36
+ /**
37
+ * The calendar day a value denotes, or `null` when it doesn't denote one.
38
+ * Accepts a bare date or a midnight-padded timestamp; the day is taken
39
+ * LITERALLY from the string (never shifted through a timezone), matching
40
+ * pydantic - `2026-07-06T00:00:00+02:00` is 2026-07-06, not the 5th.
41
+ */
42
+ declare function asCalendarDate(value: string): string | null;
43
+ /**
44
+ * The calendar day inside a value that carries a REAL time - precisely the case
45
+ * `asCalendarDate` refuses. `null` when the value isn't a timestamp at all, or
46
+ * when it IS already an acceptable day (nothing to report).
47
+ *
48
+ * This exists to EXPLAIN, never to coerce. `2026-07-06T14:30:00+02:00` is
49
+ * rejected for a `format: date` field, and the rejection is right (DT3, "no
50
+ * silent midnight" - dropping 14:30 corrupts the value). But telling the user
51
+ * "pick a valid date" while the picker shows them a perfectly good 06/07/2026
52
+ * reads as a lie: `toDateInputValue` renders the day, the stored value carries
53
+ * the time, and only one of those is what gets validated. Naming both the value
54
+ * we hold and the day it would have to become is what makes the error legible.
55
+ */
56
+ declare function dayWithinDateTime(value: string): string | null;
57
+ /**
58
+ * What `format: "date"` accepts: a bare `YYYY-MM-DD`, and nothing else.
59
+ *
60
+ * DO NOT relax this to take a midnight-padded timestamp. `DateContent` in
61
+ * pipelex carries a `mode="before"` field validator (`_reject_lax_temporal`)
62
+ * that deliberately CLOSES pydantic's lax coercion for exactly this case:
63
+ *
64
+ * "A Date's `date` field takes a calendar date alone; a string carrying a
65
+ * time (e.g. '2026-07-07T00:00:00') would drop it - put the time and offset
66
+ * in `time` instead."
67
+ *
68
+ * That rule (DT3, "no silent midnight") is the concept's whole point, so a
69
+ * lenient client here does not help anyone: it just forwards the value and the
70
+ * runner answers with a 500 instead of the field saying so up front.
71
+ *
72
+ * The leniency belongs in `asCalendarDate` - normalize the pasted timestamp to
73
+ * the day it denotes, then this passes. Accept-and-fix, not accept-and-forward.
74
+ */
75
+ declare function isAcceptableDate(value: string): boolean;
76
+ /** What `format: "date-time"` should accept - a bare day is midnight. */
77
+ declare function isAcceptableDateTime(value: string): boolean;
78
+ /** The `YYYY-MM-DD` a native date input shows, from any stored date/date-time. */
79
+ declare function toDateInputValue(value: string | undefined): string;
80
+ /**
81
+ * The value to store for a picked day. A `date-time` field becomes midnight-UTC
82
+ * (`2026-07-06T00:00:00Z`); a plain `date` field keeps the bare `YYYY-MM-DD`.
83
+ * An empty day clears the value.
84
+ */
85
+ declare function toStoredDateValue(day: string, datetime: boolean): string | undefined;
86
+
87
+ export { asCalendarDate, dayWithinDateTime, isAcceptableDate, isAcceptableDateTime, toDateInputValue, toStoredDateValue };
@@ -0,0 +1,3 @@
1
+ export { asCalendarDate, dayWithinDateTime, isAcceptableDate, isAcceptableDateTime, toDateInputValue, toStoredDateValue } from '../chunk-GKV77RAS.js';
2
+ //# sourceMappingURL=date-format.js.map
3
+ //# sourceMappingURL=date-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"date-format.js"}
@@ -0,0 +1,55 @@
1
+ import { PipeInputFormDescriptor, PipeInputContract, InputForm } from 'mthds/protocol';
2
+ import { RunField } from './descriptor.js';
3
+ import './contracts.js';
4
+
5
+ /**
6
+ * The ONE derivation function: wire input-form descriptor → `RunField[]`.
7
+ *
8
+ * `buildRunFields` maps the standard's per-pipe descriptor STRUCTURALLY onto
9
+ * the kernel's `RunField` shape. It decides nothing: what a field IS - its
10
+ * kind, its constraints, its refinement chain, whether Run gates on it - is
11
+ * stated by the wire, derived by the engine from the resolved library. The
12
+ * heuristics that used to live here (native-concept sets, the url-bearing
13
+ * object test, the depth-based prose/label rule) are deleted, not bypassed;
14
+ * docs/derivation-swap.md is the record of that swap and of every behaviour
15
+ * that moved with it.
16
+ *
17
+ * The contract is still consulted, for exactly two facts the wire deliberately
18
+ * does not carry:
19
+ *
20
+ * - **`contentKey`** - the single property a native scalar's value sits inside
21
+ * on the wire (`TextContent {text}`, `NumberContent {number}`). A renderer
22
+ * holds the bare scalar; the value bridge wraps by this name. It is read off
23
+ * the contract's `json_schema` because it is a fact about the PAYLOAD shape,
24
+ * which is the schema's job - the descriptor describes the form, never the
25
+ * payload.
26
+ * - **Nested list bounds** - the wire puts `item_count` only on a top-level
27
+ * fixed `[N]` slot, but the model behind a structured concept may state
28
+ * `minItems`/`maxItems` on an array property of its own, and ajv enforces
29
+ * them. Reading them off the schema is what keeps readiness from being more
30
+ * permissive than the gate.
31
+ *
32
+ * Both are structural reads (a property count, two keywords ajv itself
33
+ * enforces), not judgements - the mapper stays honest about owning no taxonomy.
34
+ */
35
+
36
+ /**
37
+ * Look up a pipe's input-form descriptor by pipe code, tolerant of both key
38
+ * conventions - the exact mirror of `getPipeIOContract`, because the standard
39
+ * keys `input_form` by the same `pipe_ref` set as `pipe_io_contracts` and a
40
+ * host tracks the same bare codes either way.
41
+ */
42
+ declare function getPipeInputForm(inputForm: InputForm | null | undefined, domain: string | null | undefined, pipeCode: string | null | undefined): PipeInputFormDescriptor | undefined;
43
+ /**
44
+ * Map a pipe's wire input-form descriptor onto the kernel's field descriptors,
45
+ * with the pipe's input contracts beside it for the two schema-derived facts.
46
+ *
47
+ * The order is the DESCRIPTOR's - authored input order, the fact the contract's
48
+ * `inputs` map deliberately does not carry. `required`, `presence` and `gating`
49
+ * are taken from the wire verbatim: the standard's top-level type ties
50
+ * `required` to the presence marker, and `gating` is the producer-derived
51
+ * answer `mustBeFilled` reads - the kernel re-derives none of them.
52
+ */
53
+ declare function buildRunFields(descriptor: PipeInputFormDescriptor, inputs: Record<string, PipeInputContract>): RunField[];
54
+
55
+ export { buildRunFields, getPipeInputForm };
@@ -0,0 +1,6 @@
1
+ export { buildRunFields, getPipeInputForm } from '../chunk-IIJRJGZQ.js';
2
+ import '../chunk-OD3VYXUY.js';
3
+ import '../chunk-XYQ7NXQZ.js';
4
+ import '../chunk-V3OV2XQT.js';
5
+ //# sourceMappingURL=derive.js.map
6
+ //# sourceMappingURL=derive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"derive.js"}
@@ -0,0 +1,172 @@
1
+ import { InputPresence } from './contracts.js';
2
+ import 'mthds/protocol';
3
+
4
+ /**
5
+ * The `RunField` descriptor - the consumer-facing currency of the form kernel.
6
+ *
7
+ * A method's inputs arrive from the API as two sibling artifacts: the wire
8
+ * input-form descriptor (`input_form`, the ordered presentation view the MTHDS
9
+ * standard specifies) and the `pipe_io_contracts` entry whose `json_schema`
10
+ * stays the validation contract. `buildRunFields` (in `derive.ts`) maps the
11
+ * wire descriptor structurally onto this `RunField[]` shape - so the
12
+ * presentational field components never touch either wire artifact directly,
13
+ * and stories can describe any input shape declaratively.
14
+ *
15
+ * Pure module: no React, no API calls. Trivially unit-testable.
16
+ */
17
+
18
+ type RunFieldKind = 'text' | 'prose' | 'date' | 'number' | 'boolean' | 'enum' | 'document' | 'image' | 'object' | 'list' | 'unknown';
19
+ interface RunFieldCommon {
20
+ /** The variable identifier as written in the method (rendered in mono). */
21
+ name: string;
22
+ /** Human label; falls back to `name` when absent. */
23
+ title?: string;
24
+ /** The concept this input carries, e.g. `native.Document`, `demo.Invoice`. */
25
+ conceptRef?: string;
26
+ /**
27
+ * The concept's refinement chain, immediate parent first, walked to its end
28
+ * (`['legal.BaseClause', 'native.Text']`). Straight off the wire descriptor;
29
+ * absent when the concept refines nothing. "Does this refine `native.X`?" is
30
+ * a membership test on this list, never shape sniffing.
31
+ */
32
+ refines?: string[];
33
+ /** One-line helper text shown under the label. */
34
+ description?: string;
35
+ /** Required inputs always show; optional ones can collapse. */
36
+ required: boolean;
37
+ /**
38
+ * The authored presence marker of the pipe's input slot, verbatim - set on
39
+ * TOP-LEVEL fields only, like `gating`. `required` is its two-valued
40
+ * projection (`presence !== 'optional'`); the marker itself is carried so a
41
+ * renderer can distinguish the authored `!` assertion from a plain slot.
42
+ */
43
+ presence?: InputPresence;
44
+ /**
45
+ * The value applied when the caller omits the field - present only when a
46
+ * default was AUTHORED, never the `null` a schema projection attaches to an
47
+ * optional field. A defaulted field always arrives `required: false`.
48
+ */
49
+ defaultValue?: unknown;
50
+ /** Example values for the field, when the wire carries any. */
51
+ examples?: unknown[];
52
+ /**
53
+ * The node's effective intent hints - the flat `string → string` merge the
54
+ * language defines, carried verbatim. Non-normative: a renderer that ignores
55
+ * them stays correct, and nothing in the kernel's gating reads them.
56
+ */
57
+ hints?: Record<string, string>;
58
+ /**
59
+ * Whether Run waits for this input - set on TOP-LEVEL fields only, straight
60
+ * from `inputMustBeFilled` (see `mustBeFilled` in `readiness.ts` for why this
61
+ * is not the same question as `required`). Nested fields leave it undefined;
62
+ * they are gated through their parent's `fieldFilled`.
63
+ */
64
+ gating?: boolean;
65
+ /**
66
+ * The single property this field's value sits inside on the wire, when its
67
+ * concept declares a scalar content model: `native.Text` is `TextContent
68
+ * {text}`, `native.Number` is `NumberContent {number}`, `native.YesNo` is
69
+ * `YesNoContent {yes_no}`. The control still holds the plain scalar; the
70
+ * value bridge wraps into this property on the way out and unwraps on the way
71
+ * back in.
72
+ *
73
+ * A property NAME, never a schema - the descriptor stays the only currency
74
+ * and `buildRunFields` stays the only reader of JSON Schema. Absent whenever
75
+ * the value is already the whole content (a structured concept, a file, a
76
+ * child property of a structure): wrapping those would double-nest them.
77
+ */
78
+ contentKey?: string;
79
+ }
80
+ /**
81
+ * The constraint slots the two text kinds share, mapped from the wire's
82
+ * `min_length` / `max_length` / `pattern` / `format`. `format` is the open
83
+ * string set carrying schema formats the `date` kind does not absorb
84
+ * (`'time'`, `'uri'`, …); a renderer may hint on it and ignore it safely.
85
+ */
86
+ interface TextConstraints {
87
+ minLength?: number;
88
+ maxLength?: number;
89
+ pattern?: string;
90
+ format?: string;
91
+ }
92
+ interface TextRunField extends RunFieldCommon, TextConstraints {
93
+ kind: 'text';
94
+ placeholder?: string;
95
+ }
96
+ interface ProseRunField extends RunFieldCommon, TextConstraints {
97
+ kind: 'prose';
98
+ placeholder?: string;
99
+ }
100
+ interface DateRunField extends RunFieldCommon {
101
+ kind: 'date';
102
+ /**
103
+ * The wire's `datetime` flag - true when the value carries a time of day
104
+ * (the backing schema is `format: date-time`): the stored value is a full
105
+ * RFC 3339 timestamp (`2026-07-06T00:00:00Z`) so it passes the API's
106
+ * `date-time` validation, even though the control only asks for a day. False
107
+ * for a bare calendar date, where the value stays `YYYY-MM-DD`.
108
+ */
109
+ datetime: boolean;
110
+ }
111
+ interface NumberRunField extends RunFieldCommon {
112
+ kind: 'number';
113
+ integer: boolean;
114
+ min?: number;
115
+ max?: number;
116
+ }
117
+ interface BooleanRunField extends RunFieldCommon {
118
+ kind: 'boolean';
119
+ }
120
+ interface EnumRunField extends RunFieldCommon {
121
+ kind: 'enum';
122
+ options: string[];
123
+ }
124
+ interface FileRunField extends RunFieldCommon {
125
+ kind: 'document' | 'image';
126
+ /** Accept hint shown to the user, e.g. "PDF, PNG, JPG". */
127
+ accept?: string;
128
+ }
129
+ interface ObjectRunField extends RunFieldCommon {
130
+ kind: 'object';
131
+ fields: RunField[];
132
+ }
133
+ interface ListRunField extends RunFieldCommon {
134
+ kind: 'list';
135
+ /** The element descriptor (its `name` is unused; the index labels items). */
136
+ item: RunField;
137
+ /**
138
+ * The FEWEST items the slot accepts. Absent for a variable `Concept[]` list,
139
+ * which demands none.
140
+ *
141
+ * On a top-level slot this is the wire descriptor's `item_count` (present
142
+ * exactly on a fixed `[N]` slot); an engine emits `minItems`/`maxItems` = N
143
+ * on the same slot's schema, so the number `fieldFilled` gates on and the
144
+ * keyword ajv reads agree by construction.
145
+ *
146
+ * The wire never puts `item_count` on a NESTED list, but the model behind a
147
+ * structured concept may state `minItems` on an array property of its own -
148
+ * so on nested nodes this is read off the schema, which is the direction
149
+ * that cannot leave readiness more permissive than the gate.
150
+ */
151
+ itemCount?: number;
152
+ /**
153
+ * The MOST items the slot accepts, off the schema's `maxItems` (which a
154
+ * fixed `[N]` slot also states as N).
155
+ *
156
+ * A separate field because it is a separate fact, and conflating them is a
157
+ * bug that hides: `itemCount` was read as a minimum by readiness and as a
158
+ * maximum by the control that stops offering `Add`, which agree only while
159
+ * a contract states both bounds equal. A list told "at least two" was then
160
+ * presented as a list of exactly two.
161
+ */
162
+ maxItemCount?: number;
163
+ }
164
+ interface UnknownRunField extends RunFieldCommon {
165
+ kind: 'unknown';
166
+ }
167
+ type RunField = TextRunField | ProseRunField | DateRunField | NumberRunField | BooleanRunField | EnumRunField | FileRunField | ObjectRunField | ListRunField | UnknownRunField;
168
+ /** The short, friendly category label for a concept_ref (drives the pill dot). */
169
+ type ConceptCategory = 'text' | 'date' | 'document' | 'image' | 'number' | 'boolean' | 'choice' | 'structured' | 'list';
170
+ declare function conceptCategory(field: RunField): ConceptCategory;
171
+
172
+ export { type BooleanRunField, type ConceptCategory, type DateRunField, type EnumRunField, type FileRunField, type ListRunField, type NumberRunField, type ObjectRunField, type ProseRunField, type RunField, type RunFieldCommon, type RunFieldKind, type TextConstraints, type TextRunField, type UnknownRunField, conceptCategory };
@@ -0,0 +1,3 @@
1
+ export { conceptCategory } from '../chunk-25I6JDIK.js';
2
+ //# sourceMappingURL=descriptor.js.map
3
+ //# sourceMappingURL=descriptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"descriptor.js"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * One validation error, in the shape hosts read (`property`, `message`,
3
+ * `name`, `stack`, `params.format`). Structurally a superset-compatible twin of
4
+ * RJSF's `RJSFValidationError`, so a host surface still holding RJSF form errors
5
+ * (an RJSF-based inputs panel) can hand them to the same message renderer.
6
+ */
7
+ interface RunInputError {
8
+ /** The ajv keyword that failed (`required`, `format`, `type`, …). */
9
+ name?: string;
10
+ /** Dotted path into the form data (`.quote_date.date`); for a `required`
11
+ * error the missing property name is appended (top-level: no leading dot). */
12
+ property?: string;
13
+ message?: string;
14
+ params?: Record<string, unknown>;
15
+ /** The human line: `property message`, or `'title' message` when the parent
16
+ * schema titles the field. */
17
+ stack: string;
18
+ schemaPath?: string;
19
+ }
20
+ /**
21
+ * Validate `data` against `schema`, returning mapped errors (empty = valid).
22
+ * MUTATES `data` where ajv coerces (`"20"` → `20`) - deliberate and relied on:
23
+ * the string the user typed is stored as a number going forward.
24
+ */
25
+ declare function validateRunInputsSchema(data: unknown, schema: object): RunInputError[];
26
+
27
+ export { type RunInputError, validateRunInputsSchema };
@@ -0,0 +1,4 @@
1
+ export { validateRunInputsSchema } from '../chunk-VTPUDBSJ.js';
2
+ import '../chunk-GKV77RAS.js';
3
+ //# sourceMappingURL=gate-validator.js.map
4
+ //# sourceMappingURL=gate-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"gate-validator.js"}
@@ -0,0 +1,144 @@
1
+ import { RunInputError } from './gate-validator.js';
2
+ import { PipeInputContract, PipeIOContract } from 'mthds/protocol';
3
+
4
+ type Dict = Record<string, unknown>;
5
+ /**
6
+ * Combine the per-input schemas into one object schema.
7
+ *
8
+ * Each input variable becomes a property, titled with the variable name and
9
+ * described with the concept code. Only the inputs the method actually demands
10
+ * land in `required` - an optional (`?`) or variable-plural (`[]`) input must
11
+ * not block the form (see `inputMustBeFilled`).
12
+ *
13
+ * The per-input schema travels verbatim, which is how a fixed-count (`[N]`)
14
+ * list's declared count reaches ajv: pipelex states it as `minItems`/`maxItems`
15
+ * on the array wrapper, so the count is enforced here without the kernel
16
+ * restating it.
17
+ */
18
+ declare function buildRunInputsSchema(inputs: Record<string, PipeInputContract>): Dict;
19
+ /**
20
+ * Last-gate repair, in two passes against the exact schema ajv validates.
21
+ *
22
+ * 1. **HEAL** - persisted `inputData` may still carry the legacy double-wrapped
23
+ * `{ text: { text } }` shape (or a form's state may lag a store fix).
24
+ * 2. **PRUNE** - RJSF fills every unset string with `""` on mount, which an
25
+ * optional CONSTRAINED field rejects (`DateContent.time` is `format: "time"`)
26
+ * - blocking a run on a field the user never opened. An absent optional field
27
+ * is what pipelex means by `None` anyway, so dropping it is the semantically
28
+ * correct repair, not a validation dodge.
29
+ */
30
+ declare function prepareRunInputs(data: Dict, schema: Dict): Dict;
31
+ interface RunInputsVerdict {
32
+ isValid: boolean;
33
+ /** Variable names the scan could pin a missing required field on. */
34
+ missingInputs: string[];
35
+ /** Raw ajv errors, for when the scan cannot name anything. */
36
+ errors: RunInputError[];
37
+ }
38
+ /**
39
+ * Validate PREPARED data against the combined schema.
40
+ *
41
+ * `missingInputs` is reported by VARIABLE NAME - the name on the node the user
42
+ * clicks - never by `concept_ref`, which used to produce "Missing required
43
+ * fields in: native.Text" and named neither the input at fault nor, when several
44
+ * inputs share a concept, which of them. Inputs the method declared omittable
45
+ * are skipped: an empty `?` input is not a missing one.
46
+ *
47
+ * The scan can come up empty even on an invalid form (a wrong value shape, a
48
+ * nested mismatch); `errors` is what the caller falls back to then, so a failure
49
+ * is never undiagnosable.
50
+ *
51
+ * `preparedData` is `unknown` for the same reason `gateRunInputs`' `data` is: on
52
+ * a public endpoint the body is whatever was sent, and ajv must be shown that
53
+ * rather than a repair of it, or `must be object` is a verdict no caller can
54
+ * ever receive. The name-the-variable scan below needs something indexable, so
55
+ * a body that is not a record scans against nothing and names every input the
56
+ * method demands - which is the honest answer for a body that carried none.
57
+ */
58
+ declare function validateRunInputs(preparedData: unknown, inputs: Record<string, PipeInputContract>, schema: Dict): RunInputsVerdict;
59
+ /**
60
+ * Build the `{ concept, content }` map a run expects, from PREPARED data.
61
+ *
62
+ * The two ways a method says "this may be nothing" land differently on the wire:
63
+ * - an **optional (`?`)** input left blank is OMITTED, so the runtime records a
64
+ * real absence the method can branch on - sending `{ text: "" }` would look
65
+ * like the caller supplied an empty string;
66
+ * - a **plural (`[]`)** input keeps its key, because a plural slot is never
67
+ * absent - its empty form IS the empty list, and the runtime requires the key.
68
+ * When that list is EMPTY it is sent BARE, without the envelope: the envelope
69
+ * is the runtime's "explicit form", which bypasses the top-down shaper and
70
+ * hands the value to the bottom-up factory, and that factory types a list from
71
+ * its first item - so an empty list there raises "Cannot create Stuff from
72
+ * empty list in content". The bare form keeps the shaper, which reads the
73
+ * DECLARED concept and builds the empty list correctly. (pipelex/#1096 makes
74
+ * the envelope agree; the bare form runs today and stays correct after.)
75
+ *
76
+ * A fixed-count (`[N]`) list takes that same empty-list path, and an empty one
77
+ * is not a payload the method can accept. It is unreachable through the gate,
78
+ * which demands the input and rejects a short list on `minItems`; building the
79
+ * payload straight from unvalidated data is what would reach it, and inventing
80
+ * a different absence here would only hide that.
81
+ */
82
+ declare function apiInputsFromSchemaData(preparedData: Dict, inputs: Record<string, PipeInputContract>): Dict;
83
+ /** The gate's verdict: the payload to send, or why the run may not start. */
84
+ type RunInputsGateResult = {
85
+ ok: true;
86
+ inputs: Dict;
87
+ } | {
88
+ ok: false;
89
+ /** Variable names the caller left empty or short. */
90
+ missingInputs: string[];
91
+ /** Raw ajv errors, for when the scan cannot name anything. */
92
+ errors: RunInputError[];
93
+ /** The repaired data the verdict was reached on - what
94
+ * `describeValidationError` needs to quote the value it received. */
95
+ preparedData: Dict;
96
+ };
97
+ /**
98
+ * Gate a run: repair the caller's data, validate it against the contract,
99
+ * refuse anything the Run button would have refused, and build the payload.
100
+ *
101
+ * Returns a verdict rather than throwing. A server endpoint must not throw
102
+ * across the boundary to its client - frameworks routinely strip the message to
103
+ * an opaque digest in a production build - and a rejection here is an ordinary
104
+ * outcome, not an exceptional one.
105
+ *
106
+ * **`data` is `unknown` because that is what it is.** A run endpoint is public
107
+ * and no framework enforces a declared parameter type, so the argument is
108
+ * whatever the caller put in the body. The chain indexes the payload by
109
+ * variable name without first checking it is indexable, so a `null` body would
110
+ * throw *after* ajv had already reported `must be object` and before any
111
+ * verdict came back.
112
+ *
113
+ * So the two uses are separated rather than collapsed into one repair. **ajv
114
+ * judges the value the caller actually sent**, which is what makes `must be
115
+ * object` the verdict for a body that is not one; the indexing that follows
116
+ * uses a safe `{}` stand-in, which is what keeps the gate from throwing. Repair
117
+ * FIRST - coercing a non-object to `{}` before validating - is the shape that
118
+ * looks equivalent and is not: a contract whose inputs are all optional has an
119
+ * empty `required` list, so `{}` validates, and `gateRunInputs("garbage", …)`
120
+ * came back `ok: true` on a body the caller never meant as a run. Fail-closed
121
+ * costs a malformed caller an error it earned; the other direction bills a run.
122
+ *
123
+ * **The schema alone is not the rule.** ajv's `required` asserts only that a
124
+ * key is PRESENT, and a content model carries no `minLength` - so a required
125
+ * input that arrived empty (`{document: {url: ""}}`, `{text: {text: ""}}`)
126
+ * satisfies the schema. That is the natural payload, not a contrived one:
127
+ * `rjsfDataFromRunValues({}, fields)` emits exactly that when nothing is
128
+ * selected. So the gate re-applies the emptiness rule using
129
+ * `computeReadiness`'s OWN two functions over the same derived fields, which is
130
+ * the only way to be sure the two sides cannot disagree.
131
+ *
132
+ * Picking a pair that merely looks equivalent is not enough, and this is the
133
+ * whole reason the assembly lives here rather than in each host: the obvious
134
+ * choice, `inputMustBeFilled` + `isFilled`, agrees on every leaf kind and
135
+ * diverges on a structured concept in BOTH directions - `isFilled` on an object
136
+ * is `some(child filled)` where `fieldFilled` is `every(required child
137
+ * filled)`. That accepts a half-filled struct the browser refuses (a paid run
138
+ * past a disabled button) and rejects a filled all-optional struct the browser
139
+ * accepts. A host whose methods happen to use only native concepts has no test
140
+ * that can catch either.
141
+ */
142
+ declare function gateRunInputs(contract: PipeIOContract, data: unknown): RunInputsGateResult;
143
+
144
+ export { type RunInputsGateResult, type RunInputsVerdict, apiInputsFromSchemaData, buildRunInputsSchema, gateRunInputs, prepareRunInputs, validateRunInputs };