@xemahq/dsl 0.1.1
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.
- package/LICENSE +201 -0
- package/dist/deliverable-spec/index.d.ts +3 -0
- package/dist/deliverable-spec/index.d.ts.map +1 -0
- package/dist/deliverable-spec/index.js +19 -0
- package/dist/deliverable-spec/index.js.map +1 -0
- package/dist/deliverable-spec/lib/schema.d.ts +151 -0
- package/dist/deliverable-spec/lib/schema.d.ts.map +1 -0
- package/dist/deliverable-spec/lib/schema.js +139 -0
- package/dist/deliverable-spec/lib/schema.js.map +1 -0
- package/dist/deliverable-spec/lib/types.d.ts +8 -0
- package/dist/deliverable-spec/lib/types.d.ts.map +1 -0
- package/dist/deliverable-spec/lib/types.js +3 -0
- package/dist/deliverable-spec/lib/types.js.map +1 -0
- package/dist/payload-codec/index.d.ts +8 -0
- package/dist/payload-codec/index.d.ts.map +1 -0
- package/dist/payload-codec/index.js +27 -0
- package/dist/payload-codec/index.js.map +1 -0
- package/dist/payload-codec/lib/blob-store.d.ts +37 -0
- package/dist/payload-codec/lib/blob-store.d.ts.map +1 -0
- package/dist/payload-codec/lib/blob-store.js +0 -0
- package/dist/payload-codec/lib/blob-store.js.map +1 -0
- package/dist/payload-codec/lib/codec-context.d.ts +6 -0
- package/dist/payload-codec/lib/codec-context.d.ts.map +1 -0
- package/dist/payload-codec/lib/codec-context.js +16 -0
- package/dist/payload-codec/lib/codec-context.js.map +1 -0
- package/dist/payload-codec/lib/codec.d.ts +51 -0
- package/dist/payload-codec/lib/codec.d.ts.map +1 -0
- package/dist/payload-codec/lib/codec.js +330 -0
- package/dist/payload-codec/lib/codec.js.map +1 -0
- package/dist/payload-codec/lib/enums.d.ts +18 -0
- package/dist/payload-codec/lib/enums.d.ts.map +1 -0
- package/dist/payload-codec/lib/enums.js +23 -0
- package/dist/payload-codec/lib/enums.js.map +1 -0
- package/dist/payload-codec/lib/errors.d.ts +18 -0
- package/dist/payload-codec/lib/errors.d.ts.map +1 -0
- package/dist/payload-codec/lib/errors.js +39 -0
- package/dist/payload-codec/lib/errors.js.map +1 -0
- package/dist/payload-codec/lib/http-blob-store.d.ts +21 -0
- package/dist/payload-codec/lib/http-blob-store.d.ts.map +1 -0
- package/dist/payload-codec/lib/http-blob-store.js +139 -0
- package/dist/payload-codec/lib/http-blob-store.js.map +1 -0
- package/dist/payload-codec/lib/lru-cache.d.ts +12 -0
- package/dist/payload-codec/lib/lru-cache.d.ts.map +1 -0
- package/dist/payload-codec/lib/lru-cache.js +59 -0
- package/dist/payload-codec/lib/lru-cache.js.map +1 -0
- package/dist/schema/action.schema.json +181 -0
- package/dist/schema/reusable-workflow.schema.json +46 -0
- package/dist/schema/workflow.schema.json +373 -0
- package/dist/workflow/index.d.ts +14 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +49 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/lib/action-input-validator.d.ts +10 -0
- package/dist/workflow/lib/action-input-validator.d.ts.map +1 -0
- package/dist/workflow/lib/action-input-validator.js +69 -0
- package/dist/workflow/lib/action-input-validator.js.map +1 -0
- package/dist/workflow/lib/compiler/action-shape.d.ts +5 -0
- package/dist/workflow/lib/compiler/action-shape.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/action-shape.js +43 -0
- package/dist/workflow/lib/compiler/action-shape.js.map +1 -0
- package/dist/workflow/lib/compiler/canonical-json.d.ts +3 -0
- package/dist/workflow/lib/compiler/canonical-json.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/canonical-json.js +45 -0
- package/dist/workflow/lib/compiler/canonical-json.js.map +1 -0
- package/dist/workflow/lib/compiler/compile.d.ts +4 -0
- package/dist/workflow/lib/compiler/compile.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/compile.js +794 -0
- package/dist/workflow/lib/compiler/compile.js.map +1 -0
- package/dist/workflow/lib/compiler/concurrency.d.ts +5 -0
- package/dist/workflow/lib/compiler/concurrency.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/concurrency.js +104 -0
- package/dist/workflow/lib/compiler/concurrency.js.map +1 -0
- package/dist/workflow/lib/compiler/dag.d.ts +10 -0
- package/dist/workflow/lib/compiler/dag.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/dag.js +74 -0
- package/dist/workflow/lib/compiler/dag.js.map +1 -0
- package/dist/workflow/lib/compiler/index.d.ts +6 -0
- package/dist/workflow/lib/compiler/index.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/index.js +14 -0
- package/dist/workflow/lib/compiler/index.js.map +1 -0
- package/dist/workflow/lib/compiler/inputs.d.ts +4 -0
- package/dist/workflow/lib/compiler/inputs.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/inputs.js +108 -0
- package/dist/workflow/lib/compiler/inputs.js.map +1 -0
- package/dist/workflow/lib/compiler/installation-resource-validator.d.ts +9 -0
- package/dist/workflow/lib/compiler/installation-resource-validator.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/installation-resource-validator.js +76 -0
- package/dist/workflow/lib/compiler/installation-resource-validator.js.map +1 -0
- package/dist/workflow/lib/compiler/manifest-source.d.ts +4 -0
- package/dist/workflow/lib/compiler/manifest-source.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/manifest-source.js +100 -0
- package/dist/workflow/lib/compiler/manifest-source.js.map +1 -0
- package/dist/workflow/lib/compiler/matrix.d.ts +4 -0
- package/dist/workflow/lib/compiler/matrix.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/matrix.js +76 -0
- package/dist/workflow/lib/compiler/matrix.js.map +1 -0
- package/dist/workflow/lib/compiler/mount-plan.d.ts +4 -0
- package/dist/workflow/lib/compiler/mount-plan.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/mount-plan.js +96 -0
- package/dist/workflow/lib/compiler/mount-plan.js.map +1 -0
- package/dist/workflow/lib/compiler/payload-reach-in.d.ts +14 -0
- package/dist/workflow/lib/compiler/payload-reach-in.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/payload-reach-in.js +273 -0
- package/dist/workflow/lib/compiler/payload-reach-in.js.map +1 -0
- package/dist/workflow/lib/compiler/permissions.d.ts +6 -0
- package/dist/workflow/lib/compiler/permissions.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/permissions.js +43 -0
- package/dist/workflow/lib/compiler/permissions.js.map +1 -0
- package/dist/workflow/lib/compiler/retry-timeout.d.ts +6 -0
- package/dist/workflow/lib/compiler/retry-timeout.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/retry-timeout.js +64 -0
- package/dist/workflow/lib/compiler/retry-timeout.js.map +1 -0
- package/dist/workflow/lib/compiler/review-step.d.ts +18 -0
- package/dist/workflow/lib/compiler/review-step.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/review-step.js +247 -0
- package/dist/workflow/lib/compiler/review-step.js.map +1 -0
- package/dist/workflow/lib/compiler/types.d.ts +42 -0
- package/dist/workflow/lib/compiler/types.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/types.js +3 -0
- package/dist/workflow/lib/compiler/types.js.map +1 -0
- package/dist/workflow/lib/compiler/variable-requirements.d.ts +5 -0
- package/dist/workflow/lib/compiler/variable-requirements.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/variable-requirements.js +119 -0
- package/dist/workflow/lib/compiler/variable-requirements.js.map +1 -0
- package/dist/workflow/lib/deliverable-spec-keys.d.ts +3 -0
- package/dist/workflow/lib/deliverable-spec-keys.d.ts.map +1 -0
- package/dist/workflow/lib/deliverable-spec-keys.js +90 -0
- package/dist/workflow/lib/deliverable-spec-keys.js.map +1 -0
- package/dist/workflow/lib/dispatch-inputs/index.d.ts +23 -0
- package/dist/workflow/lib/dispatch-inputs/index.d.ts.map +1 -0
- package/dist/workflow/lib/dispatch-inputs/index.js +106 -0
- package/dist/workflow/lib/dispatch-inputs/index.js.map +1 -0
- package/dist/workflow/lib/dispatch-inputs/to-json-schema.d.ts +3 -0
- package/dist/workflow/lib/dispatch-inputs/to-json-schema.d.ts.map +1 -0
- package/dist/workflow/lib/dispatch-inputs/to-json-schema.js +43 -0
- package/dist/workflow/lib/dispatch-inputs/to-json-schema.js.map +1 -0
- package/dist/workflow/lib/duration.d.ts +2 -0
- package/dist/workflow/lib/duration.d.ts.map +1 -0
- package/dist/workflow/lib/duration.js +26 -0
- package/dist/workflow/lib/duration.js.map +1 -0
- package/dist/workflow/lib/errors.d.ts +9 -0
- package/dist/workflow/lib/errors.d.ts.map +1 -0
- package/dist/workflow/lib/errors.js +28 -0
- package/dist/workflow/lib/errors.js.map +1 -0
- package/dist/workflow/lib/expression/ast.d.ts +61 -0
- package/dist/workflow/lib/expression/ast.d.ts.map +1 -0
- package/dist/workflow/lib/expression/ast.js +34 -0
- package/dist/workflow/lib/expression/ast.js.map +1 -0
- package/dist/workflow/lib/expression/context.d.ts +63 -0
- package/dist/workflow/lib/expression/context.d.ts.map +1 -0
- package/dist/workflow/lib/expression/context.js +32 -0
- package/dist/workflow/lib/expression/context.js.map +1 -0
- package/dist/workflow/lib/expression/evaluator.d.ts +5 -0
- package/dist/workflow/lib/expression/evaluator.d.ts.map +1 -0
- package/dist/workflow/lib/expression/evaluator.js +291 -0
- package/dist/workflow/lib/expression/evaluator.js.map +1 -0
- package/dist/workflow/lib/expression/index.d.ts +9 -0
- package/dist/workflow/lib/expression/index.d.ts.map +1 -0
- package/dist/workflow/lib/expression/index.js +26 -0
- package/dist/workflow/lib/expression/index.js.map +1 -0
- package/dist/workflow/lib/expression/interpolation.d.ts +9 -0
- package/dist/workflow/lib/expression/interpolation.d.ts.map +1 -0
- package/dist/workflow/lib/expression/interpolation.js +51 -0
- package/dist/workflow/lib/expression/interpolation.js.map +1 -0
- package/dist/workflow/lib/expression/parser.d.ts +4 -0
- package/dist/workflow/lib/expression/parser.d.ts.map +1 -0
- package/dist/workflow/lib/expression/parser.js +203 -0
- package/dist/workflow/lib/expression/parser.js.map +1 -0
- package/dist/workflow/lib/expression/template.d.ts +18 -0
- package/dist/workflow/lib/expression/template.d.ts.map +1 -0
- package/dist/workflow/lib/expression/template.js +63 -0
- package/dist/workflow/lib/expression/template.js.map +1 -0
- package/dist/workflow/lib/expression/tokenizer.d.ts +3 -0
- package/dist/workflow/lib/expression/tokenizer.d.ts.map +1 -0
- package/dist/workflow/lib/expression/tokenizer.js +153 -0
- package/dist/workflow/lib/expression/tokenizer.js.map +1 -0
- package/dist/workflow/lib/expression/tokens.d.ts +25 -0
- package/dist/workflow/lib/expression/tokens.d.ts.map +1 -0
- package/dist/workflow/lib/expression/tokens.js +24 -0
- package/dist/workflow/lib/expression/tokens.js.map +1 -0
- package/dist/workflow/lib/expression/walk-artifact-refs.d.ts +5 -0
- package/dist/workflow/lib/expression/walk-artifact-refs.d.ts.map +1 -0
- package/dist/workflow/lib/expression/walk-artifact-refs.js +138 -0
- package/dist/workflow/lib/expression/walk-artifact-refs.js.map +1 -0
- package/dist/workflow/lib/installation-resource-kind.d.ts +14 -0
- package/dist/workflow/lib/installation-resource-kind.d.ts.map +1 -0
- package/dist/workflow/lib/installation-resource-kind.js +59 -0
- package/dist/workflow/lib/installation-resource-kind.js.map +1 -0
- package/dist/workflow/lib/schemas-loader.d.ts +4 -0
- package/dist/workflow/lib/schemas-loader.d.ts.map +1 -0
- package/dist/workflow/lib/schemas-loader.js +36 -0
- package/dist/workflow/lib/schemas-loader.js.map +1 -0
- package/dist/workflow/lib/serializer.d.ts +3 -0
- package/dist/workflow/lib/serializer.d.ts.map +1 -0
- package/dist/workflow/lib/serializer.js +15 -0
- package/dist/workflow/lib/serializer.js.map +1 -0
- package/dist/workflow/lib/types.d.ts +179 -0
- package/dist/workflow/lib/types.d.ts.map +1 -0
- package/dist/workflow/lib/types.js +3 -0
- package/dist/workflow/lib/types.js.map +1 -0
- package/dist/workflow/lib/validate.d.ts +8 -0
- package/dist/workflow/lib/validate.d.ts.map +1 -0
- package/dist/workflow/lib/validate.js +119 -0
- package/dist/workflow/lib/validate.js.map +1 -0
- package/dist/workspace-manifest/index.d.ts +6 -0
- package/dist/workspace-manifest/index.d.ts.map +1 -0
- package/dist/workspace-manifest/index.js +22 -0
- package/dist/workspace-manifest/index.js.map +1 -0
- package/dist/workspace-manifest/lib/compile.d.ts +8 -0
- package/dist/workspace-manifest/lib/compile.d.ts.map +1 -0
- package/dist/workspace-manifest/lib/compile.js +439 -0
- package/dist/workspace-manifest/lib/compile.js.map +1 -0
- package/dist/workspace-manifest/lib/interpolate.d.ts +12 -0
- package/dist/workspace-manifest/lib/interpolate.d.ts.map +1 -0
- package/dist/workspace-manifest/lib/interpolate.js +81 -0
- package/dist/workspace-manifest/lib/interpolate.js.map +1 -0
- package/dist/workspace-manifest/lib/resolve-extends.d.ts +10 -0
- package/dist/workspace-manifest/lib/resolve-extends.d.ts.map +1 -0
- package/dist/workspace-manifest/lib/resolve-extends.js +108 -0
- package/dist/workspace-manifest/lib/resolve-extends.js.map +1 -0
- package/dist/workspace-manifest/lib/schema.d.ts +710 -0
- package/dist/workspace-manifest/lib/schema.d.ts.map +1 -0
- package/dist/workspace-manifest/lib/schema.js +355 -0
- package/dist/workspace-manifest/lib/schema.js.map +1 -0
- package/dist/workspace-manifest/lib/types.d.ts +153 -0
- package/dist/workspace-manifest/lib/types.d.ts.map +1 -0
- package/dist/workspace-manifest/lib/types.js +10 -0
- package/dist/workspace-manifest/lib/types.js.map +1 -0
- package/package.json +79 -0
- package/schema/action.schema.json +181 -0
- package/schema/reusable-workflow.schema.json +46 -0
- package/schema/workflow.schema.json +373 -0
- package/src/deliverable-spec/index.ts +19 -0
- package/src/deliverable-spec/lib/schema.ts +248 -0
- package/src/deliverable-spec/lib/types.ts +26 -0
- package/src/payload-codec/index.ts +40 -0
- package/src/payload-codec/lib/blob-store.ts +0 -0
- package/src/payload-codec/lib/codec-context.ts +38 -0
- package/src/payload-codec/lib/codec.ts +593 -0
- package/src/payload-codec/lib/enums.ts +58 -0
- package/src/payload-codec/lib/errors.ts +54 -0
- package/src/payload-codec/lib/http-blob-store.ts +257 -0
- package/src/payload-codec/lib/lru-cache.ts +81 -0
- package/src/workflow/index.ts +98 -0
- package/src/workflow/lib/action-input-validator.ts +160 -0
- package/src/workflow/lib/compiler/action-shape.ts +71 -0
- package/src/workflow/lib/compiler/canonical-json.ts +53 -0
- package/src/workflow/lib/compiler/compile.ts +1518 -0
- package/src/workflow/lib/compiler/concurrency.ts +223 -0
- package/src/workflow/lib/compiler/dag.ts +108 -0
- package/src/workflow/lib/compiler/index.ts +10 -0
- package/src/workflow/lib/compiler/inputs.ts +199 -0
- package/src/workflow/lib/compiler/installation-resource-validator.ts +114 -0
- package/src/workflow/lib/compiler/manifest-source.ts +176 -0
- package/src/workflow/lib/compiler/matrix.ts +135 -0
- package/src/workflow/lib/compiler/mount-plan.ts +202 -0
- package/src/workflow/lib/compiler/payload-reach-in.ts +497 -0
- package/src/workflow/lib/compiler/permissions.ts +64 -0
- package/src/workflow/lib/compiler/retry-timeout.ts +105 -0
- package/src/workflow/lib/compiler/review-step.ts +517 -0
- package/src/workflow/lib/compiler/types.ts +170 -0
- package/src/workflow/lib/compiler/variable-requirements.ts +208 -0
- package/src/workflow/lib/deliverable-spec-keys.ts +109 -0
- package/src/workflow/lib/dispatch-inputs/index.ts +160 -0
- package/src/workflow/lib/dispatch-inputs/to-json-schema.ts +60 -0
- package/src/workflow/lib/duration.ts +48 -0
- package/src/workflow/lib/errors.ts +37 -0
- package/src/workflow/lib/expression/ast.ts +108 -0
- package/src/workflow/lib/expression/context.ts +148 -0
- package/src/workflow/lib/expression/evaluator.ts +492 -0
- package/src/workflow/lib/expression/index.ts +28 -0
- package/src/workflow/lib/expression/interpolation.ts +84 -0
- package/src/workflow/lib/expression/parser.ts +264 -0
- package/src/workflow/lib/expression/template.ts +117 -0
- package/src/workflow/lib/expression/tokenizer.ts +200 -0
- package/src/workflow/lib/expression/tokens.ts +30 -0
- package/src/workflow/lib/expression/walk-artifact-refs.ts +232 -0
- package/src/workflow/lib/installation-resource-kind.ts +107 -0
- package/src/workflow/lib/schemas-loader.ts +64 -0
- package/src/workflow/lib/serializer.ts +30 -0
- package/src/workflow/lib/types.ts +361 -0
- package/src/workflow/lib/validate.ts +199 -0
- package/src/workspace-manifest/index.ts +27 -0
- package/src/workspace-manifest/lib/compile.ts +608 -0
- package/src/workspace-manifest/lib/interpolate.ts +140 -0
- package/src/workspace-manifest/lib/resolve-extends.ts +260 -0
- package/src/workspace-manifest/lib/schema.ts +612 -0
- package/src/workspace-manifest/lib/types.ts +392 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WorkflowErrorCode,
|
|
3
|
+
type WalletContents,
|
|
4
|
+
type WorkflowVariableRequirement,
|
|
5
|
+
} from '@xemahq/kernel-contracts/workflow';
|
|
6
|
+
import { WorkflowDslError } from '../errors';
|
|
7
|
+
import {
|
|
8
|
+
compileExpression,
|
|
9
|
+
ExpressionNodeKind,
|
|
10
|
+
extractInterpolations,
|
|
11
|
+
type ExpressionNode,
|
|
12
|
+
} from '../expression';
|
|
13
|
+
import { ExpressionRoot } from '../expression/ast';
|
|
14
|
+
import { stripInterpolation } from '../expression/interpolation';
|
|
15
|
+
import type { WorkflowDocument } from '../types';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compile-time handling of `requires.wallets` and the `vars.*` /
|
|
19
|
+
* `secrets.*` expression namespaces.
|
|
20
|
+
*
|
|
21
|
+
* The compiler trusts the engine to pre-fetch the contents of every
|
|
22
|
+
* wallet listed in `workflow.requires.wallets` (plus any additional
|
|
23
|
+
* wallets the dispatch caller asked for) and pass them as the
|
|
24
|
+
* `resolvedWallets` map. Two responsibilities here, both fail-fast:
|
|
25
|
+
*
|
|
26
|
+
* 1. `WORKFLOW_WALLET_NOT_FOUND` — every wallet name declared under
|
|
27
|
+
* `requires.wallets` must have an entry in `resolvedWallets`.
|
|
28
|
+
* Missing entry = the engine couldn't find the wallet for the
|
|
29
|
+
* workflow's project/org scope.
|
|
30
|
+
*
|
|
31
|
+
* 2. `DSL_EXPRESSION_INVALID` — every `${{ vars.X }}` / `${{ secrets.X }}`
|
|
32
|
+
* member access must resolve to a key in the union of the resolved
|
|
33
|
+
* wallets' contents (modulo the YAML-static `vars:` block which can
|
|
34
|
+
* also satisfy a `vars.X` lookup). Catches typos / stale references
|
|
35
|
+
* at compile time instead of a runtime evaluator failure.
|
|
36
|
+
*
|
|
37
|
+
* The function returns the canonical flat list emitted into
|
|
38
|
+
* `CompiledRun.requiredVariables` — the union of every wallet's
|
|
39
|
+
* `(key, isSecret)` entries with duplicates collapsed (last writer wins
|
|
40
|
+
* for `isSecret` if two wallets disagree; the engine surfaces the
|
|
41
|
+
* conflict at fetch time with a different code).
|
|
42
|
+
*/
|
|
43
|
+
export function resolveWalletRequirements(
|
|
44
|
+
doc: WorkflowDocument,
|
|
45
|
+
resolvedWallets: Readonly<Record<string, WalletContents>> | undefined,
|
|
46
|
+
): readonly WorkflowVariableRequirement[] {
|
|
47
|
+
const declaredWallets = doc.requires?.wallets ?? [];
|
|
48
|
+
|
|
49
|
+
if (resolvedWallets === undefined) {
|
|
50
|
+
// Engine opted out (preview-raw / tests). Skip both checks and
|
|
51
|
+
// emit an empty requirements list — `${{ vars.X }}` / `${{ secrets.X }}`
|
|
52
|
+
// references that don't satisfy the YAML-static fallback will still
|
|
53
|
+
// surface at runtime, which is the documented contract for opt-out.
|
|
54
|
+
return Object.freeze([]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const missingWallets: string[] = [];
|
|
58
|
+
for (const name of declaredWallets) {
|
|
59
|
+
if (!Object.hasOwn(resolvedWallets, name)) {
|
|
60
|
+
missingWallets.push(name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (missingWallets.length > 0) {
|
|
64
|
+
throw new WorkflowDslError(
|
|
65
|
+
WorkflowErrorCode.WORKFLOW_WALLET_NOT_FOUND,
|
|
66
|
+
`Wallet(s) declared in requires.wallets are not available for this project/org scope: ${missingWallets.join(', ')}`,
|
|
67
|
+
{ missing: missingWallets },
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const merged = new Map<string, WorkflowVariableRequirement>();
|
|
72
|
+
for (const name of declaredWallets) {
|
|
73
|
+
const wallet = resolvedWallets[name];
|
|
74
|
+
if (!wallet) {
|
|
75
|
+
// `declaredWallets` is derived from the same `resolvedWallets`
|
|
76
|
+
// map upstream, so a missing entry here would be an internal
|
|
77
|
+
// invariant violation — surface it instead of silently producing
|
|
78
|
+
// an empty merge.
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Internal: declaredWallets references unresolved wallet '${name}'.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
for (const entry of wallet.entries) {
|
|
84
|
+
merged.set(entry.key, {
|
|
85
|
+
name: entry.key,
|
|
86
|
+
secret: entry.isSecret,
|
|
87
|
+
optional: false,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return Object.freeze([...merged.values()]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Walk every `${{ ... }}` expression body in the workflow and, for each
|
|
96
|
+
* `vars.<NAME>` / `secrets.<NAME>` member access, verify the referenced
|
|
97
|
+
* name is satisfiable from the resolved wallets (or, for `vars.*` only,
|
|
98
|
+
* from the YAML-static `vars:` block).
|
|
99
|
+
*/
|
|
100
|
+
export function validateVariableReferences(
|
|
101
|
+
doc: WorkflowDocument,
|
|
102
|
+
requiredVariables: readonly WorkflowVariableRequirement[],
|
|
103
|
+
resolvedWallets: Readonly<Record<string, WalletContents>> | undefined,
|
|
104
|
+
): void {
|
|
105
|
+
if (resolvedWallets === undefined) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const staticVarNames = new Set(Object.keys(doc.vars ?? {}));
|
|
109
|
+
const allowedVars = new Set<string>(staticVarNames);
|
|
110
|
+
const allowedSecrets = new Set<string>();
|
|
111
|
+
for (const r of requiredVariables) {
|
|
112
|
+
if (r.secret) {
|
|
113
|
+
allowedSecrets.add(r.name);
|
|
114
|
+
} else {
|
|
115
|
+
allowedVars.add(r.name);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const { source, pathLabel } of collectAllExpressions(doc)) {
|
|
120
|
+
const ast = compileExpression(source);
|
|
121
|
+
walkVariableAccesses(ast, (root, name) => {
|
|
122
|
+
if (root === ExpressionRoot.VARS && !allowedVars.has(name)) {
|
|
123
|
+
throw new WorkflowDslError(
|
|
124
|
+
WorkflowErrorCode.DSL_EXPRESSION_INVALID,
|
|
125
|
+
`Expression at ${pathLabel} reads vars.${name}, but '${name}' is not provided by workflow.vars or any wallet in requires.wallets.`,
|
|
126
|
+
{ pathLabel, root, name, source },
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (root === ExpressionRoot.SECRETS && !allowedSecrets.has(name)) {
|
|
130
|
+
throw new WorkflowDslError(
|
|
131
|
+
WorkflowErrorCode.DSL_EXPRESSION_INVALID,
|
|
132
|
+
`Expression at ${pathLabel} reads secrets.${name}, but '${name}' is not provided by any wallet in requires.wallets (or the wallet entry is not marked secret).`,
|
|
133
|
+
{ pathLabel, root, name, source },
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface CollectedExpression {
|
|
141
|
+
readonly source: string;
|
|
142
|
+
readonly pathLabel: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function collectAllExpressions(doc: WorkflowDocument): CollectedExpression[] {
|
|
146
|
+
const out: CollectedExpression[] = [];
|
|
147
|
+
for (const [jobKey, job] of Object.entries(doc.jobs)) {
|
|
148
|
+
for (const expr of extractInterpolations(job.with, ['jobs', jobKey, 'with'])) {
|
|
149
|
+
out.push({ source: expr.source, pathLabel: expr.path.join('.') });
|
|
150
|
+
}
|
|
151
|
+
for (const expr of extractInterpolations(job.outputs, ['jobs', jobKey, 'outputs'])) {
|
|
152
|
+
out.push({ source: expr.source, pathLabel: expr.path.join('.') });
|
|
153
|
+
}
|
|
154
|
+
if (typeof job.if === 'string' && job.if.length > 0) {
|
|
155
|
+
out.push({ source: stripInterpolation(job.if), pathLabel: `jobs.${jobKey}.if` });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Visits every `<root>.<name>` member access in the AST and invokes
|
|
163
|
+
* `onAccess(root, name)`. Other access shapes are ignored — the caller
|
|
164
|
+
* validates only the first hop, which is sufficient because both
|
|
165
|
+
* `vars.*` and `secrets.*` are string-keyed flat maps.
|
|
166
|
+
*/
|
|
167
|
+
function walkVariableAccesses(
|
|
168
|
+
node: ExpressionNode,
|
|
169
|
+
onAccess: (root: ExpressionRoot, name: string) => void,
|
|
170
|
+
): void {
|
|
171
|
+
switch (node.kind) {
|
|
172
|
+
case ExpressionNodeKind.MEMBER: {
|
|
173
|
+
const target = node.target;
|
|
174
|
+
if (
|
|
175
|
+
target.kind === ExpressionNodeKind.IDENTIFIER &&
|
|
176
|
+
(target.name === ExpressionRoot.VARS ||
|
|
177
|
+
target.name === ExpressionRoot.SECRETS)
|
|
178
|
+
) {
|
|
179
|
+
onAccess(target.name as ExpressionRoot, node.property);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
walkVariableAccesses(target, onAccess);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
case ExpressionNodeKind.INDEX:
|
|
186
|
+
walkVariableAccesses(node.target, onAccess);
|
|
187
|
+
walkVariableAccesses(node.index, onAccess);
|
|
188
|
+
return;
|
|
189
|
+
case ExpressionNodeKind.CALL:
|
|
190
|
+
for (const arg of node.args) {
|
|
191
|
+
walkVariableAccesses(arg, onAccess);
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
case ExpressionNodeKind.UNARY_NOT:
|
|
195
|
+
walkVariableAccesses(node.operand, onAccess);
|
|
196
|
+
return;
|
|
197
|
+
case ExpressionNodeKind.BINARY_EQ:
|
|
198
|
+
case ExpressionNodeKind.BINARY_NEQ:
|
|
199
|
+
case ExpressionNodeKind.BINARY_AND:
|
|
200
|
+
case ExpressionNodeKind.BINARY_OR:
|
|
201
|
+
walkVariableAccesses(node.left, onAccess);
|
|
202
|
+
walkVariableAccesses(node.right, onAccess);
|
|
203
|
+
return;
|
|
204
|
+
case ExpressionNodeKind.IDENTIFIER:
|
|
205
|
+
case ExpressionNodeKind.LITERAL:
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static introspection of deliverable-spec content to recover the
|
|
3
|
+
* top-level keys an agent's output object MUST expose. The compiler uses
|
|
4
|
+
* these keys to validate workflow expressions of shape
|
|
5
|
+
* `${{ job.outputs.deliverable.content.value.<field> }}` against the
|
|
6
|
+
* spec the producing job declared via `with.deliverableSpecRef` —
|
|
7
|
+
* catching "the YAML reads `.changeUnits` but the spec has
|
|
8
|
+
* `.handoffPackage`" at compile time, not at runtime when projection
|
|
9
|
+
* blows up.
|
|
10
|
+
*
|
|
11
|
+
* Two extractors:
|
|
12
|
+
* - `extractZodTopLevelObjectKeys`: parses a `z.object({ ... })`
|
|
13
|
+
* literal out of TypeScript Zod source. Same regex-based brace walker
|
|
14
|
+
* the deliverable-specs-api ZodSchemaHandler uses for harvest-time
|
|
15
|
+
* validation; single source of truth lives here so the DSL compiler
|
|
16
|
+
* and the harvester agree.
|
|
17
|
+
* - `extractJsonSchemaTopLevelKeys`: pulls keys from the `properties`
|
|
18
|
+
* object of a JSON Schema document.
|
|
19
|
+
*
|
|
20
|
+
* Both return `null` when introspection fails so callers can skip the
|
|
21
|
+
* field check (preview / specs without introspectable shape).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse the FIRST `z.object({ ... })` literal in the source and return
|
|
26
|
+
* the top-level field identifiers. Returns `null` when the literal is
|
|
27
|
+
* absent or its braces are unbalanced — the spec layer fails fast on
|
|
28
|
+
* such specs at content-validation time, but here in the compiler we
|
|
29
|
+
* gracefully degrade so a malformed spec doesn't block unrelated
|
|
30
|
+
* expression checks elsewhere.
|
|
31
|
+
*/
|
|
32
|
+
export function extractZodTopLevelObjectKeys(source: string): readonly string[] | null {
|
|
33
|
+
if (!source || source.trim().length === 0) return null;
|
|
34
|
+
const body = sliceTopLevelObjectBody(source);
|
|
35
|
+
if (body === null) return null;
|
|
36
|
+
return Object.freeze(parseObjectKeysAtTopLevel(body));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Pull the top-level field names from a JSON Schema document. Accepts
|
|
41
|
+
* either a parsed object or a raw JSON string. Honors `properties` —
|
|
42
|
+
* the conventional shape — and ignores `patternProperties` /
|
|
43
|
+
* `additionalProperties` / Draft 2020 `unevaluatedProperties` because
|
|
44
|
+
* those don't enumerate concrete keys an author can reference by name.
|
|
45
|
+
*
|
|
46
|
+
* Returns `null` when the schema is unparseable or has no `properties`
|
|
47
|
+
* map.
|
|
48
|
+
*/
|
|
49
|
+
export function extractJsonSchemaTopLevelKeys(
|
|
50
|
+
source: string | object,
|
|
51
|
+
): readonly string[] | null {
|
|
52
|
+
let parsed: unknown;
|
|
53
|
+
if (typeof source === 'string') {
|
|
54
|
+
if (source.trim().length === 0) return null;
|
|
55
|
+
try {
|
|
56
|
+
parsed = JSON.parse(source);
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
parsed = source;
|
|
62
|
+
}
|
|
63
|
+
if (!isPlainObject(parsed)) return null;
|
|
64
|
+
const properties = (parsed as Record<string, unknown>)['properties'];
|
|
65
|
+
if (!isPlainObject(properties)) return null;
|
|
66
|
+
const keys = Object.keys(properties);
|
|
67
|
+
return Object.freeze(keys);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
71
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Substring between matching braces of the first `z.object({ ... })`, or null. */
|
|
75
|
+
function sliceTopLevelObjectBody(source: string): string | null {
|
|
76
|
+
const match = /z\.object\s*\(\s*\{/.exec(source);
|
|
77
|
+
if (!match) return null;
|
|
78
|
+
const start = match.index + match[0].length;
|
|
79
|
+
let depth = 1;
|
|
80
|
+
for (let i = start; i < source.length; i += 1) {
|
|
81
|
+
const ch = source[i];
|
|
82
|
+
if (ch === '{') depth += 1;
|
|
83
|
+
else if (ch === '}' && --depth === 0) return source.slice(start, i);
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Walk the body once, recording identifiers at brace+paren depth 0. */
|
|
89
|
+
function parseObjectKeysAtTopLevel(body: string): string[] {
|
|
90
|
+
const keys: string[] = [];
|
|
91
|
+
const depth = { brace: 0, paren: 0 };
|
|
92
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
93
|
+
if (updateDepth(depth, body[i])) continue;
|
|
94
|
+
if (depth.brace !== 0 || depth.paren !== 0) continue;
|
|
95
|
+
if (i !== 0 && body[i] !== '\n' && body[i] !== ',') continue;
|
|
96
|
+
const rest = body.slice(i === 0 ? 0 : i + 1);
|
|
97
|
+
const keyMatch = /^\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*:/.exec(rest);
|
|
98
|
+
if (keyMatch?.[1]) keys.push(keyMatch[1]);
|
|
99
|
+
}
|
|
100
|
+
return keys;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function updateDepth(depth: { brace: number; paren: number }, ch: string | undefined): boolean {
|
|
104
|
+
if (ch === '{') { depth.brace += 1; return true; }
|
|
105
|
+
if (ch === '}') { depth.brace = Math.max(0, depth.brace - 1); return true; }
|
|
106
|
+
if (ch === '(') { depth.paren += 1; return true; }
|
|
107
|
+
if (ch === ')') { depth.paren = Math.max(0, depth.paren - 1); return true; }
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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: hours limited to 8760 (= 365 days). The cap is a sanity guard
|
|
11
|
+
* against typos (`100000h` ≈ 11 years), not a policy bound — real
|
|
12
|
+
* human-driven flows (compliance reviews, multi-stakeholder approvals,
|
|
13
|
+
* SCM PRs that go a quarter without merging) routinely take months.
|
|
14
|
+
*
|
|
15
|
+
* For redraft loops (`xema/review@v3`), the workflow `continueAsNew`s
|
|
16
|
+
* every 25 iterations, which resets the runtime clock — the practical
|
|
17
|
+
* ceiling on a single review is unbounded. This cap applies only to a
|
|
18
|
+
* single iteration window of the child workflow.
|
|
19
|
+
*/
|
|
20
|
+
const MAX_HOURS = 8760;
|
|
21
|
+
|
|
22
|
+
export function parseDurationMs(literal: string): number {
|
|
23
|
+
const match = /^([1-9][0-9]*)(s|m|h)$/.exec(literal);
|
|
24
|
+
if (!match) {
|
|
25
|
+
throw new WorkflowDslError(
|
|
26
|
+
WorkflowErrorCode.DSL_SCHEMA_INVALID,
|
|
27
|
+
`Duration literal does not match pattern <int><s|m|h>: ${literal}`,
|
|
28
|
+
{ literal },
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const value = Number.parseInt(match[1]!, 10);
|
|
32
|
+
const unit = match[2] as 's' | 'm' | 'h';
|
|
33
|
+
if (unit === 'h' && value > MAX_HOURS) {
|
|
34
|
+
throw new WorkflowDslError(
|
|
35
|
+
WorkflowErrorCode.DSL_SCHEMA_INVALID,
|
|
36
|
+
`Duration exceeds cap of ${MAX_HOURS}h: ${literal}`,
|
|
37
|
+
{ literal, maxHours: MAX_HOURS },
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
switch (unit) {
|
|
41
|
+
case 's':
|
|
42
|
+
return value * 1_000;
|
|
43
|
+
case 'm':
|
|
44
|
+
return value * 60_000;
|
|
45
|
+
case 'h':
|
|
46
|
+
return value * 3_600_000;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
}
|