@kontourai/survey 0.4.21 → 0.4.22

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.
@@ -1,7 +1,7 @@
1
1
  import { type ReviewQueueSessionState, type ReviewWorkbenchDecision, type ReviewWorkbenchState } from "./review-queue-session.js";
2
2
  import { type ReviewSessionReplayIssue } from "./review-session-replay.js";
3
3
  import { type ReviewPresentationAdapter } from "./review-presentation.js";
4
- import { type ReviewCandidate, type ReviewDecision, type ReviewSession, type ReviewSessionEvent } from "../review-resource.js";
4
+ import { type ReviewCandidate, type ReviewDecision, type ReviewItem, type ReviewSession, type ReviewSessionEvent } from "../review-resource.js";
5
5
  export { buildReviewSessionEvents, buildReviewSessionEvent, buildReviewSessionResource, candidateForDecision, 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";
6
6
  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";
7
7
  export { buildSurfaceProjectionPreview, type PreviewAuthorityTrace, type PreviewCandidateHistory, type PreviewClaim, type PreviewIntegrityPosture, type PreviewReviewEvent, type PreviewSourceAuthority, type PreviewSourceEvidence, type SurfaceProjectionPreview, } from "./review-surface-preview.js";
@@ -98,7 +98,60 @@ export interface ReviewWorkbenchResult {
98
98
  readonly status: ReviewDecision["spec"]["status"];
99
99
  readonly rationale?: string;
100
100
  }
101
+ export interface ReviewApplyResultExpectation {
102
+ readonly decision?: ReviewWorkbenchDecision;
103
+ readonly status?: ReviewDecision["spec"]["status"];
104
+ readonly selectedCandidateRole?: ReviewCandidate["role"];
105
+ readonly selectedCandidateId?: string;
106
+ readonly selectedValue?: unknown;
107
+ readonly equals?: (left: unknown, right: unknown) => boolean;
108
+ }
109
+ export interface ReviewApplyActionMapping<TAction> {
110
+ readonly result: ReviewWorkbenchResult;
111
+ readonly item: ReviewItem;
112
+ readonly action: TAction;
113
+ }
114
+ export type ReviewApplyActionIssue = {
115
+ readonly code: "unknown-review-item";
116
+ readonly reviewItemName: string;
117
+ readonly message: string;
118
+ } | {
119
+ readonly code: "duplicate-review-target";
120
+ readonly target: string;
121
+ readonly message: string;
122
+ } | {
123
+ readonly code: "selected-candidate-mismatch";
124
+ readonly reviewItemName: string;
125
+ readonly message: string;
126
+ } | {
127
+ readonly code: "unmapped-review-result";
128
+ readonly reviewItemName: string;
129
+ readonly message: string;
130
+ };
131
+ export declare class ReviewApplyActionMappingError extends Error {
132
+ readonly name = "ReviewApplyActionMappingError";
133
+ readonly issues: readonly ReviewApplyActionIssue[];
134
+ constructor(issues: readonly ReviewApplyActionIssue[]);
135
+ }
136
+ export interface ReviewApplyActionContext {
137
+ readonly result: ReviewWorkbenchResult;
138
+ readonly item: ReviewItem;
139
+ readonly target: ReviewItem["spec"]["target"];
140
+ readonly selectedCandidate: ReviewCandidate;
141
+ readonly unselectedCandidates: readonly ReviewCandidate[];
142
+ }
143
+ export interface MapReviewWorkbenchResultsToApplyActionsOptions<TAction> {
144
+ readonly results: readonly ReviewWorkbenchResult[];
145
+ readonly items: readonly ReviewItem[];
146
+ readonly requireUniqueTargets?: boolean;
147
+ readonly skip?: (context: ReviewApplyActionContext) => boolean;
148
+ readonly map: (context: ReviewApplyActionContext) => TAction | readonly TAction[] | undefined;
149
+ }
101
150
  export declare function buildReviewDecisionsFromSession(session: ReviewQueueSessionState): ReviewDecision[];
151
+ export declare function requireReviewResultForItem(item: ReviewItem, results: readonly ReviewWorkbenchResult[]): ReviewWorkbenchResult;
152
+ export declare function assertReviewResultMatches(result: ReviewWorkbenchResult, expectation: ReviewApplyResultExpectation): void;
153
+ export declare function mapReviewWorkbenchResultsToApplyActions<TAction>(options: MapReviewWorkbenchResultsToApplyActionsOptions<TAction>): ReviewApplyActionMapping<TAction>[];
154
+ export declare const mapReviewApplyActions: typeof mapReviewWorkbenchResultsToApplyActions;
102
155
  export declare function buildReviewWorkbenchResultsFromSession(session: ReviewQueueSessionState): ReviewWorkbenchResult[];
103
156
  export declare function buildReviewWorkbenchSessionExport(session: ReviewQueueSessionState, events?: readonly ReviewSessionEvent[]): ReviewWorkbenchSessionExport;
104
157
  export declare function replayReviewSessionEventsForSnapshot(snapshot: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewQueueSessionState;
@@ -42,6 +42,14 @@ export function buildReviewDecision(state) {
42
42
  },
43
43
  };
44
44
  }
