@xemahq/dsl 0.8.0 → 0.8.2
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/dist/payload-codec/lib/blob-store.js +0 -0
- package/dist/payload-codec/lib/blob-store.js.map +1 -1
- package/package.json +3 -4
- package/src/deliverable-spec/index.ts +0 -19
- package/src/deliverable-spec/lib/schema.ts +0 -270
- package/src/deliverable-spec/lib/types.ts +0 -26
- package/src/payload-codec/index.ts +0 -44
- package/src/payload-codec/lib/blob-store.ts +0 -0
- package/src/payload-codec/lib/codec-context.ts +0 -38
- package/src/payload-codec/lib/codec.ts +0 -605
- package/src/payload-codec/lib/enums.ts +0 -58
- package/src/payload-codec/lib/errors.ts +0 -54
- package/src/payload-codec/lib/http-blob-store.ts +0 -267
- package/src/payload-codec/lib/lru-cache.ts +0 -81
- package/src/payload-codec/lib/payload.ts +0 -26
- package/src/payload-codec/temporal/index.ts +0 -36
- package/src/workflow/index.ts +0 -108
- package/src/workflow/lib/action-input-validator.ts +0 -160
- package/src/workflow/lib/compiler/action-shape.ts +0 -71
- package/src/workflow/lib/compiler/canonical-json.ts +0 -66
- package/src/workflow/lib/compiler/compile.ts +0 -1742
- package/src/workflow/lib/compiler/concurrency.ts +0 -223
- package/src/workflow/lib/compiler/dag.ts +0 -108
- package/src/workflow/lib/compiler/gate-defaults.ts +0 -153
- package/src/workflow/lib/compiler/index.ts +0 -11
- package/src/workflow/lib/compiler/inputs.ts +0 -254
- package/src/workflow/lib/compiler/installation-resource-validator.ts +0 -114
- package/src/workflow/lib/compiler/manifest-source.ts +0 -71
- package/src/workflow/lib/compiler/matrix.ts +0 -135
- package/src/workflow/lib/compiler/mount-plan.ts +0 -190
- package/src/workflow/lib/compiler/payload-reach-in.ts +0 -497
- package/src/workflow/lib/compiler/permissions.ts +0 -64
- package/src/workflow/lib/compiler/retry-timeout.ts +0 -105
- package/src/workflow/lib/compiler/review-step.ts +0 -548
- package/src/workflow/lib/compiler/types.ts +0 -172
- package/src/workflow/lib/compiler/variable-requirements.ts +0 -208
- package/src/workflow/lib/deliverable-spec-introspection-error.ts +0 -63
- package/src/workflow/lib/deliverable-spec-keys.ts +0 -147
- package/src/workflow/lib/deliverable-spec-source-scan.ts +0 -280
- package/src/workflow/lib/dispatch-inputs/index.ts +0 -160
- package/src/workflow/lib/dispatch-inputs/to-json-schema.ts +0 -60
- package/src/workflow/lib/duration.ts +0 -43
- package/src/workflow/lib/errors.ts +0 -37
- package/src/workflow/lib/expression/ast.ts +0 -108
- package/src/workflow/lib/expression/context.ts +0 -148
- package/src/workflow/lib/expression/evaluator.ts +0 -492
- package/src/workflow/lib/expression/index.ts +0 -28
- package/src/workflow/lib/expression/interpolation.ts +0 -84
- package/src/workflow/lib/expression/parser.ts +0 -264
- package/src/workflow/lib/expression/template.ts +0 -117
- package/src/workflow/lib/expression/tokenizer.ts +0 -200
- package/src/workflow/lib/expression/tokens.ts +0 -30
- package/src/workflow/lib/expression/walk-artifact-refs.ts +0 -232
- package/src/workflow/lib/installation-resource-kind.ts +0 -107
- package/src/workflow/lib/schemas-loader.ts +0 -64
- package/src/workflow/lib/serializer.ts +0 -30
- package/src/workflow/lib/types.ts +0 -417
- package/src/workflow/lib/validate.ts +0 -199
- package/src/workspace-manifest/index.ts +0 -27
- package/src/workspace-manifest/lib/compile.ts +0 -619
- package/src/workspace-manifest/lib/interpolate.ts +0 -166
- package/src/workspace-manifest/lib/resolve-extends.ts +0 -260
- package/src/workspace-manifest/lib/schema.ts +0 -692
- package/src/workspace-manifest/lib/types.ts +0 -446
|
@@ -1,548 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compile-time helpers for `xema/review@*` steps.
|
|
3
|
-
*
|
|
4
|
-
* The review action's author surface is intentionally small:
|
|
5
|
-
*
|
|
6
|
-
* uses: xema/review@<semver>
|
|
7
|
-
* needs: [<producer>]
|
|
8
|
-
* with:
|
|
9
|
-
* subject: ${{ needs.<producer>.outputs.deliverable }} # or an array
|
|
10
|
-
* redraft: { step: <producer> } # optional
|
|
11
|
-
* reviewers: [...]
|
|
12
|
-
* policy: { kind: ... }
|
|
13
|
-
* iterationTimeoutSeconds: ...
|
|
14
|
-
* onIterationTimeout: ...
|
|
15
|
-
*
|
|
16
|
-
* The worker contract on `reviewWorkflow` is different: it expects
|
|
17
|
-
* `subjects: unknown[]` (always-array) and `redraft: { uses, with }`
|
|
18
|
-
* (embedded action invocation, not a step ref). This module bridges
|
|
19
|
-
* the two:
|
|
20
|
-
*
|
|
21
|
-
* 1. `validateReviewSteps` — throws on bad author shapes BEFORE
|
|
22
|
-
* anything else compiles. Catches missing/dangling step refs,
|
|
23
|
-
* shape mismatches, and the forbidden `redraft set without
|
|
24
|
-
* subject` combination.
|
|
25
|
-
*
|
|
26
|
-
* 2. `computeReviewNeedsExtensions` — returns a map of
|
|
27
|
-
* `reviewJobKey → string[]` of extra needs the compiler must add
|
|
28
|
-
* so the review step's expression context (built from `needs:` at
|
|
29
|
-
* dispatch) can resolve every `${{ needs.* }}` reference embedded
|
|
30
|
-
* in the redraft step's inherited `with:` block. Authors don't
|
|
31
|
-
* have to repeat the producer's transitive needs by hand.
|
|
32
|
-
*
|
|
33
|
-
* 3. `rewriteReviewStepWith` — produces the IR `with:` for a review
|
|
34
|
-
* step: `subject` normalized to `subjects: unknown[]`, and
|
|
35
|
-
* `redraft.step` resolved into the named producer's
|
|
36
|
-
* `{ uses, with }`.
|
|
37
|
-
*
|
|
38
|
-
* The action manifest's input schema (in built-in-action-manifests.ts)
|
|
39
|
-
* describes the AUTHOR shape. The IR shape produced here is the
|
|
40
|
-
* worker's runtime contract — they intentionally diverge.
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
import {
|
|
44
|
-
WorkflowErrorCode,
|
|
45
|
-
type CompiledManifestSource,
|
|
46
|
-
} from '@xemahq/kernel-contracts/workflow';
|
|
47
|
-
|
|
48
|
-
import { WorkflowDslError } from '../errors';
|
|
49
|
-
import {
|
|
50
|
-
compileExpression,
|
|
51
|
-
ExpressionNodeKind,
|
|
52
|
-
type ExpressionNode,
|
|
53
|
-
} from '../expression';
|
|
54
|
-
import { stripInterpolation } from '../expression/interpolation';
|
|
55
|
-
import type {
|
|
56
|
-
WorkflowDocument,
|
|
57
|
-
WorkflowJobDeclaration,
|
|
58
|
-
WorkflowStrategyDeclaration,
|
|
59
|
-
} from '../types';
|
|
60
|
-
|
|
61
|
-
/** Matches `xema/review@<semver>` (1, 2, or 3 dotted version segments). */
|
|
62
|
-
const REVIEW_ACTION_RE =
|
|
63
|
-
/^xema\/review@[0-9]+(\.[0-9]+(\.[0-9]+(-[A-Za-z0-9.-]+)?)?)?$/;
|
|
64
|
-
|
|
65
|
-
/** Matches `xema/agent@<semver>` — only valid `redraft.step` producer kind. */
|
|
66
|
-
const AGENT_ACTION_RE =
|
|
67
|
-
/^xema\/agent@[0-9]+(\.[0-9]+(\.[0-9]+(-[A-Za-z0-9.-]+)?)?)?$/;
|
|
68
|
-
|
|
69
|
-
export function isReviewActionRef(uses: string): boolean {
|
|
70
|
-
return REVIEW_ACTION_RE.test(uses);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
interface ReviewWithShape {
|
|
74
|
-
readonly subject: string | readonly string[] | undefined;
|
|
75
|
-
readonly redraft: { readonly step: string } | undefined;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Per-job redraft-strategy descriptor carried on the IR `redraft` block
|
|
80
|
-
* when the producer step has a matrix strategy. Mirrors the worker
|
|
81
|
-
* consumer that runs the review workflow — kept JSON-serializable so the
|
|
82
|
-
* IR survives Temporal payload encoding.
|
|
83
|
-
*
|
|
84
|
-
* `static` entries are already cartesian-expanded at compile time;
|
|
85
|
-
* `dynamic.fromExpression` is the producer's expression body (already
|
|
86
|
-
* `${{ … }}`-stripped) which the parent dispatcher evaluates against
|
|
87
|
-
* the review step's expression context before spawning the child
|
|
88
|
-
* workflow. `keyBy` is informational only — per-CU keyed targeting is
|
|
89
|
-
* a planned future feature; today every matrix entry is always re-run.
|
|
90
|
-
*/
|
|
91
|
-
export type ReviewRedraftStrategyIR =
|
|
92
|
-
| {
|
|
93
|
-
readonly kind: 'static';
|
|
94
|
-
readonly entries: readonly Readonly<Record<string, unknown>>[];
|
|
95
|
-
readonly maxParallel: number;
|
|
96
|
-
}
|
|
97
|
-
| {
|
|
98
|
-
readonly kind: 'dynamic';
|
|
99
|
-
readonly fromExpression: string;
|
|
100
|
-
readonly bindingName: string;
|
|
101
|
-
readonly maxEntries: number;
|
|
102
|
-
readonly maxParallel: number;
|
|
103
|
-
readonly keyBy: string | null;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Lightweight typed projection of a review step's `with:` block.
|
|
108
|
-
* Throws DSL_SEMANTIC_INVALID on malformed shape; downstream consumers
|
|
109
|
-
* (rewriter, validators) trust the projection.
|
|
110
|
-
*/
|
|
111
|
-
function projectReviewWith(
|
|
112
|
-
jobKey: string,
|
|
113
|
-
rawWith: Readonly<Record<string, unknown>> | undefined,
|
|
114
|
-
): ReviewWithShape {
|
|
115
|
-
const w = rawWith ?? {};
|
|
116
|
-
const subjectRaw = (w as Record<string, unknown>)['subject'];
|
|
117
|
-
let subject: ReviewWithShape['subject'];
|
|
118
|
-
if (subjectRaw === undefined) {
|
|
119
|
-
subject = undefined;
|
|
120
|
-
} else if (typeof subjectRaw === 'string') {
|
|
121
|
-
if (subjectRaw.length === 0) {
|
|
122
|
-
throw new WorkflowDslError(
|
|
123
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
124
|
-
`Job '${jobKey}' with.subject must be a non-empty string or array of strings.`,
|
|
125
|
-
{ jobKey, fieldPath: 'with.subject' },
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
subject = subjectRaw;
|
|
129
|
-
} else if (Array.isArray(subjectRaw)) {
|
|
130
|
-
if (subjectRaw.length === 0) {
|
|
131
|
-
throw new WorkflowDslError(
|
|
132
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
133
|
-
`Job '${jobKey}' with.subject must be a non-empty array (got []).`,
|
|
134
|
-
{ jobKey, fieldPath: 'with.subject' },
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
for (let i = 0; i < subjectRaw.length; i++) {
|
|
138
|
-
const item = subjectRaw[i];
|
|
139
|
-
if (typeof item !== 'string' || item.length === 0) {
|
|
140
|
-
throw new WorkflowDslError(
|
|
141
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
142
|
-
`Job '${jobKey}' with.subject[${i}] must be a non-empty string.`,
|
|
143
|
-
{ jobKey, fieldPath: `with.subject[${i}]` },
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
subject = subjectRaw as readonly string[];
|
|
148
|
-
} else {
|
|
149
|
-
throw new WorkflowDslError(
|
|
150
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
151
|
-
`Job '${jobKey}' with.subject must be a string or an array of strings (got ${typeof subjectRaw}).`,
|
|
152
|
-
{ jobKey, fieldPath: 'with.subject' },
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const redraftRaw = (w as Record<string, unknown>)['redraft'];
|
|
157
|
-
let redraft: ReviewWithShape['redraft'];
|
|
158
|
-
if (redraftRaw === undefined) {
|
|
159
|
-
redraft = undefined;
|
|
160
|
-
} else if (
|
|
161
|
-
typeof redraftRaw === 'object' &&
|
|
162
|
-
redraftRaw !== null &&
|
|
163
|
-
!Array.isArray(redraftRaw)
|
|
164
|
-
) {
|
|
165
|
-
const stepRaw = (redraftRaw as Record<string, unknown>)['step'];
|
|
166
|
-
if (typeof stepRaw !== 'string' || stepRaw.length === 0) {
|
|
167
|
-
throw new WorkflowDslError(
|
|
168
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
169
|
-
`Job '${jobKey}' with.redraft.step must be a non-empty string naming a producer step.`,
|
|
170
|
-
{ jobKey, fieldPath: 'with.redraft.step' },
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
redraft = { step: stepRaw };
|
|
174
|
-
} else {
|
|
175
|
-
throw new WorkflowDslError(
|
|
176
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
177
|
-
`Job '${jobKey}' with.redraft must be an object of shape '{ step: <stepName> }'.`,
|
|
178
|
-
{ jobKey, fieldPath: 'with.redraft' },
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return { subject, redraft };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Validate every review step in the workflow. Run before topo sort +
|
|
187
|
-
* expression validation so author errors surface with clear messages
|
|
188
|
-
* before downstream passes throw less-helpful errors.
|
|
189
|
-
*/
|
|
190
|
-
export function validateReviewSteps(workflow: WorkflowDocument): void {
|
|
191
|
-
for (const [jobKey, job] of Object.entries(workflow.jobs)) {
|
|
192
|
-
if (!isReviewActionRef(job.uses)) {
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
const shape = projectReviewWith(jobKey, job.with);
|
|
196
|
-
|
|
197
|
-
// `redraft` set without `subject` is forbidden — the producer
|
|
198
|
-
// must be authored as a separate step that publishes a deliverable
|
|
199
|
-
// as the iter-1 subject. This keeps the standalone draft step
|
|
200
|
-
// observable in the run UI and avoids the "ghost step that runs
|
|
201
|
-
// but is ignored" footgun.
|
|
202
|
-
if (shape.redraft !== undefined && shape.subject === undefined) {
|
|
203
|
-
throw new WorkflowDslError(
|
|
204
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
205
|
-
`Job '${jobKey}' has 'redraft' set but no 'subject'. Author the producer as a separate step and reference it via 'subject: \${{ needs.${shape.redraft.step}.outputs.deliverable }}'.`,
|
|
206
|
-
{ jobKey, fieldPath: 'with' },
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (shape.redraft !== undefined) {
|
|
211
|
-
const stepName = shape.redraft.step;
|
|
212
|
-
const producer = workflow.jobs[stepName];
|
|
213
|
-
if (!producer) {
|
|
214
|
-
throw new WorkflowDslError(
|
|
215
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
216
|
-
`Job '${jobKey}' with.redraft.step references unknown job '${stepName}'.`,
|
|
217
|
-
{ jobKey, fieldPath: 'with.redraft.step', referencedJob: stepName },
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
if (stepName === jobKey) {
|
|
221
|
-
throw new WorkflowDslError(
|
|
222
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
223
|
-
`Job '${jobKey}' with.redraft.step cannot reference itself.`,
|
|
224
|
-
{ jobKey, fieldPath: 'with.redraft.step' },
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
if (!AGENT_ACTION_RE.test(producer.uses)) {
|
|
228
|
-
throw new WorkflowDslError(
|
|
229
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
230
|
-
`Job '${jobKey}' with.redraft.step = '${stepName}' must reference a step that uses 'xema/agent@<semver>' (got '${producer.uses}').`,
|
|
231
|
-
{
|
|
232
|
-
jobKey,
|
|
233
|
-
fieldPath: 'with.redraft.step',
|
|
234
|
-
referencedJob: stepName,
|
|
235
|
-
referencedUses: producer.uses,
|
|
236
|
-
},
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
// Matrix-strategy producers ARE allowed here — on `reject`, the
|
|
240
|
-
// worker re-dispatches every matrix entry of the producer in
|
|
241
|
-
// parallel (one entry → one redraft agent dispatch). The DSL
|
|
242
|
-
// pre-resolves the matrix shape onto the IR `redraft.strategy`
|
|
243
|
-
// block so the parent dispatcher (root-run.workflow.ts) can
|
|
244
|
-
// evaluate dynamic `from:` expressions against the review step's
|
|
245
|
-
// expression context before spawning the child review workflow.
|
|
246
|
-
const declaredNeeds = job.needs ?? [];
|
|
247
|
-
if (!declaredNeeds.includes(stepName)) {
|
|
248
|
-
throw new WorkflowDslError(
|
|
249
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
250
|
-
`Job '${jobKey}' with.redraft.step = '${stepName}' must also be listed in 'needs:'. Add 'needs: [${[...declaredNeeds, stepName].map((s) => `'${s}'`).join(', ')}]'.`,
|
|
251
|
-
{
|
|
252
|
-
jobKey,
|
|
253
|
-
fieldPath: 'with.redraft.step',
|
|
254
|
-
referencedJob: stepName,
|
|
255
|
-
},
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Compute extra `needs:` entries each review step requires beyond what
|
|
264
|
-
* the author declared. Specifically: for any review step with
|
|
265
|
-
* `redraft.step: <producer>`, add `<producer>`'s OWN `needs:` list so
|
|
266
|
-
* the review step's expression context (built from its `needs:`) has
|
|
267
|
-
* every upstream value the embedded redraft `with:` may reference at
|
|
268
|
-
* dispatch time.
|
|
269
|
-
*
|
|
270
|
-
* Returns an empty map when no review steps have a redraft. The caller
|
|
271
|
-
* folds these into the topo-sort input alongside `matrixGather`.
|
|
272
|
-
*/
|
|
273
|
-
export function computeReviewNeedsExtensions(
|
|
274
|
-
workflow: WorkflowDocument,
|
|
275
|
-
): Readonly<Record<string, readonly string[]>> {
|
|
276
|
-
const out: Record<string, readonly string[]> = {};
|
|
277
|
-
for (const [jobKey, job] of Object.entries(workflow.jobs)) {
|
|
278
|
-
if (!isReviewActionRef(job.uses)) {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
const shape = projectReviewWith(jobKey, job.with);
|
|
282
|
-
if (shape.redraft === undefined) {
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
const producer = workflow.jobs[shape.redraft.step];
|
|
286
|
-
if (!producer) {
|
|
287
|
-
// validateReviewSteps already throws for this case; defensive only.
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
const declared = new Set<string>(producer.needs ?? []);
|
|
291
|
-
// For dynamic-matrix producers, the `from:` expression is evaluated
|
|
292
|
-
// by the parent dispatcher at review-dispatch time against the
|
|
293
|
-
// review step's expression context — every `needs.<X>` it
|
|
294
|
-
// references MUST therefore be in the review step's `needs:` too.
|
|
295
|
-
if (producer.strategy !== undefined && 'dynamic' in producer.strategy) {
|
|
296
|
-
const fromRefs = collectNeedsReferencesFromExpression(
|
|
297
|
-
producer.strategy.dynamic.from,
|
|
298
|
-
);
|
|
299
|
-
for (const ref of fromRefs) {
|
|
300
|
-
declared.add(ref);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
if (declared.size === 0) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
out[jobKey] = Object.freeze([...declared]) as readonly string[];
|
|
307
|
-
}
|
|
308
|
-
return Object.freeze(out);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Walk a `${{ … }}`-wrapped expression body and collect every
|
|
313
|
-
* `needs.<X>` root reference. Used by the review-step needs extension
|
|
314
|
-
* pass when the redraft producer is a dynamic matrix — its `from:`
|
|
315
|
-
* expression resolves at the parent dispatcher's review-dispatch time
|
|
316
|
-
* against the review step's expression context, so every referenced
|
|
317
|
-
* upstream step must be in the review step's `needs:` too. Returns a
|
|
318
|
-
* deduplicated sorted-by-insertion list of step keys.
|
|
319
|
-
*/
|
|
320
|
-
function collectNeedsReferencesFromExpression(
|
|
321
|
-
rawExpression: string,
|
|
322
|
-
): readonly string[] {
|
|
323
|
-
const body = stripInterpolation(rawExpression);
|
|
324
|
-
const ast = compileExpression(body);
|
|
325
|
-
const seen = new Set<string>();
|
|
326
|
-
walkForNeedsRefs(ast, seen);
|
|
327
|
-
return [...seen];
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function walkForNeedsRefs(node: ExpressionNode, sink: Set<string>): void {
|
|
331
|
-
switch (node.kind) {
|
|
332
|
-
case ExpressionNodeKind.MEMBER: {
|
|
333
|
-
// Match `needs.<X>` (target is the `needs` identifier).
|
|
334
|
-
if (
|
|
335
|
-
node.target.kind === ExpressionNodeKind.IDENTIFIER &&
|
|
336
|
-
node.target.name === 'needs'
|
|
337
|
-
) {
|
|
338
|
-
sink.add(node.property);
|
|
339
|
-
}
|
|
340
|
-
walkForNeedsRefs(node.target, sink);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
case ExpressionNodeKind.INDEX:
|
|
344
|
-
walkForNeedsRefs(node.target, sink);
|
|
345
|
-
walkForNeedsRefs(node.index, sink);
|
|
346
|
-
return;
|
|
347
|
-
case ExpressionNodeKind.CALL:
|
|
348
|
-
for (const arg of node.args) {
|
|
349
|
-
walkForNeedsRefs(arg, sink);
|
|
350
|
-
}
|
|
351
|
-
return;
|
|
352
|
-
case ExpressionNodeKind.UNARY_NOT:
|
|
353
|
-
walkForNeedsRefs(node.operand, sink);
|
|
354
|
-
return;
|
|
355
|
-
case ExpressionNodeKind.BINARY_EQ:
|
|
356
|
-
case ExpressionNodeKind.BINARY_NEQ:
|
|
357
|
-
case ExpressionNodeKind.BINARY_AND:
|
|
358
|
-
case ExpressionNodeKind.BINARY_OR:
|
|
359
|
-
walkForNeedsRefs(node.left, sink);
|
|
360
|
-
walkForNeedsRefs(node.right, sink);
|
|
361
|
-
return;
|
|
362
|
-
case ExpressionNodeKind.LITERAL:
|
|
363
|
-
case ExpressionNodeKind.IDENTIFIER:
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Rewrite a review step's `with:` block from the author shape to the
|
|
370
|
-
* worker contract:
|
|
371
|
-
*
|
|
372
|
-
* - Author `subject: <string>` → IR `subjects: [<string>]`
|
|
373
|
-
* - Author `subject: <string[]>` → IR `subjects: <string[]>`
|
|
374
|
-
* - Author `redraft: { step: X }` → IR `redraft: { uses, with }`
|
|
375
|
-
* looked up from the named producer step.
|
|
376
|
-
*
|
|
377
|
-
* Other fields pass through verbatim. Inputs that are not review steps
|
|
378
|
-
* fall back to `payload.with ?? {}` unchanged — callers may always
|
|
379
|
-
* route through this helper without an `isReviewActionRef` check.
|
|
380
|
-
*/
|
|
381
|
-
export function rewriteReviewStepWith(
|
|
382
|
-
jobKey: string,
|
|
383
|
-
payload: WorkflowJobDeclaration,
|
|
384
|
-
workflow: WorkflowDocument,
|
|
385
|
-
manifestSourcesByJob: Readonly<Record<string, CompiledManifestSource | null>>,
|
|
386
|
-
): Readonly<Record<string, unknown>> {
|
|
387
|
-
if (!isReviewActionRef(payload.uses)) {
|
|
388
|
-
return payload.with ?? {};
|
|
389
|
-
}
|
|
390
|
-
const shape = projectReviewWith(jobKey, payload.with);
|
|
391
|
-
const out: Record<string, unknown> = { ...(payload.with ?? {}) };
|
|
392
|
-
// Drop the author-only fields so the IR carries only worker-facing
|
|
393
|
-
// shapes.
|
|
394
|
-
delete out['subject'];
|
|
395
|
-
delete out['redraft'];
|
|
396
|
-
|
|
397
|
-
if (shape.subject !== undefined) {
|
|
398
|
-
out['subjects'] =
|
|
399
|
-
typeof shape.subject === 'string' ? [shape.subject] : [...shape.subject];
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (shape.redraft !== undefined) {
|
|
403
|
-
const producer = workflow.jobs[shape.redraft.step];
|
|
404
|
-
if (!producer) {
|
|
405
|
-
// validateReviewSteps already throws here; keep defensive.
|
|
406
|
-
throw new WorkflowDslError(
|
|
407
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
408
|
-
`Job '${jobKey}' with.redraft.step references unknown job '${shape.redraft.step}'.`,
|
|
409
|
-
{ jobKey, fieldPath: 'with.redraft.step' },
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
// Preserve the original step name on the IR `redraft` block. The
|
|
413
|
-
// worker uses it as the `artifactScope` override when the redraft's
|
|
414
|
-
// agent activity emits its outputs, so the regenerated bytes
|
|
415
|
-
// version the SAME logical artifact as the original draft (e.g.
|
|
416
|
-
// v2 of `draft.response`) rather than landing under the review
|
|
417
|
-
// step's identity (which would create a parallel `review.response`
|
|
418
|
-
// artifact and starve the reviewer's iter-2 view of the new draft).
|
|
419
|
-
//
|
|
420
|
-
// For matrix producers, also carry the producer's matrix shape
|
|
421
|
-
// onto the IR so the parent dispatcher (root-run.workflow.ts) can
|
|
422
|
-
// expand entries at review-dispatch time and pass the resolved
|
|
423
|
-
// bindings into the review child workflow. On `reject`, the worker
|
|
424
|
-
// re-dispatches every matrix entry in parallel — one redraft agent
|
|
425
|
-
// dispatch per entry.
|
|
426
|
-
const ir: Record<string, unknown> = {
|
|
427
|
-
step: shape.redraft.step,
|
|
428
|
-
uses: producer.uses,
|
|
429
|
-
with: producer.with ?? {},
|
|
430
|
-
manifestSource: requireRedraftManifestSource(
|
|
431
|
-
jobKey,
|
|
432
|
-
shape.redraft.step,
|
|
433
|
-
manifestSourcesByJob,
|
|
434
|
-
),
|
|
435
|
-
};
|
|
436
|
-
const strategy = compileRedraftStrategyIR(producer.strategy);
|
|
437
|
-
if (strategy !== null) {
|
|
438
|
-
ir['strategy'] = strategy;
|
|
439
|
-
}
|
|
440
|
-
if (producer.matrixGather && producer.matrixGather.length > 0) {
|
|
441
|
-
ir['matrixGather'] = [...producer.matrixGather];
|
|
442
|
-
}
|
|
443
|
-
out['redraft'] = ir;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return out;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function requireRedraftManifestSource(
|
|
450
|
-
reviewJobKey: string,
|
|
451
|
-
producerJobKey: string,
|
|
452
|
-
manifestSourcesByJob: Readonly<Record<string, CompiledManifestSource | null>>,
|
|
453
|
-
): CompiledManifestSource {
|
|
454
|
-
const source = manifestSourcesByJob[producerJobKey];
|
|
455
|
-
if (source === undefined || source === null) {
|
|
456
|
-
throw new WorkflowDslError(
|
|
457
|
-
WorkflowErrorCode.DSL_SEMANTIC_INVALID,
|
|
458
|
-
`Job '${reviewJobKey}' redraft producer '${producerJobKey}' has no compiled Agent revision source.`,
|
|
459
|
-
{
|
|
460
|
-
jobKey: reviewJobKey,
|
|
461
|
-
fieldPath: 'with.redraft.step',
|
|
462
|
-
referencedJob: producerJobKey,
|
|
463
|
-
},
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
return source;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Pre-resolve a producer step's authored matrix strategy into a
|
|
471
|
-
* JSON-serializable IR descriptor the worker carries through the
|
|
472
|
-
* review step's `with.redraft`. `null` for non-matrix producers — the
|
|
473
|
-
* worker takes the legacy single-dispatch path when `strategy` is
|
|
474
|
-
* absent on the IR `redraft` block. Static matrices are NOT
|
|
475
|
-
* cartesian-expanded here (that's the job of `compileStrategy` which
|
|
476
|
-
* runs against the producer's own job IR); we propagate the raw axes
|
|
477
|
-
* because the worker reuses the same compile-time expansion behavior
|
|
478
|
-
* via the parent dispatcher's per-job CompiledJob lookup. Keeping this
|
|
479
|
-
* descriptor small and shape-faithful avoids drift with
|
|
480
|
-
* `compileStrategy`'s output — the parent dispatcher reads the
|
|
481
|
-
* producer's own `CompiledJob.strategy` (Source of truth) and uses
|
|
482
|
-
* THIS descriptor only as a discriminator hint + entry-resolution
|
|
483
|
-
* fallback. Mirror of the worker contract `ReviewRedraftStrategyIR`.
|
|
484
|
-
*/
|
|
485
|
-
function compileRedraftStrategyIR(
|
|
486
|
-
strategy: WorkflowStrategyDeclaration | undefined,
|
|
487
|
-
): ReviewRedraftStrategyIR | null {
|
|
488
|
-
if (strategy === undefined) {
|
|
489
|
-
return null;
|
|
490
|
-
}
|
|
491
|
-
if ('matrix' in strategy) {
|
|
492
|
-
// Carry static axes; the worker side mirrors `compileStrategy`'s
|
|
493
|
-
// cartesian expansion. `maxParallel` defaults align with
|
|
494
|
-
// `matrix.ts:resolveMaxParallel` — when absent, the worker uses 8.
|
|
495
|
-
const entries = expandStaticMatrixAxes(strategy.matrix);
|
|
496
|
-
const maxParallel =
|
|
497
|
-
strategy.maxParallel !== undefined && strategy.maxParallel > 0
|
|
498
|
-
? Math.min(strategy.maxParallel, entries.length || 1)
|
|
499
|
-
: Math.min(8, Math.max(1, entries.length));
|
|
500
|
-
return {
|
|
501
|
-
kind: 'static',
|
|
502
|
-
entries,
|
|
503
|
-
maxParallel,
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
const dyn = strategy.dynamic;
|
|
507
|
-
const body = stripInterpolation(dyn.from);
|
|
508
|
-
const maxEntries = dyn.maxEntries ?? 64;
|
|
509
|
-
const maxParallel =
|
|
510
|
-
strategy.maxParallel !== undefined && strategy.maxParallel > 0
|
|
511
|
-
? Math.min(strategy.maxParallel, maxEntries)
|
|
512
|
-
: Math.min(8, maxEntries);
|
|
513
|
-
return {
|
|
514
|
-
kind: 'dynamic',
|
|
515
|
-
fromExpression: body,
|
|
516
|
-
bindingName: dyn.as,
|
|
517
|
-
maxEntries,
|
|
518
|
-
maxParallel,
|
|
519
|
-
keyBy: dyn.keyBy ?? null,
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Cartesian expansion of a static-matrix axes block. Mirrors
|
|
525
|
-
* `expandStaticMatrix` in `./matrix.ts` minus the cap-checking, which
|
|
526
|
-
* has already been enforced on the producer's own CompiledJob by the
|
|
527
|
-
* normal compile path. Lexicographic axis order keeps replay
|
|
528
|
-
* deterministic.
|
|
529
|
-
*/
|
|
530
|
-
function expandStaticMatrixAxes(
|
|
531
|
-
axes: Readonly<Record<string, readonly unknown[]>>,
|
|
532
|
-
): readonly Readonly<Record<string, unknown>>[] {
|
|
533
|
-
const axisKeys = Object.keys(axes).sort();
|
|
534
|
-
let entries: Record<string, unknown>[] = [{}];
|
|
535
|
-
for (const axis of axisKeys) {
|
|
536
|
-
const values = axes[axis]!;
|
|
537
|
-
const next: Record<string, unknown>[] = [];
|
|
538
|
-
for (const partial of entries) {
|
|
539
|
-
for (const value of values) {
|
|
540
|
-
next.push({ ...partial, [axis]: value });
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
entries = next;
|
|
544
|
-
}
|
|
545
|
-
return entries.map(Object.freeze) as readonly Readonly<
|
|
546
|
-
Record<string, unknown>
|
|
547
|
-
>[];
|
|
548
|
-
}
|