@chllming/wave-orchestration 0.9.13 → 0.9.15
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 +27 -0
- package/README.md +7 -7
- package/docs/README.md +3 -3
- package/docs/concepts/operating-modes.md +1 -1
- package/docs/guides/author-and-run-waves.md +1 -1
- package/docs/guides/planner.md +2 -2
- package/docs/guides/recommendations-0.9.15.md +83 -0
- package/docs/guides/sandboxed-environments.md +2 -2
- package/docs/guides/signal-wrappers.md +10 -0
- package/docs/plans/agent-first-closure-hardening.md +612 -0
- package/docs/plans/current-state.md +3 -3
- package/docs/plans/end-state-architecture.md +1 -1
- package/docs/plans/examples/wave-example-design-handoff.md +1 -1
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +75 -20
- package/docs/reference/cli-reference.md +34 -1
- package/docs/reference/coordination-and-closure.md +16 -1
- package/docs/reference/npmjs-token-publishing.md +3 -3
- package/docs/reference/package-publishing-flow.md +13 -11
- package/docs/reference/runtime-config/README.md +2 -2
- package/docs/reference/sample-waves.md +5 -5
- package/docs/reference/skills.md +1 -1
- package/docs/reference/wave-control.md +1 -1
- package/docs/roadmap.md +5 -3
- package/package.json +1 -1
- package/releases/manifest.json +35 -0
- package/scripts/wave-orchestrator/agent-state.mjs +221 -313
- package/scripts/wave-orchestrator/artifact-schemas.mjs +37 -2
- package/scripts/wave-orchestrator/closure-adjudicator.mjs +311 -0
- package/scripts/wave-orchestrator/control-cli.mjs +212 -18
- package/scripts/wave-orchestrator/dashboard-state.mjs +40 -0
- package/scripts/wave-orchestrator/derived-state-engine.mjs +3 -0
- package/scripts/wave-orchestrator/gate-engine.mjs +140 -3
- package/scripts/wave-orchestrator/install.mjs +1 -1
- package/scripts/wave-orchestrator/launcher.mjs +49 -10
- package/scripts/wave-orchestrator/signal-cli.mjs +271 -0
- package/scripts/wave-orchestrator/structured-signal-parser.mjs +499 -0
- package/scripts/wave-orchestrator/task-entity.mjs +13 -4
- package/scripts/wave.mjs +9 -0
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
writeJsonAtomic,
|
|
11
11
|
} from "./shared.mjs";
|
|
12
12
|
import { resolveEvalTargetsAgainstCatalog } from "./evals.mjs";
|
|
13
|
+
import { extractStructuredSignalPayload } from "./structured-signal-parser.mjs";
|
|
13
14
|
|
|
14
15
|
export const EXIT_CONTRACT_COMPLETION_VALUES = ["contract", "integrated", "authoritative", "live"];
|
|
15
16
|
export const EXIT_CONTRACT_DURABILITY_VALUES = ["none", "ephemeral", "durable"];
|
|
@@ -55,221 +56,6 @@ const WAVE_GAP_REGEX =
|
|
|
55
56
|
/^\[wave-gap\]\s*kind=(architecture|integration|durability|ops|docs)\s*(?:detail=(.*))?$/gim;
|
|
56
57
|
const WAVE_COMPONENT_REGEX =
|
|
57
58
|
/^\[wave-component\]\s*component=([a-z0-9._-]+)\s+level=([a-z0-9._-]+)\s+state=(met|complete|gap)\s*(?:detail=(.*))?$/gim;
|
|
58
|
-
const STRUCTURED_SIGNAL_LINE_REGEX = /^\[wave-[a-z0-9-]+(?:\]|\s|=|$).*$/i;
|
|
59
|
-
const WRAPPED_STRUCTURED_SIGNAL_LINE_REGEX = /^`\[wave-[^`]+`$/;
|
|
60
|
-
const STRUCTURED_SIGNAL_LIST_PREFIX_REGEX = /^(?:[-*+]|\d+\.)\s+/;
|
|
61
|
-
|
|
62
|
-
const STRUCTURED_SIGNAL_KIND_BY_TAG = {
|
|
63
|
-
proof: "proof",
|
|
64
|
-
"doc-delta": "docDelta",
|
|
65
|
-
"doc-closure": "docClosure",
|
|
66
|
-
integration: "integration",
|
|
67
|
-
eval: "eval",
|
|
68
|
-
security: "security",
|
|
69
|
-
design: "design",
|
|
70
|
-
gate: "gate",
|
|
71
|
-
gap: "gap",
|
|
72
|
-
component: "component",
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const STRUCTURED_SIGNAL_LINE_REGEX_BY_KIND = {
|
|
76
|
-
proof: new RegExp(WAVE_PROOF_REGEX.source, "i"),
|
|
77
|
-
docDelta: new RegExp(WAVE_DOC_DELTA_REGEX.source, "i"),
|
|
78
|
-
docClosure: new RegExp(WAVE_DOC_CLOSURE_REGEX.source, "i"),
|
|
79
|
-
integration: new RegExp(WAVE_INTEGRATION_REGEX.source, "i"),
|
|
80
|
-
eval: new RegExp(WAVE_EVAL_REGEX.source, "i"),
|
|
81
|
-
security: new RegExp(WAVE_SECURITY_REGEX.source, "i"),
|
|
82
|
-
design: new RegExp(WAVE_DESIGN_REGEX.source, "i"),
|
|
83
|
-
gate: new RegExp(WAVE_GATE_REGEX.source, "i"),
|
|
84
|
-
gap: new RegExp(WAVE_GAP_REGEX.source, "i"),
|
|
85
|
-
component: new RegExp(WAVE_COMPONENT_REGEX.source, "i"),
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
function buildEmptyStructuredSignalDiagnostics() {
|
|
89
|
-
return {
|
|
90
|
-
proof: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
91
|
-
docDelta: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
92
|
-
docClosure: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
93
|
-
integration: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
94
|
-
eval: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
95
|
-
security: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
96
|
-
design: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
97
|
-
gate: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
98
|
-
gap: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
|
|
99
|
-
component: { rawCount: 0, acceptedCount: 0, rejectedSamples: [], seenComponentIds: [] },
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function pushRejectedStructuredSignalSample(bucket, sample) {
|
|
104
|
-
if (!bucket || !sample || bucket.rejectedSamples.length >= 3) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
bucket.rejectedSamples.push(sample);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function normalizeStructuredSignalLine(line) {
|
|
111
|
-
const trimmed = String(line || "").trim();
|
|
112
|
-
if (!trimmed) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
const withoutListPrefix = trimmed.replace(STRUCTURED_SIGNAL_LIST_PREFIX_REGEX, "").trim();
|
|
116
|
-
if (STRUCTURED_SIGNAL_LINE_REGEX.test(withoutListPrefix)) {
|
|
117
|
-
return withoutListPrefix;
|
|
118
|
-
}
|
|
119
|
-
if (WRAPPED_STRUCTURED_SIGNAL_LINE_REGEX.test(withoutListPrefix)) {
|
|
120
|
-
return withoutListPrefix.slice(1, -1).trim();
|
|
121
|
-
}
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function parseStructuredSignalCandidate(line) {
|
|
126
|
-
const rawLine = String(line || "").trim();
|
|
127
|
-
if (!rawLine) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
const canonicalLine = normalizeStructuredSignalLine(rawLine);
|
|
131
|
-
if (!canonicalLine) {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
const tagMatch = canonicalLine.match(/^\[wave-([a-z0-9-]+)(?:\]|\s|=|$)/i);
|
|
135
|
-
if (!tagMatch) {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
const kind = STRUCTURED_SIGNAL_KIND_BY_TAG[String(tagMatch[1] || "").toLowerCase()] || null;
|
|
139
|
-
const componentIdMatch = canonicalLine.match(/\bcomponent=([a-z0-9._-]+)/i);
|
|
140
|
-
return {
|
|
141
|
-
rawLine,
|
|
142
|
-
canonicalLine,
|
|
143
|
-
kind,
|
|
144
|
-
componentId: componentIdMatch ? String(componentIdMatch[1] || "").trim() : null,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function appendParsedStructuredSignalCandidates(lines, candidates, { requireAll = false } = {}) {
|
|
149
|
-
const parsedCandidates = [];
|
|
150
|
-
for (const line of lines || []) {
|
|
151
|
-
const candidate = parseStructuredSignalCandidate(line);
|
|
152
|
-
if (candidate) {
|
|
153
|
-
parsedCandidates.push(candidate);
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
if (requireAll) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
candidates.push(...parsedCandidates);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function collectEmbeddedStructuredSignalTexts(value, texts) {
|
|
164
|
-
if (!value || typeof value !== "object") {
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
if (Array.isArray(value)) {
|
|
168
|
-
for (const item of value) {
|
|
169
|
-
collectEmbeddedStructuredSignalTexts(item, texts);
|
|
170
|
-
}
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (typeof value.text === "string") {
|
|
174
|
-
texts.push(value.text);
|
|
175
|
-
}
|
|
176
|
-
if (typeof value.aggregated_output === "string") {
|
|
177
|
-
texts.push(value.aggregated_output);
|
|
178
|
-
}
|
|
179
|
-
for (const nestedValue of Object.values(value)) {
|
|
180
|
-
if (nestedValue && typeof nestedValue === "object") {
|
|
181
|
-
collectEmbeddedStructuredSignalTexts(nestedValue, texts);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function extractEmbeddedStructuredSignalTextsFromJsonLine(line) {
|
|
187
|
-
const trimmed = String(line || "").trim();
|
|
188
|
-
if (!trimmed || !/^[{\[]/.test(trimmed)) {
|
|
189
|
-
return [];
|
|
190
|
-
}
|
|
191
|
-
try {
|
|
192
|
-
const payload = JSON.parse(trimmed);
|
|
193
|
-
const texts = [];
|
|
194
|
-
collectEmbeddedStructuredSignalTexts(payload, texts);
|
|
195
|
-
return texts.filter(Boolean);
|
|
196
|
-
} catch {
|
|
197
|
-
return [];
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function collectStructuredSignalCandidates(text) {
|
|
202
|
-
if (!text) {
|
|
203
|
-
return [];
|
|
204
|
-
}
|
|
205
|
-
const candidates = [];
|
|
206
|
-
let fenceLines = null;
|
|
207
|
-
for (const rawLine of String(text || "").split(/\r?\n/)) {
|
|
208
|
-
const embeddedTexts = extractEmbeddedStructuredSignalTextsFromJsonLine(rawLine);
|
|
209
|
-
for (const embeddedText of embeddedTexts) {
|
|
210
|
-
candidates.push(...collectStructuredSignalCandidates(embeddedText));
|
|
211
|
-
}
|
|
212
|
-
const trimmed = rawLine.trim();
|
|
213
|
-
if (/^```/.test(trimmed)) {
|
|
214
|
-
if (fenceLines === null) {
|
|
215
|
-
fenceLines = [];
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
appendParsedStructuredSignalCandidates(fenceLines, candidates, { requireAll: true });
|
|
219
|
-
fenceLines = null;
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
if (fenceLines !== null) {
|
|
223
|
-
if (!trimmed) {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
fenceLines.push(rawLine);
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
const candidate = parseStructuredSignalCandidate(rawLine);
|
|
230
|
-
if (candidate) {
|
|
231
|
-
candidates.push(candidate);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
if (fenceLines !== null) {
|
|
235
|
-
appendParsedStructuredSignalCandidates(fenceLines, candidates);
|
|
236
|
-
}
|
|
237
|
-
return candidates;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function buildStructuredSignalDiagnostics(candidates) {
|
|
241
|
-
const diagnostics = buildEmptyStructuredSignalDiagnostics();
|
|
242
|
-
for (const candidate of candidates || []) {
|
|
243
|
-
if (!candidate?.kind || !diagnostics[candidate.kind]) {
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
const bucket = diagnostics[candidate.kind];
|
|
247
|
-
bucket.rawCount += 1;
|
|
248
|
-
if (candidate.kind === "component" && candidate.componentId) {
|
|
249
|
-
bucket.seenComponentIds.push(candidate.componentId);
|
|
250
|
-
}
|
|
251
|
-
const strictRegex = STRUCTURED_SIGNAL_LINE_REGEX_BY_KIND[candidate.kind];
|
|
252
|
-
if (strictRegex.test(candidate.canonicalLine)) {
|
|
253
|
-
bucket.acceptedCount += 1;
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
pushRejectedStructuredSignalSample(bucket, {
|
|
257
|
-
line: candidate.rawLine,
|
|
258
|
-
...(candidate.kind === "component" && candidate.componentId ? { componentId: candidate.componentId } : {}),
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
diagnostics.component.seenComponentIds = Array.from(new Set(diagnostics.component.seenComponentIds)).sort();
|
|
262
|
-
return diagnostics;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function extractStructuredSignalPayload(text) {
|
|
266
|
-
const candidates = collectStructuredSignalCandidates(text);
|
|
267
|
-
return {
|
|
268
|
-
signalText: candidates.map((candidate) => candidate.canonicalLine).join("\n"),
|
|
269
|
-
diagnostics: buildStructuredSignalDiagnostics(candidates),
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
59
|
function cleanText(value) {
|
|
274
60
|
return String(value || "").trim();
|
|
275
61
|
}
|
|
@@ -712,6 +498,12 @@ function rejectedStructuredSignalLine(summary, key, predicate = null) {
|
|
|
712
498
|
return cleanText(match?.line || "");
|
|
713
499
|
}
|
|
714
500
|
|
|
501
|
+
function rejectedStructuredSignalSample(summary, key, predicate = null) {
|
|
502
|
+
const bucket = structuredSignalBucket(summary, key);
|
|
503
|
+
const rejected = Array.isArray(bucket?.rejectedSamples) ? bucket.rejectedSamples : [];
|
|
504
|
+
return (typeof predicate === "function" ? rejected.find(predicate) : rejected[0]) || null;
|
|
505
|
+
}
|
|
506
|
+
|
|
715
507
|
function hasRejectedStructuredSignal(summary, key) {
|
|
716
508
|
const bucket = structuredSignalBucket(summary, key);
|
|
717
509
|
return Number(bucket?.rawCount || 0) > 0 && Number(bucket?.acceptedCount || 0) === 0;
|
|
@@ -731,80 +523,157 @@ function invalidStructuredSignalDetail(agentId, markerName, summary, key, extraD
|
|
|
731
523
|
return appendTerminationHint(detailParts.join(" "), summary);
|
|
732
524
|
}
|
|
733
525
|
|
|
526
|
+
function buildValidationResult(ok, statusCode, detail, extras = {}) {
|
|
527
|
+
return {
|
|
528
|
+
ok,
|
|
529
|
+
statusCode,
|
|
530
|
+
detail,
|
|
531
|
+
failureClass: ok ? null : extras.failureClass || null,
|
|
532
|
+
eligibleForAdjudication: ok ? false : extras.eligibleForAdjudication === true,
|
|
533
|
+
adjudicationHint: ok ? null : extras.adjudicationHint || null,
|
|
534
|
+
...extras,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function implementationTransportEligibility(agent, summary) {
|
|
539
|
+
const deliverables = Array.isArray(agent?.deliverables) ? agent.deliverables : [];
|
|
540
|
+
const deliverableState = new Map(
|
|
541
|
+
Array.isArray(summary?.deliverables)
|
|
542
|
+
? summary.deliverables.map((deliverable) => [deliverable.path, deliverable])
|
|
543
|
+
: [],
|
|
544
|
+
);
|
|
545
|
+
const deliverablesPresent = deliverables.every((deliverablePath) => deliverableState.get(deliverablePath)?.exists === true);
|
|
546
|
+
const proofArtifacts = Array.isArray(agent?.proofArtifacts) ? agent.proofArtifacts : [];
|
|
547
|
+
const artifactState = new Map(
|
|
548
|
+
Array.isArray(summary?.proofArtifacts)
|
|
549
|
+
? summary.proofArtifacts.map((artifact) => [artifact.path, artifact])
|
|
550
|
+
: [],
|
|
551
|
+
);
|
|
552
|
+
const proofArtifactsPresent = proofArtifacts
|
|
553
|
+
.filter((proofArtifact) => proofArtifactRequiredForAgent(agent, proofArtifact))
|
|
554
|
+
.every((proofArtifact) => artifactState.get(proofArtifact.path)?.exists === true);
|
|
555
|
+
const exitCodeZero = Number(summary?.exitCode) === 0;
|
|
556
|
+
return {
|
|
557
|
+
deliverablesPresent,
|
|
558
|
+
proofArtifactsPresent,
|
|
559
|
+
exitCodeZero,
|
|
560
|
+
eligibleForAdjudication: exitCodeZero && deliverablesPresent && proofArtifactsPresent,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function implementationInvalidMarkerEligibility(agent, summary, key, predicate = null) {
|
|
565
|
+
const transportEligibility = implementationTransportEligibility(agent, summary);
|
|
566
|
+
const sample = rejectedStructuredSignalSample(summary, key, predicate);
|
|
567
|
+
return {
|
|
568
|
+
...transportEligibility,
|
|
569
|
+
rejectedSample: sample,
|
|
570
|
+
eligibleForAdjudication: transportEligibility.eligibleForAdjudication && Boolean(sample),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
734
574
|
export function validateImplementationSummary(agent, summary) {
|
|
735
575
|
const contract = normalizeExitContract(agent?.exitContract);
|
|
736
576
|
if (!contract) {
|
|
737
|
-
return
|
|
577
|
+
return buildValidationResult(true, "pass", "No exit contract declared.");
|
|
738
578
|
}
|
|
739
579
|
if (!summary) {
|
|
740
|
-
return {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
detail: `Missing execution summary for ${agent.agentId}.`,
|
|
744
|
-
};
|
|
580
|
+
return buildValidationResult(false, "missing-summary", `Missing execution summary for ${agent.agentId}.`, {
|
|
581
|
+
failureClass: "state-failure",
|
|
582
|
+
});
|
|
745
583
|
}
|
|
746
584
|
if (!summary.proof) {
|
|
747
585
|
if (hasRejectedStructuredSignal(summary, "proof")) {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
586
|
+
const transportEligibility = implementationInvalidMarkerEligibility(agent, summary, "proof");
|
|
587
|
+
return buildValidationResult(
|
|
588
|
+
false,
|
|
589
|
+
"invalid-wave-proof-format",
|
|
590
|
+
invalidStructuredSignalDetail(agent.agentId, "[wave-proof]", summary, "proof"),
|
|
591
|
+
{
|
|
592
|
+
failureClass: "transport-failure",
|
|
593
|
+
eligibleForAdjudication: transportEligibility.eligibleForAdjudication,
|
|
594
|
+
adjudicationHint: transportEligibility.eligibleForAdjudication
|
|
595
|
+
? "Malformed proof marker with exit 0 and landed artifacts is eligible for deterministic adjudication."
|
|
596
|
+
: null,
|
|
597
|
+
},
|
|
598
|
+
);
|
|
753
599
|
}
|
|
754
|
-
return
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
600
|
+
return buildValidationResult(
|
|
601
|
+
false,
|
|
602
|
+
"missing-wave-proof",
|
|
603
|
+
appendTerminationHint(`Missing [wave-proof] marker for ${agent.agentId}.`, summary),
|
|
604
|
+
{
|
|
605
|
+
failureClass: "transport-failure",
|
|
606
|
+
eligibleForAdjudication: false,
|
|
607
|
+
adjudicationHint: null,
|
|
608
|
+
},
|
|
609
|
+
);
|
|
759
610
|
}
|
|
760
611
|
if (summary.proof.state !== "met") {
|
|
761
|
-
return
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
612
|
+
return buildValidationResult(
|
|
613
|
+
false,
|
|
614
|
+
"wave-proof-gap",
|
|
615
|
+
`Agent ${agent.agentId} reported a proof gap${summary.proof.detail ? `: ${summary.proof.detail}` : "."}`,
|
|
616
|
+
{ failureClass: "semantic-failure" },
|
|
617
|
+
);
|
|
766
618
|
}
|
|
767
619
|
if (!meetsOrExceeds(summary.proof.completion, contract.completion, COMPLETION_ORDER)) {
|
|
768
|
-
return
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
620
|
+
return buildValidationResult(
|
|
621
|
+
false,
|
|
622
|
+
"completion-gap",
|
|
623
|
+
`Agent ${agent.agentId} only proved ${summary.proof.completion}; exit contract requires ${contract.completion}.`,
|
|
624
|
+
{ failureClass: "semantic-failure" },
|
|
625
|
+
);
|
|
773
626
|
}
|
|
774
627
|
if (!meetsOrExceeds(summary.proof.durability, contract.durability, DURABILITY_ORDER)) {
|
|
775
|
-
return
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
628
|
+
return buildValidationResult(
|
|
629
|
+
false,
|
|
630
|
+
"durability-gap",
|
|
631
|
+
`Agent ${agent.agentId} only proved ${summary.proof.durability} durability; exit contract requires ${contract.durability}.`,
|
|
632
|
+
{ failureClass: "semantic-failure" },
|
|
633
|
+
);
|
|
780
634
|
}
|
|
781
635
|
if (!meetsOrExceeds(summary.proof.proof, contract.proof, PROOF_ORDER)) {
|
|
782
|
-
return
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
636
|
+
return buildValidationResult(
|
|
637
|
+
false,
|
|
638
|
+
"proof-level-gap",
|
|
639
|
+
`Agent ${agent.agentId} only proved ${summary.proof.proof}; exit contract requires ${contract.proof}.`,
|
|
640
|
+
{ failureClass: "semantic-failure" },
|
|
641
|
+
);
|
|
787
642
|
}
|
|
788
643
|
if (!summary.docDelta) {
|
|
789
644
|
if (hasRejectedStructuredSignal(summary, "docDelta")) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
645
|
+
const transportEligibility = implementationInvalidMarkerEligibility(agent, summary, "docDelta");
|
|
646
|
+
return buildValidationResult(
|
|
647
|
+
false,
|
|
648
|
+
"invalid-doc-delta-format",
|
|
649
|
+
invalidStructuredSignalDetail(agent.agentId, "[wave-doc-delta]", summary, "docDelta"),
|
|
650
|
+
{
|
|
651
|
+
failureClass: "transport-failure",
|
|
652
|
+
eligibleForAdjudication: transportEligibility.eligibleForAdjudication,
|
|
653
|
+
adjudicationHint: transportEligibility.eligibleForAdjudication
|
|
654
|
+
? "Malformed doc-delta marker with exit 0 and landed artifacts is eligible for deterministic adjudication."
|
|
655
|
+
: null,
|
|
656
|
+
},
|
|
657
|
+
);
|
|
795
658
|
}
|
|
796
|
-
return
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
659
|
+
return buildValidationResult(
|
|
660
|
+
false,
|
|
661
|
+
"missing-doc-delta",
|
|
662
|
+
appendTerminationHint(`Missing [wave-doc-delta] marker for ${agent.agentId}.`, summary),
|
|
663
|
+
{
|
|
664
|
+
failureClass: "transport-failure",
|
|
665
|
+
eligibleForAdjudication: false,
|
|
666
|
+
adjudicationHint: null,
|
|
667
|
+
},
|
|
668
|
+
);
|
|
801
669
|
}
|
|
802
670
|
if (!meetsOrExceeds(summary.docDelta.state, contract.docImpact, DOC_IMPACT_ORDER)) {
|
|
803
|
-
return
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
671
|
+
return buildValidationResult(
|
|
672
|
+
false,
|
|
673
|
+
"doc-impact-gap",
|
|
674
|
+
`Agent ${agent.agentId} only reported ${summary.docDelta.state} doc impact; exit contract requires ${contract.docImpact}.`,
|
|
675
|
+
{ failureClass: "semantic-failure" },
|
|
676
|
+
);
|
|
808
677
|
}
|
|
809
678
|
const ownedComponents = Array.isArray(agent?.components) ? agent.components : [];
|
|
810
679
|
if (ownedComponents.length > 0) {
|
|
@@ -824,10 +693,16 @@ export function validateImplementationSummary(agent, summary) {
|
|
|
824
693
|
Number(componentDiagnostics?.rawCount || 0) > 0 &&
|
|
825
694
|
(seenComponentIds.has(componentId) || Number(componentDiagnostics?.acceptedCount || 0) === 0)
|
|
826
695
|
) {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
696
|
+
const transportEligibility = implementationInvalidMarkerEligibility(
|
|
697
|
+
agent,
|
|
698
|
+
summary,
|
|
699
|
+
"component",
|
|
700
|
+
(sample) => cleanText(sample?.componentId) === componentId,
|
|
701
|
+
);
|
|
702
|
+
return buildValidationResult(
|
|
703
|
+
false,
|
|
704
|
+
"invalid-wave-component-format",
|
|
705
|
+
invalidStructuredSignalDetail(
|
|
831
706
|
agent.agentId,
|
|
832
707
|
"[wave-component]",
|
|
833
708
|
summary,
|
|
@@ -835,30 +710,43 @@ export function validateImplementationSummary(agent, summary) {
|
|
|
835
710
|
`Expected a valid component marker for ${componentId}.`,
|
|
836
711
|
(sample) => cleanText(sample?.componentId) === componentId,
|
|
837
712
|
),
|
|
838
|
-
|
|
713
|
+
{
|
|
714
|
+
failureClass: "transport-failure",
|
|
715
|
+
eligibleForAdjudication: transportEligibility.eligibleForAdjudication,
|
|
716
|
+
adjudicationHint: transportEligibility.eligibleForAdjudication
|
|
717
|
+
? "Malformed component marker with exit 0 and landed artifacts is eligible for deterministic adjudication."
|
|
718
|
+
: null,
|
|
719
|
+
},
|
|
720
|
+
);
|
|
839
721
|
}
|
|
840
|
-
return
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
722
|
+
return buildValidationResult(
|
|
723
|
+
false,
|
|
724
|
+
"missing-wave-component",
|
|
725
|
+
`Missing [wave-component] marker for ${agent.agentId} component ${componentId}.`,
|
|
726
|
+
{
|
|
727
|
+
failureClass: "transport-failure",
|
|
728
|
+
eligibleForAdjudication: false,
|
|
729
|
+
adjudicationHint: null,
|
|
730
|
+
},
|
|
731
|
+
);
|
|
845
732
|
}
|
|
846
733
|
const expectedLevel = agent?.componentTargets?.[componentId] || null;
|
|
847
734
|
if (expectedLevel && marker.level !== expectedLevel) {
|
|
848
|
-
return
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
735
|
+
return buildValidationResult(
|
|
736
|
+
false,
|
|
737
|
+
"component-level-mismatch",
|
|
738
|
+
`Agent ${agent.agentId} reported ${componentId} at ${marker.level}; wave requires ${expectedLevel}.`,
|
|
739
|
+
{ failureClass: "semantic-failure" },
|
|
740
|
+
);
|
|
853
741
|
}
|
|
854
742
|
if (marker.state !== "met") {
|
|
855
|
-
return
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
detail
|
|
859
|
-
marker.detail ||
|
|
743
|
+
return buildValidationResult(
|
|
744
|
+
false,
|
|
745
|
+
"component-gap",
|
|
746
|
+
marker.detail ||
|
|
860
747
|
`Agent ${agent.agentId} reported a component gap for ${componentId}.`,
|
|
861
|
-
|
|
748
|
+
{ failureClass: "semantic-failure" },
|
|
749
|
+
);
|
|
862
750
|
}
|
|
863
751
|
}
|
|
864
752
|
}
|
|
@@ -872,18 +760,20 @@ export function validateImplementationSummary(agent, summary) {
|
|
|
872
760
|
for (const deliverablePath of deliverables) {
|
|
873
761
|
const deliverable = deliverableState.get(deliverablePath);
|
|
874
762
|
if (!deliverable) {
|
|
875
|
-
return
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
763
|
+
return buildValidationResult(
|
|
764
|
+
false,
|
|
765
|
+
"missing-deliverable-summary",
|
|
766
|
+
`Missing deliverable presence record for ${agent.agentId} path ${deliverablePath}.`,
|
|
767
|
+
{ failureClass: "artifact-failure" },
|
|
768
|
+
);
|
|
880
769
|
}
|
|
881
770
|
if (deliverable.exists !== true) {
|
|
882
|
-
return
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
771
|
+
return buildValidationResult(
|
|
772
|
+
false,
|
|
773
|
+
"missing-deliverable",
|
|
774
|
+
`Agent ${agent.agentId} did not land required deliverable ${deliverablePath}.`,
|
|
775
|
+
{ failureClass: "artifact-failure" },
|
|
776
|
+
);
|
|
887
777
|
}
|
|
888
778
|
}
|
|
889
779
|
}
|
|
@@ -900,30 +790,48 @@ export function validateImplementationSummary(agent, summary) {
|
|
|
900
790
|
}
|
|
901
791
|
const artifact = artifactState.get(proofArtifact.path);
|
|
902
792
|
if (!artifact) {
|
|
903
|
-
return
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
793
|
+
return buildValidationResult(
|
|
794
|
+
false,
|
|
795
|
+
"missing-proof-artifact-summary",
|
|
796
|
+
`Missing proof artifact presence record for ${agent.agentId} path ${proofArtifact.path}.`,
|
|
797
|
+
{ failureClass: "artifact-failure" },
|
|
798
|
+
);
|
|
908
799
|
}
|
|
909
800
|
if (artifact.exists !== true) {
|
|
910
|
-
return
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
801
|
+
return buildValidationResult(
|
|
802
|
+
false,
|
|
803
|
+
"missing-proof-artifact",
|
|
804
|
+
`Agent ${agent.agentId} did not land required proof artifact ${proofArtifact.path}.`,
|
|
805
|
+
{ failureClass: "artifact-failure" },
|
|
806
|
+
);
|
|
915
807
|
}
|
|
916
808
|
}
|
|
917
809
|
}
|
|
918
|
-
return {
|
|
919
|
-
ok: true,
|
|
920
|
-
statusCode: "pass",
|
|
921
|
-
detail: `Exit contract satisfied for ${agent.agentId}.`,
|
|
922
|
-
};
|
|
810
|
+
return buildValidationResult(true, "pass", `Exit contract satisfied for ${agent.agentId}.`);
|
|
923
811
|
}
|
|
924
812
|
|
|
925
|
-
export function validateDocumentationClosureSummary(agent, summary) {
|
|
813
|
+
export function validateDocumentationClosureSummary(agent, summary, options = {}) {
|
|
926
814
|
if (!summary?.docClosure) {
|
|
815
|
+
// When the agent had exit-code 0 but produced no structured output (e.g. credential
|
|
816
|
+
// broker collision, empty run), allow a graceful fallback instead of hard-failing
|
|
817
|
+
// the entire wave. The caller (gate-engine) decides whether the surrounding
|
|
818
|
+
// integration/QA state justifies auto-closing documentation.
|
|
819
|
+
const emptyRun = !summary || (
|
|
820
|
+
!summary.proof && !summary.component && !summary.integration &&
|
|
821
|
+
!summary.verdict && !summary.docClosure && !summary.security &&
|
|
822
|
+
(summary.rawSignalCount === 0 || summary.rawSignalCount === undefined)
|
|
823
|
+
);
|
|
824
|
+
if (options.allowFallbackOnEmptyRun && emptyRun) {
|
|
825
|
+
return {
|
|
826
|
+
ok: false,
|
|
827
|
+
statusCode: "missing-doc-closure-empty-run",
|
|
828
|
+
detail: appendTerminationHint(
|
|
829
|
+
`Documentation steward ${agent?.agentId || "A9"} produced no output (empty run). Eligible for fallback auto-closure.`,
|
|
830
|
+
summary,
|
|
831
|
+
),
|
|
832
|
+
eligibleForFallback: true,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
927
835
|
return {
|
|
928
836
|
ok: false,
|
|
929
837
|
statusCode: "missing-doc-closure",
|
|
@@ -5,9 +5,10 @@ import {
|
|
|
5
5
|
} from "./wave-control-schema.mjs";
|
|
6
6
|
|
|
7
7
|
export const MANIFEST_SCHEMA_VERSION = 1;
|
|
8
|
-
export const GLOBAL_DASHBOARD_SCHEMA_VERSION =
|
|
9
|
-
export const WAVE_DASHBOARD_SCHEMA_VERSION =
|
|
8
|
+
export const GLOBAL_DASHBOARD_SCHEMA_VERSION = 2;
|
|
9
|
+
export const WAVE_DASHBOARD_SCHEMA_VERSION = 2;
|
|
10
10
|
export const RELAUNCH_PLAN_SCHEMA_VERSION = 1;
|
|
11
|
+
export const CLOSURE_ADJUDICATION_SCHEMA_VERSION = 1;
|
|
11
12
|
export const RETRY_OVERRIDE_SCHEMA_VERSION = 1;
|
|
12
13
|
export const ASSIGNMENT_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
13
14
|
export const DEPENDENCY_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
@@ -19,6 +20,7 @@ export const MANIFEST_KIND = "wave-manifest";
|
|
|
19
20
|
export const GLOBAL_DASHBOARD_KIND = "global-dashboard";
|
|
20
21
|
export const WAVE_DASHBOARD_KIND = "wave-dashboard";
|
|
21
22
|
export const RELAUNCH_PLAN_KIND = "wave-relaunch-plan";
|
|
23
|
+
export const CLOSURE_ADJUDICATION_KIND = "wave-closure-adjudication";
|
|
22
24
|
export const RETRY_OVERRIDE_KIND = "wave-retry-override";
|
|
23
25
|
export const ASSIGNMENT_SNAPSHOT_KIND = "wave-assignment-snapshot";
|
|
24
26
|
export const DEPENDENCY_SNAPSHOT_KIND = "wave-dependency-snapshot";
|
|
@@ -129,6 +131,39 @@ export function writeRelaunchPlan(filePath, payload, defaults = {}) {
|
|
|
129
131
|
return normalized;
|
|
130
132
|
}
|
|
131
133
|
|
|
134
|
+
export function normalizeClosureAdjudication(payload, defaults = {}) {
|
|
135
|
+
const source = isPlainObject(payload) ? payload : {};
|
|
136
|
+
return {
|
|
137
|
+
schemaVersion: CLOSURE_ADJUDICATION_SCHEMA_VERSION,
|
|
138
|
+
kind: CLOSURE_ADJUDICATION_KIND,
|
|
139
|
+
lane: normalizeText(source.lane, normalizeText(defaults.lane, null)),
|
|
140
|
+
wave: normalizeInteger(source.wave, normalizeInteger(defaults.wave, null)),
|
|
141
|
+
attempt: normalizeInteger(source.attempt, normalizeInteger(defaults.attempt, null)),
|
|
142
|
+
agentId: normalizeText(source.agentId, normalizeText(defaults.agentId, null)),
|
|
143
|
+
status: normalizeText(source.status, normalizeText(defaults.status, null)),
|
|
144
|
+
failureClass: normalizeText(source.failureClass, normalizeText(defaults.failureClass, null)),
|
|
145
|
+
reason: normalizeText(source.reason, normalizeText(defaults.reason, null)),
|
|
146
|
+
detail: normalizeText(source.detail, normalizeText(defaults.detail, null)),
|
|
147
|
+
evidence: Array.isArray(source.evidence) ? cloneJson(source.evidence) : [],
|
|
148
|
+
synthesizedSignals: Array.isArray(source.synthesizedSignals) ? cloneJson(source.synthesizedSignals) : [],
|
|
149
|
+
createdAt: normalizeText(source.createdAt, normalizeText(defaults.createdAt, toIsoTimestamp())),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function readClosureAdjudication(filePath, defaults = {}) {
|
|
154
|
+
const payload = readJsonOrNull(filePath);
|
|
155
|
+
if (!payload) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return normalizeClosureAdjudication(payload, defaults);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function writeClosureAdjudication(filePath, payload, defaults = {}) {
|
|
162
|
+
const normalized = normalizeClosureAdjudication(payload, defaults);
|
|
163
|
+
writeJsonAtomic(filePath, normalized);
|
|
164
|
+
return normalized;
|
|
165
|
+
}
|
|
166
|
+
|
|
132
167
|
export function normalizeRetryOverride(payload, defaults = {}) {
|
|
133
168
|
const source = isPlainObject(payload) ? payload : {};
|
|
134
169
|
return {
|