@kontourai/survey 1.14.0 → 1.16.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.
@@ -1,4 +1,5 @@
1
1
  import { publicDirectoryReviewItemExample, reviewWorkbenchQueueExamples } from "./review-workbench-data.js";
2
+ import { assertReviewResolutionConsistency } from "../producer-discipline.js";
2
3
  import { reviewResourceApiVersion, } from "../../src/review-resource.js";
3
4
  export const reviewWorkbenchSessionStorageKey = "kontourai.survey.review-workbench.session-events.v1";
4
5
  export const defaultReviewSessionName = "review-workbench-session";
@@ -21,6 +22,12 @@ export const workbenchDecisionDefinitions = {
21
22
  candidateRole: "proposed",
22
23
  status: "rejected",
23
24
  },
25
+ "could-not-confirm": {
26
+ label: "Could not confirm",
27
+ effect: "The review round ends without changing or escalating the proposed claim.",
28
+ candidateRole: "proposed",
29
+ status: "proposed",
30
+ },
24
31
  };
25
32
  export function initialReviewWorkbenchState(item = publicDirectoryReviewItemExample) {
26
33
  return {
@@ -49,6 +56,7 @@ export function currentReviewWorkbenchState(session) {
49
56
  note: session.notesByItemName[item.metadata.name] ?? "",
50
57
  decision: session.decisionsByItemName[item.metadata.name],
51
58
  editedValue: session.editedValuesByItemName?.[item.metadata.name],
59
+ attemptEvidenceIds: session.attemptEvidenceIdsByItemName?.[item.metadata.name],
52
60
  reviewedAt: session.reviewedAt,
53
61
  actorId: session.actorId,
54
62
  };
@@ -65,6 +73,9 @@ export function deriveQueueRowStatus(item, session) {
65
73
  if (decision === "reject-proposed") {
66
74
  return "rejected";
67
75
  }
76
+ if (decision === "could-not-confirm") {
77
+ return "could-not-confirm";
78
+ }
68
79
  if (decision === "accept-proposed" || decision === "keep-current" || item.spec.candidateSetStatus === "resolved") {
69
80
  return "resolved";
70
81
  }
@@ -96,6 +107,9 @@ export function reviewSessionSummary(session) {
96
107
  if (decision === "reject-proposed") {
97
108
  return { ...summary, rejected: summary.rejected + 1 };
98
109
  }
110
+ if (decision === "could-not-confirm") {
111
+ return { ...summary, couldNotConfirm: (summary.couldNotConfirm ?? 0) + 1 };
112
+ }
99
113
  if (item.spec.candidateSetStatus === "escalated") {
100
114
  return { ...summary, escalated: summary.escalated + 1 };
101
115
  }
@@ -194,6 +208,9 @@ export function buildReviewSessionEvents(session, sessionName = defaultReviewSes
194
208
  if (!decision) {
195
209
  continue;
196
210
  }
211
+ if (decision === "could-not-confirm" && !note?.trim()) {
212
+ throw new Error(`ReviewItem ${item.metadata.name} could not confirm requires a non-empty reason.`);
213
+ }
197
214
  const candidate = candidateForDecision(item, decision);
198
215
  const definition = workbenchDecisionDefinitions[decision];
199
216
  const reviewDecisionName = `${item.metadata.name}-${decision}`;
@@ -203,9 +220,20 @@ export function buildReviewSessionEvents(session, sessionName = defaultReviewSes
203
220
  // the server apply boundary derives effectiveValue from it. Without this
204
221
  // the edit lives only in browser state and never survives replay.
205
222
  const editedValue = decision === "accept-proposed" ? session.editedValuesByItemName?.[item.metadata.name] : undefined;
223
+ const attemptEvidenceIds = decision === "could-not-confirm"
224
+ ? session.attemptEvidenceIdsByItemName?.[item.metadata.name]
225
+ : undefined;
226
+ assertReviewResolutionConsistency(`ReviewItem ${item.metadata.name}`, {
227
+ status: definition.status,
228
+ resolution: decision === "could-not-confirm" ? "could_not_confirm" : undefined,
229
+ resolutionReason: decision === "could-not-confirm" ? note : undefined,
230
+ attemptEvidenceIds,
231
+ actor: session.actorId,
232
+ reviewedAt: session.reviewedAt,
233
+ });
206
234
  const data = editedValue !== undefined
207
235
  ? { workbenchDecision: decision, workbenchEditedValue: editedValue }
208
- : { workbenchDecision: decision };
236
+ : { workbenchDecision: decision, ...(attemptEvidenceIds?.length ? { attemptEvidenceIds } : {}) };
209
237
  events.push(buildReviewSessionEvent(session, {
210
238
  sessionName,
211
239
  sequence: events.length + 1,
@@ -215,6 +243,13 @@ export function buildReviewSessionEvents(session, sessionName = defaultReviewSes
215
243
  reviewDecisionName,
216
244
  candidateId: candidate.id,
217
245
  status: definition.status,
246
+ ...(decision === "could-not-confirm"
247
+ ? {
248
+ resolution: "could_not_confirm",
249
+ resolutionReason: note,
250
+ ...(attemptEvidenceIds ? { attemptEvidenceIds: [...attemptEvidenceIds] } : {}),
251
+ }
252
+ : {}),
218
253
  data,
219
254
  }));
220
255
  events.push(buildReviewSessionEvent(session, {
@@ -227,6 +262,13 @@ export function buildReviewSessionEvents(session, sessionName = defaultReviewSes
227
262
  candidateId: candidate.id,
228
263
  status: definition.status,
229
264
  rationale: note,
265
+ ...(decision === "could-not-confirm"
266
+ ? {
267
+ resolution: "could_not_confirm",
268
+ resolutionReason: note,
269
+ ...(attemptEvidenceIds ? { attemptEvidenceIds: [...attemptEvidenceIds] } : {}),
270
+ }
271
+ : {}),
230
272
  data,
231
273
  }));
232
274
  }
@@ -264,7 +306,13 @@ export function replayReviewSessionEvents(startState, events) {
264
306
  if (isClearedWorkbenchDecisionEvent(event)) {
265
307
  const { [itemName]: _removedDecision, ...remainingDecisions } = session.decisionsByItemName;
266
308
  const { [itemName]: _removedEdit, ...remainingEdits } = session.editedValuesByItemName ?? {};
267
- return { ...session, decisionsByItemName: remainingDecisions, editedValuesByItemName: remainingEdits };
309
+ const { [itemName]: _removedAttempts, ...remainingAttempts } = session.attemptEvidenceIdsByItemName ?? {};
310
+ return {
311
+ ...session,
312
+ decisionsByItemName: remainingDecisions,
313
+ editedValuesByItemName: remainingEdits,
314
+ attemptEvidenceIdsByItemName: remainingAttempts,
315
+ };
268
316
  }
269
317
  const decision = workbenchDecisionFromEvent(event);
270
318
  if (!decision) {
@@ -276,12 +324,19 @@ export function replayReviewSessionEvents(startState, events) {
276
324
  // reviewer moved away from.
277
325
  const editedValue = workbenchEditedValueFromEvent(event);
278
326
  const editedValuesByItemName = { ...session.editedValuesByItemName };
327
+ const attemptEvidenceIdsByItemName = { ...session.attemptEvidenceIdsByItemName };
279
328
  if (decision === "accept-proposed" && editedValue !== undefined) {
280
329
  editedValuesByItemName[itemName] = editedValue;
281
330
  }
282
331
  else {
283
332
  delete editedValuesByItemName[itemName];
284
333
  }
334
+ if (decision === "could-not-confirm" && event.spec.attemptEvidenceIds?.length) {
335
+ attemptEvidenceIdsByItemName[itemName] = [...event.spec.attemptEvidenceIds];
336
+ }
337
+ else {
338
+ delete attemptEvidenceIdsByItemName[itemName];
339
+ }
285
340
  return {
286
341
  ...session,
287
342
  decisionsByItemName: {
@@ -289,6 +344,7 @@ export function replayReviewSessionEvents(startState, events) {
289
344
  [itemName]: decision,
290
345
  },
291
346
  editedValuesByItemName,
347
+ attemptEvidenceIdsByItemName,
292
348
  };
293
349
  }
294
350
  return session;
@@ -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";
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";
4
4
  export interface ReviewSessionReplayIssue {
5
5
  readonly code: ReviewSessionReplayIssueCode;
6
6
  readonly eventName: string;
@@ -76,6 +76,25 @@ export function validateReviewSessionEventsForSnapshot(snapshot, events) {
76
76
  message: `ReviewSessionEvent ${event.metadata.name} decision ${decision} expects status ${expectedStatus}, but references ${event.spec.status ?? "no status"}.`,
77
77
  });
78
78
  }
79
+ const expectedResolution = decision === "could-not-confirm" ? "could_not_confirm" : undefined;
80
+ if (event.spec.resolution !== expectedResolution) {
81
+ issues.push({
82
+ ...eventRef,
83
+ code: "decision-resolution-mismatch",
84
+ reviewItemName: itemName,
85
+ candidateId: event.spec.candidateId,
86
+ message: `ReviewSessionEvent ${event.metadata.name} decision ${decision} expects resolution ${expectedResolution ?? "none"}, but references ${event.spec.resolution ?? "no resolution"}.`,
87
+ });
88
+ }
89
+ if (decision === "could-not-confirm" && !event.spec.resolutionReason?.trim()) {
90
+ issues.push({
91
+ ...eventRef,
92
+ code: "missing-resolution-reason",
93
+ reviewItemName: itemName,
94
+ candidateId: event.spec.candidateId,
95
+ message: `ReviewSessionEvent ${event.metadata.name} decision could-not-confirm requires a non-empty resolutionReason.`,
96
+ });
97
+ }
79
98
  }
80
99
  }
81
100
  if (event.spec.candidateId && itemName && itemsByName.has(itemName)) {
@@ -8,7 +8,7 @@ export function buildSurfaceProjectionPreview(item, decision, presentationAdapte
8
8
  canonicalClaim: buildPreviewClaim(item, selectedCandidate, decision, projection, presentationAdapter),
9
9
  candidateHistory: buildCandidateHistory(item, selectedCandidate, presentationAdapter),
10
10
  sourceEvidence: buildSourceEvidence(selectedCandidate),
11
- reviewEvent: buildReviewEvent(decision, projection),
11
+ reviewEvent: decision.spec.resolution === "could_not_confirm" ? undefined : buildReviewEvent(decision, projection),
12
12
  integrityPosture: buildIntegrityPosture(item, selectedCandidate, projection),
13
13
  authorityTrace: portableAuthorityTrace(selectedCandidate.producer?.authorityTrace),
14
14
  postureDisclaimer: postureDisclaimer(),
@@ -6,149 +6,149 @@ export const REVIEW_WORKBENCH_CSS = `/* Bundled, scoped Survey Review Workbench
6
6
  color-scheme: dark;
7
7
 
8
8
  /* structure */
9
- --k-bg: #0a0e13;
10
- --k-panel: #111824;
11
- --k-panel-raised: #16202d;
12
- --k-line: rgba(150, 180, 210, 0.12);
13
- --k-line-strong: rgba(150, 180, 210, 0.22);
14
- --k-shadow: 0 26px 60px -42px rgba(0, 0, 0, 0.95);
9
+ --k-bg: var(--k-bg, #0a0e13);
10
+ --k-panel: var(--k-panel, #111824);
11
+ --k-panel-raised: var(--k-panel-raised, #16202d);
12
+ --k-line: var(--k-line, rgba(150, 180, 210, 0.12));
13
+ --k-line-strong: var(--k-line-strong, rgba(150, 180, 210, 0.22));
14
+ --k-shadow: var(--k-shadow, 0 26px 60px -42px rgba(0, 0, 0, 0.95));
15
15
 
16
16
  /* text */
17
- --k-text: #eef3f8;
18
- --k-text-muted: #aebccb;
19
- --k-text-faint: #72869b;
17
+ --k-text: var(--k-text, #eef3f8);
18
+ --k-text-muted: var(--k-text-muted, #aebccb);
19
+ --k-text-faint: var(--k-text-faint, #72869b);
20
20
 
21
21
  /* brand */
22
- --k-brand: #5ce0c6;
23
- --k-brand-contrast: #06080b;
22
+ --k-brand: var(--k-brand, #5ce0c6);
23
+ --k-brand-contrast: var(--k-brand-contrast, #06080b);
24
24
 
25
25
  /* semantic status scale */
26
- --k-positive: #34d399;
27
- --k-caution: #f3b14b;
28
- --k-negative: #ff6f6f;
29
- --k-neutral: #6f8095;
30
- --k-active: #7aa2ff;
26
+ --k-positive: var(--k-positive, #34d399);
27
+ --k-caution: var(--k-caution, #f3b14b);
28
+ --k-negative: var(--k-negative, #ff6f6f);
29
+ --k-neutral: var(--k-neutral, #6f8095);
30
+ --k-active: var(--k-active, #7aa2ff);
31
31
  --k-positive-soft: color-mix(in oklab, var(--k-positive) 14%, transparent);
32
32
  --k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
33
33
  --k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
34
34
  --k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
35
35
 
36
36
  /* spacing */
37
- --k-space-1: 4px;
38
- --k-space-2: 8px;
39
- --k-space-3: 12px;
40
- --k-space-4: 16px;
41
- --k-space-5: 24px;
42
- --k-space-6: 32px;
37
+ --k-space-1: var(--k-space-1, 4px);
38
+ --k-space-2: var(--k-space-2, 8px);
39
+ --k-space-3: var(--k-space-3, 12px);
40
+ --k-space-4: var(--k-space-4, 16px);
41
+ --k-space-5: var(--k-space-5, 24px);
42
+ --k-space-6: var(--k-space-6, 32px);
43
43
 
44
44
  /* radius */
45
- --k-radius-sm: 9px;
46
- --k-radius-md: 14px;
45
+ --k-radius-sm: var(--k-radius-sm, 9px);
46
+ --k-radius-md: var(--k-radius-md, 14px);
47
47
 
48
48
  /* type */
49
- --k-text-xs: 11px;
50
- --k-text-sm: 12.5px;
51
- --k-text-md: 14px;
52
- --k-text-lg: 18px;
53
- --k-text-xl: 22px;
54
- --k-text-2xl: clamp(26px, 3.4vw, 38px);
49
+ --k-text-xs: var(--k-text-xs, 11px);
50
+ --k-text-sm: var(--k-text-sm, 12.5px);
51
+ --k-text-md: var(--k-text-md, 14px);
52
+ --k-text-lg: var(--k-text-lg, 18px);
53
+ --k-text-xl: var(--k-text-xl, 22px);
54
+ --k-text-2xl: var(--k-text-2xl, clamp(26px, 3.4vw, 38px));
55
55
 
56
56
  /* fonts */
57
- --k-font-display: "Fraunces", Georgia, "Times New Roman", serif;
58
- --k-font-ui: "Hanken Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
59
- --k-font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
57
+ --k-font-display: var(--k-font-display, "Fraunces", Georgia, "Times New Roman", serif);
58
+ --k-font-ui: var(--k-font-ui, "Hanken Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
59
+ --k-font-mono: var(--k-font-mono, "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace);
60
60
 
61
61
  /* motion */
62
- --k-ease: cubic-bezier(0.22, 1, 0.36, 1);
63
- --k-dur: 0.16s;
62
+ --k-ease: var(--k-ease, cubic-bezier(0.22, 1, 0.36, 1));
63
+ --k-dur: var(--k-dur, 0.16s);
64
64
  }
65
65
 
66
66
  .survey-workbench-embed[data-theme="light"]{
67
67
  color-scheme: light;
68
- --k-bg: #f5f4ef;
69
- --k-panel: #ffffff;
70
- --k-panel-raised: #fbfaf7;
71
- --k-line: rgba(36, 40, 46, 0.12);
72
- --k-line-strong: rgba(36, 40, 46, 0.20);
73
- --k-shadow: 0 18px 50px -30px rgba(0, 0, 0, 0.25);
74
- --k-text: #202124;
75
- --k-text-muted: #5b626b;
76
- --k-text-faint: #707782;
77
- --k-brand-contrast: #ffffff;
78
- --k-positive: #168257;
79
- --k-caution: #8a5a00;
80
- --k-negative: #c83b3b;
81
- --k-neutral: #5f6975;
82
- --k-active: #3f6fd6;
68
+ --k-bg: var(--k-bg, #f5f4ef);
69
+ --k-panel: var(--k-panel, #ffffff);
70
+ --k-panel-raised: var(--k-panel-raised, #fbfaf7);
71
+ --k-line: var(--k-line, rgba(36, 40, 46, 0.12));
72
+ --k-line-strong: var(--k-line-strong, rgba(36, 40, 46, 0.20));
73
+ --k-shadow: var(--k-shadow, 0 18px 50px -30px rgba(0, 0, 0, 0.25));
74
+ --k-text: var(--k-text, #202124);
75
+ --k-text-muted: var(--k-text-muted, #5b626b);
76
+ --k-text-faint: var(--k-text-faint, #707782);
77
+ --k-brand-contrast: var(--k-brand-contrast, #ffffff);
78
+ --k-positive: var(--k-positive, #168257);
79
+ --k-caution: var(--k-caution, #8a5a00);
80
+ --k-negative: var(--k-negative, #c83b3b);
81
+ --k-neutral: var(--k-neutral, #5f6975);
82
+ --k-active: var(--k-active, #3f6fd6);
83
83
  }
84
84
 
85
85
 
86
86
  .survey-workbench-embed.theme-survey{
87
- --k-bg: #06080b;
88
- --k-brand: #5ce0c6;
89
- --k-text-faint: #6f8095;
87
+ --k-bg: var(--k-bg, #06080b);
88
+ --k-brand: var(--k-brand, #5ce0c6);
89
+ --k-text-faint: var(--k-text-faint, #6f8095);
90
90
  }
91
91
 
92
92
  .survey-workbench-embed.theme-console{
93
- --k-font-ui: "Aptos Narrow", "DIN Condensed", "IBM Plex Sans Condensed", "Gill Sans", sans-serif;
94
- --k-font-display: "Aptos Narrow", "DIN Condensed", "IBM Plex Sans Condensed", "Gill Sans", sans-serif;
95
- --k-radius-sm: 0;
96
- --k-radius-md: 0;
97
- --k-brand: #c9ff4a;
98
- --k-brand-contrast: #11120f;
99
- --k-bg: #11120f;
100
- --k-panel: #191b16;
101
- --k-panel-raised: #20231e;
102
- --k-line: #3a4035;
103
- --k-line-strong: #8ea36e;
104
- --k-text: #f2f0e8;
105
- --k-text-muted: #a6ab9c;
106
- --k-text-faint: #747a6c;
107
- --k-positive: #a6d37b;
108
- --k-caution: #e8c15f;
109
- --k-negative: #ee776f;
110
- --k-active: #84d8c8;
93
+ --k-font-ui: var(--k-font-ui, "Aptos Narrow", "DIN Condensed", "IBM Plex Sans Condensed", "Gill Sans", sans-serif);
94
+ --k-font-display: var(--k-font-display, "Aptos Narrow", "DIN Condensed", "IBM Plex Sans Condensed", "Gill Sans", sans-serif);
95
+ --k-radius-sm: var(--k-radius-sm, 0);
96
+ --k-radius-md: var(--k-radius-md, 0);
97
+ --k-brand: var(--k-brand, #c9ff4a);
98
+ --k-brand-contrast: var(--k-brand-contrast, #11120f);
99
+ --k-bg: var(--k-bg, #11120f);
100
+ --k-panel: var(--k-panel, #191b16);
101
+ --k-panel-raised: var(--k-panel-raised, #20231e);
102
+ --k-line: var(--k-line, #3a4035);
103
+ --k-line-strong: var(--k-line-strong, #8ea36e);
104
+ --k-text: var(--k-text, #f2f0e8);
105
+ --k-text-muted: var(--k-text-muted, #a6ab9c);
106
+ --k-text-faint: var(--k-text-faint, #747a6c);
107
+ --k-positive: var(--k-positive, #a6d37b);
108
+ --k-caution: var(--k-caution, #e8c15f);
109
+ --k-negative: var(--k-negative, #ee776f);
110
+ --k-active: var(--k-active, #84d8c8);
111
111
  }
112
112
 
113
113
  .survey-workbench-embed.theme-flow{
114
- --k-brand: #2f88a6;
114
+ --k-brand: var(--k-brand, #2f88a6);
115
115
  }
116
116
 
117
117
  .survey-workbench-embed.theme-surface{
118
- --k-brand: #14a37a;
118
+ --k-brand: var(--k-brand, #14a37a);
119
119
  }
120
120
 
121
121
  .survey-workbench-embed[data-theme="light"].theme-survey,
122
122
  .survey-workbench-embed[data-theme="light"] .theme-survey{
123
- --k-brand: #16806f;
123
+ --k-brand: var(--k-brand, #16806f);
124
124
  }
125
125
 
126
126
  .survey-workbench-embed[data-theme="light"].theme-console,
127
127
  .survey-workbench-embed[data-theme="light"] .theme-console{
128
- --k-brand: #6c9400;
129
- --k-brand-contrast: #ffffff;
130
- --k-bg: #f3f5eb;
131
- --k-panel: #fbfcf7;
132
- --k-panel-raised: #eef2e6;
133
- --k-line: #ccd5bf;
134
- --k-line-strong: #8fa36f;
135
- --k-text: #1e2319;
136
- --k-text-muted: #596250;
137
- --k-text-faint: #77816d;
138
- --k-positive: #2f7d32;
139
- --k-caution: #8a6500;
140
- --k-negative: #b93a36;
141
- --k-active: #247f75;
128
+ --k-brand: var(--k-brand, #6c9400);
129
+ --k-brand-contrast: var(--k-brand-contrast, #ffffff);
130
+ --k-bg: var(--k-bg, #f3f5eb);
131
+ --k-panel: var(--k-panel, #fbfcf7);
132
+ --k-panel-raised: var(--k-panel-raised, #eef2e6);
133
+ --k-line: var(--k-line, #ccd5bf);
134
+ --k-line-strong: var(--k-line-strong, #8fa36f);
135
+ --k-text: var(--k-text, #1e2319);
136
+ --k-text-muted: var(--k-text-muted, #596250);
137
+ --k-text-faint: var(--k-text-faint, #77816d);
138
+ --k-positive: var(--k-positive, #2f7d32);
139
+ --k-caution: var(--k-caution, #8a6500);
140
+ --k-negative: var(--k-negative, #b93a36);
141
+ --k-active: var(--k-active, #247f75);
142
142
  }
143
143
 
144
144
  .survey-workbench-embed[data-theme="light"].theme-flow,
145
145
  .survey-workbench-embed[data-theme="light"] .theme-flow{
146
- --k-brand: #1f6f88;
146
+ --k-brand: var(--k-brand, #1f6f88);
147
147
  }
148
148
 
149
149
  .survey-workbench-embed[data-theme="light"].theme-surface,
150
150
  .survey-workbench-embed[data-theme="light"] .theme-surface{
151
- --k-brand: #0f6b52;
151
+ --k-brand: var(--k-brand, #0f6b52);
152
152
  }
153
153
 
154
154
 
@@ -437,6 +437,7 @@ export const REVIEW_WORKBENCH_CSS = `/* Bundled, scoped Survey Review Workbench
437
437
  .survey-workbench-embed .field[data-state="accepted"] .stripe{ background: var(--k-positive); }
438
438
  .survey-workbench-embed .field[data-state="kept"] .stripe{ background: var(--k-faint); }
439
439
  .survey-workbench-embed .field[data-state="rejected"] .stripe{ background: var(--k-negative); }
440
+ .survey-workbench-embed .field[data-state="could-not-confirm"] .stripe{ background: var(--k-caution); }
440
441
  .survey-workbench-embed .field[data-state="review"] .stripe{ background: var(--k-brand); }
441
442
 
442
443
  .survey-workbench-embed .fbody{
@@ -491,6 +492,7 @@ export const REVIEW_WORKBENCH_CSS = `/* Bundled, scoped Survey Review Workbench
491
492
  .survey-workbench-embed .chip.accepted{ color: var(--k-positive); background: var(--k-positive-wash); }
492
493
  .survey-workbench-embed .chip.kept{ color: var(--k-muted); background: var(--k-sunken); }
493
494
  .survey-workbench-embed .chip.rejected{ color: var(--k-negative); background: var(--k-negative-wash); }
495
+ .survey-workbench-embed .chip.could-not-confirm{ color: var(--k-caution); background: var(--k-caution-wash); }
494
496
 
495
497
  .survey-workbench-embed .frow1 .push{
496
498
  margin-left: auto;
@@ -755,6 +757,12 @@ export const REVIEW_WORKBENCH_CSS = `/* Bundled, scoped Survey Review Workbench
755
757
  background: var(--k-panel-raised, var(--k-raised));
756
758
  }
757
759
 
760
+ .survey-workbench-embed .btn.unconfirmed{
761
+ border-color: var(--k-caution);
762
+ color: var(--k-caution);
763
+ background: var(--k-caution-wash);
764
+ }
765
+
758
766
  /* "Suggestion was wrong" — optional quiet calibration toggle on Keep current.
759
767
  Ticking it turns a keep-current decision into the reject signal. Not a third action. */
760
768
  .survey-workbench-embed .wrong{