@kontourai/survey 2.5.0 → 4.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 +6 -1
- package/dist/examples/calibrated-auto-accept.d.ts +22 -15
- package/dist/examples/calibrated-auto-accept.js +40 -36
- package/dist/src/agent-utterance.d.ts +87 -11
- package/dist/src/agent-utterance.js +135 -44
- package/dist/src/calibration.d.ts +48 -21
- package/dist/src/calibration.js +72 -33
- package/dist/src/console/review-console-server.d.ts +3 -1
- package/dist/src/console/review-console-server.js +203 -50
- package/dist/src/extraction-envelope.d.ts +22 -0
- package/dist/src/extraction-envelope.js +25 -4
- package/dist/src/index.d.ts +9 -8
- package/dist/src/index.js +2 -2
- package/dist/src/inquiry-mapping.d.ts +15 -1
- package/dist/src/inquiry-mapping.js +10 -2
- package/dist/src/mcp/review-mcp.js +219 -279
- package/dist/src/producer-profile.d.ts +41 -2
- package/dist/src/producer-profile.js +29 -2
- package/dist/src/review-session-file.d.ts +64 -0
- package/dist/src/review-session-file.js +320 -0
- package/dist/src/review-workbench/edited-value.d.ts +70 -0
- package/dist/src/review-workbench/edited-value.js +147 -0
- package/dist/src/review-workbench/review-presentation.d.ts +44 -0
- package/dist/src/review-workbench/review-presentation.js +49 -0
- package/dist/src/review-workbench/review-queue-session.js +8 -1
- package/dist/src/review-workbench/review-session-replay.d.ts +35 -1
- package/dist/src/review-workbench/review-session-replay.js +77 -0
- package/dist/src/review-workbench/review-workbench.d.ts +8 -4
- package/dist/src/review-workbench/review-workbench.js +19 -7
- package/dist/src/review-workbench/server-review-session.d.ts +3 -1
- package/dist/src/review-workbench/server-review-session.js +1 -0
- package/dist/src/reviewed-candidate-resolution.js +13 -7
- package/dist/src/schema-mapping.d.ts +23 -0
- package/dist/src/schema-mapping.js +30 -20
- package/dist/src/to-surface.d.ts +30 -6
- package/dist/src/to-surface.js +196 -18
- package/dist/src/types.d.ts +39 -0
- package/package.json +8 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReviewCandidate, type ReviewItem } from "../review-resource.js";
|
|
2
|
+
import type { InterpretationAnswerImpact, InterpretationReadingKind } from "../types.js";
|
|
2
3
|
import { type ReviewWorkbenchResult } from "./review-workbench.js";
|
|
3
4
|
export interface ReviewPresentationAdapter {
|
|
4
5
|
readonly labelForTarget?: (target: string, context: ReviewItemPresentationContext) => string | undefined;
|
|
@@ -61,6 +62,49 @@ export interface ReviewResultPresentation {
|
|
|
61
62
|
readonly reviewItemLink?: ReviewPresentationLink;
|
|
62
63
|
readonly traceRefs: readonly ReviewTraceRef[];
|
|
63
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Structural input for {@link buildInterpretationReadingPresentation}: either
|
|
67
|
+
* a Survey `Interpretation` record (`id`) or the entry Survey projects onto a
|
|
68
|
+
* claim at `metadata.survey.interpretations[]` (`interpretationId`). Kind and
|
|
69
|
+
* impact arrive as plain strings when read back from projected metadata.
|
|
70
|
+
*/
|
|
71
|
+
export interface InterpretationReadingSource {
|
|
72
|
+
readonly id?: string;
|
|
73
|
+
readonly interpretationId?: string;
|
|
74
|
+
readonly readingKind?: string;
|
|
75
|
+
readonly answerImpact?: string;
|
|
76
|
+
readonly ruleLocator: string;
|
|
77
|
+
readonly reading: string;
|
|
78
|
+
readonly actor: string;
|
|
79
|
+
readonly recordedAt: string;
|
|
80
|
+
}
|
|
81
|
+
export interface InterpretationReadingPresentation {
|
|
82
|
+
readonly interpretationId: string;
|
|
83
|
+
readonly readingKind: InterpretationReadingKind;
|
|
84
|
+
readonly kindLabel: string;
|
|
85
|
+
readonly answerImpact?: InterpretationAnswerImpact;
|
|
86
|
+
readonly answerImpactLabel?: string;
|
|
87
|
+
readonly reading: string;
|
|
88
|
+
readonly actor: string;
|
|
89
|
+
readonly recordedAt: string;
|
|
90
|
+
readonly ruleLocator: string;
|
|
91
|
+
/**
|
|
92
|
+
* Always `"authored-judgment"`. This is DERIVED from the record type, not a
|
|
93
|
+
* stored flag: every Interpretation reading is a producer-authored reading
|
|
94
|
+
* by contract (CONTEXT.md "Interpretation Record"), never a machine-observed
|
|
95
|
+
* fact. Renderers must present readings under this marking, visually
|
|
96
|
+
* distinct from machine-observed values — the StatementBadge / ADR 0003 §4
|
|
97
|
+
* discipline (blending the two is the defect class of #247).
|
|
98
|
+
*/
|
|
99
|
+
readonly provenance: "authored-judgment";
|
|
100
|
+
readonly provenanceLabel: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Presents one interpretation reading as authored judgment. Fails closed on
|
|
104
|
+
* unknown reading-kind / answer-impact vocabulary rather than rendering an
|
|
105
|
+
* authored record under a label nothing derived.
|
|
106
|
+
*/
|
|
107
|
+
export declare function buildInterpretationReadingPresentation(source: InterpretationReadingSource): InterpretationReadingPresentation;
|
|
64
108
|
export declare function buildReviewItemPresentation(item: ReviewItem, adapter?: ReviewPresentationAdapter): ReviewItemPresentation;
|
|
65
109
|
export declare function buildReviewCandidatePresentation(item: ReviewItem, candidate: ReviewCandidate, adapter?: ReviewPresentationAdapter, targetLabel?: string): ReviewCandidatePresentation;
|
|
66
110
|
export declare function buildReviewResultPresentation(result: ReviewWorkbenchResult, item: ReviewItem | undefined, adapter?: ReviewPresentationAdapter): ReviewResultPresentation;
|
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
import { findSoleCandidateById } from "../review-resource.js";
|
|
2
2
|
import { formatValue } from "./review-surface-preview.js";
|
|
3
|
+
const INTERPRETATION_KIND_LABELS = {
|
|
4
|
+
"policy-standard": "Policy-standard reading",
|
|
5
|
+
gleaned: "Gleaned from results",
|
|
6
|
+
answerImpact: "Answer impact",
|
|
7
|
+
};
|
|
8
|
+
const ANSWER_IMPACT_LABELS = {
|
|
9
|
+
supported: "Supported the answer",
|
|
10
|
+
narrowed: "Narrowed the answer",
|
|
11
|
+
"accepted-risk": "Accepted as a risk",
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Presents one interpretation reading as authored judgment. Fails closed on
|
|
15
|
+
* unknown reading-kind / answer-impact vocabulary rather than rendering an
|
|
16
|
+
* authored record under a label nothing derived.
|
|
17
|
+
*/
|
|
18
|
+
export function buildInterpretationReadingPresentation(source) {
|
|
19
|
+
const interpretationId = source.interpretationId ?? source.id;
|
|
20
|
+
if (!interpretationId) {
|
|
21
|
+
throw new Error("Interpretation reading presentation requires an id or interpretationId.");
|
|
22
|
+
}
|
|
23
|
+
const readingKind = (source.readingKind ?? "policy-standard");
|
|
24
|
+
const kindLabel = INTERPRETATION_KIND_LABELS[readingKind];
|
|
25
|
+
if (!kindLabel) {
|
|
26
|
+
throw new Error(`Interpretation ${interpretationId} has unknown readingKind ${String(source.readingKind)}`);
|
|
27
|
+
}
|
|
28
|
+
const answerImpact = source.answerImpact;
|
|
29
|
+
const answerImpactLabel = answerImpact === undefined ? undefined : ANSWER_IMPACT_LABELS[answerImpact];
|
|
30
|
+
if (answerImpact !== undefined && !answerImpactLabel) {
|
|
31
|
+
throw new Error(`Interpretation ${interpretationId} has unknown answerImpact ${String(source.answerImpact)}`);
|
|
32
|
+
}
|
|
33
|
+
if (readingKind === "answerImpact" && answerImpact === undefined) {
|
|
34
|
+
throw new Error(`Interpretation ${interpretationId} readingKind answerImpact requires an answerImpact value`);
|
|
35
|
+
}
|
|
36
|
+
if (readingKind !== "answerImpact" && answerImpact !== undefined) {
|
|
37
|
+
throw new Error(`Interpretation ${interpretationId} sets answerImpact but readingKind is ${readingKind}`);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
interpretationId,
|
|
41
|
+
readingKind,
|
|
42
|
+
kindLabel,
|
|
43
|
+
...(answerImpact !== undefined ? { answerImpact, answerImpactLabel } : {}),
|
|
44
|
+
reading: source.reading,
|
|
45
|
+
actor: source.actor,
|
|
46
|
+
recordedAt: source.recordedAt,
|
|
47
|
+
ruleLocator: source.ruleLocator,
|
|
48
|
+
provenance: "authored-judgment",
|
|
49
|
+
provenanceLabel: "Authored judgment",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
3
52
|
export function buildReviewItemPresentation(item, adapter = {}) {
|
|
4
53
|
const context = { item };
|
|
5
54
|
const targetLabel = adapter.labelForTarget?.(item.spec.target, context) ?? humanizeIdentifier(item.spec.target);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { publicDirectoryReviewItemExample, reviewWorkbenchQueueExamples } from "./review-workbench-data.js";
|
|
2
2
|
import { assertReviewResolutionConsistency } from "../producer-discipline.js";
|
|
3
|
+
import { checkEditedValueForItem } from "./edited-value.js";
|
|
3
4
|
import { assertSoleCandidateId, reviewResourceApiVersion, } from "../../src/review-resource.js";
|
|
4
5
|
export const reviewWorkbenchSessionStorageKey = "kontourai.survey.review-workbench.session-events.v1";
|
|
5
6
|
export const defaultReviewSessionName = "review-workbench-session";
|
|
@@ -368,7 +369,13 @@ export function replayReviewSessionEvents(startState, events) {
|
|
|
368
369
|
const editedValuesByItemName = { ...session.editedValuesByItemName };
|
|
369
370
|
const attemptEvidenceIdsByItemName = { ...session.attemptEvidenceIdsByItemName };
|
|
370
371
|
if (decision === "accept-proposed" && editedValue !== undefined) {
|
|
371
|
-
|
|
372
|
+
// Legacy sessions stored typed edits as editor text ("42"); store the
|
|
373
|
+
// descriptor-typed value so effectiveValue is 42. An edit the item does
|
|
374
|
+
// not allow is left as carried: validated replay refuses it before this
|
|
375
|
+
// point (kontourai/survey#278).
|
|
376
|
+
const item = session.items.find((entry) => entry.metadata.name === itemName);
|
|
377
|
+
const check = item ? checkEditedValueForItem(item, editedValue) : undefined;
|
|
378
|
+
editedValuesByItemName[itemName] = check?.ok ? check.value : editedValue;
|
|
372
379
|
}
|
|
373
380
|
else {
|
|
374
381
|
delete editedValuesByItemName[itemName];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReviewSessionEvent } from "../review-resource.js";
|
|
2
2
|
import { type ReviewQueueSessionState } from "./review-queue-session.js";
|
|
3
|
-
export type ReviewSessionReplayIssueCode = "invalid-sequence" | "duplicate-sequence" | "non-contiguous-sequence" | "unknown-active-item" | "unknown-review-item" | "unknown-candidate" | "missing-review-item" | "invalid-workbench-decision" | "decision-candidate-mismatch" | "decision-status-mismatch" | "decision-resolution-mismatch" | "missing-resolution-reason";
|
|
3
|
+
export type ReviewSessionReplayIssueCode = "invalid-sequence" | "duplicate-sequence" | "non-contiguous-sequence" | "unknown-active-item" | "unknown-review-item" | "unknown-candidate" | "missing-review-item" | "invalid-workbench-decision" | "decision-candidate-mismatch" | "decision-status-mismatch" | "decision-resolution-mismatch" | "missing-resolution-reason" | "edited-value-not-editable" | "edited-value-type-mismatch";
|
|
4
4
|
export interface ReviewSessionReplayIssue {
|
|
5
5
|
readonly code: ReviewSessionReplayIssueCode;
|
|
6
6
|
readonly eventName: string;
|
|
@@ -10,3 +10,37 @@ export interface ReviewSessionReplayIssue {
|
|
|
10
10
|
readonly message: string;
|
|
11
11
|
}
|
|
12
12
|
export declare function validateReviewSessionEventsForSnapshot(snapshot: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewSessionReplayIssue[];
|
|
13
|
+
/**
|
|
14
|
+
* A ReviewSessionEvent whose `data.workbenchEditedValue` was rewritten from
|
|
15
|
+
* legacy editor text to its descriptor-typed value during replay (see
|
|
16
|
+
* checkEditedValueForItem). Not an error: the apply result still succeeds, and
|
|
17
|
+
* the warning lets a consumer see that a saved session was normalized.
|
|
18
|
+
*/
|
|
19
|
+
export type ReviewSessionReplayWarning = {
|
|
20
|
+
readonly code: "edited-value-converted-from-text";
|
|
21
|
+
readonly eventName: string;
|
|
22
|
+
readonly sequence: number;
|
|
23
|
+
readonly reviewItemName: string;
|
|
24
|
+
readonly originalValue: string;
|
|
25
|
+
readonly convertedValue: unknown;
|
|
26
|
+
readonly message: string;
|
|
27
|
+
} | {
|
|
28
|
+
/**
|
|
29
|
+
* An edit that would be refused (see checkEditedValueForItem) on a
|
|
30
|
+
* decision event that a later decision for the same item superseded. It
|
|
31
|
+
* never reaches effectiveValue, so it does not fail the session.
|
|
32
|
+
*/
|
|
33
|
+
readonly code: "superseded-edited-value-refused";
|
|
34
|
+
readonly eventName: string;
|
|
35
|
+
readonly sequence: number;
|
|
36
|
+
readonly reviewItemName: string;
|
|
37
|
+
readonly refusedCode: "edited-value-not-editable" | "edited-value-type-mismatch";
|
|
38
|
+
readonly editedValue: unknown;
|
|
39
|
+
readonly message: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Lists the legacy text edits replay converts to typed values, and refused
|
|
43
|
+
* edits that a later decision superseded. Call it only on an event stream that
|
|
44
|
+
* validateReviewSessionEventsForSnapshot accepted.
|
|
45
|
+
*/
|
|
46
|
+
export declare function reviewSessionReplayWarningsForSnapshot(snapshot: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewSessionReplayWarning[];
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { candidateForDecision, isClearedWorkbenchDecisionEvent, workbenchDecisionDefinitions, } from "./review-queue-session.js";
|
|
2
|
+
import { checkEditedValueForItem } from "./edited-value.js";
|
|
2
3
|
export function validateReviewSessionEventsForSnapshot(snapshot, events) {
|
|
3
4
|
const itemsByName = new Map(snapshot.items.map((item) => [item.metadata.name, item]));
|
|
4
5
|
const sequenceIssues = validateEventSequence(events);
|
|
6
|
+
const finalDecisionEvents = finalDecisionEventPerItem(events);
|
|
5
7
|
const replayIssues = events.flatMap((event) => {
|
|
6
8
|
const issues = [];
|
|
7
9
|
const activeItemName = event.spec.activeItemName;
|
|
@@ -86,6 +88,22 @@ export function validateReviewSessionEventsForSnapshot(snapshot, events) {
|
|
|
86
88
|
message: `ReviewSessionEvent ${event.metadata.name} decision ${decision} expects resolution ${expectedResolution ?? "none"}, but references ${event.spec.resolution ?? "no resolution"}.`,
|
|
87
89
|
});
|
|
88
90
|
}
|
|
91
|
+
const editedValue = editedValueFromDecisionEvent(event);
|
|
92
|
+
// Only the item's final decision decides its effectiveValue, so only an
|
|
93
|
+
// edit on that event is refused. An edit a later decision superseded is
|
|
94
|
+
// reported as a warning instead of failing the whole session.
|
|
95
|
+
if (item && decision === "accept-proposed" && editedValue !== undefined && finalDecisionEvents.has(event)) {
|
|
96
|
+
const check = checkEditedValueForItem(item, editedValue);
|
|
97
|
+
if (!check.ok) {
|
|
98
|
+
issues.push({
|
|
99
|
+
...eventRef,
|
|
100
|
+
code: check.code,
|
|
101
|
+
reviewItemName: itemName,
|
|
102
|
+
candidateId: event.spec.candidateId,
|
|
103
|
+
message: `ReviewSessionEvent ${event.metadata.name}: ${check.message}`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
89
107
|
if (decision === "could-not-confirm" && !event.spec.resolutionReason?.trim()) {
|
|
90
108
|
issues.push({
|
|
91
109
|
...eventRef,
|
|
@@ -114,6 +132,65 @@ export function validateReviewSessionEventsForSnapshot(snapshot, events) {
|
|
|
114
132
|
});
|
|
115
133
|
return [...sequenceIssues, ...replayIssues];
|
|
116
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Lists the legacy text edits replay converts to typed values, and refused
|
|
137
|
+
* edits that a later decision superseded. Call it only on an event stream that
|
|
138
|
+
* validateReviewSessionEventsForSnapshot accepted.
|
|
139
|
+
*/
|
|
140
|
+
export function reviewSessionReplayWarningsForSnapshot(snapshot, events) {
|
|
141
|
+
const itemsByName = new Map(snapshot.items.map((item) => [item.metadata.name, item]));
|
|
142
|
+
const finalDecisionEvents = finalDecisionEventPerItem(events);
|
|
143
|
+
return events.flatMap((event) => {
|
|
144
|
+
const itemName = event.spec.reviewItemName;
|
|
145
|
+
const item = itemName ? itemsByName.get(itemName) : undefined;
|
|
146
|
+
const editedValue = editedValueFromDecisionEvent(event);
|
|
147
|
+
if (!item || !itemName || editedValue === undefined
|
|
148
|
+
|| replayableWorkbenchDecision(event.spec.data?.workbenchDecision) !== "accept-proposed") {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
const check = checkEditedValueForItem(item, editedValue);
|
|
152
|
+
if (!check.ok) {
|
|
153
|
+
return finalDecisionEvents.has(event) ? [] : [{
|
|
154
|
+
code: "superseded-edited-value-refused",
|
|
155
|
+
eventName: event.metadata.name,
|
|
156
|
+
sequence: event.spec.sequence,
|
|
157
|
+
reviewItemName: itemName,
|
|
158
|
+
refusedCode: check.code,
|
|
159
|
+
editedValue,
|
|
160
|
+
message: `ReviewSessionEvent ${event.metadata.name} carries an edit a later decision superseded, which would be refused: ${check.message}`,
|
|
161
|
+
}];
|
|
162
|
+
}
|
|
163
|
+
if (!check.convertedFromText || typeof editedValue !== "string") {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
return [{
|
|
167
|
+
code: "edited-value-converted-from-text",
|
|
168
|
+
eventName: event.metadata.name,
|
|
169
|
+
sequence: event.spec.sequence,
|
|
170
|
+
reviewItemName: itemName,
|
|
171
|
+
originalValue: editedValue,
|
|
172
|
+
convertedValue: check.value,
|
|
173
|
+
message: `ReviewSessionEvent ${event.metadata.name} stores edited value ${JSON.stringify(editedValue)} as text; replay converted it to ${JSON.stringify(check.value)} per ReviewItem ${itemName}'s value descriptor.`,
|
|
174
|
+
}];
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/** The last decision event (by sequence, as replay applies them) for each ReviewItem. */
|
|
178
|
+
function finalDecisionEventPerItem(events) {
|
|
179
|
+
const lastByItem = new Map();
|
|
180
|
+
for (const event of [...events].sort((left, right) => left.spec.sequence - right.spec.sequence)) {
|
|
181
|
+
if ((event.spec.eventType === "decision-changed" || event.spec.eventType === "decision-submitted")
|
|
182
|
+
&& event.spec.reviewItemName) {
|
|
183
|
+
lastByItem.set(event.spec.reviewItemName, event);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return new Set(lastByItem.values());
|
|
187
|
+
}
|
|
188
|
+
function editedValueFromDecisionEvent(event) {
|
|
189
|
+
return (event.spec.eventType === "decision-changed" || event.spec.eventType === "decision-submitted")
|
|
190
|
+
&& event.spec.data && "workbenchEditedValue" in event.spec.data
|
|
191
|
+
? event.spec.data.workbenchEditedValue
|
|
192
|
+
: undefined;
|
|
193
|
+
}
|
|
117
194
|
function replayableWorkbenchDecision(value) {
|
|
118
195
|
return typeof value === "string" && value in workbenchDecisionDefinitions
|
|
119
196
|
? value
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReviewQueueSessionState, type ReviewWorkbenchDecision, type ReviewWorkbenchState } from "./review-queue-session.js";
|
|
2
|
-
import { type ReviewSessionReplayIssue } from "./review-session-replay.js";
|
|
2
|
+
import { type ReviewSessionReplayIssue, type ReviewSessionReplayWarning } from "./review-session-replay.js";
|
|
3
3
|
import { type ReviewPresentationAdapter } from "./review-presentation.js";
|
|
4
4
|
import { type ReviewCandidate, type ReviewDecision, type ReviewItem, type ReviewSession, type ReviewSessionEvent, type ReviewValueDescriptor } from "../review-resource.js";
|
|
5
5
|
export { reviewAuditRowKeys, type ReviewAuditRowKey } from "./audit-rows.js";
|
|
@@ -8,7 +8,8 @@ export { buildExtractionInspectorModel, exportExtractionInspector, filterExtract
|
|
|
8
8
|
export { buildReviewSessionEvents, buildReviewSessionEvent, buildReviewSessionResource, candidateForDecision, keepActionDecision, currentReviewItem, currentReviewWorkbenchState, defaultReviewSessionName, deriveQueueRowStatus, initialReviewQueueSessionState, initialReviewWorkbenchState, nextUnresolvedItemName, replayReviewSessionEvents, reviewSessionSummary, reviewWorkbenchSessionStorageKey, selectedCandidateRole, workbenchDecisionDefinitions, type ReviewQueueRowStatus, type ReviewQueueSessionState, type ReviewSessionSummary, type ReviewWorkbenchDecision, type ReviewWorkbenchState, } from "./review-queue-session.js";
|
|
9
9
|
export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, type ReviewCandidatePresentation, type ReviewCandidatePresentationContext, type ReviewItemPresentation, type ReviewItemPresentationContext, type ReviewPresentationAdapter, type ReviewPresentationLink, type ReviewResultPresentation, type ReviewTracePresentationContext, type ReviewTraceRef, type ReviewValuePresentationContext, } from "./review-presentation.js";
|
|
10
10
|
export { buildSurfaceProjectionPreview, type PreviewAuthorityTrace, type PreviewCandidateHistory, type PreviewClaim, type PreviewIntegrityPosture, type PreviewReviewEvent, type PreviewSourceAuthority, type PreviewSourceEvidence, type SurfaceProjectionPreview, } from "./review-surface-preview.js";
|
|
11
|
-
export { validateReviewSessionEventsForSnapshot, type ReviewSessionReplayIssue, type ReviewSessionReplayIssueCode, } from "./review-session-replay.js";
|
|
11
|
+
export { reviewSessionReplayWarningsForSnapshot, validateReviewSessionEventsForSnapshot, type ReviewSessionReplayIssue, type ReviewSessionReplayIssueCode, type ReviewSessionReplayWarning, } from "./review-session-replay.js";
|
|
12
|
+
export { checkEditedValueForItem, editedValueFromEditorText, type EditedValueCheck, } from "./edited-value.js";
|
|
12
13
|
export { assertReviewDecisionModeAllows, DecisionModeViolationError, validateReviewDecisionMode, type ReviewDecisionModeIssue, type ReviewDecisionModeIssueCode, type ReviewDecisionModeResult, } from "./producer-decision-mode.js";
|
|
13
14
|
export declare function buildReviewDecision(state: ReviewWorkbenchState): ReviewDecision | undefined;
|
|
14
15
|
export interface ReviewWorkbenchSessionExport {
|
|
@@ -34,6 +35,8 @@ export interface DeriveReviewSessionApplyResultForSnapshotOptions {
|
|
|
34
35
|
export type DeriveReviewSessionApplyResultForSnapshotResult = {
|
|
35
36
|
readonly ok: true;
|
|
36
37
|
readonly issues: readonly [];
|
|
38
|
+
/** Non-fatal replay notes, e.g. a legacy text edit converted to its typed value. */
|
|
39
|
+
readonly warnings?: readonly ReviewSessionReplayWarning[];
|
|
37
40
|
readonly unresolvedItemNames: readonly string[];
|
|
38
41
|
readonly replayedSession: ReviewQueueSessionState;
|
|
39
42
|
readonly sessionExport: ReviewWorkbenchSessionExport;
|
|
@@ -42,6 +45,7 @@ export type DeriveReviewSessionApplyResultForSnapshotResult = {
|
|
|
42
45
|
} | {
|
|
43
46
|
readonly ok: false;
|
|
44
47
|
readonly issues: readonly ReviewSessionApplyIssue[];
|
|
48
|
+
readonly warnings?: readonly ReviewSessionReplayWarning[];
|
|
45
49
|
readonly unresolvedItemNames: readonly string[];
|
|
46
50
|
readonly replayedSession?: ReviewQueueSessionState;
|
|
47
51
|
readonly sessionExport?: ReviewWorkbenchSessionExport;
|
|
@@ -207,8 +211,8 @@ export declare function buildReviewQueueWindow(session: ReviewQueueSessionState,
|
|
|
207
211
|
* error message when the value violates the declared type/enum constraint, or
|
|
208
212
|
* `undefined` when it is acceptable — including when there is no descriptor or
|
|
209
213
|
* the type carries no single-line constraint (string/array/object). This is a
|
|
210
|
-
* FORMAT check
|
|
211
|
-
*
|
|
214
|
+
* FORMAT check on the editor text; on accept the workbench stores the text
|
|
215
|
+
* converted to the descriptor's JSON type (`editedValueFromEditorText`).
|
|
212
216
|
*/
|
|
213
217
|
export declare function validateProposedValue(descriptor: ReviewValueDescriptor | undefined, rawValue: string): string | undefined;
|
|
214
218
|
export declare function mountReviewWorkbench(root: HTMLElement, startState?: ReviewQueueSessionState | ReviewWorkbenchState, options?: MountReviewWorkbenchOptions): void;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { canonicalJson } from "./canonical.js";
|
|
2
2
|
import { assertReviewResolutionConsistency } from "../producer-discipline.js";
|
|
3
3
|
import { candidateForDecision, keepActionDecision, buildReviewSessionEvent, buildReviewSessionEvents, buildReviewSessionResource, currentReviewWorkbenchState, defaultReviewSessionName, effectiveValueForDecision, initialReviewQueueSessionState, replayReviewSessionEvents, reviewWorkbenchSessionStorageKey, workbenchDecisionDefinitions, } from "./review-queue-session.js";
|
|
4
|
-
import { validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
4
|
+
import { reviewSessionReplayWarningsForSnapshot, validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
5
5
|
import { buildSurfaceProjectionPreview, formatValue, } from "./review-surface-preview.js";
|
|
6
6
|
import { buildReviewCandidatePresentation, buildReviewItemPresentation, } from "./review-presentation.js";
|
|
7
7
|
import { findSoleCandidateById, reviewResourceApiVersion, } from "../review-resource.js";
|
|
8
8
|
import { validateAuthorizing, buildAuthorizedActionAuthorizing } from "../review-authorizing.js";
|
|
9
9
|
import { humanizeIdentifier } from "./review-presentation.js";
|
|
10
|
+
import { editedValueFromEditorText, isIsoCalendarDate, parsePlainDecimal } from "./edited-value.js";
|
|
10
11
|
import { createAuditFactTrace, } from "./audit-rows.js";
|
|
11
12
|
export { reviewAuditRowKeys } from "./audit-rows.js";
|
|
12
13
|
export { assertReviewQueueAgainstExtractionImport, assertReviewQueueBinding, bindReviewQueue, hashReviewQueueSnapshot, UnattestedExtractionQueueError, UnattestedReviewQueueError, validateReviewQueueAgainstExtractionImport, validateReviewQueueBinding, } from "./queue-binding.js";
|
|
@@ -14,7 +15,8 @@ export { buildExtractionInspectorModel, exportExtractionInspector, filterExtract
|
|
|
14
15
|
export { buildReviewSessionEvents, buildReviewSessionEvent, buildReviewSessionResource, candidateForDecision, keepActionDecision, currentReviewItem, currentReviewWorkbenchState, defaultReviewSessionName, deriveQueueRowStatus, initialReviewQueueSessionState, initialReviewWorkbenchState, nextUnresolvedItemName, replayReviewSessionEvents, reviewSessionSummary, reviewWorkbenchSessionStorageKey, selectedCandidateRole, workbenchDecisionDefinitions, } from "./review-queue-session.js";
|
|
15
16
|
export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, } from "./review-presentation.js";
|
|
16
17
|
export { buildSurfaceProjectionPreview, } from "./review-surface-preview.js";
|
|
17
|
-
export { validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
18
|
+
export { reviewSessionReplayWarningsForSnapshot, validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
19
|
+
export { checkEditedValueForItem, editedValueFromEditorText, } from "./edited-value.js";
|
|
18
20
|
export { assertReviewDecisionModeAllows, DecisionModeViolationError, validateReviewDecisionMode, } from "./producer-decision-mode.js";
|
|
19
21
|
export function buildReviewDecision(state) {
|
|
20
22
|
if (!state.decision) {
|
|
@@ -332,6 +334,7 @@ export function deriveReviewSessionApplyResultForSnapshot(options) {
|
|
|
332
334
|
results: [],
|
|
333
335
|
};
|
|
334
336
|
}
|
|
337
|
+
const warnings = reviewSessionReplayWarningsForSnapshot(options.snapshot, options.events);
|
|
335
338
|
const replayedSession = replayReviewSessionEvents(options.snapshot, options.events);
|
|
336
339
|
const sessionExport = buildReviewWorkbenchSessionExport(replayedSession, options.events);
|
|
337
340
|
const resolvedItemNames = new Set(sessionExport.results.map((result) => result.reviewItemName));
|
|
@@ -356,6 +359,7 @@ export function deriveReviewSessionApplyResultForSnapshot(options) {
|
|
|
356
359
|
return {
|
|
357
360
|
ok: false,
|
|
358
361
|
issues,
|
|
362
|
+
warnings,
|
|
359
363
|
unresolvedItemNames,
|
|
360
364
|
replayedSession,
|
|
361
365
|
sessionExport,
|
|
@@ -366,6 +370,7 @@ export function deriveReviewSessionApplyResultForSnapshot(options) {
|
|
|
366
370
|
return {
|
|
367
371
|
ok: true,
|
|
368
372
|
issues: [],
|
|
373
|
+
warnings,
|
|
369
374
|
unresolvedItemNames,
|
|
370
375
|
replayedSession,
|
|
371
376
|
sessionExport,
|
|
@@ -804,8 +809,8 @@ function toDateInputValue(text) {
|
|
|
804
809
|
* error message when the value violates the declared type/enum constraint, or
|
|
805
810
|
* `undefined` when it is acceptable — including when there is no descriptor or
|
|
806
811
|
* the type carries no single-line constraint (string/array/object). This is a
|
|
807
|
-
* FORMAT check
|
|
808
|
-
*
|
|
812
|
+
* FORMAT check on the editor text; on accept the workbench stores the text
|
|
813
|
+
* converted to the descriptor's JSON type (`editedValueFromEditorText`).
|
|
809
814
|
*/
|
|
810
815
|
export function validateProposedValue(descriptor, rawValue) {
|
|
811
816
|
if (!descriptor)
|
|
@@ -815,7 +820,9 @@ export function validateProposedValue(descriptor, rawValue) {
|
|
|
815
820
|
case "number":
|
|
816
821
|
if (value === "")
|
|
817
822
|
return "Enter a number.";
|
|
818
|
-
return
|
|
823
|
+
return parsePlainDecimal(value) !== undefined
|
|
824
|
+
? undefined
|
|
825
|
+
: `"${rawValue}" is not a number (use plain decimal digits: any safe integer, or a fraction of at most 15 significant digits).`;
|
|
819
826
|
case "boolean":
|
|
820
827
|
if (value === "")
|
|
821
828
|
return "Choose true or false.";
|
|
@@ -823,7 +830,7 @@ export function validateProposedValue(descriptor, rawValue) {
|
|
|
823
830
|
case "date":
|
|
824
831
|
if (value === "")
|
|
825
832
|
return "Enter a date.";
|
|
826
|
-
return
|
|
833
|
+
return isIsoCalendarDate(value)
|
|
827
834
|
? undefined
|
|
828
835
|
: `"${rawValue}" is not a valid date (YYYY-MM-DD).`;
|
|
829
836
|
case "enum": {
|
|
@@ -1210,7 +1217,12 @@ function createReviewWorkbenchController(root, startState, options) {
|
|
|
1210
1217
|
const originalText = proposed ? formatValue(proposed.value) : undefined;
|
|
1211
1218
|
const nextEditedValuesByItemName = { ...session.editedValuesByItemName };
|
|
1212
1219
|
if (decision === "accept-proposed" && rawEditedValue !== undefined && rawEditedValue !== originalText) {
|
|
1213
|
-
|
|
1220
|
+
// Store the edit as the descriptor's JSON type (a number field's "42" is
|
|
1221
|
+
// 42), so the decision event and effectiveValue carry a typed value the
|
|
1222
|
+
// server apply boundary accepts (kontourai/survey#278). Text that does not
|
|
1223
|
+
// parse is kept as typed; the apply boundary refuses it.
|
|
1224
|
+
const typedValue = editedValueFromEditorText(item?.spec.valueDescriptor, rawEditedValue);
|
|
1225
|
+
nextEditedValuesByItemName[itemName] = typedValue === undefined ? rawEditedValue : typedValue;
|
|
1214
1226
|
}
|
|
1215
1227
|
else {
|
|
1216
1228
|
delete nextEditedValuesByItemName[itemName];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type DeriveReviewSessionApplyResultForSnapshotResult, type MapReviewWorkbenchResultsToApplyActionsOptions, type ReviewApplyActionIssue, type ReviewApplyActionMapping, type ReviewSessionApplyIssue, type ReviewSessionApplyResolutionRequirement, type ReviewWorkbenchResult } from "./review-workbench.js";
|
|
2
2
|
import { type ReviewDecisionModeIssue } from "./producer-decision-mode.js";
|
|
3
|
-
import { type ReviewSessionReplayIssue } from "./review-session-replay.js";
|
|
3
|
+
import { type ReviewSessionReplayIssue, type ReviewSessionReplayWarning } from "./review-session-replay.js";
|
|
4
4
|
import type { ReviewDecision, ReviewSessionEvent } from "../review-resource.js";
|
|
5
5
|
import { type ReviewQueueBinding } from "./queue-binding.js";
|
|
6
6
|
import { type ReviewQueueSessionState } from "./review-queue-session.js";
|
|
@@ -128,6 +128,8 @@ export type ApplyReviewSessionIssue = ReviewSessionApplyIssue | {
|
|
|
128
128
|
export type ApplyReviewSessionResult<TAction = never> = {
|
|
129
129
|
readonly ok: true;
|
|
130
130
|
readonly issues: readonly [];
|
|
131
|
+
/** Non-fatal replay notes, e.g. a legacy text edit converted to its typed value. */
|
|
132
|
+
readonly warnings?: readonly ReviewSessionReplayWarning[];
|
|
131
133
|
readonly decisions: readonly ReviewDecision[];
|
|
132
134
|
readonly results: readonly ReviewWorkbenchResult[];
|
|
133
135
|
readonly actions: readonly ReviewApplyActionMapping<TAction>[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { candidateReviewRecord } from "./builder.js";
|
|
2
|
+
import { ReviewAgreementError } from "./to-surface.js";
|
|
2
3
|
export function reviewedCandidateResolution(input) {
|
|
3
4
|
return candidateReviewRecord({
|
|
4
5
|
id: input.id,
|
|
@@ -11,13 +12,12 @@ export function reviewedCandidateResolution(input) {
|
|
|
11
12
|
...input.reviewOutcome,
|
|
12
13
|
candidateId: input.reviewOutcome.candidateId ?? input.selectedCandidateId,
|
|
13
14
|
},
|
|
14
|
-
observations: input.observations.map((observation) =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
})),
|
|
15
|
+
observations: input.observations.map((observation) => {
|
|
16
|
+
const status = observation.claim.status ?? claimStatusForObservation(input, observation);
|
|
17
|
+
if (observationCandidateId(observation) === input.selectedCandidateId)
|
|
18
|
+
assertSelectedStatusAgrees(input, status);
|
|
19
|
+
return { ...observation, claim: { ...observation.claim, status } };
|
|
20
|
+
}),
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
function claimStatusForObservation(input, observation) {
|
|
@@ -26,6 +26,12 @@ function claimStatusForObservation(input, observation) {
|
|
|
26
26
|
}
|
|
27
27
|
return input.unselectedClaimStatus ?? "superseded";
|
|
28
28
|
}
|
|
29
|
+
/** A trusted selected claim must carry the status its review decided. */
|
|
30
|
+
function assertSelectedStatusAgrees(input, status) {
|
|
31
|
+
if ((status === "verified" || status === "assumed") && status !== input.reviewOutcome.status) {
|
|
32
|
+
throw new ReviewAgreementError("status-mismatch", `Candidate set ${input.id} selected claim status ${status} disagrees with review outcome status ${input.reviewOutcome.status}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
29
35
|
function observationCandidateId(observation) {
|
|
30
36
|
return observation.candidate?.id ?? `${observation.id}.candidate`;
|
|
31
37
|
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* capping.
|
|
22
22
|
*/
|
|
23
23
|
import type { TrustBundle } from "@kontourai/surface";
|
|
24
|
+
import type { AutoAcceptWarning } from "./producer-profile.js";
|
|
24
25
|
import type { Candidate, CandidateSet, ReviewOutcome, SurveyInput } from "./types.js";
|
|
25
26
|
/**
|
|
26
27
|
* A stable reference to one field within one system's schema.
|
|
@@ -109,6 +110,10 @@ export interface SchemaMappingOptions {
|
|
|
109
110
|
* If set, proposals at or above this confidence threshold are auto-accepted
|
|
110
111
|
* as "assumed" (mirrors applyAutoAcceptPolicy in inquiry-mapping).
|
|
111
112
|
* Conflicting proposals are never auto-accepted.
|
|
113
|
+
*
|
|
114
|
+
* Experimental: the gate trusts the proposer's self-reported confidence.
|
|
115
|
+
* Must be a finite number in (0, 1] (otherwise `RangeError`); a proposal
|
|
116
|
+
* whose confidence is not a finite number in [0, 1] is never auto-accepted.
|
|
112
117
|
*/
|
|
113
118
|
autoAcceptMinConfidence?: number;
|
|
114
119
|
/** ISO 8601 timestamp; defaults to new Date().toISOString(). */
|
|
@@ -116,6 +121,18 @@ export interface SchemaMappingOptions {
|
|
|
116
121
|
/** Identifies the Survey producer run. */
|
|
117
122
|
source?: string;
|
|
118
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* The schema-mapping profile's own payload, carried verbatim under the core's
|
|
126
|
+
* canonical `Candidate.metadata` key (see `./producer-profile.js`). Covers
|
|
127
|
+
* every field read across `mappingReviewToSurface`'s read-back sites and
|
|
128
|
+
* written at Candidate-projection time below.
|
|
129
|
+
*/
|
|
130
|
+
/**
|
|
131
|
+
* The value a schema-mapping candidate and its claim carry: the whole mapping,
|
|
132
|
+
* source field included, so a reviewed claim's value is exactly the reviewed
|
|
133
|
+
* candidate value.
|
|
134
|
+
*/
|
|
135
|
+
export type SchemaMappingValue = Pick<MappingProposalRecord, "relation" | "sourceField" | "targetField" | "conversion">;
|
|
119
136
|
/**
|
|
120
137
|
* Run the extractor against the provided system schemas and project the
|
|
121
138
|
* resulting proposals into the standard Survey chain:
|
|
@@ -142,6 +159,12 @@ export declare function surveySchemaMapping(context: {
|
|
|
142
159
|
surveyInput: SurveyInput;
|
|
143
160
|
proposals: MappingProposalRecord[];
|
|
144
161
|
candidateSets: CandidateSet[];
|
|
162
|
+
/**
|
|
163
|
+
* Proposals the auto-accept policy refused because their confidence is not
|
|
164
|
+
* a finite number in [0, 1]. Present only when non-empty; those proposals
|
|
165
|
+
* stay in human review.
|
|
166
|
+
*/
|
|
167
|
+
autoAcceptWarnings?: AutoAcceptWarning[];
|
|
145
168
|
}>;
|
|
146
169
|
/**
|
|
147
170
|
* A reviewed (accepted or rejected) mapping record: the CandidateSet, the
|