@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
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
+
import { canonicalVerificationStatus } from "./verification-ontology.mjs";
|
|
11
12
|
|
|
12
13
|
function findSource(sources, stageId) {
|
|
13
14
|
const entries = Object.entries(sources ?? {});
|
|
@@ -298,11 +299,24 @@ function finiteNumber(value) {
|
|
|
298
299
|
function normalizeClaimStatus(status) {
|
|
299
300
|
const text = cleanText(status).toLowerCase();
|
|
300
301
|
if (!text) return "";
|
|
302
|
+
const canonical = canonicalVerificationStatus(text);
|
|
303
|
+
if (canonical !== "unverified") return canonical;
|
|
304
|
+
if (
|
|
305
|
+
text === "unverified" ||
|
|
306
|
+
text.includes("not verified") ||
|
|
307
|
+
text.includes("not_verified")
|
|
308
|
+
)
|
|
309
|
+
return "unverified";
|
|
310
|
+
if (
|
|
311
|
+
text.includes("verification_blocked") ||
|
|
312
|
+
text.includes("verification blocked")
|
|
313
|
+
)
|
|
314
|
+
return "verification_blocked";
|
|
301
315
|
if (text.includes("conflict")) return "conflicting";
|
|
302
316
|
if (text.includes("unsupported")) return "unsupported";
|
|
303
317
|
if (text.includes("partial")) return "partially_supported";
|
|
304
|
-
if (text
|
|
305
|
-
return
|
|
318
|
+
if (/\bverified\b/.test(text)) return "verified";
|
|
319
|
+
return canonical;
|
|
306
320
|
}
|
|
307
321
|
|
|
308
322
|
function coverageCounts(coverage, fallback) {
|
|
@@ -316,13 +330,18 @@ function coverageCounts(coverage, fallback) {
|
|
|
316
330
|
fallback.partially_supported,
|
|
317
331
|
unsupported: finiteNumber(coverage.unsupported) ?? fallback.unsupported,
|
|
318
332
|
conflicting: finiteNumber(coverage.conflicting) ?? fallback.conflicting,
|
|
333
|
+
verification_blocked:
|
|
334
|
+
finiteNumber(coverage.verificationBlocked) ??
|
|
335
|
+
finiteNumber(coverage.verification_blocked) ??
|
|
336
|
+
fallback.verification_blocked,
|
|
319
337
|
};
|
|
320
338
|
if (counts.total == null) {
|
|
321
339
|
counts.total =
|
|
322
340
|
counts.verified +
|
|
323
341
|
counts.partially_supported +
|
|
324
342
|
counts.unsupported +
|
|
325
|
-
counts.conflicting
|
|
343
|
+
counts.conflicting +
|
|
344
|
+
counts.verification_blocked;
|
|
326
345
|
}
|
|
327
346
|
return counts;
|
|
328
347
|
}
|
|
@@ -338,7 +357,8 @@ function packetVerdictCounts(packet, fallback) {
|
|
|
338
357
|
counts.verified +
|
|
339
358
|
counts.partially_supported +
|
|
340
359
|
counts.unsupported +
|
|
341
|
-
counts.conflicting
|
|
360
|
+
counts.conflicting +
|
|
361
|
+
counts.verification_blocked;
|
|
342
362
|
return counts;
|
|
343
363
|
}
|
|
344
364
|
|
|
@@ -350,6 +370,7 @@ function claimCounts(control, packet) {
|
|
|
350
370
|
partially_supported: 0,
|
|
351
371
|
unsupported: 0,
|
|
352
372
|
conflicting: 0,
|
|
373
|
+
verification_blocked: 0,
|
|
353
374
|
};
|
|
354
375
|
for (const claim of claims) {
|
|
355
376
|
const status = normalizeClaimStatus(claim?.status);
|
|
@@ -372,6 +393,7 @@ function claimCounts(control, packet) {
|
|
|
372
393
|
"partially_supported",
|
|
373
394
|
"unsupported",
|
|
374
395
|
"conflicting",
|
|
396
|
+
"verification_blocked",
|
|
375
397
|
]) {
|
|
376
398
|
if (coverage[key] !== counts[key]) {
|
|
377
399
|
mismatches.push({
|
|
@@ -468,7 +490,14 @@ function packetGapRows(packet) {
|
|
|
468
490
|
kind: "Coverage gap",
|
|
469
491
|
...gap,
|
|
470
492
|
}));
|
|
471
|
-
|
|
493
|
+
const sourceRefJoinFailures = asArray(packet?.sourceRefJoinFailures).map(
|
|
494
|
+
(gap, index) => ({
|
|
495
|
+
id: gapIdOf(gap) || numberedId("gap-source-ref", index),
|
|
496
|
+
kind: "Source reference gap",
|
|
497
|
+
...gap,
|
|
498
|
+
}),
|
|
499
|
+
);
|
|
500
|
+
return [...remaining, ...coverage, ...sourceRefJoinFailures];
|
|
472
501
|
}
|
|
473
502
|
|
|
474
503
|
function rowsForIds(ids, rowById, warnings, label) {
|
|
@@ -513,6 +542,8 @@ function evidenceStrength(status) {
|
|
|
513
542
|
case "unsupported":
|
|
514
543
|
return 1;
|
|
515
544
|
case "conflicting":
|
|
545
|
+
case "verification_blocked":
|
|
546
|
+
case "unverified":
|
|
516
547
|
return 0;
|
|
517
548
|
default:
|
|
518
549
|
return -1;
|
|
@@ -580,6 +611,7 @@ function coverageSummaryFromPacket(packet, fallback = {}) {
|
|
|
580
611
|
partially_supported: 0,
|
|
581
612
|
unsupported: 0,
|
|
582
613
|
conflicting: 0,
|
|
614
|
+
verification_blocked: 0,
|
|
583
615
|
});
|
|
584
616
|
if (!counts) return fallback;
|
|
585
617
|
return {
|
|
@@ -588,6 +620,7 @@ function coverageSummaryFromPacket(packet, fallback = {}) {
|
|
|
588
620
|
partiallySupported: counts.partially_supported,
|
|
589
621
|
unsupported: counts.unsupported,
|
|
590
622
|
conflicting: counts.conflicting,
|
|
623
|
+
verificationBlocked: counts.verification_blocked,
|
|
591
624
|
verificationCandidates: counts.total,
|
|
592
625
|
depth: packet?.researchMetadataSeed?.depth ?? fallback.depth,
|
|
593
626
|
researchQuestions:
|
|
@@ -968,7 +1001,7 @@ function renderAuditSummary(report, claimSummary, slots) {
|
|
|
968
1001
|
return [
|
|
969
1002
|
"## Audit summary",
|
|
970
1003
|
"",
|
|
971
|
-
`- Claims: ${claimSummary.verified} verified, ${claimSummary.partially_supported} partially supported, ${claimSummary.unsupported} unsupported, ${claimSummary.conflicting} conflicting.`,
|
|
1004
|
+
`- Claims: ${claimSummary.verified} verified, ${claimSummary.partially_supported} partially supported, ${claimSummary.unsupported} unsupported, ${claimSummary.conflicting} conflicting, ${claimSummary.verification_blocked} verification blocked.`,
|
|
972
1005
|
`- Fact slots: ${slots.filled} filled, ${slots.partial} partial, ${slots.missingOrConflicting} missing/conflicting, ${slots.total} total.`,
|
|
973
1006
|
...(mismatches.length > 0
|
|
974
1007
|
? [
|
|
@@ -987,11 +1020,7 @@ function renderAuditSummary(report, claimSummary, slots) {
|
|
|
987
1020
|
|
|
988
1021
|
function renderWarnings(sectionCounts) {
|
|
989
1022
|
const checks = [
|
|
990
|
-
["findings", "renderedFindings", "findings"],
|
|
991
|
-
["recommendations", "renderedRecommendations", "recommendations"],
|
|
992
|
-
["actionItems", "renderedActionItems", "action items"],
|
|
993
1023
|
["caveatsAndGaps", "renderedCaveatsAndGaps", "caveats/gaps"],
|
|
994
|
-
["factSlots", "renderedFactSlots", "fact slots"],
|
|
995
1024
|
["sourceUrls", "renderedSourceUrls", "source URLs"],
|
|
996
1025
|
];
|
|
997
1026
|
return checks
|
|
@@ -1237,6 +1266,7 @@ export default async function renderExecutive({
|
|
|
1237
1266
|
partially_supported: 0,
|
|
1238
1267
|
unsupported: 0,
|
|
1239
1268
|
conflicting: 0,
|
|
1269
|
+
verification_blocked: 0,
|
|
1240
1270
|
},
|
|
1241
1271
|
factSlotSummary: {
|
|
1242
1272
|
total: 0,
|
|
@@ -1261,15 +1291,6 @@ export default async function renderExecutive({
|
|
|
1261
1291
|
maxUrls: Number.isFinite(Number(options.maxUrls))
|
|
1262
1292
|
? Math.max(0, Number(options.maxUrls))
|
|
1263
1293
|
: Infinity,
|
|
1264
|
-
maxFindings: Number.isFinite(Number(options.maxFindings))
|
|
1265
|
-
? Math.max(0, Number(options.maxFindings))
|
|
1266
|
-
: undefined,
|
|
1267
|
-
maxRecommendations: Number.isFinite(Number(options.maxRecommendations))
|
|
1268
|
-
? Math.max(0, Number(options.maxRecommendations))
|
|
1269
|
-
: undefined,
|
|
1270
|
-
maxGaps: Number.isFinite(Number(options.maxGaps))
|
|
1271
|
-
? Math.max(0, Number(options.maxGaps))
|
|
1272
|
-
: undefined,
|
|
1273
1294
|
};
|
|
1274
1295
|
const rendered = renderResearchMarkdown(control, auditPacket, opts);
|
|
1275
1296
|
let markdown = rendered.markdown;
|
|
@@ -1296,7 +1317,6 @@ export default async function renderExecutive({
|
|
|
1296
1317
|
!serializationArtifact;
|
|
1297
1318
|
|
|
1298
1319
|
let executiveSidecarPath;
|
|
1299
|
-
let reportSidecarPath;
|
|
1300
1320
|
let auditSidecarPath;
|
|
1301
1321
|
try {
|
|
1302
1322
|
if (context.cwd && context.runId && context.taskId) {
|
|
@@ -1310,10 +1330,8 @@ export default async function renderExecutive({
|
|
|
1310
1330
|
);
|
|
1311
1331
|
await mkdir(taskDir, { recursive: true });
|
|
1312
1332
|
executiveSidecarPath = join(taskDir, "executive.md");
|
|
1313
|
-
reportSidecarPath = join(taskDir, "report.md");
|
|
1314
1333
|
auditSidecarPath = join(taskDir, "audit.md");
|
|
1315
1334
|
await writeFile(executiveSidecarPath, `${markdown}\n`, "utf8");
|
|
1316
|
-
await writeFile(reportSidecarPath, `${markdown}\n`, "utf8");
|
|
1317
1335
|
await writeFile(auditSidecarPath, `${auditMarkdown}\n`, "utf8");
|
|
1318
1336
|
}
|
|
1319
1337
|
} catch {
|
|
@@ -1344,9 +1362,6 @@ export default async function renderExecutive({
|
|
|
1344
1362
|
renderedAllStructuredItems,
|
|
1345
1363
|
maxWords: Number.isFinite(opts.maxWords) ? opts.maxWords : null,
|
|
1346
1364
|
maxUrls: Number.isFinite(opts.maxUrls) ? opts.maxUrls : null,
|
|
1347
|
-
maxFindings: opts.maxFindings,
|
|
1348
|
-
maxRecommendations: opts.maxRecommendations,
|
|
1349
|
-
maxGaps: opts.maxGaps,
|
|
1350
1365
|
truncated,
|
|
1351
1366
|
truncatedWithOpenGaps,
|
|
1352
1367
|
serializationArtifact,
|
|
@@ -1354,7 +1369,6 @@ export default async function renderExecutive({
|
|
|
1354
1369
|
},
|
|
1355
1370
|
auditArtifact: auditSidecarPath ? "audit.md" : "final-audit.control.json",
|
|
1356
1371
|
...(executiveSidecarPath ? { sidecarPath: "executive.md" } : {}),
|
|
1357
|
-
...(reportSidecarPath ? { reportSidecarPath: "report.md" } : {}),
|
|
1358
1372
|
...(auditSidecarPath ? { auditSidecarPath: "audit.md" } : {}),
|
|
1359
1373
|
};
|
|
1360
1374
|
}
|
|
@@ -89,12 +89,55 @@ function matchesAny(text, patterns) {
|
|
|
89
89
|
return patterns.some((pattern) => pattern.test(text));
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
const WORD_TOKEN_RE =
|
|
93
|
+
/[\p{Letter}\p{Number}][\p{Letter}\p{Number}\p{Mark}_-]*/gu;
|
|
94
|
+
const ASCII_TOKEN_RE = /^[a-z0-9][a-z0-9_-]{2,}$/iu;
|
|
95
|
+
const ASCII_RUN_RE = /[a-z0-9][a-z0-9_-]{2,}/giu;
|
|
96
|
+
const CJK_RUN_RE =
|
|
97
|
+
/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+/gu;
|
|
98
|
+
const ASCII_CHAR_RE = /[a-z0-9]/iu;
|
|
99
|
+
|
|
100
|
+
function addCjkRunTokens(tokens, run) {
|
|
101
|
+
const chars = [...run];
|
|
102
|
+
if (chars.length === 0) return;
|
|
103
|
+
if (chars.length === 1) {
|
|
104
|
+
tokens.add(chars[0]);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
tokens.add(run);
|
|
108
|
+
for (const size of [2, 3]) {
|
|
109
|
+
if (chars.length < size) continue;
|
|
110
|
+
for (let index = 0; index <= chars.length - size; index += 1) {
|
|
111
|
+
tokens.add(chars.slice(index, index + size).join(""));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
92
116
|
function tokenSet(value) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
117
|
+
const tokens = new Set();
|
|
118
|
+
const normalized = String(value ?? "")
|
|
119
|
+
.normalize("NFKC")
|
|
120
|
+
.toLocaleLowerCase();
|
|
121
|
+
for (const match of normalized.matchAll(WORD_TOKEN_RE)) {
|
|
122
|
+
const token = match[0].replace(/^[_-]+|[_-]+$/gu, "");
|
|
123
|
+
if (!token) continue;
|
|
124
|
+
for (const asciiMatch of token.matchAll(ASCII_RUN_RE)) {
|
|
125
|
+
tokens.add(asciiMatch[0]);
|
|
126
|
+
}
|
|
127
|
+
if (ASCII_TOKEN_RE.test(token)) continue;
|
|
128
|
+
let cjkMatched = false;
|
|
129
|
+
for (const cjkMatch of token.matchAll(CJK_RUN_RE)) {
|
|
130
|
+
cjkMatched = true;
|
|
131
|
+
addCjkRunTokens(tokens, cjkMatch[0]);
|
|
132
|
+
}
|
|
133
|
+
if (!cjkMatched && [...token].length >= 3) tokens.add(token);
|
|
134
|
+
}
|
|
135
|
+
return tokens;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function hasAsciiToken(tokens) {
|
|
139
|
+
for (const token of tokens) if (ASCII_CHAR_RE.test(token)) return true;
|
|
140
|
+
return false;
|
|
98
141
|
}
|
|
99
142
|
|
|
100
143
|
function setIntersectionCount(left, right) {
|
|
@@ -235,10 +278,15 @@ function evidenceHintsForCandidate(candidate, hintRows) {
|
|
|
235
278
|
candidateSlots,
|
|
236
279
|
);
|
|
237
280
|
const tokenHits = setIntersectionCount(row._tokens, candidateTokens);
|
|
281
|
+
const unicodeSlotOnlyFallback =
|
|
282
|
+
slotHits > 0 &&
|
|
283
|
+
tokenHits < 2 &&
|
|
284
|
+
!hasAsciiToken(candidateTokens) &&
|
|
285
|
+
!hasAsciiToken(row._tokens);
|
|
238
286
|
if (slotHits === 0 && tokenHits < 2) continue;
|
|
239
287
|
const score =
|
|
240
288
|
refHits * 6 + urlHits * 5 + slotHits * 2 + Math.min(tokenHits, 5);
|
|
241
|
-
if (score < 7) continue;
|
|
289
|
+
if (score < 7 && !unicodeSlotOnlyFallback) continue;
|
|
242
290
|
scored.push({ score, row });
|
|
243
291
|
}
|
|
244
292
|
scored.sort((left, right) => right.score - left.score);
|
|
@@ -376,11 +424,14 @@ function rewrittenCandidate(candidate, reasons, hints, urlToSourceRef) {
|
|
|
376
424
|
if (!hint) return null;
|
|
377
425
|
const replacement = stringOf(hint.value) || stringOf(hint.quote);
|
|
378
426
|
if (!replacement || replacement === claimText(candidate)) return null;
|
|
427
|
+
const refs = backfillSourceRefs(candidate, [hint], urlToSourceRef);
|
|
428
|
+
if (refs.length === 0 && localEvidenceRefs(candidate).length === 0)
|
|
429
|
+
return null;
|
|
379
430
|
return {
|
|
380
431
|
...candidate,
|
|
381
432
|
originalClaim: claimText(candidate),
|
|
382
433
|
claim: replacement,
|
|
383
|
-
sourceRefs:
|
|
434
|
+
sourceRefs: refs,
|
|
384
435
|
sourceUrls: hint.url ? [hint.url] : sourceUrls(candidate),
|
|
385
436
|
sanitizerRewriteReasons: rewriteReasons,
|
|
386
437
|
reasonToVerify: `Deterministically rewritten to a source-backed atom from ${hint.sourceTitleOrPublisher ?? hint.url ?? hint.sourceRef ?? "source evidence"}.`,
|
|
@@ -399,6 +450,16 @@ function sanitizedCandidate(candidate, hints, urlToSourceRef) {
|
|
|
399
450
|
};
|
|
400
451
|
}
|
|
401
452
|
|
|
453
|
+
function withoutSanitizerGapReason(value) {
|
|
454
|
+
return stringOf(value)
|
|
455
|
+
.split(";")
|
|
456
|
+
.map((part) => part.trim())
|
|
457
|
+
.filter(
|
|
458
|
+
(part) => part && !part.startsWith("sanitized verifier candidates:"),
|
|
459
|
+
)
|
|
460
|
+
.join("; ");
|
|
461
|
+
}
|
|
462
|
+
|
|
402
463
|
function adjustFactSlotCoverage(rows, demotedBySlot, keptIds) {
|
|
403
464
|
return asArray(rows).map((row) => {
|
|
404
465
|
const slot = { ...asObject(row) };
|
|
@@ -484,6 +545,7 @@ export default async function sanitizeVerificationCandidates({ sources }) {
|
|
|
484
545
|
const webUrlOnlyDemotedIds = [];
|
|
485
546
|
const promotedCandidateIds = [];
|
|
486
547
|
const promotedBySlot = new Map();
|
|
548
|
+
const promotedPreservedClaims = new Set();
|
|
487
549
|
const retainedCandidates = [];
|
|
488
550
|
for (const [index, candidate] of keptCandidates.entries()) {
|
|
489
551
|
const hasRefs = sourceRefs(candidate).length > 0;
|
|
@@ -550,6 +612,7 @@ export default async function sanitizeVerificationCandidates({ sources }) {
|
|
|
550
612
|
for (const entry of promotable.slice(0, webUrlOnlyDemotedIds.length)) {
|
|
551
613
|
takenIds.add(entry.id);
|
|
552
614
|
promotedCandidateIds.push(entry.id);
|
|
615
|
+
promotedPreservedClaims.add(entry.preserved);
|
|
553
616
|
for (const slotId of entry.slots) {
|
|
554
617
|
const list = promotedBySlot.get(slotId) ?? [];
|
|
555
618
|
list.push(entry.id);
|
|
@@ -584,19 +647,30 @@ export default async function sanitizeVerificationCandidates({ sources }) {
|
|
|
584
647
|
const slotId = stringOf(row.slotId ?? row.id);
|
|
585
648
|
const promoted = promotedBySlot.get(slotId) ?? [];
|
|
586
649
|
if (promoted.length === 0) return row;
|
|
587
|
-
|
|
588
|
-
...row,
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
650
|
+
const verificationCandidateIds = compactStrings(
|
|
651
|
+
[...asArray(row.verificationCandidateIds), ...promoted],
|
|
652
|
+
24,
|
|
653
|
+
);
|
|
654
|
+
const next = { ...row, verificationCandidateIds };
|
|
655
|
+
if (verificationCandidateIds.length > 0) {
|
|
656
|
+
if (next.status === "partial" || next.status === "missing") {
|
|
657
|
+
next.status = "filled";
|
|
658
|
+
}
|
|
659
|
+
const gapReason = withoutSanitizerGapReason(next.gapReason);
|
|
660
|
+
if (gapReason) next.gapReason = gapReason;
|
|
661
|
+
else delete next.gapReason;
|
|
662
|
+
}
|
|
663
|
+
return next;
|
|
594
664
|
});
|
|
665
|
+
const outputPreservedClaims =
|
|
666
|
+
promotedPreservedClaims.size === 0
|
|
667
|
+
? preservedClaims
|
|
668
|
+
: preservedClaims.filter((claim) => !promotedPreservedClaims.has(claim));
|
|
595
669
|
return {
|
|
596
670
|
schema: SCHEMA,
|
|
597
671
|
claimInventory: {
|
|
598
672
|
verificationCandidates: keptCandidates,
|
|
599
|
-
preservedClaims,
|
|
673
|
+
preservedClaims: outputPreservedClaims,
|
|
600
674
|
duplicates: asArray(claimInventory.duplicates),
|
|
601
675
|
},
|
|
602
676
|
factSlotCoverage: factSlotCoverageRows,
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// Shadow-only selective verification reporter for deep-research.
|
|
2
|
+
//
|
|
3
|
+
// This helper never skips verification. It records which candidates a future
|
|
4
|
+
// selector might skip, then joins those shadow decisions to actual audit output
|
|
5
|
+
// so adoption can be judged with evidence before any verifier is removed.
|
|
6
|
+
|
|
7
|
+
function asArray(value) {
|
|
8
|
+
if (Array.isArray(value)) return value;
|
|
9
|
+
if (value && typeof value === "object") {
|
|
10
|
+
if (Array.isArray(value.claimInventory?.verificationCandidates))
|
|
11
|
+
return value.claimInventory.verificationCandidates;
|
|
12
|
+
if (Array.isArray(value.verificationCandidates))
|
|
13
|
+
return value.verificationCandidates;
|
|
14
|
+
if (Array.isArray(value.auditedClaims)) return value.auditedClaims;
|
|
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 findAuditedClaims(sources) {
|
|
41
|
+
for (const [specId, source] of Object.entries(sources ?? {})) {
|
|
42
|
+
if (specId === "audit-claims" || specId.startsWith("audit-claims.")) {
|
|
43
|
+
const claims = asArray(source);
|
|
44
|
+
if (claims.length > 0) return claims;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function findAuditSource(sources) {
|
|
51
|
+
for (const [specId, source] of Object.entries(sources ?? {})) {
|
|
52
|
+
if (specId === "audit-claims" || specId.startsWith("audit-claims."))
|
|
53
|
+
return source && typeof source === "object" ? source : {};
|
|
54
|
+
}
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function issueCount(audit, field) {
|
|
59
|
+
const fromSummary = Number(audit?.gateSummary?.[field] ?? 0);
|
|
60
|
+
const fromArray = Array.isArray(audit?.[field]) ? audit[field].length : 0;
|
|
61
|
+
return Math.max(Number.isFinite(fromSummary) ? fromSummary : 0, fromArray);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function auditIntegrityBlockers(audit) {
|
|
65
|
+
return [
|
|
66
|
+
["audit_invalid_verifier_rows", issueCount(audit, "invalidVerifierRows")],
|
|
67
|
+
[
|
|
68
|
+
"audit_missing_verifier_results",
|
|
69
|
+
issueCount(audit, "missingVerifierResults"),
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
"audit_duplicate_verifier_rows",
|
|
73
|
+
issueCount(audit, "duplicateVerifierRows"),
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
"audit_duplicate_status_conflicts",
|
|
77
|
+
issueCount(audit, "duplicateStatusConflicts"),
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
"audit_invalid_normalized_candidates",
|
|
81
|
+
issueCount(audit, "invalidNormalizedCandidates"),
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
"audit_source_ref_join_failures",
|
|
85
|
+
issueCount(audit, "sourceRefJoinFailures"),
|
|
86
|
+
],
|
|
87
|
+
]
|
|
88
|
+
.filter(([, count]) => count > 0)
|
|
89
|
+
.map(([reason, count]) => ({ reason, count }));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function candidateId(candidate, index) {
|
|
93
|
+
const id = typeof candidate?.id === "string" ? candidate.id.trim() : "";
|
|
94
|
+
return id || `candidate-${String(index + 1).padStart(3, "0")}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function hasExactQuantitativeText(candidate) {
|
|
98
|
+
return /\b\d+(?:\.\d+)?\b|\b(v\d+(?:\.\d+)*)\b|\b\d+\s*(?:ms|s|min|hour|day|%|percent|usd|dollars?|tokens?|kb|mb|gb)\b/iu.test(
|
|
99
|
+
String(candidate?.claim ?? ""),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function hasCriticalSlots(candidate) {
|
|
104
|
+
const ids = Array.isArray(candidate?.factSlotIds)
|
|
105
|
+
? candidate.factSlotIds
|
|
106
|
+
: [];
|
|
107
|
+
return ids.some((id) =>
|
|
108
|
+
/(price|pricing|cost|ttl|limit|version|date|policy|security|numeric|slot-critical)/iu.test(
|
|
109
|
+
String(id),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function hasExactSourceHint(candidate) {
|
|
115
|
+
return (
|
|
116
|
+
Array.isArray(candidate?.sourceEvidenceHints) &&
|
|
117
|
+
candidate.sourceEvidenceHints.some(
|
|
118
|
+
(hint) =>
|
|
119
|
+
typeof hint?.quote === "string" &&
|
|
120
|
+
hint.quote.trim() &&
|
|
121
|
+
(typeof hint.sourceRef === "string" ||
|
|
122
|
+
typeof hint.sourceUrl === "string"),
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function decide(candidate) {
|
|
128
|
+
const reasonCodes = [];
|
|
129
|
+
if (hasExactQuantitativeText(candidate))
|
|
130
|
+
reasonCodes.push("exact_quantitative");
|
|
131
|
+
if (hasCriticalSlots(candidate)) reasonCodes.push("critical_fact_slot");
|
|
132
|
+
if (!hasExactSourceHint(candidate)) reasonCodes.push("no_exact_source_hint");
|
|
133
|
+
if (reasonCodes.length > 0) {
|
|
134
|
+
return { decision: "would_verify", reasonCodes };
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
decision: "would_skip_shadow_only",
|
|
138
|
+
reasonCodes: ["exact_source_hint_noncritical"],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default async function shadowSelectVerification({ sources }) {
|
|
143
|
+
const audit = findAuditSource(sources);
|
|
144
|
+
const candidates = findCandidates(sources).map((candidate, index) => ({
|
|
145
|
+
candidate,
|
|
146
|
+
id: candidateId(candidate, index),
|
|
147
|
+
}));
|
|
148
|
+
const auditedById = new Map(
|
|
149
|
+
findAuditedClaims(sources).map((claim, index) => [
|
|
150
|
+
candidateId(claim, index),
|
|
151
|
+
claim,
|
|
152
|
+
]),
|
|
153
|
+
);
|
|
154
|
+
const decisions = candidates.map(({ candidate, id }) => {
|
|
155
|
+
const decision = decide(candidate);
|
|
156
|
+
const audited = auditedById.get(id);
|
|
157
|
+
return {
|
|
158
|
+
id,
|
|
159
|
+
decision: decision.decision,
|
|
160
|
+
reasonCodes: decision.reasonCodes,
|
|
161
|
+
actualStatus:
|
|
162
|
+
typeof audited?.status === "string" ? audited.status : undefined,
|
|
163
|
+
actualVerified: audited?.status === "verified",
|
|
164
|
+
factSlotIds: Array.isArray(candidate.factSlotIds)
|
|
165
|
+
? [...candidate.factSlotIds]
|
|
166
|
+
: [],
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
const wouldSkip = decisions.filter(
|
|
170
|
+
(decision) => decision.decision === "would_skip_shadow_only",
|
|
171
|
+
);
|
|
172
|
+
const wouldVerify = decisions.filter(
|
|
173
|
+
(decision) => decision.decision === "would_verify",
|
|
174
|
+
);
|
|
175
|
+
const skippedButVerified = wouldSkip.filter(
|
|
176
|
+
(decision) => decision.actualVerified,
|
|
177
|
+
);
|
|
178
|
+
const wouldSkipWithoutAudit = wouldSkip.filter(
|
|
179
|
+
(decision) => typeof decision.actualStatus !== "string",
|
|
180
|
+
);
|
|
181
|
+
const blockers = [];
|
|
182
|
+
if (skippedButVerified.length > 0)
|
|
183
|
+
blockers.push({
|
|
184
|
+
reason: "would_skip_verified_claims",
|
|
185
|
+
count: skippedButVerified.length,
|
|
186
|
+
});
|
|
187
|
+
if (wouldSkipWithoutAudit.length > 0)
|
|
188
|
+
blockers.push({
|
|
189
|
+
reason: "would_skip_without_audit_result",
|
|
190
|
+
count: wouldSkipWithoutAudit.length,
|
|
191
|
+
});
|
|
192
|
+
blockers.push(...auditIntegrityBlockers(audit));
|
|
193
|
+
if (wouldSkip.length === 0)
|
|
194
|
+
blockers.push({ reason: "no_shadow_skip_candidates", count: 0 });
|
|
195
|
+
const realSkipReadiness = {
|
|
196
|
+
status: blockers.length === 0 ? "eligible_for_canary" : "blocked",
|
|
197
|
+
realSkippingEnabled: false,
|
|
198
|
+
adopted: false,
|
|
199
|
+
canaryRequired: true,
|
|
200
|
+
reason:
|
|
201
|
+
blockers.length === 0
|
|
202
|
+
? "Shadow selector found skip candidates with no verified or missing-audit rows; real skipping still requires a non-holdout canary."
|
|
203
|
+
: "Real selective verification is blocked until shadow decisions prove no verified or unaudited claims would be skipped.",
|
|
204
|
+
blockers,
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
schema: "deep-research-verification-shadow-selector-v1",
|
|
208
|
+
digest: `${wouldSkip.length} would-skip shadow candidate(s), ${wouldVerify.length} would-verify candidate(s); real skipping disabled`,
|
|
209
|
+
realSkippingEnabled: false,
|
|
210
|
+
candidateCount: decisions.length,
|
|
211
|
+
summary: {
|
|
212
|
+
wouldSkip: wouldSkip.length,
|
|
213
|
+
wouldVerify: wouldVerify.length,
|
|
214
|
+
skippedButVerified: skippedButVerified.length,
|
|
215
|
+
wouldSkipWithoutAudit: wouldSkipWithoutAudit.length,
|
|
216
|
+
skippedCritical: wouldSkip.filter((decision) =>
|
|
217
|
+
decision.reasonCodes.includes("critical_fact_slot"),
|
|
218
|
+
).length,
|
|
219
|
+
shadowOnly: true,
|
|
220
|
+
},
|
|
221
|
+
realSkipReadiness,
|
|
222
|
+
decisions,
|
|
223
|
+
w8FastProfilePrerequisite: {
|
|
224
|
+
status: "not_met",
|
|
225
|
+
reason:
|
|
226
|
+
"selective verification is shadow-only; no verified cost/speed reduction is available to package as an opt-in fast profile",
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Bundle-local compatibility surface for the package verification ontology.
|
|
2
|
+
//
|
|
3
|
+
// Workflow support helpers are bundled from the workflow spec directory, so this
|
|
4
|
+
// dependency-free helper intentionally lives inside the deep-research bundle.
|
|
5
|
+
// Keep it in semantic parity with src/verification-ontology.ts.
|
|
6
|
+
|
|
7
|
+
export const VERIFICATION_STATUS = Object.freeze({
|
|
8
|
+
VERIFIED: "verified",
|
|
9
|
+
PARTIALLY_SUPPORTED: "partially_supported",
|
|
10
|
+
UNSUPPORTED: "unsupported",
|
|
11
|
+
CONFLICTING: "conflicting",
|
|
12
|
+
VERIFICATION_BLOCKED: "verification_blocked",
|
|
13
|
+
UNVERIFIED: "unverified",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const VERIFICATION_STATUS_VALUES = Object.freeze([
|
|
17
|
+
VERIFICATION_STATUS.VERIFIED,
|
|
18
|
+
VERIFICATION_STATUS.PARTIALLY_SUPPORTED,
|
|
19
|
+
VERIFICATION_STATUS.UNSUPPORTED,
|
|
20
|
+
VERIFICATION_STATUS.CONFLICTING,
|
|
21
|
+
VERIFICATION_STATUS.VERIFICATION_BLOCKED,
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
export const VERIFICATION_STATUS_BUCKETS = Object.freeze({
|
|
25
|
+
[VERIFICATION_STATUS.VERIFIED]: "verified",
|
|
26
|
+
[VERIFICATION_STATUS.PARTIALLY_SUPPORTED]: "partiallySupported",
|
|
27
|
+
[VERIFICATION_STATUS.UNSUPPORTED]: "unsupported",
|
|
28
|
+
[VERIFICATION_STATUS.CONFLICTING]: "conflicting",
|
|
29
|
+
[VERIFICATION_STATUS.VERIFICATION_BLOCKED]: "verificationBlocked",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const VERIFICATION_STATUS_LABELS = Object.freeze({
|
|
33
|
+
[VERIFICATION_STATUS.VERIFIED]: "verified",
|
|
34
|
+
[VERIFICATION_STATUS.PARTIALLY_SUPPORTED]: "partially supported",
|
|
35
|
+
[VERIFICATION_STATUS.UNSUPPORTED]: "unsupported",
|
|
36
|
+
[VERIFICATION_STATUS.CONFLICTING]: "conflicting",
|
|
37
|
+
[VERIFICATION_STATUS.VERIFICATION_BLOCKED]: "verification blocked",
|
|
38
|
+
[VERIFICATION_STATUS.UNVERIFIED]: "unverified",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export function canonicalVerificationStatus(status) {
|
|
42
|
+
const text = String(status ?? "").trim();
|
|
43
|
+
if (!text) return VERIFICATION_STATUS.UNVERIFIED;
|
|
44
|
+
if (text === "partiallySupported")
|
|
45
|
+
return VERIFICATION_STATUS.PARTIALLY_SUPPORTED;
|
|
46
|
+
if (text === "verificationBlocked" || text === "blocked")
|
|
47
|
+
return VERIFICATION_STATUS.VERIFICATION_BLOCKED;
|
|
48
|
+
return Object.values(VERIFICATION_STATUS).includes(text)
|
|
49
|
+
? text
|
|
50
|
+
: VERIFICATION_STATUS.UNVERIFIED;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function verificationStatusBucket(status) {
|
|
54
|
+
return (
|
|
55
|
+
VERIFICATION_STATUS_BUCKETS[canonicalVerificationStatus(status)] ?? "other"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isVerifiedStatus(status) {
|
|
60
|
+
return canonicalVerificationStatus(status) === VERIFICATION_STATUS.VERIFIED;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isVerificationBlockedStatus(status) {
|
|
64
|
+
return (
|
|
65
|
+
canonicalVerificationStatus(status) ===
|
|
66
|
+
VERIFICATION_STATUS.VERIFICATION_BLOCKED
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isNonVerifiedTerminalStatus(status) {
|
|
71
|
+
return [
|
|
72
|
+
VERIFICATION_STATUS.PARTIALLY_SUPPORTED,
|
|
73
|
+
VERIFICATION_STATUS.UNSUPPORTED,
|
|
74
|
+
VERIFICATION_STATUS.CONFLICTING,
|
|
75
|
+
VERIFICATION_STATUS.VERIFICATION_BLOCKED,
|
|
76
|
+
].includes(canonicalVerificationStatus(status));
|
|
77
|
+
}
|
|
@@ -24,13 +24,14 @@
|
|
|
24
24
|
"sourceUrls": { "type": "array", "items": { "type": "string" } },
|
|
25
25
|
"claimSummary": {
|
|
26
26
|
"type": "object",
|
|
27
|
-
"required": ["total", "verified", "partially_supported", "unsupported", "conflicting"],
|
|
27
|
+
"required": ["total", "verified", "partially_supported", "unsupported", "conflicting", "verification_blocked"],
|
|
28
28
|
"properties": {
|
|
29
29
|
"total": { "type": "number" },
|
|
30
30
|
"verified": { "type": "number" },
|
|
31
31
|
"partially_supported": { "type": "number" },
|
|
32
32
|
"unsupported": { "type": "number" },
|
|
33
|
-
"conflicting": { "type": "number" }
|
|
33
|
+
"conflicting": { "type": "number" },
|
|
34
|
+
"verification_blocked": { "type": "number" }
|
|
34
35
|
},
|
|
35
36
|
"additionalProperties": true
|
|
36
37
|
},
|
|
@@ -56,7 +57,6 @@
|
|
|
56
57
|
},
|
|
57
58
|
"auditArtifact": { "type": "string" },
|
|
58
59
|
"sidecarPath": { "type": "string" },
|
|
59
|
-
"reportSidecarPath": { "type": "string" },
|
|
60
60
|
"auditSidecarPath": { "type": "string" },
|
|
61
61
|
"reportMarkdown": { "type": "string" },
|
|
62
62
|
"auditMarkdown": { "type": "string" },
|