@intellectif/lk-core 0.12.0 → 0.13.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +2 -1
  3. package/dist/{activity-B2QxK15j.d.cts → activity-DfAmJ1sl.d.cts} +134 -7
  4. package/dist/{activity-B2QxK15j.d.ts → activity-DfAmJ1sl.d.ts} +134 -7
  5. package/dist/{chunk-VYE6BYON.cjs → chunk-DQKJUMUW.cjs} +38 -15
  6. package/dist/chunk-DQKJUMUW.cjs.map +1 -0
  7. package/dist/{chunk-RK2NVTHZ.js → chunk-FGN4LLSP.js} +50 -6
  8. package/dist/chunk-FGN4LLSP.js.map +1 -0
  9. package/dist/{chunk-O2IVKTHV.cjs → chunk-FT7SAC3X.cjs} +4 -4
  10. package/dist/{chunk-O2IVKTHV.cjs.map → chunk-FT7SAC3X.cjs.map} +1 -1
  11. package/dist/{chunk-UQPNJP2O.js → chunk-PYP3HCRQ.js} +2 -2
  12. package/dist/{chunk-4OHWLCI6.cjs → chunk-UHQVIC3P.cjs} +58 -14
  13. package/dist/chunk-UHQVIC3P.cjs.map +1 -0
  14. package/dist/{chunk-FVN37GIH.js → chunk-XKKKWKRH.js} +1944 -177
  15. package/dist/chunk-XKKKWKRH.js.map +1 -0
  16. package/dist/{chunk-J6POE4HL.cjs → chunk-XONKWL6D.cjs} +1785 -18
  17. package/dist/chunk-XONKWL6D.cjs.map +1 -0
  18. package/dist/{chunk-7JMMPE7O.js → chunk-Y36S3X3L.js} +26 -3
  19. package/dist/chunk-Y36S3X3L.js.map +1 -0
  20. package/dist/{index-B_scInLM.d.cts → index-Bz9wUigA.d.cts} +181 -4
  21. package/dist/{index-CRKtWP5G.d.ts → index-CJbbVE9o.d.ts} +181 -4
  22. package/dist/index.cjs +128 -73
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +14 -6
  25. package/dist/index.d.ts +14 -6
  26. package/dist/index.js +60 -5
  27. package/dist/index.js.map +1 -1
  28. package/dist/schemas.cjs +17 -3
  29. package/dist/schemas.cjs.map +1 -1
  30. package/dist/schemas.d.cts +2 -2
  31. package/dist/schemas.d.ts +2 -2
  32. package/dist/schemas.js +16 -2
  33. package/dist/scoring.cjs +19 -3
  34. package/dist/scoring.cjs.map +1 -1
  35. package/dist/scoring.d.cts +150 -5
  36. package/dist/scoring.d.ts +150 -5
  37. package/dist/scoring.js +18 -2
  38. package/dist/xapi.cjs +3 -3
  39. package/dist/xapi.d.cts +1 -1
  40. package/dist/xapi.d.ts +1 -1
  41. package/dist/xapi.js +2 -2
  42. package/package.json +1 -1
  43. package/vectors/README.md +9 -1
  44. package/vectors/scoring.json +12023 -1
  45. package/dist/chunk-4OHWLCI6.cjs.map +0 -1
  46. package/dist/chunk-7JMMPE7O.js.map +0 -1
  47. package/dist/chunk-FVN37GIH.js.map +0 -1
  48. package/dist/chunk-J6POE4HL.cjs.map +0 -1
  49. package/dist/chunk-RK2NVTHZ.js.map +0 -1
  50. package/dist/chunk-VYE6BYON.cjs.map +0 -1
  51. /package/dist/{chunk-UQPNJP2O.js.map → chunk-PYP3HCRQ.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-J6POE4HL.cjs","../src/errors.ts","../src/schemas/feedback.ts","../src/schemas/media.ts","../src/schemas/fill-in-the-blanks.ts","../src/schemas/multiple-choice.ts","../src/count-words.ts","../src/schemas/gap-select.ts","../src/schemas/written-response.ts","../src/schemas/redacted.ts","../src/scoring/text-match.ts","../src/registry/registry.ts","../src/authoring/issues.ts","../src/authoring/fill-in-the-blanks.ts","../src/authoring/gap-select.ts","../src/authoring/multiple-choice.ts","../src/authoring/written-response.ts","../src/scoring/strategies/all-or-nothing.ts","../src/scoring/strategies/partial.ts","../src/scoring/activity-scorers/fill-in-the-blanks.ts","../src/scoring/activity-scorers/gap-select.ts","../src/scoring/activity-scorers/multiple-choice.ts","../src/registry/builtins.ts"],"names":["z","placeholderCounts","checkPairing"],"mappings":"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,EAAA;AACD,IAAA;AACoC,IAAA;AAC1C,EAAA;AACM,EAAA;AACA,IAAA;AACI,IAAA;AACgC,IAAA;AACvB,IAAA;AACP,IAAA;AACZ,EAAA;AACF;AAEqD;AAChD,EAAA;AACM,EAAA;AACI,EAAA;AACI,EAAA;AACP,EAAA;AACF,EAAA;AACF,IAAA;AACE,IAAA;AACW,IAAA;AACF,IAAA;AACC,IAAA;AACT,IAAA;AACG,IAAA;AACZ,EAAA;AACF;AAagD;AACvC,EAAA;AAAA;AAEG,EAAA;AACF,IAAA;AACO,IAAA;AACL,IAAA;AACV,EAAA;AACF;AAEmD;AAC9C,EAAA;AACK,EAAA;AACI,EAAA;AACF,EAAA;AACA,EAAA;AACM,EAAA;AACN,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQF,EAAA;AACV;AAME;AACM,EAAA;AAAA;AAAA;AAGE,EAAA;AAC4C,EAAA;AACkB,EAAA;AACzD,EAAA;AACG,EAAA;AACP,EAAA;AACc,IAAA;AACA,IAAA;AACc,IAAA;AAIpB,MAAA;AACf,IAAA;AACF,EAAA;AACkE,EAAA;AACvD,EAAA;AACZ;AAMC;AACM,EAAA;AACE,EAAA;AAC6C,EAAA;AAEiB,EAAA;AACzD,EAAA;AACG,EAAA;AACP,EAAA;AACc,IAAA;AACA,IAAA;AAAA;AAAA;AAAA;AAIc,IAAA;AACoC,MAAA;AACvE,IAAA;AACF,EAAA;AAC4D,EAAA;AACjD,EAAA;AACZ;AAYC;AACM,EAAA;AACE,EAAA;AACC,EAAA;AACD,IAAA;AACE,IAAA;AACqB,IAAA;AACsB,MAAA;AAC1C,MAAA;AAEkE,QAAA;AACvE,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AACgE,EAAA;AACnD,EAAA;AACG,EAAA;AACP,EAAA;AACc,IAAA;AACA,IAAA;AACW,IAAA;AAClC,EAAA;AAC0C,EAAA;AAC/B,EAAA;AACZ;AAGwF;AACjF,EAAA;AACE,EAAA;AACuC,EAAA;AAEgB,EAAA;AAClD,EAAA;AACG,EAAA;AACP,EAAA;AACc,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,IAAA;AACc,IAAA;AACsC,MAAA;AACzE,IAAA;AACF,EAAA;AAC0C,EAAA;AAC/B,EAAA;AACZ;AAEsC;AACC;AACA;AACN;AtBolD8D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-J6POE4HL.cjs","sourcesContent":[null,"import type { ValidationError } from './types/activity.js';\n\n/** Thrown when activity data fails schema validation at a component boundary. */\nexport class ActivitySchemaError extends Error {\n constructor(\n public readonly activityType: string,\n public readonly errors: ValidationError[],\n ) {\n super(`Invalid activity data for type \"${activityType}\"`);\n this.name = 'ActivitySchemaError';\n }\n}\n\n/** Thrown when an unrecognised activity type is passed to the scoring engine. */\nexport class UnknownActivityTypeError extends Error {\n constructor(public readonly activityType: string) {\n super(`Activity type \"${activityType}\" is not registered`);\n this.name = 'UnknownActivityTypeError';\n }\n}\n\n/**\n * Thrown when `score()` is asked to grade a `redact()` projection (or any\n * activity data whose answer key is missing, yielding a non-finite score).\n * Redacted data is learner-safe precisely because the key was removed, so a\n * score derived from it is meaningless — previously this produced a silent\n * `NaN` that serialized to `null` in a grade column. `evaluate()` returns\n * `{ status: 'unscorable' }` for the same input instead of throwing.\n */\nexport class RedactedScoringError extends Error {\n constructor(public readonly activityType: string) {\n super(\n `Activity data for \"${activityType}\" carries no answer key (it looks redacted), so it cannot be scored. ` +\n 'Score against the full activity data server-side, or use evaluate() which returns { status: \"unscorable\" }.',\n );\n this.name = 'RedactedScoringError';\n }\n}\n\n/**\n * Thrown when `score()` is called for an activity type whose grading is\n * deferred (asynchronous AI/human grading, e.g. `written-response`). A\n * deferred submission has no synchronous score — treating it as 0 would show\n * a learner a failing grade for work that simply has not been graded yet.\n * Call `evaluate()` instead, which returns `{ status: 'deferred', ... }`.\n */\nexport class DeferredScoringError extends Error {\n constructor(public readonly activityType: string) {\n super(\n `Activity type \"${activityType}\" is graded asynchronously and has no synchronous score. ` +\n `Use evaluate() — it returns { status: 'deferred' } for this type.`,\n );\n this.name = 'DeferredScoringError';\n }\n}\n","import { z } from 'zod/v4';\n\n/**\n * Optional authored \"overall feedback\" shown after submission, selected by\n * whether the learner passed (h5p-style overall feedback). Both fields are\n * optional; non-empty when present.\n */\nexport const FeedbackSchema = z.looseObject({\n correct: z.string().min(1).optional(),\n incorrect: z.string().min(1).optional(),\n});\n","import { z } from 'zod/v4';\n\n/**\n * Optional media attached to an activity, rendered above the question or\n * passage — e.g. a recording to listen to, or an embedded video to watch\n * before answering. URL-only by design: hosting/delivery (S3/CDN, or the\n * provider's own embed for `embed`) is the consuming app's responsibility\n * (see requirements \"Non-Goals and Shared Responsibility\").\n *\n * - `image`: rendered as `<img>` — `alt` is REQUIRED (WCAG 1.1.1 / Req 14.5).\n * - `audio` / `video`: rendered with native controls; `alt` is an optional\n * accessible label; `captionsUrl` points at a WebVTT `<track>`. `url` must\n * be a direct media file (NOT a YouTube/Vimeo page — use `embed` for those).\n * - `embed`: rendered as a sandboxed `<iframe>` for provider players\n * (YouTube/Vimeo/etc.). `url` MUST be the provider's *embeddable* URL\n * (e.g. `https://www.youtube.com/embed/<id>`). `alt` is REQUIRED and used\n * as the iframe's accessible `title` (WCAG 4.1.2 / 2.4.1).\n */\n/**\n * Media URL policy (security-reviewed): absolute URLs must use `https:`,\n * `http:`, `data:`, or `blob:`; root-relative paths (`/media/x.mp3`) are\n * allowed for same-origin hosting. Everything else — notably `javascript:`,\n * `file:`, `ftp:` — is rejected: these URLs land in `src` attributes\n * (including an iframe for `embed`), so an unvetted scheme is a stored-XSS\n * vector in every consuming app.\n */\nexport const MediaUrlSchema = z.union([\n z.url().refine((value) => /^(https?|data|blob):/i.test(value), {\n error: 'Absolute media URLs must use the https:, http:, data:, or blob: scheme.',\n }),\n z.string().regex(/^\\/(?!\\/)\\S*$/, {\n error: 'Relative media URLs must be root-relative (a single leading \"/\").',\n }),\n]);\n\n/**\n * Hints to the BROWSER'S OWN control bar, emitted as `controlsList` tokens.\n *\n * They remove a button, never a capability, and only in engines that implement\n * `controlsList` at all. `hide-download` does NOT prevent a download — the URL\n * is in the page and the bytes are in the network panel. If a recording must\n * not be kept, issue a short-lived signed URL; that is the consuming\n * application's control, not the SDK's.\n *\n * Meaningful only alongside the native bar: there is nothing to hint at once\n * the SDK owns the transport.\n */\nexport const NativeControlHintSchema = z.enum(['hide-download', 'hide-rate']);\n\n/**\n * How an audio recording may be played.\n *\n * STRICT on purpose: an unknown key here is an authoring error. {@link\n * MediaSchema} is loose, so without this a typo one level down (`maxPlay`,\n * `seeking`) would be silently accepted and the whole policy would quietly do\n * nothing on an exam that believed it was enforced.\n *\n * Nothing here is required together with anything else — `{ maxPlays: 2 }` is a\n * complete, valid policy, because `controls` and `seek` RESOLVE to the only\n * values that can keep that promise. The refinements below reject only a\n * combination an author wrote **explicitly** that the renderer cannot honour.\n */\nexport const MediaPlaybackSchema = z.strictObject({\n /**\n * `native` (the resolved default) renders today's `<audio controls>`\n * unchanged. `minimal` — resolved automatically whenever any enforcement\n * field is set — replaces the browser bar with the SDK transport:\n * play/pause, elapsed/total, mute, volume, optional speed, optional\n * scrubber, and a live plays-remaining status.\n */\n controls: z.enum(['native', 'minimal']).optional(),\n /**\n * How many times the recording may be STARTED. A play is consumed when\n * playback begins from anywhere other than where it last stopped, so\n * pausing, resuming, and paging between the questions of one listening\n * group are all free. Enforced in `practice` and `exam`; never in `review`.\n */\n maxPlays: z.number().int().min(1).max(20).optional(),\n /** `none` renders no scrubber and reverts an out-of-band seek to the high-water mark. */\n seek: z.enum(['allow', 'none']).optional(),\n /** `fixed` renders no speed control and snaps `playbackRate` back to 1. */\n rate: z.enum(['allow', 'fixed']).optional(),\n /** Advisory only. See {@link NativeControlHintSchema}. */\n nativeControlHints: z.array(NativeControlHintSchema).min(1).max(2).optional(),\n});\n\nexport const MediaSchema = z\n .looseObject({\n type: z.enum(['image', 'audio', 'video', 'embed']),\n url: MediaUrlSchema,\n alt: z.string().min(1).optional(),\n captionsUrl: MediaUrlSchema.optional(),\n playback: MediaPlaybackSchema.optional(),\n })\n .refine(\n (m) =>\n (m.type !== 'image' && m.type !== 'embed') || (typeof m.alt === 'string' && m.alt.length > 0),\n {\n error: 'image and embed media require non-empty alt text (WCAG 1.1.1 / 4.1.2).',\n path: ['alt'],\n },\n )\n .refine((m) => m.type !== 'embed' || /^https?:\\/\\//i.test(m.url), {\n error:\n 'embed media requires an absolute http(s) provider URL. data:, blob:, and relative URLs are not allowed for embeds — the embed iframe runs with allow-scripts, and a data:/same-origin document there is an XSS vector.',\n path: ['url'],\n })\n .refine((m) => m.playback === undefined || m.type === 'audio', {\n error:\n 'playback policy is supported on audio media only. An embed is a provider iframe the SDK cannot control at all (it has no reliable JS API for a third-party player); an image has nothing to play; video is deliberately deferred, because a video transport must also own fullscreen and Picture-in-Picture and the SDK will not pretend to govern those yet.',\n path: ['playback'],\n })\n .refine(\n (m) =>\n m.playback?.controls !== 'native' ||\n (m.playback.maxPlays === undefined &&\n m.playback.seek !== 'none' &&\n m.playback.rate !== 'fixed'),\n {\n error:\n 'controls: \"native\" cannot carry maxPlays, seek: \"none\" or rate: \"fixed\". The browser\\'s own bar keeps its play button enabled after a budget is spent, and keeps a scrubber that would move and then silently snap back — a control that looks operable and does nothing (WCAG 3.2.2 / 4.1.3). Omit `controls` and the SDK transport is used automatically, or use nativeControlHints for a cosmetic hint.',\n path: ['playback', 'controls'],\n },\n )\n .refine((m) => m.playback?.maxPlays === undefined || m.playback.seek !== 'allow', {\n error:\n 'maxPlays cannot be combined with seek: \"allow\". A play is consumed when playback starts from somewhere other than where it stopped, so scrubbing back mid-play would replay the whole recording without spending anything. Omit `seek` — it resolves to \"none\" under a budget.',\n path: ['playback', 'seek'],\n })\n .refine(\n (m) =>\n m.playback?.controls !== 'minimal' ||\n m.playback.maxPlays !== undefined ||\n m.playback.seek === 'none' ||\n m.playback.rate === 'fixed',\n {\n error:\n 'controls: \"minimal\" with nothing to enforce trades the browser\\'s localized, familiar control bar for the SDK\\'s, and buys nothing. Set maxPlays, seek: \"none\" or rate: \"fixed\", or omit `controls`.',\n path: ['playback', 'controls'],\n },\n )\n .refine(\n (m) =>\n m.playback?.nativeControlHints === undefined ||\n (m.playback.controls !== 'minimal' &&\n m.playback.maxPlays === undefined &&\n m.playback.seek !== 'none' &&\n m.playback.rate !== 'fixed'),\n {\n error:\n \"nativeControlHints only affects the browser's own control bar, and an enforcing policy replaces that bar with the SDK transport — so the hints would be silently inert. Use them on an otherwise unrestricted recording, or drop them.\",\n path: ['playback', 'nativeControlHints'],\n },\n );\n\n/**\n * The strict counterpart of {@link MediaSchema}, for the redacted shapes.\n *\n * `RedactedActivityDataSchema` and `RedactedStimulusSchema` are `strictObject`\n * precisely so an unknown key is a validation failure rather than a\n * passthrough — but they embedded the LOOSE `MediaSchema`, so the strictness\n * stopped at the media boundary and `media.secretAnswerHint` passed\n * `assertRedacted`. This closes it.\n */\nexport const RedactedMediaSchema = z\n .strictObject({\n type: z.enum(['image', 'audio', 'video', 'embed']),\n url: MediaUrlSchema,\n alt: z.string().min(1).optional(),\n captionsUrl: MediaUrlSchema.optional(),\n playback: MediaPlaybackSchema.optional(),\n })\n .refine(\n (m) =>\n (m.type !== 'image' && m.type !== 'embed') || (typeof m.alt === 'string' && m.alt.length > 0),\n {\n error: 'image and embed media require non-empty alt text (WCAG 1.1.1 / 4.1.2).',\n path: ['alt'],\n },\n )\n .refine((m) => m.type !== 'embed' || /^https?:\\/\\//i.test(m.url), {\n error:\n 'embed media requires an absolute http(s) provider URL. data:, blob:, and relative URLs are not allowed for embeds — the embed iframe runs with allow-scripts, and a data:/same-origin document there is an XSS vector.',\n path: ['url'],\n });\n","import { z } from 'zod/v4';\nimport { FeedbackSchema } from './feedback.js';\nimport { MediaSchema } from './media.js';\n\n/**\n * Matches `{{ blank_id }}` placeholders in a passage, capturing the trimmed id.\n * Exported for the draft checks in `authoring/`, which must read a passage\n * exactly as this schema does. It is not re-exported from the package.\n */\nexport const PLACEHOLDER_RE = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\n/**\n * Zod schema for a `TextMatchPolicy` — the opt-in matching tolerances a blank\n * may declare. Every default reproduces the v1 trim + case-fold semantics.\n */\nexport const TextMatchPolicySchema = z.looseObject({\n caseSensitive: z.boolean().optional(),\n trim: z.boolean().optional(),\n normalize: z.enum(['none', 'NFC', 'NFKC']).optional(),\n foldDiacritics: z.boolean().optional(),\n collapseInnerWhitespace: z.boolean().optional(),\n ignorePunctuation: z.boolean().optional(),\n levenshtein: z.number().int().min(0).optional(),\n locale: z.string().optional(),\n});\n\n/**\n * Zod schema for a single fill-in-the-blank slot configuration. Loose:\n * unknown keys are preserved through validation. Accepted answers must\n * contain non-whitespace characters — a whitespace-only accepted answer\n * normalizes to the empty string and would mark an empty response correct.\n */\nexport const BlankConfigSchema = z.looseObject({\n id: z.string().min(1),\n acceptedAnswers: z\n .array(\n z\n .string()\n .min(1)\n .refine((answer) => answer.trim().length > 0, {\n error: 'Accepted answers must contain non-whitespace characters.',\n }),\n )\n .min(1),\n caseSensitive: z.boolean().optional(),\n trimWhitespace: z.boolean().optional(),\n match: TextMatchPolicySchema.optional(),\n hint: z.string().optional(),\n feedback: z.string().optional(),\n});\n\n/**\n * Zod schema validating the full Fill-in-the-Blanks activity data contract.\n * Loose at every level: unknown keys are preserved, never stripped (B7).\n *\n * The refinement enforces a true one-to-one correspondence between `{{id}}`\n * placeholders and `blanks[].id`: every blank id appears EXACTLY ONCE in the\n * passage and exactly once in `blanks[]`. (The previous set-based check let\n * duplicate placeholders and duplicate blank configs through, corrupting the\n * partial-score denominator and per-item details.)\n */\nexport const FillInTheBlanksDataSchema = z\n .looseObject({\n schemaVersion: z.literal('1.0'),\n type: z.literal('fill-in-the-blanks'),\n id: z.string().min(1),\n title: z.string().min(1),\n passage: z.string().min(1),\n passageHtml: z.string().optional(),\n blanks: z.array(BlankConfigSchema).min(1),\n scoringStrategy: z.enum(['all-or-nothing', 'partial']),\n media: MediaSchema.optional(),\n feedback: FeedbackSchema.optional(),\n passThreshold: z.number().min(0).max(1).optional(),\n locale: z.string().optional(),\n learningObjectives: z.array(z.string()).optional(),\n difficultyLevel: z.literal([1, 2, 3, 4, 5]).optional(),\n })\n .refine(\n (data) => {\n const placeholderCounts = new Map<string, number>();\n for (const match of data.passage.matchAll(PLACEHOLDER_RE)) {\n const id = match[1] as string;\n placeholderCounts.set(id, (placeholderCounts.get(id) ?? 0) + 1);\n }\n const blankIds = data.blanks.map((blank) => blank.id);\n if (new Set(blankIds).size !== blankIds.length) {\n return false;\n }\n if (placeholderCounts.size !== blankIds.length) {\n return false;\n }\n return blankIds.every((id) => placeholderCounts.get(id) === 1);\n },\n {\n error:\n 'Each blank id must appear exactly once in blanks[] and have exactly one matching {{id}} placeholder in the passage, and vice versa.',\n path: ['passage'],\n },\n );\n","import { z } from 'zod/v4';\nimport { FeedbackSchema } from './feedback.js';\nimport { MediaSchema, MediaUrlSchema } from './media.js';\n\n/**\n * Zod schema for a single Multiple Choice option. Loose: unknown keys are\n * preserved through validation (forward-compat / consumer sidecars — B7).\n */\n/**\n * A picture or recording carried by one option.\n *\n * LOOSE, like `MediaSchema` and every other content schema: a consumer's own\n * fields — an asset id, a CDN key — ride along and survive validation (B7).\n * Making it strict to catch a typo would reject exactly the sidecars the SDK\n * promises to preserve, and an invariant test guards that promise by counting\n * the closed objects in the exported JSON Schema.\n *\n * The two things that must NOT be swallowed are named explicitly instead. The\n * `type` enum refuses the kinds that cannot be an option, and a refinement\n * refuses `playback`, which would otherwise sit there looking like an enforced\n * budget while nothing read it.\n *\n * The URL goes through `MediaUrlSchema`, the same security-reviewed allow-list\n * activity media uses — `javascript:`, `file:` and `ftp:` are stored-XSS\n * vectors wherever the value lands in a `src`, and that list is not forked.\n */\nexport const MultipleChoiceOptionMediaSchema = z\n .looseObject({\n // No `embed`: an iframe swallows the click that selects the option, so the\n // learner could not choose it. No `video`: a native control bar inside the\n // option's label eats the same click.\n type: z.enum(['image', 'audio']),\n url: MediaUrlSchema,\n alt: z.string().min(1).optional(),\n captionsUrl: MediaUrlSchema.optional(),\n })\n // The same rule MediaSchema applies, so there is one rule to learn: a picture\n // needs a text alternative (WCAG 1.1.1), a recording's label is optional.\n .refine((media) => media.type !== 'image' || (media.alt !== undefined && media.alt !== ''), {\n error: 'An image option requires a non-empty `alt`.',\n path: ['alt'],\n })\n // Named rather than ignored. `maxPlays` binds per slot through a\n // MediaBudgetBinding, and nothing decides yet whether four recordings in one\n // question share a budget or hold one each — so a policy written here would\n // be a promise no renderer keeps, on a listening exam that believed it had\n // one.\n .refine((media) => !('playback' in media), {\n error:\n 'An option carries no playback policy. maxPlays, seek and rate are enforced only on the media above the question.',\n path: ['playback'],\n });\n\nexport const MultipleChoiceOptionSchema = z.looseObject({\n id: z.string().min(1),\n text: z.string().min(1),\n isCorrect: z.boolean(),\n feedback: z.string().optional(),\n media: MultipleChoiceOptionMediaSchema.optional(),\n});\n\n/**\n * Zod schema validating the full Multiple Choice activity data contract.\n * Loose at every level: unknown keys are preserved, never stripped, so a\n * v0.3 runtime reading a future payload (or a consumer sidecar field) does\n * not silently delete data.\n *\n * Semantic guards: (1) at least one option must be correct, otherwise the\n * activity can never be answered correctly; (2) `mode: 'single'` must have\n * exactly one correct option — multiple correct options under single-select\n * make `showCorrectAnswers` and the xAPI correct-response ambiguous and mask\n * authoring errors; (3) option ids must be unique — the scorer looks options\n * up by id, so a duplicate id makes one option unscoreable. All are\n * unrepresentable in JSON Schema and are dropped from `toJSONSchema` output\n * by design.\n */\nexport const MultipleChoiceDataSchema = z\n .looseObject({\n schemaVersion: z.literal('1.0'),\n type: z.literal('multiple-choice'),\n id: z.string().min(1),\n title: z.string().min(1),\n question: z.string().min(1),\n questionHtml: z.string().optional(),\n mode: z.enum(['single', 'multi']),\n options: z.array(MultipleChoiceOptionSchema).min(2).max(26),\n scoringStrategy: z.enum(['all-or-nothing', 'partial']),\n media: MediaSchema.optional(),\n feedback: FeedbackSchema.optional(),\n passThreshold: z.number().min(0).max(1).optional(),\n shuffle: z.boolean().optional(),\n locale: z.string().optional(),\n learningObjectives: z.array(z.string()).optional(),\n difficultyLevel: z.literal([1, 2, 3, 4, 5]).optional(),\n })\n .refine((data) => data.options.some((option) => option.isCorrect), {\n error: 'At least one option must be marked correct.',\n path: ['options'],\n })\n .refine(\n (data) =>\n data.mode !== 'single' || data.options.filter((option) => option.isCorrect).length === 1,\n {\n error: 'Single-select activities (mode: \"single\") must have exactly one correct option.',\n path: ['options'],\n },\n )\n .refine((data) => new Set(data.options.map((option) => option.id)).size === data.options.length, {\n error: 'Option ids must be unique within the activity.',\n path: ['options'],\n });\n","/**\n * Canonical word counter for written-response bounds (Req 22.9): tokens are\n * maximal runs of non-whitespace (split on `\\s+`), so hyphenated forms\n * (`well-known`) count as one word. The empty / whitespace-only string counts\n * 0. Consumers must use this helper rather than re-implementing the split so\n * client previews, SDK scoring, and stored `wordCount` values always agree.\n */\nexport function countWords(text: string): number {\n // Tolerate non-strings: this runs on client-supplied submission payloads,\n // where a malformed value must score 0 rather than throw mid-grading.\n if (typeof text !== 'string') {\n return 0;\n }\n const trimmed = text.trim();\n if (trimmed === '') {\n return 0;\n }\n return trimmed.split(/\\s+/).length;\n}\n","import { z } from 'zod/v4';\nimport { FeedbackSchema } from './feedback.js';\nimport { PLACEHOLDER_RE } from './fill-in-the-blanks.js';\nimport { MediaSchema } from './media.js';\n\n/**\n * Zod schema for one selectable choice. Loose, like every other schema here:\n * unknown keys are preserved so a consumer sidecar survives validation.\n */\nexport const GapSelectChoiceSchema = z.looseObject({\n id: z.string().min(1),\n text: z.string().min(1),\n});\n\n/** Zod schema for a shared word bank. */\nexport const GapSelectBankSchema = z.looseObject({\n id: z.string().min(1),\n choices: z.array(GapSelectChoiceSchema).min(2),\n});\n\n/** Zod schema for one gap. The `choices` / `bankId` exclusivity is checked on the activity. */\nexport const GapSelectGapSchema = z.looseObject({\n id: z.string().min(1),\n choices: z.array(GapSelectChoiceSchema).min(2).optional(),\n bankId: z.string().min(1).optional(),\n correctChoiceId: z.string().min(1),\n feedback: z.string().optional(),\n});\n\n/** Every choice a gap offers, or `undefined` when its source is missing or unresolvable. */\nfunction resolveChoices(\n gap: z.infer<typeof GapSelectGapSchema>,\n banks: readonly z.infer<typeof GapSelectBankSchema>[],\n): z.infer<typeof GapSelectChoiceSchema>[] | undefined {\n if (gap.choices !== undefined) {\n return gap.bankId === undefined ? gap.choices : undefined;\n }\n if (gap.bankId === undefined) {\n return undefined;\n }\n return banks.find((bank) => bank.id === gap.bankId)?.choices;\n}\n\n/** The distinct `{{id}}` placeholders in a passage, in the order they appear, with their counts. */\nfunction placeholderCounts(passage: string): Map<string, number> {\n const counts = new Map<string, number>();\n for (const match of passage.matchAll(PLACEHOLDER_RE)) {\n const id = match[1] as string;\n counts.set(id, (counts.get(id) ?? 0) + 1);\n }\n return counts;\n}\n\n/**\n * Zod schema validating the full Gap Select data contract.\n *\n * Six semantic guards, none of them expressible in JSON Schema and all of them\n * authoring errors that would otherwise reach a learner:\n *\n * 1. **Bank ids are unique** — two banks sharing an id makes which one a gap\n * draws from a matter of array order.\n * 2. **Gap ids are unique** — the scorer looks a gap's selection up by id, so a\n * duplicate makes one gap unscoreable (the same defect the Multiple Choice\n * option-id guard exists for).\n * 3. **Passage placeholders and gaps pair one to one**, each `{{id}}` appearing\n * exactly once — the Fill-in-the-Blanks bijection, for the same reason: a\n * gap with nowhere to render is a question the learner never sees, and a\n * placeholder with no gap renders as literal `{{id}}` text.\n * 4. **Exactly one choice source per gap** — `choices` or `bankId`, never both\n * and never neither.\n * 5. **A `bankId` names a bank that exists.**\n * 6. **`correctChoiceId` is one of the choices the gap actually offers**, and\n * choice ids within a resolved set are unique. An answer key pointing at a\n * choice nobody can pick is an item that cannot be passed.\n */\nexport const GapSelectDataSchema = z\n .looseObject({\n schemaVersion: z.literal('1.0'),\n type: z.literal('gap-select'),\n id: z.string().min(1),\n title: z.string().min(1),\n passage: z.string().min(1),\n passageHtml: z.string().optional(),\n gaps: z.array(GapSelectGapSchema).min(1),\n banks: z.array(GapSelectBankSchema).optional(),\n scoringStrategy: z.enum(['all-or-nothing', 'partial']),\n presentation: z.literal('dropdown').optional(),\n shuffleChoices: z.boolean().optional(),\n media: MediaSchema.optional(),\n feedback: FeedbackSchema.optional(),\n passThreshold: z.number().min(0).max(1).optional(),\n locale: z.string().optional(),\n learningObjectives: z.array(z.string()).optional(),\n difficultyLevel: z.literal([1, 2, 3, 4, 5]).optional(),\n })\n .refine(\n (data) => {\n const ids = (data.banks ?? []).map((bank) => bank.id);\n return new Set(ids).size === ids.length;\n },\n { error: 'Word bank ids must be unique.', path: ['banks'] },\n )\n .refine(\n (data) => {\n const ids = data.gaps.map((gap) => gap.id);\n return new Set(ids).size === ids.length;\n },\n { error: 'Gap ids must be unique.', path: ['gaps'] },\n )\n .refine(\n (data) => {\n const counts = placeholderCounts(data.passage);\n const ids = data.gaps.map((gap) => gap.id);\n return (\n counts.size === new Set(ids).size &&\n ids.every((id) => counts.get(id) === 1) &&\n [...counts.values()].every((count) => count === 1)\n );\n },\n {\n error:\n 'Every gap must appear exactly once in the passage as {{gap_id}}, and every {{gap_id}} must have a gap.',\n path: ['passage'],\n },\n )\n .refine(\n (data) => data.gaps.every((gap) => (gap.choices === undefined) !== (gap.bankId === undefined)),\n {\n error: 'Each gap needs exactly one choice source: its own `choices`, or a `bankId`.',\n path: ['gaps'],\n },\n )\n .refine(\n (data) => {\n const bankIds = new Set((data.banks ?? []).map((bank) => bank.id));\n return data.gaps.every((gap) => gap.bankId === undefined || bankIds.has(gap.bankId));\n },\n { error: 'A gap references a `bankId` that no word bank defines.', path: ['gaps'] },\n )\n .refine(\n (data) => {\n const banks = data.banks ?? [];\n return data.gaps.every((gap) => {\n const choices = resolveChoices(gap, banks);\n if (choices === undefined) {\n // Guards 4 and 5 already report this; do not fail twice for one cause.\n return true;\n }\n const ids = choices.map((choice) => choice.id);\n return new Set(ids).size === ids.length && ids.includes(gap.correctChoiceId);\n });\n },\n {\n error:\n 'Each gap needs unique choice ids and a `correctChoiceId` that is one of the choices it offers.',\n path: ['gaps'],\n },\n );\n","import { z } from 'zod/v4';\nimport { FeedbackSchema } from './feedback.js';\nimport { MediaSchema } from './media.js';\n\n/** Zod schema for a single rubric criterion. Loose: unknown keys preserved. */\nexport const WrittenResponseRubricCriterionSchema = z.looseObject({\n name: z.string().min(1),\n description: z.string().optional(),\n weight: z.number().min(0),\n});\n\n/** Zod schema for a written-response grading rubric. Loose: unknown keys preserved. */\nexport const WrittenResponseRubricSchema = z.looseObject({\n label: z.string().optional(),\n criteria: z.array(WrittenResponseRubricCriterionSchema).min(1),\n});\n\n/**\n * The strict counterparts of the rubric schemas, for the redacted shapes.\n *\n * The authoring schemas above are loose on purpose (Req 22.5: a consumer's\n * rubric sidecars must survive `validateActivity` verbatim). The REDACTED\n * shape must not be: `RedactedWrittenResponseDataSchema` is a `strictObject`\n * precisely so an unknown key is a validation failure, and embedding the loose\n * rubric there meant the strictness stopped at the rubric boundary — a\n * `rubric.modelAnswer` reached the learner and `assertRedacted` blessed it.\n */\nexport const RedactedWrittenResponseRubricCriterionSchema = z.strictObject({\n name: z.string().min(1),\n description: z.string().optional(),\n weight: z.number().min(0),\n});\n\nexport const RedactedWrittenResponseRubricSchema = z.strictObject({\n label: z.string().optional(),\n criteria: z.array(RedactedWrittenResponseRubricCriterionSchema).min(1),\n});\n\n/**\n * Zod schema validating the Written Response activity data contract (Req 22).\n *\n * Wire-format constraints (Req 22.9): field names are locked for\n * byte-compatibility with consumer-stored rows, and the schema is loose at\n * EVERY level (Req 22.5) — unknown top-level keys, `promptHtml`, `rubric`\n * sidecars and any future fields survive `validateActivity` verbatim.\n */\nexport const WrittenResponseDataSchema = z\n .looseObject({\n schemaVersion: z.literal('1.0'),\n type: z.literal('written-response'),\n id: z.string().min(1),\n title: z.string().min(1),\n prompt: z.string(),\n promptHtml: z.string().optional(),\n minWords: z.number().int().min(0),\n maxWords: z.number().int().min(1),\n rubric: WrittenResponseRubricSchema.optional(),\n languageTarget: z.string().optional(),\n media: MediaSchema.optional(),\n feedback: FeedbackSchema.optional(),\n passThreshold: z.number().min(0).max(1).optional(),\n locale: z.string().optional(),\n learningObjectives: z.array(z.string()).optional(),\n difficultyLevel: z.literal([1, 2, 3, 4, 5]).optional(),\n })\n .refine((data) => data.maxWords >= data.minWords, {\n error: 'maxWords must be greater than or equal to minWords.',\n path: ['maxWords'],\n });\n","import { z } from 'zod/v4';\nimport { RedactedMediaSchema } from './media.js';\nimport { RedactedWrittenResponseRubricSchema } from './written-response.js';\n\n/**\n * Schemas for REDACTED activity data — the learner-safe projection `redact()`\n * produces (R7). Deliberately STRICT (`z.strictObject`), the opposite of the\n * loose content schemas: a redacted payload must prove the ABSENCE of every\n * answer-key and author-only field, so any unknown key is a validation\n * failure, not a passthrough. `assertRedacted` validates against these.\n */\n\n/** Shared fields every redacted activity carries. */\nconst redactedBase = {\n /** Marker distinguishing a redacted projection from full activity data. */\n redacted: z.literal(true),\n /** Slot identity, carried through redaction so the client and the plan agree. */\n slotKey: z.string().min(1).optional(),\n schemaVersion: z.literal('1.0'),\n id: z.string().min(1),\n title: z.string().min(1),\n media: RedactedMediaSchema.optional(),\n passThreshold: z.number().min(0).max(1).optional(),\n locale: z.string().optional(),\n learningObjectives: z.array(z.string()).optional(),\n difficultyLevel: z.literal([1, 2, 3, 4, 5]).optional(),\n};\n\n/** A redacted Multiple Choice option: id and display text only — no `isCorrect`, no feedback. */\nexport const RedactedMultipleChoiceOptionMediaSchema = z.strictObject({\n type: z.enum(['image', 'audio']),\n url: z.string().min(1),\n alt: z.string().min(1).optional(),\n captionsUrl: z.string().min(1).optional(),\n});\n\n/**\n * A redacted Multiple Choice option: id, display text and its picture or\n * recording — no `isCorrect`, no feedback.\n *\n * The media survives intact for the reason a gap-select choice does: it is the\n * option the learner is being asked to pick, and an exam that stripped it would\n * show a row of blanks. Nothing in it is an answer key — the key is `isCorrect`,\n * which is gone.\n */\nexport const RedactedMultipleChoiceOptionSchema = z.strictObject({\n id: z.string().min(1),\n text: z.string().min(1),\n media: RedactedMultipleChoiceOptionMediaSchema.optional(),\n});\n\n/**\n * Redacted Multiple Choice data: renderable (question, mode, options to pick\n * from) with the answer key, per-option feedback, overall feedback, and the\n * scoring strategy removed. `scoringStrategy` is answer-key by design: MC\n * `partial` carries a wrong-selection penalty `all-or-nothing` does not, so\n * knowing the strategy tells a learner whether guessing is free.\n */\nexport const RedactedMultipleChoiceDataSchema = z.strictObject({\n ...redactedBase,\n type: z.literal('multiple-choice'),\n question: z.string().min(1),\n questionHtml: z.string().optional(),\n mode: z.enum(['single', 'multi']),\n options: z.array(RedactedMultipleChoiceOptionSchema).min(2).max(26),\n shuffle: z.boolean().optional(),\n});\n\n/** A redacted blank: id and hint only — no accepted answers, no matching rules, no feedback. */\nexport const RedactedBlankConfigSchema = z.strictObject({\n id: z.string().min(1),\n hint: z.string().optional(),\n});\n\n/** Redacted Fill-in-the-Blanks data: passage and blank slots, key removed. */\nexport const RedactedFillInTheBlanksDataSchema = z.strictObject({\n ...redactedBase,\n type: z.literal('fill-in-the-blanks'),\n passage: z.string().min(1),\n passageHtml: z.string().optional(),\n blanks: z.array(RedactedBlankConfigSchema).min(1),\n});\n\n/** A redacted choice: exactly what it always was. Which one is correct was never stored here. */\nexport const RedactedGapSelectChoiceSchema = z.strictObject({\n id: z.string().min(1),\n text: z.string().min(1),\n});\n\n/** A redacted word bank: every choice survives, because the learner picks from them. */\nexport const RedactedGapSelectBankSchema = z.strictObject({\n id: z.string().min(1),\n choices: z.array(RedactedGapSelectChoiceSchema).min(2),\n});\n\n/** A redacted gap: its choice source survives; `correctChoiceId` and feedback do not. */\nexport const RedactedGapSelectGapSchema = z.strictObject({\n id: z.string().min(1),\n choices: z.array(RedactedGapSelectChoiceSchema).min(2).optional(),\n bankId: z.string().min(1).optional(),\n});\n\n/**\n * Redacted Gap Select data — the type where redaction **inverts** the\n * Fill-in-the-Blanks rule, and the reason this is a separate schema rather than\n * a variant of it.\n *\n * In Fill-in-the-Blanks the candidate answers ARE the key, so `acceptedAnswers`\n * is stripped. Here the learner cannot answer at all without seeing every\n * choice, so `choices` and `banks` must survive in full and `correctChoiceId`\n * is the only field withheld. A policy that treated \"the list of candidate\n * answers\" as answer-key for both types would ship an unanswerable exam.\n *\n * `scoringStrategy` stays answer-key for the same reason it does on the other\n * two: knowing whether marking is partial tells a learner whether guessing at a\n * gap is free.\n */\nexport const RedactedGapSelectDataSchema = z.strictObject({\n ...redactedBase,\n type: z.literal('gap-select'),\n passage: z.string().min(1),\n passageHtml: z.string().optional(),\n gaps: z.array(RedactedGapSelectGapSchema).min(1),\n banks: z.array(RedactedGapSelectBankSchema).optional(),\n presentation: z.literal('dropdown').optional(),\n shuffleChoices: z.boolean().optional(),\n});\n\n/**\n * Redacted Written Response data: the prompt, word bounds and rubric are\n * learner-visible (a rubric tells the learner what they are graded on);\n * authored pass/fail feedback is removed until the grade exists.\n */\nexport const RedactedWrittenResponseDataSchema = z.strictObject({\n ...redactedBase,\n type: z.literal('written-response'),\n prompt: z.string(),\n promptHtml: z.string().optional(),\n minWords: z.number().int().min(0),\n maxWords: z.number().int().min(1),\n rubric: RedactedWrittenResponseRubricSchema.optional(),\n languageTarget: z.string().optional(),\n});\n\n/**\n * The learner-safe SHAPE of each built-in type, derived from the strict schema\n * above rather than hand-written beside it.\n *\n * `redact()` returns {@link RedactedActivityData}, which proves a payload is\n * learner-safe but is index-signature typed — it deliberately says nothing\n * about what the payload still CONTAINS. That is right for the assertion and\n * useless for anything that has to render or transport the result, so every\n * integrator ends up re-declaring these interfaces by hand and they drift the\n * moment a schema changes. Deriving them with `z.infer` means the type and the\n * validator can never disagree.\n *\n * Use them for the payload a server sends an exam client, and for the props of\n * a renderer that must never see an answer key.\n */\nexport type RedactedMultipleChoiceOption = z.infer<typeof RedactedMultipleChoiceOptionSchema>;\n/** An option's picture or recording, unchanged by redaction — it is what the learner picks. */\nexport type RedactedMultipleChoiceOptionMedia = z.infer<\n typeof RedactedMultipleChoiceOptionMediaSchema\n>;\n/** A Multiple Choice item with the answer key, feedback and strategy removed. */\nexport type RedactedMultipleChoiceData = z.infer<typeof RedactedMultipleChoiceDataSchema>;\n/** A blank with its accepted answers and matching rules removed; the hint survives. */\nexport type RedactedBlankConfig = z.infer<typeof RedactedBlankConfigSchema>;\n/** A Fill-in-the-Blanks item with every accepted answer removed. */\nexport type RedactedFillInTheBlanksData = z.infer<typeof RedactedFillInTheBlanksDataSchema>;\n/** A Written Response item; the rubric survives, because it tells the learner what is assessed. */\nexport type RedactedWrittenResponseData = z.infer<typeof RedactedWrittenResponseDataSchema>;\n/** A selectable choice, unchanged by redaction. */\nexport type RedactedGapSelectChoice = z.infer<typeof RedactedGapSelectChoiceSchema>;\n/** A word bank, unchanged by redaction — the learner picks from it. */\nexport type RedactedGapSelectBank = z.infer<typeof RedactedGapSelectBankSchema>;\n/** A gap with its `correctChoiceId` removed; every choice it offers survives. */\nexport type RedactedGapSelectGap = z.infer<typeof RedactedGapSelectGapSchema>;\n/** A Gap Select item the learner can still answer: choices intact, answer key gone. */\nexport type RedactedGapSelectData = z.infer<typeof RedactedGapSelectDataSchema>;\n\n/**\n * Discriminated union of every built-in redacted activity. Narrow it on\n * `type`, exactly as you would {@link ActivityData}:\n *\n * ```ts\n * function render(item: RedactedActivity) {\n * if (item.type === 'multiple-choice') {\n * return item.options.map((option) => option.text); // no `isCorrect` to leak\n * }\n * }\n * ```\n */\nexport type RedactedActivity =\n | RedactedMultipleChoiceData\n | RedactedFillInTheBlanksData\n | RedactedWrittenResponseData\n | RedactedGapSelectData;\n","/**\n * Configurable free-text answer matching (R3.3).\n *\n * Grade-stability contract: the DEFAULTS reproduce the v1 fill-in-the-blanks\n * semantics exactly (trim, then locale-insensitive lowercase, strict `===`).\n * Every tolerance below is opt-in, because changing a default here changes\n * historical grades — and this SDK powers real summative exams.\n */\nexport interface TextMatchPolicy {\n /** Compare case-sensitively. Default `false` (v1 behaviour). */\n caseSensitive?: boolean;\n /** Strip leading/trailing whitespace before comparing. Default `true` (v1 behaviour). */\n trim?: boolean;\n /**\n * Unicode normalization applied to both sides before comparing. Default\n * `'none'` (v1 behaviour). `'NFC'` makes a decomposed `está` (combining\n * U+0301) equal its composed form — the fix for accent-input mismatches in\n * EN/ES/PT content.\n */\n normalize?: 'none' | 'NFC' | 'NFKC';\n /** Treat diacritics as equal to their base letters (`está` ≡ `esta`). Default `false`. */\n foldDiacritics?: boolean;\n /** Collapse runs of inner whitespace to a single space. Default `false`. */\n collapseInnerWhitespace?: boolean;\n /** Ignore Unicode punctuation on both sides. Default `false`. */\n ignorePunctuation?: boolean;\n /** Maximum Levenshtein edit distance still accepted as a match. Default `0`. */\n levenshtein?: number;\n /**\n * BCP 47 tag for locale-aware case folding (e.g. `'tr'` for Turkish dotted /\n * dotless I). Default: locale-insensitive `toLowerCase()` (v1 behaviour).\n */\n locale?: string;\n}\n\n/**\n * How a match was achieved. Lets a consumer award full credit for an `exact`\n * match and partial credit for a `folded` or `fuzzy` one (e.g. diacritic\n * tolerance in a listening gap-fill vs. a spelling test).\n * - `exact` — equal under the baseline trim/case rules alone.\n * - `normalized` — required Unicode normalization, whitespace collapse, or punctuation tolerance.\n * - `folded` — required diacritic folding.\n * - `fuzzy` — required Levenshtein tolerance.\n * - `none` — no accepted answer matched.\n */\nexport interface TextMatchResult {\n matched: boolean;\n via: 'exact' | 'normalized' | 'folded' | 'fuzzy' | 'none';\n}\n\n/** Combining marks (any script) removed for diacritic folding after NFD decomposition. */\nconst COMBINING_MARKS_RE = /\\p{M}/gu;\n/**\n * Unicode punctuation (`\\p{P}`) removed by `ignorePunctuation`. Deliberately\n * NOT `\\p{S}`: symbols like `$`, `+`, `%` can be the substance of an answer\n * (`$100` vs `100`), so a \"punctuation\" toggle must not erase them.\n */\nconst PUNCTUATION_RE = /\\p{P}/gu;\n\nfunction applyBaseline(value: string, policy: TextMatchPolicy): string {\n let result = value;\n if (policy.trim !== false) {\n result = result.trim();\n }\n if (policy.caseSensitive !== true) {\n result = policy.locale ? result.toLocaleLowerCase(policy.locale) : result.toLowerCase();\n }\n return result;\n}\n\nfunction applyNormalization(value: string, policy: TextMatchPolicy): string {\n let result = value;\n if (policy.normalize === 'NFC' || policy.normalize === 'NFKC') {\n result = result.normalize(policy.normalize);\n }\n if (policy.ignorePunctuation === true) {\n result = result.replace(PUNCTUATION_RE, '');\n }\n if (policy.collapseInnerWhitespace === true) {\n // \"Inner\" means between non-space runs. Only trim the ends when the\n // policy's trim is on (its default) — an explicit `trim: false` must not\n // be silently overridden by the collapse.\n result = result.replace(/(?<=\\S)\\s+(?=\\S)/g, ' ');\n if (policy.trim !== false) {\n result = result.trim();\n }\n }\n return result;\n}\n\nfunction applyDiacriticFold(value: string): string {\n return value.normalize('NFD').replace(COMBINING_MARKS_RE, '').normalize('NFC');\n}\n\n/**\n * Levenshtein distance with an early-exit bound: returns `max + 1` as soon as\n * the distance provably exceeds `max`.\n */\nexport function levenshteinDistance(a: string, b: string, max: number): number {\n if (a === b) return 0;\n if (Math.abs(a.length - b.length) > max) return max + 1;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n let previous = Array.from({ length: b.length + 1 }, (_, i) => i);\n for (let i = 1; i <= a.length; i += 1) {\n const current = [i];\n let rowMin = i;\n for (let j = 1; j <= b.length; j += 1) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n const value = Math.min(\n (previous[j] as number) + 1,\n (current[j - 1] as number) + 1,\n (previous[j - 1] as number) + cost,\n );\n current.push(value);\n if (value < rowMin) rowMin = value;\n }\n if (rowMin > max) return max + 1;\n previous = current;\n }\n return previous[b.length] as number;\n}\n\n/**\n * Matches a learner's input against one or more accepted answers under a\n * {@link TextMatchPolicy}. Tolerances are evaluated in stages — baseline\n * (trim/case), then normalization, then diacritic folding, then Levenshtein —\n * and {@link TextMatchResult.via} reports the first stage that produced the\n * match, so graders can award credit by match quality.\n *\n * With no policy (or an empty one) this is byte-for-byte the v1 matching\n * semantics: `trim` + locale-insensitive `toLowerCase` + strict equality.\n */\nexport function matchText(\n input: string,\n accepted: string | readonly string[],\n policy: TextMatchPolicy = {},\n): TextMatchResult {\n const acceptedList = typeof accepted === 'string' ? [accepted] : accepted;\n const baselineInput = applyBaseline(input, policy);\n const baselineAccepted = acceptedList.map((answer) => applyBaseline(answer, policy));\n\n if (baselineAccepted.some((answer) => answer === baselineInput)) {\n return { matched: true, via: 'exact' };\n }\n\n const usesNormalization =\n policy.normalize === 'NFC' ||\n policy.normalize === 'NFKC' ||\n policy.ignorePunctuation === true ||\n policy.collapseInnerWhitespace === true;\n const normalizedInput = usesNormalization\n ? applyNormalization(baselineInput, policy)\n : baselineInput;\n const normalizedAccepted = usesNormalization\n ? baselineAccepted.map((answer) => applyNormalization(answer, policy))\n : baselineAccepted;\n\n if (usesNormalization && normalizedAccepted.some((answer) => answer === normalizedInput)) {\n return { matched: true, via: 'normalized' };\n }\n\n const foldedInput =\n policy.foldDiacritics === true ? applyDiacriticFold(normalizedInput) : normalizedInput;\n const foldedAccepted =\n policy.foldDiacritics === true\n ? normalizedAccepted.map((answer) => applyDiacriticFold(answer))\n : normalizedAccepted;\n\n if (policy.foldDiacritics === true && foldedAccepted.some((answer) => answer === foldedInput)) {\n return { matched: true, via: 'folded' };\n }\n\n // A learner who typed nothing made no typo. Levenshtein distance from an\n // empty string is just the answer's length, so `levenshtein: 1` on a\n // one-letter blank (\"a\", \"I\") marked an UNANSWERED blank correct — the\n // fuzzy stage rescuing a blank rather than a misspelling. No author enabling\n // typo tolerance intends that, so an empty input never reaches this stage.\n // Exact and normalized matching are untouched: an author who genuinely lists\n // \"\" as an accepted answer still gets it, deliberately, one stage earlier.\n const maxDistance = policy.levenshtein ?? 0;\n if (\n maxDistance > 0 &&\n foldedInput.trim().length > 0 &&\n foldedAccepted.some(\n (answer) => levenshteinDistance(foldedInput, answer, maxDistance) <= maxDistance,\n )\n ) {\n return { matched: true, via: 'fuzzy' };\n }\n\n return { matched: false, via: 'none' };\n}\n","import type { z } from 'zod/v4';\nimport type { DeferredScoringPartial, ScoringResult } from '../types/activity.js';\nimport type { DraftContext, DraftIssue } from '../types/authoring.js';\n\n/** ScoringResult without `passed` — the public `score()` / `evaluate()` fill that in. */\nexport type PartialScoringResult = Omit<ScoringResult, 'passed'>;\n\n/**\n * Sensitivity classification of a single activity-data field, driving\n * `redact()`:\n * - `public` — safe to send to a learner before they answer.\n * - `answer-key` — reveals (or helps infer) the correct answer or the scoring\n * rules; removed unless `reveal: 'after-submit'` is requested.\n * - `author-only` — never leaves the authoring/grading context (e.g. rubrics).\n */\nexport type Sensitivity = 'public' | 'answer-key' | 'author-only';\n\n/**\n * Per-field sensitivity map for an activity type. A `Sensitivity` value\n * classifies the whole field (objects and arrays included); a nested\n * `FieldPolicy` recurses into an object field — and, for an array field,\n * applies to every element. Fail-closed: any field a policy does not mention\n * is treated as `author-only` and removed by `redact()` — adding a field\n * without classifying it hides it, never leaks it.\n */\nexport interface FieldPolicy {\n readonly [field: string]: Sensitivity | FieldPolicy;\n}\n\n/** xAPI interaction types defined by xAPI 1.0.3 (cmi.interaction vocabulary). */\nexport type XAPIInteractionType =\n | 'choice'\n | 'fill-in'\n | 'long-fill-in'\n | 'matching'\n | 'sequencing'\n | 'performance'\n | 'true-false'\n | 'other';\n\n/** Interop facts a generic statement builder cannot infer from data alone. */\nexport interface ActivityTypeInterop<TData> {\n /** IRI for the xAPI Activity `definition.type`. */\n readonly xapiActivityTypeIri?: string;\n /** xAPI `definition.interactionType` for this activity type. */\n readonly xapiInteractionType?: XAPIInteractionType;\n /** Builds the xAPI `correctResponsesPattern` strings for an item. */\n readonly correctResponsesPattern?: (data: TData) => string[];\n}\n\n/**\n * How responses to an activity type are graded:\n * - `sync` — a pure function produces the grade at submit time.\n * - `deferred` — grading happens asynchronously (AI or human) after\n * submission; `partial` reports the facts computable synchronously\n * (e.g. word counts), surfaced in `ItemOutcome.partial`.\n */\nexport type ActivityTypeScoring<TData, TResponse> =\n | {\n readonly kind: 'sync';\n readonly score: (data: TData, response: TResponse) => PartialScoringResult;\n }\n | {\n readonly kind: 'deferred';\n readonly reason: 'requires_async_grading';\n readonly partial?: (data: TData, response: TResponse | undefined) => DeferredScoringPartial;\n };\n\n/**\n * Authoring support for an activity type: the empty draft an editor starts\n * from, and the rules that tell a draft that is unfinished from one that is\n * wrong. Both are optional. Without `checkDraft`, `validateDraft` still works\n * and reports every schema failure as `invalid`; without `createDraft`,\n * `createDraft()` throws for the type.\n */\nexport interface ActivityTypeAuthoring<TData> {\n /**\n * A new draft: every required field present, nothing authored yet. It must\n * come back from `validateDraft` as `incomplete` — never `invalid`, and never\n * `complete` — so a freshly added question is not reported as an error, and\n * cannot pass validation before anyone has written it.\n */\n readonly createDraft?: (context: DraftContext) => TData;\n /**\n * Reports what is missing (`incomplete`) and what is wrong (`invalid`) in a\n * draft. Receives any plain object and must not throw on one.\n *\n * Report each problem at the path the schema reports it. `validateDraft` adds\n * every schema failure as `invalid` unless an issue returned here sits at that\n * path or inside it — so an issue at the wrong path leaves the schema's\n * message standing beside yours, and makes an unfinished draft read as wrong.\n * An issue inside a path accounts for every failure at that path, a list's\n * length included, so report a rule about a whole list at the list's own path.\n * A failure at the root of the draft is accounted for only by an issue at the\n * root. A `null` the schema refuses, where nothing here reports it, comes back\n * as `null_not_allowed` — except inside a plain `z.union`, which reports a\n * failure once, at the union's own path, under its own code.\n *\n * A code documented in `docs/authoring.md` is reported with its documented\n * severity. For a code of your own, a severity other than `'incomplete'` is\n * reported as `invalid`.\n */\n readonly checkDraft?: (draft: Readonly<Record<string, unknown>>) => DraftIssue[];\n}\n\n/**\n * A value-level description of an activity type: its contract (schema), its\n * grading, its redaction policy, and its interop facts. Registering a\n * descriptor makes `validateActivity`, `validateDraft`, `score`, `evaluate`,\n * `redact`, and `jsonSchemaFor` work for the type — an activity type is a value,\n * not a hardcoded union member (R1).\n */\nexport interface ActivityTypeDescriptor<TData extends { type: string }, TResponse> {\n /** The `type` discriminator string (kebab-case by convention). */\n readonly type: TData['type'];\n /** Zod schema validating the activity's data contract. */\n readonly schema: z.ZodType<TData>;\n /** How responses are graded. */\n readonly scoring: ActivityTypeScoring<TData, TResponse>;\n /** Whether a response counts as an answer (vs. blank/untouched). */\n readonly isAnswered?: (response: TResponse | undefined) => boolean;\n /** Per-field sensitivity map driving `redact()`. Fail-closed. */\n readonly fieldPolicy?: FieldPolicy;\n /**\n * Schema the output of `redact(data)` (with default `reveal: 'none'`) must\n * satisfy. Strict by design: it proves the ABSENCE of answer-key fields,\n * so `assertRedacted` can guarantee a payload is safe to send to a learner.\n */\n readonly redactedSchema?: z.ZodType<unknown>;\n /** Interop facts for xAPI (and later QTI) statement building. */\n readonly interop?: ActivityTypeInterop<TData>;\n /** The interaction-event kinds components for this type emit. */\n readonly interactions?: readonly string[];\n /** Draft support for authoring tools. See {@link ActivityTypeAuthoring}. */\n readonly authoring?: ActivityTypeAuthoring<TData>;\n}\n\n/**\n * Internal type-erased descriptor shape stored in the registry. Dispatch call\n * sites cast payloads back; the public generic API preserves inference.\n */\nexport interface RegisteredActivityTypeDescriptor {\n readonly type: string;\n readonly schema: z.ZodType<unknown>;\n readonly scoring:\n | {\n readonly kind: 'sync';\n readonly score: (data: unknown, response: unknown) => PartialScoringResult;\n }\n | {\n readonly kind: 'deferred';\n readonly reason: 'requires_async_grading';\n readonly partial?: (data: unknown, response: unknown) => DeferredScoringPartial;\n };\n readonly isAnswered?: (response: unknown) => boolean;\n readonly fieldPolicy?: FieldPolicy;\n readonly redactedSchema?: z.ZodType<unknown>;\n readonly interop?: ActivityTypeInterop<unknown>;\n readonly interactions?: readonly string[];\n readonly authoring?: ActivityTypeAuthoring<unknown>;\n}\n\n/**\n * Module-scoped default registry (deliberate — no registry instances until a\n * second consumer exists; see roadmap §3.2 / audit §15.3).\n */\nconst registry = new Map<string, RegisteredActivityTypeDescriptor>();\n\n/**\n * Identity helper that gives full type inference when authoring a descriptor:\n *\n * ```ts\n * const myType = defineActivityType<MyTypeData, MyTypeLearnerResponse>({ ... });\n * registerActivityType(myType);\n * ```\n */\nexport function defineActivityType<TData extends { type: string }, TResponse>(\n descriptor: ActivityTypeDescriptor<TData, TResponse>,\n): ActivityTypeDescriptor<TData, TResponse> {\n return descriptor;\n}\n\n/**\n * Registers an activity type on the default registry, making it live for\n * `validateActivity`, `score`, `evaluate`, `redact`, and `jsonSchemaFor`.\n * Re-registering the same descriptor object is a no-op; registering a\n * DIFFERENT descriptor under an existing type throws — silently replacing a\n * type's contract or scoring is exactly the class of accident a summative\n * SDK must not allow.\n */\nexport function registerActivityType<TData extends { type: string }, TResponse>(\n descriptor: ActivityTypeDescriptor<TData, TResponse>,\n): void {\n if (descriptor.type === 'item-group') {\n // The container that holds several items around one stimulus. It has no\n // learner response and no score of its own, so it can never satisfy this\n // contract — and letting a consumer register one would make `isItemGroup`\n // and `flattenSequence` misread their own container.\n throw new Error(\n '\"item-group\" is reserved for the SDK\\'s item-group container (see ItemGroup) and cannot be registered as an activity type.',\n );\n }\n const existing = registry.get(descriptor.type);\n if (existing !== undefined) {\n if ((existing as unknown) === (descriptor as unknown)) {\n return;\n }\n throw new Error(\n `Activity type \"${descriptor.type}\" is already registered. ` +\n 'Registering a different descriptor for an existing type is not allowed.',\n );\n }\n registry.set(descriptor.type, descriptor as unknown as RegisteredActivityTypeDescriptor);\n}\n\n/** Returns the registered descriptor for `type`, or `undefined`. */\nexport function getActivityTypeDescriptor(\n type: string,\n): RegisteredActivityTypeDescriptor | undefined {\n return registry.get(type);\n}\n\n/** The type strings currently registered (built-ins plus consumer-registered). */\nexport function registeredActivityTypes(): string[] {\n return [...registry.keys()];\n}\n","import { MediaSchema } from '../schemas/media.js';\nimport type { DraftIssue, DraftSeverity } from '../types/authoring.js';\n\n/**\n * Every issue code the SDK's draft checks can report, with its severity.\n *\n * A code's severity is part of its contract, so it is fixed here, once: an\n * issue is built from its code, and the same code cannot be `incomplete` in one\n * place and `invalid` in another. `docs/authoring.md` documents this table, and\n * a test holds the two together.\n */\nexport const DRAFT_ISSUE_SEVERITY = {\n // Every registered type — reported by validateDraft itself\n null_not_allowed: 'invalid',\n // Every built-in activity\n schema_version_invalid: 'invalid',\n type_mismatch: 'invalid',\n id_required: 'invalid',\n title_required: 'incomplete',\n scoring_strategy_required: 'incomplete',\n pass_threshold_invalid: 'invalid',\n difficulty_level_invalid: 'invalid',\n feedback_empty: 'incomplete',\n redacted_data: 'invalid',\n media_type_required: 'incomplete',\n media_url_required: 'incomplete',\n media_url_invalid: 'invalid',\n media_alt_required: 'incomplete',\n media_playback_invalid: 'invalid',\n media_invalid: 'invalid',\n // multiple-choice\n mc_question_required: 'incomplete',\n mc_mode_required: 'incomplete',\n mc_options_too_few: 'incomplete',\n mc_options_too_many: 'invalid',\n mc_option_id_required: 'invalid',\n mc_option_id_duplicate: 'invalid',\n mc_option_text_required: 'incomplete',\n mc_option_correctness_required: 'incomplete',\n mc_option_media_kind: 'invalid',\n mc_correct_option_required: 'incomplete',\n mc_single_mode_one_correct: 'invalid',\n // fill-in-the-blanks\n fib_passage_required: 'incomplete',\n fib_blanks_required: 'incomplete',\n fib_blank_id_required: 'invalid',\n fib_blank_id_duplicate: 'invalid',\n fib_accepted_answers_required: 'incomplete',\n fib_accepted_answer_empty: 'incomplete',\n fib_levenshtein_invalid: 'invalid',\n fib_match_locale_invalid: 'invalid',\n fib_match_invalid: 'invalid',\n fib_blank_missing: 'incomplete',\n fib_placeholder_missing: 'incomplete',\n fib_placeholder_duplicate: 'invalid',\n fib_blanks_mismatch: 'invalid',\n // item-group (the testlet container, via validateItemGroupDraft)\n ig_not_an_object: 'invalid',\n ig_stimulus_required: 'incomplete',\n ig_stimulus_id_required: 'invalid',\n ig_stimulus_kind_required: 'incomplete',\n ig_stimulus_kind_invalid: 'invalid',\n ig_stimulus_body_required: 'incomplete',\n ig_stimulus_media_required: 'incomplete',\n ig_stimulus_media_kind: 'invalid',\n ig_items_required: 'incomplete',\n ig_item_id_required: 'invalid',\n ig_item_id_duplicate: 'invalid',\n ig_item_type_required: 'incomplete',\n ig_item_type_unknown: 'invalid',\n ig_item_nested_group: 'invalid',\n ig_shuffle_invalid: 'invalid',\n // gap-select\n gs_passage_required: 'incomplete',\n gs_gaps_required: 'incomplete',\n gs_gap_id_required: 'invalid',\n gs_gap_id_duplicate: 'invalid',\n gs_gap_missing: 'incomplete',\n gs_placeholder_missing: 'incomplete',\n gs_placeholder_duplicate: 'invalid',\n gs_gaps_mismatch: 'invalid',\n gs_presentation_invalid: 'invalid',\n gs_bank_id_required: 'invalid',\n gs_bank_id_duplicate: 'invalid',\n gs_bank_unknown: 'invalid',\n gs_choice_source_required: 'incomplete',\n gs_choice_source_conflict: 'invalid',\n gs_choices_too_few: 'incomplete',\n gs_choice_id_required: 'invalid',\n gs_choice_id_duplicate: 'invalid',\n gs_choice_text_required: 'incomplete',\n gs_correct_choice_required: 'incomplete',\n gs_correct_choice_unknown: 'invalid',\n // written-response\n wr_prompt_required: 'incomplete',\n wr_min_words_required: 'incomplete',\n wr_min_words_invalid: 'invalid',\n wr_max_words_required: 'incomplete',\n wr_max_words_invalid: 'invalid',\n wr_word_bounds_order: 'invalid',\n wr_rubric_criteria_required: 'incomplete',\n wr_criterion_name_required: 'incomplete',\n wr_criterion_weight_required: 'incomplete',\n wr_criterion_weight_invalid: 'invalid',\n wr_rubric_weights_zero: 'incomplete',\n wr_rubric_weights_too_large: 'invalid',\n} as const satisfies Readonly<Record<string, DraftSeverity>>;\n\nexport type DraftIssueCode = keyof typeof DRAFT_ISSUE_SEVERITY;\n\n/** A draft as a check receives it: any plain object. */\nexport type DraftFields = Readonly<Record<string, unknown>>;\n\n/** A schema library issue, as far as the draft checks read one. */\nexport interface SchemaIssue {\n readonly code: string;\n readonly path: readonly PropertyKey[];\n readonly input?: unknown;\n}\n\nexport function issue(\n code: DraftIssueCode,\n path: readonly (string | number)[],\n message: string,\n): DraftIssue {\n return { path: path.map(String), message, code, severity: DRAFT_ISSUE_SEVERITY[code] };\n}\n\nexport function isRecord(value: unknown): value is DraftFields {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The value at `path` inside `root`, or `undefined` once the path leaves the\n * data. Used to tell a schema failure caused by `null` from any other.\n */\nexport function valueAt(root: unknown, path: readonly PropertyKey[]): unknown {\n let node: unknown = root;\n for (const segment of path) {\n if (typeof node !== 'object' || node === null) {\n return undefined;\n }\n node = (node as Record<PropertyKey, unknown>)[segment];\n }\n return node;\n}\n\n/**\n * Whether a schema issue is the schema refusing an empty value: a `null`, or an\n * `undefined` entry in a list, which JSON writes as `null`. `validateDraft`\n * reports each such refusal as `null_not_allowed`, so a check leaves them to it.\n *\n * It reads the issue's `input`, so parse with `{ reportInput: true }`. The input\n * is the value the failing check was given. A refinement on a whole object that\n * reports at one of its fields carries the object, so a rule that points at an\n * empty field keeps its own code and message. A discriminated union with no\n * member for its discriminator is the one refusal reported with the object as\n * its input.\n */\nexport function refusesEmpty(root: unknown, schemaIssue: SchemaIssue): boolean {\n const { path } = schemaIssue;\n const value = valueAt(root, path);\n const index = path.at(-1);\n const list = valueAt(root, path.slice(0, -1));\n const empty =\n value === null ||\n (value === undefined &&\n typeof index === 'number' &&\n Array.isArray(list) &&\n index < list.length);\n return empty && (schemaIssue.input === value || schemaIssue.code === 'invalid_union');\n}\n\n/** Absent or `null`: a field nobody has set. */\nexport function isUnset(value: unknown): boolean {\n return value === undefined || value === null;\n}\n\n/**\n * Not written yet: absent, `null`, or a string holding nothing but whitespace.\n *\n * Whitespace counts as unwritten even where the schema accepts it — a title of\n * `\" \"` is a valid string and an unfinished question. So does the empty string\n * a `<select>` placeholder hands over for a field that must be chosen. A value\n * of any other type is left to the schema, which reports it as `invalid`.\n */\nexport function isUnwritten(value: unknown): boolean {\n return isUnset(value) || (typeof value === 'string' && value.trim() === '');\n}\n\n/** An id with nothing in it. Whitespace is an id the schema accepts, so it is one here too. */\nexport function isMissingId(value: unknown): boolean {\n return isUnset(value) || value === '';\n}\n\n/**\n * A whole number JavaScript holds exactly — what the schemas' `.int()` accepts.\n * `2 ** 53` is a whole number, and the schemas refuse it.\n */\nexport function isWholeNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value);\n}\n\n/** A whole number above what JavaScript holds exactly, so that a message can say so. */\nexport function isTooLarge(value: unknown): boolean {\n return typeof value === 'number' && value > Number.MAX_SAFE_INTEGER && Number.isInteger(value);\n}\n\n/**\n * The envelope, `id` and `title`, which every built-in activity has, and which\n * lead its issues. `schemaVersion` and `type` identify the payload rather than\n * say anything an author wrote, so a wrong or missing one is `invalid`.\n */\nexport function checkIdentity(draft: DraftFields, type: string): DraftIssue[] {\n const issues: DraftIssue[] = [];\n if (draft.schemaVersion !== '1.0') {\n issues.push(\n issue(\n 'schema_version_invalid',\n ['schemaVersion'],\n 'The draft must have schemaVersion \"1.0\". A draft from createDraft has it.',\n ),\n );\n }\n if (draft.type !== type) {\n issues.push(issue('type_mismatch', ['type'], `The draft's type must be \"${type}\".`));\n }\n if (isMissingId(draft.id)) {\n issues.push(issue('id_required', ['id'], 'The activity has no id.'));\n }\n if (isUnwritten(draft.title)) {\n issues.push(issue('title_required', ['title'], 'Add a title.'));\n }\n return issues;\n}\n\n/** `scoringStrategy`, which both synchronously scored built-ins require. */\nexport function checkScoringStrategy(draft: DraftFields): DraftIssue[] {\n return isUnwritten(draft.scoringStrategy)\n ? [\n issue(\n 'scoring_strategy_required',\n ['scoringStrategy'],\n 'Choose how the question is scored.',\n ),\n ]\n : [];\n}\n\nconst DIFFICULTY_LEVELS: readonly unknown[] = [1, 2, 3, 4, 5];\n\n/**\n * The optional fields every built-in activity shares, checked after its own\n * fields. A `null` in any of them is left to `validateDraft`, which reports\n * `null_not_allowed` for every type alike.\n */\nexport function checkSharedOptional(draft: DraftFields): DraftIssue[] {\n const issues: DraftIssue[] = [];\n const threshold = draft.passThreshold;\n if (!isUnset(threshold) && !(typeof threshold === 'number' && threshold >= 0 && threshold <= 1)) {\n issues.push(\n issue(\n 'pass_threshold_invalid',\n ['passThreshold'],\n 'The pass threshold must be a number from 0 to 1.',\n ),\n );\n }\n if (!isUnset(draft.difficultyLevel) && !DIFFICULTY_LEVELS.includes(draft.difficultyLevel)) {\n issues.push(\n issue(\n 'difficulty_level_invalid',\n ['difficultyLevel'],\n 'The difficulty level must be a whole number from 1 to 5.',\n ),\n );\n }\n const feedback = draft.feedback;\n if (isRecord(feedback)) {\n for (const key of ['correct', 'incorrect'] as const) {\n const message = feedback[key];\n if (typeof message === 'string' && message.trim() === '') {\n issues.push(\n issue('feedback_empty', ['feedback', key], 'Write this feedback message, or remove it.'),\n );\n }\n }\n }\n // What `redact()` produces for a learner. Its answer key is gone, and the\n // components refuse it outright in `practice`, so it is never a draft.\n if (draft.redacted === true) {\n issues.push(\n issue(\n 'redacted_data',\n ['redacted'],\n 'This is a copy prepared for learners, with the answer key removed. Edit the original activity instead.',\n ),\n );\n }\n if (isRecord(draft.media)) {\n issues.push(...checkMedia(draft.media));\n }\n return issues;\n}\n\nconst URL_POLICY =\n 'Use an https:, http:, data: or blob: address, or a path on the same site that starts with a single \"/\".';\nconst EMBED_URL =\n \"An embed needs the provider's full http(s) embed address, such as https://www.youtube.com/embed/VIDEO_ID.\";\n\n/**\n * Media, checked through `MediaSchema` itself, so the URL and playback rules\n * live in one place. What this adds is the split: a kind, an address or a\n * description nobody has given yet is `incomplete`; one that is given and\n * refused is `invalid`.\n */\nexport function checkMedia(\n media: DraftFields,\n prefix: readonly (string | number)[] = ['media'],\n schema: { safeParse: typeof MediaSchema.safeParse } = MediaSchema,\n): DraftIssue[] {\n const issues: DraftIssue[] = [];\n const at = (...rest: (string | number)[]) => [...prefix, ...rest];\n const parsed = schema.safeParse(media, { reportInput: true });\n // A refused `null` inside the media block is `validateDraft`'s to report, as\n // `null_not_allowed` — unless a check below reads it as a field not set yet.\n const schemaIssues = parsed.success\n ? []\n : parsed.error.issues.filter((found) => !refusesEmpty(media, found));\n const refusal = (field: string) => schemaIssues.find((found) => found.path[0] === field);\n\n const typeUnset = isUnwritten(media.type);\n if (typeUnset) {\n issues.push(issue('media_type_required', at('type'), 'Choose what kind of media this is.'));\n }\n\n if (isUnwritten(media.url)) {\n issues.push(issue('media_url_required', at('url'), 'Add the address of the media file.'));\n } else {\n const refused = refusal('url');\n if (refused !== undefined) {\n // The only refinement on `url` is the embed rule; every other refusal is\n // the address policy itself, which zod reports only as \"Invalid input\".\n issues.push(\n issue('media_url_invalid', at('url'), refused.code === 'custom' ? EMBED_URL : URL_POLICY),\n );\n }\n }\n\n const needsAlt = media.type === 'image' || media.type === 'embed';\n const alt = media.alt;\n if ((needsAlt && isUnwritten(alt)) || (typeof alt === 'string' && alt.trim() === '')) {\n issues.push(issue('media_alt_required', at('alt'), 'Add a text description of the media.'));\n }\n\n const captions = media.captionsUrl;\n if (typeof captions === 'string' && captions.trim() === '') {\n // Optional, and left blank: unfinished, as a blank description is.\n issues.push(\n issue(\n 'media_url_required',\n at('captionsUrl'),\n 'Add the address of the captions file, or remove the captions.',\n ),\n );\n } else if (refusal('captionsUrl') !== undefined) {\n issues.push(issue('media_url_invalid', at('captionsUrl'), URL_POLICY));\n }\n\n for (const schemaIssue of schemaIssues) {\n const field = schemaIssue.path[0];\n if (\n field === 'url' ||\n field === 'alt' ||\n field === 'captionsUrl' ||\n (field === 'type' && typeUnset)\n ) {\n continue;\n }\n issues.push(\n issue(\n field === 'playback' ? 'media_playback_invalid' : 'media_invalid',\n at(...schemaIssue.path.map(String)),\n schemaIssue.message,\n ),\n );\n }\n return issues;\n}\n\n/**\n * Whether a check issue at `reported` accounts for a schema failure at `failed`:\n * at the same path, or inside it. The root is the exception. Every path is inside\n * the root, so a root-level failure is accounted for only by a root-level issue.\n */\nexport function covers(reported: readonly string[], failed: readonly string[]): boolean {\n if (failed.length === 0) {\n return reported.length === 0;\n }\n // Each segment of the failed path matches the reported path at the same\n // position. A failed path longer than the reported one cannot: the reported\n // path runs out, and no segment is ever undefined.\n return failed.every((segment, index) => reported[index] === segment);\n}\n","import type { ActivityTypeAuthoring } from '../registry/registry.js';\nimport { PLACEHOLDER_RE, TextMatchPolicySchema } from '../schemas/fill-in-the-blanks.js';\nimport type { FillInTheBlanksData } from '../types/activity.js';\nimport type { DraftIssue } from '../types/authoring.js';\nimport {\n checkIdentity,\n checkScoringStrategy,\n checkSharedOptional,\n type DraftFields,\n isMissingId,\n isRecord,\n issue,\n isTooLarge,\n isUnset,\n isUnwritten,\n isWholeNumber,\n refusesEmpty,\n} from './issues.js';\n\n/**\n * Draft support for `fill-in-the-blanks`.\n *\n * A new draft has an empty passage and no blanks, and `scoringStrategy` starts\n * at `all-or-nothing`, as for multiple choice: partial credit is the author's\n * call.\n *\n * The passage and the blanks must pair up one to one. When they do not, the\n * issue says which way. A placeholder with no blank, or a blank with no\n * placeholder, is something still to be written (`incomplete`). The same\n * placeholder twice, or two blanks sharing an id, cannot be fixed by writing\n * more (`invalid`). All of them are reported at `passage`, which is where the\n * schema reports the pairing.\n */\nexport const fillInTheBlanksAuthoring: ActivityTypeAuthoring<FillInTheBlanksData> = {\n createDraft: ({ newId }) => ({\n schemaVersion: '1.0',\n type: 'fill-in-the-blanks',\n id: newId(),\n title: '',\n passage: '',\n blanks: [],\n scoringStrategy: 'all-or-nothing',\n }),\n checkDraft: checkFillInTheBlanksDraft,\n};\n\nfunction checkFillInTheBlanksDraft(draft: DraftFields): DraftIssue[] {\n const issues = checkIdentity(draft, 'fill-in-the-blanks');\n const passage = draft.passage;\n if (isUnwritten(passage)) {\n issues.push(issue('fib_passage_required', ['passage'], 'Write the passage.'));\n }\n issues.push(...checkScoringStrategy(draft));\n\n const blanks = isUnset(draft.blanks) ? [] : draft.blanks;\n if (Array.isArray(blanks)) {\n if (blanks.length === 0) {\n issues.push(\n issue(\n 'fib_blanks_required',\n ['blanks'],\n 'Add at least one blank, and mark where it goes in the passage with {{id}}.',\n ),\n );\n }\n const ids: string[] = [];\n for (const [index, blank] of blanks.entries()) {\n if (!isRecord(blank)) {\n continue;\n }\n const name = typeof blank.id === 'string' && blank.id !== '' ? `\"${blank.id}\"` : index + 1;\n if (isMissingId(blank.id)) {\n issues.push(\n issue('fib_blank_id_required', ['blanks', index, 'id'], `Blank ${index + 1} has no id.`),\n );\n } else if (typeof blank.id === 'string') {\n ids.push(blank.id);\n }\n\n const answers = isUnset(blank.acceptedAnswers) ? [] : blank.acceptedAnswers;\n if (Array.isArray(answers)) {\n if (answers.length === 0) {\n issues.push(\n issue(\n 'fib_accepted_answers_required',\n ['blanks', index, 'acceptedAnswers'],\n `Add an accepted answer for blank ${name}.`,\n ),\n );\n }\n for (const [position, answer] of answers.entries()) {\n // A `null` entry is `validateDraft`'s to report, as `null_not_allowed`.\n if (typeof answer === 'string' && answer.trim() === '') {\n issues.push(\n issue(\n 'fib_accepted_answer_empty',\n ['blanks', index, 'acceptedAnswers', position],\n `An accepted answer for blank ${name} is empty. Fill it in or remove it.`,\n ),\n );\n }\n }\n }\n\n if (isRecord(blank.match)) {\n issues.push(...checkMatch(blank.match, index));\n }\n }\n // An unwritten passage is already reported, and pairing blanks against it\n // would only restate that every blank is missing from it.\n if (typeof passage === 'string' && passage.trim() !== '') {\n issues.push(...checkPairing(passage, blanks, ids));\n }\n }\n\n issues.push(...checkSharedOptional(draft));\n return issues;\n}\n\n/**\n * A blank's matching tolerances, checked through `TextMatchPolicySchema` so its\n * rules live in one place, plus the one thing the schema cannot see: a locale\n * the runtime refuses. `matchText` hands a non-empty `locale` to\n * `toLocaleLowerCase`, which throws on a tag that is not a language tag, so a\n * draft holding one would be \"complete\" and still crash the moment it was\n * scored.\n */\nfunction checkMatch(match: Readonly<Record<string, unknown>>, index: number): DraftIssue[] {\n const issues: DraftIssue[] = [];\n const at = (...rest: (string | number)[]) => ['blanks', index, 'match', ...rest];\n\n const tolerance = match.levenshtein;\n if (!isUnset(tolerance) && !(isWholeNumber(tolerance) && tolerance >= 0)) {\n issues.push(\n issue(\n 'fib_levenshtein_invalid',\n at('levenshtein'),\n isTooLarge(tolerance)\n ? 'Typo tolerance is too large.'\n : 'Typo tolerance must be a whole number, 0 or more.',\n ),\n );\n }\n\n const locale = match.locale;\n if (typeof locale === 'string' && locale !== '' && !isLanguageTag(locale)) {\n issues.push(\n issue(\n 'fib_match_locale_invalid',\n at('locale'),\n `\"${locale}\" is not a language tag. Use one such as \"tr\" or \"en-US\", or leave the locale out.`,\n ),\n );\n }\n\n const parsed = TextMatchPolicySchema.safeParse(match, { reportInput: true });\n if (!parsed.success) {\n for (const schemaIssue of parsed.error.issues) {\n // `levenshtein` has its own code above, and a refused `null` is\n // `validateDraft`'s to report, as `null_not_allowed`.\n if (schemaIssue.path[0] === 'levenshtein' || refusesEmpty(match, schemaIssue)) {\n continue;\n }\n issues.push(\n issue('fib_match_invalid', at(...schemaIssue.path.map(String)), schemaIssue.message),\n );\n }\n }\n return issues;\n}\n\n/** Whether the runtime accepts `tag` as a language tag — the same test `toLocaleLowerCase` applies. */\nfunction isLanguageTag(tag: string): boolean {\n try {\n Intl.getCanonicalLocales(tag);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction checkPairing(passage: string, blanks: readonly unknown[], ids: readonly string[]) {\n const issues: DraftIssue[] = [];\n const placeholders = new Map<string, number>();\n for (const match of passage.matchAll(PLACEHOLDER_RE)) {\n const id = match[1] as string;\n placeholders.set(id, (placeholders.get(id) ?? 0) + 1);\n }\n const blankIds = new Map<string, number>();\n for (const id of ids) {\n blankIds.set(id, (blankIds.get(id) ?? 0) + 1);\n }\n\n for (const [id, count] of blankIds) {\n if (count > 1) {\n issues.push(\n issue('fib_blank_id_duplicate', ['passage'], `More than one blank has the id \"${id}\".`),\n );\n }\n }\n for (const [id, count] of placeholders) {\n if (count > 1) {\n issues.push(\n issue(\n 'fib_placeholder_duplicate',\n ['passage'],\n `{{${id}}} appears more than once in the passage. Each blank goes in one place.`,\n ),\n );\n }\n if (!blankIds.has(id)) {\n issues.push(\n issue(\n 'fib_blank_missing',\n ['passage'],\n `The passage has {{${id}}}, but there is no blank with that id.`,\n ),\n );\n }\n }\n for (const id of blankIds.keys()) {\n if (!placeholders.has(id)) {\n issues.push(\n issue(\n 'fib_placeholder_missing',\n ['passage'],\n `Blank \"${id}\" is not in the passage. Put {{${id}}} where it goes.`,\n ),\n );\n }\n }\n\n // The schema's own pairing rule, stated exactly as the schema states it. Each\n // code above is one specific way of breaking it. If it is broken in a way none\n // of them names — a blank with no usable id, for one — this says so, rather\n // than leaving the schema's message to be reported as a second failure.\n if (issues.length === 0 && !pairsOneToOne(placeholders, blanks)) {\n issues.push(\n issue(\n 'fib_blanks_mismatch',\n ['passage'],\n 'The blanks and the {{id}} placeholders in the passage do not pair up one to one.',\n ),\n );\n }\n return issues;\n}\n\n/**\n * `FillInTheBlanksDataSchema`'s pairing refinement, over a draft's raw blanks.\n * An entry that is not an object is reported on its own, and the schema never\n * reaches its refinement past one, so it takes no part in the pairing.\n */\nfunction pairsOneToOne(placeholders: ReadonlyMap<string, number>, blanks: readonly unknown[]) {\n const blankIds = blanks.filter(isRecord).map((blank) => blank.id);\n if (new Set(blankIds).size !== blankIds.length) {\n return false;\n }\n if (placeholders.size !== blankIds.length) {\n return false;\n }\n return blankIds.every((id) => typeof id === 'string' && placeholders.get(id) === 1);\n}\n","import type { ActivityTypeAuthoring } from '../registry/registry.js';\nimport { PLACEHOLDER_RE } from '../schemas/fill-in-the-blanks.js';\nimport type { GapSelectData } from '../types/activity.js';\nimport type { DraftIssue } from '../types/authoring.js';\nimport {\n checkIdentity,\n checkScoringStrategy,\n checkSharedOptional,\n type DraftFields,\n isMissingId,\n isRecord,\n issue,\n isUnset,\n isUnwritten,\n} from './issues.js';\n\n/** Mirrors `GapSelectChoiceSchema` arrays: `.min(2)` on both a gap's own list and a bank. */\nconst MIN_CHOICES = 2;\n\n/**\n * Draft support for `gap-select`.\n *\n * A new draft is an empty passage with no gaps, like fill-in-the-blanks, and\n * `scoringStrategy` starts at `all-or-nothing`: partial credit is the author's\n * to give.\n *\n * The one thing this type must never do is pre-select a correct choice. A gap\n * whose `correctChoiceId` already points at the first option lets an author\n * write four plausible choices, never open the correctness control, and hold a\n * `complete` question whose answer key is whatever happened to be listed first.\n * `correctChoiceId` therefore starts unwritten and the draft stays `incomplete`\n * until somebody says which choice is right — the same rule multiple-choice\n * follows for `isCorrect`.\n */\nexport const gapSelectAuthoring: ActivityTypeAuthoring<GapSelectData> = {\n createDraft: ({ newId }) => ({\n schemaVersion: '1.0',\n type: 'gap-select',\n id: newId(),\n title: '',\n passage: '',\n gaps: [],\n scoringStrategy: 'all-or-nothing',\n }),\n checkDraft: checkGapSelectDraft,\n};\n\nfunction checkGapSelectDraft(draft: DraftFields): DraftIssue[] {\n const issues = checkIdentity(draft, 'gap-select');\n const passage = draft.passage;\n if (isUnwritten(passage)) {\n issues.push(issue('gs_passage_required', ['passage'], 'Write the passage.'));\n }\n issues.push(...checkScoringStrategy(draft));\n\n if (!isUnset(draft.presentation) && draft.presentation !== 'dropdown') {\n issues.push(\n issue(\n 'gs_presentation_invalid',\n ['presentation'],\n 'The only presentation is \"dropdown\". Leave it out unless you mean to set it.',\n ),\n );\n }\n\n const banks = isUnset(draft.banks) ? [] : draft.banks;\n const bankIds = new Set<string>();\n if (Array.isArray(banks)) {\n issues.push(...checkBanks(banks, bankIds));\n }\n\n const gaps = isUnset(draft.gaps) ? [] : draft.gaps;\n const gapIds: string[] = [];\n if (Array.isArray(gaps)) {\n if (gaps.length === 0) {\n issues.push(\n issue(\n 'gs_gaps_required',\n ['gaps'],\n 'Add at least one gap, and mark where it goes in the passage with {{id}}.',\n ),\n );\n }\n issues.push(...checkGaps(gaps, banks, bankIds, gapIds));\n // An unwritten passage is already reported; pairing every gap against it\n // would only restate that each one is missing.\n if (typeof passage === 'string' && passage.trim() !== '') {\n issues.push(...checkPairing(passage, gaps, gapIds));\n }\n }\n\n issues.push(...checkSharedOptional(draft));\n return issues;\n}\n\n/** The shared word banks. Reported at `banks`, where the schema reports their uniqueness rule. */\nfunction checkBanks(banks: readonly unknown[], bankIds: Set<string>): DraftIssue[] {\n const issues: DraftIssue[] = [];\n const repeated = new Set<string>();\n for (const [index, bank] of banks.entries()) {\n if (!isRecord(bank)) {\n continue;\n }\n if (isMissingId(bank.id)) {\n issues.push(\n issue('gs_bank_id_required', ['banks', index, 'id'], `Word bank ${index + 1} has no id.`),\n );\n } else if (typeof bank.id === 'string') {\n if (bankIds.has(bank.id)) {\n repeated.add(bank.id);\n }\n bankIds.add(bank.id);\n }\n const name = typeof bank.id === 'string' && bank.id !== '' ? `\"${bank.id}\"` : index + 1;\n issues.push(...checkChoiceList(bank.choices, ['banks', index, 'choices'], `word bank ${name}`));\n }\n for (const id of repeated) {\n issues.push(\n issue('gs_bank_id_duplicate', ['banks'], `More than one word bank has the id \"${id}\".`),\n );\n }\n return issues;\n}\n\n/**\n * Every gap. The rules the schema states over the whole array — a duplicate id,\n * a missing or doubled choice source, an unknown bank, an answer key naming a\n * choice the gap does not offer — are reported at `gaps`, because that is where\n * the schema reports them and an issue deeper inside would account for the\n * schema's failure while saying something narrower than it.\n */\nfunction checkGaps(\n gaps: readonly unknown[],\n banks: unknown,\n bankIds: ReadonlySet<string>,\n gapIds: string[],\n): DraftIssue[] {\n const issues: DraftIssue[] = [];\n const atGaps: DraftIssue[] = [];\n const repeated = new Set<string>();\n const seen = new Set<string>();\n\n for (const [index, gap] of gaps.entries()) {\n if (!isRecord(gap)) {\n continue;\n }\n const ordinal = index + 1;\n const name = typeof gap.id === 'string' && gap.id !== '' ? `\"${gap.id}\"` : ordinal;\n\n if (isMissingId(gap.id)) {\n issues.push(issue('gs_gap_id_required', ['gaps', index, 'id'], `Gap ${ordinal} has no id.`));\n } else if (typeof gap.id === 'string') {\n if (seen.has(gap.id)) {\n repeated.add(gap.id);\n }\n seen.add(gap.id);\n gapIds.push(gap.id);\n }\n\n const hasOwn = !isUnset(gap.choices);\n const hasBank = !isUnwritten(gap.bankId);\n // A `bankId` left blank is the bank selector still on its placeholder. The\n // schema refuses an empty string AT the field, so this is reported there —\n // an issue at `gaps` would not account for it, and zod's own diagnostic\n // would stand beside ours and call an unfinished gap a broken one.\n const bankBlank = typeof gap.bankId === 'string' && gap.bankId.trim() === '';\n if (bankBlank) {\n issues.push(\n issue(\n 'gs_choice_source_required',\n ['gaps', index, 'bankId'],\n `Choose a word bank for gap ${name}, or remove the empty field.`,\n ),\n );\n } else if (hasOwn && hasBank) {\n atGaps.push(\n issue(\n 'gs_choice_source_conflict',\n ['gaps'],\n `Gap ${name} has both its own choices and a word bank. Use one or the other.`,\n ),\n );\n } else if (!hasOwn && !hasBank) {\n atGaps.push(\n issue(\n 'gs_choice_source_required',\n ['gaps'],\n `Give gap ${name} a list of choices, or point it at a word bank.`,\n ),\n );\n }\n\n if (hasBank && typeof gap.bankId === 'string' && !bankIds.has(gap.bankId)) {\n atGaps.push(\n issue(\n 'gs_bank_unknown',\n ['gaps'],\n `Gap ${name} uses the word bank \"${gap.bankId}\", which does not exist.`,\n ),\n );\n }\n\n // Checked whenever the list is PRESENT, even alongside a `bankId` the gap\n // should not also have: the schema validates the array either way, so\n // skipping it while the conflict is reported left the schema's own\n // complaint about a too-short list standing on its own.\n if (hasOwn) {\n issues.push(...checkChoiceList(gap.choices, ['gaps', index, 'choices'], `gap ${name}`));\n }\n\n issues.push(...checkAnswerKey(gap, index, name, banks, bankIds));\n }\n\n for (const id of repeated) {\n atGaps.push(issue('gs_gap_id_duplicate', ['gaps'], `More than one gap has the id \"${id}\".`));\n }\n return [...issues, ...atGaps];\n}\n\n/** A gap's answer key, against the choices it actually offers. */\nfunction checkAnswerKey(\n gap: Readonly<Record<string, unknown>>,\n index: number,\n name: string | number,\n banks: unknown,\n bankIds: ReadonlySet<string>,\n): DraftIssue[] {\n const key = gap.correctChoiceId;\n if (isUnwritten(key)) {\n // At the field, not at `gaps`: the schema requires `correctChoiceId` and\n // reports a missing one HERE, and an issue at `gaps` accounts only for\n // failures AT `gaps` — never for one deeper inside it. Reported at the\n // array, this left zod's raw `invalid_type` standing beside it and made an\n // answer key nobody had chosen yet read as a broken draft.\n return [\n issue(\n 'gs_correct_choice_required',\n ['gaps', index, 'correctChoiceId'],\n `Mark the correct choice for gap ${name}.`,\n ),\n ];\n }\n if (typeof key !== 'string') {\n // A value of another type is the schema's to refuse.\n return [];\n }\n const choices = resolveChoices(gap, banks, bankIds);\n if (choices === undefined) {\n // The source is missing, doubled or unknown — already reported, and there is\n // no list to check the key against.\n return [];\n }\n const offered = choices.some((choice) => isRecord(choice) && choice.id === key);\n return offered\n ? []\n : [\n issue(\n 'gs_correct_choice_unknown',\n ['gaps'],\n `Gap ${name} is marked correct on \"${key}\", which is not one of its choices.`,\n ),\n ];\n}\n\n/** The choices a gap resolves to, or `undefined` when its source is unusable. */\nfunction resolveChoices(\n gap: Readonly<Record<string, unknown>>,\n banks: unknown,\n bankIds: ReadonlySet<string>,\n): readonly unknown[] | undefined {\n const hasOwn = !isUnset(gap.choices);\n const hasBank = !isUnwritten(gap.bankId);\n if (hasOwn === hasBank) {\n return undefined;\n }\n if (hasOwn) {\n return Array.isArray(gap.choices) ? gap.choices : undefined;\n }\n if (typeof gap.bankId !== 'string' || !bankIds.has(gap.bankId) || !Array.isArray(banks)) {\n return undefined;\n }\n const bank = banks.find((entry) => isRecord(entry) && entry.id === gap.bankId);\n return isRecord(bank) && Array.isArray(bank.choices) ? bank.choices : undefined;\n}\n\n/** One list of choices — a gap's own or a bank's. Both carry the same rules. */\nfunction checkChoiceList(\n choices: unknown,\n path: readonly (string | number)[],\n owner: string,\n): DraftIssue[] {\n const issues: DraftIssue[] = [];\n const list = isUnset(choices) ? [] : choices;\n if (!Array.isArray(list)) {\n return issues;\n }\n if (list.length < MIN_CHOICES) {\n issues.push(\n issue(\n 'gs_choices_too_few',\n path,\n `Give ${owner} at least ${MIN_CHOICES} choices to pick from.`,\n ),\n );\n }\n const seen = new Set<string>();\n const repeated = new Set<string>();\n for (const [index, choice] of list.entries()) {\n if (!isRecord(choice)) {\n continue;\n }\n if (isMissingId(choice.id)) {\n issues.push(\n issue('gs_choice_id_required', [...path, index, 'id'], `Choice ${index + 1} has no id.`),\n );\n } else if (typeof choice.id === 'string') {\n if (seen.has(choice.id)) {\n repeated.add(choice.id);\n }\n seen.add(choice.id);\n }\n if (isUnwritten(choice.text)) {\n issues.push(\n issue(\n 'gs_choice_text_required',\n [...path, index, 'text'],\n `Write the text of choice ${index + 1}.`,\n ),\n );\n }\n }\n for (const id of repeated) {\n issues.push(issue('gs_choice_id_duplicate', path, `More than one choice has the id \"${id}\".`));\n }\n return issues;\n}\n\n/**\n * The passage and the gaps, paired one to one. Reported at `passage`, which is\n * where the schema reports its pairing rule — the same split fill-in-the-blanks\n * uses: something still to write is `incomplete`, something writing more cannot\n * fix is `invalid`.\n */\nfunction checkPairing(\n passage: string,\n gaps: readonly unknown[],\n gapIds: readonly string[],\n): DraftIssue[] {\n const issues: DraftIssue[] = [];\n const placeholders = new Map<string, number>();\n for (const match of passage.matchAll(PLACEHOLDER_RE)) {\n const id = match[1] as string;\n placeholders.set(id, (placeholders.get(id) ?? 0) + 1);\n }\n const ids = new Set(gapIds);\n\n for (const [id, count] of placeholders) {\n if (count > 1) {\n issues.push(\n issue(\n 'gs_placeholder_duplicate',\n ['passage'],\n `{{${id}}} appears more than once in the passage. Each gap goes in one place.`,\n ),\n );\n }\n if (!ids.has(id)) {\n issues.push(\n issue(\n 'gs_gap_missing',\n ['passage'],\n `The passage has {{${id}}}, but there is no gap with that id.`,\n ),\n );\n }\n }\n for (const id of ids) {\n if (!placeholders.has(id)) {\n issues.push(\n issue(\n 'gs_placeholder_missing',\n ['passage'],\n `Gap \"${id}\" is not in the passage. Put {{${id}}} where it goes.`,\n ),\n );\n }\n }\n\n // The schema's pairing rule stated as the schema states it, for a break none\n // of the codes above names — a gap with no usable id beside correct ones.\n if (issues.length === 0 && !pairsOneToOne(placeholders, gaps)) {\n issues.push(\n issue(\n 'gs_gaps_mismatch',\n ['passage'],\n 'The gaps and the {{id}} placeholders in the passage do not pair up one to one.',\n ),\n );\n }\n return issues;\n}\n\nfunction pairsOneToOne(placeholders: ReadonlyMap<string, number>, gaps: readonly unknown[]) {\n const ids = gaps.filter(isRecord).map((gap) => gap.id);\n if (new Set(ids).size !== ids.length || placeholders.size !== ids.length) {\n return false;\n }\n return ids.every((id) => typeof id === 'string' && placeholders.get(id) === 1);\n}\n","import type { ActivityTypeAuthoring } from '../registry/registry.js';\nimport { MultipleChoiceOptionMediaSchema } from '../schemas/multiple-choice.js';\nimport type { MultipleChoiceData } from '../types/activity.js';\nimport type { DraftIssue } from '../types/authoring.js';\nimport {\n checkIdentity,\n checkMedia,\n checkScoringStrategy,\n checkSharedOptional,\n type DraftFields,\n isMissingId,\n isRecord,\n issue,\n isUnset,\n isUnwritten,\n} from './issues.js';\n\n/** Mirrors `MultipleChoiceDataSchema`: `options` is `.min(2).max(26)`. */\nconst MIN_OPTIONS = 2;\nconst MAX_OPTIONS = 26;\n\n/**\n * Draft support for `multiple-choice`.\n *\n * A new draft has two empty options and NO option marked correct. Pre-marking\n * one is the obvious convenience, and it is a trap: an author writes both\n * options, never touches the correctness control, and holds a `complete`\n * question whose answer key is whichever option happened to be marked by\n * default. Leaving it unmarked keeps the question `incomplete` until somebody\n * says which answer is right.\n *\n * `scoringStrategy` starts at `all-or-nothing`, the less generous strategy:\n * partial credit is something an author chooses to give.\n */\nexport const multipleChoiceAuthoring: ActivityTypeAuthoring<MultipleChoiceData> = {\n createDraft: ({ newId }) => ({\n schemaVersion: '1.0',\n type: 'multiple-choice',\n id: newId(),\n title: '',\n question: '',\n mode: 'single',\n scoringStrategy: 'all-or-nothing',\n options: [\n { id: newId(), text: '', isCorrect: false },\n { id: newId(), text: '', isCorrect: false },\n ],\n }),\n checkDraft: checkMultipleChoiceDraft,\n};\n\nfunction checkMultipleChoiceDraft(draft: DraftFields): DraftIssue[] {\n const issues = checkIdentity(draft, 'multiple-choice');\n if (isUnwritten(draft.question)) {\n issues.push(issue('mc_question_required', ['question'], 'Write the question.'));\n }\n if (isUnwritten(draft.mode)) {\n issues.push(\n issue('mc_mode_required', ['mode'], 'Choose whether learners select one option or several.'),\n );\n }\n issues.push(...checkScoringStrategy(draft));\n\n const options = isUnset(draft.options) ? [] : draft.options;\n if (Array.isArray(options)) {\n // Every option-set rule is reported at `options`, where the schema reports\n // its refinements, so none of them is repeated as a second failure.\n if (options.length < MIN_OPTIONS) {\n issues.push(issue('mc_options_too_few', ['options'], 'Add at least two options.'));\n }\n if (options.length > MAX_OPTIONS) {\n issues.push(\n issue('mc_options_too_many', ['options'], `Use no more than ${MAX_OPTIONS} options.`),\n );\n }\n const seen = new Set<string>();\n const repeated = new Set<string>();\n let correct = 0;\n for (const [index, option] of options.entries()) {\n if (!isRecord(option)) {\n continue;\n }\n const ordinal = index + 1;\n if (isMissingId(option.id)) {\n issues.push(\n issue('mc_option_id_required', ['options', index, 'id'], `Option ${ordinal} has no id.`),\n );\n } else if (typeof option.id === 'string') {\n if (seen.has(option.id)) {\n repeated.add(option.id);\n }\n seen.add(option.id);\n }\n if (isUnwritten(option.text)) {\n issues.push(\n issue(\n 'mc_option_text_required',\n ['options', index, 'text'],\n `Write the text of option ${ordinal}.`,\n ),\n );\n }\n // The schema requires the flag on every option. An editor that has not\n // asked yet — or one that stores only the options marked correct — leaves\n // it unset, which is a decision still to make, not a wrong one.\n if (isUnset(option.isCorrect)) {\n issues.push(\n issue(\n 'mc_option_correctness_required',\n ['options', index, 'isCorrect'],\n `Say whether option ${ordinal} is correct.`,\n ),\n );\n }\n if (option.isCorrect === true) {\n correct += 1;\n }\n if (isRecord(option.media)) {\n issues.push(...checkOptionMedia(option.media, index, ordinal));\n }\n }\n for (const id of repeated) {\n issues.push(\n issue('mc_option_id_duplicate', ['options'], `More than one option has the id \"${id}\".`),\n );\n }\n if (correct === 0) {\n issues.push(\n issue(\n 'mc_correct_option_required',\n ['options'],\n draft.mode === 'multi'\n ? 'Mark at least one option as correct.'\n : 'Mark the correct option.',\n ),\n );\n } else if (draft.mode === 'single' && correct > 1) {\n issues.push(\n issue(\n 'mc_single_mode_one_correct',\n ['options'],\n 'Only one option can be marked correct when learners select one option.',\n ),\n );\n }\n }\n\n issues.push(...checkSharedOptional(draft));\n return issues;\n}\n\n/**\n * An option's picture or recording, checked through the SAME rules as the\n * media above the question — one code per problem, wherever it appears, so a\n * consumer translating `media_alt_required` handles both.\n *\n * The one thing an option adds is the kind it refuses. `video` and `embed` are\n * valid above a question and impossible on an option: each renders a control\n * surface that swallows the click meant to select it, so an author who writes\n * one is told why rather than shown zod's enum message.\n */\nfunction checkOptionMedia(\n media: Readonly<Record<string, unknown>>,\n index: number,\n ordinal: number,\n): DraftIssue[] {\n const path = ['options', index, 'media'];\n const kind = media.type;\n if (kind === 'video' || kind === 'embed') {\n return [\n issue(\n 'mc_option_media_kind',\n [...path, 'type'],\n `Option ${ordinal} carries ${kind === 'embed' ? 'an embedded player' : 'a video'}, which cannot be an option: its controls swallow the click that selects the answer. Use a picture or a recording.`,\n ),\n ];\n }\n return checkMedia(media, path, MultipleChoiceOptionMediaSchema);\n}\n","import type { ActivityTypeAuthoring } from '../registry/registry.js';\nimport type { WrittenResponseData } from '../types/activity.js';\nimport type { DraftIssue } from '../types/authoring.js';\nimport {\n checkIdentity,\n checkSharedOptional,\n type DraftFields,\n isRecord,\n issue,\n isTooLarge,\n isUnset,\n isUnwritten,\n isWholeNumber,\n} from './issues.js';\n\n/**\n * Draft support for `written-response`.\n *\n * A new draft has no prompt, no rubric, `minWords: 0` and `maxWords: 0`. The\n * SDK picks no word limits and no rubric criteria: those are teaching decisions.\n *\n * - `minWords: 0` is a real setting — no lower limit, which `<WrittenResponse>`\n * renders as \"up to N words\" — so it is never reported.\n * - `maxWords: 0` is not. The schema requires at least 1, and 0 is what a\n * cleared number field produces, so it reads as \"not set yet\" (`incomplete`)\n * rather than as a wrong value.\n *\n * Stricter than the schema in four places:\n *\n * - A blank `prompt` is `incomplete` even when `promptHtml` is set. The plain\n * prompt is what `<WrittenResponse>` renders when no sanitiser is supplied, so\n * a rich-text-only prompt shows the learner nothing there.\n * - A rubric criterion whose name is only whitespace is `incomplete`.\n * - A rubric whose weights are all 0 is `incomplete`: `gradeFromRubric` cannot\n * compute a weighted total from them.\n * - A rubric whose weights add up to more than a number can hold is `invalid`,\n * for the same reason.\n */\nexport const writtenResponseAuthoring: ActivityTypeAuthoring<WrittenResponseData> = {\n createDraft: ({ newId }) => ({\n schemaVersion: '1.0',\n type: 'written-response',\n id: newId(),\n title: '',\n prompt: '',\n minWords: 0,\n maxWords: 0,\n }),\n checkDraft: checkWrittenResponseDraft,\n};\n\nfunction checkWrittenResponseDraft(draft: DraftFields): DraftIssue[] {\n const issues = checkIdentity(draft, 'written-response');\n if (isUnwritten(draft.prompt)) {\n issues.push(\n issue(\n 'wr_prompt_required',\n ['prompt'],\n isUnwritten(draft.promptHtml)\n ? 'Write the prompt.'\n : 'Write the prompt as plain text too. The rich-text prompt is shown only where a sanitiser is supplied; the plain text is shown everywhere else.',\n ),\n );\n }\n\n const min = draft.minWords;\n const max = draft.maxWords;\n if (isUnset(min)) {\n issues.push(\n issue(\n 'wr_min_words_required',\n ['minWords'],\n 'Set the minimum number of words. Use 0 for no minimum.',\n ),\n );\n } else if (!(isWholeNumber(min) && min >= 0)) {\n issues.push(\n issue(\n 'wr_min_words_invalid',\n ['minWords'],\n isTooLarge(min)\n ? 'The minimum word count is too large.'\n : 'The minimum word count must be a whole number, 0 or more.',\n ),\n );\n }\n if (isUnset(max) || max === 0) {\n issues.push(issue('wr_max_words_required', ['maxWords'], 'Set the maximum number of words.'));\n } else if (!(isWholeNumber(max) && max >= 1)) {\n issues.push(\n issue(\n 'wr_max_words_invalid',\n ['maxWords'],\n isTooLarge(max)\n ? 'The maximum word count is too large.'\n : 'The maximum word count must be a whole number, 1 or more.',\n ),\n );\n } else if (typeof min === 'number' && max < min) {\n // Reported at `maxWords`, where the schema reports the same rule.\n issues.push(\n issue(\n 'wr_word_bounds_order',\n ['maxWords'],\n 'The maximum word count cannot be lower than the minimum.',\n ),\n );\n }\n\n const rubric = draft.rubric;\n if (isRecord(rubric)) {\n const criteria = isUnset(rubric.criteria) ? [] : rubric.criteria;\n if (Array.isArray(criteria)) {\n issues.push(...checkCriteria(criteria));\n }\n }\n\n issues.push(...checkSharedOptional(draft));\n return issues;\n}\n\nfunction checkCriteria(criteria: readonly unknown[]): DraftIssue[] {\n const issues: DraftIssue[] = [];\n if (criteria.length === 0) {\n issues.push(\n issue(\n 'wr_rubric_criteria_required',\n ['rubric', 'criteria'],\n 'Add at least one rubric criterion, or remove the rubric.',\n ),\n );\n return issues;\n }\n let totalWeight = 0;\n let everyWeightUsable = true;\n for (const [index, criterion] of criteria.entries()) {\n if (!isRecord(criterion)) {\n everyWeightUsable = false;\n continue;\n }\n const ordinal = index + 1;\n const path = ['rubric', 'criteria', index];\n if (isUnwritten(criterion.name)) {\n issues.push(\n issue('wr_criterion_name_required', [...path, 'name'], `Name rubric criterion ${ordinal}.`),\n );\n }\n const weight = criterion.weight;\n if (isUnset(weight)) {\n everyWeightUsable = false;\n issues.push(\n issue(\n 'wr_criterion_weight_required',\n [...path, 'weight'],\n `Give rubric criterion ${ordinal} a weight.`,\n ),\n );\n } else if (!(typeof weight === 'number' && Number.isFinite(weight) && weight >= 0)) {\n everyWeightUsable = false;\n issues.push(\n issue(\n 'wr_criterion_weight_invalid',\n [...path, 'weight'],\n 'A rubric weight must be a number, 0 or more.',\n ),\n );\n } else {\n totalWeight += weight;\n }\n }\n if (everyWeightUsable && totalWeight === 0) {\n issues.push(\n issue(\n 'wr_rubric_weights_zero',\n ['rubric', 'criteria'],\n 'Give at least one rubric criterion a weight above 0. With every weight at 0, no weighted total can be computed.',\n ),\n );\n } else if (totalWeight === Number.POSITIVE_INFINITY) {\n // Each weight is finite, and their sum still overflows: `gradeFromRubric`\n // would divide by Infinity and produce no grade. No weight is negative, so\n // the sum stays overflowed whatever a weight not yet set turns out to be.\n issues.push(\n issue(\n 'wr_rubric_weights_too_large',\n ['rubric', 'criteria'],\n 'The rubric weights add up to more than can be calculated with. Use smaller weights.',\n ),\n );\n }\n return issues;\n}\n","/**\n * All-or-nothing scoring: full credit only when every item is correct.\n *\n * @param correctItems - per-item correctness flags for the relevant items\n * @returns `1` if every item is correct (or the list is empty), otherwise `0`\n */\nexport function allOrNothingStrategy(correctItems: boolean[]): number {\n return correctItems.every((isCorrect) => isCorrect) ? 1 : 0;\n}\n","/**\n * Partial-credit scoring for Multiple Choice — balanced/symmetric scheme.\n *\n * Reward and penalty are each normalised by their own pool: the fraction of\n * correct options found, minus the fraction of distractors wrongly chosen.\n * This is fairer than normalising the penalty by the correct-option count —\n * under that older form a single wrong pick could zero an otherwise-correct\n * response when there was only one correct answer.\n *\n * Result is always in [0, 1]: `reward ∈ [0,1]` and `penalty ∈ [0,1]`, so\n * `reward - penalty ∈ [-1,1]` and `max(0, …)` floors it at 0. Selecting every\n * option yields `1 - 1 = 0`; selecting exactly the correct set yields `1`.\n *\n * @param correctSelected - number of selected options that are correct\n * @param incorrectSelected - number of selected options that are incorrect\n * @param totalCorrect - total correct options; the `MultipleChoiceDataSchema`\n * \"≥1 correct\" guard guarantees this is ≥ 1, so the reward term cannot\n * divide by zero for schema-validated data\n * @param totalIncorrect - total incorrect options (distractors); when `0`\n * (every option is correct) the penalty term is defined as `0`\n */\nexport function partialStrategy(\n correctSelected: number,\n incorrectSelected: number,\n totalCorrect: number,\n totalIncorrect: number,\n): number {\n const reward = correctSelected / totalCorrect;\n const penalty = totalIncorrect === 0 ? 0 : incorrectSelected / totalIncorrect;\n return Math.max(0, reward - penalty);\n}\n\n/**\n * Partial-credit scoring for Fill-in-the-Blanks: fraction of blanks answered\n * correctly. Each blank is independently right or wrong, so a plain proportion\n * is already fair — no penalty term applies.\n *\n * @param correctBlanks - number of blanks answered correctly\n * @param totalBlanks - total number of blanks; the `FillInTheBlanksDataSchema`\n * `blanks.min(1)` constraint guarantees this is ≥ 1, so division by zero\n * cannot occur for schema-validated data\n */\nexport function partialBlankStrategy(correctBlanks: number, totalBlanks: number): number {\n return correctBlanks / totalBlanks;\n}\n","import type {\n BlankConfig,\n FillInTheBlanksData,\n FillInTheBlanksLearnerResponse,\n ScoringDetail,\n} from '../../types/activity.js';\nimport { allOrNothingStrategy } from '../strategies/all-or-nothing.js';\nimport { partialBlankStrategy } from '../strategies/partial.js';\nimport { matchText, type TextMatchPolicy } from '../text-match.js';\nimport type { PartialScoringResult } from './multiple-choice.js';\n\n/**\n * Resolves the effective match policy for a blank. The legacy\n * `caseSensitive` / `trimWhitespace` flags map onto the baseline policy\n * fields; an explicit `blank.match` policy takes precedence field-by-field.\n * With neither present, the result is the v1 semantics exactly.\n */\nfunction policyFor(blank: BlankConfig): TextMatchPolicy {\n return {\n ...(blank.caseSensitive !== undefined ? { caseSensitive: blank.caseSensitive } : {}),\n ...(blank.trimWhitespace !== undefined ? { trim: blank.trimWhitespace } : {}),\n ...blank.match,\n };\n}\n\n/**\n * Scores a Fill-in-the-Blanks response. Each blank is evaluated independently\n * via {@link matchText} under the blank's resolved policy; a missing answer\n * key is treated as empty input and scored incorrect.\n */\nexport function scoreFillInTheBlanks(\n data: FillInTheBlanksData,\n response: FillInTheBlanksLearnerResponse,\n): PartialScoringResult {\n const details: ScoringDetail[] = [];\n const perBlankCorrect: boolean[] = [];\n\n for (const blank of data.blanks) {\n const rawInput = response.answers[blank.id];\n const input = typeof rawInput === 'string' ? rawInput : '';\n const matched = matchText(input, blank.acceptedAnswers, policyFor(blank)).matched;\n\n perBlankCorrect.push(matched);\n details.push({\n itemId: blank.id,\n correct: matched,\n outcome: matched ? 'correct' : 'incorrect',\n learnerResponse: [input],\n correctResponse: [...blank.acceptedAnswers],\n weight: 1,\n });\n }\n\n const correctBlanks = perBlankCorrect.filter(Boolean).length;\n const scoreValue =\n data.scoringStrategy === 'all-or-nothing'\n ? allOrNothingStrategy(perBlankCorrect)\n : partialBlankStrategy(correctBlanks, data.blanks.length);\n\n return { score: scoreValue, maxScore: 1, feedback: null, details };\n}\n","import type {\n GapSelectChoice,\n GapSelectData,\n GapSelectGap,\n GapSelectLearnerResponse,\n ScoringDetail,\n} from '../../types/activity.js';\nimport { allOrNothingStrategy } from '../strategies/all-or-nothing.js';\nimport { partialBlankStrategy } from '../strategies/partial.js';\nimport type { PartialScoringResult } from './multiple-choice.js';\n\n/**\n * The choices a gap offers: its own list, or the bank it names.\n *\n * Returns an empty list when neither resolves. The schema rejects that, but the\n * scorer is reachable with data an older build stored, and an empty list scores\n * the gap incorrect rather than throwing — a paper that cannot be marked is\n * worse at an appeal than one gap nobody can pass.\n */\nfunction choicesFor(data: GapSelectData, gap: GapSelectGap): readonly GapSelectChoice[] {\n if (gap.choices !== undefined) {\n return gap.choices;\n }\n return data.banks?.find((bank) => bank.id === gap.bankId)?.choices ?? [];\n}\n\n/**\n * Scores a Gap Select response.\n *\n * Each gap is an identity comparison between the selected choice id and\n * `correctChoiceId` — no text matching of any kind, because a learner choosing\n * from a list cannot mistype, and tolerances that cannot apply must not be\n * configurable.\n *\n * A gap with no selection, or one whose selection is not a choice it offers\n * (a tampered payload, or content edited after the attempt), is scored\n * incorrect and reported as `incorrect-omission`, the outcome the SDK already\n * uses for \"the learner never answered this\". That keeps an unanswered gap\n * distinguishable from a wrong one everywhere downstream, which is the whole\n * reason the selector's first entry is blank.\n */\nexport function scoreGapSelect(\n data: GapSelectData,\n response: GapSelectLearnerResponse,\n): PartialScoringResult {\n const details: ScoringDetail[] = [];\n const perGapCorrect: boolean[] = [];\n\n for (const gap of data.gaps) {\n const raw = response.selections[gap.id];\n const selected = typeof raw === 'string' ? raw : '';\n const choices = choicesFor(data, gap);\n const offered = choices.some((choice) => choice.id === selected);\n const answered = selected !== '' && offered;\n const correct = answered && selected === gap.correctChoiceId;\n\n perGapCorrect.push(correct);\n details.push({\n itemId: gap.id,\n correct,\n outcome: correct ? 'correct' : answered ? 'incorrect' : 'incorrect-omission',\n learnerResponse: [selected],\n correctResponse: [gap.correctChoiceId],\n weight: 1,\n });\n }\n\n const correctGaps = perGapCorrect.filter(Boolean).length;\n const scoreValue =\n data.scoringStrategy === 'all-or-nothing'\n ? allOrNothingStrategy(perGapCorrect)\n : partialBlankStrategy(correctGaps, data.gaps.length);\n\n return { score: scoreValue, maxScore: 1, feedback: null, details };\n}\n","import type { PartialScoringResult } from '../../registry/registry.js';\nimport type {\n MultipleChoiceData,\n MultipleChoiceLearnerResponse,\n ScoringDetail,\n} from '../../types/activity.js';\nimport { partialStrategy } from '../strategies/partial.js';\n\nexport type { PartialScoringResult } from '../../registry/registry.js';\n\n/**\n * Scores a Multiple Choice response. Pure; trusts its typed inputs (validation\n * is the component boundary's job). Options are looked up by id; an unknown\n * selected id is treated as an incorrect selection.\n */\nexport function scoreMultipleChoice(\n data: MultipleChoiceData,\n response: MultipleChoiceLearnerResponse,\n): PartialScoringResult {\n const optionById = new Map(data.options.map((option) => [option.id, option]));\n const selected = new Set(response.selectedOptionIds);\n\n const totalCorrect = data.options.filter((option) => option.isCorrect).length;\n const totalIncorrect = data.options.length - totalCorrect;\n\n let scoreValue: number;\n\n if (data.scoringStrategy === 'all-or-nothing') {\n if (data.mode === 'single') {\n scoreValue =\n response.selectedOptionIds.length === 1 &&\n optionById.get(response.selectedOptionIds[0])?.isCorrect === true\n ? 1\n : 0;\n } else {\n const correctIds = data.options.filter((o) => o.isCorrect).map((o) => o.id);\n const allCorrectSelected = correctIds.every((id) => selected.has(id));\n scoreValue = selected.size === correctIds.length && allCorrectSelected ? 1 : 0;\n }\n } else {\n let correctSelected = 0;\n let incorrectSelected = 0;\n for (const id of selected) {\n const option = optionById.get(id);\n if (option?.isCorrect) {\n correctSelected += 1;\n } else {\n incorrectSelected += 1;\n }\n }\n scoreValue = partialStrategy(correctSelected, incorrectSelected, totalCorrect, totalIncorrect);\n }\n\n const details: ScoringDetail[] = data.options.map((option) => {\n const wasSelected = selected.has(option.id);\n const outcome = wasSelected\n ? option.isCorrect\n ? 'correct'\n : 'incorrect'\n : option.isCorrect\n ? 'incorrect-omission'\n : 'correct-omission';\n return {\n itemId: option.id,\n correct: wasSelected === option.isCorrect,\n outcome,\n learnerResponse: [wasSelected ? 'selected' : 'not-selected'],\n correctResponse: [option.isCorrect ? 'selected' : 'not-selected'],\n weight: 1,\n };\n });\n\n return { score: scoreValue, maxScore: 1, feedback: null, details };\n}\n","import type { z } from 'zod/v4';\nimport { fillInTheBlanksAuthoring } from '../authoring/fill-in-the-blanks.js';\nimport { gapSelectAuthoring } from '../authoring/gap-select.js';\nimport { multipleChoiceAuthoring } from '../authoring/multiple-choice.js';\nimport { writtenResponseAuthoring } from '../authoring/written-response.js';\nimport { countWords } from '../count-words.js';\nimport { FillInTheBlanksDataSchema } from '../schemas/fill-in-the-blanks.js';\nimport { GapSelectDataSchema } from '../schemas/gap-select.js';\nimport { MultipleChoiceDataSchema } from '../schemas/multiple-choice.js';\nimport {\n RedactedFillInTheBlanksDataSchema,\n RedactedGapSelectDataSchema,\n RedactedMultipleChoiceDataSchema,\n RedactedWrittenResponseDataSchema,\n} from '../schemas/redacted.js';\nimport { WrittenResponseDataSchema } from '../schemas/written-response.js';\nimport { scoreFillInTheBlanks } from '../scoring/activity-scorers/fill-in-the-blanks.js';\nimport { scoreGapSelect } from '../scoring/activity-scorers/gap-select.js';\nimport { scoreMultipleChoice } from '../scoring/activity-scorers/multiple-choice.js';\nimport type {\n FillInTheBlanksData,\n FillInTheBlanksLearnerResponse,\n GapSelectData,\n GapSelectLearnerResponse,\n MultipleChoiceData,\n MultipleChoiceLearnerResponse,\n WrittenResponseData,\n WrittenResponseLearnerResponse,\n} from '../types/activity.js';\nimport { defineActivityType, type FieldPolicy, registerActivityType } from './registry.js';\n\n/**\n * Field-sensitivity policies for the built-in types (R7). Fail-closed:\n * anything not listed here is dropped by `redact()`. `scoringStrategy` is\n * answer-key everywhere — MC `partial` penalises wrong selections while\n * `all-or-nothing` does not, so the strategy reveals whether guessing is\n * free. Authored feedback is answer-key (it may quote or hint the answer).\n * Rubrics are PUBLIC: a rubric tells the learner what they are assessed on,\n * and a deployment that wants it hidden tightens it per call — see the\n * `rubric` entry in the written-response policy below.\n */\n/**\n * `media` classified field by field, not as one opaque leaf.\n *\n * A scalar `Sensitivity` classifies the WHOLE field, so `media: 'public'`\n * returned the author's object by reference without recursing — and an\n * unclassified key nested under it (`media.secretAnswerHint`) survived\n * `redact()` AND passed `assertRedacted()`. The fail-closed contract the SDK\n * documents (\"a field the policy does not classify is removed\") stopped at the\n * media boundary. It no longer does.\n *\n * Everything here is public by necessity: the client is the thing that renders\n * and enforces the policy, and \"1 play remaining\" is text the learner has to\n * read. None of it is an answer key — a listening paper's answer key is\n * `stimulus.transcript`, which stays `author-only`.\n */\nexport const MEDIA_FIELD_POLICY: FieldPolicy = {\n type: 'public',\n url: 'public',\n alt: 'public',\n captionsUrl: 'public',\n playback: {\n controls: 'public',\n maxPlays: 'public',\n seek: 'public',\n rate: 'public',\n nativeControlHints: 'public',\n },\n};\n\nconst SHARED_PUBLIC_FIELDS: FieldPolicy = {\n schemaVersion: 'public',\n type: 'public',\n id: 'public',\n // Assembly metadata, not content: it names the slot this item occupies in a\n // paper. It has to survive redaction, or the exam client derives positional\n // slot ids while the server's stored plan holds keyed ones, and the\n // responses cannot be matched back to the attempt.\n slotKey: 'public',\n title: 'public',\n media: MEDIA_FIELD_POLICY,\n passThreshold: 'public',\n locale: 'public',\n learningObjectives: 'public',\n difficultyLevel: 'public',\n};\n\n/**\n * Activity-level feedback, classified field by field.\n *\n * The third object leaf to need this, after `media` and `rubric`, and the same\n * defect each time: a scalar classification assigns the author's object by\n * reference without recursing, so under `reveal: 'after-submit'` an\n * unclassified key nested inside it — a grader note, an internal cost — was\n * forwarded to the learner along with the answer key, and `redact()` handed\n * back an alias of the caller's object. `FeedbackSchema` is loose, so anything\n * can be parked there.\n *\n * Both fields stay `answer-key`: activity feedback IS the answer key's\n * commentary, so `reveal: 'none'` still drops the whole object.\n */\nexport const FEEDBACK_FIELD_POLICY: FieldPolicy = {\n correct: 'answer-key',\n incorrect: 'answer-key',\n};\n\n/**\n * A blank's matching tolerances, classified key by key.\n *\n * Same reasoning as {@link FEEDBACK_FIELD_POLICY}: `TextMatchPolicy` is a\n * documented set of knobs, and a consumer's private tuning parked beside them\n * is not part of what an after-submit reveal promises to show.\n */\nexport const TEXT_MATCH_FIELD_POLICY: FieldPolicy = {\n caseSensitive: 'answer-key',\n trim: 'answer-key',\n normalize: 'answer-key',\n foldDiacritics: 'answer-key',\n collapseInnerWhitespace: 'answer-key',\n ignorePunctuation: 'answer-key',\n levenshtein: 'answer-key',\n locale: 'answer-key',\n};\n\nconst MULTIPLE_CHOICE_FIELD_POLICY: FieldPolicy = {\n ...SHARED_PUBLIC_FIELDS,\n question: 'public',\n questionHtml: 'public',\n mode: 'public',\n shuffle: 'public',\n scoringStrategy: 'answer-key',\n feedback: FEEDBACK_FIELD_POLICY,\n options: {\n id: 'public',\n text: 'public',\n isCorrect: 'answer-key',\n feedback: 'answer-key',\n // Public, and classified key by key rather than as one leaf — the lesson\n // `media`, `rubric` and `feedback` each taught: a scalar classification\n // assigns the author's object by reference without recursing, so an\n // unclassified key parked inside it survives redact() AND assertRedacted().\n // An option's picture or recording IS the thing the learner picks, so\n // stripping it would ship a row of blank options.\n media: {\n type: 'public',\n url: 'public',\n alt: 'public',\n captionsUrl: 'public',\n },\n },\n};\n\n/**\n * Gap Select, where the sensitivity of \"the candidate answers\" INVERTS.\n *\n * Fill-in-the-Blanks classifies `acceptedAnswers` as answer-key, because the\n * list of things that would be accepted IS the key. Here the same-shaped data\n * is the opposite: a learner who cannot see `choices` cannot answer at all, so\n * every choice — and every shared bank — is `public`, and `correctChoiceId` is\n * the single field withheld. Copying the Fill-in-the-Blanks policy across would\n * have produced an exam nobody could sit, which is why the two types do not\n * share one.\n */\nconst GAP_SELECT_FIELD_POLICY: FieldPolicy = {\n ...SHARED_PUBLIC_FIELDS,\n passage: 'public',\n passageHtml: 'public',\n presentation: 'public',\n shuffleChoices: 'public',\n scoringStrategy: 'answer-key',\n feedback: FEEDBACK_FIELD_POLICY,\n banks: {\n id: 'public',\n choices: { id: 'public', text: 'public' },\n },\n gaps: {\n id: 'public',\n bankId: 'public',\n choices: { id: 'public', text: 'public' },\n correctChoiceId: 'answer-key',\n feedback: 'answer-key',\n },\n};\n\nconst FILL_IN_THE_BLANKS_FIELD_POLICY: FieldPolicy = {\n ...SHARED_PUBLIC_FIELDS,\n passage: 'public',\n passageHtml: 'public',\n scoringStrategy: 'answer-key',\n feedback: FEEDBACK_FIELD_POLICY,\n blanks: {\n id: 'public',\n hint: 'public',\n acceptedAnswers: 'answer-key',\n caseSensitive: 'answer-key',\n trimWhitespace: 'answer-key',\n match: TEXT_MATCH_FIELD_POLICY,\n feedback: 'answer-key',\n },\n};\n\n/**\n * The rubric classified field by field, not as one opaque leaf.\n *\n * A scalar `Sensitivity` classifies the WHOLE field, so `rubric: 'public'`\n * returned the author's object by reference without recursing — and an\n * unclassified key nested under it survived `redact()` AND passed\n * `assertRedacted()`. A grader's `modelAnswer` or `aiModel` stashed on the\n * rubric went straight to the exam client. Same defect as the one `media` had,\n * one field along: \"the rubric is public\" has to mean its documented fields\n * are public, not that anything anyone parks under it is.\n */\nexport const RUBRIC_FIELD_POLICY: FieldPolicy = {\n label: 'public',\n // Applies to every element of the array.\n criteria: {\n name: 'public',\n description: 'public',\n weight: 'public',\n },\n};\n\nconst WRITTEN_RESPONSE_FIELD_POLICY: FieldPolicy = {\n ...SHARED_PUBLIC_FIELDS,\n prompt: 'public',\n promptHtml: 'public',\n minWords: 'public',\n maxWords: 'public',\n languageTarget: 'public',\n feedback: FEEDBACK_FIELD_POLICY,\n // A rubric is a LEARNER affordance, not a grader secret: it tells the\n // learner what they are being graded on, which is pedagogically the point\n // of publishing one. (Classifying it author-only broke real deployments\n // that render a rubric panel during the attempt.) A deployment that wants\n // it hidden can tighten this per call via `redact(data, { policy })`.\n // Classified field by field so that stays true of the rubric's DOCUMENTED\n // fields only — see {@link RUBRIC_FIELD_POLICY}.\n rubric: RUBRIC_FIELD_POLICY,\n};\n\n/** Built-in Multiple Choice descriptor. */\nexport const multipleChoiceType = defineActivityType<\n MultipleChoiceData,\n MultipleChoiceLearnerResponse\n>({\n type: 'multiple-choice',\n // zod4 optional outputs are `T | undefined`; the hand-written wire types use\n // exact optionals. Structurally identical at runtime — cast is type-level only.\n schema: MultipleChoiceDataSchema as unknown as z.ZodType<MultipleChoiceData>,\n scoring: { kind: 'sync', score: scoreMultipleChoice },\n isAnswered: (response) => (response?.selectedOptionIds.length ?? 0) > 0,\n fieldPolicy: MULTIPLE_CHOICE_FIELD_POLICY,\n redactedSchema: RedactedMultipleChoiceDataSchema,\n interop: {\n xapiActivityTypeIri: 'http://adlnet.gov/expapi/activities/cmi.interaction',\n xapiInteractionType: 'choice',\n correctResponsesPattern: (data) => [\n data.options\n .filter((option) => option.isCorrect)\n .map((option) => option.id)\n .join('[,]'),\n ],\n },\n interactions: ['option-selected', 'option-deselected', 'submitted'],\n authoring: multipleChoiceAuthoring,\n});\n\n/** Built-in Fill-in-the-Blanks descriptor. */\nexport const fillInTheBlanksType = defineActivityType<\n FillInTheBlanksData,\n FillInTheBlanksLearnerResponse\n>({\n type: 'fill-in-the-blanks',\n schema: FillInTheBlanksDataSchema as unknown as z.ZodType<FillInTheBlanksData>,\n scoring: { kind: 'sync', score: scoreFillInTheBlanks },\n isAnswered: (response) =>\n Object.values(response?.answers ?? {}).some((answer) => answer.trim().length > 0),\n fieldPolicy: FILL_IN_THE_BLANKS_FIELD_POLICY,\n redactedSchema: RedactedFillInTheBlanksDataSchema,\n interop: {\n xapiActivityTypeIri: 'http://adlnet.gov/expapi/activities/cmi.interaction',\n xapiInteractionType: 'fill-in',\n // xAPI fill-in pattern: blank answers joined with \"[,]\". Only the first\n // accepted answer per blank is emitted (full alternates would explode\n // combinatorially); the complete key lives in the activity data.\n correctResponsesPattern: (data) => [\n data.blanks.map((blank) => blank.acceptedAnswers[0] ?? '').join('[,]'),\n ],\n },\n interactions: ['blank-filled', 'hint-requested', 'submitted'],\n authoring: fillInTheBlanksAuthoring,\n});\n\n/**\n * Built-in Written Response descriptor. Grading is DEFERRED: submissions are\n * graded asynchronously (AI or human) by the consumer; the synchronous\n * outcome reports only word-count facts. `wordCount` is recomputed from the\n * submitted text with the canonical `countWords()` — the client-supplied\n * count is informational, never trusted.\n */\nexport const writtenResponseType = defineActivityType<\n WrittenResponseData,\n WrittenResponseLearnerResponse\n>({\n type: 'written-response',\n schema: WrittenResponseDataSchema as unknown as z.ZodType<WrittenResponseData>,\n scoring: {\n kind: 'deferred',\n reason: 'requires_async_grading',\n partial: (data, response) => {\n const wordCount = countWords(response?.text ?? '');\n return {\n withinWordBounds:\n response !== undefined && wordCount >= data.minWords && wordCount <= data.maxWords,\n wordCount,\n };\n },\n },\n isAnswered: (response) => (response?.text.trim().length ?? 0) > 0,\n fieldPolicy: WRITTEN_RESPONSE_FIELD_POLICY,\n redactedSchema: RedactedWrittenResponseDataSchema,\n interop: {\n xapiActivityTypeIri: 'http://adlnet.gov/expapi/activities/cmi.interaction',\n xapiInteractionType: 'long-fill-in',\n correctResponsesPattern: () => [],\n },\n interactions: ['text-changed', 'submitted'],\n authoring: writtenResponseAuthoring,\n});\n\n/** Built-in Gap Select descriptor. */\nexport const gapSelectType = defineActivityType<GapSelectData, GapSelectLearnerResponse>({\n type: 'gap-select',\n schema: GapSelectDataSchema as unknown as z.ZodType<GapSelectData>,\n scoring: { kind: 'sync', score: scoreGapSelect },\n isAnswered: (response) =>\n Object.values(response?.selections ?? {}).some((choiceId) => choiceId !== ''),\n fieldPolicy: GAP_SELECT_FIELD_POLICY,\n redactedSchema: RedactedGapSelectDataSchema,\n interop: {\n xapiActivityTypeIri: 'http://adlnet.gov/expapi/activities/cmi.interaction',\n // `matching`, not `choice` or `fill-in`. The learner pairs a set of sources\n // (the gaps) with a set of targets (the choices), which is exactly what the\n // xAPI matching interaction describes, and it is the only built-in type\n // whose pattern can name WHICH gap took which answer. `fill-in` — what the\n // Fill-in-the-Blanks descriptor uses — would flatten the gaps into an\n // ordered list of strings and lose that.\n xapiInteractionType: 'matching',\n correctResponsesPattern: (data) => [\n data.gaps.map((gap) => `${gap.id}[.]${gap.correctChoiceId}`).join('[,]'),\n ],\n },\n interactions: ['gap-selected', 'submitted'],\n authoring: gapSelectAuthoring,\n});\n\nregisterActivityType(multipleChoiceType);\nregisterActivityType(fillInTheBlanksType);\nregisterActivityType(writtenResponseType);\nregisterActivityType(gapSelectType);\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/scoring/rounding.ts","../src/grading.ts","../src/scoring/compose.ts","../src/scoring/index.ts"],"sourcesContent":["/**\n * Rounding for assessment scores.\n *\n * Rounding is **two different operations** that must not share one policy, and\n * conflating them silently inverts one of them:\n *\n * - **Grade rounding** decides the number a learner is shown and recorded\n * against. It rounds to a fixed number of decimal places, conventionally\n * half-up, so a learner shown \"70%\" is not recorded as a fail at 69.6.\n * - **Band / level classification** decides which level someone is placed in.\n * It deliberately **floors**: placing a learner above their real level is\n * the more harmful error, so a band boundary must not be reached by\n * rounding up.\n *\n * There is therefore no default `RoundingPolicy` anywhere in this SDK, and\n * `dp` has no default either — an integrator's deliberate choice must never be\n * supplied by us.\n */\n\n/** How a value is rounded to `dp` decimal places. */\nexport type RoundingMode = 'half-up' | 'half-even' | 'floor' | 'ceil';\n\n/** A rounding policy. Both fields are required — the SDK never guesses either. */\nexport interface RoundingPolicy {\n mode: RoundingMode;\n /** Decimal places. Load-bearing: `dp: 2` is what stops 69.6 becoming a fail at 70. */\n dp: number;\n}\n\n/**\n * Float-noise guard. Binary floating point makes `1.005 * 100` come out as\n * `100.49999999999999` and `0.29 * 100` come out as `28.999999999999996`;\n * without an allowance both would move a learner's grade a step. It is applied\n * in the direction each mode needs — see {@link roundGrade}.\n */\nconst EPSILON = 1e-9;\n\n/**\n * Tolerance for deciding \"is this exactly halfway?\" in half-even mode.\n * Deliberately much larger than {@link EPSILON}: if the tie test shared the\n * nudge's tolerance, the nudge would decide its own outcome. Safe for grades,\n * which live on a [0,1] scale where representation error is far below this.\n */\nconst TIE_TOLERANCE = 1e-6;\n\nfunction scaled(value: number, dp: number): number {\n return value * 10 ** dp;\n}\n\n/**\n * Normalises `-0` to `0` without touching anything else. `-0` JSON-serialises\n * as `0` but fails `Object.is(-0, 0)`, which surprises a strict-equality\n * gradebook comparison. Deliberately NOT `|| 0`: that also swallowed `NaN`\n * (reachable with an absurd `dp`, where `10 ** dp` overflows to Infinity) and\n * turned a visible failure into a plausible-looking grade of zero.\n */\nfunction noNegZero(value: number): number {\n return Object.is(value, -0) ? 0 : value;\n}\n\n/**\n * Rounds `value` to `policy.dp` places under `policy.mode`.\n *\n * Idempotent for every mode: rounding an already-rounded value returns it\n * unchanged. That matters because a composed score is rounded once and then\n * compared through {@link gte}, which rounds again.\n */\nexport function roundGrade(value: number, policy: RoundingPolicy): number {\n if (!Number.isFinite(value)) {\n return value;\n }\n const factor = 10 ** policy.dp;\n const raw = scaled(value, policy.dp);\n\n // The nudge must oppose the mode, never point uniformly away from zero.\n // `Math.ceil` on a value that is ALREADY exact at `dp` jumps a whole\n // quantum if you add epsilon first: 0.7 became 0.71, a true 0 became 0.01,\n // and the operation stopped being idempotent (0.7 -> 0.71 -> 0.72). So\n // `ceil` nudges down and `floor` nudges up — just far enough to absorb\n // representation error (0.29 * 100 is 28.999999999999996, which must still\n // floor to 29) without ever moving a value that was already exact.\n // Half modes keep the sign-aware nudge, which is what makes a .5 tie land\n // away from zero.\n switch (policy.mode) {\n case 'floor':\n return noNegZero(Math.floor(raw + EPSILON) / factor);\n case 'ceil':\n return noNegZero(Math.ceil(raw - EPSILON) / factor);\n case 'half-even': {\n // Detect the tie on the RAW value, and with a tolerance decoupled from\n // (and far larger than) the nudge epsilon. Testing the NUDGED value\n // against EPSILON let the nudge itself decide whether a tie was seen:\n // 0.375 was detected but 0.125 was not, so half-even silently degraded\n // to half-up for some exact ties — and only ever in the direction that\n // rounds a learner UP, the opposite of what this mode is chosen for.\n const floored = Math.floor(raw);\n if (Math.abs(raw - floored - 0.5) > TIE_TOLERANCE) {\n return noNegZero(halfAway(raw) / factor);\n }\n // Exactly halfway: pick the even neighbour (banker's rounding).\n const even = floored % 2 === 0 ? floored : floored + 1;\n return noNegZero(even / factor);\n }\n default:\n return noNegZero(halfAway(raw) / factor);\n }\n}\n\n/**\n * Half-up on a scaled value, with the sign-aware float-noise nudge.\n * `Math.round` breaks .5 ties toward +Infinity, so negatives are mirrored to\n * keep the tie breaking away from zero in both directions.\n */\nfunction halfAway(raw: number): number {\n const nudged = raw >= 0 ? raw + EPSILON : raw - EPSILON;\n return raw >= 0 ? Math.round(nudged) : -Math.round(-nudged);\n}\n\n/**\n * Threshold comparison that rounds **both sides** before comparing.\n *\n * Comparing a raw float against a rounded threshold is how a learner ends up\n * shown one number and recorded against another. Rounding both sides — and\n * allowing an epsilon — makes \"what the learner sees\" and \"what the gradebook\n * decides\" the same comparison.\n */\nexport function gte(value: number, threshold: number, policy: RoundingPolicy): boolean {\n return roundGrade(value, policy) >= roundGrade(threshold, policy) - EPSILON;\n}\n\n/** A named band with an inclusive lower bound, e.g. `{ name: 'B1', min: 0.6 }`. */\nexport interface Band {\n name: string;\n /** Inclusive lower bound on the same scale as the value being classified. */\n min: number;\n}\n\n/**\n * Classifies a value into a band by **flooring**: the highest band whose `min`\n * the value actually reaches. Deliberately does NOT round up to a boundary —\n * over-placement is the more harmful error, so a learner just below a boundary\n * stays below it.\n *\n * Returns `null` when the value reaches no band's minimum.\n */\nexport function classifyBand(value: number, bands: readonly Band[]): Band | null {\n let best: Band | null = null;\n for (const band of bands) {\n if (value >= band.min - EPSILON && (best === null || band.min > best.min)) {\n best = band;\n }\n }\n return best;\n}\n","import { computePassThreshold } from './scoring/index.js';\nimport type { ActivityData, ItemOutcome } from './types/activity.js';\nimport type { CriterionScore, GradeRecord } from './types/grading.js';\n\nexport type {\n CriterionScore,\n GradeRecord,\n Grader,\n GraderKind,\n GraderUsage,\n GradingState,\n InlineCorrection,\n} from './types/grading.js';\n\n/** Options for {@link gradeFromRubric}. */\nexport interface GradeFromRubricOptions {\n /**\n * Scaled score [0,1] required to pass. Defaults to the activity's\n * `passThreshold`, or {@link DEFAULT_PASS_THRESHOLD} when absent.\n */\n passThreshold?: number;\n /** Narrative feedback for the learner, carried onto the record verbatim. */\n feedback?: string | null;\n}\n\n/**\n * Computes the weighted total from per-criterion scores — the arithmetic that\n * must NOT be delegated to a language model.\n *\n * A grader is asked for judgement (how good is this criterion?), not for\n * mental arithmetic. Asking a model to also produce the weighted total makes\n * the final grade unverifiable and unreproducible: two runs can return\n * different totals for identical criterion scores. This function makes the\n * total a pure function of the judgements, so a grade can be recomputed and\n * audited years later.\n *\n * Weights are normalised by their sum, so they need not add to 1. Criteria\n * marked `notApplicable`, and those carrying no numeric `score` (a purely\n * banded judgement), are excluded from both numerator and denominator. When\n * nothing scoreable remains the result is `unscorable`, never a zero.\n *\n * Scores need not be in [0,1]: set `maxScore` on a criterion to declare what\n * its score is out of, and each is normalised before weighting. A grader\n * working out of 100 says so and is done:\n *\n * ```ts\n * gradeFromRubric([\n * { name: 'Task achievement', score: 82, maxScore: 100, weight: 2 },\n * { name: 'Range', score: 7, maxScore: 9, weight: 1 },\n * ]);\n * ```\n *\n * The returned `GradeRecord.score` is always scaled [0,1] against\n * `maxScore: 1`, like every other score in the SDK.\n */\nexport function gradeFromRubric(\n criteria: readonly CriterionScore[],\n activityData?: ActivityData,\n options: GradeFromRubricOptions = {},\n): GradeRecord | { unscorable: true; reason: string } {\n // A corrupt score is an ERROR, not an exclusion. Exclusions (`notApplicable`,\n // and band-only criteria with no numeric score) are deliberate authoring\n // decisions; a NaN or Infinity is a broken grader. Silently dropping it\n // would regrade the learner on fewer criteria — with different effective\n // weights — and nobody would know.\n const corrupt = criteria.find(\n (criterion) =>\n criterion.notApplicable !== true &&\n criterion.score !== undefined &&\n !Number.isFinite(criterion.score),\n );\n if (corrupt !== undefined) {\n return {\n unscorable: true,\n reason: `Criterion \"${corrupt.name}\" has a non-finite score (${String(corrupt.score)}). A grade cannot be computed from it.`,\n };\n }\n\n // A `maxScore` that is absent means 1 (the scaled convention). One that is\n // present must be a positive finite number: dividing by 0, a negative, or a\n // NaN would produce a grade nobody can defend.\n const badMax = criteria.find(\n (criterion) =>\n criterion.notApplicable !== true &&\n criterion.maxScore !== undefined &&\n !(Number.isFinite(criterion.maxScore) && criterion.maxScore > 0),\n );\n if (badMax !== undefined) {\n return {\n unscorable: true,\n reason: `Criterion \"${badMax.name}\" declares maxScore ${String(badMax.maxScore)}; it must be a positive, finite number.`,\n };\n }\n\n const scoreable = criteria.filter(\n (criterion) => criterion.notApplicable !== true && typeof criterion.score === 'number',\n );\n\n /** The criterion's score as a ratio in [0,1]. `maxScore` defaults to 1. */\n const ratioOf = (criterion: CriterionScore): number =>\n (criterion.score as number) / (criterion.maxScore ?? 1);\n\n // A grader that returns raw points (4 out of 5) without declaring\n // `maxScore: 5` would otherwise yield `{ score: 4, maxScore: 1 }` and pass\n // every threshold. Reject rather than clamp: silently rescaling someone's\n // grader is worse than telling them it is out of contract. A hair outside\n // the range is float noise and is clamped further down.\n const EPSILON = 1e-9;\n const outOfRange = scoreable.find(\n (criterion) => ratioOf(criterion) < -EPSILON || ratioOf(criterion) > 1 + EPSILON,\n );\n if (outOfRange !== undefined) {\n return {\n unscorable: true,\n reason:\n `Criterion \"${outOfRange.name}\" has score ${outOfRange.score} out of ${outOfRange.maxScore ?? 1}, ` +\n 'which is outside the [0,1] range once scaled. Set `maxScore` on the criterion to declare what the ' +\n 'score is out of (e.g. `maxScore: 100` for a 0–100 grader).',\n };\n }\n\n if (scoreable.length === 0) {\n return {\n unscorable: true,\n reason: 'No criterion carried a numeric score, so no weighted total can be computed.',\n };\n }\n\n const totalWeight = scoreable.reduce((sum, criterion) => sum + (criterion.weight ?? 1), 0);\n if (!(totalWeight > 0)) {\n return {\n unscorable: true,\n reason:\n 'Criterion weights do not sum to a positive number, so the weighted total is undefined.',\n };\n }\n\n const weighted = scoreable.reduce(\n (sum, criterion) => sum + ratioOf(criterion) * (criterion.weight ?? 1),\n 0,\n );\n const rawScore = weighted / totalWeight;\n if (!Number.isFinite(rawScore)) {\n return {\n unscorable: true,\n reason: 'The weighted total is not a finite number, so no grade can be produced.',\n };\n }\n // Clamp the float-noise band only; anything genuinely out of range was\n // already rejected above.\n const score = Math.min(1, Math.max(0, rawScore));\n\n const passed =\n options.passThreshold !== undefined\n ? score >= options.passThreshold\n : activityData !== undefined\n ? computePassThreshold(activityData, score)\n : score >= 0.7;\n\n return {\n score,\n maxScore: 1,\n passed,\n feedback: options.feedback ?? null,\n criteria: [...criteria],\n };\n}\n\n/**\n * Lifts a {@link GradeRecord} into the `graded` arm of {@link ItemOutcome}, so\n * a grade that arrived asynchronously renders through the same path as a\n * synchronously scored item. The score/passed/feedback fields are mirrored\n * onto the outcome for uniform reads; `grade` carries the full record.\n */\nexport function outcomeFromGrade(grade: GradeRecord): ItemOutcome {\n return {\n status: 'graded',\n grade,\n score: grade.score,\n maxScore: grade.maxScore,\n passed: grade.passed,\n feedback: grade.feedback,\n };\n}\n\n/**\n * True when an outcome carries a real grade — either the SDK scored it\n * synchronously or a grader returned one. Use this instead of testing\n * `status === 'scored'`, which silently misses asynchronously graded work.\n */\nexport function hasGrade(\n outcome: ItemOutcome,\n): outcome is Extract<ItemOutcome, { status: 'scored' | 'graded' }> {\n return outcome.status === 'scored' || outcome.status === 'graded';\n}\n","import { hasGrade } from '../grading.js';\nimport type { ItemOutcome } from '../types/activity.js';\nimport { gte, type RoundingPolicy, roundGrade } from './rounding.js';\n\n/**\n * Sectioned assessment scoring — weights, per-section thresholds, and an\n * explicit reason when an attempt fails.\n *\n * This exists because the formula is invariably implemented twice: once on the\n * server that records the grade, and again on the client that shows a learner\n * their grade breakdown. Two implementations of one formula is exactly the\n * duplication an SDK should remove, and they drift — usually in the scale\n * (0–1 vs 0–100) or in whether a section override is honoured.\n */\n\n/** One item's contribution to a section. */\nexport interface ScoredItem {\n /**\n * Identity of the SLOT, not the activity. The same activity can legitimately\n * appear in two sections; keying on the activity id collapses them into one\n * and silently scores the second occurrence as zero.\n */\n slotId: string;\n /** The activity that filled this slot, for reporting. */\n activityId?: string;\n /** Maximum points this slot is worth. */\n points: number;\n /** What the learner achieved, or why there is no grade yet. */\n outcome: ItemOutcome;\n}\n\n/** A weighted section of an assessment. */\nexport interface AssessmentSectionInput {\n id: string;\n title?: string;\n /** Relative weight. Weights are normalised by their sum, so they need not total 1. */\n weight: number;\n /** Overrides the assessment-wide section threshold for this section only. */\n passThresholdOverride?: number;\n items: ScoredItem[];\n}\n\n/** Policy for {@link composeAssessmentScore}. */\nexport interface CompositionPolicy {\n /** Scaled [0,1] overall score required to pass. */\n passThreshold: number;\n /** Scaled [0,1] score each section must reach, when sections gate the pass. */\n sectionThreshold?: number;\n /**\n * How grades are rounded. Required, with no default: see `RoundingPolicy` —\n * grade rounding and band classification are different operations and the\n * SDK must not choose either for you.\n */\n rounding: RoundingPolicy;\n}\n\n/** Per-section result. */\nexport interface SectionScore {\n id: string;\n title?: string;\n /** The authored weight, verbatim. */\n weight: number;\n /**\n * The weight ACTUALLY used in the total, so a client can rebuild the grade\n * from `sections[]` and agree with the record:\n *\n * ```ts\n * roundGrade(\n * sections.reduce((sum, s) => sum + s.score * s.normalizedWeight, 0),\n * policy.rounding,\n * ) === result.score // exact, by construction\n * ```\n *\n * Apply the same final rounding: the raw weighted sum of already-rounded\n * section scores is not itself a rounded value (0.85 and 1.00 at equal\n * weights sum to 0.925 against a recorded 0.93), so comparing it unrounded\n * is off by up to half a quantum.\n *\n * Sections with nothing graded carry `0` here, because they contribute\n * nothing; the remaining weights are renormalised among themselves.\n */\n normalizedWeight: number;\n earnedPoints: number;\n /** Points that are currently gradable — excludes items still awaiting a grade. */\n gradedMaxPoints: number;\n /** Every point in the section, whether graded yet or not. */\n maxPoints: number;\n /** Scaled [0,1] over the GRADED points, rounded once. */\n score: number;\n passed: boolean;\n /** Threshold this section was judged against, after any override. */\n appliedThreshold: number | null;\n /** Slots still awaiting a grade. */\n pendingSlotIds: string[];\n /** Slots that can never be graded, excluded from the denominator. */\n unscorableSlotIds: string[];\n}\n\n/** Why an attempt failed, or `null` when it passed. */\nexport type PassFailureReason =\n | 'overall_below_threshold'\n | 'section_below_threshold'\n | 'both'\n | null;\n\n/** Result of composing an assessment. */\nexport interface AssessmentScore {\n sections: SectionScore[];\n /** Weighted total, scaled [0,1], rounded once. */\n score: number;\n /**\n * Whether the attempt passed — `null` while `status` is `provisional`,\n * because an attempt with work still ungraded has not passed OR failed yet.\n * Returning `false` there would let a UI keyed on `passed` show a fail for\n * an essay nobody has marked.\n */\n passed: boolean | null;\n /** `null` while provisional, for the same reason as {@link passed}. */\n passFailureReason: PassFailureReason;\n /**\n * `provisional` while any item is still awaiting a grade — the total is\n * computed over what HAS been graded, so it can still move. Do not record a\n * provisional score as final. Items that can NEVER be graded\n * (`unscorableSlotIds`) do not hold the result provisional.\n */\n status: 'final' | 'provisional';\n pendingSlotIds: string[];\n /** Slots that can never be graded. Excluded from the denominator. */\n unscorableSlotIds: string[];\n}\n\n/**\n * What one item contributes. THREE states, not two — collapsing the last two\n * into a single \"no points\" answer is what made an unscorable item block an\n * attempt from ever becoming final:\n *\n * - `graded` — real points, counted.\n * - `pending` — a grade is coming (deferred). Excluded from the denominator,\n * and the assessment stays `provisional` until it arrives.\n * - `unscorable` — a grade is never coming (unregistered type, redacted data,\n * incomplete key). Also excluded from the denominator, but it must NOT keep\n * the result provisional forever: `evaluate()` returns this precisely so a\n * mixed-version content bank does not crash an exam, and an attempt that\n * contains one still has to be recordable.\n */\ntype ItemContribution =\n | { state: 'graded'; points: number }\n | { state: 'pending' }\n | { state: 'unscorable' };\n\nfunction earned(item: ScoredItem): ItemContribution {\n const { outcome } = item;\n if (hasGrade(outcome)) {\n const max = outcome.maxScore > 0 ? outcome.maxScore : 1;\n return { state: 'graded', points: (outcome.score / max) * item.points };\n }\n if (outcome.status === 'deferred') {\n return { state: 'pending' };\n }\n return { state: 'unscorable' };\n}\n\n/**\n * Composes per-item outcomes into a sectioned assessment score.\n *\n * Weights are normalised by their sum. Each section's score is computed over\n * the points that are actually gradable and rounded ONCE, before any threshold\n * comparison, so the number a learner is shown is the number that decides the\n * outcome. Items still awaiting a grade are excluded from the denominator\n * rather than counted as zero, and the result is reported as `provisional`\n * until every item has a grade.\n */\nexport function composeAssessmentScore(\n sections: readonly AssessmentSectionInput[],\n policy: CompositionPolicy,\n): AssessmentScore {\n const pendingAll: string[] = [];\n const unscorableAll: string[] = [];\n\n // Pass 1 — score each section over the points that are actually gradable.\n const partials = sections.map((section) => {\n let earnedPoints = 0;\n let gradedMaxPoints = 0;\n let maxPoints = 0;\n const pendingSlotIds: string[] = [];\n const unscorableSlotIds: string[] = [];\n\n for (const item of section.items) {\n maxPoints += item.points;\n const contribution = earned(item);\n if (contribution.state === 'pending') {\n pendingSlotIds.push(item.slotId);\n pendingAll.push(item.slotId);\n continue;\n }\n if (contribution.state === 'unscorable') {\n unscorableSlotIds.push(item.slotId);\n unscorableAll.push(item.slotId);\n continue;\n }\n earnedPoints += contribution.points;\n gradedMaxPoints += item.points;\n }\n\n const raw = gradedMaxPoints > 0 ? earnedPoints / gradedMaxPoints : 0;\n const appliedThreshold = section.passThresholdOverride ?? policy.sectionThreshold ?? null;\n\n return {\n section,\n earnedPoints,\n gradedMaxPoints,\n maxPoints,\n score: roundGrade(raw, policy.rounding),\n appliedThreshold,\n pendingSlotIds,\n unscorableSlotIds,\n };\n });\n\n // Pass 2 — the LIVE weights. Only sections with something graded contribute,\n // and their weights are renormalised among themselves. A section whose items\n // are all still ungraded must not contribute a zero: that is the \"live\n // denominator\" bug, where a midterm reads 50% only because the essay has not\n // been marked and a learner sees a fail for work nobody has looked at.\n // These are the weights reported on each section, so that\n // `sum(score * normalizedWeight) === result.score` holds and a client\n // rendering the breakdown cannot disagree with the recorded grade.\n const contributingWeight = partials.reduce(\n (sum, partial) => sum + (partial.gradedMaxPoints > 0 ? partial.section.weight : 0),\n 0,\n );\n\n const scored: SectionScore[] = partials.map((partial) => ({\n id: partial.section.id,\n ...(partial.section.title !== undefined ? { title: partial.section.title } : {}),\n weight: partial.section.weight,\n normalizedWeight:\n partial.gradedMaxPoints > 0 && contributingWeight > 0\n ? partial.section.weight / contributingWeight\n : 0,\n earnedPoints: partial.earnedPoints,\n gradedMaxPoints: partial.gradedMaxPoints,\n maxPoints: partial.maxPoints,\n score: partial.score,\n // A section with nothing graded yet cannot be said to have failed.\n passed:\n partial.appliedThreshold === null || partial.gradedMaxPoints === 0\n ? true\n : gte(partial.score, partial.appliedThreshold, policy.rounding),\n appliedThreshold: partial.appliedThreshold,\n pendingSlotIds: partial.pendingSlotIds,\n unscorableSlotIds: partial.unscorableSlotIds,\n }));\n\n const weightedRaw = scored.reduce(\n (sum, section) => sum + section.score * section.normalizedWeight,\n 0,\n );\n const score = roundGrade(weightedRaw, policy.rounding);\n\n // Only work that is still COMING keeps the result provisional. Work that can\n // never be graded is excluded from the denominator but must not block the\n // attempt from being recorded.\n const status: 'final' | 'provisional' = pendingAll.length > 0 ? 'provisional' : 'final';\n\n if (status === 'provisional') {\n // Not passed and not failed: undetermined. Saying `false` here would let a\n // UI keyed on `passed` show a fail for unmarked work.\n return {\n sections: scored,\n score,\n passed: null,\n passFailureReason: null,\n status,\n pendingSlotIds: pendingAll,\n unscorableSlotIds: unscorableAll,\n };\n }\n\n // Nothing was gradable at all — every item unscorable, or an assessment with\n // no items. There is no evidence either way, so there is no verdict: a hard\n // `false` here records a fail at 0% for a learner whose work was never\n // gradable, which is the same fabricated-failure this module exists to\n // avoid, just reached through the other door.\n if (scored.every((section) => section.gradedMaxPoints === 0)) {\n return {\n sections: scored,\n score,\n passed: null,\n passFailureReason: null,\n status,\n pendingSlotIds: pendingAll,\n unscorableSlotIds: unscorableAll,\n };\n }\n\n const overallOk = gte(score, policy.passThreshold, policy.rounding);\n const sectionsOk = scored.every((section) => section.passed);\n const passed = overallOk && sectionsOk;\n\n const passFailureReason: PassFailureReason = passed\n ? null\n : !overallOk && !sectionsOk\n ? 'both'\n : overallOk\n ? 'section_below_threshold'\n : 'overall_below_threshold';\n\n return {\n sections: scored,\n score,\n passed,\n passFailureReason,\n status,\n pendingSlotIds: pendingAll,\n unscorableSlotIds: unscorableAll,\n };\n}\n","import { DeferredScoringError, RedactedScoringError, UnknownActivityTypeError } from '../errors.js';\nimport { getActivityTypeDescriptor } from '../registry/index.js';\nimport type {\n ActivityData,\n ActivityFeedback,\n ActivityType,\n ItemOutcome,\n LearnerResponse,\n ScoringResult,\n} from '../types/activity.js';\nimport { gte, type RoundingPolicy } from './rounding.js';\n\nexport type {\n AssessmentScore,\n AssessmentSectionInput,\n CompositionPolicy,\n PassFailureReason,\n ScoredItem,\n SectionScore,\n} from './compose.js';\nexport { composeAssessmentScore } from './compose.js';\nexport type { Band, RoundingMode, RoundingPolicy } from './rounding.js';\nexport { classifyBand, gte, roundGrade } from './rounding.js';\nexport type { TextMatchPolicy, TextMatchResult } from './text-match.js';\nexport { levenshteinDistance, matchText } from './text-match.js';\n\n/** Default minimum scaled score required to pass when `passThreshold` is absent. */\nexport const DEFAULT_PASS_THRESHOLD = 0.7;\n\n/**\n * Returns `true` iff `score` meets or exceeds the activity's `passThreshold`,\n * defaulting to {@link DEFAULT_PASS_THRESHOLD} (0.7) when the field is absent.\n *\n * Pass a {@link RoundingPolicy} to compare the way an assessment total is\n * compared — both sides rounded, via {@link gte} — so an item shown as \"70%\"\n * cannot be recorded as a fail at 69.6. It is **opt-in** rather than the\n * default because switching it on changes item-level pass/fail for scores in\n * the rounding band, and this SDK does not alter historical grades without an\n * explicit decision. Absent, the comparison is the exact raw `>=` it has\n * always been.\n */\nexport function computePassThreshold(\n activityData: ActivityData,\n score: number,\n rounding?: RoundingPolicy,\n): boolean {\n const threshold = activityData.passThreshold ?? DEFAULT_PASS_THRESHOLD;\n return rounding === undefined ? score >= threshold : gte(score, threshold, rounding);\n}\n\n/**\n * True when `data` is a `redact()` projection rather than full activity data.\n * Scoring a redacted item is always a bug: the answer key is gone by design,\n * so any \"score\" computed from it is meaningless (it used to come out `NaN`).\n */\nfunction isRedacted(data: unknown): boolean {\n return (\n typeof data === 'object' && data !== null && (data as { redacted?: unknown }).redacted === true\n );\n}\n\n/**\n * Selects the authored overall feedback for a result: `feedback.correct` when\n * the learner passed, `feedback.incorrect` otherwise; `null` when no matching\n * message was authored. Mirrors the selection the lk-react components applied\n * (keyed on `passed`, per the `ActivityFeedback` contract).\n */\nfunction selectFeedback(activityData: ActivityData, passed: boolean): string | null {\n const feedback = (activityData as { feedback?: ActivityFeedback }).feedback;\n if (feedback === undefined) {\n return null;\n }\n return (passed ? feedback.correct : feedback.incorrect) ?? null;\n}\n\n/**\n * Scores a learner response against activity data and returns a full\n * {@link ScoringResult}.\n *\n * Pure and deterministic with no side effects. It does **not** re-validate\n * `activityData` — schema validation is the component boundary's\n * responsibility; this is a low-level scoring primitive that trusts its typed\n * inputs. Dispatch is registry-backed: consumer-registered types with `sync`\n * scoring work here too. An unregistered type throws\n * {@link UnknownActivityTypeError}; a type whose grading is deferred (e.g.\n * `written-response`) throws {@link DeferredScoringError} — use\n * {@link evaluate}, which can express \"not graded yet\".\n */\nexport function score(\n activityType: ActivityType,\n activityData: ActivityData,\n learnerResponse: LearnerResponse,\n): ScoringResult {\n const descriptor = getActivityTypeDescriptor(activityType);\n if (descriptor === undefined) {\n throw new UnknownActivityTypeError(String(activityType));\n }\n if (descriptor.scoring.kind === 'deferred') {\n throw new DeferredScoringError(descriptor.type);\n }\n if (isRedacted(activityData)) {\n throw new RedactedScoringError(descriptor.type);\n }\n\n const result = descriptor.scoring.score(activityData, learnerResponse);\n if (!Number.isFinite(result.score)) {\n throw new RedactedScoringError(descriptor.type);\n }\n const passed = computePassThreshold(activityData, result.score);\n return { ...result, passed, feedback: result.feedback ?? selectFeedback(activityData, passed) };\n}\n\n/**\n * Evaluates a learner response against an activity and returns an\n * {@link ItemOutcome} — the resilient, forward-compatible alternative to\n * {@link score}:\n *\n * - Synchronously graded types return `{ status: 'scored', ... }` with the\n * same numbers `score()` produces.\n * - Asynchronously graded types (e.g. `written-response`) return\n * `{ status: 'deferred', reason, partial }` instead of a fake zero — \"not\n * graded yet\" is expressible in the type system, never conflated with\n * \"wrong\".\n * - An unregistered `data.type` returns `{ status: 'unscorable' }` rather\n * than throwing, so a mixed-version content bank cannot crash an exam run.\n *\n * The activity type is read from `data.type` — there is no separate type\n * parameter to disagree with the payload.\n */\nexport function evaluate(data: ActivityData, response: LearnerResponse): ItemOutcome {\n const type = (data as { type?: unknown }).type;\n const descriptor = typeof type === 'string' ? getActivityTypeDescriptor(type) : undefined;\n\n if (descriptor === undefined) {\n return {\n status: 'unscorable',\n reason: `Activity type \"${String(type)}\" is not registered`,\n maxScore: 1,\n };\n }\n\n if (descriptor.scoring.kind === 'deferred') {\n const partial = descriptor.scoring.partial?.(data, response);\n return {\n status: 'deferred',\n reason: descriptor.scoring.reason,\n maxScore: 1,\n ...(partial !== undefined ? { partial } : {}),\n };\n }\n\n if (isRedacted(data)) {\n return {\n status: 'unscorable',\n reason:\n 'Activity data is redacted (no answer key), so it cannot be scored on the client. Score against the full data server-side.',\n maxScore: 1,\n };\n }\n\n const result = descriptor.scoring.score(data, response);\n if (!Number.isFinite(result.score)) {\n // Defence in depth: incomplete data (a missing answer key, an empty\n // options array) used to divide by zero and surface as a real score of\n // NaN, which JSON-serializes to null in a grade column.\n return {\n status: 'unscorable',\n reason: `Scoring \"${descriptor.type}\" produced a non-finite score; the activity data is incomplete.`,\n maxScore: result.maxScore,\n };\n }\n const passed = computePassThreshold(data, result.score);\n return {\n status: 'scored',\n score: result.score,\n maxScore: result.maxScore,\n passed,\n feedback: result.feedback ?? selectFeedback(data, passed),\n details: result.details,\n };\n}\n"],"mappings":"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","names":["EPSILON","score","score","score"]}