@pipelex/mthds-form 0.7.0 → 0.9.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 (45) hide show
  1. package/README.md +6 -3
  2. package/dist/chunk-AZZAAGNX.js +194 -0
  3. package/dist/chunk-AZZAAGNX.js.map +1 -0
  4. package/dist/{chunk-IIU3YK4O.js → chunk-DARLDVJM.js} +3 -3
  5. package/dist/{chunk-IIU3YK4O.js.map → chunk-DARLDVJM.js.map} +1 -1
  6. package/dist/{chunk-R7B7ID25.js → chunk-IUWLOSAM.js} +4 -4
  7. package/dist/{chunk-R7B7ID25.js.map → chunk-IUWLOSAM.js.map} +1 -1
  8. package/dist/chunk-KG4IYFWI.js +2721 -0
  9. package/dist/chunk-KG4IYFWI.js.map +1 -0
  10. package/dist/{chunk-J4L32LD6.js → chunk-KIEOLP2L.js} +3 -3
  11. package/dist/{chunk-J4L32LD6.js.map → chunk-KIEOLP2L.js.map} +1 -1
  12. package/dist/{chunk-4BSBBG2T.js → chunk-S4CB423P.js} +3 -3
  13. package/dist/{chunk-4BSBBG2T.js.map → chunk-S4CB423P.js.map} +1 -1
  14. package/dist/{chunk-XYQ7NXQZ.js → chunk-WYH56SOZ.js} +2 -2
  15. package/dist/chunk-WYH56SOZ.js.map +1 -0
  16. package/dist/{chunk-YEVVR6FE.js → chunk-Y2ND6W4W.js} +3 -3
  17. package/dist/{chunk-YEVVR6FE.js.map → chunk-Y2ND6W4W.js.map} +1 -1
  18. package/dist/{chunk-T4NSAGOS.js → chunk-YABRYTQ4.js} +4 -4
  19. package/dist/{chunk-T4NSAGOS.js.map → chunk-YABRYTQ4.js.map} +1 -1
  20. package/dist/core/contracts.d.ts +4 -4
  21. package/dist/core/contracts.js +1 -1
  22. package/dist/core/derive.js +2 -2
  23. package/dist/core/gate.js +3 -3
  24. package/dist/core/gating-fields.js +2 -2
  25. package/dist/core/index.d.ts +1 -1
  26. package/dist/core/index.js +8 -8
  27. package/dist/core/native-content.d.ts +56 -9
  28. package/dist/core/native-content.js +1 -1
  29. package/dist/core/output-form.js +2 -2
  30. package/dist/core/stuff-files.js +2 -2
  31. package/dist/core/values.js +3 -3
  32. package/dist/field-renderer-BSU2hMf6.d.ts +34 -0
  33. package/dist/generative/index.d.ts +2013 -0
  34. package/dist/generative/index.js +3171 -0
  35. package/dist/generative/index.js.map +1 -0
  36. package/dist/react/index.d.ts +42 -36
  37. package/dist/react/index.js +8 -2614
  38. package/dist/react/index.js.map +1 -1
  39. package/dist/styles.css +2 -2
  40. package/dist/theme.css +53 -44
  41. package/dist/ui-designer.mthds +304 -0
  42. package/package.json +24 -10
  43. package/dist/chunk-45C2MKK6.js +0 -94
  44. package/dist/chunk-45C2MKK6.js.map +0 -1
  45. package/dist/chunk-XYQ7NXQZ.js.map +0 -1
package/README.md CHANGED
@@ -4,18 +4,21 @@ The input form for MTHDS methods, as a library: a headless kernel that maps the
4
4
 
5
5
  It exists because "the form" is one problem that keeps being solved separately. A method declares its inputs; something has to decide what widget each input deserves, whether the Run button may light up, what shape the values take on the wire, and what a validation failure says to a person. That logic belongs in one place with tests around it, not copied into each surface that happens to need a form.
6
6
 
7
- ## Two entry points
7
+ ## Three entry points
8
8
 
9
9
  ```ts
10
10
  import { buildRunFields, computeReadiness } from '@pipelex/mthds-form'; // headless
11
11
  import { FieldRenderer } from '@pipelex/mthds-form/react'; // the controls
12
+ import { GenerativePage } from '@pipelex/mthds-form/generative'; // a produced layout
12
13
  ```
13
14
 
14
15
  `.` is the kernel: no React, no design system, no framework. Its only runtime dependency is `ajv`, which the gate validates through. It runs in a browser, in Node, in a worker.
15
16
 
16
17
  `./react` is the control set — one control per field kind behind a single dispatch point, styled with Tailwind classes over standard shadcn/ui tokens. `react` and `react-dom` are optional peer dependencies, so a consumer that only wants the kernel never installs them.
17
18
 
18
- `mthds` is a peer dependency too, and a required one, but it is **types only**: the wire types of `pipe_io_contracts` belong to the MTHDS standard, so this package re-exports the standard's declarations instead of restating them. Every import of it is an `import type` and is erased at build, so it costs an install entry and no shipped bytes. See [docs/dependency-budget.md](docs/dependency-budget.md).
19
+ `./generative` renders a **layout** — a data file a model wrote once for a method, naming paths in the same descriptor and restating nothing about what a field is. It is the only entry that carries json-render and zod, so a host that renders an ordinary form pays for neither. When there is no layout, or the one on file no longer fits the method, the page falls back to the kernel's own form. See [docs/generative-ui.md](docs/generative-ui.md).
20
+
21
+ `mthds` is an ordinary dependency rather than a peer, but it is **types only**: the wire types of `pipe_io_contracts` belong to the MTHDS standard, so this package re-exports the standard's declarations instead of restating them. Every import of it is an `import type` and is erased at build, so it costs an install entry and no shipped bytes. It is not a peer because pnpm does not install an unmet one, and an absent `mthds` degrades the re-exported types silently rather than failing. See [docs/dependency-budget.md](docs/dependency-budget.md).
19
22
 
20
23
  ## The shape of a form
21
24
 
