@happyvertical/smrt-education 0.58.0 → 0.59.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 (53) hide show
  1. package/AGENTS.md +6 -1
  2. package/agents/class-crunchlabs.md +83 -0
  3. package/agents/course-bundle-import.md +108 -18
  4. package/dist/catalog-contracts.d.ts +6 -2
  5. package/dist/catalog-contracts.d.ts.map +1 -1
  6. package/dist/catalog-materialization-service.d.ts.map +1 -1
  7. package/dist/catalog-policy.d.ts +11 -1
  8. package/dist/catalog-policy.d.ts.map +1 -1
  9. package/dist/catalog-preview.d.ts +6 -1
  10. package/dist/catalog-preview.d.ts.map +1 -1
  11. package/dist/chunks/course-authoring-contracts-C4Hqba-e.js +131 -0
  12. package/dist/chunks/course-authoring-contracts-C4Hqba-e.js.map +1 -0
  13. package/dist/chunks/course-bundle-zip-5KUe3C5H.js +323 -0
  14. package/dist/chunks/course-bundle-zip-5KUe3C5H.js.map +1 -0
  15. package/dist/chunks/{workbook-photo-service-COtyQL1y.js → workbook-photo-service-CjcB0y8Y.js} +226 -130
  16. package/dist/chunks/workbook-photo-service-CjcB0y8Y.js.map +1 -0
  17. package/dist/course-authoring-contracts.d.ts +1 -1
  18. package/dist/course-authoring-contracts.d.ts.map +1 -1
  19. package/dist/course-bundle-contracts.d.ts +21 -3
  20. package/dist/course-bundle-contracts.d.ts.map +1 -1
  21. package/dist/course-bundle-manifest-util.d.ts +15 -0
  22. package/dist/course-bundle-manifest-util.d.ts.map +1 -0
  23. package/dist/course-bundle-manifest-v2.d.ts +66 -0
  24. package/dist/course-bundle-manifest-v2.d.ts.map +1 -0
  25. package/dist/course-bundle-models.d.ts +29 -1
  26. package/dist/course-bundle-models.d.ts.map +1 -1
  27. package/dist/course-bundle-service.d.ts +104 -5
  28. package/dist/course-bundle-service.d.ts.map +1 -1
  29. package/dist/course-bundle.d.ts +81 -2
  30. package/dist/course-bundle.d.ts.map +1 -1
  31. package/dist/document-converter.d.ts +30 -18
  32. package/dist/document-converter.d.ts.map +1 -1
  33. package/dist/hearth-catalog-providers.d.ts +1 -1
  34. package/dist/hearth-catalog-providers.d.ts.map +1 -1
  35. package/dist/hearth-class-crunchlabs.d.ts +94 -0
  36. package/dist/hearth-class-crunchlabs.d.ts.map +1 -0
  37. package/dist/index.d.ts +5 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1633 -399
  40. package/dist/index.js.map +1 -1
  41. package/dist/ingestion.js +1 -1
  42. package/dist/manifest.json +418 -6
  43. package/dist/node.d.ts +3 -0
  44. package/dist/node.d.ts.map +1 -0
  45. package/dist/node.js +134 -0
  46. package/dist/node.js.map +1 -0
  47. package/dist/smrt-knowledge.json +162 -10
  48. package/dist/sources/class-crunchlabs-files.d.ts +8 -0
  49. package/dist/sources/class-crunchlabs-files.d.ts.map +1 -0
  50. package/dist/sources/class-crunchlabs.d.ts +217 -0
  51. package/dist/sources/class-crunchlabs.d.ts.map +1 -0
  52. package/package.json +12 -8
  53. package/dist/chunks/workbook-photo-service-COtyQL1y.js.map +0 -1
