@tangle-network/agent-app 0.44.16 → 0.44.18
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 +244 -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,21 @@ 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
|
+
}
|
|
707
807
|
async function summarizeQuoteVerification(config, evidence, ctx) {
|
|
708
808
|
const readSourceText = config.readSourceText;
|
|
709
809
|
if (!readSourceText) return void 0;
|
|
@@ -753,6 +853,38 @@ function summarizeClaimSupport(evidence) {
|
|
|
753
853
|
}
|
|
754
854
|
return { supported, checkable, unsupported };
|
|
755
855
|
}
|
|
856
|
+
function summarizeTargetCorrectness(evidence, groups, targetOf) {
|
|
857
|
+
let correct = 0;
|
|
858
|
+
let checkable = 0;
|
|
859
|
+
const crossed = [];
|
|
860
|
+
for (const entry of evidence) {
|
|
861
|
+
const quote = entry.locator.quote;
|
|
862
|
+
if (quote === void 0) continue;
|
|
863
|
+
const target = targetOf(entry);
|
|
864
|
+
const verdict = verifyTargetLabel(quote, target, groups);
|
|
865
|
+
if (verdict.status === "not_applicable") continue;
|
|
866
|
+
checkable += 1;
|
|
867
|
+
if (verdict.status === "identified") correct += 1;
|
|
868
|
+
else crossed.push({ id: entry.id, detail: `${entry.id} (${target} cites the ${verdict.rival} line)` });
|
|
869
|
+
}
|
|
870
|
+
return { correct, checkable, crossed };
|
|
871
|
+
}
|
|
872
|
+
function summarizeArtifactAgreement(evidence, fieldValues, targetOf) {
|
|
873
|
+
let agreeing = 0;
|
|
874
|
+
let checkable = 0;
|
|
875
|
+
const contradicting = [];
|
|
876
|
+
for (const entry of evidence) {
|
|
877
|
+
const target = targetOf(entry);
|
|
878
|
+
const agreement = verifyArtifactAgreement(target, entry.claim, fieldValues);
|
|
879
|
+
if (agreement.status === "not_applicable") continue;
|
|
880
|
+
checkable += 1;
|
|
881
|
+
if (agreement.status === "agrees") agreeing += 1;
|
|
882
|
+
else {
|
|
883
|
+
contradicting.push({ id: entry.id, detail: `${entry.id}: ${artifactAgreementErrorDetail(agreement, target)}` });
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return { agreeing, checkable, contradicting };
|
|
887
|
+
}
|
|
756
888
|
function buildWorkProductTools(config) {
|
|
757
889
|
const service = createWorkProductService({
|
|
758
890
|
store: config.store,
|
|
@@ -821,6 +953,7 @@ function buildWorkProductTools(config) {
|
|
|
821
953
|
for (let index = 0; index < raw.length; index += 1) {
|
|
822
954
|
const parsed = parseEvidenceInput(raw[index], `entries[${index}]`);
|
|
823
955
|
if (!parsed.ok) throw new ToolInputError("invalid_evidence", `${parsed.field}: ${parsed.error}`);
|
|
956
|
+
if (config.normalizeTarget) parsed.value.target = config.normalizeTarget(parsed.value.target);
|
|
824
957
|
entries.push(parsed.value);
|
|
825
958
|
}
|
|
826
959
|
for (let index = 0; index < entries.length; index += 1) {
|
|
@@ -834,6 +967,7 @@ function buildWorkProductTools(config) {
|
|
|
834
967
|
}
|
|
835
968
|
await resolveEvidenceQuotes(config, entries, ctx);
|
|
836
969
|
assertClaimsSupported(config, entries);
|
|
970
|
+
assertTargetsNotCrossed(config, entries);
|
|
837
971
|
const draft = await resolveDraft(service, config, scopeKey, ctx);
|
|
838
972
|
const record = await unwrap(() => service.upsertEvidence(draft.id, entries), "evidence_rejected");
|
|
839
973
|
return {
|
|
@@ -1015,15 +1149,55 @@ function buildWorkProductTools(config) {
|
|
|
1015
1149
|
);
|
|
1016
1150
|
}
|
|
1017
1151
|
}
|
|
1152
|
+
const targetOf = (entry) => config.normalizeTarget ? config.normalizeTarget(entry.target) : entry.target;
|
|
1153
|
+
if (config.confusableTargets && config.confusableTargets.length > 0) {
|
|
1154
|
+
const crossing = summarizeTargetCorrectness(draft.evidence, config.confusableTargets, targetOf);
|
|
1155
|
+
checks.unshift({
|
|
1156
|
+
id: TARGET_CORRECTNESS_CHECK,
|
|
1157
|
+
name: TARGET_CORRECTNESS_CHECK,
|
|
1158
|
+
passed: crossing.crossed.length === 0,
|
|
1159
|
+
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`,
|
|
1160
|
+
source: "platform"
|
|
1161
|
+
});
|
|
1162
|
+
if (crossing.crossed.length > 0) {
|
|
1163
|
+
await unwrap(() => service.recordChecks(draft.id, checks), "checks_rejected");
|
|
1164
|
+
throw new ToolInputError(
|
|
1165
|
+
"target_crossed",
|
|
1166
|
+
`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.`
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
const artifactValues = indexArtifactValues(artifact.fields, config.normalizeTarget);
|
|
1171
|
+
if (config.verifyArtifactAgreement !== false && artifactValues.size > 0) {
|
|
1172
|
+
const agreement = summarizeArtifactAgreement(draft.evidence, artifactValues, targetOf);
|
|
1173
|
+
checks.unshift({
|
|
1174
|
+
id: ARTIFACT_AGREEMENT_CHECK,
|
|
1175
|
+
name: ARTIFACT_AGREEMENT_CHECK,
|
|
1176
|
+
passed: agreement.contradicting.length === 0,
|
|
1177
|
+
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`,
|
|
1178
|
+
source: "platform"
|
|
1179
|
+
});
|
|
1180
|
+
if (agreement.contradicting.length > 0) {
|
|
1181
|
+
await unwrap(() => service.recordChecks(draft.id, checks), "checks_rejected");
|
|
1182
|
+
throw new ToolInputError(
|
|
1183
|
+
"contradicts_artifact",
|
|
1184
|
+
`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.`
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1018
1188
|
if (config.materialTargets) {
|
|
1019
|
-
const targets =
|
|
1020
|
-
|
|
1189
|
+
const targets = [
|
|
1190
|
+
...new Set(
|
|
1191
|
+
config.materialTargets(artifact).map((target) => config.normalizeTarget ? config.normalizeTarget(target) : target)
|
|
1192
|
+
)
|
|
1193
|
+
];
|
|
1194
|
+
const covered = new Set(draft.evidence.map(targetOf));
|
|
1021
1195
|
const missing = targets.filter((target) => !covered.has(target));
|
|
1022
1196
|
const anchoredTargets = new Set(
|
|
1023
|
-
draft.evidence.filter((entry) => entry.locator.quoteBasis !== void 0).map(
|
|
1197
|
+
draft.evidence.filter((entry) => entry.locator.quoteBasis !== void 0).map(targetOf)
|
|
1024
1198
|
);
|
|
1025
1199
|
const spanTargets = new Set(
|
|
1026
|
-
draft.evidence.filter((entry) => entry.locator.quoteBasis === "span").map(
|
|
1200
|
+
draft.evidence.filter((entry) => entry.locator.quoteBasis === "span").map(targetOf)
|
|
1027
1201
|
);
|
|
1028
1202
|
const present = targets.filter((target) => covered.has(target));
|
|
1029
1203
|
const unanchored = present.filter((target) => !anchoredTargets.has(target));
|
|
@@ -1161,10 +1335,13 @@ function createWorkProductRoutes(options) {
|
|
|
1161
1335
|
return { list, detail, verdict };
|
|
1162
1336
|
}
|
|
1163
1337
|
export {
|
|
1338
|
+
ARTIFACT_AGREEMENT_CHECK,
|
|
1164
1339
|
CLAIM_SUPPORT_CHECK,
|
|
1165
1340
|
EVIDENCE_COVERAGE_CHECK,
|
|
1166
1341
|
MAX_WORK_PRODUCT_BATCH,
|
|
1167
1342
|
QUOTE_VERIFICATION_CHECK,
|
|
1343
|
+
TARGET_CORRECTNESS_CHECK,
|
|
1344
|
+
artifactAgreementErrorDetail,
|
|
1168
1345
|
buildWorkProductTools,
|
|
1169
1346
|
canTransitionWorkProduct,
|
|
1170
1347
|
canonicalizeValue,
|
|
@@ -1175,6 +1352,7 @@ export {
|
|
|
1175
1352
|
createWorkProductService,
|
|
1176
1353
|
finalizeWorkProductProvenance,
|
|
1177
1354
|
findSourceLine,
|
|
1355
|
+
indexArtifactValues,
|
|
1178
1356
|
isWorkProductStatus,
|
|
1179
1357
|
isWorkProductTerminal,
|
|
1180
1358
|
normalizeQuoteText,
|
|
@@ -1188,10 +1366,13 @@ export {
|
|
|
1188
1366
|
sliceSourceSpan,
|
|
1189
1367
|
sourceContainsQuote,
|
|
1190
1368
|
stampProvenance,
|
|
1369
|
+
targetLabelErrorDetail,
|
|
1191
1370
|
unresolvedBlockingExceptions,
|
|
1192
1371
|
validateWorkProductVerdictBody,
|
|
1193
1372
|
valuesInText,
|
|
1373
|
+
verifyArtifactAgreement,
|
|
1194
1374
|
verifyClaimSupport,
|
|
1375
|
+
verifyTargetLabel,
|
|
1195
1376
|
workProductToPersistedPart,
|
|
1196
1377
|
workProductTrustInputs
|
|
1197
1378
|
};
|