@reasonlayer/sdk 0.0.1-rc.3

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 (101) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +35 -0
  3. package/bin/rl.mjs +75 -0
  4. package/dist/builder.d.ts +38 -0
  5. package/dist/builder.js +41 -0
  6. package/dist/bundler/index.d.ts +32 -0
  7. package/dist/bundler/index.js +143 -0
  8. package/dist/bundler/node.d.ts +10 -0
  9. package/dist/bundler/node.js +55 -0
  10. package/dist/cli/args.d.ts +9 -0
  11. package/dist/cli/args.js +71 -0
  12. package/dist/cli/config.d.ts +38 -0
  13. package/dist/cli/config.js +94 -0
  14. package/dist/cli/convex.d.ts +9 -0
  15. package/dist/cli/convex.js +25 -0
  16. package/dist/cli/format.d.ts +11 -0
  17. package/dist/cli/format.js +140 -0
  18. package/dist/cli/index.d.ts +1 -0
  19. package/dist/cli/index.js +721 -0
  20. package/dist/cli/keychain.d.ts +21 -0
  21. package/dist/cli/keychain.js +100 -0
  22. package/dist/cli/operations.d.ts +106 -0
  23. package/dist/cli/operations.js +623 -0
  24. package/dist/cli/sdk-package.d.ts +18 -0
  25. package/dist/cli/sdk-package.js +45 -0
  26. package/dist/cli/storage-upload.d.ts +9 -0
  27. package/dist/cli/storage-upload.js +21 -0
  28. package/dist/cli/watcher-compile.d.ts +1 -0
  29. package/dist/cli/watcher-compile.js +20 -0
  30. package/dist/cli/watcher-process.d.ts +1 -0
  31. package/dist/cli/watcher-process.js +312 -0
  32. package/dist/cli/workspace-files.d.ts +12 -0
  33. package/dist/cli/workspace-files.js +91 -0
  34. package/dist/cli/workspace-local.d.ts +27 -0
  35. package/dist/cli/workspace-local.js +310 -0
  36. package/dist/cli/workspace-session.d.ts +13 -0
  37. package/dist/cli/workspace-session.js +12 -0
  38. package/dist/compile.d.ts +27 -0
  39. package/dist/compile.js +834 -0
  40. package/dist/index.d.ts +10 -0
  41. package/dist/index.js +5 -0
  42. package/dist/schedule-validation.d.ts +1 -0
  43. package/dist/schedule-validation.js +16 -0
  44. package/dist/selector-schema.d.ts +26 -0
  45. package/dist/selector-schema.js +780 -0
  46. package/dist/selectors.d.ts +25 -0
  47. package/dist/selectors.js +99 -0
  48. package/dist/testkit.d.ts +32 -0
  49. package/dist/testkit.js +161 -0
  50. package/dist/types.d.ts +184 -0
  51. package/dist/types.js +110 -0
  52. package/node_modules/@reasonlayer/integrations/dist/catalog.d.ts +4937 -0
  53. package/node_modules/@reasonlayer/integrations/dist/catalog.js +20 -0
  54. package/node_modules/@reasonlayer/integrations/dist/generated/slack.d.ts +3014 -0
  55. package/node_modules/@reasonlayer/integrations/dist/generated/slack.js +3101 -0
  56. package/node_modules/@reasonlayer/integrations/dist/index.d.ts +2 -0
  57. package/node_modules/@reasonlayer/integrations/dist/index.js +1 -0
  58. package/node_modules/@reasonlayer/integrations/dist/internal/definitions.d.ts +44 -0
  59. package/node_modules/@reasonlayer/integrations/dist/internal/definitions.js +52 -0
  60. package/node_modules/@reasonlayer/integrations/dist/internal/transport.d.ts +7 -0
  61. package/node_modules/@reasonlayer/integrations/dist/internal/transport.js +48 -0
  62. package/node_modules/@reasonlayer/integrations/dist/testkit.d.ts +1 -0
  63. package/node_modules/@reasonlayer/integrations/dist/testkit.js +1 -0
  64. package/node_modules/@reasonlayer/integrations/package.json +26 -0
  65. package/node_modules/@reasonlayer/protocol/dist/environments.d.ts +7 -0
  66. package/node_modules/@reasonlayer/protocol/dist/environments.js +7 -0
  67. package/node_modules/@reasonlayer/protocol/dist/events.d.ts +313 -0
  68. package/node_modules/@reasonlayer/protocol/dist/events.js +20 -0
  69. package/node_modules/@reasonlayer/protocol/dist/index.d.ts +18 -0
  70. package/node_modules/@reasonlayer/protocol/dist/index.js +9 -0
  71. package/node_modules/@reasonlayer/protocol/dist/ir.d.ts +252 -0
  72. package/node_modules/@reasonlayer/protocol/dist/ir.js +116 -0
  73. package/node_modules/@reasonlayer/protocol/dist/permissions.d.ts +5 -0
  74. package/node_modules/@reasonlayer/protocol/dist/permissions.js +30 -0
  75. package/node_modules/@reasonlayer/protocol/dist/readmodels.d.ts +617 -0
  76. package/node_modules/@reasonlayer/protocol/dist/readmodels.js +36 -0
  77. package/node_modules/@reasonlayer/protocol/dist/runtime.d.ts +67 -0
  78. package/node_modules/@reasonlayer/protocol/dist/runtime.js +66 -0
  79. package/node_modules/@reasonlayer/protocol/dist/scheduling.d.ts +69 -0
  80. package/node_modules/@reasonlayer/protocol/dist/scheduling.js +126 -0
  81. package/node_modules/@reasonlayer/protocol/dist/selector.d.ts +23 -0
  82. package/node_modules/@reasonlayer/protocol/dist/selector.js +77 -0
  83. package/node_modules/@reasonlayer/protocol/dist/tasks.d.ts +69 -0
  84. package/node_modules/@reasonlayer/protocol/dist/tasks.js +6 -0
  85. package/node_modules/@reasonlayer/protocol/dist/validate.d.ts +6 -0
  86. package/node_modules/@reasonlayer/protocol/dist/validate.js +18 -0
  87. package/node_modules/@reasonlayer/protocol/package.json +21 -0
  88. package/node_modules/@reasonlayer/runner/dist/environment-materializer.d.ts +13 -0
  89. package/node_modules/@reasonlayer/runner/dist/environment-materializer.js +92 -0
  90. package/node_modules/@reasonlayer/runner/dist/flue-transcript.d.ts +17 -0
  91. package/node_modules/@reasonlayer/runner/dist/flue-transcript.js +182 -0
  92. package/node_modules/@reasonlayer/runner/dist/index.d.ts +184 -0
  93. package/node_modules/@reasonlayer/runner/dist/index.js +884 -0
  94. package/node_modules/@reasonlayer/runner/dist/mcp-supervisor.d.ts +15 -0
  95. package/node_modules/@reasonlayer/runner/dist/mcp-supervisor.js +142 -0
  96. package/node_modules/@reasonlayer/runner/dist/sandbox-env.d.ts +36 -0
  97. package/node_modules/@reasonlayer/runner/dist/sandbox-env.js +171 -0
  98. package/node_modules/@reasonlayer/runner/dist/task-child.d.ts +1 -0
  99. package/node_modules/@reasonlayer/runner/dist/task-child.js +72 -0
  100. package/node_modules/@reasonlayer/runner/package.json +21 -0
  101. package/package.json +65 -0