@@ -0,0 +1,323 @@
1
+ import { o as CourseAuthoringError } from "./course-authoring-contracts-C4Hqba-e.js";
2
+ //#region src/course-bundle-contracts.ts
3
+ var KiB = 1024;
4
+ var MiB = 1024 * KiB;
5
+ var COURSE_BUNDLE_LIMITS = Object.freeze({
6
+ maxArchiveBytes: 100 * MiB,
7
+ maxEntries: 200,
8
+ maxEntryBytes: 50 * MiB,
9
+ maxTotalBytes: 200 * MiB,
10
+ maxCompressionRatio: 100,
11
+ ratioFloorBytes: 64 * KiB,
12
+ maxPathBytes: 512,
13
+ maxSegmentBytes: 255,
14
+ maxPathDepth: 8,
15
+ maxManifestBytes: 256 * KiB,
16
+ maxLessons: 50,
17
+ maxLessonBodyChars: 1e5,
18
+ maxOfficeMembers: 50,
19
+ maxRenderedBytes: 50 * MiB,
20
+ conversionTimeoutMs: 6e4
21
+ });
22
+ var COURSE_BUNDLE_MAX_ARCHIVE_DEPTH = 1;
23
+ var COURSE_BUNDLE_MANIFEST_VERSION = 1;
24
+ var COURSE_BUNDLE_MANIFEST_VERSION_2 = 2;
25
+ var COURSE_BUNDLE_MANIFEST_PATH = "manifest.json";
26
+ function resolveCourseBundleLimits(override = {}) {
27
+ const out = { ...COURSE_BUNDLE_LIMITS };
28
+ for (const key of Object.keys(out)) {
29
+ const value = override[key];
30
+ if (value !== void 0 && Number.isSafeInteger(value) && value >= 1 && value <= COURSE_BUNDLE_LIMITS[key]) out[key] = value;
31
+ }
32
+ return out;
33
+ }
34
+ var CourseBundleRefusedError = class extends CourseAuthoringError {
35
+ constructor(reason, entry) {
36
+ super("course-bundle-refused", `Course bundle refused: ${reason}.`);
37
+ this.reason = reason;
38
+ this.entry = entry;
39
+ }
40
+ reason;
41
+ entry;
42
+ };
43
+ var CourseBundleNotFoundError = class extends CourseAuthoringError {
44
+ constructor() {
45
+ super("course-bundle-not-found", "Course bundle import was not found.");
46
+ }
47
+ };
48
+ var COURSE_BUNDLE_RETRYABLE_REASONS = /* @__PURE__ */ new Set([
49
+ "converter_unavailable",
50
+ "converter_failed",
51
+ "converter_timeout"
52
+ ]);
53
+ var COURSE_BUNDLE_PAGE_ROLES = [
54
+ "exercise",
55
+ "example",
56
+ "instructions",
57
+ "cover",
58
+ "answer_key",
59
+ "mixed",
60
+ "unknown"
61
+ ];
62
+ var COURSE_BUNDLE_STAGES = [
63
+ "unpack",
64
+ "classify",
65
+ "render",
66
+ "extract",
67
+ "propose"
68
+ ];
69
+ //#endregion
70
+ //#region src/course-bundle-zip.ts
71
+ var SIG_EOCD = 101010256;
72
+ var SIG_ZIP64_LOCATOR = 117853008;
73
+ var SIG_CENTRAL = 33639248;
74
+ var SIG_LOCAL = 67324752;
75
+ var EOCD_SIZE = 22;
76
+ var CENTRAL_SIZE = 46;
77
+ var LOCAL_SIZE = 30;
78
+ var MAX_COMMENT = 65535;
79
+ function node() {
80
+ const get = typeof process !== "undefined" && process.getBuiltinModule;
81
+ if (!get) throw new Error("Course bundle import requires a Node server");
82
+ return {
83
+ zlib: get("node:zlib"),
84
+ crypto: get("node:crypto")
85
+ };
86
+ }
87
+ function sha256Hex(bytes) {
88
+ return node().crypto.createHash("sha256").update(bytes).digest("hex");
89
+ }
90
+ var crcTable;
91
+ function crc32(bytes) {
92
+ if (!crcTable) {
93
+ crcTable = /* @__PURE__ */ new Uint32Array(256);
94
+ for (let n = 0; n < 256; n++) {
95
+ let c = n;
96
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
97
+ crcTable[n] = c >>> 0;
98
+ }
99
+ }
100
+ let crc = 4294967295;
101
+ for (let i = 0; i < bytes.length; i++) crc = crcTable[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
102
+ return (crc ^ 4294967295) >>> 0;
103
+ }
104
+ var refuse = (reason, entry) => {
105
+ throw new CourseBundleRefusedError(reason, entry);
106
+ };
107
+ var FORBIDDEN = /[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u2028-\u202e\u2066-\u2069\ufeff]/;
108
+ function isWellFormedString(value) {
109
+ return !/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/.test(value);
110
+ }
111
+ function normalizeBundlePath(raw, limits = COURSE_BUNDLE_LIMITS) {
112
+ if (typeof raw !== "string" || raw.length === 0) return refuse("invalid_path");
113
+ if (!isWellFormedString(raw) || FORBIDDEN.test(raw)) return refuse("invalid_path");
114
+ const text = raw.replaceAll("\\", "/");
115
+ if (Buffer.byteLength(text) > limits.maxPathBytes || text.startsWith("/") || /^[A-Za-z]:/.test(text) || text.includes(":")) return refuse("invalid_path");
116
+ const directory = text.endsWith("/");
117
+ const segments = (directory ? text.slice(0, -1) : text).split("/");
118
+ for (const segment of segments) if (segment === "" || segment === "." || segment === ".." || Buffer.byteLength(segment) > limits.maxSegmentBytes) return refuse("invalid_path");
119
+ if (segments.length > limits.maxPathDepth) return refuse("path_too_deep");
120
+ return {
121
+ path: segments.join("/"),
122
+ directory
123
+ };
124
+ }
125
+ var bundleNameKey = (path) => path.normalize("NFC").toLowerCase();
126
+ function findEndRecord(bytes, view) {
127
+ const low = Math.max(0, bytes.length - EOCD_SIZE - MAX_COMMENT);
128
+ const candidates = [];
129
+ for (let i = bytes.length - EOCD_SIZE; i >= low; i--) {
130
+ if (view.getUint32(i, true) !== SIG_EOCD) continue;
131
+ if (i + EOCD_SIZE + view.getUint16(i + 20, true) === bytes.length) candidates.push(i);
132
+ }
133
+ if (candidates.length === 0) return refuse(bytes.length >= 4 && view.getUint32(0, true) === SIG_LOCAL ? "truncated" : "not_a_zip");
134
+ if (candidates.length > 1) return refuse("ambiguous_end_record");
135
+ return candidates[0];
136
+ }
137
+ function readZipArchive(input, limits = COURSE_BUNDLE_LIMITS, onProgress) {
138
+ if (!(input instanceof Uint8Array)) return refuse("not_a_zip");
139
+ if (input.length > limits.maxArchiveBytes) return refuse("archive_too_large");
140
+ if (input.length < EOCD_SIZE) return refuse("not_a_zip");
141
+ const bytes = input;
142
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
143
+ const eocd = findEndRecord(bytes, view);
144
+ if (eocd >= 20 && view.getUint32(eocd - 20, true) === SIG_ZIP64_LOCATOR) return refuse("zip64_unsupported");
145
+ const disk = view.getUint16(eocd + 4, true);
146
+ const cdDisk = view.getUint16(eocd + 6, true);
147
+ const entriesDisk = view.getUint16(eocd + 8, true);
148
+ const entries = view.getUint16(eocd + 10, true);
149
+ const cdSize = view.getUint32(eocd + 12, true);
150
+ const cdOffset = view.getUint32(eocd + 16, true);
151
+ if (entries === 65535 || cdSize === 4294967295 || cdOffset === 4294967295) return refuse("zip64_unsupported");
152
+ if (disk !== 0 || cdDisk !== 0 || entriesDisk !== entries) return refuse("multi_disk");
153
+ if (entries === 0) return refuse("empty_bundle");
154
+ if (entries > limits.maxEntries) return refuse("too_many_entries");
155
+ if (cdSize < entries * CENTRAL_SIZE || cdOffset + cdSize !== eocd) return refuse("invalid_central_directory");
156
+ const decoder = new TextDecoder("utf-8", { fatal: true });
157
+ const central = [];
158
+ const names = /* @__PURE__ */ new Set();
159
+ let at = cdOffset;
160
+ for (let n = 0; n < entries; n++) {
161
+ if (at + CENTRAL_SIZE > eocd || view.getUint32(at, true) !== SIG_CENTRAL) return refuse("invalid_central_directory");
162
+ const madeBy = view.getUint16(at + 4, true);
163
+ const flags = view.getUint16(at + 8, true);
164
+ const method = view.getUint16(at + 10, true);
165
+ const crc = view.getUint32(at + 16, true);
166
+ const compressed = view.getUint32(at + 20, true);
167
+ const size = view.getUint32(at + 24, true);
168
+ const nameLength = view.getUint16(at + 28, true);
169
+ const extraLength = view.getUint16(at + 30, true);
170
+ const commentLength = view.getUint16(at + 32, true);
171
+ const diskStart = view.getUint16(at + 34, true);
172
+ const external = view.getUint32(at + 38, true);
173
+ const localOffset = view.getUint32(at + 42, true);
174
+ const next = at + CENTRAL_SIZE + nameLength + extraLength + commentLength;
175
+ if (next > eocd) return refuse("invalid_central_directory");
176
+ const rawName = bytes.subarray(at + CENTRAL_SIZE, at + CENTRAL_SIZE + nameLength);
177
+ let decoded;
178
+ try {
179
+ decoded = decoder.decode(rawName);
180
+ } catch {
181
+ return refuse("invalid_path");
182
+ }
183
+ const { path, directory } = normalizeBundlePath(decoded, limits);
184
+ if (flags & 8257 || method === 99) return refuse("encrypted_entry", path);
185
+ if (method !== 0 && method !== 8) return refuse("unsupported_method", path);
186
+ if (compressed === 4294967295 || size === 4294967295 || localOffset === 4294967295) return refuse("zip64_unsupported", path);
187
+ if (diskStart !== 0) return refuse("multi_disk", path);
188
+ if (madeBy >>> 8 === 3) {
189
+ const type = external >>> 16 & 61440;
190
+ if (type === 40960) return refuse("symlink_entry", path);
191
+ if (type !== 0 && type !== 32768 && type !== 16384) return refuse("special_entry", path);
192
+ } else if (external & 1024) return refuse("symlink_entry", path);
193
+ if (directory && (size !== 0 || compressed > 2)) return refuse("invalid_entry", path);
194
+ const key = bundleNameKey(path);
195
+ if (names.has(key)) return refuse("duplicate_name", path);
196
+ names.add(key);
197
+ if (method === 0 && compressed !== size) return refuse("size_mismatch", path);
198
+ if (size > limits.ratioFloorBytes && method === 8 && (compressed === 0 || size / compressed > limits.maxCompressionRatio)) return refuse("compression_ratio", path);
199
+ central.push({
200
+ name: path,
201
+ rawName,
202
+ method,
203
+ crc,
204
+ compressed,
205
+ size,
206
+ localOffset,
207
+ directory
208
+ });
209
+ at = next;
210
+ }
211
+ if (at !== eocd) return refuse("invalid_central_directory");
212
+ const located = [];
213
+ const ranges = [];
214
+ for (const entry of central) {
215
+ if (entry.directory) continue;
216
+ if (entry.localOffset + LOCAL_SIZE > cdOffset) return refuse("entry_out_of_range", entry.name);
217
+ if (view.getUint32(entry.localOffset, true) !== SIG_LOCAL) return refuse("local_header_mismatch", entry.name);
218
+ if (view.getUint16(entry.localOffset + 6, true) & 8257) return refuse("encrypted_entry", entry.name);
219
+ if (view.getUint16(entry.localOffset + 8, true) !== entry.method) return refuse("local_header_mismatch", entry.name);
220
+ const nameLength = view.getUint16(entry.localOffset + 26, true);
221
+ const extraLength = view.getUint16(entry.localOffset + 28, true);
222
+ const start = entry.localOffset + LOCAL_SIZE + nameLength + extraLength;
223
+ const end = start + entry.compressed;
224
+ if (end > cdOffset) return refuse("entry_out_of_range", entry.name);
225
+ const local = bytes.subarray(entry.localOffset + LOCAL_SIZE, entry.localOffset + LOCAL_SIZE + nameLength);
226
+ if (local.length !== entry.rawName.length || !local.every((b, i) => b === entry.rawName[i])) return refuse("local_header_mismatch", entry.name);
227
+ located.push({
228
+ ...entry,
229
+ start,
230
+ end
231
+ });
232
+ ranges.push([
233
+ entry.localOffset,
234
+ end,
235
+ entry.name
236
+ ]);
237
+ }
238
+ ranges.sort((a, b) => a[0] - b[0]);
239
+ for (let i = 1; i < ranges.length; i++) if (ranges[i][0] < ranges[i - 1][1]) return refuse("overlapping_entries", ranges[i][2]);
240
+ let declared = 0;
241
+ for (const entry of located) if (entry.size <= limits.maxEntryBytes) declared += entry.size;
242
+ if (declared > limits.maxTotalBytes) return refuse("total_too_large");
243
+ const { zlib } = node();
244
+ const out = [];
245
+ let total = 0;
246
+ onProgress?.(0, located.length);
247
+ for (const entry of [...located].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
248
+ if (entry.size > limits.maxEntryBytes) {
249
+ out.push({
250
+ path: entry.name,
251
+ size: entry.size,
252
+ bytes: null,
253
+ sha256: null,
254
+ refusal: "entry_too_large"
255
+ });
256
+ onProgress?.(out.length, located.length);
257
+ continue;
258
+ }
259
+ const data = bytes.subarray(entry.start, entry.end);
260
+ let inflated;
261
+ if (entry.method === 0) inflated = Buffer.from(data);
262
+ else try {
263
+ inflated = zlib.inflateRawSync(data, { maxOutputLength: Math.max(entry.size, 1) });
264
+ } catch (error) {
265
+ return refuse(error.code === "ERR_BUFFER_TOO_LARGE" ? "size_mismatch" : "inflate_failed", entry.name);
266
+ }
267
+ if (inflated.length !== entry.size) return refuse("size_mismatch", entry.name);
268
+ if (crc32(inflated) !== entry.crc) return refuse("bad_crc", entry.name);
269
+ total += inflated.length;
270
+ if (total > limits.maxTotalBytes) return refuse("total_too_large");
271
+ out.push({
272
+ path: entry.name,
273
+ size: inflated.length,
274
+ bytes: inflated,
275
+ sha256: sha256Hex(inflated),
276
+ refusal: null
277
+ });
278
+ onProgress?.(out.length, located.length);
279
+ }
280
+ return out;
281
+ }
282
+ function readFileSet(files, limits = COURSE_BUNDLE_LIMITS, onProgress) {
283
+ if (!Array.isArray(files) || files.length === 0) return refuse("empty_bundle");
284
+ if (files.length > limits.maxEntries) return refuse("too_many_entries");
285
+ const names = /* @__PURE__ */ new Set();
286
+ const out = [];
287
+ let total = 0;
288
+ let memberTotal = 0;
289
+ onProgress?.(0, files.length);
290
+ for (const file of files) {
291
+ const { path, directory } = normalizeBundlePath(file?.path, limits);
292
+ if (directory || !(file.bytes instanceof Uint8Array)) return refuse("invalid_entry", path);
293
+ const key = bundleNameKey(path);
294
+ if (names.has(key)) return refuse("duplicate_name", path);
295
+ names.add(key);
296
+ total += file.bytes.length;
297
+ if (total > limits.maxArchiveBytes) return refuse("archive_too_large");
298
+ if (file.bytes.length > limits.maxEntryBytes) out.push({
299
+ path,
300
+ size: file.bytes.length,
301
+ bytes: null,
302
+ sha256: null,
303
+ refusal: "entry_too_large"
304
+ });
305
+ else {
306
+ memberTotal += file.bytes.length;
307
+ if (memberTotal > limits.maxTotalBytes) return refuse("total_too_large");
308
+ out.push({
309
+ path,
310
+ size: file.bytes.length,
311
+ bytes: file.bytes,
312
+ sha256: sha256Hex(file.bytes),
313
+ refusal: null
314
+ });
315
+ }
316
+ onProgress?.(out.length, files.length);
317
+ }
318
+ return out.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
319
+ }
320
+ //#endregion
321
+ export { resolveCourseBundleLimits as _, readZipArchive as a, COURSE_BUNDLE_MANIFEST_PATH as c, COURSE_BUNDLE_MAX_ARCHIVE_DEPTH as d, COURSE_BUNDLE_PAGE_ROLES as f, CourseBundleRefusedError as g, CourseBundleNotFoundError as h, readFileSet as i, COURSE_BUNDLE_MANIFEST_VERSION as l, COURSE_BUNDLE_STAGES as m, isWellFormedString as n, sha256Hex as o, COURSE_BUNDLE_RETRYABLE_REASONS as p, normalizeBundlePath as r, COURSE_BUNDLE_LIMITS as s, crc32 as t, COURSE_BUNDLE_MANIFEST_VERSION_2 as u };
322
+
323
+ //# sourceMappingURL=course-bundle-zip-5KUe3C5H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"course-bundle-zip-5KUe3C5H.js","names":[],"sources":["../../src/course-bundle-contracts.ts","../../src/course-bundle-zip.ts"],"sourcesContent":["import { CourseAuthoringError } from './course-authoring-contracts.js';\n\nconst KiB = 1024;\nconst MiB = 1024 * KiB;\n\n/**\n * Hard ceilings for untrusted course bundles (SMRT #4578). A host may lower any\n * of them per call (`resolveCourseBundleLimits`) but never raise them. Declared\n * sizes in an archive are never trusted: every cap is enforced on the bytes\n * actually produced.\n */\nexport interface CourseBundleLimits {\n /** Largest accepted archive (or total input of a plain file set), in bytes. */\n maxArchiveBytes: number;\n /** Most entries (files and directories) in one archive or file set. */\n maxEntries: number;\n /** Largest single inflated member. A larger honest member is recorded refused. */\n maxEntryBytes: number;\n /** Largest sum of inflated members. */\n maxTotalBytes: number;\n /** Largest inflated/compressed ratio for a member above `ratioFloorBytes`. */\n maxCompressionRatio: number;\n /** Members at or below this inflated size skip the ratio check. */\n ratioFloorBytes: number;\n /** Longest normalised path, in UTF-8 bytes. */\n maxPathBytes: number;\n /** Longest path segment, in UTF-8 bytes. */\n maxSegmentBytes: number;\n /** Deepest directory path (segments, including the file name). */\n maxPathDepth: number;\n /** Largest `manifest.json`. */\n maxManifestBytes: number;\n /** Most lessons one bundle may propose (matches the proposal schema). */\n maxLessons: number;\n /** Longest text member used as a lesson body, in characters. */\n maxLessonBodyChars: number;\n /** Most Word/PowerPoint members rendered per bundle; later ones are recorded unsupported. */\n maxOfficeMembers: number;\n /** Largest rendered PDF accepted back from the converter, in bytes. */\n maxRenderedBytes: number;\n /** Wall-clock budget for rendering one Office member, in milliseconds. */\n conversionTimeoutMs: number;\n}\n\nexport const COURSE_BUNDLE_LIMITS: Readonly<CourseBundleLimits> = Object.freeze(\n {\n maxArchiveBytes: 100 * MiB,\n maxEntries: 200,\n maxEntryBytes: 50 * MiB,\n maxTotalBytes: 200 * MiB,\n maxCompressionRatio: 100,\n ratioFloorBytes: 64 * KiB,\n maxPathBytes: 512,\n maxSegmentBytes: 255,\n maxPathDepth: 8,\n maxManifestBytes: 256 * KiB,\n maxLessons: 50,\n maxLessonBodyChars: 100_000,\n maxOfficeMembers: 50,\n maxRenderedBytes: 50 * MiB,\n conversionTimeoutMs: 60_000,\n },\n);\n\n/** Nested archives are never expanded: depth is one, and a zip member is unsupported evidence. */\nexport const COURSE_BUNDLE_MAX_ARCHIVE_DEPTH = 1;\n/** The original manifest schema version (page roles, no materials). */\nexport const COURSE_BUNDLE_MANIFEST_VERSION = 1;\n/** Manifest version 2: material roles and audience, lesson pages, course members, videos. */\nexport const COURSE_BUNDLE_MANIFEST_VERSION_2 = 2;\n/** Manifest location: the archive (or file set) root. */\nexport const COURSE_BUNDLE_MANIFEST_PATH = 'manifest.json';\n\n/** Clamp a host override to the hard ceilings; invalid values fall back to the ceiling. */\nexport function resolveCourseBundleLimits(\n override: Partial<CourseBundleLimits> = {},\n): CourseBundleLimits {\n const out = { ...COURSE_BUNDLE_LIMITS };\n for (const key of Object.keys(out) as Array<keyof CourseBundleLimits>) {\n const value = override[key];\n if (\n value !== undefined &&\n Number.isSafeInteger(value) &&\n value >= 1 &&\n value <= COURSE_BUNDLE_LIMITS[key]\n )\n out[key] = value;\n }\n return out;\n}\n\nexport type CourseBundleRefusalReason =\n | 'archive_too_large'\n | 'not_a_zip'\n | 'truncated'\n | 'ambiguous_end_record'\n | 'zip64_unsupported'\n | 'multi_disk'\n | 'too_many_entries'\n | 'invalid_central_directory'\n | 'invalid_entry'\n | 'unsupported_method'\n | 'encrypted_entry'\n | 'symlink_entry'\n | 'special_entry'\n | 'invalid_path'\n | 'path_too_deep'\n | 'duplicate_name'\n | 'entry_out_of_range'\n | 'overlapping_entries'\n | 'local_header_mismatch'\n | 'compression_ratio'\n | 'total_too_large'\n | 'size_mismatch'\n | 'bad_crc'\n | 'inflate_failed'\n | 'empty_bundle'\n | 'manifest_invalid'\n | 'manifest_version'\n | 'manifest_unknown_key'\n /** v2: a member role that is not a material role. */\n | 'manifest_role'\n /** v2: a learner audience on a guardian-only role, or an unknown audience. */\n | 'manifest_audience'\n /** v2: a page range that is malformed or names a member that cannot be paged. */\n | 'manifest_pages'\n /** v2: a video reference that is not an allowlisted YouTube id or URL. */\n | 'manifest_video'\n | 'manifest_missing_member'\n | 'manifest_duplicate_member'\n | 'duplicate_key'\n | 'too_many_lessons'\n | 'lesson_too_large';\n\n/**\n * The whole bundle is refused: it is hostile, damaged or outside the contract.\n * `reason` is stable; `entry` names the offending member path when one applies\n * (untrusted text, never rendered as markup).\n */\nexport class CourseBundleRefusedError extends CourseAuthoringError {\n constructor(\n readonly reason: CourseBundleRefusalReason,\n readonly entry?: string,\n ) {\n super('course-bundle-refused', `Course bundle refused: ${reason}.`);\n }\n}\nexport class CourseBundleNotFoundError extends CourseAuthoringError {\n constructor() {\n super('course-bundle-not-found', 'Course bundle import was not found.');\n }\n}\n\n/** What the bytes of a bundle member turned out to be (by content, never by name alone). */\nexport type CourseBundleMemberKind =\n | 'pdf'\n | 'image'\n | 'text'\n /** Word (`.docx`): rendered to PDF by the converter, text read by the SDK processor. */\n | 'docx'\n /** PowerPoint (`.pptx`): the deck is kept as the original and as rendered pages. */\n | 'pptx'\n /** Excel (`.xlsx`): rendered to PDF by the converter; no text reader. */\n | 'xlsx'\n | 'manifest'\n | 'unsupported';\nexport type CourseBundleMemberStatus =\n /** Supported, unique content: handed to extraction and eligible for a lesson. */\n | 'accepted'\n /** Identical bytes to an earlier accepted member: evidence only, never extracted twice. */\n | 'duplicate'\n /** Visible but not understood (unknown type, nested archive, unreadable text). */\n | 'unsupported'\n /** Understood and refused (encrypted PDF, oversize member). */\n | 'refused'\n /** The bundle manifest itself. */\n | 'metadata';\nexport type CourseBundleMemberReason =\n | 'unsupported_type'\n | 'nested_archive'\n | 'unreadable_text'\n | 'encrypted_pdf'\n | 'entry_too_large'\n | 'duplicate_content'\n /** The OOXML package failed the bounded zip checks (bomb, bad paths, damaged). */\n | 'unsafe_office'\n /** More Office members than `maxOfficeMembers`. */\n | 'office_limit'\n /** No `DocumentConverter` is configured: the member cannot be rendered. */\n | 'converter_unavailable'\n /** The converter could not be reached or failed (server side). */\n | 'converter_failed'\n | 'converter_timeout'\n | 'converter_too_large'\n /** The converter refused the file, or returned something that is not a PDF. */\n | 'converter_rejected';\n\n/**\n * Converter outcomes that a deliberate retry may repair. A bundle that was\n * never rendered or whose converter was unreachable can be rendered later;\n * a file the converter rejected, or a limit, cannot be fixed by asking again.\n */\nexport const COURSE_BUNDLE_RETRYABLE_REASONS: ReadonlySet<CourseBundleMemberReason> =\n new Set(['converter_unavailable', 'converter_failed', 'converter_timeout']);\n\nexport const COURSE_BUNDLE_PAGE_ROLES = [\n 'exercise',\n 'example',\n 'instructions',\n 'cover',\n 'answer_key',\n 'mixed',\n 'unknown',\n] as const;\nexport type CourseBundlePageRole = (typeof COURSE_BUNDLE_PAGE_ROLES)[number];\n\n/** Named progress stages, in order. */\nexport const COURSE_BUNDLE_STAGES = [\n 'unpack',\n 'classify',\n 'render',\n 'extract',\n 'propose',\n] as const;\nexport type CourseBundleStage = (typeof COURSE_BUNDLE_STAGES)[number];\n/** One progress event. A stage emits `completed: 0` first and `completed === total` last. */\nexport interface CourseBundleProgress {\n stage: CourseBundleStage;\n completed: number;\n total: number;\n}\n/** Observer only: a throwing listener never changes the import. */\nexport type CourseBundleProgressListener = (\n event: CourseBundleProgress,\n) => void;\n\n/** A bundle supplied as one zip archive or as a plain set of files. */\nexport type CourseBundleInput =\n | { archive: Uint8Array }\n | { files: ReadonlyArray<{ path: string; bytes: Uint8Array }> };\n","/**\n * Bounded zip reader for untrusted course bundles (SMRT #4578). Reads the End\n * Of Central Directory and central directory, validates every offset and size\n * against the real buffer, inflates with a hard output cap and checks CRC32.\n * Stored and deflate only; zip64, multi-disk, encryption and symlinks are\n * refused with typed reasons. Nothing here touches the filesystem.\n */\nimport {\n COURSE_BUNDLE_LIMITS,\n type CourseBundleLimits,\n type CourseBundleRefusalReason,\n CourseBundleRefusedError,\n} from './course-bundle-contracts.js';\n\nconst SIG_EOCD = 0x06054b50;\nconst SIG_ZIP64_LOCATOR = 0x07064b50;\nconst SIG_CENTRAL = 0x02014b50;\nconst SIG_LOCAL = 0x04034b50;\nconst EOCD_SIZE = 22;\nconst CENTRAL_SIZE = 46;\nconst LOCAL_SIZE = 30;\nconst MAX_COMMENT = 0xffff;\n\n/** One member read from an archive or file set, before classification. */\nexport interface RawBundleMember {\n /** Normalised, validated path. */\n path: string;\n size: number;\n /** Null only when the member was refused as oversize and never inflated. */\n bytes: Uint8Array | null;\n sha256: string | null;\n /** Set when the member was understood but not inflated. */\n refusal: 'entry_too_large' | null;\n}\n\nfunction node() {\n const get = (typeof process !== 'undefined' && process.getBuiltinModule) as\n | ((id: string) => unknown)\n | false;\n if (!get) throw new Error('Course bundle import requires a Node server');\n return {\n zlib: get('node:zlib') as typeof import('node:zlib'),\n crypto: get('node:crypto') as typeof import('node:crypto'),\n };\n}\n\nexport function sha256Hex(bytes: Uint8Array | string): string {\n return node().crypto.createHash('sha256').update(bytes).digest('hex');\n}\n\nlet crcTable: Uint32Array | undefined;\nexport function crc32(bytes: Uint8Array): number {\n if (!crcTable) {\n crcTable = new Uint32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n crcTable[n] = c >>> 0;\n }\n }\n let crc = 0xffffffff;\n for (let i = 0; i < bytes.length; i++)\n crc = (crcTable[(crc ^ bytes[i]) & 0xff] as number) ^ (crc >>> 8);\n return (crc ^ 0xffffffff) >>> 0;\n}\n\nconst refuse = (reason: CourseBundleRefusalReason, entry?: string): never => {\n throw new CourseBundleRefusedError(reason, entry);\n};\n\nconst FORBIDDEN =\n // biome-ignore lint/suspicious/noControlCharactersInRegex: refusing control characters in names\n /[\\u0000-\\u001f\\u007f-\\u009f\\u200b-\\u200f\\u2028-\\u202e\\u2066-\\u2069\\ufeff]/;\n/** True when the string has no lone UTF-16 surrogates. */\nexport function isWellFormedString(value: string): boolean {\n return !/[\\ud800-\\udbff](?![\\udc00-\\udfff])|(?<![\\ud800-\\udbff])[\\udc00-\\udfff]/.test(\n value,\n );\n}\n\n/**\n * Normalise and validate an entry or file name. Backslashes become `/`; empty,\n * `.` and `..` segments, absolute and drive-letter paths, `:` anywhere, NUL and\n * other control or bidi characters, over-long or over-deep paths are refused.\n * A trailing `/` (directory) is stripped and reported.\n */\nexport function normalizeBundlePath(\n raw: unknown,\n limits: CourseBundleLimits = COURSE_BUNDLE_LIMITS,\n): { path: string; directory: boolean } {\n if (typeof raw !== 'string' || raw.length === 0)\n return refuse('invalid_path');\n if (!isWellFormedString(raw) || FORBIDDEN.test(raw))\n return refuse('invalid_path');\n const text = raw.replaceAll('\\\\', '/');\n if (\n Buffer.byteLength(text) > limits.maxPathBytes ||\n text.startsWith('/') ||\n /^[A-Za-z]:/.test(text) ||\n text.includes(':')\n )\n return refuse('invalid_path');\n const directory = text.endsWith('/');\n const segments = (directory ? text.slice(0, -1) : text).split('/');\n for (const segment of segments)\n if (\n segment === '' ||\n segment === '.' ||\n segment === '..' ||\n Buffer.byteLength(segment) > limits.maxSegmentBytes\n )\n return refuse('invalid_path');\n if (segments.length > limits.maxPathDepth) return refuse('path_too_deep');\n return { path: segments.join('/'), directory };\n}\n\n/** Case- and normalisation-insensitive identity used to refuse colliding names. */\nexport const bundleNameKey = (path: string) =>\n path.normalize('NFC').toLowerCase();\n\ninterface CentralEntry {\n name: string;\n rawName: Uint8Array;\n method: number;\n crc: number;\n compressed: number;\n size: number;\n localOffset: number;\n directory: boolean;\n}\n\nfunction findEndRecord(bytes: Uint8Array, view: DataView): number {\n const low = Math.max(0, bytes.length - EOCD_SIZE - MAX_COMMENT);\n const candidates: number[] = [];\n for (let i = bytes.length - EOCD_SIZE; i >= low; i--) {\n if (view.getUint32(i, true) !== SIG_EOCD) continue;\n // The comment must run exactly to the end of the file, so a record forged\n // inside a comment can only count if it also fits; two fits are ambiguous.\n if (i + EOCD_SIZE + view.getUint16(i + 20, true) === bytes.length)\n candidates.push(i);\n }\n if (candidates.length === 0)\n return refuse(\n bytes.length >= 4 && view.getUint32(0, true) === SIG_LOCAL\n ? 'truncated'\n : 'not_a_zip',\n );\n if (candidates.length > 1) return refuse('ambiguous_end_record');\n return candidates[0] as number;\n}\n\n/**\n * Read every member of a zip archive. Throws `CourseBundleRefusedError` for a\n * hostile or damaged archive. A member whose honest declared size exceeds\n * `maxEntryBytes` is returned `entry_too_large` (visible, never inflated);\n * every other violation refuses the whole archive.\n */\nexport function readZipArchive(\n input: Uint8Array,\n limits: CourseBundleLimits = COURSE_BUNDLE_LIMITS,\n onProgress?: (completed: number, total: number) => void,\n): RawBundleMember[] {\n if (!(input instanceof Uint8Array)) return refuse('not_a_zip');\n if (input.length > limits.maxArchiveBytes) return refuse('archive_too_large');\n if (input.length < EOCD_SIZE) return refuse('not_a_zip');\n const bytes = input;\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const eocd = findEndRecord(bytes, view);\n if (eocd >= 20 && view.getUint32(eocd - 20, true) === SIG_ZIP64_LOCATOR)\n return refuse('zip64_unsupported');\n const disk = view.getUint16(eocd + 4, true);\n const cdDisk = view.getUint16(eocd + 6, true);\n const entriesDisk = view.getUint16(eocd + 8, true);\n const entries = view.getUint16(eocd + 10, true);\n const cdSize = view.getUint32(eocd + 12, true);\n const cdOffset = view.getUint32(eocd + 16, true);\n if (entries === 0xffff || cdSize === 0xffffffff || cdOffset === 0xffffffff)\n return refuse('zip64_unsupported');\n if (disk !== 0 || cdDisk !== 0 || entriesDisk !== entries)\n return refuse('multi_disk');\n if (entries === 0) return refuse('empty_bundle');\n if (entries > limits.maxEntries) return refuse('too_many_entries');\n if (\n cdSize < entries * CENTRAL_SIZE ||\n cdOffset + cdSize !== eocd // central directory must end exactly at the end record\n )\n return refuse('invalid_central_directory');\n\n const decoder = new TextDecoder('utf-8', { fatal: true });\n const central: CentralEntry[] = [];\n const names = new Set<string>();\n let at = cdOffset;\n for (let n = 0; n < entries; n++) {\n if (at + CENTRAL_SIZE > eocd || view.getUint32(at, true) !== SIG_CENTRAL)\n return refuse('invalid_central_directory');\n const madeBy = view.getUint16(at + 4, true);\n const flags = view.getUint16(at + 8, true);\n const method = view.getUint16(at + 10, true);\n const crc = view.getUint32(at + 16, true);\n const compressed = view.getUint32(at + 20, true);\n const size = view.getUint32(at + 24, true);\n const nameLength = view.getUint16(at + 28, true);\n const extraLength = view.getUint16(at + 30, true);\n const commentLength = view.getUint16(at + 32, true);\n const diskStart = view.getUint16(at + 34, true);\n const external = view.getUint32(at + 38, true);\n const localOffset = view.getUint32(at + 42, true);\n const next = at + CENTRAL_SIZE + nameLength + extraLength + commentLength;\n if (next > eocd) return refuse('invalid_central_directory');\n const rawName = bytes.subarray(\n at + CENTRAL_SIZE,\n at + CENTRAL_SIZE + nameLength,\n );\n let decoded: string;\n try {\n decoded = decoder.decode(rawName);\n } catch {\n return refuse('invalid_path');\n }\n const { path, directory } = normalizeBundlePath(decoded, limits);\n if (flags & (0x1 | 0x40 | 0x2000) || method === 99)\n return refuse('encrypted_entry', path);\n if (method !== 0 && method !== 8) return refuse('unsupported_method', path);\n if (\n compressed === 0xffffffff ||\n size === 0xffffffff ||\n localOffset === 0xffffffff\n )\n return refuse('zip64_unsupported', path);\n if (diskStart !== 0) return refuse('multi_disk', path);\n const host = madeBy >>> 8;\n if (host === 3) {\n const type = (external >>> 16) & 0o170000;\n if (type === 0o120000) return refuse('symlink_entry', path);\n if (type !== 0 && type !== 0o100000 && type !== 0o040000)\n return refuse('special_entry', path);\n } else if (external & 0x400) {\n return refuse('symlink_entry', path); // DOS reparse point\n }\n if (directory && (size !== 0 || compressed > 2))\n return refuse('invalid_entry', path);\n const key = bundleNameKey(path);\n if (names.has(key)) return refuse('duplicate_name', path);\n names.add(key);\n if (method === 0 && compressed !== size)\n return refuse('size_mismatch', path);\n if (\n size > limits.ratioFloorBytes &&\n method === 8 &&\n (compressed === 0 || size / compressed > limits.maxCompressionRatio)\n )\n return refuse('compression_ratio', path);\n central.push({\n name: path,\n rawName,\n method,\n crc,\n compressed,\n size,\n localOffset,\n directory,\n });\n at = next;\n }\n if (at !== eocd) return refuse('invalid_central_directory');\n\n // Local headers and data ranges: inside the file, before the central\n // directory, naming the same entry, never overlapping each other.\n const located: Array<CentralEntry & { start: number; end: number }> = [];\n const ranges: Array<[number, number, string]> = [];\n for (const entry of central) {\n if (entry.directory) continue;\n if (entry.localOffset + LOCAL_SIZE > cdOffset)\n return refuse('entry_out_of_range', entry.name);\n if (view.getUint32(entry.localOffset, true) !== SIG_LOCAL)\n return refuse('local_header_mismatch', entry.name);\n const flags = view.getUint16(entry.localOffset + 6, true);\n if (flags & (0x1 | 0x40 | 0x2000))\n return refuse('encrypted_entry', entry.name);\n if (view.getUint16(entry.localOffset + 8, true) !== entry.method)\n return refuse('local_header_mismatch', entry.name);\n const nameLength = view.getUint16(entry.localOffset + 26, true);\n const extraLength = view.getUint16(entry.localOffset + 28, true);\n const start = entry.localOffset + LOCAL_SIZE + nameLength + extraLength;\n const end = start + entry.compressed;\n if (end > cdOffset) return refuse('entry_out_of_range', entry.name);\n const local = bytes.subarray(\n entry.localOffset + LOCAL_SIZE,\n entry.localOffset + LOCAL_SIZE + nameLength,\n );\n if (\n local.length !== entry.rawName.length ||\n !local.every((b, i) => b === entry.rawName[i])\n )\n return refuse('local_header_mismatch', entry.name);\n located.push({ ...entry, start, end });\n ranges.push([entry.localOffset, end, entry.name]);\n }\n ranges.sort((a, b) => a[0] - b[0]);\n for (let i = 1; i < ranges.length; i++)\n if (\n (ranges[i] as [number, number, string])[0] <\n (ranges[i - 1] as [number, number, string])[1]\n )\n return refuse(\n 'overlapping_entries',\n (ranges[i] as [number, number, string])[2],\n );\n\n // Declared totals are checked up front, actual output again while inflating.\n let declared = 0;\n for (const entry of located)\n if (entry.size <= limits.maxEntryBytes) declared += entry.size;\n if (declared > limits.maxTotalBytes) return refuse('total_too_large');\n\n const { zlib } = node();\n const out: RawBundleMember[] = [];\n let total = 0;\n onProgress?.(0, located.length);\n for (const entry of [...located].sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : 0,\n )) {\n if (entry.size > limits.maxEntryBytes) {\n out.push({\n path: entry.name,\n size: entry.size,\n bytes: null,\n sha256: null,\n refusal: 'entry_too_large',\n });\n onProgress?.(out.length, located.length);\n continue;\n }\n const data = bytes.subarray(entry.start, entry.end);\n let inflated: Buffer;\n if (entry.method === 0) {\n inflated = Buffer.from(data);\n } else {\n try {\n inflated = zlib.inflateRawSync(data, {\n maxOutputLength: Math.max(entry.size, 1),\n });\n } catch (error) {\n return refuse(\n (error as { code?: string }).code === 'ERR_BUFFER_TOO_LARGE'\n ? 'size_mismatch'\n : 'inflate_failed',\n entry.name,\n );\n }\n }\n if (inflated.length !== entry.size)\n return refuse('size_mismatch', entry.name);\n if (crc32(inflated) !== entry.crc) return refuse('bad_crc', entry.name);\n total += inflated.length;\n if (total > limits.maxTotalBytes) return refuse('total_too_large');\n out.push({\n path: entry.name,\n size: inflated.length,\n bytes: inflated,\n sha256: sha256Hex(inflated),\n refusal: null,\n });\n onProgress?.(out.length, located.length);\n }\n return out;\n}\n\n/** Validate a plain file set the same way an archive is: paths, caps, duplicates. */\nexport function readFileSet(\n files: ReadonlyArray<{ path: string; bytes: Uint8Array }>,\n limits: CourseBundleLimits = COURSE_BUNDLE_LIMITS,\n onProgress?: (completed: number, total: number) => void,\n): RawBundleMember[] {\n if (!Array.isArray(files) || files.length === 0)\n return refuse('empty_bundle');\n if (files.length > limits.maxEntries) return refuse('too_many_entries');\n const names = new Set<string>();\n const out: RawBundleMember[] = [];\n let total = 0;\n let memberTotal = 0;\n onProgress?.(0, files.length);\n for (const file of files) {\n const { path, directory } = normalizeBundlePath(file?.path, limits);\n if (directory || !(file.bytes instanceof Uint8Array))\n return refuse('invalid_entry', path);\n const key = bundleNameKey(path);\n if (names.has(key)) return refuse('duplicate_name', path);\n names.add(key);\n total += file.bytes.length;\n if (total > limits.maxArchiveBytes) return refuse('archive_too_large');\n if (file.bytes.length > limits.maxEntryBytes) {\n out.push({\n path,\n size: file.bytes.length,\n bytes: null,\n sha256: null,\n refusal: 'entry_too_large',\n });\n } else {\n // Same member-byte bound as an archive's inflated total.\n memberTotal += file.bytes.length;\n if (memberTotal > limits.maxTotalBytes) return refuse('total_too_large');\n out.push({\n path,\n size: file.bytes.length,\n bytes: file.bytes,\n sha256: sha256Hex(file.bytes),\n refusal: null,\n });\n }\n onProgress?.(out.length, files.length);\n }\n return out.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n"],"mappings":"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"}