@agwab/pi-workflow 0.2.1 → 0.4.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/README.md +3 -1
- package/dist/artifact-graph-runtime.d.ts +1 -1
- package/dist/artifact-graph-runtime.js +10 -5
- package/dist/artifact-graph-schema.js +127 -5
- package/dist/compiler.js +52 -19
- package/dist/dynamic-generated-task-runtime.js +3 -1
- package/dist/dynamic-profiles.d.ts +1 -1
- package/dist/engine-run-graph.d.ts +3 -0
- package/dist/engine-run-graph.js +194 -4
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +389 -41
- package/dist/extension.d.ts +2 -1
- package/dist/extension.js +30 -8
- package/dist/index.d.ts +11 -3
- package/dist/index.js +6 -1
- package/dist/prompt-json.d.ts +7 -0
- package/dist/prompt-json.js +13 -0
- package/dist/roles.d.ts +1 -1
- package/dist/roles.js +5 -8
- package/dist/store.d.ts +20 -1
- package/dist/store.js +139 -35
- package/dist/strings.d.ts +11 -0
- package/dist/strings.js +24 -0
- package/dist/subagent-backend.js +710 -40
- package/dist/types.d.ts +107 -1
- package/dist/verification-ontology.d.ts +31 -0
- package/dist/verification-ontology.js +66 -0
- package/dist/workflow-artifact-tool.js +5 -6
- package/dist/workflow-artifacts.d.ts +7 -0
- package/dist/workflow-artifacts.js +55 -4
- package/dist/workflow-fetch-cache-extension.d.ts +1 -0
- package/dist/workflow-fetch-cache-extension.js +57 -9
- package/dist/workflow-metrics.d.ts +113 -0
- package/dist/workflow-metrics.js +272 -0
- package/dist/workflow-output-artifacts.js +5 -3
- package/dist/workflow-partial-output.d.ts +45 -0
- package/dist/workflow-partial-output.js +205 -0
- package/dist/workflow-progress-health.js +42 -10
- package/dist/workflow-runtime.js +10 -1
- package/dist/workflow-view.js +3 -1
- package/dist/workflow-web-source-extension.js +194 -52
- package/dist/workflow-web-source.d.ts +2 -1
- package/dist/workflow-web-source.js +109 -30
- package/docs/usage.md +76 -29
- package/node_modules/@agwab/pi-subagent/README.md +3 -3
- package/node_modules/@agwab/pi-subagent/api.mjs +1 -0
- package/node_modules/@agwab/pi-subagent/docs/usage.md +63 -12
- package/node_modules/@agwab/pi-subagent/package.json +2 -2
- package/node_modules/@agwab/pi-subagent/src/api.ts +54 -1
- package/node_modules/@agwab/pi-subagent/src/artifacts/registry.ts +9 -4
- package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +8 -0
- package/node_modules/@agwab/pi-subagent/src/core/constants.ts +9 -0
- package/node_modules/@agwab/pi-subagent/src/core/validation.ts +21 -0
- package/node_modules/@agwab/pi-subagent/src/index.ts +1046 -576
- package/node_modules/@agwab/pi-subagent/src/orchestrate/async.ts +279 -156
- package/node_modules/@agwab/pi-subagent/src/orchestrate/interrupt.ts +165 -89
- package/node_modules/@agwab/pi-subagent/src/orchestrate/reconcile.ts +111 -65
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run-ref.ts +219 -0
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +88 -8
- package/node_modules/@agwab/pi-subagent/src/orchestrate/status.ts +614 -298
- package/node_modules/@agwab/pi-subagent/src/panel.ts +1356 -560
- package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +53 -5
- package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +13 -6
- package/package.json +2 -2
- package/skills/workflow-guide/SKILL.md +1 -0
- package/src/artifact-graph-runtime.ts +19 -13
- package/src/artifact-graph-schema.ts +143 -3
- package/src/cli.mjs +52 -0
- package/src/compiler.ts +63 -18
- package/src/dynamic-generated-task-runtime.ts +3 -1
- package/src/dynamic-profiles.ts +1 -1
- package/src/engine-run-graph.ts +246 -4
- package/src/engine.ts +545 -38
- package/src/extension.ts +36 -6
- package/src/index.ts +52 -1
- package/src/prompt-json.ts +13 -0
- package/src/roles.ts +6 -9
- package/src/store.ts +194 -42
- package/src/strings.ts +38 -0
- package/src/subagent-backend.ts +921 -62
- package/src/types.ts +116 -2
- package/src/verification-ontology.ts +88 -0
- package/src/workflow-artifact-tool.ts +5 -7
- package/src/workflow-artifacts.ts +83 -3
- package/src/workflow-fetch-cache-extension.ts +78 -13
- package/src/workflow-metrics.ts +478 -0
- package/src/workflow-output-artifacts.ts +5 -3
- package/src/workflow-partial-output.ts +299 -0
- package/src/workflow-progress-health.ts +47 -15
- package/src/workflow-runtime.ts +18 -2
- package/src/workflow-view.ts +2 -1
- package/src/workflow-web-source-extension.ts +654 -232
- package/src/workflow-web-source.ts +153 -39
- package/workflows/README.md +7 -25
- package/workflows/deep-research/batched-verification.spec.json +253 -0
- package/workflows/deep-research/helpers/batch-verification-candidates.mjs +136 -0
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +229 -36
- package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +81 -2
- package/workflows/deep-research/helpers/render-executive.mjs +40 -26
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
- package/workflows/deep-research/helpers/shadow-select-verification.mjs +229 -0
- package/workflows/deep-research/helpers/verification-ontology.mjs +77 -0
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +3 -3
- package/workflows/deep-research/schemas/deep-research-research-questions-control.schema.json +38 -0
- package/workflows/deep-research/schemas/deep-research-sanitize-claims-control.schema.json +63 -0
- package/workflows/deep-research/schemas/deep-research-verify-claims-batch-control.schema.json +47 -0
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +13 -3
- package/workflows/deep-research/spec.json +32 -12
- package/workflows/impact-review/spec.json +3 -3
- package/workflows/spec-review/helpers/spec-review-pipeline.mjs +1 -8
- package/dist/dynamic-loader.d.ts +0 -25
- package/dist/dynamic-loader.js +0 -13
- package/skills/workflow-guide/scaffolds/dag-required-reads/spec.json.validate.stderr +0 -0
- package/skills/workflow-guide/scaffolds/dag-required-reads/spec.json.validate.stdout +0 -13
- package/src/dynamic-loader.ts +0 -49
- package/workflows/impact-review/schemas/docs-release-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/security-performance-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/state-data-impact-control.schema.json +0 -42
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Deterministic verification candidate batch planner for deep-research.
|
|
2
|
+
//
|
|
3
|
+
// This helper is intentionally planning-only: it groups sanitized verification
|
|
4
|
+
// candidates into stable batch records but does not change verifier semantics or
|
|
5
|
+
// skip single-claim verification. A later workflow can consume these batches only
|
|
6
|
+
// after per-claim result identity and fallback gates pass.
|
|
7
|
+
|
|
8
|
+
function asArray(value) {
|
|
9
|
+
if (Array.isArray(value)) return value;
|
|
10
|
+
if (value && typeof value === "object") {
|
|
11
|
+
if (Array.isArray(value.claimInventory?.verificationCandidates))
|
|
12
|
+
return value.claimInventory.verificationCandidates;
|
|
13
|
+
if (Array.isArray(value.verificationCandidates))
|
|
14
|
+
return value.verificationCandidates;
|
|
15
|
+
if (Array.isArray(value.claims)) return value.claims;
|
|
16
|
+
if (Array.isArray(value.items)) return value.items;
|
|
17
|
+
}
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function findCandidates(sources) {
|
|
22
|
+
for (const [specId, source] of Object.entries(sources ?? {})) {
|
|
23
|
+
if (specId === "sanitize-claims" || specId.startsWith("sanitize-claims.")) {
|
|
24
|
+
const candidates = asArray(source);
|
|
25
|
+
if (candidates.length > 0) return candidates;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const [specId, source] of Object.entries(sources ?? {})) {
|
|
29
|
+
if (
|
|
30
|
+
specId === "normalize-claims" ||
|
|
31
|
+
specId.startsWith("normalize-claims.")
|
|
32
|
+
) {
|
|
33
|
+
const candidates = asArray(source);
|
|
34
|
+
if (candidates.length > 0) return candidates;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stableId(value, fallback) {
|
|
41
|
+
const id = typeof value?.id === "string" ? value.id.trim() : "";
|
|
42
|
+
return id || fallback;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sourceKey(candidate) {
|
|
46
|
+
const refs = Array.isArray(candidate?.sourceRefs)
|
|
47
|
+
? candidate.sourceRefs.filter(
|
|
48
|
+
(ref) => typeof ref === "string" && ref.trim(),
|
|
49
|
+
)
|
|
50
|
+
: [];
|
|
51
|
+
if (refs.length > 0) return `refs:${refs.slice().sort().join("|")}`;
|
|
52
|
+
const urls = Array.isArray(candidate?.sourceUrls)
|
|
53
|
+
? candidate.sourceUrls.filter(
|
|
54
|
+
(url) => typeof url === "string" && url.trim(),
|
|
55
|
+
)
|
|
56
|
+
: [];
|
|
57
|
+
if (urls.length > 0) return `urls:${urls.slice().sort().join("|")}`;
|
|
58
|
+
return "refs:none";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeMaxBatchSize(value) {
|
|
62
|
+
const parsed = Number(value ?? 2);
|
|
63
|
+
if (!Number.isInteger(parsed) || parsed < 1) return 2;
|
|
64
|
+
return Math.min(parsed, 4);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function cloneCandidate(candidate, id) {
|
|
68
|
+
return {
|
|
69
|
+
...candidate,
|
|
70
|
+
id,
|
|
71
|
+
...(Array.isArray(candidate.sourceRefs)
|
|
72
|
+
? { sourceRefs: [...candidate.sourceRefs] }
|
|
73
|
+
: {}),
|
|
74
|
+
...(Array.isArray(candidate.sourceUrls)
|
|
75
|
+
? { sourceUrls: [...candidate.sourceUrls] }
|
|
76
|
+
: {}),
|
|
77
|
+
...(Array.isArray(candidate.sourceEvidenceHints)
|
|
78
|
+
? {
|
|
79
|
+
sourceEvidenceHints: candidate.sourceEvidenceHints.map((hint) => ({
|
|
80
|
+
...hint,
|
|
81
|
+
})),
|
|
82
|
+
}
|
|
83
|
+
: {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default async function batchVerificationCandidates({
|
|
88
|
+
sources,
|
|
89
|
+
options = {},
|
|
90
|
+
}) {
|
|
91
|
+
const maxBatchSize = normalizeMaxBatchSize(options.maxBatchSize);
|
|
92
|
+
const rawCandidates = findCandidates(sources);
|
|
93
|
+
const candidates = rawCandidates
|
|
94
|
+
.map((candidate, index) => ({
|
|
95
|
+
candidate,
|
|
96
|
+
id: stableId(
|
|
97
|
+
candidate,
|
|
98
|
+
`candidate-${String(index + 1).padStart(3, "0")}`,
|
|
99
|
+
),
|
|
100
|
+
index,
|
|
101
|
+
}))
|
|
102
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
103
|
+
|
|
104
|
+
const groups = new Map();
|
|
105
|
+
for (const item of candidates) {
|
|
106
|
+
const key = sourceKey(item.candidate);
|
|
107
|
+
const group = groups.get(key) ?? [];
|
|
108
|
+
group.push(item);
|
|
109
|
+
groups.set(key, group);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const batches = [];
|
|
113
|
+
for (const [key, items] of [...groups.entries()].sort(([a], [b]) =>
|
|
114
|
+
a.localeCompare(b),
|
|
115
|
+
)) {
|
|
116
|
+
for (let offset = 0; offset < items.length; offset += maxBatchSize) {
|
|
117
|
+
const slice = items.slice(offset, offset + maxBatchSize);
|
|
118
|
+
const claimIds = slice.map((item) => item.id);
|
|
119
|
+
batches.push({
|
|
120
|
+
id: `vbatch-${String(batches.length + 1).padStart(3, "0")}`,
|
|
121
|
+
sourceKey: key,
|
|
122
|
+
claimIds,
|
|
123
|
+
claims: slice.map((item) => cloneCandidate(item.candidate, item.id)),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
schema: "deep-research-verification-batches-v1",
|
|
130
|
+
digest: `${batches.length} verification batch(es), ${candidates.length} candidate(s), maxBatchSize=${maxBatchSize}`,
|
|
131
|
+
maxBatchSize,
|
|
132
|
+
candidateCount: candidates.length,
|
|
133
|
+
batchCount: batches.length,
|
|
134
|
+
batches,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VERIFICATION_STATUS,
|
|
3
|
+
VERIFICATION_STATUS_BUCKETS,
|
|
4
|
+
canonicalVerificationStatus,
|
|
5
|
+
} from "./verification-ontology.mjs";
|
|
6
|
+
|
|
1
7
|
// Deterministic claim audit for deep-research.
|
|
2
8
|
//
|
|
3
9
|
// Sources: plan (optional), normalize-claims (optional), verify-claims foreach
|
|
@@ -16,10 +22,6 @@ function asArray(value) {
|
|
|
16
22
|
if (Array.isArray(value)) return value;
|
|
17
23
|
if (value && typeof value === "object") {
|
|
18
24
|
if (Array.isArray(value.auditedClaims)) return value.auditedClaims;
|
|
19
|
-
if (Array.isArray(value.claims)) return value.claims;
|
|
20
|
-
if (Array.isArray(value.claimVerdicts)) return value.claimVerdicts;
|
|
21
|
-
if (Array.isArray(value.verdicts)) return value.verdicts;
|
|
22
|
-
if (Array.isArray(value.items)) return value.items;
|
|
23
25
|
if (
|
|
24
26
|
"status" in value ||
|
|
25
27
|
"verdict" in value ||
|
|
@@ -28,6 +30,11 @@ function asArray(value) {
|
|
|
28
30
|
"id" in value
|
|
29
31
|
)
|
|
30
32
|
return [value];
|
|
33
|
+
if (Array.isArray(value.results)) return value.results;
|
|
34
|
+
if (Array.isArray(value.claims)) return value.claims;
|
|
35
|
+
if (Array.isArray(value.claimVerdicts)) return value.claimVerdicts;
|
|
36
|
+
if (Array.isArray(value.verdicts)) return value.verdicts;
|
|
37
|
+
if (Array.isArray(value.items)) return value.items;
|
|
31
38
|
return Object.values(value).flatMap(asArray);
|
|
32
39
|
}
|
|
33
40
|
return [];
|
|
@@ -82,6 +89,38 @@ function collectEvidenceRefs(claim) {
|
|
|
82
89
|
return refs;
|
|
83
90
|
}
|
|
84
91
|
|
|
92
|
+
function addLocalEvidenceRef(refs, value) {
|
|
93
|
+
if (typeof value !== "string") return;
|
|
94
|
+
const text = value.trim();
|
|
95
|
+
if (!text || /^https?:\/\//i.test(text) || isWorkflowSourceRef(text)) return;
|
|
96
|
+
if (looksLikeLocalSourceRef(text)) refs.add(text);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function collectLocalEvidenceRefs(claim) {
|
|
100
|
+
const refs = new Set();
|
|
101
|
+
if (!claim || typeof claim !== "object") return refs;
|
|
102
|
+
for (const key of ["file", "path", "repoPath", "localPath", "sourceRef"]) {
|
|
103
|
+
addLocalEvidenceRef(refs, claim[key]);
|
|
104
|
+
}
|
|
105
|
+
for (const value of Array.isArray(claim.sourceRefs) ? claim.sourceRefs : []) {
|
|
106
|
+
addLocalEvidenceRef(refs, value);
|
|
107
|
+
}
|
|
108
|
+
for (const row of Array.isArray(claim.evidence) ? claim.evidence : []) {
|
|
109
|
+
if (!row || typeof row !== "object") continue;
|
|
110
|
+
for (const key of [
|
|
111
|
+
"file",
|
|
112
|
+
"path",
|
|
113
|
+
"repoPath",
|
|
114
|
+
"localPath",
|
|
115
|
+
"source",
|
|
116
|
+
"sourceRef",
|
|
117
|
+
]) {
|
|
118
|
+
addLocalEvidenceRef(refs, row[key]);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return refs;
|
|
122
|
+
}
|
|
123
|
+
|
|
85
124
|
function collectWorkflowSourceRefs(value, refs = new Set()) {
|
|
86
125
|
if (typeof value === "string") {
|
|
87
126
|
for (const match of value.matchAll(/\bwsrc_[a-f0-9]{32}\b/g))
|
|
@@ -127,9 +166,11 @@ function canonicalUrlKeys(value) {
|
|
|
127
166
|
url.hash = "";
|
|
128
167
|
const serialized = stripCitationUrlPunctuation(url.toString());
|
|
129
168
|
keys.add(serialized);
|
|
169
|
+
addNpmDocsVersionAgnosticKey(keys, url);
|
|
130
170
|
if (url.pathname !== "/" && url.pathname.endsWith("/")) {
|
|
131
171
|
url.pathname = url.pathname.replace(/\/+$/u, "");
|
|
132
172
|
keys.add(stripCitationUrlPunctuation(url.toString()));
|
|
173
|
+
addNpmDocsVersionAgnosticKey(keys, url);
|
|
133
174
|
}
|
|
134
175
|
} catch {
|
|
135
176
|
// Keep the trimmed raw URL key only; malformed strings should not throw from
|
|
@@ -138,6 +179,17 @@ function canonicalUrlKeys(value) {
|
|
|
138
179
|
return [...keys].filter(Boolean);
|
|
139
180
|
}
|
|
140
181
|
|
|
182
|
+
function addNpmDocsVersionAgnosticKey(keys, url) {
|
|
183
|
+
if (url.hostname !== "docs.npmjs.com") return;
|
|
184
|
+
if (!/^\/cli\/(?:v\d+\/)?using-npm\//u.test(url.pathname)) return;
|
|
185
|
+
const versionless = new URL(url.toString());
|
|
186
|
+
versionless.pathname = versionless.pathname.replace(
|
|
187
|
+
/^\/cli\/v\d+\//u,
|
|
188
|
+
"/cli/",
|
|
189
|
+
);
|
|
190
|
+
keys.add(stripCitationUrlPunctuation(versionless.toString()));
|
|
191
|
+
}
|
|
192
|
+
|
|
141
193
|
function addUrlSourceRef(urlToSourceRef, url, sourceRef) {
|
|
142
194
|
if (!isWorkflowSourceRef(sourceRef)) return;
|
|
143
195
|
for (const key of canonicalUrlKeys(url)) {
|
|
@@ -152,13 +204,6 @@ function buildUrlSourceRefLookup(normalizeInputPacket) {
|
|
|
152
204
|
if (!source || typeof source !== "object") continue;
|
|
153
205
|
addUrlSourceRef(urlToSourceRef, source.url, source.sourceRef);
|
|
154
206
|
}
|
|
155
|
-
const sourceRefIndex = asArray(
|
|
156
|
-
normalizeInputPacket?.packet?.research?.sourceRefIndex,
|
|
157
|
-
);
|
|
158
|
-
for (const source of sourceRefIndex) {
|
|
159
|
-
if (!source || typeof source !== "object") continue;
|
|
160
|
-
addUrlSourceRef(urlToSourceRef, source.url, source.sourceRef);
|
|
161
|
-
}
|
|
162
207
|
return urlToSourceRef;
|
|
163
208
|
}
|
|
164
209
|
|
|
@@ -241,19 +286,19 @@ function strongEvidenceIssue(claim) {
|
|
|
241
286
|
|
|
242
287
|
function hasExactQuantitativeClaim(value) {
|
|
243
288
|
const text = JSON.stringify(value ?? "");
|
|
244
|
-
return /\b\d+(?:\.\d+)?\s*(
|
|
289
|
+
return /\b\d+(?:\.\d+)?\s*(?:(?:%|×|\$|n\s*=)|(?:percent|ms|s|sec|seconds|minutes|hours|x|usd|k|m|b|tokens?|users?|samples?)\b)/i.test(
|
|
245
290
|
text,
|
|
246
291
|
);
|
|
247
292
|
}
|
|
248
293
|
|
|
249
294
|
function verdictOf(claim) {
|
|
250
|
-
|
|
295
|
+
const status =
|
|
251
296
|
claim?.status ??
|
|
252
297
|
claim?.verdict ??
|
|
253
298
|
claim?.verdictDigest?.status ??
|
|
254
299
|
claim?.verdictDigest?.verdict ??
|
|
255
|
-
"unverified"
|
|
256
|
-
);
|
|
300
|
+
"unverified";
|
|
301
|
+
return canonicalVerifierStatus(status);
|
|
257
302
|
}
|
|
258
303
|
|
|
259
304
|
function withVerdict(claim, verdict, reason, details = {}) {
|
|
@@ -307,40 +352,133 @@ function compactStrings(values) {
|
|
|
307
352
|
}
|
|
308
353
|
|
|
309
354
|
function canonicalVerifierStatus(status) {
|
|
310
|
-
return status
|
|
355
|
+
return canonicalVerificationStatus(status);
|
|
311
356
|
}
|
|
312
357
|
|
|
313
358
|
function conservativeVerifierStatus(statuses) {
|
|
314
359
|
const normalized = statuses.map(canonicalVerifierStatus);
|
|
315
360
|
for (const status of [
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
361
|
+
VERIFICATION_STATUS.CONFLICTING,
|
|
362
|
+
VERIFICATION_STATUS.UNSUPPORTED,
|
|
363
|
+
VERIFICATION_STATUS.VERIFICATION_BLOCKED,
|
|
364
|
+
VERIFICATION_STATUS.PARTIALLY_SUPPORTED,
|
|
365
|
+
VERIFICATION_STATUS.UNVERIFIED,
|
|
320
366
|
]) {
|
|
321
367
|
if (normalized.includes(status)) return status;
|
|
322
368
|
}
|
|
323
|
-
if (normalized.every((status) => status ===
|
|
369
|
+
if (normalized.every((status) => status === VERIFICATION_STATUS.VERIFIED))
|
|
370
|
+
return VERIFICATION_STATUS.VERIFIED;
|
|
324
371
|
return (
|
|
325
372
|
normalized.find((status) => typeof status === "string" && status) ??
|
|
326
|
-
|
|
373
|
+
VERIFICATION_STATUS.UNVERIFIED
|
|
327
374
|
);
|
|
328
375
|
}
|
|
329
376
|
|
|
330
|
-
function issueForVerifierRow({
|
|
377
|
+
function issueForVerifierRow({
|
|
378
|
+
sourceId,
|
|
379
|
+
claim,
|
|
380
|
+
reason,
|
|
381
|
+
claimId,
|
|
382
|
+
index,
|
|
383
|
+
...details
|
|
384
|
+
}) {
|
|
331
385
|
return {
|
|
332
386
|
sourceId,
|
|
333
387
|
...(Number.isInteger(index) ? { index } : {}),
|
|
334
388
|
...(claimId ? { claimId } : {}),
|
|
389
|
+
...details,
|
|
335
390
|
reason,
|
|
336
391
|
status: verdictOf(claim),
|
|
337
392
|
nextStep:
|
|
338
393
|
reason === "unknown_claim_id"
|
|
339
394
|
? "Verify-claims output did not match any normalized verification candidate; quarantine it from claim counts."
|
|
340
|
-
:
|
|
395
|
+
: reason === "batch_result_id_not_in_source_batch"
|
|
396
|
+
? "Verifier batch output included a claim id outside the source batch; rerun or repair the batch before counting any row."
|
|
397
|
+
: reason === "unknown_verification_batch_id"
|
|
398
|
+
? "Verifier batch output came from an unknown batch id; rerun or repair the batch before counting any row."
|
|
399
|
+
: "Verifier output is missing a usable string id/claimId; rerun or repair the verifier row before counting it.",
|
|
341
400
|
};
|
|
342
401
|
}
|
|
343
402
|
|
|
403
|
+
function asBatchArray(value) {
|
|
404
|
+
if (Array.isArray(value?.batches)) return value.batches;
|
|
405
|
+
if (Array.isArray(value)) return value;
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function buildBatchMembershipById(verificationBatches) {
|
|
410
|
+
const batches = new Map();
|
|
411
|
+
for (const batch of asBatchArray(verificationBatches)) {
|
|
412
|
+
const id = typeof batch?.id === "string" ? batch.id.trim() : "";
|
|
413
|
+
if (!id) continue;
|
|
414
|
+
const claimIds = Array.isArray(batch.claimIds)
|
|
415
|
+
? batch.claimIds
|
|
416
|
+
: Array.isArray(batch.claims)
|
|
417
|
+
? batch.claims.map(
|
|
418
|
+
(claim, index) =>
|
|
419
|
+
claimIdOf(claim).id ??
|
|
420
|
+
`candidate-${String(index + 1).padStart(3, "0")}`,
|
|
421
|
+
)
|
|
422
|
+
: [];
|
|
423
|
+
batches.set(
|
|
424
|
+
id,
|
|
425
|
+
new Set(
|
|
426
|
+
claimIds
|
|
427
|
+
.filter((claimId) => typeof claimId === "string")
|
|
428
|
+
.map((claimId) => claimId.trim())
|
|
429
|
+
.filter(Boolean),
|
|
430
|
+
),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
return batches;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function verifierBatchId(sourceId) {
|
|
437
|
+
const prefix = "verify-claims.";
|
|
438
|
+
if (typeof sourceId !== "string" || !sourceId.startsWith(prefix)) return null;
|
|
439
|
+
const id = sourceId.slice(prefix.length).trim();
|
|
440
|
+
return id || null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function buildBatchIdBySourceName(sourceStatuses) {
|
|
444
|
+
const bySource = new Map();
|
|
445
|
+
for (const status of Array.isArray(sourceStatuses) ? sourceStatuses : []) {
|
|
446
|
+
const source = typeof status?.source === "string" ? status.source : "";
|
|
447
|
+
const batchId = verifierBatchId(status?.specId);
|
|
448
|
+
if (source && batchId) bySource.set(source, batchId);
|
|
449
|
+
}
|
|
450
|
+
return bySource;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function batchMembershipIssue({
|
|
454
|
+
sourceId,
|
|
455
|
+
claimId,
|
|
456
|
+
batchMembershipById,
|
|
457
|
+
batchIdBySourceName,
|
|
458
|
+
}) {
|
|
459
|
+
if (!(batchMembershipById instanceof Map) || batchMembershipById.size === 0)
|
|
460
|
+
return null;
|
|
461
|
+
const batchId =
|
|
462
|
+
verifierBatchId(sourceId) ?? batchIdBySourceName?.get(sourceId);
|
|
463
|
+
if (!batchId) return null;
|
|
464
|
+
const expectedClaimIds = batchMembershipById.get(batchId);
|
|
465
|
+
if (!expectedClaimIds) {
|
|
466
|
+
return {
|
|
467
|
+
reason: "unknown_verification_batch_id",
|
|
468
|
+
batchId,
|
|
469
|
+
expectedBatchIds: [...batchMembershipById.keys()],
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
if (!expectedClaimIds.has(claimId)) {
|
|
473
|
+
return {
|
|
474
|
+
reason: "batch_result_id_not_in_source_batch",
|
|
475
|
+
batchId,
|
|
476
|
+
expectedClaimIds: [...expectedClaimIds],
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
344
482
|
function gapForVerifierIssue(issue) {
|
|
345
483
|
return {
|
|
346
484
|
...(issue.claimId ? { claimId: issue.claimId } : {}),
|
|
@@ -399,12 +537,33 @@ function mergeVerifierRows(rows) {
|
|
|
399
537
|
};
|
|
400
538
|
}
|
|
401
539
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
540
|
+
function buildBatchAdoptionReadiness({ gateSummary, candidateCount }) {
|
|
541
|
+
const checks = [
|
|
542
|
+
["invalid_verifier_rows", gateSummary.invalidVerifierRows],
|
|
543
|
+
["missing_verifier_results", gateSummary.missingVerifierResults],
|
|
544
|
+
["duplicate_verifier_rows", gateSummary.duplicateVerifierRows],
|
|
545
|
+
["duplicate_status_conflicts", gateSummary.duplicateStatusConflicts],
|
|
546
|
+
["invalid_normalized_candidates", gateSummary.invalidNormalizedCandidates],
|
|
547
|
+
["source_ref_join_failures", gateSummary.sourceRefJoinFailures],
|
|
548
|
+
];
|
|
549
|
+
const blockers = checks
|
|
550
|
+
.filter(([, count]) => Number(count ?? 0) > 0)
|
|
551
|
+
.map(([reason, count]) => ({ reason, count }));
|
|
552
|
+
if (candidateCount === 0)
|
|
553
|
+
blockers.push({ reason: "no_verification_candidates", count: 0 });
|
|
554
|
+
return {
|
|
555
|
+
status: blockers.length === 0 ? "eligible_for_canary" : "blocked",
|
|
556
|
+
adopted: false,
|
|
557
|
+
canaryRequired: true,
|
|
558
|
+
reason:
|
|
559
|
+
blockers.length === 0
|
|
560
|
+
? "Verifier identity/sourceRef integrity is clean; batch adoption still requires a non-holdout canary before use."
|
|
561
|
+
: "Batch adoption is blocked until verifier identity/sourceRef integrity issues are resolved.",
|
|
562
|
+
blockers,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const STATUS_BUCKETS = VERIFICATION_STATUS_BUCKETS;
|
|
408
567
|
|
|
409
568
|
function findSource(sources, stageId) {
|
|
410
569
|
for (const [specId, source] of Object.entries(sources ?? {})) {
|
|
@@ -413,13 +572,18 @@ function findSource(sources, stageId) {
|
|
|
413
572
|
return null;
|
|
414
573
|
}
|
|
415
574
|
|
|
416
|
-
export default async function claimEvidenceGate({
|
|
575
|
+
export default async function claimEvidenceGate({
|
|
576
|
+
sources,
|
|
577
|
+
options = {},
|
|
578
|
+
context = {},
|
|
579
|
+
}) {
|
|
417
580
|
const plan = findSource(sources, "plan");
|
|
418
581
|
const normalizeClaims = findSource(sources, "normalize-claims");
|
|
419
|
-
const sanitizedCandidates =
|
|
420
|
-
findSource(sources, "sanitize-claims") ??
|
|
421
|
-
findSource(sources, "sanitize-verification-candidates");
|
|
582
|
+
const sanitizedCandidates = findSource(sources, "sanitize-claims");
|
|
422
583
|
const normalized = sanitizedCandidates ?? normalizeClaims;
|
|
584
|
+
const verificationBatches = findSource(sources, "verification-batches");
|
|
585
|
+
const batchMembershipById = buildBatchMembershipById(verificationBatches);
|
|
586
|
+
const batchIdBySourceName = buildBatchIdBySourceName(context.sourceStatuses);
|
|
423
587
|
const normalizeInputPacket = findSource(sources, "normalize-input-packet");
|
|
424
588
|
const urlToSourceRef = buildUrlSourceRefLookup(normalizeInputPacket);
|
|
425
589
|
const candidateRecords = [];
|
|
@@ -474,8 +638,7 @@ export default async function claimEvidenceGate({ sources, options = {} }) {
|
|
|
474
638
|
!specId.startsWith("plan") &&
|
|
475
639
|
!specId.startsWith("normalize-claims") &&
|
|
476
640
|
!specId.startsWith("normalize-input-packet") &&
|
|
477
|
-
!specId.startsWith("sanitize-claims")
|
|
478
|
-
!specId.startsWith("sanitize-verification-candidates"),
|
|
641
|
+
!specId.startsWith("sanitize-claims"),
|
|
479
642
|
)
|
|
480
643
|
.flatMap(([sourceId, source]) =>
|
|
481
644
|
asArray(source).map((claim, index) => ({ sourceId, claim, index })),
|
|
@@ -533,6 +696,25 @@ export default async function claimEvidenceGate({ sources, options = {} }) {
|
|
|
533
696
|
gateSummary.invalidVerifierRows += 1;
|
|
534
697
|
continue;
|
|
535
698
|
}
|
|
699
|
+
const batchIssue = batchMembershipIssue({
|
|
700
|
+
sourceId,
|
|
701
|
+
claimId: idCheck.id,
|
|
702
|
+
batchMembershipById,
|
|
703
|
+
batchIdBySourceName,
|
|
704
|
+
});
|
|
705
|
+
if (batchIssue) {
|
|
706
|
+
const issue = issueForVerifierRow({
|
|
707
|
+
sourceId,
|
|
708
|
+
claim,
|
|
709
|
+
index,
|
|
710
|
+
claimId: idCheck.id,
|
|
711
|
+
...batchIssue,
|
|
712
|
+
});
|
|
713
|
+
invalidVerifierRows.push(issue);
|
|
714
|
+
remainingGaps.push(gapForVerifierIssue(issue));
|
|
715
|
+
gateSummary.invalidVerifierRows += 1;
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
536
718
|
const row = {
|
|
537
719
|
sourceId,
|
|
538
720
|
claimId: idCheck.id,
|
|
@@ -558,6 +740,10 @@ export default async function claimEvidenceGate({ sources, options = {} }) {
|
|
|
558
740
|
if (!claim || typeof claim !== "object") return;
|
|
559
741
|
gateSummary.total += 1;
|
|
560
742
|
const evidenceRefs = [...collectEvidenceRefs(claim)];
|
|
743
|
+
const localEvidenceRefs = new Set([
|
|
744
|
+
...collectLocalEvidenceRefs(claim),
|
|
745
|
+
...collectLocalEvidenceRefs(candidate),
|
|
746
|
+
]);
|
|
561
747
|
const workflowSourceRefs = new Set([...collectWorkflowSourceRefs(claim)]);
|
|
562
748
|
const exactQuantitative = hasExactQuantitativeClaim(claim);
|
|
563
749
|
const fetched = hasFetchedEvidence(claim);
|
|
@@ -627,6 +813,7 @@ export default async function claimEvidenceGate({ sources, options = {} }) {
|
|
|
627
813
|
claimId &&
|
|
628
814
|
candidate &&
|
|
629
815
|
workflowSourceRefs.size === 0 &&
|
|
816
|
+
localEvidenceRefs.size === 0 &&
|
|
630
817
|
httpSourceUrls.length > 0
|
|
631
818
|
) {
|
|
632
819
|
const failure = {
|
|
@@ -766,6 +953,7 @@ export default async function claimEvidenceGate({ sources, options = {} }) {
|
|
|
766
953
|
partiallySupported: [],
|
|
767
954
|
unsupported: [],
|
|
768
955
|
conflicting: [],
|
|
956
|
+
verificationBlocked: [],
|
|
769
957
|
other: [],
|
|
770
958
|
};
|
|
771
959
|
for (const claim of auditedClaims) {
|
|
@@ -819,11 +1007,16 @@ export default async function claimEvidenceGate({ sources, options = {} }) {
|
|
|
819
1007
|
verdictDigest: claim.verdictDigest,
|
|
820
1008
|
correctionOrCounterclaim: claim.correctionOrCounterclaim,
|
|
821
1009
|
}));
|
|
1010
|
+
const batchAdoptionReadiness = buildBatchAdoptionReadiness({
|
|
1011
|
+
gateSummary,
|
|
1012
|
+
candidateCount: candidateRecords.length,
|
|
1013
|
+
});
|
|
822
1014
|
|
|
823
1015
|
return {
|
|
824
1016
|
auditedClaims,
|
|
825
1017
|
claimDigests,
|
|
826
1018
|
gateSummary,
|
|
1019
|
+
batchAdoptionReadiness,
|
|
827
1020
|
remainingGaps,
|
|
828
1021
|
sourceRefJoinFailures,
|
|
829
1022
|
invalidVerifierRows,
|
|
@@ -252,10 +252,7 @@ function buildSynthesisInput({
|
|
|
252
252
|
export default async function finalAuditPacket({ sources }) {
|
|
253
253
|
const plan = asObject(findSource(sources, "plan"));
|
|
254
254
|
const normalizeClaims = asObject(findSource(sources, "normalize-claims"));
|
|
255
|
-
const sanitizedCandidates = asObject(
|
|
256
|
-
findSource(sources, "sanitize-claims") ??
|
|
257
|
-
findSource(sources, "sanitize-verification-candidates"),
|
|
258
|
-
);
|
|
255
|
+
const sanitizedCandidates = asObject(findSource(sources, "sanitize-claims"));
|
|
259
256
|
const normalized =
|
|
260
257
|
Object.keys(sanitizedCandidates).length > 0
|
|
261
258
|
? sanitizedCandidates
|
|
@@ -157,6 +157,60 @@ function compactGap(gap, sourceId, index) {
|
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
function compactBudgetLedger(source, sourceId) {
|
|
161
|
+
const ledger = asObject(source.budgetLedger);
|
|
162
|
+
if (Object.keys(ledger).length === 0) return undefined;
|
|
163
|
+
const searchBudget = Number(ledger.searchBudget);
|
|
164
|
+
const searchCallsUsed = Number(ledger.searchCallsUsed);
|
|
165
|
+
return {
|
|
166
|
+
sourceId,
|
|
167
|
+
question: stringOf(source.question)?.slice(0, 300),
|
|
168
|
+
...(Number.isFinite(searchBudget) ? { searchBudget } : {}),
|
|
169
|
+
...(Number.isFinite(searchCallsUsed) ? { searchCallsUsed } : {}),
|
|
170
|
+
searchQueriesAttempted: compactStrings(ledger.searchQueriesAttempted, 12),
|
|
171
|
+
omittedSearchQueries: compactStrings(ledger.omittedSearchQueries, 12),
|
|
172
|
+
budgetExhausted: ledger.budgetExhausted === true,
|
|
173
|
+
gapRecorded: ledger.gapRecorded === true,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function sourceStatusesOf(context) {
|
|
178
|
+
return asArray(context?.sourceStatuses).map(asObject);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function isResearchQuestionStatus(status) {
|
|
182
|
+
return [status.source, status.specId, status.displayName, status.stageId]
|
|
183
|
+
.map((value) => String(value ?? ""))
|
|
184
|
+
.some(
|
|
185
|
+
(value) =>
|
|
186
|
+
value === "research-questions" ||
|
|
187
|
+
value.startsWith("research-questions."),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function compactSourceStatusGap(status, index) {
|
|
192
|
+
const sourceId =
|
|
193
|
+
stringOf(status.specId) ??
|
|
194
|
+
stringOf(status.source) ??
|
|
195
|
+
stringOf(status.displayName) ??
|
|
196
|
+
`research-questions.status-${String(index + 1).padStart(3, "0")}`;
|
|
197
|
+
const detail = compactStrings(
|
|
198
|
+
[status.statusDetail, status.errorType, status.lastMessage],
|
|
199
|
+
3,
|
|
200
|
+
).join("; ");
|
|
201
|
+
return {
|
|
202
|
+
originLocator: `${sourceId}.status-gap`,
|
|
203
|
+
sourceId,
|
|
204
|
+
lead: `Research question source ${sourceId} ended with status ${String(status.status ?? "unknown")}${detail ? ` (${detail.slice(0, 300)})` : ""}.`,
|
|
205
|
+
sourceUrls: [],
|
|
206
|
+
sourceRefs: [],
|
|
207
|
+
factSlotIds: [],
|
|
208
|
+
reason: "research_question_non_completed",
|
|
209
|
+
status: String(status.status ?? "unknown"),
|
|
210
|
+
...(stringOf(status.taskId) ? { taskId: stringOf(status.taskId) } : {}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
160
214
|
function pushBounded(target, overflow, items, limit, overflowKind) {
|
|
161
215
|
for (const item of items) {
|
|
162
216
|
if (target.length < limit) target.push(item);
|
|
@@ -221,7 +275,7 @@ function isRequiredOrCriticalSlot(slot) {
|
|
|
221
275
|
}
|
|
222
276
|
|
|
223
277
|
function looksQuantitative(text) {
|
|
224
|
-
return /\b\d+(?:\.\d+)?\s*(
|
|
278
|
+
return /\b\d+(?:\.\d+)?\s*(?:(?:%|×|\$|n\s*=)|(?:percent|ms|s|sec|seconds|minutes|hours|x|usd|k|m|b|tokens?|users?|samples?|gb|mb|tb|requests?|qps|rps|per\s+month|\/month)\b)/i.test(
|
|
225
279
|
String(text ?? ""),
|
|
226
280
|
);
|
|
227
281
|
}
|
|
@@ -428,19 +482,21 @@ function buildPrecisionGuard({ claims, planSlots }) {
|
|
|
428
482
|
};
|
|
429
483
|
}
|
|
430
484
|
|
|
431
|
-
export default async function normalizeInputPacket({ sources }) {
|
|
485
|
+
export default async function normalizeInputPacket({ sources, context } = {}) {
|
|
432
486
|
const plan = asObject(findSource(sources, "plan"));
|
|
433
487
|
const research = researchSources(sources);
|
|
434
488
|
const extractedFacts = [];
|
|
435
489
|
const claims = [];
|
|
436
490
|
const sourceCards = [];
|
|
437
491
|
const evidenceGaps = [];
|
|
492
|
+
const questionBudgetLedger = [];
|
|
438
493
|
const overflow = {};
|
|
439
494
|
const limits = {
|
|
440
495
|
extractedFacts: 240,
|
|
441
496
|
claims: 240,
|
|
442
497
|
sources: 160,
|
|
443
498
|
evidenceGaps: 120,
|
|
499
|
+
questionBudgetLedger: 80,
|
|
444
500
|
};
|
|
445
501
|
|
|
446
502
|
for (const { sourceId, source } of research) {
|
|
@@ -480,7 +536,29 @@ export default async function normalizeInputPacket({ sources }) {
|
|
|
480
536
|
limits.evidenceGaps,
|
|
481
537
|
"omittedEvidenceGaps",
|
|
482
538
|
);
|
|
539
|
+
const budgetLedger = compactBudgetLedger(source, sourceId);
|
|
540
|
+
if (budgetLedger) {
|
|
541
|
+
pushBounded(
|
|
542
|
+
questionBudgetLedger,
|
|
543
|
+
overflow,
|
|
544
|
+
[budgetLedger],
|
|
545
|
+
limits.questionBudgetLedger,
|
|
546
|
+
"omittedQuestionBudgetLedgers",
|
|
547
|
+
);
|
|
548
|
+
}
|
|
483
549
|
}
|
|
550
|
+
pushBounded(
|
|
551
|
+
evidenceGaps,
|
|
552
|
+
overflow,
|
|
553
|
+
sourceStatusesOf(context)
|
|
554
|
+
.filter(
|
|
555
|
+
(status) =>
|
|
556
|
+
isResearchQuestionStatus(status) && status.status !== "completed",
|
|
557
|
+
)
|
|
558
|
+
.map(compactSourceStatusGap),
|
|
559
|
+
limits.evidenceGaps,
|
|
560
|
+
"omittedEvidenceGaps",
|
|
561
|
+
);
|
|
484
562
|
|
|
485
563
|
const planSlots = asArray(plan.factSlots).map(compactPlanSlot);
|
|
486
564
|
const precisionGuard = buildPrecisionGuard({ claims, planSlots });
|
|
@@ -506,6 +584,7 @@ export default async function normalizeInputPacket({ sources }) {
|
|
|
506
584
|
claims,
|
|
507
585
|
sources: sourceCards,
|
|
508
586
|
evidenceGaps,
|
|
587
|
+
questionBudgetLedger,
|
|
509
588
|
},
|
|
510
589
|
slotPreservation,
|
|
511
590
|
precisionGuard,
|