@xemahq/dsl 0.8.1 → 0.8.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 (80) hide show
  1. package/dist/payload-codec/index.d.ts.map +1 -1
  2. package/dist/payload-codec/index.js.map +1 -1
  3. package/dist/payload-codec/lib/codec.d.ts.map +1 -1
  4. package/dist/payload-codec/lib/codec.js +2 -1
  5. package/dist/payload-codec/lib/codec.js.map +1 -1
  6. package/dist/payload-codec/lib/payload.d.ts.map +1 -1
  7. package/dist/schema/action.schema.json +205 -39
  8. package/dist/schema/workflow.schema.json +335 -88
  9. package/dist/workflow/index.d.ts.map +1 -1
  10. package/dist/workflow/index.js.map +1 -1
  11. package/dist/workflow/lib/compiler/compile.js +1 -1
  12. package/dist/workflow/lib/compiler/compile.js.map +1 -1
  13. package/package.json +2 -18
  14. package/schema/action.schema.json +205 -39
  15. package/schema/workflow.schema.json +335 -88
  16. package/dist/payload-codec/temporal/index.d.ts +0 -5
  17. package/dist/payload-codec/temporal/index.d.ts.map +0 -1
  18. package/dist/payload-codec/temporal/index.js +0 -7
  19. package/dist/payload-codec/temporal/index.js.map +0 -1
  20. package/src/deliverable-spec/index.ts +0 -19
  21. package/src/deliverable-spec/lib/schema.ts +0 -270
  22. package/src/deliverable-spec/lib/types.ts +0 -26
  23. package/src/payload-codec/index.ts +0 -44
  24. package/src/payload-codec/lib/blob-store.ts +0 -176
  25. package/src/payload-codec/lib/codec-context.ts +0 -38
  26. package/src/payload-codec/lib/codec.ts +0 -605
  27. package/src/payload-codec/lib/enums.ts +0 -58
  28. package/src/payload-codec/lib/errors.ts +0 -54
  29. package/src/payload-codec/lib/http-blob-store.ts +0 -267
  30. package/src/payload-codec/lib/lru-cache.ts +0 -81
  31. package/src/payload-codec/lib/payload.ts +0 -26
  32. package/src/payload-codec/temporal/index.ts +0 -36
  33. package/src/workflow/index.ts +0 -108
  34. package/src/workflow/lib/action-input-validator.ts +0 -160
  35. package/src/workflow/lib/compiler/action-shape.ts +0 -71
  36. package/src/workflow/lib/compiler/canonical-json.ts +0 -66
  37. package/src/workflow/lib/compiler/compile.ts +0 -1742
  38. package/src/workflow/lib/compiler/concurrency.ts +0 -223
  39. package/src/workflow/lib/compiler/dag.ts +0 -108
  40. package/src/workflow/lib/compiler/gate-defaults.ts +0 -153
  41. package/src/workflow/lib/compiler/index.ts +0 -11
  42. package/src/workflow/lib/compiler/inputs.ts +0 -254
  43. package/src/workflow/lib/compiler/installation-resource-validator.ts +0 -114
  44. package/src/workflow/lib/compiler/manifest-source.ts +0 -71
  45. package/src/workflow/lib/compiler/matrix.ts +0 -135
  46. package/src/workflow/lib/compiler/mount-plan.ts +0 -190
  47. package/src/workflow/lib/compiler/payload-reach-in.ts +0 -497
  48. package/src/workflow/lib/compiler/permissions.ts +0 -64
  49. package/src/workflow/lib/compiler/retry-timeout.ts +0 -105
  50. package/src/workflow/lib/compiler/review-step.ts +0 -548
  51. package/src/workflow/lib/compiler/types.ts +0 -172
  52. package/src/workflow/lib/compiler/variable-requirements.ts +0 -208
  53. package/src/workflow/lib/deliverable-spec-introspection-error.ts +0 -63
  54. package/src/workflow/lib/deliverable-spec-keys.ts +0 -147
  55. package/src/workflow/lib/deliverable-spec-source-scan.ts +0 -280
  56. package/src/workflow/lib/dispatch-inputs/index.ts +0 -160
  57. package/src/workflow/lib/dispatch-inputs/to-json-schema.ts +0 -60
  58. package/src/workflow/lib/duration.ts +0 -43
  59. package/src/workflow/lib/errors.ts +0 -37
  60. package/src/workflow/lib/expression/ast.ts +0 -108
  61. package/src/workflow/lib/expression/context.ts +0 -148
  62. package/src/workflow/lib/expression/evaluator.ts +0 -492
  63. package/src/workflow/lib/expression/index.ts +0 -28
  64. package/src/workflow/lib/expression/interpolation.ts +0 -84
  65. package/src/workflow/lib/expression/parser.ts +0 -264
  66. package/src/workflow/lib/expression/template.ts +0 -117
  67. package/src/workflow/lib/expression/tokenizer.ts +0 -200
  68. package/src/workflow/lib/expression/tokens.ts +0 -30
  69. package/src/workflow/lib/expression/walk-artifact-refs.ts +0 -232
  70. package/src/workflow/lib/installation-resource-kind.ts +0 -107
  71. package/src/workflow/lib/schemas-loader.ts +0 -64
  72. package/src/workflow/lib/serializer.ts +0 -30
  73. package/src/workflow/lib/types.ts +0 -417
  74. package/src/workflow/lib/validate.ts +0 -199
  75. package/src/workspace-manifest/index.ts +0 -27
  76. package/src/workspace-manifest/lib/compile.ts +0 -619
  77. package/src/workspace-manifest/lib/interpolate.ts +0 -166
  78. package/src/workspace-manifest/lib/resolve-extends.ts +0 -260
  79. package/src/workspace-manifest/lib/schema.ts +0 -692
  80. package/src/workspace-manifest/lib/types.ts +0 -446
