@tangle-network/agent-app 0.44.13 → 0.44.15
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 +146 -1
- package/dist/work-product/index.js +151 -15
- package/dist/work-product/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/work-product/service.ts","../../src/work-product/provenance.ts","../../src/work-product/quote.ts","../../src/work-product/tools.ts","../../src/work-product/route.ts"],"sourcesContent":["/**\n * The guarded work-product status machine — the `/missions` service PATTERN\n * (load → validate against a transition table → CAS-write guarded on what was\n * read → audit event) over {@link WorkProductStorePort}. Deliberately NOT a\n * reuse of the mission service: missions' cursor/plan/budget machinery does\n * not apply here, and the six-status review machine is a different contract.\n *\n * Concurrency contract: a scope's draft is driven by a single serialized turn\n * owner, so real contention is rare. The service is the typed guard layer,\n * not a serializer — every mutation re-reads the record and CAS-writes\n * guarded on the `{status, version}` it read. A guard miss surfaces as\n * `{ succeeded: false, conflict: true }` (retryable: re-read and re-apply),\n * never a silent clobber.\n */\n\nimport {\n unresolvedBlockingExceptions,\n type EvidenceEntry,\n type ExceptionEntry,\n type QualityCheck,\n type WorkProductArtifact,\n type WorkProductAuditEvent,\n type WorkProductPatch,\n type WorkProductProvenance,\n type WorkProductRecord,\n type WorkProductStatus,\n type WorkProductStorePort,\n type WorkProductVersionEntry,\n} from './types'\n\n/** Discriminated outcome for guarded operations — `conflict` distinguishes a\n * lost guarded race (retryable) from a logic rejection (illegal edge,\n * missing row — deterministic, never retried). */\nexport type WorkProductOutcome<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; error: string; conflict: boolean }\n\n// Legal status transitions. A target absent from a source's set is rejected\n// by the guarded helpers. `superseded` is terminal.\nconst WORK_PRODUCT_TRANSITIONS: Record<WorkProductStatus, ReadonlySet<WorkProductStatus>> = {\n draft: new Set<WorkProductStatus>(['blocked', 'ready']),\n blocked: new Set<WorkProductStatus>(['draft']),\n ready: new Set<WorkProductStatus>(['changes_requested', 'approved', 'superseded']),\n changes_requested: new Set<WorkProductStatus>(['draft', 'superseded']),\n approved: new Set<WorkProductStatus>(['superseded']),\n superseded: new Set<WorkProductStatus>(),\n}\n\n/** Whether a legal edge exists from `from` to `to` in the review machine. */\nexport function canTransitionWorkProduct(from: WorkProductStatus, to: WorkProductStatus): boolean {\n return WORK_PRODUCT_TRANSITIONS[from].has(to)\n}\n\n/** Statuses a work product can never leave. */\nexport function isWorkProductTerminal(status: WorkProductStatus): boolean {\n return WORK_PRODUCT_TRANSITIONS[status].size === 0\n}\n\n/** Define the input required to create a new draft work product row */\nexport interface CreateWorkProductInput {\n /** Explicit row id — omit to use the service's generator. The MODEL never\n * supplies ids; this is for deterministic server-side creation. */\n id?: string\n workspaceId: string\n threadId: string | null\n scopeKey: string\n /** Version this draft will become; default 1. The tool layer passes\n * `last reviewed version + 1` when a scope is re-engaged after approval. */\n version?: number\n provenance: WorkProductProvenance\n /** Opaque product-column values handed VERBATIM to the store's insert. */\n extras?: Record<string, unknown>\n}\n\n/** Payload for the draft→ready submit transition */\nexport interface SubmitWorkProductInput {\n artifact: WorkProductArtifact\n checks: QualityCheck[]\n provenance: WorkProductProvenance\n /** Frozen snapshot ref of this version's body for the history entry;\n * defaults to `artifact.path`. */\n artifactPath?: string\n}\n\n/** Reviewer verdict payload for the ready→approved / ready→changes_requested transition */\nexport interface WorkProductVerdictInput {\n verdict: 'approve' | 'request_changes'\n reviewedBy: string\n note?: string\n}\n\n/** Guarded mutation surface over the work-product store */\nexport interface WorkProductService {\n create(input: CreateWorkProductInput): Promise<WorkProductRecord>\n get(id: string): Promise<WorkProductRecord | null>\n /** The scope's open accumulator row (`draft`/`blocked`), or null. */\n openDraft(workspaceId: string, scopeKey: string): Promise<WorkProductRecord | null>\n /** The scope's `changes_requested` row awaiting its correction turn, or null. */\n awaitingCorrection(workspaceId: string, scopeKey: string): Promise<WorkProductRecord | null>\n /** The scope's `ready` row awaiting review, or null. */\n awaitingReview(workspaceId: string, scopeKey: string): Promise<WorkProductRecord | null>\n /** `max(version over the scope's approved/superseded rows) + 1` — the\n * version a fresh draft for the scope should carry. */\n nextVersion(workspaceId: string, scopeKey: string): Promise<number>\n /** Re-open a `changes_requested` row as the next draft: version bumps +1\n * and the correction turn accumulates into the same scope row. */\n reopen(id: string): Promise<WorkProductOutcome<WorkProductRecord>>\n /** Merge evidence entries by id (same id replaces — the agent can correct\n * itself mid-turn). Legal only while `draft`/`blocked`. */\n upsertEvidence(id: string, entries: readonly EvidenceEntry[]): Promise<WorkProductOutcome<WorkProductRecord>>\n /** Merge exception entries by id, then reconcile the blocked flag: any\n * unresolved blocking entry parks `draft`→`blocked`; resolving the last\n * one releases `blocked`→`draft`. */\n upsertExceptions(id: string, entries: readonly ExceptionEntry[]): Promise<WorkProductOutcome<WorkProductRecord>>\n /** Persist a checks array without transitioning — how a failed platform\n * gate (e.g. evidence_coverage) stays visible on the still-draft row. */\n recordChecks(id: string, checks: readonly QualityCheck[]): Promise<WorkProductOutcome<WorkProductRecord>>\n /** The terminal agent call: CAS `draft`→`ready` with artifact + checks +\n * provenance, appending the version-history entry. Refuses while an\n * unresolved blocking exception exists. */\n submit(id: string, input: SubmitWorkProductInput): Promise<WorkProductOutcome<WorkProductRecord>>\n /** Reviewer verdict: CAS `ready`→`approved`/`changes_requested` + history\n * entry. Approval also supersedes the scope's prior approved versions so\n * exactly one approved version is current per scope. */\n applyVerdict(id: string, input: WorkProductVerdictInput): Promise<WorkProductOutcome<WorkProductRecord>>\n /** Explicit replacement: CAS to `superseded` (legal from ready /\n * changes_requested / approved). */\n supersede(id: string): Promise<WorkProductOutcome<WorkProductRecord>>\n}\n\n/** Configuration options for creating a work product service */\nexport interface WorkProductServiceOptions {\n store: WorkProductStorePort\n /** Injectable clock (epoch ms). Default `Date.now`. */\n now?: () => number\n /** Row-id generator. Default `crypto.randomUUID()`. */\n generateId?: () => string\n}\n\nfunction rejected<T>(error: string): WorkProductOutcome<T> {\n return { succeeded: false, error, conflict: false }\n}\n\nfunction lostRace<T>(id: string): WorkProductOutcome<T> {\n return { succeeded: false, error: `Work product ${id} changed concurrently`, conflict: true }\n}\n\n/** Merge-by-id upsert: existing order preserved, replaced in place, new\n * entries appended in call order. */\nfunction mergeById<T extends { id: string }>(existing: readonly T[], incoming: readonly T[]): T[] {\n const merged = existing.slice()\n const indexById = new Map(merged.map((entry, index) => [entry.id, index] as const))\n for (const entry of incoming) {\n const at = indexById.get(entry.id)\n if (at === undefined) {\n indexById.set(entry.id, merged.length)\n merged.push(entry)\n } else {\n merged[at] = entry\n }\n }\n return merged\n}\n\n/** Create the guarded work-product service over a store port */\nexport function createWorkProductService(options: WorkProductServiceOptions): WorkProductService {\n const { store } = options\n const now = options.now ?? (() => Date.now())\n const generateId = options.generateId ?? (() => crypto.randomUUID())\n\n async function appendEvent(\n record: WorkProductRecord,\n step: string,\n message: string,\n metadata: Record<string, unknown> = {},\n ): Promise<void> {\n await store.appendEvent({\n workProductId: record.id,\n workspaceId: record.workspaceId,\n step,\n message,\n metadata,\n at: now(),\n })\n }\n\n // Guarded status transition: load → validate the edge → CAS guarded on the\n // {status, version} read → audit event. The loser of a racing transition\n // gets a conflict instead of silently violating the machine.\n async function transition(\n id: string,\n to: WorkProductStatus,\n patch: Omit<WorkProductPatch, 'status'> = {},\n eventMeta: Record<string, unknown> = {},\n ): Promise<WorkProductOutcome<WorkProductRecord>> {\n const record = await store.load(id)\n if (!record) return rejected(`Work product ${id} not found`)\n const from = record.status\n if (isWorkProductTerminal(from)) {\n return rejected(`Work product ${id} is terminal (${from}); cannot transition to ${to}`)\n }\n if (!canTransitionWorkProduct(from, to)) {\n return rejected(`Illegal work-product transition ${from} -> ${to} for ${id}`)\n }\n const updated = await store.update(\n id,\n { status: from, version: record.version },\n { status: to, updatedAt: now(), ...patch },\n )\n if (!updated) return lostRace(id)\n await appendEvent(updated, `wp.${to}`, `Work product ${from} -> ${to}`, { from, to, ...eventMeta })\n return { succeeded: true, value: updated }\n }\n\n // Merge-style guarded write with no status change (evidence / exceptions /\n // checks accumulate on the open row).\n async function guardedMerge(\n id: string,\n legalStatuses: readonly WorkProductStatus[],\n build: (record: WorkProductRecord) => Omit<WorkProductPatch, 'status' | 'version'>,\n event: { step: string; message: (record: WorkProductRecord) => string; metadata?: (record: WorkProductRecord) => Record<string, unknown> },\n ): Promise<WorkProductOutcome<WorkProductRecord>> {\n const record = await store.load(id)\n if (!record) return rejected(`Work product ${id} not found`)\n if (!legalStatuses.includes(record.status)) {\n return rejected(`Work product ${id} is ${record.status}; expected ${legalStatuses.join('/')}`)\n }\n const updated = await store.update(\n id,\n { status: record.status, version: record.version },\n { updatedAt: now(), ...build(record) },\n )\n if (!updated) return lostRace(id)\n await appendEvent(updated, event.step, event.message(updated), event.metadata?.(updated) ?? {})\n return { succeeded: true, value: updated }\n }\n\n const create: WorkProductService['create'] = async (input) => {\n const at = now()\n const record = await store.insert(\n {\n id: input.id ?? generateId(),\n workspaceId: input.workspaceId,\n threadId: input.threadId,\n scopeKey: input.scopeKey,\n status: 'draft',\n version: input.version ?? 1,\n artifact: null,\n evidence: [],\n exceptions: [],\n checks: [],\n provenance: input.provenance,\n history: [],\n createdAt: at,\n updatedAt: at,\n },\n input.extras,\n )\n await appendEvent(record, 'wp.created', `Work product draft v${record.version} created for ${record.scopeKey}`, {\n scopeKey: record.scopeKey,\n version: record.version,\n threadId: record.threadId,\n })\n return record\n }\n\n async function findByScopeAndStatus(\n workspaceId: string,\n scopeKey: string,\n status: WorkProductStatus,\n ): Promise<WorkProductRecord | null> {\n const rows = await store.listByWorkspace(workspaceId, { status: [status] })\n return rows.find((row) => row.scopeKey === scopeKey) ?? null\n }\n\n const nextVersion: WorkProductService['nextVersion'] = async (workspaceId, scopeKey) => {\n const reviewed = await store.listByWorkspace(workspaceId, { status: ['approved', 'superseded'] })\n const versions = reviewed.filter((row) => row.scopeKey === scopeKey).map((row) => row.version)\n return versions.length === 0 ? 1 : Math.max(...versions) + 1\n }\n\n const reopen: WorkProductService['reopen'] = async (id) => {\n const record = await store.load(id)\n if (!record) return rejected(`Work product ${id} not found`)\n if (record.status !== 'changes_requested') {\n return rejected(`Work product ${id} is ${record.status}; only changes_requested reopens`)\n }\n const updated = await store.update(\n id,\n { status: 'changes_requested', version: record.version },\n { status: 'draft', version: record.version + 1, updatedAt: now() },\n )\n if (!updated) return lostRace(id)\n await appendEvent(updated, 'wp.reopened', `Correction draft v${updated.version} opened`, {\n from: record.version,\n to: updated.version,\n })\n return { succeeded: true, value: updated }\n }\n\n const upsertEvidence: WorkProductService['upsertEvidence'] = (id, entries) =>\n guardedMerge(\n id,\n ['draft', 'blocked'],\n (record) => ({ evidence: mergeById(record.evidence, entries) }),\n {\n step: 'wp.evidence',\n message: (record) => `Evidence upserted (${entries.length} entries, ${record.evidence.length} total)`,\n metadata: () => ({ upserted: entries.map((entry) => entry.id) }),\n },\n )\n\n const upsertExceptions: WorkProductService['upsertExceptions'] = async (id, entries) => {\n const merged = await guardedMerge(\n id,\n ['draft', 'blocked'],\n (record) => ({ exceptions: mergeById(record.exceptions, entries) }),\n {\n step: 'wp.exception',\n message: (record) =>\n `Exceptions upserted (${entries.length} entries, ${unresolvedBlockingExceptions(record.exceptions).length} blocking unresolved)`,\n metadata: () => ({ upserted: entries.map((entry) => entry.id) }),\n },\n )\n if (!merged.succeeded) return merged\n // Reconcile the blocked flag AFTER the merge commits: an unresolved\n // blocking entry parks the draft; resolving the last one releases it.\n const record = merged.value\n const blocking = unresolvedBlockingExceptions(record.exceptions).length\n if (record.status === 'draft' && blocking > 0) {\n return transition(id, 'blocked', {}, { blocking })\n }\n if (record.status === 'blocked' && blocking === 0) {\n return transition(id, 'draft', {}, { blocking })\n }\n return merged\n }\n\n const recordChecks: WorkProductService['recordChecks'] = (id, checks) =>\n guardedMerge(\n id,\n ['draft', 'blocked'],\n () => ({ checks: checks.slice() }),\n {\n step: 'wp.checks',\n message: () => `Checks recorded (${checks.length}, ${checks.filter((check) => !check.passed).length} failed)`,\n metadata: () => ({ failed: checks.filter((check) => !check.passed).map((check) => check.name) }),\n },\n )\n\n const submit: WorkProductService['submit'] = async (id, input) => {\n const record = await store.load(id)\n if (!record) return rejected(`Work product ${id} not found`)\n if (record.status !== 'draft') {\n return rejected(`Work product ${id} is ${record.status}; only a draft submits`)\n }\n const blocking = unresolvedBlockingExceptions(record.exceptions)\n if (blocking.length > 0) {\n return rejected(\n `Work product ${id} has ${blocking.length} unresolved blocking exception(s): ${blocking.map((entry) => entry.id).join(', ')}`,\n )\n }\n const artifactPath = input.artifactPath ?? input.artifact.path\n const entry: WorkProductVersionEntry = {\n version: record.version,\n status: 'ready',\n provenance: input.provenance,\n ...(artifactPath === undefined ? {} : { artifactPath }),\n at: now(),\n }\n return transition(\n id,\n 'ready',\n {\n artifact: input.artifact,\n checks: input.checks.slice(),\n provenance: input.provenance,\n history: [...record.history, entry],\n },\n { version: record.version, failedChecks: input.checks.filter((check) => !check.passed).length },\n )\n }\n\n const applyVerdict: WorkProductService['applyVerdict'] = async (id, input) => {\n const record = await store.load(id)\n if (!record) return rejected(`Work product ${id} not found`)\n if (record.status !== 'ready') {\n return rejected(`Work product ${id} is ${record.status}; a verdict applies only to ready`)\n }\n const to: WorkProductStatus = input.verdict === 'approve' ? 'approved' : 'changes_requested'\n const entry: WorkProductVersionEntry = {\n version: record.version,\n status: to,\n provenance: record.provenance,\n ...(record.artifact?.path === undefined ? {} : { artifactPath: record.artifact.path }),\n reviewedBy: input.reviewedBy,\n ...(input.note === undefined ? {} : { reviewNote: input.note }),\n at: now(),\n }\n const outcome = await transition(\n id,\n to,\n { history: [...record.history, entry] },\n { verdict: input.verdict, reviewedBy: input.reviewedBy },\n )\n if (!outcome.succeeded || to !== 'approved') return outcome\n // Exactly one approved version per scope: supersede prior approved rows.\n const priorApproved = await store.listByWorkspace(record.workspaceId, { status: ['approved'] })\n for (const prior of priorApproved) {\n if (prior.id === id || prior.scopeKey !== record.scopeKey) continue\n await transition(prior.id, 'superseded', {}, { supersededBy: id })\n }\n return outcome\n }\n\n return {\n create,\n get: (id) => store.load(id),\n openDraft: (workspaceId, scopeKey) => store.findDraft(workspaceId, scopeKey),\n awaitingCorrection: (workspaceId, scopeKey) => findByScopeAndStatus(workspaceId, scopeKey, 'changes_requested'),\n awaitingReview: (workspaceId, scopeKey) => findByScopeAndStatus(workspaceId, scopeKey, 'ready'),\n nextVersion,\n reopen,\n upsertEvidence,\n upsertExceptions,\n recordChecks,\n submit,\n applyVerdict,\n supersede: (id) => transition(id, 'superseded'),\n }\n}\n\n// ── in-memory store ──────────────────────────────────────────────────────────\n\n/** In-memory store surface with audit trail access and unguarded direct writes for tests */\nexport interface InMemoryWorkProductStore extends WorkProductStorePort {\n /** The full audit trail, append order. */\n events(): WorkProductAuditEvent[]\n /** Unguarded direct write — simulates a concurrent owner in tests. */\n put(record: WorkProductRecord): void\n}\n\n/**\n * In-memory {@link WorkProductStorePort} — the portable backend for tests and\n * reference assemblies. Records are deep-copied on every boundary so callers\n * can never mutate stored state around the guards.\n */\nexport function createInMemoryWorkProductStore(): InMemoryWorkProductStore {\n const rows = new Map<string, WorkProductRecord>()\n const events: WorkProductAuditEvent[] = []\n\n return {\n async load(id) {\n const record = rows.get(id)\n return record ? structuredClone(record) : null\n },\n async findDraft(workspaceId, scopeKey) {\n for (const record of rows.values()) {\n if (\n record.workspaceId === workspaceId &&\n record.scopeKey === scopeKey &&\n (record.status === 'draft' || record.status === 'blocked')\n ) {\n return structuredClone(record)\n }\n }\n return null\n },\n async listByWorkspace(workspaceId, opts) {\n const out: WorkProductRecord[] = []\n for (const record of rows.values()) {\n if (record.workspaceId !== workspaceId) continue\n if (opts?.status && !opts.status.includes(record.status)) continue\n out.push(structuredClone(record))\n }\n return out\n },\n async insert(record) {\n if (rows.has(record.id)) throw new Error(`Work product ${record.id} already exists`)\n rows.set(record.id, structuredClone(record))\n return structuredClone(record)\n },\n async update(id, guard, patch) {\n const current = rows.get(id)\n if (!current) return null\n if (guard.status !== undefined && current.status !== guard.status) return null\n if (guard.version !== undefined && current.version !== guard.version) return null\n const next: WorkProductRecord = { ...current }\n if (patch.status !== undefined) next.status = patch.status\n if (patch.version !== undefined) next.version = patch.version\n if (patch.artifact !== undefined) next.artifact = patch.artifact\n if (patch.evidence !== undefined) next.evidence = patch.evidence\n if (patch.exceptions !== undefined) next.exceptions = patch.exceptions\n if (patch.checks !== undefined) next.checks = patch.checks\n if (patch.provenance !== undefined) next.provenance = patch.provenance\n if (patch.history !== undefined) next.history = patch.history\n if (patch.updatedAt !== undefined) next.updatedAt = patch.updatedAt\n rows.set(id, structuredClone(next))\n return structuredClone(next)\n },\n async appendEvent(event) {\n events.push(structuredClone(event))\n },\n events() {\n return events.map((event) => structuredClone(event))\n },\n put(record) {\n rows.set(record.id, structuredClone(record))\n },\n }\n}\n","/**\n * Provenance stamping — the backtest spine. Every version of every work\n * product carries `profileHash + runId + servingModels`, stamped at the two\n * honest moments:\n *\n * 1. At dispatch, the product route composes the turn's profile and computes\n * agent-eval's `agentProfileHash(profile)` — the SAME hash that keys the\n * app's scorecard cells — and closes it (plus runId/sessionId) into the\n * `provenance` seam `buildWorkProductTools` receives, so the model can\n * neither omit nor forge it.\n * 2. At turn completion, the route's existing lifecycle seam calls\n * {@link finalizeWorkProductProvenance}: serving model and cost come from\n * the usage receipt, because only the completed turn knows what actually\n * served. `servingModels` is honestly EMPTY until then.\n *\n * The trust bridge ({@link workProductTrustInputs}) maps judge-sourced\n * quality verdicts into `/eval-campaign`'s `trustVerdicts()` input with\n * type-only imports — products call `trustVerdicts` from\n * `@tangle-network/agent-app/eval-campaign` (which they already import for\n * their ensemble loop). Deliberately NOT a value re-export here: it would put\n * agent-eval's runtime on `/work-product`'s import path and drag the eval\n * engine into every product worker bundle.\n */\n\nimport type { JudgeVerdict } from '@tangle-network/agent-eval'\nimport type { TrustItem } from '../eval-campaign/trust-gate'\nimport type {\n WorkProductProvenance,\n WorkProductRecord,\n WorkProductStorePort,\n} from './types'\n\nexport type { TrustItem }\n\n/** The dispatch-time provenance closure's output: everything the route knows\n * before the turn completes. */\nexport type WorkProductProvenanceBase = Omit<WorkProductProvenance, 'servingModels' | 'producedAt'>\n\n/** Stamp a full provenance from the dispatch-time base. `servingModels` starts\n * empty (honestly absent — never guessed) until the completion back-fill. */\nexport function stampProvenance(base: WorkProductProvenanceBase, now: () => number = Date.now): WorkProductProvenance {\n return { ...base, servingModels: [], producedAt: now() }\n}\n\n/** Completion receipt for one run, from the turn's usage/lifecycle seam. */\nexport interface FinalizeWorkProductProvenanceInput {\n workspaceId: string\n /** The chat turnId / mission-step run id the records were stamped with. */\n runId: string\n /** What actually served, from the usage receipt / serving-model header. */\n servingModels: readonly string[]\n costUsd?: number\n logger?: Pick<Console, 'warn'>\n}\n\n/**\n * Back-fill `servingModels`/`costUsd` onto every record (and its history\n * entries) stamped with `runId` — wire it into the chat route's existing\n * `lifecycle.onTurnComplete` seam; no new hook. Returns the updated records.\n * A CAS miss on one record is logged and skipped (the next completion or a\n * re-read re-applies); it never throws mid-fleet.\n */\nexport async function finalizeWorkProductProvenance(\n store: WorkProductStorePort,\n input: FinalizeWorkProductProvenanceInput,\n): Promise<WorkProductRecord[]> {\n const rows = await store.listByWorkspace(input.workspaceId)\n const updated: WorkProductRecord[] = []\n for (const record of rows) {\n const recordMatches = record.provenance.runId === input.runId\n const historyMatches = record.history.some((entry) => entry.provenance.runId === input.runId)\n if (!recordMatches && !historyMatches) continue\n const finalize = (provenance: WorkProductProvenance): WorkProductProvenance => ({\n ...provenance,\n servingModels: [...input.servingModels],\n ...(input.costUsd === undefined ? {} : { costUsd: input.costUsd }),\n })\n const next = await store.update(\n record.id,\n { status: record.status, version: record.version },\n {\n ...(recordMatches ? { provenance: finalize(record.provenance) } : {}),\n history: record.history.map((entry) =>\n entry.provenance.runId === input.runId ? { ...entry, provenance: finalize(entry.provenance) } : entry,\n ),\n },\n )\n if (!next) {\n input.logger?.warn(`[work-product] provenance back-fill lost a race on ${record.id}; skipped`)\n continue\n }\n await store.appendEvent({\n workProductId: record.id,\n workspaceId: record.workspaceId,\n step: 'wp.provenance',\n message: `Serving models back-filled for run ${input.runId}`,\n metadata: { runId: input.runId, servingModels: [...input.servingModels], costUsd: input.costUsd ?? null },\n at: Date.now(),\n })\n updated.push(next)\n }\n return updated\n}\n\n/**\n * Trust-gate bridge: one `TrustItem` per work product whose production\n * quality was scored by the product's eval-campaign ensemble. `verdictsFor`\n * returns the per-judge raw verdicts the product retained for a record (the\n * same verdicts `aggregateJudgeVerdicts` reduces); records without verdicts\n * are omitted. Feed the result to `/eval-campaign`'s `trustVerdicts()` —\n * an untrusted verdict renders as \"quality: unverified\", never a naked\n * number. Zero statistics code here: pure mapping.\n */\nexport function workProductTrustInputs<D extends string = string>(\n records: readonly WorkProductRecord[],\n verdictsFor: (record: WorkProductRecord) => readonly JudgeVerdict<D>[] | undefined,\n): TrustItem<D>[] {\n const items: TrustItem<D>[] = []\n for (const record of records) {\n const verdicts = verdictsFor(record)\n if (!verdicts || verdicts.length === 0) continue\n items.push({ itemId: record.id, verdicts })\n }\n return items\n}\n","/**\n * Verbatim quote verification — the lineage gate.\n *\n * An evidence `locator.quote` is the reviewer's click target: it must land on\n * text that is actually in the document the entry names. Nothing here is\n * fuzzy. The only latitude is representational — the same characters written\n * differently by a PDF extractor, a text transcription, or a model repeating\n * what it read: Unicode compatibility forms, curly quotes, the several dash\n * codepoints, non-breaking spaces, and run-length whitespace. A quote that\n * still does not occur after that is not a formatting difference; it is text\n * the document does not contain.\n *\n * Deliberately NOT tolerated, because each one re-opens the hole this closes:\n * case (a quote is a quote), token subsets, edit distance, word-overlap\n * scoring, and \"the numbers match\" heuristics. A fabricated quote and a real\n * one differ by exactly the thing a fuzzy matcher forgives.\n */\n\n/** Whitespace folded to a single ASCII space: the Unicode space separators\n * (`\\p{Zs}`), the line/paragraph separators, and the zero-width characters a\n * PDF text layer leaves behind (ZWSP / ZWNJ / ZWJ / BOM). */\nconst WHITESPACE = /[\\s\\p{Zs}\\u2028\\u2029\\u200b-\\u200d\\ufeff]+/gu\n\n/** Dash-like codepoints that render alike but differ by byte: non-breaking\n * hyphen, figure / en / em / horizontal dash, minus sign, small and fullwidth\n * forms. */\nconst DASHES = /[\\u2010-\\u2015\\u2212\\ufe58\\ufe63\\uff0d]/gu\n\n/** Curly, prime and grave apostrophes folded to ASCII `'`. */\nconst SINGLE_QUOTES = /[\\u2018\\u2019\\u201a\\u201b\\u2032\\u2035\\u00b4`]/gu\n\n/** Curly, low, prime and guillemet double quotes folded to ASCII `\"`. */\nconst DOUBLE_QUOTES = /[\\u201c\\u201d\\u201e\\u201f\\u2033\\u2036\\u00ab\\u00bb]/gu\n\n/**\n * Fold a string to the form both sides of a quote comparison are measured in.\n * Composition and presentation only: NFKC, one dash, one apostrophe, one\n * double quote, runs of any whitespace to a single space, trimmed. Case is\n * PRESERVED — lowercasing would let \"Box 1\" match \"box 1\", and a citation\n * that cannot reproduce capitalization did not read the document.\n */\nexport function normalizeQuoteText(value: string): string {\n return value\n .normalize('NFKC')\n .replace(DASHES, '-')\n .replace(SINGLE_QUOTES, \"'\")\n .replace(DOUBLE_QUOTES, '\"')\n .replace(WHITESPACE, ' ')\n .trim()\n}\n\n/**\n * Does `quote` occur in `sourceText`? Exact substring first (the common case,\n * and free); the normalized comparison second, for the representational\n * differences above. Nothing else.\n *\n * An empty or whitespace-only quote is NOT a match — it would otherwise be a\n * substring of every document and pass the gate vacuously.\n */\nexport function sourceContainsQuote(sourceText: string, quote: string): boolean {\n const trimmed = quote.trim()\n if (trimmed.length === 0) return false\n if (sourceText.includes(trimmed)) return true\n const normalizedQuote = normalizeQuoteText(quote)\n if (normalizedQuote.length === 0) return false\n return normalizeQuoteText(sourceText).includes(normalizedQuote)\n}\n\n// ── spans: the citation form that cannot be wrong ────────────────────────────\n\n/**\n * Slice a citation out of the source text by character offset — the reason\n * this module exists in its stronger form.\n *\n * Verification above is a REJECTION gate: the model retypes a quote and the\n * shell refuses it when the characters do not occur. That gate is correct and\n * it works, but a model that reproduces a line character-for-character only\n * some of the time cannot USE it — every miss is a refusal, and the package\n * ends up with no lineage at all rather than false lineage. A measured 9 of 59\n * on the live tax surface is what \"some of the time\" meant in practice.\n *\n * A span inverts it. The model names two integers into text it just read; the\n * PLATFORM produces the quote from the bytes it already holds. There is no\n * retyping step to get wrong, so a fabricated quote is not rejected — it is\n * unrepresentable. `sourceContainsQuote(text, sliceSourceSpan(text, span))` is\n * true for every span this function returns, by construction.\n *\n * Offsets index the SAME string the product's document-reading tool pages\n * with an offset, which is the same string its `readSourceText` seam returns.\n * That is the one contract a product must keep; violate it and spans point at\n * the wrong characters (still real characters of that document — never\n * invented text, but the wrong line).\n *\n * Half-open `[start, end)`, matching `String.prototype.slice` and the\n * `offset`/`offset + text.length` window a paged read already reports.\n */\nexport type SourceSpanFailure =\n | { reason: 'not_integer'; field: 'start' | 'end' }\n | { reason: 'negative'; field: 'start' | 'end' }\n | { reason: 'inverted' }\n | { reason: 'out_of_range'; totalChars: number }\n | { reason: 'blank' }\n\nexport type SourceSpanResult =\n | { ok: true; quote: string }\n | { ok: false; failure: SourceSpanFailure }\n\n/**\n * Locate the line containing `value` and return it as a span — the citation\n * form for a model that cannot count characters.\n *\n * Measured on production (tax session 135b7cc3, gpt-4.1-mini): given the\n * document text and told exactly which line to cite, the model produced\n * offsets that landed on the WRONG line four times out of four, then missed\n * again on a second attempt after being shown the text its offsets had\n * selected. Character arithmetic is not something this model class does.\n *\n * What it DOES do reliably is read the value: all four `claim` fields in that\n * same run were correct to the cent. So the model names the value it read and\n * the PLATFORM finds it. Both failure modes close at once —\n *\n * - the quote cannot be invented, because the platform slices it;\n * - the span cannot be mis-addressed, because the platform computed it from\n * a needle it PROVED occurs in the text.\n *\n * A needle that is not in the document is refused, which is the same fail-loud\n * posture as a quote that does not occur — and correctly so: the model is\n * asserting the document says something it does not.\n *\n * The cited span is the whole LINE, not the needle: \"128,450.00\" alone is not\n * a click target a reviewer can judge, whereas the line it sits on says what\n * the number IS. Number-only needles are common and deliberately supported.\n */\nexport type SourceFindFailure =\n | { reason: 'blank_needle' }\n | { reason: 'not_found' }\n | { reason: 'occurrence_out_of_range'; found: number }\n\nexport type SourceFindResult =\n | { ok: true; span: { start: number; end: number }; quote: string; occurrences: number }\n | { ok: false; failure: SourceFindFailure }\n\n/** Line bounds containing `index`, trimmed of the newline terminators. */\nfunction lineAround(text: string, index: number): { start: number; end: number } {\n let start = text.lastIndexOf('\\n', index)\n start = start < 0 ? 0 : start + 1\n let end = text.indexOf('\\n', index)\n if (end < 0) end = text.length\n // A CRLF document leaves a trailing \\r inside the line; drop it so the\n // quote is the line a reader sees rather than the line plus a control char.\n if (end > start && text[end - 1] === '\\r') end -= 1\n return { start, end }\n}\n\nexport function findSourceLine(\n sourceText: string,\n needle: string,\n occurrence = 1,\n): SourceFindResult {\n const trimmed = needle.trim()\n if (trimmed.length === 0) return { ok: false, failure: { reason: 'blank_needle' } }\n\n // Exact hits first (free, and the common case). Fall back to the normalized\n // text ONLY to locate a position — the returned quote is always sliced from\n // the ORIGINAL string, so representation folding never leaks into a stored\n // citation.\n const positions: number[] = []\n for (let at = sourceText.indexOf(trimmed); at >= 0; at = sourceText.indexOf(trimmed, at + 1)) {\n positions.push(at)\n }\n if (positions.length === 0) {\n // Second chance for a needle that differs only in representation: a model\n // reading \"128,450.00\" off a PDF layer may report \"128450.00\". Scan lines\n // and compare normalized forms — cheap, and it keeps the honest-but-\n // differently-typed citation working without loosening the exact path.\n const wanted = normalizeQuoteText(trimmed)\n if (wanted.length > 0) {\n let cursor = 0\n while (cursor <= sourceText.length) {\n const bound = lineAround(sourceText, cursor)\n const line = sourceText.slice(bound.start, bound.end)\n if (normalizeQuoteText(line).includes(wanted) || normalizeQuoteText(line.replace(/,/gu, '')).includes(wanted)) {\n positions.push(bound.start)\n }\n if (bound.end >= sourceText.length) break\n cursor = bound.end + 1\n }\n }\n }\n if (positions.length === 0) return { ok: false, failure: { reason: 'not_found' } }\n if (occurrence < 1 || occurrence > positions.length) {\n return { ok: false, failure: { reason: 'occurrence_out_of_range', found: positions.length } }\n }\n\n const bound = lineAround(sourceText, positions[occurrence - 1]!)\n const quote = sourceText.slice(bound.start, bound.end)\n if (quote.trim().length === 0) return { ok: false, failure: { reason: 'not_found' } }\n return { ok: true, span: bound, quote, occurrences: positions.length }\n}\n\n/** Resolve `[start, end)` against `sourceText`. Every rejection is a caller\n * mistake the model can correct from the paged read it already has, so each\n * carries the discriminator a tool layer turns into a specific message. */\nexport function sliceSourceSpan(\n sourceText: string,\n span: { start: number; end: number },\n): SourceSpanResult {\n for (const field of ['start', 'end'] as const) {\n const value = span[field]\n if (!Number.isInteger(value)) return { ok: false, failure: { reason: 'not_integer', field } }\n if (value < 0) return { ok: false, failure: { reason: 'negative', field } }\n }\n if (span.end <= span.start) return { ok: false, failure: { reason: 'inverted' } }\n if (span.end > sourceText.length) {\n return { ok: false, failure: { reason: 'out_of_range', totalChars: sourceText.length } }\n }\n const quote = sourceText.slice(span.start, span.end)\n // A whitespace-only slice is a real slice of the document and would pass\n // `sourceContainsQuote` on any text — the same vacuous pass an empty quote\n // gets there, refused for the same reason: it is not a click target.\n if (quote.trim().length === 0) return { ok: false, failure: { reason: 'blank' } }\n return { ok: true, quote }\n}\n","/**\n * The agent-facing work-product side channel — three registry `customTools`\n * built on `/tools`' `defineAppTool`, dispatched through `dispatchAppTool`'s\n * single validation/outcome path (a thrown `ToolInputError` → correctable 4xx\n * back to the model; any other throw → internal error; a call never silently\n * succeeds without its effect). Deliberately NOT new `/tools` built-ins:\n * extending `AppToolHandlers` would break every existing consumer, and the\n * registry seam exists precisely for a product tool family like this.\n *\n * Partial-emission contract across a long turn: the draft ROW is the\n * accumulator. Evidence and exceptions stream in as found via small batched\n * calls; the artifact arrives once at the end. The agent addresses everything\n * by `scopeKey` — the server mints row ids, the model never invents them, and\n * identity (userId/workspaceId/threadId) rides the trusted `AppToolContext`\n * from headers, never model args. A turn that dies mid-emission leaves a\n * consistent draft the next turn resumes by the same scopeKey.\n */\n\nimport { ToolInputError } from '../tools/errors'\nimport { defineAppTool, type AppToolDefinition } from '../tools/registry'\nimport type { AppToolContext } from '../tools/types'\nimport { createWorkProductService, type WorkProductOutcome, type WorkProductService } from './service'\nimport { stampProvenance, type WorkProductProvenanceBase } from './provenance'\nimport { findSourceLine, sliceSourceSpan, sourceContainsQuote, type SourceFindFailure, type SourceSpanFailure } from './quote'\nimport {\n parseAgentCheckInput,\n parseArtifactInput,\n parseEvidenceInput,\n parseExceptionInput,\n unresolvedBlockingExceptions,\n type EvidenceEntry,\n type ExceptionEntry,\n type QualityCheck,\n type WorkProductArtifact,\n type WorkProductRecord,\n type WorkProductStorePort,\n} from './types'\n\n/** Max entries per `upsert_evidence`/`flag_exception` call — keeps each call a\n * small batch the model can correct precisely on a named-index failure. */\nexport const MAX_WORK_PRODUCT_BATCH = 50\n\n/** Platform check: every material target has ≥1 evidence row. Recorded as\n * `QualityCheck{source:'platform'}`. */\nexport const EVIDENCE_COVERAGE_CHECK = 'evidence_coverage'\n\n/** Platform check: how many evidence entries carry a quote the shell PROVED\n * occurs in the source it names. Recorded when `readSourceText` is wired, so\n * a reviewer reads the strength of the lineage off the row itself rather than\n * trusting that a quote was checked. */\nexport const QUOTE_VERIFICATION_CHECK = 'quote_verification'\n\n/** Domain seams for the three work-product tools — every domain word is a\n * parameter; the shell bakes none. */\nexport interface WorkProductToolConfig {\n store: WorkProductStorePort\n /** PARAMETER — accepted `artifact.kind` values, validated on submit. */\n artifactKinds: readonly string[]\n /** PARAMETER — accepted exception `kind` values. */\n exceptionKinds: readonly string[]\n /** Fail-loud source check: resolve an evidence `sourceRef` to existence\n * (vault stat / attachment lookup). A dangling ref is a `ToolInputError`\n * naming the entry index — lineage can never point at nothing. */\n resolveSourceRef: (ref: string, ctx: AppToolContext) => Promise<boolean>\n /** Fail-loud QUOTE verification: return the source document's TEXT for a\n * ref so the shell can prove each `locator.quote` occurs in it verbatim.\n * Wiring this turns the gate ON — a quote that does not occur is a\n * `ToolInputError` naming the entry index, so the model re-extracts from\n * the document instead of persisting invented lineage.\n *\n * Return `null` ONLY when the ref genuinely has no extractable text (an\n * image scan, an opaque blob). The gate is fail-CLOSED on `null`: a quote\n * that cannot be checked is refused, because \"unverifiable\" and \"verified\"\n * must never look the same to a reviewer. Such an entry is still recordable\n * without `locator.quote` — `claim` carries the assertion.\n *\n * Omit the seam entirely and no quote is checked. */\n readSourceText?: (ref: string, ctx: AppToolContext) => Promise<string | null>\n /** The material targets the platform coverage check requires evidence for.\n * Product-owned vocabulary; omit to skip the coverage gate.\n *\n * Return ONLY targets a source document can actually evidence. A gate that\n * demands document lineage for a value the session COMPUTED is not\n * satisfiable by any honest answer, and an unsatisfiable gate does not stop\n * a submit — it selects for an invented one. Computed values belong to a\n * product's own computation check, where \"matches what we already\n * calculated\" is satisfiable by construction. */\n materialTargets?: (artifact: WorkProductArtifact) => string[]\n /** Require every material target to carry a SOURCE ANCHOR — a span-sliced or\n * verified quote — not merely an evidence row. Off by default (a bare\n * `claim` is legitimate lineage for products with no readable sources);\n * turn it ON once `readSourceText` is wired and `materialTargets` names only\n * document-derived targets, and coverage stops being satisfiable by\n * assertion. */\n requireAnchoredEvidence?: boolean\n /** Per-turn provenance closure the ROUTE supplies (profileHash + runId are\n * known at dispatch; trusted, never read from model args). */\n provenance: (ctx: AppToolContext) => WorkProductProvenanceBase\n /** Called on the draft→ready commit so the route persists the transcript\n * anchor part and the queue projection updates. */\n onReady?: (record: WorkProductRecord, ctx: AppToolContext) => void | Promise<void>\n /** Injectable clock / id generator (tests, deterministic ids). */\n now?: () => number\n generateId?: () => string\n}\n\n/** Unwrap a guarded outcome, retrying ONCE on a lost race (the single\n * serialized turn owner makes real contention rare; one re-read-and-retry\n * absorbs the benign case). A deterministic rejection maps to a correctable\n * `ToolInputError` so the model learns exactly why. */\nasync function unwrap<T>(\n run: () => Promise<WorkProductOutcome<T>>,\n code: string,\n): Promise<T> {\n let outcome = await run()\n if (!outcome.succeeded && outcome.conflict) outcome = await run()\n if (!outcome.succeeded) throw new ToolInputError(code, outcome.error, outcome.conflict ? 409 : 400)\n return outcome.value\n}\n\nfunction requireScopeKey(args: Record<string, unknown>): string {\n const scopeKey = typeof args.scopeKey === 'string' ? args.scopeKey.trim() : ''\n if (!scopeKey) throw new ToolInputError('missing_scope_key', 'scopeKey is required — the engagement key this work product belongs to.')\n return scopeKey\n}\n\nfunction requireBatch(args: Record<string, unknown>, field: string): unknown[] {\n const raw = args[field]\n if (!Array.isArray(raw) || raw.length === 0) {\n throw new ToolInputError('missing_entries', `${field} must be a non-empty array.`)\n }\n if (raw.length > MAX_WORK_PRODUCT_BATCH) {\n throw new ToolInputError('batch_too_large', `${field} accepts at most ${MAX_WORK_PRODUCT_BATCH} entries per call — send smaller batches.`)\n }\n return raw\n}\n\n/**\n * Resolve the scope's open draft row, creating or reopening as needed:\n * an open `draft`/`blocked` row resumes; a `changes_requested` row reopens as\n * the correction draft (version +1); a `ready` row refuses — the package is\n * awaiting review and must receive a verdict first; otherwise a fresh draft\n * is created at `last reviewed version + 1` with dispatch-stamped provenance.\n */\nasync function resolveDraft(\n service: WorkProductService,\n config: WorkProductToolConfig,\n scopeKey: string,\n ctx: AppToolContext,\n): Promise<WorkProductRecord> {\n const open = await service.openDraft(ctx.workspaceId, scopeKey)\n if (open) return open\n const awaitingReview = await service.awaitingReview(ctx.workspaceId, scopeKey)\n if (awaitingReview) {\n throw new ToolInputError(\n 'awaiting_review',\n `Work product for ${scopeKey} (v${awaitingReview.version}) is awaiting review — no further emission until a reviewer verdict.`,\n 409,\n )\n }\n const awaitingCorrection = await service.awaitingCorrection(ctx.workspaceId, scopeKey)\n if (awaitingCorrection) {\n return unwrap(() => service.reopen(awaitingCorrection.id), 'reopen_failed')\n }\n return service.create({\n workspaceId: ctx.workspaceId,\n threadId: ctx.threadId,\n scopeKey,\n version: await service.nextVersion(ctx.workspaceId, scopeKey),\n provenance: stampProvenance(config.provenance(ctx), config.now),\n })\n}\n\n/** Turn a span rejection into the sentence the model can act on WITHOUT\n * re-reading the document — every message names the number that was wrong and\n * the number it must be under. */\nfunction spanErrorDetail(failure: SourceSpanFailure): string {\n switch (failure.reason) {\n case 'not_integer':\n return `${failure.field} must be a whole character offset.`\n case 'negative':\n return `${failure.field} must not be negative.`\n case 'inverted':\n return 'end must be greater than start — the range is half-open [start, end).'\n case 'out_of_range':\n return `end is past the end of the document, which is ${failure.totalChars} characters. Offsets are absolute in the whole document: when you read with an offset, add that offset to the position within the returned text.`\n case 'blank':\n return 'that range is only whitespace. Widen it to the characters that carry the value.'\n }\n}\n\n/** Turn a locate failure into a sentence naming what was searched for. */\nfunction findErrorDetail(failure: SourceFindFailure, needle: string): string {\n switch (failure.reason) {\n case 'blank_needle':\n return 'the value to locate is empty.'\n case 'not_found':\n return `${JSON.stringify(needle)} does not occur in that document. Read it again and cite a value it actually contains, or — if this figure is COMPUTED rather than read — omit the locator and state the computation in claim.`\n case 'occurrence_out_of_range':\n return `that value occurs ${failure.found} time(s) in the document; findOccurrence is out of range.`\n }\n}\n\n/**\n * Resolve every entry's quote against the document it names — the one place\n * lineage text is decided.\n *\n * Two citation forms, and the asymmetry between them is the point:\n *\n * - `locator.span` — the model names `[start, end)` into text it just read\n * and the PLATFORM slices the quote out of the source bytes. There is no\n * retyping step, so a fabricated quote is not rejected, it is impossible.\n * The slice REPLACES any quote the model also sent (the document is the\n * authority, not the model's transcription of it) and the resolved text\n * goes back in the tool result, so the model sees what was stored.\n * - `locator.quote` alone — the legacy free-text form, still verified\n * character-for-character. Kept because evidence written before spans\n * existed, and sources reached through paths that cannot offer offsets,\n * must stay expressible; a product does not lose lineage by upgrading.\n *\n * Entries are MUTATED in place with the resolved quote and its server-set\n * `quoteBasis`, then persisted — so what a reviewer reads is what the platform\n * proved, not what the model typed.\n *\n * Source texts are read once per distinct `sourceRef` in the batch — a\n * 50-entry batch citing three documents is three reads, not fifty.\n *\n * Without `readSourceText` the product has given the shell no way to see its\n * documents: free-text quotes go unchecked (as before — inventing a weaker\n * check would report unverified lineage as verified), and a span is a LOUD\n * refusal rather than a silently dropped locator, because a span the platform\n * cannot slice is a wiring bug in the product, not a model mistake.\n */\nasync function resolveEvidenceQuotes(\n config: WorkProductToolConfig,\n entries: readonly EvidenceEntry[],\n ctx: AppToolContext,\n): Promise<void> {\n const readSourceText = config.readSourceText\n const texts = new Map<string, string | null>()\n const readText = async (ref: string): Promise<string | null> => {\n if (!texts.has(ref)) texts.set(ref, await readSourceText!(ref, ctx))\n return texts.get(ref) ?? null\n }\n\n for (let index = 0; index < entries.length; index += 1) {\n const entry = entries[index]!\n // `quoteBasis` arrives unset — `parseEvidenceInput` drops a model-supplied\n // one — so this function only ever WRITES it, and only on a path that\n // established it. Every `continue` below therefore leaves the entry\n // unlabelled, which is the honest state for a quote nothing proved.\n const find = entry.locator.find\n const span = entry.locator.span\n const quote = entry.locator.quote\n\n if (find !== undefined) {\n if (!readSourceText) {\n throw new ToolInputError(\n 'span_unsupported',\n `entries[${index}].locator.find: this deployment cannot read source text, so a value cannot be located. Record the entry without locator.find.`,\n 500,\n )\n }\n const text = await readText(entry.sourceRef)\n if (text === null) {\n throw new ToolInputError(\n 'unverifiable_quote',\n `entries[${index}].locator.find: \"${entry.sourceRef}\" has no readable text, so the value cannot be located. Record the entry without a locator and state the basis in claim.`,\n )\n }\n const located = findSourceLine(text, find, entry.locator.findOccurrence ?? 1)\n if (!located.ok) {\n throw new ToolInputError(\n 'value_not_found',\n `entries[${index}].locator.find into \"${entry.sourceRef}\": ${findErrorDetail(located.failure, find)}`,\n )\n }\n // The platform owns BOTH halves now: it found the position and it cut\n // the text. The model contributed a value it read, which is the one\n // thing it does reliably.\n entry.locator.span = located.span\n entry.locator.quote = located.quote\n entry.locator.quoteBasis = 'span'\n continue\n }\n\n if (span) {\n if (!readSourceText) {\n throw new ToolInputError(\n 'span_unsupported',\n `entries[${index}].locator.span: this deployment cannot read source text, so a span cannot be resolved into a quote. Record the entry without locator.span.`,\n 500,\n )\n }\n const text = await readText(entry.sourceRef)\n if (text === null) {\n throw new ToolInputError(\n 'unverifiable_quote',\n `entries[${index}].locator.span: \"${entry.sourceRef}\" has no readable text, so the span cannot be resolved. Record the entry without locator.span or locator.quote and state the basis in claim.`,\n )\n }\n const sliced = sliceSourceSpan(text, span)\n if (!sliced.ok) {\n throw new ToolInputError(\n 'invalid_span',\n `entries[${index}].locator.span [${span.start}, ${span.end}) into \"${entry.sourceRef}\": ${spanErrorDetail(sliced.failure)}`,\n )\n }\n entry.locator.quote = sliced.quote\n entry.locator.quoteBasis = 'span'\n continue\n }\n\n if (quote === undefined || quote.trim().length === 0) continue\n if (!readSourceText) continue\n const text = await readText(entry.sourceRef)\n if (text === null) {\n throw new ToolInputError(\n 'unverifiable_quote',\n `entries[${index}].locator.quote: \"${entry.sourceRef}\" has no readable text, so the quote cannot be verified. Record the entry without locator.quote and state the basis in claim.`,\n )\n }\n if (!sourceContainsQuote(text, quote)) {\n throw new ToolInputError(\n 'quote_not_found',\n `entries[${index}].locator.quote: ${JSON.stringify(quote)} does not occur in \"${entry.sourceRef}\". Cite locator.find instead — the value as it appears in the document — and this platform locates it and writes the quote itself, so the text is right by construction. If the value is COMPUTED rather than read, omit both and state the computation in claim.`,\n )\n }\n entry.locator.quoteBasis = 'model'\n }\n}\n\n/** Re-verify every persisted quote at submit time. The upsert gate stops new\n * fabrication; this stops a package whose evidence was written BEFORE the\n * gate existed (or under a since-corrected document) from reaching a\n * reviewer. Entries with no quote are neither verified nor failures — they\n * are lineage with no click target, counted separately so the recorded check\n * states how much of the package is quote-backed.\n *\n * A span-anchored entry is re-checked by RE-SLICING the current document and\n * comparing to the stored text, not by substring search. The difference\n * matters exactly when a source has been replaced since the citation was\n * written: a substring search would still pass if the sentence survived\n * anywhere in the new file, while the reviewer's click target has silently\n * moved. Re-slicing says the offsets still select the same characters. */\nasync function summarizeQuoteVerification(\n config: WorkProductToolConfig,\n evidence: readonly EvidenceEntry[],\n ctx: AppToolContext,\n): Promise<{ verified: number; spanAnchored: number; withoutQuote: number; failed: string[] } | undefined> {\n const readSourceText = config.readSourceText\n if (!readSourceText) return undefined\n const texts = new Map<string, string | null>()\n let verified = 0\n let spanAnchored = 0\n let withoutQuote = 0\n const failed: string[] = []\n for (const entry of evidence) {\n const quote = entry.locator.quote\n if (quote === undefined || quote.trim().length === 0) {\n withoutQuote += 1\n continue\n }\n if (!texts.has(entry.sourceRef)) texts.set(entry.sourceRef, await readSourceText(entry.sourceRef, ctx))\n const text = texts.get(entry.sourceRef)\n if (typeof text !== 'string') {\n failed.push(entry.id)\n continue\n }\n const span = entry.locator.span\n if (span) {\n const sliced = sliceSourceSpan(text, span)\n if (sliced.ok && sliced.quote === quote) {\n verified += 1\n spanAnchored += 1\n } else failed.push(entry.id)\n continue\n }\n if (sourceContainsQuote(text, quote)) verified += 1\n else failed.push(entry.id)\n }\n return { verified, spanAnchored, withoutQuote, failed }\n}\n\n/** Build the three work-product tools for `customTools` registration on the\n * MCP server / HTTP handler / runtime executor. */\nexport function buildWorkProductTools(config: WorkProductToolConfig): AppToolDefinition[] {\n const service = createWorkProductService({\n store: config.store,\n ...(config.now ? { now: config.now } : {}),\n ...(config.generateId ? { generateId: config.generateId } : {}),\n })\n\n const upsertEvidence = defineAppTool({\n name: 'upsert_evidence',\n description:\n 'Record source→field lineage for the current work product, incrementally as you find it. Each entry links a source document (sourceRef + locator) to one artifact target and states the claim it supports. Cite by locator.find — the value exactly as it appears in the document — and the platform locates it and writes the supporting quote for you. Re-emitting an entry id replaces that entry. Address the work product by scopeKey; the first call creates the draft.',\n parameters: {\n type: 'object',\n properties: {\n scopeKey: { type: 'string', description: 'Engagement key for this work product.' },\n entries: {\n type: 'array',\n minItems: 1,\n maxItems: MAX_WORK_PRODUCT_BATCH,\n items: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Stable entry id — re-emit to replace.' },\n sourceRef: { type: 'string', description: 'Vault path / attachment id of the SOURCE document.' },\n locator: {\n type: 'object',\n properties: {\n page: { type: 'number' },\n range: { type: 'string', description: \"Free-form location: 'L120-L134' | 'B7' | '¶4'.\" },\n find: {\n type: 'string',\n description:\n 'PREFERRED — use this whenever the value was READ from a document. The value exactly as it appears in the source (for example \"128,450.00\"), or a short distinctive phrase from the supporting line. The platform LOCATES it, cites the whole line it sits on, and returns that line to you. You do not retype the quote and you do not compute character offsets — so the citation can neither be invented nor land on the wrong line. If the value is not in the document, the entry is refused.',\n },\n findOccurrence: {\n type: 'integer',\n minimum: 1,\n description: 'Which occurrence of `find` to cite when the value appears more than once. Defaults to the first.',\n },\n span: {\n type: 'object',\n description:\n 'Only when you have exact character offsets from a tool that computed them. Absolute offsets into the whole document: start is the first character, end is one past the last. Prefer `find` — offsets computed by hand land on the wrong line.',\n properties: {\n start: { type: 'integer', minimum: 0 },\n end: { type: 'integer', minimum: 1 },\n },\n required: ['start', 'end'],\n },\n quote: {\n type: 'string',\n description:\n 'Fallback for sources that cannot give you character offsets: the supporting text copied character-for-character. The platform checks it occurs in the document and REFUSES the entry if it does not. Prefer span. For a value you COMPUTED rather than read, omit both and state the computation in claim.',\n },\n },\n },\n target: { type: 'string', description: 'Artifact field/claim this evidence supports.' },\n claim: { type: 'string', description: 'The value/assertion at the target.' },\n confidence: { type: 'number', minimum: 0, maximum: 1 },\n },\n required: ['id', 'sourceRef', 'target', 'claim'],\n },\n },\n },\n required: ['scopeKey', 'entries'],\n },\n async execute(args: Record<string, unknown>, ctx: AppToolContext) {\n const scopeKey = requireScopeKey(args)\n const raw = requireBatch(args, 'entries')\n const entries: EvidenceEntry[] = []\n for (let index = 0; index < raw.length; index += 1) {\n const parsed = parseEvidenceInput(raw[index], `entries[${index}]`)\n if (!parsed.ok) throw new ToolInputError('invalid_evidence', `${parsed.field}: ${parsed.error}`)\n entries.push(parsed.value)\n }\n // Fail-loud source resolution: lineage can never point at nothing.\n for (let index = 0; index < entries.length; index += 1) {\n const entry = entries[index]!\n if (!(await config.resolveSourceRef(entry.sourceRef, ctx))) {\n throw new ToolInputError(\n 'unknown_source_ref',\n `entries[${index}].sourceRef: \"${entry.sourceRef}\" does not resolve to an existing source document.`,\n )\n }\n }\n // Fail-loud quote verification: a quote must occur in the document the\n // entry names. Rejected BEFORE the draft is resolved, so a batch with a\n // fabricated quote persists nothing — the model corrects and re-sends\n // rather than leaving a half-written row behind.\n await resolveEvidenceQuotes(config, entries, ctx)\n const draft = await resolveDraft(service, config, scopeKey, ctx)\n const record = await unwrap(() => service.upsertEvidence(draft.id, entries), 'evidence_rejected')\n return {\n workProductId: record.id,\n version: record.version,\n evidenceCount: record.evidence.length,\n // Echo what was actually STORED for the entries in this call. A span\n // citation's quote is produced here, not sent here, so the model must\n // be able to see the text its offsets selected — that is how it\n // notices an off-by-a-line span without a second read.\n entries: entries.map((entry) => ({\n id: entry.id,\n target: entry.target,\n ...(entry.locator.quote === undefined ? {} : { quote: entry.locator.quote }),\n ...(entry.locator.quoteBasis === undefined ? {} : { quoteBasis: entry.locator.quoteBasis }),\n })),\n }\n },\n })\n\n const flagException = defineAppTool({\n name: 'flag_exception',\n description:\n 'Flag problems with the current work product (missing documents, inconsistent sources, …). An unresolved blocking exception parks the work product until it is resolved; re-emit the same id with resolved:true to release it. Address by scopeKey.',\n parameters: {\n type: 'object',\n properties: {\n scopeKey: { type: 'string', description: 'Engagement key for this work product.' },\n exceptions: {\n type: 'array',\n minItems: 1,\n maxItems: MAX_WORK_PRODUCT_BATCH,\n items: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Stable entry id — re-emit to replace.' },\n severity: { type: 'string', enum: ['blocking', 'material', 'advisory'] },\n kind: { type: 'string', description: 'Exception kind from the product vocabulary.' },\n message: { type: 'string' },\n targets: { type: 'array', items: { type: 'string' } },\n resolved: { type: 'boolean' },\n resolutionNote: { type: 'string' },\n },\n required: ['id', 'severity', 'kind', 'message'],\n },\n },\n },\n required: ['scopeKey', 'exceptions'],\n },\n async execute(args: Record<string, unknown>, ctx: AppToolContext) {\n const scopeKey = requireScopeKey(args)\n const raw = requireBatch(args, 'exceptions')\n const entries: ExceptionEntry[] = []\n for (let index = 0; index < raw.length; index += 1) {\n const parsed = parseExceptionInput(raw[index], `exceptions[${index}]`)\n if (!parsed.ok) throw new ToolInputError('invalid_exception', `${parsed.field}: ${parsed.error}`)\n if (!config.exceptionKinds.includes(parsed.value.kind)) {\n throw new ToolInputError(\n 'invalid_exception',\n `exceptions[${index}].kind: must be one of: ${config.exceptionKinds.join(', ')}`,\n )\n }\n // An agent resolving its own exception is tagged as such; the reviewer\n // tag is reserved for the verdict route.\n if (parsed.value.resolved && parsed.value.resolvedBy === undefined) parsed.value.resolvedBy = 'agent'\n entries.push(parsed.value)\n }\n const draft = await resolveDraft(service, config, scopeKey, ctx)\n const record = await unwrap(() => service.upsertExceptions(draft.id, entries), 'exception_rejected')\n return {\n workProductId: record.id,\n version: record.version,\n status: record.status,\n unresolvedBlocking: unresolvedBlockingExceptions(record.exceptions).length,\n }\n },\n })\n\n const submitWorkProduct = defineAppTool({\n name: 'submit_work_product',\n description:\n 'Submit the finished work product for professional review — the terminal call after evidence and exceptions are recorded. Refused while a blocking exception is unresolved, or when a material target lacks evidence. Include your own quality checks in `checks`.',\n parameters: {\n type: 'object',\n properties: {\n scopeKey: { type: 'string', description: 'Engagement key for this work product.' },\n artifact: {\n type: 'object',\n properties: {\n kind: { type: 'string', description: 'Artifact kind from the product vocabulary.' },\n title: { type: 'string' },\n path: { type: 'string', description: 'Vault/object-store ref of the rendered document.' },\n content: { type: 'string', description: 'Inline body when small (markdown/JSON).' },\n mediaType: { type: 'string' },\n baseline: {\n type: 'object',\n properties: { path: { type: 'string' }, content: { type: 'string' } },\n description: 'For diff-first artifacts: the source document being redlined.',\n },\n fields: { type: 'object', description: 'Structured field map lineage targets anchor to.' },\n },\n required: ['kind', 'title'],\n },\n checks: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n name: { type: 'string' },\n passed: { type: 'boolean' },\n detail: { type: 'string' },\n },\n required: ['id', 'name', 'passed'],\n },\n description: 'Your own quality self-checks (recorded as agent-sourced).',\n },\n },\n required: ['scopeKey', 'artifact'],\n },\n async execute(args: Record<string, unknown>, ctx: AppToolContext) {\n const scopeKey = requireScopeKey(args)\n const parsedArtifact = parseArtifactInput(args.artifact)\n if (!parsedArtifact.ok) throw new ToolInputError('invalid_artifact', `${parsedArtifact.field}: ${parsedArtifact.error}`)\n const artifact = parsedArtifact.value\n if (!config.artifactKinds.includes(artifact.kind)) {\n throw new ToolInputError('invalid_artifact', `artifact.kind: must be one of: ${config.artifactKinds.join(', ')}`)\n }\n const agentChecks: QualityCheck[] = []\n if (args.checks !== undefined) {\n if (!Array.isArray(args.checks)) throw new ToolInputError('invalid_checks', 'checks must be an array when present.')\n for (let index = 0; index < args.checks.length; index += 1) {\n const parsed = parseAgentCheckInput(args.checks[index], `checks[${index}]`)\n if (!parsed.ok) throw new ToolInputError('invalid_checks', `${parsed.field}: ${parsed.error}`)\n agentChecks.push({ ...parsed.value, source: 'agent' })\n }\n }\n\n const draft = await resolveDraft(service, config, scopeKey, ctx)\n const blocking = unresolvedBlockingExceptions(draft.exceptions)\n if (blocking.length > 0) {\n throw new ToolInputError(\n 'blocking_exceptions_unresolved',\n `Cannot submit: ${blocking.length} unresolved blocking exception(s) (${blocking.map((entry) => entry.id).join(', ')}). Resolve each via flag_exception with resolved:true, or downgrade its severity with a resolutionNote justifying why it does not block.`,\n 409,\n )\n }\n\n // Platform check one: every persisted quote still occurs in the source\n // it names. The upsert gate stops NEW fabrication; this stops a package\n // whose evidence predates the gate from reaching a reviewer. Recorded on\n // the row AND rejected fail-loud — the same discipline as coverage.\n const checks: QualityCheck[] = [...agentChecks]\n const quotes = await summarizeQuoteVerification(config, draft.evidence, ctx)\n if (quotes) {\n const quoted = quotes.verified + quotes.failed.length\n checks.unshift({\n id: QUOTE_VERIFICATION_CHECK,\n name: QUOTE_VERIFICATION_CHECK,\n passed: quotes.failed.length === 0,\n detail:\n quotes.failed.length === 0\n ? `${quotes.verified}/${quoted} quoted evidence entries verified against their source (${quotes.spanAnchored} platform-sliced from a source span, ${quotes.verified - quotes.spanAnchored} model-quoted and proved to occur); ${quotes.withoutQuote} recorded without a quote`\n : `Unverifiable quotes on: ${quotes.failed.join(', ')}`,\n source: 'platform',\n })\n if (quotes.failed.length > 0) {\n await unwrap(() => service.recordChecks(draft.id, checks), 'checks_rejected')\n throw new ToolInputError(\n 'quote_verification_failed',\n `Cannot submit: ${quotes.failed.length} evidence entr${quotes.failed.length === 1 ? 'y quotes' : 'ies quote'} text that does not occur in the source named (${quotes.failed.join(', ')}). Re-emit each with a quote copied character-for-character from that document, or without locator.quote if the value is computed.`,\n )\n }\n }\n\n // Platform check two: every material target has ≥1 evidence row. A\n // failing coverage check is BOTH recorded on the row (visible to the\n // queue) and rejected fail-loud — a lineage-free package can never reach\n // a reviewer.\n if (config.materialTargets) {\n const targets = config.materialTargets(artifact)\n const covered = new Set(draft.evidence.map((entry) => entry.target))\n const missing = targets.filter((target) => !covered.has(target))\n // How each covered target is BACKED, not merely that a row exists.\n // A coverage count that cannot distinguish a platform-sliced citation\n // from a bare assertion is the number that let 13 invented quotes read\n // as \"13/13 evidenced\" on the row a reviewer then opened.\n const anchoredTargets = new Set(\n draft.evidence.filter((entry) => entry.locator.quoteBasis !== undefined).map((entry) => entry.target),\n )\n const spanTargets = new Set(\n draft.evidence.filter((entry) => entry.locator.quoteBasis === 'span').map((entry) => entry.target),\n )\n const present = targets.filter((target) => covered.has(target))\n const unanchored = present.filter((target) => !anchoredTargets.has(target))\n const spanCount = present.filter((target) => spanTargets.has(target)).length\n const breakdown = `${spanCount} span-anchored, ${present.length - spanCount - unanchored.length} quote-verified, ${unanchored.length} claim-only`\n const coverage: QualityCheck = {\n id: EVIDENCE_COVERAGE_CHECK,\n name: EVIDENCE_COVERAGE_CHECK,\n passed: missing.length === 0 && !(config.requireAnchoredEvidence && unanchored.length > 0),\n detail:\n missing.length > 0\n ? `Missing evidence for: ${missing.join(', ')}`\n : config.requireAnchoredEvidence && unanchored.length > 0\n ? `No source anchor for: ${unanchored.join(', ')}`\n : `${targets.length}/${targets.length} material targets evidenced (${breakdown})`,\n source: 'platform',\n }\n checks.unshift(coverage)\n if (missing.length > 0) {\n await unwrap(() => service.recordChecks(draft.id, checks), 'checks_rejected')\n throw new ToolInputError(\n 'evidence_coverage_failed',\n `Cannot submit: material targets lack evidence: ${missing.join(', ')}. Add upsert_evidence entries targeting each, then resubmit.`,\n )\n }\n if (config.requireAnchoredEvidence && unanchored.length > 0) {\n await unwrap(() => service.recordChecks(draft.id, checks), 'checks_rejected')\n throw new ToolInputError(\n 'evidence_not_anchored',\n `Cannot submit: these material targets have an evidence row but no source anchor: ${unanchored.join(', ')}. Re-emit each with locator.find — the value exactly as it appears in the document. The platform locates it and writes the quote itself, so you never retype source text or count characters.`,\n )\n }\n }\n\n const provenance = stampProvenance(config.provenance(ctx), config.now)\n const record = await unwrap(\n () => service.submit(draft.id, { artifact, checks, provenance }),\n 'submit_rejected',\n )\n await config.onReady?.(record, ctx)\n return {\n workProductId: record.id,\n version: record.version,\n status: record.status,\n checks: record.checks.map((check) => ({ name: check.name, passed: check.passed, source: check.source })),\n }\n },\n })\n\n return [upsertEvidence, flagException, submitWorkProduct]\n}\n","/**\n * Framework-neutral work-product review endpoints — the\n * `createInteractionAnswerRoute` factory pattern: web-standard\n * `Request`/`Response`, ONE product-supplied `authorize` seam (session auth,\n * workspace access, reviewer identity, rate limits), everything behind it\n * mechanism. Server-only and subpath-only: never reachable from a client\n * bundle (enforced by the browser-safety test).\n *\n * The verdict endpoint is deliberately NOT a second approval broker: agent\n * asks stay on `/interactions`; this is the REVIEWER's plain authorized\n * verdict on a ready package — one human-in-the-loop channel per direction.\n * A `request_changes` note re-enters chat as the correction turn via the\n * `onVerdict` seam; chat remains the driver surface.\n */\n\nimport {\n createWorkProductService,\n type WorkProductService,\n} from './service'\nimport {\n workProductToPersistedPart,\n type WorkProductPersistedPart,\n type WorkProductRecord,\n type WorkProductStorePort,\n} from './types'\n\n/** Reviewer verdict wire body for the POST endpoint. */\nexport type WorkProductVerdictBody =\n | { ok: true; id: string; verdict: 'approve'; note?: string }\n | { ok: true; id: string; verdict: 'request_changes'; note: string }\n | { ok: false; error: string }\n\n/** Validate the verdict POST body: `{ id, verdict, note? }`; a\n * `request_changes` verdict REQUIRES a non-empty note — the note IS the\n * correction turn the agent works from. */\nexport function validateWorkProductVerdictBody(body: Record<string, unknown>): WorkProductVerdictBody {\n const id = typeof body.id === 'string' && body.id.trim() ? body.id.trim() : null\n if (!id) return { ok: false, error: 'Missing work product id' }\n const verdict = body.verdict\n if (verdict !== 'approve' && verdict !== 'request_changes') {\n return { ok: false, error: 'Invalid verdict: expected approve or request_changes' }\n }\n const note = body.note === undefined ? undefined : typeof body.note === 'string' ? body.note.trim() : null\n if (note === null) return { ok: false, error: 'Invalid note: expected a string' }\n if (verdict === 'request_changes') {\n if (!note) return { ok: false, error: 'request_changes requires a note — it becomes the correction instruction in chat' }\n return { ok: true, id, verdict, note }\n }\n return note ? { ok: true, id, verdict, note } : { ok: true, id, verdict }\n}\n\n/** The product seam's verdict for one request: authenticated reviewer +\n * workspace, or a product-authored short-circuit Response (401/403/429…). */\nexport type WorkProductRouteAuthorization =\n | { ok: true; workspaceId: string; reviewedBy: string }\n | { ok: false; response: Response }\n\n/** Auth seam arguments carrying the request, the endpoint intent, and the parsed verdict body */\nexport interface WorkProductAuthorizeArgs {\n request: Request\n intent: 'list' | 'detail' | 'verdict'\n /** The parsed, validated POST body (verdict intent only). */\n body?: Record<string, unknown>\n}\n\n/** Configuration options assembling the review endpoints from the store and product seams */\nexport interface WorkProductRoutesOptions {\n store: WorkProductStorePort\n /** Authenticate + authorize the caller; the ONLY product access step. */\n authorize: (args: WorkProductAuthorizeArgs) => Promise<WorkProductRouteAuthorization>\n /** Post-verdict product seam: post the `request_changes` note into the\n * driving chat thread as the correction turn, notify, etc. Runs after the\n * transition commits; a throw is logged, never unwinds the verdict. */\n onVerdict?: (args: {\n record: WorkProductRecord\n verdict: 'approve' | 'request_changes'\n note?: string\n reviewedBy: string\n }) => void | Promise<void>\n /** Persist/update the transcript anchor part reflecting the new status, so\n * the chat card flips with the verdict. */\n persistAnchorPart?: (part: WorkProductPersistedPart, record: WorkProductRecord) => void | Promise<void>\n /** Future integration point fired on approval (push to a DMS/CRM/export\n * pipeline). A stub seam by design — export itself stays the product's\n * signed object-store download. */\n onExport?: (record: WorkProductRecord) => void | Promise<void>\n logger?: Pick<Console, 'warn' | 'error'>\n now?: () => number\n}\n\n/** Assembled review endpoints returning web-standard Responses */\nexport interface WorkProductRoutes {\n /** GET — the workspace's records for the queue projection. Optional\n * `?status=a,b` filter. */\n list: (request: Request) => Promise<Response>\n /** GET — one record by id (404 when absent or outside the workspace). */\n detail: (request: Request, id: string) => Promise<Response>\n /** POST `{ id, verdict, note? }` — the reviewer verdict: CAS transition +\n * history entry + product seams. 409 when the record is no longer ready. */\n verdict: (request: Request) => Promise<Response>\n}\n\n/** Create the work-product review endpoints over the store port and product seams */\nexport function createWorkProductRoutes(options: WorkProductRoutesOptions): WorkProductRoutes {\n const logger = options.logger ?? console\n const service: WorkProductService = createWorkProductService({\n store: options.store,\n ...(options.now ? { now: options.now } : {}),\n })\n\n async function list(request: Request): Promise<Response> {\n const auth = await options.authorize({ request, intent: 'list' })\n if (!auth.ok) return auth.response\n const url = new URL(request.url)\n const statusParam = url.searchParams.get('status')\n const statuses = statusParam\n ? statusParam.split(',').map((value) => value.trim()).filter(Boolean)\n : null\n const workProducts = await options.store.listByWorkspace(\n auth.workspaceId,\n statuses ? { status: statuses as WorkProductRecord['status'][] } : undefined,\n )\n return Response.json({ workProducts })\n }\n\n async function detail(request: Request, id: string): Promise<Response> {\n const auth = await options.authorize({ request, intent: 'detail' })\n if (!auth.ok) return auth.response\n const record = await options.store.load(id)\n if (!record || record.workspaceId !== auth.workspaceId) {\n return Response.json({ error: 'Work product not found' }, { status: 404 })\n }\n return Response.json({ workProduct: record })\n }\n\n async function verdict(request: Request): Promise<Response> {\n const body = (await request.json().catch(() => null)) as Record<string, unknown> | null\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const validation = validateWorkProductVerdictBody(body)\n if (!validation.ok) return Response.json({ error: validation.error }, { status: 400 })\n\n const auth = await options.authorize({ request, intent: 'verdict', body })\n if (!auth.ok) return auth.response\n\n const existing = await options.store.load(validation.id)\n if (!existing || existing.workspaceId !== auth.workspaceId) {\n return Response.json({ error: 'Work product not found' }, { status: 404 })\n }\n\n const outcome = await service.applyVerdict(validation.id, {\n verdict: validation.verdict,\n reviewedBy: auth.reviewedBy,\n ...(validation.note === undefined ? {} : { note: validation.note }),\n })\n if (!outcome.succeeded) {\n // A lost race and an illegal edge both mean \"the row is no longer the\n // ready version you looked at\" — 409 so the client re-reads.\n return Response.json({ code: 'VERDICT_CONFLICT', error: outcome.error }, { status: 409 })\n }\n const record = outcome.value\n\n // Product seams run AFTER the committed transition; their failures are\n // logged, never unwound — the verdict is durable truth at this point.\n try {\n await options.persistAnchorPart?.(workProductToPersistedPart(record), record)\n } catch (error) {\n logger.error('[work-product] persistAnchorPart failed:', error)\n }\n try {\n await options.onVerdict?.({\n record,\n verdict: validation.verdict,\n ...(validation.note === undefined ? {} : { note: validation.note }),\n reviewedBy: auth.reviewedBy,\n })\n } catch (error) {\n logger.error('[work-product] onVerdict failed:', error)\n }\n if (validation.verdict === 'approve') {\n try {\n await options.onExport?.(record)\n } catch (error) {\n logger.error('[work-product] onExport failed:', error)\n }\n }\n\n return Response.json({ ok: true, workProduct: record })\n }\n\n return { list, detail, verdict }\n}\n"],"mappings":"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","names":["record","entry","bound","text"]}
|
|
1
|
+
{"version":3,"sources":["../../src/work-product/service.ts","../../src/work-product/provenance.ts","../../src/work-product/claim-support.ts","../../src/work-product/quote.ts","../../src/work-product/tools.ts","../../src/work-product/route.ts"],"sourcesContent":["/**\n * The guarded work-product status machine — the `/missions` service PATTERN\n * (load → validate against a transition table → CAS-write guarded on what was\n * read → audit event) over {@link WorkProductStorePort}. Deliberately NOT a\n * reuse of the mission service: missions' cursor/plan/budget machinery does\n * not apply here, and the six-status review machine is a different contract.\n *\n * Concurrency contract: a scope's draft is driven by a single serialized turn\n * owner, so real contention is rare. The service is the typed guard layer,\n * not a serializer — every mutation re-reads the record and CAS-writes\n * guarded on the `{status, version}` it read. A guard miss surfaces as\n * `{ succeeded: false, conflict: true }` (retryable: re-read and re-apply),\n * never a silent clobber.\n */\n\nimport {\n unresolvedBlockingExceptions,\n type EvidenceEntry,\n type ExceptionEntry,\n type QualityCheck,\n type WorkProductArtifact,\n type WorkProductAuditEvent,\n type WorkProductPatch,\n type WorkProductProvenance,\n type WorkProductRecord,\n type WorkProductStatus,\n type WorkProductStorePort,\n type WorkProductVersionEntry,\n} from './types'\n\n/** Discriminated outcome for guarded operations — `conflict` distinguishes a\n * lost guarded race (retryable) from a logic rejection (illegal edge,\n * missing row — deterministic, never retried). */\nexport type WorkProductOutcome<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; error: string; conflict: boolean }\n\n// Legal status transitions. A target absent from a source's set is rejected\n// by the guarded helpers. `superseded` is terminal.\nconst WORK_PRODUCT_TRANSITIONS: Record<WorkProductStatus, ReadonlySet<WorkProductStatus>> = {\n draft: new Set<WorkProductStatus>(['blocked', 'ready']),\n blocked: new Set<WorkProductStatus>(['draft']),\n ready: new Set<WorkProductStatus>(['changes_requested', 'approved', 'superseded']),\n changes_requested: new Set<WorkProductStatus>(['draft', 'superseded']),\n approved: new Set<WorkProductStatus>(['superseded']),\n superseded: new Set<WorkProductStatus>(),\n}\n\n/** Whether a legal edge exists from `from` to `to` in the review machine. */\nexport function canTransitionWorkProduct(from: WorkProductStatus, to: WorkProductStatus): boolean {\n return WORK_PRODUCT_TRANSITIONS[from].has(to)\n}\n\n/** Statuses a work product can never leave. */\nexport function isWorkProductTerminal(status: WorkProductStatus): boolean {\n return WORK_PRODUCT_TRANSITIONS[status].size === 0\n}\n\n/** Define the input required to create a new draft work product row */\nexport interface CreateWorkProductInput {\n /** Explicit row id — omit to use the service's generator. The MODEL never\n * supplies ids; this is for deterministic server-side creation. */\n id?: string\n workspaceId: string\n threadId: string | null\n scopeKey: string\n /** Version this draft will become; default 1. The tool layer passes\n * `last reviewed version + 1` when a scope is re-engaged after approval. */\n version?: number\n provenance: WorkProductProvenance\n /** Opaque product-column values handed VERBATIM to the store's insert. */\n extras?: Record<string, unknown>\n}\n\n/** Payload for the draft→ready submit transition */\nexport interface SubmitWorkProductInput {\n artifact: WorkProductArtifact\n checks: QualityCheck[]\n provenance: WorkProductProvenance\n /** Frozen snapshot ref of this version's body for the history entry;\n * defaults to `artifact.path`. */\n artifactPath?: string\n}\n\n/** Reviewer verdict payload for the ready→approved / ready→changes_requested transition */\nexport interface WorkProductVerdictInput {\n verdict: 'approve' | 'request_changes'\n reviewedBy: string\n note?: string\n}\n\n/** Guarded mutation surface over the work-product store */\nexport interface WorkProductService {\n create(input: CreateWorkProductInput): Promise<WorkProductRecord>\n get(id: string): Promise<WorkProductRecord | null>\n /** The scope's open accumulator row (`draft`/`blocked`), or null. */\n openDraft(workspaceId: string, scopeKey: string): Promise<WorkProductRecord | null>\n /** The scope's `changes_requested` row awaiting its correction turn, or null. */\n awaitingCorrection(workspaceId: string, scopeKey: string): Promise<WorkProductRecord | null>\n /** The scope's `ready` row awaiting review, or null. */\n awaitingReview(workspaceId: string, scopeKey: string): Promise<WorkProductRecord | null>\n /** `max(version over the scope's approved/superseded rows) + 1` — the\n * version a fresh draft for the scope should carry. */\n nextVersion(workspaceId: string, scopeKey: string): Promise<number>\n /** Re-open a `changes_requested` row as the next draft: version bumps +1\n * and the correction turn accumulates into the same scope row. */\n reopen(id: string): Promise<WorkProductOutcome<WorkProductRecord>>\n /** Merge evidence entries by id (same id replaces — the agent can correct\n * itself mid-turn). Legal only while `draft`/`blocked`. */\n upsertEvidence(id: string, entries: readonly EvidenceEntry[]): Promise<WorkProductOutcome<WorkProductRecord>>\n /** Merge exception entries by id, then reconcile the blocked flag: any\n * unresolved blocking entry parks `draft`→`blocked`; resolving the last\n * one releases `blocked`→`draft`. */\n upsertExceptions(id: string, entries: readonly ExceptionEntry[]): Promise<WorkProductOutcome<WorkProductRecord>>\n /** Persist a checks array without transitioning — how a failed platform\n * gate (e.g. evidence_coverage) stays visible on the still-draft row. */\n recordChecks(id: string, checks: readonly QualityCheck[]): Promise<WorkProductOutcome<WorkProductRecord>>\n /** The terminal agent call: CAS `draft`→`ready` with artifact + checks +\n * provenance, appending the version-history entry. Refuses while an\n * unresolved blocking exception exists. */\n submit(id: string, input: SubmitWorkProductInput): Promise<WorkProductOutcome<WorkProductRecord>>\n /** Reviewer verdict: CAS `ready`→`approved`/`changes_requested` + history\n * entry. Approval also supersedes the scope's prior approved versions so\n * exactly one approved version is current per scope. */\n applyVerdict(id: string, input: WorkProductVerdictInput): Promise<WorkProductOutcome<WorkProductRecord>>\n /** Explicit replacement: CAS to `superseded` (legal from ready /\n * changes_requested / approved). */\n supersede(id: string): Promise<WorkProductOutcome<WorkProductRecord>>\n}\n\n/** Configuration options for creating a work product service */\nexport interface WorkProductServiceOptions {\n store: WorkProductStorePort\n /** Injectable clock (epoch ms). Default `Date.now`. */\n now?: () => number\n /** Row-id generator. Default `crypto.randomUUID()`. */\n generateId?: () => string\n}\n\nfunction rejected<T>(error: string): WorkProductOutcome<T> {\n return { succeeded: false, error, conflict: false }\n}\n\nfunction lostRace<T>(id: string): WorkProductOutcome<T> {\n return { succeeded: false, error: `Work product ${id} changed concurrently`, conflict: true }\n}\n\n/** Merge-by-id upsert: existing order preserved, replaced in place, new\n * entries appended in call order. */\nfunction mergeById<T extends { id: string }>(existing: readonly T[], incoming: readonly T[]): T[] {\n const merged = existing.slice()\n const indexById = new Map(merged.map((entry, index) => [entry.id, index] as const))\n for (const entry of incoming) {\n const at = indexById.get(entry.id)\n if (at === undefined) {\n indexById.set(entry.id, merged.length)\n merged.push(entry)\n } else {\n merged[at] = entry\n }\n }\n return merged\n}\n\n/** Create the guarded work-product service over a store port */\nexport function createWorkProductService(options: WorkProductServiceOptions): WorkProductService {\n const { store } = options\n const now = options.now ?? (() => Date.now())\n const generateId = options.generateId ?? (() => crypto.randomUUID())\n\n async function appendEvent(\n record: WorkProductRecord,\n step: string,\n message: string,\n metadata: Record<string, unknown> = {},\n ): Promise<void> {\n await store.appendEvent({\n workProductId: record.id,\n workspaceId: record.workspaceId,\n step,\n message,\n metadata,\n at: now(),\n })\n }\n\n // Guarded status transition: load → validate the edge → CAS guarded on the\n // {status, version} read → audit event. The loser of a racing transition\n // gets a conflict instead of silently violating the machine.\n async function transition(\n id: string,\n to: WorkProductStatus,\n patch: Omit<WorkProductPatch, 'status'> = {},\n eventMeta: Record<string, unknown> = {},\n ): Promise<WorkProductOutcome<WorkProductRecord>> {\n const record = await store.load(id)\n if (!record) return rejected(`Work product ${id} not found`)\n const from = record.status\n if (isWorkProductTerminal(from)) {\n return rejected(`Work product ${id} is terminal (${from}); cannot transition to ${to}`)\n }\n if (!canTransitionWorkProduct(from, to)) {\n return rejected(`Illegal work-product transition ${from} -> ${to} for ${id}`)\n }\n const updated = await store.update(\n id,\n { status: from, version: record.version },\n { status: to, updatedAt: now(), ...patch },\n )\n if (!updated) return lostRace(id)\n await appendEvent(updated, `wp.${to}`, `Work product ${from} -> ${to}`, { from, to, ...eventMeta })\n return { succeeded: true, value: updated }\n }\n\n // Merge-style guarded write with no status change (evidence / exceptions /\n // checks accumulate on the open row).\n async function guardedMerge(\n id: string,\n legalStatuses: readonly WorkProductStatus[],\n build: (record: WorkProductRecord) => Omit<WorkProductPatch, 'status' | 'version'>,\n event: { step: string; message: (record: WorkProductRecord) => string; metadata?: (record: WorkProductRecord) => Record<string, unknown> },\n ): Promise<WorkProductOutcome<WorkProductRecord>> {\n const record = await store.load(id)\n if (!record) return rejected(`Work product ${id} not found`)\n if (!legalStatuses.includes(record.status)) {\n return rejected(`Work product ${id} is ${record.status}; expected ${legalStatuses.join('/')}`)\n }\n const updated = await store.update(\n id,\n { status: record.status, version: record.version },\n { updatedAt: now(), ...build(record) },\n )\n if (!updated) return lostRace(id)\n await appendEvent(updated, event.step, event.message(updated), event.metadata?.(updated) ?? {})\n return { succeeded: true, value: updated }\n }\n\n const create: WorkProductService['create'] = async (input) => {\n const at = now()\n const record = await store.insert(\n {\n id: input.id ?? generateId(),\n workspaceId: input.workspaceId,\n threadId: input.threadId,\n scopeKey: input.scopeKey,\n status: 'draft',\n version: input.version ?? 1,\n artifact: null,\n evidence: [],\n exceptions: [],\n checks: [],\n provenance: input.provenance,\n history: [],\n createdAt: at,\n updatedAt: at,\n },\n input.extras,\n )\n await appendEvent(record, 'wp.created', `Work product draft v${record.version} created for ${record.scopeKey}`, {\n scopeKey: record.scopeKey,\n version: record.version,\n threadId: record.threadId,\n })\n return record\n }\n\n async function findByScopeAndStatus(\n workspaceId: string,\n scopeKey: string,\n status: WorkProductStatus,\n ): Promise<WorkProductRecord | null> {\n const rows = await store.listByWorkspace(workspaceId, { status: [status] })\n return rows.find((row) => row.scopeKey === scopeKey) ?? null\n }\n\n const nextVersion: WorkProductService['nextVersion'] = async (workspaceId, scopeKey) => {\n const reviewed = await store.listByWorkspace(workspaceId, { status: ['approved', 'superseded'] })\n const versions = reviewed.filter((row) => row.scopeKey === scopeKey).map((row) => row.version)\n return versions.length === 0 ? 1 : Math.max(...versions) + 1\n }\n\n const reopen: WorkProductService['reopen'] = async (id) => {\n const record = await store.load(id)\n if (!record) return rejected(`Work product ${id} not found`)\n if (record.status !== 'changes_requested') {\n return rejected(`Work product ${id} is ${record.status}; only changes_requested reopens`)\n }\n const updated = await store.update(\n id,\n { status: 'changes_requested', version: record.version },\n { status: 'draft', version: record.version + 1, updatedAt: now() },\n )\n if (!updated) return lostRace(id)\n await appendEvent(updated, 'wp.reopened', `Correction draft v${updated.version} opened`, {\n from: record.version,\n to: updated.version,\n })\n return { succeeded: true, value: updated }\n }\n\n const upsertEvidence: WorkProductService['upsertEvidence'] = (id, entries) =>\n guardedMerge(\n id,\n ['draft', 'blocked'],\n (record) => ({ evidence: mergeById(record.evidence, entries) }),\n {\n step: 'wp.evidence',\n message: (record) => `Evidence upserted (${entries.length} entries, ${record.evidence.length} total)`,\n metadata: () => ({ upserted: entries.map((entry) => entry.id) }),\n },\n )\n\n const upsertExceptions: WorkProductService['upsertExceptions'] = async (id, entries) => {\n const merged = await guardedMerge(\n id,\n ['draft', 'blocked'],\n (record) => ({ exceptions: mergeById(record.exceptions, entries) }),\n {\n step: 'wp.exception',\n message: (record) =>\n `Exceptions upserted (${entries.length} entries, ${unresolvedBlockingExceptions(record.exceptions).length} blocking unresolved)`,\n metadata: () => ({ upserted: entries.map((entry) => entry.id) }),\n },\n )\n if (!merged.succeeded) return merged\n // Reconcile the blocked flag AFTER the merge commits: an unresolved\n // blocking entry parks the draft; resolving the last one releases it.\n const record = merged.value\n const blocking = unresolvedBlockingExceptions(record.exceptions).length\n if (record.status === 'draft' && blocking > 0) {\n return transition(id, 'blocked', {}, { blocking })\n }\n if (record.status === 'blocked' && blocking === 0) {\n return transition(id, 'draft', {}, { blocking })\n }\n return merged\n }\n\n const recordChecks: WorkProductService['recordChecks'] = (id, checks) =>\n guardedMerge(\n id,\n ['draft', 'blocked'],\n () => ({ checks: checks.slice() }),\n {\n step: 'wp.checks',\n message: () => `Checks recorded (${checks.length}, ${checks.filter((check) => !check.passed).length} failed)`,\n metadata: () => ({ failed: checks.filter((check) => !check.passed).map((check) => check.name) }),\n },\n )\n\n const submit: WorkProductService['submit'] = async (id, input) => {\n const record = await store.load(id)\n if (!record) return rejected(`Work product ${id} not found`)\n if (record.status !== 'draft') {\n return rejected(`Work product ${id} is ${record.status}; only a draft submits`)\n }\n const blocking = unresolvedBlockingExceptions(record.exceptions)\n if (blocking.length > 0) {\n return rejected(\n `Work product ${id} has ${blocking.length} unresolved blocking exception(s): ${blocking.map((entry) => entry.id).join(', ')}`,\n )\n }\n const artifactPath = input.artifactPath ?? input.artifact.path\n const entry: WorkProductVersionEntry = {\n version: record.version,\n status: 'ready',\n provenance: input.provenance,\n ...(artifactPath === undefined ? {} : { artifactPath }),\n at: now(),\n }\n return transition(\n id,\n 'ready',\n {\n artifact: input.artifact,\n checks: input.checks.slice(),\n provenance: input.provenance,\n history: [...record.history, entry],\n },\n { version: record.version, failedChecks: input.checks.filter((check) => !check.passed).length },\n )\n }\n\n const applyVerdict: WorkProductService['applyVerdict'] = async (id, input) => {\n const record = await store.load(id)\n if (!record) return rejected(`Work product ${id} not found`)\n if (record.status !== 'ready') {\n return rejected(`Work product ${id} is ${record.status}; a verdict applies only to ready`)\n }\n const to: WorkProductStatus = input.verdict === 'approve' ? 'approved' : 'changes_requested'\n const entry: WorkProductVersionEntry = {\n version: record.version,\n status: to,\n provenance: record.provenance,\n ...(record.artifact?.path === undefined ? {} : { artifactPath: record.artifact.path }),\n reviewedBy: input.reviewedBy,\n ...(input.note === undefined ? {} : { reviewNote: input.note }),\n at: now(),\n }\n const outcome = await transition(\n id,\n to,\n { history: [...record.history, entry] },\n { verdict: input.verdict, reviewedBy: input.reviewedBy },\n )\n if (!outcome.succeeded || to !== 'approved') return outcome\n // Exactly one approved version per scope: supersede prior approved rows.\n const priorApproved = await store.listByWorkspace(record.workspaceId, { status: ['approved'] })\n for (const prior of priorApproved) {\n if (prior.id === id || prior.scopeKey !== record.scopeKey) continue\n await transition(prior.id, 'superseded', {}, { supersededBy: id })\n }\n return outcome\n }\n\n return {\n create,\n get: (id) => store.load(id),\n openDraft: (workspaceId, scopeKey) => store.findDraft(workspaceId, scopeKey),\n awaitingCorrection: (workspaceId, scopeKey) => findByScopeAndStatus(workspaceId, scopeKey, 'changes_requested'),\n awaitingReview: (workspaceId, scopeKey) => findByScopeAndStatus(workspaceId, scopeKey, 'ready'),\n nextVersion,\n reopen,\n upsertEvidence,\n upsertExceptions,\n recordChecks,\n submit,\n applyVerdict,\n supersede: (id) => transition(id, 'superseded'),\n }\n}\n\n// ── in-memory store ──────────────────────────────────────────────────────────\n\n/** In-memory store surface with audit trail access and unguarded direct writes for tests */\nexport interface InMemoryWorkProductStore extends WorkProductStorePort {\n /** The full audit trail, append order. */\n events(): WorkProductAuditEvent[]\n /** Unguarded direct write — simulates a concurrent owner in tests. */\n put(record: WorkProductRecord): void\n}\n\n/**\n * In-memory {@link WorkProductStorePort} — the portable backend for tests and\n * reference assemblies. Records are deep-copied on every boundary so callers\n * can never mutate stored state around the guards.\n */\nexport function createInMemoryWorkProductStore(): InMemoryWorkProductStore {\n const rows = new Map<string, WorkProductRecord>()\n const events: WorkProductAuditEvent[] = []\n\n return {\n async load(id) {\n const record = rows.get(id)\n return record ? structuredClone(record) : null\n },\n async findDraft(workspaceId, scopeKey) {\n for (const record of rows.values()) {\n if (\n record.workspaceId === workspaceId &&\n record.scopeKey === scopeKey &&\n (record.status === 'draft' || record.status === 'blocked')\n ) {\n return structuredClone(record)\n }\n }\n return null\n },\n async listByWorkspace(workspaceId, opts) {\n const out: WorkProductRecord[] = []\n for (const record of rows.values()) {\n if (record.workspaceId !== workspaceId) continue\n if (opts?.status && !opts.status.includes(record.status)) continue\n out.push(structuredClone(record))\n }\n return out\n },\n async insert(record) {\n if (rows.has(record.id)) throw new Error(`Work product ${record.id} already exists`)\n rows.set(record.id, structuredClone(record))\n return structuredClone(record)\n },\n async update(id, guard, patch) {\n const current = rows.get(id)\n if (!current) return null\n if (guard.status !== undefined && current.status !== guard.status) return null\n if (guard.version !== undefined && current.version !== guard.version) return null\n const next: WorkProductRecord = { ...current }\n if (patch.status !== undefined) next.status = patch.status\n if (patch.version !== undefined) next.version = patch.version\n if (patch.artifact !== undefined) next.artifact = patch.artifact\n if (patch.evidence !== undefined) next.evidence = patch.evidence\n if (patch.exceptions !== undefined) next.exceptions = patch.exceptions\n if (patch.checks !== undefined) next.checks = patch.checks\n if (patch.provenance !== undefined) next.provenance = patch.provenance\n if (patch.history !== undefined) next.history = patch.history\n if (patch.updatedAt !== undefined) next.updatedAt = patch.updatedAt\n rows.set(id, structuredClone(next))\n return structuredClone(next)\n },\n async appendEvent(event) {\n events.push(structuredClone(event))\n },\n events() {\n return events.map((event) => structuredClone(event))\n },\n put(record) {\n rows.set(record.id, structuredClone(record))\n },\n }\n}\n","/**\n * Provenance stamping — the backtest spine. Every version of every work\n * product carries `profileHash + runId + servingModels`, stamped at the two\n * honest moments:\n *\n * 1. At dispatch, the product route composes the turn's profile and computes\n * agent-eval's `agentProfileHash(profile)` — the SAME hash that keys the\n * app's scorecard cells — and closes it (plus runId/sessionId) into the\n * `provenance` seam `buildWorkProductTools` receives, so the model can\n * neither omit nor forge it.\n * 2. At turn completion, the route's existing lifecycle seam calls\n * {@link finalizeWorkProductProvenance}: serving model and cost come from\n * the usage receipt, because only the completed turn knows what actually\n * served. `servingModels` is honestly EMPTY until then.\n *\n * The trust bridge ({@link workProductTrustInputs}) maps judge-sourced\n * quality verdicts into `/eval-campaign`'s `trustVerdicts()` input with\n * type-only imports — products call `trustVerdicts` from\n * `@tangle-network/agent-app/eval-campaign` (which they already import for\n * their ensemble loop). Deliberately NOT a value re-export here: it would put\n * agent-eval's runtime on `/work-product`'s import path and drag the eval\n * engine into every product worker bundle.\n */\n\nimport type { JudgeVerdict } from '@tangle-network/agent-eval'\nimport type { TrustItem } from '../eval-campaign/trust-gate'\nimport type {\n WorkProductProvenance,\n WorkProductRecord,\n WorkProductStorePort,\n} from './types'\n\nexport type { TrustItem }\n\n/** The dispatch-time provenance closure's output: everything the route knows\n * before the turn completes. */\nexport type WorkProductProvenanceBase = Omit<WorkProductProvenance, 'servingModels' | 'producedAt'>\n\n/** Stamp a full provenance from the dispatch-time base. `servingModels` starts\n * empty (honestly absent — never guessed) until the completion back-fill. */\nexport function stampProvenance(base: WorkProductProvenanceBase, now: () => number = Date.now): WorkProductProvenance {\n return { ...base, servingModels: [], producedAt: now() }\n}\n\n/** Completion receipt for one run, from the turn's usage/lifecycle seam. */\nexport interface FinalizeWorkProductProvenanceInput {\n workspaceId: string\n /** The chat turnId / mission-step run id the records were stamped with. */\n runId: string\n /** What actually served, from the usage receipt / serving-model header. */\n servingModels: readonly string[]\n costUsd?: number\n logger?: Pick<Console, 'warn'>\n}\n\n/**\n * Back-fill `servingModels`/`costUsd` onto every record (and its history\n * entries) stamped with `runId` — wire it into the chat route's existing\n * `lifecycle.onTurnComplete` seam; no new hook. Returns the updated records.\n * A CAS miss on one record is logged and skipped (the next completion or a\n * re-read re-applies); it never throws mid-fleet.\n */\nexport async function finalizeWorkProductProvenance(\n store: WorkProductStorePort,\n input: FinalizeWorkProductProvenanceInput,\n): Promise<WorkProductRecord[]> {\n const rows = await store.listByWorkspace(input.workspaceId)\n const updated: WorkProductRecord[] = []\n for (const record of rows) {\n const recordMatches = record.provenance.runId === input.runId\n const historyMatches = record.history.some((entry) => entry.provenance.runId === input.runId)\n if (!recordMatches && !historyMatches) continue\n const finalize = (provenance: WorkProductProvenance): WorkProductProvenance => ({\n ...provenance,\n servingModels: [...input.servingModels],\n ...(input.costUsd === undefined ? {} : { costUsd: input.costUsd }),\n })\n const next = await store.update(\n record.id,\n { status: record.status, version: record.version },\n {\n ...(recordMatches ? { provenance: finalize(record.provenance) } : {}),\n history: record.history.map((entry) =>\n entry.provenance.runId === input.runId ? { ...entry, provenance: finalize(entry.provenance) } : entry,\n ),\n },\n )\n if (!next) {\n input.logger?.warn(`[work-product] provenance back-fill lost a race on ${record.id}; skipped`)\n continue\n }\n await store.appendEvent({\n workProductId: record.id,\n workspaceId: record.workspaceId,\n step: 'wp.provenance',\n message: `Serving models back-filled for run ${input.runId}`,\n metadata: { runId: input.runId, servingModels: [...input.servingModels], costUsd: input.costUsd ?? null },\n at: Date.now(),\n })\n updated.push(next)\n }\n return updated\n}\n\n/**\n * Trust-gate bridge: one `TrustItem` per work product whose production\n * quality was scored by the product's eval-campaign ensemble. `verdictsFor`\n * returns the per-judge raw verdicts the product retained for a record (the\n * same verdicts `aggregateJudgeVerdicts` reduces); records without verdicts\n * are omitted. Feed the result to `/eval-campaign`'s `trustVerdicts()` —\n * an untrusted verdict renders as \"quality: unverified\", never a naked\n * number. Zero statistics code here: pure mapping.\n */\nexport function workProductTrustInputs<D extends string = string>(\n records: readonly WorkProductRecord[],\n verdictsFor: (record: WorkProductRecord) => readonly JudgeVerdict<D>[] | undefined,\n): TrustItem<D>[] {\n const items: TrustItem<D>[] = []\n for (const record of records) {\n const verdicts = verdictsFor(record)\n if (!verdicts || verdicts.length === 0) continue\n items.push({ itemId: record.id, verdicts })\n }\n return items\n}\n","/**\n * Claim support — does the anchored text actually say what the entry claims?\n *\n * The two gates before this one answer different questions. `sourceContainsQuote`\n * asks whether the quote is really in the document; `findSourceLine` /\n * `sliceSourceSpan` make the platform produce the quote so it cannot be typed\n * wrong. Both are about the TEXT's provenance. Neither one looks at `claim`.\n *\n * Production row `7256ef49` is what that gap costs. Four evidence entries, all\n * `quoteBasis:'span'`, every quote a genuine slice of the document it named —\n * and every one landing on the employer/payer line about 200 characters above\n * the figure:\n *\n * claim 128450.00 -> \"tics LLC EIN 84-2213907\\nEmployee: Dana\"\n * claim 812.44 -> \"nt Savings Bank TIN 22-5510983\\nRecip\"\n * claim 2204.18 -> \"ndex Fund Trust TIN 47-3320115\\nRec\"\n * claim 1955.02 -> \"pient: Dana R. Whitfield\\n------------\"\n *\n * That is strictly worse than a fabricated quote. A fabricated quote fails the\n * verbatim gate; this one passes every gate, is real text from the right\n * document, and reads to a reviewer as an authoritative citation while\n * supporting nothing. `locator.find` (the platform locating a value the model\n * names) prevents the model from ADDRESSING the wrong line, and it is the right\n * primary fix. This module is the independent check underneath it: whatever\n * anchoring form produced the text, the text has to contain the figure.\n *\n * That also settles what a raw `locator.span` is worth. Hand-computed offsets\n * stay accepted, but only when they independently verify — which is exactly\n * \"the slice contains the claimed value\".\n *\n * ── The rule, and why it is drawn here ──────────────────────────────────────\n *\n * Strict on figures, silent on everything else. An unsatisfiable gate does not\n * stop a bad submit, it SELECTS for one — that is the measured mechanism behind\n * the 38 fabricated quotes on row `a68b1943`, where a coverage gate demanded\n * document lineage for computed values and got thirteen invented citations\n * forty seconds later. So the rule fires only where an honest citation can\n * always satisfy it:\n *\n * - The claim IS a value (`\"128450.00\"`, `\"$30,000\"`, `\"3\"`) — the anchored\n * text MUST contain that value. No latitude. This is the shape every tax\n * figure takes and the shape row `7256ef49` failed.\n * - The claim is prose naming figures (`\"indemnity capped at $5,000,000\"`) —\n * at least one of its currency-shaped figures must occur. Not all of them:\n * a claim may legitimately narrate a computation over several lines while\n * anchoring to the one line under discussion, and refusing that would make\n * an honest citation unrepresentable for the sake of a stricter-sounding\n * rule. One is enough to keep the anchor tethered to the claim's subject.\n * - The claim carries no figure at all (`\"Married Filing Jointly\"`,\n * `\"Dana R. Whitfield\"`, `\"2025-04-15\"`) — nothing to check, and the entry\n * passes. A filing status has no number to find, and inventing a\n * word-overlap score here would re-open the hole the verbatim gate closes.\n * - The entry has no anchored text at all — nothing to check. A computed\n * value cites its computation, not a document.\n *\n * A bare year, a form number and a box number are deliberately NOT figures:\n * `2025`, `1040` and `Box 1` have no thousands separator, no cent pair and no\n * currency symbol, so prose mentioning them does not trip the rule.\n *\n * ── Matching is value-wise, never substring ─────────────────────────────────\n *\n * `text.includes(claim)` would be the obvious implementation and it is wrong in\n * the direction that matters: it passes `\"450.00\"` against `\"128,450.00\"`, so a\n * claim citing the wrong figure survives whenever its digits happen to be a tail\n * of a real one. Both sides are tokenized into numbers and compared as VALUES,\n * so `450` and `128450` are simply different.\n */\n\n/** Currency marks stripped before a token is read as a number. */\nconst CURRENCY = /[$€£¥₹]/gu\n\n/**\n * Numbers as they occur in document text. The grouped form is tried first so\n * `128,450.00` is read as one value rather than `128` followed by `450.00` —\n * the whole point of comparing values instead of substrings.\n *\n * A leading `[$€£¥₹]?\\s*` is absorbed so `$ 1,200` tokenizes once, and the\n * dot-leaders typical of a form line (`Box 1 Wages ..... 128,450.00`) are not\n * digits, so they never join a token.\n */\nconst NUMBER_IN_TEXT = /[$€£¥₹]?\\s*\\d{1,3}(?:,\\d{3})+(?:\\.\\d+)?|[$€£¥₹]?\\s*\\d+(?:\\.\\d+)?/gu\n\n/**\n * A figure inside prose: a currency symbol, OR thousands separators, OR a cent\n * pair. Each of the three is a positive signal that the writer meant a\n * quantity rather than an identifier, which is what keeps `2025`, `1040` and\n * `Box 1` out.\n */\nconst FIGURE_IN_PROSE =\n /[$€£¥₹]\\s*[-+]?\\d[\\d,]*(?:\\.\\d+)?|[-+]?\\d{1,3}(?:,\\d{3})+(?:\\.\\d+)?|[-+]?\\d+\\.\\d{2}(?!\\d)/gu\n\n/** The whole string is one value: optional currency, sign, digits with\n * optional grouping and decimals, optional percent. Anchored, so an EIN\n * (`84-2213907`), a date (`2025-04-15`) and a range (`10-20`) are NOT values. */\nconst WHOLE_VALUE = /^[$€£¥₹]?\\s*[-+]?\\s*(?:\\d{1,3}(?:,\\d{3})+|\\d+)(?:\\.\\d+)?\\s*%?$/u\n\n/**\n * Reduce a numeric token to the form both sides are compared in: no currency,\n * no grouping, no trailing zeros in the fraction, no leading zeros.\n *\n * Sign and accounting parentheses are stripped rather than preserved, so\n * `(1,234.00)`, `-1,234.00` and `1,234.00` all reduce to `1234`. A document\n * renders the same deduction all three ways depending on the form and the\n * extractor, and the sign is a property of the TARGET LINE's semantics, not of\n * the document's typography. Comparing magnitudes keeps those honest citations\n * working, and it concedes nothing to fabrication: the digits still have to be\n * the document's digits.\n *\n * Returns `null` for anything that is not a plain number.\n */\nexport function canonicalizeValue(token: string): string | null {\n let text = token.trim()\n if (text.length === 0) return null\n if (/^\\(.*\\)$/u.test(text)) text = text.slice(1, -1).trim()\n text = text.replace(CURRENCY, '').trim()\n text = text.replace(/^[-+]\\s*/u, '').trim()\n text = text.replace(/%$/u, '').trim()\n if (!/^(?:\\d{1,3}(?:,\\d{3})+|\\d+)(?:\\.\\d+)?$/u.test(text)) return null\n text = text.replace(/,/gu, '')\n if (text.includes('.')) text = text.replace(/0+$/u, '').replace(/\\.$/u, '')\n text = text.replace(/^0+(?=\\d)/u, '')\n return text.length === 0 ? null : text\n}\n\n/** Every distinct value appearing in `text`, canonicalized. */\nexport function valuesInText(text: string): string[] {\n const seen = new Set<string>()\n for (const match of text.matchAll(NUMBER_IN_TEXT)) {\n const canonical = canonicalizeValue(match[0])\n if (canonical !== null) seen.add(canonical)\n }\n return [...seen]\n}\n\n/**\n * The values a claim asserts, canonicalized — empty when the claim asserts no\n * figure, which is the \"nothing to check\" case.\n *\n * A claim that is ENTIRELY one value yields that value (strict path). Otherwise\n * only currency-shaped figures inside the prose count, so an assertion that\n * merely mentions a year or a form number yields nothing.\n */\nexport function claimValues(claim: string): string[] {\n const trimmed = claim.trim()\n if (WHOLE_VALUE.test(trimmed)) {\n const whole = canonicalizeValue(trimmed)\n if (whole !== null) return [whole]\n }\n const seen = new Set<string>()\n for (const match of trimmed.matchAll(FIGURE_IN_PROSE)) {\n const canonical = canonicalizeValue(match[0])\n if (canonical !== null) seen.add(canonical)\n }\n return [...seen]\n}\n\nexport type ClaimSupport =\n /** No figure to check: a non-numeric claim, or no anchored text. */\n | { status: 'not_applicable' }\n | { status: 'supported'; matched: string }\n | { status: 'unsupported'; claimed: string[]; present: string[] }\n\n/**\n * Does `quote` carry the figure `claim` asserts?\n *\n * `supported` requires ONE claimed value to occur, which is exact for the\n * single-value claim (there is only one) and deliberate latitude for prose (see\n * the rule note at the top of the file).\n */\nexport function verifyClaimSupport(quote: string, claim: string): ClaimSupport {\n if (quote.trim().length === 0) return { status: 'not_applicable' }\n const claimed = claimValues(claim)\n if (claimed.length === 0) return { status: 'not_applicable' }\n const present = valuesInText(quote)\n const matched = claimed.find((value) => present.includes(value))\n if (matched !== undefined) return { status: 'supported', matched }\n return { status: 'unsupported', claimed, present }\n}\n\n/** Short, quotable rendering of the anchored text for an error message. */\nfunction excerpt(quote: string, limit = 120): string {\n const flat = quote.replace(/\\s+/gu, ' ').trim()\n return flat.length <= limit ? flat : `${flat.slice(0, limit)}…`\n}\n\n/**\n * The sentence a model can act on without re-reading the document: what it\n * claimed, what the line it cited actually says, what figures that line does\n * carry, and the two ways out — cite the value, or drop the locator because the\n * figure was computed. Naming both keeps the gate satisfiable, which is the\n * property that stops it manufacturing the citation it screens for.\n */\nexport function claimSupportErrorDetail(\n failure: Extract<ClaimSupport, { status: 'unsupported' }>,\n quote: string,\n): string {\n const wanted = failure.claimed.length === 1 ? failure.claimed[0]! : `any of ${failure.claimed.join(', ')}`\n const carries =\n failure.present.length === 0\n ? 'that line carries no figure at all'\n : `the only figures on it are ${failure.present.join(', ')}`\n 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.`\n}\n","/**\n * Verbatim quote verification — the lineage gate.\n *\n * An evidence `locator.quote` is the reviewer's click target: it must land on\n * text that is actually in the document the entry names. Nothing here is\n * fuzzy. The only latitude is representational — the same characters written\n * differently by a PDF extractor, a text transcription, or a model repeating\n * what it read: Unicode compatibility forms, curly quotes, the several dash\n * codepoints, non-breaking spaces, and run-length whitespace. A quote that\n * still does not occur after that is not a formatting difference; it is text\n * the document does not contain.\n *\n * Deliberately NOT tolerated, because each one re-opens the hole this closes:\n * case (a quote is a quote), token subsets, edit distance, word-overlap\n * scoring, and \"the numbers match\" heuristics. A fabricated quote and a real\n * one differ by exactly the thing a fuzzy matcher forgives.\n */\n\nimport { canonicalizeValue, valuesInText } from './claim-support'\n\n/** Whitespace folded to a single ASCII space: the Unicode space separators\n * (`\\p{Zs}`), the line/paragraph separators, and the zero-width characters a\n * PDF text layer leaves behind (ZWSP / ZWNJ / ZWJ / BOM). */\nconst WHITESPACE = /[\\s\\p{Zs}\\u2028\\u2029\\u200b-\\u200d\\ufeff]+/gu\n\n/** Dash-like codepoints that render alike but differ by byte: non-breaking\n * hyphen, figure / en / em / horizontal dash, minus sign, small and fullwidth\n * forms. */\nconst DASHES = /[\\u2010-\\u2015\\u2212\\ufe58\\ufe63\\uff0d]/gu\n\n/** Curly, prime and grave apostrophes folded to ASCII `'`. */\nconst SINGLE_QUOTES = /[\\u2018\\u2019\\u201a\\u201b\\u2032\\u2035\\u00b4`]/gu\n\n/** Curly, low, prime and guillemet double quotes folded to ASCII `\"`. */\nconst DOUBLE_QUOTES = /[\\u201c\\u201d\\u201e\\u201f\\u2033\\u2036\\u00ab\\u00bb]/gu\n\n/**\n * Fold a string to the form both sides of a quote comparison are measured in.\n * Composition and presentation only: NFKC, one dash, one apostrophe, one\n * double quote, runs of any whitespace to a single space, trimmed. Case is\n * PRESERVED — lowercasing would let \"Box 1\" match \"box 1\", and a citation\n * that cannot reproduce capitalization did not read the document.\n */\nexport function normalizeQuoteText(value: string): string {\n return value\n .normalize('NFKC')\n .replace(DASHES, '-')\n .replace(SINGLE_QUOTES, \"'\")\n .replace(DOUBLE_QUOTES, '\"')\n .replace(WHITESPACE, ' ')\n .trim()\n}\n\n/**\n * Does `quote` occur in `sourceText`? Exact substring first (the common case,\n * and free); the normalized comparison second, for the representational\n * differences above. Nothing else.\n *\n * An empty or whitespace-only quote is NOT a match — it would otherwise be a\n * substring of every document and pass the gate vacuously.\n */\nexport function sourceContainsQuote(sourceText: string, quote: string): boolean {\n const trimmed = quote.trim()\n if (trimmed.length === 0) return false\n if (sourceText.includes(trimmed)) return true\n const normalizedQuote = normalizeQuoteText(quote)\n if (normalizedQuote.length === 0) return false\n return normalizeQuoteText(sourceText).includes(normalizedQuote)\n}\n\n// ── spans: the citation form that cannot be wrong ────────────────────────────\n\n/**\n * Slice a citation out of the source text by character offset — the reason\n * this module exists in its stronger form.\n *\n * Verification above is a REJECTION gate: the model retypes a quote and the\n * shell refuses it when the characters do not occur. That gate is correct and\n * it works, but a model that reproduces a line character-for-character only\n * some of the time cannot USE it — every miss is a refusal, and the package\n * ends up with no lineage at all rather than false lineage. A measured 9 of 59\n * on the live tax surface is what \"some of the time\" meant in practice.\n *\n * A span inverts it. The model names two integers into text it just read; the\n * PLATFORM produces the quote from the bytes it already holds. There is no\n * retyping step to get wrong, so a fabricated quote is not rejected — it is\n * unrepresentable. `sourceContainsQuote(text, sliceSourceSpan(text, span))` is\n * true for every span this function returns, by construction.\n *\n * Offsets index the SAME string the product's document-reading tool pages\n * with an offset, which is the same string its `readSourceText` seam returns.\n * That is the one contract a product must keep; violate it and spans point at\n * the wrong characters (still real characters of that document — never\n * invented text, but the wrong line).\n *\n * Half-open `[start, end)`, matching `String.prototype.slice` and the\n * `offset`/`offset + text.length` window a paged read already reports.\n */\nexport type SourceSpanFailure =\n | { reason: 'not_integer'; field: 'start' | 'end' }\n | { reason: 'negative'; field: 'start' | 'end' }\n | { reason: 'inverted' }\n | { reason: 'out_of_range'; totalChars: number }\n | { reason: 'blank' }\n\nexport type SourceSpanResult =\n | { ok: true; quote: string }\n | { ok: false; failure: SourceSpanFailure }\n\n/**\n * Locate the line containing `value` and return it as a span — the citation\n * form for a model that cannot count characters.\n *\n * Measured on production (tax session 135b7cc3, gpt-4.1-mini): given the\n * document text and told exactly which line to cite, the model produced\n * offsets that landed on the WRONG line four times out of four, then missed\n * again on a second attempt after being shown the text its offsets had\n * selected. Character arithmetic is not something this model class does.\n *\n * What it DOES do reliably is read the value: all four `claim` fields in that\n * same run were correct to the cent. So the model names the value it read and\n * the PLATFORM finds it. Both failure modes close at once —\n *\n * - the quote cannot be invented, because the platform slices it;\n * - the span cannot be mis-addressed, because the platform computed it from\n * a needle it PROVED occurs in the text.\n *\n * A needle that is not in the document is refused, which is the same fail-loud\n * posture as a quote that does not occur — and correctly so: the model is\n * asserting the document says something it does not.\n *\n * The cited span is the whole LINE, not the needle: \"128,450.00\" alone is not\n * a click target a reviewer can judge, whereas the line it sits on says what\n * the number IS. Number-only needles are common and deliberately supported.\n */\nexport type SourceFindFailure =\n | { reason: 'blank_needle' }\n | { reason: 'not_found' }\n | { reason: 'occurrence_out_of_range'; found: number }\n | { reason: 'not_distinctive'; needle: string; found: number }\n\n/** A needle must be long enough to identify a place in the document. Two\n * characters is not a citation: measured on production work product\n * b9a37e44, nine evidence entries for lines the 1099-DIV does not state were\n * cited with the value \"0\", and the platform faithfully matched the \"0\"\n * inside \"Tax Year 2025\" in the header. Every one re-sliced byte-exactly and\n * every one was worthless as evidence. */\nconst MIN_NEEDLE_LENGTH = 3\n\n/** Above this many hits the needle names no particular place, so citing the\n * first is arbitrary rather than evidential. `findOccurrence` remains the way\n * to cite a genuinely repeated value on purpose. */\nconst MAX_AMBIGUOUS_MATCHES = 8\n\nexport type SourceFindResult =\n | { ok: true; span: { start: number; end: number }; quote: string; occurrences: number }\n | { ok: false; failure: SourceFindFailure }\n\n/** Line bounds containing `index`, trimmed of the newline terminators. */\nfunction lineAround(text: string, index: number): { start: number; end: number } {\n let start = text.lastIndexOf('\\n', index)\n start = start < 0 ? 0 : start + 1\n let end = text.indexOf('\\n', index)\n if (end < 0) end = text.length\n // A CRLF document leaves a trailing \\r inside the line; drop it so the\n // quote is the line a reader sees rather than the line plus a control char.\n if (end > start && text[end - 1] === '\\r') end -= 1\n return { start, end }\n}\n\nexport function findSourceLine(\n sourceText: string,\n needle: string,\n occurrence = 1,\n): SourceFindResult {\n const trimmed = needle.trim()\n if (trimmed.length === 0) return { ok: false, failure: { reason: 'blank_needle' } }\n if (trimmed.length < MIN_NEEDLE_LENGTH) {\n return { ok: false, failure: { reason: 'not_distinctive', needle: trimmed, found: 0 } }\n }\n\n /** Walk the document a line at a time. */\n const eachLine = (visit: (bound: { start: number; end: number }, line: string) => void): void => {\n let cursor = 0\n while (cursor <= sourceText.length) {\n const bound = lineAround(sourceText, cursor)\n visit(bound, sourceText.slice(bound.start, bound.end))\n if (bound.end >= sourceText.length) break\n cursor = bound.end + 1\n }\n }\n\n const positions: number[] = []\n\n // A needle that IS a figure is matched BY VALUE, per line — never as a\n // substring. Substring matching quietly answers the wrong question here: a\n // model citing a genuine \"450.00\" line in a document that also carries\n // \"128,450.00\" gets the WAGE line back, because \"450.00\" is a tail of it and\n // occurs earlier. The citation then fails claim support and the honest\n // anchor is unreachable at occurrence 1, which is the unsatisfiable-gate\n // failure this whole area exists to avoid. Matching values also subsumes the\n // representation cases the old second-chance pass handled — \"128450.00\"\n // finds \"128,450.00\" because both canonicalize to the same number.\n const wantedValue = canonicalizeValue(trimmed)\n if (wantedValue !== null) {\n eachLine((bound, line) => {\n if (valuesInText(line).includes(wantedValue)) positions.push(bound.start)\n })\n } else {\n // A PHRASE needle. Exact hits first (free, and the common case), then the\n // normalized comparison ONLY to locate a position — the returned quote is\n // always sliced from the ORIGINAL string, so representation folding never\n // leaks into a stored citation.\n for (let at = sourceText.indexOf(trimmed); at >= 0; at = sourceText.indexOf(trimmed, at + 1)) {\n positions.push(at)\n }\n if (positions.length === 0) {\n const wanted = normalizeQuoteText(trimmed)\n if (wanted.length > 0) {\n eachLine((bound, line) => {\n if (normalizeQuoteText(line).includes(wanted)) positions.push(bound.start)\n })\n }\n }\n }\n if (positions.length === 0) return { ok: false, failure: { reason: 'not_found' } }\n // An explicit `findOccurrence` is the caller saying \"yes, it repeats, I mean\n // that one\" — so ambiguity is only a failure when they did NOT say which.\n if (occurrence === 1 && positions.length > MAX_AMBIGUOUS_MATCHES) {\n return { ok: false, failure: { reason: 'not_distinctive', needle: trimmed, found: positions.length } }\n }\n if (occurrence < 1 || occurrence > positions.length) {\n return { ok: false, failure: { reason: 'occurrence_out_of_range', found: positions.length } }\n }\n\n const bound = lineAround(sourceText, positions[occurrence - 1]!)\n const quote = sourceText.slice(bound.start, bound.end)\n if (quote.trim().length === 0) return { ok: false, failure: { reason: 'not_found' } }\n return { ok: true, span: bound, quote, occurrences: positions.length }\n}\n\n/** Resolve `[start, end)` against `sourceText`. Every rejection is a caller\n * mistake the model can correct from the paged read it already has, so each\n * carries the discriminator a tool layer turns into a specific message. */\nexport function sliceSourceSpan(\n sourceText: string,\n span: { start: number; end: number },\n): SourceSpanResult {\n for (const field of ['start', 'end'] as const) {\n const value = span[field]\n if (!Number.isInteger(value)) return { ok: false, failure: { reason: 'not_integer', field } }\n if (value < 0) return { ok: false, failure: { reason: 'negative', field } }\n }\n if (span.end <= span.start) return { ok: false, failure: { reason: 'inverted' } }\n if (span.end > sourceText.length) {\n return { ok: false, failure: { reason: 'out_of_range', totalChars: sourceText.length } }\n }\n const quote = sourceText.slice(span.start, span.end)\n // A whitespace-only slice is a real slice of the document and would pass\n // `sourceContainsQuote` on any text — the same vacuous pass an empty quote\n // gets there, refused for the same reason: it is not a click target.\n if (quote.trim().length === 0) return { ok: false, failure: { reason: 'blank' } }\n return { ok: true, quote }\n}\n","/**\n * The agent-facing work-product side channel — three registry `customTools`\n * built on `/tools`' `defineAppTool`, dispatched through `dispatchAppTool`'s\n * single validation/outcome path (a thrown `ToolInputError` → correctable 4xx\n * back to the model; any other throw → internal error; a call never silently\n * succeeds without its effect). Deliberately NOT new `/tools` built-ins:\n * extending `AppToolHandlers` would break every existing consumer, and the\n * registry seam exists precisely for a product tool family like this.\n *\n * Partial-emission contract across a long turn: the draft ROW is the\n * accumulator. Evidence and exceptions stream in as found via small batched\n * calls; the artifact arrives once at the end. The agent addresses everything\n * by `scopeKey` — the server mints row ids, the model never invents them, and\n * identity (userId/workspaceId/threadId) rides the trusted `AppToolContext`\n * from headers, never model args. A turn that dies mid-emission leaves a\n * consistent draft the next turn resumes by the same scopeKey.\n */\n\nimport { ToolInputError } from '../tools/errors'\nimport { defineAppTool, type AppToolDefinition } from '../tools/registry'\nimport type { AppToolContext } from '../tools/types'\nimport { createWorkProductService, type WorkProductOutcome, type WorkProductService } from './service'\nimport { stampProvenance, type WorkProductProvenanceBase } from './provenance'\nimport { findSourceLine, sliceSourceSpan, sourceContainsQuote, type SourceFindFailure, type SourceSpanFailure } from './quote'\nimport { claimSupportErrorDetail, verifyClaimSupport } from './claim-support'\nimport {\n parseAgentCheckInput,\n parseArtifactInput,\n parseEvidenceInput,\n parseExceptionInput,\n unresolvedBlockingExceptions,\n type EvidenceEntry,\n type ExceptionEntry,\n type QualityCheck,\n type WorkProductArtifact,\n type WorkProductRecord,\n type WorkProductStorePort,\n} from './types'\n\n/** Max entries per `upsert_evidence`/`flag_exception` call — keeps each call a\n * small batch the model can correct precisely on a named-index failure. */\nexport const MAX_WORK_PRODUCT_BATCH = 50\n\n/** Platform check: every material target has ≥1 evidence row. Recorded as\n * `QualityCheck{source:'platform'}`. */\nexport const EVIDENCE_COVERAGE_CHECK = 'evidence_coverage'\n\n/** Platform check: how many evidence entries carry a quote the shell PROVED\n * occurs in the source it names. Recorded when `readSourceText` is wired, so\n * a reviewer reads the strength of the lineage off the row itself rather than\n * trusting that a quote was checked. */\nexport const QUOTE_VERIFICATION_CHECK = 'quote_verification'\n\n/** Platform check: how many quoted evidence entries anchor to text that\n * actually CARRIES the figure the entry claims. Distinct from\n * `quote_verification`, which only proves the text came from the document —\n * production row `7256ef49` passed that one on all four entries while\n * supporting none of them. */\nexport const CLAIM_SUPPORT_CHECK = 'claim_support'\n\n/** Domain seams for the three work-product tools — every domain word is a\n * parameter; the shell bakes none. */\nexport interface WorkProductToolConfig {\n store: WorkProductStorePort\n /** PARAMETER — accepted `artifact.kind` values, validated on submit. */\n artifactKinds: readonly string[]\n /** PARAMETER — accepted exception `kind` values. */\n exceptionKinds: readonly string[]\n /** Fail-loud source check: resolve an evidence `sourceRef` to existence\n * (vault stat / attachment lookup). A dangling ref is a `ToolInputError`\n * naming the entry index — lineage can never point at nothing. */\n resolveSourceRef: (ref: string, ctx: AppToolContext) => Promise<boolean>\n /** Fail-loud QUOTE verification: return the source document's TEXT for a\n * ref so the shell can prove each `locator.quote` occurs in it verbatim.\n * Wiring this turns the gate ON — a quote that does not occur is a\n * `ToolInputError` naming the entry index, so the model re-extracts from\n * the document instead of persisting invented lineage.\n *\n * Return `null` ONLY when the ref genuinely has no extractable text (an\n * image scan, an opaque blob). The gate is fail-CLOSED on `null`: a quote\n * that cannot be checked is refused, because \"unverifiable\" and \"verified\"\n * must never look the same to a reviewer. Such an entry is still recordable\n * without `locator.quote` — `claim` carries the assertion.\n *\n * Omit the seam entirely and no quote is checked. */\n readSourceText?: (ref: string, ctx: AppToolContext) => Promise<string | null>\n /** The material targets the platform coverage check requires evidence for.\n * Product-owned vocabulary; omit to skip the coverage gate.\n *\n * Return ONLY targets a source document can actually evidence. A gate that\n * demands document lineage for a value the session COMPUTED is not\n * satisfiable by any honest answer, and an unsatisfiable gate does not stop\n * a submit — it selects for an invented one. Computed values belong to a\n * product's own computation check, where \"matches what we already\n * calculated\" is satisfiable by construction. */\n materialTargets?: (artifact: WorkProductArtifact) => string[]\n /** Require every material target to carry a SOURCE ANCHOR — a span-sliced or\n * verified quote — not merely an evidence row. Off by default (a bare\n * `claim` is legitimate lineage for products with no readable sources);\n * turn it ON once `readSourceText` is wired and `materialTargets` names only\n * document-derived targets, and coverage stops being satisfiable by\n * assertion. */\n requireAnchoredEvidence?: boolean\n /** Verify that each anchored quote CARRIES the figure its entry claims.\n * ON by default — an anchor that does not support its claim is the one\n * failure mode every other gate here passes, and it reads to a reviewer as\n * the most authoritative citation on the row.\n *\n * Only claims that assert a figure are checked, so a filing status, a name\n * or a date is unaffected; see `./claim-support` for exactly where the line\n * is drawn and why it is drawn to stay satisfiable. Set `false` only for a\n * product whose claims are figures the source states in a form no numeric\n * comparison can reach. */\n verifyClaimSupport?: boolean\n /** Per-turn provenance closure the ROUTE supplies (profileHash + runId are\n * known at dispatch; trusted, never read from model args). */\n provenance: (ctx: AppToolContext) => WorkProductProvenanceBase\n /** Called on the draft→ready commit so the route persists the transcript\n * anchor part and the queue projection updates. */\n onReady?: (record: WorkProductRecord, ctx: AppToolContext) => void | Promise<void>\n /** Injectable clock / id generator (tests, deterministic ids). */\n now?: () => number\n generateId?: () => string\n}\n\n/** Unwrap a guarded outcome, retrying ONCE on a lost race (the single\n * serialized turn owner makes real contention rare; one re-read-and-retry\n * absorbs the benign case). A deterministic rejection maps to a correctable\n * `ToolInputError` so the model learns exactly why. */\nasync function unwrap<T>(\n run: () => Promise<WorkProductOutcome<T>>,\n code: string,\n): Promise<T> {\n let outcome = await run()\n if (!outcome.succeeded && outcome.conflict) outcome = await run()\n if (!outcome.succeeded) throw new ToolInputError(code, outcome.error, outcome.conflict ? 409 : 400)\n return outcome.value\n}\n\nfunction requireScopeKey(args: Record<string, unknown>): string {\n const scopeKey = typeof args.scopeKey === 'string' ? args.scopeKey.trim() : ''\n if (!scopeKey) throw new ToolInputError('missing_scope_key', 'scopeKey is required — the engagement key this work product belongs to.')\n return scopeKey\n}\n\nfunction requireBatch(args: Record<string, unknown>, field: string): unknown[] {\n const raw = args[field]\n if (!Array.isArray(raw) || raw.length === 0) {\n throw new ToolInputError('missing_entries', `${field} must be a non-empty array.`)\n }\n if (raw.length > MAX_WORK_PRODUCT_BATCH) {\n throw new ToolInputError('batch_too_large', `${field} accepts at most ${MAX_WORK_PRODUCT_BATCH} entries per call — send smaller batches.`)\n }\n return raw\n}\n\n/**\n * Resolve the scope's open draft row, creating or reopening as needed:\n * an open `draft`/`blocked` row resumes; a `changes_requested` row reopens as\n * the correction draft (version +1); a `ready` row refuses — the package is\n * awaiting review and must receive a verdict first; otherwise a fresh draft\n * is created at `last reviewed version + 1` with dispatch-stamped provenance.\n */\nasync function resolveDraft(\n service: WorkProductService,\n config: WorkProductToolConfig,\n scopeKey: string,\n ctx: AppToolContext,\n): Promise<WorkProductRecord> {\n const open = await service.openDraft(ctx.workspaceId, scopeKey)\n if (open) return open\n const awaitingReview = await service.awaitingReview(ctx.workspaceId, scopeKey)\n if (awaitingReview) {\n throw new ToolInputError(\n 'awaiting_review',\n `Work product for ${scopeKey} (v${awaitingReview.version}) is awaiting review — no further emission until a reviewer verdict.`,\n 409,\n )\n }\n const awaitingCorrection = await service.awaitingCorrection(ctx.workspaceId, scopeKey)\n if (awaitingCorrection) {\n return unwrap(() => service.reopen(awaitingCorrection.id), 'reopen_failed')\n }\n return service.create({\n workspaceId: ctx.workspaceId,\n threadId: ctx.threadId,\n scopeKey,\n version: await service.nextVersion(ctx.workspaceId, scopeKey),\n provenance: stampProvenance(config.provenance(ctx), config.now),\n })\n}\n\n/** Turn a span rejection into the sentence the model can act on WITHOUT\n * re-reading the document — every message names the number that was wrong and\n * the number it must be under. */\nfunction spanErrorDetail(failure: SourceSpanFailure): string {\n switch (failure.reason) {\n case 'not_integer':\n return `${failure.field} must be a whole character offset.`\n case 'negative':\n return `${failure.field} must not be negative.`\n case 'inverted':\n return 'end must be greater than start — the range is half-open [start, end).'\n case 'out_of_range':\n return `end is past the end of the document, which is ${failure.totalChars} characters. Offsets are absolute in the whole document: when you read with an offset, add that offset to the position within the returned text.`\n case 'blank':\n return 'that range is only whitespace. Widen it to the characters that carry the value.'\n }\n}\n\n/** Turn a locate failure into a sentence naming what was searched for. */\nfunction findErrorDetail(failure: SourceFindFailure, needle: string): string {\n switch (failure.reason) {\n case 'blank_needle':\n return 'the value to locate is empty.'\n case 'not_found':\n return `${JSON.stringify(needle)} does not occur in that document. Read it again and cite a value it actually contains, or — if this figure is COMPUTED rather than read — omit the locator and state the computation in claim.`\n case 'occurrence_out_of_range':\n return `that value occurs ${failure.found} time(s) in the document; findOccurrence is out of range.`\n case 'not_distinctive':\n return failure.found === 0\n ? `${JSON.stringify(failure.needle)} is too short to identify a place in the document — 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.`\n : `${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.`\n }\n}\n\n/**\n * Resolve every entry's quote against the document it names — the one place\n * lineage text is decided.\n *\n * Two citation forms, and the asymmetry between them is the point:\n *\n * - `locator.span` — the model names `[start, end)` into text it just read\n * and the PLATFORM slices the quote out of the source bytes. There is no\n * retyping step, so a fabricated quote is not rejected, it is impossible.\n * The slice REPLACES any quote the model also sent (the document is the\n * authority, not the model's transcription of it) and the resolved text\n * goes back in the tool result, so the model sees what was stored.\n * - `locator.quote` alone — the legacy free-text form, still verified\n * character-for-character. Kept because evidence written before spans\n * existed, and sources reached through paths that cannot offer offsets,\n * must stay expressible; a product does not lose lineage by upgrading.\n *\n * Entries are MUTATED in place with the resolved quote and its server-set\n * `quoteBasis`, then persisted — so what a reviewer reads is what the platform\n * proved, not what the model typed.\n *\n * Source texts are read once per distinct `sourceRef` in the batch — a\n * 50-entry batch citing three documents is three reads, not fifty.\n *\n * Without `readSourceText` the product has given the shell no way to see its\n * documents: free-text quotes go unchecked (as before — inventing a weaker\n * check would report unverified lineage as verified), and a span is a LOUD\n * refusal rather than a silently dropped locator, because a span the platform\n * cannot slice is a wiring bug in the product, not a model mistake.\n */\nasync function resolveEvidenceQuotes(\n config: WorkProductToolConfig,\n entries: readonly EvidenceEntry[],\n ctx: AppToolContext,\n): Promise<void> {\n const readSourceText = config.readSourceText\n const texts = new Map<string, string | null>()\n const readText = async (ref: string): Promise<string | null> => {\n if (!texts.has(ref)) texts.set(ref, await readSourceText!(ref, ctx))\n return texts.get(ref) ?? null\n }\n\n for (let index = 0; index < entries.length; index += 1) {\n const entry = entries[index]!\n // `quoteBasis` arrives unset — `parseEvidenceInput` drops a model-supplied\n // one — so this function only ever WRITES it, and only on a path that\n // established it. Every `continue` below therefore leaves the entry\n // unlabelled, which is the honest state for a quote nothing proved.\n const find = entry.locator.find\n const span = entry.locator.span\n const quote = entry.locator.quote\n\n if (find !== undefined) {\n if (!readSourceText) {\n throw new ToolInputError(\n 'span_unsupported',\n `entries[${index}].locator.find: this deployment cannot read source text, so a value cannot be located. Record the entry without locator.find.`,\n 500,\n )\n }\n const text = await readText(entry.sourceRef)\n if (text === null) {\n throw new ToolInputError(\n 'unverifiable_quote',\n `entries[${index}].locator.find: \"${entry.sourceRef}\" has no readable text, so the value cannot be located. Record the entry without a locator and state the basis in claim.`,\n )\n }\n const located = findSourceLine(text, find, entry.locator.findOccurrence ?? 1)\n if (!located.ok) {\n throw new ToolInputError(\n 'value_not_found',\n `entries[${index}].locator.find into \"${entry.sourceRef}\": ${findErrorDetail(located.failure, find)}`,\n )\n }\n // The platform owns BOTH halves now: it found the position and it cut\n // the text. The model contributed a value it read, which is the one\n // thing it does reliably.\n entry.locator.span = located.span\n entry.locator.quote = located.quote\n entry.locator.quoteBasis = 'span'\n continue\n }\n\n if (span) {\n if (!readSourceText) {\n throw new ToolInputError(\n 'span_unsupported',\n `entries[${index}].locator.span: this deployment cannot read source text, so a span cannot be resolved into a quote. Record the entry without locator.span.`,\n 500,\n )\n }\n const text = await readText(entry.sourceRef)\n if (text === null) {\n throw new ToolInputError(\n 'unverifiable_quote',\n `entries[${index}].locator.span: \"${entry.sourceRef}\" has no readable text, so the span cannot be resolved. Record the entry without locator.span or locator.quote and state the basis in claim.`,\n )\n }\n const sliced = sliceSourceSpan(text, span)\n if (!sliced.ok) {\n throw new ToolInputError(\n 'invalid_span',\n `entries[${index}].locator.span [${span.start}, ${span.end}) into \"${entry.sourceRef}\": ${spanErrorDetail(sliced.failure)}`,\n )\n }\n entry.locator.quote = sliced.quote\n entry.locator.quoteBasis = 'span'\n continue\n }\n\n if (quote === undefined || quote.trim().length === 0) continue\n if (!readSourceText) continue\n const text = await readText(entry.sourceRef)\n if (text === null) {\n throw new ToolInputError(\n 'unverifiable_quote',\n `entries[${index}].locator.quote: \"${entry.sourceRef}\" has no readable text, so the quote cannot be verified. Record the entry without locator.quote and state the basis in claim.`,\n )\n }\n if (!sourceContainsQuote(text, quote)) {\n throw new ToolInputError(\n 'quote_not_found',\n `entries[${index}].locator.quote: ${JSON.stringify(quote)} does not occur in \"${entry.sourceRef}\". Cite locator.find instead — the value as it appears in the document — and this platform locates it and writes the quote itself, so the text is right by construction. If the value is COMPUTED rather than read, omit both and state the computation in claim.`,\n )\n }\n entry.locator.quoteBasis = 'model'\n }\n}\n\n/**\n * Refuse any entry whose anchored text does not carry the figure it claims.\n *\n * This runs AFTER `resolveEvidenceQuotes`, on the text the platform decided,\n * and it is deliberately independent of how that text was anchored. `find`\n * makes a mis-addressed citation unlikely — the platform locates the line — but\n * `span` still accepts offsets a caller computed, and this is what \"accepted\n * only when they independently verify\" means concretely: the slice has to\n * contain the value.\n *\n * Row `7256ef49` is the case. Four span citations, every quote a real slice of\n * the right document, every one ~200 characters above the figure it claimed.\n * `resolveEvidenceQuotes` passed all four; nothing else here would have caught\n * them; a reviewer would have read four authoritative-looking citations\n * supporting nothing.\n *\n * Rejection is a `ToolInputError` so it folds back to the model mid-turn, the\n * same posture a non-occurring quote already gets, and the message names both\n * ways out — cite the value, or drop the locator because the figure was\n * computed. An unsatisfiable gate does not stop a bad submit, it selects for\n * one, so naming the second exit is load-bearing rather than politeness.\n */\nfunction assertClaimsSupported(\n config: WorkProductToolConfig,\n entries: readonly EvidenceEntry[],\n): void {\n if (config.verifyClaimSupport === false) return\n for (let index = 0; index < entries.length; index += 1) {\n const entry = entries[index]!\n const quote = entry.locator.quote\n if (quote === undefined) continue\n const support = verifyClaimSupport(quote, entry.claim)\n if (support.status !== 'unsupported') continue\n throw new ToolInputError(\n 'claim_not_supported',\n `entries[${index}].claim ${JSON.stringify(entry.claim)} is not supported by the text it cites in \"${entry.sourceRef}\": ${claimSupportErrorDetail(support, quote)}`,\n )\n }\n}\n\n/** Re-verify every persisted quote at submit time. The upsert gate stops new\n * fabrication; this stops a package whose evidence was written BEFORE the\n * gate existed (or under a since-corrected document) from reaching a\n * reviewer. Entries with no quote are neither verified nor failures — they\n * are lineage with no click target, counted separately so the recorded check\n * states how much of the package is quote-backed.\n *\n * A span-anchored entry is re-checked by RE-SLICING the current document and\n * comparing to the stored text, not by substring search. The difference\n * matters exactly when a source has been replaced since the citation was\n * written: a substring search would still pass if the sentence survived\n * anywhere in the new file, while the reviewer's click target has silently\n * moved. Re-slicing says the offsets still select the same characters. */\nasync function summarizeQuoteVerification(\n config: WorkProductToolConfig,\n evidence: readonly EvidenceEntry[],\n ctx: AppToolContext,\n): Promise<{ verified: number; spanAnchored: number; withoutQuote: number; failed: string[] } | undefined> {\n const readSourceText = config.readSourceText\n if (!readSourceText) return undefined\n const texts = new Map<string, string | null>()\n let verified = 0\n let spanAnchored = 0\n let withoutQuote = 0\n const failed: string[] = []\n for (const entry of evidence) {\n const quote = entry.locator.quote\n if (quote === undefined || quote.trim().length === 0) {\n withoutQuote += 1\n continue\n }\n if (!texts.has(entry.sourceRef)) texts.set(entry.sourceRef, await readSourceText(entry.sourceRef, ctx))\n const text = texts.get(entry.sourceRef)\n if (typeof text !== 'string') {\n failed.push(entry.id)\n continue\n }\n const span = entry.locator.span\n if (span) {\n const sliced = sliceSourceSpan(text, span)\n if (sliced.ok && sliced.quote === quote) {\n verified += 1\n spanAnchored += 1\n } else failed.push(entry.id)\n continue\n }\n if (sourceContainsQuote(text, quote)) verified += 1\n else failed.push(entry.id)\n }\n return { verified, spanAnchored, withoutQuote, failed }\n}\n\n/**\n * Re-run claim support over every PERSISTED entry at submit time.\n *\n * Unlike `summarizeQuoteVerification` this needs no `readSourceText`: the\n * question is whether the stored quote carries the stored claim, and both are\n * on the row. So it runs for every product, including one that cannot read its\n * sources back — and it is the check that catches a package whose evidence was\n * written before this gate existed. Row `7256ef49` is exactly that package.\n */\nfunction summarizeClaimSupport(evidence: readonly EvidenceEntry[]): {\n supported: number\n checkable: number\n unsupported: string[]\n} {\n let supported = 0\n let checkable = 0\n const unsupported: string[] = []\n for (const entry of evidence) {\n const quote = entry.locator.quote\n if (quote === undefined) continue\n const support = verifyClaimSupport(quote, entry.claim)\n if (support.status === 'not_applicable') continue\n checkable += 1\n if (support.status === 'supported') supported += 1\n else unsupported.push(entry.id)\n }\n return { supported, checkable, unsupported }\n}\n\n/** Build the three work-product tools for `customTools` registration on the\n * MCP server / HTTP handler / runtime executor. */\nexport function buildWorkProductTools(config: WorkProductToolConfig): AppToolDefinition[] {\n const service = createWorkProductService({\n store: config.store,\n ...(config.now ? { now: config.now } : {}),\n ...(config.generateId ? { generateId: config.generateId } : {}),\n })\n\n const upsertEvidence = defineAppTool({\n name: 'upsert_evidence',\n description:\n 'Record source→field lineage for the current work product, incrementally as you find it. Each entry links a source document (sourceRef + locator) to one artifact target and states the claim it supports. Cite by locator.find — the value exactly as it appears in the document — and the platform locates it and writes the supporting quote for you. Re-emitting an entry id replaces that entry. Address the work product by scopeKey; the first call creates the draft.',\n parameters: {\n type: 'object',\n properties: {\n scopeKey: { type: 'string', description: 'Engagement key for this work product.' },\n entries: {\n type: 'array',\n minItems: 1,\n maxItems: MAX_WORK_PRODUCT_BATCH,\n items: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Stable entry id — re-emit to replace.' },\n sourceRef: { type: 'string', description: 'Vault path / attachment id of the SOURCE document.' },\n locator: {\n type: 'object',\n properties: {\n page: { type: 'number' },\n range: { type: 'string', description: \"Free-form location: 'L120-L134' | 'B7' | '¶4'.\" },\n find: {\n type: 'string',\n description:\n 'PREFERRED — use this whenever the value was READ from a document. The value exactly as it appears in the source (for example \"128,450.00\"), or a short distinctive phrase from the supporting line. The platform LOCATES it, cites the whole line it sits on, and returns that line to you. You do not retype the quote and you do not compute character offsets — so the citation can neither be invented nor land on the wrong line. If the value is not in the document, the entry is refused.',\n },\n findOccurrence: {\n type: 'integer',\n minimum: 1,\n description: 'Which occurrence of `find` to cite when the value appears more than once. Defaults to the first.',\n },\n span: {\n type: 'object',\n description:\n 'Only when you have exact character offsets from a tool that computed them. Absolute offsets into the whole document: start is the first character, end is one past the last. Prefer `find` — offsets computed by hand land on the wrong line.',\n properties: {\n start: { type: 'integer', minimum: 0 },\n end: { type: 'integer', minimum: 1 },\n },\n required: ['start', 'end'],\n },\n quote: {\n type: 'string',\n description:\n 'Fallback for sources that cannot give you character offsets: the supporting text copied character-for-character. The platform checks it occurs in the document and REFUSES the entry if it does not. Prefer span. For a value you COMPUTED rather than read, omit both and state the computation in claim.',\n },\n },\n },\n target: { type: 'string', description: 'Artifact field/claim this evidence supports.' },\n claim: { type: 'string', description: 'The value/assertion at the target.' },\n confidence: { type: 'number', minimum: 0, maximum: 1 },\n },\n required: ['id', 'sourceRef', 'target', 'claim'],\n },\n },\n },\n required: ['scopeKey', 'entries'],\n },\n async execute(args: Record<string, unknown>, ctx: AppToolContext) {\n const scopeKey = requireScopeKey(args)\n const raw = requireBatch(args, 'entries')\n const entries: EvidenceEntry[] = []\n for (let index = 0; index < raw.length; index += 1) {\n const parsed = parseEvidenceInput(raw[index], `entries[${index}]`)\n if (!parsed.ok) throw new ToolInputError('invalid_evidence', `${parsed.field}: ${parsed.error}`)\n entries.push(parsed.value)\n }\n // Fail-loud source resolution: lineage can never point at nothing.\n for (let index = 0; index < entries.length; index += 1) {\n const entry = entries[index]!\n if (!(await config.resolveSourceRef(entry.sourceRef, ctx))) {\n throw new ToolInputError(\n 'unknown_source_ref',\n `entries[${index}].sourceRef: \"${entry.sourceRef}\" does not resolve to an existing source document.`,\n )\n }\n }\n // Fail-loud quote verification: a quote must occur in the document the\n // entry names. Rejected BEFORE the draft is resolved, so a batch with a\n // fabricated quote persists nothing — the model corrects and re-sends\n // rather than leaving a half-written row behind.\n await resolveEvidenceQuotes(config, entries, ctx)\n // ...and the text that resolution produced must carry the figure the\n // entry claims. Same batch, same fail-before-persist discipline: an\n // entry citing the payer line for a wage figure never reaches the draft.\n assertClaimsSupported(config, entries)\n const draft = await resolveDraft(service, config, scopeKey, ctx)\n const record = await unwrap(() => service.upsertEvidence(draft.id, entries), 'evidence_rejected')\n return {\n workProductId: record.id,\n version: record.version,\n evidenceCount: record.evidence.length,\n // Echo what was actually STORED for the entries in this call. A span\n // citation's quote is produced here, not sent here, so the model must\n // be able to see the text its offsets selected — that is how it\n // notices an off-by-a-line span without a second read.\n entries: entries.map((entry) => ({\n id: entry.id,\n target: entry.target,\n ...(entry.locator.quote === undefined ? {} : { quote: entry.locator.quote }),\n ...(entry.locator.quoteBasis === undefined ? {} : { quoteBasis: entry.locator.quoteBasis }),\n })),\n }\n },\n })\n\n const flagException = defineAppTool({\n name: 'flag_exception',\n description:\n 'Flag problems with the current work product (missing documents, inconsistent sources, …). An unresolved blocking exception parks the work product until it is resolved; re-emit the same id with resolved:true to release it. Address by scopeKey.',\n parameters: {\n type: 'object',\n properties: {\n scopeKey: { type: 'string', description: 'Engagement key for this work product.' },\n exceptions: {\n type: 'array',\n minItems: 1,\n maxItems: MAX_WORK_PRODUCT_BATCH,\n items: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Stable entry id — re-emit to replace.' },\n severity: { type: 'string', enum: ['blocking', 'material', 'advisory'] },\n kind: { type: 'string', description: 'Exception kind from the product vocabulary.' },\n message: { type: 'string' },\n targets: { type: 'array', items: { type: 'string' } },\n resolved: { type: 'boolean' },\n resolutionNote: { type: 'string' },\n },\n required: ['id', 'severity', 'kind', 'message'],\n },\n },\n },\n required: ['scopeKey', 'exceptions'],\n },\n async execute(args: Record<string, unknown>, ctx: AppToolContext) {\n const scopeKey = requireScopeKey(args)\n const raw = requireBatch(args, 'exceptions')\n const entries: ExceptionEntry[] = []\n for (let index = 0; index < raw.length; index += 1) {\n const parsed = parseExceptionInput(raw[index], `exceptions[${index}]`)\n if (!parsed.ok) throw new ToolInputError('invalid_exception', `${parsed.field}: ${parsed.error}`)\n if (!config.exceptionKinds.includes(parsed.value.kind)) {\n throw new ToolInputError(\n 'invalid_exception',\n `exceptions[${index}].kind: must be one of: ${config.exceptionKinds.join(', ')}`,\n )\n }\n // An agent resolving its own exception is tagged as such; the reviewer\n // tag is reserved for the verdict route.\n if (parsed.value.resolved && parsed.value.resolvedBy === undefined) parsed.value.resolvedBy = 'agent'\n entries.push(parsed.value)\n }\n const draft = await resolveDraft(service, config, scopeKey, ctx)\n const record = await unwrap(() => service.upsertExceptions(draft.id, entries), 'exception_rejected')\n return {\n workProductId: record.id,\n version: record.version,\n status: record.status,\n unresolvedBlocking: unresolvedBlockingExceptions(record.exceptions).length,\n }\n },\n })\n\n const submitWorkProduct = defineAppTool({\n name: 'submit_work_product',\n description:\n 'Submit the finished work product for professional review — the terminal call after evidence and exceptions are recorded. Refused while a blocking exception is unresolved, or when a material target lacks evidence. Include your own quality checks in `checks`.',\n parameters: {\n type: 'object',\n properties: {\n scopeKey: { type: 'string', description: 'Engagement key for this work product.' },\n artifact: {\n type: 'object',\n properties: {\n kind: { type: 'string', description: 'Artifact kind from the product vocabulary.' },\n title: { type: 'string' },\n path: { type: 'string', description: 'Vault/object-store ref of the rendered document.' },\n content: { type: 'string', description: 'Inline body when small (markdown/JSON).' },\n mediaType: { type: 'string' },\n baseline: {\n type: 'object',\n properties: { path: { type: 'string' }, content: { type: 'string' } },\n description: 'For diff-first artifacts: the source document being redlined.',\n },\n fields: { type: 'object', description: 'Structured field map lineage targets anchor to.' },\n },\n required: ['kind', 'title'],\n },\n checks: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n name: { type: 'string' },\n passed: { type: 'boolean' },\n detail: { type: 'string' },\n },\n required: ['id', 'name', 'passed'],\n },\n description: 'Your own quality self-checks (recorded as agent-sourced).',\n },\n },\n required: ['scopeKey', 'artifact'],\n },\n async execute(args: Record<string, unknown>, ctx: AppToolContext) {\n const scopeKey = requireScopeKey(args)\n const parsedArtifact = parseArtifactInput(args.artifact)\n if (!parsedArtifact.ok) throw new ToolInputError('invalid_artifact', `${parsedArtifact.field}: ${parsedArtifact.error}`)\n const artifact = parsedArtifact.value\n if (!config.artifactKinds.includes(artifact.kind)) {\n throw new ToolInputError('invalid_artifact', `artifact.kind: must be one of: ${config.artifactKinds.join(', ')}`)\n }\n const agentChecks: QualityCheck[] = []\n if (args.checks !== undefined) {\n if (!Array.isArray(args.checks)) throw new ToolInputError('invalid_checks', 'checks must be an array when present.')\n for (let index = 0; index < args.checks.length; index += 1) {\n const parsed = parseAgentCheckInput(args.checks[index], `checks[${index}]`)\n if (!parsed.ok) throw new ToolInputError('invalid_checks', `${parsed.field}: ${parsed.error}`)\n agentChecks.push({ ...parsed.value, source: 'agent' })\n }\n }\n\n const draft = await resolveDraft(service, config, scopeKey, ctx)\n const blocking = unresolvedBlockingExceptions(draft.exceptions)\n if (blocking.length > 0) {\n throw new ToolInputError(\n 'blocking_exceptions_unresolved',\n `Cannot submit: ${blocking.length} unresolved blocking exception(s) (${blocking.map((entry) => entry.id).join(', ')}). Resolve each via flag_exception with resolved:true, or downgrade its severity with a resolutionNote justifying why it does not block.`,\n 409,\n )\n }\n\n // Platform check one: every persisted quote still occurs in the source\n // it names. The upsert gate stops NEW fabrication; this stops a package\n // whose evidence predates the gate from reaching a reviewer. Recorded on\n // the row AND rejected fail-loud — the same discipline as coverage.\n const checks: QualityCheck[] = [...agentChecks]\n const quotes = await summarizeQuoteVerification(config, draft.evidence, ctx)\n if (quotes) {\n const quoted = quotes.verified + quotes.failed.length\n checks.unshift({\n id: QUOTE_VERIFICATION_CHECK,\n name: QUOTE_VERIFICATION_CHECK,\n passed: quotes.failed.length === 0,\n detail:\n quotes.failed.length === 0\n ? `${quotes.verified}/${quoted} quoted evidence entries verified against their source (${quotes.spanAnchored} platform-sliced from a source span, ${quotes.verified - quotes.spanAnchored} model-quoted and proved to occur); ${quotes.withoutQuote} recorded without a quote`\n : `Unverifiable quotes on: ${quotes.failed.join(', ')}`,\n source: 'platform',\n })\n if (quotes.failed.length > 0) {\n await unwrap(() => service.recordChecks(draft.id, checks), 'checks_rejected')\n throw new ToolInputError(\n 'quote_verification_failed',\n `Cannot submit: ${quotes.failed.length} evidence entr${quotes.failed.length === 1 ? 'y quotes' : 'ies quote'} text that does not occur in the source named (${quotes.failed.join(', ')}). Re-emit each with a quote copied character-for-character from that document, or without locator.quote if the value is computed.`,\n )\n }\n }\n\n // Platform check two: every citation's text carries the figure it\n // claims. Recorded AND rejected, like the other two — a citation that\n // points at real text supporting nothing is the failure a reviewer is\n // least able to catch by eye, because it looks exactly like a good one.\n if (config.verifyClaimSupport !== false) {\n const support = summarizeClaimSupport(draft.evidence)\n checks.unshift({\n id: CLAIM_SUPPORT_CHECK,\n name: CLAIM_SUPPORT_CHECK,\n passed: support.unsupported.length === 0,\n detail:\n support.unsupported.length > 0\n ? `Cited text does not contain the claimed figure on: ${support.unsupported.join(', ')}`\n : support.checkable === 0\n ? // Honest about a vacuous pass: no entry paired a quote with a\n // figure, so nothing was checked. Reporting \"0/0 verified\"\n // would read to a reviewer as assurance that was never earned.\n 'No citation pairs a quote with a claimed figure — nothing to check'\n : `${support.supported}/${support.checkable} value-bearing citations anchor to text containing the claimed figure`,\n source: 'platform',\n })\n if (support.unsupported.length > 0) {\n await unwrap(() => service.recordChecks(draft.id, checks), 'checks_rejected')\n throw new ToolInputError(\n 'claim_not_supported',\n `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.`,\n )\n }\n }\n\n // Platform check three: every material target has ≥1 evidence row. A\n // failing coverage check is BOTH recorded on the row (visible to the\n // queue) and rejected fail-loud — a lineage-free package can never reach\n // a reviewer.\n if (config.materialTargets) {\n const targets = config.materialTargets(artifact)\n const covered = new Set(draft.evidence.map((entry) => entry.target))\n const missing = targets.filter((target) => !covered.has(target))\n // How each covered target is BACKED, not merely that a row exists.\n // A coverage count that cannot distinguish a platform-sliced citation\n // from a bare assertion is the number that let 13 invented quotes read\n // as \"13/13 evidenced\" on the row a reviewer then opened.\n const anchoredTargets = new Set(\n draft.evidence.filter((entry) => entry.locator.quoteBasis !== undefined).map((entry) => entry.target),\n )\n const spanTargets = new Set(\n draft.evidence.filter((entry) => entry.locator.quoteBasis === 'span').map((entry) => entry.target),\n )\n const present = targets.filter((target) => covered.has(target))\n const unanchored = present.filter((target) => !anchoredTargets.has(target))\n const spanCount = present.filter((target) => spanTargets.has(target)).length\n const breakdown = `${spanCount} span-anchored, ${present.length - spanCount - unanchored.length} quote-verified, ${unanchored.length} claim-only`\n const coverage: QualityCheck = {\n id: EVIDENCE_COVERAGE_CHECK,\n name: EVIDENCE_COVERAGE_CHECK,\n passed: missing.length === 0 && !(config.requireAnchoredEvidence && unanchored.length > 0),\n detail:\n missing.length > 0\n ? `Missing evidence for: ${missing.join(', ')}`\n : config.requireAnchoredEvidence && unanchored.length > 0\n ? `No source anchor for: ${unanchored.join(', ')}`\n : `${targets.length}/${targets.length} material targets evidenced (${breakdown})`,\n source: 'platform',\n }\n checks.unshift(coverage)\n if (missing.length > 0) {\n await unwrap(() => service.recordChecks(draft.id, checks), 'checks_rejected')\n throw new ToolInputError(\n 'evidence_coverage_failed',\n `Cannot submit: material targets lack evidence: ${missing.join(', ')}. Add upsert_evidence entries targeting each, then resubmit.`,\n )\n }\n if (config.requireAnchoredEvidence && unanchored.length > 0) {\n await unwrap(() => service.recordChecks(draft.id, checks), 'checks_rejected')\n throw new ToolInputError(\n 'evidence_not_anchored',\n `Cannot submit: these material targets have an evidence row but no source anchor: ${unanchored.join(', ')}. Re-emit each with locator.find — the value exactly as it appears in the document. The platform locates it and writes the quote itself, so you never retype source text or count characters.`,\n )\n }\n }\n\n const provenance = stampProvenance(config.provenance(ctx), config.now)\n const record = await unwrap(\n () => service.submit(draft.id, { artifact, checks, provenance }),\n 'submit_rejected',\n )\n await config.onReady?.(record, ctx)\n return {\n workProductId: record.id,\n version: record.version,\n status: record.status,\n checks: record.checks.map((check) => ({ name: check.name, passed: check.passed, source: check.source })),\n }\n },\n })\n\n return [upsertEvidence, flagException, submitWorkProduct]\n}\n","/**\n * Framework-neutral work-product review endpoints — the\n * `createInteractionAnswerRoute` factory pattern: web-standard\n * `Request`/`Response`, ONE product-supplied `authorize` seam (session auth,\n * workspace access, reviewer identity, rate limits), everything behind it\n * mechanism. Server-only and subpath-only: never reachable from a client\n * bundle (enforced by the browser-safety test).\n *\n * The verdict endpoint is deliberately NOT a second approval broker: agent\n * asks stay on `/interactions`; this is the REVIEWER's plain authorized\n * verdict on a ready package — one human-in-the-loop channel per direction.\n * A `request_changes` note re-enters chat as the correction turn via the\n * `onVerdict` seam; chat remains the driver surface.\n */\n\nimport {\n createWorkProductService,\n type WorkProductService,\n} from './service'\nimport {\n workProductToPersistedPart,\n type WorkProductPersistedPart,\n type WorkProductRecord,\n type WorkProductStorePort,\n} from './types'\n\n/** Reviewer verdict wire body for the POST endpoint. */\nexport type WorkProductVerdictBody =\n | { ok: true; id: string; verdict: 'approve'; note?: string }\n | { ok: true; id: string; verdict: 'request_changes'; note: string }\n | { ok: false; error: string }\n\n/** Validate the verdict POST body: `{ id, verdict, note? }`; a\n * `request_changes` verdict REQUIRES a non-empty note — the note IS the\n * correction turn the agent works from. */\nexport function validateWorkProductVerdictBody(body: Record<string, unknown>): WorkProductVerdictBody {\n const id = typeof body.id === 'string' && body.id.trim() ? body.id.trim() : null\n if (!id) return { ok: false, error: 'Missing work product id' }\n const verdict = body.verdict\n if (verdict !== 'approve' && verdict !== 'request_changes') {\n return { ok: false, error: 'Invalid verdict: expected approve or request_changes' }\n }\n const note = body.note === undefined ? undefined : typeof body.note === 'string' ? body.note.trim() : null\n if (note === null) return { ok: false, error: 'Invalid note: expected a string' }\n if (verdict === 'request_changes') {\n if (!note) return { ok: false, error: 'request_changes requires a note — it becomes the correction instruction in chat' }\n return { ok: true, id, verdict, note }\n }\n return note ? { ok: true, id, verdict, note } : { ok: true, id, verdict }\n}\n\n/** The product seam's verdict for one request: authenticated reviewer +\n * workspace, or a product-authored short-circuit Response (401/403/429…). */\nexport type WorkProductRouteAuthorization =\n | { ok: true; workspaceId: string; reviewedBy: string }\n | { ok: false; response: Response }\n\n/** Auth seam arguments carrying the request, the endpoint intent, and the parsed verdict body */\nexport interface WorkProductAuthorizeArgs {\n request: Request\n intent: 'list' | 'detail' | 'verdict'\n /** The parsed, validated POST body (verdict intent only). */\n body?: Record<string, unknown>\n}\n\n/** Configuration options assembling the review endpoints from the store and product seams */\nexport interface WorkProductRoutesOptions {\n store: WorkProductStorePort\n /** Authenticate + authorize the caller; the ONLY product access step. */\n authorize: (args: WorkProductAuthorizeArgs) => Promise<WorkProductRouteAuthorization>\n /** Post-verdict product seam: post the `request_changes` note into the\n * driving chat thread as the correction turn, notify, etc. Runs after the\n * transition commits; a throw is logged, never unwinds the verdict. */\n onVerdict?: (args: {\n record: WorkProductRecord\n verdict: 'approve' | 'request_changes'\n note?: string\n reviewedBy: string\n }) => void | Promise<void>\n /** Persist/update the transcript anchor part reflecting the new status, so\n * the chat card flips with the verdict. */\n persistAnchorPart?: (part: WorkProductPersistedPart, record: WorkProductRecord) => void | Promise<void>\n /** Future integration point fired on approval (push to a DMS/CRM/export\n * pipeline). A stub seam by design — export itself stays the product's\n * signed object-store download. */\n onExport?: (record: WorkProductRecord) => void | Promise<void>\n logger?: Pick<Console, 'warn' | 'error'>\n now?: () => number\n}\n\n/** Assembled review endpoints returning web-standard Responses */\nexport interface WorkProductRoutes {\n /** GET — the workspace's records for the queue projection. Optional\n * `?status=a,b` filter. */\n list: (request: Request) => Promise<Response>\n /** GET — one record by id (404 when absent or outside the workspace). */\n detail: (request: Request, id: string) => Promise<Response>\n /** POST `{ id, verdict, note? }` — the reviewer verdict: CAS transition +\n * history entry + product seams. 409 when the record is no longer ready. */\n verdict: (request: Request) => Promise<Response>\n}\n\n/** Create the work-product review endpoints over the store port and product seams */\nexport function createWorkProductRoutes(options: WorkProductRoutesOptions): WorkProductRoutes {\n const logger = options.logger ?? console\n const service: WorkProductService = createWorkProductService({\n store: options.store,\n ...(options.now ? { now: options.now } : {}),\n })\n\n async function list(request: Request): Promise<Response> {\n const auth = await options.authorize({ request, intent: 'list' })\n if (!auth.ok) return auth.response\n const url = new URL(request.url)\n const statusParam = url.searchParams.get('status')\n const statuses = statusParam\n ? statusParam.split(',').map((value) => value.trim()).filter(Boolean)\n : null\n const workProducts = await options.store.listByWorkspace(\n auth.workspaceId,\n statuses ? { status: statuses as WorkProductRecord['status'][] } : undefined,\n )\n return Response.json({ workProducts })\n }\n\n async function detail(request: Request, id: string): Promise<Response> {\n const auth = await options.authorize({ request, intent: 'detail' })\n if (!auth.ok) return auth.response\n const record = await options.store.load(id)\n if (!record || record.workspaceId !== auth.workspaceId) {\n return Response.json({ error: 'Work product not found' }, { status: 404 })\n }\n return Response.json({ workProduct: record })\n }\n\n async function verdict(request: Request): Promise<Response> {\n const body = (await request.json().catch(() => null)) as Record<string, unknown> | null\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const validation = validateWorkProductVerdictBody(body)\n if (!validation.ok) return Response.json({ error: validation.error }, { status: 400 })\n\n const auth = await options.authorize({ request, intent: 'verdict', body })\n if (!auth.ok) return auth.response\n\n const existing = await options.store.load(validation.id)\n if (!existing || existing.workspaceId !== auth.workspaceId) {\n return Response.json({ error: 'Work product not found' }, { status: 404 })\n }\n\n const outcome = await service.applyVerdict(validation.id, {\n verdict: validation.verdict,\n reviewedBy: auth.reviewedBy,\n ...(validation.note === undefined ? {} : { note: validation.note }),\n })\n if (!outcome.succeeded) {\n // A lost race and an illegal edge both mean \"the row is no longer the\n // ready version you looked at\" — 409 so the client re-reads.\n return Response.json({ code: 'VERDICT_CONFLICT', error: outcome.error }, { status: 409 })\n }\n const record = outcome.value\n\n // Product seams run AFTER the committed transition; their failures are\n // logged, never unwound — the verdict is durable truth at this point.\n try {\n await options.persistAnchorPart?.(workProductToPersistedPart(record), record)\n } catch (error) {\n logger.error('[work-product] persistAnchorPart failed:', error)\n }\n try {\n await options.onVerdict?.({\n record,\n verdict: validation.verdict,\n ...(validation.note === undefined ? {} : { note: validation.note }),\n reviewedBy: auth.reviewedBy,\n })\n } catch (error) {\n logger.error('[work-product] onVerdict failed:', error)\n }\n if (validation.verdict === 'approve') {\n try {\n await options.onExport?.(record)\n } catch (error) {\n logger.error('[work-product] onExport failed:', error)\n }\n }\n\n return Response.json({ ok: true, workProduct: record })\n }\n\n return { list, detail, verdict }\n}\n"],"mappings":"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","names":["record","entry","bound","text"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.15",
|
|
4
4
|
"packageManager": "pnpm@10.33.4",
|
|
5
5
|
"description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
|
|
6
6
|
"keywords": [
|