@pipelex/mthds-form 0.4.0 → 0.6.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 (76) hide show
  1. package/README.md +15 -6
  2. package/dist/{chunk-6XD6UZSU.js → chunk-25I6JDIK.js} +2 -2
  3. package/dist/chunk-25I6JDIK.js.map +1 -0
  4. package/dist/chunk-2JWRG53S.js +33 -0
  5. package/dist/chunk-2JWRG53S.js.map +1 -0
  6. package/dist/chunk-45C2MKK6.js +94 -0
  7. package/dist/chunk-45C2MKK6.js.map +1 -0
  8. package/dist/chunk-4BSBBG2T.js +80 -0
  9. package/dist/chunk-4BSBBG2T.js.map +1 -0
  10. package/dist/{chunk-7BMOPXDY.js → chunk-GCXV76WU.js} +17 -9
  11. package/dist/chunk-GCXV76WU.js.map +1 -0
  12. package/dist/chunk-IIU3YK4O.js +16 -0
  13. package/dist/chunk-IIU3YK4O.js.map +1 -0
  14. package/dist/chunk-J4L32LD6.js +137 -0
  15. package/dist/chunk-J4L32LD6.js.map +1 -0
  16. package/dist/{chunk-KFZ5MZOM.js → chunk-LBFZXYCY.js} +3 -3
  17. package/dist/{chunk-KFZ5MZOM.js.map → chunk-LBFZXYCY.js.map} +1 -1
  18. package/dist/{chunk-O4O3QNVC.js → chunk-OD3VYXUY.js} +13 -3
  19. package/dist/chunk-OD3VYXUY.js.map +1 -0
  20. package/dist/{chunk-XEPD5VJQ.js → chunk-R7B7ID25.js} +8 -8
  21. package/dist/chunk-R7B7ID25.js.map +1 -0
  22. package/dist/{chunk-WXBSM2CE.js → chunk-T4NSAGOS.js} +8 -8
  23. package/dist/chunk-T4NSAGOS.js.map +1 -0
  24. package/dist/{chunk-HARYEWNG.js → chunk-XYQ7NXQZ.js} +2 -2
  25. package/dist/chunk-XYQ7NXQZ.js.map +1 -0
  26. package/dist/chunk-YEVVR6FE.js +66 -0
  27. package/dist/chunk-YEVVR6FE.js.map +1 -0
  28. package/dist/core/contracts.d.ts +27 -56
  29. package/dist/core/contracts.js +1 -1
  30. package/dist/core/derive.d.ts +79 -11
  31. package/dist/core/derive.js +4 -4
  32. package/dist/core/descriptor.d.ts +70 -26
  33. package/dist/core/descriptor.js +1 -1
  34. package/dist/core/file-formats.d.ts +102 -0
  35. package/dist/core/file-formats.js +3 -0
  36. package/dist/core/file-formats.js.map +1 -0
  37. package/dist/core/gate.d.ts +1 -1
  38. package/dist/core/gate.js +7 -8
  39. package/dist/core/gating-fields.d.ts +38 -0
  40. package/dist/core/gating-fields.js +6 -0
  41. package/dist/core/gating-fields.js.map +1 -0
  42. package/dist/core/index.d.ts +8 -3
  43. package/dist/core/index.js +14 -10
  44. package/dist/core/native-content.d.ts +219 -0
  45. package/dist/core/native-content.js +4 -0
  46. package/dist/core/{native-concepts.js.map → native-content.js.map} +1 -1
  47. package/dist/core/normalize-schema.js +2 -2
  48. package/dist/core/output-form.d.ts +45 -0
  49. package/dist/core/output-form.js +5 -0
  50. package/dist/core/output-form.js.map +1 -0
  51. package/dist/core/readiness.d.ts +2 -0
  52. package/dist/core/schema-utils.d.ts +13 -1
  53. package/dist/core/schema-utils.js +1 -1
  54. package/dist/core/stuff-files.d.ts +52 -0
  55. package/dist/core/stuff-files.js +5 -0
  56. package/dist/core/stuff-files.js.map +1 -0
  57. package/dist/core/values.d.ts +10 -3
  58. package/dist/core/values.js +7 -7
  59. package/dist/core/wire-format.js +3 -4
  60. package/dist/react/index.d.ts +278 -1
  61. package/dist/react/index.js +1378 -29
  62. package/dist/react/index.js.map +1 -1
  63. package/dist/styles.css +1 -1
  64. package/package.json +17 -3
  65. package/dist/chunk-6XD6UZSU.js.map +0 -1
  66. package/dist/chunk-7BMOPXDY.js.map +0 -1
  67. package/dist/chunk-HARYEWNG.js.map +0 -1
  68. package/dist/chunk-NMT7BRLO.js +0 -40
  69. package/dist/chunk-NMT7BRLO.js.map +0 -1
  70. package/dist/chunk-O4O3QNVC.js.map +0 -1
  71. package/dist/chunk-VMLXO5JS.js +0 -132
  72. package/dist/chunk-VMLXO5JS.js.map +0 -1
  73. package/dist/chunk-WXBSM2CE.js.map +0 -1
  74. package/dist/chunk-XEPD5VJQ.js.map +0 -1
  75. package/dist/core/native-concepts.d.ts +0 -78
  76. package/dist/core/native-concepts.js +0 -3
@@ -0,0 +1,137 @@
1
+ import { acceptLabelForKind } from './chunk-2JWRG53S.js';
2
+ import { collectSchemaDefs, resolveSchemaNode } from './chunk-OD3VYXUY.js';
3
+ import { buildPipeRef } from './chunk-XYQ7NXQZ.js';
4
+ import { ownProp } from './chunk-V3OV2XQT.js';
5
+
6
+ // src/core/derive.ts
7
+ function getPipeInputForm(inputForm, domain, pipeCode) {
8
+ if (!inputForm || !pipeCode) return void 0;
9
+ if (domain) {
10
+ const byRef = ownProp(inputForm, buildPipeRef(domain, pipeCode));
11
+ if (byRef) return byRef;
12
+ }
13
+ return ownProp(inputForm, pipeCode);
14
+ }
15
+ function scalarWrapperKey(schema) {
16
+ const props = schema?.properties;
17
+ const keys = props ? Object.keys(props) : [];
18
+ return keys.length === 1 ? keys[0] : void 0;
19
+ }
20
+ function numOrUndef(v) {
21
+ return typeof v === "number" ? v : void 0;
22
+ }
23
+ function inclusiveFrom(bound, integer, toNearest) {
24
+ return typeof bound === "number" && integer === true ? toNearest(bound) : void 0;
25
+ }
26
+ function resolveSchema(schema, defs) {
27
+ return schema ? resolveSchemaNode(schema, defs) : void 0;
28
+ }
29
+ function commonOf(node, name) {
30
+ return {
31
+ name,
32
+ title: node.title,
33
+ conceptRef: node.concept_ref,
34
+ refines: node.refines,
35
+ description: node.description,
36
+ required: node.required,
37
+ defaultValue: node.default_value,
38
+ examples: node.examples,
39
+ hints: node.hints
40
+ };
41
+ }
42
+ function mapNode(node, name, rawSchema, defs) {
43
+ const schema = resolveSchema(rawSchema, defs);
44
+ const common = commonOf(node, name);
45
+ switch (node.kind) {
46
+ case "text":
47
+ case "prose":
48
+ return {
49
+ ...common,
50
+ contentKey: scalarWrapperKey(schema),
51
+ kind: node.kind,
52
+ minLength: node.min_length,
53
+ maxLength: node.max_length,
54
+ pattern: node.pattern,
55
+ format: node.format
56
+ };
57
+ case "date":
58
+ return { ...common, kind: "date", datetime: node.datetime };
59
+ case "number":
60
+ return {
61
+ ...common,
62
+ contentKey: scalarWrapperKey(schema),
63
+ kind: "number",
64
+ integer: node.integer,
65
+ // A number control clamps; it does not express open intervals. So an
66
+ // exclusive bound becomes the nearest inclusive one where that is
67
+ // exact (integers) and is dropped where it is not (floats) - never
68
+ // the excluded endpoint itself, which the control would clamp onto
69
+ // and the gate would then refuse. ajv enforces the exact keyword off
70
+ // the schema either way, so the bound is a hint, never the rule.
71
+ min: node.minimum ?? inclusiveFrom(
72
+ node.exclusive_minimum,
73
+ node.integer,
74
+ (endpoint) => Math.floor(endpoint) + 1
75
+ ),
76
+ max: node.maximum ?? inclusiveFrom(
77
+ node.exclusive_maximum,
78
+ node.integer,
79
+ (endpoint) => Math.ceil(endpoint) - 1
80
+ )
81
+ };
82
+ case "boolean":
83
+ return { ...common, contentKey: scalarWrapperKey(schema), kind: "boolean" };
84
+ case "enum":
85
+ return { ...common, kind: "enum", options: node.choices.map(String) };
86
+ case "document":
87
+ case "image":
88
+ return { ...common, kind: node.kind, accept: acceptLabelForKind(node.kind) };
89
+ case "object": {
90
+ const props = schema?.properties;
91
+ return {
92
+ ...common,
93
+ kind: "object",
94
+ fields: node.fields.map(
95
+ (child) => mapNode(child, child.name, props ? ownProp(props, child.name) : void 0, defs)
96
+ )
97
+ };
98
+ }
99
+ case "list": {
100
+ const item = mapNode(node.item, name, schema?.items, defs);
101
+ const itemCount = node.item_count ?? numOrUndef(schema?.minItems);
102
+ const maxItemCount = numOrUndef(schema?.maxItems) ?? node.item_count;
103
+ return {
104
+ ...common,
105
+ kind: "list",
106
+ item,
107
+ ...itemCount === void 0 ? {} : { itemCount },
108
+ ...maxItemCount === void 0 ? {} : { maxItemCount }
109
+ };
110
+ }
111
+ case "unknown":
112
+ return { ...common, kind: "unknown" };
113
+ }
114
+ return { ...common, kind: "unknown" };
115
+ }
116
+ function buildResultField(descriptor, schema) {
117
+ const node = descriptor.field;
118
+ const defs = {};
119
+ collectSchemaDefs(schema, defs, { traverseArrays: true });
120
+ const wrapperKey = node.kind === "object" ? void 0 : scalarWrapperKey(schema);
121
+ const inner = wrapperKey === void 0 ? schema : schema.properties[wrapperKey];
122
+ const field = mapNode(node, node.name, inner, defs);
123
+ return wrapperKey === void 0 ? field : { ...field, contentKey: wrapperKey };
124
+ }
125
+ function buildRunFields(descriptor, inputs) {
126
+ return descriptor.fields.map((node) => {
127
+ const input = ownProp(inputs, node.name);
128
+ const defs = {};
129
+ if (input) collectSchemaDefs(input.json_schema, defs, { traverseArrays: true });
130
+ const field = mapNode(node, node.name, input?.json_schema, defs);
131
+ return { ...field, presence: node.presence, gating: node.gating };
132
+ });
133
+ }
134
+
135
+ export { buildResultField, buildRunFields, getPipeInputForm };
136
+ //# sourceMappingURL=chunk-J4L32LD6.js.map
137
+ //# sourceMappingURL=chunk-J4L32LD6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/derive.ts"],"names":[],"mappings":"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","file":"chunk-J4L32LD6.js","sourcesContent":["/**\n * The ONE derivation function: wire input-form descriptor → `RunField[]`.\n *\n * `buildRunFields` maps the standard's per-pipe descriptor STRUCTURALLY onto\n * the kernel's `RunField` shape. It decides nothing: what a field IS - its\n * kind, its constraints, its refinement chain, whether Run gates on it - is\n * stated by the wire, derived by the engine from the resolved library. The\n * heuristics that used to live here (native-concept sets, the url-bearing\n * object test, the depth-based prose/label rule) are deleted, not bypassed;\n * docs/derivation-swap.md is the record of that swap and of every behaviour\n * that moved with it.\n *\n * The contract is still consulted, for exactly two facts the wire deliberately\n * does not carry:\n *\n * - **`contentKey`** - the single property a native scalar's value sits inside\n * on the wire (`TextContent {text}`, `NumberContent {number}`). A renderer\n * holds the bare scalar; the value bridge wraps by this name. It is read off\n * the contract's `json_schema` because it is a fact about the PAYLOAD shape,\n * which is the schema's job - the descriptor describes the form, never the\n * payload.\n * - **Nested list bounds** - the wire puts `item_count` only on a top-level\n * fixed `[N]` slot, but the model behind a structured concept may state\n * `minItems`/`maxItems` on an array property of its own, and ajv enforces\n * them. Reading them off the schema is what keeps readiness from being more\n * permissive than the gate.\n *\n * Both are structural reads (a property count, two keywords ajv itself\n * enforces), not judgements - the mapper stays honest about owning no taxonomy.\n */\n\nimport type { PipeInputFormDescriptor } from 'mthds/protocol';\nimport type { InputForm, InputFormField, InputFormItem } from 'mthds/protocol';\n\nimport { buildPipeRef, type PipeInputContract } from './contracts';\nimport { acceptLabelForKind } from './file-formats';\nimport type { RunField, RunFieldCommon } from './descriptor';\nimport { ownProp } from './own-property';\nimport { collectSchemaDefs, resolveSchemaNode, type JsonSchema } from './schema-utils';\n\n/**\n * Look up a pipe's input-form descriptor by pipe code, tolerant of both key\n * conventions - the exact mirror of `getPipeIOContract`, because the standard\n * keys `input_form` by the same `pipe_ref` set as `pipe_io_contracts` and a\n * host tracks the same bare codes either way.\n */\nexport function getPipeInputForm(\n inputForm: InputForm | null | undefined,\n domain: string | null | undefined,\n pipeCode: string | null | undefined,\n): PipeInputFormDescriptor | undefined {\n if (!inputForm || !pipeCode) return undefined;\n if (domain) {\n const byRef = ownProp(inputForm, buildPipeRef(domain, pipeCode));\n if (byRef) return byRef;\n }\n return ownProp(inputForm, pipeCode);\n}\n\n/**\n * The single property a scalar content model holds its value in - `TextContent\n * {text}`, `NumberContent {number}`, `YesNoContent {yes_no}`.\n *\n * A wrapper is a schema declaring EXACTLY ONE property. A multi-property\n * content model (`DateContent {date, time}`, `HtmlContent {inner_html,\n * css_class}`) is not one and deliberately answers `undefined` - those travel\n * as the structures they are, which the wire now also says by giving them\n * `kind: \"object\"`. Reading the declared shape rather than keeping a list of\n * wrapping concepts is what covers a concept nobody remembered to add.\n */\nfunction scalarWrapperKey(schema: JsonSchema | undefined): string | undefined {\n const props = schema?.properties as Record<string, JsonSchema> | undefined;\n const keys = props ? Object.keys(props) : [];\n return keys.length === 1 ? keys[0] : undefined;\n}\n\nfunction numOrUndef(v: unknown): number | undefined {\n return typeof v === 'number' ? v : undefined;\n}\n\n/** The inclusive bound a control can express for a wire-stated exclusive one.\n * On an integer the open interval has a nearest neighbour and the conversion\n * is exact (`> 0` IS `>= 1`), so `toNearest` steps off the endpoint onto it.\n * It rounds rather than adding one, because the endpoint need not be integral\n * - JSON Schema allows `> 0.5` on an integer node, whose nearest neighbour is\n * `1`, not `1.5`. On a float the interval has no nearest neighbour, and the\n * answer is to publish no bound at all: a control that only clamps cannot\n * express an open interval, and naming the excluded endpoint would advertise\n * the one value the gate is about to refuse. */\nfunction inclusiveFrom(\n bound: number | undefined,\n integer: boolean | undefined,\n toNearest: (endpoint: number) => number,\n): number | undefined {\n return typeof bound === 'number' && integer === true ? toNearest(bound) : undefined;\n}\n\n/** Resolve one schema node the same way the gate's validator will see it -\n * to the FIXPOINT, so a nullable `$ref` (pydantic's Optional concept-typed\n * field) yields its definition rather than an unresolved reference. */\nfunction resolveSchema(\n schema: JsonSchema | undefined,\n defs: Record<string, JsonSchema>,\n): JsonSchema | undefined {\n return schema ? resolveSchemaNode(schema, defs) : undefined;\n}\n\n/** The slots every wire node carries, mapped onto the descriptor's names. */\nfunction commonOf(node: InputFormItem, name: string): RunFieldCommon {\n return {\n name,\n title: node.title,\n conceptRef: node.concept_ref,\n refines: node.refines,\n description: node.description,\n required: node.required,\n defaultValue: node.default_value,\n examples: node.examples,\n hints: node.hints,\n };\n}\n\n/**\n * Map one wire node onto a `RunField`, walking the aligned `json_schema` node\n * beside it. The schema walk supplies only `contentKey` and list bounds (see\n * the module header); a missing or misaligned schema degrades to their absence,\n * never to a different field.\n */\nfunction mapNode(\n node: InputFormItem,\n name: string,\n rawSchema: JsonSchema | undefined,\n defs: Record<string, JsonSchema>,\n): RunField {\n const schema = resolveSchema(rawSchema, defs);\n const common = commonOf(node, name);\n\n switch (node.kind) {\n case 'text':\n case 'prose':\n return {\n ...common,\n contentKey: scalarWrapperKey(schema),\n kind: node.kind,\n minLength: node.min_length,\n maxLength: node.max_length,\n pattern: node.pattern,\n format: node.format,\n };\n case 'date':\n return { ...common, kind: 'date', datetime: node.datetime };\n case 'number':\n return {\n ...common,\n contentKey: scalarWrapperKey(schema),\n kind: 'number',\n integer: node.integer,\n // A number control clamps; it does not express open intervals. So an\n // exclusive bound becomes the nearest inclusive one where that is\n // exact (integers) and is dropped where it is not (floats) - never\n // the excluded endpoint itself, which the control would clamp onto\n // and the gate would then refuse. ajv enforces the exact keyword off\n // the schema either way, so the bound is a hint, never the rule.\n min:\n node.minimum ??\n inclusiveFrom(\n node.exclusive_minimum,\n node.integer,\n (endpoint) => Math.floor(endpoint) + 1,\n ),\n max:\n node.maximum ??\n inclusiveFrom(\n node.exclusive_maximum,\n node.integer,\n (endpoint) => Math.ceil(endpoint) - 1,\n ),\n };\n case 'boolean':\n return { ...common, contentKey: scalarWrapperKey(schema), kind: 'boolean' };\n case 'enum':\n // The wire carries the authored `choices` verbatim (`unknown[]`); the\n // control set renders and stores strings, as it always has.\n return { ...common, kind: 'enum', options: node.choices.map(String) };\n case 'document':\n case 'image':\n // `accept` is a renderer-side affordance the wire deliberately does not\n // state - which bytes a runtime can decode is a property of the runtime,\n // not of the method. The list is therefore ours, and it is one table\n // (`./file-formats`) rather than a literal here, so the label a user reads\n // and the filter the dropzone enforces cannot disagree. They used to: this\n // line advertised `TXT`, which no DocumentFormat covers, and omitted PPTX,\n // which is one.\n return { ...common, kind: node.kind, accept: acceptLabelForKind(node.kind) };\n case 'object': {\n const props = schema?.properties as Record<string, JsonSchema> | undefined;\n return {\n ...common,\n kind: 'object',\n fields: node.fields.map((child: InputFormField) =>\n mapNode(child, child.name, props ? ownProp(props, child.name) : undefined, defs),\n ),\n };\n }\n case 'list': {\n // The item keeps the parent's name (unused for labels - the index labels\n // items), exactly as the wire's `item` carries no `name` of its own.\n const item = mapNode(node.item, name, schema?.items as JsonSchema | undefined, defs);\n const itemCount = node.item_count ?? numOrUndef(schema?.minItems);\n const maxItemCount = numOrUndef(schema?.maxItems) ?? node.item_count;\n return {\n ...common,\n kind: 'list',\n item,\n ...(itemCount === undefined ? {} : { itemCount }),\n ...(maxItemCount === undefined ? {} : { maxItemCount }),\n };\n }\n case 'unknown':\n return { ...common, kind: 'unknown' };\n }\n // Version drift: the wire states a kind this build's pinned peer does not\n // define. The switch above is exhaustive over `InputFormItem`, so\n // `satisfies never` keeps the build LOUD when the PEER grows a kind - and\n // this return keeps the mapper TOTAL when a SERVER is ahead of the peer,\n // which no type can rule out. The standard's own escape hatch is the honest\n // answer (`unknown` exists so a derivation can be total truthfully): the\n // field keeps its name and falls back to raw entry against the contract's\n // `json_schema`. Returning nothing produced a field with no `name` at all -\n // unaddressable by the value bridge and unnameable by readiness.\n node satisfies never;\n return { ...common, kind: 'unknown' };\n}\n\n/**\n * Map a pipe's wire input-form descriptor onto the kernel's field descriptors,\n * with the pipe's input contracts beside it for the two schema-derived facts.\n *\n * The order is the DESCRIPTOR's - authored input order, the fact the contract's\n * `inputs` map deliberately does not carry. `required`, `presence` and `gating`\n * are taken from the wire verbatim: the standard's top-level type ties\n * `required` to the presence marker, and `gating` is the producer-derived\n * answer `mustBeFilled` reads - the kernel re-derives none of them.\n */\n/**\n * The same mapping, for a single node that belongs to no slot — a pipe's OUTPUT.\n *\n * `buildRunFields` maps a descriptor's fields and then stamps each with the slot\n * facts (`presence`, `gating`) the wire states per input. An output has no slot,\n * so this stamps nothing: the node arrives without them and leaves without them,\n * which is exactly what the standard's optional members are for.\n *\n * Everything else is shared, deliberately. An output is a concept ref like an\n * input is, so its kinds, its nesting, its list bounds and its `contentKey` are\n * the same questions with the same answers — and a second mapper would be a\n * second place for them to drift.\n *\n * ## `schema` is REQUIRED, and that is the whole point\n *\n * It was optional once, and the renderer paid for it: with no schema it could\n * not know that `native.Text`'s payload sits under `text`, so it worked the\n * unwrapping out by COUNTING the value's properties, and worked a plural payload\n * out by looking for an `items` key. Both are shape sniffing — the exact pattern\n * [../../docs/derivation-swap.md] records deleting from the input side, where\n * `contentKey` is read off a schema and handed to a control as a property NAME.\n *\n * So the parameter is required, the two heuristics are gone, and what the\n * argument must be is stated precisely: **the schema of the PAYLOAD**, which for\n * every kind but `object` is the content model that wraps it —\n * `TextContent {text}` for a `native.Text` output, `ListContent {items}` for a\n * plural one. That is a change the standard has to make on the output contract\n * (see `./output-form`); until it does, a producer supplies it beside the\n * descriptor.\n */\nexport function buildResultField(\n descriptor: { field: InputFormField },\n schema: Record<string, unknown>,\n): RunField {\n const node = descriptor.field;\n const defs: Record<string, JsonSchema> = {};\n collectSchemaDefs(schema as JsonSchema, defs, { traverseArrays: true });\n\n // Unwrap the content model, ONCE, at the top.\n //\n // A result's payload is its concept's content model, and for every kind but\n // `object` that model is a wrapper: the value sits under a single property the\n // schema names. Reading it here rather than in `mapNode` is what keeps the\n // schema walk ALIGNED with the descriptor beneath it — a plural output's node\n // is a `list`, so the schema it must be walked against is the `items` array,\n // not the `ListContent` object around it.\n //\n // Gated on the node's stated KIND, never on the value: an `object` output IS\n // its content model, and a structured concept that happens to declare exactly\n // one field would otherwise be mistaken for a wrapper. The kind comes from the\n // descriptor, so this is a read of what the field is, not a guess at it.\n const wrapperKey = node.kind === 'object' ? undefined : scalarWrapperKey(schema as JsonSchema);\n const inner =\n wrapperKey === undefined\n ? (schema as JsonSchema)\n : ((schema as JsonSchema).properties as Record<string, JsonSchema>)[wrapperKey];\n\n const field = mapNode(node, node.name, inner, defs);\n return wrapperKey === undefined ? field : { ...field, contentKey: wrapperKey };\n}\n\nexport function buildRunFields(\n descriptor: PipeInputFormDescriptor,\n inputs: Record<string, PipeInputContract>,\n): RunField[] {\n return descriptor.fields.map((node) => {\n const input = ownProp(inputs, node.name);\n const defs: Record<string, JsonSchema> = {};\n if (input) collectSchemaDefs(input.json_schema, defs, { traverseArrays: true });\n const field = mapNode(node, node.name, input?.json_schema as JsonSchema | undefined, defs);\n return { ...field, presence: node.presence, gating: node.gating };\n });\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { flattenAnyOf, hoistDefsToRoot } from './chunk-O4O3QNVC.js';
1
+ import { flattenAnyOf, hoistDefsToRoot } from './chunk-OD3VYXUY.js';
2
2
 
