@kontourai/survey 1.18.1 → 2.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/README.md +1 -1
- package/dist/src/agent-utterance.js +4 -5
- package/dist/src/canonical-reviewed-trust-input.d.ts +31 -0
- package/dist/src/canonical-reviewed-trust-input.js +265 -0
- package/dist/src/extraction-improvement-proposal.d.ts +148 -0
- package/dist/src/extraction-improvement-proposal.js +396 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +2 -0
- package/dist/src/to-surface.d.ts +11 -0
- package/dist/src/to-surface.js +27 -6
- package/package.json +3 -11
- package/dist/src/anthropic.d.ts +0 -104
- package/dist/src/anthropic.js +0 -383
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ One observation, one chain: the page it came from, what the extractor read, who
|
|
|
103
103
|
|
|
104
104
|
Keep producer operational state outside Survey. Queue status, reviewer form state, retries, source caches, and product policy decisions belong in the producer's own data model. Survey carries only the portable evidence chain records needed by Surface.
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
Model-backed producers implement `MappingProposer` or `UtteranceClaimExtractor` in the product that owns the prompt, parsing, runtime policy, and domain mapping. Survey accepts their normalized proposals through the same framework-neutral interfaces and review path; it does not acquire credentials or depend on an AI runtime.
|
|
107
107
|
|
|
108
108
|
When you build an `authorized-action` authorizing block outside the workbench, pair `buildAuthorizedActionAuthorizing` with `buildPromptRef({ module, component, version?, scheme? })` — `buildPromptRef` formats a well-formed, versioned `promptRef` (bare `"review-workbench/decision-card@v1"` or scheme-prefixed `"survey://<module>/<component>@v1"`) that `buildAuthorizedActionAuthorizing` accepts directly, instead of hand-formatting the string.
|
|
109
109
|
|
|
@@ -22,17 +22,16 @@ import { projectProposalsToCandidateSet } from "./producer-profile.js";
|
|
|
22
22
|
/**
|
|
23
23
|
* The Candidate Conflict comparison key for an utterance-proposed value.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
* one in `./anthropic.js`) normalizes `value` before it reaches
|
|
25
|
+
* Extractors do not normalize `value` before it reaches
|
|
27
26
|
* `ExtractedStatement` — case and internal formatting are preserved
|
|
28
27
|
* verbatim. String values are the only case where "representation noise"
|
|
29
28
|
* (leading/trailing whitespace from excerpt boundaries, incidental case
|
|
30
|
-
* differences like "Healthy" vs "healthy") is plausible
|
|
31
|
-
*
|
|
29
|
+
* differences like "Healthy" vs "healthy") is plausible in producer output,
|
|
30
|
+
* so this key trims + lowercases STRING values
|
|
32
31
|
* in the COMPARISON KEY ONLY — the stored `Candidate.value`/`Extraction.value`
|
|
33
32
|
* stay byte-for-byte verbatim; this function only feeds `equivalenceKey`,
|
|
34
33
|
* never `value`. Non-string values (number, boolean, null — the other types
|
|
35
|
-
* the
|
|
34
|
+
* supported by the portable record contract) compare via exact canonical
|
|
36
35
|
* `JSON.stringify`, so there is no cross-type coercion that could silently
|
|
37
36
|
* equate e.g. "5" and 5, or lose a genuine numeric disagreement (5 vs 6 is
|
|
38
37
|
* never noise). This mirrors the Producer Profile core's established
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SurveyInput } from "./types.js";
|
|
2
|
+
import type { ReviewItem } from "./review-resource.js";
|
|
3
|
+
import type { ReviewWorkbenchResult } from "./review-workbench/review-workbench.js";
|
|
4
|
+
export interface BuildCanonicalReviewedTrustInputOptions {
|
|
5
|
+
/** Producer identity for the resulting SurveyInput batch. */
|
|
6
|
+
readonly source: string;
|
|
7
|
+
/** Server-controlled projection time. */
|
|
8
|
+
readonly generatedAt: string;
|
|
9
|
+
/** Stable producer-owned identity for this review projection. */
|
|
10
|
+
readonly projectionContextId: string;
|
|
11
|
+
/** Canonical ReviewItems from the server-owned pre-decision snapshot. */
|
|
12
|
+
readonly items: readonly ReviewItem[];
|
|
13
|
+
/** Results derived by Survey's server apply boundary from snapshot + events. */
|
|
14
|
+
readonly results: readonly ReviewWorkbenchResult[];
|
|
15
|
+
}
|
|
16
|
+
export interface CanonicalReviewedTrustInput {
|
|
17
|
+
readonly surveyInput: SurveyInput;
|
|
18
|
+
/** Pass unchanged to buildSurveyTrustBundle's projectionContextId option. */
|
|
19
|
+
readonly projectionContextId: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Projects server-applied review records into the complete SurveyInput consumed
|
|
23
|
+
* by buildSurveyTrustBundle. The ReviewItem and ReviewWorkbenchResult are the
|
|
24
|
+
* authority: callers cannot override status, value, identity, or provenance.
|
|
25
|
+
*
|
|
26
|
+
* The helper deliberately returns the projection context beside SurveyInput
|
|
27
|
+
* because repeated append-only projections need that context at the Surface
|
|
28
|
+
* projection boundary, while SurveyInput's existing byte shape remains
|
|
29
|
+
* unchanged for compatibility.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildCanonicalReviewedTrustInput(options: BuildCanonicalReviewedTrustInputOptions): CanonicalReviewedTrustInput;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { SURVEY_INPUT_CONTRACT_VERSION } from "./types.js";
|
|
2
|
+
import { canonicalJson } from "./review-workbench/canonical.js";
|
|
3
|
+
import { workbenchDecisionDefinitions } from "./review-workbench/review-queue-session.js";
|
|
4
|
+
/**
|
|
5
|
+
* Projects server-applied review records into the complete SurveyInput consumed
|
|
6
|
+
* by buildSurveyTrustBundle. The ReviewItem and ReviewWorkbenchResult are the
|
|
7
|
+
* authority: callers cannot override status, value, identity, or provenance.
|
|
8
|
+
*
|
|
9
|
+
* The helper deliberately returns the projection context beside SurveyInput
|
|
10
|
+
* because repeated append-only projections need that context at the Surface
|
|
11
|
+
* projection boundary, while SurveyInput's existing byte shape remains
|
|
12
|
+
* unchanged for compatibility.
|
|
13
|
+
*/
|
|
14
|
+
export function buildCanonicalReviewedTrustInput(options) {
|
|
15
|
+
requireNonEmpty(options.source, "source");
|
|
16
|
+
requireTimestamp(options.generatedAt, "generatedAt");
|
|
17
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(options.projectionContextId)) {
|
|
18
|
+
throw new Error("projectionContextId must be a non-empty portable resource-name fragment.");
|
|
19
|
+
}
|
|
20
|
+
const itemsByName = uniqueBy(options.items, (item) => item.metadata.name, "ReviewItem");
|
|
21
|
+
const resultsByName = uniqueBy(options.results, (result) => result.reviewItemName, "ReviewWorkbenchResult");
|
|
22
|
+
if (itemsByName.size !== resultsByName.size) {
|
|
23
|
+
throw new Error("Canonical review projection requires exactly one resolved result for every ReviewItem.");
|
|
24
|
+
}
|
|
25
|
+
const rawSources = new Map();
|
|
26
|
+
const extractions = new Map();
|
|
27
|
+
const candidateSets = new Map();
|
|
28
|
+
const reviewOutcomes = new Map();
|
|
29
|
+
const claims = new Map();
|
|
30
|
+
for (const item of options.items) {
|
|
31
|
+
const result = resultsByName.get(item.metadata.name);
|
|
32
|
+
if (!result) {
|
|
33
|
+
throw new Error(`ReviewItem ${item.metadata.name} has no canonical server-applied result.`);
|
|
34
|
+
}
|
|
35
|
+
assertCanonicalResult(item, result);
|
|
36
|
+
const candidates = item.spec.candidates.map((candidate) => {
|
|
37
|
+
const records = projectCandidate(item, candidate);
|
|
38
|
+
addConsistent(rawSources, records.rawSource, "raw source");
|
|
39
|
+
addConsistent(extractions, records.extraction, "extraction");
|
|
40
|
+
return records.candidate;
|
|
41
|
+
});
|
|
42
|
+
const selected = item.spec.candidates.find((candidate) => candidate.id === result.selectedCandidateId);
|
|
43
|
+
const selectedRecordId = selected.projection?.candidateId ?? selected.id;
|
|
44
|
+
const candidateSetId = selected.projection?.candidateSetId
|
|
45
|
+
?? item.spec.projection?.candidateSetId
|
|
46
|
+
?? `${item.metadata.name}.candidates`;
|
|
47
|
+
if (candidates.some((candidate) => candidate.metadata?.candidateSetId !== candidateSetId)) {
|
|
48
|
+
throw new Error(`ReviewItem ${item.metadata.name} carries conflicting candidate-set projection ids.`);
|
|
49
|
+
}
|
|
50
|
+
const candidateSet = {
|
|
51
|
+
id: candidateSetId,
|
|
52
|
+
target: item.spec.target,
|
|
53
|
+
candidates: candidates.map(({ metadata, ...candidate }) => ({
|
|
54
|
+
...candidate,
|
|
55
|
+
...(metadata && Object.keys(metadata).length > 1
|
|
56
|
+
? { metadata: Object.fromEntries(Object.entries(metadata).filter(([key]) => key !== "candidateSetId")) }
|
|
57
|
+
: {}),
|
|
58
|
+
})),
|
|
59
|
+
selectedCandidateId: selectedRecordId,
|
|
60
|
+
status: result.decision === "could-not-confirm"
|
|
61
|
+
? (item.spec.candidateSetStatus ?? "needs-review")
|
|
62
|
+
: "resolved",
|
|
63
|
+
...(result.rationale ?? item.spec.rationale
|
|
64
|
+
? { rationale: result.rationale ?? item.spec.rationale }
|
|
65
|
+
: {}),
|
|
66
|
+
};
|
|
67
|
+
addConsistent(candidateSets, candidateSet, "candidate set");
|
|
68
|
+
const decision = result.reviewDecision.spec;
|
|
69
|
+
const reviewOutcomeId = decision.projection?.reviewOutcomeId
|
|
70
|
+
?? selected.projection?.reviewOutcomeId
|
|
71
|
+
?? `${item.metadata.name}.${result.decision}.review-outcome`;
|
|
72
|
+
const reviewOutcome = {
|
|
73
|
+
id: reviewOutcomeId,
|
|
74
|
+
candidateSetId,
|
|
75
|
+
candidateId: selectedRecordId,
|
|
76
|
+
status: result.status,
|
|
77
|
+
...(decision.resolution ? { resolution: decision.resolution } : {}),
|
|
78
|
+
...(decision.resolutionReason ? { resolutionReason: decision.resolutionReason } : {}),
|
|
79
|
+
...(decision.attemptEvidenceIds?.length ? { attemptEvidenceIds: [...decision.attemptEvidenceIds] } : {}),
|
|
80
|
+
...(decision.actor?.id ? { actor: decision.actor.id } : {}),
|
|
81
|
+
...(decision.reviewedAt ? { reviewedAt: decision.reviewedAt } : {}),
|
|
82
|
+
...(decision.rationale ? { rationale: decision.rationale } : {}),
|
|
83
|
+
...(decision.evidenceIds?.length ? { evidenceIds: [...decision.evidenceIds] } : {}),
|
|
84
|
+
...(decision.withinComfortZone !== undefined ? { withinComfortZone: decision.withinComfortZone } : {}),
|
|
85
|
+
...(decision.comfortZoneNote ? { comfortZoneNote: decision.comfortZoneNote } : {}),
|
|
86
|
+
...(decision.authorizing ? { authorizing: decision.authorizing } : {}),
|
|
87
|
+
metadata: {
|
|
88
|
+
workbenchDecision: result.decision,
|
|
89
|
+
...(result.editedValue !== undefined ? { editedValue: result.editedValue } : {}),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
addConsistent(reviewOutcomes, reviewOutcome, "review outcome");
|
|
93
|
+
const hint = selected.claimTarget;
|
|
94
|
+
assertSingleProjectionId("claim", item.metadata.name, [
|
|
95
|
+
decision.projection?.claimId,
|
|
96
|
+
item.spec.projection?.claimId,
|
|
97
|
+
...item.spec.candidates.flatMap((candidate) => [candidate.projection?.claimId, candidate.claimTarget.claimId]),
|
|
98
|
+
]);
|
|
99
|
+
const claimId = decision.projection?.claimId
|
|
100
|
+
?? selected.projection?.claimId
|
|
101
|
+
?? item.spec.projection?.claimId
|
|
102
|
+
?? hint.claimId
|
|
103
|
+
?? `${item.metadata.name}.claim`;
|
|
104
|
+
const claim = {
|
|
105
|
+
id: claimId,
|
|
106
|
+
candidateSetId,
|
|
107
|
+
candidateId: selectedRecordId,
|
|
108
|
+
subjectType: hint.subjectType,
|
|
109
|
+
subjectId: hint.subjectId,
|
|
110
|
+
facet: hint.facet,
|
|
111
|
+
claimType: hint.claimType,
|
|
112
|
+
fieldOrBehavior: hint.fieldOrBehavior,
|
|
113
|
+
value: result.effectiveValue,
|
|
114
|
+
status: result.status,
|
|
115
|
+
impactLevel: hint.impactLevel,
|
|
116
|
+
updatedAt: decision.reviewedAt ?? options.generatedAt,
|
|
117
|
+
...(hint.evidenceType ? { evidenceType: hint.evidenceType } : {}),
|
|
118
|
+
...(hint.evidenceMethod ? { evidenceMethod: hint.evidenceMethod } : {}),
|
|
119
|
+
...(hint.derivedFrom ? { derivedFrom: [...hint.derivedFrom] } : {}),
|
|
120
|
+
collectedBy: hint.collectedBy ?? selected.extraction.extractor ?? options.source,
|
|
121
|
+
...(decision.actor?.id ? { actor: decision.actor.id } : {}),
|
|
122
|
+
};
|
|
123
|
+
addConsistent(claims, claim, "claim target");
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
projectionContextId: options.projectionContextId,
|
|
127
|
+
surveyInput: {
|
|
128
|
+
contractVersion: SURVEY_INPUT_CONTRACT_VERSION,
|
|
129
|
+
source: options.source,
|
|
130
|
+
generatedAt: options.generatedAt,
|
|
131
|
+
rawSources: [...rawSources.values()],
|
|
132
|
+
extractions: [...extractions.values()],
|
|
133
|
+
candidateSets: [...candidateSets.values()],
|
|
134
|
+
reviewOutcomes: [...reviewOutcomes.values()],
|
|
135
|
+
claims: [...claims.values()],
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function assertCanonicalResult(item, result) {
|
|
140
|
+
const selected = item.spec.candidates.find((candidate) => candidate.id === result.selectedCandidateId);
|
|
141
|
+
if (!selected) {
|
|
142
|
+
throw new Error(`Review result ${result.reviewItemName} selects an unknown candidate.`);
|
|
143
|
+
}
|
|
144
|
+
if (canonicalJson(selected) !== canonicalJson(result.selectedCandidate)) {
|
|
145
|
+
throw new Error(`Review result ${result.reviewItemName} selected candidate does not match its canonical ReviewItem.`);
|
|
146
|
+
}
|
|
147
|
+
const unselected = item.spec.candidates.filter((candidate) => candidate.id !== selected.id);
|
|
148
|
+
if (canonicalJson(unselected) !== canonicalJson(result.unselectedCandidates)) {
|
|
149
|
+
throw new Error(`Review result ${result.reviewItemName} unselected candidates do not match its canonical ReviewItem.`);
|
|
150
|
+
}
|
|
151
|
+
if (result.selectedCandidateRole !== selected.role || canonicalJson(result.selectedValue) !== canonicalJson(selected.value)) {
|
|
152
|
+
throw new Error(`Review result ${result.reviewItemName} selected identity does not match its canonical ReviewItem.`);
|
|
153
|
+
}
|
|
154
|
+
const decision = result.reviewDecision.spec;
|
|
155
|
+
const definition = workbenchDecisionDefinitions[result.decision];
|
|
156
|
+
if (decision.reviewItemName !== item.metadata.name
|
|
157
|
+
|| decision.candidateId !== result.selectedCandidateId
|
|
158
|
+
|| decision.status !== result.status
|
|
159
|
+
|| decision.status !== definition.status
|
|
160
|
+
|| decision.rationale !== result.rationale
|
|
161
|
+
|| canonicalJson(decision.editedValue) !== canonicalJson(result.editedValue)) {
|
|
162
|
+
throw new Error(`Review result ${result.reviewItemName} contradicts its canonical ReviewDecision.`);
|
|
163
|
+
}
|
|
164
|
+
if ((result.decision === "could-not-confirm") !== (decision.resolution === "could_not_confirm")) {
|
|
165
|
+
throw new Error(`Review result ${result.reviewItemName} contradicts its canonical review resolution.`);
|
|
166
|
+
}
|
|
167
|
+
const expectedEffective = result.editedValue !== undefined && result.decision === "accept-proposed"
|
|
168
|
+
? result.editedValue
|
|
169
|
+
: selected.value;
|
|
170
|
+
if (canonicalJson(expectedEffective) !== canonicalJson(result.effectiveValue)) {
|
|
171
|
+
throw new Error(`Review result ${result.reviewItemName} effective value is not canonical.`);
|
|
172
|
+
}
|
|
173
|
+
for (const candidate of item.spec.candidates) {
|
|
174
|
+
if (canonicalJson(claimTargetIdentity(candidate.claimTarget)) !== canonicalJson(claimTargetIdentity(selected.claimTarget))) {
|
|
175
|
+
throw new Error(`ReviewItem ${item.metadata.name} candidates carry conflicting claim targets.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function claimTargetIdentity(target) {
|
|
180
|
+
const { claimId: _claimId, ...identity } = target;
|
|
181
|
+
return identity;
|
|
182
|
+
}
|
|
183
|
+
function projectCandidate(item, input) {
|
|
184
|
+
const rawSourceId = input.projection?.rawSourceId ?? input.source.sourceId ?? `${item.metadata.name}.${input.id}.source`;
|
|
185
|
+
const extractionId = input.projection?.extractionId ?? input.extraction.extractionId ?? `${item.metadata.name}.${input.id}.extraction`;
|
|
186
|
+
const candidateSetId = input.projection?.candidateSetId ?? item.spec.projection?.candidateSetId ?? `${item.metadata.name}.candidates`;
|
|
187
|
+
const sourceKind = input.source.kind;
|
|
188
|
+
const observedAt = input.source.observedAt;
|
|
189
|
+
const locatorScheme = input.source.locatorScheme ?? input.locator?.scheme;
|
|
190
|
+
const extractor = input.extraction.extractor;
|
|
191
|
+
const extractedAt = input.extraction.extractedAt;
|
|
192
|
+
if (!sourceKind || !observedAt || !locatorScheme || !extractor || !extractedAt) {
|
|
193
|
+
throw new Error(`ReviewCandidate ${input.id} lacks source or extraction provenance required for TrustInput projection.`);
|
|
194
|
+
}
|
|
195
|
+
const rawSource = {
|
|
196
|
+
id: rawSourceId,
|
|
197
|
+
kind: sourceKind,
|
|
198
|
+
sourceRef: input.source.sourceRef,
|
|
199
|
+
observedAt,
|
|
200
|
+
...(input.source.fetchedAt ? { fetchedAt: input.source.fetchedAt } : {}),
|
|
201
|
+
...(input.source.checksum ? { checksum: input.source.checksum } : {}),
|
|
202
|
+
locatorScheme,
|
|
203
|
+
};
|
|
204
|
+
const extraction = {
|
|
205
|
+
id: extractionId,
|
|
206
|
+
sourceId: rawSourceId,
|
|
207
|
+
target: input.extraction.target,
|
|
208
|
+
value: input.value,
|
|
209
|
+
...(input.extraction.confidence ?? input.confidence) !== undefined
|
|
210
|
+
? { confidence: input.extraction.confidence ?? input.confidence }
|
|
211
|
+
: {},
|
|
212
|
+
...(input.locator?.locator ? { locator: input.locator.locator } : {}),
|
|
213
|
+
...(input.locator?.excerpt ? { excerpt: input.locator.excerpt } : {}),
|
|
214
|
+
extractor,
|
|
215
|
+
extractedAt,
|
|
216
|
+
...(input.extraction.model ? { metadata: { model: input.extraction.model } } : {}),
|
|
217
|
+
};
|
|
218
|
+
const candidate = {
|
|
219
|
+
id: input.projection?.candidateId ?? input.id,
|
|
220
|
+
extractionId,
|
|
221
|
+
value: input.value,
|
|
222
|
+
...(input.confidence !== undefined ? { confidence: input.confidence } : {}),
|
|
223
|
+
...(input.sourceRank !== undefined ? { sourceRank: input.sourceRank } : {}),
|
|
224
|
+
...(input.rejectionReason ? { rejectionReason: input.rejectionReason } : {}),
|
|
225
|
+
metadata: {
|
|
226
|
+
candidateSetId,
|
|
227
|
+
...(input.role ? { role: input.role } : {}),
|
|
228
|
+
...(input.producer ? { producer: input.producer } : {}),
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
return { rawSource, extraction, candidate };
|
|
232
|
+
}
|
|
233
|
+
function uniqueBy(values, id, label) {
|
|
234
|
+
const output = new Map();
|
|
235
|
+
for (const value of values) {
|
|
236
|
+
const key = requireNonEmpty(id(value), `${label} identity`);
|
|
237
|
+
if (output.has(key)) {
|
|
238
|
+
throw new Error(`Canonical review projection received duplicate ${label} ${key}.`);
|
|
239
|
+
}
|
|
240
|
+
output.set(key, value);
|
|
241
|
+
}
|
|
242
|
+
return output;
|
|
243
|
+
}
|
|
244
|
+
function addConsistent(records, value, label) {
|
|
245
|
+
const existing = records.get(value.id);
|
|
246
|
+
if (existing && canonicalJson(existing) !== canonicalJson(value)) {
|
|
247
|
+
throw new Error(`Canonical review projection found conflicting ${label} ${value.id}.`);
|
|
248
|
+
}
|
|
249
|
+
records.set(value.id, value);
|
|
250
|
+
}
|
|
251
|
+
function requireNonEmpty(value, label) {
|
|
252
|
+
if (!value.trim())
|
|
253
|
+
throw new Error(`${label} must be non-empty.`);
|
|
254
|
+
return value;
|
|
255
|
+
}
|
|
256
|
+
function requireTimestamp(value, label) {
|
|
257
|
+
if (!value.trim() || Number.isNaN(Date.parse(value)))
|
|
258
|
+
throw new Error(`${label} must be an ISO timestamp.`);
|
|
259
|
+
}
|
|
260
|
+
function assertSingleProjectionId(label, itemName, values) {
|
|
261
|
+
const ids = new Set(values.filter((value) => value !== undefined));
|
|
262
|
+
if (ids.size > 1) {
|
|
263
|
+
throw new Error(`ReviewItem ${itemName} carries conflicting ${label} projection ids.`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { type ExtractionEnvelopeImport } from "./extraction-envelope.js";
|
|
2
|
+
import { type ReviewDecision, type ReviewItem } from "./review-resource.js";
|
|
3
|
+
import type { ReviewOutcome, SurveyInput } from "./types.js";
|
|
4
|
+
/** Portable reference to a producer-owned task. Survey never copies its executable schema. */
|
|
5
|
+
export interface ExtractionTaskSpecReference {
|
|
6
|
+
version: string;
|
|
7
|
+
digest: string;
|
|
8
|
+
exampleDigests: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface ExtractionImprovementRecordDigests {
|
|
11
|
+
extractionImport: string;
|
|
12
|
+
reviewItem: string;
|
|
13
|
+
reviewDecision: string;
|
|
14
|
+
reviewOutcome: string;
|
|
15
|
+
}
|
|
16
|
+
/** Canonical projection derived from validated Survey records and their joins. */
|
|
17
|
+
export interface ExtractionImprovementLineage {
|
|
18
|
+
taskSpec: ExtractionTaskSpecReference;
|
|
19
|
+
extractionImportName: string;
|
|
20
|
+
extractionId: string;
|
|
21
|
+
proposalId: string;
|
|
22
|
+
reviewItemName: string;
|
|
23
|
+
reviewDecisionName: string;
|
|
24
|
+
reviewOutcomeId: string;
|
|
25
|
+
sourceSnapshotRef: string;
|
|
26
|
+
preparedArtifact: {
|
|
27
|
+
ref: string;
|
|
28
|
+
digest: string;
|
|
29
|
+
};
|
|
30
|
+
excerptLocator: string;
|
|
31
|
+
recordDigests: ExtractionImprovementRecordDigests;
|
|
32
|
+
}
|
|
33
|
+
export interface BadExtractionDiagnosis {
|
|
34
|
+
kind: "bad-extraction";
|
|
35
|
+
requestedTaskChanges: Array<"example-addition" | "guidance-update">;
|
|
36
|
+
}
|
|
37
|
+
export interface AcceptedExtractionDiagnosis {
|
|
38
|
+
kind: "accepted-extraction";
|
|
39
|
+
requestedTaskChanges: Array<"grounded-positive-example" | "guidance-affirmation">;
|
|
40
|
+
}
|
|
41
|
+
export interface InsufficientSourceEvidenceDiagnosis {
|
|
42
|
+
kind: "insufficient-source-evidence";
|
|
43
|
+
sourceRemediation: "obtain-authoritative-source" | "refresh-source-snapshot" | "expand-source-scope";
|
|
44
|
+
}
|
|
45
|
+
/** Caller-supplied diagnosis; Survey never infers it from rationale prose. */
|
|
46
|
+
export type ExtractionImprovementDiagnosis = BadExtractionDiagnosis | AcceptedExtractionDiagnosis | InsufficientSourceEvidenceDiagnosis;
|
|
47
|
+
export interface ExtractionImprovementReview {
|
|
48
|
+
resolution: "accepted" | "rejected" | "could_not_confirm";
|
|
49
|
+
status: ReviewOutcome["status"];
|
|
50
|
+
reviewedAt: string;
|
|
51
|
+
rationale: string;
|
|
52
|
+
evidenceIds: string[];
|
|
53
|
+
attemptEvidenceIds: string[];
|
|
54
|
+
}
|
|
55
|
+
export interface ExtractionImprovementDraft {
|
|
56
|
+
id: string;
|
|
57
|
+
kind: "survey.extraction-improvement-proposal";
|
|
58
|
+
schemaVersion: 1;
|
|
59
|
+
state: "draft";
|
|
60
|
+
createdAt: string;
|
|
61
|
+
lineage: ExtractionImprovementLineage;
|
|
62
|
+
diagnosis: ExtractionImprovementDiagnosis;
|
|
63
|
+
review: ExtractionImprovementReview;
|
|
64
|
+
}
|
|
65
|
+
export interface BuildExtractionImprovementProposalInput {
|
|
66
|
+
createdAt: string;
|
|
67
|
+
/** Version label paired with the task digest carried by the canonical import. */
|
|
68
|
+
priorTaskSpecVersion: string;
|
|
69
|
+
extractionImport: ExtractionEnvelopeImport;
|
|
70
|
+
proposalIndex: number;
|
|
71
|
+
reviewItem: ReviewItem;
|
|
72
|
+
reviewDecision: ReviewDecision;
|
|
73
|
+
survey: SurveyInput;
|
|
74
|
+
reviewOutcomeId: string;
|
|
75
|
+
diagnosis: ExtractionImprovementDiagnosis;
|
|
76
|
+
}
|
|
77
|
+
export interface ProducerApproval {
|
|
78
|
+
id: string;
|
|
79
|
+
actor: string;
|
|
80
|
+
approvedAt: string;
|
|
81
|
+
rationale: string;
|
|
82
|
+
evidenceIds: string[];
|
|
83
|
+
}
|
|
84
|
+
/** Data-only request; the task owner must validate, store, and activate it. */
|
|
85
|
+
export interface ExtractionImprovementActivationRequest {
|
|
86
|
+
id: string;
|
|
87
|
+
dispositionKey: string;
|
|
88
|
+
kind: "survey.extraction-improvement-activation-request";
|
|
89
|
+
schemaVersion: 1;
|
|
90
|
+
state: "approved";
|
|
91
|
+
draftId: string;
|
|
92
|
+
approval: ProducerApproval;
|
|
93
|
+
nextTaskSpec: ExtractionTaskSpecReference;
|
|
94
|
+
rollbackTaskSpec: ExtractionTaskSpecReference;
|
|
95
|
+
guidanceChangeProofDigest?: string;
|
|
96
|
+
}
|
|
97
|
+
export interface ApproveExtractionImprovementProposalInput {
|
|
98
|
+
draft: ExtractionImprovementDraft;
|
|
99
|
+
approval: ProducerApproval;
|
|
100
|
+
nextTaskSpec: ExtractionTaskSpecReference;
|
|
101
|
+
rollbackTaskSpec: ExtractionTaskSpecReference;
|
|
102
|
+
/** Required when the explicit remedy updates or affirms guidance. */
|
|
103
|
+
guidanceChangeProofDigest?: string;
|
|
104
|
+
}
|
|
105
|
+
export interface ExtractionImprovementRejection {
|
|
106
|
+
id: string;
|
|
107
|
+
dispositionKey: string;
|
|
108
|
+
kind: "survey.extraction-improvement-rejection";
|
|
109
|
+
schemaVersion: 1;
|
|
110
|
+
state: "rejected";
|
|
111
|
+
draftId: string;
|
|
112
|
+
rejection: {
|
|
113
|
+
id: string;
|
|
114
|
+
actor: string;
|
|
115
|
+
rejectedAt: string;
|
|
116
|
+
rationale: string;
|
|
117
|
+
evidenceIds: string[];
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export interface RejectExtractionImprovementProposalInput {
|
|
121
|
+
draft: ExtractionImprovementDraft;
|
|
122
|
+
rejection: ExtractionImprovementRejection["rejection"];
|
|
123
|
+
}
|
|
124
|
+
/** One producer/store disposition is allowed for each shared dispositionKey. */
|
|
125
|
+
export type ExtractionImprovementDisposition = ExtractionImprovementActivationRequest | ExtractionImprovementRejection;
|
|
126
|
+
export interface ExtractionImprovementDispositionConflict {
|
|
127
|
+
kind: "survey.extraction-improvement-disposition-conflict";
|
|
128
|
+
dispositionKey: string;
|
|
129
|
+
/** Canonically ordered distinct records. The fold never selects a winner. */
|
|
130
|
+
dispositions: readonly ExtractionImprovementDisposition[];
|
|
131
|
+
}
|
|
132
|
+
export interface FoldExtractionImprovementDispositionsResult {
|
|
133
|
+
/** One canonically ordered record for each uncontested disposition key. */
|
|
134
|
+
dispositions: readonly ExtractionImprovementDisposition[];
|
|
135
|
+
/** Canonically ordered conflicts for keys with more than one distinct record. */
|
|
136
|
+
conflicts: readonly ExtractionImprovementDispositionConflict[];
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Folds producer-owned disposition records without performing I/O or selecting
|
|
140
|
+
* between conflicting decisions. Byte-equivalent replay is idempotent.
|
|
141
|
+
*/
|
|
142
|
+
export declare function foldExtractionImprovementDispositions(input: readonly ExtractionImprovementDisposition[]): FoldExtractionImprovementDispositionsResult;
|
|
143
|
+
/** Builds a frozen draft from validated canonical records. Performs no I/O. */
|
|
144
|
+
export declare function buildExtractionImprovementProposal(input: BuildExtractionImprovementProposalInput): ExtractionImprovementDraft;
|
|
145
|
+
/** Emits a reversible activation request, never an activation. */
|
|
146
|
+
export declare function approveExtractionImprovementProposal(input: ApproveExtractionImprovementProposalInput): ExtractionImprovementActivationRequest;
|
|
147
|
+
/** Emits an idempotent terminal rejection with the same disposition key as approval. */
|
|
148
|
+
export declare function rejectExtractionImprovementProposal(input: RejectExtractionImprovementProposalInput): ExtractionImprovementRejection;
|