@happyvertical/smrt-education 0.0.0-stage → 0.56.2

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 (41) hide show
  1. package/AGENTS.md +32 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +139 -2
  5. package/agents/catalog-service.md +45 -0
  6. package/agents/learner-service.md +101 -0
  7. package/agents/source-import.md +63 -0
  8. package/agents/submission-service.md +122 -0
  9. package/dist/__smrt-register__.d.ts +2 -0
  10. package/dist/__smrt-register__.d.ts.map +1 -0
  11. package/dist/catalog-service.d.ts +50 -0
  12. package/dist/catalog-service.d.ts.map +1 -0
  13. package/dist/catalog.d.ts +72 -0
  14. package/dist/catalog.d.ts.map +1 -0
  15. package/dist/collections.d.ts +70 -0
  16. package/dist/collections.d.ts.map +1 -0
  17. package/dist/course-source.d.ts +25 -0
  18. package/dist/course-source.d.ts.map +1 -0
  19. package/dist/index.d.ts +24 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +24733 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/learner-service.d.ts +95 -0
  24. package/dist/learner-service.d.ts.map +1 -0
  25. package/dist/learners.d.ts +58 -0
  26. package/dist/learners.d.ts.map +1 -0
  27. package/dist/lesson-assets.d.ts +12 -0
  28. package/dist/lesson-assets.d.ts.map +1 -0
  29. package/dist/manifest.json +9484 -0
  30. package/dist/recipes.d.ts +50 -0
  31. package/dist/recipes.d.ts.map +1 -0
  32. package/dist/smrt-knowledge.json +3600 -0
  33. package/dist/source-import-service.d.ts +50 -0
  34. package/dist/source-import-service.d.ts.map +1 -0
  35. package/dist/sources/easy-peasy-parser.d.ts +23 -0
  36. package/dist/sources/easy-peasy-parser.d.ts.map +1 -0
  37. package/dist/submission-service.d.ts +110 -0
  38. package/dist/submission-service.d.ts.map +1 -0
  39. package/dist/submissions.d.ts +70 -0
  40. package/dist/submissions.d.ts.map +1 -0
  41. package/package.json +64 -3
