@tangle-network/agent-app 0.44.12 → 0.44.14
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.
|
@@ -215,6 +215,12 @@ declare const EVIDENCE_COVERAGE_CHECK = "evidence_coverage";
|
|
|
215
215
|
* a reviewer reads the strength of the lineage off the row itself rather than
|
|
216
216
|
* trusting that a quote was checked. */
|
|
217
217
|
declare const QUOTE_VERIFICATION_CHECK = "quote_verification";
|
|
218
|
+
/** Platform check: how many quoted evidence entries anchor to text that
|
|
219
|
+
* actually CARRIES the figure the entry claims. Distinct from
|
|
220
|
+
* `quote_verification`, which only proves the text came from the document —
|
|
221
|
+
* production row `7256ef49` passed that one on all four entries while
|
|
222
|
+
* supporting none of them. */
|
|
223
|
+
declare const CLAIM_SUPPORT_CHECK = "claim_support";
|
|
218
224
|
/** Domain seams for the three work-product tools — every domain word is a
|
|
219
225
|
* parameter; the shell bakes none. */
|
|
220
226
|
interface WorkProductToolConfig {
|
|
@@ -258,6 +264,17 @@ interface WorkProductToolConfig {
|
|
|
258
264
|
* document-derived targets, and coverage stops being satisfiable by
|
|
259
265
|
* assertion. */
|
|
260
266
|
requireAnchoredEvidence?: boolean;
|
|
267
|
+
/** Verify that each anchored quote CARRIES the figure its entry claims.
|
|
268
|
+
* ON by default — an anchor that does not support its claim is the one
|
|
269
|
+
* failure mode every other gate here passes, and it reads to a reviewer as
|
|
270
|
+
* the most authoritative citation on the row.
|
|
271
|
+
*
|
|
272
|
+
* Only claims that assert a figure are checked, so a filing status, a name
|
|
273
|
+
* or a date is unaffected; see `./claim-support` for exactly where the line
|
|
274
|
+
* is drawn and why it is drawn to stay satisfiable. Set `false` only for a
|
|
275
|
+
* product whose claims are figures the source states in a form no numeric
|
|
276
|
+
* comparison can reach. */
|
|
277
|
+
verifyClaimSupport?: boolean;
|
|
261
278
|
/** Per-turn provenance closure the ROUTE supplies (profileHash + runId are
|
|
262
279
|
* known at dispatch; trusted, never read from model args). */
|
|
263
280
|
provenance: (ctx: AppToolContext) => WorkProductProvenanceBase;
|
|
@@ -386,6 +403,10 @@ type SourceFindFailure = {
|
|
|
386
403
|
} | {
|
|
387
404
|
reason: 'occurrence_out_of_range';
|
|
388
405
|
found: number;
|
|
406
|
+
} | {
|
|
407
|
+
reason: 'not_distinctive';
|
|
408
|
+
needle: string;
|
|
409
|
+
found: number;
|
|
389
410
|
};
|
|
390
411
|
type SourceFindResult = {
|
|
391
412
|
ok: true;
|
|
@@ -408,6 +429,130 @@ declare function sliceSourceSpan(sourceText: string, span: {
|
|
|
408
429
|
end: number;
|
|
409
430
|
}): SourceSpanResult;
|
|
410
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Claim support — does the anchored text actually say what the entry claims?
|
|
434
|
+
*
|
|
435
|
+
* The two gates before this one answer different questions. `sourceContainsQuote`
|
|
436
|
+
* asks whether the quote is really in the document; `findSourceLine` /
|
|
437
|
+
* `sliceSourceSpan` make the platform produce the quote so it cannot be typed
|
|
438
|
+
* wrong. Both are about the TEXT's provenance. Neither one looks at `claim`.
|
|
439
|
+
*
|
|
440
|
+
* Production row `7256ef49` is what that gap costs. Four evidence entries, all
|
|
441
|
+
* `quoteBasis:'span'`, every quote a genuine slice of the document it named —
|
|
442
|
+
* and every one landing on the employer/payer line about 200 characters above
|
|
443
|
+
* the figure:
|
|
444
|
+
*
|
|
445
|
+
* claim 128450.00 -> "tics LLC EIN 84-2213907\nEmployee: Dana"
|
|
446
|
+
* claim 812.44 -> "nt Savings Bank TIN 22-5510983\nRecip"
|
|
447
|
+
* claim 2204.18 -> "ndex Fund Trust TIN 47-3320115\nRec"
|
|
448
|
+
* claim 1955.02 -> "pient: Dana R. Whitfield\n------------"
|
|
449
|
+
*
|
|
450
|
+
* That is strictly worse than a fabricated quote. A fabricated quote fails the
|
|
451
|
+
* verbatim gate; this one passes every gate, is real text from the right
|
|
452
|
+
* document, and reads to a reviewer as an authoritative citation while
|
|
453
|
+
* supporting nothing. `locator.find` (the platform locating a value the model
|
|
454
|
+
* names) prevents the model from ADDRESSING the wrong line, and it is the right
|
|
455
|
+
* primary fix. This module is the independent check underneath it: whatever
|
|
456
|
+
* anchoring form produced the text, the text has to contain the figure.
|
|
457
|
+
*
|
|
458
|
+
* That also settles what a raw `locator.span` is worth. Hand-computed offsets
|
|
459
|
+
* stay accepted, but only when they independently verify — which is exactly
|
|
460
|
+
* "the slice contains the claimed value".
|
|
461
|
+
*
|
|
462
|
+
* ── The rule, and why it is drawn here ──────────────────────────────────────
|
|
463
|
+
*
|
|
464
|
+
* Strict on figures, silent on everything else. An unsatisfiable gate does not
|
|
465
|
+
* stop a bad submit, it SELECTS for one — that is the measured mechanism behind
|
|
466
|
+
* the 38 fabricated quotes on row `a68b1943`, where a coverage gate demanded
|
|
467
|
+
* document lineage for computed values and got thirteen invented citations
|
|
468
|
+
* forty seconds later. So the rule fires only where an honest citation can
|
|
469
|
+
* always satisfy it:
|
|
470
|
+
*
|
|
471
|
+
* - The claim IS a value (`"128450.00"`, `"$30,000"`, `"3"`) — the anchored
|
|
472
|
+
* text MUST contain that value. No latitude. This is the shape every tax
|
|
473
|
+
* figure takes and the shape row `7256ef49` failed.
|
|
474
|
+
* - The claim is prose naming figures (`"indemnity capped at $5,000,000"`) —
|
|
475
|
+
* at least one of its currency-shaped figures must occur. Not all of them:
|
|
476
|
+
* a claim may legitimately narrate a computation over several lines while
|
|
477
|
+
* anchoring to the one line under discussion, and refusing that would make
|
|
478
|
+
* an honest citation unrepresentable for the sake of a stricter-sounding
|
|
479
|
+
* rule. One is enough to keep the anchor tethered to the claim's subject.
|
|
480
|
+
* - The claim carries no figure at all (`"Married Filing Jointly"`,
|
|
481
|
+
* `"Dana R. Whitfield"`, `"2025-04-15"`) — nothing to check, and the entry
|
|
482
|
+
* passes. A filing status has no number to find, and inventing a
|
|
483
|
+
* word-overlap score here would re-open the hole the verbatim gate closes.
|
|
484
|
+
* - The entry has no anchored text at all — nothing to check. A computed
|
|
485
|
+
* value cites its computation, not a document.
|
|
486
|
+
*
|
|
487
|
+
* A bare year, a form number and a box number are deliberately NOT figures:
|
|
488
|
+
* `2025`, `1040` and `Box 1` have no thousands separator, no cent pair and no
|
|
489
|
+
* currency symbol, so prose mentioning them does not trip the rule.
|
|
490
|
+
*
|
|
491
|
+
* ── Matching is value-wise, never substring ─────────────────────────────────
|
|
492
|
+
*
|
|
493
|
+
* `text.includes(claim)` would be the obvious implementation and it is wrong in
|
|
494
|
+
* the direction that matters: it passes `"450.00"` against `"128,450.00"`, so a
|
|
495
|
+
* claim citing the wrong figure survives whenever its digits happen to be a tail
|
|
496
|
+
* of a real one. Both sides are tokenized into numbers and compared as VALUES,
|
|
497
|
+
* so `450` and `128450` are simply different.
|
|
498
|
+
*/
|
|
499
|
+
/**
|
|
500
|
+
* Reduce a numeric token to the form both sides are compared in: no currency,
|
|
501
|
+
* no grouping, no trailing zeros in the fraction, no leading zeros.
|
|
502
|
+
*
|
|
503
|
+
* Sign and accounting parentheses are stripped rather than preserved, so
|
|
504
|
+
* `(1,234.00)`, `-1,234.00` and `1,234.00` all reduce to `1234`. A document
|
|
505
|
+
* renders the same deduction all three ways depending on the form and the
|
|
506
|
+
* extractor, and the sign is a property of the TARGET LINE's semantics, not of
|
|
507
|
+
* the document's typography. Comparing magnitudes keeps those honest citations
|
|
508
|
+
* working, and it concedes nothing to fabrication: the digits still have to be
|
|
509
|
+
* the document's digits.
|
|
510
|
+
*
|
|
511
|
+
* Returns `null` for anything that is not a plain number.
|
|
512
|
+
*/
|
|
513
|
+
declare function canonicalizeValue(token: string): string | null;
|
|
514
|
+
/** Every distinct value appearing in `text`, canonicalized. */
|
|
515
|
+
declare function valuesInText(text: string): string[];
|
|
516
|
+
/**
|
|
517
|
+
* The values a claim asserts, canonicalized — empty when the claim asserts no
|
|
518
|
+
* figure, which is the "nothing to check" case.
|
|
519
|
+
*
|
|
520
|
+
* A claim that is ENTIRELY one value yields that value (strict path). Otherwise
|
|
521
|
+
* only currency-shaped figures inside the prose count, so an assertion that
|
|
522
|
+
* merely mentions a year or a form number yields nothing.
|
|
523
|
+
*/
|
|
524
|
+
declare function claimValues(claim: string): string[];
|
|
525
|
+
type ClaimSupport =
|
|
526
|
+
/** No figure to check: a non-numeric claim, or no anchored text. */
|
|
527
|
+
{
|
|
528
|
+
status: 'not_applicable';
|
|
529
|
+
} | {
|
|
530
|
+
status: 'supported';
|
|
531
|
+
matched: string;
|
|
532
|
+
} | {
|
|
533
|
+
status: 'unsupported';
|
|
534
|
+
claimed: string[];
|
|
535
|
+
present: string[];
|
|
536
|
+
};
|
|
537
|
+
/**
|
|
538
|
+
* Does `quote` carry the figure `claim` asserts?
|
|
539
|
+
*
|
|
540
|
+
* `supported` requires ONE claimed value to occur, which is exact for the
|
|
541
|
+
* single-value claim (there is only one) and deliberate latitude for prose (see
|
|
542
|
+
* the rule note at the top of the file).
|
|
543
|
+
*/
|
|
544
|
+
declare function verifyClaimSupport(quote: string, claim: string): ClaimSupport;
|
|
545
|
+
/**
|
|
546
|
+
* The sentence a model can act on without re-reading the document: what it
|
|
547
|
+
* claimed, what the line it cited actually says, what figures that line does
|
|
548
|
+
* carry, and the two ways out — cite the value, or drop the locator because the
|
|
549
|
+
* figure was computed. Naming both keeps the gate satisfiable, which is the
|
|
550
|
+
* property that stops it manufacturing the citation it screens for.
|
|
551
|
+
*/
|
|
552
|
+
declare function claimSupportErrorDetail(failure: Extract<ClaimSupport, {
|
|
553
|
+
status: 'unsupported';
|
|
554
|
+
}>, quote: string): string;
|
|
555
|
+
|
|
411
556
|
/**
|
|
412
557
|
* Framework-neutral work-product review endpoints — the
|
|
413
558
|
* `createInteractionAnswerRoute` factory pattern: web-standard
|
|
@@ -497,4 +642,4 @@ interface WorkProductRoutes {
|
|
|
497
642
|
/** Create the work-product review endpoints over the store port and product seams */
|
|
498
643
|
declare function createWorkProductRoutes(options: WorkProductRoutesOptions): WorkProductRoutes;
|
|
499
644
|
|
|
500
|
-
export { type CreateWorkProductInput, EVIDENCE_COVERAGE_CHECK, EvidenceEntry, ExceptionEntry, type FinalizeWorkProductProvenanceInput, type InMemoryWorkProductStore, MAX_WORK_PRODUCT_BATCH, QUOTE_VERIFICATION_CHECK, QualityCheck, type SourceFindFailure, type SourceFindResult, type SourceSpanFailure, type SourceSpanResult, type SubmitWorkProductInput, TrustItem, WorkProductArtifact, WorkProductAuditEvent, type WorkProductAuthorizeArgs, type WorkProductOutcome, WorkProductPersistedPart, WorkProductProvenance, type WorkProductProvenanceBase, WorkProductRecord, type WorkProductRouteAuthorization, type WorkProductRoutes, type WorkProductRoutesOptions, type WorkProductService, type WorkProductServiceOptions, WorkProductStatus, WorkProductStorePort, type WorkProductToolConfig, type WorkProductVerdictBody, type WorkProductVerdictInput, buildWorkProductTools, canTransitionWorkProduct, createInMemoryWorkProductStore, createWorkProductRoutes, createWorkProductService, finalizeWorkProductProvenance, findSourceLine, isWorkProductTerminal, normalizeQuoteText, sliceSourceSpan, sourceContainsQuote, stampProvenance, validateWorkProductVerdictBody, workProductTrustInputs };
|
|
645
|
+
export { CLAIM_SUPPORT_CHECK, type ClaimSupport, type CreateWorkProductInput, EVIDENCE_COVERAGE_CHECK, EvidenceEntry, ExceptionEntry, type FinalizeWorkProductProvenanceInput, type InMemoryWorkProductStore, MAX_WORK_PRODUCT_BATCH, QUOTE_VERIFICATION_CHECK, QualityCheck, type SourceFindFailure, type SourceFindResult, type SourceSpanFailure, type SourceSpanResult, type SubmitWorkProductInput, TrustItem, WorkProductArtifact, WorkProductAuditEvent, type WorkProductAuthorizeArgs, type WorkProductOutcome, WorkProductPersistedPart, WorkProductProvenance, type WorkProductProvenanceBase, WorkProductRecord, type WorkProductRouteAuthorization, type WorkProductRoutes, type WorkProductRoutesOptions, type WorkProductService, type WorkProductServiceOptions, WorkProductStatus, WorkProductStorePort, type WorkProductToolConfig, type WorkProductVerdictBody, type WorkProductVerdictInput, buildWorkProductTools, canTransitionWorkProduct, canonicalizeValue, claimSupportErrorDetail, claimValues, createInMemoryWorkProductStore, createWorkProductRoutes, createWorkProductService, finalizeWorkProductProvenance, findSourceLine, isWorkProductTerminal, normalizeQuoteText, sliceSourceSpan, sourceContainsQuote, stampProvenance, validateWorkProductVerdictBody, valuesInText, verifyClaimSupport, workProductTrustInputs };
|
|
@@ -387,6 +387,64 @@ function workProductTrustInputs(records, verdictsFor) {
|
|
|
387
387
|
return items;
|
|
388
388
|
}
|
|
389
389
|
|
|
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
|
+
|
|
390
448
|
// src/work-product/quote.ts
|
|
391
449
|
var WHITESPACE = /[\s\p{Zs}\u2028\u2029\u200b-\u200d\ufeff]+/gu;
|
|
392
450
|
var DASHES = /[\u2010-\u2015\u2212\ufe58\ufe63\uff0d]/gu;
|
|
@@ -403,6 +461,8 @@ function sourceContainsQuote(sourceText, quote) {
|
|
|
403
461
|
if (normalizedQuote.length === 0) return false;
|
|
404
462
|
return normalizeQuoteText(sourceText).includes(normalizedQuote);
|
|
405
463
|
}
|
|
464
|
+
var MIN_NEEDLE_LENGTH = 3;
|
|
465
|
+
var MAX_AMBIGUOUS_MATCHES = 8;
|
|
406
466
|
function lineAround(text, index) {
|
|
407
467
|
let start = text.lastIndexOf("\n", index);
|
|
408
468
|
start = start < 0 ? 0 : start + 1;
|
|
@@ -414,26 +474,41 @@ function lineAround(text, index) {
|
|
|
414
474
|
function findSourceLine(sourceText, needle, occurrence = 1) {
|
|
415
475
|
const trimmed = needle.trim();
|
|
416
476
|
if (trimmed.length === 0) return { ok: false, failure: { reason: "blank_needle" } };
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
positions.push(at);
|
|
477
|
+
if (trimmed.length < MIN_NEEDLE_LENGTH) {
|
|
478
|
+
return { ok: false, failure: { reason: "not_distinctive", needle: trimmed, found: 0 } };
|
|
420
479
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
480
|
+
const eachLine = (visit) => {
|
|
481
|
+
let cursor = 0;
|
|
482
|
+
while (cursor <= sourceText.length) {
|
|
483
|
+
const bound2 = lineAround(sourceText, cursor);
|
|
484
|
+
visit(bound2, sourceText.slice(bound2.start, bound2.end));
|
|
485
|
+
if (bound2.end >= sourceText.length) break;
|
|
486
|
+
cursor = bound2.end + 1;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
const positions = [];
|
|
490
|
+
const wantedValue = canonicalizeValue(trimmed);
|
|
491
|
+
if (wantedValue !== null) {
|
|
492
|
+
eachLine((bound2, line) => {
|
|
493
|
+
if (valuesInText(line).includes(wantedValue)) positions.push(bound2.start);
|
|
494
|
+
});
|
|
495
|
+
} else {
|
|
496
|
+
for (let at = sourceText.indexOf(trimmed); at >= 0; at = sourceText.indexOf(trimmed, at + 1)) {
|
|
497
|
+
positions.push(at);
|
|
498
|
+
}
|
|
499
|
+
if (positions.length === 0) {
|
|
500
|
+
const wanted = normalizeQuoteText(trimmed);
|
|
501
|
+
if (wanted.length > 0) {
|
|
502
|
+
eachLine((bound2, line) => {
|
|
503
|
+
if (normalizeQuoteText(line).includes(wanted)) positions.push(bound2.start);
|
|
504
|
+
});
|
|
433
505
|
}
|
|
434
506
|
}
|
|
435
507
|
}
|
|
436
508
|
if (positions.length === 0) return { ok: false, failure: { reason: "not_found" } };
|
|
509
|
+
if (occurrence === 1 && positions.length > MAX_AMBIGUOUS_MATCHES) {
|
|
510
|
+
return { ok: false, failure: { reason: "not_distinctive", needle: trimmed, found: positions.length } };
|
|
511
|
+
}
|
|
437
512
|
if (occurrence < 1 || occurrence > positions.length) {
|
|
438
513
|
return { ok: false, failure: { reason: "occurrence_out_of_range", found: positions.length } };
|
|
439
514
|
}
|
|
@@ -461,6 +536,7 @@ function sliceSourceSpan(sourceText, span) {
|
|
|
461
536
|
var MAX_WORK_PRODUCT_BATCH = 50;
|
|
462
537
|
var EVIDENCE_COVERAGE_CHECK = "evidence_coverage";
|
|
463
538
|
var QUOTE_VERIFICATION_CHECK = "quote_verification";
|
|
539
|
+
var CLAIM_SUPPORT_CHECK = "claim_support";
|
|
464
540
|
async function unwrap(run, code) {
|
|
465
541
|
let outcome = await run();
|
|
466
542
|
if (!outcome.succeeded && outcome.conflict) outcome = await run();
|
|
@@ -527,6 +603,8 @@ function findErrorDetail(failure, needle) {
|
|
|
527
603
|
return `${JSON.stringify(needle)} does not occur in that document. Read it again and cite a value it actually contains, or \u2014 if this figure is COMPUTED rather than read \u2014 omit the locator and state the computation in claim.`;
|
|
528
604
|
case "occurrence_out_of_range":
|
|
529
605
|
return `that value occurs ${failure.found} time(s) in the document; findOccurrence is out of range.`;
|
|
606
|
+
case "not_distinctive":
|
|
607
|
+
return failure.found === 0 ? `${JSON.stringify(failure.needle)} is too short to identify a place in the document \u2014 a digit or two matches somewhere in almost any text. Cite the labelled line instead (for example "Box 1 Wages, tips, other compensation ......... 128,450.00"). If the document does not state this value at all, omit the locator and say so in claim rather than pointing at an unrelated line.` : `${JSON.stringify(failure.needle)} occurs ${failure.found} times, so it names no particular place. Cite a longer stretch of the supporting line, or pass findOccurrence to say which one you mean.`;
|
|
530
608
|
}
|
|
531
609
|
}
|
|
532
610
|
async function resolveEvidenceQuotes(config, entries, ctx) {
|
|
@@ -612,6 +690,20 @@ async function resolveEvidenceQuotes(config, entries, ctx) {
|
|
|
612
690
|
entry.locator.quoteBasis = "model";
|
|
613
691
|
}
|
|
614
692
|
}
|
|
693
|
+
function assertClaimsSupported(config, entries) {
|
|
694
|
+
if (config.verifyClaimSupport === false) return;
|
|
695
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
696
|
+
const entry = entries[index];
|
|
697
|
+
const quote = entry.locator.quote;
|
|
698
|
+
if (quote === void 0) continue;
|
|
699
|
+
const support = verifyClaimSupport(quote, entry.claim);
|
|
700
|
+
if (support.status !== "unsupported") continue;
|
|
701
|
+
throw new ToolInputError(
|
|
702
|
+
"claim_not_supported",
|
|
703
|
+
`entries[${index}].claim ${JSON.stringify(entry.claim)} is not supported by the text it cites in "${entry.sourceRef}": ${claimSupportErrorDetail(support, quote)}`
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
615
707
|
async function summarizeQuoteVerification(config, evidence, ctx) {
|
|
616
708
|
const readSourceText = config.readSourceText;
|
|
617
709
|
if (!readSourceText) return void 0;
|
|
@@ -646,6 +738,21 @@ async function summarizeQuoteVerification(config, evidence, ctx) {
|
|
|
646
738
|
}
|
|
647
739
|
return { verified, spanAnchored, withoutQuote, failed };
|
|
648
740
|
}
|
|
741
|
+
function summarizeClaimSupport(evidence) {
|
|
742
|
+
let supported = 0;
|
|
743
|
+
let checkable = 0;
|
|
744
|
+
const unsupported = [];
|
|
745
|
+
for (const entry of evidence) {
|
|
746
|
+
const quote = entry.locator.quote;
|
|
747
|
+
if (quote === void 0) continue;
|
|
748
|
+
const support = verifyClaimSupport(quote, entry.claim);
|
|
749
|
+
if (support.status === "not_applicable") continue;
|
|
750
|
+
checkable += 1;
|
|
751
|
+
if (support.status === "supported") supported += 1;
|
|
752
|
+
else unsupported.push(entry.id);
|
|
753
|
+
}
|
|
754
|
+
return { supported, checkable, unsupported };
|
|
755
|
+
}
|
|
649
756
|
function buildWorkProductTools(config) {
|
|
650
757
|
const service = createWorkProductService({
|
|
651
758
|
store: config.store,
|
|
@@ -726,6 +833,7 @@ function buildWorkProductTools(config) {
|
|
|
726
833
|
}
|
|
727
834
|
}
|
|
728
835
|
await resolveEvidenceQuotes(config, entries, ctx);
|
|
836
|
+
assertClaimsSupported(config, entries);
|
|
729
837
|
const draft = await resolveDraft(service, config, scopeKey, ctx);
|
|
730
838
|
const record = await unwrap(() => service.upsertEvidence(draft.id, entries), "evidence_rejected");
|
|
731
839
|
return {
|
|
@@ -885,6 +993,28 @@ function buildWorkProductTools(config) {
|
|
|
885
993
|
);
|
|
886
994
|
}
|
|
887
995
|
}
|
|
996
|
+
if (config.verifyClaimSupport !== false) {
|
|
997
|
+
const support = summarizeClaimSupport(draft.evidence);
|
|
998
|
+
checks.unshift({
|
|
999
|
+
id: CLAIM_SUPPORT_CHECK,
|
|
1000
|
+
name: CLAIM_SUPPORT_CHECK,
|
|
1001
|
+
passed: support.unsupported.length === 0,
|
|
1002
|
+
detail: support.unsupported.length > 0 ? `Cited text does not contain the claimed figure on: ${support.unsupported.join(", ")}` : support.checkable === 0 ? (
|
|
1003
|
+
// Honest about a vacuous pass: no entry paired a quote with a
|
|
1004
|
+
// figure, so nothing was checked. Reporting "0/0 verified"
|
|
1005
|
+
// would read to a reviewer as assurance that was never earned.
|
|
1006
|
+
"No citation pairs a quote with a claimed figure \u2014 nothing to check"
|
|
1007
|
+
) : `${support.supported}/${support.checkable} value-bearing citations anchor to text containing the claimed figure`,
|
|
1008
|
+
source: "platform"
|
|
1009
|
+
});
|
|
1010
|
+
if (support.unsupported.length > 0) {
|
|
1011
|
+
await unwrap(() => service.recordChecks(draft.id, checks), "checks_rejected");
|
|
1012
|
+
throw new ToolInputError(
|
|
1013
|
+
"claim_not_supported",
|
|
1014
|
+
`Cannot submit: ${support.unsupported.length} evidence entr${support.unsupported.length === 1 ? "y cites" : "ies cite"} text that does not contain the figure claimed (${support.unsupported.join(", ")}). Re-emit each with locator.find set to the value exactly as it appears in the document, or without a locator if the figure was computed rather than read.`
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
888
1018
|
if (config.materialTargets) {
|
|
889
1019
|
const targets = config.materialTargets(artifact);
|
|
890
1020
|
const covered = new Set(draft.evidence.map((entry) => entry.target));
|
|
@@ -1031,11 +1161,15 @@ function createWorkProductRoutes(options) {
|
|
|
1031
1161
|
return { list, detail, verdict };
|
|
1032
1162
|
}
|
|
1033
1163
|
export {
|
|
1164
|
+
CLAIM_SUPPORT_CHECK,
|
|
1034
1165
|
EVIDENCE_COVERAGE_CHECK,
|
|
1035
1166
|
MAX_WORK_PRODUCT_BATCH,
|
|
1036
1167
|
QUOTE_VERIFICATION_CHECK,
|
|
1037
1168
|
buildWorkProductTools,
|
|
1038
1169
|
canTransitionWorkProduct,
|
|
1170
|
+
canonicalizeValue,
|
|
1171
|
+
claimSupportErrorDetail,
|
|
1172
|
+
claimValues,
|
|
1039
1173
|
createInMemoryWorkProductStore,
|
|
1040
1174
|
createWorkProductRoutes,
|
|
1041
1175
|
createWorkProductService,
|
|
@@ -1056,6 +1190,8 @@ export {
|
|
|
1056
1190
|
stampProvenance,
|
|
1057
1191
|
unresolvedBlockingExceptions,
|
|
1058
1192
|
validateWorkProductVerdictBody,
|
|
1193
|
+
valuesInText,
|
|
1194
|
+
verifyClaimSupport,
|
|
1059
1195
|
workProductToPersistedPart,
|
|
1060
1196
|
workProductTrustInputs
|
|
1061
1197
|
};
|