@@ -73,7 +76,7 @@ The controls carry Tailwind classes over the standard shadcn/ui semantic tokens
73
76
  make install
74
77
  make check # lint + format + typecheck
75
78
  make test
76
- make build # both entry points, plus the prebuilt stylesheet
79
+ make build # every entry point, plus the prebuilt stylesheet
77
80
  ```
78
81
 
79
82
  MIT licensed.
@@ -0,0 +1,194 @@
1
+ import { ownProp, hasOwnProp } from './chunk-V3OV2XQT.js';
2
+
3
+ // src/core/native-content.ts
4
+ function isRecord(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ function readText(record, key) {
8
+ const value = ownProp(record, key);
9
+ return typeof value === "string" && value.length > 0 ? value : void 0;
10
+ }
11
+ function readNumber(record, key) {
12
+ const value = ownProp(record, key);
13
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
14
+ }
15
+ function readDocumentContent(value) {
16
+ if (!isRecord(value)) return void 0;
17
+ const url = readText(value, "url");
18
+ if (url === void 0) return void 0;
19
+ return {
20
+ url,
21
+ publicUrl: readText(value, "public_url"),
22
+ mimeType: readText(value, "mime_type"),
23
+ filename: readText(value, "filename"),
24
+ title: readText(value, "title"),
25
+ snippet: readText(value, "snippet")
26
+ };
27
+ }
28
+ function readImageContent(value) {
29
+ if (!isRecord(value)) return void 0;
30
+ const url = readText(value, "url");
31
+ if (url === void 0) return void 0;
32
+ return {
33
+ url,
34
+ publicUrl: readText(value, "public_url"),
35
+ caption: readText(value, "caption"),
36
+ mimeType: readText(value, "mime_type"),
37
+ filename: readText(value, "filename"),
38
+ width: readNumber(value, "width"),
39
+ height: readNumber(value, "height"),
40
+ sourcePrompt: readText(value, "source_prompt")
41
+ };
42
+ }
43
+ function readHtmlContent(value) {
44
+ if (!isRecord(value)) return void 0;
45
+ const innerHtml = readText(value, "inner_html");
46
+ if (innerHtml === void 0) return void 0;
47
+ return { innerHtml, cssClass: readText(value, "css_class") };
48
+ }
49
+ var NATIVE_HTML_CONCEPT_REF = "native.Html";
50
+ var NATIVE_DATE_CONCEPT_REF = "native.Date";
51
+ var NATIVE_COMPOSITE_CONCEPT_REF = "native.Composite";
52
+ function readCompositeContent(value) {
53
+ if (!isRecord(value)) return void 0;
54
+ return Object.entries(value).map(([name, member]) => ({ name, value: member }));
55
+ }
56
+ function isNativeHtmlNode(node) {
57
+ return refinesNative(node, NATIVE_HTML_CONCEPT_REF);
58
+ }
59
+ function isNativeDateNode(node) {
60
+ return refinesNative(node, NATIVE_DATE_CONCEPT_REF);
61
+ }
62
+ function isNativeCompositeNode(node) {
63
+ return refinesNative(node, NATIVE_COMPOSITE_CONCEPT_REF);
64
+ }
65
+ function refinesNative(node, ref) {
66
+ if (node.conceptRef === ref) return true;
67
+ return node.refines?.includes(ref) ?? false;
68
+ }
69
+ function readDateContent(value) {
70
+ if (typeof value === "string") {
71
+ return value.length > 0 ? { date: value } : void 0;
72
+ }
73
+ if (!isRecord(value)) return void 0;
74
+ const inner = ownProp(value, "date");
75
+ const date = typeof inner === "string" ? inner : isRecord(inner) ? readText(inner, "date") : void 0;
76
+ if (date === void 0) return void 0;
77
+ return { date, time: readText(value, "time") };
78
+ }
79
+ function formatDateContent(content) {
80
+ return content.time ? `${content.date} ${content.time}` : content.date;
81
+ }
82
+ var VIEWABLE_DATA_MEDIA_TYPES = /* @__PURE__ */ new Set([
83
+ "image/png",
84
+ "image/jpeg",
85
+ "image/gif",
86
+ "image/webp",
87
+ "image/avif",
88
+ "application/pdf"
89
+ ]);
90
+ var RELATIVE_SENTINEL_ORIGIN = "https://url-gate.invalid";
91
+ var OWN_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
92
+ function stripUrlWhitespace(candidate) {
93
+ let start = 0;
94
+ let end = candidate.length;
95
+ while (start < end && candidate.charCodeAt(start) <= 32) start += 1;
96
+ while (end > start && candidate.charCodeAt(end - 1) <= 32) end -= 1;
97
+ for (let index = start; index < end; index += 1) {
98
+ const code = candidate.charCodeAt(index);
99
+ if (code === 9 || code === 10 || code === 13) {
100
+ let stripped = candidate.slice(start, index);
101
+ for (let rest = index; rest < end; rest += 1) {
102
+ const restCode = candidate.charCodeAt(rest);
103
+ if (restCode !== 9 && restCode !== 10 && restCode !== 13)
104
+ stripped += candidate[rest];
105
+ }
106
+ return stripped;
107
+ }
108
+ }
109
+ return start === 0 && end === candidate.length ? candidate : candidate.slice(start, end);
110
+ }
111
+ function readDataUrl(candidate) {
112
+ const url = candidate.trimStart();
113
+ if (!/^data:/i.test(url)) return void 0;
114
+ const comma = url.indexOf(",");
115
+ if (comma === -1) return void 0;
116
+ const [declared = "", ...parameters] = url.slice("data:".length, comma).split(";");
117
+ const trimmed = declared.trim().toLowerCase();
118
+ const fragment = url.indexOf("#", comma);
119
+ const payload = fragment === -1 ? url.slice(comma + 1) : url.slice(comma + 1, fragment);
120
+ const base64 = parameters.some((parameter) => parameter.trim().toLowerCase() === "base64");
121
+ return {
122
+ mediaType: trimmed === "" ? "text/plain" : trimmed,
123
+ bytes: base64 ? base64Bytes(payload) : percentEncodedBytes(payload)
124
+ };
125
+ }
126
+ function base64Bytes(payload) {
127
+ let symbols = 0;
128
+ for (let index = 0; index < payload.length; index += 1) {
129
+ let code = payload.charCodeAt(index);
130
+ if (code === 37) {
131
+ const escaped = Number.parseInt(payload.slice(index + 1, index + 3), 16);
132
+ if (!Number.isNaN(escaped) && index + 2 < payload.length) {
133
+ code = escaped;
134
+ index += 2;
135
+ }
136
+ }
137
+ if (code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 43 || code === 47 || code === 45 || code === 95)
138
+ symbols += 1;
139
+ }
140
+ return Math.floor(symbols * 3 / 4);
141
+ }
142
+ function percentEncodedBytes(payload) {
143
+ const escapes = payload.match(/%[0-9a-f]{2}/gi)?.length ?? 0;
144
+ return new TextEncoder().encode(payload).length - escapes * 2;
145
+ }
146
+ function dataUrlMediaType(parsed) {
147
+ const declared = parsed.pathname.split(/[;,]/, 1)[0] ?? "";
148
+ const trimmed = declared.trim().toLowerCase();
149
+ return trimmed === "" ? "text/plain" : trimmed;
150
+ }
151
+ function viewableUrl(candidate) {
152
+ if (!candidate) return void 0;
153
+ const stripped = stripUrlWhitespace(candidate);
154
+ if (stripped === "") return void 0;
155
+ if (OWN_SCHEME_RE.test(stripped)) {
156
+ let parsed2;
157
+ try {
158
+ parsed2 = new URL(stripped);
159
+ } catch {
160
+ return void 0;
161
+ }
162
+ switch (parsed2.protocol) {
163
+ case "http:":
164
+ case "https:":
165
+ case "blob:":
166
+ return parsed2.href;
167
+ case "data:":
168
+ return VIEWABLE_DATA_MEDIA_TYPES.has(dataUrlMediaType(parsed2)) ? parsed2.href : void 0;
169
+ default:
170
+ return void 0;
171
+ }
172
+ }
173
+ if (!stripped.startsWith("/")) return void 0;
174
+ const afterSlash = stripped.charCodeAt(1);
175
+ if (afterSlash === 47 || afterSlash === 92) return void 0;
176
+ let parsed;
177
+ try {
178
+ parsed = new URL(stripped, RELATIVE_SENTINEL_ORIGIN);
179
+ } catch {
180
+ return void 0;
181
+ }
182
+ return parsed.origin === RELATIVE_SENTINEL_ORIGIN ? stripped : void 0;
183
+ }
184
+ function isViewableUrl(url) {
185
+ return viewableUrl(url) !== void 0;
186
+ }
187
+ var TYPED_SCALAR_MARKERS = ["__class__", "__module__"];
188
+ function hasTypedScalarMarkers(value) {
189
+ return isRecord(value) && TYPED_SCALAR_MARKERS.every((marker) => hasOwnProp(value, marker));
190
+ }
191
+
192
+ export { NATIVE_COMPOSITE_CONCEPT_REF, NATIVE_DATE_CONCEPT_REF, NATIVE_HTML_CONCEPT_REF, TYPED_SCALAR_MARKERS, formatDateContent, hasTypedScalarMarkers, isNativeCompositeNode, isNativeDateNode, isNativeHtmlNode, isViewableUrl, readCompositeContent, readDataUrl, readDateContent, readDocumentContent, readHtmlContent, readImageContent, viewableUrl };
193
+ //# sourceMappingURL=chunk-AZZAAGNX.js.map
194
+ //# sourceMappingURL=chunk-AZZAAGNX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/native-content.ts"],"names":["parsed"],"mappings":"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","file":"chunk-AZZAAGNX.js","sourcesContent":["/**\n * How to READ the native content models — the result side's twin of\n * `./file-formats`.\n *\n * A renderer handed a result knows the field's `kind` from the descriptor, and\n * for the kinds whose value is a structured content model rather than a bare\n * scalar (`document`, `image`, `date`) it must then read that model's members.\n * **That is reading the standard, not sniffing at it.** The distinction is the\n * whole reason this module exists as data rather than as heuristics inside a\n * control: a reader keyed by a STATED kind consults a pinned definition, where\n * a reader that works out *which* kind it is holding by counting properties is\n * guessing — and guessing is exactly what [../../docs/derivation-swap.md]\n * records removing from the input side.\n *\n * ## Where each shape comes from\n *\n * `native.Image` and `native.Document` are pinned by the standard, in\n * `docs/spec/native-concepts.md` § \"The Pinned Set (MTHDS 1.0.0)\". Both carry a\n * required `url` — a storage URI, an HTTP(S) URL, or a base64 data URL — and\n * optional presentation members beside it. Nothing here adds a member the spec\n * does not state.\n *\n * `native.Date` is pinned the same way (`{date, time}`), but a `date` FIELD\n * inside a structured concept is not a `native.Date` value: it is a python\n * `datetime.date`, and the serialization layer stamps it with its class. So a\n * date arrives in one of three legal shapes, all three of which a run really\n * produces:\n *\n * \"2026-03-14\" a plain ISO string\n * { date: \"2026-03-14\", __class__: \"date\", … } a typed scalar\n * { date: \"2026-03-14\", time: \"15:40:00\" } native.Date\n *\n * The typed-scalar envelope is the one shape the standard does NOT pin — it is\n * a serialization fact, measured off a real run rather than read off a spec —\n * and it is called out as such here so nobody mistakes it for normative.\n *\n * This module lives in core, not beside the control that renders it, for the\n * reason `file-formats.ts` established: a host that shows a result its own way\n * needs the same answer the control uses, and two copies of an answer is two\n * places for it to drift. Pure data and pure functions — no React.\n */\n\nimport { hasOwnProp, ownProp } from './own-property';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A member the content models declare as `type = \"text\"`, when it is one. */\nfunction readText(record: Record<string, unknown>, key: string): string | undefined {\n const value = ownProp(record, key);\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction readNumber(record: Record<string, unknown>, key: string): number | undefined {\n const value = ownProp(record, key);\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * `native.Document`, read.\n *\n * `url` is the only required member; everything else is a producer's courtesy,\n * so a reader that demands any of them describes a value the standard permits\n * as malformed.\n */\nexport interface DocumentContentView {\n url: string;\n publicUrl?: string;\n mimeType?: string;\n filename?: string;\n title?: string;\n snippet?: string;\n}\n\n/** `native.Image`, read. `width`/`height` are optional but PAIRED by the spec. */\nexport interface ImageContentView {\n url: string;\n publicUrl?: string;\n caption?: string;\n mimeType?: string;\n filename?: string;\n width?: number;\n height?: number;\n sourcePrompt?: string;\n}\n\n/** `native.Date` (and the typed-scalar envelope a nested `date` field arrives in). */\nexport interface DateContentView {\n /** ISO 8601 calendar date, `YYYY-MM-DD`. */\n date: string;\n /** ISO 8601 time of day, present only when the source stated one. */\n time?: string;\n}\n\n/**\n * Read a `document`-kind value. `undefined` when the value carries no `url`,\n * which is the one member the standard requires — a caller renders its own\n * absence rather than being handed a half-value to test.\n */\nexport function readDocumentContent(value: unknown): DocumentContentView | undefined {\n if (!isRecord(value)) return undefined;\n const url = readText(value, 'url');\n if (url === undefined) return undefined;\n return {\n url,\n publicUrl: readText(value, 'public_url'),\n mimeType: readText(value, 'mime_type'),\n filename: readText(value, 'filename'),\n title: readText(value, 'title'),\n snippet: readText(value, 'snippet'),\n };\n}\n\n/** Read an `image`-kind value. Same contract as `readDocumentContent`. */\nexport function readImageContent(value: unknown): ImageContentView | undefined {\n if (!isRecord(value)) return undefined;\n const url = readText(value, 'url');\n if (url === undefined) return undefined;\n return {\n url,\n publicUrl: readText(value, 'public_url'),\n caption: readText(value, 'caption'),\n mimeType: readText(value, 'mime_type'),\n filename: readText(value, 'filename'),\n width: readNumber(value, 'width'),\n height: readNumber(value, 'height'),\n sourcePrompt: readText(value, 'source_prompt'),\n };\n}\n\n/**\n * `native.Html`, read.\n *\n * The only content model here that is NOT reached by a field kind, and the\n * reason is a deliberate decision of the standard: the kind vocabulary has no\n * `html` member, so a `native.Html` value is an `object` node over its two\n * declared fields. That is right for the descriptor — the kinds name how a value\n * is ENTERED, and markup is entered as text — and wrong for a result view, which\n * would otherwise print a page's source at a reader.\n *\n * So this one is keyed by CONCEPT rather than by kind, through\n * `isNativeHtmlNode` below. That is a membership test on facts the descriptor\n * states (`concept_ref`, `refines`), which the standard's own page calls out as\n * the supported way to ask \"does this refine `native.X`?\" — the opposite of\n * inspecting a value to guess what it is.\n */\nexport interface HtmlContentView {\n /** The markup itself. The one required member. */\n innerHtml: string;\n /** A class name for a wrapper a consumer may put around it, when stated. */\n cssClass?: string;\n}\n\n/** Read an `native.Html` value. `undefined` when it carries no markup. */\nexport function readHtmlContent(value: unknown): HtmlContentView | undefined {\n if (!isRecord(value)) return undefined;\n const innerHtml = readText(value, 'inner_html');\n if (innerHtml === undefined) return undefined;\n return { innerHtml, cssClass: readText(value, 'css_class') };\n}\n\n/** The concept refs whose content model is multi-property, spelled once each. */\nexport const NATIVE_HTML_CONCEPT_REF = 'native.Html';\nexport const NATIVE_DATE_CONCEPT_REF = 'native.Date';\nexport const NATIVE_COMPOSITE_CONCEPT_REF = 'native.Composite';\n\n/** One member of a composite: the name it was composed under, and its content. */\nexport interface CompositeMember {\n name: string;\n value: unknown;\n}\n\n/**\n * Read a `native.Composite` — a named composition of contents.\n *\n * The members are returned in the order the payload states them, which is the\n * order the method composed them in and the only order there is: a composite\n * declares no fields, so there is no authored order to prefer over it.\n *\n * `undefined` for anything that is not a record, so the caller falls back.\n */\nexport function readCompositeContent(value: unknown): CompositeMember[] | undefined {\n if (!isRecord(value)) return undefined;\n return Object.entries(value).map(([name, member]) => ({ name, value: member }));\n}\n\n/**\n * Whether a descriptor node carries markup — its concept IS `native.Html`, or\n * refines it.\n *\n * The refinement chain is checked, not just the leaf, because a method may\n * declare `legal.ClauseMarkup` refining `native.Html` and a reader of that result\n * wants the markup rendered just the same. `refines` is on the wire for exactly\n * this question.\n */\nexport function isNativeHtmlNode(node: {\n conceptRef?: string;\n refines?: readonly string[];\n}): boolean {\n return refinesNative(node, NATIVE_HTML_CONCEPT_REF);\n}\n\n/**\n * Whether a node IS a `native.Date` — the other content model the kind\n * vocabulary cannot name.\n *\n * `native.Date` is `{date, time}`, two properties, so nothing unwraps it and its\n * node is an `object`. Correct for the descriptor and wrong for a reader: a date\n * result rendered structurally is a two-field card whose second field says \"not\n * provided\", where the answer is a date. The `date` ARM already reads exactly\n * this model — `readDateContent` takes `{date, time}` — it just never fires,\n * because the kind says `object`.\n *\n * A `date` FIELD inside a structure is a different thing and needs none of this:\n * it carries `kind: \"date\"` already.\n */\nexport function isNativeDateNode(node: {\n conceptRef?: string;\n refines?: readonly string[];\n}): boolean {\n return refinesNative(node, NATIVE_DATE_CONCEPT_REF);\n}\n\n/**\n * Whether a node is a `native.Composite` — a named bag of contents.\n *\n * The third concept the kind vocabulary cannot name, and the one where it is\n * least able to: a composite declares no members at all, so its descriptor is\n * `kind: \"unknown\"` and its payload schema is `{additionalProperties: true}`\n * with no properties. Both are TRUE — the standard's escape hatch, honestly\n * used — and both leave a renderer with nothing to read, so the default\n * fallback prints the whole thing as a JSON blob.\n *\n * That is the worst available answer to a value that is, by definition, a map\n * of ordinary `StuffContent`s. Keyed by concept, a renderer can at least show\n * the members as the named things they are. See `CompositeValue`.\n */\nexport function isNativeCompositeNode(node: {\n conceptRef?: string;\n refines?: readonly string[];\n}): boolean {\n return refinesNative(node, NATIVE_COMPOSITE_CONCEPT_REF);\n}\n\n/** Its concept IS the named native, or refines it. */\nfunction refinesNative(\n node: { conceptRef?: string; refines?: readonly string[] },\n ref: string,\n): boolean {\n if (node.conceptRef === ref) return true;\n return node.refines?.includes(ref) ?? false;\n}\n\n/**\n * Read a `date`-kind value, in any of the three shapes a run produces.\n *\n * The `date` member is read one level deep, because `native.Date`'s own `date`\n * member is a `datetime.date` and therefore arrives inside the same typed-scalar\n * envelope the whole value may already be wrapped in. One level is enough and\n * the recursion is bounded on purpose: a second envelope would be a\n * serialization bug, and following it forever would turn one into a hang.\n */\nexport function readDateContent(value: unknown): DateContentView | undefined {\n if (typeof value === 'string') {\n return value.length > 0 ? { date: value } : undefined;\n }\n if (!isRecord(value)) return undefined;\n\n const inner = ownProp(value, 'date');\n const date =\n typeof inner === 'string' ? inner : isRecord(inner) ? readText(inner, 'date') : undefined;\n if (date === undefined) return undefined;\n return { date, time: readText(value, 'time') };\n}\n\n/** `{date}` / `{date, time}` as one line, which is how both are read aloud. */\nexport function formatDateContent(content: DateContentView): string {\n return content.time ? `${content.date} ${content.time}` : content.date;\n}\n\n/**\n * The media types a `data:` URL may carry and still be viewable.\n *\n * An allow-list rather than a deny-list, because the question a sink asks is\n * \"can this paint without executing\", and only a closed set answers it. The\n * raster types are the ones the preview arms already name; `application/pdf` is\n * here because the input control previews a `data:application/pdf` value a\n * storage-less host wrote back, through an `<object>`, and reads this same\n * predicate to decide.\n *\n * `image/svg+xml` is deliberately absent: an SVG paints as a picture and\n * EXECUTES as a document the moment it is opened in a tab, which is one click\n * away from every image this package paints.\n */\nconst VIEWABLE_DATA_MEDIA_TYPES = new Set([\n 'image/png',\n 'image/jpeg',\n 'image/gif',\n 'image/webp',\n 'image/avif',\n 'application/pdf',\n]);\n\n/**\n * An origin no host can be, to resolve a relative reference against.\n *\n * `.invalid` is reserved by RFC 2606 and resolves nowhere, so nothing can be\n * fetched from it by accident. It exists only so a path can be parsed at all;\n * the answer taken from the parse is whether the reference STAYED on it.\n */\nconst RELATIVE_SENTINEL_ORIGIN = 'https://url-gate.invalid';\n\n/**\n * A scheme, by RFC 3986's grammar: a letter, then letters, digits, `+`, `-`, `.`\n *\n * Read off the string rather than inferred from a parse, because a parse needs a\n * base and the base is what makes the answer wrong — see {@link viewableUrl}.\n */\nconst OWN_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;\n\n/**\n * What the WHATWG URL parser strips before it parses, applied before we judge.\n *\n * Leading and trailing C0 controls and spaces are removed, and every internal\n * tab, line feed and carriage return. This is not tidying: a scheme test run on\n * the raw string and a browser running on the parsed one disagree about\n * `\" https://cdn/x.png\"` — `new URL()` reads it as `https:` and `/^https?:/`\n * does not — and a host that validated a member by parsing while this gate\n * prefix-matched would have judged a different string than the one painted.\n */\nfunction stripUrlWhitespace(candidate: string): string {\n // Written out rather than as a character-class regex: the range IS the control\n // characters, which is exactly what a regex may not spell (`no-control-regex`).\n let start = 0;\n let end = candidate.length;\n while (start < end && candidate.charCodeAt(start) <= 0x20) start += 1;\n while (end > start && candidate.charCodeAt(end - 1) <= 0x20) end -= 1;\n // Scan before rebuilding, and rebuild only from the first character that has\n // to go. Almost every URL has nothing to strip inside it, and the one that\n // does is the one that is huge: an inline `data:` image is judged three or\n // four times per render, and a per-character rebuild of a megabyte-long\n // payload each time is hundreds of milliseconds of frozen main thread for a\n // string that comes back identical.\n for (let index = start; index < end; index += 1) {\n const code = candidate.charCodeAt(index);\n // Tab, line feed, carriage return - removed wherever they appear, not only\n // at the edges, which is what the parser does with them.\n if (code === 0x09 || code === 0x0a || code === 0x0d) {\n let stripped = candidate.slice(start, index);\n for (let rest = index; rest < end; rest += 1) {\n const restCode = candidate.charCodeAt(rest);\n if (restCode !== 0x09 && restCode !== 0x0a && restCode !== 0x0d)\n stripped += candidate[rest];\n }\n return stripped;\n }\n }\n return start === 0 && end === candidate.length ? candidate : candidate.slice(start, end);\n}\n\n/** What a `data:` URL says about the bytes it carries. */\nexport interface DataUrlView {\n /** Lowercased, without its parameters; `text/plain` when the URL declares none. */\n mediaType: string;\n /** The size of the DECODED payload, in bytes. */\n bytes: number;\n}\n\n/**\n * A `data:` URL's media type and decoded size, or `undefined` for any other URL.\n *\n * A host that encodes a picked file in the browser writes the file itself into\n * the value's `url`, so a thirty-kilobyte PDF is a forty-thousand-character\n * string, and printed where a reference would be it tells a person nothing. Its\n * type and its size are what they can read.\n *\n * The size is COUNTED rather than decoded. A base64 payload's size follows from\n * how many symbols it holds, and decoding a megabyte to measure it would be the\n * frozen main thread {@link stripUrlWhitespace} is written to avoid. This is a\n * reading for display, not a gate: whether the URL may be painted is still\n * {@link viewableUrl}'s answer alone.\n */\nexport function readDataUrl(candidate: string): DataUrlView | undefined {\n const url = candidate.trimStart();\n if (!/^data:/i.test(url)) return undefined;\n const comma = url.indexOf(',');\n if (comma === -1) return undefined;\n const [declared = '', ...parameters] = url.slice('data:'.length, comma).split(';');\n const trimmed = declared.trim().toLowerCase();\n // A fragment is not payload. `#page=2` on an inline PDF is the viewer's, and\n // counting it as bytes is counting the URL rather than the file.\n const fragment = url.indexOf('#', comma);\n const payload = fragment === -1 ? url.slice(comma + 1) : url.slice(comma + 1, fragment);\n const base64 = parameters.some((parameter) => parameter.trim().toLowerCase() === 'base64');\n return {\n mediaType: trimmed === '' ? 'text/plain' : trimmed,\n bytes: base64 ? base64Bytes(payload) : percentEncodedBytes(payload),\n };\n}\n\n/**\n * Three bytes for every four symbols, ignoring what is not one: the `=`\n * padding, and the line breaks a MIME encoder wraps its output at. Both\n * alphabets count, the standard one and the URL-safe one.\n *\n * A percent-escape is resolved to the character it stands for before it is\n * judged, because a payload may be escaped anywhere — `YQ%3D%3D` is `YQ==`,\n * one byte, and counting `3` and `D` as symbols made it four.\n */\nfunction base64Bytes(payload: string): number {\n let symbols = 0;\n for (let index = 0; index < payload.length; index += 1) {\n let code = payload.charCodeAt(index);\n if (code === 0x25) {\n const escaped = Number.parseInt(payload.slice(index + 1, index + 3), 16);\n if (!Number.isNaN(escaped) && index + 2 < payload.length) {\n code = escaped;\n index += 2;\n }\n }\n if (\n (code >= 0x41 && code <= 0x5a) ||\n (code >= 0x61 && code <= 0x7a) ||\n (code >= 0x30 && code <= 0x39) ||\n code === 0x2b ||\n code === 0x2f ||\n code === 0x2d ||\n code === 0x5f\n )\n symbols += 1;\n }\n return Math.floor((symbols * 3) / 4);\n}\n\n/** A percent-escape is one byte; any other character is its UTF-8 length. */\nfunction percentEncodedBytes(payload: string): number {\n const escapes = payload.match(/%[0-9a-f]{2}/gi)?.length ?? 0;\n return new TextEncoder().encode(payload).length - escapes * 2;\n}\n\n/** The media type a `data:` URL declares, lowercased; `text/plain` when it declares none. */\nfunction dataUrlMediaType(parsed: URL): string {\n const declared = parsed.pathname.split(/[;,]/, 1)[0] ?? '';\n const trimmed = declared.trim().toLowerCase();\n return trimmed === '' ? 'text/plain' : trimmed;\n}\n\n/**\n * The URL a browser can paint, normalised — or `undefined` when there is none.\n *\n * **The string this returns is the string a sink must use.** That is the whole\n * point of returning one rather than answering yes or no: the gate judges a\n * normalised URL, so handing the caller back the raw member would let the two\n * diverge again on exactly the whitespace the parser strips.\n *\n * The gate parses rather than prefix-matches, and branches on the protocol the\n * parser reports:\n *\n * - `http:` and `https:` are viewable.\n * - `blob:` is viewable. A blob URL is bound to the origin that minted it, so a\n * payload cannot forge one that points anywhere else.\n * - `data:` is viewable only for a media type in {@link VIEWABLE_DATA_MEDIA_TYPES}.\n * `data:text/html` is a document at the EMBEDDING page's own origin, which is\n * what made an unsandboxed frame over a payload URL a real hole.\n * - Everything else is not: `javascript:`, `file:` (a run on a developer's\n * machine writes one, and a browser refuses to load it from an http page) and\n * `pipelex-storage://`, which cannot be resolved client-side at all — that is\n * the upload seam's job (see [../../docs/upload-seam.md]), and a renderer with\n * no resolver must show the reference rather than a broken `<img>`.\n *\n * **A root-relative path counts**, and leaving it out was a real bug rather\n * than an omission. A host's URL resolver naturally hands back a path on its\n * OWN origin — `/api/assets/…`, an authenticated route that streams the object\n * — which is the whole point of resolving rather than exposing storage. This\n * predicate rejected it for having no scheme, so the resolved URL was thrown\n * away and the arms fell back to the payload's `public_url`: a presigned URL\n * that had already expired. The image stayed broken with the fix in place.\n *\n * A path is rejected when it is protocol-relative — `//host/x`, and the two\n * backslash spellings the parser reads the same way (`\\\\host\\x`, `/\\host/x`) —\n * and then checked against the sentinel as well. An accepted path is returned\n * relative, because making it absolute against a sentinel would point it at\n * nowhere.\n */\nexport function viewableUrl(candidate: string | undefined): string | undefined {\n if (!candidate) return undefined;\n const stripped = stripUrlWhitespace(candidate);\n if (stripped === '') return undefined;\n\n // Whether the string carries a scheme is read OFF THE STRING, before any base\n // is applied, because the parser reads a candidate whose scheme equals the\n // base's as relative to that base. Deciding it with a second, base-less parse\n // let the two disagree: `https:cdn.example/x.png` was judged absolute and\n // resolved against the sentinel, so the gate handed a sink\n // `https://url-gate.invalid/cdn.example/x.png` — an internal placeholder, in a\n // real `<img src>`, in place of a URL that had worked. The grammar is RFC\n // 3986's, and it agrees with the parser on every string either accepts.\n if (OWN_SCHEME_RE.test(stripped)) {\n let parsed: URL;\n try {\n parsed = new URL(stripped);\n } catch {\n return undefined;\n }\n switch (parsed.protocol) {\n case 'http:':\n case 'https:':\n case 'blob:':\n return parsed.href;\n case 'data:':\n return VIEWABLE_DATA_MEDIA_TYPES.has(dataUrlMediaType(parsed)) ? parsed.href : undefined;\n default:\n return undefined;\n }\n }\n\n // No scheme of its own: a reference relative to wherever the page is. Only a\n // root-relative path qualifies.\n if (!stripped.startsWith('/')) return undefined;\n // Protocol-relative, rejected on the SPELLING. \"Did it stay on the sentinel\"\n // reads as the same rule and is blind in exactly one place — the sentinel's\n // own host — so `//url-gate.invalid/x` passed the test written to stop it.\n const afterSlash = stripped.charCodeAt(1);\n if (afterSlash === 0x2f || afterSlash === 0x5c) return undefined;\n let parsed: URL;\n try {\n parsed = new URL(stripped, RELATIVE_SENTINEL_ORIGIN);\n } catch {\n return undefined;\n }\n return parsed.origin === RELATIVE_SENTINEL_ORIGIN ? stripped : undefined;\n}\n\n/**\n * Whether a browser can paint this URL as-is — {@link viewableUrl} as a guard.\n *\n * Prefer `viewableUrl` wherever the URL is then USED: this answers the question\n * without handing back the normalised string, so a caller that acts on the raw\n * candidate acts on a string the gate did not judge.\n */\nexport function isViewableUrl(url: string | undefined): url is string {\n return viewableUrl(url) !== undefined;\n}\n\n/**\n * The typed-scalar markers the serialization layer stamps onto a value.\n *\n * Exported so a host can recognise (and strip) them; nothing in the kernel\n * BRANCHES on them, which is the point — a reader keyed by these would be\n * reading the serializer instead of the standard.\n */\nexport const TYPED_SCALAR_MARKERS = ['__class__', '__module__'] as const;\n\n/** Whether a record carries the typed-scalar markers. Informational. */\nexport function hasTypedScalarMarkers(value: unknown): boolean {\n return isRecord(value) && TYPED_SCALAR_MARKERS.every((marker) => hasOwnProp(value, marker));\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { buildPipeRef } from './chunk-XYQ7NXQZ.js';
1
+ import { buildPipeRef } from './chunk-WYH56SOZ.js';
2
2
  import { ownProp } from './chunk-V3OV2XQT.js';
