@chllming/wave-orchestration 0.7.1 → 0.7.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/CHANGELOG.md +15 -0
- package/README.md +8 -8
- package/docs/plans/component-cutover-matrix.json +50 -3
- package/docs/plans/current-state.md +1 -1
- package/docs/plans/end-state-architecture.md +927 -0
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +2 -2
- package/docs/plans/waves/wave-1.md +376 -0
- package/docs/plans/waves/wave-2.md +292 -0
- package/docs/plans/waves/wave-3.md +342 -0
- package/docs/plans/waves/wave-4.md +391 -0
- package/docs/plans/waves/wave-5.md +382 -0
- package/docs/plans/waves/wave-6.md +321 -0
- package/docs/reference/npmjs-trusted-publishing.md +2 -2
- package/docs/reference/sample-waves.md +4 -4
- package/package.json +1 -1
- package/releases/manifest.json +19 -0
- package/scripts/wave-orchestrator/agent-state.mjs +447 -33
- package/scripts/wave-orchestrator/artifact-schemas.mjs +81 -0
- package/scripts/wave-orchestrator/control-cli.mjs +7 -1
- package/scripts/wave-orchestrator/coordination.mjs +11 -10
- package/scripts/wave-orchestrator/human-input-workflow.mjs +289 -0
- package/scripts/wave-orchestrator/install.mjs +22 -0
- package/scripts/wave-orchestrator/launcher-derived-state.mjs +915 -0
- package/scripts/wave-orchestrator/launcher-gates.mjs +1061 -0
- package/scripts/wave-orchestrator/launcher-retry.mjs +873 -0
- package/scripts/wave-orchestrator/launcher-supervisor.mjs +704 -0
- package/scripts/wave-orchestrator/launcher.mjs +153 -2922
- package/scripts/wave-orchestrator/task-entity.mjs +557 -0
- package/scripts/wave-orchestrator/wave-files.mjs +11 -2
- package/scripts/wave-orchestrator/wave-state-reducer.mjs +566 -0
- package/wave.config.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This repo now includes a dedicated npmjs publish workflow at [publish-npm.yml](../../.github/workflows/publish-npm.yml).
|
|
4
4
|
|
|
5
|
-
The current `0.7.
|
|
5
|
+
The current `0.7.2` release procedure publishes through a repository Actions secret named `NPM_TOKEN`.
|
|
6
6
|
|
|
7
7
|
## What This Repo Already Does
|
|
8
8
|
|
|
@@ -47,6 +47,6 @@ If this repo later needs private npm dependencies during CI, consider a separate
|
|
|
47
47
|
1. Confirm [publish-npm.yml](../../.github/workflows/publish-npm.yml) is on the default branch.
|
|
48
48
|
2. Confirm `NPM_TOKEN` exists in the GitHub repo secrets.
|
|
49
49
|
3. Confirm the package version has been bumped and committed.
|
|
50
|
-
4. Push the release commit and release tag, for example `v0.7.
|
|
50
|
+
4. Push the release commit and release tag, for example `v0.7.2`.
|
|
51
51
|
5. Verify both `publish-npm.yml` and `publish-package.yml` start from the tag push.
|
|
52
52
|
6. Verify the npmjs publish completes successfully for the tagged source.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Sample Waves"
|
|
3
|
-
summary: "Showcase-first sample waves that demonstrate the current 0.7.
|
|
3
|
+
summary: "Showcase-first sample waves that demonstrate the current 0.7.2 Wave surface."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Sample Waves
|
|
7
7
|
|
|
8
|
-
This guide points to showcase-first sample waves that demonstrate the current `0.7.
|
|
8
|
+
This guide points to showcase-first sample waves that demonstrate the current `0.7.2` authored Wave surface.
|
|
9
9
|
|
|
10
10
|
The examples are intentionally denser than typical production waves. Their job is to teach the current authoring and runtime surface quickly, not to be the smallest possible launch-ready files.
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ The examples are intentionally denser than typical production waves. Their job i
|
|
|
15
15
|
Shows what a good `repo-landed` outcome looks like when one promoted component only closes honestly if desired-state records, reconcile-loop substrate, and cluster-view surfaces land together. It emphasizes maturity discipline, explicit deliverables, and shared-plan closure without drifting into `pilot-live` claims.
|
|
16
16
|
|
|
17
17
|
- [Full modern sample wave](../plans/examples/wave-example-live-proof.md)
|
|
18
|
-
Shows the combined `0.7.
|
|
18
|
+
Shows the combined `0.7.2` authored surface in one file: closure roles, `E0`, optional security review, delegated and pinned benchmark targets, richer executor config, `### Skills`, `### Capabilities`, `### Deliverables`, `### Exit contract`, `### Proof artifacts`, sticky retry, deploy environments, and proof-first live-wave structure.
|
|
19
19
|
|
|
20
20
|
## What These Examples Teach
|
|
21
21
|
|
|
@@ -38,7 +38,7 @@ The examples are intentionally denser than typical production waves. Their job i
|
|
|
38
38
|
|
|
39
39
|
## Feature Coverage Map
|
|
40
40
|
|
|
41
|
-
Together these samples cover the main surfaces added or hardened for `0.7.
|
|
41
|
+
Together these samples cover the main surfaces added or hardened for `0.7.2`:
|
|
42
42
|
|
|
43
43
|
- repo-landed maturity discipline and anti-overclaim framing
|
|
44
44
|
- explicit shared-plan closure for future-wave safety
|
package/package.json
CHANGED
package/releases/manifest.json
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "@chllming/wave-orchestration",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.7.2",
|
|
7
|
+
"date": "2026-03-23",
|
|
8
|
+
"summary": "Implementation marker parsing repair, proof-centric summary refresh, and 0.7.2 release-surface alignment.",
|
|
9
|
+
"features": [
|
|
10
|
+
"Implementation summaries now accept final `[wave-proof]`, `[wave-doc-delta]`, and `[wave-component]` markers when agents emit them as bullet-prefixed structured blocks.",
|
|
11
|
+
"Validation now surfaces parse-specific proof, doc-delta, and component errors when raw structured marker text was seen in the log but rejected by the strict parser.",
|
|
12
|
+
"Legacy proof-centric summaries are refreshed from source logs only when the stored summary is actually missing required proof, doc-delta, or owned-component markers, preserving valid historical summaries.",
|
|
13
|
+
"Implementation prompts now keep incomplete work inside the required final markers with `state=gap` and route unresolved issues through `wave coord post` instead of trailing `[wave-gap]` lines.",
|
|
14
|
+
"Shipped package metadata, README, migration guidance, sample-wave docs, and npm publishing instructions now point at the `0.7.2` release surface."
|
|
15
|
+
],
|
|
16
|
+
"manualSteps": [
|
|
17
|
+
"If an older lane still contains proof-centric `.summary.json` files missing parsed markers, rerun the relevant launcher or status surfaces once so they can self-refresh from the source logs.",
|
|
18
|
+
"If implementation agents still carry custom prompt guidance about trailing `[wave-gap]` markers in repo-owned docs, update that guidance so final output ends with only the required implementation markers.",
|
|
19
|
+
"If an adopted `0.6.x` repo fails `wave doctor` after the `0.7.x` upgrade, sync the repo-owned planner starter surface (`docs/agents/wave-planner-role.md`, `skills/role-planner/`, `docs/context7/planner-agent/`, `docs/reference/wave-planning-lessons.md`, and the `planner-agentic` bundle entry) before relying on planner-aware validation.",
|
|
20
|
+
"Run `pnpm exec wave doctor` and `pnpm exec wave launch --lane main --dry-run --no-dashboard` after upgrading once your repo-owned wave files satisfy the current validation contract."
|
|
21
|
+
],
|
|
22
|
+
"breaking": false
|
|
23
|
+
},
|
|
5
24
|
{
|
|
6
25
|
"version": "0.7.1",
|
|
7
26
|
"date": "2026-03-23",
|
|
@@ -53,14 +53,98 @@ const WAVE_GAP_REGEX =
|
|
|
53
53
|
/^\[wave-gap\]\s*kind=(architecture|integration|durability|ops|docs)\s*(?:detail=(.*))?$/gim;
|
|
54
54
|
const WAVE_COMPONENT_REGEX =
|
|
55
55
|
/^\[wave-component\]\s*component=([a-z0-9._-]+)\s+level=([a-z0-9._-]+)\s+state=(met|gap)\s*(?:detail=(.*))?$/gim;
|
|
56
|
-
const STRUCTURED_SIGNAL_LINE_REGEX = /^\[wave-[
|
|
56
|
+
const STRUCTURED_SIGNAL_LINE_REGEX = /^\[wave-[a-z0-9-]+(?:\]|\s|=|$).*$/i;
|
|
57
57
|
const WRAPPED_STRUCTURED_SIGNAL_LINE_REGEX = /^`\[wave-[^`]+`$/;
|
|
58
|
+
const STRUCTURED_SIGNAL_LIST_PREFIX_REGEX = /^(?:[-*+]|\d+\.)\s+/;
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
const STRUCTURED_SIGNAL_KIND_BY_TAG = {
|
|
61
|
+
proof: "proof",
|
|
62
|
+
"doc-delta": "docDelta",
|
|
63
|
+
"doc-closure": "docClosure",
|
|
64
|
+
integration: "integration",
|
|
65
|
+
eval: "eval",
|
|
66
|
+
security: "security",
|
|
67
|
+
gate: "gate",
|
|
68
|
+
gap: "gap",
|
|
69
|
+
component: "component",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const STRUCTURED_SIGNAL_LINE_REGEX_BY_KIND = {
|
|
73
|
+
proof: new RegExp(WAVE_PROOF_REGEX.source, "i"),
|
|
74
|
+
docDelta: new RegExp(WAVE_DOC_DELTA_REGEX.source, "i"),
|
|
75
|
+
docClosure: new RegExp(WAVE_DOC_CLOSURE_REGEX.source, "i"),
|
|
76
|
+
integration: new RegExp(WAVE_INTEGRATION_REGEX.source, "i"),
|
|
77
|
+
eval: new RegExp(WAVE_EVAL_REGEX.source, "i"),
|
|
78
|
+
security: new RegExp(WAVE_SECURITY_REGEX.source, "i"),
|
|
79
|
+
gate: new RegExp(WAVE_GATE_REGEX.source, "i"),
|
|
80
|
+
gap: new RegExp(WAVE_GAP_REGEX.source, "i"),
|
|
81
|
+
component: new RegExp(WAVE_COMPONENT_REGEX.source, "i"),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
function buildEmptyStructuredSignalDiagnostics() {
|
|
85
|
+
return {
|
|
86
|
+
proof: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
87
|
+
docDelta: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
88
|
+
docClosure: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
89
|
+
integration: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
90
|
+
eval: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
91
|
+
security: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
92
|
+
gate: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
93
|
+
gap: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
94
|
+
component: { rawCount: 0, acceptedCount: 0, rejectedSamples: [], seenComponentIds: [] },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function pushRejectedStructuredSignalSample(bucket, sample) {
|
|
99
|
+
if (!bucket || !sample || bucket.rejectedSamples.length >= 3) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
bucket.rejectedSamples.push(sample);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizeStructuredSignalLine(line) {
|
|
106
|
+
const trimmed = String(line || "").trim();
|
|
107
|
+
if (!trimmed) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const withoutListPrefix = trimmed.replace(STRUCTURED_SIGNAL_LIST_PREFIX_REGEX, "").trim();
|
|
111
|
+
if (STRUCTURED_SIGNAL_LINE_REGEX.test(withoutListPrefix)) {
|
|
112
|
+
return withoutListPrefix;
|
|
113
|
+
}
|
|
114
|
+
if (WRAPPED_STRUCTURED_SIGNAL_LINE_REGEX.test(withoutListPrefix)) {
|
|
115
|
+
return withoutListPrefix.slice(1, -1).trim();
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseStructuredSignalCandidate(line) {
|
|
121
|
+
const rawLine = String(line || "").trim();
|
|
122
|
+
if (!rawLine) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const canonicalLine = normalizeStructuredSignalLine(rawLine);
|
|
126
|
+
if (!canonicalLine) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const tagMatch = canonicalLine.match(/^\[wave-([a-z0-9-]+)(?:\]|\s|=|$)/i);
|
|
130
|
+
if (!tagMatch) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const kind = STRUCTURED_SIGNAL_KIND_BY_TAG[String(tagMatch[1] || "").toLowerCase()] || null;
|
|
134
|
+
const componentIdMatch = canonicalLine.match(/\bcomponent=([a-z0-9._-]+)/i);
|
|
135
|
+
return {
|
|
136
|
+
rawLine,
|
|
137
|
+
canonicalLine,
|
|
138
|
+
kind,
|
|
139
|
+
componentId: componentIdMatch ? String(componentIdMatch[1] || "").trim() : null,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function collectStructuredSignalCandidates(text) {
|
|
60
144
|
if (!text) {
|
|
61
|
-
return
|
|
145
|
+
return [];
|
|
62
146
|
}
|
|
63
|
-
const
|
|
147
|
+
const candidates = [];
|
|
64
148
|
let fenceLines = null;
|
|
65
149
|
for (const rawLine of String(text || "").split(/\r?\n/)) {
|
|
66
150
|
const trimmed = rawLine.trim();
|
|
@@ -69,11 +153,11 @@ function normalizeStructuredSignalText(text) {
|
|
|
69
153
|
fenceLines = [];
|
|
70
154
|
continue;
|
|
71
155
|
}
|
|
72
|
-
const
|
|
73
|
-
.map((line) =>
|
|
156
|
+
const fenceCandidates = fenceLines
|
|
157
|
+
.map((line) => parseStructuredSignalCandidate(line))
|
|
74
158
|
.filter(Boolean);
|
|
75
|
-
if (
|
|
76
|
-
|
|
159
|
+
if (fenceCandidates.length > 0 && fenceCandidates.length === fenceLines.length) {
|
|
160
|
+
candidates.push(...fenceCandidates);
|
|
77
161
|
}
|
|
78
162
|
fenceLines = null;
|
|
79
163
|
continue;
|
|
@@ -82,29 +166,48 @@ function normalizeStructuredSignalText(text) {
|
|
|
82
166
|
if (!trimmed) {
|
|
83
167
|
continue;
|
|
84
168
|
}
|
|
85
|
-
fenceLines.push(
|
|
169
|
+
fenceLines.push(rawLine);
|
|
86
170
|
continue;
|
|
87
171
|
}
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
|
|
172
|
+
const candidate = parseStructuredSignalCandidate(rawLine);
|
|
173
|
+
if (candidate) {
|
|
174
|
+
candidates.push(candidate);
|
|
91
175
|
}
|
|
92
176
|
}
|
|
93
|
-
return
|
|
177
|
+
return candidates;
|
|
94
178
|
}
|
|
95
179
|
|
|
96
|
-
function
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
180
|
+
function buildStructuredSignalDiagnostics(candidates) {
|
|
181
|
+
const diagnostics = buildEmptyStructuredSignalDiagnostics();
|
|
182
|
+
for (const candidate of candidates || []) {
|
|
183
|
+
if (!candidate?.kind || !diagnostics[candidate.kind]) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const bucket = diagnostics[candidate.kind];
|
|
187
|
+
bucket.rawCount += 1;
|
|
188
|
+
if (candidate.kind === "component" && candidate.componentId) {
|
|
189
|
+
bucket.seenComponentIds.push(candidate.componentId);
|
|
190
|
+
}
|
|
191
|
+
const strictRegex = STRUCTURED_SIGNAL_LINE_REGEX_BY_KIND[candidate.kind];
|
|
192
|
+
if (strictRegex.test(candidate.canonicalLine)) {
|
|
193
|
+
bucket.acceptedCount += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
pushRejectedStructuredSignalSample(bucket, {
|
|
197
|
+
line: candidate.rawLine,
|
|
198
|
+
...(candidate.kind === "component" && candidate.componentId ? { componentId: candidate.componentId } : {}),
|
|
199
|
+
});
|
|
106
200
|
}
|
|
107
|
-
|
|
201
|
+
diagnostics.component.seenComponentIds = Array.from(new Set(diagnostics.component.seenComponentIds)).sort();
|
|
202
|
+
return diagnostics;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function extractStructuredSignalPayload(text) {
|
|
206
|
+
const candidates = collectStructuredSignalCandidates(text);
|
|
207
|
+
return {
|
|
208
|
+
signalText: candidates.map((candidate) => candidate.canonicalLine).join("\n"),
|
|
209
|
+
diagnostics: buildStructuredSignalDiagnostics(candidates),
|
|
210
|
+
};
|
|
108
211
|
}
|
|
109
212
|
|
|
110
213
|
function cleanText(value) {
|
|
@@ -335,17 +438,10 @@ export function agentSummaryPathFromStatusPath(statusPath) {
|
|
|
335
438
|
: `${statusPath}.summary.json`;
|
|
336
439
|
}
|
|
337
440
|
|
|
338
|
-
export function readAgentExecutionSummary(summaryPathOrStatusPath) {
|
|
339
|
-
const summaryPath = summaryPathOrStatusPath.endsWith(".summary.json")
|
|
340
|
-
? summaryPathOrStatusPath
|
|
341
|
-
: agentSummaryPathFromStatusPath(summaryPathOrStatusPath);
|
|
342
|
-
const payload = readJsonOrNull(summaryPath);
|
|
343
|
-
return payload && typeof payload === "object" ? payload : null;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
441
|
export function buildAgentExecutionSummary({ agent, statusRecord, logPath, reportPath = null }) {
|
|
347
442
|
const logText = readFileTail(logPath, 60000);
|
|
348
|
-
const
|
|
443
|
+
const structuredSignals = extractStructuredSignalPayload(logText);
|
|
444
|
+
const signalText = structuredSignals.signalText;
|
|
349
445
|
const reportText =
|
|
350
446
|
reportPath && readJsonOrNull(reportPath) === null
|
|
351
447
|
? readFileTail(reportPath, 60000)
|
|
@@ -442,6 +538,7 @@ export function buildAgentExecutionSummary({ agent, statusRecord, logPath, repor
|
|
|
442
538
|
detail: cleanText(verdict.detail),
|
|
443
539
|
}
|
|
444
540
|
: null,
|
|
541
|
+
structuredSignalDiagnostics: structuredSignals.diagnostics,
|
|
445
542
|
terminationReason: termination.reason,
|
|
446
543
|
terminationHint: termination.hint,
|
|
447
544
|
terminationObservedTurnLimit:
|
|
@@ -459,6 +556,113 @@ export function writeAgentExecutionSummary(summaryPathOrStatusPath, summary) {
|
|
|
459
556
|
return summaryPath;
|
|
460
557
|
}
|
|
461
558
|
|
|
559
|
+
function resolveStatusRecordForSummaryRead(summaryPathOrStatusPath, options = {}) {
|
|
560
|
+
if (options.statusRecord && typeof options.statusRecord === "object") {
|
|
561
|
+
return options.statusRecord;
|
|
562
|
+
}
|
|
563
|
+
const explicitStatusPath =
|
|
564
|
+
typeof options.statusPath === "string" && options.statusPath.trim() ? options.statusPath : null;
|
|
565
|
+
const derivedStatusPath =
|
|
566
|
+
!summaryPathOrStatusPath.endsWith(".summary.json") ? summaryPathOrStatusPath : null;
|
|
567
|
+
const statusPath = explicitStatusPath || derivedStatusPath;
|
|
568
|
+
if (!statusPath) {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
const payload = readJsonOrNull(statusPath);
|
|
572
|
+
return payload && typeof payload === "object" ? payload : null;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function summaryNeedsStructuredSignalRefresh(payload, options = {}) {
|
|
576
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
if (payload.structuredSignalDiagnostics && typeof payload.structuredSignalDiagnostics === "object") {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
const agent = options.agent;
|
|
583
|
+
const contract = normalizeExitContract(agent?.exitContract);
|
|
584
|
+
if (!contract) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
if (!payload.proof || !payload.docDelta) {
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
const ownedComponents = Array.isArray(agent?.components) ? agent.components : [];
|
|
591
|
+
if (ownedComponents.length === 0) {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
const componentMarkers = new Map(
|
|
595
|
+
Array.isArray(payload.components)
|
|
596
|
+
? payload.components.map((component) => [component.componentId, component])
|
|
597
|
+
: [],
|
|
598
|
+
);
|
|
599
|
+
return ownedComponents.some((componentId) => !componentMarkers.has(componentId));
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function refreshExecutionSummaryIfStale(summaryPathOrStatusPath, payload, options = {}) {
|
|
603
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
604
|
+
return payload;
|
|
605
|
+
}
|
|
606
|
+
if (!summaryNeedsStructuredSignalRefresh(payload, options)) {
|
|
607
|
+
return payload;
|
|
608
|
+
}
|
|
609
|
+
if (!options.agent || !options.logPath || !fs.existsSync(options.logPath)) {
|
|
610
|
+
return payload;
|
|
611
|
+
}
|
|
612
|
+
const refreshed = buildAgentExecutionSummary({
|
|
613
|
+
agent: options.agent,
|
|
614
|
+
statusRecord: resolveStatusRecordForSummaryRead(summaryPathOrStatusPath, options),
|
|
615
|
+
logPath: options.logPath,
|
|
616
|
+
reportPath: options.reportPath || null,
|
|
617
|
+
});
|
|
618
|
+
writeAgentExecutionSummary(summaryPathOrStatusPath, refreshed);
|
|
619
|
+
return refreshed;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export function readAgentExecutionSummary(summaryPathOrStatusPath, options = {}) {
|
|
623
|
+
const summaryPath = summaryPathOrStatusPath.endsWith(".summary.json")
|
|
624
|
+
? summaryPathOrStatusPath
|
|
625
|
+
: agentSummaryPathFromStatusPath(summaryPathOrStatusPath);
|
|
626
|
+
const payload = readJsonOrNull(summaryPath);
|
|
627
|
+
const summary = payload && typeof payload === "object" ? payload : null;
|
|
628
|
+
return refreshExecutionSummaryIfStale(summaryPathOrStatusPath, summary, options);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function structuredSignalBucket(summary, key) {
|
|
632
|
+
const diagnostics = summary?.structuredSignalDiagnostics;
|
|
633
|
+
if (!diagnostics || typeof diagnostics !== "object") {
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
const bucket = diagnostics[key];
|
|
637
|
+
return bucket && typeof bucket === "object" ? bucket : null;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function rejectedStructuredSignalLine(summary, key, predicate = null) {
|
|
641
|
+
const bucket = structuredSignalBucket(summary, key);
|
|
642
|
+
const rejected = Array.isArray(bucket?.rejectedSamples) ? bucket.rejectedSamples : [];
|
|
643
|
+
const match = typeof predicate === "function" ? rejected.find(predicate) : rejected[0];
|
|
644
|
+
return cleanText(match?.line || "");
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function hasRejectedStructuredSignal(summary, key) {
|
|
648
|
+
const bucket = structuredSignalBucket(summary, key);
|
|
649
|
+
return Number(bucket?.rawCount || 0) > 0 && Number(bucket?.acceptedCount || 0) === 0;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function invalidStructuredSignalDetail(agentId, markerName, summary, key, extraDetail = "", predicate = null) {
|
|
653
|
+
const sample = rejectedStructuredSignalLine(summary, key, predicate);
|
|
654
|
+
const detailParts = [
|
|
655
|
+
`Saw raw ${markerName} marker text for ${agentId}, but none of it was accepted into the structured summary.`,
|
|
656
|
+
];
|
|
657
|
+
if (extraDetail) {
|
|
658
|
+
detailParts.push(extraDetail);
|
|
659
|
+
}
|
|
660
|
+
if (sample) {
|
|
661
|
+
detailParts.push(`Rejected sample: ${sample}`);
|
|
662
|
+
}
|
|
663
|
+
return appendTerminationHint(detailParts.join(" "), summary);
|
|
664
|
+
}
|
|
665
|
+
|
|
462
666
|
export function validateImplementationSummary(agent, summary) {
|
|
463
667
|
const contract = normalizeExitContract(agent?.exitContract);
|
|
464
668
|
if (!contract) {
|
|
@@ -472,6 +676,13 @@ export function validateImplementationSummary(agent, summary) {
|
|
|
472
676
|
};
|
|
473
677
|
}
|
|
474
678
|
if (!summary.proof) {
|
|
679
|
+
if (hasRejectedStructuredSignal(summary, "proof")) {
|
|
680
|
+
return {
|
|
681
|
+
ok: false,
|
|
682
|
+
statusCode: "invalid-wave-proof-format",
|
|
683
|
+
detail: invalidStructuredSignalDetail(agent.agentId, "[wave-proof]", summary, "proof"),
|
|
684
|
+
};
|
|
685
|
+
}
|
|
475
686
|
return {
|
|
476
687
|
ok: false,
|
|
477
688
|
statusCode: "missing-wave-proof",
|
|
@@ -507,6 +718,13 @@ export function validateImplementationSummary(agent, summary) {
|
|
|
507
718
|
};
|
|
508
719
|
}
|
|
509
720
|
if (!summary.docDelta) {
|
|
721
|
+
if (hasRejectedStructuredSignal(summary, "docDelta")) {
|
|
722
|
+
return {
|
|
723
|
+
ok: false,
|
|
724
|
+
statusCode: "invalid-doc-delta-format",
|
|
725
|
+
detail: invalidStructuredSignalDetail(agent.agentId, "[wave-doc-delta]", summary, "docDelta"),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
510
728
|
return {
|
|
511
729
|
ok: false,
|
|
512
730
|
statusCode: "missing-doc-delta",
|
|
@@ -522,6 +740,10 @@ export function validateImplementationSummary(agent, summary) {
|
|
|
522
740
|
}
|
|
523
741
|
const ownedComponents = Array.isArray(agent?.components) ? agent.components : [];
|
|
524
742
|
if (ownedComponents.length > 0) {
|
|
743
|
+
const componentDiagnostics = structuredSignalBucket(summary, "component");
|
|
744
|
+
const seenComponentIds = new Set(
|
|
745
|
+
Array.isArray(componentDiagnostics?.seenComponentIds) ? componentDiagnostics.seenComponentIds : [],
|
|
746
|
+
);
|
|
525
747
|
const componentMarkers = new Map(
|
|
526
748
|
Array.isArray(summary.components)
|
|
527
749
|
? summary.components.map((component) => [component.componentId, component])
|
|
@@ -530,6 +752,23 @@ export function validateImplementationSummary(agent, summary) {
|
|
|
530
752
|
for (const componentId of ownedComponents) {
|
|
531
753
|
const marker = componentMarkers.get(componentId);
|
|
532
754
|
if (!marker) {
|
|
755
|
+
if (
|
|
756
|
+
Number(componentDiagnostics?.rawCount || 0) > 0 &&
|
|
757
|
+
(seenComponentIds.has(componentId) || Number(componentDiagnostics?.acceptedCount || 0) === 0)
|
|
758
|
+
) {
|
|
759
|
+
return {
|
|
760
|
+
ok: false,
|
|
761
|
+
statusCode: "invalid-wave-component-format",
|
|
762
|
+
detail: invalidStructuredSignalDetail(
|
|
763
|
+
agent.agentId,
|
|
764
|
+
"[wave-component]",
|
|
765
|
+
summary,
|
|
766
|
+
"component",
|
|
767
|
+
`Expected a valid component marker for ${componentId}.`,
|
|
768
|
+
(sample) => cleanText(sample?.componentId) === componentId,
|
|
769
|
+
),
|
|
770
|
+
};
|
|
771
|
+
}
|
|
533
772
|
return {
|
|
534
773
|
ok: false,
|
|
535
774
|
statusCode: "missing-wave-component",
|
|
@@ -928,3 +1167,178 @@ export function validateContQaSummary(agent, summary, options = {}) {
|
|
|
928
1167
|
detail: summary.verdict.detail || summary.gate.detail || "cont-QA gate passed.",
|
|
929
1168
|
};
|
|
930
1169
|
}
|
|
1170
|
+
|
|
1171
|
+
// ---------------------------------------------------------------------------
|
|
1172
|
+
// Agent Result Envelope — Wave 3
|
|
1173
|
+
// ---------------------------------------------------------------------------
|
|
1174
|
+
|
|
1175
|
+
import { toIsoTimestamp } from "./shared.mjs";
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* Path to the envelope file derived from the status path.
|
|
1179
|
+
*
|
|
1180
|
+
* @param {string} statusPath - Path to the .status or .summary file
|
|
1181
|
+
* @returns {string} The envelope file path
|
|
1182
|
+
*/
|
|
1183
|
+
export function agentEnvelopePathFromStatusPath(statusPath) {
|
|
1184
|
+
if (statusPath.endsWith(".summary.json")) {
|
|
1185
|
+
return statusPath.replace(/\.summary\.json$/i, ".envelope.json");
|
|
1186
|
+
}
|
|
1187
|
+
if (statusPath.endsWith(".status")) {
|
|
1188
|
+
return statusPath.replace(/\.status$/i, ".envelope.json");
|
|
1189
|
+
}
|
|
1190
|
+
return `${statusPath}.envelope.json`;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* Build a structured result envelope from an already-parsed execution summary.
|
|
1195
|
+
* Pure function — the envelope is a normalized projection of the summary.
|
|
1196
|
+
*
|
|
1197
|
+
* @param {object} agent - Agent definition from wave
|
|
1198
|
+
* @param {object} summary - Execution summary from buildAgentExecutionSummary
|
|
1199
|
+
* @returns {object} AgentResultEnvelope
|
|
1200
|
+
*/
|
|
1201
|
+
export function buildAgentResultEnvelope(agent, summary) {
|
|
1202
|
+
const safeAgent = agent || {};
|
|
1203
|
+
const safeSummary = summary || {};
|
|
1204
|
+
|
|
1205
|
+
// Exit contract from proof dimensions + doc delta
|
|
1206
|
+
const proof = safeSummary.proof || {};
|
|
1207
|
+
const docDelta = safeSummary.docDelta || {};
|
|
1208
|
+
const exitContract = {
|
|
1209
|
+
completion: proof.completion || null,
|
|
1210
|
+
durability: proof.durability || null,
|
|
1211
|
+
proof: proof.proof || null,
|
|
1212
|
+
docImpact: docDelta.state || null,
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
// Proof artifacts
|
|
1216
|
+
const proofArtifacts = Array.isArray(safeSummary.proofArtifacts)
|
|
1217
|
+
? safeSummary.proofArtifacts.map((artifact) => ({
|
|
1218
|
+
path: artifact.path || null,
|
|
1219
|
+
kind: artifact.kind || null,
|
|
1220
|
+
sha256: artifact.sha256 || null,
|
|
1221
|
+
exists: artifact.exists === true,
|
|
1222
|
+
}))
|
|
1223
|
+
: [];
|
|
1224
|
+
|
|
1225
|
+
// Deliverables
|
|
1226
|
+
const deliverables = Array.isArray(safeSummary.deliverables)
|
|
1227
|
+
? safeSummary.deliverables.map((d) => ({
|
|
1228
|
+
path: d.path || null,
|
|
1229
|
+
exists: d.exists === true,
|
|
1230
|
+
}))
|
|
1231
|
+
: [];
|
|
1232
|
+
|
|
1233
|
+
// Components
|
|
1234
|
+
const components = Array.isArray(safeSummary.components)
|
|
1235
|
+
? safeSummary.components.map((c) => ({
|
|
1236
|
+
componentId: c.componentId || null,
|
|
1237
|
+
level: c.level || null,
|
|
1238
|
+
state: c.state || null,
|
|
1239
|
+
}))
|
|
1240
|
+
: [];
|
|
1241
|
+
|
|
1242
|
+
// Gate claims from the gate marker
|
|
1243
|
+
const gateClaims = [];
|
|
1244
|
+
if (safeSummary.gate) {
|
|
1245
|
+
const gateKeys = ["architecture", "integration", "durability", "live", "docs"];
|
|
1246
|
+
for (const key of gateKeys) {
|
|
1247
|
+
if (safeSummary.gate[key]) {
|
|
1248
|
+
gateClaims.push({
|
|
1249
|
+
gateId: key,
|
|
1250
|
+
claim: safeSummary.gate[key],
|
|
1251
|
+
detail: safeSummary.gate.detail || null,
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// Validation outputs from proof state
|
|
1258
|
+
const validationOutputs = {
|
|
1259
|
+
testsPassed: proof.state === "met" && proof.proof != null,
|
|
1260
|
+
buildPassed: proof.state === "met",
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
// Risk notes
|
|
1264
|
+
const riskNotes = Array.isArray(safeSummary.riskNotes) ? safeSummary.riskNotes : [];
|
|
1265
|
+
|
|
1266
|
+
// Unresolved blockers
|
|
1267
|
+
const unresolvedBlockers = Array.isArray(safeSummary.unresolvedBlockers)
|
|
1268
|
+
? safeSummary.unresolvedBlockers
|
|
1269
|
+
: [];
|
|
1270
|
+
|
|
1271
|
+
// Docs deltas
|
|
1272
|
+
const docsDeltas = [];
|
|
1273
|
+
if (docDelta.state) {
|
|
1274
|
+
docsDeltas.push({
|
|
1275
|
+
state: docDelta.state,
|
|
1276
|
+
paths: Array.isArray(docDelta.paths) ? docDelta.paths : [],
|
|
1277
|
+
detail: docDelta.detail || null,
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// Security findings
|
|
1282
|
+
const securityFindings = [];
|
|
1283
|
+
if (safeSummary.security) {
|
|
1284
|
+
securityFindings.push({
|
|
1285
|
+
state: safeSummary.security.state || null,
|
|
1286
|
+
findings: safeSummary.security.findings || 0,
|
|
1287
|
+
approvals: safeSummary.security.approvals || 0,
|
|
1288
|
+
detail: safeSummary.security.detail || null,
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Integration claims
|
|
1293
|
+
const integrationClaims = [];
|
|
1294
|
+
if (safeSummary.integration) {
|
|
1295
|
+
integrationClaims.push({
|
|
1296
|
+
state: safeSummary.integration.state || null,
|
|
1297
|
+
claims: safeSummary.integration.claims || 0,
|
|
1298
|
+
conflicts: safeSummary.integration.conflicts || 0,
|
|
1299
|
+
blockers: safeSummary.integration.blockers || 0,
|
|
1300
|
+
detail: safeSummary.integration.detail || null,
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
return {
|
|
1305
|
+
envelopeVersion: 1,
|
|
1306
|
+
agentId: safeAgent.agentId || safeSummary.agentId || null,
|
|
1307
|
+
exitContract,
|
|
1308
|
+
proofArtifacts,
|
|
1309
|
+
deliverables,
|
|
1310
|
+
components,
|
|
1311
|
+
gateClaims,
|
|
1312
|
+
validationOutputs,
|
|
1313
|
+
riskNotes,
|
|
1314
|
+
unresolvedBlockers,
|
|
1315
|
+
docsDeltas,
|
|
1316
|
+
securityFindings,
|
|
1317
|
+
integrationClaims,
|
|
1318
|
+
createdAt: toIsoTimestamp(),
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* Write an agent result envelope alongside the summary file.
|
|
1324
|
+
*
|
|
1325
|
+
* @param {string} statusPath - Path to the .status file
|
|
1326
|
+
* @param {object} envelope - Result from buildAgentResultEnvelope
|
|
1327
|
+
*/
|
|
1328
|
+
export function writeAgentResultEnvelope(statusPath, envelope) {
|
|
1329
|
+
const envelopePath = agentEnvelopePathFromStatusPath(statusPath);
|
|
1330
|
+
writeJsonAtomic(envelopePath, envelope);
|
|
1331
|
+
return envelopePath;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Read an agent result envelope if it exists.
|
|
1336
|
+
*
|
|
1337
|
+
* @param {string} statusPath - Path to the .status file
|
|
1338
|
+
* @returns {object|null} The envelope or null
|
|
1339
|
+
*/
|
|
1340
|
+
export function readAgentResultEnvelope(statusPath) {
|
|
1341
|
+
const envelopePath = agentEnvelopePathFromStatusPath(statusPath);
|
|
1342
|
+
const payload = readJsonOrNull(envelopePath);
|
|
1343
|
+
return payload && typeof payload === "object" ? payload : null;
|
|
1344
|
+
}
|