@kontourai/survey 3.0.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 +4 -0
- package/dist/example-data/public-directory-review-resource.d.ts +3 -3
- package/dist/examples/calibrated-auto-accept.d.ts +22 -15
- package/dist/examples/calibrated-auto-accept.js +40 -36
- 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 +8 -7
- 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 +70 -65
- 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 +5 -4
|
@@ -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
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* so that resolveInquiry can resolve across systems with weakest-link
|
|
21
21
|
* capping.
|
|
22
22
|
*/
|
|
23
|
-
import { evaluateAutoAccept, getProducerProposal, projectProposalsToCandidateSet, } from "./producer-profile.js";
|
|
23
|
+
import { assertValidAutoAcceptThreshold, evaluateAutoAccept, getProducerProposal, projectProposalsToCandidateSet, } from "./producer-profile.js";
|
|
24
24
|
import { buildSurveyTrustBundle } from "./to-surface.js";
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
// Canonical pair key
|
|
@@ -35,6 +35,14 @@ function fieldPairKey(a, b) {
|
|
|
35
35
|
function mappingSubjectId(a, b) {
|
|
36
36
|
return fieldPairKey(a, b);
|
|
37
37
|
}
|
|
38
|
+
function mappingValue(proposal) {
|
|
39
|
+
return {
|
|
40
|
+
relation: proposal.relation,
|
|
41
|
+
sourceField: proposal.sourceField,
|
|
42
|
+
targetField: proposal.targetField,
|
|
43
|
+
conversion: proposal.conversion,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
38
46
|
// ---------------------------------------------------------------------------
|
|
39
47
|
// surveySchemaMapping
|
|
40
48
|
// ---------------------------------------------------------------------------
|
|
@@ -56,9 +64,12 @@ function mappingSubjectId(a, b) {
|
|
|
56
64
|
* storage) or for mappingReviewToSurface after human review.
|
|
57
65
|
*/
|
|
58
66
|
export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
67
|
+
if (options.autoAcceptMinConfidence !== undefined)
|
|
68
|
+
assertValidAutoAcceptThreshold(options.autoAcceptMinConfidence);
|
|
59
69
|
const generatedAt = options.generatedAt ?? new Date().toISOString();
|
|
60
70
|
const source = options.source ?? `schema-mapping:${extractor.name}`;
|
|
61
71
|
const proposals = await Promise.resolve(extractor.extract(context));
|
|
72
|
+
const autoAcceptWarnings = [];
|
|
62
73
|
// One RawSource per system schema
|
|
63
74
|
const rawSources = context.systems.map((s) => ({
|
|
64
75
|
id: `schema-mapping.source.${s.system}`,
|
|
@@ -93,11 +104,7 @@ export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
|
93
104
|
id: extractionId,
|
|
94
105
|
sourceId: rawSource.id,
|
|
95
106
|
target: `${proposal.sourceField.entity}.${proposal.sourceField.field}:maps-to:${proposal.targetField.entity}.${proposal.targetField.field}`,
|
|
96
|
-
value:
|
|
97
|
-
relation: proposal.relation,
|
|
98
|
-
targetField: proposal.targetField,
|
|
99
|
-
conversion: proposal.conversion,
|
|
100
|
-
},
|
|
107
|
+
value: mappingValue(proposal),
|
|
101
108
|
confidence: proposal.confidence,
|
|
102
109
|
locator: proposal.sourceField.locator ?? `structured-field:${proposal.sourceField.entity}.${proposal.sourceField.field}`,
|
|
103
110
|
excerpt: proposal.evidence.map((e) => `[${e.system}] ${e.excerpt}`).join(" | "),
|
|
@@ -120,11 +127,7 @@ export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
|
120
127
|
return {
|
|
121
128
|
candidateId: `schema-mapping.candidate.${proposal.id}`,
|
|
122
129
|
extractionId,
|
|
123
|
-
value:
|
|
124
|
-
relation: proposal.relation,
|
|
125
|
-
targetField: proposal.targetField,
|
|
126
|
-
conversion: proposal.conversion,
|
|
127
|
-
},
|
|
130
|
+
value: mappingValue(proposal),
|
|
128
131
|
confidence: proposal.confidence,
|
|
129
132
|
equivalenceKey: proposal.relation,
|
|
130
133
|
metadata: {
|
|
@@ -179,6 +182,13 @@ export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
|
179
182
|
rationale: selectedProposal?.rationale,
|
|
180
183
|
proposedAt: selectedProposal?.proposedAt,
|
|
181
184
|
}, hasConflict, { minConfidence: options.autoAcceptMinConfidence }, generatedAt);
|
|
185
|
+
if (decision.warning) {
|
|
186
|
+
autoAcceptWarnings.push({
|
|
187
|
+
code: decision.warning,
|
|
188
|
+
proposalId: selectedProposal?.proposalId ?? selectedCandidate.id,
|
|
189
|
+
confidence: decision.confidence,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
182
192
|
if (decision.accepted) {
|
|
183
193
|
const reviewId = `schema-mapping.review.${pairKey}`;
|
|
184
194
|
reviewOutcomes.push({
|
|
@@ -208,12 +218,8 @@ export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
|
208
218
|
facet: "schema-mapping.profile",
|
|
209
219
|
claimType: "schema-mapping.field-link",
|
|
210
220
|
fieldOrBehavior: "maps-to",
|
|
211
|
-
value:
|
|
212
|
-
|
|
213
|
-
sourceField: first.sourceField,
|
|
214
|
-
targetField: first.targetField,
|
|
215
|
-
conversion: first.conversion,
|
|
216
|
-
},
|
|
221
|
+
// No value override: the claim carries the selected candidate's value,
|
|
222
|
+
// which is the whole mapping a reviewer (or auto-accept) decided on.
|
|
217
223
|
...(claimStatus ? { status: claimStatus } : {}),
|
|
218
224
|
impactLevel: "medium",
|
|
219
225
|
collectedBy: extractor.name,
|
|
@@ -241,7 +247,12 @@ export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
|
241
247
|
reviewOutcomes,
|
|
242
248
|
claims,
|
|
243
249
|
};
|
|
244
|
-
return {
|
|
250
|
+
return {
|
|
251
|
+
surveyInput,
|
|
252
|
+
proposals,
|
|
253
|
+
candidateSets,
|
|
254
|
+
...(autoAcceptWarnings.length > 0 ? { autoAcceptWarnings } : {}),
|
|
255
|
+
};
|
|
245
256
|
}
|
|
246
257
|
// ---------------------------------------------------------------------------
|
|
247
258
|
// mappingReviewToSurface
|
|
@@ -315,7 +326,7 @@ export function mappingReviewToSurface(reviewedMappings, options = {}) {
|
|
|
315
326
|
id: extractionId,
|
|
316
327
|
sourceId: rawSourceId,
|
|
317
328
|
target: `${sourceField.entity}.${sourceField.field}:maps-to:${targetField.entity}.${targetField.field}`,
|
|
318
|
-
value: { relation, targetField, conversion },
|
|
329
|
+
value: { relation, sourceField, targetField, conversion },
|
|
319
330
|
confidence,
|
|
320
331
|
locator: sourceField.locator ?? `structured-field:${sourceField.entity}.${sourceField.field}`,
|
|
321
332
|
excerpt: evidence.map((e) => `[${e.system}] ${e.excerpt}`).join(" | "),
|
|
@@ -352,7 +363,6 @@ export function mappingReviewToSurface(reviewedMappings, options = {}) {
|
|
|
352
363
|
facet: "schema-mapping.profile",
|
|
353
364
|
claimType: "schema-mapping.field-link",
|
|
354
365
|
fieldOrBehavior: "maps-to",
|
|
355
|
-
value: { relation, sourceField, targetField, conversion },
|
|
356
366
|
status: rm.reviewOutcome.status,
|
|
357
367
|
impactLevel: "medium",
|
|
358
368
|
collectedBy: proposedBy,
|
package/dist/src/to-surface.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ import type { TrustBundle } from "@kontourai/surface";
|
|
|
2
2
|
import { type CalibrationMetrics } from "./calibration.js";
|
|
3
3
|
import type { SurveyInput } from "./types.js";
|
|
4
4
|
export interface SurveyCalibrationOptions {
|
|
5
|
+
/**
|
|
6
|
+
* EXPERIMENTAL opt-in, required for any `conclusionConfidence.value` to be
|
|
7
|
+
* set (#279). The value is the affirmation rate of the claim's whole
|
|
8
|
+
* extractor/field GROUP — a base rate every affirmed claim in the group
|
|
9
|
+
* shares — not a per-claim probability. Without this flag the `calibration`
|
|
10
|
+
* option sets no value.
|
|
11
|
+
*/
|
|
12
|
+
experimentalConclusionValue?: boolean;
|
|
5
13
|
/**
|
|
6
14
|
* Precomputed calibration to source the value from — typically derived over a
|
|
7
15
|
* LONGER history than the current batch (a better-grounded curve, and it avoids
|
|
@@ -30,16 +38,32 @@ export interface BuildSurveyTrustBundleOptions {
|
|
|
30
38
|
*/
|
|
31
39
|
projectionContextId?: string;
|
|
32
40
|
/**
|
|
33
|
-
* Populate `conclusionConfidence.value` from empirical review
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
41
|
+
* EXPERIMENTAL. Populate `conclusionConfidence.value` from empirical review
|
|
42
|
+
* calibration — the affirmation rate of the claim's extractor/field group
|
|
43
|
+
* (a group base rate, not a per-claim probability; see #114/#137/#279).
|
|
44
|
+
* A value is set ONLY with `{ experimentalConclusionValue: true }`; `true` or
|
|
45
|
+
* an object without that flag sets no value. The object may also supply
|
|
46
|
+
* precomputed `metrics` and/or a `minSamples` floor. Absent → `value` stays
|
|
47
|
+
* unset and only the comfort-zone signal is carried.
|
|
39
48
|
*
|
|
40
49
|
* ADVISORY (ADR 0003 §4): this only enriches the emitted conclusion confidence;
|
|
41
50
|
* it never changes a claim's `status`.
|
|
42
51
|
*/
|
|
43
52
|
calibration?: boolean | SurveyCalibrationOptions;
|
|
44
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Thrown when a claim's trusted status or value does not agree with the review
|
|
56
|
+
* outcome it cites, or when the governing review cannot be chosen.
|
|
57
|
+
* - `status-mismatch`: a `verified`/`assumed` claim cites a review whose status differs.
|
|
58
|
+
* - `value-mismatch`: a `verified`/`assumed` claim carries a value other than the reviewed value.
|
|
59
|
+
* - `ambiguous-review-order`: several reviews apply to one candidate and the latest
|
|
60
|
+
* cannot be determined from `reviewedAt` (missing, unparseable, or tied).
|
|
61
|
+
*/
|
|
62
|
+
export declare class ReviewAgreementError extends Error {
|
|
63
|
+
readonly name = "ReviewAgreementError";
|
|
64
|
+
readonly code: "status-mismatch" | "value-mismatch" | "ambiguous-review-order";
|
|
65
|
+
constructor(code: ReviewAgreementError["code"], message: string);
|
|
66
|
+
}
|
|
45
67
|
export declare function buildSurveyTrustBundle(input: SurveyInput, options?: BuildSurveyTrustBundleOptions): TrustBundle;
|
|
68
|
+
/** Test-only: re-arm the once-per-process calibration opt-in warning. Not exported from the package index. */
|
|
69
|
+
export declare function resetCalibrationOptInWarningForTests(): void;
|