@kontourai/survey 6.1.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/example-data/public-directory-review-resource.d.ts +3 -3
  2. package/dist/src/canonical-reviewed-trust-input.js +30 -1
  3. package/dist/src/console/review-console-server.js +22 -4
  4. package/dist/src/index.d.ts +3 -3
  5. package/dist/src/index.js +1 -1
  6. package/dist/src/mcp/review-mcp.js +139 -36
  7. package/dist/src/review-resource.d.ts +7 -0
  8. package/dist/src/review-session-file.d.ts +7 -0
  9. package/dist/src/review-workbench/audit-rows.d.ts +1 -1
  10. package/dist/src/review-workbench/audit-rows.js +1 -0
  11. package/dist/src/review-workbench/queue-binding.d.ts +45 -2
  12. package/dist/src/review-workbench/queue-binding.js +45 -0
  13. package/dist/src/review-workbench/review-presentation.d.ts +23 -0
  14. package/dist/src/review-workbench/review-presentation.js +51 -10
  15. package/dist/src/review-workbench/review-queue-session.d.ts +47 -4
  16. package/dist/src/review-workbench/review-queue-session.js +73 -8
  17. package/dist/src/review-workbench/review-session-replay.d.ts +11 -1
  18. package/dist/src/review-workbench/review-session-replay.js +35 -1
  19. package/dist/src/review-workbench/review-workbench-css.generated.js +94 -0
  20. package/dist/src/review-workbench/review-workbench-element.d.ts +10 -0
  21. package/dist/src/review-workbench/review-workbench-element.js +21 -3
  22. package/dist/src/review-workbench/review-workbench.css +94 -0
  23. package/dist/src/review-workbench/review-workbench.d.ts +28 -8
  24. package/dist/src/review-workbench/review-workbench.js +157 -62
  25. package/dist/src/review-workbench/review-workbench.standalone.css +94 -0
  26. package/dist/src/review-workbench/server-review-session.d.ts +18 -0
  27. package/dist/src/review-workbench/server-review-session.js +20 -2
  28. package/dist/src/surface-reviewed-extraction.d.ts +23 -0
  29. package/dist/src/surface-reviewed-extraction.js +64 -1
  30. package/dist/src/to-surface.js +32 -3
  31. package/package.json +1 -1
@@ -35,12 +35,12 @@ export declare const publicDirectoryReviewItemExample: {
35
35
  excerpt: string;
36
36
  };
37
37
  extraction: {
38
- model?: undefined;
39
38
  extractionId: string;
40
39
  target: string;
41
40
  confidence: number;
42
41
  extractor: string;
43
42
  extractedAt: string;
43
+ model?: undefined;
44
44
  };
