lens-content-processor 0.28.0 → 0.34.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.
@@ -0,0 +1,499 @@
1
+ // src/parser/response-segments.ts
2
+ //
3
+ // Response (subtyped question) segments: `#### Question: Open|Rating|Choice|
4
+ // FillBlank|Ranking`. One segment family collects one learner response per
5
+ // segment, with the same syntax in lenses, Learning Outcome tests, and
6
+ // surveys. Context determines grading: surveys never grade. A bare
7
+ // `#### Question` (no title) stays the legacy segment everywhere and never
8
+ // reaches this module.
9
+ import { isValidUuid } from "../validator/uuid.js";
10
+ import { stripQuotes, validatePromptImportSyntax, } from "./lens.js";
11
+ export const RESPONSE_QUESTION_SUBTYPES = [
12
+ "open",
13
+ "rating",
14
+ "choice",
15
+ "fillblank",
16
+ "ranking",
17
+ ];
18
+ /** Author-facing capitalization, for error messages. */
19
+ const SUBTYPE_DISPLAY = {
20
+ open: "Open",
21
+ rating: "Rating",
22
+ choice: "Choice",
23
+ fillblank: "FillBlank",
24
+ ranking: "Ranking",
25
+ };
26
+ const VALID_SUBTYPES_HINT = RESPONSE_QUESTION_SUBTYPES.map((s) => SUBTYPE_DISPLAY[s]).join(", ");
27
+ const DEFAULT_RATING_SCALE = 5;
28
+ const MIN_RATING_SCALE = 2;
29
+ const MAX_RATING_SCALE = 10;
30
+ /**
31
+ * Resolve a raw segment to its "question:<subtype>" schema key, for field
32
+ * validation against QUESTION_SUBTYPE_SCHEMAS. Returns null for anything
33
+ * that is not a titled question segment with a known subtype (legacy
34
+ * questions fall back to the plain "question" schema).
35
+ */
36
+ export function responseSegmentSchemaKey(raw) {
37
+ if (raw.type !== "question" || !raw.title)
38
+ return null;
39
+ const subtype = raw.title.trim().toLowerCase();
40
+ return RESPONSE_QUESTION_SUBTYPES.includes(subtype)
41
+ ? `question:${subtype}`
42
+ : null;
43
+ }
44
+ function parseBool(raw, field) {
45
+ return raw.fields[field]?.toLowerCase() === "true" ? true : undefined;
46
+ }
47
+ /** Parse a positive-integer field; error and drop on anything else. */
48
+ function parsePositiveInt(raw, field, file, errors) {
49
+ const value = raw.fields[field];
50
+ if (value === undefined)
51
+ return undefined;
52
+ const trimmed = value.trim();
53
+ if (!/^\d+$/.test(trimmed) || parseInt(trimmed, 10) === 0) {
54
+ errors.push({
55
+ file,
56
+ line: raw.line,
57
+ message: `Field '${field}' must be a positive integer, got '${value}'`,
58
+ suggestion: `Use a number like '${field}:: 500', or remove the field`,
59
+ severity: "error",
60
+ });
61
+ return undefined;
62
+ }
63
+ return parseInt(trimmed, 10);
64
+ }
65
+ /** Parse scale:: as an integer in [2, 10] (default 5). Null = hard error. */
66
+ function parseScale(raw, file, errors) {
67
+ const value = raw.fields.scale;
68
+ if (value === undefined)
69
+ return DEFAULT_RATING_SCALE;
70
+ const trimmed = value.trim();
71
+ if (!/^\d+$/.test(trimmed)) {
72
+ errors.push({
73
+ file,
74
+ line: raw.line,
75
+ message: `Field 'scale' must be an integer, got '${value}'`,
76
+ suggestion: `Use a number from ${MIN_RATING_SCALE} to ${MAX_RATING_SCALE} (default ${DEFAULT_RATING_SCALE}), or omit the field`,
77
+ severity: "error",
78
+ });
79
+ return null;
80
+ }
81
+ const scale = parseInt(trimmed, 10);
82
+ if (scale < MIN_RATING_SCALE || scale > MAX_RATING_SCALE) {
83
+ errors.push({
84
+ file,
85
+ line: raw.line,
86
+ message: `Field 'scale' is ${scale}; ratings support ${MIN_RATING_SCALE}–${MAX_RATING_SCALE}`,
87
+ suggestion: `Use a value from ${MIN_RATING_SCALE} to ${MAX_RATING_SCALE}`,
88
+ severity: "error",
89
+ });
90
+ return null;
91
+ }
92
+ return scale;
93
+ }
94
+ /**
95
+ * Parse a multiline `- Item` list field shared by choice options:: and
96
+ * ranking items::. `allowCorrectMarks` (choice in lens/LO context) accepts
97
+ * `- [x] Item` and records its index; explicit-unchecked `- [ ]` is always
98
+ * an error, as is checkbox syntax where none is allowed.
99
+ */
100
+ function parseListField(raw, field, allowCorrectMarks, file, errors) {
101
+ const value = raw.fields[field];
102
+ const capitalized = field === "options" ? "Choice" : "Ranking";
103
+ if (value === undefined || value.trim() === "") {
104
+ errors.push({
105
+ file,
106
+ line: raw.line,
107
+ message: `${capitalized} segment is missing ${field}::`,
108
+ suggestion: `Add a list:\n${field}::\n- First ${field === "options" ? "option" : "item"}\n- Second ${field === "options" ? "option" : "item"}`,
109
+ severity: "error",
110
+ });
111
+ return null;
112
+ }
113
+ const items = [];
114
+ const correct = [];
115
+ for (const line of value.split("\n")) {
116
+ const trimmed = line.trim();
117
+ if (!trimmed)
118
+ continue;
119
+ const uncheckedBox = trimmed.match(/^-\s*\[\s?\]\s*(.*)$/);
120
+ if (uncheckedBox) {
121
+ errors.push({
122
+ file,
123
+ line: raw.line,
124
+ message: `Explicit-unchecked checkbox syntax is not supported in ${field}::: "${trimmed}"`,
125
+ suggestion: allowCorrectMarks
126
+ ? "Use plain '- Option text' items; mark correct options with '- [x] Option text'"
127
+ : "Use plain '- Item text' items without brackets",
128
+ severity: "error",
129
+ });
130
+ return null;
131
+ }
132
+ const checkedBox = trimmed.match(/^-\s*\[[xX]\]\s*(.*)$/);
133
+ if (checkedBox) {
134
+ if (!allowCorrectMarks) {
135
+ errors.push({
136
+ file,
137
+ line: raw.line,
138
+ message: field === "options"
139
+ ? `Survey choices never have correct answers, got checkbox syntax: "${trimmed}"`
140
+ : `Ranking items must be plain list items, got checkbox syntax: "${trimmed}"`,
141
+ suggestion: field === "options"
142
+ ? "Use plain '- Option text' items without brackets"
143
+ : "Use plain '- Item text' items in the intended order — the intended order is the answer",
144
+ severity: "error",
145
+ });
146
+ return null;
147
+ }
148
+ const text = checkedBox[1].trim();
149
+ if (!text) {
150
+ errors.push({
151
+ file,
152
+ line: raw.line,
153
+ message: `Empty ${field}:: list item: "${trimmed}"`,
154
+ suggestion: "Every list item needs text after the marker",
155
+ severity: "error",
156
+ });
157
+ return null;
158
+ }
159
+ correct.push(items.length);
160
+ items.push(text);
161
+ continue;
162
+ }
163
+ const item = trimmed.match(/^-\s+(.+)$/);
164
+ if (!item) {
165
+ errors.push({
166
+ file,
167
+ line: raw.line,
168
+ message: `Line in ${field}:: is not a list item: "${trimmed}"`,
169
+ suggestion: `Every entry must be on its own '- ${field === "options" ? "Option" : "Item"} text' line`,
170
+ severity: "error",
171
+ });
172
+ return null;
173
+ }
174
+ items.push(item[1].trim());
175
+ }
176
+ if (items.length < 2) {
177
+ errors.push({
178
+ file,
179
+ line: raw.line,
180
+ message: `${capitalized} segment needs at least 2 ${field}, got ${items.length}`,
181
+ suggestion: `Add more '- …' lines under ${field}::`,
182
+ severity: "error",
183
+ });
184
+ return null;
185
+ }
186
+ return { items, correct };
187
+ }
188
+ /**
189
+ * A number reference for a graded `{{number <ref>}}` blank: optional minus,
190
+ * digits either plain or with comma thousands separators in exact 3-digit
191
+ * groups, optional decimal part.
192
+ */
193
+ const REFERENCE_NUMBER = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/;
194
+ /**
195
+ * Parse `{{…}}` blanks out of a fillblank content:: value:
196
+ * - `{{blank}}` — ungraded text blank
197
+ * - `{{number}}` — ungraded number blank
198
+ * - `{{number <ref>}}` — graded number blank (reference validated)
199
+ * - anything else — graded text blank, `|`-separated alternatives
200
+ */
201
+ function parseFillBlanks(content, raw, file, errors) {
202
+ const blanks = [];
203
+ let failed = false;
204
+ const rewritten = content.replace(/\{\{(.*?)\}\}/gs, (match, inner) => {
205
+ const index = blanks.length;
206
+ const trimmed = String(inner).trim();
207
+ const keyword = trimmed.toLowerCase();
208
+ if (trimmed === "") {
209
+ errors.push({
210
+ file,
211
+ line: raw.line,
212
+ message: "Empty blank {{}} in fillblank content::",
213
+ suggestion: "Use {{blank}} for an ungraded blank, or {{expected answer}} for a graded one",
214
+ severity: "error",
215
+ });
216
+ failed = true;
217
+ return match;
218
+ }
219
+ if (keyword === "blank") {
220
+ blanks.push({ kind: "text", graded: false });
221
+ return `{{${index}}}`;
222
+ }
223
+ if (keyword === "number") {
224
+ blanks.push({ kind: "number", graded: false });
225
+ return `{{${index}}}`;
226
+ }
227
+ const numberRef = trimmed.match(/^number\s+(.+)$/i);
228
+ if (numberRef) {
229
+ const ref = numberRef[1].trim();
230
+ if (!REFERENCE_NUMBER.test(ref)) {
231
+ errors.push({
232
+ file,
233
+ line: raw.line,
234
+ message: `Malformed reference number in blank {{${trimmed}}}: '${ref}'`,
235
+ suggestion: "Use digits with optional comma thousands separators and decimal part, e.g. {{number 149,600,000}} or {{number 3.14}}",
236
+ severity: "error",
237
+ });
238
+ failed = true;
239
+ return match;
240
+ }
241
+ blanks.push({ kind: "number", graded: true, referenceNumber: ref });
242
+ return `{{${index}}}`;
243
+ }
244
+ const alternatives = trimmed.split("|").map((alt) => alt.trim());
245
+ if (alternatives.some((alt) => alt === "")) {
246
+ errors.push({
247
+ file,
248
+ line: raw.line,
249
+ message: `Empty alternative in blank {{${trimmed}}}`,
250
+ suggestion: "Separate accepted answers with '|', each non-empty: {{Paris|paris}}",
251
+ severity: "error",
252
+ });
253
+ failed = true;
254
+ return match;
255
+ }
256
+ blanks.push({ kind: "text", graded: true, expected: alternatives });
257
+ return `{{${index}}}`;
258
+ });
259
+ if (failed)
260
+ return null;
261
+ if (blanks.length === 0) {
262
+ errors.push({
263
+ file,
264
+ line: raw.line,
265
+ message: "FillBlank segment has no {{…}} blanks in content::",
266
+ suggestion: "Add at least one blank, e.g. 'The capital of France is {{Paris}}.'",
267
+ severity: "error",
268
+ });
269
+ return null;
270
+ }
271
+ return { content: rewritten, blanks };
272
+ }
273
+ /**
274
+ * Parse a `#### Question: <Subtype>` segment. Shared by lens parsing
275
+ * (context "lens", also used for LO tests via convertSegment) and survey
276
+ * parsing (context "survey": never graded, `[x]` forbidden,
277
+ * assessment-instructions warned but passed through).
278
+ */
279
+ export function parseResponseSegment(raw, context, file) {
280
+ const errors = [];
281
+ const subtypeRaw = (raw.title ?? "").trim();
282
+ const subtype = subtypeRaw.toLowerCase();
283
+ if (!RESPONSE_QUESTION_SUBTYPES.includes(subtype)) {
284
+ errors.push({
285
+ file,
286
+ line: raw.line,
287
+ message: `Unknown question subtype '${subtypeRaw}'`,
288
+ suggestion: `Valid subtypes: ${VALID_SUBTYPES_HINT} (e.g. '#### Question: Open'), or use plain '#### Question' for a legacy question`,
289
+ severity: "error",
290
+ });
291
+ return { segment: null, errors };
292
+ }
293
+ const questionType = subtype;
294
+ const display = `Question: ${SUBTYPE_DISPLAY[questionType]}`;
295
+ // id:: — plain UUID, quotes stripped like roleplay ids (a quoted id would
296
+ // reach the frontend verbatim and fail server-side UUID parsing).
297
+ const rawId = raw.fields.id?.trim();
298
+ const responseId = rawId ? stripQuotes(rawId).trim() : rawId;
299
+ if (!responseId) {
300
+ errors.push({
301
+ file,
302
+ line: raw.line,
303
+ message: `${display} segment missing id:: field`,
304
+ suggestion: "Add 'id:: <uuid>' — the id is the stable response key and must never change",
305
+ severity: "error",
306
+ });
307
+ }
308
+ else if (!isValidUuid(responseId)) {
309
+ errors.push({
310
+ file,
311
+ line: raw.line,
312
+ message: `${display} id:: is not a valid UUID: ${responseId}`,
313
+ suggestion: "Use a plain UUID, e.g. 'id:: 17a55209-deab-48c8-afb0-fc522722fe8f'",
314
+ severity: "error",
315
+ });
316
+ return { segment: null, errors };
317
+ }
318
+ const content = raw.fields.content;
319
+ if (!content || content.trim() === "") {
320
+ errors.push({
321
+ file,
322
+ line: raw.line,
323
+ message: `${display} segment missing content:: field`,
324
+ suggestion: "Add 'content:: Your question here'",
325
+ severity: "error",
326
+ });
327
+ }
328
+ if (!responseId || !content || content.trim() === "") {
329
+ return { segment: null, errors };
330
+ }
331
+ // assessment-instructions:: — grading rubric. Ratings are never graded;
332
+ // surveys never grade (warn, but pass the field through).
333
+ let assessmentInstructions = raw.fields["assessment-instructions"] || undefined;
334
+ if (assessmentInstructions && questionType === "rating") {
335
+ errors.push({
336
+ file,
337
+ line: raw.line,
338
+ message: "assessment-instructions:: on a Question: Rating segment — ratings are never graded",
339
+ suggestion: "Remove the assessment-instructions:: field",
340
+ severity: "error",
341
+ });
342
+ assessmentInstructions = undefined;
343
+ }
344
+ if (assessmentInstructions) {
345
+ errors.push(...validatePromptImportSyntax(assessmentInstructions, "assessment-instructions", file, raw.line));
346
+ if (context === "survey") {
347
+ errors.push({
348
+ file,
349
+ line: raw.line,
350
+ message: "assessment-instructions:: in a survey segment — surveys never grade, so this field will be ignored",
351
+ suggestion: "Remove the field, or move the segment into a lens",
352
+ severity: "warning",
353
+ });
354
+ }
355
+ }
356
+ const feedbackInstructions = raw.fields["feedback-instructions"] || undefined;
357
+ if (feedbackInstructions) {
358
+ errors.push(...validatePromptImportSyntax(feedbackInstructions, "feedback-instructions", file, raw.line));
359
+ }
360
+ const segment = {
361
+ type: "question",
362
+ questionType,
363
+ // Canonical lowercase: the backend matches segment ids against Postgres
364
+ // uuid strings (always lowercase), so the cache must never carry an
365
+ // uppercase-authored id.
366
+ responseId: responseId.toLowerCase(),
367
+ content,
368
+ graded: false, // derived per subtype below; always false in surveys
369
+ assessmentInstructions,
370
+ feedbackInstructions,
371
+ optional: parseBool(raw, "optional"),
372
+ };
373
+ switch (questionType) {
374
+ case "open": {
375
+ segment.maxChars = parsePositiveInt(raw, "max-chars", file, errors);
376
+ segment.maxTime = raw.fields["max-time"] || undefined;
377
+ const placeholder = raw.fields.placeholder?.trim();
378
+ if (placeholder)
379
+ segment.placeholder = placeholder;
380
+ segment.enforceVoice = parseBool(raw, "enforce-voice");
381
+ segment.graded = Boolean(assessmentInstructions);
382
+ break;
383
+ }
384
+ case "rating": {
385
+ const scale = parseScale(raw, file, errors);
386
+ if (scale === null)
387
+ return { segment: null, errors };
388
+ segment.scale = scale;
389
+ const lowLabel = raw.fields["low-label"]?.trim();
390
+ const highLabel = raw.fields["high-label"]?.trim();
391
+ if (lowLabel)
392
+ segment.lowLabel = lowLabel;
393
+ if (highLabel)
394
+ segment.highLabel = highLabel;
395
+ segment.graded = false;
396
+ break;
397
+ }
398
+ case "choice": {
399
+ const parsed = parseListField(raw, "options", context === "lens", file, errors);
400
+ if (parsed === null)
401
+ return { segment: null, errors };
402
+ segment.multi = parseBool(raw, "multi");
403
+ if (parsed.correct.length > 1 && !segment.multi) {
404
+ // A single-choice submission carries exactly one selection, so a
405
+ // multi-option correct set could never be matched — every learner
406
+ // would score 0.
407
+ errors.push({
408
+ file,
409
+ line: raw.line,
410
+ message: `Choice has ${parsed.correct.length} correct options but is single-choice — add 'multi:: true' or mark exactly one option`,
411
+ suggestion: "Add 'multi:: true' to allow multiple selections, or keep exactly one '- [x]' option",
412
+ severity: "error",
413
+ });
414
+ return { segment: null, errors };
415
+ }
416
+ segment.options = parsed.items;
417
+ if (parsed.correct.length > 0)
418
+ segment.correctOptions = parsed.correct;
419
+ segment.shuffle = parseBool(raw, "shuffle");
420
+ segment.graded = parsed.correct.length > 0;
421
+ break;
422
+ }
423
+ case "fillblank": {
424
+ const parsed = parseFillBlanks(content, raw, file, errors);
425
+ if (parsed === null)
426
+ return { segment: null, errors };
427
+ segment.content = parsed.content;
428
+ segment.blanks = parsed.blanks;
429
+ segment.graded =
430
+ parsed.blanks.some((b) => b.graded) || Boolean(assessmentInstructions);
431
+ break;
432
+ }
433
+ case "ranking": {
434
+ const parsed = parseListField(raw, "items", false, file, errors);
435
+ if (parsed === null)
436
+ return { segment: null, errors };
437
+ segment.items = parsed.items;
438
+ segment.graded = Boolean(assessmentInstructions);
439
+ break;
440
+ }
441
+ }
442
+ if (context === "survey")
443
+ segment.graded = false;
444
+ return { segment, errors };
445
+ }
446
+ /**
447
+ * Map a parsed response segment to its flattened QuestionSegment shape.
448
+ * Used directly by survey parsing (surveys have no flatten pass) and by the
449
+ * lens flattener, which then replaces assessmentInstructions /
450
+ * feedbackInstructions with their prompt-import-expanded values.
451
+ */
452
+ export function toFlattenedResponseSegment(parsed) {
453
+ const segment = {
454
+ type: "question",
455
+ questionType: parsed.questionType,
456
+ responseId: parsed.responseId,
457
+ content: parsed.content,
458
+ graded: parsed.graded,
459
+ };
460
+ if (parsed.assessmentInstructions)
461
+ segment.assessmentInstructions = parsed.assessmentInstructions;
462
+ if (parsed.feedbackInstructions)
463
+ segment.feedbackInstructions = parsed.feedbackInstructions;
464
+ if (parsed.optional)
465
+ segment.optional = true;
466
+ // open:
467
+ if (parsed.maxTime)
468
+ segment.maxTime = parsed.maxTime;
469
+ if (parsed.maxChars !== undefined)
470
+ segment.maxChars = parsed.maxChars;
471
+ if (parsed.enforceVoice)
472
+ segment.enforceVoice = true;
473
+ if (parsed.placeholder)
474
+ segment.placeholder = parsed.placeholder;
475
+ // rating:
476
+ if (parsed.scale !== undefined)
477
+ segment.scale = parsed.scale;
478
+ if (parsed.lowLabel)
479
+ segment.lowLabel = parsed.lowLabel;
480
+ if (parsed.highLabel)
481
+ segment.highLabel = parsed.highLabel;
482
+ // choice:
483
+ if (parsed.options)
484
+ segment.options = parsed.options;
485
+ if (parsed.correctOptions)
486
+ segment.correctOptions = parsed.correctOptions;
487
+ if (parsed.multi)
488
+ segment.multi = true;
489
+ if (parsed.shuffle)
490
+ segment.shuffle = true;
491
+ // fillblank:
492
+ if (parsed.blanks)
493
+ segment.blanks = parsed.blanks;
494
+ // ranking:
495
+ if (parsed.items)
496
+ segment.items = parsed.items;
497
+ return segment;
498
+ }
499
+ //# sourceMappingURL=response-segments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-segments.js","sourceRoot":"","sources":["../../src/parser/response-segments.ts"],"names":[],"mappings":"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"}
@@ -9,16 +9,21 @@
9
9
  // breaking response continuity (and the platform can special-case keys like
