@chllming/wave-orchestration 0.7.2 → 0.8.0
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/CHANGELOG.md +33 -0
- package/README.md +14 -13
- package/docs/README.md +3 -1
- package/docs/agents/wave-cont-qa-role.md +1 -0
- package/docs/agents/wave-integration-role.md +1 -0
- package/docs/agents/wave-launcher-role.md +4 -1
- package/docs/agents/wave-orchestrator-role.md +5 -3
- package/docs/concepts/operating-modes.md +1 -1
- package/docs/concepts/runtime-agnostic-orchestration.md +5 -4
- package/docs/concepts/what-is-a-wave.md +12 -10
- package/docs/guides/author-and-run-waves.md +3 -3
- package/docs/plans/architecture-hardening-migration.md +206 -0
- package/docs/plans/current-state.md +5 -3
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/master-plan.md +2 -2
- package/docs/plans/migration.md +12 -2
- package/docs/plans/wave-orchestrator.md +10 -8
- package/docs/reference/coordination-and-closure.md +8 -4
- package/docs/reference/npmjs-trusted-publishing.md +2 -2
- package/docs/reference/sample-waves.md +4 -4
- package/docs/reference/skills.md +3 -0
- package/docs/reference/wave-control.md +4 -2
- package/docs/research/coordination-failure-review.md +4 -4
- package/docs/roadmap.md +1 -1
- package/package.json +1 -1
- package/releases/manifest.json +36 -0
- package/scripts/wave-orchestrator/agent-state.mjs +434 -105
- package/scripts/wave-orchestrator/contradiction-entity.mjs +487 -0
- package/scripts/wave-orchestrator/launcher-gates.mjs +79 -11
- package/scripts/wave-orchestrator/launcher-retry.mjs +36 -6
- package/scripts/wave-orchestrator/launcher.mjs +163 -2
- package/scripts/wave-orchestrator/task-entity.mjs +425 -51
- package/scripts/wave-orchestrator/wave-control-schema.mjs +2 -0
- package/scripts/wave-orchestrator/wave-state-reducer.mjs +260 -111
- package/skills/README.md +3 -0
- package/skills/repo-coding-rules/SKILL.md +1 -1
- package/skills/role-cont-qa/SKILL.md +2 -2
- package/skills/role-deploy/SKILL.md +1 -1
- package/skills/role-documentation/SKILL.md +1 -1
- package/skills/role-implementation/SKILL.md +1 -1
- package/skills/role-infra/SKILL.md +1 -1
- package/skills/role-integration/SKILL.md +2 -2
- package/skills/role-security/SKILL.md +1 -1
- package/skills/runtime-claude/SKILL.md +1 -1
- package/skills/runtime-codex/SKILL.md +1 -1
- package/skills/runtime-opencode/SKILL.md +1 -1
- package/skills/wave-core/SKILL.md +14 -6
- package/skills/wave-core/references/marker-syntax.md +1 -1
|
@@ -49,11 +49,19 @@ export const LEASE_STATES = new Set([
|
|
|
49
49
|
"expired",
|
|
50
50
|
]);
|
|
51
51
|
|
|
52
|
+
export const TASK_STATUSES = new Set([
|
|
53
|
+
"pending",
|
|
54
|
+
"in_progress",
|
|
55
|
+
"proven",
|
|
56
|
+
"blocked",
|
|
57
|
+
"completed",
|
|
58
|
+
]);
|
|
59
|
+
|
|
52
60
|
const VALID_PRIORITIES = new Set(["low", "normal", "high", "urgent"]);
|
|
53
61
|
|
|
54
62
|
const CLOSURE_TRANSITIONS = {
|
|
55
63
|
open: new Set(["owned_slice_proven", "cancelled", "superseded"]),
|
|
56
|
-
owned_slice_proven: new Set(["wave_closure_ready", "cancelled", "superseded"]),
|
|
64
|
+
owned_slice_proven: new Set(["open", "wave_closure_ready", "cancelled", "superseded"]),
|
|
57
65
|
wave_closure_ready: new Set(["closed", "cancelled", "superseded"]),
|
|
58
66
|
closed: new Set(),
|
|
59
67
|
cancelled: new Set(),
|
|
@@ -88,6 +96,153 @@ function normalizePlainObject(value) {
|
|
|
88
96
|
: null;
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
function slugTaskScopeSegment(value, fallback = "item") {
|
|
100
|
+
const normalized = normalizeText(value, fallback)
|
|
101
|
+
.toLowerCase()
|
|
102
|
+
.replace(/[^a-z0-9._:-]+/g, "-")
|
|
103
|
+
.replace(/-+/g, "-")
|
|
104
|
+
.replace(/^-+|-+$/g, "");
|
|
105
|
+
return normalized || fallback;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Build a stable semantic task ID.
|
|
110
|
+
* Format: "wave-{waveNumber}:{agentId}:{scope}"
|
|
111
|
+
*/
|
|
112
|
+
export function buildSemanticTaskId(waveNumber, agentId, scope) {
|
|
113
|
+
const safeWave = Number.isFinite(waveNumber) ? waveNumber : 0;
|
|
114
|
+
const safeAgent = normalizeText(agentId, "unassigned");
|
|
115
|
+
const safeScope = normalizeText(scope, "primary");
|
|
116
|
+
return `wave-${safeWave}:${safeAgent}:${safeScope}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function buildCoordinationTaskId({
|
|
120
|
+
waveNumber,
|
|
121
|
+
ownerAgentId,
|
|
122
|
+
taskType,
|
|
123
|
+
sourceRecordId,
|
|
124
|
+
title,
|
|
125
|
+
detail,
|
|
126
|
+
}) {
|
|
127
|
+
const sourceScope = normalizeText(sourceRecordId);
|
|
128
|
+
const fingerprint = sourceScope || crypto
|
|
129
|
+
.createHash("sha1")
|
|
130
|
+
.update(JSON.stringify({
|
|
131
|
+
taskType: normalizeText(taskType, "task"),
|
|
132
|
+
title: normalizeText(title),
|
|
133
|
+
detail: normalizeText(detail),
|
|
134
|
+
ownerAgentId: normalizeText(ownerAgentId, "system"),
|
|
135
|
+
waveNumber: Number.isFinite(waveNumber) ? waveNumber : 0,
|
|
136
|
+
}))
|
|
137
|
+
.digest("hex")
|
|
138
|
+
.slice(0, 12);
|
|
139
|
+
return buildSemanticTaskId(
|
|
140
|
+
waveNumber,
|
|
141
|
+
normalizeText(ownerAgentId, "system"),
|
|
142
|
+
`${slugTaskScopeSegment(taskType, "task")}-${slugTaskScopeSegment(fingerprint)}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Compute a content hash (SHA256) for change detection over a task definition subset.
|
|
148
|
+
*/
|
|
149
|
+
export function computeContentHash(definitionSubset) {
|
|
150
|
+
const payload = JSON.stringify(definitionSubset ?? {});
|
|
151
|
+
return crypto.createHash("sha256").update(payload).digest("hex");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Normalize a deliverable entry to the end-state schema: { path, exists, sha256 }.
|
|
156
|
+
*/
|
|
157
|
+
function normalizeDeliverable(entry) {
|
|
158
|
+
if (typeof entry === "string") {
|
|
159
|
+
return { path: entry, exists: false, sha256: null };
|
|
160
|
+
}
|
|
161
|
+
if (entry && typeof entry === "object") {
|
|
162
|
+
return {
|
|
163
|
+
path: normalizeText(entry.path),
|
|
164
|
+
exists: entry.exists === true,
|
|
165
|
+
sha256: normalizeText(entry.sha256, null),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return { path: "", exists: false, sha256: null };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Normalize proofRequirements to end-state object shape.
|
|
173
|
+
* Accepts both legacy string[] and new object shape.
|
|
174
|
+
*/
|
|
175
|
+
function normalizeProofRequirements(raw, defaults) {
|
|
176
|
+
// Already in new object shape
|
|
177
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
178
|
+
return {
|
|
179
|
+
proofLevel: normalizeText(raw.proofLevel, "unit"),
|
|
180
|
+
proofCentric: raw.proofCentric === true,
|
|
181
|
+
maturityTarget: normalizeText(raw.maturityTarget, null),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// Legacy string array: infer from contents
|
|
185
|
+
if (Array.isArray(raw) && raw.length > 0) {
|
|
186
|
+
return {
|
|
187
|
+
proofLevel: "unit",
|
|
188
|
+
proofCentric: raw.includes("proof-artifacts-present"),
|
|
189
|
+
maturityTarget: raw.includes("component-level-met") ? "component" : null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// Default from defaults arg
|
|
193
|
+
if (defaults && typeof defaults === "object" && !Array.isArray(defaults)) {
|
|
194
|
+
return {
|
|
195
|
+
proofLevel: normalizeText(defaults.proofLevel, "unit"),
|
|
196
|
+
proofCentric: defaults.proofCentric === true,
|
|
197
|
+
maturityTarget: normalizeText(defaults.maturityTarget, null),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return { proofLevel: "unit", proofCentric: false, maturityTarget: null };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Normalize dependencyEdges to end-state shape: [{ taskId, kind, status }].
|
|
205
|
+
* Accepts both legacy { targetTaskId, kind } and new { taskId, kind, status } shapes.
|
|
206
|
+
*/
|
|
207
|
+
function normalizeDependencyEdges(raw) {
|
|
208
|
+
if (!Array.isArray(raw)) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
return raw.map((edge) => ({
|
|
212
|
+
taskId: normalizeText(edge.taskId || edge.targetTaskId),
|
|
213
|
+
kind: normalizeText(edge.kind, "blocks"),
|
|
214
|
+
status: normalizeText(edge.status, "pending"),
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Normalize components array: [{ componentId, targetLevel }].
|
|
220
|
+
*/
|
|
221
|
+
function normalizeComponents(raw) {
|
|
222
|
+
if (!Array.isArray(raw)) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
return raw
|
|
226
|
+
.filter((c) => c && typeof c === "object")
|
|
227
|
+
.map((c) => ({
|
|
228
|
+
componentId: normalizeText(c.componentId),
|
|
229
|
+
targetLevel: normalizeText(c.targetLevel, null),
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Derive components from componentTargets for backward compatibility.
|
|
235
|
+
*/
|
|
236
|
+
function deriveComponentsFromTargets(componentTargets) {
|
|
237
|
+
if (!componentTargets || typeof componentTargets !== "object" || Array.isArray(componentTargets)) {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
return Object.entries(componentTargets).map(([componentId, targetLevel]) => ({
|
|
241
|
+
componentId,
|
|
242
|
+
targetLevel: normalizeText(targetLevel, null),
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
|
|
91
246
|
export function normalizeTask(rawTask, defaults = {}) {
|
|
92
247
|
if (!rawTask || typeof rawTask !== "object" || Array.isArray(rawTask)) {
|
|
93
248
|
throw new Error("Task must be an object");
|
|
@@ -109,28 +264,82 @@ export function normalizeTask(rawTask, defaults = {}) {
|
|
|
109
264
|
if (!VALID_PRIORITIES.has(priority)) {
|
|
110
265
|
throw new Error(`priority must be one of ${[...VALID_PRIORITIES].join(", ")} (got: ${priority})`);
|
|
111
266
|
}
|
|
267
|
+
|
|
268
|
+
// Status field (end-state P0-4)
|
|
269
|
+
const rawStatus = normalizeText(rawTask.status) || defaults.status || "pending";
|
|
270
|
+
const status = TASK_STATUSES.has(rawStatus) ? rawStatus : "pending";
|
|
271
|
+
|
|
112
272
|
const now = toIsoTimestamp();
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
273
|
+
const createdAt = normalizeText(rawTask.createdAt) || defaults.createdAt || now;
|
|
274
|
+
const updatedAt = normalizeText(rawTask.updatedAt) || defaults.updatedAt || createdAt;
|
|
275
|
+
|
|
276
|
+
// Normalize artifactContract with end-state deliverables shape
|
|
277
|
+
const rawContract = normalizePlainObject(rawTask.artifactContract) || {};
|
|
278
|
+
const deliverables = Array.isArray(rawContract.deliverables)
|
|
279
|
+
? rawContract.deliverables.map(normalizeDeliverable)
|
|
280
|
+
: [];
|
|
281
|
+
const proofArtifacts = Array.isArray(rawContract.proofArtifacts)
|
|
282
|
+
? rawContract.proofArtifacts
|
|
283
|
+
: (Array.isArray(rawContract.requiredPaths) ? [] : []);
|
|
284
|
+
const exitContract =
|
|
285
|
+
rawContract.exitContract && typeof rawContract.exitContract === "object"
|
|
286
|
+
? { ...rawContract.exitContract }
|
|
287
|
+
: null;
|
|
288
|
+
|
|
289
|
+
// Also maintain backward-compat: if old shape had requiredPaths + proofArtifacts,
|
|
290
|
+
// keep requiredPaths in the contract for backward-compat readers
|
|
291
|
+
const requiredPaths = Array.isArray(rawContract.requiredPaths) ? rawContract.requiredPaths : [];
|
|
292
|
+
const componentTargets =
|
|
293
|
+
rawContract.componentTargets && typeof rawContract.componentTargets === "object"
|
|
294
|
+
&& !Array.isArray(rawContract.componentTargets)
|
|
295
|
+
? { ...rawContract.componentTargets }
|
|
296
|
+
: {};
|
|
297
|
+
|
|
298
|
+
const artifactContract = {
|
|
299
|
+
deliverables,
|
|
300
|
+
proofArtifacts,
|
|
301
|
+
exitContract,
|
|
302
|
+
requiredPaths,
|
|
303
|
+
componentTargets,
|
|
118
304
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
305
|
+
|
|
306
|
+
// Version field
|
|
307
|
+
const version = typeof rawTask.version === "number" ? rawTask.version : 1;
|
|
308
|
+
|
|
309
|
+
// Wave number and lane
|
|
310
|
+
const waveNumber = typeof rawTask.waveNumber === "number" ? rawTask.waveNumber
|
|
311
|
+
: (typeof defaults.waveNumber === "number" ? defaults.waveNumber : null);
|
|
312
|
+
const lane = normalizeText(rawTask.lane) || defaults.lane || null;
|
|
313
|
+
|
|
314
|
+
// Content hash for change detection
|
|
315
|
+
const definitionSubset = {
|
|
316
|
+
taskType,
|
|
317
|
+
title: normalizeText(rawTask.title, defaults.title || ""),
|
|
318
|
+
ownerAgentId: normalizeText(rawTask.ownerAgentId) || defaults.ownerAgentId || null,
|
|
319
|
+
artifactContract,
|
|
320
|
+
};
|
|
321
|
+
const contentHash = normalizeText(rawTask.contentHash) || computeContentHash(definitionSubset);
|
|
322
|
+
|
|
323
|
+
// Components (end-state top-level field)
|
|
324
|
+
const components = Array.isArray(rawTask.components)
|
|
325
|
+
? normalizeComponents(rawTask.components)
|
|
326
|
+
: deriveComponentsFromTargets(componentTargets);
|
|
327
|
+
|
|
328
|
+
// ProofRequirements as end-state object
|
|
329
|
+
const proofRequirements = normalizeProofRequirements(
|
|
330
|
+
rawTask.proofRequirements,
|
|
331
|
+
defaults.proofRequirements,
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Dependency edges with status
|
|
335
|
+
const dependencyEdges = normalizeDependencyEdges(rawTask.dependencyEdges);
|
|
131
336
|
|
|
132
337
|
return {
|
|
133
338
|
taskId,
|
|
339
|
+
version,
|
|
340
|
+
contentHash,
|
|
341
|
+
waveNumber,
|
|
342
|
+
lane,
|
|
134
343
|
taskType,
|
|
135
344
|
title: normalizeText(rawTask.title, defaults.title || ""),
|
|
136
345
|
detail: normalizeText(rawTask.detail, defaults.detail || ""),
|
|
@@ -142,18 +351,15 @@ export function normalizeTask(rawTask, defaults = {}) {
|
|
|
142
351
|
leaseExpiresAt: normalizeText(rawTask.leaseExpiresAt) || null,
|
|
143
352
|
leaseHeartbeatAt: normalizeText(rawTask.leaseHeartbeatAt) || null,
|
|
144
353
|
artifactContract,
|
|
145
|
-
proofRequirements
|
|
146
|
-
dependencyEdges
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
kind: normalizeText(edge.kind, "blocks"),
|
|
150
|
-
}))
|
|
151
|
-
: [],
|
|
354
|
+
proofRequirements,
|
|
355
|
+
dependencyEdges,
|
|
356
|
+
components,
|
|
357
|
+
status,
|
|
152
358
|
closureState,
|
|
153
359
|
sourceRecordId: normalizeText(rawTask.sourceRecordId) || defaults.sourceRecordId || null,
|
|
154
360
|
priority,
|
|
155
|
-
createdAt
|
|
156
|
-
updatedAt
|
|
361
|
+
createdAt,
|
|
362
|
+
updatedAt,
|
|
157
363
|
};
|
|
158
364
|
}
|
|
159
365
|
|
|
@@ -211,6 +417,25 @@ export function releaseLease(task) {
|
|
|
211
417
|
};
|
|
212
418
|
}
|
|
213
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Expire a lease: transition from leased to expired.
|
|
422
|
+
*/
|
|
423
|
+
export function expireLease(task) {
|
|
424
|
+
if (!task || typeof task !== "object") {
|
|
425
|
+
throw new Error("task must be an object");
|
|
426
|
+
}
|
|
427
|
+
if (task.leaseState !== "leased") {
|
|
428
|
+
throw new Error(`Cannot expire task ${task.taskId} in leaseState ${task.leaseState}`);
|
|
429
|
+
}
|
|
430
|
+
const now = toIsoTimestamp();
|
|
431
|
+
return {
|
|
432
|
+
...task,
|
|
433
|
+
leaseState: "expired",
|
|
434
|
+
leaseExpiresAt: now,
|
|
435
|
+
updatedAt: now,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
214
439
|
export function heartbeatLease(task) {
|
|
215
440
|
if (!task || typeof task !== "object") {
|
|
216
441
|
throw new Error("task must be an object");
|
|
@@ -249,7 +474,12 @@ export function buildTasksFromWaveDefinition(waveDefinition, laneConfig = {}) {
|
|
|
249
474
|
const contEvalAgentId = laneConfig.contEvalAgentId || "E0";
|
|
250
475
|
const integrationAgentId = laneConfig.integrationAgentId || "A8";
|
|
251
476
|
const documentationAgentId = laneConfig.documentationAgentId || "A9";
|
|
252
|
-
const
|
|
477
|
+
const waveNumber = typeof waveDefinition.wave === "number" ? waveDefinition.wave : 0;
|
|
478
|
+
const lane = laneConfig.lane || "main";
|
|
479
|
+
const seededAt = normalizeText(
|
|
480
|
+
laneConfig.seededAt,
|
|
481
|
+
normalizeText(waveDefinition.generatedAt, "1970-01-01T00:00:00.000Z"),
|
|
482
|
+
);
|
|
253
483
|
const tasks = [];
|
|
254
484
|
|
|
255
485
|
for (const agent of agents) {
|
|
@@ -274,67 +504,121 @@ export function buildTasksFromWaveDefinition(waveDefinition, laneConfig = {}) {
|
|
|
274
504
|
agent.componentTargets && typeof agent.componentTargets === "object"
|
|
275
505
|
? { ...agent.componentTargets }
|
|
276
506
|
: {};
|
|
277
|
-
|
|
507
|
+
|
|
508
|
+
// Build end-state proofRequirements object
|
|
509
|
+
const proofLevel = exitContract?.proof || "unit";
|
|
510
|
+
let proofCentric = false;
|
|
511
|
+
let maturityTarget = null;
|
|
278
512
|
if (taskType === "implementation") {
|
|
279
|
-
proofRequirements.push("implementation-exit-met");
|
|
280
|
-
if (Object.keys(componentTargets).length > 0) {
|
|
281
|
-
proofRequirements.push("component-level-met");
|
|
282
|
-
}
|
|
283
513
|
if (Array.isArray(agent.deliverables) && agent.deliverables.length > 0) {
|
|
284
|
-
|
|
514
|
+
proofCentric = true;
|
|
515
|
+
}
|
|
516
|
+
if (Object.keys(componentTargets).length > 0) {
|
|
517
|
+
maturityTarget = "component";
|
|
285
518
|
}
|
|
286
519
|
}
|
|
520
|
+
|
|
521
|
+
// Build deliverables in end-state shape: [{ path, exists, sha256 }]
|
|
522
|
+
const deliverables = Array.isArray(agent.deliverables)
|
|
523
|
+
? agent.deliverables.map(normalizeDeliverable)
|
|
524
|
+
: [];
|
|
525
|
+
|
|
526
|
+
// Build proofArtifacts in existing shape
|
|
527
|
+
const proofArtifacts = Array.isArray(agent.deliverables)
|
|
528
|
+
? agent.deliverables.map((deliverable) => ({
|
|
529
|
+
path: typeof deliverable === "string" ? deliverable : deliverable?.path || "",
|
|
530
|
+
kind: typeof deliverable === "object" ? deliverable?.kind || "file" : "file",
|
|
531
|
+
requiredFor: typeof deliverable === "object" ? deliverable?.requiredFor || null : null,
|
|
532
|
+
}))
|
|
533
|
+
: [];
|
|
534
|
+
|
|
535
|
+
// Components from componentTargets
|
|
536
|
+
const components = deriveComponentsFromTargets(componentTargets);
|
|
537
|
+
|
|
538
|
+
// Semantic task ID
|
|
539
|
+
const scope = agent.title
|
|
540
|
+
? normalizeText(agent.title).toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "")
|
|
541
|
+
: "primary";
|
|
542
|
+
const semanticId = buildSemanticTaskId(waveNumber, agentId, scope);
|
|
543
|
+
|
|
287
544
|
tasks.push(
|
|
288
545
|
normalizeTask(
|
|
289
546
|
{
|
|
547
|
+
taskId: semanticId,
|
|
290
548
|
taskType,
|
|
291
549
|
title: `${agentId}: ${agent.title || ""}`.trim(),
|
|
292
550
|
detail: agent.detail || "",
|
|
293
551
|
ownerAgentId: agentId,
|
|
294
552
|
assigneeAgentId: agentId,
|
|
553
|
+
waveNumber,
|
|
554
|
+
lane,
|
|
295
555
|
artifactContract: {
|
|
556
|
+
deliverables,
|
|
557
|
+
proofArtifacts,
|
|
296
558
|
requiredPaths: normalizeStringArray(agent.ownedPaths || []),
|
|
297
|
-
proofArtifacts: Array.isArray(agent.deliverables)
|
|
298
|
-
? agent.deliverables.map((deliverable) => ({
|
|
299
|
-
path: typeof deliverable === "string" ? deliverable : deliverable?.path || "",
|
|
300
|
-
kind: typeof deliverable === "object" ? deliverable?.kind || "file" : "file",
|
|
301
|
-
requiredFor: typeof deliverable === "object" ? deliverable?.requiredFor || null : null,
|
|
302
|
-
}))
|
|
303
|
-
: [],
|
|
304
559
|
exitContract,
|
|
305
560
|
componentTargets,
|
|
306
561
|
},
|
|
307
|
-
proofRequirements
|
|
562
|
+
proofRequirements: {
|
|
563
|
+
proofLevel,
|
|
564
|
+
proofCentric,
|
|
565
|
+
maturityTarget,
|
|
566
|
+
},
|
|
567
|
+
dependencyEdges: [],
|
|
568
|
+
components,
|
|
569
|
+
status: "pending",
|
|
308
570
|
closureState: "open",
|
|
309
571
|
priority:
|
|
310
572
|
taskType === "implementation"
|
|
311
573
|
? "normal"
|
|
312
574
|
: "high",
|
|
313
575
|
},
|
|
314
|
-
{
|
|
576
|
+
{
|
|
577
|
+
createdAt: seededAt,
|
|
578
|
+
updatedAt: seededAt,
|
|
579
|
+
waveNumber,
|
|
580
|
+
lane,
|
|
581
|
+
},
|
|
315
582
|
),
|
|
316
583
|
);
|
|
317
584
|
}
|
|
318
585
|
|
|
319
586
|
for (const promotion of waveDefinition.componentPromotions || []) {
|
|
587
|
+
const semanticId = buildSemanticTaskId(waveNumber, "system", `promote-${promotion.componentId}`);
|
|
320
588
|
tasks.push(
|
|
321
589
|
normalizeTask(
|
|
322
590
|
{
|
|
591
|
+
taskId: semanticId,
|
|
323
592
|
taskType: "component",
|
|
324
593
|
title: `Promote ${promotion.componentId} to ${promotion.targetLevel}`,
|
|
325
594
|
detail: "",
|
|
326
595
|
ownerAgentId: null,
|
|
596
|
+
waveNumber,
|
|
597
|
+
lane,
|
|
327
598
|
artifactContract: {
|
|
328
|
-
|
|
599
|
+
deliverables: [],
|
|
329
600
|
proofArtifacts: [],
|
|
601
|
+
requiredPaths: [promotion.componentId],
|
|
330
602
|
exitContract: null,
|
|
331
603
|
componentTargets: { [promotion.componentId]: promotion.targetLevel },
|
|
332
604
|
},
|
|
333
|
-
proofRequirements:
|
|
605
|
+
proofRequirements: {
|
|
606
|
+
proofLevel: "unit",
|
|
607
|
+
proofCentric: false,
|
|
608
|
+
maturityTarget: promotion.targetLevel,
|
|
609
|
+
},
|
|
610
|
+
dependencyEdges: [],
|
|
611
|
+
components: [{ componentId: promotion.componentId, targetLevel: promotion.targetLevel }],
|
|
612
|
+
status: "pending",
|
|
334
613
|
closureState: "open",
|
|
335
614
|
priority: "high",
|
|
336
615
|
},
|
|
337
|
-
{
|
|
616
|
+
{
|
|
617
|
+
createdAt: seededAt,
|
|
618
|
+
updatedAt: seededAt,
|
|
619
|
+
waveNumber,
|
|
620
|
+
lane,
|
|
621
|
+
},
|
|
338
622
|
),
|
|
339
623
|
);
|
|
340
624
|
}
|
|
@@ -346,25 +630,41 @@ export function buildTasksFromCoordinationState(coordinationState, feedbackReque
|
|
|
346
630
|
if (!coordinationState || typeof coordinationState !== "object") {
|
|
347
631
|
return [];
|
|
348
632
|
}
|
|
349
|
-
const
|
|
633
|
+
const fallbackTimestamp = "1970-01-01T00:00:00.000Z";
|
|
350
634
|
const tasks = [];
|
|
351
635
|
|
|
352
636
|
for (const record of coordinationState.clarifications || []) {
|
|
353
637
|
if (!isOpenCoordinationStatus(record.status)) {
|
|
354
638
|
continue;
|
|
355
639
|
}
|
|
640
|
+
const waveNumber = Number.isFinite(record.wave) ? record.wave : 0;
|
|
641
|
+
const lane = normalizeText(record.lane) || null;
|
|
642
|
+
const createdAt = normalizeText(record.createdAt, fallbackTimestamp);
|
|
643
|
+
const updatedAt = normalizeText(record.updatedAt, createdAt);
|
|
356
644
|
tasks.push(
|
|
357
645
|
normalizeTask(
|
|
358
646
|
{
|
|
647
|
+
taskId: buildCoordinationTaskId({
|
|
648
|
+
waveNumber,
|
|
649
|
+
ownerAgentId: record.agentId,
|
|
650
|
+
taskType: "clarification",
|
|
651
|
+
sourceRecordId: record.id,
|
|
652
|
+
title: record.summary || record.id,
|
|
653
|
+
detail: record.detail || "",
|
|
654
|
+
}),
|
|
359
655
|
taskType: "clarification",
|
|
360
656
|
title: `Clarification: ${record.summary || record.id}`,
|
|
361
657
|
detail: record.detail || "",
|
|
362
658
|
ownerAgentId: record.agentId || null,
|
|
363
659
|
sourceRecordId: record.id || null,
|
|
660
|
+
waveNumber,
|
|
661
|
+
lane,
|
|
364
662
|
closureState: "open",
|
|
365
663
|
priority: record.priority || "normal",
|
|
664
|
+
createdAt,
|
|
665
|
+
updatedAt,
|
|
366
666
|
},
|
|
367
|
-
{ createdAt
|
|
667
|
+
{ createdAt, updatedAt, waveNumber, lane },
|
|
368
668
|
),
|
|
369
669
|
);
|
|
370
670
|
}
|
|
@@ -373,18 +673,34 @@ export function buildTasksFromCoordinationState(coordinationState, feedbackReque
|
|
|
373
673
|
if (!isOpenCoordinationStatus(record.status)) {
|
|
374
674
|
continue;
|
|
375
675
|
}
|
|
676
|
+
const waveNumber = Number.isFinite(record.wave) ? record.wave : 0;
|
|
677
|
+
const lane = normalizeText(record.lane) || null;
|
|
678
|
+
const createdAt = normalizeText(record.createdAt, fallbackTimestamp);
|
|
679
|
+
const updatedAt = normalizeText(record.updatedAt, createdAt);
|
|
376
680
|
tasks.push(
|
|
377
681
|
normalizeTask(
|
|
378
682
|
{
|
|
683
|
+
taskId: buildCoordinationTaskId({
|
|
684
|
+
waveNumber,
|
|
685
|
+
ownerAgentId: record.agentId,
|
|
686
|
+
taskType: "human-input",
|
|
687
|
+
sourceRecordId: record.id,
|
|
688
|
+
title: record.summary || record.id,
|
|
689
|
+
detail: record.detail || "",
|
|
690
|
+
}),
|
|
379
691
|
taskType: "human-input",
|
|
380
692
|
title: `Human feedback: ${record.summary || record.id}`,
|
|
381
693
|
detail: record.detail || "",
|
|
382
694
|
ownerAgentId: record.agentId || null,
|
|
383
695
|
sourceRecordId: record.id || null,
|
|
696
|
+
waveNumber,
|
|
697
|
+
lane,
|
|
384
698
|
closureState: "open",
|
|
385
699
|
priority: record.priority || "high",
|
|
700
|
+
createdAt,
|
|
701
|
+
updatedAt,
|
|
386
702
|
},
|
|
387
|
-
{ createdAt
|
|
703
|
+
{ createdAt, updatedAt, waveNumber, lane },
|
|
388
704
|
),
|
|
389
705
|
);
|
|
390
706
|
}
|
|
@@ -393,18 +709,34 @@ export function buildTasksFromCoordinationState(coordinationState, feedbackReque
|
|
|
393
709
|
if (!isOpenCoordinationStatus(record.status)) {
|
|
394
710
|
continue;
|
|
395
711
|
}
|
|
712
|
+
const waveNumber = Number.isFinite(record.wave) ? record.wave : 0;
|
|
713
|
+
const lane = normalizeText(record.lane) || null;
|
|
714
|
+
const createdAt = normalizeText(record.createdAt, fallbackTimestamp);
|
|
715
|
+
const updatedAt = normalizeText(record.updatedAt, createdAt);
|
|
396
716
|
tasks.push(
|
|
397
717
|
normalizeTask(
|
|
398
718
|
{
|
|
719
|
+
taskId: buildCoordinationTaskId({
|
|
720
|
+
waveNumber,
|
|
721
|
+
ownerAgentId: record.agentId,
|
|
722
|
+
taskType: "escalation",
|
|
723
|
+
sourceRecordId: record.id,
|
|
724
|
+
title: record.summary || record.id,
|
|
725
|
+
detail: record.detail || "",
|
|
726
|
+
}),
|
|
399
727
|
taskType: "escalation",
|
|
400
728
|
title: `Escalation: ${record.summary || record.id}`,
|
|
401
729
|
detail: record.detail || "",
|
|
402
730
|
ownerAgentId: record.agentId || null,
|
|
403
731
|
sourceRecordId: record.id || null,
|
|
732
|
+
waveNumber,
|
|
733
|
+
lane,
|
|
404
734
|
closureState: "open",
|
|
405
735
|
priority: "urgent",
|
|
736
|
+
createdAt,
|
|
737
|
+
updatedAt,
|
|
406
738
|
},
|
|
407
|
-
{ createdAt
|
|
739
|
+
{ createdAt, updatedAt, waveNumber, lane },
|
|
408
740
|
),
|
|
409
741
|
);
|
|
410
742
|
}
|
|
@@ -413,18 +745,34 @@ export function buildTasksFromCoordinationState(coordinationState, feedbackReque
|
|
|
413
745
|
if (!isOpenCoordinationStatus(request.status || "open")) {
|
|
414
746
|
continue;
|
|
415
747
|
}
|
|
748
|
+
const waveNumber = Number.isFinite(request.wave) ? request.wave : 0;
|
|
749
|
+
const lane = normalizeText(request.lane) || null;
|
|
750
|
+
const createdAt = normalizeText(request.createdAt, fallbackTimestamp);
|
|
751
|
+
const updatedAt = normalizeText(request.updatedAt, createdAt);
|
|
416
752
|
tasks.push(
|
|
417
753
|
normalizeTask(
|
|
418
754
|
{
|
|
755
|
+
taskId: buildCoordinationTaskId({
|
|
756
|
+
waveNumber,
|
|
757
|
+
ownerAgentId: request.agentId,
|
|
758
|
+
taskType: "human-input",
|
|
759
|
+
sourceRecordId: request.id,
|
|
760
|
+
title: request.summary || request.id || "",
|
|
761
|
+
detail: request.detail || "",
|
|
762
|
+
}),
|
|
419
763
|
taskType: "human-input",
|
|
420
764
|
title: `Feedback request: ${request.summary || request.id || ""}`,
|
|
421
765
|
detail: request.detail || "",
|
|
422
766
|
ownerAgentId: request.agentId || null,
|
|
423
767
|
sourceRecordId: request.id || null,
|
|
768
|
+
waveNumber,
|
|
769
|
+
lane,
|
|
424
770
|
closureState: "open",
|
|
425
771
|
priority: request.priority || "high",
|
|
772
|
+
createdAt,
|
|
773
|
+
updatedAt,
|
|
426
774
|
},
|
|
427
|
-
{ createdAt
|
|
775
|
+
{ createdAt, updatedAt, waveNumber, lane },
|
|
428
776
|
),
|
|
429
777
|
);
|
|
430
778
|
}
|
|
@@ -489,6 +837,28 @@ export function evaluateOwnedSliceProven(task, agentResult, proofBundles = []) {
|
|
|
489
837
|
return { proven: true, reason: "Exit contract satisfied" };
|
|
490
838
|
}
|
|
491
839
|
|
|
840
|
+
if (task.taskType === "component") {
|
|
841
|
+
// Component promotion task: validate that all relevant owners have promoted
|
|
842
|
+
const componentTargets = task.artifactContract?.componentTargets || {};
|
|
843
|
+
const componentIds = Object.keys(componentTargets);
|
|
844
|
+
if (componentIds.length === 0) {
|
|
845
|
+
return { proven: false, reason: "No component targets declared" };
|
|
846
|
+
}
|
|
847
|
+
const componentMarkers = new Map(
|
|
848
|
+
Array.isArray(agentResult?.components)
|
|
849
|
+
? agentResult.components.map((component) => [component.componentId, component])
|
|
850
|
+
: [],
|
|
851
|
+
);
|
|
852
|
+
for (const componentId of componentIds) {
|
|
853
|
+
const expectedLevel = componentTargets[componentId];
|
|
854
|
+
const marker = componentMarkers.get(componentId);
|
|
855
|
+
if (!marker || marker.state !== "met" || (expectedLevel && marker.level !== expectedLevel)) {
|
|
856
|
+
return { proven: false, reason: `Component ${componentId} not promoted to ${expectedLevel || "target level"}` };
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return { proven: true, reason: "Component promotion validated" };
|
|
860
|
+
}
|
|
861
|
+
|
|
492
862
|
if (task.taskType === "cont-qa") {
|
|
493
863
|
const validation = validateContQaSummary(agent, agentResult, { mode: "live" });
|
|
494
864
|
return validation.ok
|
|
@@ -501,6 +871,10 @@ export function evaluateOwnedSliceProven(task, agentResult, proofBundles = []) {
|
|
|
501
871
|
if (!evalValidation.ok) {
|
|
502
872
|
return { proven: false, reason: evalValidation.detail || evalValidation.statusCode };
|
|
503
873
|
}
|
|
874
|
+
// Differentiate: report-only vs implementation-owning cont-eval
|
|
875
|
+
if (isContEvalReportOnlyAgent(agent, { contEvalAgentId: agent.agentId })) {
|
|
876
|
+
return { proven: true, reason: "Cont-EVAL report-only satisfied" };
|
|
877
|
+
}
|
|
504
878
|
if (isContEvalImplementationOwningAgent(agent, { contEvalAgentId: agent.agentId })) {
|
|
505
879
|
const implValidation = validateImplementationSummary(agent, agentResult);
|
|
506
880
|
if (!implValidation.ok) {
|