@kontourai/survey 0.4.22 → 0.4.23

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.
@@ -91,73 +91,85 @@ export function assertReviewResultMatches(result, expectation) {
91
91
  }
92
92
  }
93
93
  export function mapReviewWorkbenchResultsToApplyActions(options) {
94
- const issues = [];
94
+ const issues = options.requireUniqueTargets ? duplicateTargetIssues(options.items) : [];
95
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
- });
96
+ const mappings = options.results.flatMap((result) => mapReviewApplyResultToActions({ result, itemByName, options, issues }));
155
97
  if (issues.length > 0) {
156
98
  throw new ReviewApplyActionMappingError(issues);
157
99
  }
158
100
  return mappings;
159
101
  }
160
102
  export const mapReviewApplyActions = mapReviewWorkbenchResultsToApplyActions;
103
+ function duplicateTargetIssues(items) {
104
+ const issues = [];
105
+ const seenTargets = new Set();
106
+ for (const item of items) {
107
+ if (seenTargets.has(item.spec.target)) {
108
+ issues.push({
109
+ code: "duplicate-review-target",
110
+ target: item.spec.target,
111
+ message: `Review target ${item.spec.target} appears on more than one ReviewItem.`,
112
+ });
113
+ }
114
+ seenTargets.add(item.spec.target);
115
+ }
116
+ return issues;
117
+ }
118
+ function mapReviewApplyResultToActions(input) {
119
+ const context = buildReviewApplyActionContext(input.result, input.itemByName, input.issues);
120
+ if (!context || input.options.skip?.(context)) {
121
+ return [];
122
+ }
123
+ const action = input.options.map(context);
124
+ if (action === undefined) {
125
+ input.issues.push({
126
+ code: "unmapped-review-result",
127
+ reviewItemName: input.result.reviewItemName,
128
+ message: `Review result ${input.result.reviewItemName} did not produce an apply action.`,
129
+ });
130
+ return [];
131
+ }
132
+ return (Array.isArray(action) ? action : [action]).map((entry) => ({
133
+ result: context.result,
134
+ item: context.item,
135
+ action: entry,
136
+ }));
137
+ }
138
+ function buildReviewApplyActionContext(result, itemByName, issues) {
139
+ const item = itemByName.get(result.reviewItemName);
140
+ if (!item) {
141
+ issues.push({
142
+ code: "unknown-review-item",
143
+ reviewItemName: result.reviewItemName,
144
+ message: `Review result ${result.reviewItemName} does not reference a known ReviewItem.`,
145
+ });
146
+ return undefined;
147
+ }
148
+ const selectedCandidate = matchingSelectedCandidate(item, result);
149
+ if (!selectedCandidate) {
150
+ issues.push({
151
+ code: "selected-candidate-mismatch",
152
+ reviewItemName: result.reviewItemName,
153
+ message: `Review result ${result.reviewItemName} selected candidate does not match the supplied ReviewItem.`,
154
+ });
155
+ return undefined;
156
+ }
157
+ return {
158
+ result,
159
+ item,
160
+ target: item.spec.target,
161
+ selectedCandidate,
162
+ unselectedCandidates: item.spec.candidates.filter((candidate) => candidate.id !== selectedCandidate.id),
163
+ };
164
+ }
165
+ function matchingSelectedCandidate(item, result) {
166
+ const candidate = item.spec.candidates.find((entry) => entry.id === result.selectedCandidateId);
167
+ return candidate
168
+ && candidate.role === result.selectedCandidateRole
169
+ && structuralEqual(candidate.value, result.selectedValue)
170
+ ? candidate
171
+ : undefined;
172
+ }
161
173
  function structuralEqual(left, right) {
162
174
  return JSON.stringify(canonicalJson(left)) === JSON.stringify(canonicalJson(right));
163
175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontourai/survey",
3
- "version": "0.4.22",
3
+ "version": "0.4.23",
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",