@happyvertical/smrt-education 0.60.1 → 0.60.3

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.
package/AGENTS.md CHANGED
@@ -61,7 +61,8 @@ Course bundles (zip with a closed manifest v1 or v2, or a file set; Word/PowerPo
61
61
  [CourseBundleImportService](agents/course-bundle-import.md). Parse only with the
62
62
  bounded reader; refuse, never trim, hostile input; unsupported members stay
63
63
  visible; nothing auto-published. `attachCourse` makes one family draft course per
64
- unit key (parts merge in any order, mismatches fail closed); review choices never
64
+ unit key (parts merge in any order, mismatches fail closed); `supersede: true`
65
+ is the only way to take a newer build of an attached part (retires, never deletes); review choices never
65
66
  open keys, guidance or an editable original to a learner.
66
67
 
67
68
  Class CrunchLabs (a download read into a manifest v2 bundle; an operator-only
package/README.md CHANGED
@@ -133,7 +133,8 @@ duplicate members as append-only evidence, hands accepted members to ingestion a
133
133
  proposes sections and lessons through `CourseGenerationReviewService`.
134
134
  `attachCourse` turns an import (or each part of a unit split across bundles, in
135
135
  any order) into one family course with lessons, materials, pages and videos;
136
- `chooseMaterialAudience` records review choices before attach; see
136
+ `attachCourse({ supersede: true })` brings an attached unit up to a newer build
137
+ keeping learner progress; `chooseMaterialAudience` records review choices before attach; see
137
138
  [bundle import](agents/course-bundle-import.md).
138
139
  `requestGeneration` also accepts a structured `brief` (subject, category, area,
139
140
  grade, objective, a `course` or one-`lesson` target and a per-request interest
@@ -90,6 +90,27 @@ skipped) or `memoryClassCrunchLabsSource(files)`. It never reads video files.
90
90
  Matches become `videos` references with a title and a step order (materials
91
91
  prep videos are guardian audience); unit-level videos (a teaser) and unmatched
92
92
  ones are only reported.
93
+ - Playlists without step numbers (#4647): the live course playlists title items
94
+ `<Unit>: <name>` in teaching order. Videos that no numbered item names fall back to
95
+ title and kind evidence, never over a numbered match. Playlist items titled
96
+ `Lesson N Recap` are anchors: each pairs with lesson N+1's single `Recap` video
97
+ and they cut the playlist into one segment per lesson (lesson 1 runs from the
98
+ start; the last download lesson runs to the end). Inside a segment a video and
99
+ an item pair when each is the other's single best match (ties pair nothing).
100
+ Evidence is normalized tokens (case, `&` is `and`, simple plurals, the unit
101
+ prefix and `Part N` dropped, `N` kept) for the title, and kind words:
102
+ `Prep`/`MaterialsPrep`, `Exploration`/`StudentHandsOnExploration`,
103
+ `Anchor`/`AnchorCheckIn`, `Explained`/`ForcesExplainer`, `Wrap Up`/`WrapUp`,
104
+ `Recap`, `Check-in`, `Link up`, `Look closer`. Differing numbers (`Part 1` against
105
+ `Sumo Spheres 2`) or an item naming another kind contradict. What is left in a
106
+ segment pairs by order only when the leftover videos and items are equally many,
107
+ the videos sit on distinct steps and nothing contradicts; otherwise they stay
108
+ unmatched. No pair crosses a segment. A playlist with no anchor is not segmented:
109
+ only pairs with title evidence count and nothing is filled by order. Reasons:
110
+ `no_item` (no unnumbered item is left for it, also a whole unit without anchors),
111
+ `ambiguous` (items remain but evidence ties or the leftovers do not pair one to
112
+ one), `unanchored` (its lesson is not bounded by recap anchors in the playlist),
113
+ plus the existing `no_playlist` and `item_taken`.
93
114
  - De-duplication: repeated exports of one logical path collapse (CRC and size
94
115
  when known; the newer export wins, a changed file is `superseded`); the same
95
116
  bytes under another name or place keep one copy (the lesson-named file in its
@@ -297,6 +297,49 @@ thumbnail? }` asset ids the host stored from `receiveMember`.
297
297
  later, then attach again: the replay adds the newly accepted materials.
298
298
  - `readImport` reports `attachment` (course, unit key, part) once attached.
299
299
 
300
+ ### Bringing a unit up to a newer build (SMRT #4645)
301
+
302
+ `attachCourse({ ..., supersede: true })` is the explicit, opt-in way to replace
303
+ the build attached for a part. Without it nothing changes: another import for an
304
+ attached part is `part-conflict`, and a placement whose title differs is
305
+ `part-conflict`. It uses the same authorization (`education.course.author`),
306
+ tenant rules and lock order (import row, then unit row) as any attach.
307
+
308
+ - **Which build is newer**: manifests carry no build stamp, so "newer" is the
309
+ import record: the importing bundle's `recordedAt` must be strictly later
310
+ than the attached import's. Otherwise `stale-build` (an older or
311
+ same-instant build never overwrites a newer one; re-attaching a superseded
312
+ import is the same). `supersede` with no other build attached for the part is
313
+ an ordinary attach.
314
+ - **Identity**: sections and lessons keep their ids (derived from course id +
315
+ key), so enrolments, progress and submissions stay attached. Titles and order
316
+ follow the newer build (`renamed` counts changed titles); a key that a lower
317
+ part decided still must agree (`part-conflict`). Lesson `body` is not
318
+ rewritten. Sections and lessons the newer build no longer lists stay.
319
+ - **Part count change** stays `unit-mismatch`, supersede or not.
320
+ - **Materials**: the newer build's materials are attached; an existing row
321
+ (same lesson, asset, role) is restated in place (order, title, current again),
322
+ not a `conflict`; its audience stays the family's. Pages are restated and
323
+ never retired (learner work points at page keys).
324
+ - **Retirement**: file materials and external videos the replaced build wrote
325
+ that the newer build does not carry get `retiredAt`. Rows are never deleted,
326
+ so work that references them still resolves; learner and catalog material
327
+ lists skip retired rows. `retired` counts them. Which rows belong to the
328
+ replaced build: `sourceImportId` (new on `lesson_assets`, `course_assets`,
329
+ `lesson_external_videos`, written by every attach/apply); rows from before it
330
+ existed (null) are adopted when their lesson is one of the replaced import's
331
+ lessons, and course materials when that import had course members. Rows
332
+ from another part or source are never touched. A material that returns in a
333
+ later build is current again.
334
+ - **Audit**: the part's `CourseBundleAttachment` moves to the newer import (the
335
+ one sanctioned attachment change, `supersedeWith`) and an append-only
336
+ `CourseBundleSupersession` row (`course_bundle_supersessions`: course, unit
337
+ key, part, new import, replaced import, actor, time) records which replaced
338
+ which. The replaced import no longer reads as attached.
339
+ - **Replay** of the same supersede is `replayed: true` and changes nothing.
340
+ - Schema (additive): `retired_at` and `source_import_id` on `lesson_assets`,
341
+ `course_assets`, `lesson_external_videos`; table `course_bundle_supersessions`.
342
+
300
343
  ### Audience before and after attach
301
344
 
302
345
  `chooseMaterialAudience({ ...actor, importId, path | video: { lessonKey, videoId },
@@ -375,6 +418,8 @@ reason, duplicate-of, role, audience, section and lesson key) and
375
418
  bytes stay with the host's ingestion receipts. Rewrites and deletes throw.
376
419
  `CourseBundleAttachment` (`course_bundle_attachments`) and the mutable
377
420
  `CourseBundleAudienceChoice` (`course_bundle_audience_choices`) and
421
+ `CourseBundleSupersession` (`course_bundle_supersessions`, append-only audit of a
422
+ newer build taking a part) and
378
423
  `CourseBundlePlacement` (`course_bundle_placements`) and `CourseBundleUnit`
379
424
  (`course_bundle_units`) belong to the attach path. Normal manifest migrations create the tables and add `audience` (NULL
380
425
  on older rows) and, on `lesson_assets`/`course_assets`, `rendition`,
@@ -1 +1 @@
1
- {"version":3,"file":"catalog-service.d.ts","sourceRoot":"","sources":["../src/catalog-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AAExE,iFAAiF;AACjF,MAAM,MAAM,sBAAsB,GAChC,gBAAgB,CAAC,4BAA4B,CAAC,CAAC;AACjD,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAC1C,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CACnD,CAAC,CAAC,CAAC,CAAC;AAEL,MAAM,MAAM,iBAAiB,GAAG,CAC9B,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC,GAAG;IAC3C,+EAA+E;IAC/E,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,EACF,wBAAwB,GACxB,gCAAgC,GAChC,mCAAmC,GACnC,0BAA0B,CAAC;CAChC,KACE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;CAClE;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC;QACd,OAAO,EAAE,aAAa,CAAC;QACvB,OAAO,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,WAAW,EAAE,CAAA;SAAE,CAAC,CAAC;KAC3D,CAAC,CAAC;CACJ;AAED,qBAAa,yBAA0B,SAAQ,KAAK;;CAKnD;AAUD;;;;GAIG;AACH,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAIxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBART,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,uBAAuB,EACjC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,qBAAqB,EAC7B,OAAO,EAAE,IAAI,CAC5B,gBAAgB,EAChB,4BAA4B,CAC7B,EACgB,SAAS,EAAE,iBAAiB;IAE/C,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,gBAAgB;IAKlB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAuDjE,wFAAwF;IAClF,aAAa,CACjB,OAAO,EAAE,kBAAkB,GAAG;QAC5B,QAAQ,EAAE,MAAM,CAAC;QACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,GACA,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;CAqErC"}
1
+ {"version":3,"file":"catalog-service.d.ts","sourceRoot":"","sources":["../src/catalog-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AAExE,iFAAiF;AACjF,MAAM,MAAM,sBAAsB,GAChC,gBAAgB,CAAC,4BAA4B,CAAC,CAAC;AACjD,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAC1C,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CACnD,CAAC,CAAC,CAAC,CAAC;AAEL,MAAM,MAAM,iBAAiB,GAAG,CAC9B,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC,GAAG;IAC3C,+EAA+E;IAC/E,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,EACF,wBAAwB,GACxB,gCAAgC,GAChC,mCAAmC,GACnC,0BAA0B,CAAC;CAChC,KACE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;CAClE;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC;QACd,OAAO,EAAE,aAAa,CAAC;QACvB,OAAO,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,WAAW,EAAE,CAAA;SAAE,CAAC,CAAC;KAC3D,CAAC,CAAC;CACJ;AAED,qBAAa,yBAA0B,SAAQ,KAAK;;CAKnD;AAUD;;;;GAIG;AACH,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAIxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBART,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,uBAAuB,EACjC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,qBAAqB,EAC7B,OAAO,EAAE,IAAI,CAC5B,gBAAgB,EAChB,4BAA4B,CAC7B,EACgB,SAAS,EAAE,iBAAiB;IAE/C,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,gBAAgB;IAKlB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAuDjE,wFAAwF;IAClF,aAAa,CACjB,OAAO,EAAE,kBAAkB,GAAG;QAC5B,QAAQ,EAAE,MAAM,CAAC;QACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,GACA,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;CAsErC"}
@@ -678,6 +678,8 @@ var LessonAsset = class extends SmrtObject {
678
678
  rendition = "";
679
679
  renditionOfAssetId = null;
680
680
  thumbnailAssetId = null;
681
+ retiredAt = null;
682
+ sourceImportId = null;
681
683
  constructor(options = {}) {
682
684
  super(options);
683
685
  for (const [key, value] of Object.entries(options)) if (Object.hasOwn(this, key)) this[key] = value;
@@ -709,6 +711,14 @@ __decorateClass$10([crossPackageRef("@happyvertical/smrt-assets:Asset", { requir
709
711
  __decorateClass$10([field({ required: true })], LessonAsset.prototype, "relationship", 2);
710
712
  __decorateClass$10([crossPackageRef("@happyvertical/smrt-assets:Asset", { nullable: true })], LessonAsset.prototype, "renditionOfAssetId", 2);
711
713
  __decorateClass$10([crossPackageRef("@happyvertical/smrt-assets:Asset", { nullable: true })], LessonAsset.prototype, "thumbnailAssetId", 2);
714
+ __decorateClass$10([field({
715
+ type: "datetime",
716
+ nullable: true
717
+ })], LessonAsset.prototype, "retiredAt", 2);
718
+ __decorateClass$10([field({
719
+ type: "text",
720
+ nullable: true
721
+ })], LessonAsset.prototype, "sourceImportId", 2);
712
722
  LessonAsset = __decorateClass$10([TenantScoped({ mode: "optional" }), smrt({
713
723
  tableName: "lesson_assets",
714
724
  conflictColumns: [
@@ -733,6 +743,8 @@ var CourseAsset = class extends SmrtObject {
733
743
  rendition = "";
734
744
  renditionOfAssetId = null;
735
745
  thumbnailAssetId = null;
746
+ retiredAt = null;
747
+ sourceImportId = null;
736
748
  constructor(options = {}) {
737
749
  super(options);
738
750
  for (const [key, value] of Object.entries(options)) if (Object.hasOwn(this, key)) this[key] = value;
@@ -754,6 +766,14 @@ __decorateClass$10([crossPackageRef("@happyvertical/smrt-assets:Asset", { requir
754
766
  __decorateClass$10([field({ required: true })], CourseAsset.prototype, "relationship", 2);
755
767
  __decorateClass$10([crossPackageRef("@happyvertical/smrt-assets:Asset", { nullable: true })], CourseAsset.prototype, "renditionOfAssetId", 2);
756
768
  __decorateClass$10([crossPackageRef("@happyvertical/smrt-assets:Asset", { nullable: true })], CourseAsset.prototype, "thumbnailAssetId", 2);
769
+ __decorateClass$10([field({
770
+ type: "datetime",
771
+ nullable: true
772
+ })], CourseAsset.prototype, "retiredAt", 2);
773
+ __decorateClass$10([field({
774
+ type: "text",
775
+ nullable: true
776
+ })], CourseAsset.prototype, "sourceImportId", 2);
757
777
  CourseAsset = __decorateClass$10([TenantScoped({ mode: "optional" }), smrt({
758
778
  tableName: "course_assets",
759
779
  conflictColumns: [
@@ -812,6 +832,8 @@ var LessonExternalVideo = class extends SmrtObject {
812
832
  title = "";
813
833
  stepOrder = 0;
814
834
  audience = "";
835
+ retiredAt = null;
836
+ sourceImportId = null;
815
837
  constructor(options = {}) {
816
838
  super(options);
817
839
  for (const [key, value] of Object.entries(options)) if (Object.hasOwn(this, key)) this[key] = value;
@@ -829,6 +851,14 @@ var LessonExternalVideo = class extends SmrtObject {
829
851
  __publicField$10(LessonExternalVideo, "__smrtPackage__", "@happyvertical/smrt-education");
830
852
  __decorateClass$10([tenantId({ nullable: true })], LessonExternalVideo.prototype, "tenantId", 2);
831
853
  __decorateClass$10([foreignKey("Lesson", { required: true })], LessonExternalVideo.prototype, "lessonId", 2);
854
+ __decorateClass$10([field({
855
+ type: "datetime",
856
+ nullable: true
857
+ })], LessonExternalVideo.prototype, "retiredAt", 2);
858
+ __decorateClass$10([field({
859
+ type: "text",
860
+ nullable: true
861
+ })], LessonExternalVideo.prototype, "sourceImportId", 2);
832
862
  LessonExternalVideo = __decorateClass$10([TenantScoped({ mode: "optional" }), smrt({
833
863
  tableName: "lesson_external_videos",
834
864
  conflictColumns: [
@@ -1437,7 +1467,7 @@ var CatalogService = class {
1437
1467
  section,
1438
1468
  lessons: await Promise.all(lessons.filter((lesson) => lesson.tenantId === course.tenantId).map(async (lesson) => ({
1439
1469
  lesson: projectLesson(lesson, includeAnswerKeys),
1440
- assets: (await this.assets.byLeft(String(lesson.id))).filter((asset) => asset.tenantId === course.tenantId && (includeAnswerKeys || asset.relationship !== "answer-key" && materialAudience(asset.relationship, asset.audience) === "learner"))
1470
+ assets: (await this.assets.byLeft(String(lesson.id))).filter((asset) => asset.tenantId === course.tenantId && !asset.retiredAt && (includeAnswerKeys || asset.relationship !== "answer-key" && materialAudience(asset.relationship, asset.audience) === "learner"))
1441
1471
  })))
1442
1472
  });
1443
1473
  }
@@ -33303,8 +33333,8 @@ var SubmissionService = class SubmissionService extends LearnerService {
33303
33333
  if (!lesson) throw new LearnerAuthorizationError();
33304
33334
  const tenant = lesson.tenant_id ?? null;
33305
33335
  const learnerOnly = await this.learnerAudienceOnly(request);
33306
- const files = (await this.db.list("lesson_assets", { lesson_id: request.lessonId })).filter((row) => (row.tenant_id ?? null) === tenant && this.visibleTo(learnerOnly, String(row.relationship), String(row.audience ?? ""))).sort((a, b) => Number(a.sort_order) - Number(b.sort_order) || String(a.id).localeCompare(String(b.id)));
33307
- const videos = await this.db.tableExists("lesson_external_videos") ? (await this.db.list("lesson_external_videos", { lesson_id: request.lessonId })).filter((row) => (row.tenant_id ?? null) === tenant && this.visibleTo(learnerOnly, "video", String(row.audience ?? ""))).sort((a, b) => Number(a.step_order) - Number(b.step_order) || String(a.id).localeCompare(String(b.id))) : [];
33336
+ const files = (await this.db.list("lesson_assets", { lesson_id: request.lessonId })).filter((row) => (row.tenant_id ?? null) === tenant && !row.retired_at && this.visibleTo(learnerOnly, String(row.relationship), String(row.audience ?? ""))).sort((a, b) => Number(a.sort_order) - Number(b.sort_order) || String(a.id).localeCompare(String(b.id)));
33337
+ const videos = await this.db.tableExists("lesson_external_videos") ? (await this.db.list("lesson_external_videos", { lesson_id: request.lessonId })).filter((row) => (row.tenant_id ?? null) === tenant && !row.retired_at && this.visibleTo(learnerOnly, "video", String(row.audience ?? ""))).sort((a, b) => Number(a.step_order) - Number(b.step_order) || String(a.id).localeCompare(String(b.id))) : [];
33308
33338
  const fileViews = [];
33309
33339
  for (const row of files) fileViews.push(await this.fileView(row, "lesson"));
33310
33340
  const result = pairRenditions(files, fileViews);
@@ -33334,7 +33364,7 @@ var SubmissionService = class SubmissionService extends LearnerService {
33334
33364
  const course = await this.db.get("courses", { id: enrollment.courseId });
33335
33365
  if (!course) throw new LearnerAuthorizationError();
33336
33366
  const learnerOnly = await this.learnerAudienceOnly(request);
33337
- const rows = (await this.db.list("course_assets", { course_id: enrollment.courseId })).filter((row) => (row.tenant_id ?? null) === (course.tenant_id ?? null) && this.visibleTo(learnerOnly, String(row.relationship), String(row.audience ?? ""))).sort((a, b) => Number(a.sort_order) - Number(b.sort_order) || String(a.id).localeCompare(String(b.id)));
33367
+ const rows = (await this.db.list("course_assets", { course_id: enrollment.courseId })).filter((row) => (row.tenant_id ?? null) === (course.tenant_id ?? null) && !row.retired_at && this.visibleTo(learnerOnly, String(row.relationship), String(row.audience ?? ""))).sort((a, b) => Number(a.sort_order) - Number(b.sort_order) || String(a.id).localeCompare(String(b.id)));
33338
33368
  const views = [];
33339
33369
  for (const row of rows) views.push(await this.fileView(row, "course"));
33340
33370
  return pairRenditions(rows, views);
@@ -36940,4 +36970,4 @@ var WorkbookPhotoService = class WorkbookPhotoService extends LessonWorkService
36940
36970
  //#endregion
36941
36971
  export { LessonAlreadyCompleteError as $, Curriculum as $i, QUESTION_COMPLETION_MODES as $n, ReleasedLesson as $r, WorkFeedbackRelease as $t, QUESTION_EXTRACTION_VERSION as A, Attestation as Ai, QuestionRevision as An, CourseCoverChangeCollection as Ar, WorkbookPhotoDecision as At, SELF_ATTESTABLE_LESSON_KINDS as B, CourseAsset as Bi, normalizeQuestionContent as Bn, CourseUpgradeChoiceCollection as Br, WorkCompletionDecision as Bt, MAX_EXTRACTED_OMISSIONS as C, ReadingCollection as Ci, LearningObjectiveCollection as Cn, MAX_REFUSAL_EXPLANATION_LENGTH as Cr, canonicalGroupSharingPolicy as Ct, MAX_EXTRACTED_STUDENT_WORK as D, SubmissionIntakeBindingCollection as Di, QuestionCollection as Dn, CourseArchiveDecision as Dr, BookContextRevisionCollection as Dt, MAX_EXTRACTED_SECTIONS as E, SubmissionCollection as Ei, Question as En, readDelegation as Er, BookContextRevision as Et, normalizeWorkObservedLayout as F, Submission as Fi, resolveEnrollmentLessons as Fn, CourseRelease as Fr, FamilyIntakeAsset as Ft, EnrollmentNotActiveError as G, LESSON_ASSET_RELATIONSHIPS as Gi, MAX_PRINTABLE_ITEMS as Gn, EnrollmentAssignmentRecord as Gr, LessonWorkFiling as Gt, SubmissionStorageCleanupError as H, EXTERNAL_VIDEO_PROVIDERS as Hi, GENERATED_PRINTABLE_RELATIONSHIPS as Hn, CourseVisibilityChangeCollection as Hr, readWorkCompletion as Ht, normalizeWorkAnswerRegions as I, SubmissionAsset as Ii, applyProposalFields as In, CourseReleaseCollection as Ir, FamilyIntakeAssetCollection as It, InterestThemeError as J, LessonExternalVideoCollection as Ji, MAX_PRINTABLE_TOTAL_TEXT as Jn, EnrollmentAutoUpgradeSkipCollection as Jr, WorkAssessmentRevisionCollection as Jt, EnrollmentPaceError as K, LessonAsset as Ki, MAX_PRINTABLE_PAGES as Kn, EnrollmentAssignmentRecordCollection as Kr, LessonWorkFilingCollection as Kt, LessonWorkWorkflowRequiredError as L, SubmissionIntakeBinding as Li, draftFromRevision as Ln, CourseRevision as Lr, FamilyIntakeCollection as Lt, QuestionExtractionPendingError as M, Grade as Mi, CourseAuthoringService as Mn, CourseDiscardTombstoneCollection as Mr, WorkbookPhotoIntake as Mt, QuestionExtractionValidationError as N, LessonSelfAttestation as Ni, authoredVisibilityPermitsGroups as Nn, CourseDraftHead as Nr, WorkbookPhotoIntakeCollection as Nt, MAX_EXTRACTED_TEXT as O, VideoCollection as Oi, QuestionResponse as On, CourseArchiveDecisionCollection as Or, WorkbookPhotoAsset as Ot, LessonWorkService as P, ScannedSubmission as Pi, effectiveCourseVisibility as Pn, CourseDraftHeadCollection as Pr, FamilyIntake as Pt, LearnerValidationError as Q, parseYouTubeReference as Qi, PrintableStorageNotConfiguredError as Qn, LessonRevisionCollection as Qr, WorkFeedbackDeliveryCollection as Qt, OwnAttestationConflictError as R, parseQuestionRubric as Ri, planProposal as Rn, CourseRevisionCollection as Rr, FamilyIntakeDecision as Rt, MAX_EXTRACTED_ANCHORS as S, LessonSelfAttestationCollection as Si, LearningObjective as Sn, MAX_REFUSAL_CATEGORY_LENGTH as Sr, GroupSharingPolicyValidationError as St, MAX_EXTRACTED_QUESTIONS as T, SubmissionAssetCollection as Ti, LearningObjectiveRevisionCollection as Tn, delegationRecordFields as Tr, familyIntakeScopeId as Tt, submissionPageKey as U, GUARDIAN_ONLY_RELATIONSHIPS as Ui, LearningObjectiveKeyConflictError as Un, CurriculumCreationRecord as Ur, LessonActivity as Ut, SubmissionService as V, CourseAssetCollection as Vi, GENERATED_PRINTABLE_AUDIENCES as Vn, CourseVisibilityChange as Vr, WorkCompletionDecisionCollection as Vt, ENROLLMENT_STATUS_TRANSITIONS as W, LESSON_ASSET_DEFAULT_AUDIENCE as Wi, LearningObjectiveNotFoundError as Wn, CurriculumCreationRecordCollection as Wr, LessonActivityCollection as Wt, LearnerHistoryUnavailableError as X, externalVideoEmbedUrl as Xi, PrintableOwnerError as Xn, EnrollmentReleasePinCollection as Xr, WorkAttemptCollection as Xt, LearnerAuthorizationError as Y, MATERIAL_RENDITIONS as Yi, MAX_RESOLVE_QUESTION_REVISIONS as Yn, EnrollmentReleasePin as Yr, WorkAttempt as Yt, LearnerService as Z, materialAudience as Zi, PrintableStorageCleanupError as Zn, LessonRevision as Zr, WorkFeedbackDelivery as Zt, EXTRACTED_OMISSION_REASONS as _, GradeCollection as _i, renderGuardianWorksheetPdf as _n, CourseProposalFieldError as _r, LessonProgressTransitionCollection as _t, questionExtractionProposalDigest as a, interestThemeFromColumns as aa, mostRestrictiveLicense as ai, aggregateWorkQuestions as an, QuestionReferenceError as ar, MIN_LESSONS_PER_WEEK as at, EXTRACTED_QUESTION_STATUSES as b, LessonCollection as bi, GeneratedPrintable as bn, MAX_GENERATION_GRADE as br, GroupSharingAuthorizationError as bt, QuestionExtractionRecord as c, COURSE_VISIBILITIES as ca, AttestationCollection as ci, LessonPage as cn, COURSE_GENERATION_STATUSES as cr, EnrollmentPaceChange as ct, extractedQuestionContent as d, Exercise as da, CourseSectionCollection as di, validLessonPageKey as dn, CourseGenerationBriefError as dr, EnrollmentStatusChangeCollection as dt, Enrollment as ea, ReleasedLessonCollection as ei, WorkFeedbackReleaseCollection as en, QUESTION_FIELDS as er, LessonPutBackError as et, matchExtractedQuestions as f, ExternalCourse as fa, CurriculumCollection as fi, QuestionAuthoringService as fn, CourseGenerationInProgressError as fr, LearnerInterestThemeChange as ft, EXTRACTED_MEDIA_KINDS as g, Worksheet as ga, ExternalCourseCollection as gi, worksheetVersionId as gn, CourseGeneratorNotConfiguredError as gr, LessonProgressTransition as gt, EXTRACTED_LEGIBILITY as h, Video as ha, ExternalCompletionCollection as hi, canonical$2 as hn, CourseGenerationRefusal as hr, LessonCompletionRecordCollection as ht, QuestionExtractionService as i, MAX_INTEREST_THEME_LABEL_CHARS as ia, isGroupShareableLicense as ii, WorkCompletionConflictError as in, QuestionNotFoundError as ir, MAX_NEXT_LESSONS_COUNT as it, QuestionExtractionNotFoundError as j, ExternalCompletion as ji, QuestionRevisionCollection as jn, CourseDiscardTombstone as jr, WorkbookPhotoDecisionCollection as jt, QUESTION_EXTRACTION_SOURCE_KINDS as k, WorksheetCollection as ki, QuestionResponseCollection as kn, CourseCoverChange as kr, WorkbookPhotoAssetCollection as kt, QuestionExtractionRecordCollection as l, Course as la, AuthoredCourseCollection as li, LessonPageCollection as ln, COURSE_GENERATION_TARGETS as lr, EnrollmentPaceChangeCollection as lt, questionExtractionSourceIdentity as m, Reading as ma, ExerciseCollection as mi, buildLearnerWorksheet as mn, CourseGenerationOutputError as mr, LessonCompletionRecord as mt, preservePhotoProposal as n, LessonProgress as na, AUTHORED_RELEASE_SOURCE_KEY as ni, WORK_MATH_VOCABULARY as nn, QUESTION_PROVENANCES as nr, LessonWorkOwnedError as nt, ExtractedQuestionLink as o, parseInterestTheme as oa, CatalogAuthorizationError as oi, validateWorkAssessment as on, WorksheetVersionMismatchError as or, enrollmentView as ot, normalizeQuestionExtraction as p, Lesson as pa, EnrollmentCollection as pi, buildGuardianWorksheet as pn, CourseGenerationNotFoundError as pr, LearnerInterestThemeChangeCollection as pt, EnrollmentStatusTransitionError as q, LessonExternalVideo as qi, MAX_PRINTABLE_TEXT_LENGTH as qn, EnrollmentAutoUpgradeSkip as qr, WorkAssessmentRevision as qt, validateWorkbookPhotoIdentification as r, MAX_INTEREST_THEME_KEY_CHARS as ra, isAuthoredReleaseGroupShareable as ri, WorkAssessmentValidationError as rn, QUESTION_RESPONSE_TYPES as rr, MAX_LESSONS_PER_WEEK as rt, ExtractedQuestionLinkCollection as s, AuthoredCourse as sa, CatalogService as si, workAggregateFeedback as sn, groupSharingDigest as sr, requirePolicyBasis as st, WorkbookPhotoService as t, Learner as ta, AUTHORED_IDENTITY_SOURCE_KEY as ti, LessonWorkPlacementError as tn, QUESTION_PROMPT_SUMMARY_LENGTH as tr, LessonSkippedError as tt, QUESTION_EXTRACTION_ARGS_SCHEMA as u, CourseSection as ua, CourseCollection as ui, lessonPageView as un, COURSE_GENERATION_TERMINAL as ur, EnrollmentStatusChange as ut, EXTRACTED_ORIGINS as v, LearnerCollection as vi, renderLearnerWorksheetPdf as vn, CourseProposalNotFoundError as vr, evaluateGroupSharingPolicy as vt, MAX_EXTRACTED_PAGES as w, ScannedSubmissionCollection as wi, LearningObjectiveRevision as wn, delegationActorFields as wr, validateGroupSharingPolicy as wt, EXTRACTED_READABILITY as x, LessonProgressCollection as xi, GeneratedPrintableCollection as xn, MAX_GENERATION_OBJECTIVE_CHARS as xr, GroupSharingConflictError as xt, EXTRACTED_PAGE_ROLES as y, LessonAssetCollection as yi, es_exports as yn, MAX_GENERATION_BRIEF_FIELD_CHARS as yr, summarizeWorkEvidence as yt, OwnAttestationLessonKindError as z, validateRubricResults as zi, validateCourseGenerationOutput as zn, CourseUpgradeChoice as zr, FamilyIntakeDecisionCollection as zt };
36942
36972
 
36943
- //# sourceMappingURL=workbook-photo-service-DHP5ja82.js.map
36973
+ //# sourceMappingURL=workbook-photo-service-DGIkYT6Q.js.map