10
10
  // `buddy_texted`).
11
11
  //
12
- // Surveys deliberately do NOT reuse the lens question machinery: there are no
13
- // correct answers, no AI assessment, and no reveal semantics. Keeping the
14
- // vocabulary separate also avoids colliding with the quiz `question-type::`
15
- // syntax if that ever lands on lens questions.
12
+ // Legacy survey segments deliberately do NOT reuse the lens question
13
+ // machinery: there are no correct answers, no AI assessment, and no reveal
14
+ // semantics. New-style response segments (`#### Question: <Subtype>` with a
15
+ // stable id:: instead of key::) DO share the lens parser
16
+ // (parser/response-segments.ts), but in survey context: never graded, and
17
+ // correct-answer marks are forbidden.
16
18
  import { parseFrontmatter } from "./frontmatter.js";
17
19
  import { parseSections } from "./sections.js";
18
20
  import { validateFrontmatter } from "../validator/validate-frontmatter.js";
19
21
  import { detectFieldTypos } from "../validator/field-typos.js";
22
+ import { validateSegmentFields } from "../validator/segment-fields.js";
23
+ import { validateFieldValues } from "../validator/field-values.js";
20
24
  import { stripAuthoringMarkup } from "./lens.js";
21
25
  import { SURVEY_SEGMENT_SCHEMAS } from "../content-schema.js";
