@kontourai/survey 4.0.0 → 6.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.
- package/dist/examples/review-workbench/server-apply-consumer.js +5 -0
- package/dist/src/canonical-reviewed-trust-input.js +73 -34
- package/dist/src/extraction-envelope.d.ts +125 -3
- package/dist/src/extraction-envelope.js +332 -25
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +1 -1
- package/dist/src/mcp/review-mcp.js +62 -25
- package/dist/src/review-workbench/audit-rows.d.ts +1 -1
- package/dist/src/review-workbench/audit-rows.js +1 -0
- package/dist/src/review-workbench/extraction-inspector.d.ts +31 -13
- package/dist/src/review-workbench/extraction-inspector.js +77 -47
- package/dist/src/review-workbench/queue-binding.js +1 -1
- package/dist/src/review-workbench/review-presentation.d.ts +26 -1
- package/dist/src/review-workbench/review-presentation.js +75 -1
- package/dist/src/review-workbench/review-queue-session.d.ts +19 -5
- package/dist/src/review-workbench/review-queue-session.js +46 -13
- package/dist/src/review-workbench/review-session-replay.js +5 -3
- package/dist/src/review-workbench/review-workbench-css.generated.js +10 -1
- package/dist/src/review-workbench/review-workbench.css +10 -1
- package/dist/src/review-workbench/review-workbench.d.ts +14 -7
- package/dist/src/review-workbench/review-workbench.js +111 -20
- package/dist/src/review-workbench/review-workbench.standalone.css +10 -1
- package/dist/src/surface-reviewed-extraction.js +4 -0
- package/dist/src/to-surface.js +116 -0
- package/dist/src/types.d.ts +5 -1
- package/package.json +1 -1
|
@@ -24,6 +24,11 @@ export function prepareFacilityCredentialServerApply(input) {
|
|
|
24
24
|
if (!currentCandidate || JSON.stringify(currentCandidate.value) !== JSON.stringify(input.currentRecord.credential)) {
|
|
25
25
|
return { ok: false, message: "Current credential no longer matches the review session snapshot." };
|
|
26
26
|
}
|
|
27
|
+
// A decision that selects no candidate (reject-all or could-not-confirm on a
|
|
28
|
+
// conflict) has no value to apply.
|
|
29
|
+
if (result.selectedCandidateId === undefined) {
|
|
30
|
+
return { ok: false, message: "Review result selects no candidate, so there is nothing to apply." };
|
|
31
|
+
}
|
|
27
32
|
if (input.currentRecord.appliedReviewItemNames.includes(result.reviewItemName)) {
|
|
28
33
|
return { ok: false, message: "Review result was already applied." };
|
|
29
34
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SURVEY_INPUT_CONTRACT_VERSION } from "./types.js";
|
|
2
2
|
import { canonicalJson } from "./review-workbench/canonical.js";
|
|
3
|
-
import { workbenchDecisionDefinitions } from "./review-workbench/review-queue-session.js";
|
|
3
|
+
import { decisionSelectsNoCandidate, workbenchDecisionDefinitions } from "./review-workbench/review-queue-session.js";
|
|
4
4
|
/**
|
|
5
5
|
* Projects server-applied review records into the complete SurveyInput consumed
|
|
6
6
|
* by buildSurveyTrustBundle. The ReviewItem and ReviewWorkbenchResult are the
|
|
@@ -33,15 +33,24 @@ export function buildCanonicalReviewedTrustInput(options) {
|
|
|
33
33
|
throw new Error(`ReviewItem ${item.metadata.name} has no canonical server-applied result.`);
|
|
34
34
|
}
|
|
35
35
|
assertCanonicalResult(item, result);
|
|
36
|
+
// Reject-all or could-not-confirm on a conflict selects no candidate: the
|
|
37
|
+
// set, the review outcome and the claim then name none of its values.
|
|
38
|
+
const selectsNone = decisionSelectsNoCandidate(item, result.decision);
|
|
39
|
+
const rejectsAll = selectsNone && result.decision === "reject-proposed";
|
|
40
|
+
const rejectedRole = workbenchDecisionDefinitions[result.decision].candidateRole;
|
|
41
|
+
const rejectAllReason = result.rationale?.trim() || "Every proposed value for this claim was rejected.";
|
|
36
42
|
const candidates = item.spec.candidates.map((candidate) => {
|
|
37
|
-
const records = projectCandidate(item, candidate
|
|
43
|
+
const records = projectCandidate(item, rejectsAll && candidate.role === rejectedRole
|
|
44
|
+
? { ...candidate, rejectionReason: candidate.rejectionReason ?? rejectAllReason }
|
|
45
|
+
: candidate);
|
|
38
46
|
addConsistent(rawSources, records.rawSource, "raw source");
|
|
39
47
|
addConsistent(extractions, records.extraction, "extraction");
|
|
40
48
|
return records.candidate;
|
|
41
49
|
});
|
|
42
|
-
const selected = item.spec.candidates.find((candidate) => candidate.id === result.selectedCandidateId);
|
|
43
|
-
const selectedRecordId = selected.projection?.candidateId ?? selected.id;
|
|
44
|
-
|
|
50
|
+
const selected = selectsNone ? undefined : item.spec.candidates.find((candidate) => candidate.id === result.selectedCandidateId);
|
|
51
|
+
const selectedRecordId = selected ? selected.projection?.candidateId ?? selected.id : undefined;
|
|
52
|
+
// Every candidate carries the same candidate-set id (checked below).
|
|
53
|
+
const candidateSetId = (selected ?? item.spec.candidates[0])?.projection?.candidateSetId
|
|
45
54
|
?? item.spec.projection?.candidateSetId
|
|
46
55
|
?? `${item.metadata.name}.candidates`;
|
|
47
56
|
if (candidates.some((candidate) => candidate.metadata?.candidateSetId !== candidateSetId)) {
|
|
@@ -56,10 +65,10 @@ export function buildCanonicalReviewedTrustInput(options) {
|
|
|
56
65
|
? { metadata: Object.fromEntries(Object.entries(metadata).filter(([key]) => key !== "candidateSetId")) }
|
|
57
66
|
: {}),
|
|
58
67
|
})),
|
|
59
|
-
selectedCandidateId: selectedRecordId,
|
|
68
|
+
...(selectedRecordId !== undefined ? { selectedCandidateId: selectedRecordId } : {}),
|
|
60
69
|
status: result.decision === "could-not-confirm"
|
|
61
70
|
? (item.spec.candidateSetStatus ?? "needs-review")
|
|
62
|
-
: "resolved",
|
|
71
|
+
: rejectsAll ? "rejected" : "resolved",
|
|
63
72
|
...(result.rationale ?? item.spec.rationale
|
|
64
73
|
? { rationale: result.rationale ?? item.spec.rationale }
|
|
65
74
|
: {}),
|
|
@@ -67,12 +76,12 @@ export function buildCanonicalReviewedTrustInput(options) {
|
|
|
67
76
|
addConsistent(candidateSets, candidateSet, "candidate set");
|
|
68
77
|
const decision = result.reviewDecision.spec;
|
|
69
78
|
const reviewOutcomeId = decision.projection?.reviewOutcomeId
|
|
70
|
-
?? selected.projection?.reviewOutcomeId
|
|
79
|
+
?? (selected ?? item.spec).projection?.reviewOutcomeId
|
|
71
80
|
?? `${item.metadata.name}.${result.decision}.review-outcome`;
|
|
72
81
|
const reviewOutcome = {
|
|
73
82
|
id: reviewOutcomeId,
|
|
74
83
|
candidateSetId,
|
|
75
|
-
candidateId: selectedRecordId,
|
|
84
|
+
...(selectedRecordId !== undefined ? { candidateId: selectedRecordId } : {}),
|
|
76
85
|
status: result.status,
|
|
77
86
|
...(decision.resolution ? { resolution: decision.resolution } : {}),
|
|
78
87
|
...(decision.resolutionReason ? { resolutionReason: decision.resolutionReason } : {}),
|
|
@@ -90,34 +99,42 @@ export function buildCanonicalReviewedTrustInput(options) {
|
|
|
90
99
|
},
|
|
91
100
|
};
|
|
92
101
|
addConsistent(reviewOutcomes, reviewOutcome, "review outcome");
|
|
93
|
-
|
|
102
|
+
// Every candidate names the same claim target (checked by assertCanonicalResult).
|
|
103
|
+
const hint = (selected ?? item.spec.candidates[0]).claimTarget;
|
|
94
104
|
assertSingleProjectionId("claim", item.metadata.name, [
|
|
95
105
|
decision.projection?.claimId,
|
|
96
106
|
item.spec.projection?.claimId,
|
|
97
107
|
...item.spec.candidates.flatMap((candidate) => [candidate.projection?.claimId, candidate.claimTarget.claimId]),
|
|
98
108
|
]);
|
|
99
109
|
const claimId = decision.projection?.claimId
|
|
100
|
-
?? selected
|
|
110
|
+
?? selected?.projection?.claimId
|
|
101
111
|
?? item.spec.projection?.claimId
|
|
102
112
|
?? hint.claimId
|
|
103
113
|
?? `${item.metadata.name}.claim`;
|
|
104
114
|
const claim = {
|
|
105
115
|
id: claimId,
|
|
106
116
|
candidateSetId,
|
|
107
|
-
candidateId: selectedRecordId,
|
|
117
|
+
...(selectedRecordId !== undefined ? { candidateId: selectedRecordId } : {}),
|
|
108
118
|
subjectType: hint.subjectType,
|
|
109
119
|
subjectId: hint.subjectId,
|
|
110
120
|
facet: hint.facet,
|
|
111
121
|
claimType: hint.claimType,
|
|
112
122
|
fieldOrBehavior: hint.fieldOrBehavior,
|
|
113
|
-
value
|
|
114
|
-
|
|
123
|
+
// Surface requires a claim value; a claim that selects none of its
|
|
124
|
+
// candidates carries null, and buildSurveyTrustBundle lists every value.
|
|
125
|
+
value: selectsNone ? null : result.effectiveValue,
|
|
126
|
+
// Could-not-confirm keeps the pre-review posture, and a conflicting or
|
|
127
|
+
// escalated candidate set is disputed before any review (see
|
|
128
|
+
// docs/decisions/could-not-confirm.md), never merely proposed.
|
|
129
|
+
status: result.decision === "could-not-confirm" && (candidateSet.status === "conflict" || candidateSet.status === "escalated")
|
|
130
|
+
? "disputed"
|
|
131
|
+
: result.status,
|
|
115
132
|
impactLevel: hint.impactLevel,
|
|
116
133
|
updatedAt: decision.reviewedAt ?? options.generatedAt,
|
|
117
134
|
...(hint.evidenceType ? { evidenceType: hint.evidenceType } : {}),
|
|
118
135
|
...(hint.evidenceMethod ? { evidenceMethod: hint.evidenceMethod } : {}),
|
|
119
136
|
...(hint.derivedFrom ? { derivedFrom: [...hint.derivedFrom] } : {}),
|
|
120
|
-
collectedBy: hint.collectedBy ?? selected.
|
|
137
|
+
collectedBy: hint.collectedBy ?? sharedExtractor(selected ? [selected] : item.spec.candidates) ?? options.source,
|
|
121
138
|
...(decision.actor?.id ? { actor: decision.actor.id } : {}),
|
|
122
139
|
};
|
|
123
140
|
addConsistent(claims, claim, "claim target");
|
|
@@ -137,24 +154,38 @@ export function buildCanonicalReviewedTrustInput(options) {
|
|
|
137
154
|
};
|
|
138
155
|
}
|
|
139
156
|
function assertCanonicalResult(item, result) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
157
|
+
const selectsNone = decisionSelectsNoCandidate(item, result.decision);
|
|
158
|
+
let selected;
|
|
159
|
+
if (selectsNone) {
|
|
160
|
+
if (result.selectedCandidate !== undefined || result.selectedCandidateId !== undefined || result.selectedCandidateRole !== undefined
|
|
161
|
+
|| result.selectedValue !== undefined || result.selectedDisplayValue !== undefined
|
|
162
|
+
|| result.effectiveValue !== undefined || result.effectiveDisplayValue !== undefined || result.editedValue !== undefined) {
|
|
163
|
+
throw new Error(`Review result ${result.reviewItemName} names a selected value, but its ${result.decision} decision selects no candidate.`);
|
|
164
|
+
}
|
|
165
|
+
if (canonicalJson(item.spec.candidates) !== canonicalJson(result.unselectedCandidates)) {
|
|
166
|
+
throw new Error(`Review result ${result.reviewItemName} unselected candidates do not match its canonical ReviewItem.`);
|
|
167
|
+
}
|
|
150
168
|
}
|
|
151
|
-
|
|
152
|
-
|
|
169
|
+
else {
|
|
170
|
+
selected = item.spec.candidates.find((candidate) => candidate.id === result.selectedCandidateId);
|
|
171
|
+
if (!selected) {
|
|
172
|
+
throw new Error(`Review result ${result.reviewItemName} selects an unknown candidate.`);
|
|
173
|
+
}
|
|
174
|
+
if (canonicalJson(selected) !== canonicalJson(result.selectedCandidate)) {
|
|
175
|
+
throw new Error(`Review result ${result.reviewItemName} selected candidate does not match its canonical ReviewItem.`);
|
|
176
|
+
}
|
|
177
|
+
const unselected = item.spec.candidates.filter((candidate) => candidate.id !== selected.id);
|
|
178
|
+
if (canonicalJson(unselected) !== canonicalJson(result.unselectedCandidates)) {
|
|
179
|
+
throw new Error(`Review result ${result.reviewItemName} unselected candidates do not match its canonical ReviewItem.`);
|
|
180
|
+
}
|
|
181
|
+
if (result.selectedCandidateRole !== selected.role || canonicalJson(result.selectedValue) !== canonicalJson(selected.value)) {
|
|
182
|
+
throw new Error(`Review result ${result.reviewItemName} selected identity does not match its canonical ReviewItem.`);
|
|
183
|
+
}
|
|
153
184
|
}
|
|
154
185
|
const decision = result.reviewDecision.spec;
|
|
155
186
|
const definition = workbenchDecisionDefinitions[result.decision];
|
|
156
187
|
if (decision.reviewItemName !== item.metadata.name
|
|
157
|
-
|| decision.candidateId !==
|
|
188
|
+
|| decision.candidateId !== selected?.id
|
|
158
189
|
|| decision.status !== result.status
|
|
159
190
|
|| decision.status !== definition.status
|
|
160
191
|
|| decision.rationale !== result.rationale
|
|
@@ -164,14 +195,17 @@ function assertCanonicalResult(item, result) {
|
|
|
164
195
|
if ((result.decision === "could-not-confirm") !== (decision.resolution === "could_not_confirm")) {
|
|
165
196
|
throw new Error(`Review result ${result.reviewItemName} contradicts its canonical review resolution.`);
|
|
166
197
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
198
|
+
if (selected) {
|
|
199
|
+
const expectedEffective = result.editedValue !== undefined && result.decision === "accept-proposed"
|
|
200
|
+
? result.editedValue
|
|
201
|
+
: selected.value;
|
|
202
|
+
if (canonicalJson(expectedEffective) !== canonicalJson(result.effectiveValue)) {
|
|
203
|
+
throw new Error(`Review result ${result.reviewItemName} effective value is not canonical.`);
|
|
204
|
+
}
|
|
172
205
|
}
|
|
206
|
+
const reference = selected ?? item.spec.candidates[0];
|
|
173
207
|
for (const candidate of item.spec.candidates) {
|
|
174
|
-
if (canonicalJson(claimTargetIdentity(candidate.claimTarget)) !== canonicalJson(claimTargetIdentity(
|
|
208
|
+
if (canonicalJson(claimTargetIdentity(candidate.claimTarget)) !== canonicalJson(claimTargetIdentity(reference.claimTarget))) {
|
|
175
209
|
throw new Error(`ReviewItem ${item.metadata.name} candidates carry conflicting claim targets.`);
|
|
176
210
|
}
|
|
177
211
|
}
|
|
@@ -263,3 +297,8 @@ function assertSingleProjectionId(label, itemName, values) {
|
|
|
263
297
|
throw new Error(`ReviewItem ${itemName} carries conflicting ${label} projection ids.`);
|
|
264
298
|
}
|
|
265
299
|
}
|
|
300
|
+
/** The extractor every candidate shares, or undefined when they differ. */
|
|
301
|
+
function sharedExtractor(candidates) {
|
|
302
|
+
const extractors = new Set(candidates.map((candidate) => candidate.extraction.extractor));
|
|
303
|
+
return extractors.size === 1 ? [...extractors][0] : undefined;
|
|
304
|
+
}
|
|
@@ -38,7 +38,12 @@ export interface PortableExtractionEvidenceMatch {
|
|
|
38
38
|
export interface PortableExtractionProposal {
|
|
39
39
|
fieldPath: string;
|
|
40
40
|
candidateValue: unknown;
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The proposer's own uncalibrated self-report in `0..1`, absent when the
|
|
43
|
+
* proposer reported none. Survey carries it only when present and never
|
|
44
|
+
* substitutes a default.
|
|
45
|
+
*/
|
|
46
|
+
confidence?: number;
|
|
42
47
|
provenance: {
|
|
43
48
|
excerpt: string;
|
|
44
49
|
locator: string;
|
|
@@ -71,6 +76,25 @@ export type PortablePreparedArtifactState = {
|
|
|
71
76
|
actualDigest: string;
|
|
72
77
|
actualContentLength: number;
|
|
73
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* Why a run stopped short. The first four are early stops; the last three mean
|
|
81
|
+
* a dispatched chunk was not (fully) read or answered, and such an envelope
|
|
82
|
+
* names the affected ranges in `result.coverage`.
|
|
83
|
+
*/
|
|
84
|
+
export type PortableExtractionPartialReason = "cancelled" | "max-provider-calls" | "max-total-tokens" | "max-chunks" | "provider-failure" | "content-truncated" | "output-truncated";
|
|
85
|
+
/**
|
|
86
|
+
* One prepared-text range (UTF-16 offsets, the space of `chars:` locators) and
|
|
87
|
+
* whether it was read and answered. `reason` is present exactly when `status`
|
|
88
|
+
* is `unread`, and then covers exactly the unread span.
|
|
89
|
+
*/
|
|
90
|
+
export interface PortableExtractionCoverageEntry {
|
|
91
|
+
/** 1-based chunk number. */
|
|
92
|
+
chunk: number;
|
|
93
|
+
start: number;
|
|
94
|
+
end: number;
|
|
95
|
+
status: "complete" | "unread" | "output-truncated";
|
|
96
|
+
reason?: "provider-failure" | "content-truncated" | "missing-tool-call" | "not-dispatched";
|
|
97
|
+
}
|
|
74
98
|
export interface PortableExtractionResultEnvelope {
|
|
75
99
|
format: typeof portableExtractionResultFormat;
|
|
76
100
|
version: typeof portableExtractionResultVersion;
|
|
@@ -90,7 +114,7 @@ export interface PortableExtractionResultEnvelope {
|
|
|
90
114
|
status: "success";
|
|
91
115
|
} | {
|
|
92
116
|
status: "partial";
|
|
93
|
-
reason:
|
|
117
|
+
reason: PortableExtractionPartialReason;
|
|
94
118
|
} | {
|
|
95
119
|
status: "failure";
|
|
96
120
|
category: "invalid-config" | "invalid-task" | "preparation" | "provider" | "unexpected";
|
|
@@ -104,11 +128,13 @@ export interface PortableExtractionResultEnvelope {
|
|
|
104
128
|
providerCalls: number;
|
|
105
129
|
totalTokensUsed: number;
|
|
106
130
|
partial?: {
|
|
107
|
-
reason:
|
|
131
|
+
reason: PortableExtractionPartialReason;
|
|
108
132
|
completedChunks: number;
|
|
109
133
|
remainingChunks: number;
|
|
110
134
|
tokenOvershoot?: number;
|
|
111
135
|
};
|
|
136
|
+
/** Per-chunk read coverage, ordered by `start`; ranges may overlap. Requires `preparedArtifact`. */
|
|
137
|
+
coverage?: PortableExtractionCoverageEntry[];
|
|
112
138
|
/** `code` is the upstream error code, informational only; `kind` stays authoritative. */
|
|
113
139
|
providerFailures?: Array<{
|
|
114
140
|
provider: string;
|
|
@@ -142,11 +168,52 @@ export interface ExtractionEnvelopeImportOptions {
|
|
|
142
168
|
sourceKind: RawSource["kind"];
|
|
143
169
|
/** Survey meaning is supplied at the boundary; it is not added to the upstream wire contract. */
|
|
144
170
|
claimTarget: (proposal: PortableExtractionProposal, index: number) => ClaimTargetHint;
|
|
171
|
+
/**
|
|
172
|
+
* The prepared artifact as the caller resolved it (the same shape the source
|
|
173
|
+
* inspector takes). When supplied, the import checks the text against the
|
|
174
|
+
* envelope's prepared-artifact digest and length, and every proposal's span
|
|
175
|
+
* against its excerpt: a proposal whose span does not match gets no
|
|
176
|
+
* ReviewItem and an `excerpt-mismatch` diagnostic, and an artifact that does
|
|
177
|
+
* not verify makes the import `unresolved`. The text is not stored in the
|
|
178
|
+
* record. Without it the import records `status.provenance: "unverified"`.
|
|
179
|
+
*/
|
|
180
|
+
artifact?: ResolvedExtractionArtifact;
|
|
145
181
|
}
|
|
182
|
+
export type ArtifactUnavailableCode = "not-found" | "storage-error" | "access-denied" | "invalid-artifact" | "unknown";
|
|
183
|
+
/** A prepared artifact as a caller resolved it: its text and digest, or why it could not be read. */
|
|
184
|
+
export type ResolvedExtractionArtifact = {
|
|
185
|
+
status: "available";
|
|
186
|
+
text: string;
|
|
187
|
+
actualDigest: string;
|
|
188
|
+
} | {
|
|
189
|
+
status: "unavailable";
|
|
190
|
+
code: ArtifactUnavailableCode;
|
|
191
|
+
} | {
|
|
192
|
+
status: "digest-mismatch";
|
|
193
|
+
actualDigest: string;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Whether the import checked its proposals' excerpts against the prepared
|
|
197
|
+
* artifact text. `verified` means the supplied text matched the envelope's
|
|
198
|
+
* digest and length and every proposal that produced a ReviewItem matched its
|
|
199
|
+
* span; `unverified` means no text was supplied, or the text did not verify
|
|
200
|
+
* (a diagnostic then says why). Records written before this field existed have
|
|
201
|
+
* no `provenance` and are unverified.
|
|
202
|
+
*/
|
|
203
|
+
export type ExtractionEnvelopeImportProvenance = "verified" | "unverified";
|
|
204
|
+
/**
|
|
205
|
+
* Why an import is `unresolved`: its prepared artifact did not resolve, or the
|
|
206
|
+
* extraction itself produced nothing reviewable. `extraction-failed` is a
|
|
207
|
+
* failure outcome; `extraction-incomplete` is a partial outcome that proposed
|
|
208
|
+
* nothing, so no candidate can carry the reason. Either way the import is not
|
|
209
|
+
* a complete run that found no values.
|
|
210
|
+
*/
|
|
146
211
|
export type ExtractionEnvelopeImportDiagnostic = {
|
|
147
212
|
kind: "artifact-unavailable";
|
|
148
213
|
status: "unavailable" | "storage-error" | "identity-mismatch" | "invalid-artifact";
|
|
149
214
|
artifactRef?: string;
|
|
215
|
+
/** The caller's resolver code, present when the artifact supplied at import could not be read. */
|
|
216
|
+
code?: ArtifactUnavailableCode;
|
|
150
217
|
message: string;
|
|
151
218
|
} | {
|
|
152
219
|
kind: "digest-mismatch";
|
|
@@ -154,6 +221,22 @@ export type ExtractionEnvelopeImportDiagnostic = {
|
|
|
154
221
|
expectedDigest: string;
|
|
155
222
|
actualDigest: string;
|
|
156
223
|
message: string;
|
|
224
|
+
} | {
|
|
225
|
+
kind: "extraction-failed";
|
|
226
|
+
category: string;
|
|
227
|
+
code: string;
|
|
228
|
+
message: string;
|
|
229
|
+
} | {
|
|
230
|
+
kind: "extraction-incomplete";
|
|
231
|
+
reason: PortableExtractionPartialReason;
|
|
232
|
+
message: string;
|
|
233
|
+
}
|
|
234
|
+
/** The prepared text at this proposal's span is not its excerpt; the proposal produced no ReviewItem. */
|
|
235
|
+
| {
|
|
236
|
+
kind: "excerpt-mismatch";
|
|
237
|
+
proposalIndex: number;
|
|
238
|
+
locator: string;
|
|
239
|
+
message: string;
|
|
157
240
|
};
|
|
158
241
|
export interface ExtractionEnvelopeImport {
|
|
159
242
|
apiVersion: typeof extractionEnvelopeImportApiVersion;
|
|
@@ -170,6 +253,7 @@ export interface ExtractionEnvelopeImport {
|
|
|
170
253
|
status: {
|
|
171
254
|
state: "grounded" | "unresolved";
|
|
172
255
|
diagnostics: ExtractionEnvelopeImportDiagnostic[];
|
|
256
|
+
provenance?: ExtractionEnvelopeImportProvenance;
|
|
173
257
|
};
|
|
174
258
|
}
|
|
175
259
|
export interface ExtractionEnvelopeImportResult {
|
|
@@ -182,9 +266,47 @@ export interface ExtractionEnvelopeResolutionIdentity {
|
|
|
182
266
|
}
|
|
183
267
|
/** Parse an untrusted upstream document and create Survey's durable import projection. */
|
|
184
268
|
export declare function importExtractionEnvelope(serialized: string | PortableExtractionResultEnvelope, options: ExtractionEnvelopeImportOptions): ExtractionEnvelopeImportResult;
|
|
269
|
+
/**
|
|
270
|
+
* One ReviewItem per claim slot: every proposal whose claim target names the
|
|
271
|
+
* same claim (subject, facet, claim type, field or behavior, and claim id when
|
|
272
|
+
* one is set) at the same `pathIndices` is one candidate set, so two values for
|
|
273
|
+
* one claim can never be accepted as two separate verified claims. The slot is
|
|
274
|
+
* the claim the proposal would project to, not the upstream `fieldPath` (two
|
|
275
|
+
* field paths mapped to one claim are one slot) and not the value.
|
|
276
|
+
*
|
|
277
|
+
* Within a slot there is one candidate per distinct canonical value; further
|
|
278
|
+
* proposals of an already-seen value are recorded on that candidate as
|
|
279
|
+
* `sameValueProposals`. Two or more distinct values make the set `conflict`.
|
|
280
|
+
* Distinct `pathIndices` (array items) or distinct claim ids are separate slots,
|
|
281
|
+
* which is how a producer declares a multi-valued field.
|
|
282
|
+
*/
|
|
185
283
|
export declare function buildReviewItemsFromExtractionEnvelopeImport(record: ExtractionEnvelopeImport): ReviewItem[];
|
|
186
284
|
export declare function exportExtractionEnvelopeImport(record: ExtractionEnvelopeImport): string;
|
|
187
285
|
export declare function reimportExtractionEnvelope(serialized: string): ExtractionEnvelopeImport;
|
|
188
286
|
export declare function createExtractionEnvelopeResolutionIdentity(record: ExtractionEnvelopeImport, proposalIndex: number): ExtractionEnvelopeResolutionIdentity;
|
|
287
|
+
/** Proposals the import excluded because their span did not match their excerpt. */
|
|
288
|
+
export declare function excerptMismatchProposalIndices(record: ExtractionEnvelopeImport): Set<number>;
|
|
289
|
+
/** Validate the caller-resolved artifact shape; resolver detail beyond the code is refused. */
|
|
290
|
+
export declare function assertResolvedExtractionArtifact(artifact: unknown): asserts artifact is ResolvedExtractionArtifact;
|
|
291
|
+
export type PreparedArtifactCheck = {
|
|
292
|
+
status: "aligned";
|
|
293
|
+
text: string;
|
|
294
|
+
} | {
|
|
295
|
+
status: "unavailable";
|
|
296
|
+
code: ArtifactUnavailableCode;
|
|
297
|
+
} | {
|
|
298
|
+
status: "digest-mismatch";
|
|
299
|
+
actualDigest: string;
|
|
300
|
+
} | {
|
|
301
|
+
status: "wrong-length";
|
|
302
|
+
};
|
|
303
|
+
/**
|
|
304
|
+
* Checks resolved text against the envelope's prepared-artifact identity: the
|
|
305
|
+
* caller's reported digest, the digest of the text itself, and the declared
|
|
306
|
+
* length. Shared by import-time verification and the source inspector.
|
|
307
|
+
*/
|
|
308
|
+
export declare function checkPreparedArtifact(prepared: PortableExtractionResultEnvelope["result"]["preparedArtifact"], artifact: ResolvedExtractionArtifact): PreparedArtifactCheck;
|
|
309
|
+
/** Whether the verified prepared text at a proposal's `chars:` span is exactly its excerpt. */
|
|
310
|
+
export declare function proposalSpanMatches(text: string, proposal: PortableExtractionProposal): boolean;
|
|
189
311
|
/** Validate and defensively clone a persisted or browser-delivered import. */
|
|
190
312
|
export declare function validateExtractionEnvelopeImport(value: unknown): ExtractionEnvelopeImport;
|