3
3
  // src/core/normalize-schema.ts
4
4
  function normalizeSchemaForRjsf(schema) {
@@ -40,5 +40,5 @@ function prepareSchemaForRjsf(schema) {
40
40
  }
41
41
 
42
42
  export { normalizeSchemaForRjsf, prepareSchemaForRjsf };
43
- //# sourceMappingURL=chunk-KFZ5MZOM.js.map
44
- //# sourceMappingURL=chunk-KFZ5MZOM.js.map
43
+ //# sourceMappingURL=chunk-LBFZXYCY.js.map
44
+ //# sourceMappingURL=chunk-LBFZXYCY.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/normalize-schema.ts"],"names":[],"mappings":";;;AAsBO,SAAS,uBAAuB,MAAA,EAA0D;AAE/F,EAAA,IAAI,UAAA,GAAa,aAAa,MAAM,CAAA;AAGpC,EAAA,IAAI,UAAA,CAAW,UAAA,IAAc,OAAO,UAAA,CAAW,eAAe,QAAA,EAAU;AACtE,IAAA,MAAM,QAAQ,UAAA,CAAW,UAAA;AACzB,IAAA,MAAM,kBAA2C,EAAC;AAClD,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAChD,MAAA,eAAA,CAAgB,GAAG,IACjB,KAAA,IAAS,OAAO,UAAU,QAAA,GACtB,sBAAA,CAAuB,KAAgC,CAAA,GACvD,KAAA;AAAA,IACR;AACA,IAAA,UAAA,GAAa,EAAE,GAAG,UAAA,EAAY,UAAA,EAAY,eAAA,EAAgB;AAAA,EAC5D;AAGA,EAAA,IACE,UAAA,CAAW,KAAA,IACX,OAAO,UAAA,CAAW,KAAA,KAAU,QAAA,IAC5B,CAAC,KAAA,CAAM,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA,EAC/B;AACA,IAAA,UAAA,GAAa;AAAA,MACX,GAAG,UAAA;AAAA,MACH,KAAA,EAAO,sBAAA,CAAuB,UAAA,CAAW,KAAgC;AAAA,KAC3E;AAAA,EACF;AAGA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA,EAAG;AACnC,IAAA,UAAA,GAAa;AAAA,MACX,GAAG,UAAA;AAAA,MACH,KAAA,EAAQ,WAAW,KAAA,CAAoC,GAAA;AAAA,QAAI,CAAC,WAC1D,OAAO,MAAA,KAAW,YAAY,MAAA,KAAW,IAAA,GACrC,sBAAA,CAAuB,MAAiC,CAAA,GACxD;AAAA;AACN,KACF;AAAA,EACF;AAGA,EAAA,IACE,UAAA,CAAW,KAAA,IACX,OAAO,UAAA,CAAW,KAAA,KAAU,QAAA,IAC5B,CAAC,KAAA,CAAM,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA,EAC/B;AACA,IAAA,MAAM,OAAO,UAAA,CAAW,KAAA;AACxB,IAAA,MAAM,iBAA0C,EAAC;AACjD,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,EAAG;AAC/C,MAAA,cAAA,CAAe,GAAG,IAChB,KAAA,IAAS,OAAO,UAAU,QAAA,GACtB,sBAAA,CAAuB,KAAgC,CAAA,GACvD,KAAA;AAAA,IACR;AACA,IAAA,UAAA,GAAa,EAAE,GAAG,UAAA,EAAY,KAAA,EAAO,cAAA,EAAe;AAAA,EACtD;AAEA,EAAA,OAAO,UAAA;AACT;AAOO,SAAS,qBAAqB,MAAA,EAA0D;AAC7F,EAAA,OAAO,sBAAA,CAAuB,eAAA,CAAgB,MAAM,CAAC,CAAA;AACvD","file":"chunk-KFZ5MZOM.js","sourcesContent":["/**\n * Normalizes a JSON Schema for RJSF compatibility.\n *\n * Pydantic v2 generates `anyOf: [{type: \"string\"}, {type: \"null\"}]` for optional\n * fields (`str | None`). RJSF renders this as a dropdown selector asking the user\n * to pick a sub-schema, which is terrible UX. The normalizer flattens those\n * patterns on every node (see `flattenAnyOf` in `schema-utils.ts` for the exact\n * rules) and hoists nested `$defs` to the root so composed sub-schemas keep\n * their refs resolvable.\n *\n * The collapse and hoist engines live in `schema-utils.ts` - the\n * consolidated home for the kernel's JSON-Schema plumbing; this module owns the\n * recursive application over a whole schema tree.\n */\nimport { flattenAnyOf, hoistDefsToRoot } from './schema-utils';\n\nexport { hoistDefsToRoot };\n\n/**\n * Recursively normalize a JSON Schema for RJSF.\n * Walks the entire schema tree and flattens anyOf patterns on every node.\n */\nexport function normalizeSchemaForRjsf(schema: Record<string, unknown>): Record<string, unknown> {\n // Flatten anyOf at this level\n let normalized = flattenAnyOf(schema);\n\n // Recurse into properties\n if (normalized.properties && typeof normalized.properties === 'object') {\n const props = normalized.properties as Record<string, unknown>;\n const normalizedProps: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(props)) {\n normalizedProps[key] =\n value && typeof value === 'object'\n ? normalizeSchemaForRjsf(value as Record<string, unknown>)\n : value;\n }\n normalized = { ...normalized, properties: normalizedProps };\n }\n\n // Recurse into items (array schemas)\n if (\n normalized.items &&\n typeof normalized.items === 'object' &&\n !Array.isArray(normalized.items)\n ) {\n normalized = {\n ...normalized,\n items: normalizeSchemaForRjsf(normalized.items as Record<string, unknown>),\n };\n }\n\n // Recurse into anyOf branches that weren't flattened (complex unions)\n if (Array.isArray(normalized.anyOf)) {\n normalized = {\n ...normalized,\n anyOf: (normalized.anyOf as Record<string, unknown>[]).map((branch) =>\n typeof branch === 'object' && branch !== null\n ? normalizeSchemaForRjsf(branch as Record<string, unknown>)\n : branch,\n ),\n };\n }\n\n // Recurse into $defs so nested definitions (e.g. ImageSize) also get anyOf normalized\n if (\n normalized.$defs &&\n typeof normalized.$defs === 'object' &&\n !Array.isArray(normalized.$defs)\n ) {\n const defs = normalized.$defs as Record<string, unknown>;\n const normalizedDefs: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(defs)) {\n normalizedDefs[key] =\n value && typeof value === 'object'\n ? normalizeSchemaForRjsf(value as Record<string, unknown>)\n : value;\n }\n normalized = { ...normalized, $defs: normalizedDefs };\n }\n\n return normalized;\n}\n\n/**\n * Prepare a schema for RJSF: hoist all nested `$defs` to the root, then normalize\n * anyOf patterns. Call this on any schema that composes multiple sub-schemas\n * (each of which may ship its own `$defs`).\n */\nexport function prepareSchemaForRjsf(schema: Record<string, unknown>): Record<string, unknown> {\n return normalizeSchemaForRjsf(hoistDefsToRoot(schema));\n}\n"]}
1
+ {"version":3,"sources":["../src/core/normalize-schema.ts"],"names":[],"mappings":";;;AAsBO,SAAS,uBAAuB,MAAA,EAA0D;AAE/F,EAAA,IAAI,UAAA,GAAa,aAAa,MAAM,CAAA;AAGpC,EAAA,IAAI,UAAA,CAAW,UAAA,IAAc,OAAO,UAAA,CAAW,eAAe,QAAA,EAAU;AACtE,IAAA,MAAM,QAAQ,UAAA,CAAW,UAAA;AACzB,IAAA,MAAM,kBAA2C,EAAC;AAClD,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAChD,MAAA,eAAA,CAAgB,GAAG,IACjB,KAAA,IAAS,OAAO,UAAU,QAAA,GACtB,sBAAA,CAAuB,KAAgC,CAAA,GACvD,KAAA;AAAA,IACR;AACA,IAAA,UAAA,GAAa,EAAE,GAAG,UAAA,EAAY,UAAA,EAAY,eAAA,EAAgB;AAAA,EAC5D;AAGA,EAAA,IACE,UAAA,CAAW,KAAA,IACX,OAAO,UAAA,CAAW,KAAA,KAAU,QAAA,IAC5B,CAAC,KAAA,CAAM,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA,EAC/B;AACA,IAAA,UAAA,GAAa;AAAA,MACX,GAAG,UAAA;AAAA,MACH,KAAA,EAAO,sBAAA,CAAuB,UAAA,CAAW,KAAgC;AAAA,KAC3E;AAAA,EACF;AAGA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA,EAAG;AACnC,IAAA,UAAA,GAAa;AAAA,MACX,GAAG,UAAA;AAAA,MACH,KAAA,EAAQ,WAAW,KAAA,CAAoC,GAAA;AAAA,QAAI,CAAC,WAC1D,OAAO,MAAA,KAAW,YAAY,MAAA,KAAW,IAAA,GACrC,sBAAA,CAAuB,MAAiC,CAAA,GACxD;AAAA;AACN,KACF;AAAA,EACF;AAGA,EAAA,IACE,UAAA,CAAW,KAAA,IACX,OAAO,UAAA,CAAW,KAAA,KAAU,QAAA,IAC5B,CAAC,KAAA,CAAM,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA,EAC/B;AACA,IAAA,MAAM,OAAO,UAAA,CAAW,KAAA;AACxB,IAAA,MAAM,iBAA0C,EAAC;AACjD,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,EAAG;AAC/C,MAAA,cAAA,CAAe,GAAG,IAChB,KAAA,IAAS,OAAO,UAAU,QAAA,GACtB,sBAAA,CAAuB,KAAgC,CAAA,GACvD,KAAA;AAAA,IACR;AACA,IAAA,UAAA,GAAa,EAAE,GAAG,UAAA,EAAY,KAAA,EAAO,cAAA,EAAe;AAAA,EACtD;AAEA,EAAA,OAAO,UAAA;AACT;AAOO,SAAS,qBAAqB,MAAA,EAA0D;AAC7F,EAAA,OAAO,sBAAA,CAAuB,eAAA,CAAgB,MAAM,CAAC,CAAA;AACvD","file":"chunk-LBFZXYCY.js","sourcesContent":["/**\n * Normalizes a JSON Schema for RJSF compatibility.\n *\n * Pydantic v2 generates `anyOf: [{type: \"string\"}, {type: \"null\"}]` for optional\n * fields (`str | None`). RJSF renders this as a dropdown selector asking the user\n * to pick a sub-schema, which is terrible UX. The normalizer flattens those\n * patterns on every node (see `flattenAnyOf` in `schema-utils.ts` for the exact\n * rules) and hoists nested `$defs` to the root so composed sub-schemas keep\n * their refs resolvable.\n *\n * The collapse and hoist engines live in `schema-utils.ts` - the\n * consolidated home for the kernel's JSON-Schema plumbing; this module owns the\n * recursive application over a whole schema tree.\n */\nimport { flattenAnyOf, hoistDefsToRoot } from './schema-utils';\n\nexport { hoistDefsToRoot };\n\n/**\n * Recursively normalize a JSON Schema for RJSF.\n * Walks the entire schema tree and flattens anyOf patterns on every node.\n */\nexport function normalizeSchemaForRjsf(schema: Record<string, unknown>): Record<string, unknown> {\n // Flatten anyOf at this level\n let normalized = flattenAnyOf(schema);\n\n // Recurse into properties\n if (normalized.properties && typeof normalized.properties === 'object') {\n const props = normalized.properties as Record<string, unknown>;\n const normalizedProps: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(props)) {\n normalizedProps[key] =\n value && typeof value === 'object'\n ? normalizeSchemaForRjsf(value as Record<string, unknown>)\n : value;\n }\n normalized = { ...normalized, properties: normalizedProps };\n }\n\n // Recurse into items (array schemas)\n if (\n normalized.items &&\n typeof normalized.items === 'object' &&\n !Array.isArray(normalized.items)\n ) {\n normalized = {\n ...normalized,\n items: normalizeSchemaForRjsf(normalized.items as Record<string, unknown>),\n };\n }\n\n // Recurse into anyOf branches that weren't flattened (complex unions)\n if (Array.isArray(normalized.anyOf)) {\n normalized = {\n ...normalized,\n anyOf: (normalized.anyOf as Record<string, unknown>[]).map((branch) =>\n typeof branch === 'object' && branch !== null\n ? normalizeSchemaForRjsf(branch as Record<string, unknown>)\n : branch,\n ),\n };\n }\n\n // Recurse into $defs so nested definitions (e.g. ImageSize) also get anyOf normalized\n if (\n normalized.$defs &&\n typeof normalized.$defs === 'object' &&\n !Array.isArray(normalized.$defs)\n ) {\n const defs = normalized.$defs as Record<string, unknown>;\n const normalizedDefs: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(defs)) {\n normalizedDefs[key] =\n value && typeof value === 'object'\n ? normalizeSchemaForRjsf(value as Record<string, unknown>)\n : value;\n }\n normalized = { ...normalized, $defs: normalizedDefs };\n }\n\n return normalized;\n}\n\n/**\n * Prepare a schema for RJSF: hoist all nested `$defs` to the root, then normalize\n * anyOf patterns. Call this on any schema that composes multiple sub-schemas\n * (each of which may ship its own `$defs`).\n */\nexport function prepareSchemaForRjsf(schema: Record<string, unknown>): Record<string, unknown> {\n return normalizeSchemaForRjsf(hoistDefsToRoot(schema));\n}\n"]}
@@ -89,6 +89,16 @@ function resolveSchemaIndirection(schema, defs) {
89
89
  return current;
90
90
  }
91
91
  }