3
3
 
4
4
  // src/core/output-form.ts
@@ -12,5 +12,5 @@ function getPipeOutputForm(outputForm, domain, pipeCode) {
12
12
  }
13
13
 
14
14
  export { getPipeOutputForm };
15
- //# sourceMappingURL=chunk-IIU3YK4O.js.map
16
- //# sourceMappingURL=chunk-IIU3YK4O.js.map
15
+ //# sourceMappingURL=chunk-DARLDVJM.js.map
16
+ //# sourceMappingURL=chunk-DARLDVJM.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/output-form.ts"],"names":[],"mappings":";;;;AA6CO,SAAS,iBAAA,CACd,UAAA,EACA,MAAA,EACA,QAAA,EACsC;AACtC,EAAA,IAAI,CAAC,UAAA,IAAc,CAAC,QAAA,EAAU,OAAO,MAAA;AACrC,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,MAAM,QAAQ,OAAA,CAAQ,UAAA,EAAY,YAAA,CAAa,MAAA,EAAQ,QAAQ,CAAC,CAAA;AAChE,IAAA,IAAI,OAAO,OAAO,KAAA;AAAA,EACpB;AACA,EAAA,OAAO,OAAA,CAAQ,YAAY,QAAQ,CAAA;AACrC","file":"chunk-IIU3YK4O.js","sourcesContent":["/**\n * The output-form descriptor lookup.\n *\n * **This module used to declare the artifact; now it only addresses it.** The\n * types come from `mthds/protocol`, exactly as the input side's do — the\n * standard grew `output_form` as a page of its own, and grew `json_schema` on\n * the output contract in the same version, so a renderer reads both off the wire\n * instead of having them supplied beside it. What is left here is the twin of\n * `getPipeInputForm`: the lookup, and the tolerance for the same two key\n * conventions. [docs/contract-mirror.md](../../docs/contract-mirror.md) states\n * the rule this now obeys — the contract types are the standard's, not this\n * package's, and a member this package wants on one is a change to the standard\n * before it is a change here.\n *\n * **An output is a concept ref exactly like an input is.** Same concept, same\n * structure, same field kinds, same nesting; what differs is only the three SLOT\n * facts an output has none of (an authored `name`, the three-valued `presence`,\n * `gating`), all three optional on the standard's node precisely so a node can\n * exist without them. That is why `buildResultField` maps an output node through\n * the very mapper `buildRunFields` uses, and why there is no second node union\n * anywhere in this package.\n *\n * | | Inputs | Outputs |\n * | --- | --- | --- |\n * | identity, plurality, optionality | `pipe_io_contracts` | `pipe_io_contracts` |\n * | shape / JSON Schema | `json_schema` on the input contract | `json_schema` on the output contract |\n * | presentation view | `input_form` | `output_form` |\n *\n * The two rows that used to read \"nothing\" are the change this package was built\n * against while it was still a proposal; `wip/output-form-standard-change.md` is\n * the argument that landed it. The one asymmetry the language keeps is\n * `presence` versus `optional`: `!` MUST NOT appear on an output, so a\n * three-valued output marker would have an arm nothing could ever produce.\n */\n\nimport type { OutputForm, PipeOutputFormDescriptor } from 'mthds/protocol';\nimport { buildPipeRef } from './contracts';\nimport { ownProp } from './own-property';\n\nexport type { OutputForm, PipeOutputFormDescriptor };\n\n/**\n * Look a pipe's output descriptor up by code — the exact twin of\n * `getPipeInputForm`, tolerant of the same two key conventions.\n */\nexport function getPipeOutputForm(\n outputForm: OutputForm | null | undefined,\n domain: string | null | undefined,\n pipeCode: string | null | undefined,\n): PipeOutputFormDescriptor | undefined {\n if (!outputForm || !pipeCode) return undefined;\n if (domain) {\n const byRef = ownProp(outputForm, buildPipeRef(domain, pipeCode));\n if (byRef) return byRef;\n }\n return ownProp(outputForm, pipeCode);\n}\n"]}
1
+ {"version":3,"sources":["../src/core/output-form.ts"],"names":[],"mappings":";;;;AA6CO,SAAS,iBAAA,CACd,UAAA,EACA,MAAA,EACA,QAAA,EACsC;AACtC,EAAA,IAAI,CAAC,UAAA,IAAc,CAAC,QAAA,EAAU,OAAO,MAAA;AACrC,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,MAAM,QAAQ,OAAA,CAAQ,UAAA,EAAY,YAAA,CAAa,MAAA,EAAQ,QAAQ,CAAC,CAAA;AAChE,IAAA,IAAI,OAAO,OAAO,KAAA;AAAA,EACpB;AACA,EAAA,OAAO,OAAA,CAAQ,YAAY,QAAQ,CAAA;AACrC","file":"chunk-DARLDVJM.js","sourcesContent":["/**\n * The output-form descriptor lookup.\n *\n * **This module used to declare the artifact; now it only addresses it.** The\n * types come from `mthds/protocol`, exactly as the input side's do — the\n * standard grew `output_form` as a page of its own, and grew `json_schema` on\n * the output contract in the same version, so a renderer reads both off the wire\n * instead of having them supplied beside it. What is left here is the twin of\n * `getPipeInputForm`: the lookup, and the tolerance for the same two key\n * conventions. [docs/contract-mirror.md](../../docs/contract-mirror.md) states\n * the rule this now obeys — the contract types are the standard's, not this\n * package's, and a member this package wants on one is a change to the standard\n * before it is a change here.\n *\n * **An output is a concept ref exactly like an input is.** Same concept, same\n * structure, same field kinds, same nesting; what differs is only the three SLOT\n * facts an output has none of (an authored `name`, the three-valued `presence`,\n * `gating`), all three optional on the standard's node precisely so a node can\n * exist without them. That is why `buildResultField` maps an output node through\n * the very mapper `buildRunFields` uses, and why there is no second node union\n * anywhere in this package.\n *\n * | | Inputs | Outputs |\n * | --- | --- | --- |\n * | identity, plurality, optionality | `pipe_io_contracts` | `pipe_io_contracts` |\n * | shape / JSON Schema | `json_schema` on the input contract | `json_schema` on the output contract |\n * | presentation view | `input_form` | `output_form` |\n *\n * The two rows that used to read \"nothing\" are the change this package was built\n * against while it was still a proposal; `wip/output-form-standard-change.md` is\n * the argument that landed it. The one asymmetry the language keeps is\n * `presence` versus `optional`: `!` MUST NOT appear on an output, so a\n * three-valued output marker would have an arm nothing could ever produce.\n */\n\nimport type { OutputForm, PipeOutputFormDescriptor } from 'mthds/protocol';\nimport { buildPipeRef } from './contracts';\nimport { ownProp } from './own-property';\n\nexport type { OutputForm, PipeOutputFormDescriptor };\n\n/**\n * Look a pipe's output descriptor up by code — the exact twin of\n * `getPipeInputForm`, tolerant of the same two key conventions.\n */\nexport function getPipeOutputForm(\n outputForm: OutputForm | null | undefined,\n domain: string | null | undefined,\n pipeCode: string | null | undefined,\n): PipeOutputFormDescriptor | undefined {\n if (!outputForm || !pipeCode) return undefined;\n if (domain) {\n const byRef = ownProp(outputForm, buildPipeRef(domain, pipeCode));\n if (byRef) return byRef;\n }\n return ownProp(outputForm, pipeCode);\n}\n"]}
@@ -2,8 +2,8 @@ import { pruneEmptyOptionals, healStringWrappers } from './chunk-GCXV76WU.js';
2
2
  import { prepareSchemaForRjsf } from './chunk-LBFZXYCY.js';