@@ -0,0 +1,780 @@
1
+ /**
2
+ * Proves that selector-produced review drafts satisfy their decision schemas.
3
+ *
4
+ * Reviews are the first consumer. The same type-erasure hole exists for code
5
+ * step inputs and the workflow output; this module can grow to check every
6
+ * step input and the workflow output. Move it to @reasonlayer/protocol if the
7
+ * backend ever needs to validate manually uploaded IR.
8
+ */
9
+ import { validateAgainstSchema } from "@reasonlayer/protocol";
10
+ const UNSUPPORTED_KEYWORDS = [
11
+ "$ref",
12
+ "$defs",
13
+ "$dynamicRef",
14
+ "definitions",
15
+ "allOf",
16
+ "oneOf",
17
+ "not",
18
+ "if",
19
+ "then",
20
+ "else",
21
+ "prefixItems",
22
+ "contains",
23
+ "patternProperties",
24
+ "propertyNames",
25
+ "dependentRequired",
26
+ "dependentSchemas",
27
+ "unevaluatedItems",
28
+ "unevaluatedProperties",
29
+ ];
30
+ const METADATA_KEYWORDS = new Set([
31
+ "$schema",
32
+ "$id",
33
+ "$comment",
34
+ "title",
35
+ "description",
36
+ "default",
37
+ "examples",
38
+ "deprecated",
39
+ "readOnly",
40
+ "writeOnly",
41
+ ]);
42
+ function unprovable(location, detail) {
43
+ return `compatibility cannot be proven at ${location}: ${detail}; reshape the selection or simplify the schema`;
44
+ }
45
+ function isRecord(value) {
46
+ return value !== null && typeof value === "object" && !Array.isArray(value);
47
+ }
48
+ function hasOwn(record, key) {
49
+ return Object.prototype.hasOwnProperty.call(record, key);
50
+ }
51
+ function schemaRecord(value) {
52
+ return isRecord(value) ? value : undefined;
53
+ }
54
+ function schemaBranches(schema) {
55
+ if (Array.isArray(schema["anyOf"])) {
56
+ const branches = schema["anyOf"].map(schemaRecord);
57
+ return branches.every((branch) => branch !== undefined)
58
+ ? branches
59
+ : undefined;
60
+ }
61
+ const type = schema["type"];
62
+ if (Array.isArray(type) && type.every((entry) => typeof entry === "string")) {
63
+ return type.map((entry) => ({ ...schema, type: entry }));
64
+ }
65
+ return undefined;
66
+ }
67
+ function unsupportedIssue(schema, location, side) {
68
+ for (const keyword of UNSUPPORTED_KEYWORDS) {
69
+ if (keyword in schema) {
70
+ return unprovable(location, `${side} schema uses unsupported "${keyword}"`);
71
+ }
72
+ }
73
+ if ("anyOf" in schema) {
74
+ if (schemaBranches(schema) === undefined) {
75
+ return unprovable(location, `${side} schema has an invalid "anyOf"`);
76
+ }
77
+ const sibling = Object.keys(schema).find((key) => key !== "anyOf" && !METADATA_KEYWORDS.has(key));
78
+ if (sibling) {
79
+ return unprovable(location, `${side} schema combines "anyOf" with unsupported sibling constraint "${sibling}"`);
80
+ }
81
+ }
82
+ const type = schema["type"];
83
+ if (Array.isArray(type) &&
84
+ (type.length === 0 || !type.every((entry) => typeof entry === "string"))) {
85
+ return unprovable(location, `${side} schema has an invalid "type" array`);
86
+ }
87
+ const values = schema["enum"];
88
+ if ("enum" in schema && (!Array.isArray(values) || values.length === 0)) {
89
+ return unprovable(location, `${side} schema has an invalid "enum"`);
90
+ }
91
+ const nested = [];
92
+ const properties = schema["properties"];
93
+ if (isRecord(properties)) {
94
+ for (const [key, value] of Object.entries(properties)) {
95
+ const child = schemaRecord(value);
96
+ if (child)
97
+ nested.push([`${location}.${key}`, child]);
98
+ }
99
+ }
100
+ const additional = schemaRecord(schema["additionalProperties"]);
101
+ if (additional)
102
+ nested.push([`${location}.*`, additional]);
103
+ const items = schema["items"];
104
+ if (Array.isArray(items)) {
105
+ for (const [index, value] of items.entries()) {
106
+ const child = schemaRecord(value);
107
+ if (child)
108
+ nested.push([`${location}[${index}]`, child]);
109
+ }
110
+ }
111
+ else {
112
+ const child = schemaRecord(items);
113
+ if (child)
114
+ nested.push([`${location}[]`, child]);
115
+ }
116
+ if (Array.isArray(schema["anyOf"])) {
117
+ for (const [index, value] of schema["anyOf"].entries()) {
118
+ const child = schemaRecord(value);
119
+ if (child)
120
+ nested.push([`${location} (branch ${index + 1})`, child]);
121
+ }
122
+ }
123
+ for (const [childLocation, child] of nested) {
124
+ const issue = unsupportedIssue(child, childLocation, side);
125
+ if (issue)
126
+ return issue;
127
+ }
128
+ return undefined;
129
+ }
130
+ function schemaType(schema) {
131
+ const type = schema["type"];
132
+ if (typeof type === "string")
133
+ return type;
134
+ if ("const" in schema)
135
+ return valueType(schema["const"]);
136
+ const values = schema["enum"];
137
+ if (Array.isArray(values) && values.length > 0) {
138
+ const types = new Set(values.map(valueType));
139
+ if (types.size === 1)
140
+ return [...types][0];
141
+ }
142
+ return undefined;
143
+ }
144
+ function valueType(value) {
145
+ if (value === null)
146
+ return "null";
147
+ if (Array.isArray(value))
148
+ return "array";
149
+ switch (typeof value) {
150
+ case "string":
151
+ case "boolean":
152
+ return typeof value;
153
+ case "number":
154
+ return Number.isInteger(value) ? "integer" : "number";
155
+ case "object":
156
+ return "object";
157
+ default:
158
+ return undefined;
159
+ }
160
+ }
161
+ function typeLabel(schema) {
162
+ const type = schemaType(schema);
163
+ return type ? `an ${type}` : "the declared schema";
164
+ }
165
+ function selectorLabel(selector) {
166
+ switch (selector.kind) {
167
+ case "input":
168
+ return pathLabel("input", selector.path);
169
+ case "step":
170
+ return pathLabel(`steps.${selector.stepId}`, selector.path);
171
+ case "variable":
172
+ return pathLabel(`variables.${selector.name}`, selector.path);
173
+ case "mapItem":
174
+ return pathLabel("mapItem", selector.path);
175
+ default:
176
+ return "input";
177
+ }
178
+ }
179
+ function pathLabel(root, path) {
180
+ return path.reduce((label, segment) => (/^(0|[1-9]\d*)$/.test(segment) ? `${label}[${segment}]` : `${label}.${segment}`), root);
181
+ }
182
+ function propertiesOf(schema) {
183
+ const raw = schema["properties"];
184
+ if (raw === undefined)
185
+ return Object.create(null);
186
+ if (!isRecord(raw))
187
+ return undefined;
188
+ const entries = Object.entries(raw).map(([key, value]) => [key, schemaRecord(value)]);
189
+ if (entries.some(([, value]) => value === undefined))
190
+ return undefined;
191
+ return Object.assign(Object.create(null), Object.fromEntries(entries));
192
+ }
193
+ function requiredOf(schema) {
194
+ const raw = schema["required"];
195
+ if (raw === undefined)
196
+ return [];
197
+ return Array.isArray(raw) && raw.every((entry) => typeof entry === "string") ? raw : undefined;
198
+ }
199
+ function semanticKeywords(schema) {
200
+ return Object.keys(schema).filter((key) => !METADATA_KEYWORDS.has(key));
201
+ }
202
+ function unsupportedExtraKeyword(schema, allowed, location, side) {
203
+ const allowedSet = new Set(allowed);
204
+ const keyword = semanticKeywords(schema).find((key) => !allowedSet.has(key));
205
+ return keyword
206
+ ? unprovable(location, `${side} schema uses unsupported keyword "${keyword}"`)
207
+ : undefined;
208
+ }
209
+ function compatibilityKeywordIssue(schema, location, side) {
210
+ const unsupported = unsupportedIssue(schema, location, side);
211
+ if (unsupported)
212
+ return unsupported;
213
+ const branches = schemaBranches(schema);
214
+ if (branches) {
215
+ for (const [index, branch] of branches.entries()) {
216
+ const issue = compatibilityKeywordIssue(branch, `${location} (branch ${index + 1})`, side);
217
+ if (issue)
218
+ return issue;
219
+ }
220
+ return undefined;
221
+ }
222
+ const type = schemaType(schema);
223
+ const allowed = type === "object"
224
+ ? ["type", "properties", "required", "additionalProperties", "const", "enum"]
225
+ : type === "array"
226
+ ? ["type", "items", "const", "enum"]
227
+ : ["type", "const", "enum"];
228
+ const extra = unsupportedExtraKeyword(schema, allowed, location, side);
229
+ if (extra)
230
+ return extra;
231
+ if (type === "object") {
232
+ const properties = propertiesOf(schema);
233
+ const required = requiredOf(schema);
234
+ if (!properties || !required) {
235
+ return unprovable(location, `${side} object schema has invalid properties or required fields`);
236
+ }
237
+ const additional = schema["additionalProperties"];
238
+ if (additional !== undefined &&
239
+ additional !== true &&
240
+ additional !== false &&
241
+ !schemaRecord(additional)) {
242
+ return unprovable(location, `${side} object schema has invalid additionalProperties`);
243
+ }
244
+ for (const [key, property] of Object.entries(properties)) {
245
+ const issue = compatibilityKeywordIssue(property, `${location}.${key}`, side);
246
+ if (issue)
247
+ return issue;
248
+ }
249
+ const additionalSchema = schemaRecord(additional);
250
+ if (additionalSchema) {
251
+ const issue = compatibilityKeywordIssue(additionalSchema, `${location}.*`, side);
252
+ if (issue)
253
+ return issue;
254
+ }
255
+ }
256
+ if (type === "array") {
257
+ const items = schema["items"];
258
+ if (Array.isArray(items)) {
259
+ return unprovable(location, `${side} schema uses tuple-form "items"`);
260
+ }
261
+ const itemSchema = schemaRecord(items);
262
+ if (itemSchema) {
263
+ const issue = compatibilityKeywordIssue(itemSchema, `${location}[]`, side);
264
+ if (issue)
265
+ return issue;
266
+ }
267
+ }
268
+ return undefined;
269
+ }
270
+ function walkSchema(schema, path, label) {
271
+ if (path.length === 0)
272
+ return { schema, issues: [] };
273
+ const unsupported = unsupportedIssue(schema, label, "source");
274
+ if (unsupported)
275
+ return { issues: [unsupported] };
276
+ const branches = schemaBranches(schema);
277
+ if (branches) {
278
+ const walked = branches.map((branch) => walkSchema(branch, path, label));
279
+ const issue = walked.find((result) => result.issues.length > 0);
280
+ if (issue)
281
+ return { issues: issue.issues };
282
+ return {
283
+ schema: { anyOf: walked.map((result) => result.schema) },
284
+ issues: [],
285
+ };
286
+ }
287
+ const [segment, ...rest] = path;
288
+ const type = schemaType(schema);
289
+ if (type === "object") {
290
+ const properties = propertiesOf(schema);
291
+ const required = requiredOf(schema);
292
+ if (!properties || !required) {
293
+ return { issues: [unprovable(label, "source object schema has invalid properties or required fields")] };
294
+ }
295
+ let property = hasOwn(properties, segment) ? properties[segment] : undefined;
296
+ if (!property && !required.includes(segment)) {
297
+ return {
298
+ issues: [`${pathLabel(label, path.slice(0, 1))}: schema has no property "${segment}"`],
299
+ };
300
+ }
301
+ if (property && !required.includes(segment)) {
302
+ return {
303
+ issues: [unprovable(pathLabel(label, [segment]), `source property "${segment}" is optional`)],
304
+ };
305
+ }
306
+ if (!property) {
307
+ property = schemaRecord(schema["additionalProperties"]);
308
+ if (!property) {
309
+ return {
310
+ issues: [
311
+ unprovable(pathLabel(label, [segment]), `source property "${segment}" has no declared value schema`),
312
+ ],
313
+ };
314
+ }
315
+ }
316
+ return walkSchema(property, rest, pathLabel(label, [segment]));
317
+ }
318
+ if (type === "array") {
319
+ if (!/^(0|[1-9]\d*)$/.test(segment)) {
320
+ return { issues: [`${label}: array schema cannot select property "${segment}"`] };
321
+ }
322
+ const index = Number(segment);
323
+ const minItems = schema["minItems"];
324
+ if (typeof minItems !== "number" || !Number.isInteger(minItems) || minItems <= index) {
325
+ return {
326
+ issues: [
327
+ unprovable(pathLabel(label, [segment]), `source array does not guarantee that index ${segment} exists`),
328
+ ],
329
+ };
330
+ }
331
+ const items = schema["items"];
332
+ if (Array.isArray(items)) {
333
+ return { issues: [unprovable(label, "source schema uses tuple-form \"items\"")] };
334
+ }
335
+ const itemSchema = schemaRecord(items);
336
+ if (!itemSchema) {
337
+ return { issues: [unprovable(label, "source array schema does not declare its items")] };
338
+ }
339
+ return walkSchema(itemSchema, rest, pathLabel(label, [segment]));
340
+ }
341
+ return {
342
+ issues: [`${label}: cannot select property "${segment}" from ${type ? `a ${type}` : "this schema"}`],
343
+ };
344
+ }
345
+ /** Resolve a wire-reference selector to its declared source schema. */
346
+ export function resolveSelectorSchema(selector, context) {
347
+ let source;
348
+ switch (selector.kind) {
349
+ case "input":
350
+ source = context.inputSchema;
351
+ break;
352
+ case "step":
353
+ source = hasOwn(context.stepSchemas, selector.stepId)
354
+ ? context.stepSchemas[selector.stepId]
355
+ : undefined;
356
+ if (!source) {
357
+ return { issues: [unprovable(selectorLabel(selector), "the referenced step has no output schema")] };
358
+ }
359
+ break;
360
+ case "variable":
361
+ source =
362
+ context.variableSchemas && hasOwn(context.variableSchemas, selector.name)
363
+ ? context.variableSchemas[selector.name]
364
+ : undefined;
365
+ if (!source) {
366
+ return { issues: [unprovable(selectorLabel(selector), "the variable has no declared schema")] };
367
+ }
368
+ break;
369
+ case "mapItem":
370
+ source = context.mapItemSchema;
371
+ if (!source) {
372
+ return { issues: [unprovable(selectorLabel(selector), "the map item schema is unavailable")] };
373
+ }
374
+ break;
375
+ case "literal":
376
+ case "object":
377
+ case "array":
378
+ return {
379
+ issues: [unprovable("input", "a composed selector has no single declared source schema")],
380
+ };
381
+ }
382
+ const root = selector.kind === "step"
383
+ ? `steps.${selector.stepId}`
384
+ : selector.kind === "variable"
385
+ ? `variables.${selector.name}`
386
+ : selector.kind;
387
+ return walkSchema(source, selector.path, root);
388
+ }
389
+ function unionOf(schemas) {
390
+ return schemas.length === 1 ? schemas[0] : { anyOf: schemas };
391
+ }
392
+ /** Synthesize the schema a composed selector produces at run time. */
393
+ function synthesizeSelectorSchema(selector, context) {
394
+ switch (selector.kind) {
395
+ case "input":
396
+ case "step":
397
+ case "variable":
398
+ case "mapItem":
399
+ return resolveSelectorSchema(selector, context);
400
+ case "literal":
401
+ // lower() only emits scalar or null literals; containers become object/array selectors.
402
+ return { schema: { const: selector.value }, issues: [] };
403
+ case "object": {
404
+ const properties = {};
405
+ for (const [key, field] of Object.entries(selector.fields)) {
406
+ const resolved = synthesizeSelectorSchema(field, context);
407
+ if (!resolved.schema)
408
+ return resolved;
409
+ properties[key] = resolved.schema;
410
+ }
411
+ return {
412
+ schema: {
413
+ type: "object",
414
+ properties,
415
+ required: Object.keys(selector.fields),
416
+ additionalProperties: false,
417
+ },
418
+ issues: [],
419
+ };
420
+ }
421
+ case "array": {
422
+ const items = selector.items.map((item) => synthesizeSelectorSchema(item, context));
423
+ const failed = items.find((item) => !item.schema);
424
+ if (failed)
425
+ return failed;
426
+ return {
427
+ schema: { type: "array", items: unionOf(items.map((item) => item.schema)) },
428
+ issues: [],
429
+ };
430
+ }
431
+ }
432
+ }
433
+ /** Resolve the item schema of an array-valued selector, composed or referenced. */
434
+ export function resolveSelectorArrayItemSchema(selector, context) {
435
+ if (selector.kind === "array") {
436
+ const items = selector.items.map((item) => synthesizeSelectorSchema(item, context));
437
+ const failed = items.find((item) => !item.schema);
438
+ if (failed)
439
+ return failed;
440
+ return { schema: unionOf(items.map((item) => item.schema)), issues: [] };
441
+ }
442
+ if (selector.kind === "object") {
443
+ return { issues: ["the composed value is an object, but map over expects an array"] };
444
+ }
445
+ if (selector.kind === "literal") {
446
+ return {
447
+ issues: [
448
+ `the literal ${JSON.stringify(selector.value)} is not an array, but map over expects an array`,
449
+ ],
450
+ };
451
+ }
452
+ const resolved = resolveSelectorSchema(selector, context);
453
+ if (!resolved.schema)
454
+ return resolved;
455
+ const location = selectorLabel(selector);
456
+ const extract = (schema) => {
457
+ const unsupported = unsupportedIssue(schema, location, "source");
458
+ if (unsupported)
459
+ return { issues: [unsupported] };
460
+ const branches = schemaBranches(schema);
461
+ if (branches) {
462
+ const items = branches.map(extract);
463
+ const failed = items.find((item) => !item.schema);
464
+ if (failed)
465
+ return failed;
466
+ return { schema: { anyOf: items.map((item) => item.schema) }, issues: [] };
467
+ }
468
+ if (schemaType(schema) !== "array") {
469
+ return { issues: [`${location} is ${typeLabel(schema)}, but map over expects an array`] };
470
+ }
471
+ if (Array.isArray(schema["items"])) {
472
+ return { issues: [unprovable(location, "source schema uses tuple-form \"items\"")] };
473
+ }
474
+ const itemSchema = schemaRecord(schema["items"]);
475
+ return itemSchema
476
+ ? { schema: itemSchema, issues: [] }
477
+ : { issues: [unprovable(location, "source array schema does not declare its items")] };
478
+ };
479
+ return extract(resolved.schema);
480
+ }
481
+ function deepEqual(left, right) {
482
+ if (Object.is(left, right))
483
+ return true;
484
+ if (Array.isArray(left) && Array.isArray(right)) {
485
+ return left.length === right.length && left.every((value, index) => deepEqual(value, right[index]));
486
+ }
487
+ if (isRecord(left) && isRecord(right)) {
488
+ const leftKeys = Object.keys(left).sort();
489
+ const rightKeys = Object.keys(right).sort();
490
+ return (leftKeys.length === rightKeys.length &&
491
+ leftKeys.every((key, index) => key === rightKeys[index] && deepEqual(left[key], right[key])));
492
+ }
493
+ return false;
494
+ }
495
+ function valuesAllowedBy(schema) {
496
+ if ("const" in schema)
497
+ return [schema["const"]];
498
+ return Array.isArray(schema["enum"]) ? schema["enum"] : undefined;
499
+ }
500
+ function schemaAccepts(schema, value) {
501
+ try {
502
+ validateAgainstSchema(value, schema, "value");
503
+ return true;
504
+ }
505
+ catch {
506
+ return false;
507
+ }
508
+ }
509
+ function finiteSourceCompatible(source, target, location) {
510
+ let candidates = valuesAllowedBy(source);
511
+ if (!candidates)
512
+ return undefined;
513
+ const enumValues = source["enum"];
514
+ if ("const" in source && Array.isArray(enumValues)) {
515
+ candidates = candidates.filter((candidate) => enumValues.some((enumValue) => deepEqual(candidate, enumValue)));
516
+ }
517
+ const declaredTypes = typeof source["type"] === "string"
518
+ ? [source["type"]]
519
+ : Array.isArray(source["type"])
520
+ ? source["type"]
521
+ : undefined;
522
+ if (declaredTypes) {
523
+ candidates = candidates.filter((candidate) => {
524
+ const candidateType = valueType(candidate);
525
+ return declaredTypes.some((declaredType) => declaredType === candidateType ||
526
+ (declaredType === "number" && candidateType === "integer"));
527
+ });
528
+ }
529
+ // Container const/enum equality is validator-dependent. Fall back to the
530
+ // structural checker rather than treating the listed container as exhaustive;
531
+ // remove this fallback if the shared validator guarantees JSON container equality.
532
+ if (candidates.some((candidate) => candidate !== null && typeof candidate === "object")) {
533
+ return undefined;
534
+ }
535
+ const outside = candidates.find((value) => !schemaAccepts(target, value));
536
+ return outside === undefined
537
+ ? []
538
+ : [`${location} allows ${JSON.stringify(outside)}, which the output schema does not allow`];
539
+ }
540
+ function primitiveCompatible(source, target, location) {
541
+ const sourceValues = valuesAllowedBy(source);
542
+ const targetValues = valuesAllowedBy(target);
543
+ if (targetValues) {
544
+ if (!sourceValues)
545
+ return [`${location} can contain values outside the output schema's allowed values`];
546
+ const outside = sourceValues.find((sourceValue) => !targetValues.some((targetValue) => deepEqual(sourceValue, targetValue)));
547
+ return outside === undefined
548
+ ? []
549
+ : [`${location} allows ${JSON.stringify(outside)}, which the output schema does not allow`];
550
+ }
551
+ const sourceType = schemaType(source);
552
+ const targetType = schemaType(target);
553
+ if (!sourceType)
554
+ return [unprovable(location, "source schema does not declare a type")];
555
+ if (!targetType)
556
+ return [unprovable(location, "output schema does not declare a type")];
557
+ if (sourceType === targetType || (sourceType === "integer" && targetType === "number"))
558
+ return [];
559
+ return [`${location} is ${typeLabel(source)}, but output expects ${typeLabel(target)}`];
560
+ }
561
+ function satisfies(source, target, location) {
562
+ // `{}` is the accept-all target. It is also schemaToJson's error sentinel;
563
+ // conversion already records a compile error, so this vacuous pass cannot hide success.
564
+ if (semanticKeywords(target).length === 0)
565
+ return [];
566
+ const targetKeywordIssue = compatibilityKeywordIssue(target, location, "output");
567
+ if (targetKeywordIssue)
568
+ return [targetKeywordIssue];
569
+ const sourceKeywordIssue = compatibilityKeywordIssue(source, location, "source");
570
+ if (sourceKeywordIssue)
571
+ return [sourceKeywordIssue];
572
+ const finiteCompatibility = finiteSourceCompatible(source, target, location);
573
+ if (finiteCompatibility)
574
+ return finiteCompatibility;
575
+ const sourceBranches = schemaBranches(source);
576
+ if (sourceBranches) {
577
+ return sourceBranches.flatMap((branch, index) => satisfies(branch, target, `${location} (source branch ${index + 1})`));
578
+ }
579
+ const targetBranches = schemaBranches(target);
580
+ if (targetBranches) {
581
+ if (targetBranches.length === 0) {
582
+ return [`${location} cannot satisfy an output schema that allows no values`];
583
+ }
584
+ const attempts = targetBranches.map((branch) => satisfies(source, branch, location));
585
+ if (attempts.some((issues) => issues.length === 0))
586
+ return [];
587
+ return attempts.sort((left, right) => left.length - right.length)[0] ?? [];
588
+ }
589
+ const sourceType = schemaType(source);
590
+ const targetType = schemaType(target);
591
+ if (targetType === "object") {
592
+ if (sourceType !== "object") {
593
+ return [`${location} is ${typeLabel(source)}, but output expects an object`];
594
+ }
595
+ const sourceKeywordIssue = unsupportedExtraKeyword(source, ["type", "properties", "required", "additionalProperties"], location, "source");
596
+ if (sourceKeywordIssue)
597
+ return [sourceKeywordIssue];
598
+ const targetKeywordIssue = unsupportedExtraKeyword(target, ["type", "properties", "required", "additionalProperties"], location, "output");
599
+ if (targetKeywordIssue)
600
+ return [targetKeywordIssue];
601
+ const sourceProperties = propertiesOf(source);
602
+ const targetProperties = propertiesOf(target);
603
+ const sourceRequired = requiredOf(source);
604
+ const targetRequired = requiredOf(target);
605
+ if (!sourceProperties || !targetProperties || !sourceRequired || !targetRequired) {
606
+ return [unprovable(location, "object schema properties or required fields are invalid")];
607
+ }
608
+ const sourceAdditional = source["additionalProperties"];
609
+ const targetAdditional = target["additionalProperties"];
610
+ if (sourceAdditional !== undefined &&
611
+ sourceAdditional !== true &&
612
+ sourceAdditional !== false &&
613
+ !schemaRecord(sourceAdditional)) {
614
+ return [unprovable(location, "source object schema has invalid additionalProperties")];
615
+ }
616
+ if (targetAdditional !== undefined &&
617
+ targetAdditional !== true &&
618
+ targetAdditional !== false &&
619
+ !schemaRecord(targetAdditional)) {
620
+ return [unprovable(location, "output object schema has invalid additionalProperties")];
621
+ }
622
+ const sourceAdditionalSchema = schemaRecord(sourceAdditional);
623
+ const targetAdditionalSchema = schemaRecord(targetAdditional);
624
+ const targetAdditionalAcceptsAll = targetAdditionalSchema !== undefined && semanticKeywords(targetAdditionalSchema).length === 0;
625
+ const issues = [];
626
+ for (const key of targetRequired) {
627
+ if (!sourceRequired.includes(key))
628
+ issues.push(`missing required field "${key}"`);
629
+ }
630
+ for (const [key, targetProperty] of Object.entries(targetProperties)) {
631
+ if (semanticKeywords(targetProperty).length === 0)
632
+ continue;
633
+ const sourceProperty = hasOwn(sourceProperties, key) ? sourceProperties[key] : undefined;
634
+ if (sourceProperty) {
635
+ issues.push(...satisfies(sourceProperty, targetProperty, `${location}.${key}`));
636
+ }
637
+ else if (sourceAdditionalSchema) {
638
+ issues.push(...satisfies(sourceAdditionalSchema, targetProperty, `${location}.${key}`));
639
+ }
640
+ else if (sourceAdditional !== false || sourceRequired.includes(key)) {
641
+ issues.push(unprovable(`${location}.${key}`, `source property "${key}" has no declared value schema`));
642
+ }
643
+ }
644
+ for (const [key, sourceProperty] of Object.entries(sourceProperties)) {
645
+ if (hasOwn(targetProperties, key))
646
+ continue;
647
+ if (targetAdditional === false) {
648
+ issues.push(`field "${key}" is not allowed by the strict output schema`);
649
+ }
650
+ else if (targetAdditionalSchema && !targetAdditionalAcceptsAll) {
651
+ issues.push(...satisfies(sourceProperty, targetAdditionalSchema, `${location}.${key}`));
652
+ }
653
+ }
654
+ if (targetAdditional === false) {
655
+ if (sourceAdditional !== false) {
656
+ issues.push(`${location} may contain fields not allowed by the strict output schema`);
657
+ }
658
+ }
659
+ else if (targetAdditionalSchema && !targetAdditionalAcceptsAll) {
660
+ if (sourceAdditionalSchema) {
661
+ issues.push(...satisfies(sourceAdditionalSchema, targetAdditionalSchema, `${location}.*`));
662
+ }
663
+ else if (sourceAdditional !== false) {
664
+ issues.push(unprovable(`${location}.*`, "source object allows undeclared fields without a value schema"));
665
+ }
666
+ }
667
+ return issues;
668
+ }
669
+ if (targetType === "array") {
670
+ if (sourceType !== "array") {
671
+ return [`${location} is ${typeLabel(source)}, but output expects an array`];
672
+ }
673
+ const sourceKeywordIssue = unsupportedExtraKeyword(source, ["type", "items"], location, "source");
674
+ if (sourceKeywordIssue)
675
+ return [sourceKeywordIssue];
676
+ const targetKeywordIssue = unsupportedExtraKeyword(target, ["type", "items"], location, "output");
677
+ if (targetKeywordIssue)
678
+ return [targetKeywordIssue];
679
+ const sourceItems = schemaRecord(source["items"]);
680
+ const targetItems = schemaRecord(target["items"]);
681
+ if (!sourceItems || !targetItems) {
682
+ return [unprovable(location, "array schema does not declare a single item schema")];
683
+ }
684
+ return satisfies(sourceItems, targetItems, `${location}[]`);
685
+ }
686
+ return primitiveCompatible(source, target, location);
687
+ }
688
+ function checkSelector(selector, target, context, location) {
689
+ const targetUnsupported = unsupportedIssue(target, location, "output");
690
+ if (targetUnsupported)
691
+ return [targetUnsupported];
692
+ if (selector.kind === "literal") {
693
+ try {
694
+ validateAgainstSchema(selector.value, target, location);
695
+ return [];
696
+ }
697
+ catch (error) {
698
+ return [error.message.replace(`${location} does not match schema — `, `${location}: `)];
699
+ }
700
+ }
701
+ if (selector.kind === "input" ||
702
+ selector.kind === "step" ||
703
+ selector.kind === "variable" ||
704
+ selector.kind === "mapItem") {
705
+ const resolved = resolveSelectorSchema(selector, context);
706
+ if (!resolved.schema)
707
+ return resolved.issues;
708
+ return satisfies(resolved.schema, target, location);
709
+ }
710
+ if (semanticKeywords(target).length === 0)
711
+ return [];
712
+ const targetBranches = schemaBranches(target);
713
+ if (targetBranches) {
714
+ if (targetBranches.length === 0) {
715
+ return [`${location} cannot satisfy an output schema that allows no values`];
716
+ }
717
+ const attempts = targetBranches.map((branch) => checkSelector(selector, branch, context, location));
718
+ if (attempts.some((issues) => issues.length === 0))
719
+ return [];
720
+ return attempts.sort((left, right) => left.length - right.length)[0] ?? [];
721
+ }
722
+ if (selector.kind === "object") {
723
+ if (schemaType(target) !== "object") {
724
+ return [`${location} is an object, but output expects ${typeLabel(target)}`];
725
+ }
726
+ const targetKeywordIssue = unsupportedExtraKeyword(target, ["type", "properties", "required", "additionalProperties"], location, "output");
727
+ if (targetKeywordIssue)
728
+ return [targetKeywordIssue];
729
+ const properties = propertiesOf(target);
730
+ const required = requiredOf(target);
731
+ if (!properties || !required) {
732
+ return [unprovable(location, "output object schema properties or required fields are invalid")];
733
+ }
734
+ const additional = target["additionalProperties"];
735
+ if (additional !== undefined &&
736
+ additional !== true &&
737
+ additional !== false &&
738
+ !schemaRecord(additional)) {
739
+ return [unprovable(location, "output object schema has invalid additionalProperties")];
740
+ }
741
+ const issues = [];
742
+ for (const key of required) {
743
+ if (!hasOwn(selector.fields, key))
744
+ issues.push(`missing required field "${key}"`);
745
+ }
746
+ for (const [key, field] of Object.entries(selector.fields)) {
747
+ const property = hasOwn(properties, key) ? properties[key] : undefined;
748
+ if (property) {
749
+ issues.push(...checkSelector(field, property, context, `${location}.${key}`));
750
+ }
751
+ else if (target["additionalProperties"] === false) {
752
+ issues.push(`field "${key}" is not allowed by the strict output schema`);
753
+ }
754
+ else {
755
+ const additional = schemaRecord(target["additionalProperties"]);
756
+ if (additional)
757
+ issues.push(...checkSelector(field, additional, context, `${location}.${key}`));
758
+ }
759
+ }
760
+ return issues;
761
+ }
762
+ if (selector.kind === "array") {
763
+ if (schemaType(target) !== "array") {
764
+ return [`${location} is an array, but output expects ${typeLabel(target)}`];
765
+ }
766
+ const targetKeywordIssue = unsupportedExtraKeyword(target, ["type", "items"], location, "output");
767
+ if (targetKeywordIssue)
768
+ return [targetKeywordIssue];
769
+ const items = schemaRecord(target["items"]);
770
+ if (!items)
771
+ return [unprovable(location, "output array schema does not declare a single item schema")];
772
+ return selector.items.flatMap((item, index) => checkSelector(item, items, context, `${location}[${index}]`));
773
+ }
774
+ const exhaustive = selector;
775
+ return exhaustive;
776
+ }
777
+ /** Returns mismatch descriptions; an empty array means compatibility was proven. */
778
+ export function checkSelectorSatisfiesSchema(selector, target, context) {
779
+ return checkSelector(selector, target, context, "input");
780
+ }