92
+ var MAX_INDIRECTION_HOPS = 16;
93
+ function resolveSchemaNode(schema, defs) {
94
+ let current = schema;
95
+ for (let hop = 0; hop < MAX_INDIRECTION_HOPS; hop++) {
96
+ const next = collapseNullable(derefSchema(current, defs));
97
+ if (next === current) return current;
98
+ current = next;
99
+ }
100
+ return current;
101
+ }
92
102
  function collectSchemaDefs(schema, into, options) {
93
103
  for (const [key, value] of Object.entries(schema)) {
94
104
  if (key === "$defs" && value && typeof value === "object" && !Array.isArray(value)) {
@@ -141,6 +151,6 @@ function hoistDefsToRoot(schema) {
141
151
  return stripped;
142
152
  }
143
153
 
144
- export { collapseNullable, collectSchemaDefs, derefSchema, flattenAnyOf, hoistDefsToRoot, resolveSchemaIndirection, schemaTypeOf };
145
- //# sourceMappingURL=chunk-O4O3QNVC.js.map
146
- //# sourceMappingURL=chunk-O4O3QNVC.js.map
154
+ export { collapseNullable, collectSchemaDefs, derefSchema, flattenAnyOf, hoistDefsToRoot, resolveSchemaIndirection, resolveSchemaNode, schemaTypeOf };
155
+ //# sourceMappingURL=chunk-OD3VYXUY.js.map
156
+ //# sourceMappingURL=chunk-OD3VYXUY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/schema-utils.ts"],"names":[],"mappings":"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","file":"chunk-OD3VYXUY.js","sourcesContent":["/**\n * The ONE place JSON-Schema plumbing lives for the run form kernel: nullable\n * `anyOf` collapsing, `$defs` collection/hoisting, `$ref` resolution, and type\n * extraction. Consolidated from three drifted private copies (`field-model.ts`,\n * `normalize-schema.ts`, `input-format.ts`) during the form-kernel extraction.\n *\n * Two collapse engines are exported ON PURPOSE - their semantics genuinely\n * differ, and each caller's observable behavior is characterized and preserved\n * (see `schema-utils-characterization.test.ts`):\n *\n * - `collapseNullable` (the field mapper's rule): collapse ONLY when exactly one\n * non-null branch remains, dropping the null. A multi-branch union stays an\n * `anyOf` and maps to the raw-JSON escape hatch.\n * - `flattenAnyOf` (the RJSF/ajv rule): a union of simple primitives becomes a\n * `type` ARRAY (null preserved as a type entry - ajv validates `type:\n * [\"number\",\"null\"]` natively); a 2-branch nullable non-primitive drops the\n * null branch.\n *\n * Whether these converge (and which semantics win) is an M1 question, decided\n * when the descriptor derivation moves server-side - not silently here.\n */\n\nexport type JsonSchema = Record<string, unknown>;\n\n// ─── anyOf collapsing ────────────────────────────────────────────────────────\n\n/** Pydantic emits `anyOf: [T, {type:'null'}]` for Optional[T]; collapse to T.\n * Collapses ONLY when exactly one non-null branch remains; the branch merges\n * OVER the outer schema (its constraints win). */\nexport function collapseNullable(schema: JsonSchema): JsonSchema {\n const anyOf = schema.anyOf;\n if (!Array.isArray(anyOf)) return schema;\n const branches = anyOf as JsonSchema[];\n const nonNull = branches.filter((b) => b?.type !== 'null');\n if (nonNull.length === 1) return { ...stripAnyOf(schema), ...nonNull[0] };\n return schema;\n}\n\nconst PRIMITIVE_TYPES = new Set(['string', 'number', 'integer', 'boolean', 'null']);\n\n/**\n * An `anyOf` entry that is a simple primitive with no constraints:\n * `{type: \"string\"}` is simple, `{type: \"string\", maxLength: 10}` is not.\n */\nfunction isSimplePrimitive(schema: JsonSchema): boolean {\n const type = schema.type;\n if (typeof type !== 'string' || !PRIMITIVE_TYPES.has(type)) return false;\n const allowedKeys = new Set(['type', 'default', 'description', 'title']);\n return Object.keys(schema).every((key) => allowedKeys.has(key));\n}\n\n/**\n * The RJSF/ajv collapse: a union of simple primitives flattens to a `type`\n * array (`anyOf: [{string},{null}]` → `type: [\"string\",\"null\"]`, which RJSF\n * renders as a plain input instead of a sub-schema picker); an exactly-two\n * `[T, {null}]` union of anything else drops the null branch and merges T over\n * the outer schema. Anything else passes through unchanged.\n */\nexport function flattenAnyOf(schema: JsonSchema): JsonSchema {\n const anyOf = schema.anyOf;\n if (!Array.isArray(anyOf)) return schema;\n\n const branches = anyOf as JsonSchema[];\n\n // Case 1: all branches are simple primitives → type array.\n if (\n branches.every(\n (branch) => typeof branch === 'object' && branch !== null && isSimplePrimitive(branch),\n )\n ) {\n const types: string[] = [];\n let mergedDefault: unknown = undefined;\n for (const branch of branches) {\n types.push(branch.type as string);\n if (branch.default !== undefined) mergedDefault = branch.default;\n }\n const { anyOf: _, ...rest } = schema;\n const result: JsonSchema = { ...rest, type: types };\n if (mergedDefault !== undefined && result.default === undefined) {\n result.default = mergedDefault;\n }\n return result;\n }\n\n // Case 2: exactly [T, {type: \"null\"}] - nullable non-primitive → drop null.\n if (branches.length === 2) {\n const nullBranch = branches.find(\n (b) => typeof b === 'object' && b !== null && b.type === 'null',\n );\n const otherBranch = branches.find((b) => b !== nullBranch);\n if (nullBranch && otherBranch && typeof otherBranch === 'object') {\n const { anyOf: _, ...rest } = schema;\n return { ...rest, ...otherBranch };\n }\n }\n\n return schema;\n}\n\nfunction stripAnyOf(schema: JsonSchema): JsonSchema {\n const { anyOf: _drop, ...rest } = schema;\n return rest;\n}\n\n// ─── Type & $ref helpers ─────────────────────────────────────────────────────\n\n/** The schema's non-null type: a bare `type` string, or the first non-null\n * entry of a `type` array (`[\"number\",\"null\"]` → `\"number\"`). */\nexport function schemaTypeOf(schema: JsonSchema): string | undefined {\n const t = schema.type;\n if (typeof t === 'string') return t;\n if (Array.isArray(t)) return t.find((x) => x !== 'null') as string | undefined;\n return undefined;\n}\n\n/** Resolve `{ $ref: \"#/$defs/Foo\" }` against a collected `$defs` map. Sibling\n * keys on the referencing schema override the definition's. */\nexport function derefSchema(schema: JsonSchema, defs: Record<string, JsonSchema>): JsonSchema {\n const ref = schema.$ref;\n if (typeof ref === 'string' && ref.startsWith('#/$defs/')) {\n const resolved = defs[ref.slice('#/$defs/'.length)];\n if (resolved) return { ...resolved, ...stripRef(schema) };\n }\n return schema;\n}\n\nfunction stripRef(schema: JsonSchema): JsonSchema {\n const { $ref: _drop, ...rest } = schema;\n return rest;\n}\n\n/**\n * Resolve the indirection layers pydantic puts in front of a value schema - a\n * `#/$defs/...` ref, a nullable `anyOf: [T, {null}]` wrapper, or a stack of\n * both - until a plain schema remains. This is the resolver `healStringWrappers`\n * and `pruneEmptyOptionals` apply before reading `type`. Unlike `derefSchema`\n * it REPLACES the schema with the definition (no sibling merge) - exactly what\n * the callers did inline.\n */\nexport function resolveSchemaIndirection(\n schema: JsonSchema,\n defs: Record<string, JsonSchema>,\n): JsonSchema {\n let current = schema;\n for (;;) {\n const ref = current.$ref;\n if (typeof ref === 'string' && ref.startsWith('#/$defs/')) {\n const resolved = defs[ref.slice('#/$defs/'.length)];\n if (resolved) {\n current = resolved;\n continue;\n }\n }\n if (Array.isArray(current.anyOf)) {\n const nonNull = (current.anyOf as JsonSchema[]).filter((b) => b?.type !== 'null');\n if (nonNull.length === 1) {\n current = nonNull[0]!;\n continue;\n }\n }\n return current;\n }\n}\n\n/** How many indirection layers `resolveSchemaNode` unwraps before concluding\n * the chain is cyclic and returning what it has. Pydantic stacks at most a\n * handful (`$ref` inside a nullable `anyOf`); real chains end long before. */\nconst MAX_INDIRECTION_HOPS = 16;\n\n/**\n * Resolve one schema node to its fixpoint under `derefSchema` +\n * `collapseNullable`, so a nullable REFERENCE - pydantic's `anyOf: [{$ref},\n * {type:'null'}]` for an Optional concept-typed field - resolves the same way\n * a bare `$ref` or an inline nullable does. One pass of either helper cannot:\n * collapsing the nullable surfaces a `$ref` that was inside a branch, and\n * dereferencing can surface a nullable the definition holds. Sibling keys\n * still merge over the definition (`derefSchema`'s semantics - unlike\n * `resolveSchemaIndirection`, which replaces). Identity is the fixpoint test:\n * both helpers return the schema unchanged when they have nothing to do.\n */\nexport function resolveSchemaNode(\n schema: JsonSchema,\n defs: Record<string, JsonSchema>,\n): JsonSchema {\n let current = schema;\n for (let hop = 0; hop < MAX_INDIRECTION_HOPS; hop++) {\n const next = collapseNullable(derefSchema(current, defs));\n if (next === current) return current;\n current = next;\n }\n return current;\n}\n\n// ─── $defs collection ────────────────────────────────────────────────────────\n\nexport interface CollectDefsOptions {\n /**\n * Whether the walk descends into JSON arrays (`anyOf`/`allOf` branches,\n * tuple `items`). The field mapper always has (so a def declared inside an\n * `anyOf` branch resolves); the wire-format heal path historically has NOT -\n * both behaviors are characterized, and each caller states its mode\n * explicitly until M1 retires the difference.\n */\n traverseArrays: boolean;\n}\n\n/**\n * Collect every nested `$defs` entry into one flat map so `#/$defs/...` refs\n * resolve from anywhere. First definition of a name wins. The input schema is\n * NOT modified (see `hoistDefsToRoot` for the stripping variant).\n */\nexport function collectSchemaDefs(\n schema: JsonSchema,\n into: Record<string, JsonSchema>,\n options: CollectDefsOptions,\n): Record<string, JsonSchema> {\n for (const [key, value] of Object.entries(schema)) {\n if (key === '$defs' && value && typeof value === 'object' && !Array.isArray(value)) {\n for (const [name, def] of Object.entries(value as Record<string, JsonSchema>)) {\n if (!(name in into) && def && typeof def === 'object') {\n into[name] = def;\n collectSchemaDefs(def, into, options);\n }\n }\n } else if (value && typeof value === 'object' && !Array.isArray(value)) {\n collectSchemaDefs(value as JsonSchema, into, options);\n } else if (options.traverseArrays && Array.isArray(value)) {\n // Any object item recurses - including a nested array, whose indices then\n // walk as keys (matches the historical field-model walker exactly).\n for (const item of value) {\n if (item && typeof item === 'object') {\n collectSchemaDefs(item as JsonSchema, into, options);\n }\n }\n }\n }\n return into;\n}\n\n// ─── $defs hoisting (the RJSF wrapper-schema case) ──────────────────────────\n\n/**\n * Walk a schema, collect every `$defs` entry into `collected`, and return the\n * schema with those nested `$defs` STRIPPED. Cycle-safe: a def name is claimed\n * (placeholder) before its body is walked.\n */\nfunction walkAndCollectDefs(schema: JsonSchema, collected: JsonSchema): JsonSchema {\n const out: JsonSchema = {};\n for (const [key, value] of Object.entries(schema)) {\n if (key === '$defs' && value && typeof value === 'object' && !Array.isArray(value)) {\n for (const [defName, defValue] of Object.entries(value as JsonSchema)) {\n if (!(defName in collected) && defValue && typeof defValue === 'object') {\n collected[defName] = {};\n collected[defName] = walkAndCollectDefs(defValue as JsonSchema, collected);\n }\n }\n // Don't copy `$defs` into out - it's hoisted to the root.\n } else if (value && typeof value === 'object' && !Array.isArray(value)) {\n out[key] = walkAndCollectDefs(value as JsonSchema, collected);\n } else if (Array.isArray(value)) {\n out[key] = value.map((item) =>\n item && typeof item === 'object' && !Array.isArray(item)\n ? walkAndCollectDefs(item as JsonSchema, collected)\n : item,\n );\n } else {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Hoist every nested `$defs` to the schema root, so a wrapper schema composed\n * from several pydantic sub-schemas keeps its `#/$defs/...` refs resolvable.\n * (Pydantic emits one `$defs` per model; composing models as properties of a\n * wrapper object leaves those maps below the root the refs point to.)\n */\nexport function hoistDefsToRoot(schema: JsonSchema): JsonSchema {\n const collected: JsonSchema = {};\n const stripped = walkAndCollectDefs(schema, collected);\n if (Object.keys(collected).length > 0) {\n stripped.$defs = collected;\n }\n return stripped;\n}\n"]}
@@ -1,10 +1,10 @@
1
- import { prepareSchemaForRjsf } from './chunk-KFZ5MZOM.js';
2
- import { pruneEmptyOptionals, healStringWrappers } from './chunk-7BMOPXDY.js';
1
+ import { pruneEmptyOptionals, healStringWrappers } from './chunk-GCXV76WU.js';
2
+ import { prepareSchemaForRjsf } from './chunk-LBFZXYCY.js';
3
3
  import { isFilled, mustBeFilled, fieldFilled } from './chunk-PM55AHRO.js';
4
- import { buildRunFields } from './chunk-VMLXO5JS.js';
5
- import { inputMustBeFilled, isOptionalInput, isPluralInput } from './chunk-HARYEWNG.js';
6
- import { ownProp } from './chunk-V3OV2XQT.js';
7
4
  import { validateRunInputsSchema } from './chunk-VTPUDBSJ.js';
5
+ import { gatingFieldsFromInputs } from './chunk-YEVVR6FE.js';
6
+ import { inputMustBeFilled, isOptionalInput, isPluralInput } from './chunk-XYQ7NXQZ.js';
7
+ import { ownProp } from './chunk-V3OV2XQT.js';
8
8
 
9
9
  // src/core/gate.ts
10
10
  function buildRunInputsSchema(inputs) {
@@ -89,11 +89,11 @@ function gateRunInputs(contract, data) {
89
89
  preparedData
90
90
  };
91
91
  }
92
- const missingInputs = buildRunFields(contract.inputs).filter((field) => mustBeFilled(field) || isFilled(ownProp(preparedData, field.name))).filter((field) => !fieldFilled(field, ownProp(preparedData, field.name))).map((field) => field.name);
92
+ const missingInputs = gatingFieldsFromInputs(contract.inputs).filter((field) => mustBeFilled(field) || isFilled(ownProp(preparedData, field.name))).filter((field) => !fieldFilled(field, ownProp(preparedData, field.name))).map((field) => field.name);
93
93
  if (missingInputs.length) return { ok: false, missingInputs, errors: [], preparedData };
94
94
  return { ok: true, inputs: apiInputsFromSchemaData(preparedData, contract.inputs) };
95
95
  }
96
96
 
97
97
  export { apiInputsFromSchemaData, buildRunInputsSchema, gateRunInputs, prepareRunInputs, validateRunInputs };
98
- //# sourceMappingURL=chunk-XEPD5VJQ.js.map
99
- //# sourceMappingURL=chunk-XEPD5VJQ.js.map
98
+ //# sourceMappingURL=chunk-R7B7ID25.js.map
99
+ //# sourceMappingURL=chunk-R7B7ID25.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/gate.ts"],"names":[],"mappings":"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","file":"chunk-R7B7ID25.js","sourcesContent":["/**\n * The gate every run passes through, whichever surface starts it.\n *\n * A host may offer more than one place to run a method - a schema-driven\n * inputs panel (an RJSF form) and a standalone run page (a bespoke field\n * renderer), say. **Only the rendering of inputs differs.** Everything between\n * \"the user pressed Run\" and \"the payload goes on the wire\" lives here, so\n * those surfaces cannot drift:\n *\n * 1. `buildRunInputsSchema` - the combined JSON Schema, whose `required` list\n * is `inputMustBeFilled` (optional `?` and variable-plural `[]` never\n * block; a fixed-count `[N]` list does).\n * 2. `prepareRunInputs` - heal legacy wrappers, then prune empty optionals.\n * 3. `validateRunInputs` - ajv, plus the missing-input scan that names the\n * VARIABLE at fault.\n * 4. `apiInputsFromSchemaData` - the `{concept, content}` payload, built from\n * the PREPARED data so the pruning reaches the wire too.\n *\n * **`gateRunInputs` is that whole chain as one call**, and it is what a server\n * should use. The four steps stay exported because a host that renders its own\n * panel needs the schema and the verdict separately, but assembling them is not\n * a host's job: the step between validation and the payload is an emptiness\n * check with four look-alike predicates to choose from, only two of which are\n * the ones the Run button reads, and choosing the near-miss pair produces a\n * server that is more permissive than the button in front of it.\n *\n * This module is pure: no React, no toasts, no i18n. Each caller renders the\n * verdict in its own idiom.\n */\nimport { inputMustBeFilled, isOptionalInput, isPluralInput } from './contracts';\nimport { gatingFieldsFromInputs } from './gating-fields';\nimport { fieldFilled, isFilled, mustBeFilled } from './readiness';\nimport { validateRunInputsSchema, type RunInputError } from './gate-validator';\nimport { healStringWrappers, pruneEmptyOptionals } from './wire-format';\nimport { prepareSchemaForRjsf } from './normalize-schema';\nimport { ownProp } from './own-property';\nimport type { PipeIOContract, PipeInputContract } from './contracts';\n\ntype Dict = Record<string, unknown>;\n\n/**\n * Combine the per-input schemas into one object schema.\n *\n * Each input variable becomes a property, titled with the variable name and\n * described with the concept code. Only the inputs the method actually demands\n * land in `required` - an optional (`?`) or variable-plural (`[]`) input must\n * not block the form (see `inputMustBeFilled`).\n *\n * The per-input schema travels verbatim, which is how a fixed-count (`[N]`)\n * list's declared count reaches ajv: pipelex states it as `minItems`/`maxItems`\n * on the array wrapper, so the count is enforced here without the kernel\n * restating it.\n */\nexport function buildRunInputsSchema(inputs: Record<string, PipeInputContract>): Dict {\n const properties: Dict = {};\n const required: string[] = [];\n\n for (const [varName, input] of Object.entries(inputs)) {\n const inputSchema = { ...input.json_schema } as Dict;\n inputSchema.title = varName;\n if (input.concept_ref && input.concept_ref !== varName) {\n inputSchema.description = input.concept_ref;\n }\n properties[varName] = inputSchema;\n if (inputMustBeFilled(input)) required.push(varName);\n }\n\n return prepareSchemaForRjsf({ type: 'object', properties, required });\n}\n\n/**\n * Last-gate repair, in two passes against the exact schema ajv validates.\n *\n * 1. **HEAL** - persisted `inputData` may still carry the legacy double-wrapped\n * `{ text: { text } }` shape (or a form's state may lag a store fix).\n * 2. **PRUNE** - RJSF fills every unset string with `\"\"` on mount, which an\n * optional CONSTRAINED field rejects (`DateContent.time` is `format: \"time\"`)\n * - blocking a run on a field the user never opened. An absent optional field\n * is what pipelex means by `None` anyway, so dropping it is the semantically\n * correct repair, not a validation dodge.\n */\nexport function prepareRunInputs(data: Dict, schema: Dict): Dict {\n return pruneEmptyOptionals(healStringWrappers(data, schema), schema) as Dict;\n}\n\nexport interface RunInputsVerdict {\n isValid: boolean;\n /** Variable names the scan could pin a missing required field on. */\n missingInputs: string[];\n /** Raw ajv errors, for when the scan cannot name anything. */\n errors: RunInputError[];\n}\n\n/**\n * Validate PREPARED data against the combined schema.\n *\n * `missingInputs` is reported by VARIABLE NAME - the name on the node the user\n * clicks - never by `concept_ref`, which used to produce \"Missing required\n * fields in: native.Text\" and named neither the input at fault nor, when several\n * inputs share a concept, which of them. Inputs the method declared omittable\n * are skipped: an empty `?` input is not a missing one.\n *\n * The scan can come up empty even on an invalid form (a wrong value shape, a\n * nested mismatch); `errors` is what the caller falls back to then, so a failure\n * is never undiagnosable.\n *\n * `preparedData` is `unknown` for the same reason `gateRunInputs`' `data` is: on\n * a public endpoint the body is whatever was sent, and ajv must be shown that\n * rather than a repair of it, or `must be object` is a verdict no caller can\n * ever receive. The name-the-variable scan below needs something indexable, so\n * a body that is not a record scans against nothing and names every input the\n * method demands - which is the honest answer for a body that carried none.\n */\nexport function validateRunInputs(\n preparedData: unknown,\n inputs: Record<string, PipeInputContract>,\n schema: Dict,\n): RunInputsVerdict {\n const errors = validateRunInputsSchema(preparedData, schema);\n if (errors.length === 0) return { isValid: true, missingInputs: [], errors: [] };\n\n const record0 =\n typeof preparedData === 'object' && preparedData !== null && !Array.isArray(preparedData)\n ? (preparedData as Dict)\n : undefined;\n const missingInputs: string[] = [];\n for (const [varName, input] of Object.entries(inputs)) {\n if (!inputMustBeFilled(input)) continue;\n const varData = ownProp(record0, varName);\n // A demanded input that is not there at all is missing whatever its concept\n // declares inside. The check has to come BEFORE the required-children one:\n // a struct whose concept demands no child used to fall out of the scan\n // here, so the run was refused by the combined schema's `required` list\n // while the scan named nothing and the caller could only quote ajv.\n if (varData == null) {\n missingInputs.push(varName);\n continue;\n }\n const inputSchema = input.json_schema as Dict;\n const requiredFields = (inputSchema.required as string[]) || [];\n if (requiredFields.length === 0) continue;\n if (typeof varData !== 'object') {\n missingInputs.push(varName);\n continue;\n }\n const record = varData as Dict;\n if (\n requiredFields.some((f) => {\n const child = ownProp(record, f);\n return child === undefined || child === '';\n })\n ) {\n missingInputs.push(varName);\n }\n }\n\n return { isValid: false, missingInputs, errors };\n}\n\n/**\n * Build the `{ concept, content }` map a run expects, from PREPARED data.\n *\n * The two ways a method says \"this may be nothing\" land differently on the wire:\n * - an **optional (`?`)** input left blank is OMITTED, so the runtime records a\n * real absence the method can branch on - sending `{ text: \"\" }` would look\n * like the caller supplied an empty string;\n * - a **plural (`[]`)** input keeps its key, because a plural slot is never\n * absent - its empty form IS the empty list, and the runtime requires the key.\n * When that list is EMPTY it is sent BARE, without the envelope: the envelope\n * is the runtime's \"explicit form\", which bypasses the top-down shaper and\n * hands the value to the bottom-up factory, and that factory types a list from\n * its first item - so an empty list there raises \"Cannot create Stuff from\n * empty list in content\". The bare form keeps the shaper, which reads the\n * DECLARED concept and builds the empty list correctly. (pipelex/#1096 makes\n * the envelope agree; the bare form runs today and stays correct after.)\n *\n * A fixed-count (`[N]`) list takes that same empty-list path, and an empty one\n * is not a payload the method can accept. It is unreachable through the gate,\n * which demands the input and rejects a short list on `minItems`; building the\n * payload straight from unvalidated data is what would reach it, and inventing\n * a different absence here would only hide that.\n */\nexport function apiInputsFromSchemaData(\n preparedData: Dict,\n inputs: Record<string, PipeInputContract>,\n): Dict {\n const out: Dict = {};\n for (const [varName, input] of Object.entries(inputs)) {\n const raw = ownProp(preparedData, varName);\n if (isOptionalInput(input) && !isFilled(raw)) continue;\n if (isPluralInput(input) && !isFilled(raw)) {\n out[varName] = [];\n continue;\n }\n const content = isPluralInput(input) && !Array.isArray(raw) ? [] : raw;\n out[varName] = { concept: input.concept_ref, content };\n }\n return out;\n}\n\n// ─── The whole chain, as one call ────────────────────────────────────────────\n\n/** The gate's verdict: the payload to send, or why the run may not start. */\nexport type RunInputsGateResult =\n | { ok: true; inputs: Dict }\n | {\n ok: false;\n /** Variable names the caller left empty or short. */\n missingInputs: string[];\n /** Raw ajv errors, for when the scan cannot name anything. */\n errors: RunInputError[];\n /** The repaired data the verdict was reached on - what\n * `describeValidationError` needs to quote the value it received. */\n preparedData: Dict;\n };\n\n/**\n * One schema object per contract, for the lifetime of the process.\n *\n * `buildRunInputsSchema` is a pure function of the contract, so rebuilding it is\n * *semantically* free - but validation runs through a module-level ajv whose\n * compiled-schema cache is keyed on **schema object identity** and is never\n * evicted. A fresh object per call therefore misses every time and retains\n * another compiled validator. On a publicly callable server endpoint that is\n * unbounded growth driven by the cheapest request there is: an empty body,\n * rejected in a fraction of a millisecond, costing nothing to send.\n *\n * Weakly keyed so the map never pins a contract that goes out of scope. The\n * schema a run has been gated against is pinned for the process lifetime anyway\n * by ajv's own cache; the point is that the NUMBER of them is bounded by the\n * number of distinct contracts rather than by traffic.\n */\nconst SCHEMA_CACHE = new WeakMap<PipeIOContract, Dict>();\n\nfunction schemaForContract(contract: PipeIOContract): Dict {\n const cached = SCHEMA_CACHE.get(contract);\n if (cached) return cached;\n const schema = buildRunInputsSchema(contract.inputs);\n SCHEMA_CACHE.set(contract, schema);\n return schema;\n}\n\n/**\n * Gate a run: repair the caller's data, validate it against the contract,\n * refuse anything the Run button would have refused, and build the payload.\n *\n * Returns a verdict rather than throwing. A server endpoint must not throw\n * across the boundary to its client - frameworks routinely strip the message to\n * an opaque digest in a production build - and a rejection here is an ordinary\n * outcome, not an exceptional one.\n *\n * **`data` is `unknown` because that is what it is.** A run endpoint is public\n * and no framework enforces a declared parameter type, so the argument is\n * whatever the caller put in the body. The chain indexes the payload by\n * variable name without first checking it is indexable, so a `null` body would\n * throw *after* ajv had already reported `must be object` and before any\n * verdict came back.\n *\n * So the two uses are separated rather than collapsed into one repair. **ajv\n * judges the value the caller actually sent**, which is what makes `must be\n * object` the verdict for a body that is not one; the indexing that follows\n * uses a safe `{}` stand-in, which is what keeps the gate from throwing. Repair\n * FIRST - coercing a non-object to `{}` before validating - is the shape that\n * looks equivalent and is not: a contract whose inputs are all optional has an\n * empty `required` list, so `{}` validates, and `gateRunInputs(\"garbage\", …)`\n * came back `ok: true` on a body the caller never meant as a run. Fail-closed\n * costs a malformed caller an error it earned; the other direction bills a run.\n *\n * **The schema alone is not the rule.** ajv's `required` asserts only that a\n * key is PRESENT, and a content model carries no `minLength` - so a required\n * input that arrived empty (`{document: {url: \"\"}}`, `{text: {text: \"\"}}`)\n * satisfies the schema. That is the natural payload, not a contrived one:\n * `rjsfDataFromRunValues({}, fields)` emits exactly that when nothing is\n * selected. So the gate re-applies the emptiness rule using\n * `computeReadiness`'s OWN two functions over the same derived fields, which is\n * the only way to be sure the two sides cannot disagree.\n *\n * Picking a pair that merely looks equivalent is not enough, and this is the\n * whole reason the assembly lives here rather than in each host: the obvious\n * choice, `inputMustBeFilled` + `isFilled`, agrees on every leaf kind and\n * diverges on a structured concept in BOTH directions - `isFilled` on an object\n * is `some(child filled)` where `fieldFilled` is `every(required child\n * filled)`. That accepts a half-filled struct the browser refuses (a paid run\n * past a disabled button) and rejects a filled all-optional struct the browser\n * accepts. A host whose methods happen to use only native concepts has no test\n * that can catch either.\n */\nexport function gateRunInputs(contract: PipeIOContract, data: unknown): RunInputsGateResult {\n const isRecord = typeof data === 'object' && data !== null && !Array.isArray(data);\n const payload = isRecord ? (data as Dict) : {};\n const schema = schemaForContract(contract);\n const preparedData = prepareRunInputs(payload, schema);\n\n // The value ajv is shown is the caller's, not the stand-in - see above.\n const verdict = validateRunInputs(isRecord ? preparedData : data, contract.inputs, schema);\n if (!verdict.isValid) {\n return {\n ok: false,\n missingInputs: verdict.missingInputs,\n errors: verdict.errors,\n preparedData,\n };\n }\n\n // The SELECTION is half the rule, and leaving it out is what made the two\n // halves disagree. `computeReadiness` reckons with an input the method\n // demands AND one it left optional that the user has touched - because a\n // structure with something in it owes its concept every field the concept\n // declares. Filtering on `mustBeFilled` alone dropped the second kind, so a\n // half-filled optional struct was refused by the button and accepted here.\n //\n // ajv cannot cover for it: a required child that is a plain string arrives as\n // `''`, which satisfies `required`. And the payload builder below then SENDS\n // that input, because `isFilled` is exactly what it tests. Which is the rule\n // stated properly: the gate holds to its concept precisely the inputs it is\n // about to put on the wire, and nothing it will omit.\n //\n // The tree the predicates run over is the gate's own, walked structurally\n // from the same `json_schema` ajv just validated - NOT the render tree, which\n // is built from the input-form descriptor. A server validating a payload must\n // never need that presentation artifact; see `gating-fields.ts`.\n const missingInputs = gatingFieldsFromInputs(contract.inputs)\n .filter((field) => mustBeFilled(field) || isFilled(ownProp(preparedData, field.name)))\n .filter((field) => !fieldFilled(field, ownProp(preparedData, field.name)))\n .map((field) => field.name);\n if (missingInputs.length) return { ok: false, missingInputs, errors: [], preparedData };\n\n return { ok: true, inputs: apiInputsFromSchemaData(preparedData, contract.inputs) };\n}\n"]}
@@ -1,7 +1,7 @@
1
- import { inflateAllInputs, deflateAllInputs } from './chunk-7BMOPXDY.js';
1
+ import { buildRunFields } from './chunk-J4L32LD6.js';
2
+ import { inflateAllInputs, deflateAllInputs } from './chunk-GCXV76WU.js';
2
3
  import { isFilled } from './chunk-PM55AHRO.js';
3
- import { buildRunFields } from './chunk-VMLXO5JS.js';
4
- import { isOptionalInput, isPluralInput } from './chunk-HARYEWNG.js';
4
+ import { isOptionalInput, isPluralInput } from './chunk-XYQ7NXQZ.js';
5
5
  import { ownProp } from './chunk-V3OV2XQT.js';
6
6
 
7
7
  // src/core/values.ts
@@ -29,7 +29,7 @@ function fromRjsf(field, value) {
29
29
  switch (field.kind) {
30
30
  case "text":
31
31
  case "prose":
32
- return textFromStored(value);
32
+ return textFromStored(unwrapContent(field, value));
33
33
  case "number": {
34
34
  const raw = unwrapContent(field, value);
35
35
  if (typeof raw === "number") return Number.isFinite(raw) ? raw : void 0;
@@ -177,8 +177,8 @@ function inputDataFromWorkingMemory(workingMemory, inputSchemas) {
177
177
  if (!found) return null;
178
178
  return deflateAllInputs(full, inputSchemas);
179
179
  }
180
- function fieldsForContract(contract) {
181
- return contract?.inputs ? buildRunFields(contract.inputs) : [];
180
+ function fieldsForContract(contract, descriptor) {
181
+ return contract?.inputs && descriptor ? buildRunFields(descriptor, contract.inputs) : [];
182
182
  }
183
183
  function setValueAtPath(root, path, value) {
184
184
  const head = path[0];
@@ -197,5 +197,5 @@ function setValueAtPath(root, path, value) {
197
197
  }
198
198
 
199
199
  export { apiInputsFromRunValues, fieldsForContract, inputDataFromWorkingMemory, outputsFromPipeOutput, rjsfDataFromRunValues, runValuesFromStore, setValueAtPath, storeInputDataFromRunValues };
200
- //# sourceMappingURL=chunk-WXBSM2CE.js.map
201
- //# sourceMappingURL=chunk-WXBSM2CE.js.map
200
+ //# sourceMappingURL=chunk-T4NSAGOS.js.map
201
+ //# sourceMappingURL=chunk-T4NSAGOS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/values.ts"],"names":[],"mappings":";;;;;;;AA+CA,SAAS,WAAA,CAAY,OAAiB,KAAA,EAAyB;AAC7D,EAAA,OAAO,KAAA,CAAM,eAAe,MAAA,GAAY,KAAA,GAAQ,EAAE,CAAC,KAAA,CAAM,UAAU,GAAG,KAAA,EAAM;AAC9E;AAOA,SAAS,aAAA,CAAc,OAAiB,KAAA,EAAyB;AAC/D,EAAA,MAAM,MAAM,KAAA,CAAM,UAAA;AAClB,EAAA,IAAI,GAAA,KAAQ,UAAa,CAAC,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA;AACjF,IAAA,OAAO,KAAA;AACT,EAAA,OAAO,OAAA,CAAQ,OAAkC,GAAG,CAAA;AACtD;AAGA,IAAM,gBAAA,GAAmB,CAAA;AAsBzB,SAAS,cAAA,CAAe,KAAA,EAAgB,KAAA,GAAQ,CAAA,EAAuB;AACrE,EAAA,IAAI,KAAA,IAAS,MAAM,OAAO,MAAA;AAC1B,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA;AACtC,EAAA,IAAI,OAAO,UAAU,QAAA,IAAY,OAAO,UAAU,SAAA,EAAW,OAAO,OAAO,KAAK,CAAA;AAChF,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,IAAS,kBAAkB,OAAO,MAAA;AACnE,EAAA,MAAM,QAAA,GAAW,KAAA;AACjB,EAAA,IAAI,UAAU,QAAA,EAAU,OAAO,eAAe,QAAA,CAAS,IAAA,EAAM,QAAQ,CAAC,CAAA;AACtE,EAAA,IAAI,aAAa,QAAA,EAAU,OAAO,eAAe,QAAA,CAAS,OAAA,EAAS,QAAQ,CAAC,CAAA;AAC5E,EAAA,OAAO,MAAA;AACT;AAGA,SAAS,QAAA,CAAS,OAAiB,KAAA,EAAyB;AAC1D,EAAA,QAAQ,MAAM,IAAA;AAAM,IAClB,KAAK,MAAA;AAAA,IACL,KAAK,OAAA;AACH,MAAA,OAAO,cAAA,CAAe,aAAA,CAAc,KAAA,EAAO,KAAK,CAAC,CAAA;AAAA,IACnD,KAAK,QAAA,EAAU;AAIb,MAAA,MAAM,GAAA,GAAM,aAAA,CAAc,KAAA,EAAO,KAAK,CAAA;AACtC,MAAA,IAAI,OAAO,QAAQ,QAAA,EAAU,OAAO,OAAO,QAAA,CAAS,GAAG,IAAI,GAAA,GAAM,MAAA;AACjE,MAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,IAAA,OAAW,EAAA,EAAI;AAChD,QAAA,MAAM,MAAA,GAAS,OAAO,GAAG,CAAA;AACzB,QAAA,OAAO,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,GAAI,MAAA,GAAS,MAAA;AAAA,MAC5C;AACA,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,IACA,KAAK,SAAA,EAAW;AAId,MAAA,MAAM,GAAA,GAAM,aAAA,CAAc,KAAA,EAAO,KAAK,CAAA;AACtC,MAAA,OAAO,OAAO,GAAA,KAAQ,SAAA,GAAY,GAAA,GAAM,MAAA;AAAA,IAC1C;AAAA,IACA,KAAK,SAAA;AAIH,MAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA;AACtC,MAAA,IAAI,KAAA,IAAS,MAAM,OAAO,MAAA;AAC1B,MAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,QAAA,IAAI;AACF,UAAA,OAAO,IAAA,CAAK,SAAA,CAAU,KAAA,EAAO,IAAA,EAAM,CAAC,CAAA;AAAA,QACtC,CAAA,CAAA,MAAQ;AACN,UAAA,OAAO,MAAA;AAAA,QACT;AAAA,MACF;AACA,MAAA,OAAO,OAAO,KAAK,CAAA;AAAA,IACrB,KAAK,UAAA;AAAA,IACL,KAAK,OAAA,EAAS;AACZ,MAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AACtC,QAAA,MAAM,CAAA,GAAI,KAAA;AACV,QAAA,OAAO,CAAA,CAAE,GAAA,GACL,EAAE,GAAA,EAAK,MAAA,CAAO,CAAA,CAAE,GAAG,CAAA,EAAG,QAAA,EAAU,CAAA,CAAE,QAAA,EAA+B,GACjE,MAAA;AAAA,MACN;AACA,MAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,QAAQ,EAAE,GAAA,EAAK,OAAM,GAAI,MAAA;AAAA,IAC/D;AAAA,IACA,KAAK,QAAA,EAAU;AACb,MAAA,MAAM,MAAO,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,GAAW,QAAQ,EAAC;AAC3D,MAAA,MAAM,MAAY,EAAC;AACnB,MAAA,KAAA,MAAW,KAAA,IAAS,KAAA,CAAM,MAAA,EAAQ,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,GAAI,QAAA,CAAS,KAAA,EAAO,OAAA,CAAQ,GAAA,EAAK,KAAA,CAAM,IAAI,CAAC,CAAA;AAC5F,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,IACA,KAAK,MAAA,EAAQ;AACX,MAAA,MAAM,MAAM,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,QAAQ,EAAC;AAC5C,MAAA,OAAO,GAAA,CAAI,IAAI,CAAC,IAAA,KAAS,SAAS,KAAA,CAAM,IAAA,EAAM,IAAI,CAAC,CAAA;AAAA,IACrD;AAAA,IACA;AACE,MAAA,OAAO,KAAA;AAAA;AAEb;AAUA,SAAS,MAAA,CAAO,KAAA,EAAiB,KAAA,EAAgB,aAAA,GAAgB,IAAA,EAAe;AAC9E,EAAA,QAAQ,MAAM,IAAA;AAAM,IAClB,KAAK,MAAA;AAAA,IACL,KAAK,OAAA;AACH,MAAA,OAAO,YAAY,KAAA,EAAO,OAAO,KAAA,KAAU,QAAA,GAAW,QAAQ,EAAE,CAAA;AAAA,IAClE,KAAK,QAAA;AAKH,MAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,GACrD,WAAA,CAAY,KAAA,EAAO,KAAK,CAAA,GACxB,MAAA;AAAA,IACN,KAAK,SAAA;AAGH,MAAA,OAAO,OAAO,KAAA,KAAU,SAAA,GAAY,WAAA,CAAY,KAAA,EAAO,KAAK,CAAA,GAAI,MAAA;AAAA,IAClE,KAAK,SAAA;AACH,MAAA,OAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,EAAA;AAAA,IAC7C,KAAK,UAAA;AAAA,IACL,KAAK,OAAA,EAAS;AACZ,MAAA,MAAM,MAAM,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,GAAY,MAA4B,GAAA,GAAM,KAAA;AACpF,MAAA,MAAM,WACJ,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,GAAY,MAAiC,QAAA,GAAW,MAAA;AACpF,MAAA,OAAO,EAAE,GAAA,EAAK,GAAA,GAAM,MAAA,CAAO,GAAG,CAAA,GAAI,EAAA,EAAI,GAAI,QAAA,GAAW,EAAE,QAAA,EAAS,GAAI,EAAC,EAAG;AAAA,IAC1E;AAAA,IACA,KAAK,QAAA,EAAU;AACb,MAAA,MAAM,MAAO,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,GAAW,QAAQ,EAAC;AAC3D,MAAA,MAAM,MAAY,EAAC;AACnB,MAAA,KAAA,MAAW,KAAA,IAAS,KAAA,CAAM,MAAA,EAAQ,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,GAAI,MAAA,CAAO,KAAA,EAAO,OAAA,CAAQ,GAAA,EAAK,KAAA,CAAM,IAAI,CAAC,CAAA;AAkB1F,MAAA,OAAO,aAAA,IAAiB,CAAC,QAAA,CAAS,GAAG,IAAI,MAAA,GAAY,GAAA;AAAA,IACvD;AAAA,IACA,KAAK,MAAA,EAAQ;AACX,MAAA,MAAM,MAAM,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,QAAQ,EAAC;AAO5C,MAAA,OAAO,GAAA,CAAI,IAAI,CAAC,IAAA,KAAS,OAAO,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAK,CAAC,CAAA;AAAA,IAC1D;AAAA,IACA;AACE,MAAA,OAAO,KAAA;AAAA;AAEb;AAKO,SAAS,kBAAA,CACd,SAAA,EACA,MAAA,EACA,YAAA,EACM;AACN,EAAA,MAAM,IAAA,GAAO,gBAAA,CAAiB,SAAA,IAAa,IAAI,YAAY,CAAA;AAC3D,EAAA,MAAM,MAAY,EAAC;AACnB,EAAA,KAAA,MAAW,KAAA,IAAS,MAAA,EAAQ,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,GAAI,QAAA,CAAS,KAAA,EAAO,OAAA,CAAQ,IAAA,EAAM,KAAA,CAAM,IAAI,CAAC,CAAA;AACvF,EAAA,OAAO,GAAA;AACT;AAUO,SAAS,qBAAA,CAAsB,QAAc,MAAA,EAA0B;AAC5E,EAAA,MAAM,OAAa,EAAC;AACpB,EAAA,KAAA,MAAW,KAAA,IAAS,MAAA,EAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,GAAI,MAAA,CAAO,KAAA,EAAO,OAAA,CAAQ,MAAA,EAAQ,KAAA,CAAM,IAAI,CAAC,CAAA;AACxF,EAAA,OAAO,IAAA;AACT;AAGO,SAAS,2BAAA,CACd,MAAA,EACA,MAAA,EACA,YAAA,EACM;AACN,EAAA,OAAO,gBAAA,CAAiB,qBAAA,CAAsB,MAAA,EAAQ,MAAM,GAAG,YAAY,CAAA;AAC7E;AAoBO,SAAS,sBAAA,CACd,MAAA,EACA,MAAA,EACA,YAAA,EACyB;AACzB,EAAA,MAAM,MAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,YAAA,EAAc,KAAA,CAAM,IAAI,CAAA;AAC/C,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,MAAA,EAAQ,KAAA,CAAM,IAAI,CAAA;AACxC,IAAA,IAAI,gBAAgB,MAAM,CAAA,IAAK,CAAC,QAAA,CAAS,KAAK,CAAA,EAAG;AACjD,IAAA,IAAI,cAAc,MAAM,CAAA,IAAK,CAAC,QAAA,CAAS,KAAK,CAAA,EAAG;AAC7C,MAAA,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,GAAI,EAAC;AACnB,MAAA;AAAA,IACF;AACA,IAAA,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,GAAI,EAAE,OAAA,EAAS,MAAA,CAAO,WAAA,EAAa,OAAA,EAAS,MAAA,CAAO,KAAA,EAAO,KAAK,CAAA,EAAE;AAAA,EACjF;AACA,EAAA,OAAO,GAAA;AACT;AAyBA,SAAS,qBAAqB,KAAA,EAAsD;AAClF,EAAA,OACE,CAAC,CAAC,KAAA,IACF,OAAO,UAAU,QAAA,IACjB,gBAAA,IAAoB,KAAA,IACpB,CAAC,CAAE,KAAA,CAAkD,cAAA,IACrD,OAAQ,KAAA,CAAiD,eAAe,IAAA,KAAS,QAAA;AAErF;AAGA,SAAS,gBAAgB,IAAA,EAAmC;AAC1D,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,UAAU,OAAO,MAAA;AAC9C,EAAA,MAAM,MAAO,IAAA,CAAuD,OAAA;AACpE,EAAA,IAAI,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA;AACxB,IAAA,OAAA,CACI,GAAA,CAAkC,YAAA,IAAiB,GAAA,CAA0B,IAAA,KAC/E,MAAA;AAEJ,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,EAAU,OAAO,GAAA;AACpC,EAAA,OAAQ,KAAmC,YAAA,IAAgB,MAAA;AAC7D;AASO,SAAS,qBAAA,CACd,YACA,SAAA,EAC4D;AAC5D,EAAA,IAAI,CAAC,oBAAA,CAAqB,UAAU,CAAA,EAAG,OAAO,IAAA;AAC9C,EAAA,MAAM,KAAK,UAAA,CAAW,cAAA;AAItB,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,EAAA,CAAG,IAAI,CAAA,CAAE,MAAA,CAAO,CAAC,CAAC,GAAG,CAAA,KAAM,CAAC,SAAA,CAAU,QAAA,CAAS,GAAG,CAAC,CAAA;AAClF,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAEjC,EAAA,MAAM,OAAA,GAAU,CAAC,CAAC,GAAA,EAAK,IAAI,CAAA,MAAuC;AAAA,IAChE,GAAA;AAAA,IACA,UAAA,EAAY,gBAAgB,IAAI,CAAA;AAAA,IAChC,KAAA,EAAQ,MAAgC,OAAA,IAAW;AAAA,GACrD,CAAA;AAGA,EAAA,MAAM,OAAA,GAAU,EAAA,CAAG,OAAA,GAAU,YAAY,CAAA;AACzC,EAAA,MAAM,SAAA,GACJ,WAAW,MAAA,CAAO,SAAA,CAAU,eAAe,IAAA,CAAK,EAAA,CAAG,MAAM,OAAO,CAAA,GAC5D,CAAC,OAAA,EAAS,EAAA,CAAG,KAAK,OAAO,CAAC,IAC1B,OAAA,CAAQ,OAAA,CAAQ,SAAS,CAAC,CAAA;AAChC,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAC,GAAG,CAAA,KAAM,GAAA,KAAQ,SAAA,CAAU,CAAC,CAAC,CAAA,CAAE,IAAI,OAAO,CAAA;AAEjF,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,CAAQ,SAAS,GAAG,aAAA,EAAc;AACnD;AAUO,SAAS,0BAAA,CACd,eACA,YAAA,EACa;AACb,EAAA,MAAM,OAAQ,aAAA,EAAyE,IAAA;AACvF,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,UAAU,OAAO,IAAA;AAC9C,EAAA,MAAM,OAAa,EAAC;AACpB,EAAA,IAAI,KAAA,GAAQ,KAAA;AACZ,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,YAAY,CAAA,EAAG;AAC3C,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA;AAC/B,IAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,IAAY,aAAa,KAAA,EAAO;AAC5D,MAAA,IAAA,CAAK,GAAG,IAAK,KAAA,CAA+B,OAAA;AAC5C,MAAA,KAAA,GAAQ,IAAA;AAAA,IACV;AAAA,EACF;AACA,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,OAAO,gBAAA,CAAiB,MAAM,YAAY,CAAA;AAC5C;AASO,SAAS,iBAAA,CACd,UACA,UAAA,EACY;AACZ,EAAA,OAAO,QAAA,EAAU,UAAU,UAAA,GAAa,cAAA,CAAe,YAAY,QAAA,CAAS,MAAM,IAAI,EAAC;AACzF;AAMO,SAAS,cAAA,CAAe,IAAA,EAAY,IAAA,EAAgB,KAAA,EAAsB;AAC/E,EAAA,MAAM,IAAA,GAAO,KAAK,CAAC,CAAA;AACnB,EAAA,IAAI,IAAA,KAAS,QAAW,OAAO,IAAA;AAC/B,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA;AACzB,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,EAAE,GAAG,IAAA,EAAM,CAAC,IAAI,GAAG,KAAA,EAAM;AAEvD,EAAA,MAAM,OAAA,GAAU,KAAK,CAAC,CAAA;AACtB,EAAA,MAAM,KAAA,GAAQ,KAAK,IAAI,CAAA;AACvB,EAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,OAAO,CAAA,EAAG;AACzB,IAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,KAAK,IAAI,KAAA,CAAM,KAAA,KAAU,EAAC;AACpD,IAAA,MAAM,GAAA,GAAM,OAAO,OAAO,CAAA;AAC1B,IAAA,GAAA,CAAI,GAAG,CAAA,GACL,IAAA,CAAK,MAAA,KAAW,CAAA,GAAI,QAAQ,cAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,IAAc,EAAC,EAAG,IAAA,CAAK,KAAA,CAAM,CAAC,GAAG,KAAK,CAAA;AAC3F,IAAA,OAAO,EAAE,GAAG,IAAA,EAAM,CAAC,IAAI,GAAG,GAAA,EAAI;AAAA,EAChC;AACA,EAAA,OAAO,EAAE,GAAG,IAAA,EAAM,CAAC,IAAI,GAAG,cAAA,CAAgB,KAAA,IAAkB,EAAC,EAAG,IAAA,EAAM,KAAK,CAAA,EAAE;AAC/E","file":"chunk-T4NSAGOS.js","sourcesContent":["/**\n * Bridges the `MethodRunner`'s field values to the rest of the host.\n *\n * The method-store keeps inputs in the *simplified* format and runs send the\n * *full* RJSF form shape - both already handled by `inflate/deflateAllInputs`.\n * The only gap is that a few field controls use a friendlier value than RJSF: a\n * scalar is the bare scalar where the full shape is the content model its\n * concept declares (`{ text }`, `{ number }`, `{ yes_no }`), and a file is\n * `{ url, filename }`. `fromRjsf`/`toRjsf` translate just those, so store\n * reads/writes and run payloads stay byte-for-byte compatible with the proven\n * playground path.\n *\n * Which property a scalar wraps into is NOT decided here - it arrives on the\n * descriptor as `contentKey`, derived from the contract by `buildRunFields`.\n *\n * Pure module - no React, unit-tested.\n */\nimport type { PipeInputFormDescriptor } from 'mthds/protocol';\n\nimport type { RunField } from './descriptor';\nimport { buildRunFields } from './derive';\nimport { ownProp } from './own-property';\nimport { isFilled } from './readiness';\nimport { isOptionalInput, isPluralInput } from './contracts';\nimport { deflateAllInputs, inflateAllInputs } from './wire-format';\nimport type { PipeIOContract, PipeInputContract } from './contracts';\n\ntype Dict = Record<string, unknown>;\ntype InputSchemas = Record<string, PipeInputContract>;\n\n// ─── RJSF full shape ↔ RunField value ──────────────────────────────────────\n\n/**\n * Put a scalar back inside the content model its concept declares:\n * `\"hi\"` → `{ text: \"hi\" }`, `2` → `{ number: 2 }`, `false` → `{ yes_no: false }`.\n *\n * The property name comes from the DESCRIPTOR (`contentKey`, stamped by\n * `buildRunFields` - the only reader of JSON Schema). This module holds no\n * taxonomy of its own, which is the point: the version that did held a list of\n * concepts that wrap, `native.Number` was missing from it, and every run\n * carrying a number was rejected by the gate against `NumberContent`.\n *\n * A field with no `contentKey` keeps its value plain. That is not an oversight\n * either - a child property of a structured concept, or a custom concept whose\n * schema is a bare string, IS its value, and wrapping it double-nests it\n * (`{ text: { text } }`) into a payload no schema accepts.\n */\nfunction wrapContent(field: RunField, value: unknown): unknown {\n return field.contentKey === undefined ? value : { [field.contentKey]: value };\n}\n\n/**\n * The inverse. A value that is already bare passes through untouched: persisted\n * `inputData` predates the number and yes/no wrappers, and an inputs.json\n * written by hand or by an agent says `2`, not `{number: 2}`.\n */\nfunction unwrapContent(field: RunField, value: unknown): unknown {\n const key = field.contentKey;\n if (key === undefined || !value || typeof value !== 'object' || Array.isArray(value))\n return value;\n return ownProp(value as Record<string, unknown>, key);\n}\n\n/** How deep a nested `{ text: … }` / `{ content: … }` chain we bother unwrapping. */\nconst MAX_UNWRAP_DEPTH = 8;\n\n/**\n * Pull a plain string out of whatever the store hands a text field.\n *\n * Persisted `inputData` carries historical corruptions - a double-wrapped\n * `{ text: { text } }`, a `{ concept, content }` envelope that was never\n * deflated - and the old fallback ran `String()` over them, which renders the\n * literal `\"[object Object]\"` into the textarea. That is worse than showing\n * nothing: it looks exactly like content the user typed, and one keystroke\n * later it is content the user typed. So unwrap the envelopes we know, and give\n * up with `undefined` (an empty field) rather than displaying garbage.\n *\n * Genuine primitives still stringify - a number or a boolean in a text slot is\n * a value, not a wrapper.\n *\n * The envelope names here are the HISTORICAL corruptions, not the wrapper key:\n * the declared wrapper is stripped by `unwrapContent` first, off the field's\n * `contentKey`, exactly as the number and boolean paths do. A text wrapper\n * whose property is not literally `text` would otherwise read back empty - and\n * one save later, BE empty.\n */\nfunction textFromStored(value: unknown, depth = 0): string | undefined {\n if (value == null) return undefined;\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (typeof value !== 'object' || depth >= MAX_UNWRAP_DEPTH) return undefined;\n const envelope = value as { text?: unknown; content?: unknown };\n if ('text' in envelope) return textFromStored(envelope.text, depth + 1);\n if ('content' in envelope) return textFromStored(envelope.content, depth + 1);\n return undefined;\n}\n\n/** RJSF full form value → the runner's field value. */\nfunction fromRjsf(field: RunField, value: unknown): unknown {\n switch (field.kind) {\n case 'text':\n case 'prose':\n return textFromStored(unwrapContent(field, value));\n case 'number': {\n // The control holds `number | undefined`. A numeric STRING is tolerated\n // because that is what a hand-written inputs.json tends to carry; anything\n // else empties the field rather than feeding NaN to a number input.\n const raw = unwrapContent(field, value);\n if (typeof raw === 'number') return Number.isFinite(raw) ? raw : undefined;\n if (typeof raw === 'string' && raw.trim() !== '') {\n const parsed = Number(raw);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n return undefined;\n }\n case 'boolean': {\n // Strictly a real boolean, deliberately: pipelex declares `YesNoContent.\n // yes_no` with `strict=True` precisely to refuse \"yes\"/1/\"true\", and\n // coercing here would smuggle back in what the runtime closed off.\n const raw = unwrapContent(field, value);\n return typeof raw === 'boolean' ? raw : undefined;\n }\n case 'unknown':\n // The escape hatch is a raw-JSON textarea, so a structured value belongs\n // there as JSON - `String(value)` would have shown \"[object Object]\" and\n // thrown the actual value away.\n if (typeof value === 'string') return value;\n if (value == null) return undefined;\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return undefined;\n }\n }\n return String(value);\n case 'document':\n case 'image': {\n if (value && typeof value === 'object') {\n const o = value as { url?: unknown; filename?: unknown };\n return o.url\n ? { url: String(o.url), filename: o.filename as string | undefined }\n : undefined;\n }\n return typeof value === 'string' && value ? { url: value } : undefined;\n }\n case 'object': {\n const obj = (value && typeof value === 'object' ? value : {}) as Dict;\n const out: Dict = {};\n for (const child of field.fields) out[child.name] = fromRjsf(child, ownProp(obj, child.name));\n return out;\n }\n case 'list': {\n const arr = Array.isArray(value) ? value : [];\n return arr.map((item) => fromRjsf(field.item, item));\n }\n default:\n return value;\n }\n}\n\n/**\n * The runner's field value → RJSF full form value (what runs/store expect).\n *\n * `collapseEmpty` is what a SINGULAR slot does and a LIST ITEM must not: an\n * untouched structure in a singular slot is absent, but an item exists only\n * because the user added it, so adding IS the touch. Only the structure case\n * reads the flag.\n */\nfunction toRjsf(field: RunField, value: unknown, collapseEmpty = true): unknown {\n switch (field.kind) {\n case 'text':\n case 'prose':\n return wrapContent(field, typeof value === 'string' ? value : '');\n case 'number':\n // An untouched number stays ABSENT rather than becoming an empty wrapper.\n // The gate then does the right thing on both counts: it prunes an absent\n // optional input, and it reports a required one as missing by name -\n // where `{number: undefined}` would read as a malformed value instead.\n return typeof value === 'number' && Number.isFinite(value)\n ? wrapContent(field, value)\n : undefined;\n case 'boolean':\n // `false` is a value, not an absence - hence the type test rather than a\n // truthiness one.\n return typeof value === 'boolean' ? wrapContent(field, value) : undefined;\n case 'unknown':\n return typeof value === 'string' ? value : '';\n case 'document':\n case 'image': {\n const url = value && typeof value === 'object' ? (value as { url?: unknown }).url : value;\n const filename =\n value && typeof value === 'object' ? (value as { filename?: unknown }).filename : undefined;\n return { url: url ? String(url) : '', ...(filename ? { filename } : {}) };\n }\n case 'object': {\n const obj = (value && typeof value === 'object' ? value : {}) as Dict;\n const out: Dict = {};\n for (const child of field.fields) out[child.name] = toRjsf(child, ownProp(obj, child.name));\n // A structure nobody put anything into stays ABSENT, exactly as an\n // untouched number does above - it does NOT become a shell of empty\n // children. Materializing that shell made an OPTIONAL structured input\n // unrunnable whenever its concept declared a required child: readiness\n // ignored the input (correctly - the method said it may be omitted), Run\n // lit up, and the gate then judged an object the kernel had invented\n // against the concept's full schema and rejected the run for a child the\n // user was never asked to fill.\n //\n // Emptiness is `isFilled`, the SAME predicate readiness and the wire\n // payload use, so the three cannot disagree about whether this input is\n // there. Which also says what the touch is NOT: opening the optional\n // disclosure is local view state the value never sees, so a section\n // opened and left blank collapses exactly like one never opened. What\n // keeps a structure is a VALUE in it - and then the whole shell survives,\n // empty children and all, so a required child left blank still fails, and\n // loudly.\n return collapseEmpty && !isFilled(out) ? undefined : out;\n }\n case 'list': {\n const arr = Array.isArray(value) ? value : [];\n // An item is NEVER absent: it is in the array because the user added it,\n // and `ListField`'s \"Add\" seeds an object item with `{}`. Letting it\n // collapse the way an untouched singular slot does put `undefined` in the\n // array, which ajv rejects as `must be object` - blocking an empty item\n // the item schema allowed, and turning a required-child complaint into a\n // type error that names nothing the user can act on.\n return arr.map((item) => toRjsf(field.item, item, false));\n }\n default:\n return value;\n }\n}\n\n// ─── Store ↔ RunField values ──────────────────────────────────────────────\n\n/** Seed the runner's form values from the method-store's simplified inputData. */\nexport function runValuesFromStore(\n inputData: Dict | undefined,\n fields: RunField[],\n inputSchemas: InputSchemas,\n): Dict {\n const full = inflateAllInputs(inputData ?? {}, inputSchemas);\n const out: Dict = {};\n for (const field of fields) out[field.name] = fromRjsf(field, ownProp(full, field.name));\n return out;\n}\n\n/**\n * The runner's form values in **schema shape** - the same shape an RJSF panel\n * holds, and therefore the shape `gate.ts` validates against the per-input\n * JSON Schemas.\n *\n * Deliberately stops short of `deflateAllInputs`: deflation is the STORE's\n * compact form, and ajv validates the full one.\n */\nexport function rjsfDataFromRunValues(values: Dict, fields: RunField[]): Dict {\n const full: Dict = {};\n for (const field of fields) full[field.name] = toRjsf(field, ownProp(values, field.name));\n return full;\n}\n\n/** Convert the runner's form values back to the store's simplified inputData. */\nexport function storeInputDataFromRunValues(\n values: Dict,\n fields: RunField[],\n inputSchemas: InputSchemas,\n): Dict {\n return deflateAllInputs(rjsfDataFromRunValues(values, fields), inputSchemas);\n}\n\n/**\n * Build the `{ concept, content }` map a run expects from the form values.\n *\n * The two ways a method says \"this may be nothing\" land differently on the wire,\n * and both matter:\n * - an **optional (`?`)** input left blank is OMITTED, so the runtime records a\n * real absence the method can branch on - sending `{ text: \"\" }` would look\n * like the caller supplied an empty string;\n * - a **plural (`[]`)** input keeps its key, because a plural slot is never\n * absent - its empty form IS the empty list, and the runtime requires the key.\n * When that list is EMPTY it is sent BARE, without the envelope: the envelope\n * is the runtime's \"explicit form\", which bypasses the top-down shaper and\n * hands the value to the bottom-up factory, and that factory types a list from\n * its first item - so an empty list there raises \"Cannot create Stuff from\n * empty list in content\". The bare form keeps the shaper, which reads the\n * DECLARED concept and builds the empty list correctly. (pipelex/#1096 makes\n * the envelope agree; the bare form runs today and stays correct after.)\n */\nexport function apiInputsFromRunValues(\n values: Dict,\n fields: RunField[],\n inputSchemas: InputSchemas,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const field of fields) {\n const schema = ownProp(inputSchemas, field.name);\n if (!schema) continue;\n const value = ownProp(values, field.name);\n if (isOptionalInput(schema) && !isFilled(value)) continue;\n if (isPluralInput(schema) && !isFilled(value)) {\n out[field.name] = [];\n continue;\n }\n out[field.name] = { concept: schema.concept_ref, content: toRjsf(field, value) };\n }\n return out;\n}\n\n// ─── Pipe output → result entries ────────────────────────────────────────────\n\n/** One named output pulled out of a run's working memory, ready to render. */\nexport interface OutputEntry {\n /** The output variable key (mono). */\n key: string;\n conceptRef?: string;\n value: unknown;\n}\n\n/**\n * The slice of a serialized pipe output this module actually reads. Structural\n * on purpose: the SDK's `DictPipeOutput` is the host's wire type, and the kernel\n * must not depend on `@pipelex/sdk` - the guard below verifies the shape at\n * runtime anyway.\n */\ninterface PipeOutputWithWorkingMemory {\n working_memory: {\n root: Record<string, unknown>;\n aliases?: Record<string, string>;\n };\n}\n\nfunction hasWorkingMemoryRoot(value: unknown): value is PipeOutputWithWorkingMemory {\n return (\n !!value &&\n typeof value === 'object' &&\n 'working_memory' in value &&\n !!(value as { working_memory?: { root?: unknown } }).working_memory &&\n typeof (value as { working_memory: { root?: unknown } }).working_memory.root === 'object'\n );\n}\n\n/** The concept code of a working-memory entry (`{concept}` may be obj/str/absent). */\nfunction entryConceptRef(data: unknown): string | undefined {\n if (!data || typeof data !== 'object') return undefined;\n const raw = (data as { concept?: unknown; concept_code?: unknown }).concept;\n if (raw && typeof raw === 'object')\n return (\n ((raw as { concept_code?: string }).concept_code ?? (raw as { code?: string }).code) ||\n undefined\n );\n if (typeof raw === 'string') return raw;\n return (data as { concept_code?: string }).concept_code || undefined;\n}\n\n/**\n * Split a run's pipe output into the main result and any intermediate outputs,\n * excluding the inputs. The main output's identity comes from the working\n * memory's `main_stuff` alias (the authoritative pointer to the output variable)\n * - so its label is the REAL stuff name, not a positional guess. Falls back to\n * the last produced stuff only when the alias is absent.\n */\nexport function outputsFromPipeOutput(\n pipeOutput: unknown,\n inputKeys: string[],\n): { main: OutputEntry; intermediates: OutputEntry[] } | null {\n if (!hasWorkingMemoryRoot(pipeOutput)) return null;\n const wm = pipeOutput.working_memory as {\n root: Record<string, unknown>;\n aliases?: Record<string, string>;\n };\n const entries = Object.entries(wm.root).filter(([key]) => !inputKeys.includes(key));\n if (entries.length === 0) return null;\n\n const toEntry = ([key, data]: [string, unknown]): OutputEntry => ({\n key,\n conceptRef: entryConceptRef(data),\n value: (data as { content?: unknown })?.content ?? data,\n });\n\n // The real main-output name: `aliases.main_stuff` → its root key.\n const mainKey = wm.aliases?.['main_stuff'];\n const mainEntry: [string, unknown] =\n mainKey && Object.prototype.hasOwnProperty.call(wm.root, mainKey)\n ? [mainKey, wm.root[mainKey]]\n : entries[entries.length - 1]!;\n const intermediates = entries.filter(([key]) => key !== mainEntry[0]).map(toEntry);\n\n return { main: toEntry(mainEntry), intermediates };\n}\n\n/**\n * Recover the inputs a run was executed with from its working memory. The run's\n * `working_memory.root` holds every named stuff - inputs AND outputs - keyed by\n * variable name; we pull the entries whose keys are declared inputs and deflate\n * their `content` (the full form shape) back into the store's simplified\n * `inputData`. Lets the runner repopulate the form for a past/shared run - the\n * inputs that produced the result. Returns `null` when no inputs are present.\n */\nexport function inputDataFromWorkingMemory(\n workingMemory: unknown,\n inputSchemas: InputSchemas,\n): Dict | null {\n const root = (workingMemory as { root?: Record<string, unknown> } | null | undefined)?.root;\n if (!root || typeof root !== 'object') return null;\n const full: Dict = {};\n let found = false;\n for (const key of Object.keys(inputSchemas)) {\n const entry = ownProp(root, key);\n if (entry && typeof entry === 'object' && 'content' in entry) {\n full[key] = (entry as { content: unknown }).content;\n found = true;\n }\n }\n if (!found) return null;\n return deflateAllInputs(full, inputSchemas);\n}\n\n/**\n * Field descriptors for a method's main pipe - the wire descriptor mapped over\n * the contract, or `[]` while either artifact has not arrived yet. The\n * descriptor comes back from `validate` beside `pipe_io_contracts` when the\n * request opts in with `views: [\"input_form\"]`; `getPipeInputForm` is the\n * lookup that matches `getPipeIOContract`.\n */\nexport function fieldsForContract(\n contract: PipeIOContract | undefined,\n descriptor: PipeInputFormDescriptor | undefined,\n): RunField[] {\n return contract?.inputs && descriptor ? buildRunFields(descriptor, contract.inputs) : [];\n}\n\n/**\n * Immutably set a value at a dotted/indexed path (`a.0.b`). Used to write an\n * uploaded file's URL back to a field anywhere in the (possibly nested) tree.\n */\nexport function setValueAtPath(root: Dict, path: string[], value: unknown): Dict {\n const head = path[0];\n if (head === undefined) return root;\n const rest = path.slice(1);\n if (rest.length === 0) return { ...root, [head]: value };\n\n const nextSeg = rest[0] as string;\n const child = root[head];\n if (/^\\d+$/.test(nextSeg)) {\n const arr = Array.isArray(child) ? child.slice() : [];\n const idx = Number(nextSeg);\n arr[idx] =\n rest.length === 1 ? value : setValueAtPath((arr[idx] as Dict) ?? {}, rest.slice(1), value);\n return { ...root, [head]: arr };\n }\n return { ...root, [head]: setValueAtPath((child as Dict) ?? {}, rest, value) };\n}\n"]}
@@ -27,5 +27,5 @@ function inputMustBeFilled(input) {
27
27
  }
28
28
 
29
29
  export { buildPipeRef, getPipeIOContract, inputMustBeFilled, isFixedCountInput, isOptionalInput, isPluralInput };
30
- //# sourceMappingURL=chunk-HARYEWNG.js.map
31
- //# sourceMappingURL=chunk-HARYEWNG.js.map
30
+ //# sourceMappingURL=chunk-XYQ7NXQZ.js.map
31
+ //# sourceMappingURL=chunk-XYQ7NXQZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/contracts.ts"],"names":[],"mappings":";;;AA4DO,SAAS,YAAA,CAAa,QAAgB,QAAA,EAA0B;AACrE,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAA;AAC9B;AAkBO,SAAS,iBAAA,CACd,eAAA,EACA,MAAA,EACA,QAAA,EAC4B;AAC5B,EAAA,IAAI,CAAC,eAAA,IAAmB,CAAC,QAAA,EAAU,OAAO,MAAA;AAC1C,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,MAAM,QAAQ,OAAA,CAAQ,eAAA,EAAiB,YAAA,CAAa,MAAA,EAAQ,QAAQ,CAAC,CAAA;AACrE,IAAA,IAAI,OAAO,OAAO,KAAA;AAAA,EACpB;AACA,EAAA,OAAO,OAAA,CAAQ,iBAAiB,QAAQ,CAAA;AAC1C;AAWO,SAAS,gBAAgB,KAAA,EAAmC;AACjE,EAAA,OAAO,MAAM,QAAA,KAAa,UAAA;AAC5B;AAcO,SAAS,cAAc,KAAA,EAAmC;AAC/D,EAAA,OAAQ,KAAA,CAAM,aAAgD,IAAA,KAAS,OAAA;AACzE;AASO,SAAS,kBAAkB,KAAA,EAAmC;AACnE,EAAA,OAAO,MAAM,YAAA,KAAiB,OAAA;AAChC;AAqBO,SAAS,kBAAkB,KAAA,EAAmC;AACnE,EAAA,IAAI,eAAA,CAAgB,KAAK,CAAA,EAAG,OAAO,KAAA;AACnC,EAAA,OAAO,CAAC,aAAA,CAAc,KAAK,CAAA,IAAK,kBAAkB,KAAK,CAAA;AACzD","file":"chunk-XYQ7NXQZ.js","sourcesContent":["/**\n * The `pipe_io_contracts` types and the kernel's gating predicates over them.\n *\n * The types are the STANDARD'S, imported from `mthds/protocol` and re-exported\n * under the names this package has always exported. `pipe_io_contracts` is an\n * MTHDS artifact - the standard specifies it (the \"Pipe I/O Contracts\" page)\n * and its TypeScript client declares it - so this module states no shape of its\n * own any more. What it keeps is the half that is genuinely the kernel's: the\n * predicates that turn a declared slot into a gating answer, and the lookup\n * that tolerates both key conventions a real payload arrives with.\n *\n * `mthds` is a TYPES-ONLY peer dependency: every import here is `import type`,\n * so nothing survives into `dist/` and no consumer ships the standard's CLI.\n * `scripts/assert-bundle.mjs` holds that on the built graph and lint holds it\n * on the source. See docs/dependency-budget.md.\n *\n * The map itself is keyed by namespaced pipe ref (`<domain>.<pipe_code>`),\n * NOT bare pipe code - use `getPipeIOContract` to look entries up by the bare\n * codes the UI tracks.\n */\n\nimport type {\n IOMultiplicity,\n PipeInputContract,\n PipeIOContract,\n PipeIOContracts,\n PipeOutputContract,\n PresenceMarker,\n} from 'mthds/protocol';\n\nimport { ownProp } from './own-property';\n\nexport type {\n IOMultiplicity,\n PipeInputContract,\n PipeIOContract,\n PipeIOContracts,\n PipeOutputContract,\n};\n\n/**\n * The authored presence marker on an input slot, verbatim - the standard's\n * `PresenceMarker` under the name this package exports.\n *\n * - `plain` - no marker: the caller must supply the input.\n * - `optional` (`?`) - the caller may omit it and the pipe handles the absence\n * itself.\n * - `force` (`!`) - a use-site assertion that the value IS present. It must be\n * supplied, exactly like `plain`; the distinction is the authored assertion,\n * which lint and graph surfaces read and a form does not.\n *\n * Kept as an alias rather than renamed because a consumer imports it by this\n * name; a host that would rather read the standard's spelling has `mthds`\n * installed through this package's peer and can import `PresenceMarker`\n * straight from `mthds/protocol`.\n */\nexport type InputPresence = PresenceMarker;\n\n/** Namespaced pipe ref (`<domain>.<pipe_code>`) - the identity convention the\n * validate artifacts key on. */\nexport function buildPipeRef(domain: string, pipeCode: string): string {\n return `${domain}.${pipeCode}`;\n}\n\n/**\n * Look up a pipe's IO contract by pipe code, tolerant of both key conventions.\n *\n * The map may be keyed by namespaced `pipe_ref` (`<domain>.<code>`, the\n * canonical convention) OR by bare pipe code - the current hosted `/validate`\n * keys `pipe_structures` by bare code, and methods persisted before the\n * pipe_ref re-keying also carry bare-code keys. Try the namespaced ref first\n * (when the caller supplies a domain), then fall back to the bare code.\n * Returns `undefined` (never crashes) when neither matches.\n *\n * Both lookups read OWN properties only. A bare index returned the inherited\n * `Object` constructor for a pipe code named `constructor` (or `toString`, …) -\n * a truthy non-contract that sailed straight through the `if (!contract)` guard\n * hosts are shown writing, so a pipe that does not exist rendered as one taking\n * no inputs instead of reaching the host's not-found path. See `ownProp`.\n */\nexport function getPipeIOContract(\n pipeIoContracts: PipeIOContracts | null | undefined,\n domain: string | null | undefined,\n pipeCode: string | null | undefined,\n): PipeIOContract | undefined {\n if (!pipeIoContracts || !pipeCode) return undefined;\n if (domain) {\n const byRef = ownProp(pipeIoContracts, buildPipeRef(domain, pipeCode));\n if (byRef) return byRef;\n }\n return ownProp(pipeIoContracts, pipeCode);\n}\n\n/**\n * True when the method declared the input omittable (`?`).\n *\n * The ONE reader of `presence` in the package: everything that asks \"may this\n * be absent?\" - the descriptor's `required`, the gate's `required` list, the\n * payload builders that omit an unfilled input - goes through here, so a\n * marker the wire adds later cannot mean one thing on one surface and another\n * elsewhere. `force` (`!`) reads as NOT optional, like `plain`.\n */\nexport function isOptionalInput(input: PipeInputContract): boolean {\n return input.presence === 'optional';\n}\n\n/**\n * True when the input carries a plural concept (`Image[]`, `Image[3]`): its\n * JSON Schema is an array wrapper.\n *\n * Deliberately a SCHEMA test rather than a read of the declared `multiplicity`,\n * even though the wire now states both. The schema is what `buildRunFields`\n * maps and therefore what the user is shown; a predicate reading the other\n * field could disagree with the rendered control, which is the exact failure\n * mode `mustBeFilled` (readiness.ts) exists to prevent. The two agree by\n * construction upstream - the engine wraps the schema in an array exactly when\n * it reports `variable` or `fixed`.\n */\nexport function isPluralInput(input: PipeInputContract): boolean {\n return (input.json_schema as { type?: unknown } | undefined)?.type === 'array';\n}\n\n/**\n * True when the slot declares an EXACT number of items (`Concept[N]`), whose\n * count is `item_count`.\n *\n * The distinction that matters to the gate: a variable list can legitimately be\n * empty, a fixed one cannot.\n */\nexport function isFixedCountInput(input: PipeInputContract): boolean {\n return input.multiplicity === 'fixed';\n}\n\n/**\n * True when the user MUST put a value in before a run can start.\n *\n * Two declarations relieve an input of that duty, for different reasons:\n * - **optional (`?`)** - the method itself states the caller may omit it;\n * - **variable-plural (`[]`)** - the empty list is a legitimate value and the\n * language cannot declare \"at least one\", so gating on it would invent a\n * constraint the method never made.\n *\n * Everything else gates: `plain`, `force` (`!`), and a **fixed-count** list\n * (`[N]`), which is a list whose empty form the method has explicitly ruled out.\n * This is the derivation the input-form descriptor spec states for `gating`.\n *\n * Emptiness is all this predicate answers for - WHETHER a slot gates, never how\n * much it needs. A fixed-count list that holds fewer items than it declares\n * gates here and is refused by `fieldFilled`, which reads the declared count off\n * the descriptor's `itemCount`, and by the gate's ajv pass on the schema's\n * `minItems`. All three read the one number the method stated.\n */\nexport function inputMustBeFilled(input: PipeInputContract): boolean {\n if (isOptionalInput(input)) return false;\n return !isPluralInput(input) || isFixedCountInput(input);\n}\n"]}
@@ -0,0 +1,66 @@
1
+ import { collectSchemaDefs, collapseNullable, derefSchema, schemaTypeOf } from './chunk-OD3VYXUY.js';
2
+ import { isOptionalInput, inputMustBeFilled } from './chunk-XYQ7NXQZ.js';
3
+
4
+ // src/core/gating-fields.ts
5
+ function numOrUndef(v) {
6
+ return typeof v === "number" ? v : void 0;
7
+ }
8
+ function gatingNode(name, rawSchema, required, defs, active) {
9
+ let schema = rawSchema;
10
+ let path = active;
11
+ for (; ; ) {
12
+ const ref = schema.$ref;
13
+ if (typeof ref === "string") {
14
+ if (path.has(ref)) return { name, required, kind: "unknown" };
15
+ path = new Set(path).add(ref);
16
+ }
17
+ const next = collapseNullable(derefSchema(schema, defs));
18
+ if (next === schema) break;
19
+ schema = next;
20
+ }
21
+ const type = schemaTypeOf(schema);
22
+ if (type === "array") {
23
+ const item = gatingNode(name, schema.items ?? {}, true, defs, path);
24
+ const itemCount = numOrUndef(schema.minItems);
25
+ const maxItemCount = numOrUndef(schema.maxItems);
26
+ return {
27
+ name,
28
+ required,
29
+ kind: "list",
30
+ item,
31
+ ...itemCount === void 0 ? {} : { itemCount },
32
+ ...maxItemCount === void 0 ? {} : { maxItemCount }
33
+ };
34
+ }
35
+ if (type === "object") {
36
+ const props = schema.properties ?? {};
37
+ const req = new Set(Array.isArray(schema.required) ? schema.required : []);
38
+ return {
39
+ name,
40
+ required,
41
+ kind: "object",
42
+ fields: Object.entries(props).map(
43
+ ([childName, childSchema]) => gatingNode(childName, childSchema ?? {}, req.has(childName), defs, path)
44
+ )
45
+ };
46
+ }
47
+ return { name, required, kind: "unknown" };
48
+ }
49
+ function gatingFieldsFromInputs(inputs) {
50
+ return Object.entries(inputs).map(([name, input]) => {
51
+ const defs = {};
52
+ collectSchemaDefs(input.json_schema, defs, { traverseArrays: true });
53
+ const node = gatingNode(
54
+ name,
55
+ input.json_schema ?? {},
56
+ !isOptionalInput(input),
57
+ defs,
58
+ /* @__PURE__ */ new Set()
59
+ );
60
+ return { ...node, gating: inputMustBeFilled(input) };
61
+ });
62
+ }
63
+
64
+ export { gatingFieldsFromInputs };
65
+ //# sourceMappingURL=chunk-YEVVR6FE.js.map
66
+ //# sourceMappingURL=chunk-YEVVR6FE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/gating-fields.ts"],"names":[],"mappings":"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","file":"chunk-YEVVR6FE.js","sourcesContent":["/**\n * The gate's own field tree - structural, schema-derived, and private.\n *\n * `gateRunInputs` re-applies the Run button's emptiness rule with\n * `mustBeFilled`/`fieldFilled`, which read a `RunField` tree. The RENDER tree\n * is built from the wire input-form descriptor - but the gate must not require\n * that artifact: the descriptor is a presentation view, and the workspace\n * doctrine is that a machine consumer never needs it to validate a payload -\n * `json_schema` keeps that job. A server holds contracts; it may never have\n * asked for the view.\n *\n * So the gate walks the contract's `json_schema` into the MINIMAL tree the\n * emptiness predicates read: `object` nodes with their required children,\n * `list` nodes with their declared bounds, and opaque leaves. This is not a\n * revival of the deleted heuristics - it reads exactly the keywords ajv itself\n * enforces (`type`, `properties`, `required`, `items`, `minItems`, `maxItems`)\n * and decides nothing about what a field IS. No kind here ever reaches a\n * renderer: the tree exists for one call inside `gateRunInputs` and is not\n * exported from the package.\n *\n * Agreement with the browser's readiness (which runs over the render tree) is\n * held by construction - both trees take required children and list bounds\n * from the same model the engine derived both artifacts from - and asserted in\n * `gate-agreement.test.ts`, which runs both sides over one table.\n */\n\nimport { inputMustBeFilled, isOptionalInput, type PipeInputContract } from './contracts';\nimport type { RunField } from './descriptor';\nimport {\n collapseNullable,\n collectSchemaDefs,\n derefSchema,\n schemaTypeOf,\n type JsonSchema,\n} from './schema-utils';\n\nfunction numOrUndef(v: unknown): number | undefined {\n return typeof v === 'number' ? v : undefined;\n}\n\nfunction gatingNode(\n name: string,\n rawSchema: JsonSchema,\n required: boolean,\n defs: Record<string, JsonSchema>,\n active: ReadonlySet<string>,\n): RunField {\n // Resolve `$ref` / nullable-`anyOf` layers to a fixpoint - one pass of\n // either helper cannot see through pydantic's Optional concept-typed shape,\n // `anyOf: [{$ref}, {type:'null'}]` - keeping the set of references OPEN ON\n // THIS PATH. A reference met again while still open is a self-referencing\n // model: expanding it has no floor, so the node degrades to the opaque leaf\n // instead of overflowing the stack - the gate returns a verdict, never a\n // throw. Open on this path, not seen anywhere: two siblings referencing one\n // definition are ordinary composition and both expand in full. The degrade\n // is OPEN, and deliberately so: ajv - which resolves recursion natively,\n // bounded by the data - has already enforced PRESENCE of required children\n // at every depth, but presence is all it enforces, so a required scalar\n // that is present-but-blank below the cycle point is caught by neither\n // side. That residual leniency is a recorded follow-up, not a safety\n // property; failing CLOSED here would refuse every legitimate recursive\n // payload, which is the worse defect.\n let schema = rawSchema;\n let path = active;\n for (;;) {\n const ref = schema.$ref;\n if (typeof ref === 'string') {\n if (path.has(ref)) return { name, required, kind: 'unknown' };\n path = new Set(path).add(ref);\n }\n const next = collapseNullable(derefSchema(schema, defs));\n if (next === schema) break;\n schema = next;\n }\n const type = schemaTypeOf(schema);\n\n if (type === 'array') {\n const item = gatingNode(name, (schema.items as JsonSchema) ?? {}, true, defs, path);\n const itemCount = numOrUndef(schema.minItems);\n const maxItemCount = numOrUndef(schema.maxItems);\n return {\n name,\n required,\n kind: 'list',\n item,\n ...(itemCount === undefined ? {} : { itemCount }),\n ...(maxItemCount === undefined ? {} : { maxItemCount }),\n };\n }\n if (type === 'object') {\n const props = (schema.properties as Record<string, JsonSchema>) ?? {};\n const req = new Set(Array.isArray(schema.required) ? (schema.required as string[]) : []);\n return {\n name,\n required,\n kind: 'object',\n fields: Object.entries(props).map(([childName, childSchema]) =>\n gatingNode(childName, childSchema ?? {}, req.has(childName), defs, path),\n ),\n };\n }\n // Everything else is a leaf to the emptiness rule: `fieldFilled` answers it\n // with `isFilled` alone, whatever kind it carries. `unknown` states that\n // honestly.\n return { name, required, kind: 'unknown' };\n}\n\n/**\n * The tree `gateRunInputs`' emptiness re-check runs over, one node per input,\n * with `gating` stamped from the contract - the same `inputMustBeFilled` the\n * Run button's `required` list is built from.\n */\nexport function gatingFieldsFromInputs(inputs: Record<string, PipeInputContract>): RunField[] {\n return Object.entries(inputs).map(([name, input]) => {\n const defs: Record<string, JsonSchema> = {};\n collectSchemaDefs(input.json_schema, defs, { traverseArrays: true });\n const node = gatingNode(\n name,\n (input.json_schema as JsonSchema) ?? {},\n !isOptionalInput(input),\n defs,\n new Set(),\n );\n return { ...node, gating: inputMustBeFilled(input) };\n });\n}\n"]}