3
3
  import { isFilled, mustBeFilled, fieldFilled } from './chunk-PM55AHRO.js';
4
4
  import { validateRunInputsSchema } from './chunk-VTPUDBSJ.js';
5
- import { gatingFieldsFromInputs } from './chunk-YEVVR6FE.js';
6
- import { inputMustBeFilled, isOptionalInput, isPluralInput } from './chunk-XYQ7NXQZ.js';
5
+ import { gatingFieldsFromInputs } from './chunk-Y2ND6W4W.js';
6
+ import { inputMustBeFilled, isOptionalInput, isPluralInput } from './chunk-WYH56SOZ.js';
7
7
  import { ownProp } from './chunk-V3OV2XQT.js';
8
8
 
9
9
  // src/core/gate.ts
@@ -95,5 +95,5 @@ function gateRunInputs(contract, data) {
95
95
  }
96
96
 
97
97
  export { apiInputsFromSchemaData, buildRunInputsSchema, gateRunInputs, prepareRunInputs, validateRunInputs };
98
- //# sourceMappingURL=chunk-R7B7ID25.js.map
99
- //# sourceMappingURL=chunk-R7B7ID25.js.map
98
+ //# sourceMappingURL=chunk-IUWLOSAM.js.map
99
+ //# sourceMappingURL=chunk-IUWLOSAM.js.map
@@ -1 +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
+ {"version":3,"sources":["../src/core/gate.ts"],"names":[],"mappings":"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","file":"chunk-IUWLOSAM.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"]}