@intellectif/lk-core 0.3.1 → 0.5.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 (58) hide show
  1. package/dist/{activity-zdcAMtFB.d.cts → activity-wkzRemHx.d.cts} +113 -1
  2. package/dist/{activity-zdcAMtFB.d.ts → activity-wkzRemHx.d.ts} +113 -1
  3. package/dist/{chunk-QOAORTA4.cjs → chunk-4JR3UXX6.cjs} +51 -2
  4. package/dist/chunk-4JR3UXX6.cjs.map +1 -0
  5. package/dist/chunk-DUQVGLQ3.js +333 -0
  6. package/dist/chunk-DUQVGLQ3.js.map +1 -0
  7. package/dist/chunk-FCM5VJBS.cjs +187 -0
  8. package/dist/chunk-FCM5VJBS.cjs.map +1 -0
  9. package/dist/chunk-LSHDNA2T.js +187 -0
  10. package/dist/chunk-LSHDNA2T.js.map +1 -0
  11. package/dist/{chunk-5GJJHGY5.js → chunk-NUCEUU4P.js} +50 -1
  12. package/dist/chunk-NUCEUU4P.js.map +1 -0
  13. package/dist/{chunk-RUGIOQZY.cjs → chunk-R7PDJBRX.cjs} +29 -6
  14. package/dist/chunk-R7PDJBRX.cjs.map +1 -0
  15. package/dist/chunk-SM5HUGYU.cjs +333 -0
  16. package/dist/chunk-SM5HUGYU.cjs.map +1 -0
  17. package/dist/{chunk-HS7BYCGE.js → chunk-WV5WQ3SZ.js} +29 -6
  18. package/dist/chunk-WV5WQ3SZ.js.map +1 -0
  19. package/dist/index-BKyZrd94.d.cts +732 -0
  20. package/dist/index-CLmXzBhB.d.ts +732 -0
  21. package/dist/index.cjs +206 -15
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +182 -6
  24. package/dist/index.d.ts +182 -6
  25. package/dist/index.js +203 -12
  26. package/dist/index.js.map +1 -1
  27. package/dist/schemas.cjs +17 -4
  28. package/dist/schemas.cjs.map +1 -1
  29. package/dist/schemas.d.cts +3 -460
  30. package/dist/schemas.d.ts +3 -460
  31. package/dist/schemas.js +16 -3
  32. package/dist/scoring.cjs +11 -4
  33. package/dist/scoring.cjs.map +1 -1
  34. package/dist/scoring.d.cts +199 -4
  35. package/dist/scoring.d.ts +199 -4
  36. package/dist/scoring.js +10 -3
  37. package/dist/xapi.cjs +5 -3
  38. package/dist/xapi.cjs.map +1 -1
  39. package/dist/xapi.d.cts +26 -2
  40. package/dist/xapi.d.ts +26 -2
  41. package/dist/xapi.js +6 -4
  42. package/package.json +38 -15
  43. package/dist/chunk-3YAVDV5F.js +0 -47
  44. package/dist/chunk-3YAVDV5F.js.map +0 -1
  45. package/dist/chunk-55O4M45K.js +0 -57
  46. package/dist/chunk-55O4M45K.js.map +0 -1
  47. package/dist/chunk-5GJJHGY5.js.map +0 -1
  48. package/dist/chunk-HS7BYCGE.js.map +0 -1
  49. package/dist/chunk-PIMX4B4D.cjs +0 -47
  50. package/dist/chunk-PIMX4B4D.cjs.map +0 -1
  51. package/dist/chunk-QOAORTA4.cjs.map +0 -1
  52. package/dist/chunk-QSVBYTNM.js +0 -94
  53. package/dist/chunk-QSVBYTNM.js.map +0 -1
  54. package/dist/chunk-RUGIOQZY.cjs.map +0 -1
  55. package/dist/chunk-THSZMZND.cjs +0 -94
  56. package/dist/chunk-THSZMZND.cjs.map +0 -1
  57. package/dist/chunk-YNTYWHZI.cjs +0 -57
  58. package/dist/chunk-YNTYWHZI.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -3,22 +3,21 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkTHSZMZNDcjs = require('./chunk-THSZMZND.cjs');
7
6
 
8
7
 
9
8
 
10
9
 
11
10
 
12
- var _chunkRUGIOQZYcjs = require('./chunk-RUGIOQZY.cjs');
13
11
 
14
12
 
13
+ var _chunkSM5HUGYUcjs = require('./chunk-SM5HUGYU.cjs');
15
14
 
16
15
 
17
16
 
18
17
 
19
- var _chunkYNTYWHZIcjs = require('./chunk-YNTYWHZI.cjs');
20
18
 
21
19
 
20
+ var _chunkR7PDJBRXcjs = require('./chunk-R7PDJBRX.cjs');
22
21
 
23
22
 
24
23
 
@@ -32,6 +31,7 @@ var _chunkYNTYWHZIcjs = require('./chunk-YNTYWHZI.cjs');
32
31
 
33
32
 
34
33
 
34
+ var _chunkFCM5VJBScjs = require('./chunk-FCM5VJBS.cjs');
35
35
 
36
36
 
37
37
 
@@ -44,13 +44,96 @@ var _chunkYNTYWHZIcjs = require('./chunk-YNTYWHZI.cjs');
44
44
 
45
45
 
46
46
 
47
- var _chunkQOAORTA4cjs = require('./chunk-QOAORTA4.cjs');
48
47
 
49
48
 
50
49
 
51
50
 
52
51
 
53
- var _chunkPIMX4B4Dcjs = require('./chunk-PIMX4B4D.cjs');
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+ var _chunk4JR3UXX6cjs = require('./chunk-4JR3UXX6.cjs');
67
+
68
+ // src/shuffle.ts
69
+ function hashSeed(input) {
70
+ let h = 2166136261 >>> 0;
71
+ for (let i = 0; i < input.length; i += 1) {
72
+ h ^= input.charCodeAt(i);
73
+ h = Math.imul(h, 16777619) >>> 0;
74
+ }
75
+ return h;
76
+ }
77
+ function shuffleWithSeed(items, seed, version) {
78
+ const out = [...items];
79
+ let s = seed >>> 0 || 1;
80
+ for (let i = out.length - 1; i > 0; i -= 1) {
81
+ s = Math.imul(s, 1664525) + 1013904223 >>> 0;
82
+ const j = version === 2 ? Math.floor(s / 4294967296 * (i + 1)) : s % (i + 1);
83
+ [out[i], out[j]] = [out[j], out[i]];
84
+ }
85
+ return out;
86
+ }
87
+ function seededShuffle(items, seed, options = {}) {
88
+ return shuffleWithSeed(items, hashSeed(seed), _nullishCoalesce(options.version, () => ( 1)));
89
+ }
90
+
91
+ // src/item-group.ts
92
+ function isItemGroup(entry) {
93
+ return entry.type === "item-group";
94
+ }
95
+ function flattenSequence(entries, options = {}) {
96
+ const shuffleEntries = options.shuffleEntries === true;
97
+ const needsSeed = shuffleEntries || entries.some((entry) => isItemGroup(entry) && entry.shuffle === "within-group");
98
+ if (needsSeed && options.seed === void 0) {
99
+ throw new Error(
100
+ 'flattenSequence: a seed is required when shuffling (shuffleEntries, or a group with shuffle: "within-group"). Pass the attempt id so the server and the client derive the same order.'
101
+ );
102
+ }
103
+ const seed = _nullishCoalesce(options.seed, () => ( ""));
104
+ const authored = entries.map((entry, entryIndex) => ({ entry, entryIndex }));
105
+ const ordered = shuffleEntries ? seededShuffle(authored, `${seed}:entries`) : authored;
106
+ const slots = [];
107
+ for (const { entry, entryIndex } of ordered) {
108
+ if (!isItemGroup(entry)) {
109
+ slots.push({ slotId: String(entryIndex), index: slots.length, activity: entry });
110
+ continue;
111
+ }
112
+ if (entry.items.length === 0) {
113
+ throw new Error(
114
+ `flattenSequence: item group "${entry.id}" has no items. An empty group would silently remove its stimulus and its questions from the presented sequence.`
115
+ );
116
+ }
117
+ const items = entry.items.map((item, itemIndex) => ({ item, itemIndex }));
118
+ const presented = entry.shuffle === "within-group" ? seededShuffle(items, `${seed}:group:${entry.id}`) : items;
119
+ const size = presented.length;
120
+ presented.forEach(({ item, itemIndex }, position) => {
121
+ slots.push({
122
+ slotId: `${entryIndex}.${itemIndex}`,
123
+ index: slots.length,
124
+ activity: item,
125
+ group: {
126
+ id: entry.id,
127
+ ...entry.title !== void 0 ? { title: entry.title } : {},
128
+ stimulus: entry.stimulus,
129
+ position,
130
+ size
131
+ }
132
+ });
133
+ });
134
+ }
135
+ return slots;
136
+ }
54
137
 
55
138
  // src/redact.ts
