@tangle-network/agent-app 0.44.16 → 0.44.17
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/dist/work-product/index.d.ts +332 -127
- package/dist/work-product/index.js +262 -63
- package/dist/work-product/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -17,6 +17,124 @@ import {
|
|
|
17
17
|
defineAppTool
|
|
18
18
|
} from "../chunk-YEFFHORB.js";
|
|
19
19
|
|
|
20
|
+
// src/work-product/claim-support.ts
|
|
21
|
+
var CURRENCY = /[$€£¥₹]/gu;
|
|
22
|
+
var NUMBER_IN_TEXT = /[$€£¥₹]?\s*\d{1,3}(?:,\d{3})+(?:\.\d+)?|[$€£¥₹]?\s*\d+(?:\.\d+)?/gu;
|
|
23
|
+
var FIGURE_IN_PROSE = /[$€£¥₹]\s*[-+]?\d[\d,]*(?:\.\d+)?|[-+]?\d{1,3}(?:,\d{3})+(?:\.\d+)?|[-+]?\d+\.\d{2}(?!\d)/gu;
|
|
24
|
+
var WHOLE_VALUE = /^[$€£¥₹]?\s*[-+]?\s*(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?\s*%?$/u;
|
|
25
|
+
function canonicalizeValue(token) {
|
|
26
|
+
let text = token.trim();
|
|
27
|
+
if (text.length === 0) return null;
|
|
28
|
+
if (/^\(.*\)$/u.test(text)) text = text.slice(1, -1).trim();
|
|
29
|
+
text = text.replace(CURRENCY, "").trim();
|
|
30
|
+
text = text.replace(/^[-+]\s*/u, "").trim();
|
|
31
|
+
text = text.replace(/%$/u, "").trim();
|
|
32
|
+
if (!/^(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?$/u.test(text)) return null;
|
|
33
|
+
text = text.replace(/,/gu, "");
|
|
34
|
+
if (text.includes(".")) text = text.replace(/0+$/u, "").replace(/\.$/u, "");
|
|
35
|
+
text = text.replace(/^0+(?=\d)/u, "");
|
|
36
|
+
return text.length === 0 ? null : text;
|
|
37
|
+
}
|
|
38
|
+
function valuesInText(text) {
|
|
39
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40
|
+
for (const match of text.matchAll(NUMBER_IN_TEXT)) {
|
|
41
|
+
const canonical = canonicalizeValue(match[0]);
|
|
42
|
+
if (canonical !== null) seen.add(canonical);
|
|
43
|
+
}
|
|
44
|
+
return [...seen];
|
|
45
|
+
}
|
|
46
|
+
function claimValues(claim) {
|
|
47
|
+
const trimmed = claim.trim();
|
|
48
|
+
if (WHOLE_VALUE.test(trimmed)) {
|
|
49
|
+
const whole = canonicalizeValue(trimmed);
|
|
50
|
+
if (whole !== null) return [whole];
|
|
51
|
+
}
|
|
52
|
+
const seen = /* @__PURE__ */ new Set();
|
|
53
|
+
for (const match of trimmed.matchAll(FIGURE_IN_PROSE)) {
|
|
54
|
+
const canonical = canonicalizeValue(match[0]);
|
|
55
|
+
if (canonical !== null) seen.add(canonical);
|
|
56
|
+
}
|
|
57
|
+
return [...seen];
|
|
58
|
+
}
|
|
59
|
+
function verifyClaimSupport(quote, claim) {
|
|
60
|
+
if (quote.trim().length === 0) return { status: "not_applicable" };
|
|
61
|
+
const claimed = claimValues(claim);
|
|
62
|
+
if (claimed.length === 0) return { status: "not_applicable" };
|
|
63
|
+
const present = valuesInText(quote);
|
|
64
|
+
const matched = claimed.find((value) => present.includes(value));
|
|
65
|
+
if (matched !== void 0) return { status: "supported", matched };
|
|
66
|
+
return { status: "unsupported", claimed, present };
|
|
67
|
+
}
|
|
68
|
+
function excerpt(quote, limit = 120) {
|
|
69
|
+
const flat = quote.replace(/\s+/gu, " ").trim();
|
|
70
|
+
return flat.length <= limit ? flat : `${flat.slice(0, limit)}\u2026`;
|
|
71
|
+
}
|
|
72
|
+
function claimSupportErrorDetail(failure, quote) {
|
|
73
|
+
const wanted = failure.claimed.length === 1 ? failure.claimed[0] : `any of ${failure.claimed.join(", ")}`;
|
|
74
|
+
const carries = failure.present.length === 0 ? "that line carries no figure at all" : `the only figures on it are ${failure.present.join(", ")}`;
|
|
75
|
+
return `the cited text does not contain ${wanted}. It reads "${excerpt(quote)}", and ${carries}. Cite locator.find with the value exactly as it appears in the document and the platform will locate the right line for you. If this figure was COMPUTED rather than read from the document, omit the locator entirely and state the computation in claim.`;
|
|
76
|
+
}
|
|
77
|
+
function foldLabel(value) {
|
|
78
|
+
return value.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
79
|
+
}
|
|
80
|
+
function verifyTargetLabel(quote, target, groups) {
|
|
81
|
+
if (quote.trim().length === 0) return { status: "not_applicable" };
|
|
82
|
+
const group = groups.find((candidate) => Object.hasOwn(candidate.labels, target));
|
|
83
|
+
if (!group) return { status: "not_applicable" };
|
|
84
|
+
const line = ` ${foldLabel(quote)} `;
|
|
85
|
+
const own = group.labels[target] ?? [];
|
|
86
|
+
for (const label of own) {
|
|
87
|
+
const folded = foldLabel(label);
|
|
88
|
+
if (folded.length > 0 && line.includes(folded)) return { status: "identified", label };
|
|
89
|
+
}
|
|
90
|
+
for (const [rival, labels] of Object.entries(group.labels)) {
|
|
91
|
+
if (rival === target) continue;
|
|
92
|
+
for (const label of labels) {
|
|
93
|
+
const folded = foldLabel(label);
|
|
94
|
+
if (folded.length === 0 || !line.includes(folded)) continue;
|
|
95
|
+
return {
|
|
96
|
+
status: "crossed",
|
|
97
|
+
rival,
|
|
98
|
+
rivalLabel: label,
|
|
99
|
+
expected: own,
|
|
100
|
+
...group.note === void 0 ? {} : { note: group.note }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { status: "not_applicable" };
|
|
105
|
+
}
|
|
106
|
+
function targetLabelErrorDetail(failure, target, quote) {
|
|
107
|
+
const ownLooks = failure.expected.length === 0 ? "" : ` A citation for ${target} should land on the line naming ${failure.expected.map((label) => JSON.stringify(label)).join(" or ")}.`;
|
|
108
|
+
const note = failure.note === void 0 ? "" : ` (${failure.note})`;
|
|
109
|
+
return `the line it cites reads "${excerpt(quote)}", which is the line for ${failure.rival} \u2014 it names ${JSON.stringify(failure.rivalLabel)}${note}.${ownLooks} Cite the line that belongs to this target, or attach this citation to ${failure.rival} instead. The figure is real; it is on the wrong line.`;
|
|
110
|
+
}
|
|
111
|
+
function indexArtifactValues(fields, normalizeTarget) {
|
|
112
|
+
const index = /* @__PURE__ */ new Map();
|
|
113
|
+
for (const [key, raw] of Object.entries(fields ?? {})) {
|
|
114
|
+
const value = typeof raw === "number" && Number.isFinite(raw) ? canonicalizeValue(String(raw)) : typeof raw === "string" ? canonicalizeValue(raw) : null;
|
|
115
|
+
if (value === null) continue;
|
|
116
|
+
index.set(normalizeTarget ? normalizeTarget(key) : key, value);
|
|
117
|
+
}
|
|
118
|
+
return index;
|
|
119
|
+
}
|
|
120
|
+
function verifyArtifactAgreement(target, claim, fieldValues) {
|
|
121
|
+
const expected = fieldValues.get(target);
|
|
122
|
+
if (expected === void 0) return { status: "not_applicable" };
|
|
123
|
+
const claimed = claimValues(claim);
|
|
124
|
+
if (claimed.length === 0) return { status: "not_applicable" };
|
|
125
|
+
if (claimed.includes(expected)) return { status: "agrees", value: expected };
|
|
126
|
+
for (const value of claimed) {
|
|
127
|
+
for (const [other, otherValue] of fieldValues) {
|
|
128
|
+
if (other === target || otherValue !== value) continue;
|
|
129
|
+
return { status: "contradicts", claimed: value, expected, belongsTo: other };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { status: "not_applicable" };
|
|
133
|
+
}
|
|
134
|
+
function artifactAgreementErrorDetail(failure, target) {
|
|
135
|
+
return `the artifact reports ${failure.expected} on ${target} and ${failure.claimed} on ${failure.belongsTo}, so a citation claiming ${failure.claimed} does not support ${target} \u2014 it supports ${failure.belongsTo}. Attach this citation to ${failure.belongsTo}, or correct the artifact if ${target} really is ${failure.claimed}. The package cannot state both.`;
|
|
136
|
+
}
|
|
137
|
+
|
|
20
138
|
// src/work-product/service.ts
|
|
21
139
|
var WORK_PRODUCT_TRANSITIONS = {
|
|
22
140
|
draft: /* @__PURE__ */ new Set(["blocked", "ready"]),
|
|
@@ -52,6 +170,29 @@ function mergeById(existing, incoming) {
|
|
|
52
170
|
}
|
|
53
171
|
return merged;
|
|
54
172
|
}
|
|
173
|
+
function evidenceIdentity(entry) {
|
|
174
|
+
const whole = canonicalizeValue(entry.claim);
|
|
175
|
+
const claimKey = whole ?? entry.claim.normalize("NFKC").toLowerCase().replace(/\s+/gu, " ").trim();
|
|
176
|
+
return `${entry.target}\0${entry.sourceRef}\0${claimKey}`;
|
|
177
|
+
}
|
|
178
|
+
function mergeEvidence(existing, incoming) {
|
|
179
|
+
const merged = existing.slice();
|
|
180
|
+
for (const entry of incoming) {
|
|
181
|
+
const identity = evidenceIdentity(entry);
|
|
182
|
+
const hits = [];
|
|
183
|
+
for (let index = 0; index < merged.length; index += 1) {
|
|
184
|
+
const candidate = merged[index];
|
|
185
|
+
if (candidate.id === entry.id || evidenceIdentity(candidate) === identity) hits.push(index);
|
|
186
|
+
}
|
|
187
|
+
if (hits.length === 0) {
|
|
188
|
+
merged.push(entry);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
merged[hits[0]] = entry;
|
|
192
|
+
for (const index of hits.slice(1).reverse()) merged.splice(index, 1);
|
|
193
|
+
}
|
|
194
|
+
return merged;
|
|
195
|
+
}
|
|
55
196
|
function createWorkProductService(options) {
|
|
56
197
|
const { store } = options;
|
|
57
198
|
const now = options.now ?? (() => Date.now());
|
|
@@ -158,7 +299,7 @@ function createWorkProductService(options) {
|
|
|
158
299
|
const upsertEvidence = (id, entries) => guardedMerge(
|
|
159
300
|
id,
|
|
160
301
|
["draft", "blocked"],
|
|
161
|
-
(record) => ({ evidence:
|
|
302
|
+
(record) => ({ evidence: mergeEvidence(record.evidence, entries) }),
|
|
162
303
|
{
|
|
163
304
|
step: "wp.evidence",
|
|
164
305
|
message: (record) => `Evidence upserted (${entries.length} entries, ${record.evidence.length} total)`,
|
|
@@ -387,64 +528,6 @@ function workProductTrustInputs(records, verdictsFor) {
|
|
|
387
528
|
return items;
|
|
388
529
|
}
|
|
389
530
|
|
|
390
|
-
// src/work-product/claim-support.ts
|
|
391
|
-
var CURRENCY = /[$€£¥₹]/gu;
|
|
392
|
-
var NUMBER_IN_TEXT = /[$€£¥₹]?\s*\d{1,3}(?:,\d{3})+(?:\.\d+)?|[$€£¥₹]?\s*\d+(?:\.\d+)?/gu;
|
|
393
|
-
var FIGURE_IN_PROSE = /[$€£¥₹]\s*[-+]?\d[\d,]*(?:\.\d+)?|[-+]?\d{1,3}(?:,\d{3})+(?:\.\d+)?|[-+]?\d+\.\d{2}(?!\d)/gu;
|
|
394
|
-
var WHOLE_VALUE = /^[$€£¥₹]?\s*[-+]?\s*(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?\s*%?$/u;
|
|
395
|
-
function canonicalizeValue(token) {
|
|
396
|
-
let text = token.trim();
|
|
397
|
-
if (text.length === 0) return null;
|
|
398
|
-
if (/^\(.*\)$/u.test(text)) text = text.slice(1, -1).trim();
|
|
399
|
-
text = text.replace(CURRENCY, "").trim();
|
|
400
|
-
text = text.replace(/^[-+]\s*/u, "").trim();
|
|
401
|
-
text = text.replace(/%$/u, "").trim();
|
|
402
|
-
if (!/^(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?$/u.test(text)) return null;
|
|
403
|
-
text = text.replace(/,/gu, "");
|
|
404
|
-
if (text.includes(".")) text = text.replace(/0+$/u, "").replace(/\.$/u, "");
|
|
405
|
-
text = text.replace(/^0+(?=\d)/u, "");
|
|
406
|
-
return text.length === 0 ? null : text;
|
|
407
|
-
}
|
|
408
|
-
function valuesInText(text) {
|
|
409
|
-
const seen = /* @__PURE__ */ new Set();
|
|
410
|
-
for (const match of text.matchAll(NUMBER_IN_TEXT)) {
|
|
411
|
-
const canonical = canonicalizeValue(match[0]);
|
|
412
|
-
if (canonical !== null) seen.add(canonical);
|
|
413
|
-
}
|
|
414
|
-
return [...seen];
|
|
415
|
-
}
|
|
416
|
-
function claimValues(claim) {
|
|
417
|
-
const trimmed = claim.trim();
|
|
418
|
-
if (WHOLE_VALUE.test(trimmed)) {
|
|
419
|
-
const whole = canonicalizeValue(trimmed);
|
|
420
|
-
if (whole !== null) return [whole];
|
|
421
|
-
}
|
|
422
|
-
const seen = /* @__PURE__ */ new Set();
|
|
423
|
-
for (const match of trimmed.matchAll(FIGURE_IN_PROSE)) {
|
|
424
|
-
const canonical = canonicalizeValue(match[0]);
|
|
425
|
-
if (canonical !== null) seen.add(canonical);
|
|
426
|
-
}
|
|
427
|
-
return [...seen];
|
|
428
|
-
}
|
|
429
|
-
function verifyClaimSupport(quote, claim) {
|
|
430
|
-
if (quote.trim().length === 0) return { status: "not_applicable" };
|
|
431
|
-
const claimed = claimValues(claim);
|
|
432
|
-
if (claimed.length === 0) return { status: "not_applicable" };
|
|
433
|
-
const present = valuesInText(quote);
|
|
434
|
-
const matched = claimed.find((value) => present.includes(value));
|
|
435
|
-
if (matched !== void 0) return { status: "supported", matched };
|
|
436
|
-
return { status: "unsupported", claimed, present };
|
|
437
|
-
}
|
|
438
|
-
function excerpt(quote, limit = 120) {
|
|
439
|
-
const flat = quote.replace(/\s+/gu, " ").trim();
|
|
440
|
-
return flat.length <= limit ? flat : `${flat.slice(0, limit)}\u2026`;
|
|
441
|
-
}
|
|
442
|
-
function claimSupportErrorDetail(failure, quote) {
|
|
443
|
-
const wanted = failure.claimed.length === 1 ? failure.claimed[0] : `any of ${failure.claimed.join(", ")}`;
|
|
444
|
-
const carries = failure.present.length === 0 ? "that line carries no figure at all" : `the only figures on it are ${failure.present.join(", ")}`;
|
|
445
|
-
return `the cited text does not contain ${wanted}. It reads "${excerpt(quote)}", and ${carries}. Cite locator.find with the value exactly as it appears in the document and the platform will locate the right line for you. If this figure was COMPUTED rather than read from the document, omit the locator entirely and state the computation in claim.`;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
531
|
// src/work-product/quote.ts
|
|
449
532
|
var WHITESPACE = /[\s\p{Zs}\u2028\u2029\u200b-\u200d\ufeff]+/gu;
|
|
450
533
|
var DASHES = /[\u2010-\u2015\u2212\ufe58\ufe63\uff0d]/gu;
|
|
@@ -537,6 +620,8 @@ var MAX_WORK_PRODUCT_BATCH = 50;
|
|
|
537
620
|
var EVIDENCE_COVERAGE_CHECK = "evidence_coverage";
|
|
538
621
|
var QUOTE_VERIFICATION_CHECK = "quote_verification";
|
|
539
622
|
var CLAIM_SUPPORT_CHECK = "claim_support";
|
|
623
|
+
var TARGET_CORRECTNESS_CHECK = "target_correctness";
|
|
624
|
+
var ARTIFACT_AGREEMENT_CHECK = "artifact_agreement";
|
|
540
625
|
async function unwrap(run, code) {
|
|
541
626
|
let outcome = await run();
|
|
542
627
|
if (!outcome.succeeded && outcome.conflict) outcome = await run();
|
|
@@ -704,6 +789,35 @@ function assertClaimsSupported(config, entries) {
|
|
|
704
789
|
);
|
|
705
790
|
}
|
|
706
791
|
}
|
|
792
|
+
function assertTargetsNotCrossed(config, entries) {
|
|
793
|
+
const groups = config.confusableTargets;
|
|
794
|
+
if (!groups || groups.length === 0) return;
|
|
795
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
796
|
+
const entry = entries[index];
|
|
797
|
+
const quote = entry.locator.quote;
|
|
798
|
+
if (quote === void 0) continue;
|
|
799
|
+
const verdict = verifyTargetLabel(quote, entry.target, groups);
|
|
800
|
+
if (verdict.status !== "crossed") continue;
|
|
801
|
+
throw new ToolInputError(
|
|
802
|
+
"target_crossed",
|
|
803
|
+
`entries[${index}] is attached to ${entry.target} but ${targetLabelErrorDetail(verdict, entry.target, quote)}`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
function assertEvidenceAgreesWithArtifact(config, entries, artifact) {
|
|
808
|
+
if (config.verifyArtifactAgreement === false) return;
|
|
809
|
+
const fieldValues = indexArtifactValues(artifact?.fields, config.normalizeTarget);
|
|
810
|
+
if (fieldValues.size === 0) return;
|
|
811
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
812
|
+
const entry = entries[index];
|
|
813
|
+
const agreement = verifyArtifactAgreement(entry.target, entry.claim, fieldValues);
|
|
814
|
+
if (agreement.status !== "contradicts") continue;
|
|
815
|
+
throw new ToolInputError(
|
|
816
|
+
"contradicts_artifact",
|
|
817
|
+
`entries[${index}].claim ${JSON.stringify(entry.claim)} contradicts this work product's own artifact: ${artifactAgreementErrorDetail(agreement, entry.target)}`
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
707
821
|
async function summarizeQuoteVerification(config, evidence, ctx) {
|
|
708
822
|
const readSourceText = config.readSourceText;
|
|
709
823
|
if (!readSourceText) return void 0;
|
|
@@ -753,6 +867,41 @@ function summarizeClaimSupport(evidence) {
|
|
|
753
867
|
}
|
|
754
868
|
return { supported, checkable, unsupported };
|
|
755
869
|
}
|
|
870
|
+
function summarizeTargetCorrectness(evidence, groups, targetOf) {
|
|
871
|
+
let correct = 0;
|
|
872
|
+
let checkable = 0;
|
|
873
|
+
const crossed = [];
|
|
874
|
+
for (const entry of evidence) {
|
|
875
|
+
const quote = entry.locator.quote;
|
|
876
|
+
if (quote === void 0) continue;
|
|
877
|
+
const target = targetOf(entry);
|
|
878
|
+
const verdict = verifyTargetLabel(quote, target, groups);
|
|
879
|
+
if (verdict.status === "not_applicable") continue;
|
|
880
|
+
checkable += 1;
|
|
881
|
+
if (verdict.status === "identified") correct += 1;
|
|
882
|
+
else crossed.push({ id: entry.id, detail: `${entry.id} (${target} cites the ${verdict.rival} line)` });
|
|
883
|
+
}
|
|
884
|
+
return { correct, checkable, crossed };
|
|
885
|
+
}
|
|
886
|
+
function summarizeArtifactAgreement(evidence, fieldValues, targetOf) {
|
|
887
|
+
let agreeing = 0;
|
|
888
|
+
let checkable = 0;
|
|
889
|
+
const contradicting = [];
|
|
890
|
+
for (const entry of evidence) {
|
|
891
|
+
const target = targetOf(entry);
|
|
892
|
+
const agreement = verifyArtifactAgreement(target, entry.claim, fieldValues);
|
|
893
|
+
if (agreement.status === "not_applicable") continue;
|
|
894
|
+
checkable += 1;
|
|
895
|
+
if (agreement.status === "agrees") agreeing += 1;
|
|
896
|
+
else {
|
|
897
|
+
contradicting.push({
|
|
898
|
+
id: entry.id,
|
|
899
|
+
detail: `${entry.id} (${target} claims ${agreement.claimed}, which the artifact reports on ${agreement.belongsTo}; ${target} is ${agreement.expected})`
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return { agreeing, checkable, contradicting };
|
|
904
|
+
}
|
|
756
905
|
function buildWorkProductTools(config) {
|
|
757
906
|
const service = createWorkProductService({
|
|
758
907
|
store: config.store,
|
|
@@ -821,6 +970,7 @@ function buildWorkProductTools(config) {
|
|
|
821
970
|
for (let index = 0; index < raw.length; index += 1) {
|
|
822
971
|
const parsed = parseEvidenceInput(raw[index], `entries[${index}]`);
|
|
823
972
|
if (!parsed.ok) throw new ToolInputError("invalid_evidence", `${parsed.field}: ${parsed.error}`);
|
|
973
|
+
if (config.normalizeTarget) parsed.value.target = config.normalizeTarget(parsed.value.target);
|
|
824
974
|
entries.push(parsed.value);
|
|
825
975
|
}
|
|
826
976
|
for (let index = 0; index < entries.length; index += 1) {
|
|
@@ -834,7 +984,9 @@ function buildWorkProductTools(config) {
|
|
|
834
984
|
}
|
|
835
985
|
await resolveEvidenceQuotes(config, entries, ctx);
|
|
836
986
|
assertClaimsSupported(config, entries);
|
|
987
|
+
assertTargetsNotCrossed(config, entries);
|
|
837
988
|
const draft = await resolveDraft(service, config, scopeKey, ctx);
|
|
989
|
+
assertEvidenceAgreesWithArtifact(config, entries, draft.artifact);
|
|
838
990
|
const record = await unwrap(() => service.upsertEvidence(draft.id, entries), "evidence_rejected");
|
|
839
991
|
return {
|
|
840
992
|
workProductId: record.id,
|
|
@@ -1015,15 +1167,55 @@ function buildWorkProductTools(config) {
|
|
|
1015
1167
|
);
|
|
1016
1168
|
}
|
|
1017
1169
|
}
|
|
1170
|
+
const targetOf = (entry) => config.normalizeTarget ? config.normalizeTarget(entry.target) : entry.target;
|
|
1171
|
+
if (config.confusableTargets && config.confusableTargets.length > 0) {
|
|
1172
|
+
const crossing = summarizeTargetCorrectness(draft.evidence, config.confusableTargets, targetOf);
|
|
1173
|
+
checks.unshift({
|
|
1174
|
+
id: TARGET_CORRECTNESS_CHECK,
|
|
1175
|
+
name: TARGET_CORRECTNESS_CHECK,
|
|
1176
|
+
passed: crossing.crossed.length === 0,
|
|
1177
|
+
detail: crossing.crossed.length > 0 ? `Citations attached to the wrong target: ${crossing.crossed.map((item) => item.detail).join("; ")}` : crossing.checkable === 0 ? "No citation lands on a line this product can tell apart from a sibling target \u2014 nothing to check" : `${crossing.correct}/${crossing.checkable} citations land on a line belonging to their own target`,
|
|
1178
|
+
source: "platform"
|
|
1179
|
+
});
|
|
1180
|
+
if (crossing.crossed.length > 0) {
|
|
1181
|
+
await unwrap(() => service.recordChecks(draft.id, checks), "checks_rejected");
|
|
1182
|
+
throw new ToolInputError(
|
|
1183
|
+
"target_crossed",
|
|
1184
|
+
`Cannot submit: ${crossing.crossed.length} evidence entr${crossing.crossed.length === 1 ? "y cites" : "ies cite"} a line belonging to a different target \u2014 ${crossing.crossed.map((item) => item.detail).join("; ")}. Re-emit each against the target whose line it actually cites, or cite the line that belongs to the target it is attached to.`
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const artifactValues = indexArtifactValues(artifact.fields, config.normalizeTarget);
|
|
1189
|
+
if (config.verifyArtifactAgreement !== false && artifactValues.size > 0) {
|
|
1190
|
+
const agreement = summarizeArtifactAgreement(draft.evidence, artifactValues, targetOf);
|
|
1191
|
+
checks.unshift({
|
|
1192
|
+
id: ARTIFACT_AGREEMENT_CHECK,
|
|
1193
|
+
name: ARTIFACT_AGREEMENT_CHECK,
|
|
1194
|
+
passed: agreement.contradicting.length === 0,
|
|
1195
|
+
detail: agreement.contradicting.length > 0 ? `Evidence contradicts the artifact on: ${agreement.contradicting.map((item) => item.detail).join("; ")}` : agreement.checkable === 0 ? "No evidence claim states a figure the artifact also states \u2014 nothing to check" : `${agreement.agreeing}/${agreement.checkable} evidence claims agree with the artifact field they support`,
|
|
1196
|
+
source: "platform"
|
|
1197
|
+
});
|
|
1198
|
+
if (agreement.contradicting.length > 0) {
|
|
1199
|
+
await unwrap(() => service.recordChecks(draft.id, checks), "checks_rejected");
|
|
1200
|
+
throw new ToolInputError(
|
|
1201
|
+
"contradicts_artifact",
|
|
1202
|
+
`Cannot submit: ${agreement.contradicting.length} evidence entr${agreement.contradicting.length === 1 ? "y contradicts" : "ies contradict"} the artifact they support \u2014 ${agreement.contradicting.map((item) => item.detail).join("; ")}. Move each citation to the target it actually supports, or correct the artifact. The package cannot state both.`
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1018
1206
|
if (config.materialTargets) {
|
|
1019
|
-
const targets =
|
|
1020
|
-
|
|
1207
|
+
const targets = [
|
|
1208
|
+
...new Set(
|
|
1209
|
+
config.materialTargets(artifact).map((target) => config.normalizeTarget ? config.normalizeTarget(target) : target)
|
|
1210
|
+
)
|
|
1211
|
+
];
|
|
1212
|
+
const covered = new Set(draft.evidence.map(targetOf));
|
|
1021
1213
|
const missing = targets.filter((target) => !covered.has(target));
|
|
1022
1214
|
const anchoredTargets = new Set(
|
|
1023
|
-
draft.evidence.filter((entry) => entry.locator.quoteBasis !== void 0).map(
|
|
1215
|
+
draft.evidence.filter((entry) => entry.locator.quoteBasis !== void 0).map(targetOf)
|
|
1024
1216
|
);
|
|
1025
1217
|
const spanTargets = new Set(
|
|
1026
|
-
draft.evidence.filter((entry) => entry.locator.quoteBasis === "span").map(
|
|
1218
|
+
draft.evidence.filter((entry) => entry.locator.quoteBasis === "span").map(targetOf)
|
|
1027
1219
|
);
|
|
1028
1220
|
const present = targets.filter((target) => covered.has(target));
|
|
1029
1221
|
const unanchored = present.filter((target) => !anchoredTargets.has(target));
|
|
@@ -1161,10 +1353,13 @@ function createWorkProductRoutes(options) {
|
|
|
1161
1353
|
return { list, detail, verdict };
|
|
1162
1354
|
}
|
|
1163
1355
|
export {
|
|
1356
|
+
ARTIFACT_AGREEMENT_CHECK,
|
|
1164
1357
|
CLAIM_SUPPORT_CHECK,
|
|
1165
1358
|
EVIDENCE_COVERAGE_CHECK,
|
|
1166
1359
|
MAX_WORK_PRODUCT_BATCH,
|
|
1167
1360
|
QUOTE_VERIFICATION_CHECK,
|
|
1361
|
+
TARGET_CORRECTNESS_CHECK,
|
|
1362
|
+
artifactAgreementErrorDetail,
|
|
1168
1363
|
buildWorkProductTools,
|
|
1169
1364
|
canTransitionWorkProduct,
|
|
1170
1365
|
canonicalizeValue,
|
|
@@ -1175,6 +1370,7 @@ export {
|
|
|
1175
1370
|
createWorkProductService,
|
|
1176
1371
|
finalizeWorkProductProvenance,
|
|
1177
1372
|
findSourceLine,
|
|
1373
|
+
indexArtifactValues,
|
|
1178
1374
|
isWorkProductStatus,
|
|
1179
1375
|
isWorkProductTerminal,
|
|
1180
1376
|
normalizeQuoteText,
|
|
@@ -1188,10 +1384,13 @@ export {
|
|
|
1188
1384
|
sliceSourceSpan,
|
|
1189
1385
|
sourceContainsQuote,
|
|
1190
1386
|
stampProvenance,
|
|
1387
|
+
targetLabelErrorDetail,
|
|
1191
1388
|
unresolvedBlockingExceptions,
|
|
1192
1389
|
validateWorkProductVerdictBody,
|
|
1193
1390
|
valuesInText,
|
|
1391
|
+
verifyArtifactAgreement,
|
|
1194
1392
|
verifyClaimSupport,
|
|
1393
|
+
verifyTargetLabel,
|
|
1195
1394
|
workProductToPersistedPart,
|
|
1196
1395
|
workProductTrustInputs
|
|
1197
1396
|
};
|