@@ -1,280 +0,0 @@
1
- /**
2
- * Character-level scanner over TypeScript Zod source, used to locate the
3
- * `z.object({ … })` literals a deliverable spec declares at the TOP level
4
- * and to read their field names.
5
- *
6
- * Deliberately NOT a TypeScript parse or a Zod evaluation: introspection
7
- * runs inside the DSL compiler, the CI gate, and a request-scoped API
8
- * handler, none of which may transpile or execute spec-authored code.
9
- * A scanner is enough because the only question asked is "which
10
- * identifiers sit at depth 0 of this object literal".
11
- *
12
- * What it tracks, and why the naive regex it replaced could not:
13
- * - comments, strings, template literals and regex literals, so a
14
- * `z.object(`, a brace, or a comma appearing INSIDE one of those is
15
- * never read as code;
16
- * - brace + paren depth, so a NESTED `z.object(` (a field's own shape)
17
- * is never confused with a top-level schema declaration;
18
- * - the `const <Name> =` binding preceding each top-level literal, so a
19
- * spec can name its contract instead of relying on source order.
20
- *
21
- * All three walkers below share ONE trivia primitive (`skipTriviaAt`) so
22
- * they cannot drift into disagreeing about what counts as code.
23
- */
24
-
25
- /** A `z.object({ … })` literal declared at the top level of the source. */
26
- export interface TopLevelZodObjectDeclaration {
27
- /** Binding name (`export const Foo = z.object({…})` → `Foo`), or null when anonymous. */
28
- readonly exportName: string | null;
29
- /** Field identifiers at depth 0 of the literal, in source order, de-duplicated. */
30
- readonly keys: readonly string[];
31
- }
32
-
33
- /**
34
- * If a comment / string / template / regex literal STARTS at `index`,
35
- * return the index just past its end; otherwise return `index` unchanged.
36
- * An unterminated token consumes the rest of the source, which makes the
37
- * callers treat it as "no readable shape from here" rather than
38
- * re-entering code state mid-literal.
39
- *
40
- * Regex detection is bounded on purpose: a `/` is treated as a regex only
41
- * when the previous non-whitespace code character is `(` or `,` — the only
42
- * positions a regex occupies in a schema-declaration module
43
- * (`.regex(/…/)`, `.refine(x, /…/)`). Division never appears in that
44
- * position, so there is no ambiguous case to guess at.
45
- */
46
- function skipTriviaAt(source: string, index: number, prevCode: string): number {
47
- const ch = source[index];
48
- const next = source[index + 1];
49
-
50
- if (ch === '/' && next === '/') return endOfLineComment(source, index + 2);
51
- if (ch === '/' && next === '*') return endOfBlockComment(source, index + 2);
52
- if (ch === "'" || ch === '"' || ch === '`') {
53
- return endOfQuoted(source, index + 1, ch);
54
- }
55
- if (ch === '/' && (prevCode === '(' || prevCode === ',')) {
56
- return endOfQuoted(source, index + 1, '/');
57
- }
58
- return index;
59
- }
60
-
61
- function endOfLineComment(source: string, from: number): number {
62
- const nl = source.indexOf('\n', from);
63
- return nl === -1 ? source.length : nl + 1;
64
- }
65
-
66
- function endOfBlockComment(source: string, from: number): number {
67
- const end = source.indexOf('*/', from);
68
- return end === -1 ? source.length : end + 2;
69
- }
70
-
71
- /** End of a `'…'` / `"…"` / `` `…` `` / `/…/` token, honoring backslash escapes. */
72
- function endOfQuoted(source: string, from: number, terminator: string): number {
73
- for (let i = from; i < source.length; i += 1) {
74
- const ch = source[i];
75
- if (ch === '\\') {
76
- i += 1;
77
- continue;
78
- }
79
- if (ch === terminator) return i + 1;
80
- }
81
- return source.length;
82
- }
83
-
84
- /** Nesting counters shared by the depth-tracking walkers. */
85
- interface Depths {
86
- brace: number;
87
- paren: number;
88
- bracket: number;
89
- }
90
-
91
- function newDepths(): Depths {
92
- return { brace: 0, paren: 0, bracket: 0 };
93
- }
94
-
95
- function applyDepth(depths: Depths, ch: string | undefined): void {
96
- if (ch === '{') depths.brace += 1;
97
- else if (ch === '}') depths.brace = Math.max(0, depths.brace - 1);
98
- else if (ch === '(') depths.paren += 1;
99
- else if (ch === ')') depths.paren = Math.max(0, depths.paren - 1);
100
- else if (ch === '[') depths.bracket += 1;
101
- else if (ch === ']') depths.bracket = Math.max(0, depths.bracket - 1);
102
- }
103
-
104
- function isAtTopLevel(depths: Depths): boolean {
105
- return (
106
- depths.brace === 0 && depths.paren === 0 && depths.bracket === 0
107
- );
108
- }
109
-
110
- /**
111
- * Find every `z.object({ … })` literal that sits at depth 0 — i.e. every
112
- * schema the module declares in its own right, as opposed to a shape
113
- * nested inside another schema.
114
- *
115
- * Literals whose braces never close are skipped: an unbalanced source has
116
- * no readable shape, and reporting a truncated key list would be worse
117
- * than reporting none.
118
- */
119
- export function scanTopLevelZodObjectDeclarations(
120
- source: string,
121
- ): readonly TopLevelZodObjectDeclaration[] {
122
- const out: TopLevelZodObjectDeclaration[] = [];
123
- const depths = newDepths();
124
- let prevCode = '';
125
- let i = 0;
126
-
127
- while (i < source.length) {
128
- const afterTrivia = skipTriviaAt(source, i, prevCode);
129
- if (afterTrivia !== i) {
130
- i = afterTrivia;
131
- continue;
132
- }
133
-
134
- if (isAtTopLevel(depths) && matchesZodObjectAt(source, i)) {
135
- const bodyStart = openingBraceIndexAfterZodObject(source, i);
136
- const bodyEnd =
137
- bodyStart === null ? null : matchingBraceIndex(source, bodyStart);
138
- if (bodyStart !== null && bodyEnd !== null) {
139
- out.push({
140
- exportName: bindingNameBefore(source, i),
141
- keys: parseObjectKeysAtTopLevel(source.slice(bodyStart, bodyEnd)),
142
- });
143
- // Resume AFTER the literal: its interior is already accounted
144
- // for, and re-walking it could only produce nested matches we
145
- // deliberately exclude.
146
- i = bodyEnd + 1;
147
- prevCode = '}';
148
- continue;
149
- }
150
- }
151
-
152
- const ch = source[i];
153
- applyDepth(depths, ch);
154
- if (ch !== undefined && !/\s/.test(ch)) prevCode = ch;
155
- i += 1;
156
- }
157
-
158
- return out;
159
- }
160
-
161
- /** True when `z.object` starts exactly at `index` and is not part of a longer identifier. */
162
- function matchesZodObjectAt(source: string, index: number): boolean {
163
- if (!source.startsWith('z.object', index)) return false;
164
- const prev = index > 0 ? (source[index - 1] ?? '') : '';
165
- return !/[A-Za-z0-9_$.]/.test(prev);
166
- }
167
-
168
- /** Index just past the `{` of `z.object ( {`, or null when the call is not an object literal. */
169
- function openingBraceIndexAfterZodObject(
170
- source: string,
171
- index: number,
172
- ): number | null {
173
- let i = skipWhitespace(source, index + 'z.object'.length);
174
- if (source[i] !== '(') return null;
175
- i = skipWhitespace(source, i + 1);
176
- if (source[i] !== '{') return null;
177
- return i + 1;
178
- }
179
-
180
- /**
181
- * Index of the `}` closing the literal that starts at `bodyStart`, or null
182
- * when it never closes.
183
- */
184
- function matchingBraceIndex(source: string, bodyStart: number): number | null {
185
- let depth = 1;
186
- let prevCode = '{';
187
- let i = bodyStart;
188
-
189
- while (i < source.length) {
190
- const afterTrivia = skipTriviaAt(source, i, prevCode);
191
- if (afterTrivia !== i) {
192
- i = afterTrivia;
193
- continue;
194
- }
195
- const ch = source[i];
196
- if (ch === '{') depth += 1;
197
- else if (ch === '}') {
198
- depth -= 1;
199
- if (depth === 0) return i;
200
- }
201
- if (ch !== undefined && !/\s/.test(ch)) prevCode = ch;
202
- i += 1;
203
- }
204
- return null;
205
- }
206
-
207
- const BINDING_LOOKBEHIND = 400;
208
- const BINDING_TAIL =
209
- /(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?::[^=]*)?=\s*$/;
210
-
211
- /** Name bound to the literal starting at `index` (`export const Foo = z.object(`), or null. */
212
- function bindingNameBefore(source: string, index: number): string | null {
213
- const from = Math.max(0, index - BINDING_LOOKBEHIND);
214
- return BINDING_TAIL.exec(source.slice(from, index))?.[1] ?? null;
215
- }
216
-
217
- function skipWhitespace(source: string, index: number): number {
218
- let i = index;
219
- while (i < source.length && /\s/.test(source[i]!)) i += 1;
220
- return i;
221
- }
222
-
223
- /**
224
- * Field identifiers at depth 0 of an object-literal body.
225
- *
226
- * Splits on depth-0 commas and reads the leading key of each segment, so
227
- * a key is recorded exactly once. (The predecessor scanned for a key after
228
- * BOTH the comma and the newline that followed it, emitting every
229
- * multi-line key twice — harmless for a set-membership test, but the
230
- * duplicates leaked into compiler error messages and the "known fields"
231
- * preview shown to authors.)
232
- */
233
- function parseObjectKeysAtTopLevel(body: string): readonly string[] {
234
- const keys: string[] = [];
235
- const seen = new Set<string>();
236
- for (const segment of splitTopLevelSegments(body)) {
237
- const key = leadingKeyOf(segment);
238
- if (key !== null && !seen.has(key)) {
239
- seen.add(key);
240
- keys.push(key);
241
- }
242
- }
243
- return Object.freeze(keys);
244
- }
245
-
246
- function splitTopLevelSegments(body: string): readonly string[] {
247
- const segments: string[] = [];
248
- const depths = newDepths();
249
- let prevCode = '';
250
- let start = 0;
251
- let i = 0;
252
-
253
- while (i < body.length) {
254
- const afterTrivia = skipTriviaAt(body, i, prevCode);
255
- if (afterTrivia !== i) {
256
- i = afterTrivia;
257
- continue;
258
- }
259
- const ch = body[i];
260
- if (ch === ',' && isAtTopLevel(depths)) {
261
- segments.push(body.slice(start, i));
262
- start = i + 1;
263
- } else {
264
- applyDepth(depths, ch);
265
- }
266
- if (ch !== undefined && !/\s/.test(ch)) prevCode = ch;
267
- i += 1;
268
- }
269
- segments.push(body.slice(start));
270
- return segments;
271
- }
272
-
273
- const LEADING_TRIVIA = /^(?:\s|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)+/;
274
- const LEADING_KEY = /^(?:([A-Za-z_$][A-Za-z0-9_$]*)|'([^']+)'|"([^"]+)")\s*:/;
275
-
276
- function leadingKeyOf(segment: string): string | null {
277
- const match = LEADING_KEY.exec(segment.replace(LEADING_TRIVIA, ''));
278
- if (!match) return null;
279
- return match[1] ?? match[2] ?? match[3] ?? null;
280
- }
@@ -1,160 +0,0 @@
1
- // ═══════════════════════════════════════════════════════════════════════════
2
- // ── Dispatch-input descriptor extraction ──
3
- //
4
- // Shared, tree-shakable module that projects a parsed workflow
5
- // document's `on.workflow_dispatch.inputs` declaration into a
6
- // normalized `DispatchInputDescriptor[]` the frontend renders as a
7
- // dispatch form and the engine consumes for endpoint-returned schema
8
- // metadata.
9
- //
10
- // Single source of truth: the engine and the web frontend both call
11
- // this function. Keeping the extraction + field-type enumeration in
12
- // one place removes the "two validation authorities" anti-pattern
13
- // (per CLAUDE.md fail-fast rule). The pure extraction logic here is
14
- // NOT validation — the engine's DSL validator (in `lib/validate.ts`)
15
- // remains the sole validator at compile + dispatch time.
16
- //
17
- // No Ajv, no js-yaml — this module is deliberately self-contained so
18
- // the frontend bundle can import it without pulling heavy
19
- // transitive deps through tree-shaking.
20
- // ═══════════════════════════════════════════════════════════════════════════
21
-
22
- /**
23
- * Closed set of field types surfaced to the dispatch form. Mirrors
24
- * `WorkflowInputDeclaration.type` but narrows to the subset the form
25
- * renderer knows how to render. Unknown type values in the parsed
26
- * document cause the field to be skipped (fail-fast — no silent
27
- * downgrade to a string input).
28
- */
29
- export enum DispatchInputFieldType {
30
- String = 'string',
31
- Number = 'number',
32
- Integer = 'integer',
33
- Boolean = 'boolean',
34
- Object = 'object',
35
- Array = 'array',
36
- }
37
-
38
- /**
39
- * Normalized descriptor the dispatch form consumes. Flat + closed
40
- * shape so renderer code doesn't have to probe optional fields.
41
- */
42
- export interface DispatchInputDescriptor {
43
- readonly name: string;
44
- readonly type: DispatchInputFieldType;
45
- readonly required: boolean;
46
- readonly description: string | null;
47
- /** String values only — non-string enum members are dropped. */
48
- readonly enumValues: readonly string[] | null;
49
- /** For `type: string`, a hint for multiline rendering. Closed set. */
50
- readonly format: DispatchInputStringFormat | null;
51
- /** For `type: array`, the item type (only `string[]` is form-rendered; other item types fall to raw JSON). */
52
- readonly itemType: DispatchInputFieldType | null;
53
- readonly defaultValue: unknown;
54
- }
55
-
56
- export enum DispatchInputStringFormat {
57
- Multiline = 'multiline',
58
- }
59
-
60
- /**
61
- * Extracts descriptors from a parsed workflow document. Returns an
62
- * empty array when the document declares no dispatch inputs (reusable
63
- * workflows or schedule/webhook-only workflows). Never throws — a
64
- * malformed inputs bag (unknown `type`, non-object entries) drops the
65
- * offending field rather than failing the whole extraction, so the
66
- * dispatch UI stays renderable even if one input declaration is
67
- * stale. The engine's compile path remains authoritative for
68
- * validation.
69
- */
70
- export function extractDispatchInputDescriptors(
71
- parsedDocument: unknown,
72
- ): readonly DispatchInputDescriptor[] {
73
- const inputs = readDispatchInputsBag(parsedDocument);
74
- if (!inputs) return [];
75
- const descriptors: DispatchInputDescriptor[] = [];
76
- for (const [name, raw] of Object.entries(inputs)) {
77
- const descriptor = toDescriptor(name, raw);
78
- if (descriptor !== null) descriptors.push(descriptor);
79
- }
80
- return descriptors;
81
- }
82
-
83
- function readDispatchInputsBag(
84
- parsedDocument: unknown,
85
- ): Record<string, unknown> | null {
86
- if (!parsedDocument || typeof parsedDocument !== 'object') return null;
87
- const doc = parsedDocument as Record<string, unknown>;
88
- const on = doc['on'];
89
- if (!on || typeof on !== 'object') return null;
90
- const wfDispatch = (on as Record<string, unknown>)['workflow_dispatch'];
91
- if (!wfDispatch || typeof wfDispatch !== 'object') return null;
92
- const inputs = (wfDispatch as Record<string, unknown>)['inputs'];
93
- if (!inputs || typeof inputs !== 'object' || Array.isArray(inputs)) {
94
- return null;
95
- }
96
- return inputs as Record<string, unknown>;
97
- }
98
-
99
- function toDescriptor(
100
- name: string,
101
- raw: unknown,
102
- ): DispatchInputDescriptor | null {
103
- if (!raw || typeof raw !== 'object') return null;
104
- const field = raw as Record<string, unknown>;
105
- const type = parseFieldType(field['type']);
106
- if (type === null) return null;
107
- const required = field['required'] === true;
108
- const description =
109
- typeof field['description'] === 'string' ? field['description'] : null;
110
- const enumValues = parseEnumValues(field['enum']);
111
- const format = parseStringFormat(field['format']);
112
- const itemType = parseItemType(field['items']);
113
- return {
114
- name,
115
- type,
116
- required,
117
- description,
118
- enumValues,
119
- format,
120
- itemType,
121
- defaultValue: field['default'] ?? undefined,
122
- };
123
- }
124
-
125
- function parseFieldType(value: unknown): DispatchInputFieldType | null {
126
- if (typeof value !== 'string') return null;
127
- switch (value) {
128
- case 'string':
129
- return DispatchInputFieldType.String;
130
- case 'number':
131
- return DispatchInputFieldType.Number;
132
- case 'integer':
133
- return DispatchInputFieldType.Integer;
134
- case 'boolean':
135
- return DispatchInputFieldType.Boolean;
136
- case 'object':
137
- return DispatchInputFieldType.Object;
138
- case 'array':
139
- return DispatchInputFieldType.Array;
140
- default:
141
- return null;
142
- }
143
- }
144
-
145
- function parseEnumValues(value: unknown): readonly string[] | null {
146
- if (!Array.isArray(value) || value.length === 0) return null;
147
- const strings = value.filter((v): v is string => typeof v === 'string');
148
- return strings.length === value.length ? strings : null;
149
- }
150
-
151
- function parseStringFormat(value: unknown): DispatchInputStringFormat | null {
152
- if (value === 'multiline') return DispatchInputStringFormat.Multiline;
153
- return null;
154
- }
155
-
156
- function parseItemType(value: unknown): DispatchInputFieldType | null {
157
- if (!value || typeof value !== 'object') return null;
158
- const items = value as Record<string, unknown>;
159
- return parseFieldType(items['type']);
160
- }
@@ -1,60 +0,0 @@
1
- import {
2
- DispatchInputFieldType,
3
- DispatchInputStringFormat,
4
- type DispatchInputDescriptor,
5
- } from './index';
6
-
7
- /**
8
- * Project a flat `DispatchInputDescriptor[]` into a JSON Schema
9
- * Draft 2020-12 object the MCP gateway uses as `inputSchema` on
10
- * workflow-tool projections (and the frontend dispatch dialog uses
11
- * for form rendering parity).
12
- *
13
- * Single source of truth for the descriptor → JSON Schema mapping so
14
- * the gateway, the engine, and the frontend agree on the shape an
15
- * agent sees vs the shape a human sees in the dispatch dialog.
16
- *
17
- * Closed-set conservatively: unknown enum values are dropped (the
18
- * extractor already filters), unknown type combinations fall back to
19
- * `{ type: 'object' }` to keep the schema valid rather than throwing.
20
- */
21
- export function dispatchInputsToJsonSchema(
22
- descriptors: readonly DispatchInputDescriptor[],
23
- ): Readonly<Record<string, unknown>> {
24
- const properties: Record<string, Record<string, unknown>> = {};
25
- const required: string[] = [];
26
- for (const d of descriptors) {
27
- properties[d.name] = descriptorToProperty(d);
28
- if (d.required) required.push(d.name);
29
- }
30
- const schema: Record<string, unknown> = {
31
- $schema: 'https://json-schema.org/draft/2020-12/schema',
32
- type: 'object',
33
- properties,
34
- additionalProperties: false,
35
- };
36
- if (required.length > 0) {
37
- schema['required'] = required;
38
- }
39
- return Object.freeze(schema);
40
- }
41
-
42
- function descriptorToProperty(d: DispatchInputDescriptor): Record<string, unknown> {
43
- const prop: Record<string, unknown> = { type: d.type };
44
- if (d.description) {
45
- prop['description'] = d.description;
46
- }
47
- if (d.enumValues && d.enumValues.length > 0) {
48
- prop['enum'] = [...d.enumValues];
49
- }
50
- if (d.format === DispatchInputStringFormat.Multiline) {
51
- prop['x-widget'] = 'multiline';
52
- }
53
- if (d.type === DispatchInputFieldType.Array && d.itemType) {
54
- prop['items'] = { type: d.itemType };
55
- }
56
- if (d.defaultValue !== undefined) {
57
- prop['default'] = d.defaultValue;
58
- }
59
- return prop;
60
- }
@@ -1,43 +0,0 @@
1
- import { WorkflowErrorCode } from '@xemahq/kernel-contracts/workflow';
2
- import { WorkflowDslError } from './errors';
3
-
4
- /**
5
- * Duration literal parser. Accepts `<int><unit>` where unit is `s`, `m`, or
6
- * `h`. Deterministic; rejects malformed input with a typed error. There is
7
- * no silent zero fallback — callers must pass a value that matches the
8
- * schema pattern (already enforced by Ajv upstream).
9
- *
10
- * Cap: total duration limited to 8760h (= 365 days), regardless of the
11
- * unit used. The cap is a sanity guard
12
- * against typos (`100000h` ≈ 11 years), not a policy bound — real
13
- * human-driven flows (compliance reviews, multi-stakeholder approvals,
14
- * SCM PRs that go a quarter without merging) routinely take months.
15
- *
16
- * For redraft loops (`xema/review@v3`), the workflow `continueAsNew`s
17
- * every 25 iterations, which resets the runtime clock — the practical
18
- * ceiling on a single review is unbounded. This cap applies only to a
19
- * single iteration window of the child workflow.
20
- */
21
- const MAX_HOURS = 8760;
22
-
23
- export function parseDurationMs(literal: string): number {
24
- const match = /^([1-9][0-9]*)(s|m|h)$/.exec(literal);
25
- if (!match) {
26
- throw new WorkflowDslError(
27
- WorkflowErrorCode.DSL_SCHEMA_INVALID,
28
- `Duration literal does not match pattern <int><s|m|h>: ${literal}`,
29
- { literal },
30
- );
31
- }
32
- const value = Number.parseInt(match[1]!, 10);
33
- const unit = match[2] as 's' | 'm' | 'h';
34
- const ms = unit === 's' ? value * 1_000 : unit === 'm' ? value * 60_000 : value * 3_600_000;
35
- if (ms > MAX_HOURS * 3_600_000) {
36
- throw new WorkflowDslError(
37
- WorkflowErrorCode.DSL_SCHEMA_INVALID,
38
- `Duration exceeds cap of ${MAX_HOURS}h: ${literal}`,
39
- { literal, maxHours: MAX_HOURS },
40
- );
41
- }
42
- return ms;
43
- }
@@ -1,37 +0,0 @@
1
- import { WorkflowErrorCode } from '@xemahq/kernel-contracts/workflow';
2
-
3
- /**
4
- * Structured error produced by every DSL entry point (validate, parseYaml,
5
- * evaluateExpression, compile). Callers MUST branch on `.code` — free-form
6
- * message matching is forbidden so error handling stays exhaustive.
7
- */
8
- export class WorkflowDslError extends Error {
9
- readonly code: WorkflowErrorCode;
10
- readonly details: Readonly<Record<string, unknown>>;
11
-
12
- constructor(
13
- code: WorkflowErrorCode,
14
- message: string,
15
- details: Readonly<Record<string, unknown>> = {},
16
- ) {
17
- super(message);
18
- this.name = 'WorkflowDslError';
19
- this.code = code;
20
- this.details = details;
21
- Object.setPrototypeOf(this, new.target.prototype);
22
- }
23
-
24
- toJSON(): Readonly<Record<string, unknown>> {
25
- return {
26
- name: this.name,
27
- code: this.code,
28
- message: this.message,
29
- details: this.details,
30
- };
31
- }
32
- }
33
-
34
- /** Narrowing helper. */
35
- export function isWorkflowDslError(err: unknown): err is WorkflowDslError {
36
- return err instanceof WorkflowDslError;
37
- }
@@ -1,108 +0,0 @@
1
- /**
2
- * AST node kinds for parsed expressions. Closed set.
3
- */
4
- export enum ExpressionNodeKind {
5
- LITERAL = 'literal',
6
- IDENTIFIER = 'identifier',
7
- MEMBER = 'member',
8
- INDEX = 'index',
9
- CALL = 'call',
10
- UNARY_NOT = 'unary-not',
11
- BINARY_EQ = 'binary-eq',
12
- BINARY_NEQ = 'binary-neq',
13
- BINARY_AND = 'binary-and',
14
- BINARY_OR = 'binary-or',
15
- }
16
-
17
- /** Closed set of root identifier bindings allowed in expressions. */
18
- export enum ExpressionRoot {
19
- NEEDS = 'needs',
20
- MATRIX = 'matrix',
21
- INPUTS = 'inputs',
22
- VARS = 'vars',
23
- /**
24
- * Project / org secret variables resolved at dispatch from the
25
- * project-registry-api variable store, declared by the workflow under
26
- * `requires.variables[*].secret: true`. Values are strings; expressions
27
- * reading this namespace are subject to log redaction by the runtime.
28
- */
29
- SECRETS = 'secrets',
30
- TRIGGER = 'trigger',
31
- JOB = 'job',
32
- /**
33
- * System-managed canonical context for the run. Mirrors GitHub
34
- * Actions' `github.*` namespace: stable, system-populated, available
35
- * in every expression. Exposes org/project/run/workflow/job/actor
36
- * fields so authors don't have to reach into trigger metadata for
37
- * what is really run-level state.
38
- */
39
- XEMA = 'xema',
40
- }
41
-
42
- /** Closed set of built-in function names. Any other identifier-call fails. */
43
- export enum ExpressionFunction {
44
- FROM_JSON = 'fromJSON',
45
- TO_JSON = 'toJSON',
46
- /**
47
- * `format(template, ...args)` — GitHub-Actions-style positional
48
- * interpolation. The template is a literal string with `{0}`, `{1}`,
49
- * ... placeholders; each is replaced with the stringified arg at that
50
- * index. Authors use this when they need to combine an expression
51
- * value with surrounding literal text — partial `${{ ... }}`
52
- * interpolation in YAML is intentionally rejected, so `format` is the
53
- * supported way to build composed strings.
54
- */
55
- FORMAT = 'format',
56
- }
57
-
58
- export type ExpressionNode =
59
- | LiteralNode
60
- | IdentifierNode
61
- | MemberNode
62
- | IndexNode
63
- | CallNode
64
- | UnaryNotNode
65
- | BinaryNode;
66
-
67
- export interface LiteralNode {
68
- readonly kind: ExpressionNodeKind.LITERAL;
69
- readonly value: string | number | boolean | null;
70
- }
71
-
72
- export interface IdentifierNode {
73
- readonly kind: ExpressionNodeKind.IDENTIFIER;
74
- readonly name: string;
75
- }
76
-
77
- export interface MemberNode {
78
- readonly kind: ExpressionNodeKind.MEMBER;
79
- readonly target: ExpressionNode;
80
- readonly property: string;
81
- }
82
-
83
- export interface IndexNode {
84
- readonly kind: ExpressionNodeKind.INDEX;
85
- readonly target: ExpressionNode;
86
- readonly index: ExpressionNode;
87
- }
88
-
89
- export interface CallNode {
90
- readonly kind: ExpressionNodeKind.CALL;
91
- readonly callee: ExpressionFunction;
92
- readonly args: readonly ExpressionNode[];
93
- }
94
-
95
- export interface UnaryNotNode {
96
- readonly kind: ExpressionNodeKind.UNARY_NOT;
97
- readonly operand: ExpressionNode;
98
- }
99
-
100
- export interface BinaryNode {
101
- readonly kind:
102
- | ExpressionNodeKind.BINARY_EQ
103
- | ExpressionNodeKind.BINARY_NEQ
104
- | ExpressionNodeKind.BINARY_AND
105
- | ExpressionNodeKind.BINARY_OR;
106
- readonly left: ExpressionNode;
107
- readonly right: ExpressionNode;
108
- }