56
139
  function mergePolicy(base, overrides) {
@@ -105,9 +188,9 @@ function redactValue(value, policy, reveal) {
105
188
  }
106
189
  function redact(data, options = {}) {
107
190
  const reveal = _nullishCoalesce(options.reveal, () => ( "none"));
108
- const descriptor = _chunkQOAORTA4cjs.getActivityTypeDescriptor.call(void 0, data.type);
191
+ const descriptor = _chunk4JR3UXX6cjs.getActivityTypeDescriptor.call(void 0, data.type);
109
192
  if (descriptor === void 0) {
110
- throw new (0, _chunkPIMX4B4Dcjs.UnknownActivityTypeError)(String(data.type));
193
+ throw new (0, _chunk4JR3UXX6cjs.UnknownActivityTypeError)(String(data.type));
111
194
  }
112
195
  if (descriptor.fieldPolicy === void 0) {
113
196
  throw new Error(
@@ -120,7 +203,7 @@ function redact(data, options = {}) {
120
203
  if (reveal === "none" && descriptor.redactedSchema !== void 0) {
121
204
  const parsed = descriptor.redactedSchema.safeParse(result);
122
205
  if (!parsed.success) {
123
- throw new (0, _chunkPIMX4B4Dcjs.ActivitySchemaError)(
206
+ throw new (0, _chunk4JR3UXX6cjs.ActivitySchemaError)(
124
207
  descriptor.type,
125
208
  parsed.error.issues.map((issue) => ({
126
209
  path: issue.path.map(String),
@@ -134,13 +217,13 @@ function redact(data, options = {}) {
134
217
  }
135
218
  function assertRedacted(data) {
136
219
  if (typeof data !== "object" || data === null) {
137
- throw new (0, _chunkPIMX4B4Dcjs.ActivitySchemaError)("unknown", [
220
+ throw new (0, _chunk4JR3UXX6cjs.ActivitySchemaError)("unknown", [
138
221
  { path: [], message: "Redacted activity data must be an object.", code: "invalid_type" }
139
222
  ]);
140
223
  }
141
224
  const candidate = data;
142
225
  if (candidate.redacted !== true) {
143
- throw new (0, _chunkPIMX4B4Dcjs.ActivitySchemaError)(String(_nullishCoalesce(candidate.type, () => ( "unknown"))), [
226
+ throw new (0, _chunk4JR3UXX6cjs.ActivitySchemaError)(String(_nullishCoalesce(candidate.type, () => ( "unknown"))), [
144
227
  {
145
228
  path: ["redacted"],
146
229
  message: "Missing redacted marker \u2014 this payload is not a redact() projection.",
@@ -149,12 +232,12 @@ function assertRedacted(data) {
149
232
  ]);
150
233
  }
151
234
  const type = typeof candidate.type === "string" ? candidate.type : "";
152
- const descriptor = _chunkQOAORTA4cjs.getActivityTypeDescriptor.call(void 0, type);
235
+ const descriptor = _chunk4JR3UXX6cjs.getActivityTypeDescriptor.call(void 0, type);
153
236
  if (descriptor === void 0) {
154
- throw new (0, _chunkPIMX4B4Dcjs.UnknownActivityTypeError)(type);
237
+ throw new (0, _chunk4JR3UXX6cjs.UnknownActivityTypeError)(type);
155
238
  }
156
239
  if (descriptor.redactedSchema === void 0) {
157
- throw new (0, _chunkPIMX4B4Dcjs.ActivitySchemaError)(descriptor.type, [
240
+ throw new (0, _chunk4JR3UXX6cjs.ActivitySchemaError)(descriptor.type, [
158
241
  {
159
242
  path: [],
160
243
  message: `Activity type "${descriptor.type}" registers no redactedSchema; assertRedacted cannot prove this payload is learner-safe.`,
@@ -164,7 +247,7 @@ function assertRedacted(data) {
164
247
  }
165
248
  const parsed = descriptor.redactedSchema.safeParse(candidate);
166
249
  if (!parsed.success) {
167
- throw new (0, _chunkPIMX4B4Dcjs.ActivitySchemaError)(
250
+ throw new (0, _chunk4JR3UXX6cjs.ActivitySchemaError)(
168
251
  descriptor.type,
169
252
  parsed.error.issues.map((issue) => ({
170
253
  path: issue.path.map(String),
@@ -174,6 +257,114 @@ function assertRedacted(data) {
174
257
  );
175
258
  }
176
259
  }
260
+ var STIMULUS_FIELD_POLICY = {
261
+ id: "public",
262
+ kind: "public",
263
+ title: "public",
264
+ body: "public",
265
+ bodyHtml: "public",
266
+ media: "public",
267
+ locale: "public",
268
+ attribution: "public",
269
+ transcript: "author-only"
270
+ };
271
+ var ITEM_GROUP_FIELD_POLICY = {
272
+ schemaVersion: "public",
273
+ type: "public",
274
+ id: "public",
275
+ title: "public",
276
+ shuffle: "public",
277
+ stimulus: STIMULUS_FIELD_POLICY
278
+ };
279
+ function redactItemGroup(group, options = {}) {
280
+ const reveal = _nullishCoalesce(options.reveal, () => ( "none"));
281
+ const { items, ...container } = group;
282
+ const projected = redactValue(container, ITEM_GROUP_FIELD_POLICY, reveal);
283
+ const result = {
284
+ ...projected,
285
+ items: items.map((item) => redact(item, options)),
286
+ redacted: true
287
+ };
288
+ if (reveal === "none") {
289
+ const parsed = _chunkFCM5VJBScjs.RedactedItemGroupSchema.safeParse(result);
290
+ if (!parsed.success) {
291
+ throw new (0, _chunk4JR3UXX6cjs.ActivitySchemaError)(
292
+ "item-group",
293
+ parsed.error.issues.map((issue) => ({
294
+ path: issue.path.map(String),
295
+ message: issue.message,
296
+ code: issue.code
297
+ }))
298
+ );
299
+ }
300
+ }
301
+ return result;
302
+ }
303
+ function assertRedactedItemGroup(data) {
304
+ if (typeof data !== "object" || data === null) {
305
+ throw new (0, _chunk4JR3UXX6cjs.ActivitySchemaError)("item-group", [
306
+ { path: [], message: "Redacted item group must be an object.", code: "invalid_type" }
307
+ ]);
308
+ }
309
+ const candidate = data;
310
+ if (candidate.redacted !== true) {
311
+ throw new (0, _chunk4JR3UXX6cjs.ActivitySchemaError)("item-group", [
312
+ {
313
+ path: ["redacted"],
314
+ message: "Missing redacted marker \u2014 this payload is not a redactItemGroup() projection.",
315
+ code: "custom"
316
+ }
317
+ ]);
318
+ }
319
+ const parsed = _chunkFCM5VJBScjs.RedactedItemGroupSchema.safeParse(candidate);
320
+ if (!parsed.success) {
321
+ throw new (0, _chunk4JR3UXX6cjs.ActivitySchemaError)(
322
+ "item-group",
323
+ parsed.error.issues.map((issue) => ({
324
+ path: issue.path.map(String),
325
+ message: issue.message,
326
+ code: issue.code
327
+ }))
328
+ );
329
+ }
330
+ parsed.data.items.forEach((item, index) => {
331
+ try {
332
+ assertRedacted(item);
333
+ } catch (error) {
334
+ if (error instanceof _chunk4JR3UXX6cjs.ActivitySchemaError) {
335
+ throw new (0, _chunk4JR3UXX6cjs.ActivitySchemaError)(
336
+ "item-group",
337
+ error.errors.map((issue) => ({
338
+ ...issue,
339
+ path: ["items", String(index), ...issue.path]
340
+ }))
341
+ );
342
+ }
343
+ throw error;
344
+ }
345
+ });
346
+ }
347
+
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
177
368
 
178
369
 
179
370
 
@@ -220,5 +411,5 @@ function assertRedacted(data) {
220
411
 
221
412
 
222
413
 
223
- exports.ActivitySchemaError = _chunkPIMX4B4Dcjs.ActivitySchemaError; exports.BlankConfigSchema = _chunkQOAORTA4cjs.BlankConfigSchema; exports.DEFAULT_PASS_THRESHOLD = _chunkTHSZMZNDcjs.DEFAULT_PASS_THRESHOLD; exports.DeferredScoringError = _chunkPIMX4B4Dcjs.DeferredScoringError; exports.FeedbackSchema = _chunkQOAORTA4cjs.FeedbackSchema; exports.FillInTheBlanksDataSchema = _chunkQOAORTA4cjs.FillInTheBlanksDataSchema; exports.MediaSchema = _chunkQOAORTA4cjs.MediaSchema; exports.MediaUrlSchema = _chunkQOAORTA4cjs.MediaUrlSchema; exports.MultipleChoiceDataSchema = _chunkQOAORTA4cjs.MultipleChoiceDataSchema; exports.MultipleChoiceOptionSchema = _chunkQOAORTA4cjs.MultipleChoiceOptionSchema; exports.RedactedBlankConfigSchema = _chunkQOAORTA4cjs.RedactedBlankConfigSchema; exports.RedactedFillInTheBlanksDataSchema = _chunkQOAORTA4cjs.RedactedFillInTheBlanksDataSchema; exports.RedactedMultipleChoiceDataSchema = _chunkQOAORTA4cjs.RedactedMultipleChoiceDataSchema; exports.RedactedMultipleChoiceOptionSchema = _chunkQOAORTA4cjs.RedactedMultipleChoiceOptionSchema; exports.RedactedScoringError = _chunkPIMX4B4Dcjs.RedactedScoringError; exports.RedactedWrittenResponseDataSchema = _chunkQOAORTA4cjs.RedactedWrittenResponseDataSchema; exports.TextMatchPolicySchema = _chunkQOAORTA4cjs.TextMatchPolicySchema; exports.UnknownActivityTypeError = _chunkPIMX4B4Dcjs.UnknownActivityTypeError; exports.WrittenResponseDataSchema = _chunkQOAORTA4cjs.WrittenResponseDataSchema; exports.WrittenResponseRubricCriterionSchema = _chunkQOAORTA4cjs.WrittenResponseRubricCriterionSchema; exports.WrittenResponseRubricSchema = _chunkQOAORTA4cjs.WrittenResponseRubricSchema; exports.XAPIVerb = _chunkRUGIOQZYcjs.XAPIVerb; exports.XAPI_VERB_DISPLAY = _chunkRUGIOQZYcjs.XAPI_VERB_DISPLAY; exports.assertRedacted = assertRedacted; exports.computePassThreshold = _chunkTHSZMZNDcjs.computePassThreshold; exports.countWords = _chunkQOAORTA4cjs.countWords; exports.defineActivityType = _chunkQOAORTA4cjs.defineActivityType; exports.evaluate = _chunkTHSZMZNDcjs.evaluate; exports.fillInTheBlanksJsonSchema = _chunkYNTYWHZIcjs.fillInTheBlanksJsonSchema; exports.fillInTheBlanksType = _chunkQOAORTA4cjs.fillInTheBlanksType; exports.getActivityTypeDescriptor = _chunkQOAORTA4cjs.getActivityTypeDescriptor; exports.jsonSchemaFor = _chunkYNTYWHZIcjs.jsonSchemaFor; exports.levenshteinDistance = _chunkQOAORTA4cjs.levenshteinDistance; exports.matchText = _chunkQOAORTA4cjs.matchText; exports.multipleChoiceJsonSchema = _chunkYNTYWHZIcjs.multipleChoiceJsonSchema; exports.multipleChoiceType = _chunkQOAORTA4cjs.multipleChoiceType; exports.redact = redact; exports.registerActivityType = _chunkQOAORTA4cjs.registerActivityType; exports.registeredActivityTypes = _chunkQOAORTA4cjs.registeredActivityTypes; exports.score = _chunkTHSZMZNDcjs.score; exports.validateActivity = _chunkYNTYWHZIcjs.validateActivity; exports.validateXAPIStatement = _chunkRUGIOQZYcjs.validateXAPIStatement; exports.writtenResponseJsonSchema = _chunkYNTYWHZIcjs.writtenResponseJsonSchema; exports.writtenResponseType = _chunkQOAORTA4cjs.writtenResponseType; exports.xAPIBuilder = _chunkRUGIOQZYcjs.xAPIBuilder;
414
+ exports.ActivitySchemaError = _chunk4JR3UXX6cjs.ActivitySchemaError; exports.BlankConfigSchema = _chunk4JR3UXX6cjs.BlankConfigSchema; exports.DEFAULT_PASS_THRESHOLD = _chunkSM5HUGYUcjs.DEFAULT_PASS_THRESHOLD; exports.DeferredScoringError = _chunk4JR3UXX6cjs.DeferredScoringError; exports.FeedbackSchema = _chunk4JR3UXX6cjs.FeedbackSchema; exports.FillInTheBlanksDataSchema = _chunk4JR3UXX6cjs.FillInTheBlanksDataSchema; exports.ItemGroupSchema = _chunkFCM5VJBScjs.ItemGroupSchema; exports.MediaSchema = _chunk4JR3UXX6cjs.MediaSchema; exports.MediaUrlSchema = _chunk4JR3UXX6cjs.MediaUrlSchema; exports.MultipleChoiceDataSchema = _chunk4JR3UXX6cjs.MultipleChoiceDataSchema; exports.MultipleChoiceOptionSchema = _chunk4JR3UXX6cjs.MultipleChoiceOptionSchema; exports.RedactedBlankConfigSchema = _chunk4JR3UXX6cjs.RedactedBlankConfigSchema; exports.RedactedFillInTheBlanksDataSchema = _chunk4JR3UXX6cjs.RedactedFillInTheBlanksDataSchema; exports.RedactedItemGroupSchema = _chunkFCM5VJBScjs.RedactedItemGroupSchema; exports.RedactedMultipleChoiceDataSchema = _chunk4JR3UXX6cjs.RedactedMultipleChoiceDataSchema; exports.RedactedMultipleChoiceOptionSchema = _chunk4JR3UXX6cjs.RedactedMultipleChoiceOptionSchema; exports.RedactedScoringError = _chunk4JR3UXX6cjs.RedactedScoringError; exports.RedactedStimulusSchema = _chunkFCM5VJBScjs.RedactedStimulusSchema; exports.RedactedWrittenResponseDataSchema = _chunk4JR3UXX6cjs.RedactedWrittenResponseDataSchema; exports.StimulusSchema = _chunkFCM5VJBScjs.StimulusSchema; exports.TextMatchPolicySchema = _chunk4JR3UXX6cjs.TextMatchPolicySchema; exports.UnknownActivityTypeError = _chunk4JR3UXX6cjs.UnknownActivityTypeError; exports.WrittenResponseDataSchema = _chunk4JR3UXX6cjs.WrittenResponseDataSchema; exports.WrittenResponseRubricCriterionSchema = _chunk4JR3UXX6cjs.WrittenResponseRubricCriterionSchema; exports.WrittenResponseRubricSchema = _chunk4JR3UXX6cjs.WrittenResponseRubricSchema; exports.XAPIVerb = _chunkR7PDJBRXcjs.XAPIVerb; exports.XAPI_VERB_DISPLAY = _chunkR7PDJBRXcjs.XAPI_VERB_DISPLAY; exports.assertRedacted = assertRedacted; exports.assertRedactedItemGroup = assertRedactedItemGroup; exports.classifyBand = _chunkSM5HUGYUcjs.classifyBand; exports.composeAssessmentScore = _chunkSM5HUGYUcjs.composeAssessmentScore; exports.computePassThreshold = _chunkSM5HUGYUcjs.computePassThreshold; exports.countWords = _chunk4JR3UXX6cjs.countWords; exports.defineActivityType = _chunk4JR3UXX6cjs.defineActivityType; exports.evaluate = _chunkSM5HUGYUcjs.evaluate; exports.fillInTheBlanksJsonSchema = _chunkFCM5VJBScjs.fillInTheBlanksJsonSchema; exports.fillInTheBlanksType = _chunk4JR3UXX6cjs.fillInTheBlanksType; exports.flattenSequence = flattenSequence; exports.getActivityTypeDescriptor = _chunk4JR3UXX6cjs.getActivityTypeDescriptor; exports.gradeFromRubric = _chunkSM5HUGYUcjs.gradeFromRubric; exports.gte = _chunkSM5HUGYUcjs.gte; exports.hasGrade = _chunkSM5HUGYUcjs.hasGrade; exports.hashSeed = hashSeed; exports.isItemGroup = isItemGroup; exports.itemGroupJsonSchema = _chunkFCM5VJBScjs.itemGroupJsonSchema; exports.jsonSchemaFor = _chunkFCM5VJBScjs.jsonSchemaFor; exports.levenshteinDistance = _chunk4JR3UXX6cjs.levenshteinDistance; exports.matchText = _chunk4JR3UXX6cjs.matchText; exports.multipleChoiceJsonSchema = _chunkFCM5VJBScjs.multipleChoiceJsonSchema; exports.multipleChoiceType = _chunk4JR3UXX6cjs.multipleChoiceType; exports.outcomeFromGrade = _chunkSM5HUGYUcjs.outcomeFromGrade; exports.redact = redact; exports.redactItemGroup = redactItemGroup; exports.registerActivityType = _chunk4JR3UXX6cjs.registerActivityType; exports.registeredActivityTypes = _chunk4JR3UXX6cjs.registeredActivityTypes; exports.roundGrade = _chunkSM5HUGYUcjs.roundGrade; exports.score = _chunkSM5HUGYUcjs.score; exports.seededShuffle = seededShuffle; exports.stimulusJsonSchema = _chunkFCM5VJBScjs.stimulusJsonSchema; exports.validateActivity = _chunkFCM5VJBScjs.validateActivity; exports.validateItemGroup = _chunkFCM5VJBScjs.validateItemGroup; exports.validateXAPIStatement = _chunkR7PDJBRXcjs.validateXAPIStatement; exports.writtenResponseJsonSchema = _chunkFCM5VJBScjs.writtenResponseJsonSchema; exports.writtenResponseType = _chunk4JR3UXX6cjs.writtenResponseType; exports.xAPIBuilder = _chunkR7PDJBRXcjs.xAPIBuilder; exports.xapiDefinitionFor = _chunkR7PDJBRXcjs.xapiDefinitionFor;
224
415
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/index.cjs","../src/redact.ts"],"names":[],"mappings":"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","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/index.cjs","sourcesContent":[null,"import { ActivitySchemaError, UnknownActivityTypeError } from './errors.js';\nimport { getActivityTypeDescriptor } from './registry/index.js';\nimport type { FieldPolicy, Sensitivity } from './registry/registry.js';\n\n/**\n * A learner-safe projection of activity data produced by {@link redact}:\n * the activity's public fields plus the `redacted: true` marker. Renderable\n * and validatable (each built-in type registers a strict redacted schema),\n * but stripped of the answer key, scoring rules, authored feedback, and\n * author-only assets.\n */\nexport interface RedactedActivityData {\n redacted: true;\n schemaVersion: string;\n type: string;\n id: string;\n title: string;\n [key: string]: unknown;\n}\n\n/** Options for {@link redact}. */\nexport interface RedactOptions {\n /**\n * Which sensitivity tiers to keep beyond `public`:\n * - `'none'` (default) — public fields only; the output satisfies the\n * type's strict redacted schema and `assertRedacted`.\n * - `'after-submit'` — public + answer-key fields (for post-submission\n * review renders). `author-only` fields and unclassified fields are\n * STILL removed; the output will NOT pass `assertRedacted`.\n */\n reveal?: 'none' | 'after-submit';\n /**\n * Per-call sensitivity overrides, merged over the type's registered\n * `fieldPolicy` (top-level keys replace; nested objects merge one level).\n *\n * Sensitivity is partly a PEDAGOGICAL decision, not purely a security one —\n * whether a rubric or a hint is learner-visible differs legitimately between\n * deployments — and the SDK must not freeze that choice. Use this to tighten\n * a field the SDK ships as `public`:\n *\n * ```ts\n * redact(essay, { policy: { rubric: 'author-only' } });\n * ```\n *\n * Overrides can only be applied to fields; they cannot re-open a field the\n * caller has not classified, because unclassified still means removed.\n * Note that tightening below what the type's `redactedSchema` requires is\n * allowed — the schema check only rejects payloads that reveal MORE than\n * the learner-safe shape.\n */\n policy?: FieldPolicy;\n}\n\n/** Merges per-call overrides over a registered policy, one level deep. */\nfunction mergePolicy(base: FieldPolicy, overrides: FieldPolicy | undefined): FieldPolicy {\n if (overrides === undefined) {\n return base;\n }\n const merged: Record<string, Sensitivity | FieldPolicy> = { ...base };\n for (const [key, override] of Object.entries(overrides)) {\n const current = merged[key];\n merged[key] =\n typeof override === 'object' && typeof current === 'object'\n ? { ...current, ...override }\n : override;\n }\n return merged;\n}\n\nfunction isSensitivity(value: Sensitivity | FieldPolicy): value is Sensitivity {\n return typeof value === 'string';\n}\n\nfunction keepField(sensitivity: Sensitivity, reveal: 'none' | 'after-submit'): boolean {\n if (sensitivity === 'public') {\n return true;\n }\n return sensitivity === 'answer-key' && reveal === 'after-submit';\n}\n\nfunction redactValue(\n value: unknown,\n policy: FieldPolicy,\n reveal: 'none' | 'after-submit',\n): unknown {\n if (Array.isArray(value)) {\n return value.map((element) => redactValue(element, policy, reveal));\n }\n if (value === null || typeof value !== 'object') {\n // A nested FieldPolicy cannot classify a primitive's sub-fields; the\n // policy author classified an object shape that is not there. Fail\n // closed: drop it (handled by the caller returning undefined).\n return undefined;\n }\n\n const source = value as Record<string, unknown>;\n const output: Record<string, unknown> = {};\n for (const [key, fieldValue] of Object.entries(source)) {\n if (fieldValue === undefined) {\n continue;\n }\n const classification = policy[key];\n if (classification === undefined) {\n // Fail closed: unclassified fields are never emitted, at any reveal\n // level. Adding a field without classifying it hides it — never leaks it.\n continue;\n }\n if (isSensitivity(classification)) {\n if (keepField(classification, reveal)) {\n output[key] = fieldValue;\n }\n continue;\n }\n const nested = redactValue(fieldValue, classification, reveal);\n if (nested !== undefined) {\n output[key] = nested;\n }\n }\n return output;\n}\n\n/**\n * Produces the learner-safe projection of activity data (R7), driven by the\n * `fieldPolicy` registered for `data.type`. Fail-closed and exhaustive by\n * construction: a field the policy does not classify is removed — including\n * unknown passthrough fields — so a NEW field added by a future SDK or a\n * consumer sidecar can never leak through an out-of-date redactor.\n *\n * With the default `reveal: 'none'`, the output validates against the type's\n * strict redacted schema (verified here; an invalid projection throws\n * {@link ActivitySchemaError} rather than shipping an unproven payload).\n *\n * @throws UnknownActivityTypeError when `data.type` is not registered.\n * @throws Error when the registered descriptor declares no `fieldPolicy`\n * (redaction cannot guess sensitivities).\n */\nexport function redact<T extends { type: string }>(\n data: T,\n options: RedactOptions = {},\n): RedactedActivityData {\n const reveal = options.reveal ?? 'none';\n const descriptor = getActivityTypeDescriptor(data.type);\n if (descriptor === undefined) {\n throw new UnknownActivityTypeError(String(data.type));\n }\n if (descriptor.fieldPolicy === undefined) {\n throw new Error(\n `Activity type \"${descriptor.type}\" has no fieldPolicy; redact() cannot run fail-closed redaction without one.`,\n );\n }\n\n const effectivePolicy = mergePolicy(descriptor.fieldPolicy, options.policy);\n const projected = redactValue(data, effectivePolicy, reveal) as Record<string, unknown>;\n const result = { ...projected, redacted: true as const } as RedactedActivityData;\n\n if (reveal === 'none' && descriptor.redactedSchema !== undefined) {\n const parsed = descriptor.redactedSchema.safeParse(result);\n if (!parsed.success) {\n throw new ActivitySchemaError(\n descriptor.type,\n parsed.error.issues.map((issue) => ({\n path: issue.path.map(String),\n message: issue.message,\n code: issue.code,\n })),\n );\n }\n }\n\n return result;\n}\n\n/**\n * Asserts that `data` is a learner-safe redacted projection: it carries the\n * `redacted: true` marker and, when its type registers a strict redacted\n * schema, validates against it (proving the absence of answer-key fields).\n * Use this at the server boundary before sending activity data to a client\n * that must not hold the key.\n */\nexport function assertRedacted(data: unknown): asserts data is RedactedActivityData {\n if (typeof data !== 'object' || data === null) {\n throw new ActivitySchemaError('unknown', [\n { path: [], message: 'Redacted activity data must be an object.', code: 'invalid_type' },\n ]);\n }\n const candidate = data as Record<string, unknown>;\n if (candidate.redacted !== true) {\n throw new ActivitySchemaError(String(candidate.type ?? 'unknown'), [\n {\n path: ['redacted'],\n message: 'Missing redacted marker — this payload is not a redact() projection.',\n code: 'custom',\n },\n ]);\n }\n const type = typeof candidate.type === 'string' ? candidate.type : '';\n const descriptor = getActivityTypeDescriptor(type);\n if (descriptor === undefined) {\n throw new UnknownActivityTypeError(type);\n }\n if (descriptor.redactedSchema === undefined) {\n // Fail closed: without a registered redacted schema there is no way to\n // PROVE the absence of answer-key fields, and a marker alone proves\n // nothing (any object can carry `redacted: true`).\n throw new ActivitySchemaError(descriptor.type, [\n {\n path: [],\n message: `Activity type \"${descriptor.type}\" registers no redactedSchema; assertRedacted cannot prove this payload is learner-safe.`,\n code: 'custom',\n },\n ]);\n }\n const parsed = descriptor.redactedSchema.safeParse(candidate);\n if (!parsed.success) {\n throw new ActivitySchemaError(\n descriptor.type,\n parsed.error.issues.map((issue) => ({\n path: issue.path.map(String),\n message: issue.message,\n code: issue.code,\n })),\n );\n }\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/index.cjs","../src/shuffle.ts","../src/item-group.ts","../src/redact.ts"],"names":[],"mappings":"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AACpC,QAAA;AACR,MAAA;AACD,IAAA;AACH,EAAA;AACmD,EAAA;AAC9B,EAAA;AACT,IAAA;AACG,MAAA;AACyB,MAAA;AACP,QAAA;AACZ,QAAA;AACH,QAAA;AACZ,MAAA;AACJ,IAAA;AACF,EAAA;AACF;AAQ2C;AACrC,EAAA;AACE,EAAA;AACC,EAAA;AACD,EAAA;AACI,EAAA;AACH,EAAA;AACC,EAAA;AACK,EAAA;AACD,EAAA;AACd;AAG6C;AAC5B,EAAA;AACT,EAAA;AACF,EAAA;AACG,EAAA;AACE,EAAA;AACC,EAAA;AACZ;AAkBqB;AACc,EAAA;AACD,EAAA;AACS,EAAA;AAI1B,EAAA;AACV,IAAA;AAC6C,IAAA;AACtC,IAAA;AACZ,EAAA;AAEuB,EAAA;AACkC,IAAA;AAClC,IAAA;AACT,MAAA;AACR,QAAA;AACoC,QAAA;AACP,UAAA;AACZ,UAAA;AACH,UAAA;AACZ,QAAA;AACJ,MAAA;AACF,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAU0F;AACzC,EAAA;AACD,IAAA;AACrB,MAAA;AACtB,IAAA;AACH,EAAA;AACkB,EAAA;AACe,EAAA;AACa,IAAA;AAC1C,MAAA;AACmB,QAAA;AACR,QAAA;AACH,QAAA;AACR,MAAA;AACD,IAAA;AACH,EAAA;AAC0D,EAAA;AACrC,EAAA;AACT,IAAA;AACR,MAAA;AACoC,MAAA;AACP,QAAA;AACZ,QAAA;AACH,QAAA;AACZ,MAAA;AACJ,IAAA;AACF,EAAA;AAC2C,EAAA;AACrC,IAAA;AACiB,MAAA;AACL,IAAA;AAC4B,MAAA;AAC9B,QAAA;AACR,UAAA;AAC6B,UAAA;AACxB,YAAA;AACyC,YAAA;AAC5C,UAAA;AACJ,QAAA;AACF,MAAA;AACM,MAAA;AACR,IAAA;AACD,EAAA;AACH;AHL4D;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/index.cjs","sourcesContent":[null,"/**\n * Seeded, deterministic shuffling — the ONE algorithm every presentation-order\n * decision in the SDK goes through: option order in a multiple-choice item,\n * item order inside an item group, entry order in a sequence. A server and a\n * client holding the same seed derive the same order, and an order recorded\n * against an attempt can be rebuilt later from nothing but its seed.\n *\n * STABILITY: the hash and the generator are part of the wire contract. A\n * stored attempt may hold only a seed and rely on this function to reproduce\n * the order it presented; changing either would silently re-order every\n * recorded attempt. Any change here is a package major, and the pinned\n * permutation test exists so an accidental one fails loudly.\n */\n\n/** FNV-1a hash of a string → unsigned 32-bit integer. */\nexport function hashSeed(input: string): number {\n let h = 2166136261 >>> 0;\n for (let i = 0; i < input.length; i += 1) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 16777619) >>> 0;\n }\n return h;\n}\n\n/**\n * Which draw the Fisher–Yates step uses.\n *\n * - `1` — the original. Takes the index from the LCG's LOW bits (`s % (i+1)`).\n * - `2` — takes it from the HIGH bits. Same LCG, same seed, different draw.\n *\n * The difference is not cosmetic. In a linear congruential generator with a\n * power-of-two modulus, the low bits have drastically short periods: bit 0\n * alternates, bit 1 has period 4, and so on. `% (i+1)` reads exactly those\n * bits, so consecutive draws are correlated and most permutations become\n * unreachable FOR EVERY SEED THAT WILL EVER EXIST:\n *\n * | items | permutations | reachable under v1 | under v2 |\n * | ----- | ------------ | ------------------ | -------- |\n * | 4 | 24 | 12 | 24 |\n * | 5 | 120 | 60 | 120 |\n * | 6 | 720 | 180 | 720 |\n *\n * It also biases WHERE an option lands. On a four-option item under v1 the\n * last authored option takes the first presented position 8.3% of the time\n * and the second position 41.7%, against 25% each — a systematic advantage to\n * anyone who notices. For a summative exam that is a fairness defect, not a\n * curiosity.\n *\n * v1 remains the DEFAULT regardless, because these permutations are a wire\n * contract: an attempt may be stored with nothing but its seed, and a review\n * render of that attempt has to reproduce the order the learner actually saw.\n * Switching the default would silently re-order every recorded attempt, so it\n * is a package major — scheduled, not smuggled in. Choose v2 for new content\n * where no attempt has been recorded yet.\n */\nexport type ShuffleVersion = 1 | 2;\n\n/** Options for {@link seededShuffle}. */\nexport interface SeededShuffleOptions {\n /** Draw algorithm. Defaults to `1` — see {@link ShuffleVersion}. */\n version?: ShuffleVersion;\n}\n\n/** LCG-driven Fisher–Yates over a 32-bit seed; returns a permutation (no loss, no duplicate). */\nfunction shuffleWithSeed<T>(items: readonly T[], seed: number, version: ShuffleVersion): T[] {\n const out = [...items];\n let s = seed >>> 0 || 1;\n for (let i = out.length - 1; i > 0; i -= 1) {\n s = (Math.imul(s, 1664525) + 1013904223) >>> 0;\n // v1 reads the low bits; v2 scales the whole 32-bit word, which uses the\n // high bits and reaches every permutation.\n const j = version === 2 ? Math.floor((s / 4294967296) * (i + 1)) : s % (i + 1);\n [out[i], out[j]] = [out[j] as T, out[i] as T];\n }\n return out;\n}\n\n/**\n * Returns a new array holding a permutation of `items` determined entirely by\n * `seed` (and the chosen {@link ShuffleVersion}): same inputs, same order —\n * across processes, runtimes and releases. The input is never mutated.\n *\n * Compose the seed from the attempt identity AND the thing being shuffled\n * (`${attemptId}:${itemId}`), so two shuffles in one attempt do not share an\n * order.\n */\nexport function seededShuffle<T>(\n items: readonly T[],\n seed: string,\n options: SeededShuffleOptions = {},\n): T[] {\n return shuffleWithSeed(items, hashSeed(seed), options.version ?? 1);\n}\n","import { seededShuffle } from './shuffle.js';\nimport type { ItemGroup, SequenceEntry, SequenceSlot } from './types/item-group.js';\n\n/** Narrows a sequence entry to an item group. */\nexport function isItemGroup<TItem extends { type: string }>(\n entry: SequenceEntry<TItem>,\n): entry is ItemGroup<TItem> {\n return entry.type === 'item-group';\n}\n\n/** Options for {@link flattenSequence}. */\nexport interface FlattenSequenceOptions {\n /**\n * Shuffle the top-level entries. A group moves as ONE block — its items are\n * never interleaved with other entries — which is the reason a group exists\n * as a container rather than as a flag on each item.\n */\n shuffleEntries?: boolean;\n /**\n * Seed for every shuffle in this call: the entries, and each group whose\n * `shuffle` is `within-group`. REQUIRED whenever anything shuffles. The SDK\n * never invents one, because the server that stores an attempt and the\n * client that renders it must derive the SAME order, and only a shared seed\n * makes that true. The attempt id is the natural choice.\n */\n seed?: string;\n}\n\n/**\n * Turns a sequence definition — loose activities and item groups, in authored\n * order — into the ordered list of slots to present. Pure and deterministic:\n * call it on the server to record an attempt's order, and on the client to\n * render it, and the two agree.\n *\n * Shuffling is opt-in and seeded. Groups are shuffle-atomic: with\n * `shuffleEntries` a group changes position but stays one contiguous block,\n * and only a group that declares `shuffle: 'within-group'` has its items\n * reordered. Every slot carries a `slotId` derived from the authored\n * position, so identities survive shuffling.\n *\n * @throws Error when a shuffle is requested without a `seed`.\n */\nexport function flattenSequence<TItem extends { id: string; type: string }>(\n entries: readonly SequenceEntry<TItem>[],\n options: FlattenSequenceOptions = {},\n): SequenceSlot<TItem>[] {\n const shuffleEntries = options.shuffleEntries === true;\n const needsSeed =\n shuffleEntries ||\n entries.some((entry) => isItemGroup(entry) && entry.shuffle === 'within-group');\n if (needsSeed && options.seed === undefined) {\n throw new Error(\n 'flattenSequence: a seed is required when shuffling (shuffleEntries, or a group with ' +\n 'shuffle: \"within-group\"). Pass the attempt id so the server and the client derive the same order.',\n );\n }\n const seed = options.seed ?? '';\n\n // Carry the AUTHORED index through the shuffle: slot identity comes from\n // where an entry was written, never from where it happens to be shown.\n const authored = entries.map((entry, entryIndex) => ({ entry, entryIndex }));\n const ordered = shuffleEntries ? seededShuffle(authored, `${seed}:entries`) : authored;\n\n const slots: SequenceSlot<TItem>[] = [];\n for (const { entry, entryIndex } of ordered) {\n if (!isItemGroup(entry)) {\n slots.push({ slotId: String(entryIndex), index: slots.length, activity: entry });\n continue;\n }\n // An empty group contributes no slots, so it would DISAPPEAR — stimulus,\n // questions and all — from a sequence that still looks well-formed. A\n // listening section filtered to nothing upstream would leave an 18-slot\n // paper presented as 12 slots, and `composeAssessmentScore` would then\n // report a `final` grade over the survivors with nothing pending. The\n // type already declares `items` non-empty; this makes that enforceable at\n // the point where the omission would otherwise become invisible.\n if (entry.items.length === 0) {\n throw new Error(\n `flattenSequence: item group \"${entry.id}\" has no items. An empty group would silently ` +\n 'remove its stimulus and its questions from the presented sequence.',\n );\n }\n const items = entry.items.map((item, itemIndex) => ({ item, itemIndex }));\n const presented =\n entry.shuffle === 'within-group' ? seededShuffle(items, `${seed}:group:${entry.id}`) : items;\n const size = presented.length;\n presented.forEach(({ item, itemIndex }, position) => {\n slots.push({\n slotId: `${entryIndex}.${itemIndex}`,\n index: slots.length,\n activity: item,\n group: {\n id: entry.id,\n ...(entry.title !== undefined ? { title: entry.title } : {}),\n stimulus: entry.stimulus,\n position,\n size,\n },\n });\n });\n }\n return slots;\n}\n","import { ActivitySchemaError, UnknownActivityTypeError } from './errors.js';\nimport { getActivityTypeDescriptor } from './registry/index.js';\nimport type { FieldPolicy, Sensitivity } from './registry/registry.js';\nimport { RedactedItemGroupSchema } from './schemas/item-group.js';\nimport type { ItemGroup } from './types/item-group.js';\n\n/**\n * A learner-safe projection of activity data produced by {@link redact}:\n * the activity's public fields plus the `redacted: true` marker. Renderable\n * and validatable (each built-in type registers a strict redacted schema),\n * but stripped of the answer key, scoring rules, authored feedback, and\n * author-only assets.\n */\nexport interface RedactedActivityData {\n redacted: true;\n schemaVersion: string;\n type: string;\n id: string;\n title: string;\n [key: string]: unknown;\n}\n\n/** Options for {@link redact}. */\nexport interface RedactOptions {\n /**\n * Which sensitivity tiers to keep beyond `public`:\n * - `'none'` (default) — public fields only; the output satisfies the\n * type's strict redacted schema and `assertRedacted`.\n * - `'after-submit'` — public + answer-key fields (for post-submission\n * review renders). `author-only` fields and unclassified fields are\n * STILL removed; the output will NOT pass `assertRedacted`.\n */\n reveal?: 'none' | 'after-submit';\n /**\n * Per-call sensitivity overrides, merged over the type's registered\n * `fieldPolicy` (top-level keys replace; nested objects merge one level).\n *\n * Sensitivity is partly a PEDAGOGICAL decision, not purely a security one —\n * whether a rubric or a hint is learner-visible differs legitimately between\n * deployments — and the SDK must not freeze that choice. Use this to tighten\n * a field the SDK ships as `public`:\n *\n * ```ts\n * redact(essay, { policy: { rubric: 'author-only' } });\n * ```\n *\n * Overrides can only be applied to fields; they cannot re-open a field the\n * caller has not classified, because unclassified still means removed.\n * Note that tightening below what the type's `redactedSchema` requires is\n * allowed — the schema check only rejects payloads that reveal MORE than\n * the learner-safe shape.\n */\n policy?: FieldPolicy;\n}\n\n/** Merges per-call overrides over a registered policy, one level deep. */\nfunction mergePolicy(base: FieldPolicy, overrides: FieldPolicy | undefined): FieldPolicy {\n if (overrides === undefined) {\n return base;\n }\n const merged: Record<string, Sensitivity | FieldPolicy> = { ...base };\n for (const [key, override] of Object.entries(overrides)) {\n const current = merged[key];\n merged[key] =\n typeof override === 'object' && typeof current === 'object'\n ? { ...current, ...override }\n : override;\n }\n return merged;\n}\n\nfunction isSensitivity(value: Sensitivity | FieldPolicy): value is Sensitivity {\n return typeof value === 'string';\n}\n\nfunction keepField(sensitivity: Sensitivity, reveal: 'none' | 'after-submit'): boolean {\n if (sensitivity === 'public') {\n return true;\n }\n return sensitivity === 'answer-key' && reveal === 'after-submit';\n}\n\nfunction redactValue(\n value: unknown,\n policy: FieldPolicy,\n reveal: 'none' | 'after-submit',\n): unknown {\n if (Array.isArray(value)) {\n return value.map((element) => redactValue(element, policy, reveal));\n }\n if (value === null || typeof value !== 'object') {\n // A nested FieldPolicy cannot classify a primitive's sub-fields; the\n // policy author classified an object shape that is not there. Fail\n // closed: drop it (handled by the caller returning undefined).\n return undefined;\n }\n\n const source = value as Record<string, unknown>;\n const output: Record<string, unknown> = {};\n for (const [key, fieldValue] of Object.entries(source)) {\n if (fieldValue === undefined) {\n continue;\n }\n const classification = policy[key];\n if (classification === undefined) {\n // Fail closed: unclassified fields are never emitted, at any reveal\n // level. Adding a field without classifying it hides it — never leaks it.\n continue;\n }\n if (isSensitivity(classification)) {\n if (keepField(classification, reveal)) {\n output[key] = fieldValue;\n }\n continue;\n }\n const nested = redactValue(fieldValue, classification, reveal);\n if (nested !== undefined) {\n output[key] = nested;\n }\n }\n return output;\n}\n\n/**\n * Produces the learner-safe projection of activity data (R7), driven by the\n * `fieldPolicy` registered for `data.type`. Fail-closed and exhaustive by\n * construction: a field the policy does not classify is removed — including\n * unknown passthrough fields — so a NEW field added by a future SDK or a\n * consumer sidecar can never leak through an out-of-date redactor.\n *\n * With the default `reveal: 'none'`, the output validates against the type's\n * strict redacted schema (verified here; an invalid projection throws\n * {@link ActivitySchemaError} rather than shipping an unproven payload).\n *\n * @throws UnknownActivityTypeError when `data.type` is not registered.\n * @throws Error when the registered descriptor declares no `fieldPolicy`\n * (redaction cannot guess sensitivities).\n */\nexport function redact<T extends { type: string }>(\n data: T,\n options: RedactOptions = {},\n): RedactedActivityData {\n const reveal = options.reveal ?? 'none';\n const descriptor = getActivityTypeDescriptor(data.type);\n if (descriptor === undefined) {\n throw new UnknownActivityTypeError(String(data.type));\n }\n if (descriptor.fieldPolicy === undefined) {\n throw new Error(\n `Activity type \"${descriptor.type}\" has no fieldPolicy; redact() cannot run fail-closed redaction without one.`,\n );\n }\n\n const effectivePolicy = mergePolicy(descriptor.fieldPolicy, options.policy);\n const projected = redactValue(data, effectivePolicy, reveal) as Record<string, unknown>;\n const result = { ...projected, redacted: true as const } as RedactedActivityData;\n\n if (reveal === 'none' && descriptor.redactedSchema !== undefined) {\n const parsed = descriptor.redactedSchema.safeParse(result);\n if (!parsed.success) {\n throw new ActivitySchemaError(\n descriptor.type,\n parsed.error.issues.map((issue) => ({\n path: issue.path.map(String),\n message: issue.message,\n code: issue.code,\n })),\n );\n }\n }\n\n return result;\n}\n\n/**\n * Asserts that `data` is a learner-safe redacted projection: it carries the\n * `redacted: true` marker and, when its type registers a strict redacted\n * schema, validates against it (proving the absence of answer-key fields).\n * Use this at the server boundary before sending activity data to a client\n * that must not hold the key.\n */\nexport function assertRedacted(data: unknown): asserts data is RedactedActivityData {\n if (typeof data !== 'object' || data === null) {\n throw new ActivitySchemaError('unknown', [\n { path: [], message: 'Redacted activity data must be an object.', code: 'invalid_type' },\n ]);\n }\n const candidate = data as Record<string, unknown>;\n if (candidate.redacted !== true) {\n throw new ActivitySchemaError(String(candidate.type ?? 'unknown'), [\n {\n path: ['redacted'],\n message: 'Missing redacted marker — this payload is not a redact() projection.',\n code: 'custom',\n },\n ]);\n }\n const type = typeof candidate.type === 'string' ? candidate.type : '';\n const descriptor = getActivityTypeDescriptor(type);\n if (descriptor === undefined) {\n throw new UnknownActivityTypeError(type);\n }\n if (descriptor.redactedSchema === undefined) {\n // Fail closed: without a registered redacted schema there is no way to\n // PROVE the absence of answer-key fields, and a marker alone proves\n // nothing (any object can carry `redacted: true`).\n throw new ActivitySchemaError(descriptor.type, [\n {\n path: [],\n message: `Activity type \"${descriptor.type}\" registers no redactedSchema; assertRedacted cannot prove this payload is learner-safe.`,\n code: 'custom',\n },\n ]);\n }\n const parsed = descriptor.redactedSchema.safeParse(candidate);\n if (!parsed.success) {\n throw new ActivitySchemaError(\n descriptor.type,\n parsed.error.issues.map((issue) => ({\n path: issue.path.map(String),\n message: issue.message,\n code: issue.code,\n })),\n );\n }\n}\n\n/**\n * Sensitivity of a stimulus. Everything is learner-visible — that is what a\n * stimulus IS — except the author transcript. Fail-closed like every other\n * policy: a field added to `Stimulus` without a classification here is\n * dropped, never leaked.\n */\nconst STIMULUS_FIELD_POLICY: FieldPolicy = {\n id: 'public',\n kind: 'public',\n title: 'public',\n body: 'public',\n bodyHtml: 'public',\n media: 'public',\n locale: 'public',\n attribution: 'public',\n transcript: 'author-only',\n};\n\n/** The group container's own fields. `items` is handled separately, per item type. */\nconst ITEM_GROUP_FIELD_POLICY: FieldPolicy = {\n schemaVersion: 'public',\n type: 'public',\n id: 'public',\n title: 'public',\n shuffle: 'public',\n stimulus: STIMULUS_FIELD_POLICY,\n};\n\n/** A learner-safe item group: the container with its marker, holding `redact()` projections. */\nexport type RedactedItemGroup = ItemGroup<RedactedActivityData> & { redacted: true };\n\n/**\n * Produces the learner-safe projection of an item group: the container and\n * stimulus under their own fail-closed policy (the author transcript goes;\n * the passage, media and attribution stay — the learner is meant to see\n * them), and every item through {@link redact} with the same `options`, so\n * a per-call `policy` tightens each item exactly as it would alone.\n *\n * With the default `reveal: 'none'` the container is verified against the\n * strict redacted schema; each item was already verified by `redact`.\n */\nexport function redactItemGroup<TItem extends { type: string }>(\n group: ItemGroup<TItem>,\n options: RedactOptions = {},\n): RedactedItemGroup {\n const reveal = options.reveal ?? 'none';\n const { items, ...container } = group;\n const projected = redactValue(container, ITEM_GROUP_FIELD_POLICY, reveal) as Record<\n string,\n unknown\n >;\n const result = {\n ...projected,\n items: items.map((item) => redact(item, options)),\n redacted: true as const,\n } as RedactedItemGroup;\n\n if (reveal === 'none') {\n const parsed = RedactedItemGroupSchema.safeParse(result);\n if (!parsed.success) {\n throw new ActivitySchemaError(\n 'item-group',\n parsed.error.issues.map((issue) => ({\n path: issue.path.map(String),\n message: issue.message,\n code: issue.code,\n })),\n );\n }\n }\n return result;\n}\n\n/**\n * Asserts that `data` is a learner-safe item group: it carries the marker,\n * its container and stimulus satisfy the strict redacted schema (so no\n * transcript, no unclassified field), and EVERY item passes\n * {@link assertRedacted} against its own type's redacted schema. Item\n * failures are reported at `items.<index>.…`. Use it at the server boundary\n * before sending a group to a client that must not hold the key.\n */\nexport function assertRedactedItemGroup(data: unknown): asserts data is RedactedItemGroup {\n if (typeof data !== 'object' || data === null) {\n throw new ActivitySchemaError('item-group', [\n { path: [], message: 'Redacted item group must be an object.', code: 'invalid_type' },\n ]);\n }\n const candidate = data as Record<string, unknown>;\n if (candidate.redacted !== true) {\n throw new ActivitySchemaError('item-group', [\n {\n path: ['redacted'],\n message: 'Missing redacted marker — this payload is not a redactItemGroup() projection.',\n code: 'custom',\n },\n ]);\n }\n const parsed = RedactedItemGroupSchema.safeParse(candidate);\n if (!parsed.success) {\n throw new ActivitySchemaError(\n 'item-group',\n parsed.error.issues.map((issue) => ({\n path: issue.path.map(String),\n message: issue.message,\n code: issue.code,\n })),\n );\n }\n parsed.data.items.forEach((item, index) => {\n try {\n assertRedacted(item);\n } catch (error) {\n if (error instanceof ActivitySchemaError) {\n throw new ActivitySchemaError(\n 'item-group',\n error.errors.map((issue) => ({\n ...issue,\n path: ['items', String(index), ...issue.path],\n })),\n );\n }\n throw error;\n }\n });\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,9 +1,10 @@
1
- import { V as ValidationError, m as ScoringResult, D as DeferredScoringPartial, F as FillInTheBlanksData, f as FillInTheBlanksLearnerResponse, M as MultipleChoiceData, j as MultipleChoiceLearnerResponse, W as WrittenResponseData, p as WrittenResponseLearnerResponse } from './activity-zdcAMtFB.cjs';
2
- export { A as ActivityData, a as ActivityDataMap, b as ActivityFeedback, c as ActivityMedia, d as ActivityResult, e as ActivityType, B as BlankConfig, I as InteractionEvent, g as InteractionKind, h as ItemOutcome, L as LearnerResponse, i as LearnerResponseMap, k as MultipleChoiceOption, S as ScoringDetail, l as ScoringOutcome, T as TextMatchPolicy, n as TextMatchResult, o as ValidationResult, q as WrittenResponseRubric, r as WrittenResponseRubricCriterion, X as XAPIActor, s as XAPIConfig, t as XAPIContext, u as XAPIContextActivities, v as XAPIError, w as XAPIObject, x as XAPIResult, y as XAPIScore, z as XAPIStatement, C as XAPIVerbObject, E as levenshteinDistance, G as matchText } from './activity-zdcAMtFB.cjs';
1
+ import { V as ValidationError, C as CriterionScore, A as ActivityData, G as GradeRecord, m as ItemOutcome, r as ScoringResult, D as DeferredScoringPartial, F as FillInTheBlanksData, f as FillInTheBlanksLearnerResponse, M as MultipleChoiceData, o as MultipleChoiceLearnerResponse, W as WrittenResponseData, u as WrittenResponseLearnerResponse } from './activity-wkzRemHx.cjs';
2
+ export { a as ActivityDataMap, b as ActivityFeedback, c as ActivityMedia, d as ActivityResult, e as ActivityType, B as BlankConfig, g as Grader, h as GraderKind, i as GraderUsage, j as GradingState, I as InlineCorrection, k as InteractionEvent, l as InteractionKind, L as LearnerResponse, n as LearnerResponseMap, p as MultipleChoiceOption, S as ScoringDetail, q as ScoringOutcome, T as TextMatchPolicy, s as TextMatchResult, t as ValidationResult, v as WrittenResponseRubric, w as WrittenResponseRubricCriterion, X as XAPIActor, x as XAPIConfig, y as XAPIContext, z as XAPIContextActivities, E as XAPIError, H as XAPIObject, J as XAPIResult, K as XAPIScore, N as XAPIStatement, O as XAPIVerbObject, P as levenshteinDistance, Q as matchText } from './activity-wkzRemHx.cjs';
3
+ import { S as SequenceEntry, l as SequenceSlot, I as ItemGroup } from './index-BKyZrd94.cjs';
4
+ export { B as BlankConfigSchema, F as FeedbackSchema, a as FillInTheBlanksDataSchema, b as ItemGroupSchema, M as MediaSchema, c as MediaUrlSchema, d as MultipleChoiceDataSchema, e as MultipleChoiceOptionSchema, R as RedactedBlankConfigSchema, f as RedactedFillInTheBlanksDataSchema, g as RedactedItemGroupSchema, h as RedactedMultipleChoiceDataSchema, i as RedactedMultipleChoiceOptionSchema, j as RedactedStimulusSchema, k as RedactedWrittenResponseDataSchema, m as SequenceSlotGroup, n as Stimulus, o as StimulusKind, p as StimulusSchema, T as TextMatchPolicySchema, W as WrittenResponseDataSchema, q as WrittenResponseRubricCriterionSchema, r as WrittenResponseRubricSchema, s as fillInTheBlanksJsonSchema, t as itemGroupJsonSchema, u as jsonSchemaFor, v as multipleChoiceJsonSchema, w as stimulusJsonSchema, x as validateActivity, y as validateItemGroup, z as writtenResponseJsonSchema } from './index-BKyZrd94.cjs';
3
5
  import { z } from 'zod/v4';
4
- export { BlankConfigSchema, FeedbackSchema, FillInTheBlanksDataSchema, MediaSchema, MediaUrlSchema, MultipleChoiceDataSchema, MultipleChoiceOptionSchema, RedactedBlankConfigSchema, RedactedFillInTheBlanksDataSchema, RedactedMultipleChoiceDataSchema, RedactedMultipleChoiceOptionSchema, RedactedWrittenResponseDataSchema, TextMatchPolicySchema, WrittenResponseDataSchema, WrittenResponseRubricCriterionSchema, WrittenResponseRubricSchema, fillInTheBlanksJsonSchema, jsonSchemaFor, multipleChoiceJsonSchema, validateActivity, writtenResponseJsonSchema } from './schemas.cjs';
5
- export { DEFAULT_PASS_THRESHOLD, computePassThreshold, evaluate, score } from './scoring.cjs';
6
- export { AnsweredStatementParams, CompletedStatementParams, SubmittedStatementParams, XAPIObjectParams, XAPIStatementParams, XAPIVerb, XAPIVerbKey, XAPI_VERB_DISPLAY, validateXAPIStatement, xAPIBuilder } from './xapi.cjs';
6
+ export { AssessmentScore, AssessmentSectionInput, Band, CompositionPolicy, DEFAULT_PASS_THRESHOLD, PassFailureReason, RoundingMode, RoundingPolicy, ScoredItem, SectionScore, classifyBand, composeAssessmentScore, computePassThreshold, evaluate, gte, roundGrade, score } from './scoring.cjs';
7
+ export { AnsweredStatementParams, CompletedStatementParams, SubmittedStatementParams, XAPIObjectParams, XAPIStatementParams, XAPIVerb, XAPIVerbKey, XAPI_VERB_DISPLAY, validateXAPIStatement, xAPIBuilder, xapiDefinitionFor } from './xapi.cjs';
7
8
 
8
9
  /**
9
10
  * Canonical word counter for written-response bounds (Req 22.9): tokens are
@@ -49,6 +50,92 @@ declare class DeferredScoringError extends Error {
49
50
  constructor(activityType: string);
50
51
  }
51
52
 
53
+ /** Options for {@link gradeFromRubric}. */
54
+ interface GradeFromRubricOptions {
55
+ /**
56
+ * Scaled score [0,1] required to pass. Defaults to the activity's
57
+ * `passThreshold`, or {@link DEFAULT_PASS_THRESHOLD} when absent.
58
+ */
59
+ passThreshold?: number;
60
+ /** Narrative feedback for the learner, carried onto the record verbatim. */
61
+ feedback?: string | null;
62
+ }
63
+ /**
64
+ * Computes the weighted total from per-criterion scores — the arithmetic that
65
+ * must NOT be delegated to a language model.
66
+ *
67
+ * A grader is asked for judgement (how good is this criterion?), not for
68
+ * mental arithmetic. Asking a model to also produce the weighted total makes
69
+ * the final grade unverifiable and unreproducible: two runs can return
70
+ * different totals for identical criterion scores. This function makes the
71
+ * total a pure function of the judgements, so a grade can be recomputed and
72
+ * audited years later.
73
+ *
74
+ * Weights are normalised by their sum, so they need not add to 1. Criteria
75
+ * marked `notApplicable`, and those carrying no numeric `score` (a purely
76
+ * banded judgement), are excluded from both numerator and denominator. When
77
+ * nothing scoreable remains the result is `unscorable`, never a zero.
78
+ */
79
+ declare function gradeFromRubric(criteria: readonly CriterionScore[], activityData?: ActivityData, options?: GradeFromRubricOptions): GradeRecord | {
80
+ unscorable: true;
81
+ reason: string;
82
+ };
83
+ /**
84
+ * Lifts a {@link GradeRecord} into the `graded` arm of {@link ItemOutcome}, so
85
+ * a grade that arrived asynchronously renders through the same path as a
86
+ * synchronously scored item. The score/passed/feedback fields are mirrored
87
+ * onto the outcome for uniform reads; `grade` carries the full record.
88
+ */
89
+ declare function outcomeFromGrade(grade: GradeRecord): ItemOutcome;
90
+ /**
91
+ * True when an outcome carries a real grade — either the SDK scored it
92
+ * synchronously or a grader returned one. Use this instead of testing
93
+ * `status === 'scored'`, which silently misses asynchronously graded work.
94
+ */
95
+ declare function hasGrade(outcome: ItemOutcome): outcome is Extract<ItemOutcome, {
96
+ status: 'scored' | 'graded';
97
+ }>;
98
+
99
+ /** Narrows a sequence entry to an item group. */
100
+ declare function isItemGroup<TItem extends {
101
+ type: string;
102
+ }>(entry: SequenceEntry<TItem>): entry is ItemGroup<TItem>;
103
+ /** Options for {@link flattenSequence}. */
104
+ interface FlattenSequenceOptions {
105
+ /**
106
+ * Shuffle the top-level entries. A group moves as ONE block — its items are
107
+ * never interleaved with other entries — which is the reason a group exists
108
+ * as a container rather than as a flag on each item.
109
+ */
110
+ shuffleEntries?: boolean;
111
+ /**
112
+ * Seed for every shuffle in this call: the entries, and each group whose
113
+ * `shuffle` is `within-group`. REQUIRED whenever anything shuffles. The SDK
114
+ * never invents one, because the server that stores an attempt and the
115
+ * client that renders it must derive the SAME order, and only a shared seed
116
+ * makes that true. The attempt id is the natural choice.
117
+ */
118
+ seed?: string;
119
+ }
120
+ /**
121
+ * Turns a sequence definition — loose activities and item groups, in authored
122
+ * order — into the ordered list of slots to present. Pure and deterministic:
123
+ * call it on the server to record an attempt's order, and on the client to
124
+ * render it, and the two agree.
125
+ *
126
+ * Shuffling is opt-in and seeded. Groups are shuffle-atomic: with
127
+ * `shuffleEntries` a group changes position but stays one contiguous block,
128
+ * and only a group that declares `shuffle: 'within-group'` has its items
129
+ * reordered. Every slot carries a `slotId` derived from the authored
130
+ * position, so identities survive shuffling.
131
+ *
132
+ * @throws Error when a shuffle is requested without a `seed`.
133
+ */
134
+ declare function flattenSequence<TItem extends {
135
+ id: string;
136
+ type: string;
137
+ }>(entries: readonly SequenceEntry<TItem>[], options?: FlattenSequenceOptions): SequenceSlot<TItem>[];
138
+
52
139
  /** ScoringResult without `passed` — the public `score()` / `evaluate()` fill that in. */
53
140
  type PartialScoringResult = Omit<ScoringResult, 'passed'>;
54
141
  /**
@@ -249,6 +336,32 @@ declare function redact<T extends {
249
336
  * that must not hold the key.
250
337
  */
251
338
  declare function assertRedacted(data: unknown): asserts data is RedactedActivityData;
339
+ /** A learner-safe item group: the container with its marker, holding `redact()` projections. */
340
+ type RedactedItemGroup = ItemGroup<RedactedActivityData> & {
341
+ redacted: true;
342
+ };
343
+ /**
344
+ * Produces the learner-safe projection of an item group: the container and
345
+ * stimulus under their own fail-closed policy (the author transcript goes;
346
+ * the passage, media and attribution stay — the learner is meant to see
347
+ * them), and every item through {@link redact} with the same `options`, so
348
+ * a per-call `policy` tightens each item exactly as it would alone.
349
+ *
350
+ * With the default `reveal: 'none'` the container is verified against the
351
+ * strict redacted schema; each item was already verified by `redact`.
352
+ */
353
+ declare function redactItemGroup<TItem extends {
354
+ type: string;
355
+ }>(group: ItemGroup<TItem>, options?: RedactOptions): RedactedItemGroup;
356
+ /**
357
+ * Asserts that `data` is a learner-safe item group: it carries the marker,
358
+ * its container and stimulus satisfy the strict redacted schema (so no
359
+ * transcript, no unclassified field), and EVERY item passes
360
+ * {@link assertRedacted} against its own type's redacted schema. Item
361
+ * failures are reported at `items.<index>.…`. Use it at the server boundary
362
+ * before sending a group to a client that must not hold the key.
363
+ */
364
+ declare function assertRedactedItemGroup(data: unknown): asserts data is RedactedItemGroup;
252
365
 
253
366
  /** Built-in Multiple Choice descriptor. */
254
367
  declare const multipleChoiceType: ActivityTypeDescriptor<MultipleChoiceData, MultipleChoiceLearnerResponse>;
@@ -263,6 +376,69 @@ declare const fillInTheBlanksType: ActivityTypeDescriptor<FillInTheBlanksData, F
263
376
  */
264
377
  declare const writtenResponseType: ActivityTypeDescriptor<WrittenResponseData, WrittenResponseLearnerResponse>;
265
378
 
379
+ /**
380
+ * Seeded, deterministic shuffling — the ONE algorithm every presentation-order
381
+ * decision in the SDK goes through: option order in a multiple-choice item,
382
+ * item order inside an item group, entry order in a sequence. A server and a
383
+ * client holding the same seed derive the same order, and an order recorded
384
+ * against an attempt can be rebuilt later from nothing but its seed.
385
+ *
386
+ * STABILITY: the hash and the generator are part of the wire contract. A
387
+ * stored attempt may hold only a seed and rely on this function to reproduce
388
+ * the order it presented; changing either would silently re-order every
389
+ * recorded attempt. Any change here is a package major, and the pinned
390
+ * permutation test exists so an accidental one fails loudly.
391
+ */
392
+ /** FNV-1a hash of a string → unsigned 32-bit integer. */
393
+ declare function hashSeed(input: string): number;
394
+ /**
395
+ * Which draw the Fisher–Yates step uses.
396
+ *
397
+ * - `1` — the original. Takes the index from the LCG's LOW bits (`s % (i+1)`).
398
+ * - `2` — takes it from the HIGH bits. Same LCG, same seed, different draw.
399
+ *
400
+ * The difference is not cosmetic. In a linear congruential generator with a
401
+ * power-of-two modulus, the low bits have drastically short periods: bit 0
402
+ * alternates, bit 1 has period 4, and so on. `% (i+1)` reads exactly those
403
+ * bits, so consecutive draws are correlated and most permutations become
404
+ * unreachable FOR EVERY SEED THAT WILL EVER EXIST:
405
+ *
406
+ * | items | permutations | reachable under v1 | under v2 |
407
+ * | ----- | ------------ | ------------------ | -------- |
408
+ * | 4 | 24 | 12 | 24 |
409
+ * | 5 | 120 | 60 | 120 |
410
+ * | 6 | 720 | 180 | 720 |
411
+ *
412
+ * It also biases WHERE an option lands. On a four-option item under v1 the
413
+ * last authored option takes the first presented position 8.3% of the time
414
+ * and the second position 41.7%, against 25% each — a systematic advantage to
415
+ * anyone who notices. For a summative exam that is a fairness defect, not a
416
+ * curiosity.
417
+ *
418
+ * v1 remains the DEFAULT regardless, because these permutations are a wire
419
+ * contract: an attempt may be stored with nothing but its seed, and a review
420
+ * render of that attempt has to reproduce the order the learner actually saw.
421
+ * Switching the default would silently re-order every recorded attempt, so it
422
+ * is a package major — scheduled, not smuggled in. Choose v2 for new content
423
+ * where no attempt has been recorded yet.
424
+ */
425
+ type ShuffleVersion = 1 | 2;
426
+ /** Options for {@link seededShuffle}. */
427
+ interface SeededShuffleOptions {
428
+ /** Draw algorithm. Defaults to `1` — see {@link ShuffleVersion}. */
429
+ version?: ShuffleVersion;
430
+ }
431
+ /**
432
+ * Returns a new array holding a permutation of `items` determined entirely by
433
+ * `seed` (and the chosen {@link ShuffleVersion}): same inputs, same order —
434
+ * across processes, runtimes and releases. The input is never mutated.
435
+ *
436
+ * Compose the seed from the attempt identity AND the thing being shuffled
437
+ * (`${attemptId}:${itemId}`), so two shuffles in one attempt do not share an
438
+ * order.
439
+ */
440
+ declare function seededShuffle<T>(items: readonly T[], seed: string, options?: SeededShuffleOptions): T[];
441
+
266
442
  /**
267
443
  * All CSS custom property tokens consumed by learning-kit components.
268
444
  * Every key maps to a `--lk-*` CSS variable name; every value is a CSS value string.
@@ -299,4 +475,4 @@ interface ThemeTokens {
299
475
  '--lk-transition-base': string;
300
476
  }
301
477
 
302
- export { ActivitySchemaError, type ActivityTypeDescriptor, type ActivityTypeInterop, type ActivityTypeScoring, DeferredScoringError, DeferredScoringPartial, type FieldPolicy, FillInTheBlanksData, FillInTheBlanksLearnerResponse, MultipleChoiceData, MultipleChoiceLearnerResponse, type PartialScoringResult, type RedactOptions, type RedactedActivityData, RedactedScoringError, type RegisteredActivityTypeDescriptor, ScoringResult, type Sensitivity, type ThemeTokens, UnknownActivityTypeError, ValidationError, WrittenResponseData, WrittenResponseLearnerResponse, type XAPIInteractionType, assertRedacted, countWords, defineActivityType, fillInTheBlanksType, getActivityTypeDescriptor, multipleChoiceType, redact, registerActivityType, registeredActivityTypes, writtenResponseType };
478
+ export { ActivityData, ActivitySchemaError, type ActivityTypeDescriptor, type ActivityTypeInterop, type ActivityTypeScoring, CriterionScore, DeferredScoringError, DeferredScoringPartial, type FieldPolicy, FillInTheBlanksData, FillInTheBlanksLearnerResponse, type FlattenSequenceOptions, type GradeFromRubricOptions, GradeRecord, ItemGroup, ItemOutcome, MultipleChoiceData, MultipleChoiceLearnerResponse, type PartialScoringResult, type RedactOptions, type RedactedActivityData, type RedactedItemGroup, RedactedScoringError, type RegisteredActivityTypeDescriptor, ScoringResult, type SeededShuffleOptions, type Sensitivity, SequenceEntry, SequenceSlot, type ShuffleVersion, type ThemeTokens, UnknownActivityTypeError, ValidationError, WrittenResponseData, WrittenResponseLearnerResponse, type XAPIInteractionType, assertRedacted, assertRedactedItemGroup, countWords, defineActivityType, fillInTheBlanksType, flattenSequence, getActivityTypeDescriptor, gradeFromRubric, hasGrade, hashSeed, isItemGroup, multipleChoiceType, outcomeFromGrade, redact, redactItemGroup, registerActivityType, registeredActivityTypes, seededShuffle, writtenResponseType };