@@ -0,0 +1,3600 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "sensitiveFieldsExcluded": true,
4
+ "generatedAt": "1970-01-01T00:00:00.000Z",
5
+ "packageName": "@happyvertical/smrt-education",
6
+ "packageVersion": "0.56.2",
7
+ "sourceManifestPath": "dist/manifest.json",
8
+ "agentDocPath": "AGENTS.md",
9
+ "sourceHashes": {
10
+ "manifest": "ba40f11d9a241581d0e6bc4ce83818f7bf546e7a43bf1ab2b0e06afe0356fe0d",
11
+ "packageJson": "370192de746d408c549f77656e5d67b952b6d55909aa471a49c360a1b63b87d3",
12
+ "agents": "bad10d5dcf79f6a2afbe9b81eb945397c9e7b7c2ca2385304e15a30939bc8f3a",
13
+ "moduleDoc:agents/learner-service.md": "74a1cf0496ae234691eef827854dd5eb51bb2d6d4a90de6875f2f327457283a7",
14
+ "moduleDoc:agents/submission-service.md": "405ca1c7c2cc8dbda32033a6b71c375fea048519119ce11da1127d399c60985c",
15
+ "moduleDoc:agents/source-import.md": "efdc436bd2a410230df5aa89669fa82d571d36085d8c9be744c2d1621de25d70"
16
+ },
17
+ "exports": [
18
+ ".",
19
+ "./manifest",
20
+ "./manifest.json"
21
+ ],
22
+ "dependencies": {
23
+ "@happyvertical/smrt-assets": "workspace:*",
24
+ "@happyvertical/smrt-core": "workspace:*",
25
+ "@happyvertical/smrt-profiles": "workspace:*",
26
+ "@happyvertical/smrt-tenancy": "workspace:*",
27
+ "@happyvertical/smrt-users": "workspace:*",
28
+ "@happyvertical/sql": "catalog:",
29
+ "pdf-lib": "1.17.1",
30
+ "@happyvertical/smrt-vitest": "workspace:*",
31
+ "@types/node": "24.13.6",
32
+ "typescript": "6.0.3",
33
+ "vite": "8.1.4",
34
+ "vitest": "4.1.10"
35
+ },
36
+ "smrtDependencies": [
37
+ "@happyvertical/smrt-assets",
38
+ "@happyvertical/smrt-core",
39
+ "@happyvertical/smrt-profiles",
40
+ "@happyvertical/smrt-tenancy",
41
+ "@happyvertical/smrt-users",
42
+ "@happyvertical/smrt-vitest"
43
+ ],
44
+ "sdkDependencies": [
45
+ "@happyvertical/sql"
46
+ ],
47
+ "tags": [
48
+ "catalog",
49
+ "courses",
50
+ "cross-package",
51
+ "education",
52
+ "smrt"
53
+ ],
54
+ "risks": [
55
+ "cross-package-refs:5",
56
+ "sensitive-fields-excluded",
57
+ "sti-inheritance"
58
+ ],
59
+ "objects": [
60
+ {
61
+ "name": "Course",
62
+ "qualifiedName": "@happyvertical/smrt-education:Course",
63
+ "displayLabelField": "title",
64
+ "collection": "courses",
65
+ "tableName": "courses",
66
+ "packageName": "@happyvertical/smrt-education",
67
+ "extends": "SmrtObject",
68
+ "fields": [
69
+ {
70
+ "name": "created_at",
71
+ "type": "datetime",
72
+ "required": false,
73
+ "columnType": "TIMESTAMP"
74
+ },
75
+ {
76
+ "name": "updated_at",
77
+ "type": "datetime",
78
+ "required": false,
79
+ "columnType": "TIMESTAMP"
80
+ },
81
+ {
82
+ "name": "tenantId",
83
+ "type": "text",
84
+ "required": false,
85
+ "columnType": "UUID"
86
+ },
87
+ {
88
+ "name": "title",
89
+ "type": "text",
90
+ "required": true,
91
+ "columnType": "TEXT"
92
+ },
93
+ {
94
+ "name": "summary",
95
+ "type": "text",
96
+ "required": false,
97
+ "columnType": "TEXT"
98
+ },
99
+ {
100
+ "name": "description",
101
+ "type": "text",
102
+ "required": false,
103
+ "columnType": "TEXT"
104
+ },
105
+ {
106
+ "name": "sourceKey",
107
+ "type": "text",
108
+ "required": false,
109
+ "columnType": "TEXT"
110
+ },
111
+ {
112
+ "name": "sourceUrl",
113
+ "type": "text",
114
+ "required": false,
115
+ "columnType": "TEXT"
116
+ },
117
+ {
118
+ "name": "sourceRef",
119
+ "type": "text",
120
+ "required": false,
121
+ "columnType": "TEXT"
122
+ },
123
+ {
124
+ "name": "license",
125
+ "type": "text",
126
+ "required": false,
127
+ "columnType": "TEXT"
128
+ },
129
+ {
130
+ "name": "licenseKind",
131
+ "type": "text",
132
+ "required": false,
133
+ "columnType": "TEXT",
134
+ "default": "own-work"
135
+ },
136
+ {
137
+ "name": "licenseNote",
138
+ "type": "text",
139
+ "required": false,
140
+ "columnType": "TEXT"
141
+ },
142
+ {
143
+ "name": "attestedBy",
144
+ "type": "text",
145
+ "required": false,
146
+ "columnType": "TEXT"
147
+ },
148
+ {
149
+ "name": "subject",
150
+ "type": "text",
151
+ "required": false,
152
+ "columnType": "TEXT"
153
+ },
154
+ {
155
+ "name": "tags",
156
+ "type": "text",
157
+ "required": false,
158
+ "columnType": "TEXT"
159
+ },
160
+ {
161
+ "name": "levelMin",
162
+ "type": "integer",
163
+ "required": true,
164
+ "columnType": "INTEGER",
165
+ "default": 0
166
+ },
167
+ {
168
+ "name": "levelMax",
169
+ "type": "integer",
170
+ "required": true,
171
+ "columnType": "INTEGER",
172
+ "default": 0
173
+ },
174
+ {
175
+ "name": "difficulty",
176
+ "type": "decimal",
177
+ "required": true,
178
+ "columnType": "REAL",
179
+ "default": 0
180
+ },
181
+ {
182
+ "name": "estimatedMinutes",
183
+ "type": "integer",
184
+ "required": true,
185
+ "columnType": "INTEGER",
186
+ "default": 0
187
+ },
188
+ {
189
+ "name": "modality",
190
+ "type": "text",
191
+ "required": false,
192
+ "columnType": "TEXT",
193
+ "default": "online"
194
+ },
195
+ {
196
+ "name": "status",
197
+ "type": "text",
198
+ "required": false,
199
+ "columnType": "TEXT",
200
+ "default": "draft"
201
+ }
202
+ ],
203
+ "relationships": [],
204
+ "methods": [
205
+ "save"
206
+ ],
207
+ "methodSignatures": [
208
+ {
209
+ "name": "save",
210
+ "async": true,
211
+ "returns": "Promise<this>"
212
+ }
213
+ ],
214
+ "tenant": {
215
+ "scoped": true,
216
+ "mode": "optional",
217
+ "field": "tenantId"
218
+ },
219
+ "tableStrategy": "sti",
220
+ "conflictColumns": [
221
+ "tenant_id",
222
+ "slug",
223
+ "context",
224
+ "_meta_type"
225
+ ],
226
+ "surfaces": [],
227
+ "withheldSurfaces": [
228
+ {
229
+ "kind": "api",
230
+ "operation": "save",
231
+ "code": "api-disabled",
232
+ "reason": "api is disabled",
233
+ "objectName": "@happyvertical/smrt-education:Course"
234
+ }
235
+ ],
236
+ "relationshipFeatures": [
237
+ "uuidColumns"
238
+ ],
239
+ "tags": [],
240
+ "risks": []
241
+ },
242
+ {
243
+ "name": "ExternalCourse",
244
+ "qualifiedName": "@happyvertical/smrt-education:ExternalCourse",
245
+ "displayLabelField": "title",
246
+ "collection": "courses",
247
+ "tableName": "courses",
248
+ "packageName": "@happyvertical/smrt-education",
249
+ "extends": "Course",
250
+ "fields": [
251
+ {
252
+ "name": "created_at",
253
+ "type": "datetime",
254
+ "required": false,
255
+ "columnType": "TIMESTAMP"
256
+ },
257
+ {
258
+ "name": "updated_at",
259
+ "type": "datetime",
260
+ "required": false,
261
+ "columnType": "TIMESTAMP"
262
+ },
263
+ {
264
+ "name": "tenantId",
265
+ "type": "text",
266
+ "required": false,
267
+ "columnType": "UUID"
268
+ },
269
+ {
270
+ "name": "title",
271
+ "type": "text",
272
+ "required": true,
273
+ "columnType": "TEXT"
274
+ },
275
+ {
276
+ "name": "summary",
277
+ "type": "text",
278
+ "required": false,
279
+ "columnType": "TEXT"
280
+ },
281
+ {
282
+ "name": "description",
283
+ "type": "text",
284
+ "required": false,
285
+ "columnType": "TEXT"
286
+ },
287
+ {
288
+ "name": "sourceKey",
289
+ "type": "text",
290
+ "required": false,
291
+ "columnType": "TEXT"
292
+ },
293
+ {
294
+ "name": "sourceUrl",
295
+ "type": "text",
296
+ "required": false,
297
+ "columnType": "TEXT"
298
+ },
299
+ {
300
+ "name": "sourceRef",
301
+ "type": "text",
302
+ "required": false,
303
+ "columnType": "TEXT"
304
+ },
305
+ {
306
+ "name": "license",
307
+ "type": "text",
308
+ "required": false,
309
+ "columnType": "TEXT"
310
+ },
311
+ {
312
+ "name": "licenseKind",
313
+ "type": "text",
314
+ "required": false,
315
+ "columnType": "TEXT",
316
+ "default": "own-work"
317
+ },
318
+ {
319
+ "name": "licenseNote",
320
+ "type": "text",
321
+ "required": false,
322
+ "columnType": "TEXT"
323
+ },
324
+ {
325
+ "name": "attestedBy",
326
+ "type": "text",
327
+ "required": false,
328
+ "columnType": "TEXT"
329
+ },
330
+ {
331
+ "name": "subject",
332
+ "type": "text",
333
+ "required": false,
334
+ "columnType": "TEXT"
335
+ },
336
+ {
337
+ "name": "tags",
338
+ "type": "text",
339
+ "required": false,
340
+ "columnType": "TEXT"
341
+ },
342
+ {
343
+ "name": "levelMin",
344
+ "type": "integer",
345
+ "required": true,
346
+ "columnType": "INTEGER",
347
+ "default": 0
348
+ },
349
+ {
350
+ "name": "levelMax",
351
+ "type": "integer",
352
+ "required": true,
353
+ "columnType": "INTEGER",
354
+ "default": 0
355
+ },
356
+ {
357
+ "name": "difficulty",
358
+ "type": "decimal",
359
+ "required": true,
360
+ "columnType": "REAL",
361
+ "default": 0
362
+ },
363
+ {
364
+ "name": "estimatedMinutes",
365
+ "type": "integer",
366
+ "required": true,
367
+ "columnType": "INTEGER",
368
+ "default": 0
369
+ },
370
+ {
371
+ "name": "modality",
372
+ "type": "text",
373
+ "required": false,
374
+ "columnType": "TEXT",
375
+ "default": "online"
376
+ },
377
+ {
378
+ "name": "status",
379
+ "type": "text",
380
+ "required": false,
381
+ "columnType": "TEXT",
382
+ "default": "draft"
383
+ }
384
+ ],
385
+ "relationships": [],
386
+ "methods": [
387
+ "save"
388
+ ],
389
+ "methodSignatures": [
390
+ {
391
+ "name": "save",
392
+ "async": true,
393
+ "returns": "Promise<this>"
394
+ }
395
+ ],
396
+ "tableStrategy": "sti",
397
+ "surfaces": [],
398
+ "withheldSurfaces": [
399
+ {
400
+ "kind": "api",
401
+ "operation": "save",
402
+ "code": "api-disabled",
403
+ "reason": "api is disabled",
404
+ "objectName": "@happyvertical/smrt-education:ExternalCourse"
405
+ }
406
+ ],
407
+ "relationshipFeatures": [
408
+ "uuidColumns"
409
+ ],
410
+ "tags": [],
411
+ "risks": []
412
+ },
413
+ {
414
+ "name": "AuthoredCourse",
415
+ "qualifiedName": "@happyvertical/smrt-education:AuthoredCourse",
416
+ "displayLabelField": "title",
417
+ "collection": "courses",
418
+ "tableName": "courses",
419
+ "packageName": "@happyvertical/smrt-education",
420
+ "extends": "Course",
421
+ "fields": [
422
+ {
423
+ "name": "created_at",
424
+ "type": "datetime",
425
+ "required": false,
426
+ "columnType": "TIMESTAMP"
427
+ },
428
+ {
429
+ "name": "updated_at",
430
+ "type": "datetime",
431
+ "required": false,
432
+ "columnType": "TIMESTAMP"
433
+ },
434
+ {
435
+ "name": "tenantId",
436
+ "type": "text",
437
+ "required": false,
438
+ "columnType": "UUID"
439
+ },
440
+ {
441
+ "name": "title",
442
+ "type": "text",
443
+ "required": true,
444
+ "columnType": "TEXT"
445
+ },
446
+ {
447
+ "name": "summary",
448
+ "type": "text",
449
+ "required": false,
450
+ "columnType": "TEXT"
451
+ },
452
+ {
453
+ "name": "description",
454
+ "type": "text",
455
+ "required": false,
456
+ "columnType": "TEXT"
457
+ },
458
+ {
459
+ "name": "sourceKey",
460
+ "type": "text",
461
+ "required": false,
462
+ "columnType": "TEXT"
463
+ },
464
+ {
465
+ "name": "sourceUrl",
466
+ "type": "text",
467
+ "required": false,
468
+ "columnType": "TEXT"
469
+ },
470
+ {
471
+ "name": "sourceRef",
472
+ "type": "text",
473
+ "required": false,
474
+ "columnType": "TEXT"
475
+ },
476
+ {
477
+ "name": "license",
478
+ "type": "text",
479
+ "required": false,
480
+ "columnType": "TEXT"
481
+ },
482
+ {
483
+ "name": "licenseKind",
484
+ "type": "text",
485
+ "required": false,
486
+ "columnType": "TEXT",
487
+ "default": "own-work"
488
+ },
489
+ {
490
+ "name": "licenseNote",
491
+ "type": "text",
492
+ "required": false,
493
+ "columnType": "TEXT"
494
+ },
495
+ {
496
+ "name": "attestedBy",
497
+ "type": "text",
498
+ "required": false,
499
+ "columnType": "TEXT"
500
+ },
501
+ {
502
+ "name": "subject",
503
+ "type": "text",
504
+ "required": false,
505
+ "columnType": "TEXT"
506
+ },
507
+ {
508
+ "name": "tags",
509
+ "type": "text",
510
+ "required": false,
511
+ "columnType": "TEXT"
512
+ },
513
+ {
514
+ "name": "levelMin",
515
+ "type": "integer",
516
+ "required": true,
517
+ "columnType": "INTEGER",
518
+ "default": 0
519
+ },
520
+ {
521
+ "name": "levelMax",
522
+ "type": "integer",
523
+ "required": true,
524
+ "columnType": "INTEGER",
525
+ "default": 0
526
+ },
527
+ {
528
+ "name": "difficulty",
529
+ "type": "decimal",
530
+ "required": true,
531
+ "columnType": "REAL",
532
+ "default": 0
533
+ },
534
+ {
535
+ "name": "estimatedMinutes",
536
+ "type": "integer",
537
+ "required": true,
538
+ "columnType": "INTEGER",
539
+ "default": 0
540
+ },
541
+ {
542
+ "name": "modality",
543
+ "type": "text",
544
+ "required": false,
545
+ "columnType": "TEXT",
546
+ "default": "online"
547
+ },
548
+ {
549
+ "name": "status",
550
+ "type": "text",
551
+ "required": false,
552
+ "columnType": "TEXT",
553
+ "default": "draft"
554
+ }
555
+ ],
556
+ "relationships": [],
557
+ "methods": [
558
+ "save"
559
+ ],
560
+ "methodSignatures": [
561
+ {
562
+ "name": "save",
563
+ "async": true,
564
+ "returns": "Promise<this>"
565
+ }
566
+ ],
567
+ "tableStrategy": "sti",
568
+ "surfaces": [],
569
+ "withheldSurfaces": [
570
+ {
571
+ "kind": "api",
572
+ "operation": "save",
573
+ "code": "api-disabled",
574
+ "reason": "api is disabled",
575
+ "objectName": "@happyvertical/smrt-education:AuthoredCourse"
576
+ }
577
+ ],
578
+ "relationshipFeatures": [
579
+ "uuidColumns"
580
+ ],
581
+ "tags": [],
582
+ "risks": []
583
+ },
584
+ {
585
+ "name": "CourseSection",
586
+ "qualifiedName": "@happyvertical/smrt-education:CourseSection",
587
+ "displayLabelField": "title",
588
+ "collection": "coursesections",
589
+ "tableName": "course_sections",
590
+ "packageName": "@happyvertical/smrt-education",
591
+ "extends": "SmrtObject",
592
+ "fields": [
593
+ {
594
+ "name": "tenantId",
595
+ "type": "text",
596
+ "required": false,
597
+ "columnType": "UUID"
598
+ },
599
+ {
600
+ "name": "courseId",
601
+ "type": "foreignKey",
602
+ "required": true,
603
+ "related": "Course",
604
+ "columnType": "UUID"
605
+ },
606
+ {
607
+ "name": "title",
608
+ "type": "text",
609
+ "required": true,
610
+ "columnType": "TEXT"
611
+ },
612
+ {
613
+ "name": "sortOrder",
614
+ "type": "integer",
615
+ "required": true,
616
+ "columnType": "INTEGER",
617
+ "default": 0
618
+ }
619
+ ],
620
+ "relationships": [
621
+ {
622
+ "name": "courseId",
623
+ "type": "foreignKey",
624
+ "required": true,
625
+ "related": "Course",
626
+ "columnType": "UUID"
627
+ }
628
+ ],
629
+ "methods": [
630
+ "save"
631
+ ],
632
+ "methodSignatures": [
633
+ {
634
+ "name": "save",
635
+ "async": true,
636
+ "returns": "Promise<this>"
637
+ }
638
+ ],
639
+ "tenant": {
640
+ "scoped": true,
641
+ "mode": "optional",
642
+ "field": "tenantId"
643
+ },
644
+ "conflictColumns": [
645
+ "tenant_id",
646
+ "slug",
647
+ "context"
648
+ ],
649
+ "surfaces": [],
650
+ "withheldSurfaces": [
651
+ {
652
+ "kind": "api",
653
+ "operation": "save",
654
+ "code": "api-disabled",
655
+ "reason": "api is disabled",
656
+ "objectName": "@happyvertical/smrt-education:CourseSection"
657
+ }
658
+ ],
659
+ "relationshipFeatures": [
660
+ "foreignKey",
661
+ "uuidColumns"
662
+ ],
663
+ "tags": [],
664
+ "risks": []
665
+ },
666
+ {
667
+ "name": "Lesson",
668
+ "qualifiedName": "@happyvertical/smrt-education:Lesson",
669
+ "displayLabelField": "title",
670
+ "collection": "lessons",
671
+ "tableName": "lessons",
672
+ "packageName": "@happyvertical/smrt-education",
673
+ "extends": "SmrtObject",
674
+ "fields": [
675
+ {
676
+ "name": "created_at",
677
+ "type": "datetime",
678
+ "required": false,
679
+ "columnType": "TIMESTAMP"
680
+ },
681
+ {
682
+ "name": "updated_at",
683
+ "type": "datetime",
684
+ "required": false,
685
+ "columnType": "TIMESTAMP"
686
+ },
687
+ {
688
+ "name": "tenantId",
689
+ "type": "text",
690
+ "required": false,
691
+ "columnType": "UUID"
692
+ },
693
+ {
694
+ "name": "sectionId",
695
+ "type": "foreignKey",
696
+ "required": true,
697
+ "related": "CourseSection",
698
+ "columnType": "UUID"
699
+ },
700
+ {
701
+ "name": "title",
702
+ "type": "text",
703
+ "required": true,
704
+ "columnType": "TEXT"
705
+ },
706
+ {
707
+ "name": "body",
708
+ "type": "text",
709
+ "required": false,
710
+ "columnType": "TEXT"
711
+ },
712
+ {
713
+ "name": "sortOrder",
714
+ "type": "integer",
715
+ "required": true,
716
+ "columnType": "INTEGER",
717
+ "default": 0
718
+ },
719
+ {
720
+ "name": "estimatedMinutes",
721
+ "type": "integer",
722
+ "required": true,
723
+ "columnType": "INTEGER",
724
+ "default": 0
725
+ },
726
+ {
727
+ "name": "difficulty",
728
+ "type": "decimal",
729
+ "required": true,
730
+ "columnType": "REAL",
731
+ "default": 0
732
+ },
733
+ {
734
+ "name": "modality",
735
+ "type": "text",
736
+ "required": false,
737
+ "columnType": "TEXT",
738
+ "default": "online"
739
+ },
740
+ {
741
+ "name": "completionRule",
742
+ "type": "text",
743
+ "required": false,
744
+ "columnType": "TEXT",
745
+ "default": "attest"
746
+ },
747
+ {
748
+ "name": "externalRef",
749
+ "type": "text",
750
+ "required": false,
751
+ "columnType": "TEXT"
752
+ },
753
+ {
754
+ "name": "externalUrl",
755
+ "type": "text",
756
+ "required": false,
757
+ "columnType": "TEXT"
758
+ }
759
+ ],
760
+ "relationships": [
761
+ {
762
+ "name": "sectionId",
763
+ "type": "foreignKey",
764
+ "required": true,
765
+ "related": "CourseSection",
766
+ "columnType": "UUID"
767
+ }
768
+ ],
769
+ "methods": [
770
+ "save"
771
+ ],
772
+ "methodSignatures": [
773
+ {
774
+ "name": "save",
775
+ "async": true,
776
+ "returns": "Promise<this>"
777
+ }
778
+ ],
779
+ "tenant": {
780
+ "scoped": true,
781
+ "mode": "optional",
782
+ "field": "tenantId"
783
+ },
784
+ "tableStrategy": "sti",
785
+ "conflictColumns": [
786
+ "tenant_id",
787
+ "slug",
788
+ "context",
789
+ "_meta_type"
790
+ ],
791
+ "surfaces": [],
792
+ "withheldSurfaces": [
793
+ {
794
+ "kind": "api",
795
+ "operation": "save",
796
+ "code": "api-disabled",
797
+ "reason": "api is disabled",
798
+ "objectName": "@happyvertical/smrt-education:Lesson"
799
+ }
800
+ ],
801
+ "relationshipFeatures": [
802
+ "foreignKey",
803
+ "uuidColumns"
804
+ ],
805
+ "tags": [],
806
+ "risks": []
807
+ },
808
+ {
809
+ "name": "Reading",
810
+ "qualifiedName": "@happyvertical/smrt-education:Reading",
811
+ "displayLabelField": "title",
812
+ "collection": "lessons",
813
+ "tableName": "lessons",
814
+ "packageName": "@happyvertical/smrt-education",
815
+ "extends": "Lesson",
816
+ "fields": [
817
+ {
818
+ "name": "created_at",
819
+ "type": "datetime",
820
+ "required": false,
821
+ "columnType": "TIMESTAMP"
822
+ },
823
+ {
824
+ "name": "updated_at",
825
+ "type": "datetime",
826
+ "required": false,
827
+ "columnType": "TIMESTAMP"
828
+ },
829
+ {
830
+ "name": "tenantId",
831
+ "type": "text",
832
+ "required": false,
833
+ "columnType": "UUID"
834
+ },
835
+ {
836
+ "name": "sectionId",
837
+ "type": "foreignKey",
838
+ "required": true,
839
+ "related": "CourseSection",
840
+ "columnType": "UUID"
841
+ },
842
+ {
843
+ "name": "title",
844
+ "type": "text",
845
+ "required": true,
846
+ "columnType": "TEXT"
847
+ },
848
+ {
849
+ "name": "body",
850
+ "type": "text",
851
+ "required": false,
852
+ "columnType": "TEXT"
853
+ },
854
+ {
855
+ "name": "sortOrder",
856
+ "type": "integer",
857
+ "required": true,
858
+ "columnType": "INTEGER",
859
+ "default": 0
860
+ },
861
+ {
862
+ "name": "estimatedMinutes",
863
+ "type": "integer",
864
+ "required": true,
865
+ "columnType": "INTEGER",
866
+ "default": 0
867
+ },
868
+ {
869
+ "name": "difficulty",
870
+ "type": "decimal",
871
+ "required": true,
872
+ "columnType": "REAL",
873
+ "default": 0
874
+ },
875
+ {
876
+ "name": "modality",
877
+ "type": "text",
878
+ "required": false,
879
+ "columnType": "TEXT",
880
+ "default": "online"
881
+ },
882
+ {
883
+ "name": "completionRule",
884
+ "type": "text",
885
+ "required": false,
886
+ "columnType": "TEXT",
887
+ "default": "attest"
888
+ },
889
+ {
890
+ "name": "externalRef",
891
+ "type": "text",
892
+ "required": false,
893
+ "columnType": "TEXT"
894
+ },
895
+ {
896
+ "name": "externalUrl",
897
+ "type": "text",
898
+ "required": false,
899
+ "columnType": "TEXT"
900
+ }
901
+ ],
902
+ "relationships": [
903
+ {
904
+ "name": "sectionId",
905
+ "type": "foreignKey",
906
+ "required": true,
907
+ "related": "CourseSection",
908
+ "columnType": "UUID"
909
+ }
910
+ ],
911
+ "methods": [
912
+ "save"
913
+ ],
914
+ "methodSignatures": [
915
+ {
916
+ "name": "save",
917
+ "async": true,
918
+ "returns": "Promise<this>"
919
+ }
920
+ ],
921
+ "tableStrategy": "sti",
922
+ "surfaces": [],
923
+ "withheldSurfaces": [
924
+ {
925
+ "kind": "api",
926
+ "operation": "save",
927
+ "code": "api-disabled",
928
+ "reason": "api is disabled",
929
+ "objectName": "@happyvertical/smrt-education:Reading"
930
+ }
931
+ ],
932
+ "relationshipFeatures": [
933
+ "foreignKey",
934
+ "uuidColumns"
935
+ ],
936
+ "tags": [],
937
+ "risks": []
938
+ },
939
+ {
940
+ "name": "Video",
941
+ "qualifiedName": "@happyvertical/smrt-education:Video",
942
+ "displayLabelField": "title",
943
+ "collection": "lessons",
944
+ "tableName": "lessons",
945
+ "packageName": "@happyvertical/smrt-education",
946
+ "extends": "Lesson",
947
+ "fields": [
948
+ {
949
+ "name": "created_at",
950
+ "type": "datetime",
951
+ "required": false,
952
+ "columnType": "TIMESTAMP"
953
+ },
954
+ {
955
+ "name": "updated_at",
956
+ "type": "datetime",
957
+ "required": false,
958
+ "columnType": "TIMESTAMP"
959
+ },
960
+ {
961
+ "name": "tenantId",
962
+ "type": "text",
963
+ "required": false,
964
+ "columnType": "UUID"
965
+ },
966
+ {
967
+ "name": "sectionId",
968
+ "type": "foreignKey",
969
+ "required": true,
970
+ "related": "CourseSection",
971
+ "columnType": "UUID"
972
+ },
973
+ {
974
+ "name": "title",
975
+ "type": "text",
976
+ "required": true,
977
+ "columnType": "TEXT"
978
+ },
979
+ {
980
+ "name": "body",
981
+ "type": "text",
982
+ "required": false,
983
+ "columnType": "TEXT"
984
+ },
985
+ {
986
+ "name": "sortOrder",
987
+ "type": "integer",
988
+ "required": true,
989
+ "columnType": "INTEGER",
990
+ "default": 0
991
+ },
992
+ {
993
+ "name": "estimatedMinutes",
994
+ "type": "integer",
995
+ "required": true,
996
+ "columnType": "INTEGER",
997
+ "default": 0
998
+ },
999
+ {
1000
+ "name": "difficulty",
1001
+ "type": "decimal",
1002
+ "required": true,
1003
+ "columnType": "REAL",
1004
+ "default": 0
1005
+ },
1006
+ {
1007
+ "name": "modality",
1008
+ "type": "text",
1009
+ "required": false,
1010
+ "columnType": "TEXT",
1011
+ "default": "online"
1012
+ },
1013
+ {
1014
+ "name": "completionRule",
1015
+ "type": "text",
1016
+ "required": false,
1017
+ "columnType": "TEXT",
1018
+ "default": "attest"
1019
+ },
1020
+ {
1021
+ "name": "externalRef",
1022
+ "type": "text",
1023
+ "required": false,
1024
+ "columnType": "TEXT"
1025
+ },
1026
+ {
1027
+ "name": "externalUrl",
1028
+ "type": "text",
1029
+ "required": false,
1030
+ "columnType": "TEXT"
1031
+ },
1032
+ {
1033
+ "name": "videoUrl",
1034
+ "type": "text",
1035
+ "required": false,
1036
+ "columnType": "TEXT"
1037
+ }
1038
+ ],
1039
+ "relationships": [
1040
+ {
1041
+ "name": "sectionId",
1042
+ "type": "foreignKey",
1043
+ "required": true,
1044
+ "related": "CourseSection",
1045
+ "columnType": "UUID"
1046
+ }
1047
+ ],
1048
+ "methods": [
1049
+ "save"
1050
+ ],
1051
+ "methodSignatures": [
1052
+ {
1053
+ "name": "save",
1054
+ "async": true,
1055
+ "returns": "Promise<this>"
1056
+ }
1057
+ ],
1058
+ "tableStrategy": "sti",
1059
+ "surfaces": [],
1060
+ "withheldSurfaces": [
1061
+ {
1062
+ "kind": "api",
1063
+ "operation": "save",
1064
+ "code": "api-disabled",
1065
+ "reason": "api is disabled",
1066
+ "objectName": "@happyvertical/smrt-education:Video"
1067
+ }
1068
+ ],
1069
+ "relationshipFeatures": [
1070
+ "foreignKey",
1071
+ "uuidColumns"
1072
+ ],
1073
+ "tags": [],
1074
+ "risks": []
1075
+ },
1076
+ {
1077
+ "name": "Worksheet",
1078
+ "qualifiedName": "@happyvertical/smrt-education:Worksheet",
1079
+ "displayLabelField": "title",
1080
+ "collection": "lessons",
1081
+ "tableName": "lessons",
1082
+ "packageName": "@happyvertical/smrt-education",
1083
+ "extends": "Lesson",
1084
+ "fields": [
1085
+ {
1086
+ "name": "created_at",
1087
+ "type": "datetime",
1088
+ "required": false,
1089
+ "columnType": "TIMESTAMP"
1090
+ },
1091
+ {
1092
+ "name": "updated_at",
1093
+ "type": "datetime",
1094
+ "required": false,
1095
+ "columnType": "TIMESTAMP"
1096
+ },
1097
+ {
1098
+ "name": "tenantId",
1099
+ "type": "text",
1100
+ "required": false,
1101
+ "columnType": "UUID"
1102
+ },
1103
+ {
1104
+ "name": "sectionId",
1105
+ "type": "foreignKey",
1106
+ "required": true,
1107
+ "related": "CourseSection",
1108
+ "columnType": "UUID"
1109
+ },
1110
+ {
1111
+ "name": "title",
1112
+ "type": "text",
1113
+ "required": true,
1114
+ "columnType": "TEXT"
1115
+ },
1116
+ {
1117
+ "name": "body",
1118
+ "type": "text",
1119
+ "required": false,
1120
+ "columnType": "TEXT"
1121
+ },
1122
+ {
1123
+ "name": "sortOrder",
1124
+ "type": "integer",
1125
+ "required": true,
1126
+ "columnType": "INTEGER",
1127
+ "default": 0
1128
+ },
1129
+ {
1130
+ "name": "estimatedMinutes",
1131
+ "type": "integer",
1132
+ "required": true,
1133
+ "columnType": "INTEGER",
1134
+ "default": 0
1135
+ },
1136
+ {
1137
+ "name": "difficulty",
1138
+ "type": "decimal",
1139
+ "required": true,
1140
+ "columnType": "REAL",
1141
+ "default": 0
1142
+ },
1143
+ {
1144
+ "name": "modality",
1145
+ "type": "text",
1146
+ "required": false,
1147
+ "columnType": "TEXT",
1148
+ "default": "online"
1149
+ },
1150
+ {
1151
+ "name": "completionRule",
1152
+ "type": "text",
1153
+ "required": false,
1154
+ "columnType": "TEXT",
1155
+ "default": "submit"
1156
+ },
1157
+ {
1158
+ "name": "externalRef",
1159
+ "type": "text",
1160
+ "required": false,
1161
+ "columnType": "TEXT"
1162
+ },
1163
+ {
1164
+ "name": "externalUrl",
1165
+ "type": "text",
1166
+ "required": false,
1167
+ "columnType": "TEXT"
1168
+ },
1169
+ {
1170
+ "name": "printableUrl",
1171
+ "type": "text",
1172
+ "required": false,
1173
+ "columnType": "TEXT"
1174
+ },
1175
+ {
1176
+ "name": "answerUrl",
1177
+ "type": "text",
1178
+ "required": false,
1179
+ "columnType": "TEXT"
1180
+ },
1181
+ {
1182
+ "name": "pages",
1183
+ "type": "integer",
1184
+ "required": true,
1185
+ "columnType": "INTEGER",
1186
+ "default": 0
1187
+ }
1188
+ ],
1189
+ "relationships": [
1190
+ {
1191
+ "name": "sectionId",
1192
+ "type": "foreignKey",
1193
+ "required": true,
1194
+ "related": "CourseSection",
1195
+ "columnType": "UUID"
1196
+ }
1197
+ ],
1198
+ "methods": [
1199
+ "save"
1200
+ ],
1201
+ "methodSignatures": [
1202
+ {
1203
+ "name": "save",
1204
+ "async": true,
1205
+ "returns": "Promise<this>"
1206
+ }
1207
+ ],
1208
+ "tableStrategy": "sti",
1209
+ "surfaces": [],
1210
+ "withheldSurfaces": [
1211
+ {
1212
+ "kind": "api",
1213
+ "operation": "save",
1214
+ "code": "api-disabled",
1215
+ "reason": "api is disabled",
1216
+ "objectName": "@happyvertical/smrt-education:Worksheet"
1217
+ }
1218
+ ],
1219
+ "relationshipFeatures": [
1220
+ "foreignKey",
1221
+ "uuidColumns"
1222
+ ],
1223
+ "tags": [],
1224
+ "risks": []
1225
+ },
1226
+ {
1227
+ "name": "Exercise",
1228
+ "qualifiedName": "@happyvertical/smrt-education:Exercise",
1229
+ "displayLabelField": "title",
1230
+ "collection": "lessons",
1231
+ "tableName": "lessons",
1232
+ "packageName": "@happyvertical/smrt-education",
1233
+ "extends": "Lesson",
1234
+ "fields": [
1235
+ {
1236
+ "name": "created_at",
1237
+ "type": "datetime",
1238
+ "required": false,
1239
+ "columnType": "TIMESTAMP"
1240
+ },
1241
+ {
1242
+ "name": "updated_at",
1243
+ "type": "datetime",
1244
+ "required": false,
1245
+ "columnType": "TIMESTAMP"
1246
+ },
1247
+ {
1248
+ "name": "tenantId",
1249
+ "type": "text",
1250
+ "required": false,
1251
+ "columnType": "UUID"
1252
+ },
1253
+ {
1254
+ "name": "sectionId",
1255
+ "type": "foreignKey",
1256
+ "required": true,
1257
+ "related": "CourseSection",
1258
+ "columnType": "UUID"
1259
+ },
1260
+ {
1261
+ "name": "title",
1262
+ "type": "text",
1263
+ "required": true,
1264
+ "columnType": "TEXT"
1265
+ },
1266
+ {
1267
+ "name": "body",
1268
+ "type": "text",
1269
+ "required": false,
1270
+ "columnType": "TEXT"
1271
+ },
1272
+ {
1273
+ "name": "sortOrder",
1274
+ "type": "integer",
1275
+ "required": true,
1276
+ "columnType": "INTEGER",
1277
+ "default": 0
1278
+ },
1279
+ {
1280
+ "name": "estimatedMinutes",
1281
+ "type": "integer",
1282
+ "required": true,
1283
+ "columnType": "INTEGER",
1284
+ "default": 0
1285
+ },
1286
+ {
1287
+ "name": "difficulty",
1288
+ "type": "decimal",
1289
+ "required": true,
1290
+ "columnType": "REAL",
1291
+ "default": 0
1292
+ },
1293
+ {
1294
+ "name": "modality",
1295
+ "type": "text",
1296
+ "required": false,
1297
+ "columnType": "TEXT",
1298
+ "default": "online"
1299
+ },
1300
+ {
1301
+ "name": "completionRule",
1302
+ "type": "text",
1303
+ "required": false,
1304
+ "columnType": "TEXT",
1305
+ "default": "attest"
1306
+ },
1307
+ {
1308
+ "name": "externalRef",
1309
+ "type": "text",
1310
+ "required": false,
1311
+ "columnType": "TEXT"
1312
+ },
1313
+ {
1314
+ "name": "externalUrl",
1315
+ "type": "text",
1316
+ "required": false,
1317
+ "columnType": "TEXT"
1318
+ }
1319
+ ],
1320
+ "relationships": [
1321
+ {
1322
+ "name": "sectionId",
1323
+ "type": "foreignKey",
1324
+ "required": true,
1325
+ "related": "CourseSection",
1326
+ "columnType": "UUID"
1327
+ }
1328
+ ],
1329
+ "methods": [
1330
+ "save"
1331
+ ],
1332
+ "methodSignatures": [
1333
+ {
1334
+ "name": "save",
1335
+ "async": true,
1336
+ "returns": "Promise<this>"
1337
+ }
1338
+ ],
1339
+ "tableStrategy": "sti",
1340
+ "surfaces": [],
1341
+ "withheldSurfaces": [
1342
+ {
1343
+ "kind": "api",
1344
+ "operation": "save",
1345
+ "code": "api-disabled",
1346
+ "reason": "api is disabled",
1347
+ "objectName": "@happyvertical/smrt-education:Exercise"
1348
+ }
1349
+ ],
1350
+ "relationshipFeatures": [
1351
+ "foreignKey",
1352
+ "uuidColumns"
1353
+ ],
1354
+ "tags": [],
1355
+ "risks": []
1356
+ },
1357
+ {
1358
+ "name": "CourseCollection",
1359
+ "qualifiedName": "@happyvertical/smrt-education:CourseCollection",
1360
+ "collection": "courses",
1361
+ "tableName": "courses",
1362
+ "packageName": "@happyvertical/smrt-education",
1363
+ "extends": "SmrtCollection",
1364
+ "fields": [],
1365
+ "relationships": [],
1366
+ "methods": [],
1367
+ "surfaces": [],
1368
+ "relationshipFeatures": [
1369
+ "uuidColumns"
1370
+ ],
1371
+ "tags": [],
1372
+ "risks": []
1373
+ },
1374
+ {
1375
+ "name": "ExternalCourseCollection",
1376
+ "qualifiedName": "@happyvertical/smrt-education:ExternalCourseCollection",
1377
+ "collection": "courses",
1378
+ "tableName": "courses",
1379
+ "packageName": "@happyvertical/smrt-education",
1380
+ "extends": "SmrtCollection",
1381
+ "fields": [],
1382
+ "relationships": [],
1383
+ "methods": [],
1384
+ "surfaces": [],
1385
+ "relationshipFeatures": [
1386
+ "uuidColumns"
1387
+ ],
1388
+ "tags": [],
1389
+ "risks": []
1390
+ },
1391
+ {
1392
+ "name": "AuthoredCourseCollection",
1393
+ "qualifiedName": "@happyvertical/smrt-education:AuthoredCourseCollection",
1394
+ "collection": "courses",
1395
+ "tableName": "courses",
1396
+ "packageName": "@happyvertical/smrt-education",
1397
+ "extends": "SmrtCollection",
1398
+ "fields": [],
1399
+ "relationships": [],
1400
+ "methods": [],
1401
+ "surfaces": [],
1402
+ "relationshipFeatures": [
1403
+ "uuidColumns"
1404
+ ],
1405
+ "tags": [],
1406
+ "risks": []
1407
+ },
1408
+ {
1409
+ "name": "CourseSectionCollection",
1410
+ "qualifiedName": "@happyvertical/smrt-education:CourseSectionCollection",
1411
+ "collection": "coursesections",
1412
+ "tableName": "course_sections",
1413
+ "packageName": "@happyvertical/smrt-education",
1414
+ "extends": "SmrtCollection",
1415
+ "fields": [],
1416
+ "relationships": [],
1417
+ "methods": [],
1418
+ "surfaces": [],
1419
+ "relationshipFeatures": [
1420
+ "uuidColumns"
1421
+ ],
1422
+ "tags": [],
1423
+ "risks": []
1424
+ },
1425
+ {
1426
+ "name": "LessonCollection",
1427
+ "qualifiedName": "@happyvertical/smrt-education:LessonCollection",
1428
+ "collection": "lessons",
1429
+ "tableName": "lessons",
1430
+ "packageName": "@happyvertical/smrt-education",
1431
+ "extends": "SmrtCollection",
1432
+ "fields": [],
1433
+ "relationships": [],
1434
+ "methods": [],
1435
+ "surfaces": [],
1436
+ "relationshipFeatures": [
1437
+ "uuidColumns"
1438
+ ],
1439
+ "tags": [],
1440
+ "risks": []
1441
+ },
1442
+ {
1443
+ "name": "ReadingCollection",
1444
+ "qualifiedName": "@happyvertical/smrt-education:ReadingCollection",
1445
+ "collection": "lessons",
1446
+ "tableName": "lessons",
1447
+ "packageName": "@happyvertical/smrt-education",
1448
+ "extends": "SmrtCollection",
1449
+ "fields": [],
1450
+ "relationships": [],
1451
+ "methods": [],
1452
+ "surfaces": [],
1453
+ "relationshipFeatures": [
1454
+ "uuidColumns"
1455
+ ],
1456
+ "tags": [],
1457
+ "risks": []
1458
+ },
1459
+ {
1460
+ "name": "VideoCollection",
1461
+ "qualifiedName": "@happyvertical/smrt-education:VideoCollection",
1462
+ "collection": "lessons",
1463
+ "tableName": "lessons",
1464
+ "packageName": "@happyvertical/smrt-education",
1465
+ "extends": "SmrtCollection",
1466
+ "fields": [],
1467
+ "relationships": [],
1468
+ "methods": [],
1469
+ "surfaces": [],
1470
+ "relationshipFeatures": [
1471
+ "uuidColumns"
1472
+ ],
1473
+ "tags": [],
1474
+ "risks": []
1475
+ },
1476
+ {
1477
+ "name": "WorksheetCollection",
1478
+ "qualifiedName": "@happyvertical/smrt-education:WorksheetCollection",
1479
+ "collection": "lessons",
1480
+ "tableName": "lessons",
1481
+ "packageName": "@happyvertical/smrt-education",
1482
+ "extends": "SmrtCollection",
1483
+ "fields": [],
1484
+ "relationships": [],
1485
+ "methods": [],
1486
+ "surfaces": [],
1487
+ "relationshipFeatures": [
1488
+ "uuidColumns"
1489
+ ],
1490
+ "tags": [],
1491
+ "risks": []
1492
+ },
1493
+ {
1494
+ "name": "ExerciseCollection",
1495
+ "qualifiedName": "@happyvertical/smrt-education:ExerciseCollection",
1496
+ "collection": "lessons",
1497
+ "tableName": "lessons",
1498
+ "packageName": "@happyvertical/smrt-education",
1499
+ "extends": "SmrtCollection",
1500
+ "fields": [],
1501
+ "relationships": [],
1502
+ "methods": [],
1503
+ "surfaces": [],
1504
+ "relationshipFeatures": [
1505
+ "uuidColumns"
1506
+ ],
1507
+ "tags": [],
1508
+ "risks": []
1509
+ },
1510
+ {
1511
+ "name": "LessonAssetCollection",
1512
+ "qualifiedName": "@happyvertical/smrt-education:LessonAssetCollection",
1513
+ "collection": "lessonassets",
1514
+ "tableName": "lesson_assets",
1515
+ "packageName": "@happyvertical/smrt-education",
1516
+ "extends": "SmrtJunction",
1517
+ "fields": [],
1518
+ "relationships": [],
1519
+ "methods": [
1520
+ "attach",
1521
+ "byLeft",
1522
+ "byRight",
1523
+ "detach",
1524
+ "setLinks"
1525
+ ],
1526
+ "methodSignatures": [
1527
+ {
1528
+ "name": "attach",
1529
+ "async": true,
1530
+ "params": [
1531
+ "leftId: string",
1532
+ "rightId: string",
1533
+ "opts?: JunctionAttachOptions"
1534
+ ],
1535
+ "returns": "Promise<TItem>"
1536
+ },
1537
+ {
1538
+ "name": "byLeft",
1539
+ "async": true,
1540
+ "params": [
1541
+ "leftId: string",
1542
+ "opts?: JunctionFilterOptions"
1543
+ ],
1544
+ "returns": "Promise<TItem[]>"
1545
+ },
1546
+ {
1547
+ "name": "byRight",
1548
+ "async": true,
1549
+ "params": [
1550
+ "rightId: string",
1551
+ "opts?: JunctionFilterOptions"
1552
+ ],
1553
+ "returns": "Promise<TItem[]>"
1554
+ },
1555
+ {
1556
+ "name": "detach",
1557
+ "async": true,
1558
+ "params": [
1559
+ "leftId: string",
1560
+ "rightId: string",
1561
+ "opts?: JunctionFilterOptions"
1562
+ ],
1563
+ "returns": "Promise<void>"
1564
+ },
1565
+ {
1566
+ "name": "setLinks",
1567
+ "async": true,
1568
+ "params": [
1569
+ "leftId: string",
1570
+ "rightIds: string[]",
1571
+ "opts?: JunctionAttachOptions"
1572
+ ],
1573
+ "returns": "Promise<void>"
1574
+ }
1575
+ ],
1576
+ "surfaces": [],
1577
+ "withheldSurfaces": [
1578
+ {
1579
+ "kind": "api",
1580
+ "operation": "attach",
1581
+ "code": "api-disabled",
1582
+ "reason": "api is disabled",
1583
+ "objectName": "@happyvertical/smrt-education:LessonAssetCollection"
1584
+ },
1585
+ {
1586
+ "kind": "api",
1587
+ "operation": "byLeft",
1588
+ "code": "api-disabled",
1589
+ "reason": "api is disabled",
1590
+ "objectName": "@happyvertical/smrt-education:LessonAssetCollection"
1591
+ },
1592
+ {
1593
+ "kind": "api",
1594
+ "operation": "byRight",
1595
+ "code": "api-disabled",
1596
+ "reason": "api is disabled",
1597
+ "objectName": "@happyvertical/smrt-education:LessonAssetCollection"
1598
+ },
1599
+ {
1600
+ "kind": "api",
1601
+ "operation": "detach",
1602
+ "code": "api-disabled",
1603
+ "reason": "api is disabled",
1604
+ "objectName": "@happyvertical/smrt-education:LessonAssetCollection"
1605
+ },
1606
+ {
1607
+ "kind": "api",
1608
+ "operation": "setLinks",
1609
+ "code": "api-disabled",
1610
+ "reason": "api is disabled",
1611
+ "objectName": "@happyvertical/smrt-education:LessonAssetCollection"
1612
+ }
1613
+ ],
1614
+ "relationshipFeatures": [
1615
+ "SmrtJunction",
1616
+ "uuidColumns"
1617
+ ],
1618
+ "tags": [],
1619
+ "risks": []
1620
+ },
1621
+ {
1622
+ "name": "LearnerCollection",
1623
+ "qualifiedName": "@happyvertical/smrt-education:LearnerCollection",
1624
+ "collection": "learners",
1625
+ "tableName": "learners",
1626
+ "packageName": "@happyvertical/smrt-education",
1627
+ "extends": "SmrtCollection",
1628
+ "fields": [],
1629
+ "relationships": [],
1630
+ "methods": [],
1631
+ "surfaces": [],
1632
+ "relationshipFeatures": [
1633
+ "uuidColumns"
1634
+ ],
1635
+ "tags": [],
1636
+ "risks": []
1637
+ },
1638
+ {
1639
+ "name": "CurriculumCollection",
1640
+ "qualifiedName": "@happyvertical/smrt-education:CurriculumCollection",
1641
+ "collection": "curriculums",
1642
+ "tableName": "curricula",
1643
+ "packageName": "@happyvertical/smrt-education",
1644
+ "extends": "SmrtCollection",
1645
+ "fields": [],
1646
+ "relationships": [],
1647
+ "methods": [],
1648
+ "surfaces": [],
1649
+ "relationshipFeatures": [
1650
+ "uuidColumns"
1651
+ ],
1652
+ "tags": [],
1653
+ "risks": []
1654
+ },
1655
+ {
1656
+ "name": "EnrollmentCollection",
1657
+ "qualifiedName": "@happyvertical/smrt-education:EnrollmentCollection",
1658
+ "collection": "enrollments",
1659
+ "tableName": "enrollments",
1660
+ "packageName": "@happyvertical/smrt-education",
1661
+ "extends": "SmrtCollection",
1662
+ "fields": [],
1663
+ "relationships": [],
1664
+ "methods": [],
1665
+ "surfaces": [],
1666
+ "relationshipFeatures": [
1667
+ "uuidColumns"
1668
+ ],
1669
+ "tags": [],
1670
+ "risks": []
1671
+ },
1672
+ {
1673
+ "name": "LessonProgressCollection",
1674
+ "qualifiedName": "@happyvertical/smrt-education:LessonProgressCollection",
1675
+ "collection": "lessonprogresses",
1676
+ "tableName": "lesson_progress",
1677
+ "packageName": "@happyvertical/smrt-education",
1678
+ "extends": "SmrtCollection",
1679
+ "fields": [],
1680
+ "relationships": [],
1681
+ "methods": [],
1682
+ "surfaces": [],
1683
+ "relationshipFeatures": [
1684
+ "uuidColumns"
1685
+ ],
1686
+ "tags": [],
1687
+ "risks": []
1688
+ },
1689
+ {
1690
+ "name": "SubmissionCollection",
1691
+ "qualifiedName": "@happyvertical/smrt-education:SubmissionCollection",
1692
+ "collection": "submissions",
1693
+ "tableName": "submissions",
1694
+ "packageName": "@happyvertical/smrt-education",
1695
+ "extends": "SmrtCollection",
1696
+ "fields": [],
1697
+ "relationships": [],
1698
+ "methods": [],
1699
+ "surfaces": [],
1700
+ "relationshipFeatures": [
1701
+ "uuidColumns"
1702
+ ],
1703
+ "tags": [],
1704
+ "risks": []
1705
+ },
1706
+ {
1707
+ "name": "ScannedSubmissionCollection",
1708
+ "qualifiedName": "@happyvertical/smrt-education:ScannedSubmissionCollection",
1709
+ "collection": "submissions",
1710
+ "tableName": "submissions",
1711
+ "packageName": "@happyvertical/smrt-education",
1712
+ "extends": "SmrtCollection",
1713
+ "fields": [],
1714
+ "relationships": [],
1715
+ "methods": [],
1716
+ "surfaces": [],
1717
+ "relationshipFeatures": [
1718
+ "uuidColumns"
1719
+ ],
1720
+ "tags": [],
1721
+ "risks": []
1722
+ },
1723
+ {
1724
+ "name": "ExternalCompletionCollection",
1725
+ "qualifiedName": "@happyvertical/smrt-education:ExternalCompletionCollection",
1726
+ "collection": "submissions",
1727
+ "tableName": "submissions",
1728
+ "packageName": "@happyvertical/smrt-education",
1729
+ "extends": "SmrtCollection",
1730
+ "fields": [],
1731
+ "relationships": [],
1732
+ "methods": [],
1733
+ "surfaces": [],
1734
+ "relationshipFeatures": [
1735
+ "uuidColumns"
1736
+ ],
1737
+ "tags": [],
1738
+ "risks": []
1739
+ },
1740
+ {
1741
+ "name": "AttestationCollection",
1742
+ "qualifiedName": "@happyvertical/smrt-education:AttestationCollection",
1743
+ "collection": "submissions",
1744
+ "tableName": "submissions",
1745
+ "packageName": "@happyvertical/smrt-education",
1746
+ "extends": "SmrtCollection",
1747
+ "fields": [],
1748
+ "relationships": [],
1749
+ "methods": [],
1750
+ "surfaces": [],
1751
+ "relationshipFeatures": [
1752
+ "uuidColumns"
1753
+ ],
1754
+ "tags": [],
1755
+ "risks": []
1756
+ },
1757
+ {
1758
+ "name": "GradeCollection",
1759
+ "qualifiedName": "@happyvertical/smrt-education:GradeCollection",
1760
+ "collection": "grades",
1761
+ "tableName": "grades",
1762
+ "packageName": "@happyvertical/smrt-education",
1763
+ "extends": "SmrtCollection",
1764
+ "fields": [],
1765
+ "relationships": [],
1766
+ "methods": [],
1767
+ "surfaces": [],
1768
+ "relationshipFeatures": [
1769
+ "uuidColumns"
1770
+ ],
1771
+ "tags": [],
1772
+ "risks": []
1773
+ },
1774
+ {
1775
+ "name": "SubmissionAssetCollection",
1776
+ "qualifiedName": "@happyvertical/smrt-education:SubmissionAssetCollection",
1777
+ "collection": "submissionassets",
1778
+ "tableName": "submission_assets",
1779
+ "packageName": "@happyvertical/smrt-education",
1780
+ "extends": "SmrtJunction",
1781
+ "fields": [],
1782
+ "relationships": [],
1783
+ "methods": [
1784
+ "attach",
1785
+ "byLeft",
1786
+ "byRight",
1787
+ "detach",
1788
+ "setLinks"
1789
+ ],
1790
+ "methodSignatures": [
1791
+ {
1792
+ "name": "attach",
1793
+ "async": true,
1794
+ "params": [
1795
+ "leftId: string",
1796
+ "rightId: string",
1797
+ "opts?: JunctionAttachOptions"
1798
+ ],
1799
+ "returns": "Promise<TItem>"
1800
+ },
1801
+ {
1802
+ "name": "byLeft",
1803
+ "async": true,
1804
+ "params": [
1805
+ "leftId: string",
1806
+ "opts?: JunctionFilterOptions"
1807
+ ],
1808
+ "returns": "Promise<TItem[]>"
1809
+ },
1810
+ {
1811
+ "name": "byRight",
1812
+ "async": true,
1813
+ "params": [
1814
+ "rightId: string",
1815
+ "opts?: JunctionFilterOptions"
1816
+ ],
1817
+ "returns": "Promise<TItem[]>"
1818
+ },
1819
+ {
1820
+ "name": "detach",
1821
+ "async": true,
1822
+ "params": [
1823
+ "leftId: string",
1824
+ "rightId: string",
1825
+ "opts?: JunctionFilterOptions"
1826
+ ],
1827
+ "returns": "Promise<void>"
1828
+ },
1829
+ {
1830
+ "name": "setLinks",
1831
+ "async": true,
1832
+ "params": [
1833
+ "leftId: string",
1834
+ "rightIds: string[]",
1835
+ "opts?: JunctionAttachOptions"
1836
+ ],
1837
+ "returns": "Promise<void>"
1838
+ }
1839
+ ],
1840
+ "surfaces": [],
1841
+ "withheldSurfaces": [
1842
+ {
1843
+ "kind": "api",
1844
+ "operation": "attach",
1845
+ "code": "api-disabled",
1846
+ "reason": "api is disabled",
1847
+ "objectName": "@happyvertical/smrt-education:SubmissionAssetCollection"
1848
+ },
1849
+ {
1850
+ "kind": "api",
1851
+ "operation": "byLeft",
1852
+ "code": "api-disabled",
1853
+ "reason": "api is disabled",
1854
+ "objectName": "@happyvertical/smrt-education:SubmissionAssetCollection"
1855
+ },
1856
+ {
1857
+ "kind": "api",
1858
+ "operation": "byRight",
1859
+ "code": "api-disabled",
1860
+ "reason": "api is disabled",
1861
+ "objectName": "@happyvertical/smrt-education:SubmissionAssetCollection"
1862
+ },
1863
+ {
1864
+ "kind": "api",
1865
+ "operation": "detach",
1866
+ "code": "api-disabled",
1867
+ "reason": "api is disabled",
1868
+ "objectName": "@happyvertical/smrt-education:SubmissionAssetCollection"
1869
+ },
1870
+ {
1871
+ "kind": "api",
1872
+ "operation": "setLinks",
1873
+ "code": "api-disabled",
1874
+ "reason": "api is disabled",
1875
+ "objectName": "@happyvertical/smrt-education:SubmissionAssetCollection"
1876
+ }
1877
+ ],
1878
+ "relationshipFeatures": [
1879
+ "SmrtJunction",
1880
+ "uuidColumns"
1881
+ ],
1882
+ "tags": [],
1883
+ "risks": []
1884
+ },
1885
+ {
1886
+ "name": "CourseSource",
1887
+ "qualifiedName": "@happyvertical/smrt-education:CourseSource",
1888
+ "displayLabelField": "name",
1889
+ "collection": "coursesources",
1890
+ "description": "Host-managed source policy; deliberately has no generated CRUD surface.",
1891
+ "tableName": "course_sources",
1892
+ "packageName": "@happyvertical/smrt-education",
1893
+ "extends": "SmrtObject",
1894
+ "fields": [
1895
+ {
1896
+ "name": "tenantId",
1897
+ "type": "text",
1898
+ "required": true,
1899
+ "columnType": "UUID"
1900
+ },
1901
+ {
1902
+ "name": "key",
1903
+ "type": "text",
1904
+ "required": true,
1905
+ "columnType": "TEXT"
1906
+ },
1907
+ {
1908
+ "name": "name",
1909
+ "type": "text",
1910
+ "required": true,
1911
+ "columnType": "TEXT"
1912
+ },
1913
+ {
1914
+ "name": "kind",
1915
+ "type": "text",
1916
+ "required": false,
1917
+ "columnType": "TEXT",
1918
+ "default": "adapter"
1919
+ },
1920
+ {
1921
+ "name": "adapterKey",
1922
+ "type": "text",
1923
+ "required": false,
1924
+ "columnType": "TEXT"
1925
+ },
1926
+ {
1927
+ "name": "config",
1928
+ "type": "text",
1929
+ "required": false,
1930
+ "columnType": "TEXT"
1931
+ },
1932
+ {
1933
+ "name": "sharing",
1934
+ "type": "text",
1935
+ "required": false,
1936
+ "columnType": "TEXT",
1937
+ "default": "private"
1938
+ }
1939
+ ],
1940
+ "relationships": [],
1941
+ "methods": [
1942
+ "getConfig",
1943
+ "save",
1944
+ "setConfig"
1945
+ ],
1946
+ "methodSignatures": [
1947
+ {
1948
+ "name": "getConfig",
1949
+ "returns": "CourseSourceConfig"
1950
+ },
1951
+ {
1952
+ "name": "save",
1953
+ "async": true,
1954
+ "returns": "Promise<this>"
1955
+ },
1956
+ {
1957
+ "name": "setConfig",
1958
+ "params": [
1959
+ "value: CourseSourceConfig"
1960
+ ],
1961
+ "returns": "void"
1962
+ }
1963
+ ],
1964
+ "tenant": {
1965
+ "scoped": true,
1966
+ "mode": "required",
1967
+ "field": "tenantId"
1968
+ },
1969
+ "conflictColumns": [
1970
+ "tenant_id",
1971
+ "slug",
1972
+ "context"
1973
+ ],
1974
+ "surfaces": [],
1975
+ "withheldSurfaces": [
1976
+ {
1977
+ "kind": "api",
1978
+ "operation": "getConfig",
1979
+ "code": "api-disabled",
1980
+ "reason": "api is disabled",
1981
+ "objectName": "@happyvertical/smrt-education:CourseSource"
1982
+ },
1983
+ {
1984
+ "kind": "api",
1985
+ "operation": "save",
1986
+ "code": "api-disabled",
1987
+ "reason": "api is disabled",
1988
+ "objectName": "@happyvertical/smrt-education:CourseSource"
1989
+ },
1990
+ {
1991
+ "kind": "api",
1992
+ "operation": "setConfig",
1993
+ "code": "api-disabled",
1994
+ "reason": "api is disabled",
1995
+ "objectName": "@happyvertical/smrt-education:CourseSource"
1996
+ }
1997
+ ],
1998
+ "relationshipFeatures": [
1999
+ "uuidColumns"
2000
+ ],
2001
+ "tags": [],
2002
+ "risks": []
2003
+ },
2004
+ {
2005
+ "name": "CourseSourceCollection",
2006
+ "qualifiedName": "@happyvertical/smrt-education:CourseSourceCollection",
2007
+ "collection": "coursesources",
2008
+ "tableName": "course_sources",
2009
+ "packageName": "@happyvertical/smrt-education",
2010
+ "extends": "SmrtCollection",
2011
+ "fields": [],
2012
+ "relationships": [],
2013
+ "methods": [],
2014
+ "surfaces": [],
2015
+ "relationshipFeatures": [
2016
+ "uuidColumns"
2017
+ ],
2018
+ "tags": [],
2019
+ "risks": []
2020
+ },
2021
+ {
2022
+ "name": "Learner",
2023
+ "qualifiedName": "@happyvertical/smrt-education:Learner",
2024
+ "collection": "learners",
2025
+ "description": "A learner is a real user/profile identity with active family membership.",
2026
+ "tableName": "learners",
2027
+ "packageName": "@happyvertical/smrt-education",
2028
+ "extends": "SmrtObject",
2029
+ "fields": [
2030
+ {
2031
+ "name": "tenantId",
2032
+ "type": "text",
2033
+ "required": true,
2034
+ "columnType": "UUID"
2035
+ },
2036
+ {
2037
+ "name": "userId",
2038
+ "type": "crossPackageRef",
2039
+ "required": true,
2040
+ "related": "@happyvertical/smrt-users:User",
2041
+ "columnType": "UUID"
2042
+ },
2043
+ {
2044
+ "name": "profileId",
2045
+ "type": "crossPackageRef",
2046
+ "required": true,
2047
+ "related": "@happyvertical/smrt-profiles:Profile",
2048
+ "columnType": "UUID"
2049
+ },
2050
+ {
2051
+ "name": "displayName",
2052
+ "type": "text",
2053
+ "required": false,
2054
+ "columnType": "TEXT"
2055
+ },
2056
+ {
2057
+ "name": "birthDate",
2058
+ "type": "datetime",
2059
+ "required": false,
2060
+ "columnType": "TIMESTAMP"
2061
+ },
2062
+ {
2063
+ "name": "level",
2064
+ "type": "integer",
2065
+ "required": true,
2066
+ "columnType": "INTEGER",
2067
+ "default": 0
2068
+ },
2069
+ {
2070
+ "name": "settings",
2071
+ "type": "text",
2072
+ "required": false,
2073
+ "columnType": "TEXT"
2074
+ }
2075
+ ],
2076
+ "relationships": [
2077
+ {
2078
+ "name": "userId",
2079
+ "type": "crossPackageRef",
2080
+ "required": true,
2081
+ "related": "@happyvertical/smrt-users:User",
2082
+ "columnType": "UUID"
2083
+ },
2084
+ {
2085
+ "name": "profileId",
2086
+ "type": "crossPackageRef",
2087
+ "required": true,
2088
+ "related": "@happyvertical/smrt-profiles:Profile",
2089
+ "columnType": "UUID"
2090
+ }
2091
+ ],
2092
+ "methods": [
2093
+ "getSettings",
2094
+ "save",
2095
+ "setSettings"
2096
+ ],
2097
+ "methodSignatures": [
2098
+ {
2099
+ "name": "getSettings",
2100
+ "returns": "Record<string, unknown>"
2101
+ },
2102
+ {
2103
+ "name": "save",
2104
+ "async": true,
2105
+ "returns": "Promise<this>"
2106
+ },
2107
+ {
2108
+ "name": "setSettings",
2109
+ "params": [
2110
+ "value: Record<string, unknown>"
2111
+ ],
2112
+ "returns": "void"
2113
+ }
2114
+ ],
2115
+ "tenant": {
2116
+ "scoped": true,
2117
+ "mode": "required",
2118
+ "field": "tenantId"
2119
+ },
2120
+ "conflictColumns": [
2121
+ "tenant_id",
2122
+ "slug",
2123
+ "context"
2124
+ ],
2125
+ "surfaces": [],
2126
+ "withheldSurfaces": [
2127
+ {
2128
+ "kind": "api",
2129
+ "operation": "getSettings",
2130
+ "code": "api-disabled",
2131
+ "reason": "api is disabled",
2132
+ "objectName": "@happyvertical/smrt-education:Learner"
2133
+ },
2134
+ {
2135
+ "kind": "api",
2136
+ "operation": "save",
2137
+ "code": "api-disabled",
2138
+ "reason": "api is disabled",
2139
+ "objectName": "@happyvertical/smrt-education:Learner"
2140
+ },
2141
+ {
2142
+ "kind": "api",
2143
+ "operation": "setSettings",
2144
+ "code": "api-disabled",
2145
+ "reason": "api is disabled",
2146
+ "objectName": "@happyvertical/smrt-education:Learner"
2147
+ }
2148
+ ],
2149
+ "relationshipFeatures": [
2150
+ "crossPackageRef",
2151
+ "uuidColumns"
2152
+ ],
2153
+ "tags": [],
2154
+ "risks": []
2155
+ },
2156
+ {
2157
+ "name": "Curriculum",
2158
+ "qualifiedName": "@happyvertical/smrt-education:Curriculum",
2159
+ "displayLabelField": "title",
2160
+ "collection": "curriculums",
2161
+ "description": "A distinct plan retains its own enrollment history.",
2162
+ "tableName": "curricula",
2163
+ "packageName": "@happyvertical/smrt-education",
2164
+ "extends": "SmrtObject",
2165
+ "fields": [
2166
+ {
2167
+ "name": "tenantId",
2168
+ "type": "text",
2169
+ "required": true,
2170
+ "columnType": "UUID"
2171
+ },
2172
+ {
2173
+ "name": "learnerId",
2174
+ "type": "foreignKey",
2175
+ "required": true,
2176
+ "related": "Learner",
2177
+ "columnType": "UUID"
2178
+ },
2179
+ {
2180
+ "name": "title",
2181
+ "type": "text",
2182
+ "required": true,
2183
+ "columnType": "TEXT"
2184
+ },
2185
+ {
2186
+ "name": "startsOn",
2187
+ "type": "datetime",
2188
+ "required": false,
2189
+ "columnType": "TIMESTAMP"
2190
+ },
2191
+ {
2192
+ "name": "endsOn",
2193
+ "type": "datetime",
2194
+ "required": false,
2195
+ "columnType": "TIMESTAMP"
2196
+ },
2197
+ {
2198
+ "name": "status",
2199
+ "type": "text",
2200
+ "required": false,
2201
+ "columnType": "TEXT",
2202
+ "default": "planned"
2203
+ }
2204
+ ],
2205
+ "relationships": [
2206
+ {
2207
+ "name": "learnerId",
2208
+ "type": "foreignKey",
2209
+ "required": true,
2210
+ "related": "Learner",
2211
+ "columnType": "UUID"
2212
+ }
2213
+ ],
2214
+ "methods": [
2215
+ "save"
2216
+ ],
2217
+ "methodSignatures": [
2218
+ {
2219
+ "name": "save",
2220
+ "async": true,
2221
+ "returns": "Promise<this>"
2222
+ }
2223
+ ],
2224
+ "tenant": {
2225
+ "scoped": true,
2226
+ "mode": "required",
2227
+ "field": "tenantId"
2228
+ },
2229
+ "conflictColumns": [
2230
+ "tenant_id",
2231
+ "slug",
2232
+ "context"
2233
+ ],
2234
+ "surfaces": [],
2235
+ "withheldSurfaces": [
2236
+ {
2237
+ "kind": "api",
2238
+ "operation": "save",
2239
+ "code": "api-disabled",
2240
+ "reason": "api is disabled",
2241
+ "objectName": "@happyvertical/smrt-education:Curriculum"
2242
+ }
2243
+ ],
2244
+ "relationshipFeatures": [
2245
+ "foreignKey",
2246
+ "uuidColumns"
2247
+ ],
2248
+ "tags": [],
2249
+ "risks": []
2250
+ },
2251
+ {
2252
+ "name": "Enrollment",
2253
+ "qualifiedName": "@happyvertical/smrt-education:Enrollment",
2254
+ "collection": "enrollments",
2255
+ "description": "One course in one curriculum; a new curriculum means a new enrollment.",
2256
+ "tableName": "enrollments",
2257
+ "packageName": "@happyvertical/smrt-education",
2258
+ "extends": "SmrtObject",
2259
+ "fields": [
2260
+ {
2261
+ "name": "tenantId",
2262
+ "type": "text",
2263
+ "required": true,
2264
+ "columnType": "UUID"
2265
+ },
2266
+ {
2267
+ "name": "curriculumId",
2268
+ "type": "foreignKey",
2269
+ "required": true,
2270
+ "related": "Curriculum",
2271
+ "columnType": "UUID"
2272
+ },
2273
+ {
2274
+ "name": "learnerId",
2275
+ "type": "foreignKey",
2276
+ "required": true,
2277
+ "related": "Learner",
2278
+ "columnType": "UUID"
2279
+ },
2280
+ {
2281
+ "name": "courseId",
2282
+ "type": "foreignKey",
2283
+ "required": true,
2284
+ "related": "Course",
2285
+ "columnType": "UUID"
2286
+ },
2287
+ {
2288
+ "name": "status",
2289
+ "type": "text",
2290
+ "required": false,
2291
+ "columnType": "TEXT",
2292
+ "default": "planned"
2293
+ },
2294
+ {
2295
+ "name": "startedAt",
2296
+ "type": "datetime",
2297
+ "required": false,
2298
+ "columnType": "TIMESTAMP"
2299
+ },
2300
+ {
2301
+ "name": "completedAt",
2302
+ "type": "datetime",
2303
+ "required": false,
2304
+ "columnType": "TIMESTAMP"
2305
+ },
2306
+ {
2307
+ "name": "targetMinutesPerWeek",
2308
+ "type": "integer",
2309
+ "required": true,
2310
+ "columnType": "INTEGER",
2311
+ "default": 0
2312
+ },
2313
+ {
2314
+ "name": "sortOrder",
2315
+ "type": "integer",
2316
+ "required": true,
2317
+ "columnType": "INTEGER",
2318
+ "default": 0
2319
+ }
2320
+ ],
2321
+ "relationships": [
2322
+ {
2323
+ "name": "curriculumId",
2324
+ "type": "foreignKey",
2325
+ "required": true,
2326
+ "related": "Curriculum",
2327
+ "columnType": "UUID"
2328
+ },
2329
+ {
2330
+ "name": "learnerId",
2331
+ "type": "foreignKey",
2332
+ "required": true,
2333
+ "related": "Learner",
2334
+ "columnType": "UUID"
2335
+ },
2336
+ {
2337
+ "name": "courseId",
2338
+ "type": "foreignKey",
2339
+ "required": true,
2340
+ "related": "Course",
2341
+ "columnType": "UUID"
2342
+ }
2343
+ ],
2344
+ "methods": [
2345
+ "save"
2346
+ ],
2347
+ "methodSignatures": [
2348
+ {
2349
+ "name": "save",
2350
+ "async": true,
2351
+ "returns": "Promise<this>"
2352
+ }
2353
+ ],
2354
+ "tenant": {
2355
+ "scoped": true,
2356
+ "mode": "required",
2357
+ "field": "tenantId"
2358
+ },
2359
+ "conflictColumns": [
2360
+ "tenant_id",
2361
+ "curriculum_id",
2362
+ "course_id"
2363
+ ],
2364
+ "surfaces": [],
2365
+ "withheldSurfaces": [
2366
+ {
2367
+ "kind": "api",
2368
+ "operation": "save",
2369
+ "code": "api-disabled",
2370
+ "reason": "api is disabled",
2371
+ "objectName": "@happyvertical/smrt-education:Enrollment"
2372
+ }
2373
+ ],
2374
+ "relationshipFeatures": [
2375
+ "foreignKey",
2376
+ "uuidColumns"
2377
+ ],
2378
+ "tags": [],
2379
+ "risks": []
2380
+ },
2381
+ {
2382
+ "name": "LessonProgress",
2383
+ "qualifiedName": "@happyvertical/smrt-education:LessonProgress",
2384
+ "collection": "lessonprogresses",
2385
+ "description": "Progress identity is enrollment + lesson, never learner + lesson.",
2386
+ "tableName": "lesson_progress",
2387
+ "packageName": "@happyvertical/smrt-education",
2388
+ "extends": "SmrtObject",
2389
+ "fields": [
2390
+ {
2391
+ "name": "tenantId",
2392
+ "type": "text",
2393
+ "required": true,
2394
+ "columnType": "UUID"
2395
+ },
2396
+ {
2397
+ "name": "enrollmentId",
2398
+ "type": "foreignKey",
2399
+ "required": true,
2400
+ "related": "Enrollment",
2401
+ "columnType": "UUID"
2402
+ },
2403
+ {
2404
+ "name": "learnerId",
2405
+ "type": "foreignKey",
2406
+ "required": true,
2407
+ "related": "Learner",
2408
+ "columnType": "UUID"
2409
+ },
2410
+ {
2411
+ "name": "lessonId",
2412
+ "type": "foreignKey",
2413
+ "required": true,
2414
+ "related": "Lesson",
2415
+ "columnType": "UUID"
2416
+ },
2417
+ {
2418
+ "name": "status",
2419
+ "type": "text",
2420
+ "required": false,
2421
+ "columnType": "TEXT",
2422
+ "default": "not_started"
2423
+ },
2424
+ {
2425
+ "name": "startedAt",
2426
+ "type": "datetime",
2427
+ "required": false,
2428
+ "columnType": "TIMESTAMP"
2429
+ },
2430
+ {
2431
+ "name": "completedAt",
2432
+ "type": "datetime",
2433
+ "required": false,
2434
+ "columnType": "TIMESTAMP"
2435
+ },
2436
+ {
2437
+ "name": "timeSpentSeconds",
2438
+ "type": "integer",
2439
+ "required": true,
2440
+ "columnType": "INTEGER",
2441
+ "default": 0
2442
+ },
2443
+ {
2444
+ "name": "attempts",
2445
+ "type": "integer",
2446
+ "required": true,
2447
+ "columnType": "INTEGER",
2448
+ "default": 0
2449
+ },
2450
+ {
2451
+ "name": "bestScore",
2452
+ "type": "decimal",
2453
+ "required": false,
2454
+ "columnType": "REAL"
2455
+ }
2456
+ ],
2457
+ "relationships": [
2458
+ {
2459
+ "name": "enrollmentId",
2460
+ "type": "foreignKey",
2461
+ "required": true,
2462
+ "related": "Enrollment",
2463
+ "columnType": "UUID"
2464
+ },
2465
+ {
2466
+ "name": "learnerId",
2467
+ "type": "foreignKey",
2468
+ "required": true,
2469
+ "related": "Learner",
2470
+ "columnType": "UUID"
2471
+ },
2472
+ {
2473
+ "name": "lessonId",
2474
+ "type": "foreignKey",
2475
+ "required": true,
2476
+ "related": "Lesson",
2477
+ "columnType": "UUID"
2478
+ }
2479
+ ],
2480
+ "methods": [
2481
+ "save"
2482
+ ],
2483
+ "methodSignatures": [
2484
+ {
2485
+ "name": "save",
2486
+ "async": true,
2487
+ "returns": "Promise<this>"
2488
+ }
2489
+ ],
2490
+ "tenant": {
2491
+ "scoped": true,
2492
+ "mode": "required",
2493
+ "field": "tenantId"
2494
+ },
2495
+ "conflictColumns": [
2496
+ "tenant_id",
2497
+ "enrollment_id",
2498
+ "lesson_id"
2499
+ ],
2500
+ "surfaces": [],
2501
+ "withheldSurfaces": [
2502
+ {
2503
+ "kind": "api",
2504
+ "operation": "save",
2505
+ "code": "api-disabled",
2506
+ "reason": "api is disabled",
2507
+ "objectName": "@happyvertical/smrt-education:LessonProgress"
2508
+ }
2509
+ ],
2510
+ "relationshipFeatures": [
2511
+ "foreignKey",
2512
+ "uuidColumns"
2513
+ ],
2514
+ "tags": [],
2515
+ "risks": []
2516
+ },
2517
+ {
2518
+ "name": "LessonAsset",
2519
+ "qualifiedName": "@happyvertical/smrt-education:LessonAsset",
2520
+ "collection": "lessonassets",
2521
+ "tableName": "lesson_assets",
2522
+ "packageName": "@happyvertical/smrt-education",
2523
+ "extends": "SmrtObject",
2524
+ "fields": [
2525
+ {
2526
+ "name": "tenantId",
2527
+ "type": "text",
2528
+ "required": false,
2529
+ "columnType": "UUID"
2530
+ },
2531
+ {
2532
+ "name": "lessonId",
2533
+ "type": "foreignKey",
2534
+ "required": true,
2535
+ "related": "Lesson",
2536
+ "columnType": "UUID"
2537
+ },
2538
+ {
2539
+ "name": "assetId",
2540
+ "type": "crossPackageRef",
2541
+ "required": true,
2542
+ "related": "@happyvertical/smrt-assets:Asset",
2543
+ "columnType": "UUID"
2544
+ },
2545
+ {
2546
+ "name": "relationship",
2547
+ "type": "text",
2548
+ "required": true,
2549
+ "columnType": "TEXT",
2550
+ "default": "attachment"
2551
+ },
2552
+ {
2553
+ "name": "sortOrder",
2554
+ "type": "integer",
2555
+ "required": true,
2556
+ "columnType": "INTEGER",
2557
+ "default": 0
2558
+ }
2559
+ ],
2560
+ "relationships": [
2561
+ {
2562
+ "name": "lessonId",
2563
+ "type": "foreignKey",
2564
+ "required": true,
2565
+ "related": "Lesson",
2566
+ "columnType": "UUID"
2567
+ },
2568
+ {
2569
+ "name": "assetId",
2570
+ "type": "crossPackageRef",
2571
+ "required": true,
2572
+ "related": "@happyvertical/smrt-assets:Asset",
2573
+ "columnType": "UUID"
2574
+ }
2575
+ ],
2576
+ "methods": [
2577
+ "save"
2578
+ ],
2579
+ "methodSignatures": [
2580
+ {
2581
+ "name": "save",
2582
+ "async": true,
2583
+ "returns": "Promise<this>"
2584
+ }
2585
+ ],
2586
+ "tenant": {
2587
+ "scoped": true,
2588
+ "mode": "optional",
2589
+ "field": "tenantId"
2590
+ },
2591
+ "conflictColumns": [
2592
+ "tenant_id",
2593
+ "lesson_id",
2594
+ "asset_id",
2595
+ "relationship"
2596
+ ],
2597
+ "surfaces": [],
2598
+ "withheldSurfaces": [
2599
+ {
2600
+ "kind": "api",
2601
+ "operation": "save",
2602
+ "code": "api-disabled",
2603
+ "reason": "api is disabled",
2604
+ "objectName": "@happyvertical/smrt-education:LessonAsset"
2605
+ }
2606
+ ],
2607
+ "relationshipFeatures": [
2608
+ "crossPackageRef",
2609
+ "foreignKey",
2610
+ "uuidColumns"
2611
+ ],
2612
+ "tags": [],
2613
+ "risks": []
2614
+ },
2615
+ {
2616
+ "name": "Submission",
2617
+ "qualifiedName": "@happyvertical/smrt-education:Submission",
2618
+ "collection": "submissions",
2619
+ "description": "Evidence that a learner completed one progress item.",
2620
+ "tableName": "submissions",
2621
+ "packageName": "@happyvertical/smrt-education",
2622
+ "extends": "SmrtObject",
2623
+ "fields": [
2624
+ {
2625
+ "name": "created_at",
2626
+ "type": "datetime",
2627
+ "required": false,
2628
+ "columnType": "TIMESTAMP"
2629
+ },
2630
+ {
2631
+ "name": "updated_at",
2632
+ "type": "datetime",
2633
+ "required": false,
2634
+ "columnType": "TIMESTAMP"
2635
+ },
2636
+ {
2637
+ "name": "tenantId",
2638
+ "type": "text",
2639
+ "required": true,
2640
+ "columnType": "UUID"
2641
+ },
2642
+ {
2643
+ "name": "learnerId",
2644
+ "type": "foreignKey",
2645
+ "required": true,
2646
+ "related": "Learner",
2647
+ "columnType": "UUID"
2648
+ },
2649
+ {
2650
+ "name": "lessonId",
2651
+ "type": "foreignKey",
2652
+ "required": true,
2653
+ "related": "Lesson",
2654
+ "columnType": "UUID"
2655
+ },
2656
+ {
2657
+ "name": "progressId",
2658
+ "type": "foreignKey",
2659
+ "required": true,
2660
+ "related": "LessonProgress",
2661
+ "columnType": "UUID"
2662
+ },
2663
+ {
2664
+ "name": "submittedAt",
2665
+ "type": "text",
2666
+ "required": false,
2667
+ "columnType": "TEXT"
2668
+ },
2669
+ {
2670
+ "name": "status",
2671
+ "type": "text",
2672
+ "required": false,
2673
+ "columnType": "TEXT",
2674
+ "default": "captured"
2675
+ },
2676
+ {
2677
+ "name": "notes",
2678
+ "type": "text",
2679
+ "required": false,
2680
+ "columnType": "TEXT"
2681
+ },
2682
+ {
2683
+ "name": "captureKey",
2684
+ "type": "text",
2685
+ "required": false,
2686
+ "columnType": "TEXT"
2687
+ },
2688
+ {
2689
+ "name": "captureDigest",
2690
+ "type": "text",
2691
+ "required": false,
2692
+ "columnType": "TEXT"
2693
+ }
2694
+ ],
2695
+ "relationships": [
2696
+ {
2697
+ "name": "learnerId",
2698
+ "type": "foreignKey",
2699
+ "required": true,
2700
+ "related": "Learner",
2701
+ "columnType": "UUID"
2702
+ },
2703
+ {
2704
+ "name": "lessonId",
2705
+ "type": "foreignKey",
2706
+ "required": true,
2707
+ "related": "Lesson",
2708
+ "columnType": "UUID"
2709
+ },
2710
+ {
2711
+ "name": "progressId",
2712
+ "type": "foreignKey",
2713
+ "required": true,
2714
+ "related": "LessonProgress",
2715
+ "columnType": "UUID"
2716
+ }
2717
+ ],
2718
+ "methods": [
2719
+ "save"
2720
+ ],
2721
+ "methodSignatures": [
2722
+ {
2723
+ "name": "save",
2724
+ "async": true,
2725
+ "returns": "Promise<this>"
2726
+ }
2727
+ ],
2728
+ "tenant": {
2729
+ "scoped": true,
2730
+ "mode": "required",
2731
+ "field": "tenantId"
2732
+ },
2733
+ "tableStrategy": "sti",
2734
+ "conflictColumns": [
2735
+ "tenant_id",
2736
+ "slug",
2737
+ "context",
2738
+ "_meta_type"
2739
+ ],
2740
+ "surfaces": [],
2741
+ "withheldSurfaces": [
2742
+ {
2743
+ "kind": "api",
2744
+ "operation": "save",
2745
+ "code": "api-disabled",
2746
+ "reason": "api is disabled",
2747
+ "objectName": "@happyvertical/smrt-education:Submission"
2748
+ }
2749
+ ],
2750
+ "relationshipFeatures": [
2751
+ "foreignKey",
2752
+ "uuidColumns"
2753
+ ],
2754
+ "tags": [],
2755
+ "risks": []
2756
+ },
2757
+ {
2758
+ "name": "ScannedSubmission",
2759
+ "qualifiedName": "@happyvertical/smrt-education:ScannedSubmission",
2760
+ "collection": "submissions",
2761
+ "description": "Photos or scans of work submitted for review.",
2762
+ "tableName": "submissions",
2763
+ "packageName": "@happyvertical/smrt-education",
2764
+ "extends": "Submission",
2765
+ "fields": [
2766
+ {
2767
+ "name": "created_at",
2768
+ "type": "datetime",
2769
+ "required": false,
2770
+ "columnType": "TIMESTAMP"
2771
+ },
2772
+ {
2773
+ "name": "updated_at",
2774
+ "type": "datetime",
2775
+ "required": false,
2776
+ "columnType": "TIMESTAMP"
2777
+ },
2778
+ {
2779
+ "name": "tenantId",
2780
+ "type": "text",
2781
+ "required": true,
2782
+ "columnType": "UUID"
2783
+ },
2784
+ {
2785
+ "name": "learnerId",
2786
+ "type": "foreignKey",
2787
+ "required": true,
2788
+ "related": "Learner",
2789
+ "columnType": "UUID"
2790
+ },
2791
+ {
2792
+ "name": "lessonId",
2793
+ "type": "foreignKey",
2794
+ "required": true,
2795
+ "related": "Lesson",
2796
+ "columnType": "UUID"
2797
+ },
2798
+ {
2799
+ "name": "progressId",
2800
+ "type": "foreignKey",
2801
+ "required": true,
2802
+ "related": "LessonProgress",
2803
+ "columnType": "UUID"
2804
+ },
2805
+ {
2806
+ "name": "submittedAt",
2807
+ "type": "text",
2808
+ "required": false,
2809
+ "columnType": "TEXT"
2810
+ },
2811
+ {
2812
+ "name": "status",
2813
+ "type": "text",
2814
+ "required": false,
2815
+ "columnType": "TEXT",
2816
+ "default": "captured"
2817
+ },
2818
+ {
2819
+ "name": "notes",
2820
+ "type": "text",
2821
+ "required": false,
2822
+ "columnType": "TEXT"
2823
+ },
2824
+ {
2825
+ "name": "captureKey",
2826
+ "type": "text",
2827
+ "required": false,
2828
+ "columnType": "TEXT"
2829
+ },
2830
+ {
2831
+ "name": "captureDigest",
2832
+ "type": "text",
2833
+ "required": false,
2834
+ "columnType": "TEXT"
2835
+ },
2836
+ {
2837
+ "name": "pageCount",
2838
+ "type": "integer",
2839
+ "required": true,
2840
+ "columnType": "INTEGER",
2841
+ "default": 0
2842
+ },
2843
+ {
2844
+ "name": "ocrText",
2845
+ "type": "text",
2846
+ "required": false,
2847
+ "columnType": "TEXT"
2848
+ },
2849
+ {
2850
+ "name": "extracted",
2851
+ "type": "text",
2852
+ "required": false,
2853
+ "columnType": "TEXT"
2854
+ }
2855
+ ],
2856
+ "relationships": [
2857
+ {
2858
+ "name": "learnerId",
2859
+ "type": "foreignKey",
2860
+ "required": true,
2861
+ "related": "Learner",
2862
+ "columnType": "UUID"
2863
+ },
2864
+ {
2865
+ "name": "lessonId",
2866
+ "type": "foreignKey",
2867
+ "required": true,
2868
+ "related": "Lesson",
2869
+ "columnType": "UUID"
2870
+ },
2871
+ {
2872
+ "name": "progressId",
2873
+ "type": "foreignKey",
2874
+ "required": true,
2875
+ "related": "LessonProgress",
2876
+ "columnType": "UUID"
2877
+ }
2878
+ ],
2879
+ "methods": [
2880
+ "getExtracted",
2881
+ "save",
2882
+ "setExtracted"
2883
+ ],
2884
+ "methodSignatures": [
2885
+ {
2886
+ "name": "getExtracted",
2887
+ "returns": "Record<string, unknown>"
2888
+ },
2889
+ {
2890
+ "name": "save",
2891
+ "async": true,
2892
+ "returns": "Promise<this>"
2893
+ },
2894
+ {
2895
+ "name": "setExtracted",
2896
+ "params": [
2897
+ "value: Record<string, unknown>"
2898
+ ],
2899
+ "returns": "void"
2900
+ }
2901
+ ],
2902
+ "tableStrategy": "sti",
2903
+ "surfaces": [],
2904
+ "withheldSurfaces": [
2905
+ {
2906
+ "kind": "api",
2907
+ "operation": "getExtracted",
2908
+ "code": "api-disabled",
2909
+ "reason": "api is disabled",
2910
+ "objectName": "@happyvertical/smrt-education:ScannedSubmission"
2911
+ },
2912
+ {
2913
+ "kind": "api",
2914
+ "operation": "save",
2915
+ "code": "api-disabled",
2916
+ "reason": "api is disabled",
2917
+ "objectName": "@happyvertical/smrt-education:ScannedSubmission"
2918
+ },
2919
+ {
2920
+ "kind": "api",
2921
+ "operation": "setExtracted",
2922
+ "code": "api-disabled",
2923
+ "reason": "api is disabled",
2924
+ "objectName": "@happyvertical/smrt-education:ScannedSubmission"
2925
+ }
2926
+ ],
2927
+ "relationshipFeatures": [
2928
+ "foreignKey",
2929
+ "uuidColumns"
2930
+ ],
2931
+ "tags": [],
2932
+ "risks": []
2933
+ },
2934
+ {
2935
+ "name": "ExternalCompletion",
2936
+ "qualifiedName": "@happyvertical/smrt-education:ExternalCompletion",
2937
+ "collection": "submissions",
2938
+ "description": "Completion reported by the learner, guardian, or external source.",
2939
+ "tableName": "submissions",
2940
+ "packageName": "@happyvertical/smrt-education",
2941
+ "extends": "Submission",
2942
+ "fields": [
2943
+ {
2944
+ "name": "created_at",
2945
+ "type": "datetime",
2946
+ "required": false,
2947
+ "columnType": "TIMESTAMP"
2948
+ },
2949
+ {
2950
+ "name": "updated_at",
2951
+ "type": "datetime",
2952
+ "required": false,
2953
+ "columnType": "TIMESTAMP"
2954
+ },
2955
+ {
2956
+ "name": "tenantId",
2957
+ "type": "text",
2958
+ "required": true,
2959
+ "columnType": "UUID"
2960
+ },
2961
+ {
2962
+ "name": "learnerId",
2963
+ "type": "foreignKey",
2964
+ "required": true,
2965
+ "related": "Learner",
2966
+ "columnType": "UUID"
2967
+ },
2968
+ {
2969
+ "name": "lessonId",
2970
+ "type": "foreignKey",
2971
+ "required": true,
2972
+ "related": "Lesson",
2973
+ "columnType": "UUID"
2974
+ },
2975
+ {
2976
+ "name": "progressId",
2977
+ "type": "foreignKey",
2978
+ "required": true,
2979
+ "related": "LessonProgress",
2980
+ "columnType": "UUID"
2981
+ },
2982
+ {
2983
+ "name": "submittedAt",
2984
+ "type": "text",
2985
+ "required": false,
2986
+ "columnType": "TEXT"
2987
+ },
2988
+ {
2989
+ "name": "status",
2990
+ "type": "text",
2991
+ "required": false,
2992
+ "columnType": "TEXT",
2993
+ "default": "captured"
2994
+ },
2995
+ {
2996
+ "name": "notes",
2997
+ "type": "text",
2998
+ "required": false,
2999
+ "columnType": "TEXT"
3000
+ },
3001
+ {
3002
+ "name": "captureKey",
3003
+ "type": "text",
3004
+ "required": false,
3005
+ "columnType": "TEXT"
3006
+ },
3007
+ {
3008
+ "name": "captureDigest",
3009
+ "type": "text",
3010
+ "required": false,
3011
+ "columnType": "TEXT"
3012
+ },
3013
+ {
3014
+ "name": "reportedBy",
3015
+ "type": "text",
3016
+ "required": false,
3017
+ "columnType": "TEXT",
3018
+ "default": "learner"
3019
+ },
3020
+ {
3021
+ "name": "evidenceUrl",
3022
+ "type": "text",
3023
+ "required": false,
3024
+ "columnType": "TEXT"
3025
+ },
3026
+ {
3027
+ "name": "reportedScore",
3028
+ "type": "decimal",
3029
+ "required": false,
3030
+ "columnType": "REAL"
3031
+ }
3032
+ ],
3033
+ "relationships": [
3034
+ {
3035
+ "name": "learnerId",
3036
+ "type": "foreignKey",
3037
+ "required": true,
3038
+ "related": "Learner",
3039
+ "columnType": "UUID"
3040
+ },
3041
+ {
3042
+ "name": "lessonId",
3043
+ "type": "foreignKey",
3044
+ "required": true,
3045
+ "related": "Lesson",
3046
+ "columnType": "UUID"
3047
+ },
3048
+ {
3049
+ "name": "progressId",
3050
+ "type": "foreignKey",
3051
+ "required": true,
3052
+ "related": "LessonProgress",
3053
+ "columnType": "UUID"
3054
+ }
3055
+ ],
3056
+ "methods": [
3057
+ "save"
3058
+ ],
3059
+ "methodSignatures": [
3060
+ {
3061
+ "name": "save",
3062
+ "async": true,
3063
+ "returns": "Promise<this>"
3064
+ }
3065
+ ],
3066
+ "tableStrategy": "sti",
3067
+ "surfaces": [],
3068
+ "withheldSurfaces": [
3069
+ {
3070
+ "kind": "api",
3071
+ "operation": "save",
3072
+ "code": "api-disabled",
3073
+ "reason": "api is disabled",
3074
+ "objectName": "@happyvertical/smrt-education:ExternalCompletion"
3075
+ }
3076
+ ],
3077
+ "relationshipFeatures": [
3078
+ "foreignKey",
3079
+ "uuidColumns"
3080
+ ],
3081
+ "tags": [],
3082
+ "risks": []
3083
+ },
3084
+ {
3085
+ "name": "Attestation",
3086
+ "qualifiedName": "@happyvertical/smrt-education:Attestation",
3087
+ "collection": "submissions",
3088
+ "description": "A guardian's statement that work was completed.",
3089
+ "tableName": "submissions",
3090
+ "packageName": "@happyvertical/smrt-education",
3091
+ "extends": "Submission",
3092
+ "fields": [
3093
+ {
3094
+ "name": "created_at",
3095
+ "type": "datetime",
3096
+ "required": false,
3097
+ "columnType": "TIMESTAMP"
3098
+ },
3099
+ {
3100
+ "name": "updated_at",
3101
+ "type": "datetime",
3102
+ "required": false,
3103
+ "columnType": "TIMESTAMP"
3104
+ },
3105
+ {
3106
+ "name": "tenantId",
3107
+ "type": "text",
3108
+ "required": true,
3109
+ "columnType": "UUID"
3110
+ },
3111
+ {
3112
+ "name": "learnerId",
3113
+ "type": "foreignKey",
3114
+ "required": true,
3115
+ "related": "Learner",
3116
+ "columnType": "UUID"
3117
+ },
3118
+ {
3119
+ "name": "lessonId",
3120
+ "type": "foreignKey",
3121
+ "required": true,
3122
+ "related": "Lesson",
3123
+ "columnType": "UUID"
3124
+ },
3125
+ {
3126
+ "name": "progressId",
3127
+ "type": "foreignKey",
3128
+ "required": true,
3129
+ "related": "LessonProgress",
3130
+ "columnType": "UUID"
3131
+ },
3132
+ {
3133
+ "name": "submittedAt",
3134
+ "type": "text",
3135
+ "required": false,
3136
+ "columnType": "TEXT"
3137
+ },
3138
+ {
3139
+ "name": "status",
3140
+ "type": "text",
3141
+ "required": false,
3142
+ "columnType": "TEXT",
3143
+ "default": "captured"
3144
+ },
3145
+ {
3146
+ "name": "notes",
3147
+ "type": "text",
3148
+ "required": false,
3149
+ "columnType": "TEXT"
3150
+ },
3151
+ {
3152
+ "name": "captureKey",
3153
+ "type": "text",
3154
+ "required": false,
3155
+ "columnType": "TEXT"
3156
+ },
3157
+ {
3158
+ "name": "captureDigest",
3159
+ "type": "text",
3160
+ "required": false,
3161
+ "columnType": "TEXT"
3162
+ }
3163
+ ],
3164
+ "relationships": [
3165
+ {
3166
+ "name": "learnerId",
3167
+ "type": "foreignKey",
3168
+ "required": true,
3169
+ "related": "Learner",
3170
+ "columnType": "UUID"
3171
+ },
3172
+ {
3173
+ "name": "lessonId",
3174
+ "type": "foreignKey",
3175
+ "required": true,
3176
+ "related": "Lesson",
3177
+ "columnType": "UUID"
3178
+ },
3179
+ {
3180
+ "name": "progressId",
3181
+ "type": "foreignKey",
3182
+ "required": true,
3183
+ "related": "LessonProgress",
3184
+ "columnType": "UUID"
3185
+ }
3186
+ ],
3187
+ "methods": [
3188
+ "save"
3189
+ ],
3190
+ "methodSignatures": [
3191
+ {
3192
+ "name": "save",
3193
+ "async": true,
3194
+ "returns": "Promise<this>"
3195
+ }
3196
+ ],
3197
+ "tableStrategy": "sti",
3198
+ "surfaces": [],
3199
+ "withheldSurfaces": [
3200
+ {
3201
+ "kind": "api",
3202
+ "operation": "save",
3203
+ "code": "api-disabled",
3204
+ "reason": "api is disabled",
3205
+ "objectName": "@happyvertical/smrt-education:Attestation"
3206
+ }
3207
+ ],
3208
+ "relationshipFeatures": [
3209
+ "foreignKey",
3210
+ "uuidColumns"
3211
+ ],
3212
+ "tags": [],
3213
+ "risks": []
3214
+ },
3215
+ {
3216
+ "name": "SubmissionAsset",
3217
+ "qualifiedName": "@happyvertical/smrt-education:SubmissionAsset",
3218
+ "collection": "submissionassets",
3219
+ "description": "The tenant-owned files attached to a submission.",
3220
+ "tableName": "submission_assets",
3221
+ "packageName": "@happyvertical/smrt-education",
3222
+ "extends": "SmrtObject",
3223
+ "fields": [
3224
+ {
3225
+ "name": "tenantId",
3226
+ "type": "text",
3227
+ "required": true,
3228
+ "columnType": "UUID"
3229
+ },
3230
+ {
3231
+ "name": "submissionId",
3232
+ "type": "foreignKey",
3233
+ "required": true,
3234
+ "related": "Submission",
3235
+ "columnType": "UUID"
3236
+ },
3237
+ {
3238
+ "name": "assetId",
3239
+ "type": "crossPackageRef",
3240
+ "required": true,
3241
+ "related": "@happyvertical/smrt-assets:Asset",
3242
+ "columnType": "UUID"
3243
+ },
3244
+ {
3245
+ "name": "relationship",
3246
+ "type": "text",
3247
+ "required": true,
3248
+ "columnType": "TEXT",
3249
+ "default": "page"
3250
+ },
3251
+ {
3252
+ "name": "sortOrder",
3253
+ "type": "integer",
3254
+ "required": true,
3255
+ "columnType": "INTEGER",
3256
+ "default": 0
3257
+ }
3258
+ ],
3259
+ "relationships": [
3260
+ {
3261
+ "name": "submissionId",
3262
+ "type": "foreignKey",
3263
+ "required": true,
3264
+ "related": "Submission",
3265
+ "columnType": "UUID"
3266
+ },
3267
+ {
3268
+ "name": "assetId",
3269
+ "type": "crossPackageRef",
3270
+ "required": true,
3271
+ "related": "@happyvertical/smrt-assets:Asset",
3272
+ "columnType": "UUID"
3273
+ }
3274
+ ],
3275
+ "methods": [
3276
+ "save"
3277
+ ],
3278
+ "methodSignatures": [
3279
+ {
3280
+ "name": "save",
3281
+ "async": true,
3282
+ "returns": "Promise<this>"
3283
+ }
3284
+ ],
3285
+ "tenant": {
3286
+ "scoped": true,
3287
+ "mode": "required",
3288
+ "field": "tenantId"
3289
+ },
3290
+ "conflictColumns": [
3291
+ "tenant_id",
3292
+ "submission_id",
3293
+ "asset_id",
3294
+ "relationship"
3295
+ ],
3296
+ "surfaces": [],
3297
+ "withheldSurfaces": [
3298
+ {
3299
+ "kind": "api",
3300
+ "operation": "save",
3301
+ "code": "api-disabled",
3302
+ "reason": "api is disabled",
3303
+ "objectName": "@happyvertical/smrt-education:SubmissionAsset"
3304
+ }
3305
+ ],
3306
+ "relationshipFeatures": [
3307
+ "crossPackageRef",
3308
+ "foreignKey",
3309
+ "uuidColumns"
3310
+ ],
3311
+ "tags": [],
3312
+ "risks": []
3313
+ },
3314
+ {
3315
+ "name": "Grade",
3316
+ "qualifiedName": "@happyvertical/smrt-education:Grade",
3317
+ "collection": "grades",
3318
+ "description": "An assessment of a submission; the service selects the authoritative grade.",
3319
+ "tableName": "grades",
3320
+ "packageName": "@happyvertical/smrt-education",
3321
+ "extends": "SmrtObject",
3322
+ "fields": [
3323
+ {
3324
+ "name": "tenantId",
3325
+ "type": "text",
3326
+ "required": true,
3327
+ "columnType": "UUID"
3328
+ },
3329
+ {
3330
+ "name": "submissionId",
3331
+ "type": "foreignKey",
3332
+ "required": true,
3333
+ "related": "Submission",
3334
+ "columnType": "UUID"
3335
+ },
3336
+ {
3337
+ "name": "graderKind",
3338
+ "type": "text",
3339
+ "required": true,
3340
+ "columnType": "TEXT",
3341
+ "default": "auto"
3342
+ },
3343
+ {
3344
+ "name": "graderProfileId",
3345
+ "type": "crossPackageRef",
3346
+ "required": false,
3347
+ "related": "@happyvertical/smrt-profiles:Profile",
3348
+ "columnType": "UUID"
3349
+ },
3350
+ {
3351
+ "name": "score",
3352
+ "type": "decimal",
3353
+ "required": false,
3354
+ "columnType": "REAL"
3355
+ },
3356
+ {
3357
+ "name": "pointsEarned",
3358
+ "type": "integer",
3359
+ "required": false,
3360
+ "columnType": "INTEGER"
3361
+ },
3362
+ {
3363
+ "name": "pointsPossible",
3364
+ "type": "integer",
3365
+ "required": false,
3366
+ "columnType": "INTEGER"
3367
+ },
3368
+ {
3369
+ "name": "rubricResults",
3370
+ "type": "text",
3371
+ "required": false,
3372
+ "columnType": "TEXT"
3373
+ },
3374
+ {
3375
+ "name": "feedback",
3376
+ "type": "text",
3377
+ "required": false,
3378
+ "columnType": "TEXT"
3379
+ },
3380
+ {
3381
+ "name": "gradedAt",
3382
+ "type": "text",
3383
+ "required": false,
3384
+ "columnType": "TEXT"
3385
+ }
3386
+ ],
3387
+ "relationships": [
3388
+ {
3389
+ "name": "submissionId",
3390
+ "type": "foreignKey",
3391
+ "required": true,
3392
+ "related": "Submission",
3393
+ "columnType": "UUID"
3394
+ },
3395
+ {
3396
+ "name": "graderProfileId",
3397
+ "type": "crossPackageRef",
3398
+ "required": false,
3399
+ "related": "@happyvertical/smrt-profiles:Profile",
3400
+ "columnType": "UUID"
3401
+ }
3402
+ ],
3403
+ "methods": [
3404
+ "getRubricResults",
3405
+ "save",
3406
+ "setRubricResults"
3407
+ ],
3408
+ "methodSignatures": [
3409
+ {
3410
+ "name": "getRubricResults",
3411
+ "returns": "Record<string, unknown>"
3412
+ },
3413
+ {
3414
+ "name": "save",
3415
+ "async": true,
3416
+ "returns": "Promise<this>"
3417
+ },
3418
+ {
3419
+ "name": "setRubricResults",
3420
+ "params": [
3421
+ "value: Record<string, unknown>"
3422
+ ],
3423
+ "returns": "void"
3424
+ }
3425
+ ],
3426
+ "tenant": {
3427
+ "scoped": true,
3428
+ "mode": "required",
3429
+ "field": "tenantId"
3430
+ },
3431
+ "conflictColumns": [
3432
+ "tenant_id",
3433
+ "slug",
3434
+ "context"
3435
+ ],
3436
+ "surfaces": [],
3437
+ "withheldSurfaces": [
3438
+ {
3439
+ "kind": "api",
3440
+ "operation": "getRubricResults",
3441
+ "code": "api-disabled",
3442
+ "reason": "api is disabled",
3443
+ "objectName": "@happyvertical/smrt-education:Grade"
3444
+ },
3445
+ {
3446
+ "kind": "api",
3447
+ "operation": "save",
3448
+ "code": "api-disabled",
3449
+ "reason": "api is disabled",
3450
+ "objectName": "@happyvertical/smrt-education:Grade"
3451
+ },
3452
+ {
3453
+ "kind": "api",
3454
+ "operation": "setRubricResults",
3455
+ "code": "api-disabled",
3456
+ "reason": "api is disabled",
3457
+ "objectName": "@happyvertical/smrt-education:Grade"
3458
+ }
3459
+ ],
3460
+ "relationshipFeatures": [
3461
+ "crossPackageRef",
3462
+ "foreignKey",
3463
+ "uuidColumns"
3464
+ ],
3465
+ "tags": [],
3466
+ "risks": []
3467
+ }
3468
+ ],
3469
+ "surfaces": [],
3470
+ "prompts": [],
3471
+ "relationshipsV2": {
3472
+ "foreignKeyFields": 28,
3473
+ "crossPackageRefFields": 5,
3474
+ "junctionCollections": 2,
3475
+ "hierarchicalObjects": 0,
3476
+ "polymorphicAssociations": 0,
3477
+ "uuidColumns": 150
3478
+ },
3479
+ "agentDoc": "# smrt-education\n\nCatalog objects are optional-tenant scoped and have no generated REST, CLI, MCP,\nor AI surface. Applications use `CatalogService`, supplying their actor-bound\nauthorization decision and the trusted `TenantCollection.withTenantCatalogReadScope`\nadapter from smrt-users. That read scope may widen only an allowlisted catalog\ncollection's `list` to self, ancestors, and globals; it never authorizes a get,\nwrite, raw query, sibling, or descendant read.\n\n`personal-use-copy` courses and all `answer-key` lesson assets are private\nfamily material. Do not publish them, promote them, or expose them through a\nlearner-facing result. `LessonAsset` is the sole owner join for lesson files;\ndo not add asset-id fields to lesson subclasses.\n\nLearner objects require exact tenant ownership and close generated surfaces.\n`LearnerService` owns actor/resource authorization and transactional planning and\nstart-progress; see [learner contract](agents/learner-service.md). Guardian\nrelationships require a host decision about their type and effective terms.\nNever expose direct collection writes as learner handlers, reset progress on a\nretry, or treat an unscored nullable decimal as zero.\n\nSubmission capture, guardian grading and private PDF packet splitting use\n`SubmissionService`; see [submission contract](agents/submission-service.md).\nKeep asset metadata on the same transaction and compensate only newly planned\nfiles after rollback; never expose source URIs or accept learner grade patches.\n\nValidation: `pnpm --filter @happyvertical/smrt-education test`,\n`test:postgres`, `typecheck`, and `build`.\n\nSource import: [host boundary and transaction contract](agents/source-import.md).\n`SourceImportService` requires host opt-in, matching active actor/tenant and\nexplicit licensing authorization; never add a system/global write bypass.\n",
3480
+ "moduleDocs": [
3481
+ {
3482
+ "path": "agents/learner-service.md",
3483
+ "module": "learner-service",
3484
+ "content": "# Learner service boundary\n\n`Learner`, `Curriculum`, `Enrollment`, and `LessonProgress` require an exact\nowning tenant. Their generated API, CLI, MCP, and AI operations are closed.\nThe corresponding collections and model saves are trusted server persistence\nAPIs, not request handlers. Hosts must not wrap them in generic learner CRUD.\n\n## Identity and authority\n\nProvision a Person Profile, a linked User, and an active Membership before\ncreating a Learner with explicit `tenantId`, `userId`, and `profileId`. User\nemail is optional. Learner identity cannot be reassigned after creation.\nGuardianship is a directed `ProfileRelationship`, not a Learner `parentId`.\nThe shared Tenant has no family discriminator: “family” means a tenant that\nthe host has provisioned and authorized for this purpose.\n\nConstruct `LearnerService(db, authorizeLearner, authorizeCatalog)` with one\nroot database executor and trusted server-side authorizers. Enable tenancy\nand establish `withTenant({ tenantId, userId }, callback)` from authenticated\ncredentials. Every request must match that active actor and tenant exactly;\nmissing context, spoofed identifiers, and super-admin bypass contexts fail.\nBoth the actor and learner must hold active memberships in that exact tenant.\nA learner's User must still link to the learner's Person Profile.\n\nFor a different actor, supply `guardianRelationshipId`. The service checks\nthat this relationship is owned by the active tenant and directed from the\nactor's User Profile to the learner's Profile. `authorizeLearner` receives\n`own`, `action`, and the verified `relationshipTypeId`; the host must check\nthat the type represents guardianship, its terms are effective, and that\nits permission policy allows the action. An arbitrary relationship is not a\nguardian grant. Missing relationships and host denials/exceptions fail closed.\nSame-tenant membership alone never grants sibling access.\n\n## Supported workflow\n\n- `getLearner` returns authorized learner metadata and parsed settings.\n- `createCurriculum` creates a **planned** curriculum for an authorized guardian.\n A learner cannot plan their own curriculum. Title is required.\n- `enroll` requires that guardian authority and a matching tenant curriculum.\n It returns the existing enrollment for the same curriculum/course unchanged.\n- `startLesson` allows the learner or authorized guardian to create their own\n enrollment's lesson progress in `in_progress`. It derives learner identity\n from the validated enrollment. Repeated starts return existing state without\n resetting counters, scores, completion, or timestamps.\n- `listProgress` reads only the validated learner/enrollment and lessons still\n visible in that enrollment's course. Inconsistent persisted links fail\n closed or are omitted from the list.\n\nService results are data-only views, with no persistence or database methods.\nExtra request fields are never model patches: a learner cannot set score,\nattempt count, or completion status through this API. Curriculum activation,\ncompletion transitions and dashboard aggregation are not supported by this\nbase service. SubmissionService extends its owning transaction/auth boundary for\ncapture and guardian grading; see [submission contract](submission-service.md).\nDirect model writes require trusted authority.\n\nCatalog visibility and child ownership reuse `CatalogService` on the same\nexecutor. Learner operations request private-inclusive catalog access, so the\nhost catalog authorizer must permit both `education.catalog.read` and\n`education.catalog.read-private` for the supported enrollment workflow.\nPrivate copies remain restricted to the exact owner. The service constructs a\nwhitelisted catalog request; caller options and answer-key flags are discarded.\nAnswer keys are never requested or returned. A catalog grant does not grant learner authorization.\n\n## Transactions and history\n\n`withTransaction(async transaction => ...)` composes service operations on the\ncallback's transaction-bound service. Use that service for every operation in\nthe unit. Throwing rolls back its curriculum, enrollment, and progress writes.\nThe callback's service closes when the transaction ends and rejects later calls.\nA failed mutation poisons the unit even if the callback catches its rejection;\nqueued mutations are cancelled. Callback failure stops pending work and waits for\nalready-running mutations to settle before rollback releases the executor. On\nsuccess, already-enqueued mutations finish before commit. Do not retain the\ncallback's service beyond the callback. The constructor\naccepts a root database; callers composing work use this method instead of\npassing an unmanaged transaction handle.\n\nPostgreSQL locks the parent curriculum/enrollment row before find-or-create.\nEmbedded writes use the core transaction queue. Concurrent mutations inside\none callback transaction are ordered on that connection, so duplicate starts\nwithin a `Promise.all` also return the same row. Database natural-key constraints\narbitrate enrollment `(tenant_id, curriculum_id, course_id)` and progress\n`(tenant_id, enrollment_id, lesson_id)` identities. Explicit storage conflict\nkeys include the tenant prefix because core does not add it to explicit keys.\nThe logical keys required by #3968 remain curriculum/course and enrollment/lesson:\nimmutable parent ownership and parent-tenant checks reject any second tenant\nfor the same parent. The storage prefix cannot create a second valid logical row. Ordinary repeated and concurrent calls\nreturn one row; an aborted workflow can be retried. No in-memory process lock\nis used for PostgreSQL concurrency.\n\nA new year is a new curriculum and enrollment, producing independent progress.\nUnscored `bestScore` persists as null; score zero is a real score. Scores are\nnullable decimals in [0,1]; counters are nonnegative safe integers. Settings\nare stored as JSON text and accessed through guarded `getSettings`/`setSettings`.\n\nThe shared executable workflows in `src/__tests__/learner-workflows.ts` run\nagainst SQLite and PostgreSQL, including actor/resource/context denial,\nconcurrent creation, rollback, history preservation, and zero/null scores.\nFull DuckDB schemas remain blocked by existing upstream DDL restrictions;\nno full learner workflow support on DuckDB is claimed.\n"
3485
+ },
3486
+ {
3487
+ "path": "agents/submission-service.md",
3488
+ "module": "submission-service",
3489
+ "content": "# Submission and paper workflow\n\nConstruct `SubmissionService(db, authorizeLearner, authorizeCatalog, {storage,\nstoreOptions?, signAsset?})` from trusted server configuration. It extends\n`LearnerService`: the same active actor/tenant, memberships, linked person,\ndirected guardian relationship and host permission checks apply. The host must\nallow `education.learner.submit`, `education.learner.grade` or\n`education.learner.packet` as appropriate. Grade and packet actions always reject\nthe learner's own identity, even if the host authorizer returns true.\n\nThe protected transaction/auth hooks are internal owning-service extension\npoints. They are not handler APIs. All public service results remain data-only.\nUse the callback's service in `withTransaction`; escaped services are closed.\nSubmission operations share its serial queue, poison-on-failure behavior and\nPostgreSQL enrollment lock. No runtime schema creation occurs.\n\n## Capture and grading\n\n`captureSubmission` takes the learner request, `enrollmentId`, `lessonId`, a\nnonempty stable `requestKey`, and `pages` containing `name`, `mimeType` and Buffer\n`data`. Each upload becomes a fresh Asset owned by the learner's profile and\nexact tenant, joined solely through `SubmissionAsset`. Up to 100 uploads of\n25 MiB each are accepted (JPEG, PNG, WebP, or PDF). It atomically creates the\nsubmission/asset metadata and moves only that enrollment's progress to\n`submitted`, incrementing attempts once. Caller status, score, grader, asset ID,\nand answer-key flags are never patches. Equal request-key/content retries return\nthe existing submission; altered retries fail. Submitted/review/complete/skipped\nprogress rejects new captures. Content includes ordered filenames, MIME types,\nbytes and notes. Hosts should impose tighter request/body limits as needed.\n\n`reviewSubmission` accepts an existing authorized submission and optional score\nand feedback. `attestLesson` creates/reuses an Attestation for a matching progress\nitem without requiring uploads. Both require a verified guardian and create a\nnew guardian Grade, set submission to `graded` and progress to `complete`.\nOmitted/null score is unscored; zero is a real score. The latest guardian grade\nwins over auto/AI/instructor proposals, and its score replaces progress's score.\nReview history is retained. ExternalCompletion is available for trusted server\npersistence; phase-zero learner handlers cannot forge reported scores. Online\nsubmissions and automatic grading are not implemented.\n\n`getSubmission` returns only submission metadata, page asset IDs and the selected\ngrade. It does not include catalog answers, extracted answers, storage paths,\nmodel objects, or database handles.\n\n## Storage and signed routes\n\nThe service creates an AssetRuntime bound to the operation's transaction. It\nrequires a resolved URI whose filename retains the fresh asset UUID and MIME\nextension; custom resolvers may choose its provider/directory but must route\nthat URI to the corresponding unique file. Shared existing locators are\nrejected before preservation or cleanup registration. It plans and persists\nthat unique asset URI before writing, registering that new\nasset for cleanup before the provider can partially write. After rollback,\nfiles are removed through the runtime store. Before deletion, the root database\nmust confirm that no asset owner committed: uncertain commit/read failures never\ncause committed bytes to be removed. Only this operation's newly created assets\nare candidates; pre-existing files are never adopted into its cleanup ledger.\n\nDatabase metadata is atomic; external storage uses compensation, not a database\ntransaction. If cleanup cannot complete, `SubmissionStorageCleanupError` contains\nfresh asset IDs and the original cause. Operators must reconcile those IDs in\nthe configured `education-page/<id>.<extension>` storage namespace (custom\nresolvers must retain their routing audit). A successful retry uses fresh IDs\nand cannot delete or reuse orphaned bytes. Process termination may also leave\nunreferenced files; hosts need storage orphan reconciliation. Do not suppress\ncleanup failures or claim crash-safe distributed atomicity.\n\n`signSubmissionAsset` verifies the submission/enrollment/learner ownership and\nits noun join, then verifies the Asset's tenant and learner profile before\ncalling the configured trusted signer. Claims contain actor, tenant, learner,\nenrollment, submission, asset and a five-minute expiry; no static source URI is\nreturned. The host signs every claim and its serving endpoint must verify the\nsignature, expiry, current actor/tenant and object authorization before serving.\nA bearer URL must never become a cross-family or sibling access bypass.\n\n## Personal worksheet packets\n\n`splitWorksheetPacket` takes the guardian learner request, exact-owned\n`personal-use-copy` `courseId`, PDF bytes and ranges `{lessonId, firstPage,\nlastPage}`. Page bounds are inclusive and one-based. Ranges must be in bounds,\ndisjoint and target distinct Worksheet lessons in that private course. The\n50 MiB / 100-range limits are enforced before PDF parsing. `pdf-lib` extracts\nactual pages into independent PDFs and records private printable LessonAssets.\nAll resulting metadata commits together; storage failure compensates new PDFs.\nThe source packet is not retained. No shared/global course is accepted and no\nanswer-key join is created. A successful repeated packet upload intentionally\nadds another printable version; a failed upload can be retried safely.\n\nShared integration workflows run on SQLite and real PostgreSQL. Full DuckDB\nworkflow support remains outside the existing supported schema boundary.\n\n## Private asset boundary\n\nCaptured pages and split packet pages use `Asset.accessMode: 'private'` from their\ninitial insert. Ordinary generated Asset REST/MCP operations cannot list, hydrate,\nupdate or delete them, including through the parent STI model. The service opens\nan exact tenant/actor/learner-owner/asset-ID capability only after its normal learner\nrelationship checks. A host's signed serving route must repeat authorization and\nopen that exact capability before loading the asset. See the owning\n[private asset contract](../../assets/agents/private-assets.md).\n\nStorage must be private (including provider ACLs). Public HTTP(S) source locators\nare rejected; a hidden URL alone cannot protect publicly accessible bytes. Apply\nthe manifest migration adding `assets.access_mode` before deploying this runtime;\nlegacy ordinary assets retain the public default. Runtime creates no schema.\n\nPrivate lesson files must already be private Assets in the same tenant. Saving\nan answer-key join, or any join under a personal-use-copy course, requires the\nhost's exact actor/owner/asset capability; a public asset is never automatically\npromoted. Packet splitting retains this capability while creating each join.\nPersisted course tenant/license and section/lesson parent identity cannot change;\ncreate new structure and explicitly re-author its joins instead. Mandatory\nwrite predicates enforce this even for natural-key adoption and concurrent writes.\nOrdinary content and metadata refresh remains supported. This\nprevents existing public material being reclassified by moving its parent.\n\nFor pre-existing invalid joins, an authorized host must inventory private-material\njoins and remove invalid associations, recreate the bytes in private storage as a\nnew privately classified Asset, then re-author the join under its exact capability.\nDo not merely hide the URI, mutate access_mode with raw SQL, or delete shared\npublic bytes. This repair requires explicit storage/ownership authorization;\nnormal runtime does not migrate existing records.\n"
3490
+ },
3491
+ {
3492
+ "path": "agents/source-import.md",
3493
+ "module": "source-import",
3494
+ "content": "# Source import integration\n\n`SourceImportService` is a server-side host boundary, with no generated REST,\nCLI, MCP or AI operation. Recipes remain metadata only. Provision a closed\n`CourseSource` in the owning tenant, then construct the service with the root\nSQL database and an actor-bound `SourceImportAuthorizer`. Enable tenancy and\ncall within `withTenant({ tenantId, userId })`; the request must match both.\nMissing context, a different actor/tenant and super-admin bypass fail closed.\n\nRegister Easy Peasy only after the host operator accepts its terms, using\n`registerEasyPeasy({ termsAccepted: true, fetchPage })`. Do not derive that\nboolean from an HTTP request. `fetchPage(url, { redirect: 'error' })` must reject\nredirects, bound time/size and return `{ html, url }`. It fetches the page only,\nnever a PDF. The service checks the approved HTTPS URL before fetching, checks\nthe response URL, limits HTML to 2 million characters, then parses the complete\npage before any write. There is no automatic registration or network access. Structural h1–h6 headings delimit day content, including trailing Resources\nsections; ordinary non-lesson bold instructions do not. Supported\nheading blocks are h1–h6 or a lesson marker as the first bold run in a\nparagraph/list item.\nRecognizable markers outside that layout, including multiple markers in one\nparagraph, fail closed rather than silently assigning a PDF to the wrong day.\n\nThe authorizer must verify the authenticated actor's management grant and the\nprovisioned tenant's family/group classification. Tenant has no built-in kind;\nneither hierarchy position nor client input establishes a family. Authorization\nis required before fetch and again inside the transaction; callbacks must be\nread-only decisions, since a rolled-back contention retry can call them again.\n\n`sharing` is a ceiling: private permits private-family import only; group and\nglobal permit private-family or group import. Group imports additionally require\nnonblank host-recorded `config.licenseEvidence` and an explicit host grant that\nvalidates that permission. Global/null destinations are unsupported and rejected;\nthere is no global/system bypass. Widening a source never promotes existing\nimports. Private imports are draft `personal-use-copy` courses and retain the\ncatalog's immutable owner/license and no-publication protection. A host may\ncreate a new licensed group source and import there after receiving permission.\n\nUse a stable `courseRef` independent of source URLs. Identities derive from the\nowning tenant, source row ID, course reference and lesson day; retain the source\nrow when updating policy. Changes to titles/page/PDF URLs update in place.\nMissing days are retained, not deleted, so enrollment, progress, submission and\nasset foreign keys remain valid. A changed lesson kind fails the whole import\nrather than replacing a row. Only title and pointer fields are refreshed; body,\nanswer keys, learner state and manually authored settings are never replaced.\nThe packet URL is returned as a source link and is not downloaded or mirrored.\n\nAll source policy rechecks and catalog mutations use collections bound to the\nsame SQL transaction. Embedded writes use the public write queue; PostgreSQL\nserializes an import identity with a transaction-scoped advisory lock. Only\nrolled-back busy/serialization/deadlock errors retry (at most three retries),\nwithout repeating fetch. Connection-invalidated and other failures propagate.\nDo not wrap the service in a caller transaction if the caller expects a durable\ncommit before returning; any outer transaction remains the caller's boundary.\n\nSQLite and PostgreSQL integration tests exercise real transactions and rollback.\nFull catalog children remain unavailable on DuckDB due to core's existing\nON UPDATE CASCADE/self-referential schema limitations documented in\n[catalog-service.md](catalog-service.md); no alternate schema is substituted.\nTests use synthetic local HTML and an injected fetch callback, never the network.\n\nGeneric “Printable” and “Printables” labels identify lesson PDFs; course packets\nrequire explicit packet/whole-course/complete-course wording. Only actual href\nattributes supply pointers; data-href metadata does not.\n"
3495
+ }
3496
+ ],
3497
+ "recipes": [
3498
+ {
3499
+ "id": "education.catalog",
3500
+ "className": "EducationCatalogRecipe",
3501
+ "label": "Course catalog",
3502
+ "summary": "Organize courses, sections, lessons, and their authorized learning materials.",
3503
+ "synonyms": [
3504
+ "courses",
3505
+ "lessons",
3506
+ "curriculum catalog"
3507
+ ],
3508
+ "models": [
3509
+ "@happyvertical/smrt-education:Course",
3510
+ "@happyvertical/smrt-education:CourseSection",
3511
+ "@happyvertical/smrt-education:Lesson",
3512
+ "@happyvertical/smrt-education:LessonAsset"
3513
+ ],
3514
+ "nav": [
3515
+ {
3516
+ "label": "Courses",
3517
+ "model": "@happyvertical/smrt-education:Course",
3518
+ "icon": "folder",
3519
+ "description": "Courses available to this family.",
3520
+ "noun": "course"
3521
+ }
3522
+ ],
3523
+ "requires": [],
3524
+ "section": {
3525
+ "id": "education",
3526
+ "label": "Education",
3527
+ "icon": "folder",
3528
+ "description": "Courses and lessons for learning."
3529
+ },
3530
+ "help": {
3531
+ "markdown": "# Course catalog\n\nManage courses, their sections, and lessons. Private family copies and answer\nkeys remain visible only through the application’s authorized catalog service.\n",
3532
+ "fieldRefs": []
3533
+ },
3534
+ "runtime": "both",
3535
+ "demo": {
3536
+ "mode": "server",
3537
+ "reasons": [
3538
+ "Needs a server because its package depends on @happyvertical/smrt-profiles, @happyvertical/smrt-tenancy, @happyvertical/smrt-users, which do not build for a browser (#3617, #3624, #3626)."
3539
+ ]
3540
+ }
3541
+ },
3542
+ {
3543
+ "id": "education.learner",
3544
+ "className": "EducationLearnerRecipe",
3545
+ "label": "Learners",
3546
+ "summary": "Plan learner curricula and retain enrollment-scoped progress.",
3547
+ "synonyms": [
3548
+ "learners",
3549
+ "enrollments",
3550
+ "learning progress"
3551
+ ],
3552
+ "models": [
3553
+ "@happyvertical/smrt-education:Learner",
3554
+ "@happyvertical/smrt-education:Curriculum",
3555
+ "@happyvertical/smrt-education:Enrollment",
3556
+ "@happyvertical/smrt-education:LessonProgress"
3557
+ ],
3558
+ "nav": [
3559
+ {
3560
+ "label": "Learners",
3561
+ "model": "@happyvertical/smrt-education:Learner",
3562
+ "icon": "users",
3563
+ "description": "Authorized learners in this family.",
3564
+ "noun": "learner"
3565
+ }
3566
+ ],
3567
+ "requires": [],
3568
+ "section": {
3569
+ "id": "education",
3570
+ "label": "Education",
3571
+ "icon": "folder",
3572
+ "description": "Courses and lessons for learning."
3573
+ },
3574
+ "help": {
3575
+ "markdown": "# Learners\n\nUse `LearnerService` in an authenticated exact-tenant context to read a learner,\nplan a curriculum as an authorized guardian, enroll in a visible course, and\nstart a lesson. Recipe metadata does not expose model CRUD. Hosts authenticate\nusers, provision family membership, and validate guardian ProfileRelationship\ntypes and effective terms. See `agents/learner-service.md` for the boundary.\n",
3576
+ "fieldRefs": []
3577
+ },
3578
+ "runtime": "both",
3579
+ "demo": {
3580
+ "mode": "server",
3581
+ "reasons": [
3582
+ "Needs a server because its package depends on @happyvertical/smrt-profiles, @happyvertical/smrt-tenancy, @happyvertical/smrt-users, which do not build for a browser (#3617, #3624, #3626)."
3583
+ ]
3584
+ }
3585
+ }
3586
+ ],
3587
+ "browser": {
3588
+ "status": "server-only",
3589
+ "issues": [
3590
+ "#3617",
3591
+ "#3624",
3592
+ "#3626"
3593
+ ],
3594
+ "via": [
3595
+ "@happyvertical/smrt-profiles",
3596
+ "@happyvertical/smrt-tenancy",
3597
+ "@happyvertical/smrt-users"
3598
+ ]
3599
+ }
3600
+ }