45
45
  claimTarget: {
46
46
  claimId: string;
@@ -61,13 +61,13 @@ export declare const publicDirectoryReviewItemExample: {
61
61
  claimId: string;
62
62
  };
63
63
  producer: {
64
- proposalId?: undefined;
65
- oldValue?: undefined;
66
64
  sourceAuthority: {
67
65
  authorityClass: string;
68
66
  declaredBy: string;
69
67
  scope: string;
70
68
  };
69
+ proposalId?: undefined;
70
+ oldValue?: undefined;
71
71
  };
72
72
  } | {
73
73
  id: string;
@@ -39,10 +39,17 @@ export function buildCanonicalReviewedTrustInput(options) {
39
39
  const rejectsAll = selectsNone && result.decision === "reject-proposed";
40
40
  const rejectedRole = workbenchDecisionDefinitions[result.decision].candidateRole;
41
41
  const rejectAllReason = result.rationale?.trim() || "Every proposed value for this claim was rejected.";
42
+ // A choice of one conflicting value passes over the others: each keeps a
43
+ // record that it was seen and not chosen (and so its own rejected-candidate
44
+ // learning), never silently dropped.
45
+ const unchosenIds = new Set(result.decision === "select-proposed" ? result.reviewDecision.spec.unselectedCandidateIds ?? [] : []);
46
+ const notChosenReason = `Not chosen: the reviewer chose candidate ${recordIdFor(item, result.selectedCandidateId ?? "")} for this claim.`;
42
47
  const candidates = item.spec.candidates.map((candidate) => {
43
48
  const records = projectCandidate(item, rejectsAll && candidate.role === rejectedRole
44
49
  ? { ...candidate, rejectionReason: candidate.rejectionReason ?? rejectAllReason }
45
- : candidate);
50
+ : unchosenIds.has(candidate.id)
51
+ ? { ...candidate, rejectionReason: candidate.rejectionReason ?? notChosenReason }
52
+ : candidate);
46
53
  addConsistent(rawSources, records.rawSource, "raw source");
47
54
  addConsistent(extractions, records.extraction, "extraction");
48
55
  return records.candidate;
@@ -96,6 +103,9 @@ export function buildCanonicalReviewedTrustInput(options) {
96
103
  metadata: {
97
104
  workbenchDecision: result.decision,
98
105
  ...(result.editedValue !== undefined ? { editedValue: result.editedValue } : {}),
106
+ ...(decision.unselectedCandidateIds?.length
107
+ ? { unselectedCandidateIds: decision.unselectedCandidateIds.map((id) => recordIdFor(item, id)) }
108
+ : {}),
99
109
  },
100
110
  };
101
111
  addConsistent(reviewOutcomes, reviewOutcome, "review outcome");
@@ -192,6 +202,20 @@ function assertCanonicalResult(item, result) {
192
202
  || canonicalJson(decision.editedValue) !== canonicalJson(result.editedValue)) {
193
203
  throw new Error(`Review result ${result.reviewItemName} contradicts its canonical ReviewDecision.`);
194
204
  }
205
+ // Accepting one value of a conflict would project it as if it had been the
206
+ // only value; a choice between conflicting values is select-proposed.
207
+ if (result.decision === "accept-proposed" && item.spec.candidates.filter((candidate) => candidate.role === "proposed").length > 1) {
208
+ throw new Error(`Review result ${result.reviewItemName} accepts one proposed value of a conflict; a choice between conflicting values must be a select-proposed decision that records the values it passed over.`);
209
+ }
210
+ // A choice names exactly the proposed values it passed over; no other
211
+ // decision names any.
212
+ const expectedUnselected = result.decision === "select-proposed" && selected
213
+ ? item.spec.candidates.filter((candidate) => candidate.role === "proposed" && candidate.id !== selected.id).map((candidate) => candidate.id)
214
+ : [];
215
+ if (canonicalJson(decision.unselectedCandidateIds ?? []) !== canonicalJson(expectedUnselected)
216
+ || (result.decision === "select-proposed" && expectedUnselected.length === 0)) {
217
+ throw new Error(`Review result ${result.reviewItemName} does not record the proposed values its ${result.decision} decision passed over.`);
218
+ }
195
219
  if ((result.decision === "could-not-confirm") !== (decision.resolution === "could_not_confirm")) {
196
220
  throw new Error(`Review result ${result.reviewItemName} contradicts its canonical review resolution.`);
197
221
  }
@@ -210,6 +234,11 @@ function assertCanonicalResult(item, result) {
210
234
  }
211
235
  }
212
236
  }
237
+ /** The projected Candidate record id of a ReviewItem candidate. */
238
+ function recordIdFor(item, candidateId) {
239
+ const candidate = item.spec.candidates.find((entry) => entry.id === candidateId);
240
+ return candidate?.projection?.candidateId ?? candidateId;
241
+ }
213
242
  function claimTargetIdentity(target) {
214
243
  const { claimId: _claimId, ...identity } = target;
215
244
  return identity;
@@ -18,6 +18,7 @@ import { readFile as readFileAsync } from "node:fs/promises";
18
18
  import { resolve, join, dirname, extname, normalize, sep } from "node:path";
19
19
  import { fileURLToPath } from "node:url";
20
20
  import { createServerReviewSessionRecord, currentSessionState, deriveServerReviewSessionApplyResult, } from "../review-workbench/server-review-session.js";
21
+ import { attestReviewQueueExtraction } from "../review-workbench/queue-binding.js";
21
22
  import { appendReviewSessionEvents, readReviewSessionFile, reviewSessionRevision, storedReviewSessionName, updateReviewSessionFile, } from "../review-session-file.js";
22
23
  // ---------------------------------------------------------------------------
23
24
  // Constants
@@ -404,9 +405,15 @@ let activeItemName = null;
404
405
  async function fetchAndMount() {
405
406
  try {
406
407
  const res = await fetch("/api/session");
407
- if (!res.ok) throw new Error("Session fetch failed: " + res.status);
408
+ if (!res.ok) {
409
+ const body = await res.json().catch(() => ({}));
410
+ showSaveStatus(body.error || ("The session could not be loaded (HTTP " + res.status + ")."));
411
+ const root = document.getElementById("review-workbench");
412
+ if (root) root.innerHTML = "";
413
+ return;
414
+ }
408
415
  const data = await res.json();
409
- const { snapshot, events, revision } = data;
416
+ const { snapshot, events, revision, extractionImport } = data;
410
417
  const state = events && events.length > 0
411
418
  ? replayReviewSessionEvents(snapshot, events)
412
419
  : snapshot;
@@ -419,7 +426,7 @@ async function fetchAndMount() {
419
426
  const root = document.getElementById("review-workbench");
420
427
  if (!root) return;
421
428
 
422
- mountReviewWorkbench(root, state, { eventStore: createConsoleEventStore(revision) });
429
+ mountReviewWorkbench(root, state, { eventStore: createConsoleEventStore(revision), ...(extractionImport ? { extractionImport } : {}) });
423
430
  } catch (err) {
424
431
  console.error("[console] Mount error:", err);
425
432
  }
@@ -602,12 +609,22 @@ export async function startReviewConsoleServer(options) {
602
609
  // ---- Session read ----
603
610
  if (pathname === "/api/session" && req.method === "GET") {
604
611
  const content = await readSession(sessionPath);
612
+ // A queue that does not match the extraction import stored with it is
613
+ // not served for review; one without a stored import is served, and the
614
+ // workbench marks it unverified.
615
+ const attestation = attestReviewQueueExtraction(content.snapshot.items, content.extractionImport);
616
+ if (attestation.state === "diverges") {
617
+ sendJson(res, 409, { error: attestation.message, queueAttestation: attestation.state });
618
+ return;
619
+ }
605
620
  sendJson(res, 200, {
606
621
  session: content.session,
607
622
  snapshot: content.snapshot,
608
623
  events: content.events,
609
624
  revision: reviewSessionRevision(content.events),
610
625
  state: currentSessionState(content.snapshot, content.events),
626
+ queueAttestation: attestation.state,
627
+ ...(content.extractionImport !== undefined ? { extractionImport: content.extractionImport } : {}),
611
628
  });
612
629
  return;
613
630
  }
@@ -660,6 +677,7 @@ export async function startReviewConsoleServer(options) {
660
677
  record,
661
678
  events: nextEvents,
662
679
  requiredResolvedItems: "none",
680
+ extractionImport: content.extractionImport,
663
681
  });
664
682
  issueMessages = applyResult.ok
665
683
  ? []
@@ -672,7 +690,7 @@ export async function startReviewConsoleServer(options) {
672
690
  return { result: { status: 422, body: { error: `Validation failed: ${issueMessages.join("; ")}` } } };
673
691
  }
674
692
  return {
675
- next: { session: content.session, snapshot, events: nextEvents },
693
+ next: { ...content, session: content.session, snapshot, events: nextEvents },
676
694
  result: {
677
695
  status: 200,
678
696
  body: { ok: true, eventCount: nextEvents.length, revision: reviewSessionRevision(nextEvents) },
@@ -3,8 +3,8 @@ export { SURVEY_INPUT_CONTRACT_VERSION } from "./types.js";
3
3
  export { reviewResourceApiVersion } from "./review-resource.js";
4
4
  export { buildReviewItemsFromExtractionEnvelopeImport, createExtractionEnvelopeResolutionIdentity, exportExtractionEnvelopeImport, extractionEnvelopeImportApiVersion, importExtractionEnvelope, portableExtractionResultFormat, portableExtractionResultVersion, reimportExtractionEnvelope, validateExtractionEnvelopeImport, } from "./extraction-envelope.js";
5
5
  export { buildExtractionInspectorModel, exportExtractionInspector, inspectorSourcePosture, filterExtractionInspectorCandidates, } from "./review-workbench/extraction-inspector.js";
6
- export { assertReviewQueueAgainstExtractionImport, assertReviewQueueBinding, bindReviewQueue, hashReviewQueueSnapshot, UnattestedExtractionQueueError, UnattestedReviewQueueError, validateReviewQueueAgainstExtractionImport, validateReviewQueueBinding, } from "./review-workbench/queue-binding.js";
7
- export type { BindReviewQueueOptions, ReviewQueueBinding, ReviewQueueBindingIssue, ReviewQueueBindingIssueCode, ReviewQueueExtractionIssue, ReviewQueueExtractionIssueCode, ValidateReviewQueueBindingOptions, } from "./review-workbench/queue-binding.js";
6
+ export { assertReviewQueueAgainstExtractionImport, assertReviewQueueBinding, attestReviewQueueExtraction, bindReviewQueue, reviewItemCarriesExtractionBinding, unverifiedExtractionQueueSentence, hashReviewQueueSnapshot, UnattestedExtractionQueueError, UnattestedReviewQueueError, validateReviewQueueAgainstExtractionImport, validateReviewQueueBinding, } from "./review-workbench/queue-binding.js";
7
+ export type { BindReviewQueueOptions, ReviewQueueBinding, ReviewQueueBindingIssue, ReviewQueueBindingIssueCode, ReviewQueueExtractionAttestation, ReviewQueueExtractionIssue, ReviewQueueExtractionIssueCode, ValidateReviewQueueBindingOptions, } from "./review-workbench/queue-binding.js";
8
8
  export { resolvePortablePdfRegion } from "./pdf-layout.js";
9
9
  export type { PortablePdfBoundingBox, PortablePdfLayout, PortablePdfPageGeometry, PortablePdfRegionContext, PortablePdfTable, PortablePdfTableCell, PortablePdfTextElement, PortablePdfTextRange, } from "./pdf-layout.js";
10
10
  export type { ExtractionAlignmentState, ArtifactUnavailableCode, BuiltExtractionInspectorCandidate, BuiltExtractionInspectorModel, ExtractionInspectorCandidate, ExtractionInspectorEntry, ExtractionInspectorExportOptions, ExtractionInspectorFilters, ExtractionInspectorInput, ExtractionInspectorModel, ExtractionInspectorSource, ResolvedExtractionArtifact, } from "./review-workbench/extraction-inspector.js";
@@ -38,7 +38,7 @@ export type { SourceAuthorityClass, SourceAuthorityMetadata, SourceOfAuthorityOb
38
38
  export { buildCandidateVerification, candidateVerificationKind, DEFAULT_SUPPORT_VERIFIER_TIMEOUT_MS, MAX_SUPPORT_VERIFIER_TIMEOUT_MS, foldCandidateVerifications, runSupportVerifier, supportVerificationInputDigest, validateCandidateVerification, valueDigest, } from "./candidate-verification.js";
39
39
  export type { BuildCandidateVerificationInput, CandidateVerification, CandidateVerificationInapplicableReason, CandidateVerificationStatus, CandidateVerificationSubject, FoldCandidateVerificationsResult, RunSupportVerifierOptions, SupportAbstainReason, SupportEvidence, SupportVerdict, SupportVerificationInput, SupportVerificationMethod, SupportVerificationResult, SupportVerifier, } from "./candidate-verification.js";
40
40
  export { buildInterpretationReadingPresentation, buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, candidateVerificationNotes, humanizeIdentifier, } from "./review-workbench/review-presentation.js";
41
- export type { CandidateVerificationNote, CandidateVerificationRecordPresentation, InterpretationReadingPresentation, InterpretationReadingSource, ReviewCandidatePresentation, ReviewCandidatePresentationContext, ReviewItemPresentation, ReviewItemPresentationContext, ReviewPresentationAdapter, ReviewPresentationLink, ReviewResultPresentation, ReviewTracePresentationContext, ReviewTraceRef, ReviewValuePresentationContext, } from "./review-workbench/review-presentation.js";
41
+ export type { CandidateVerificationNote, CandidateVerificationRecordPresentation, ExcludedProposalPresentation, ExcludedProposalsUnreadable, InterpretationReadingPresentation, InterpretationReadingSource, ReviewCandidatePresentation, ReviewCandidatePresentationContext, ReviewItemPresentation, ReviewItemPresentationContext, ReviewPresentationAdapter, ReviewPresentationLink, ReviewResultPresentation, ReviewTracePresentationContext, ReviewTraceRef, ReviewValuePresentationContext, } from "./review-workbench/review-presentation.js";
42
42
  export { apiRecordSource, manualEntrySource, policyStandardSource, uploadedDocumentSource, webPageSource, } from "./raw-source.js";
43
43
  export type { ApiRecordSourceInput, ChecksumInput, ManualEntrySourceInput, PolicyStandardMetadata, PolicyStandardSourceInput, RawSourceInput, UploadedDocumentSourceInput, WebPageSourceInput, } from "./raw-source.js";
44
44
  export { applyAutoAcceptPolicy, applyMappingReview, buildMappingReviewItems, lookupMapping, lookupRejectedMapping, normalizeQuestion, proposalsToCandidateSet, referenceMappingProposer, resolveQuestion, } from "./inquiry-mapping.js";
package/dist/src/index.js CHANGED
@@ -2,7 +2,7 @@ export { SURVEY_INPUT_CONTRACT_VERSION } from "./types.js";
2
2
  export { reviewResourceApiVersion } from "./review-resource.js";
3
3
  export { buildReviewItemsFromExtractionEnvelopeImport, createExtractionEnvelopeResolutionIdentity, exportExtractionEnvelopeImport, extractionEnvelopeImportApiVersion, importExtractionEnvelope, portableExtractionResultFormat, portableExtractionResultVersion, reimportExtractionEnvelope, validateExtractionEnvelopeImport, } from "./extraction-envelope.js";
4
4
  export { buildExtractionInspectorModel, exportExtractionInspector, inspectorSourcePosture, filterExtractionInspectorCandidates, } from "./review-workbench/extraction-inspector.js";
5
- export { assertReviewQueueAgainstExtractionImport, assertReviewQueueBinding, bindReviewQueue, hashReviewQueueSnapshot, UnattestedExtractionQueueError, UnattestedReviewQueueError, validateReviewQueueAgainstExtractionImport, validateReviewQueueBinding, } from "./review-workbench/queue-binding.js";
5
+ export { assertReviewQueueAgainstExtractionImport, assertReviewQueueBinding, attestReviewQueueExtraction, bindReviewQueue, reviewItemCarriesExtractionBinding, unverifiedExtractionQueueSentence, hashReviewQueueSnapshot, UnattestedExtractionQueueError, UnattestedReviewQueueError, validateReviewQueueAgainstExtractionImport, validateReviewQueueBinding, } from "./review-workbench/queue-binding.js";
6
6
  export { resolvePortablePdfRegion } from "./pdf-layout.js";
7
7
  export { toSurfaceReviewedExtractionDecision, toSurfaceReviewedExtractionImport, toSurfaceReviewedExtractionItem, } from "./surface-reviewed-extraction.js";
8
8
  export { candidateReviewRecord, candidateSetStatusFor, SurveyInputBuilder } from "./builder.js";