@danypops/papyrus 0.41.0 → 0.42.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/README.md +8 -11
- package/package.json +2 -2
- package/src/adapters/sqlite-artifact-scope-store.ts +18 -9
- package/src/adapters/sqlite-artifact-store.ts +7 -5
- package/src/adapters/sqlite-discussion-round-store.ts +26 -17
- package/src/adapters/sqlite-gate-runner.ts +1 -1
- package/src/adapters/sqlite-graph-projection-store.ts +14 -10
- package/src/adapters/sqlite-log-store.ts +36 -17
- package/src/adapters/sqlite-note-event-store.ts +20 -16
- package/src/adapters/sqlite-session-identity-store.ts +13 -7
- package/src/adapters/sqlite-task-event-store.ts +29 -21
- package/src/adapters/sqlite-task-focus-store.ts +36 -10
- package/src/adapters/sqlite-task-lease-store.ts +12 -6
- package/src/adapters/sqlite-task-scope-store.ts +25 -14
- package/src/artifact-relationship-view.ts +4 -4
- package/src/artifact-subtree.ts +4 -2
- package/src/authority-registry.ts +2 -1
- package/src/cli.ts +794 -354
- package/src/client.ts +6 -3
- package/src/constants.ts +34 -56
- package/src/daemon-state.ts +4 -12
- package/src/daemon.ts +31 -9
- package/src/db.ts +153 -105
- package/src/discussion-service.ts +109 -44
- package/src/domain/artifact-event.ts +18 -5
- package/src/domain/artifact.ts +3 -1
- package/src/domain/blueprint-definition.ts +268 -0
- package/src/domain/checklist.ts +20 -17
- package/src/domain/discussion.ts +37 -18
- package/src/domain/gate.ts +7 -7
- package/src/domain/log-entry.ts +1 -1
- package/src/domain/note-event.ts +20 -7
- package/src/domain/task-event.ts +17 -7
- package/src/domain-services.ts +362 -288
- package/src/graph-projection-service.ts +34 -8
- package/src/id-migration.ts +17 -4
- package/src/index.ts +16 -11
- package/src/log-service.ts +6 -5
- package/src/log.ts +19 -0
- package/src/modules/discuss.ts +63 -28
- package/src/modules/docs.ts +74 -17
- package/src/modules/graph-projection.ts +20 -9
- package/src/modules/logs.ts +34 -22
- package/src/modules/notes.ts +66 -28
- package/src/modules/playbooks.ts +93 -32
- package/src/modules/rules.ts +57 -15
- package/src/modules/session-identity.ts +6 -2
- package/src/modules/tasks.ts +142 -67
- package/src/note-service.ts +11 -7
- package/src/ops.ts +134 -69
- package/src/playbook-definition.ts +124 -39
- package/src/playbook-execution.ts +18 -25
- package/src/ports/artifact-scope-store.ts +1 -1
- package/src/ports/note-event-store.ts +6 -4
- package/src/ports/task-event-store.ts +9 -6
- package/src/ports/task-focus-store.ts +17 -4
- package/src/ports/task-lease-store.ts +9 -4
- package/src/ports/task-scope-store.ts +3 -1
- package/src/service.ts +148 -110
- package/src/session-identity-service.ts +10 -2
- package/src/task-context.ts +28 -16
- package/src/task-execution.ts +4 -12
- package/src/task-graph-view.ts +12 -12
- package/src/task-relationship-view.ts +1 -3
- package/src/task-service.ts +168 -73
- package/src/vehicle/artifact-trash-vehicle.ts +26 -14
- package/src/vehicle/artifact-vehicle-shared.ts +32 -13
- package/src/vehicle/docs-vehicle.ts +50 -18
- package/src/vehicle/notes-vehicle.ts +26 -8
- package/src/vehicle/papyrus-vehicle.ts +16 -8
- package/src/vehicle/playbooks-vehicle.ts +88 -19
- package/src/vehicle/rules-vehicle.ts +58 -21
- package/src/vehicle/tasks-vehicle.ts +366 -54
- package/src/version.ts +1 -1
- package/src/workflow-execution.ts +198 -109
- package/src/domain/skill-definition.ts +0 -270
- package/src/modules/skills.ts +0 -158
- package/src/vehicle/skills-vehicle.ts +0 -194
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
SKILL_MAX_RENDERED_BYTES,
|
|
4
|
+
SKILL_RUN_ID_MAX_LENGTH,
|
|
5
|
+
SKILL_WORKFLOW_MAX_NESTING_DEPTH,
|
|
6
|
+
TASK_EXECUTION_MAX_EDGES,
|
|
7
|
+
} from "./constants.ts";
|
|
3
8
|
import type { Artifact } from "./domain/artifact.ts";
|
|
4
|
-
import { validateChecklist } from "./domain/checklist.ts";
|
|
5
9
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "./domain/
|
|
12
|
-
import
|
|
10
|
+
type BlueprintArgumentValue,
|
|
11
|
+
type BlueprintDefinition,
|
|
12
|
+
type CallBlueprint,
|
|
13
|
+
resolveBlueprintArguments,
|
|
14
|
+
validateBlueprintDefinition,
|
|
15
|
+
} from "./domain/blueprint-definition.ts";
|
|
16
|
+
import { validateChecklist } from "./domain/checklist.ts";
|
|
13
17
|
import type { TaskEventContext } from "./domain/task-event.ts";
|
|
14
|
-
import type { TaskEventStore } from "./ports/task-event-store.ts";
|
|
15
|
-
import type { TaskScopeStore } from "./ports/task-scope-store.ts";
|
|
16
18
|
import { normalizeProjectRoot } from "./domain/task-scope.ts";
|
|
19
|
+
import { compilePlaybookDefinition, type PlaybookExternalLink } from "./playbook-definition.ts";
|
|
20
|
+
import type { ArtifactStore } from "./ports/artifact-store.ts";
|
|
17
21
|
import { requireAtomicArtifactStore } from "./ports/atomic-artifact-store.ts";
|
|
22
|
+
import type { TaskEventStore } from "./ports/task-event-store.ts";
|
|
23
|
+
import type { TaskScopeStore } from "./ports/task-scope-store.ts";
|
|
18
24
|
import { projectTaskExecution, type TaskExecutionPlan } from "./task-execution.ts";
|
|
19
25
|
import type { TaskGraph, TaskNode, TaskStatus } from "./task-service.ts";
|
|
20
26
|
|
|
@@ -34,7 +40,7 @@ export interface InstantiateSkillWorkflowInput {
|
|
|
34
40
|
* Identifies who owns a materialized run for tagging purposes: which artifact gets the
|
|
35
41
|
* `triggers` edges to its root tasks, which extra-bag key records run lineage on each created
|
|
36
42
|
* artifact, and which label prefix scopes them. Defaults used by instantiateSkillWorkflow
|
|
37
|
-
* (ownerId: the
|
|
43
|
+
* (ownerId: the target's own id, extraKey: "skillRun", labelPrefix: "skill-run") are unchanged
|
|
38
44
|
* from before this was made pluggable -- a Playbook-compiled run supplies its own (playbook id,
|
|
39
45
|
* "playbookRun", "playbook-run") instead, the only thing that actually differs between the two.
|
|
40
46
|
*/
|
|
@@ -47,15 +53,15 @@ export interface WorkflowLineage {
|
|
|
47
53
|
export interface WorkflowRunResult {
|
|
48
54
|
skillId: string;
|
|
49
55
|
runId: string;
|
|
50
|
-
arguments: Record<string,
|
|
56
|
+
arguments: Record<string, BlueprintArgumentValue>;
|
|
51
57
|
created: {
|
|
52
58
|
docs: string[];
|
|
53
59
|
rules: string[];
|
|
54
60
|
tasks: string[];
|
|
55
|
-
/** Nested workflow
|
|
61
|
+
/** Nested workflow-definition runs this pipeline triggered as pipeline steps, in execution order. */
|
|
56
62
|
skillRuns: string[];
|
|
57
63
|
};
|
|
58
|
-
/** Real starting points: for a nested
|
|
64
|
+
/** Real starting points: for a nested call root step, that nested run's own root tasks (recursively), not just "all its tasks". */
|
|
59
65
|
rootTaskIds: string[];
|
|
60
66
|
/** Resolved from input.focusRef when supplied -- the one real task id a caller (e.g. Playbook invocation) should focus, undefined when focusRef was not requested or names an unknown ref. */
|
|
61
67
|
entryTaskId?: string;
|
|
@@ -63,34 +69,39 @@ export interface WorkflowRunResult {
|
|
|
63
69
|
execution: TaskExecutionPlan;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
|
|
72
|
+
/**
|
|
73
|
+
* A definition-shaped target: kind=playbook (Skill-the-kind is retired; every remaining
|
|
74
|
+
* definition-holding row -- migrated legacy or freshly constructed -- lives under kind=playbook
|
|
75
|
+
* now) with subtype=workflow, distinguishing it from an ordinary steps/trigger-shaped Playbook.
|
|
76
|
+
*/
|
|
77
|
+
function requireWorkflowSkill(artifacts: ArtifactStore, skillId: string): { skill: Artifact; definition: BlueprintDefinition } {
|
|
67
78
|
const skill = artifacts.get(skillId);
|
|
68
|
-
if (!skill) throw new Error(`
|
|
69
|
-
if (skill.kind !== "
|
|
70
|
-
throw new Error(`artifact "${skillId}" is not a workflow
|
|
79
|
+
if (!skill) throw new Error(`playbook artifact "${skillId}" not found`);
|
|
80
|
+
if (skill.kind !== "playbook" || skill.subtype !== "workflow") {
|
|
81
|
+
throw new Error(`artifact "${skillId}" is not a workflow-definition playbook`);
|
|
71
82
|
}
|
|
72
|
-
if (skill.status !== "active") throw new Error(`cannot run workflow
|
|
73
|
-
return { skill, definition:
|
|
83
|
+
if (skill.status !== "active") throw new Error(`cannot run workflow playbook from ${skill.status}`);
|
|
84
|
+
return { skill, definition: validateBlueprintDefinition(skill.extra.definition) };
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
function normalizeRunId(skillId: string, requested: string | undefined): string {
|
|
77
88
|
const runId = requested ?? `${skillId.slice(0, 40)}-${randomUUID().replaceAll("-", "").slice(0, 12)}`;
|
|
78
89
|
if (runId.length > SKILL_RUN_ID_MAX_LENGTH || !RUN_ID_PATTERN.test(runId)) {
|
|
79
|
-
throw new Error(`
|
|
90
|
+
throw new Error(`run id must match ${RUN_ID_PATTERN} and contain at most ${SKILL_RUN_ID_MAX_LENGTH} characters`);
|
|
80
91
|
}
|
|
81
92
|
return runId;
|
|
82
93
|
}
|
|
83
94
|
|
|
84
|
-
function renderValue(value: unknown, arguments_: Record<string,
|
|
95
|
+
function renderValue(value: unknown, arguments_: Record<string, BlueprintArgumentValue>): unknown {
|
|
85
96
|
if (typeof value === "string") {
|
|
86
97
|
const exact = value.match(EXACT_PLACEHOLDER_PATTERN);
|
|
87
98
|
if (exact) {
|
|
88
99
|
const name = exact[1]!;
|
|
89
|
-
if (!(name in arguments_)) throw new Error(`
|
|
100
|
+
if (!(name in arguments_)) throw new Error(`input placeholder "${name}" has no argument value`);
|
|
90
101
|
return arguments_[name]!;
|
|
91
102
|
}
|
|
92
103
|
return value.replace(PLACEHOLDER_PATTERN, (_placeholder, name: string) => {
|
|
93
|
-
if (!(name in arguments_)) throw new Error(`
|
|
104
|
+
if (!(name in arguments_)) throw new Error(`input placeholder "${name}" has no argument value`);
|
|
94
105
|
return String(arguments_[name]!);
|
|
95
106
|
});
|
|
96
107
|
}
|
|
@@ -98,34 +109,35 @@ function renderValue(value: unknown, arguments_: Record<string, SkillArgumentVal
|
|
|
98
109
|
if (typeof value !== "object" || value === null) return value;
|
|
99
110
|
const rendered: Record<string, unknown> = {};
|
|
100
111
|
for (const [key, entry] of Object.entries(value)) {
|
|
101
|
-
if (UNSAFE_KEYS.has(key)) throw new Error(`unsafe
|
|
112
|
+
if (UNSAFE_KEYS.has(key)) throw new Error(`unsafe blueprint key "${key}"`);
|
|
102
113
|
rendered[key] = renderValue(entry, arguments_);
|
|
103
114
|
}
|
|
104
115
|
return rendered;
|
|
105
116
|
}
|
|
106
117
|
|
|
107
|
-
function renderDefinition(definition:
|
|
108
|
-
const rendered = renderValue(definition, arguments_) as
|
|
118
|
+
function renderDefinition(definition: BlueprintDefinition, arguments_: Record<string, BlueprintArgumentValue>): BlueprintDefinition {
|
|
119
|
+
const rendered = renderValue(definition, arguments_) as BlueprintDefinition;
|
|
109
120
|
const bytes = new TextEncoder().encode(JSON.stringify(rendered)).byteLength;
|
|
110
|
-
if (bytes > SKILL_MAX_RENDERED_BYTES) throw new Error(`rendered
|
|
121
|
+
if (bytes > SKILL_MAX_RENDERED_BYTES) throw new Error(`rendered workflow exceeds ${SKILL_MAX_RENDERED_BYTES} bytes`);
|
|
111
122
|
for (const task of rendered.blueprints.tasks) {
|
|
112
|
-
if (task.extra?.
|
|
113
|
-
task.extra
|
|
123
|
+
if (task.extra?.checklist !== undefined) {
|
|
124
|
+
task.extra.checklist = validateChecklist(task.extra.checklist);
|
|
114
125
|
}
|
|
115
126
|
}
|
|
116
|
-
return
|
|
127
|
+
return validateBlueprintDefinition(rendered);
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
function withRunLabel(labels: string[] | undefined, labelPrefix: string, runId: string): string[] {
|
|
120
131
|
return [...new Set([...(labels ?? []), `${labelPrefix}:${runId}`])];
|
|
121
132
|
}
|
|
122
133
|
|
|
123
|
-
function executionGraph(tasks: Artifact[], definition:
|
|
134
|
+
function executionGraph(tasks: Artifact[], definition: BlueprintDefinition, ids: Map<string, string>, extraKey: string): TaskGraph {
|
|
124
135
|
const byRef = new Map(definition.blueprints.tasks.map((task) => [task.ref, task]));
|
|
125
136
|
const nodes: TaskNode[] = tasks.map((task) => {
|
|
126
|
-
const ref =
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
const ref =
|
|
138
|
+
task.extra[extraKey] && typeof task.extra[extraKey] === "object"
|
|
139
|
+
? ((task.extra[extraKey] as Record<string, unknown>).ref as string)
|
|
140
|
+
: "";
|
|
129
141
|
const blueprint = byRef.get(ref)!;
|
|
130
142
|
return {
|
|
131
143
|
task,
|
|
@@ -138,15 +150,15 @@ function executionGraph(tasks: Artifact[], definition: SkillDefinition, ids: Map
|
|
|
138
150
|
return { nodes, rootIds: nodes.filter((node) => node.parentIds.length === 0).map((node) => node.task.id) };
|
|
139
151
|
}
|
|
140
152
|
|
|
141
|
-
/** projectRoot is optional -- skills.run always supplies one (workflow
|
|
153
|
+
/** projectRoot is optional -- skills.run always supplies one (workflow-definition runs are always project-scoped today), while a Playbook invocation may legitimately be ad hoc/cross-project (e.g. a lab-deploy playbook not tied to any one repo), landing its tasks in the same "unscoped" bucket Tasks.create already supports for a caller that omits projectRoot entirely. */
|
|
142
154
|
export type WorkflowRunHistory = { events: TaskEventStore; scopes: TaskScopeStore; projectRoot?: string; context?: TaskEventContext };
|
|
143
155
|
|
|
144
156
|
/**
|
|
145
157
|
* Public entry point: wraps one complete pipeline run (including every nested sub-pipeline
|
|
146
158
|
* it triggers) in exactly one atomic transaction. The recursive core (runWorkflowSteps) never
|
|
147
159
|
* opens its own atomic wrapper -- SQLite savepoint nesting (inTransaction in db.ts) would
|
|
148
|
-
* tolerate it, but wrapping once here keeps the atomicity story unambiguous: one
|
|
149
|
-
*
|
|
160
|
+
* tolerate it, but wrapping once here keeps the atomicity story unambiguous: one run call is
|
|
161
|
+
* one all-or-nothing graph mutation, however many nested targets it triggers.
|
|
150
162
|
*/
|
|
151
163
|
export function instantiateSkillWorkflow(
|
|
152
164
|
artifacts: ArtifactStore,
|
|
@@ -159,31 +171,84 @@ export function instantiateSkillWorkflow(
|
|
|
159
171
|
return requireAtomicArtifactStore(artifacts).atomic(run);
|
|
160
172
|
}
|
|
161
173
|
|
|
174
|
+
/** Reads each created task's own lineage.ref tag back off the store -- resolves a compiled Playbook's blueprint refs back to real task ids after materialization, without threading an extra ref-to-id map out of materializeWorkflowDefinition's own return shape. Shared by top-level Playbook invocation (playbook-execution.ts) and a nested Playbook pipeline-call step alike. */
|
|
175
|
+
export function resolveRefToTaskId(artifacts: ArtifactStore, taskIds: string[], extraKey: string): Map<string, string> {
|
|
176
|
+
const map = new Map<string, string>();
|
|
177
|
+
for (const taskId of taskIds) {
|
|
178
|
+
const lineage = artifacts.get(taskId)?.extra[extraKey];
|
|
179
|
+
if (typeof lineage !== "object" || lineage === null || Array.isArray(lineage)) continue;
|
|
180
|
+
const ref = (lineage as Record<string, unknown>).ref;
|
|
181
|
+
if (typeof ref === "string") map.set(ref, taskId);
|
|
182
|
+
}
|
|
183
|
+
return map;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Applies a compiled Playbook's external links (a Rule that `gates` it, a Doc it `references`, etc.) once its blueprint refs have resolved to real task ids -- shared by top-level Playbook invocation and a nested Playbook pipeline-call step alike. */
|
|
187
|
+
export function applyPlaybookExternalLinks(
|
|
188
|
+
artifacts: ArtifactStore,
|
|
189
|
+
externalLinks: PlaybookExternalLink[],
|
|
190
|
+
refToTaskId: Map<string, string>,
|
|
191
|
+
): void {
|
|
192
|
+
for (const link of externalLinks) {
|
|
193
|
+
const taskId = refToTaskId.get(link.rootRef);
|
|
194
|
+
if (!taskId) continue; // defensive -- every rootRef the compiler emits is always materialized
|
|
195
|
+
if (link.ownerIsFrom) artifacts.link({ from: taskId, relation: link.relation, to: link.otherArtifactId });
|
|
196
|
+
else artifacts.link({ from: link.otherArtifactId, relation: link.relation, to: taskId });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
162
200
|
/**
|
|
163
|
-
* The recursive pipeline core. A
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
201
|
+
* The recursive pipeline core. A `skills` blueprint entry's target can be either a
|
|
202
|
+
* workflow-definition Playbook (an already-persisted, versioned JSON blueprint) or an ordinary
|
|
203
|
+
* steps/trigger-shaped Playbook (its steps/composition tree compiled fresh, right here, the
|
|
204
|
+
* same way a top-level Playbook invocation does) -- the Jenkins "downstream job" / Ansible
|
|
205
|
+
* "include_tasks" primitive either way, dispatched purely by the target artifact's own subtype.
|
|
206
|
+
* Nested runs execute BEFORE this level's dependsOn/parent edges are wired, since a step
|
|
207
|
+
* depending on a call ref needs to know every task id that nested run actually produced (not
|
|
208
|
+
* knowable ahead of time -- it depends on the nested target's own definition). `ancestorIds`
|
|
209
|
+
* tracks the current call CHAIN (not a global ever-visited set): sibling calls under the same
|
|
210
|
+
* parent are independent and may legitimately share a called target; only a real cycle back to
|
|
211
|
+
* an ancestor is rejected. This is a SEPARATE cycle/depth check from a Playbook's own
|
|
212
|
+
* contains/depends_on composition tree (playbook-definition.ts's compileNode) -- a cycle
|
|
213
|
+
* threading through BOTH graphs at once (Playbook composition -> a call step -> back into that
|
|
214
|
+
* same Playbook's composition) is not cross-checked between the two, but each graph's own
|
|
215
|
+
* independent depth cap still bounds it; it fails with a nesting-depth error rather than a
|
|
216
|
+
* precise cycle message, not an infinite loop.
|
|
171
217
|
*/
|
|
172
218
|
function runWorkflowSteps(
|
|
173
219
|
artifacts: ArtifactStore,
|
|
174
|
-
|
|
220
|
+
targetId: string,
|
|
175
221
|
input: InstantiateSkillWorkflowInput,
|
|
176
222
|
history: WorkflowRunHistory | undefined,
|
|
177
|
-
|
|
223
|
+
ancestorIds: ReadonlySet<string>,
|
|
178
224
|
depth: number,
|
|
179
225
|
): WorkflowRunResult {
|
|
180
|
-
if (
|
|
181
|
-
if (depth > SKILL_WORKFLOW_MAX_NESTING_DEPTH) throw new Error(`
|
|
182
|
-
const nextAncestors = new Set([...
|
|
183
|
-
const
|
|
226
|
+
if (ancestorIds.has(targetId)) throw new Error(`workflow nesting cycle includes "${targetId}"`);
|
|
227
|
+
if (depth > SKILL_WORKFLOW_MAX_NESTING_DEPTH) throw new Error(`workflow nesting exceeds ${SKILL_WORKFLOW_MAX_NESTING_DEPTH} levels`);
|
|
228
|
+
const nextAncestors = new Set([...ancestorIds, targetId]);
|
|
229
|
+
const target = artifacts.get(targetId);
|
|
230
|
+
// subtype=workflow is the legacy definition-shaped case (raw extra.definition, no
|
|
231
|
+
// extra.steps) -- compilePlaybookDefinition assumes an ordinary steps/trigger-shaped
|
|
232
|
+
// Playbook and must not see it; it falls through to requireWorkflowSkill below instead.
|
|
233
|
+
if (target?.kind === "playbook" && target.subtype !== "workflow") {
|
|
234
|
+
const compiled = compilePlaybookDefinition(artifacts, targetId);
|
|
235
|
+
const result = materializeWorkflowDefinition(
|
|
236
|
+
artifacts,
|
|
237
|
+
{ ownerId: targetId, extraKey: "playbookRun", labelPrefix: "playbook-run" },
|
|
238
|
+
compiled.definition,
|
|
239
|
+
{ ...input, focusRef: compiled.entryRef },
|
|
240
|
+
history,
|
|
241
|
+
nextAncestors,
|
|
242
|
+
depth,
|
|
243
|
+
);
|
|
244
|
+
const refToTaskId = resolveRefToTaskId(artifacts, result.created.tasks, "playbookRun");
|
|
245
|
+
applyPlaybookExternalLinks(artifacts, compiled.externalLinks, refToTaskId);
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
const { definition } = requireWorkflowSkill(artifacts, targetId);
|
|
184
249
|
return materializeWorkflowDefinition(
|
|
185
250
|
artifacts,
|
|
186
|
-
{ ownerId:
|
|
251
|
+
{ ownerId: targetId, extraKey: "skillRun", labelPrefix: "skill-run" },
|
|
187
252
|
definition,
|
|
188
253
|
input,
|
|
189
254
|
history,
|
|
@@ -193,23 +258,23 @@ function runWorkflowSteps(
|
|
|
193
258
|
}
|
|
194
259
|
|
|
195
260
|
/**
|
|
196
|
-
* The definition-materialization core, shared by workflow
|
|
197
|
-
* via runWorkflowSteps above) and Playbook invocation
|
|
198
|
-
* ALREADY-RESOLVED
|
|
199
|
-
* path, compiled in-memory from a Playbook's
|
|
200
|
-
* depends_on composition tree for the Playbook path --
|
|
201
|
-
* wires dependsOn/parent/links, recurses into nested
|
|
202
|
-
* Playbook-compiled definition, which never populates
|
|
203
|
-
* created artifact and the run's containing labels via
|
|
204
|
-
* "skillRun"/"skill-run" shape -- the only thing that differs
|
|
205
|
-
* `ancestorSkillIds`/`depth` are the same cycle/nesting-depth tracking
|
|
206
|
-
* enforced before this was extracted; a Playbook caller with no nested
|
|
207
|
-
* into passes an empty set and depth 0 and never revisits this function itself.
|
|
261
|
+
* The definition-materialization core, shared by workflow-definition targets
|
|
262
|
+
* (instantiateSkillWorkflow, via runWorkflowSteps above) and Playbook invocation
|
|
263
|
+
* (playbook-execution.ts): given an ALREADY-RESOLVED BlueprintDefinition -- fetched from a
|
|
264
|
+
* persisted workflow-definition artifact for that path, compiled in-memory from a Playbook's
|
|
265
|
+
* steps/trigger/arguments and its contains/depends_on composition tree for the Playbook path --
|
|
266
|
+
* creates every blueprint artifact, wires dependsOn/parent/links, recurses into nested call
|
|
267
|
+
* pipeline steps (a no-op for a Playbook-compiled definition, which never populates
|
|
268
|
+
* blueprints.skills), and tags every created artifact and the run's containing labels via
|
|
269
|
+
* `lineage` rather than a hardcoded "skillRun"/"skill-run" shape -- the only thing that differs
|
|
270
|
+
* between the two callers. `ancestorSkillIds`/`depth` are the same cycle/nesting-depth tracking
|
|
271
|
+
* runWorkflowSteps already enforced before this was extracted; a Playbook caller with no nested
|
|
272
|
+
* calls to recurse into passes an empty set and depth 0 and never revisits this function itself.
|
|
208
273
|
*/
|
|
209
274
|
export function materializeWorkflowDefinition(
|
|
210
275
|
artifacts: ArtifactStore,
|
|
211
276
|
lineage: WorkflowLineage,
|
|
212
|
-
definition:
|
|
277
|
+
definition: BlueprintDefinition,
|
|
213
278
|
input: InstantiateSkillWorkflowInput,
|
|
214
279
|
history: WorkflowRunHistory | undefined,
|
|
215
280
|
ancestorSkillIds: ReadonlySet<string>,
|
|
@@ -217,7 +282,7 @@ export function materializeWorkflowDefinition(
|
|
|
217
282
|
): WorkflowRunResult {
|
|
218
283
|
const { ownerId, extraKey, labelPrefix } = lineage;
|
|
219
284
|
const projectRoot = history?.projectRoot !== undefined ? normalizeProjectRoot(history.projectRoot) : undefined;
|
|
220
|
-
const arguments_ =
|
|
285
|
+
const arguments_ = resolveBlueprintArguments(definition, input.arguments);
|
|
221
286
|
const rendered = renderDefinition(definition, arguments_);
|
|
222
287
|
const runId = normalizeRunId(ownerId, input.runId);
|
|
223
288
|
const refs = [
|
|
@@ -231,40 +296,45 @@ export function materializeWorkflowDefinition(
|
|
|
231
296
|
// A bound at THIS level's own blueprint size; nested runs are independently bounded the same
|
|
232
297
|
// way at their own level, and nesting depth is separately capped -- so total blast radius
|
|
233
298
|
// across a whole pipeline stays bounded on both dimensions even though a step's dependency
|
|
234
|
-
// on a
|
|
235
|
-
const relationshipCount =
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
299
|
+
// on a call ref can fan out to more edges than this per-level count captures exactly.
|
|
300
|
+
const relationshipCount =
|
|
301
|
+
rendered.links.length +
|
|
302
|
+
rendered.blueprints.tasks.reduce((count, task) => count + (task.dependsOn?.length ?? 0) + (task.parent ? 2 : 0), 0) +
|
|
303
|
+
rendered.blueprints.skills.reduce((count, call) => count + (call.dependsOn?.length ?? 0) + (call.parent ? 2 : 0), 0) +
|
|
304
|
+
rendered.blueprints.tasks.filter((task) => (task.dependsOn?.length ?? 0) === 0).length +
|
|
305
|
+
rendered.blueprints.skills.filter((call) => (call.dependsOn?.length ?? 0) === 0).length;
|
|
240
306
|
if (relationshipCount > TASK_EXECUTION_MAX_EDGES) {
|
|
241
|
-
throw new Error(`
|
|
307
|
+
throw new Error(`workflow run exceeds ${TASK_EXECUTION_MAX_EDGES} relationships`);
|
|
242
308
|
}
|
|
243
309
|
|
|
244
|
-
const docs = rendered.blueprints.docs.map((blueprint) =>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
310
|
+
const docs = rendered.blueprints.docs.map((blueprint) =>
|
|
311
|
+
artifacts.create({
|
|
312
|
+
id: ids.get(blueprint.ref),
|
|
313
|
+
kind: "doc",
|
|
314
|
+
title: blueprint.title,
|
|
315
|
+
body: blueprint.body,
|
|
316
|
+
subtype: blueprint.subtype,
|
|
317
|
+
labels: withRunLabel(blueprint.labels, labelPrefix, runId),
|
|
318
|
+
extra: { ...(blueprint.extra ?? {}), [extraKey]: { id: runId, ownerId, ref: blueprint.ref } },
|
|
319
|
+
}),
|
|
320
|
+
);
|
|
321
|
+
const rules = rendered.blueprints.rules.map((blueprint) =>
|
|
322
|
+
artifacts.create({
|
|
323
|
+
id: ids.get(blueprint.ref),
|
|
324
|
+
kind: "rule",
|
|
325
|
+
title: blueprint.title,
|
|
326
|
+
body: blueprint.body,
|
|
327
|
+
labels: withRunLabel(blueprint.labels, labelPrefix, runId),
|
|
328
|
+
extra: {
|
|
329
|
+
...(blueprint.extra ?? {}),
|
|
330
|
+
...(blueprint.condition ? { condition: blueprint.condition } : {}),
|
|
331
|
+
...(blueprint.action ? { action: blueprint.action } : {}),
|
|
332
|
+
...(blueprint.severity ? { severity: blueprint.severity } : {}),
|
|
333
|
+
[extraKey]: { id: runId, ownerId, ref: blueprint.ref },
|
|
334
|
+
scope: { type: labelPrefix, runId, taskIds },
|
|
335
|
+
},
|
|
336
|
+
}),
|
|
337
|
+
);
|
|
268
338
|
const tasks = rendered.blueprints.tasks.map((blueprint) => {
|
|
269
339
|
const task = artifacts.create({
|
|
270
340
|
id: ids.get(blueprint.ref),
|
|
@@ -291,17 +361,24 @@ export function materializeWorkflowDefinition(
|
|
|
291
361
|
|
|
292
362
|
// Nested pipeline steps run before edge-wiring: dependents need to know what tasks each
|
|
293
363
|
// nested run actually produced. stepTaskIds/stepRootTaskIds map EVERY step ref (task or
|
|
294
|
-
//
|
|
295
|
-
//
|
|
364
|
+
// call) to the task id(s) it resolves to, so dependsOn/parent wiring below treats both
|
|
365
|
+
// kinds of step uniformly.
|
|
296
366
|
const nestedRuns: WorkflowRunResult[] = [];
|
|
297
367
|
const stepTaskIds = new Map<string, string[]>(tasks.map((task, index) => [rendered.blueprints.tasks[index]!.ref, [task.id]]));
|
|
298
368
|
const stepRootTaskIds = new Map<string, string[]>(
|
|
299
|
-
tasks.map((task, index) => [
|
|
369
|
+
tasks.map((task, index) => [
|
|
370
|
+
rendered.blueprints.tasks[index]!.ref,
|
|
371
|
+
(rendered.blueprints.tasks[index]!.dependsOn?.length ?? 0) === 0 ? [task.id] : [],
|
|
372
|
+
]),
|
|
300
373
|
);
|
|
301
|
-
|
|
374
|
+
// A call ref never gets its own real task -- it resolves to whatever the nested run's entry
|
|
375
|
+
// (or, absent a resolvable one, its first root task) actually is. Lets a focusRef chain
|
|
376
|
+
// straight through an arbitrary number of nested calls down to a real task, recursively.
|
|
377
|
+
const stepEntryTaskIds = new Map<string, string>();
|
|
378
|
+
for (const call of rendered.blueprints.skills as CallBlueprint[]) {
|
|
302
379
|
const nested = runWorkflowSteps(
|
|
303
380
|
artifacts,
|
|
304
|
-
call.
|
|
381
|
+
call.targetId,
|
|
305
382
|
{ runId: `${runId}-${call.ref}`, arguments: call.arguments },
|
|
306
383
|
history,
|
|
307
384
|
ancestorSkillIds,
|
|
@@ -310,6 +387,8 @@ export function materializeWorkflowDefinition(
|
|
|
310
387
|
nestedRuns.push(nested);
|
|
311
388
|
stepTaskIds.set(call.ref, nested.created.tasks);
|
|
312
389
|
stepRootTaskIds.set(call.ref, nested.rootTaskIds);
|
|
390
|
+
const entry = nested.entryTaskId ?? nested.rootTaskIds[0];
|
|
391
|
+
if (entry !== undefined) stepEntryTaskIds.set(call.ref, entry);
|
|
313
392
|
}
|
|
314
393
|
|
|
315
394
|
for (const blueprint of rendered.blueprints.tasks) {
|
|
@@ -323,7 +402,7 @@ export function materializeWorkflowDefinition(
|
|
|
323
402
|
artifacts.link({ from: id, relation: "part_of", to: parentId });
|
|
324
403
|
}
|
|
325
404
|
}
|
|
326
|
-
for (const call of rendered.blueprints.skills as
|
|
405
|
+
for (const call of rendered.blueprints.skills as CallBlueprint[]) {
|
|
327
406
|
const stepTaskIdsForCall = stepTaskIds.get(call.ref) ?? [];
|
|
328
407
|
for (const dependency of call.dependsOn ?? []) {
|
|
329
408
|
for (const dependencyId of stepTaskIds.get(dependency) ?? []) {
|
|
@@ -346,17 +425,27 @@ export function materializeWorkflowDefinition(
|
|
|
346
425
|
|
|
347
426
|
const rootTaskIds = [
|
|
348
427
|
...rendered.blueprints.tasks.filter((task) => (task.dependsOn?.length ?? 0) === 0).map((task) => ids.get(task.ref)!),
|
|
349
|
-
...(rendered.blueprints.skills as
|
|
428
|
+
...(rendered.blueprints.skills as CallBlueprint[])
|
|
350
429
|
.filter((call) => (call.dependsOn?.length ?? 0) === 0)
|
|
351
430
|
.flatMap((call) => stepRootTaskIds.get(call.ref) ?? []),
|
|
352
431
|
];
|
|
353
432
|
for (const task of rendered.blueprints.tasks) {
|
|
354
433
|
if ((task.dependsOn?.length ?? 0) === 0) artifacts.link({ from: ownerId, relation: "triggers", to: ids.get(task.ref)! });
|
|
355
434
|
}
|
|
356
|
-
for (const call of rendered.blueprints.skills as
|
|
357
|
-
if ((call.dependsOn?.length ?? 0) === 0) artifacts.link({ from: ownerId, relation: "triggers", to: call.
|
|
435
|
+
for (const call of rendered.blueprints.skills as CallBlueprint[]) {
|
|
436
|
+
if ((call.dependsOn?.length ?? 0) === 0) artifacts.link({ from: ownerId, relation: "triggers", to: call.targetId });
|
|
358
437
|
}
|
|
359
438
|
|
|
439
|
+
// A focusRef naming an ordinary task ref resolves directly through ids; one naming a call
|
|
440
|
+
// ref resolves through the nested run it triggered instead (never through ids, which only
|
|
441
|
+
// maps a call ref to a synthetic placeholder that was never actually created).
|
|
442
|
+
const focusTaskId =
|
|
443
|
+
input.focusRef === undefined
|
|
444
|
+
? undefined
|
|
445
|
+
: rendered.blueprints.tasks.some((task) => task.ref === input.focusRef)
|
|
446
|
+
? ids.get(input.focusRef)
|
|
447
|
+
: stepEntryTaskIds.get(input.focusRef);
|
|
448
|
+
|
|
360
449
|
return {
|
|
361
450
|
skillId: ownerId,
|
|
362
451
|
runId,
|
|
@@ -368,7 +457,7 @@ export function materializeWorkflowDefinition(
|
|
|
368
457
|
skillRuns: [...nestedRuns.map((run) => run.runId), ...nestedRuns.flatMap((run) => run.created.skillRuns)],
|
|
369
458
|
},
|
|
370
459
|
rootTaskIds,
|
|
371
|
-
...(
|
|
460
|
+
...(focusTaskId !== undefined ? { entryTaskId: focusTaskId } : {}),
|
|
372
461
|
execution: projectTaskExecution(executionGraph(tasks, rendered, ids, extraKey)),
|
|
373
462
|
};
|
|
374
463
|
}
|