26
+ import { parseResponseSegment, responseSegmentSchemaKey, toFlattenedResponseSegment, } from "./response-segments.js";
22
27
  // Valid segment types for survey H4 headers
23
28
  export const SURVEY_SEGMENT_TYPES = new Set([
24
29
  "text",
@@ -233,6 +238,18 @@ function validateSurveySegmentFields(raw, file) {
233
238
  }
234
239
  function convertSurveySegment(raw, file) {
235
240
  const errors = [];
241
+ // `#### Question: <Subtype>` — new-style response segment, shared with
242
+ // lens parsing but in survey context: keyed by id:: (no key::), never
243
+ // graded, correct-answer marks forbidden. Legacy survey segments
244
+ // (untitled Question, and Rating/Choice) are untouched below.
245
+ if (raw.type === "question" && raw.title) {
246
+ const { segment, errors: responseErrors } = parseResponseSegment(raw, "survey", file);
247
+ errors.push(...responseErrors);
248
+ return {
249
+ segment: segment ? toFlattenedResponseSegment(segment) : null,
250
+ errors,
251
+ };
252
+ }
236
253
  if (raw.title) {
237
254
  const capitalized = raw.type[0].toUpperCase() + raw.type.slice(1);
238
255
  errors.push({
@@ -360,25 +377,40 @@ export function parseSurvey(content, file) {
360
377
  const seenKeys = new Map(); // key -> first line
361
378
  for (const rawSeg of rawSegments) {
362
379
  rawSeg.line += bodyStartLine - 1;
363
- errors.push(...validateSurveySegmentFields(rawSeg, file));
380
+ // New-style response segments validate against their
381
+ // "question:<subtype>" schema; everything else against the survey
382
+ // vocabulary.
383
+ const responseSchemaKey = responseSegmentSchemaKey(rawSeg);
384
+ if (responseSchemaKey) {
385
+ errors.push(...validateSegmentFields(responseSchemaKey, rawSeg.fields, file, rawSeg.line));
386
+ errors.push(...validateFieldValues(rawSeg.fields, file, rawSeg.line));
387
+ }
388
+ else {
389
+ errors.push(...validateSurveySegmentFields(rawSeg, file));
390
+ }
364
391
  errors.push(...detectFieldTypos(rawSeg.fields, file, rawSeg.line));
365
392
  const { segment, errors: conversionErrors } = convertSurveySegment(rawSeg, file);
366
393
  errors.push(...conversionErrors);
367
394
  if (!segment)
368
395
  continue;
369
396
  if (segment.type !== "text") {
370
- const firstLine = seenKeys.get(segment.key);
397
+ // Answerable segments need a unique identifier: legacy segments their
398
+ // key::, new-style response segments their id:: (responseId).
399
+ const isLegacy = "key" in segment;
400
+ const dedupKey = isLegacy ? segment.key : segment.responseId;
401
+ const label = isLegacy ? "key" : "id";
402
+ const firstLine = seenKeys.get(dedupKey);
371
403
  if (firstLine !== undefined) {
372
404
  errors.push({
373
405
  file,
374
406
  line: rawSeg.line,
375
- message: `Duplicate key '${segment.key}' (already used at line ${firstLine})`,
376
- suggestion: "Every answerable segment needs a unique key",
407
+ message: `Duplicate ${label} '${dedupKey}' (already used at line ${firstLine})`,
408
+ suggestion: `Every answerable segment needs a unique ${label}`,
377
409
  severity: "error",
378
410
  });
379
411
  continue;
380
412
  }
381
- seenKeys.set(segment.key, rawSeg.line);
413
+ seenKeys.set(dedupKey, rawSeg.line);
382
414
  }
383
415
  segments.push(segment);
384
416
  }