45
+ export class ReviewApplyActionMappingError extends Error {
46
+ name = "ReviewApplyActionMappingError";
47
+ issues;
48
+ constructor(issues) {
49
+ super(`Review apply action mapping failed: ${issues.map((issue) => issue.message).join(" ")}`);
50
+ this.issues = issues;
51
+ }
52
+ }
45
53
  export function buildReviewDecisionsFromSession(session) {
46
54
  return session.items.flatMap((item) => {
47
55
  const decision = session.decisionsByItemName[item.metadata.name];
@@ -55,6 +63,116 @@ export function buildReviewDecisionsFromSession(session) {
55
63
  return reviewDecision ? [reviewDecision] : [];
56
64
  });
57
65
  }
66
+ export function requireReviewResultForItem(item, results) {
67
+ const matching = results.filter((result) => result.reviewItemName === item.metadata.name);
68
+ if (matching.length !== 1) {
69
+ throw new Error(`Expected exactly one resolved Survey result for ${item.metadata.name}.`);
70
+ }
71
+ return matching[0];
72
+ }
73
+ export function assertReviewResultMatches(result, expectation) {
74
+ if (expectation.decision && result.decision !== expectation.decision) {
75
+ throw new Error(`Review result ${result.reviewItemName} has decision ${result.decision}; expected ${expectation.decision}.`);
76
+ }
77
+ if (expectation.status && result.status !== expectation.status) {
78
+ throw new Error(`Review result ${result.reviewItemName} has status ${result.status}; expected ${expectation.status}.`);
79
+ }
80
+ if (expectation.selectedCandidateRole && result.selectedCandidateRole !== expectation.selectedCandidateRole) {
81
+ throw new Error(`Review result ${result.reviewItemName} selected ${result.selectedCandidateRole ?? "unknown"} candidate; expected ${expectation.selectedCandidateRole}.`);
82
+ }
83
+ if (expectation.selectedCandidateId && result.selectedCandidateId !== expectation.selectedCandidateId) {
84
+ throw new Error(`Review result ${result.reviewItemName} selected candidate ${result.selectedCandidateId}; expected ${expectation.selectedCandidateId}.`);
85
+ }
86
+ if ("selectedValue" in expectation) {
87
+ const equals = expectation.equals ?? Object.is;
88
+ if (!equals(result.selectedValue, expectation.selectedValue)) {
89
+ throw new Error(`Review result ${result.reviewItemName} selected value does not match the expected value.`);
90
+ }
91
+ }
92
+ }
93
+ export function mapReviewWorkbenchResultsToApplyActions(options) {
94
+ const issues = [];
95
+ const itemByName = new Map(options.items.map((item) => [item.metadata.name, item]));
96
+ if (options.requireUniqueTargets) {
97
+ const seenTargets = new Set();
98
+ for (const item of options.items) {
99
+ if (seenTargets.has(item.spec.target)) {
100
+ issues.push({
101
+ code: "duplicate-review-target",
102
+ target: item.spec.target,
103
+ message: `Review target ${item.spec.target} appears on more than one ReviewItem.`,
104
+ });
105
+ }
106
+ seenTargets.add(item.spec.target);
107
+ }
108
+ }
109
+ const mappings = options.results.flatMap((result) => {
110
+ const item = itemByName.get(result.reviewItemName);
111
+ if (!item) {
112
+ issues.push({
113
+ code: "unknown-review-item",
114
+ reviewItemName: result.reviewItemName,
115
+ message: `Review result ${result.reviewItemName} does not reference a known ReviewItem.`,
116
+ });
117
+ return [];
118
+ }
119
+ const selectedCandidate = item.spec.candidates.find((candidate) => candidate.id === result.selectedCandidateId);
120
+ if (!selectedCandidate
121
+ || selectedCandidate.role !== result.selectedCandidateRole
122
+ || !structuralEqual(selectedCandidate.value, result.selectedValue)) {
123
+ issues.push({
124
+ code: "selected-candidate-mismatch",
125
+ reviewItemName: result.reviewItemName,
126
+ message: `Review result ${result.reviewItemName} selected candidate does not match the supplied ReviewItem.`,
127
+ });
128
+ return [];
129
+ }
130
+ const context = {
131
+ result,
132
+ item,
133
+ target: item.spec.target,
134
+ selectedCandidate,
135
+ unselectedCandidates: item.spec.candidates.filter((candidate) => candidate.id !== selectedCandidate.id),
136
+ };
137
+ if (options.skip?.(context)) {
138
+ return [];
139
+ }
140
+ const action = options.map(context);
141
+ if (action === undefined) {
142
+ issues.push({
143
+ code: "unmapped-review-result",
144
+ reviewItemName: result.reviewItemName,
145
+ message: `Review result ${result.reviewItemName} did not produce an apply action.`,
146
+ });
147
+ return [];
148
+ }
149
+ return (Array.isArray(action) ? action : [action]).map((entry) => ({
150
+ result,
151
+ item,
152
+ action: entry,
153
+ }));
154
+ });
155
+ if (issues.length > 0) {
156
+ throw new ReviewApplyActionMappingError(issues);
157
+ }
158
+ return mappings;
159
+ }
160
+ export const mapReviewApplyActions = mapReviewWorkbenchResultsToApplyActions;
161
+ function structuralEqual(left, right) {
162
+ return JSON.stringify(canonicalJson(left)) === JSON.stringify(canonicalJson(right));
163
+ }
164
+ function canonicalJson(value) {
165
+ if (!value || typeof value !== "object") {
166
+ return value;
167
+ }
168
+ if (Array.isArray(value)) {
169
+ return value.map(canonicalJson);
170
+ }
171
+ return Object.fromEntries(Object.entries(value)
172
+ .filter(([, entry]) => entry !== undefined)
173
+ .sort(([left], [right]) => left.localeCompare(right))
174
+ .map(([key, entry]) => [key, canonicalJson(entry)]));
175
+ }
58
176
  export function buildReviewWorkbenchResultsFromSession(session) {
59
177
  return session.items.flatMap((item) => {
60
178
  const decision = session.decisionsByItemName[item.metadata.name];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontourai/survey",
3
- "version": "0.4.21",
3
+ "version": "0.4.22",
4
4
  "description": "Producer-side source, extraction, candidate, and review contracts for projecting verified claims into Surface.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",