@happyvertical/smrt-education 0.57.8 → 0.57.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/catalog-sync.md +35 -0
- package/agents/ingestion.md +93 -0
- package/agents/learner-service.md +9 -0
- package/agents/source-import.md +9 -0
- package/agents/submission-service.md +18 -0
- package/dist/catalog-acquisition-service.d.ts +58 -0
- package/dist/catalog-acquisition-service.d.ts.map +1 -0
- package/dist/catalog-collections.d.ts +18 -0
- package/dist/catalog-collections.d.ts.map +1 -0
- package/dist/catalog-contracts.d.ts +88 -0
- package/dist/catalog-contracts.d.ts.map +1 -0
- package/dist/catalog-database-sync-store.d.ts +13 -0
- package/dist/catalog-database-sync-store.d.ts.map +1 -0
- package/dist/catalog-materialization-service.d.ts +78 -0
- package/dist/catalog-materialization-service.d.ts.map +1 -0
- package/dist/catalog-models.d.ts +63 -0
- package/dist/catalog-models.d.ts.map +1 -0
- package/dist/catalog-policy.d.ts +12 -0
- package/dist/catalog-policy.d.ts.map +1 -0
- package/dist/catalog-read-service.d.ts +44 -0
- package/dist/catalog-read-service.d.ts.map +1 -0
- package/dist/catalog-sync-service.d.ts +19 -0
- package/dist/catalog-sync-service.d.ts.map +1 -0
- package/dist/collections.d.ts +4 -1
- package/dist/collections.d.ts.map +1 -1
- package/dist/hearth-catalog-providers.d.ts +74 -0
- package/dist/hearth-catalog-providers.d.ts.map +1 -0
- package/dist/hearth-easy-peasy-grade4.d.ts +40 -0
- package/dist/hearth-easy-peasy-grade4.d.ts.map +1 -0
- package/dist/index.d.ts +28 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2279 -175
- package/dist/index.js.map +1 -1
- package/dist/ingestion-isolation.d.ts +8 -0
- package/dist/ingestion-isolation.d.ts.map +1 -0
- package/dist/ingestion.d.ts +103 -0
- package/dist/ingestion.d.ts.map +1 -0
- package/dist/ingestion.js +364 -0
- package/dist/ingestion.js.map +1 -0
- package/dist/learner-service.d.ts +74 -1
- package/dist/learner-service.d.ts.map +1 -1
- package/dist/manifest.json +2686 -481
- package/dist/smrt-knowledge.json +713 -14
- package/dist/source-import-service.d.ts +11 -1
- package/dist/source-import-service.d.ts.map +1 -1
- package/dist/sources/easy-peasy-parser.d.ts.map +1 -1
- package/dist/sources/google-drive-adapter.d.ts +125 -0
- package/dist/sources/google-drive-adapter.d.ts.map +1 -0
- package/dist/sources/illustrative-mathematics-parser.d.ts +8 -0
- package/dist/sources/illustrative-mathematics-parser.d.ts.map +1 -0
- package/dist/sources/oer-commons-rss-parser.d.ts +8 -0
- package/dist/sources/oer-commons-rss-parser.d.ts.map +1 -0
- package/dist/submission-service.d.ts +48 -1
- package/dist/submission-service.d.ts.map +1 -1
- package/dist/submissions.d.ts +18 -0
- package/dist/submissions.d.ts.map +1 -1
- package/package.json +16 -10
package/dist/index.js
CHANGED
|
@@ -4,8 +4,8 @@ import { AssetCollection, createAssetRuntime, withPrivateAssetAccess } from "@ha
|
|
|
4
4
|
import { TenantCollection } from "@happyvertical/smrt-users";
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
6
6
|
var __create = Object.create;
|
|
7
|
-
var __defProp$
|
|
8
|
-
var __getOwnPropDesc$
|
|
7
|
+
var __defProp$6 = Object.defineProperty;
|
|
8
|
+
var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
|
|
9
9
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
10
|
var __getProtoOf = Object.getPrototypeOf;
|
|
11
11
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
@@ -13,38 +13,38 @@ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).expor
|
|
|
13
13
|
var __copyProps = (to, from, except, desc) => {
|
|
14
14
|
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
15
15
|
key = keys[i];
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp$
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp$6(to, key, {
|
|
17
17
|
get: ((k) => from[k]).bind(null, key),
|
|
18
|
-
enumerable: !(desc = __getOwnPropDesc$
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc$6(from, key)) || desc.enumerable
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
return to;
|
|
22
22
|
};
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp$
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp$6(target, "default", {
|
|
24
24
|
value: mod,
|
|
25
25
|
enumerable: true
|
|
26
26
|
}) : target, mod));
|
|
27
27
|
//#endregion
|
|
28
28
|
//#region src/__smrt-register__.ts
|
|
29
|
-
ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-education","packageVersion":"0.57.8","objects":{"@happyvertical/smrt-education:Course":{"name":"course","className":"Course","qualifiedName":"@happyvertical/smrt-education:Course","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"title":{"type":"text","required":true,"_meta":{"required":true}},"summary":{"type":"text","required":false},"description":{"type":"text","required":false},"sourceKey":{"type":"text","required":false},"sourceUrl":{"type":"text","required":false},"sourceRef":{"type":"text","required":false},"license":{"type":"text","required":false},"licenseKind":{"type":"text","required":false,"default":"own-work","enum":["own-work","open-license","personal-use-copy","licensed-for-sharing"]},"licenseNote":{"type":"text","required":false},"attestedBy":{"type":"text","required":false},"subject":{"type":"text","required":false},"tags":{"type":"text","required":false},"levelMin":{"type":"integer","required":true,"default":0},"levelMax":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"status":{"type":"text","required":false,"default":"draft","enum":["draft","published","archived"]}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableStrategy":"sti","api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context","_meta_type"]},"extends":"SmrtObject","exportName":"Course","collectionExportName":"CourseCollection","displayLabelField":"title","validationRules":[{"field":"title","rule":"required","fieldType":"text"},{"field":"levelMin","rule":"required","fieldType":"integer"},{"field":"levelMax","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"}],"schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ExternalCourse":{"name":"externalcourse","className":"ExternalCourse","qualifiedName":"@happyvertical/smrt-education:ExternalCourse","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"title":{"type":"text","required":true,"_meta":{"required":true}},"summary":{"type":"text","required":false},"description":{"type":"text","required":false},"sourceKey":{"type":"text","required":false},"sourceUrl":{"type":"text","required":false},"sourceRef":{"type":"text","required":false},"license":{"type":"text","required":false},"licenseKind":{"type":"text","required":false,"default":"own-work","enum":["own-work","open-license","personal-use-copy","licensed-for-sharing"]},"licenseNote":{"type":"text","required":false},"attestedBy":{"type":"text","required":false},"subject":{"type":"text","required":false},"tags":{"type":"text","required":false},"levelMin":{"type":"integer","required":true,"default":0},"levelMax":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"status":{"type":"text","required":false,"default":"draft","enum":["draft","published","archived"]}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"courses","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Course","exportName":"ExternalCourse","collectionExportName":"ExternalCourseCollection","displayLabelField":"title","validationRules":[{"field":"title","rule":"required","fieldType":"text"},{"field":"levelMin","rule":"required","fieldType":"integer"},{"field":"levelMax","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"}],"schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:AuthoredCourse":{"name":"authoredcourse","className":"AuthoredCourse","qualifiedName":"@happyvertical/smrt-education:AuthoredCourse","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"title":{"type":"text","required":true,"_meta":{"required":true}},"summary":{"type":"text","required":false},"description":{"type":"text","required":false},"sourceKey":{"type":"text","required":false},"sourceUrl":{"type":"text","required":false},"sourceRef":{"type":"text","required":false},"license":{"type":"text","required":false},"licenseKind":{"type":"text","required":false,"default":"own-work","enum":["own-work","open-license","personal-use-copy","licensed-for-sharing"]},"licenseNote":{"type":"text","required":false},"attestedBy":{"type":"text","required":false},"subject":{"type":"text","required":false},"tags":{"type":"text","required":false},"levelMin":{"type":"integer","required":true,"default":0},"levelMax":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"status":{"type":"text","required":false,"default":"draft","enum":["draft","published","archived"]}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"courses","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Course","exportName":"AuthoredCourse","collectionExportName":"AuthoredCourseCollection","displayLabelField":"title","validationRules":[{"field":"title","rule":"required","fieldType":"text"},{"field":"levelMin","rule":"required","fieldType":"integer"},{"field":"levelMax","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"}],"schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CourseSection":{"name":"coursesection","className":"CourseSection","qualifiedName":"@happyvertical/smrt-education:CourseSection","collection":"coursesections","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"courseId":{"type":"foreignKey","required":true,"related":"Course","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"sortOrder":{"type":"integer","required":true,"default":0}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"CourseSection","collectionExportName":"CourseSectionCollection","displayLabelField":"title","validationRules":[{"field":"courseId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"}],"schema":{"tableName":"course_sections","ddl":"CREATE TABLE IF NOT EXISTS \\"course_sections\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"course_id\\" UUID NOT NULL,\\n \\"title\\" TEXT NOT NULL,\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"course_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"courses","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":true,"unique":false},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"course_sections_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"course_sections_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"course_sections_course_id_idx","columns":["course_id"]}],"version":"e20480d7"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Lesson":{"name":"lesson","className":"Lesson","qualifiedName":"@happyvertical/smrt-education:Lesson","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"sectionId":{"type":"foreignKey","required":true,"related":"CourseSection","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"body":{"type":"text","required":false},"sortOrder":{"type":"integer","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"completionRule":{"type":"text","required":false,"default":"attest","enum":["attest","submit","score","external"]},"externalRef":{"type":"text","required":false},"externalUrl":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableStrategy":"sti","api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context","_meta_type"]},"extends":"SmrtObject","exportName":"Lesson","collectionExportName":"LessonCollection","displayLabelField":"title","validationRules":[{"field":"sectionId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"}],"schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"092df043"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Reading":{"name":"reading","className":"Reading","qualifiedName":"@happyvertical/smrt-education:Reading","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"sectionId":{"type":"foreignKey","required":true,"related":"CourseSection","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"body":{"type":"text","required":false},"sortOrder":{"type":"integer","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"completionRule":{"type":"text","required":false,"default":"attest","enum":["attest","submit","score","external"]},"externalRef":{"type":"text","required":false},"externalUrl":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Lesson","exportName":"Reading","collectionExportName":"ReadingCollection","displayLabelField":"title","validationRules":[{"field":"sectionId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"}],"schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"9d8b6fcd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Video":{"name":"video","className":"Video","qualifiedName":"@happyvertical/smrt-education:Video","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"sectionId":{"type":"foreignKey","required":true,"related":"CourseSection","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"body":{"type":"text","required":false},"sortOrder":{"type":"integer","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"completionRule":{"type":"text","required":false,"default":"attest","enum":["attest","submit","score","external"]},"externalRef":{"type":"text","required":false},"externalUrl":{"type":"text","required":false},"videoUrl":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Lesson","exportName":"Video","collectionExportName":"VideoCollection","displayLabelField":"title","validationRules":[{"field":"sectionId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"}],"schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"9e0ef984"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Worksheet":{"name":"worksheet","className":"Worksheet","qualifiedName":"@happyvertical/smrt-education:Worksheet","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"sectionId":{"type":"foreignKey","required":true,"related":"CourseSection","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"body":{"type":"text","required":false},"sortOrder":{"type":"integer","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"completionRule":{"type":"text","required":false,"default":"submit","enum":["attest","submit","score","external"]},"externalRef":{"type":"text","required":false},"externalUrl":{"type":"text","required":false},"printableUrl":{"type":"text","required":false},"answerUrl":{"type":"text","required":false},"pages":{"type":"integer","required":true,"default":0}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Lesson","exportName":"Worksheet","collectionExportName":"WorksheetCollection","displayLabelField":"title","validationRules":[{"field":"sectionId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"},{"field":"pages","rule":"required","fieldType":"integer"}],"schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"10976938"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Exercise":{"name":"exercise","className":"Exercise","qualifiedName":"@happyvertical/smrt-education:Exercise","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"sectionId":{"type":"foreignKey","required":true,"related":"CourseSection","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"body":{"type":"text","required":false},"sortOrder":{"type":"integer","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"completionRule":{"type":"text","required":false,"default":"attest","enum":["attest","submit","score","external"]},"externalRef":{"type":"text","required":false},"externalUrl":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Lesson","exportName":"Exercise","collectionExportName":"ExerciseCollection","displayLabelField":"title","validationRules":[{"field":"sectionId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"}],"schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"145eadbe"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CourseCollection":{"name":"coursecollection","className":"CourseCollection","qualifiedName":"@happyvertical/smrt-education:CourseCollection","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Course","exportName":"CourseCollection","collectionExportName":"CourseCollectionCollection","schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ExternalCourseCollection":{"name":"externalcoursecollection","className":"ExternalCourseCollection","qualifiedName":"@happyvertical/smrt-education:ExternalCourseCollection","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"courses","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"ExternalCourse","exportName":"ExternalCourseCollection","collectionExportName":"ExternalCourseCollectionCollection","schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:AuthoredCourseCollection":{"name":"authoredcoursecollection","className":"AuthoredCourseCollection","qualifiedName":"@happyvertical/smrt-education:AuthoredCourseCollection","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"courses","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"AuthoredCourse","exportName":"AuthoredCourseCollection","collectionExportName":"AuthoredCourseCollectionCollection","schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CourseSectionCollection":{"name":"coursesectioncollection","className":"CourseSectionCollection","qualifiedName":"@happyvertical/smrt-education:CourseSectionCollection","collection":"coursesections","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"CourseSection","exportName":"CourseSectionCollection","collectionExportName":"CourseSectionCollectionCollection","schema":{"tableName":"course_sections","ddl":"CREATE TABLE IF NOT EXISTS \\"course_sections\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"course_id\\" UUID NOT NULL,\\n \\"title\\" TEXT NOT NULL,\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"course_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"courses","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":true,"unique":false},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"course_sections_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"course_sections_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"course_sections_course_id_idx","columns":["course_id"]}],"version":"e20480d7"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LessonCollection":{"name":"lessoncollection","className":"LessonCollection","qualifiedName":"@happyvertical/smrt-education:LessonCollection","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Lesson","exportName":"LessonCollection","collectionExportName":"LessonCollectionCollection","schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"092df043"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ReadingCollection":{"name":"readingcollection","className":"ReadingCollection","qualifiedName":"@happyvertical/smrt-education:ReadingCollection","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Reading","exportName":"ReadingCollection","collectionExportName":"ReadingCollectionCollection","schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"9d8b6fcd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:VideoCollection":{"name":"videocollection","className":"VideoCollection","qualifiedName":"@happyvertical/smrt-education:VideoCollection","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Video","exportName":"VideoCollection","collectionExportName":"VideoCollectionCollection","schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"9e0ef984"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:WorksheetCollection":{"name":"worksheetcollection","className":"WorksheetCollection","qualifiedName":"@happyvertical/smrt-education:WorksheetCollection","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Worksheet","exportName":"WorksheetCollection","collectionExportName":"WorksheetCollectionCollection","schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"10976938"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ExerciseCollection":{"name":"exercisecollection","className":"ExerciseCollection","qualifiedName":"@happyvertical/smrt-education:ExerciseCollection","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Exercise","exportName":"ExerciseCollection","collectionExportName":"ExerciseCollectionCollection","schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"145eadbe"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LessonAssetCollection":{"name":"lessonassetcollection","className":"LessonAssetCollection","qualifiedName":"@happyvertical/smrt-education:LessonAssetCollection","collection":"lessonassets","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{"byRight":{"name":"byRight","async":true,"parameters":[{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"byLeft":{"name":"byLeft","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"attach":{"name":"attach","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<TItem>","isStatic":false,"isPublic":true},"detach":{"name":"detach","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"setLinks":{"name":"setLinks","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightIds","type":"string[]","optional":false},{"name":"opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lesson_assets","api":false,"mcp":false,"cli":false},"extends":"SmrtJunction","extendsTypeArg":"LessonAsset","exportName":"LessonAssetCollection","collectionExportName":"LessonAssetCollectionCollection","schema":{"tableName":"lesson_assets","ddl":"CREATE TABLE IF NOT EXISTS \\"lesson_assets\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"lesson_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"relationship\\" TEXT NOT NULL DEFAULT 'attachment',\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"relationship":{"type":"TEXT","notNull":true,"unique":false,"default":"attachment"},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"lesson_assets_tenant_id_lesson_id_idx","columns":["tenant_id","lesson_id","asset_id","relationship"],"unique":true,"nullsNotDistinct":true},{"name":"lesson_assets_slug_context_idx","columns":["slug","context"]},{"name":"lesson_assets_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lesson_assets_lesson_id_idx","columns":["lesson_id"]},{"name":"lesson_assets_asset_id_idx","columns":["asset_id"]}],"version":"d96385c6"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LearnerCollection":{"name":"learnercollection","className":"LearnerCollection","qualifiedName":"@happyvertical/smrt-education:LearnerCollection","collection":"learners","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Learner","exportName":"LearnerCollection","collectionExportName":"LearnerCollectionCollection","schema":{"tableName":"learners","ddl":"CREATE TABLE IF NOT EXISTS \\"learners\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"user_id\\" UUID NOT NULL,\\n \\"profile_id\\" UUID NOT NULL,\\n \\"display_name\\" TEXT,\\n \\"birth_date\\" TIMESTAMP,\\n \\"level\\" INTEGER NOT NULL DEFAULT 0,\\n \\"settings\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"user_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"display_name":{"type":"TEXT","notNull":false,"unique":false},"birth_date":{"type":"TIMESTAMP","notNull":false,"unique":false},"level":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"settings":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"learners_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"learners_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"learners_user_id_idx","columns":["user_id"]},{"name":"learners_profile_id_idx","columns":["profile_id"]}],"version":"ca9907b5"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CurriculumCollection":{"name":"curriculumcollection","className":"CurriculumCollection","qualifiedName":"@happyvertical/smrt-education:CurriculumCollection","collection":"curriculums","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"curricula","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Curriculum","exportName":"CurriculumCollection","collectionExportName":"CurriculumCollectionCollection","schema":{"tableName":"curricula","ddl":"CREATE TABLE IF NOT EXISTS \\"curricula\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"title\\" TEXT NOT NULL,\\n \\"starts_on\\" TIMESTAMP,\\n \\"ends_on\\" TIMESTAMP,\\n \\"status\\" TEXT DEFAULT 'planned'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":true,"unique":false},"starts_on":{"type":"TIMESTAMP","notNull":false,"unique":false},"ends_on":{"type":"TIMESTAMP","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"planned"}},"indexes":[{"name":"curricula_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"curricula_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"curricula_learner_id_idx","columns":["learner_id"]}],"version":"b7e90cbd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:EnrollmentCollection":{"name":"enrollmentcollection","className":"EnrollmentCollection","qualifiedName":"@happyvertical/smrt-education:EnrollmentCollection","collection":"enrollments","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Enrollment","exportName":"EnrollmentCollection","collectionExportName":"EnrollmentCollectionCollection","schema":{"tableName":"enrollments","ddl":"CREATE TABLE IF NOT EXISTS \\"enrollments\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"curriculum_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"course_id\\" UUID NOT NULL,\\n \\"status\\" TEXT DEFAULT 'planned',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"target_minutes_per_week\\" INTEGER NOT NULL DEFAULT 0,\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"curriculum_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"curricula","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"course_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"courses","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"planned"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"target_minutes_per_week":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"enrollments_tenant_id_curriculum_id_idx","columns":["tenant_id","curriculum_id","course_id"],"unique":true},{"name":"enrollments_slug_context_idx","columns":["slug","context"]},{"name":"enrollments_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"enrollments_curriculum_id_idx","columns":["curriculum_id"]},{"name":"enrollments_learner_id_idx","columns":["learner_id"]},{"name":"enrollments_course_id_idx","columns":["course_id"]}],"version":"2a34b5ee"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LessonProgressCollection":{"name":"lessonprogresscollection","className":"LessonProgressCollection","qualifiedName":"@happyvertical/smrt-education:LessonProgressCollection","collection":"lessonprogresses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"lesson_progress","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"LessonProgress","exportName":"LessonProgressCollection","collectionExportName":"LessonProgressCollectionCollection","schema":{"tableName":"lesson_progress","ddl":"CREATE TABLE IF NOT EXISTS \\"lesson_progress\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"enrollment_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"lesson_id\\" UUID NOT NULL,\\n \\"status\\" TEXT DEFAULT 'not_started',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"time_spent_seconds\\" INTEGER NOT NULL DEFAULT 0,\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"best_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"enrollment_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"enrollments","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"not_started"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"time_spent_seconds":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"best_score":{"type":"REAL","notNull":false,"unique":false}},"indexes":[{"name":"lesson_progress_tenant_id_enrollment_id_idx","columns":["tenant_id","enrollment_id","lesson_id"],"unique":true},{"name":"lesson_progress_slug_context_idx","columns":["slug","context"]},{"name":"lesson_progress_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lesson_progress_enrollment_id_idx","columns":["enrollment_id"]},{"name":"lesson_progress_learner_id_idx","columns":["learner_id"]},{"name":"lesson_progress_lesson_id_idx","columns":["lesson_id"]}],"version":"36704fbd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:SubmissionCollection":{"name":"submissioncollection","className":"SubmissionCollection","qualifiedName":"@happyvertical/smrt-education:SubmissionCollection","collection":"submissions","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Submission","exportName":"SubmissionCollection","collectionExportName":"SubmissionCollectionCollection","schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]}],"version":"9839d766"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ScannedSubmissionCollection":{"name":"scannedsubmissioncollection","className":"ScannedSubmissionCollection","qualifiedName":"@happyvertical/smrt-education:ScannedSubmissionCollection","collection":"submissions","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"ScannedSubmission","exportName":"ScannedSubmissionCollection","collectionExportName":"ScannedSubmissionCollectionCollection","schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]}],"version":"9cf5d690"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ExternalCompletionCollection":{"name":"externalcompletioncollection","className":"ExternalCompletionCollection","qualifiedName":"@happyvertical/smrt-education:ExternalCompletionCollection","collection":"submissions","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"ExternalCompletion","exportName":"ExternalCompletionCollection","collectionExportName":"ExternalCompletionCollectionCollection","schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]}],"version":"97981928"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:AttestationCollection":{"name":"attestationcollection","className":"AttestationCollection","qualifiedName":"@happyvertical/smrt-education:AttestationCollection","collection":"submissions","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Attestation","exportName":"AttestationCollection","collectionExportName":"AttestationCollectionCollection","schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]}],"version":"03b3a269"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:GradeCollection":{"name":"gradecollection","className":"GradeCollection","qualifiedName":"@happyvertical/smrt-education:GradeCollection","collection":"grades","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Grade","exportName":"GradeCollection","collectionExportName":"GradeCollectionCollection","schema":{"tableName":"grades","ddl":"CREATE TABLE IF NOT EXISTS \\"grades\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"submission_id\\" UUID NOT NULL,\\n \\"grader_kind\\" TEXT NOT NULL DEFAULT 'auto',\\n \\"grader_profile_id\\" UUID,\\n \\"score\\" REAL,\\n \\"points_earned\\" INTEGER,\\n \\"points_possible\\" INTEGER,\\n \\"rubric_results\\" TEXT,\\n \\"feedback\\" TEXT,\\n \\"graded_at\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"grader_kind":{"type":"TEXT","notNull":true,"unique":false,"default":"auto"},"grader_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"score":{"type":"REAL","notNull":false,"unique":false},"points_earned":{"type":"INTEGER","notNull":false,"unique":false},"points_possible":{"type":"INTEGER","notNull":false,"unique":false},"rubric_results":{"type":"TEXT","notNull":false,"unique":false},"feedback":{"type":"TEXT","notNull":false,"unique":false},"graded_at":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"grades_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"grades_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"grades_submission_id_idx","columns":["submission_id"]},{"name":"grades_grader_profile_id_idx","columns":["grader_profile_id"]}],"version":"7d629fec"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:SubmissionAssetCollection":{"name":"submissionassetcollection","className":"SubmissionAssetCollection","qualifiedName":"@happyvertical/smrt-education:SubmissionAssetCollection","collection":"submissionassets","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{"byRight":{"name":"byRight","async":true,"parameters":[{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"byLeft":{"name":"byLeft","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"attach":{"name":"attach","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<TItem>","isStatic":false,"isPublic":true},"detach":{"name":"detach","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"setLinks":{"name":"setLinks","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightIds","type":"string[]","optional":false},{"name":"opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submission_assets","api":false,"mcp":false,"cli":false},"extends":"SmrtJunction","extendsTypeArg":"SubmissionAsset","exportName":"SubmissionAssetCollection","collectionExportName":"SubmissionAssetCollectionCollection","schema":{"tableName":"submission_assets","ddl":"CREATE TABLE IF NOT EXISTS \\"submission_assets\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"submission_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"relationship\\" TEXT NOT NULL DEFAULT 'page',\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"relationship":{"type":"TEXT","notNull":true,"unique":false,"default":"page"},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"submission_assets_tenant_id_submission_id_idx","columns":["tenant_id","submission_id","asset_id","relationship"],"unique":true},{"name":"submission_assets_slug_context_idx","columns":["slug","context"]},{"name":"submission_assets_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submission_assets_submission_id_idx","columns":["submission_id"]},{"name":"submission_assets_asset_id_idx","columns":["asset_id"]}],"version":"d9b61c8a"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CourseSource":{"name":"coursesource","className":"CourseSource","qualifiedName":"@happyvertical/smrt-education:CourseSource","collection":"coursesources","description":"Host-managed source policy; deliberately has no generated CRUD surface.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/course-source.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"key":{"type":"text","required":true,"_meta":{"required":true}},"name":{"type":"text","required":true,"_meta":{"required":true}},"kind":{"type":"text","required":false,"default":"adapter","enum":["manual","url","adapter"]},"adapterKey":{"type":"text","required":false},"config":{"type":"text","required":false},"sharing":{"type":"text","required":false,"default":"private","enum":["private","group","global"]}},"methods":{"getConfig":{"name":"getConfig","async":false,"parameters":[],"returnType":"CourseSourceConfig","isStatic":false,"isPublic":true},"setConfig":{"name":"setConfig","async":false,"parameters":[{"name":"value","type":"CourseSourceConfig","optional":false}],"returnType":"void","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"cli":false,"mcp":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"CourseSource","collectionExportName":"CourseSourceCollection","displayLabelField":"name","validationRules":[{"field":"key","rule":"required","fieldType":"text"},{"field":"name","rule":"required","fieldType":"text"}],"schema":{"tableName":"course_sources","ddl":"CREATE TABLE IF NOT EXISTS \\"course_sources\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"key\\" TEXT NOT NULL,\\n \\"name\\" TEXT NOT NULL,\\n \\"kind\\" TEXT DEFAULT 'adapter',\\n \\"adapter_key\\" TEXT,\\n \\"config\\" TEXT,\\n \\"sharing\\" TEXT DEFAULT 'private'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"key":{"type":"TEXT","notNull":true,"unique":false},"name":{"type":"TEXT","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"adapter"},"adapter_key":{"type":"TEXT","notNull":false,"unique":false},"config":{"type":"TEXT","notNull":false,"unique":false},"sharing":{"type":"TEXT","notNull":false,"unique":false,"default":"private"}},"indexes":[{"name":"course_sources_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"course_sources_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"98177eb3"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CourseSourceCollection":{"name":"coursesourcecollection","className":"CourseSourceCollection","qualifiedName":"@happyvertical/smrt-education:CourseSourceCollection","collection":"coursesources","filePath":"/home/runner/work/smrt/smrt/packages/education/src/course-source.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"CourseSource","exportName":"CourseSourceCollection","collectionExportName":"CourseSourceCollectionCollection","schema":{"tableName":"course_sources","ddl":"CREATE TABLE IF NOT EXISTS \\"course_sources\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"key\\" TEXT NOT NULL,\\n \\"name\\" TEXT NOT NULL,\\n \\"kind\\" TEXT DEFAULT 'adapter',\\n \\"adapter_key\\" TEXT,\\n \\"config\\" TEXT,\\n \\"sharing\\" TEXT DEFAULT 'private'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"key":{"type":"TEXT","notNull":true,"unique":false},"name":{"type":"TEXT","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"adapter"},"adapter_key":{"type":"TEXT","notNull":false,"unique":false},"config":{"type":"TEXT","notNull":false,"unique":false},"sharing":{"type":"TEXT","notNull":false,"unique":false,"default":"private"}},"indexes":[{"name":"course_sources_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"course_sources_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"98177eb3"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Learner":{"name":"learner","className":"Learner","qualifiedName":"@happyvertical/smrt-education:Learner","collection":"learners","description":"A learner is a real user/profile identity with active family membership.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/learners.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"userId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-users:User"},"profileId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-profiles:Profile"},"displayName":{"type":"text","required":false},"birthDate":{"type":"datetime","required":false},"level":{"type":"integer","required":true,"default":0},"settings":{"type":"text","required":false}},"methods":{"getSettings":{"name":"getSettings","async":false,"parameters":[],"returnType":"Record<string, unknown>","isStatic":false,"isPublic":true},"setSettings":{"name":"setSettings","async":false,"parameters":[{"name":"value","type":"Record<string, unknown>","optional":false}],"returnType":"void","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"Learner","collectionExportName":"LearnerCollection","validationRules":[{"field":"userId","rule":"required","fieldType":"crossPackageRef"},{"field":"profileId","rule":"required","fieldType":"crossPackageRef"},{"field":"level","rule":"required","fieldType":"integer"}],"schema":{"tableName":"learners","ddl":"CREATE TABLE IF NOT EXISTS \\"learners\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"user_id\\" UUID NOT NULL,\\n \\"profile_id\\" UUID NOT NULL,\\n \\"display_name\\" TEXT,\\n \\"birth_date\\" TIMESTAMP,\\n \\"level\\" INTEGER NOT NULL DEFAULT 0,\\n \\"settings\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"user_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"display_name":{"type":"TEXT","notNull":false,"unique":false},"birth_date":{"type":"TIMESTAMP","notNull":false,"unique":false},"level":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"settings":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"learners_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"learners_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"learners_user_id_idx","columns":["user_id"]},{"name":"learners_profile_id_idx","columns":["profile_id"]}],"version":"ca9907b5"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Curriculum":{"name":"curriculum","className":"Curriculum","qualifiedName":"@happyvertical/smrt-education:Curriculum","collection":"curriculums","description":"A distinct plan retains its own enrollment history.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/learners.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"startsOn":{"type":"datetime","required":false},"endsOn":{"type":"datetime","required":false},"status":{"type":"text","required":false,"default":"planned","enum":["planned","active","completed","archived"]}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tableName":"curricula","tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"Curriculum","collectionExportName":"CurriculumCollection","displayLabelField":"title","validationRules":[{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"}],"schema":{"tableName":"curricula","ddl":"CREATE TABLE IF NOT EXISTS \\"curricula\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"title\\" TEXT NOT NULL,\\n \\"starts_on\\" TIMESTAMP,\\n \\"ends_on\\" TIMESTAMP,\\n \\"status\\" TEXT DEFAULT 'planned'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":true,"unique":false},"starts_on":{"type":"TIMESTAMP","notNull":false,"unique":false},"ends_on":{"type":"TIMESTAMP","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"planned"}},"indexes":[{"name":"curricula_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"curricula_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"curricula_learner_id_idx","columns":["learner_id"]}],"version":"b7e90cbd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Enrollment":{"name":"enrollment","className":"Enrollment","qualifiedName":"@happyvertical/smrt-education:Enrollment","collection":"enrollments","description":"One course in one curriculum; a new curriculum means a new enrollment.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/learners.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"curriculumId":{"type":"foreignKey","required":true,"related":"Curriculum","_meta":{"required":true}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"courseId":{"type":"foreignKey","required":true,"related":"Course","_meta":{"required":true}},"status":{"type":"text","required":false,"default":"planned","enum":["planned","active","paused","completed","dropped"]},"startedAt":{"type":"datetime","required":false},"completedAt":{"type":"datetime","required":false},"targetMinutesPerWeek":{"type":"integer","required":true,"default":0},"sortOrder":{"type":"integer","required":true,"default":0}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"conflictColumns":["tenant_id","curriculum_id","course_id"],"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"Enrollment","collectionExportName":"EnrollmentCollection","validationRules":[{"field":"curriculumId","rule":"required","fieldType":"foreignKey"},{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"courseId","rule":"required","fieldType":"foreignKey"},{"field":"targetMinutesPerWeek","rule":"required","fieldType":"integer"},{"field":"sortOrder","rule":"required","fieldType":"integer"}],"schema":{"tableName":"enrollments","ddl":"CREATE TABLE IF NOT EXISTS \\"enrollments\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"curriculum_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"course_id\\" UUID NOT NULL,\\n \\"status\\" TEXT DEFAULT 'planned',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"target_minutes_per_week\\" INTEGER NOT NULL DEFAULT 0,\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"curriculum_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"curricula","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"course_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"courses","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"planned"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"target_minutes_per_week":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"enrollments_tenant_id_curriculum_id_idx","columns":["tenant_id","curriculum_id","course_id"],"unique":true},{"name":"enrollments_slug_context_idx","columns":["slug","context"]},{"name":"enrollments_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"enrollments_curriculum_id_idx","columns":["curriculum_id"]},{"name":"enrollments_learner_id_idx","columns":["learner_id"]},{"name":"enrollments_course_id_idx","columns":["course_id"]}],"version":"2a34b5ee"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LessonProgress":{"name":"lessonprogress","className":"LessonProgress","qualifiedName":"@happyvertical/smrt-education:LessonProgress","collection":"lessonprogresses","description":"Progress identity is enrollment + lesson, never learner + lesson.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/learners.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"enrollmentId":{"type":"foreignKey","required":true,"related":"Enrollment","_meta":{"required":true}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"status":{"type":"text","required":false,"default":"not_started","enum":["not_started","in_progress","submitted","needs_review","complete","skipped"]},"startedAt":{"type":"datetime","required":false},"completedAt":{"type":"datetime","required":false},"timeSpentSeconds":{"type":"integer","required":true,"default":0},"attempts":{"type":"integer","required":true,"default":0},"bestScore":{"type":"decimal","required":false,"_meta":{"nullable":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tableName":"lesson_progress","conflictColumns":["tenant_id","enrollment_id","lesson_id"],"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"LessonProgress","collectionExportName":"LessonProgressCollection","validationRules":[{"field":"enrollmentId","rule":"required","fieldType":"foreignKey"},{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"timeSpentSeconds","rule":"required","fieldType":"integer"},{"field":"attempts","rule":"required","fieldType":"integer"}],"schema":{"tableName":"lesson_progress","ddl":"CREATE TABLE IF NOT EXISTS \\"lesson_progress\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"enrollment_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"lesson_id\\" UUID NOT NULL,\\n \\"status\\" TEXT DEFAULT 'not_started',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"time_spent_seconds\\" INTEGER NOT NULL DEFAULT 0,\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"best_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"enrollment_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"enrollments","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"not_started"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"time_spent_seconds":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"best_score":{"type":"REAL","notNull":false,"unique":false}},"indexes":[{"name":"lesson_progress_tenant_id_enrollment_id_idx","columns":["tenant_id","enrollment_id","lesson_id"],"unique":true},{"name":"lesson_progress_slug_context_idx","columns":["slug","context"]},{"name":"lesson_progress_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lesson_progress_enrollment_id_idx","columns":["enrollment_id"]},{"name":"lesson_progress_learner_id_idx","columns":["learner_id"]},{"name":"lesson_progress_lesson_id_idx","columns":["lesson_id"]}],"version":"36704fbd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LessonAsset":{"name":"lessonasset","className":"LessonAsset","qualifiedName":"@happyvertical/smrt-education:LessonAsset","collection":"lessonassets","filePath":"/home/runner/work/smrt/smrt/packages/education/src/lesson-assets.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"assetId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-assets:Asset"},"relationship":{"type":"text","required":true,"default":"attachment","_meta":{"required":true},"enum":["printable","answer-key","video","attachment"]},"sortOrder":{"type":"integer","required":true,"default":0}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lesson_assets","conflictColumns":["tenant_id","lesson_id","asset_id","relationship"],"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"LessonAsset","collectionExportName":"LessonAssetCollection","validationRules":[{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"assetId","rule":"required","fieldType":"crossPackageRef"},{"field":"relationship","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"}],"schema":{"tableName":"lesson_assets","ddl":"CREATE TABLE IF NOT EXISTS \\"lesson_assets\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"lesson_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"relationship\\" TEXT NOT NULL DEFAULT 'attachment',\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"relationship":{"type":"TEXT","notNull":true,"unique":false,"default":"attachment"},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"lesson_assets_tenant_id_lesson_id_idx","columns":["tenant_id","lesson_id","asset_id","relationship"],"unique":true,"nullsNotDistinct":true},{"name":"lesson_assets_slug_context_idx","columns":["slug","context"]},{"name":"lesson_assets_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lesson_assets_lesson_id_idx","columns":["lesson_id"]},{"name":"lesson_assets_asset_id_idx","columns":["asset_id"]}],"version":"d96385c6"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Submission":{"name":"submission","className":"Submission","qualifiedName":"@happyvertical/smrt-education:Submission","collection":"submissions","description":"Evidence that a learner completed one progress item.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"progressId":{"type":"foreignKey","required":true,"related":"LessonProgress","_meta":{"required":true}},"submittedAt":{"type":"text","required":false},"status":{"type":"text","required":false,"default":"captured","enum":["captured","extracting","needs_review","graded","rejected"]},"notes":{"type":"text","required":false},"captureKey":{"type":"text","required":false},"captureDigest":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableStrategy":"sti","api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context","_meta_type"]},"extends":"SmrtObject","exportName":"Submission","collectionExportName":"SubmissionCollection","validationRules":[{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"progressId","rule":"required","fieldType":"foreignKey"}],"schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]}],"version":"9839d766"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ScannedSubmission":{"name":"scannedsubmission","className":"ScannedSubmission","qualifiedName":"@happyvertical/smrt-education:ScannedSubmission","collection":"submissions","description":"Photos or scans of work submitted for review.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false}}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"progressId":{"type":"foreignKey","required":true,"related":"LessonProgress","_meta":{"required":true}},"submittedAt":{"type":"text","required":false},"status":{"type":"text","required":false,"default":"captured","enum":["captured","extracting","needs_review","graded","rejected"]},"notes":{"type":"text","required":false},"captureKey":{"type":"text","required":false},"captureDigest":{"type":"text","required":false},"pageCount":{"type":"integer","required":true,"default":0},"ocrText":{"type":"text","required":false},"extracted":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"getExtracted":{"name":"getExtracted","async":false,"parameters":[],"returnType":"Record<string, unknown>","isStatic":false,"isPublic":true},"setExtracted":{"name":"setExtracted","async":false,"parameters":[{"name":"value","type":"Record<string, unknown>","optional":false}],"returnType":"void","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Submission","exportName":"ScannedSubmission","collectionExportName":"ScannedSubmissionCollection","validationRules":[{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"progressId","rule":"required","fieldType":"foreignKey"},{"field":"pageCount","rule":"required","fieldType":"integer"}],"schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]}],"version":"9cf5d690"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ExternalCompletion":{"name":"externalcompletion","className":"ExternalCompletion","qualifiedName":"@happyvertical/smrt-education:ExternalCompletion","collection":"submissions","description":"Completion reported by the learner, guardian, or external source.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false}}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"progressId":{"type":"foreignKey","required":true,"related":"LessonProgress","_meta":{"required":true}},"submittedAt":{"type":"text","required":false},"status":{"type":"text","required":false,"default":"captured","enum":["captured","extracting","needs_review","graded","rejected"]},"notes":{"type":"text","required":false},"captureKey":{"type":"text","required":false},"captureDigest":{"type":"text","required":false},"reportedBy":{"type":"text","required":false,"default":"learner","enum":["learner","guardian","source"]},"evidenceUrl":{"type":"text","required":false},"reportedScore":{"type":"decimal","required":false,"_meta":{}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Submission","exportName":"ExternalCompletion","collectionExportName":"ExternalCompletionCollection","validationRules":[{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"progressId","rule":"required","fieldType":"foreignKey"}],"schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]}],"version":"97981928"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Attestation":{"name":"attestation","className":"Attestation","qualifiedName":"@happyvertical/smrt-education:Attestation","collection":"submissions","description":"A guardian's statement that work was completed.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false}}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"progressId":{"type":"foreignKey","required":true,"related":"LessonProgress","_meta":{"required":true}},"submittedAt":{"type":"text","required":false},"status":{"type":"text","required":false,"default":"captured","enum":["captured","extracting","needs_review","graded","rejected"]},"notes":{"type":"text","required":false},"captureKey":{"type":"text","required":false},"captureDigest":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Submission","exportName":"Attestation","collectionExportName":"AttestationCollection","validationRules":[{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"progressId","rule":"required","fieldType":"foreignKey"}],"schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]}],"version":"03b3a269"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:SubmissionAsset":{"name":"submissionasset","className":"SubmissionAsset","qualifiedName":"@happyvertical/smrt-education:SubmissionAsset","collection":"submissionassets","description":"The tenant-owned files attached to a submission.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"submissionId":{"type":"foreignKey","required":true,"related":"Submission","_meta":{"required":true}},"assetId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-assets:Asset"},"relationship":{"type":"text","required":true,"default":"page","_meta":{"required":true},"enum":["page","attachment"]},"sortOrder":{"type":"integer","required":true,"default":0}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submission_assets","conflictColumns":["tenant_id","submission_id","asset_id","relationship"],"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"SubmissionAsset","collectionExportName":"SubmissionAssetCollection","validationRules":[{"field":"submissionId","rule":"required","fieldType":"foreignKey"},{"field":"assetId","rule":"required","fieldType":"crossPackageRef"},{"field":"relationship","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"}],"schema":{"tableName":"submission_assets","ddl":"CREATE TABLE IF NOT EXISTS \\"submission_assets\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"submission_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"relationship\\" TEXT NOT NULL DEFAULT 'page',\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"relationship":{"type":"TEXT","notNull":true,"unique":false,"default":"page"},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"submission_assets_tenant_id_submission_id_idx","columns":["tenant_id","submission_id","asset_id","relationship"],"unique":true},{"name":"submission_assets_slug_context_idx","columns":["slug","context"]},{"name":"submission_assets_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submission_assets_submission_id_idx","columns":["submission_id"]},{"name":"submission_assets_asset_id_idx","columns":["asset_id"]}],"version":"d9b61c8a"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Grade":{"name":"grade","className":"Grade","qualifiedName":"@happyvertical/smrt-education:Grade","collection":"grades","description":"An assessment of a submission; the service selects the authoritative grade.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"submissionId":{"type":"foreignKey","required":true,"related":"Submission","_meta":{"required":true}},"graderKind":{"type":"text","required":true,"default":"auto","_meta":{"required":true},"enum":["auto","ai","guardian","instructor"]},"graderProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile","_meta":{"nullable":true}},"score":{"type":"decimal","required":false,"_meta":{"nullable":true}},"pointsEarned":{"type":"integer","required":false,"_meta":{"nullable":true}},"pointsPossible":{"type":"integer","required":false,"_meta":{"nullable":true}},"rubricResults":{"type":"text","required":false},"feedback":{"type":"text","required":false},"gradedAt":{"type":"text","required":false}},"methods":{"getRubricResults":{"name":"getRubricResults","async":false,"parameters":[],"returnType":"Record<string, unknown>","isStatic":false,"isPublic":true},"setRubricResults":{"name":"setRubricResults","async":false,"parameters":[{"name":"value","type":"Record<string, unknown>","optional":false}],"returnType":"void","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"Grade","collectionExportName":"GradeCollection","validationRules":[{"field":"submissionId","rule":"required","fieldType":"foreignKey"},{"field":"graderKind","rule":"required","fieldType":"text"}],"schema":{"tableName":"grades","ddl":"CREATE TABLE IF NOT EXISTS \\"grades\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"submission_id\\" UUID NOT NULL,\\n \\"grader_kind\\" TEXT NOT NULL DEFAULT 'auto',\\n \\"grader_profile_id\\" UUID,\\n \\"score\\" REAL,\\n \\"points_earned\\" INTEGER,\\n \\"points_possible\\" INTEGER,\\n \\"rubric_results\\" TEXT,\\n \\"feedback\\" TEXT,\\n \\"graded_at\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"grader_kind":{"type":"TEXT","notNull":true,"unique":false,"default":"auto"},"grader_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"score":{"type":"REAL","notNull":false,"unique":false},"points_earned":{"type":"INTEGER","notNull":false,"unique":false},"points_possible":{"type":"INTEGER","notNull":false,"unique":false},"rubric_results":{"type":"TEXT","notNull":false,"unique":false},"feedback":{"type":"TEXT","notNull":false,"unique":false},"graded_at":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"grades_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"grades_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"grades_submission_id_idx","columns":["submission_id"]},{"name":"grades_grader_profile_id_idx","columns":["grader_profile_id"]}],"version":"7d629fec"},"importPath":"@happyvertical/smrt-education"}},"moduleType":"smrt","recipes":[{"id":"education.catalog","className":"EducationCatalogRecipe","label":"Course catalog","summary":"Organize courses, sections, lessons, and their authorized learning materials.","synonyms":["courses","lessons","curriculum catalog"],"models":["@happyvertical/smrt-education:Course","@happyvertical/smrt-education:CourseSection","@happyvertical/smrt-education:Lesson","@happyvertical/smrt-education:LessonAsset"],"nav":[{"label":"Courses","model":"@happyvertical/smrt-education:Course","icon":"folder","description":"Courses available to this family.","noun":"course"}],"requires":[],"section":{"id":"education","label":"Education","icon":"folder","description":"Courses and lessons for learning."},"help":{"markdown":"# Course catalog\\n\\nManage courses, their sections, and lessons. Private family copies and answer\\nkeys remain visible only through the application\u2019s authorized catalog service.\\n","fieldRefs":[]},"runtime":"both","demo":{"mode":"server","reasons":["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)."]}},{"id":"education.learner","className":"EducationLearnerRecipe","label":"Learners","summary":"Plan learner curricula and retain enrollment-scoped progress.","synonyms":["learners","enrollments","learning progress"],"models":["@happyvertical/smrt-education:Learner","@happyvertical/smrt-education:Curriculum","@happyvertical/smrt-education:Enrollment","@happyvertical/smrt-education:LessonProgress"],"nav":[{"label":"Learners","model":"@happyvertical/smrt-education:Learner","icon":"users","description":"Authorized learners in this family.","noun":"learner"}],"requires":[],"section":{"id":"education","label":"Education","icon":"folder","description":"Courses and lessons for learning."},"help":{"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","fieldRefs":[]},"runtime":"both","demo":{"mode":"server","reasons":["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)."]}}],"smrtDependencies":["@happyvertical/smrt-assets","@happyvertical/smrt-core","@happyvertical/smrt-profiles","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"],"browser":{"status":"server-only","issues":["#3617","#3624","#3626"],"via":["@happyvertical/smrt-profiles","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"]},"stampsConstructors":true}`));
|
|
29
|
+
ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-education","packageVersion":"0.57.10","objects":{"@happyvertical/smrt-education:CatalogProviderCollection":{"name":"catalogprovidercollection","className":"CatalogProviderCollection","qualifiedName":"@happyvertical/smrt-education:CatalogProviderCollection","collection":"catalogproviders","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog-collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"CatalogProvider","exportName":"CatalogProviderCollection","collectionExportName":"CatalogProviderCollectionCollection","schema":{"tableName":"catalog_providers","ddl":"CREATE TABLE IF NOT EXISTS \\"catalog_providers\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"provider_id\\" TEXT NOT NULL,\\n \\"name\\" TEXT NOT NULL,\\n \\"active\\" BOOLEAN NOT NULL DEFAULT TRUE\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"provider_id":{"type":"TEXT","notNull":true,"unique":false},"name":{"type":"TEXT","notNull":true,"unique":false},"active":{"type":"BOOLEAN","notNull":true,"unique":false,"default":true}},"indexes":[{"name":"catalog_providers_slug_context_idx","columns":["slug","context"],"unique":true},{"name":"catalog_providers_created_at_idx","columns":["created_at"]}],"version":"f35557a4"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CatalogResourceCollection":{"name":"catalogresourcecollection","className":"CatalogResourceCollection","qualifiedName":"@happyvertical/smrt-education:CatalogResourceCollection","collection":"catalogresources","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog-collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"CatalogResource","exportName":"CatalogResourceCollection","collectionExportName":"CatalogResourceCollectionCollection","schema":{"tableName":"catalog_resources","ddl":"CREATE TABLE IF NOT EXISTS \\"catalog_resources\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"provider_id\\" TEXT NOT NULL,\\n \\"stable_id\\" TEXT NOT NULL,\\n \\"revision\\" TEXT NOT NULL,\\n \\"canonical_url\\" TEXT,\\n \\"title\\" TEXT NOT NULL,\\n \\"supplied_description\\" TEXT,\\n \\"author\\" TEXT,\\n \\"subjects\\" TEXT,\\n \\"grades\\" TEXT,\\n \\"language\\" TEXT,\\n \\"resource_type\\" TEXT,\\n \\"parent_stable_id\\" TEXT,\\n \\"available_formats\\" TEXT,\\n \\"license_evidence\\" TEXT,\\n \\"license_state\\" TEXT DEFAULT 'unknown',\\n \\"visibility\\" TEXT DEFAULT 'catalog',\\n \\"last_seen_run_id\\" TEXT,\\n \\"withdrawn_at\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"provider_id":{"type":"TEXT","notNull":true,"unique":false},"stable_id":{"type":"TEXT","notNull":true,"unique":false},"revision":{"type":"TEXT","notNull":true,"unique":false},"canonical_url":{"type":"TEXT","notNull":false,"unique":false},"title":{"type":"TEXT","notNull":true,"unique":false},"supplied_description":{"type":"TEXT","notNull":false,"unique":false},"author":{"type":"TEXT","notNull":false,"unique":false},"subjects":{"type":"TEXT","notNull":false,"unique":false},"grades":{"type":"TEXT","notNull":false,"unique":false},"language":{"type":"TEXT","notNull":false,"unique":false},"resource_type":{"type":"TEXT","notNull":false,"unique":false},"parent_stable_id":{"type":"TEXT","notNull":false,"unique":false},"available_formats":{"type":"TEXT","notNull":false,"unique":false},"license_evidence":{"type":"TEXT","notNull":false,"unique":false},"license_state":{"type":"TEXT","notNull":false,"unique":false,"default":"unknown"},"visibility":{"type":"TEXT","notNull":false,"unique":false,"default":"catalog"},"last_seen_run_id":{"type":"TEXT","notNull":false,"unique":false},"withdrawn_at":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"catalog_resources_slug_context_idx","columns":["slug","context"],"unique":true},{"name":"catalog_resources_created_at_idx","columns":["created_at"]}],"version":"441b7271"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CatalogSyncStateCollection":{"name":"catalogsyncstatecollection","className":"CatalogSyncStateCollection","qualifiedName":"@happyvertical/smrt-education:CatalogSyncStateCollection","collection":"catalogsyncstates","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog-collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"CatalogSyncState","exportName":"CatalogSyncStateCollection","collectionExportName":"CatalogSyncStateCollectionCollection","schema":{"tableName":"catalog_sync_states","ddl":"CREATE TABLE IF NOT EXISTS \\"catalog_sync_states\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"provider_id\\" TEXT NOT NULL,\\n \\"cursor\\" TEXT,\\n \\"run_id\\" TEXT,\\n \\"complete\\" BOOLEAN NOT NULL DEFAULT FALSE,\\n \\"paused\\" BOOLEAN NOT NULL DEFAULT FALSE,\\n \\"pause_reason\\" TEXT,\\n \\"imported_count\\" INTEGER NOT NULL DEFAULT 0,\\n \\"failures\\" TEXT,\\n \\"last_successful_at\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"provider_id":{"type":"TEXT","notNull":true,"unique":false},"cursor":{"type":"TEXT","notNull":false,"unique":false},"run_id":{"type":"TEXT","notNull":false,"unique":false},"complete":{"type":"BOOLEAN","notNull":true,"unique":false,"default":false},"paused":{"type":"BOOLEAN","notNull":true,"unique":false,"default":false},"pause_reason":{"type":"TEXT","notNull":false,"unique":false},"imported_count":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"failures":{"type":"TEXT","notNull":false,"unique":false},"last_successful_at":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"catalog_sync_states_slug_context_idx","columns":["slug","context"],"unique":true},{"name":"catalog_sync_states_created_at_idx","columns":["created_at"]}],"version":"52dc3ee1"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CatalogSelectionCollection":{"name":"catalogselectioncollection","className":"CatalogSelectionCollection","qualifiedName":"@happyvertical/smrt-education:CatalogSelectionCollection","collection":"catalogselections","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog-collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"CatalogSelection","exportName":"CatalogSelectionCollection","collectionExportName":"CatalogSelectionCollectionCollection","schema":{"tableName":"catalog_selections","ddl":"CREATE TABLE IF NOT EXISTS \\"catalog_selections\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"resource_id\\" UUID NOT NULL,\\n \\"selected_by_user_id\\" TEXT NOT NULL,\\n \\"selected_revision\\" TEXT NOT NULL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"resource_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"catalog_resources","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"selected_by_user_id":{"type":"TEXT","notNull":true,"unique":false},"selected_revision":{"type":"TEXT","notNull":true,"unique":false}},"indexes":[{"name":"catalog_selections_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"catalog_selections_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"catalog_selections_resource_id_idx","columns":["resource_id"]}],"version":"c6b02eb9"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CatalogAcquisitionCollection":{"name":"catalogacquisitioncollection","className":"CatalogAcquisitionCollection","qualifiedName":"@happyvertical/smrt-education:CatalogAcquisitionCollection","collection":"catalogacquisitions","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog-collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"CatalogAcquisition","exportName":"CatalogAcquisitionCollection","collectionExportName":"CatalogAcquisitionCollectionCollection","schema":{"tableName":"catalog_acquisitions","ddl":"CREATE TABLE IF NOT EXISTS \\"catalog_acquisitions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"resource_id\\" UUID NOT NULL,\\n \\"selection_id\\" UUID NOT NULL,\\n \\"revision\\" TEXT NOT NULL,\\n \\"format\\" TEXT NOT NULL,\\n \\"sha256\\" TEXT NOT NULL,\\n \\"attribution\\" TEXT,\\n \\"notices\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"resource_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"catalog_resources","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"selection_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"catalog_selections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"revision":{"type":"TEXT","notNull":true,"unique":false},"format":{"type":"TEXT","notNull":true,"unique":false},"sha256":{"type":"TEXT","notNull":true,"unique":false},"attribution":{"type":"TEXT","notNull":false,"unique":false},"notices":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"catalog_acquisitions_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"catalog_acquisitions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"catalog_acquisitions_resource_id_idx","columns":["resource_id"]},{"name":"catalog_acquisitions_selection_id_idx","columns":["selection_id"]}],"version":"ee9c5add"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CatalogProvider":{"name":"catalogprovider","className":"CatalogProvider","qualifiedName":"@happyvertical/smrt-education:CatalogProvider","collection":"catalogproviders","description":"Globally visible provider registration. Hosts configure adapters and credentials out of band.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog-models.ts","packageName":"@happyvertical/smrt-education","fields":{"providerId":{"type":"text","required":true,"_meta":{"required":true}},"name":{"type":"text","required":true,"_meta":{"required":true}},"active":{"type":"boolean","required":true,"default":true}},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtObject","exportName":"CatalogProvider","collectionExportName":"CatalogProviderCollection","displayLabelField":"name","validationRules":[{"field":"providerId","rule":"required","fieldType":"text"},{"field":"name","rule":"required","fieldType":"text"},{"field":"active","rule":"required","fieldType":"boolean"}],"schema":{"tableName":"catalog_providers","ddl":"CREATE TABLE IF NOT EXISTS \\"catalog_providers\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"provider_id\\" TEXT NOT NULL,\\n \\"name\\" TEXT NOT NULL,\\n \\"active\\" BOOLEAN NOT NULL DEFAULT TRUE\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"provider_id":{"type":"TEXT","notNull":true,"unique":false},"name":{"type":"TEXT","notNull":true,"unique":false},"active":{"type":"BOOLEAN","notNull":true,"unique":false,"default":true}},"indexes":[{"name":"catalog_providers_slug_context_idx","columns":["slug","context"],"unique":true},{"name":"catalog_providers_created_at_idx","columns":["created_at"]}],"version":"f35557a4"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CatalogResource":{"name":"catalogresource","className":"CatalogResource","qualifiedName":"@happyvertical/smrt-education:CatalogResource","collection":"catalogresources","description":"A provider-native record. Rows are intentionally not merged across providers.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog-models.ts","packageName":"@happyvertical/smrt-education","fields":{"providerId":{"type":"text","required":true,"_meta":{"required":true}},"stableId":{"type":"text","required":true,"_meta":{"required":true}},"revision":{"type":"text","required":true,"_meta":{"required":true}},"canonicalUrl":{"type":"text","required":false},"title":{"type":"text","required":true,"_meta":{"required":true}},"suppliedDescription":{"type":"text","required":false},"author":{"type":"text","required":false},"subjects":{"type":"text","required":false},"grades":{"type":"text","required":false},"language":{"type":"text","required":false},"resourceType":{"type":"text","required":false},"parentStableId":{"type":"text","required":false},"availableFormats":{"type":"text","required":false},"licenseEvidence":{"type":"text","required":false},"licenseState":{"type":"text","required":false,"default":"unknown","enum":["verified","unknown","conflicting","restricted"]},"visibility":{"type":"text","required":false,"default":"catalog","enum":["catalog","teacher"]},"lastSeenRunId":{"type":"text","required":false},"withdrawnAt":{"type":"text","required":false}},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtObject","exportName":"CatalogResource","collectionExportName":"CatalogResourceCollection","displayLabelField":"title","validationRules":[{"field":"providerId","rule":"required","fieldType":"text"},{"field":"stableId","rule":"required","fieldType":"text"},{"field":"revision","rule":"required","fieldType":"text"},{"field":"title","rule":"required","fieldType":"text"}],"schema":{"tableName":"catalog_resources","ddl":"CREATE TABLE IF NOT EXISTS \\"catalog_resources\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"provider_id\\" TEXT NOT NULL,\\n \\"stable_id\\" TEXT NOT NULL,\\n \\"revision\\" TEXT NOT NULL,\\n \\"canonical_url\\" TEXT,\\n \\"title\\" TEXT NOT NULL,\\n \\"supplied_description\\" TEXT,\\n \\"author\\" TEXT,\\n \\"subjects\\" TEXT,\\n \\"grades\\" TEXT,\\n \\"language\\" TEXT,\\n \\"resource_type\\" TEXT,\\n \\"parent_stable_id\\" TEXT,\\n \\"available_formats\\" TEXT,\\n \\"license_evidence\\" TEXT,\\n \\"license_state\\" TEXT DEFAULT 'unknown',\\n \\"visibility\\" TEXT DEFAULT 'catalog',\\n \\"last_seen_run_id\\" TEXT,\\n \\"withdrawn_at\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"provider_id":{"type":"TEXT","notNull":true,"unique":false},"stable_id":{"type":"TEXT","notNull":true,"unique":false},"revision":{"type":"TEXT","notNull":true,"unique":false},"canonical_url":{"type":"TEXT","notNull":false,"unique":false},"title":{"type":"TEXT","notNull":true,"unique":false},"supplied_description":{"type":"TEXT","notNull":false,"unique":false},"author":{"type":"TEXT","notNull":false,"unique":false},"subjects":{"type":"TEXT","notNull":false,"unique":false},"grades":{"type":"TEXT","notNull":false,"unique":false},"language":{"type":"TEXT","notNull":false,"unique":false},"resource_type":{"type":"TEXT","notNull":false,"unique":false},"parent_stable_id":{"type":"TEXT","notNull":false,"unique":false},"available_formats":{"type":"TEXT","notNull":false,"unique":false},"license_evidence":{"type":"TEXT","notNull":false,"unique":false},"license_state":{"type":"TEXT","notNull":false,"unique":false,"default":"unknown"},"visibility":{"type":"TEXT","notNull":false,"unique":false,"default":"catalog"},"last_seen_run_id":{"type":"TEXT","notNull":false,"unique":false},"withdrawn_at":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"catalog_resources_slug_context_idx","columns":["slug","context"],"unique":true},{"name":"catalog_resources_created_at_idx","columns":["created_at"]}],"version":"441b7271"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CatalogSyncState":{"name":"catalogsyncstate","className":"CatalogSyncState","qualifiedName":"@happyvertical/smrt-education:CatalogSyncState","collection":"catalogsyncstates","description":"Durable per-provider enumeration state. A failed or partial feed is never complete.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog-models.ts","packageName":"@happyvertical/smrt-education","fields":{"providerId":{"type":"text","required":true,"_meta":{"required":true}},"cursor":{"type":"text","required":false},"runId":{"type":"text","required":false},"complete":{"type":"boolean","required":true,"default":false},"paused":{"type":"boolean","required":true,"default":false},"pauseReason":{"type":"text","required":false},"importedCount":{"type":"integer","required":true,"default":0},"failures":{"type":"text","required":false},"lastSuccessfulAt":{"type":"text","required":false}},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtObject","exportName":"CatalogSyncState","collectionExportName":"CatalogSyncStateCollection","validationRules":[{"field":"providerId","rule":"required","fieldType":"text"},{"field":"complete","rule":"required","fieldType":"boolean"},{"field":"paused","rule":"required","fieldType":"boolean"},{"field":"importedCount","rule":"required","fieldType":"integer"}],"schema":{"tableName":"catalog_sync_states","ddl":"CREATE TABLE IF NOT EXISTS \\"catalog_sync_states\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"provider_id\\" TEXT NOT NULL,\\n \\"cursor\\" TEXT,\\n \\"run_id\\" TEXT,\\n \\"complete\\" BOOLEAN NOT NULL DEFAULT FALSE,\\n \\"paused\\" BOOLEAN NOT NULL DEFAULT FALSE,\\n \\"pause_reason\\" TEXT,\\n \\"imported_count\\" INTEGER NOT NULL DEFAULT 0,\\n \\"failures\\" TEXT,\\n \\"last_successful_at\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"provider_id":{"type":"TEXT","notNull":true,"unique":false},"cursor":{"type":"TEXT","notNull":false,"unique":false},"run_id":{"type":"TEXT","notNull":false,"unique":false},"complete":{"type":"BOOLEAN","notNull":true,"unique":false,"default":false},"paused":{"type":"BOOLEAN","notNull":true,"unique":false,"default":false},"pause_reason":{"type":"TEXT","notNull":false,"unique":false},"imported_count":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"failures":{"type":"TEXT","notNull":false,"unique":false},"last_successful_at":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"catalog_sync_states_slug_context_idx","columns":["slug","context"],"unique":true},{"name":"catalog_sync_states_created_at_idx","columns":["created_at"]}],"version":"52dc3ee1"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CatalogSelection":{"name":"catalogselection","className":"CatalogSelection","qualifiedName":"@happyvertical/smrt-education:CatalogSelection","collection":"catalogselections","description":"Tenant-owned intent to use a metadata record, distinct from acquiring bytes.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog-models.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"resourceId":{"type":"foreignKey","required":true,"related":"CatalogResource","_meta":{"required":true}},"selectedByUserId":{"type":"text","required":true,"_meta":{"required":true}},"selectedRevision":{"type":"text","required":true,"_meta":{"required":true}}},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"CatalogSelection","collectionExportName":"CatalogSelectionCollection","validationRules":[{"field":"resourceId","rule":"required","fieldType":"foreignKey"},{"field":"selectedByUserId","rule":"required","fieldType":"text"},{"field":"selectedRevision","rule":"required","fieldType":"text"}],"schema":{"tableName":"catalog_selections","ddl":"CREATE TABLE IF NOT EXISTS \\"catalog_selections\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"resource_id\\" UUID NOT NULL,\\n \\"selected_by_user_id\\" TEXT NOT NULL,\\n \\"selected_revision\\" TEXT NOT NULL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"resource_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"catalog_resources","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"selected_by_user_id":{"type":"TEXT","notNull":true,"unique":false},"selected_revision":{"type":"TEXT","notNull":true,"unique":false}},"indexes":[{"name":"catalog_selections_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"catalog_selections_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"catalog_selections_resource_id_idx","columns":["resource_id"]}],"version":"c6b02eb9"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CatalogAcquisition":{"name":"catalogacquisition","className":"CatalogAcquisition","qualifiedName":"@happyvertical/smrt-education:CatalogAcquisition","collection":"catalogacquisitions","description":"A pinned, tenant-owned acquisition record. Bytes belong in a host storage service.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog-models.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"resourceId":{"type":"foreignKey","required":true,"related":"CatalogResource","_meta":{"required":true}},"selectionId":{"type":"foreignKey","required":true,"related":"CatalogSelection","_meta":{"required":true}},"revision":{"type":"text","required":true,"_meta":{"required":true}},"format":{"type":"text","required":true,"_meta":{"required":true}},"sha256":{"type":"text","required":true,"_meta":{"required":true}},"attribution":{"type":"text","required":false},"notices":{"type":"text","required":false}},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"CatalogAcquisition","collectionExportName":"CatalogAcquisitionCollection","validationRules":[{"field":"resourceId","rule":"required","fieldType":"foreignKey"},{"field":"selectionId","rule":"required","fieldType":"foreignKey"},{"field":"revision","rule":"required","fieldType":"text"},{"field":"format","rule":"required","fieldType":"text"},{"field":"sha256","rule":"required","fieldType":"text"}],"schema":{"tableName":"catalog_acquisitions","ddl":"CREATE TABLE IF NOT EXISTS \\"catalog_acquisitions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"resource_id\\" UUID NOT NULL,\\n \\"selection_id\\" UUID NOT NULL,\\n \\"revision\\" TEXT NOT NULL,\\n \\"format\\" TEXT NOT NULL,\\n \\"sha256\\" TEXT NOT NULL,\\n \\"attribution\\" TEXT,\\n \\"notices\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"resource_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"catalog_resources","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"selection_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"catalog_selections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"revision":{"type":"TEXT","notNull":true,"unique":false},"format":{"type":"TEXT","notNull":true,"unique":false},"sha256":{"type":"TEXT","notNull":true,"unique":false},"attribution":{"type":"TEXT","notNull":false,"unique":false},"notices":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"catalog_acquisitions_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"catalog_acquisitions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"catalog_acquisitions_resource_id_idx","columns":["resource_id"]},{"name":"catalog_acquisitions_selection_id_idx","columns":["selection_id"]}],"version":"ee9c5add"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Course":{"name":"course","className":"Course","qualifiedName":"@happyvertical/smrt-education:Course","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"title":{"type":"text","required":true,"_meta":{"required":true}},"summary":{"type":"text","required":false},"description":{"type":"text","required":false},"sourceKey":{"type":"text","required":false},"sourceUrl":{"type":"text","required":false},"sourceRef":{"type":"text","required":false},"license":{"type":"text","required":false},"licenseKind":{"type":"text","required":false,"default":"own-work","enum":["own-work","open-license","personal-use-copy","licensed-for-sharing"]},"licenseNote":{"type":"text","required":false},"attestedBy":{"type":"text","required":false},"subject":{"type":"text","required":false},"tags":{"type":"text","required":false},"levelMin":{"type":"integer","required":true,"default":0},"levelMax":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"status":{"type":"text","required":false,"default":"draft","enum":["draft","published","archived"]}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableStrategy":"sti","api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context","_meta_type"]},"extends":"SmrtObject","exportName":"Course","collectionExportName":"CourseCollection","displayLabelField":"title","validationRules":[{"field":"title","rule":"required","fieldType":"text"},{"field":"levelMin","rule":"required","fieldType":"integer"},{"field":"levelMax","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"}],"schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ExternalCourse":{"name":"externalcourse","className":"ExternalCourse","qualifiedName":"@happyvertical/smrt-education:ExternalCourse","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"title":{"type":"text","required":true,"_meta":{"required":true}},"summary":{"type":"text","required":false},"description":{"type":"text","required":false},"sourceKey":{"type":"text","required":false},"sourceUrl":{"type":"text","required":false},"sourceRef":{"type":"text","required":false},"license":{"type":"text","required":false},"licenseKind":{"type":"text","required":false,"default":"own-work","enum":["own-work","open-license","personal-use-copy","licensed-for-sharing"]},"licenseNote":{"type":"text","required":false},"attestedBy":{"type":"text","required":false},"subject":{"type":"text","required":false},"tags":{"type":"text","required":false},"levelMin":{"type":"integer","required":true,"default":0},"levelMax":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"status":{"type":"text","required":false,"default":"draft","enum":["draft","published","archived"]}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"courses","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Course","exportName":"ExternalCourse","collectionExportName":"ExternalCourseCollection","displayLabelField":"title","validationRules":[{"field":"title","rule":"required","fieldType":"text"},{"field":"levelMin","rule":"required","fieldType":"integer"},{"field":"levelMax","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"}],"schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:AuthoredCourse":{"name":"authoredcourse","className":"AuthoredCourse","qualifiedName":"@happyvertical/smrt-education:AuthoredCourse","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"title":{"type":"text","required":true,"_meta":{"required":true}},"summary":{"type":"text","required":false},"description":{"type":"text","required":false},"sourceKey":{"type":"text","required":false},"sourceUrl":{"type":"text","required":false},"sourceRef":{"type":"text","required":false},"license":{"type":"text","required":false},"licenseKind":{"type":"text","required":false,"default":"own-work","enum":["own-work","open-license","personal-use-copy","licensed-for-sharing"]},"licenseNote":{"type":"text","required":false},"attestedBy":{"type":"text","required":false},"subject":{"type":"text","required":false},"tags":{"type":"text","required":false},"levelMin":{"type":"integer","required":true,"default":0},"levelMax":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"status":{"type":"text","required":false,"default":"draft","enum":["draft","published","archived"]}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"courses","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Course","exportName":"AuthoredCourse","collectionExportName":"AuthoredCourseCollection","displayLabelField":"title","validationRules":[{"field":"title","rule":"required","fieldType":"text"},{"field":"levelMin","rule":"required","fieldType":"integer"},{"field":"levelMax","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"}],"schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CourseSection":{"name":"coursesection","className":"CourseSection","qualifiedName":"@happyvertical/smrt-education:CourseSection","collection":"coursesections","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"courseId":{"type":"foreignKey","required":true,"related":"Course","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"sortOrder":{"type":"integer","required":true,"default":0}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"CourseSection","collectionExportName":"CourseSectionCollection","displayLabelField":"title","validationRules":[{"field":"courseId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"}],"schema":{"tableName":"course_sections","ddl":"CREATE TABLE IF NOT EXISTS \\"course_sections\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"course_id\\" UUID NOT NULL,\\n \\"title\\" TEXT NOT NULL,\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"course_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"courses","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":true,"unique":false},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"course_sections_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"course_sections_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"course_sections_course_id_idx","columns":["course_id"]}],"version":"e20480d7"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Lesson":{"name":"lesson","className":"Lesson","qualifiedName":"@happyvertical/smrt-education:Lesson","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"sectionId":{"type":"foreignKey","required":true,"related":"CourseSection","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"body":{"type":"text","required":false},"sortOrder":{"type":"integer","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"completionRule":{"type":"text","required":false,"default":"attest","enum":["attest","submit","score","external"]},"externalRef":{"type":"text","required":false},"externalUrl":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableStrategy":"sti","api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context","_meta_type"]},"extends":"SmrtObject","exportName":"Lesson","collectionExportName":"LessonCollection","displayLabelField":"title","validationRules":[{"field":"sectionId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"}],"schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"092df043"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Reading":{"name":"reading","className":"Reading","qualifiedName":"@happyvertical/smrt-education:Reading","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"sectionId":{"type":"foreignKey","required":true,"related":"CourseSection","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"body":{"type":"text","required":false},"sortOrder":{"type":"integer","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"completionRule":{"type":"text","required":false,"default":"attest","enum":["attest","submit","score","external"]},"externalRef":{"type":"text","required":false},"externalUrl":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Lesson","exportName":"Reading","collectionExportName":"ReadingCollection","displayLabelField":"title","validationRules":[{"field":"sectionId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"}],"schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"9d8b6fcd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Video":{"name":"video","className":"Video","qualifiedName":"@happyvertical/smrt-education:Video","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"sectionId":{"type":"foreignKey","required":true,"related":"CourseSection","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"body":{"type":"text","required":false},"sortOrder":{"type":"integer","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"completionRule":{"type":"text","required":false,"default":"attest","enum":["attest","submit","score","external"]},"externalRef":{"type":"text","required":false},"externalUrl":{"type":"text","required":false},"videoUrl":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Lesson","exportName":"Video","collectionExportName":"VideoCollection","displayLabelField":"title","validationRules":[{"field":"sectionId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"}],"schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"9e0ef984"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Worksheet":{"name":"worksheet","className":"Worksheet","qualifiedName":"@happyvertical/smrt-education:Worksheet","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"sectionId":{"type":"foreignKey","required":true,"related":"CourseSection","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"body":{"type":"text","required":false},"sortOrder":{"type":"integer","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"completionRule":{"type":"text","required":false,"default":"submit","enum":["attest","submit","score","external"]},"externalRef":{"type":"text","required":false},"externalUrl":{"type":"text","required":false},"printableUrl":{"type":"text","required":false},"answerUrl":{"type":"text","required":false},"pages":{"type":"integer","required":true,"default":0}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Lesson","exportName":"Worksheet","collectionExportName":"WorksheetCollection","displayLabelField":"title","validationRules":[{"field":"sectionId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"},{"field":"pages","rule":"required","fieldType":"integer"}],"schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"10976938"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Exercise":{"name":"exercise","className":"Exercise","qualifiedName":"@happyvertical/smrt-education:Exercise","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/catalog.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true}}},"sectionId":{"type":"foreignKey","required":true,"related":"CourseSection","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"body":{"type":"text","required":false},"sortOrder":{"type":"integer","required":true,"default":0},"estimatedMinutes":{"type":"integer","required":true,"default":0},"difficulty":{"type":"decimal","required":true,"default":0},"modality":{"type":"text","required":false,"default":"online","enum":["online","printable","kit","video","game","mixed"]},"completionRule":{"type":"text","required":false,"default":"attest","enum":["attest","submit","score","external"]},"externalRef":{"type":"text","required":false},"externalUrl":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Lesson","exportName":"Exercise","collectionExportName":"ExerciseCollection","displayLabelField":"title","validationRules":[{"field":"sectionId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"},{"field":"estimatedMinutes","rule":"required","fieldType":"integer"},{"field":"difficulty","rule":"required","fieldType":"decimal"}],"schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"145eadbe"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CourseCollection":{"name":"coursecollection","className":"CourseCollection","qualifiedName":"@happyvertical/smrt-education:CourseCollection","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Course","exportName":"CourseCollection","collectionExportName":"CourseCollectionCollection","schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ExternalCourseCollection":{"name":"externalcoursecollection","className":"ExternalCourseCollection","qualifiedName":"@happyvertical/smrt-education:ExternalCourseCollection","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"courses","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"ExternalCourse","exportName":"ExternalCourseCollection","collectionExportName":"ExternalCourseCollectionCollection","schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:AuthoredCourseCollection":{"name":"authoredcoursecollection","className":"AuthoredCourseCollection","qualifiedName":"@happyvertical/smrt-education:AuthoredCourseCollection","collection":"courses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"courses","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"AuthoredCourse","exportName":"AuthoredCourseCollection","collectionExportName":"AuthoredCourseCollectionCollection","schema":{"tableName":"courses","ddl":"CREATE TABLE IF NOT EXISTS \\"courses\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_key\\" TEXT,\\n \\"source_url\\" TEXT,\\n \\"source_ref\\" TEXT,\\n \\"license\\" TEXT,\\n \\"license_kind\\" TEXT DEFAULT 'own-work',\\n \\"license_note\\" TEXT,\\n \\"attested_by\\" TEXT,\\n \\"subject\\" TEXT,\\n \\"tags\\" TEXT,\\n \\"level_min\\" INTEGER DEFAULT 0,\\n \\"level_max\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"status\\" TEXT DEFAULT 'draft'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"title":{"type":"TEXT","notNull":false},"summary":{"type":"TEXT","notNull":false},"description":{"type":"TEXT","notNull":false},"source_key":{"type":"TEXT","notNull":false},"source_url":{"type":"TEXT","notNull":false},"source_ref":{"type":"TEXT","notNull":false},"license":{"type":"TEXT","notNull":false},"license_kind":{"type":"TEXT","notNull":false,"default":"own-work"},"license_note":{"type":"TEXT","notNull":false},"attested_by":{"type":"TEXT","notNull":false},"subject":{"type":"TEXT","notNull":false},"tags":{"type":"TEXT","notNull":false},"level_min":{"type":"INTEGER","notNull":false,"default":0},"level_max":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"status":{"type":"TEXT","notNull":false,"default":"draft"}},"indexes":[{"name":"courses_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"courses_meta_type_idx","columns":["_meta_type"]},{"name":"courses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"88cc23ab"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CourseSectionCollection":{"name":"coursesectioncollection","className":"CourseSectionCollection","qualifiedName":"@happyvertical/smrt-education:CourseSectionCollection","collection":"coursesections","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"CourseSection","exportName":"CourseSectionCollection","collectionExportName":"CourseSectionCollectionCollection","schema":{"tableName":"course_sections","ddl":"CREATE TABLE IF NOT EXISTS \\"course_sections\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"course_id\\" UUID NOT NULL,\\n \\"title\\" TEXT NOT NULL,\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"course_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"courses","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":true,"unique":false},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"course_sections_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"course_sections_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"course_sections_course_id_idx","columns":["course_id"]}],"version":"e20480d7"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LessonCollection":{"name":"lessoncollection","className":"LessonCollection","qualifiedName":"@happyvertical/smrt-education:LessonCollection","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Lesson","exportName":"LessonCollection","collectionExportName":"LessonCollectionCollection","schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"092df043"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ReadingCollection":{"name":"readingcollection","className":"ReadingCollection","qualifiedName":"@happyvertical/smrt-education:ReadingCollection","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Reading","exportName":"ReadingCollection","collectionExportName":"ReadingCollectionCollection","schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"9d8b6fcd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:VideoCollection":{"name":"videocollection","className":"VideoCollection","qualifiedName":"@happyvertical/smrt-education:VideoCollection","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Video","exportName":"VideoCollection","collectionExportName":"VideoCollectionCollection","schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"9e0ef984"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:WorksheetCollection":{"name":"worksheetcollection","className":"WorksheetCollection","qualifiedName":"@happyvertical/smrt-education:WorksheetCollection","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Worksheet","exportName":"WorksheetCollection","collectionExportName":"WorksheetCollectionCollection","schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"10976938"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ExerciseCollection":{"name":"exercisecollection","className":"ExerciseCollection","qualifiedName":"@happyvertical/smrt-education:ExerciseCollection","collection":"lessons","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"lessons","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Exercise","exportName":"ExerciseCollection","collectionExportName":"ExerciseCollectionCollection","schema":{"tableName":"lessons","ddl":"CREATE TABLE IF NOT EXISTS \\"lessons\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"section_id\\" UUID,\\n \\"title\\" TEXT,\\n \\"body\\" TEXT,\\n \\"sort_order\\" INTEGER DEFAULT 0,\\n \\"estimated_minutes\\" INTEGER DEFAULT 0,\\n \\"difficulty\\" REAL DEFAULT 0,\\n \\"modality\\" TEXT DEFAULT 'online',\\n \\"completion_rule\\" TEXT DEFAULT 'attest',\\n \\"external_ref\\" TEXT,\\n \\"external_url\\" TEXT,\\n \\"video_url\\" TEXT,\\n \\"printable_url\\" TEXT,\\n \\"answer_url\\" TEXT,\\n \\"pages\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"section_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"course_sections","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":false},"body":{"type":"TEXT","notNull":false},"sort_order":{"type":"INTEGER","notNull":false,"default":0},"estimated_minutes":{"type":"INTEGER","notNull":false,"default":0},"difficulty":{"type":"REAL","notNull":false,"default":0},"modality":{"type":"TEXT","notNull":false,"default":"online"},"completion_rule":{"type":"TEXT","notNull":false,"default":"attest"},"external_ref":{"type":"TEXT","notNull":false},"external_url":{"type":"TEXT","notNull":false},"video_url":{"type":"TEXT","notNull":false},"printable_url":{"type":"TEXT","notNull":false},"answer_url":{"type":"TEXT","notNull":false},"pages":{"type":"INTEGER","notNull":false,"default":0}},"indexes":[{"name":"lessons_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"lessons_meta_type_idx","columns":["_meta_type"]},{"name":"lessons_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lessons_section_id_idx","columns":["section_id"]}],"version":"145eadbe"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LessonAssetCollection":{"name":"lessonassetcollection","className":"LessonAssetCollection","qualifiedName":"@happyvertical/smrt-education:LessonAssetCollection","collection":"lessonassets","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{"byRight":{"name":"byRight","async":true,"parameters":[{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"byLeft":{"name":"byLeft","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"attach":{"name":"attach","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<TItem>","isStatic":false,"isPublic":true},"detach":{"name":"detach","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"setLinks":{"name":"setLinks","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightIds","type":"string[]","optional":false},{"name":"opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lesson_assets","api":false,"mcp":false,"cli":false},"extends":"SmrtJunction","extendsTypeArg":"LessonAsset","exportName":"LessonAssetCollection","collectionExportName":"LessonAssetCollectionCollection","schema":{"tableName":"lesson_assets","ddl":"CREATE TABLE IF NOT EXISTS \\"lesson_assets\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"lesson_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"relationship\\" TEXT NOT NULL DEFAULT 'attachment',\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"relationship":{"type":"TEXT","notNull":true,"unique":false,"default":"attachment"},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"lesson_assets_tenant_id_lesson_id_idx","columns":["tenant_id","lesson_id","asset_id","relationship"],"unique":true,"nullsNotDistinct":true},{"name":"lesson_assets_slug_context_idx","columns":["slug","context"]},{"name":"lesson_assets_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lesson_assets_lesson_id_idx","columns":["lesson_id"]},{"name":"lesson_assets_asset_id_idx","columns":["asset_id"]}],"version":"d96385c6"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LearnerCollection":{"name":"learnercollection","className":"LearnerCollection","qualifiedName":"@happyvertical/smrt-education:LearnerCollection","collection":"learners","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Learner","exportName":"LearnerCollection","collectionExportName":"LearnerCollectionCollection","schema":{"tableName":"learners","ddl":"CREATE TABLE IF NOT EXISTS \\"learners\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"user_id\\" UUID NOT NULL,\\n \\"profile_id\\" UUID NOT NULL,\\n \\"display_name\\" TEXT,\\n \\"birth_date\\" TIMESTAMP,\\n \\"level\\" INTEGER NOT NULL DEFAULT 0,\\n \\"settings\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"user_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"display_name":{"type":"TEXT","notNull":false,"unique":false},"birth_date":{"type":"TIMESTAMP","notNull":false,"unique":false},"level":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"settings":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"learners_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"learners_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"learners_user_id_idx","columns":["user_id"]},{"name":"learners_profile_id_idx","columns":["profile_id"]}],"version":"ca9907b5"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CurriculumCollection":{"name":"curriculumcollection","className":"CurriculumCollection","qualifiedName":"@happyvertical/smrt-education:CurriculumCollection","collection":"curriculums","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"curricula","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Curriculum","exportName":"CurriculumCollection","collectionExportName":"CurriculumCollectionCollection","schema":{"tableName":"curricula","ddl":"CREATE TABLE IF NOT EXISTS \\"curricula\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"title\\" TEXT NOT NULL,\\n \\"starts_on\\" TIMESTAMP,\\n \\"ends_on\\" TIMESTAMP,\\n \\"status\\" TEXT DEFAULT 'planned'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":true,"unique":false},"starts_on":{"type":"TIMESTAMP","notNull":false,"unique":false},"ends_on":{"type":"TIMESTAMP","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"planned"}},"indexes":[{"name":"curricula_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"curricula_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"curricula_learner_id_idx","columns":["learner_id"]}],"version":"b7e90cbd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:EnrollmentCollection":{"name":"enrollmentcollection","className":"EnrollmentCollection","qualifiedName":"@happyvertical/smrt-education:EnrollmentCollection","collection":"enrollments","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Enrollment","exportName":"EnrollmentCollection","collectionExportName":"EnrollmentCollectionCollection","schema":{"tableName":"enrollments","ddl":"CREATE TABLE IF NOT EXISTS \\"enrollments\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"curriculum_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"course_id\\" UUID NOT NULL,\\n \\"status\\" TEXT DEFAULT 'planned',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"target_minutes_per_week\\" INTEGER NOT NULL DEFAULT 0,\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"curriculum_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"curricula","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"course_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"courses","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"planned"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"target_minutes_per_week":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"enrollments_tenant_id_curriculum_id_idx","columns":["tenant_id","curriculum_id","course_id"],"unique":true},{"name":"enrollments_slug_context_idx","columns":["slug","context"]},{"name":"enrollments_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"enrollments_curriculum_id_idx","columns":["curriculum_id"]},{"name":"enrollments_learner_id_idx","columns":["learner_id"]},{"name":"enrollments_course_id_idx","columns":["course_id"]}],"version":"2a34b5ee"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LessonProgressCollection":{"name":"lessonprogresscollection","className":"LessonProgressCollection","qualifiedName":"@happyvertical/smrt-education:LessonProgressCollection","collection":"lessonprogresses","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"lesson_progress","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"LessonProgress","exportName":"LessonProgressCollection","collectionExportName":"LessonProgressCollectionCollection","schema":{"tableName":"lesson_progress","ddl":"CREATE TABLE IF NOT EXISTS \\"lesson_progress\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"enrollment_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"lesson_id\\" UUID NOT NULL,\\n \\"status\\" TEXT DEFAULT 'not_started',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"time_spent_seconds\\" INTEGER NOT NULL DEFAULT 0,\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"best_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"enrollment_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"enrollments","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"not_started"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"time_spent_seconds":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"best_score":{"type":"REAL","notNull":false,"unique":false}},"indexes":[{"name":"lesson_progress_tenant_id_enrollment_id_idx","columns":["tenant_id","enrollment_id","lesson_id"],"unique":true},{"name":"lesson_progress_slug_context_idx","columns":["slug","context"]},{"name":"lesson_progress_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lesson_progress_enrollment_id_idx","columns":["enrollment_id"]},{"name":"lesson_progress_learner_id_idx","columns":["learner_id"]},{"name":"lesson_progress_lesson_id_idx","columns":["lesson_id"]}],"version":"36704fbd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:SubmissionCollection":{"name":"submissioncollection","className":"SubmissionCollection","qualifiedName":"@happyvertical/smrt-education:SubmissionCollection","collection":"submissions","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Submission","exportName":"SubmissionCollection","collectionExportName":"SubmissionCollectionCollection","schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"kind\\" TEXT DEFAULT 'completed_work',\\n \\"previous_submission_id\\" UUID,\\n \\"intake_item_id\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"kind":{"type":"TEXT","notNull":false,"default":"completed_work"},"previous_submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"intake_item_id":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]},{"name":"submissions_previous_submission_id_idx","columns":["previous_submission_id"]}],"version":"27a21ab4"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:SubmissionIntakeBindingCollection":{"name":"submissionintakebindingcollection","className":"SubmissionIntakeBindingCollection","qualifiedName":"@happyvertical/smrt-education:SubmissionIntakeBindingCollection","collection":"submissionintakebindings","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"submission_intake_bindings","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"SubmissionIntakeBinding","exportName":"SubmissionIntakeBindingCollection","collectionExportName":"SubmissionIntakeBindingCollectionCollection","schema":{"tableName":"submission_intake_bindings","ddl":"CREATE TABLE IF NOT EXISTS \\"submission_intake_bindings\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"intake_item_id\\" TEXT NOT NULL,\\n \\"submission_id\\" UUID NOT NULL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"intake_item_id":{"type":"TEXT","notNull":true,"unique":false},"submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}}},"indexes":[{"name":"submission_intake_bindings_tenant_id_intake_item_id_idx","columns":["tenant_id","intake_item_id"],"unique":true},{"name":"submission_intake_bindings_slug_context_idx","columns":["slug","context"]},{"name":"submission_intake_bindings_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submission_intake_bindings_submission_id_idx","columns":["submission_id"]}],"version":"7102c909"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ScannedSubmissionCollection":{"name":"scannedsubmissioncollection","className":"ScannedSubmissionCollection","qualifiedName":"@happyvertical/smrt-education:ScannedSubmissionCollection","collection":"submissions","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"ScannedSubmission","exportName":"ScannedSubmissionCollection","collectionExportName":"ScannedSubmissionCollectionCollection","schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"kind\\" TEXT DEFAULT 'completed_work',\\n \\"previous_submission_id\\" UUID,\\n \\"intake_item_id\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"kind":{"type":"TEXT","notNull":false,"default":"completed_work"},"previous_submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"intake_item_id":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]},{"name":"submissions_previous_submission_id_idx","columns":["previous_submission_id"]}],"version":"7c394510"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ExternalCompletionCollection":{"name":"externalcompletioncollection","className":"ExternalCompletionCollection","qualifiedName":"@happyvertical/smrt-education:ExternalCompletionCollection","collection":"submissions","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"ExternalCompletion","exportName":"ExternalCompletionCollection","collectionExportName":"ExternalCompletionCollectionCollection","schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"kind\\" TEXT DEFAULT 'completed_work',\\n \\"previous_submission_id\\" UUID,\\n \\"intake_item_id\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"kind":{"type":"TEXT","notNull":false,"default":"completed_work"},"previous_submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"intake_item_id":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]},{"name":"submissions_previous_submission_id_idx","columns":["previous_submission_id"]}],"version":"eff3715d"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:AttestationCollection":{"name":"attestationcollection","className":"AttestationCollection","qualifiedName":"@happyvertical/smrt-education:AttestationCollection","collection":"submissions","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Attestation","exportName":"AttestationCollection","collectionExportName":"AttestationCollectionCollection","schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"kind\\" TEXT DEFAULT 'completed_work',\\n \\"previous_submission_id\\" UUID,\\n \\"intake_item_id\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"kind":{"type":"TEXT","notNull":false,"default":"completed_work"},"previous_submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"intake_item_id":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]},{"name":"submissions_previous_submission_id_idx","columns":["previous_submission_id"]}],"version":"4f3230a3"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:GradeCollection":{"name":"gradecollection","className":"GradeCollection","qualifiedName":"@happyvertical/smrt-education:GradeCollection","collection":"grades","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"Grade","exportName":"GradeCollection","collectionExportName":"GradeCollectionCollection","schema":{"tableName":"grades","ddl":"CREATE TABLE IF NOT EXISTS \\"grades\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"submission_id\\" UUID NOT NULL,\\n \\"grader_kind\\" TEXT NOT NULL DEFAULT 'auto',\\n \\"grader_profile_id\\" UUID,\\n \\"score\\" REAL,\\n \\"points_earned\\" INTEGER,\\n \\"points_possible\\" INTEGER,\\n \\"rubric_results\\" TEXT,\\n \\"feedback\\" TEXT,\\n \\"graded_at\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"grader_kind":{"type":"TEXT","notNull":true,"unique":false,"default":"auto"},"grader_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"score":{"type":"REAL","notNull":false,"unique":false},"points_earned":{"type":"INTEGER","notNull":false,"unique":false},"points_possible":{"type":"INTEGER","notNull":false,"unique":false},"rubric_results":{"type":"TEXT","notNull":false,"unique":false},"feedback":{"type":"TEXT","notNull":false,"unique":false},"graded_at":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"grades_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"grades_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"grades_submission_id_idx","columns":["submission_id"]},{"name":"grades_grader_profile_id_idx","columns":["grader_profile_id"]}],"version":"7d629fec"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:SubmissionAssetCollection":{"name":"submissionassetcollection","className":"SubmissionAssetCollection","qualifiedName":"@happyvertical/smrt-education:SubmissionAssetCollection","collection":"submissionassets","filePath":"/home/runner/work/smrt/smrt/packages/education/src/collections.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{"byRight":{"name":"byRight","async":true,"parameters":[{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"byLeft":{"name":"byLeft","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"attach":{"name":"attach","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<TItem>","isStatic":false,"isPublic":true},"detach":{"name":"detach","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"setLinks":{"name":"setLinks","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightIds","type":"string[]","optional":false},{"name":"opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submission_assets","api":false,"mcp":false,"cli":false},"extends":"SmrtJunction","extendsTypeArg":"SubmissionAsset","exportName":"SubmissionAssetCollection","collectionExportName":"SubmissionAssetCollectionCollection","schema":{"tableName":"submission_assets","ddl":"CREATE TABLE IF NOT EXISTS \\"submission_assets\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"submission_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"relationship\\" TEXT NOT NULL DEFAULT 'page',\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"relationship":{"type":"TEXT","notNull":true,"unique":false,"default":"page"},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"submission_assets_tenant_id_submission_id_idx","columns":["tenant_id","submission_id","asset_id","relationship"],"unique":true},{"name":"submission_assets_slug_context_idx","columns":["slug","context"]},{"name":"submission_assets_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submission_assets_submission_id_idx","columns":["submission_id"]},{"name":"submission_assets_asset_id_idx","columns":["asset_id"]}],"version":"d9b61c8a"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CourseSource":{"name":"coursesource","className":"CourseSource","qualifiedName":"@happyvertical/smrt-education:CourseSource","collection":"coursesources","description":"Host-managed source policy; deliberately has no generated CRUD surface.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/course-source.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"key":{"type":"text","required":true,"_meta":{"required":true}},"name":{"type":"text","required":true,"_meta":{"required":true}},"kind":{"type":"text","required":false,"default":"adapter","enum":["manual","url","adapter"]},"adapterKey":{"type":"text","required":false},"config":{"type":"text","required":false},"sharing":{"type":"text","required":false,"default":"private","enum":["private","group","global"]}},"methods":{"getConfig":{"name":"getConfig","async":false,"parameters":[],"returnType":"CourseSourceConfig","isStatic":false,"isPublic":true},"setConfig":{"name":"setConfig","async":false,"parameters":[{"name":"value","type":"CourseSourceConfig","optional":false}],"returnType":"void","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"cli":false,"mcp":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"CourseSource","collectionExportName":"CourseSourceCollection","displayLabelField":"name","validationRules":[{"field":"key","rule":"required","fieldType":"text"},{"field":"name","rule":"required","fieldType":"text"}],"schema":{"tableName":"course_sources","ddl":"CREATE TABLE IF NOT EXISTS \\"course_sources\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"key\\" TEXT NOT NULL,\\n \\"name\\" TEXT NOT NULL,\\n \\"kind\\" TEXT DEFAULT 'adapter',\\n \\"adapter_key\\" TEXT,\\n \\"config\\" TEXT,\\n \\"sharing\\" TEXT DEFAULT 'private'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"key":{"type":"TEXT","notNull":true,"unique":false},"name":{"type":"TEXT","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"adapter"},"adapter_key":{"type":"TEXT","notNull":false,"unique":false},"config":{"type":"TEXT","notNull":false,"unique":false},"sharing":{"type":"TEXT","notNull":false,"unique":false,"default":"private"}},"indexes":[{"name":"course_sources_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"course_sources_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"98177eb3"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:CourseSourceCollection":{"name":"coursesourcecollection","className":"CourseSourceCollection","qualifiedName":"@happyvertical/smrt-education:CourseSourceCollection","collection":"coursesources","filePath":"/home/runner/work/smrt/smrt/packages/education/src/course-source.ts","packageName":"@happyvertical/smrt-education","fields":{},"methods":{},"decoratorConfig":{"api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"CourseSource","exportName":"CourseSourceCollection","collectionExportName":"CourseSourceCollectionCollection","schema":{"tableName":"course_sources","ddl":"CREATE TABLE IF NOT EXISTS \\"course_sources\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"key\\" TEXT NOT NULL,\\n \\"name\\" TEXT NOT NULL,\\n \\"kind\\" TEXT DEFAULT 'adapter',\\n \\"adapter_key\\" TEXT,\\n \\"config\\" TEXT,\\n \\"sharing\\" TEXT DEFAULT 'private'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"key":{"type":"TEXT","notNull":true,"unique":false},"name":{"type":"TEXT","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"adapter"},"adapter_key":{"type":"TEXT","notNull":false,"unique":false},"config":{"type":"TEXT","notNull":false,"unique":false},"sharing":{"type":"TEXT","notNull":false,"unique":false,"default":"private"}},"indexes":[{"name":"course_sources_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"course_sources_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"98177eb3"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Learner":{"name":"learner","className":"Learner","qualifiedName":"@happyvertical/smrt-education:Learner","collection":"learners","description":"A learner is a real user/profile identity with active family membership.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/learners.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"userId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-users:User"},"profileId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-profiles:Profile"},"displayName":{"type":"text","required":false},"birthDate":{"type":"datetime","required":false},"level":{"type":"integer","required":true,"default":0},"settings":{"type":"text","required":false}},"methods":{"getSettings":{"name":"getSettings","async":false,"parameters":[],"returnType":"Record<string, unknown>","isStatic":false,"isPublic":true},"setSettings":{"name":"setSettings","async":false,"parameters":[{"name":"value","type":"Record<string, unknown>","optional":false}],"returnType":"void","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"Learner","collectionExportName":"LearnerCollection","validationRules":[{"field":"userId","rule":"required","fieldType":"crossPackageRef"},{"field":"profileId","rule":"required","fieldType":"crossPackageRef"},{"field":"level","rule":"required","fieldType":"integer"}],"schema":{"tableName":"learners","ddl":"CREATE TABLE IF NOT EXISTS \\"learners\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"user_id\\" UUID NOT NULL,\\n \\"profile_id\\" UUID NOT NULL,\\n \\"display_name\\" TEXT,\\n \\"birth_date\\" TIMESTAMP,\\n \\"level\\" INTEGER NOT NULL DEFAULT 0,\\n \\"settings\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"user_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"display_name":{"type":"TEXT","notNull":false,"unique":false},"birth_date":{"type":"TIMESTAMP","notNull":false,"unique":false},"level":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"settings":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"learners_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"learners_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"learners_user_id_idx","columns":["user_id"]},{"name":"learners_profile_id_idx","columns":["profile_id"]}],"version":"ca9907b5"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Curriculum":{"name":"curriculum","className":"Curriculum","qualifiedName":"@happyvertical/smrt-education:Curriculum","collection":"curriculums","description":"A distinct plan retains its own enrollment history.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/learners.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"title":{"type":"text","required":true,"_meta":{"required":true}},"startsOn":{"type":"datetime","required":false},"endsOn":{"type":"datetime","required":false},"status":{"type":"text","required":false,"default":"planned","enum":["planned","active","completed","archived"]}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tableName":"curricula","tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"Curriculum","collectionExportName":"CurriculumCollection","displayLabelField":"title","validationRules":[{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"title","rule":"required","fieldType":"text"}],"schema":{"tableName":"curricula","ddl":"CREATE TABLE IF NOT EXISTS \\"curricula\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"title\\" TEXT NOT NULL,\\n \\"starts_on\\" TIMESTAMP,\\n \\"ends_on\\" TIMESTAMP,\\n \\"status\\" TEXT DEFAULT 'planned'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"title":{"type":"TEXT","notNull":true,"unique":false},"starts_on":{"type":"TIMESTAMP","notNull":false,"unique":false},"ends_on":{"type":"TIMESTAMP","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"planned"}},"indexes":[{"name":"curricula_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"curricula_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"curricula_learner_id_idx","columns":["learner_id"]}],"version":"b7e90cbd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Enrollment":{"name":"enrollment","className":"Enrollment","qualifiedName":"@happyvertical/smrt-education:Enrollment","collection":"enrollments","description":"One course in one curriculum; a new curriculum means a new enrollment.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/learners.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"curriculumId":{"type":"foreignKey","required":true,"related":"Curriculum","_meta":{"required":true}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"courseId":{"type":"foreignKey","required":true,"related":"Course","_meta":{"required":true}},"status":{"type":"text","required":false,"default":"planned","enum":["planned","active","paused","completed","dropped"]},"startedAt":{"type":"datetime","required":false},"completedAt":{"type":"datetime","required":false},"targetMinutesPerWeek":{"type":"integer","required":true,"default":0},"sortOrder":{"type":"integer","required":true,"default":0}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"conflictColumns":["tenant_id","curriculum_id","course_id"],"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"Enrollment","collectionExportName":"EnrollmentCollection","validationRules":[{"field":"curriculumId","rule":"required","fieldType":"foreignKey"},{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"courseId","rule":"required","fieldType":"foreignKey"},{"field":"targetMinutesPerWeek","rule":"required","fieldType":"integer"},{"field":"sortOrder","rule":"required","fieldType":"integer"}],"schema":{"tableName":"enrollments","ddl":"CREATE TABLE IF NOT EXISTS \\"enrollments\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"curriculum_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"course_id\\" UUID NOT NULL,\\n \\"status\\" TEXT DEFAULT 'planned',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"target_minutes_per_week\\" INTEGER NOT NULL DEFAULT 0,\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"curriculum_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"curricula","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"course_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"courses","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"planned"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"target_minutes_per_week":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"enrollments_tenant_id_curriculum_id_idx","columns":["tenant_id","curriculum_id","course_id"],"unique":true},{"name":"enrollments_slug_context_idx","columns":["slug","context"]},{"name":"enrollments_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"enrollments_curriculum_id_idx","columns":["curriculum_id"]},{"name":"enrollments_learner_id_idx","columns":["learner_id"]},{"name":"enrollments_course_id_idx","columns":["course_id"]}],"version":"2a34b5ee"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LessonProgress":{"name":"lessonprogress","className":"LessonProgress","qualifiedName":"@happyvertical/smrt-education:LessonProgress","collection":"lessonprogresses","description":"Progress identity is enrollment + lesson, never learner + lesson.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/learners.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"enrollmentId":{"type":"foreignKey","required":true,"related":"Enrollment","_meta":{"required":true}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"status":{"type":"text","required":false,"default":"not_started","enum":["not_started","in_progress","submitted","needs_review","complete","skipped"]},"startedAt":{"type":"datetime","required":false},"completedAt":{"type":"datetime","required":false},"timeSpentSeconds":{"type":"integer","required":true,"default":0},"attempts":{"type":"integer","required":true,"default":0},"bestScore":{"type":"decimal","required":false,"_meta":{"nullable":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tableName":"lesson_progress","conflictColumns":["tenant_id","enrollment_id","lesson_id"],"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"LessonProgress","collectionExportName":"LessonProgressCollection","validationRules":[{"field":"enrollmentId","rule":"required","fieldType":"foreignKey"},{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"timeSpentSeconds","rule":"required","fieldType":"integer"},{"field":"attempts","rule":"required","fieldType":"integer"}],"schema":{"tableName":"lesson_progress","ddl":"CREATE TABLE IF NOT EXISTS \\"lesson_progress\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"enrollment_id\\" UUID NOT NULL,\\n \\"learner_id\\" UUID NOT NULL,\\n \\"lesson_id\\" UUID NOT NULL,\\n \\"status\\" TEXT DEFAULT 'not_started',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"time_spent_seconds\\" INTEGER NOT NULL DEFAULT 0,\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"best_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"enrollment_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"enrollments","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"not_started"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"time_spent_seconds":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"best_score":{"type":"REAL","notNull":false,"unique":false}},"indexes":[{"name":"lesson_progress_tenant_id_enrollment_id_idx","columns":["tenant_id","enrollment_id","lesson_id"],"unique":true},{"name":"lesson_progress_slug_context_idx","columns":["slug","context"]},{"name":"lesson_progress_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lesson_progress_enrollment_id_idx","columns":["enrollment_id"]},{"name":"lesson_progress_learner_id_idx","columns":["learner_id"]},{"name":"lesson_progress_lesson_id_idx","columns":["lesson_id"]}],"version":"36704fbd"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:LessonAsset":{"name":"lessonasset","className":"LessonAsset","qualifiedName":"@happyvertical/smrt-education:LessonAsset","collection":"lessonassets","filePath":"/home/runner/work/smrt/smrt/packages/education/src/lesson-assets.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"assetId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-assets:Asset"},"relationship":{"type":"text","required":true,"default":"attachment","_meta":{"required":true},"enum":["printable","answer-key","video","attachment"]},"sortOrder":{"type":"integer","required":true,"default":0}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"lesson_assets","conflictColumns":["tenant_id","lesson_id","asset_id","relationship"],"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"LessonAsset","collectionExportName":"LessonAssetCollection","validationRules":[{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"assetId","rule":"required","fieldType":"crossPackageRef"},{"field":"relationship","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"}],"schema":{"tableName":"lesson_assets","ddl":"CREATE TABLE IF NOT EXISTS \\"lesson_assets\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"lesson_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"relationship\\" TEXT NOT NULL DEFAULT 'attachment',\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"relationship":{"type":"TEXT","notNull":true,"unique":false,"default":"attachment"},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"lesson_assets_tenant_id_lesson_id_idx","columns":["tenant_id","lesson_id","asset_id","relationship"],"unique":true,"nullsNotDistinct":true},{"name":"lesson_assets_slug_context_idx","columns":["slug","context"]},{"name":"lesson_assets_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"lesson_assets_lesson_id_idx","columns":["lesson_id"]},{"name":"lesson_assets_asset_id_idx","columns":["asset_id"]}],"version":"d96385c6"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Submission":{"name":"submission","className":"Submission","qualifiedName":"@happyvertical/smrt-education:Submission","collection":"submissions","description":"Evidence that a learner completed one progress item.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"progressId":{"type":"foreignKey","required":true,"related":"LessonProgress","_meta":{"required":true}},"submittedAt":{"type":"text","required":false},"status":{"type":"text","required":false,"default":"captured","enum":["captured","extracting","needs_review","graded","rejected"]},"notes":{"type":"text","required":false},"captureKey":{"type":"text","required":false},"captureDigest":{"type":"text","required":false},"kind":{"type":"text","required":true,"default":"completed_work","_meta":{"required":true},"enum":["blank_reference","completed_work"]},"previousSubmissionId":{"type":"foreignKey","required":false,"related":"Submission","_meta":{"nullable":true}},"intakeItemId":{"type":"text","required":false,"_meta":{"nullable":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableStrategy":"sti","api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context","_meta_type"]},"extends":"SmrtObject","exportName":"Submission","collectionExportName":"SubmissionCollection","validationRules":[{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"progressId","rule":"required","fieldType":"foreignKey"},{"field":"kind","rule":"required","fieldType":"text"}],"schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"kind\\" TEXT DEFAULT 'completed_work',\\n \\"previous_submission_id\\" UUID,\\n \\"intake_item_id\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"kind":{"type":"TEXT","notNull":false,"default":"completed_work"},"previous_submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"intake_item_id":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]},{"name":"submissions_previous_submission_id_idx","columns":["previous_submission_id"]}],"version":"27a21ab4"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:SubmissionIntakeBinding":{"name":"submissionintakebinding","className":"SubmissionIntakeBinding","qualifiedName":"@happyvertical/smrt-education:SubmissionIntakeBinding","collection":"submissionintakebindings","description":"Durable tenant/item claim for trusted ingestion receipts. This separate table keeps ordinary uploads (which have no intake item) out of the uniqueness key.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"intakeItemId":{"type":"text","required":true,"_meta":{"required":true}},"submissionId":{"type":"foreignKey","required":true,"related":"Submission","_meta":{"required":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submission_intake_bindings","api":false,"mcp":false,"cli":false,"conflictColumns":["tenant_id","intake_item_id"],"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"SubmissionIntakeBinding","collectionExportName":"SubmissionIntakeBindingCollection","validationRules":[{"field":"intakeItemId","rule":"required","fieldType":"text"},{"field":"submissionId","rule":"required","fieldType":"foreignKey"}],"schema":{"tableName":"submission_intake_bindings","ddl":"CREATE TABLE IF NOT EXISTS \\"submission_intake_bindings\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"intake_item_id\\" TEXT NOT NULL,\\n \\"submission_id\\" UUID NOT NULL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"intake_item_id":{"type":"TEXT","notNull":true,"unique":false},"submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}}},"indexes":[{"name":"submission_intake_bindings_tenant_id_intake_item_id_idx","columns":["tenant_id","intake_item_id"],"unique":true},{"name":"submission_intake_bindings_slug_context_idx","columns":["slug","context"]},{"name":"submission_intake_bindings_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submission_intake_bindings_submission_id_idx","columns":["submission_id"]}],"version":"7102c909"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ScannedSubmission":{"name":"scannedsubmission","className":"ScannedSubmission","qualifiedName":"@happyvertical/smrt-education:ScannedSubmission","collection":"submissions","description":"Photos or scans of work submitted for review.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false}}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"progressId":{"type":"foreignKey","required":true,"related":"LessonProgress","_meta":{"required":true}},"submittedAt":{"type":"text","required":false},"status":{"type":"text","required":false,"default":"captured","enum":["captured","extracting","needs_review","graded","rejected"]},"notes":{"type":"text","required":false},"captureKey":{"type":"text","required":false},"captureDigest":{"type":"text","required":false},"kind":{"type":"text","required":true,"default":"completed_work","_meta":{"required":true},"enum":["blank_reference","completed_work"]},"previousSubmissionId":{"type":"foreignKey","required":false,"related":"Submission","_meta":{"nullable":true}},"intakeItemId":{"type":"text","required":false,"_meta":{"nullable":true}},"pageCount":{"type":"integer","required":true,"default":0},"ocrText":{"type":"text","required":false},"extracted":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"getExtracted":{"name":"getExtracted","async":false,"parameters":[],"returnType":"Record<string, unknown>","isStatic":false,"isPublic":true},"setExtracted":{"name":"setExtracted","async":false,"parameters":[{"name":"value","type":"Record<string, unknown>","optional":false}],"returnType":"void","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Submission","exportName":"ScannedSubmission","collectionExportName":"ScannedSubmissionCollection","validationRules":[{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"progressId","rule":"required","fieldType":"foreignKey"},{"field":"kind","rule":"required","fieldType":"text"},{"field":"pageCount","rule":"required","fieldType":"integer"}],"schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"kind\\" TEXT DEFAULT 'completed_work',\\n \\"previous_submission_id\\" UUID,\\n \\"intake_item_id\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"kind":{"type":"TEXT","notNull":false,"default":"completed_work"},"previous_submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"intake_item_id":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]},{"name":"submissions_previous_submission_id_idx","columns":["previous_submission_id"]}],"version":"7c394510"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:ExternalCompletion":{"name":"externalcompletion","className":"ExternalCompletion","qualifiedName":"@happyvertical/smrt-education:ExternalCompletion","collection":"submissions","description":"Completion reported by the learner, guardian, or external source.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false}}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"progressId":{"type":"foreignKey","required":true,"related":"LessonProgress","_meta":{"required":true}},"submittedAt":{"type":"text","required":false},"status":{"type":"text","required":false,"default":"captured","enum":["captured","extracting","needs_review","graded","rejected"]},"notes":{"type":"text","required":false},"captureKey":{"type":"text","required":false},"captureDigest":{"type":"text","required":false},"kind":{"type":"text","required":true,"default":"completed_work","_meta":{"required":true},"enum":["blank_reference","completed_work"]},"previousSubmissionId":{"type":"foreignKey","required":false,"related":"Submission","_meta":{"nullable":true}},"intakeItemId":{"type":"text","required":false,"_meta":{"nullable":true}},"reportedBy":{"type":"text","required":false,"default":"learner","enum":["learner","guardian","source"]},"evidenceUrl":{"type":"text","required":false},"reportedScore":{"type":"decimal","required":false,"_meta":{}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Submission","exportName":"ExternalCompletion","collectionExportName":"ExternalCompletionCollection","validationRules":[{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"progressId","rule":"required","fieldType":"foreignKey"},{"field":"kind","rule":"required","fieldType":"text"}],"schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"kind\\" TEXT DEFAULT 'completed_work',\\n \\"previous_submission_id\\" UUID,\\n \\"intake_item_id\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"kind":{"type":"TEXT","notNull":false,"default":"completed_work"},"previous_submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"intake_item_id":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]},{"name":"submissions_previous_submission_id_idx","columns":["previous_submission_id"]}],"version":"eff3715d"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Attestation":{"name":"attestation","className":"Attestation","qualifiedName":"@happyvertical/smrt-education:Attestation","collection":"submissions","description":"A guardian's statement that work was completed.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"created_at":{"type":"datetime","required":false},"updated_at":{"type":"datetime","required":false},"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false}}},"learnerId":{"type":"foreignKey","required":true,"related":"Learner","_meta":{"required":true}},"lessonId":{"type":"foreignKey","required":true,"related":"Lesson","_meta":{"required":true}},"progressId":{"type":"foreignKey","required":true,"related":"LessonProgress","_meta":{"required":true}},"submittedAt":{"type":"text","required":false},"status":{"type":"text","required":false,"default":"captured","enum":["captured","extracting","needs_review","graded","rejected"]},"notes":{"type":"text","required":false},"captureKey":{"type":"text","required":false},"captureDigest":{"type":"text","required":false},"kind":{"type":"text","required":true,"default":"completed_work","_meta":{"required":true},"enum":["blank_reference","completed_work"]},"previousSubmissionId":{"type":"foreignKey","required":false,"related":"Submission","_meta":{"nullable":true}},"intakeItemId":{"type":"text","required":false,"_meta":{"nullable":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submissions","api":false,"mcp":false,"cli":false,"tableStrategy":"sti"},"extends":"Submission","exportName":"Attestation","collectionExportName":"AttestationCollection","validationRules":[{"field":"learnerId","rule":"required","fieldType":"foreignKey"},{"field":"lessonId","rule":"required","fieldType":"foreignKey"},{"field":"progressId","rule":"required","fieldType":"foreignKey"},{"field":"kind","rule":"required","fieldType":"text"}],"schema":{"tableName":"submissions","ddl":"CREATE TABLE IF NOT EXISTS \\"submissions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"_meta_type\\" TEXT NOT NULL,\\n \\"_meta_data\\" JSON,\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"learner_id\\" UUID,\\n \\"lesson_id\\" UUID,\\n \\"progress_id\\" UUID,\\n \\"submitted_at\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'captured',\\n \\"notes\\" TEXT,\\n \\"capture_key\\" TEXT,\\n \\"capture_digest\\" TEXT,\\n \\"kind\\" TEXT DEFAULT 'completed_work',\\n \\"previous_submission_id\\" UUID,\\n \\"intake_item_id\\" TEXT,\\n \\"page_count\\" INTEGER DEFAULT 0,\\n \\"ocr_text\\" TEXT,\\n \\"extracted\\" TEXT,\\n \\"reported_by\\" TEXT DEFAULT 'learner',\\n \\"evidence_url\\" TEXT,\\n \\"reported_score\\" REAL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"_meta_type":{"type":"TEXT","notNull":true},"_meta_data":{"type":"JSON","notNull":false},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false},"learner_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"learners","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"lesson_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lessons","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"progress_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"lesson_progress","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"submitted_at":{"type":"TEXT","notNull":false},"status":{"type":"TEXT","notNull":false,"default":"captured"},"notes":{"type":"TEXT","notNull":false},"capture_key":{"type":"TEXT","notNull":false},"capture_digest":{"type":"TEXT","notNull":false},"kind":{"type":"TEXT","notNull":false,"default":"completed_work"},"previous_submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"intake_item_id":{"type":"TEXT","notNull":false},"page_count":{"type":"INTEGER","notNull":false,"default":0},"ocr_text":{"type":"TEXT","notNull":false},"extracted":{"type":"TEXT","notNull":false},"reported_by":{"type":"TEXT","notNull":false,"default":"learner"},"evidence_url":{"type":"TEXT","notNull":false},"reported_score":{"type":"REAL","notNull":false}},"indexes":[{"name":"submissions_slug_context_meta_type_idx","columns":["tenant_id","slug","context","_meta_type"],"unique":true,"nullsNotDistinct":true},{"name":"submissions_meta_type_idx","columns":["_meta_type"]},{"name":"submissions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submissions_learner_id_idx","columns":["learner_id"]},{"name":"submissions_lesson_id_idx","columns":["lesson_id"]},{"name":"submissions_progress_id_idx","columns":["progress_id"]},{"name":"submissions_previous_submission_id_idx","columns":["previous_submission_id"]}],"version":"4f3230a3"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:SubmissionAsset":{"name":"submissionasset","className":"SubmissionAsset","qualifiedName":"@happyvertical/smrt-education:SubmissionAsset","collection":"submissionassets","description":"The tenant-owned files attached to a submission.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"submissionId":{"type":"foreignKey","required":true,"related":"Submission","_meta":{"required":true}},"assetId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-assets:Asset"},"relationship":{"type":"text","required":true,"default":"page","_meta":{"required":true},"enum":["page","attachment"]},"sortOrder":{"type":"integer","required":true,"default":0}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"submission_assets","conflictColumns":["tenant_id","submission_id","asset_id","relationship"],"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"SubmissionAsset","collectionExportName":"SubmissionAssetCollection","validationRules":[{"field":"submissionId","rule":"required","fieldType":"foreignKey"},{"field":"assetId","rule":"required","fieldType":"crossPackageRef"},{"field":"relationship","rule":"required","fieldType":"text"},{"field":"sortOrder","rule":"required","fieldType":"integer"}],"schema":{"tableName":"submission_assets","ddl":"CREATE TABLE IF NOT EXISTS \\"submission_assets\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"submission_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"relationship\\" TEXT NOT NULL DEFAULT 'page',\\n \\"sort_order\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"relationship":{"type":"TEXT","notNull":true,"unique":false,"default":"page"},"sort_order":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"submission_assets_tenant_id_submission_id_idx","columns":["tenant_id","submission_id","asset_id","relationship"],"unique":true},{"name":"submission_assets_slug_context_idx","columns":["slug","context"]},{"name":"submission_assets_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"submission_assets_submission_id_idx","columns":["submission_id"]},{"name":"submission_assets_asset_id_idx","columns":["asset_id"]}],"version":"d9b61c8a"},"importPath":"@happyvertical/smrt-education"},"@happyvertical/smrt-education:Grade":{"name":"grade","className":"Grade","qualifiedName":"@happyvertical/smrt-education:Grade","collection":"grades","description":"An assessment of a submission; the service selects the authoritative grade.","filePath":"/home/runner/work/smrt/smrt/packages/education/src/submissions.ts","packageName":"@happyvertical/smrt-education","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"submissionId":{"type":"foreignKey","required":true,"related":"Submission","_meta":{"required":true}},"graderKind":{"type":"text","required":true,"default":"auto","_meta":{"required":true},"enum":["auto","ai","guardian","instructor"]},"graderProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile","_meta":{"nullable":true}},"score":{"type":"decimal","required":false,"_meta":{"nullable":true}},"pointsEarned":{"type":"integer","required":false,"_meta":{"nullable":true}},"pointsPossible":{"type":"integer","required":false,"_meta":{"nullable":true}},"rubricResults":{"type":"text","required":false},"feedback":{"type":"text","required":false},"gradedAt":{"type":"text","required":false}},"methods":{"getRubricResults":{"name":"getRubricResults","async":false,"parameters":[],"returnType":"Record<string, unknown>","isStatic":false,"isPublic":true},"setRubricResults":{"name":"setRubricResults","async":false,"parameters":[{"name":"value","type":"Record<string, unknown>","optional":false}],"returnType":"void","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"mcp":false,"cli":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"Grade","collectionExportName":"GradeCollection","validationRules":[{"field":"submissionId","rule":"required","fieldType":"foreignKey"},{"field":"graderKind","rule":"required","fieldType":"text"}],"schema":{"tableName":"grades","ddl":"CREATE TABLE IF NOT EXISTS \\"grades\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"submission_id\\" UUID NOT NULL,\\n \\"grader_kind\\" TEXT NOT NULL DEFAULT 'auto',\\n \\"grader_profile_id\\" UUID,\\n \\"score\\" REAL,\\n \\"points_earned\\" INTEGER,\\n \\"points_possible\\" INTEGER,\\n \\"rubric_results\\" TEXT,\\n \\"feedback\\" TEXT,\\n \\"graded_at\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"submission_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"submissions","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"grader_kind":{"type":"TEXT","notNull":true,"unique":false,"default":"auto"},"grader_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"score":{"type":"REAL","notNull":false,"unique":false},"points_earned":{"type":"INTEGER","notNull":false,"unique":false},"points_possible":{"type":"INTEGER","notNull":false,"unique":false},"rubric_results":{"type":"TEXT","notNull":false,"unique":false},"feedback":{"type":"TEXT","notNull":false,"unique":false},"graded_at":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"grades_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"grades_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"grades_submission_id_idx","columns":["submission_id"]},{"name":"grades_grader_profile_id_idx","columns":["grader_profile_id"]}],"version":"7d629fec"},"importPath":"@happyvertical/smrt-education"}},"moduleType":"smrt","recipes":[{"id":"education.catalog","className":"EducationCatalogRecipe","label":"Course catalog","summary":"Organize courses, sections, lessons, and their authorized learning materials.","synonyms":["courses","lessons","curriculum catalog"],"models":["@happyvertical/smrt-education:Course","@happyvertical/smrt-education:CourseSection","@happyvertical/smrt-education:Lesson","@happyvertical/smrt-education:LessonAsset"],"nav":[{"label":"Courses","model":"@happyvertical/smrt-education:Course","icon":"folder","description":"Courses available to this family.","noun":"course"}],"requires":[],"section":{"id":"education","label":"Education","icon":"folder","description":"Courses and lessons for learning."},"help":{"markdown":"# Course catalog\\n\\nManage courses, their sections, and lessons. Private family copies and answer\\nkeys remain visible only through the application\u2019s authorized catalog service.\\n","fieldRefs":[]},"runtime":"both","demo":{"mode":"server","reasons":["Needs a server because its package depends on @happyvertical/smrt-agents, @happyvertical/smrt-jobs, @happyvertical/smrt-profiles, @happyvertical/smrt-reports, @happyvertical/smrt-secrets, @happyvertical/smrt-tenancy, @happyvertical/smrt-users, which do not build for a browser (#3615, #3617, #3624, #3625, #3626, #3627)."]}},{"id":"education.learner","className":"EducationLearnerRecipe","label":"Learners","summary":"Plan learner curricula and retain enrollment-scoped progress.","synonyms":["learners","enrollments","learning progress"],"models":["@happyvertical/smrt-education:Learner","@happyvertical/smrt-education:Curriculum","@happyvertical/smrt-education:Enrollment","@happyvertical/smrt-education:LessonProgress"],"nav":[{"label":"Learners","model":"@happyvertical/smrt-education:Learner","icon":"users","description":"Authorized learners in this family.","noun":"learner"}],"requires":[],"section":{"id":"education","label":"Education","icon":"folder","description":"Courses and lessons for learning."},"help":{"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","fieldRefs":[]},"runtime":"both","demo":{"mode":"server","reasons":["Needs a server because its package depends on @happyvertical/smrt-agents, @happyvertical/smrt-jobs, @happyvertical/smrt-profiles, @happyvertical/smrt-reports, @happyvertical/smrt-secrets, @happyvertical/smrt-tenancy, @happyvertical/smrt-users, which do not build for a browser (#3615, #3617, #3624, #3625, #3626, #3627)."]}}],"smrtDependencies":["@happyvertical/smrt-assets","@happyvertical/smrt-core","@happyvertical/smrt-ingestion","@happyvertical/smrt-profiles","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"],"browser":{"status":"server-only","issues":["#3615","#3617","#3624","#3625","#3626","#3627"],"via":["@happyvertical/smrt-agents","@happyvertical/smrt-jobs","@happyvertical/smrt-profiles","@happyvertical/smrt-reports","@happyvertical/smrt-secrets","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"]},"stampsConstructors":true}`));
|
|
30
30
|
//#endregion
|
|
31
31
|
//#region src/catalog.ts
|
|
32
|
-
var __defProp$
|
|
33
|
-
var __getOwnPropDesc$
|
|
34
|
-
var __defNormalProp$
|
|
32
|
+
var __defProp$5 = Object.defineProperty;
|
|
33
|
+
var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
|
|
34
|
+
var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, {
|
|
35
35
|
enumerable: true,
|
|
36
36
|
configurable: true,
|
|
37
37
|
writable: true,
|
|
38
38
|
value
|
|
39
39
|
}) : obj[key] = value;
|
|
40
|
-
var __decorateClass$
|
|
41
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
40
|
+
var __decorateClass$5 = (decorators, target, key, kind) => {
|
|
41
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
|
|
42
42
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
43
|
-
if (kind && result) __defProp$
|
|
43
|
+
if (kind && result) __defProp$5(target, key, result);
|
|
44
44
|
return result;
|
|
45
45
|
};
|
|
46
|
-
var __publicField$
|
|
47
|
-
var closed$
|
|
46
|
+
var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
47
|
+
var closed$3 = {
|
|
48
48
|
api: false,
|
|
49
49
|
mcp: false,
|
|
50
50
|
cli: false
|
|
@@ -103,24 +103,24 @@ var Course = class extends SmrtObject {
|
|
|
103
103
|
return await super.save();
|
|
104
104
|
}
|
|
105
105
|
};
|
|
106
|
-
__publicField$
|
|
107
|
-
__decorateClass$
|
|
108
|
-
__decorateClass$
|
|
109
|
-
Course = __decorateClass$
|
|
106
|
+
__publicField$5(Course, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
107
|
+
__decorateClass$5([tenantId({ nullable: true })], Course.prototype, "tenantId", 2);
|
|
108
|
+
__decorateClass$5([field({ required: true })], Course.prototype, "title", 2);
|
|
109
|
+
Course = __decorateClass$5([TenantScoped({ mode: "optional" }), smrt({
|
|
110
110
|
tableStrategy: "sti",
|
|
111
|
-
...closed$
|
|
111
|
+
...closed$3
|
|
112
112
|
})], Course);
|
|
113
113
|
var ExternalCourse = class extends Course {};
|
|
114
|
-
__publicField$
|
|
115
|
-
ExternalCourse = __decorateClass$
|
|
114
|
+
__publicField$5(ExternalCourse, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
115
|
+
ExternalCourse = __decorateClass$5([smrt({
|
|
116
116
|
tableName: "courses",
|
|
117
|
-
...closed$
|
|
117
|
+
...closed$3
|
|
118
118
|
})], ExternalCourse);
|
|
119
119
|
var AuthoredCourse = class extends Course {};
|
|
120
|
-
__publicField$
|
|
121
|
-
AuthoredCourse = __decorateClass$
|
|
120
|
+
__publicField$5(AuthoredCourse, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
121
|
+
AuthoredCourse = __decorateClass$5([smrt({
|
|
122
122
|
tableName: "courses",
|
|
123
|
-
...closed$
|
|
123
|
+
...closed$3
|
|
124
124
|
})], AuthoredCourse);
|
|
125
125
|
var CourseSection = class extends SmrtObject {
|
|
126
126
|
tenantId = null;
|
|
@@ -141,11 +141,11 @@ var CourseSection = class extends SmrtObject {
|
|
|
141
141
|
return await super.save();
|
|
142
142
|
}
|
|
143
143
|
};
|
|
144
|
-
__publicField$
|
|
145
|
-
__decorateClass$
|
|
146
|
-
__decorateClass$
|
|
147
|
-
__decorateClass$
|
|
148
|
-
CourseSection = __decorateClass$
|
|
144
|
+
__publicField$5(CourseSection, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
145
|
+
__decorateClass$5([tenantId({ nullable: true })], CourseSection.prototype, "tenantId", 2);
|
|
146
|
+
__decorateClass$5([foreignKey("Course", { required: true })], CourseSection.prototype, "courseId", 2);
|
|
147
|
+
__decorateClass$5([field({ required: true })], CourseSection.prototype, "title", 2);
|
|
148
|
+
CourseSection = __decorateClass$5([TenantScoped({ mode: "optional" }), smrt({ ...closed$3 })], CourseSection);
|
|
149
149
|
var Lesson = class extends SmrtObject {
|
|
150
150
|
tenantId = null;
|
|
151
151
|
sectionId = "";
|
|
@@ -172,19 +172,19 @@ var Lesson = class extends SmrtObject {
|
|
|
172
172
|
return await super.save();
|
|
173
173
|
}
|
|
174
174
|
};
|
|
175
|
-
__publicField$
|
|
176
|
-
__decorateClass$
|
|
177
|
-
__decorateClass$
|
|
178
|
-
__decorateClass$
|
|
179
|
-
Lesson = __decorateClass$
|
|
175
|
+
__publicField$5(Lesson, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
176
|
+
__decorateClass$5([tenantId({ nullable: true })], Lesson.prototype, "tenantId", 2);
|
|
177
|
+
__decorateClass$5([foreignKey("CourseSection", { required: true })], Lesson.prototype, "sectionId", 2);
|
|
178
|
+
__decorateClass$5([field({ required: true })], Lesson.prototype, "title", 2);
|
|
179
|
+
Lesson = __decorateClass$5([TenantScoped({ mode: "optional" }), smrt({
|
|
180
180
|
tableStrategy: "sti",
|
|
181
|
-
...closed$
|
|
181
|
+
...closed$3
|
|
182
182
|
})], Lesson);
|
|
183
183
|
var Reading = class extends Lesson {};
|
|
184
|
-
__publicField$
|
|
185
|
-
Reading = __decorateClass$
|
|
184
|
+
__publicField$5(Reading, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
185
|
+
Reading = __decorateClass$5([smrt({
|
|
186
186
|
tableName: "lessons",
|
|
187
|
-
...closed$
|
|
187
|
+
...closed$3
|
|
188
188
|
})], Reading);
|
|
189
189
|
var Video = class extends Lesson {
|
|
190
190
|
videoUrl = "";
|
|
@@ -193,10 +193,10 @@ var Video = class extends Lesson {
|
|
|
193
193
|
applyModelOptions(this, options);
|
|
194
194
|
}
|
|
195
195
|
};
|
|
196
|
-
__publicField$
|
|
197
|
-
Video = __decorateClass$
|
|
196
|
+
__publicField$5(Video, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
197
|
+
Video = __decorateClass$5([smrt({
|
|
198
198
|
tableName: "lessons",
|
|
199
|
-
...closed$
|
|
199
|
+
...closed$3
|
|
200
200
|
})], Video);
|
|
201
201
|
var Worksheet = class extends Lesson {
|
|
202
202
|
printableUrl = "";
|
|
@@ -208,16 +208,16 @@ var Worksheet = class extends Lesson {
|
|
|
208
208
|
applyModelOptions(this, options);
|
|
209
209
|
}
|
|
210
210
|
};
|
|
211
|
-
__publicField$
|
|
212
|
-
Worksheet = __decorateClass$
|
|
211
|
+
__publicField$5(Worksheet, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
212
|
+
Worksheet = __decorateClass$5([smrt({
|
|
213
213
|
tableName: "lessons",
|
|
214
|
-
...closed$
|
|
214
|
+
...closed$3
|
|
215
215
|
})], Worksheet);
|
|
216
216
|
var Exercise = class extends Lesson {};
|
|
217
|
-
__publicField$
|
|
218
|
-
Exercise = __decorateClass$
|
|
217
|
+
__publicField$5(Exercise, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
218
|
+
Exercise = __decorateClass$5([smrt({
|
|
219
219
|
tableName: "lessons",
|
|
220
|
-
...closed$
|
|
220
|
+
...closed$3
|
|
221
221
|
})], Exercise);
|
|
222
222
|
GlobalInterceptors.register({
|
|
223
223
|
name: "education-catalog-identity",
|
|
@@ -248,114 +248,325 @@ GlobalInterceptors.register({
|
|
|
248
248
|
}
|
|
249
249
|
});
|
|
250
250
|
//#endregion
|
|
251
|
-
//#region src/catalog-
|
|
252
|
-
var
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
//#region src/catalog-policy.ts
|
|
252
|
+
var CatalogPolicy = class {
|
|
253
|
+
canView(record, actor) {
|
|
254
|
+
return record.visibility === "catalog" || actor.roles.includes("teacher");
|
|
255
|
+
}
|
|
256
|
+
authorize(record, operation, actor) {
|
|
257
|
+
if (!this.canView(record, actor)) return {
|
|
258
|
+
allowed: false,
|
|
259
|
+
reason: "teacher-material"
|
|
260
|
+
};
|
|
261
|
+
const evidence = record.licenseEvidence;
|
|
262
|
+
if (!this.isVerified(evidence)) return {
|
|
263
|
+
allowed: false,
|
|
264
|
+
reason: "unverified-license"
|
|
265
|
+
};
|
|
266
|
+
if (!evidence.permittedOperations?.includes(operation)) return {
|
|
267
|
+
allowed: false,
|
|
268
|
+
reason: "operation-not-permitted"
|
|
269
|
+
};
|
|
270
|
+
return { allowed: true };
|
|
271
|
+
}
|
|
272
|
+
isVerified(evidence) {
|
|
273
|
+
return evidence.state === "verified" && !!evidence.identifier?.trim();
|
|
256
274
|
}
|
|
257
275
|
};
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/catalog-acquisition-service.ts
|
|
278
|
+
async function digest(bytes) {
|
|
279
|
+
const hash = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
280
|
+
return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
261
281
|
}
|
|
262
|
-
var
|
|
263
|
-
constructor(
|
|
264
|
-
this.
|
|
265
|
-
this.
|
|
266
|
-
this.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
sections;
|
|
273
|
-
lessons;
|
|
274
|
-
assets;
|
|
275
|
-
tenants;
|
|
276
|
-
authorize;
|
|
277
|
-
async findVisible(request) {
|
|
278
|
-
const { tenantId, actorUserId, includePrivate = false, options = {} } = request;
|
|
279
|
-
if (!tenantId.trim() || !actorUserId.trim()) throw new CatalogAuthorizationError();
|
|
282
|
+
var CatalogAcquisitionService = class {
|
|
283
|
+
constructor(store, adapter, policy = new CatalogPolicy()) {
|
|
284
|
+
this.store = store;
|
|
285
|
+
this.adapter = adapter;
|
|
286
|
+
this.policy = policy;
|
|
287
|
+
}
|
|
288
|
+
store;
|
|
289
|
+
adapter;
|
|
290
|
+
policy;
|
|
291
|
+
async acquire(request) {
|
|
280
292
|
const actor = {
|
|
281
|
-
|
|
282
|
-
actorUserId,
|
|
283
|
-
|
|
293
|
+
tenantId: request.tenantId,
|
|
294
|
+
actorUserId: request.actorUserId,
|
|
295
|
+
roles: request.roles
|
|
284
296
|
};
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
})
|
|
297
|
+
if (!actor.tenantId.trim() || !actor.actorUserId.trim()) throw new Error("Trusted tenant and actor identity are required.");
|
|
298
|
+
const resource = await this.store.getResource(request.resourceId);
|
|
299
|
+
if (!resource) throw new Error("Catalog resource was not found.");
|
|
300
|
+
if (resource.withdrawnAt) throw new Error("Withdrawn catalog resource cannot be acquired.");
|
|
301
|
+
if (resource.revision !== request.revision) throw new Error("Catalog resource revision is no longer current; reselect it.");
|
|
302
|
+
const selection = await this.store.getSelection({
|
|
303
|
+
tenantId: actor.tenantId,
|
|
304
|
+
selectionId: request.selectionId
|
|
305
|
+
});
|
|
306
|
+
if (!selection || selection.resourceId !== resource.id || selection.selectedRevision !== request.revision) throw new Error("A matching tenant selection is required for acquisition.");
|
|
307
|
+
if (!resource.availableFormats?.includes(request.format)) throw new Error("Requested content format is unavailable.");
|
|
308
|
+
const decision = this.policy.authorize(resource, request.operation, actor);
|
|
309
|
+
if (!decision.allowed) throw new Error(`Catalog acquisition denied: ${decision.reason}`);
|
|
310
|
+
const existing = await this.store.findAcquisition({
|
|
311
|
+
tenantId: actor.tenantId,
|
|
312
|
+
resourceId: resource.id,
|
|
313
|
+
selectionId: selection.id,
|
|
314
|
+
revision: request.revision,
|
|
315
|
+
format: request.format
|
|
316
|
+
});
|
|
317
|
+
if (existing) return existing;
|
|
318
|
+
const acquired = await this.adapter.acquire({
|
|
319
|
+
providerId: resource.providerId,
|
|
320
|
+
stableId: resource.stableId,
|
|
321
|
+
revision: request.revision,
|
|
322
|
+
format: request.format
|
|
323
|
+
});
|
|
324
|
+
if (acquired.revision !== request.revision) throw new Error("Provider returned an unpinned resource revision.");
|
|
325
|
+
const actualHash = await digest(acquired.bytes);
|
|
326
|
+
if (actualHash !== acquired.sha256.toLowerCase()) throw new Error("Provider content hash did not match acquired bytes.");
|
|
327
|
+
return await this.store.createAcquisition({
|
|
328
|
+
tenantId: actor.tenantId,
|
|
329
|
+
resourceId: resource.id,
|
|
330
|
+
selectionId: selection.id,
|
|
331
|
+
revision: request.revision,
|
|
332
|
+
format: request.format,
|
|
333
|
+
sha256: actualHash,
|
|
334
|
+
attribution: resource.licenseEvidence.attribution,
|
|
335
|
+
notices: resource.licenseEvidence.exceptions,
|
|
336
|
+
bytes: acquired.bytes
|
|
337
|
+
});
|
|
312
338
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
339
|
+
};
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/catalog-models.ts
|
|
342
|
+
var __defProp$4 = Object.defineProperty;
|
|
343
|
+
var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
|
|
344
|
+
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, {
|
|
345
|
+
enumerable: true,
|
|
346
|
+
configurable: true,
|
|
347
|
+
writable: true,
|
|
348
|
+
value
|
|
349
|
+
}) : obj[key] = value;
|
|
350
|
+
var __decorateClass$4 = (decorators, target, key, kind) => {
|
|
351
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
|
|
352
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
353
|
+
if (kind && result) __defProp$4(target, key, result);
|
|
354
|
+
return result;
|
|
355
|
+
};
|
|
356
|
+
var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
357
|
+
var closed$2 = {
|
|
358
|
+
api: false,
|
|
359
|
+
mcp: false,
|
|
360
|
+
cli: false
|
|
361
|
+
};
|
|
362
|
+
var CatalogProvider = class extends SmrtObject {
|
|
363
|
+
providerId = "";
|
|
364
|
+
name = "";
|
|
365
|
+
active = true;
|
|
366
|
+
};
|
|
367
|
+
__publicField$4(CatalogProvider, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
368
|
+
__decorateClass$4([field({ required: true })], CatalogProvider.prototype, "providerId", 2);
|
|
369
|
+
__decorateClass$4([field({ required: true })], CatalogProvider.prototype, "name", 2);
|
|
370
|
+
CatalogProvider = __decorateClass$4([smrt({ ...closed$2 })], CatalogProvider);
|
|
371
|
+
var CatalogResource = class extends SmrtObject {
|
|
372
|
+
providerId = "";
|
|
373
|
+
stableId = "";
|
|
374
|
+
revision = "";
|
|
375
|
+
canonicalUrl = "";
|
|
376
|
+
title = "";
|
|
377
|
+
suppliedDescription = "";
|
|
378
|
+
author = "";
|
|
379
|
+
subjects = "[]";
|
|
380
|
+
grades = "{}";
|
|
381
|
+
language = "";
|
|
382
|
+
resourceType = "";
|
|
383
|
+
parentStableId = "";
|
|
384
|
+
availableFormats = "[]";
|
|
385
|
+
licenseEvidence = "{}";
|
|
386
|
+
licenseState = "unknown";
|
|
387
|
+
visibility = "catalog";
|
|
388
|
+
/** Last durable enumeration generation that observed this provider record. */
|
|
389
|
+
lastSeenRunId = "";
|
|
390
|
+
withdrawnAt = "";
|
|
391
|
+
};
|
|
392
|
+
__publicField$4(CatalogResource, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
393
|
+
__decorateClass$4([field({ required: true })], CatalogResource.prototype, "providerId", 2);
|
|
394
|
+
__decorateClass$4([field({ required: true })], CatalogResource.prototype, "stableId", 2);
|
|
395
|
+
__decorateClass$4([field({ required: true })], CatalogResource.prototype, "revision", 2);
|
|
396
|
+
__decorateClass$4([field({ required: true })], CatalogResource.prototype, "title", 2);
|
|
397
|
+
CatalogResource = __decorateClass$4([smrt({ ...closed$2 })], CatalogResource);
|
|
398
|
+
var CatalogSyncState = class extends SmrtObject {
|
|
399
|
+
providerId = "";
|
|
400
|
+
cursor = "";
|
|
401
|
+
/** Persists across interrupted pages so terminal withdrawal is safe on resume. */
|
|
402
|
+
runId = "";
|
|
403
|
+
complete = false;
|
|
404
|
+
paused = false;
|
|
405
|
+
pauseReason = "";
|
|
406
|
+
importedCount = 0;
|
|
407
|
+
failures = "[]";
|
|
408
|
+
lastSuccessfulAt = "";
|
|
409
|
+
};
|
|
410
|
+
__publicField$4(CatalogSyncState, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
411
|
+
__decorateClass$4([field({ required: true })], CatalogSyncState.prototype, "providerId", 2);
|
|
412
|
+
CatalogSyncState = __decorateClass$4([smrt({ ...closed$2 })], CatalogSyncState);
|
|
413
|
+
var CatalogSelection = class extends SmrtObject {
|
|
414
|
+
tenantId = "";
|
|
415
|
+
resourceId = "";
|
|
416
|
+
selectedByUserId = "";
|
|
417
|
+
selectedRevision = "";
|
|
418
|
+
};
|
|
419
|
+
__publicField$4(CatalogSelection, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
420
|
+
__decorateClass$4([tenantId()], CatalogSelection.prototype, "tenantId", 2);
|
|
421
|
+
__decorateClass$4([foreignKey("CatalogResource", { required: true })], CatalogSelection.prototype, "resourceId", 2);
|
|
422
|
+
__decorateClass$4([field({ required: true })], CatalogSelection.prototype, "selectedByUserId", 2);
|
|
423
|
+
__decorateClass$4([field({ required: true })], CatalogSelection.prototype, "selectedRevision", 2);
|
|
424
|
+
CatalogSelection = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({ ...closed$2 })], CatalogSelection);
|
|
425
|
+
var CatalogAcquisition = class extends SmrtObject {
|
|
426
|
+
tenantId = "";
|
|
427
|
+
resourceId = "";
|
|
428
|
+
selectionId = "";
|
|
429
|
+
revision = "";
|
|
430
|
+
format = "";
|
|
431
|
+
sha256 = "";
|
|
432
|
+
attribution = "";
|
|
433
|
+
notices = "";
|
|
434
|
+
};
|
|
435
|
+
__publicField$4(CatalogAcquisition, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
436
|
+
__decorateClass$4([tenantId()], CatalogAcquisition.prototype, "tenantId", 2);
|
|
437
|
+
__decorateClass$4([foreignKey("CatalogResource", { required: true })], CatalogAcquisition.prototype, "resourceId", 2);
|
|
438
|
+
__decorateClass$4([foreignKey("CatalogSelection", { required: true })], CatalogAcquisition.prototype, "selectionId", 2);
|
|
439
|
+
__decorateClass$4([field({ required: true })], CatalogAcquisition.prototype, "revision", 2);
|
|
440
|
+
__decorateClass$4([field({ required: true })], CatalogAcquisition.prototype, "format", 2);
|
|
441
|
+
__decorateClass$4([field({ required: true })], CatalogAcquisition.prototype, "sha256", 2);
|
|
442
|
+
CatalogAcquisition = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({ ...closed$2 })], CatalogAcquisition);
|
|
443
|
+
//#endregion
|
|
444
|
+
//#region src/catalog-collections.ts
|
|
445
|
+
var CatalogProviderCollection = class extends SmrtCollection {
|
|
446
|
+
static _itemClass = CatalogProvider;
|
|
447
|
+
};
|
|
448
|
+
var CatalogResourceCollection = class extends SmrtCollection {
|
|
449
|
+
static _itemClass = CatalogResource;
|
|
450
|
+
};
|
|
451
|
+
var CatalogSyncStateCollection = class extends SmrtCollection {
|
|
452
|
+
static _itemClass = CatalogSyncState;
|
|
453
|
+
};
|
|
454
|
+
var CatalogSelectionCollection = class extends SmrtCollection {
|
|
455
|
+
static _itemClass = CatalogSelection;
|
|
456
|
+
};
|
|
457
|
+
var CatalogAcquisitionCollection = class extends SmrtCollection {
|
|
458
|
+
static _itemClass = CatalogAcquisition;
|
|
459
|
+
};
|
|
460
|
+
//#endregion
|
|
461
|
+
//#region src/catalog-database-sync-store.ts
|
|
462
|
+
function json(value) {
|
|
463
|
+
return JSON.stringify(value ?? null);
|
|
464
|
+
}
|
|
465
|
+
function checkpoint(row) {
|
|
466
|
+
let failures = [];
|
|
467
|
+
try {
|
|
468
|
+
const parsed = JSON.parse(row.failures);
|
|
469
|
+
if (Array.isArray(parsed)) failures = parsed;
|
|
470
|
+
} catch {
|
|
471
|
+
failures = [{
|
|
472
|
+
code: "invalid-failure-ledger",
|
|
473
|
+
message: "Stored failure ledger is invalid."
|
|
474
|
+
}];
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
providerId: row.providerId,
|
|
478
|
+
...row.runId ? { runId: row.runId } : {},
|
|
479
|
+
...row.cursor ? { cursor: row.cursor } : {},
|
|
480
|
+
complete: row.complete,
|
|
481
|
+
...row.paused ? { paused: true } : {},
|
|
482
|
+
...row.pauseReason ? { pauseReason: row.pauseReason } : {},
|
|
483
|
+
importedCount: row.importedCount,
|
|
484
|
+
failures,
|
|
485
|
+
...row.lastSuccessfulAt ? { lastSuccessfulAt: row.lastSuccessfulAt } : {}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
var CatalogDatabaseSyncStore = class {
|
|
489
|
+
constructor(db) {
|
|
490
|
+
this.db = db;
|
|
491
|
+
}
|
|
492
|
+
db;
|
|
493
|
+
async getCheckpoint(providerId) {
|
|
494
|
+
const rows = await (await CatalogSyncStateCollection.create({ db: this.db })).list({
|
|
495
|
+
where: { providerId },
|
|
496
|
+
limit: 2
|
|
497
|
+
});
|
|
498
|
+
if (rows.length > 1) throw new Error("Catalog provider has ambiguous sync state.");
|
|
499
|
+
return rows[0] ? checkpoint(rows[0]) : void 0;
|
|
500
|
+
}
|
|
501
|
+
async saveCheckpoint(value) {
|
|
502
|
+
const states = await CatalogSyncStateCollection.create({ db: this.db });
|
|
503
|
+
const rows = await states.list({
|
|
504
|
+
where: { providerId: value.providerId },
|
|
505
|
+
limit: 2
|
|
506
|
+
});
|
|
507
|
+
if (rows.length > 1) throw new Error("Catalog provider has ambiguous sync state.");
|
|
508
|
+
const fields = {
|
|
509
|
+
providerId: value.providerId,
|
|
510
|
+
runId: value.runId ?? "",
|
|
511
|
+
cursor: value.cursor ?? "",
|
|
512
|
+
complete: value.complete,
|
|
513
|
+
paused: value.paused ?? false,
|
|
514
|
+
pauseReason: value.pauseReason ?? "",
|
|
515
|
+
importedCount: value.importedCount,
|
|
516
|
+
failures: json(value.failures),
|
|
517
|
+
lastSuccessfulAt: value.lastSuccessfulAt ?? ""
|
|
322
518
|
};
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
where: { courseId: course.id },
|
|
337
|
-
orderBy: ["sortOrder ASC", "id ASC"]
|
|
338
|
-
});
|
|
339
|
-
const result = [];
|
|
340
|
-
for (const section of sections) {
|
|
341
|
-
if (section.tenantId !== course.tenantId) continue;
|
|
342
|
-
const lessons = await this.lessons.list({
|
|
343
|
-
where: { sectionId: section.id },
|
|
344
|
-
orderBy: ["sortOrder ASC", "id ASC"]
|
|
345
|
-
});
|
|
346
|
-
result.push({
|
|
347
|
-
section,
|
|
348
|
-
lessons: await Promise.all(lessons.filter((lesson) => lesson.tenantId === course.tenantId).map(async (lesson) => ({
|
|
349
|
-
lesson: projectLesson(lesson, includeAnswerKeys),
|
|
350
|
-
assets: (await this.assets.byLeft(String(lesson.id))).filter((asset) => asset.tenantId === course.tenantId && (includeAnswerKeys || asset.relationship !== "answer-key"))
|
|
351
|
-
})))
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
return {
|
|
355
|
-
course,
|
|
356
|
-
sections: result
|
|
357
|
-
};
|
|
519
|
+
if (rows[0]) {
|
|
520
|
+
Object.assign(rows[0], fields);
|
|
521
|
+
await rows[0].save();
|
|
522
|
+
} else await states.create(fields);
|
|
523
|
+
}
|
|
524
|
+
async upsert(record, runId) {
|
|
525
|
+
const resources = await CatalogResourceCollection.create({ db: this.db });
|
|
526
|
+
const rows = await resources.list({
|
|
527
|
+
where: {
|
|
528
|
+
providerId: record.providerId,
|
|
529
|
+
stableId: record.stableId
|
|
530
|
+
},
|
|
531
|
+
limit: 2
|
|
358
532
|
});
|
|
533
|
+
if (rows.length > 1) throw new Error("Catalog provider record identity is ambiguous.");
|
|
534
|
+
const fields = {
|
|
535
|
+
providerId: record.providerId,
|
|
536
|
+
stableId: record.stableId,
|
|
537
|
+
revision: record.revision,
|
|
538
|
+
canonicalUrl: record.canonicalUrl,
|
|
539
|
+
title: record.title,
|
|
540
|
+
suppliedDescription: record.suppliedDescription ?? "",
|
|
541
|
+
author: record.author ?? "",
|
|
542
|
+
subjects: json(record.subjects ?? []),
|
|
543
|
+
grades: json(record.gradeRange ?? {}),
|
|
544
|
+
language: record.language ?? "",
|
|
545
|
+
resourceType: record.resourceType ?? "",
|
|
546
|
+
parentStableId: record.parentStableId ?? "",
|
|
547
|
+
availableFormats: json(record.availableFormats ?? []),
|
|
548
|
+
licenseEvidence: json(record.licenseEvidence),
|
|
549
|
+
licenseState: record.licenseEvidence.state,
|
|
550
|
+
visibility: record.visibility,
|
|
551
|
+
lastSeenRunId: runId,
|
|
552
|
+
withdrawnAt: ""
|
|
553
|
+
};
|
|
554
|
+
if (rows[0]) {
|
|
555
|
+
Object.assign(rows[0], fields);
|
|
556
|
+
await rows[0].save();
|
|
557
|
+
} else await resources.create(fields);
|
|
558
|
+
}
|
|
559
|
+
async withdrawUnseen(providerId, runId) {
|
|
560
|
+
const rows = await (await CatalogResourceCollection.create({ db: this.db })).list({ where: { providerId } });
|
|
561
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
562
|
+
let withdrawn = 0;
|
|
563
|
+
for (const row of rows) {
|
|
564
|
+
if (row.lastSeenRunId === runId || row.withdrawnAt) continue;
|
|
565
|
+
row.withdrawnAt = timestamp;
|
|
566
|
+
await row.save();
|
|
567
|
+
withdrawn++;
|
|
568
|
+
}
|
|
569
|
+
return withdrawn;
|
|
359
570
|
}
|
|
360
571
|
};
|
|
361
572
|
//#endregion
|
|
@@ -725,6 +936,9 @@ var Submission = class extends SmrtObject {
|
|
|
725
936
|
captureKey = "";
|
|
726
937
|
/** Service-owned content digest used to detect changed retry payloads. */
|
|
727
938
|
captureDigest = "";
|
|
939
|
+
kind = "completed_work";
|
|
940
|
+
previousSubmissionId = null;
|
|
941
|
+
intakeItemId = null;
|
|
728
942
|
constructor(options = {}) {
|
|
729
943
|
super(options);
|
|
730
944
|
applyOptions(this, options);
|
|
@@ -742,6 +956,16 @@ var Submission = class extends SmrtObject {
|
|
|
742
956
|
"graded",
|
|
743
957
|
"rejected"
|
|
744
958
|
]);
|
|
959
|
+
requireEnum(this.kind, ["blank_reference", "completed_work"]);
|
|
960
|
+
if (this.intakeItemId !== null && (!this.intakeItemId.trim() || this.intakeItemId.length > 200)) throw new Error("Submission intake item id is invalid");
|
|
961
|
+
if (this.previousSubmissionId === String(this.id)) throw new Error("Submission cannot precede itself");
|
|
962
|
+
if (this.previousSubmissionId && !await this.db.get("submissions", {
|
|
963
|
+
id: this.previousSubmissionId,
|
|
964
|
+
tenant_id: this.tenantId,
|
|
965
|
+
learner_id: this.learnerId,
|
|
966
|
+
lesson_id: this.lessonId,
|
|
967
|
+
progress_id: this.progressId
|
|
968
|
+
})) throw new Error("Submission predecessor must match its tenant progress");
|
|
745
969
|
if (!await this.db.get("lesson_progress", {
|
|
746
970
|
id: this.progressId,
|
|
747
971
|
tenant_id: this.tenantId,
|
|
@@ -756,10 +980,47 @@ __decorateClass$1([tenantId()], Submission.prototype, "tenantId", 2);
|
|
|
756
980
|
__decorateClass$1([foreignKey("Learner", { required: true })], Submission.prototype, "learnerId", 2);
|
|
757
981
|
__decorateClass$1([foreignKey("Lesson", { required: true })], Submission.prototype, "lessonId", 2);
|
|
758
982
|
__decorateClass$1([foreignKey("LessonProgress", { required: true })], Submission.prototype, "progressId", 2);
|
|
983
|
+
__decorateClass$1([field({ required: true })], Submission.prototype, "kind", 2);
|
|
984
|
+
__decorateClass$1([foreignKey("Submission", { nullable: true })], Submission.prototype, "previousSubmissionId", 2);
|
|
985
|
+
__decorateClass$1([field({
|
|
986
|
+
type: "text",
|
|
987
|
+
nullable: true
|
|
988
|
+
})], Submission.prototype, "intakeItemId", 2);
|
|
759
989
|
Submission = __decorateClass$1([TenantScoped({ mode: "required" }), smrt({
|
|
760
990
|
tableStrategy: "sti",
|
|
761
991
|
...closed
|
|
762
992
|
})], Submission);
|
|
993
|
+
var SubmissionIntakeBinding = class extends SmrtObject {
|
|
994
|
+
tenantId = "";
|
|
995
|
+
intakeItemId = "";
|
|
996
|
+
submissionId = "";
|
|
997
|
+
constructor(options = {}) {
|
|
998
|
+
super(options);
|
|
999
|
+
applyOptions(this, options);
|
|
1000
|
+
}
|
|
1001
|
+
async save() {
|
|
1002
|
+
if (!this.tenantId?.trim() || !this.intakeItemId?.trim()) throw new Error("Submission intake binding requires tenant and item");
|
|
1003
|
+
if (this.intakeItemId.length > 200) throw new Error("Submission intake item id is invalid");
|
|
1004
|
+
if (!await this.db.get("submissions", {
|
|
1005
|
+
id: this.submissionId,
|
|
1006
|
+
tenant_id: this.tenantId,
|
|
1007
|
+
intake_item_id: this.intakeItemId
|
|
1008
|
+
})) throw new Error("Submission intake binding must match its submission");
|
|
1009
|
+
return super.save();
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
__publicField$1(SubmissionIntakeBinding, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1013
|
+
__decorateClass$1([tenantId()], SubmissionIntakeBinding.prototype, "tenantId", 2);
|
|
1014
|
+
__decorateClass$1([field({
|
|
1015
|
+
required: true,
|
|
1016
|
+
type: "text"
|
|
1017
|
+
})], SubmissionIntakeBinding.prototype, "intakeItemId", 2);
|
|
1018
|
+
__decorateClass$1([foreignKey("Submission", { required: true })], SubmissionIntakeBinding.prototype, "submissionId", 2);
|
|
1019
|
+
SubmissionIntakeBinding = __decorateClass$1([TenantScoped({ mode: "required" }), smrt({
|
|
1020
|
+
tableName: "submission_intake_bindings",
|
|
1021
|
+
...closed,
|
|
1022
|
+
conflictColumns: ["tenant_id", "intake_item_id"]
|
|
1023
|
+
})], SubmissionIntakeBinding);
|
|
763
1024
|
var ScannedSubmission = class extends Submission {
|
|
764
1025
|
pageCount = 0;
|
|
765
1026
|
ocrText = "";
|
|
@@ -970,6 +1231,9 @@ var LessonProgressCollection = class extends SmrtCollection {
|
|
|
970
1231
|
var SubmissionCollection = class extends SmrtCollection {
|
|
971
1232
|
static _itemClass = Submission;
|
|
972
1233
|
};
|
|
1234
|
+
var SubmissionIntakeBindingCollection = class extends SmrtCollection {
|
|
1235
|
+
static _itemClass = SubmissionIntakeBinding;
|
|
1236
|
+
};
|
|
973
1237
|
var ScannedSubmissionCollection = class extends SmrtCollection {
|
|
974
1238
|
static _itemClass = ScannedSubmission;
|
|
975
1239
|
};
|
|
@@ -988,6 +1252,441 @@ var SubmissionAssetCollection = class extends SmrtJunction {
|
|
|
988
1252
|
rightField = "assetId";
|
|
989
1253
|
};
|
|
990
1254
|
//#endregion
|
|
1255
|
+
//#region src/catalog-materialization-service.ts
|
|
1256
|
+
async function identity$1(...parts) {
|
|
1257
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(["smrt.education.catalog.v1", ...parts])));
|
|
1258
|
+
const hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1259
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
1260
|
+
}
|
|
1261
|
+
function isPrintableAcquisitionFormat(format) {
|
|
1262
|
+
return format === "pdf" || format === "application/pdf";
|
|
1263
|
+
}
|
|
1264
|
+
var CatalogMaterializationService = class {
|
|
1265
|
+
constructor(db, store, storage, learner, authorize, policy = new CatalogPolicy()) {
|
|
1266
|
+
this.db = db;
|
|
1267
|
+
this.store = store;
|
|
1268
|
+
this.storage = storage;
|
|
1269
|
+
this.learner = learner;
|
|
1270
|
+
this.authorize = authorize;
|
|
1271
|
+
this.policy = policy;
|
|
1272
|
+
}
|
|
1273
|
+
db;
|
|
1274
|
+
store;
|
|
1275
|
+
storage;
|
|
1276
|
+
learner;
|
|
1277
|
+
authorize;
|
|
1278
|
+
policy;
|
|
1279
|
+
async materialize(request) {
|
|
1280
|
+
const active = getCurrentTenant();
|
|
1281
|
+
if (!request.tenantId?.trim() || !request.actorUserId?.trim() || active?.tenantId !== request.tenantId || active.userId !== request.actorUserId || active.superAdminBypass) throw new Error("Trusted active tenant and actor identity are required.");
|
|
1282
|
+
const authorization = await this.authorize({
|
|
1283
|
+
tenantId: request.tenantId,
|
|
1284
|
+
actorUserId: request.actorUserId,
|
|
1285
|
+
executor: this.db,
|
|
1286
|
+
action: "education.catalog.materialize"
|
|
1287
|
+
});
|
|
1288
|
+
if (!authorization.allowed || !authorization.roles.includes("guardian")) throw new Error("Active guardian authorization is required.");
|
|
1289
|
+
const actor = {
|
|
1290
|
+
tenantId: request.tenantId,
|
|
1291
|
+
actorUserId: request.actorUserId,
|
|
1292
|
+
roles: authorization.roles
|
|
1293
|
+
};
|
|
1294
|
+
const acquisition = await this.store.getAcquisition({
|
|
1295
|
+
tenantId: actor.tenantId,
|
|
1296
|
+
acquisitionId: request.acquisitionId
|
|
1297
|
+
});
|
|
1298
|
+
if (!acquisition || acquisition.tenantId !== actor.tenantId) throw new Error("Pinned tenant acquisition was not found.");
|
|
1299
|
+
const resource = await this.store.getResource(acquisition.resourceId);
|
|
1300
|
+
if (!resource || resource.revision !== acquisition.revision) throw new Error("Pinned catalog resource revision was not found.");
|
|
1301
|
+
if (resource.withdrawnAt) throw new Error("Withdrawn catalog resource cannot be materialized.");
|
|
1302
|
+
if (resource.visibility === "teacher") throw new Error("Teacher-only material cannot be materialized for learners.");
|
|
1303
|
+
const operation = isPrintableAcquisitionFormat(acquisition.format) ? "print" : "download";
|
|
1304
|
+
if (!this.policy.authorize(resource, operation, actor).allowed) throw new Error(`Current catalog license does not permit ${operation}.`);
|
|
1305
|
+
const subject = resource.subjects?.find((value) => value.trim()) ?? request.subject?.trim();
|
|
1306
|
+
if (!subject) throw new Error("Guardian subject classification is required.");
|
|
1307
|
+
const courseId = await identity$1(actor.tenantId, resource.providerId, resource.stableId);
|
|
1308
|
+
const sectionId = await identity$1(courseId, "materials");
|
|
1309
|
+
const lessonId = await identity$1(courseId, acquisition.revision, acquisition.format);
|
|
1310
|
+
const asset = await this.storage.ensurePrivateAsset({
|
|
1311
|
+
tenantId: actor.tenantId,
|
|
1312
|
+
actorUserId: actor.actorUserId,
|
|
1313
|
+
acquisition,
|
|
1314
|
+
name: request.lessonTitle?.trim() || resource.title
|
|
1315
|
+
});
|
|
1316
|
+
if (!asset.assetId) throw new Error("Materialized private asset is required.");
|
|
1317
|
+
if (!this.db.transaction) throw new Error("Catalog materialization requires a transaction.");
|
|
1318
|
+
await this.db.transaction(async (tx) => {
|
|
1319
|
+
const courses = await ExternalCourseCollection.create({ db: tx });
|
|
1320
|
+
const sections = await CourseSectionCollection.create({ db: tx });
|
|
1321
|
+
const readings = await ReadingCollection.create({ db: tx });
|
|
1322
|
+
const worksheets = await WorksheetCollection.create({ db: tx });
|
|
1323
|
+
const assets = await LessonAssetCollection.create({ db: tx });
|
|
1324
|
+
const course = await courses.get(courseId);
|
|
1325
|
+
if (course) {
|
|
1326
|
+
if (course.tenantId !== actor.tenantId || course.sourceKey !== resource.providerId || course.sourceRef !== resource.stableId || course.licenseKind !== "personal-use-copy") throw new Error("Existing materialized course ownership cannot change.");
|
|
1327
|
+
} else await courses.create({
|
|
1328
|
+
id: courseId,
|
|
1329
|
+
tenantId: actor.tenantId,
|
|
1330
|
+
title: request.courseTitle?.trim() || resource.title,
|
|
1331
|
+
subject,
|
|
1332
|
+
sourceKey: resource.providerId,
|
|
1333
|
+
sourceRef: resource.stableId,
|
|
1334
|
+
sourceUrl: resource.canonicalUrl,
|
|
1335
|
+
license: resource.licenseEvidence.identifier ?? "",
|
|
1336
|
+
licenseKind: "personal-use-copy",
|
|
1337
|
+
licenseNote: [resource.licenseEvidence.attribution, acquisition.sha256].filter(Boolean).join("\n"),
|
|
1338
|
+
status: "draft"
|
|
1339
|
+
});
|
|
1340
|
+
if (!await sections.get(sectionId)) await sections.create({
|
|
1341
|
+
id: sectionId,
|
|
1342
|
+
tenantId: actor.tenantId,
|
|
1343
|
+
courseId,
|
|
1344
|
+
title: "Materials",
|
|
1345
|
+
sortOrder: 0
|
|
1346
|
+
});
|
|
1347
|
+
const printable = isPrintableAcquisitionFormat(acquisition.format);
|
|
1348
|
+
const lessons = printable ? worksheets : readings;
|
|
1349
|
+
if (!await lessons.get(lessonId)) await lessons.create({
|
|
1350
|
+
id: lessonId,
|
|
1351
|
+
tenantId: actor.tenantId,
|
|
1352
|
+
sectionId,
|
|
1353
|
+
title: request.lessonTitle?.trim() || resource.title,
|
|
1354
|
+
externalRef: `${resource.stableId}@${acquisition.revision}`,
|
|
1355
|
+
externalUrl: resource.canonicalUrl,
|
|
1356
|
+
sortOrder: 0
|
|
1357
|
+
});
|
|
1358
|
+
if (!await assets.get({
|
|
1359
|
+
lessonId,
|
|
1360
|
+
assetId: asset.assetId,
|
|
1361
|
+
tenantId: actor.tenantId
|
|
1362
|
+
})) await assets.create({
|
|
1363
|
+
tenantId: actor.tenantId,
|
|
1364
|
+
lessonId,
|
|
1365
|
+
assetId: asset.assetId,
|
|
1366
|
+
relationship: printable ? "printable" : "attachment"
|
|
1367
|
+
});
|
|
1368
|
+
});
|
|
1369
|
+
let enrollmentId;
|
|
1370
|
+
if (request.curriculumId || request.learnerId) {
|
|
1371
|
+
if (!this.learner || !request.curriculumId || !request.learnerId) throw new Error("Learner, curriculum, and enrollment service are required.");
|
|
1372
|
+
enrollmentId = (await this.learner.enroll({
|
|
1373
|
+
tenantId: actor.tenantId,
|
|
1374
|
+
actorUserId: actor.actorUserId,
|
|
1375
|
+
learnerId: request.learnerId,
|
|
1376
|
+
...request.guardianRelationshipId ? { guardianRelationshipId: request.guardianRelationshipId } : {},
|
|
1377
|
+
curriculumId: request.curriculumId,
|
|
1378
|
+
courseId
|
|
1379
|
+
})).id;
|
|
1380
|
+
}
|
|
1381
|
+
return {
|
|
1382
|
+
courseId,
|
|
1383
|
+
lessonId,
|
|
1384
|
+
assetId: asset.assetId,
|
|
1385
|
+
revision: acquisition.revision,
|
|
1386
|
+
sha256: acquisition.sha256,
|
|
1387
|
+
...enrollmentId ? { enrollmentId } : {}
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
//#endregion
|
|
1392
|
+
//#region src/catalog-read-service.ts
|
|
1393
|
+
var CatalogReadService = class {
|
|
1394
|
+
constructor(store, policy = new CatalogPolicy()) {
|
|
1395
|
+
this.store = store;
|
|
1396
|
+
this.policy = policy;
|
|
1397
|
+
}
|
|
1398
|
+
store;
|
|
1399
|
+
policy;
|
|
1400
|
+
async search(actor, filter = {}) {
|
|
1401
|
+
if (!actor.tenantId.trim() || !actor.actorUserId.trim()) throw new Error("Trusted tenant and actor identity are required.");
|
|
1402
|
+
return (await this.store.search(filter)).filter((record) => this.policy.canView(record, actor)).map((record) => this.view(record));
|
|
1403
|
+
}
|
|
1404
|
+
view(record) {
|
|
1405
|
+
const { licenseEvidence } = record;
|
|
1406
|
+
return {
|
|
1407
|
+
id: record.id,
|
|
1408
|
+
providerId: record.providerId,
|
|
1409
|
+
stableId: record.stableId,
|
|
1410
|
+
revision: record.revision,
|
|
1411
|
+
canonicalUrl: record.canonicalUrl,
|
|
1412
|
+
title: record.title,
|
|
1413
|
+
...record.suppliedDescription ? { suppliedDescription: record.suppliedDescription } : {},
|
|
1414
|
+
...record.author ? { author: record.author } : {},
|
|
1415
|
+
...record.subjects ? { subjects: [...record.subjects] } : {},
|
|
1416
|
+
...record.gradeRange ? { gradeRange: { ...record.gradeRange } } : {},
|
|
1417
|
+
...record.language ? { language: record.language } : {},
|
|
1418
|
+
...record.resourceType ? { resourceType: record.resourceType } : {},
|
|
1419
|
+
...record.parentStableId ? { parentStableId: record.parentStableId } : {},
|
|
1420
|
+
...record.availableFormats ? { availableFormats: [...record.availableFormats] } : {},
|
|
1421
|
+
license: {
|
|
1422
|
+
state: licenseEvidence.state,
|
|
1423
|
+
...licenseEvidence.identifier ? { identifier: licenseEvidence.identifier } : {},
|
|
1424
|
+
...licenseEvidence.version ? { version: licenseEvidence.version } : {},
|
|
1425
|
+
...licenseEvidence.attribution ? { attribution: licenseEvidence.attribution } : {},
|
|
1426
|
+
...licenseEvidence.exceptions ? { exceptions: licenseEvidence.exceptions } : {},
|
|
1427
|
+
...licenseEvidence.retrievedAt ? { retrievedAt: licenseEvidence.retrievedAt } : {}
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
//#endregion
|
|
1433
|
+
//#region src/catalog-service.ts
|
|
1434
|
+
var CatalogAuthorizationError = class extends Error {
|
|
1435
|
+
constructor() {
|
|
1436
|
+
super("Education catalog access is not authorized.");
|
|
1437
|
+
this.name = "CatalogAuthorizationError";
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
function projectLesson(lesson, includeAnswerKeys) {
|
|
1441
|
+
if (includeAnswerKeys || !("answerUrl" in lesson)) return lesson;
|
|
1442
|
+
return Object.assign(Object.create(Object.getPrototypeOf(lesson)), lesson, { answerUrl: "" });
|
|
1443
|
+
}
|
|
1444
|
+
var CatalogService = class {
|
|
1445
|
+
constructor(courses, sections, lessons, assets, tenants, authorize) {
|
|
1446
|
+
this.courses = courses;
|
|
1447
|
+
this.sections = sections;
|
|
1448
|
+
this.lessons = lessons;
|
|
1449
|
+
this.assets = assets;
|
|
1450
|
+
this.tenants = tenants;
|
|
1451
|
+
this.authorize = authorize;
|
|
1452
|
+
}
|
|
1453
|
+
courses;
|
|
1454
|
+
sections;
|
|
1455
|
+
lessons;
|
|
1456
|
+
assets;
|
|
1457
|
+
tenants;
|
|
1458
|
+
authorize;
|
|
1459
|
+
async findVisible(request) {
|
|
1460
|
+
const { tenantId, actorUserId, includePrivate = false, options = {} } = request;
|
|
1461
|
+
if (!tenantId.trim() || !actorUserId.trim()) throw new CatalogAuthorizationError();
|
|
1462
|
+
const actor = {
|
|
1463
|
+
actorTenantId: tenantId,
|
|
1464
|
+
actorUserId,
|
|
1465
|
+
rootTenantId: tenantId
|
|
1466
|
+
};
|
|
1467
|
+
if (await this.authorize({
|
|
1468
|
+
...actor,
|
|
1469
|
+
action: "education.catalog.read"
|
|
1470
|
+
}) !== true) throw new CatalogAuthorizationError();
|
|
1471
|
+
if (includePrivate && await this.authorize({
|
|
1472
|
+
...actor,
|
|
1473
|
+
action: "education.catalog.read-private"
|
|
1474
|
+
}) !== true) throw new CatalogAuthorizationError();
|
|
1475
|
+
return this.tenants.withTenantCatalogReadScope({
|
|
1476
|
+
collections: [
|
|
1477
|
+
"@happyvertical/smrt-education:Course",
|
|
1478
|
+
"@happyvertical/smrt-education:CourseSection",
|
|
1479
|
+
"@happyvertical/smrt-education:Lesson",
|
|
1480
|
+
"@happyvertical/smrt-education:LessonAsset"
|
|
1481
|
+
],
|
|
1482
|
+
authorize: async (scope2) => scope2.mode === "catalog" && scope2.actorTenantId === tenantId && scope2.actorUserId === actorUserId && scope2.rootTenantId === tenantId
|
|
1483
|
+
}, async () => this.courses.list({
|
|
1484
|
+
...options,
|
|
1485
|
+
where: [
|
|
1486
|
+
[{ "licenseKind !=": "personal-use-copy" }],
|
|
1487
|
+
[{ licenseKind: null }],
|
|
1488
|
+
...includePrivate ? [[{
|
|
1489
|
+
licenseKind: "personal-use-copy",
|
|
1490
|
+
tenantId
|
|
1491
|
+
}]] : []
|
|
1492
|
+
]
|
|
1493
|
+
}));
|
|
1494
|
+
}
|
|
1495
|
+
/** Project one already-visible course without exposing answer-key assets by default. */
|
|
1496
|
+
async projectCourse(request) {
|
|
1497
|
+
const course = (await this.findVisible(request)).find((item) => item.id === request.courseId);
|
|
1498
|
+
if (!course?.id) return null;
|
|
1499
|
+
const includeAnswerKeys = request.includeAnswerKeys === true;
|
|
1500
|
+
const actor = {
|
|
1501
|
+
actorTenantId: request.tenantId,
|
|
1502
|
+
actorUserId: request.actorUserId,
|
|
1503
|
+
rootTenantId: request.tenantId
|
|
1504
|
+
};
|
|
1505
|
+
if (includeAnswerKeys && await this.authorize({
|
|
1506
|
+
...actor,
|
|
1507
|
+
action: "education.catalog.answer-key.read"
|
|
1508
|
+
}) !== true) throw new CatalogAuthorizationError();
|
|
1509
|
+
return this.tenants.withTenantCatalogReadScope({
|
|
1510
|
+
collections: [
|
|
1511
|
+
"@happyvertical/smrt-education:CourseSection",
|
|
1512
|
+
"@happyvertical/smrt-education:Lesson",
|
|
1513
|
+
"@happyvertical/smrt-education:LessonAsset"
|
|
1514
|
+
],
|
|
1515
|
+
authorize: async (scope) => scope.mode === "catalog" && scope.actorTenantId === request.tenantId && scope.actorUserId === request.actorUserId
|
|
1516
|
+
}, async () => {
|
|
1517
|
+
const sections = await this.sections.list({
|
|
1518
|
+
where: { courseId: course.id },
|
|
1519
|
+
orderBy: ["sortOrder ASC", "id ASC"]
|
|
1520
|
+
});
|
|
1521
|
+
const result = [];
|
|
1522
|
+
for (const section of sections) {
|
|
1523
|
+
if (section.tenantId !== course.tenantId) continue;
|
|
1524
|
+
const lessons = await this.lessons.list({
|
|
1525
|
+
where: { sectionId: section.id },
|
|
1526
|
+
orderBy: ["sortOrder ASC", "id ASC"]
|
|
1527
|
+
});
|
|
1528
|
+
result.push({
|
|
1529
|
+
section,
|
|
1530
|
+
lessons: await Promise.all(lessons.filter((lesson) => lesson.tenantId === course.tenantId).map(async (lesson) => ({
|
|
1531
|
+
lesson: projectLesson(lesson, includeAnswerKeys),
|
|
1532
|
+
assets: (await this.assets.byLeft(String(lesson.id))).filter((asset) => asset.tenantId === course.tenantId && (includeAnswerKeys || asset.relationship !== "answer-key"))
|
|
1533
|
+
})))
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
return {
|
|
1537
|
+
course,
|
|
1538
|
+
sections: result
|
|
1539
|
+
};
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
//#endregion
|
|
1544
|
+
//#region src/catalog-sync-service.ts
|
|
1545
|
+
function failure(error) {
|
|
1546
|
+
const value = error;
|
|
1547
|
+
return {
|
|
1548
|
+
code: String(value?.code ?? "enumeration-failed"),
|
|
1549
|
+
message: String(value?.message ?? error),
|
|
1550
|
+
...typeof value?.retryAfterMs === "number" ? { retryAfterMs: value.retryAfterMs } : {}
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
function newRunId() {
|
|
1554
|
+
return globalThis.crypto.randomUUID();
|
|
1555
|
+
}
|
|
1556
|
+
var CatalogSyncService = class {
|
|
1557
|
+
constructor(store, retries = 2, maxPages = 100) {
|
|
1558
|
+
this.store = store;
|
|
1559
|
+
this.retries = retries;
|
|
1560
|
+
this.maxPages = maxPages;
|
|
1561
|
+
}
|
|
1562
|
+
store;
|
|
1563
|
+
retries;
|
|
1564
|
+
maxPages;
|
|
1565
|
+
async sync(providerId, adapter) {
|
|
1566
|
+
if (!providerId.trim()) throw new Error("A provider ID is required.");
|
|
1567
|
+
const saved = await this.store.getCheckpoint(providerId);
|
|
1568
|
+
const resuming = saved?.complete === false;
|
|
1569
|
+
const runId = resuming ? saved.runId ?? newRunId() : newRunId();
|
|
1570
|
+
let checkpoint = {
|
|
1571
|
+
providerId,
|
|
1572
|
+
cursor: resuming ? saved?.cursor : void 0,
|
|
1573
|
+
complete: false,
|
|
1574
|
+
importedCount: resuming ? saved?.importedCount ?? 0 : 0,
|
|
1575
|
+
failures: resuming ? saved?.failures ?? [] : [],
|
|
1576
|
+
runId
|
|
1577
|
+
};
|
|
1578
|
+
let withdrawnCount = 0;
|
|
1579
|
+
const cursors = /* @__PURE__ */ new Set();
|
|
1580
|
+
if (checkpoint.cursor) cursors.add(checkpoint.cursor);
|
|
1581
|
+
for (let pageCount = 0; pageCount < this.maxPages; pageCount++) {
|
|
1582
|
+
let page;
|
|
1583
|
+
let lastFailure;
|
|
1584
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) try {
|
|
1585
|
+
page = await adapter.enumerate({
|
|
1586
|
+
cursor: checkpoint.cursor,
|
|
1587
|
+
checkpoint
|
|
1588
|
+
});
|
|
1589
|
+
break;
|
|
1590
|
+
} catch (error) {
|
|
1591
|
+
lastFailure = failure(error);
|
|
1592
|
+
}
|
|
1593
|
+
if (!page) {
|
|
1594
|
+
const terminalFailure = lastFailure ?? {
|
|
1595
|
+
code: "enumeration-failed",
|
|
1596
|
+
message: "Adapter returned no catalog page."
|
|
1597
|
+
};
|
|
1598
|
+
checkpoint = {
|
|
1599
|
+
...checkpoint,
|
|
1600
|
+
complete: false,
|
|
1601
|
+
failures: [...checkpoint.failures, terminalFailure]
|
|
1602
|
+
};
|
|
1603
|
+
await this.store.saveCheckpoint(checkpoint);
|
|
1604
|
+
return {
|
|
1605
|
+
...checkpoint,
|
|
1606
|
+
withdrawnCount
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
const previousCursor = checkpoint.cursor;
|
|
1610
|
+
if (page.complete && page.nextCursor) throw new Error("A terminal catalog page cannot have a next cursor.");
|
|
1611
|
+
if (!page.complete && !page.nextCursor && !page.paused && !page.exclusions?.length) throw new Error("A partial catalog page requires a next cursor.");
|
|
1612
|
+
for (const record of page.records) {
|
|
1613
|
+
if (record.providerId !== providerId || !record.stableId || !record.revision) throw new Error("Catalog records require matching provider, stable ID, and revision.");
|
|
1614
|
+
await this.store.upsert(record, runId);
|
|
1615
|
+
}
|
|
1616
|
+
checkpoint = {
|
|
1617
|
+
...checkpoint,
|
|
1618
|
+
cursor: page.nextCursor ?? (page.complete ? void 0 : previousCursor),
|
|
1619
|
+
complete: page.complete,
|
|
1620
|
+
paused: false,
|
|
1621
|
+
pauseReason: void 0,
|
|
1622
|
+
importedCount: checkpoint.importedCount + page.records.length,
|
|
1623
|
+
failures: [...checkpoint.failures, ...page.exclusions ?? []]
|
|
1624
|
+
};
|
|
1625
|
+
const pause = (reason, code) => {
|
|
1626
|
+
checkpoint = {
|
|
1627
|
+
...checkpoint,
|
|
1628
|
+
complete: false,
|
|
1629
|
+
paused: true,
|
|
1630
|
+
pauseReason: reason,
|
|
1631
|
+
failures: [...checkpoint.failures, {
|
|
1632
|
+
cursor: checkpoint.cursor,
|
|
1633
|
+
code,
|
|
1634
|
+
message: reason
|
|
1635
|
+
}]
|
|
1636
|
+
};
|
|
1637
|
+
return {
|
|
1638
|
+
...checkpoint,
|
|
1639
|
+
withdrawnCount
|
|
1640
|
+
};
|
|
1641
|
+
};
|
|
1642
|
+
if (page.paused || !page.nextCursor && !page.complete) {
|
|
1643
|
+
const report = pause(page.exclusions?.[0]?.message ?? "Catalog source is paused.", page.exclusions?.[0]?.code ?? "paused");
|
|
1644
|
+
await this.store.saveCheckpoint(checkpoint);
|
|
1645
|
+
return report;
|
|
1646
|
+
}
|
|
1647
|
+
if (!page.complete) {
|
|
1648
|
+
if (checkpoint.cursor && cursors.has(checkpoint.cursor)) {
|
|
1649
|
+
const report = pause("Catalog source did not advance its cursor.", "cursor-cycle");
|
|
1650
|
+
await this.store.saveCheckpoint(checkpoint);
|
|
1651
|
+
return report;
|
|
1652
|
+
}
|
|
1653
|
+
if (checkpoint.cursor) cursors.add(checkpoint.cursor);
|
|
1654
|
+
await this.store.saveCheckpoint(checkpoint);
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
withdrawnCount = await this.store.withdrawUnseen(providerId, runId);
|
|
1658
|
+
checkpoint = {
|
|
1659
|
+
providerId,
|
|
1660
|
+
complete: true,
|
|
1661
|
+
importedCount: checkpoint.importedCount,
|
|
1662
|
+
failures: checkpoint.failures,
|
|
1663
|
+
lastSuccessfulAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1664
|
+
};
|
|
1665
|
+
await this.store.saveCheckpoint(checkpoint);
|
|
1666
|
+
return {
|
|
1667
|
+
...checkpoint,
|
|
1668
|
+
withdrawnCount
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
checkpoint = {
|
|
1672
|
+
...checkpoint,
|
|
1673
|
+
complete: false,
|
|
1674
|
+
paused: true,
|
|
1675
|
+
pauseReason: "Catalog page limit reached; resume in a later invocation.",
|
|
1676
|
+
failures: [...checkpoint.failures, {
|
|
1677
|
+
cursor: checkpoint.cursor,
|
|
1678
|
+
code: "page-limit",
|
|
1679
|
+
message: "Catalog page limit reached; resume in a later invocation."
|
|
1680
|
+
}]
|
|
1681
|
+
};
|
|
1682
|
+
await this.store.saveCheckpoint(checkpoint);
|
|
1683
|
+
return {
|
|
1684
|
+
...checkpoint,
|
|
1685
|
+
withdrawnCount
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
//#endregion
|
|
991
1690
|
//#region src/course-source.ts
|
|
992
1691
|
var __defProp = Object.defineProperty;
|
|
993
1692
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -1060,6 +1759,579 @@ var CourseSourceCollection = class extends SmrtCollection {
|
|
|
1060
1759
|
static _itemClass = CourseSource;
|
|
1061
1760
|
};
|
|
1062
1761
|
//#endregion
|
|
1762
|
+
//#region src/sources/illustrative-mathematics-parser.ts
|
|
1763
|
+
var host = "im.kendallhunt.com";
|
|
1764
|
+
function text(value) {
|
|
1765
|
+
return value.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
1766
|
+
}
|
|
1767
|
+
function parseIllustrativeMathematicsK5Index(html, revision, pageUrl = "https://im.kendallhunt.com/k5/curriculum.html") {
|
|
1768
|
+
const index = new URL(pageUrl);
|
|
1769
|
+
if (index.protocol !== "https:" || index.hostname !== host) throw new Error("Illustrative Mathematics index must use the official HTTPS host.");
|
|
1770
|
+
if (!revision.trim()) throw new Error("Illustrative Mathematics revision is required.");
|
|
1771
|
+
const records = [];
|
|
1772
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1773
|
+
for (const match of html.matchAll(/<a\b([^>]*)>([\s\S]*?)<\/a\s*>/gi)) {
|
|
1774
|
+
const href = /\bhref\s*=\s*["']([^"']+)["']/i.exec(match[1])?.[1];
|
|
1775
|
+
if (!href) continue;
|
|
1776
|
+
const url = new URL(href, index);
|
|
1777
|
+
const grade = /^\/K5\/families\/(kindergarten|grade-[1-5])\/units\.html$/i.exec(url.pathname)?.[1];
|
|
1778
|
+
if (!grade || url.hostname !== host || seen.has(url.pathname)) continue;
|
|
1779
|
+
seen.add(url.pathname);
|
|
1780
|
+
const gradeLabel = grade === "kindergarten" ? "K" : grade.slice(6);
|
|
1781
|
+
records.push({
|
|
1782
|
+
providerId: "illustrative-mathematics",
|
|
1783
|
+
stableId: `k5-family-${grade.toLowerCase()}`,
|
|
1784
|
+
revision,
|
|
1785
|
+
canonicalUrl: url.toString(),
|
|
1786
|
+
title: `IM K-5 Math Grade ${gradeLabel}`,
|
|
1787
|
+
suppliedDescription: text(match[2]) || void 0,
|
|
1788
|
+
subjects: ["mathematics"],
|
|
1789
|
+
gradeRange: {
|
|
1790
|
+
min: gradeLabel,
|
|
1791
|
+
max: gradeLabel
|
|
1792
|
+
},
|
|
1793
|
+
language: "en",
|
|
1794
|
+
resourceType: "course",
|
|
1795
|
+
availableFormats: ["html"],
|
|
1796
|
+
licenseEvidence: {
|
|
1797
|
+
state: "verified",
|
|
1798
|
+
identifier: "CC BY",
|
|
1799
|
+
version: "4.0",
|
|
1800
|
+
sourceUrl: "https://creativecommons.org/licenses/by/4.0/",
|
|
1801
|
+
retrievedAt: revision,
|
|
1802
|
+
permittedOperations: [
|
|
1803
|
+
"link",
|
|
1804
|
+
"adapt",
|
|
1805
|
+
"redistribute",
|
|
1806
|
+
"commercial-use"
|
|
1807
|
+
]
|
|
1808
|
+
},
|
|
1809
|
+
visibility: "catalog"
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
if (!records.length) throw new Error("Illustrative Mathematics K-5 index has no family-grade links.");
|
|
1813
|
+
return records;
|
|
1814
|
+
}
|
|
1815
|
+
//#endregion
|
|
1816
|
+
//#region src/sources/oer-commons-rss-parser.ts
|
|
1817
|
+
var maximumFeedBytes = 2e6;
|
|
1818
|
+
var namedEntities = {
|
|
1819
|
+
"&": "&",
|
|
1820
|
+
"'": "'",
|
|
1821
|
+
">": ">",
|
|
1822
|
+
"<": "<",
|
|
1823
|
+
""": "\""
|
|
1824
|
+
};
|
|
1825
|
+
function decode(value) {
|
|
1826
|
+
return value.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1").replace(/<[^>]+>/g, " ").replace(/&(?:amp|apos|gt|lt|quot);/g, (entity) => namedEntities[entity] ?? entity).replace(/\s+/g, " ").trim();
|
|
1827
|
+
}
|
|
1828
|
+
function element(xml, names) {
|
|
1829
|
+
for (const name of names) {
|
|
1830
|
+
const match = new RegExp(`<${name}\\b[^>]*>([\\s\\S]*?)<\\/${name}\\s*>`, "i").exec(xml);
|
|
1831
|
+
const value = match ? decode(match[1]) : "";
|
|
1832
|
+
if (value) return value;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
function elements(xml, names) {
|
|
1836
|
+
const values = [];
|
|
1837
|
+
for (const name of names) {
|
|
1838
|
+
const expression = new RegExp(`<${name}\\b[^>]*>([\\s\\S]*?)<\\/${name}\\s*>`, "gi");
|
|
1839
|
+
for (const match of xml.matchAll(expression)) {
|
|
1840
|
+
const value = decode(match[1]);
|
|
1841
|
+
if (value) values.push(value);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
return [...new Set(values)];
|
|
1845
|
+
}
|
|
1846
|
+
function https(value) {
|
|
1847
|
+
if (!value) return void 0;
|
|
1848
|
+
try {
|
|
1849
|
+
const url = new URL(value);
|
|
1850
|
+
return url.protocol === "https:" && !url.username && !url.password ? url.toString() : void 0;
|
|
1851
|
+
} catch {
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
function parseOerCommonsRssFeed(xml, revision) {
|
|
1856
|
+
if (new TextEncoder().encode(xml).byteLength > maximumFeedBytes) throw new Error("OER Commons RSS feed exceeds the 2 MB metadata limit.");
|
|
1857
|
+
if (/<!DOCTYPE|<!ENTITY/i.test(xml)) throw new Error("OER Commons RSS feed must not contain DTDs or entities.");
|
|
1858
|
+
if (!revision.trim()) throw new Error("OER Commons feed revision is required.");
|
|
1859
|
+
const records = [];
|
|
1860
|
+
const items = xml.match(/<item\b[^>]*>[\s\S]*?<\/item\s*>/gi) ?? [];
|
|
1861
|
+
for (const item of items) {
|
|
1862
|
+
const canonicalUrl = https(element(item, ["link", "dc:identifier"]));
|
|
1863
|
+
const stableId = element(item, ["guid", "dc:identifier"]) ?? canonicalUrl;
|
|
1864
|
+
const title = element(item, ["title", "dc:title"]);
|
|
1865
|
+
if (!canonicalUrl || !stableId || !title) continue;
|
|
1866
|
+
const rights = element(item, [
|
|
1867
|
+
"license",
|
|
1868
|
+
"dc:rights",
|
|
1869
|
+
"rights"
|
|
1870
|
+
]);
|
|
1871
|
+
const licenseUrl = https(rights);
|
|
1872
|
+
records.push({
|
|
1873
|
+
providerId: "oer-commons",
|
|
1874
|
+
stableId,
|
|
1875
|
+
revision: element(item, ["dc:date", "pubDate"]) ?? revision,
|
|
1876
|
+
canonicalUrl,
|
|
1877
|
+
title,
|
|
1878
|
+
suppliedDescription: element(item, ["description", "dc:description"]),
|
|
1879
|
+
author: element(item, ["dc:creator", "author"]),
|
|
1880
|
+
subjects: elements(item, ["category", "dc:subject"]),
|
|
1881
|
+
language: element(item, ["dc:language", "language"]),
|
|
1882
|
+
resourceType: element(item, ["dc:type"]) ?? "resource",
|
|
1883
|
+
availableFormats: elements(item, ["dc:format"]),
|
|
1884
|
+
licenseEvidence: rights ? {
|
|
1885
|
+
state: "unknown",
|
|
1886
|
+
identifier: rights,
|
|
1887
|
+
...licenseUrl ? { sourceUrl: licenseUrl } : {},
|
|
1888
|
+
retrievedAt: revision
|
|
1889
|
+
} : {
|
|
1890
|
+
state: "unknown",
|
|
1891
|
+
retrievedAt: revision
|
|
1892
|
+
},
|
|
1893
|
+
visibility: "catalog"
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
if (!records.length) throw new Error("OER Commons RSS feed has no complete metadata items.");
|
|
1897
|
+
return records;
|
|
1898
|
+
}
|
|
1899
|
+
//#endregion
|
|
1900
|
+
//#region src/hearth-catalog-providers.ts
|
|
1901
|
+
var DEFAULT_METADATA_TIMEOUT_MS = 1e4;
|
|
1902
|
+
var providerFeedRequirements = [
|
|
1903
|
+
{
|
|
1904
|
+
providerId: "illustrative-mathematics",
|
|
1905
|
+
requirement: "A provider-approved, paginated metadata feed plus written catalog-harvesting permission."
|
|
1906
|
+
},
|
|
1907
|
+
{
|
|
1908
|
+
providerId: "ck12",
|
|
1909
|
+
requirement: "CK-12-approved catalog-feed access and credentials; ordinary web access is not an enumeration API."
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
providerId: "oer-commons",
|
|
1913
|
+
requirement: "OER Commons-approved paginated metadata-feed access with each record’s publisher and license fields."
|
|
1914
|
+
}
|
|
1915
|
+
];
|
|
1916
|
+
function unavailablePage(providerId) {
|
|
1917
|
+
return {
|
|
1918
|
+
records: [],
|
|
1919
|
+
complete: false,
|
|
1920
|
+
paused: true,
|
|
1921
|
+
exclusions: [{
|
|
1922
|
+
code: "provider_feed_not_configured",
|
|
1923
|
+
message: providerFeedRequirements.find((entry) => entry.providerId === providerId)?.requirement ?? "Provider feed is not configured."
|
|
1924
|
+
}]
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
function retryAfter(headers) {
|
|
1928
|
+
const value = headers.get("retry-after");
|
|
1929
|
+
if (!value) return void 0;
|
|
1930
|
+
const seconds = Number(value);
|
|
1931
|
+
return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1e3 : void 0;
|
|
1932
|
+
}
|
|
1933
|
+
function httpsUrl(value, provider) {
|
|
1934
|
+
let url;
|
|
1935
|
+
try {
|
|
1936
|
+
url = new URL(value);
|
|
1937
|
+
} catch {
|
|
1938
|
+
throw new Error(`${provider} metadata URL is invalid.`);
|
|
1939
|
+
}
|
|
1940
|
+
if (url.protocol !== "https:" || url.username || url.password || url.port) throw new Error(`${provider} metadata URL must be credential-free HTTPS.`);
|
|
1941
|
+
return url.toString();
|
|
1942
|
+
}
|
|
1943
|
+
async function fetchMetadata(fetch, url, accept, timeoutMs = DEFAULT_METADATA_TIMEOUT_MS) {
|
|
1944
|
+
const controller = new AbortController();
|
|
1945
|
+
const bounded = async (operation) => {
|
|
1946
|
+
let timer;
|
|
1947
|
+
try {
|
|
1948
|
+
return await Promise.race([operation, new Promise((_resolve, reject) => {
|
|
1949
|
+
timer = setTimeout(() => {
|
|
1950
|
+
controller.abort();
|
|
1951
|
+
reject(/* @__PURE__ */ new Error("Provider metadata request timed out."));
|
|
1952
|
+
}, timeoutMs);
|
|
1953
|
+
})]);
|
|
1954
|
+
} finally {
|
|
1955
|
+
if (timer) clearTimeout(timer);
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
let response;
|
|
1959
|
+
try {
|
|
1960
|
+
response = await bounded(fetch(url, {
|
|
1961
|
+
redirect: "error",
|
|
1962
|
+
headers: { accept },
|
|
1963
|
+
signal: controller.signal
|
|
1964
|
+
}));
|
|
1965
|
+
} catch (error) {
|
|
1966
|
+
return {
|
|
1967
|
+
records: [],
|
|
1968
|
+
complete: false,
|
|
1969
|
+
paused: true,
|
|
1970
|
+
exclusions: [{
|
|
1971
|
+
code: "provider_feed_failed",
|
|
1972
|
+
message: error instanceof Error ? error.message : "Metadata request failed."
|
|
1973
|
+
}]
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
if (response.status === 429) return {
|
|
1977
|
+
records: [],
|
|
1978
|
+
complete: false,
|
|
1979
|
+
exclusions: [{
|
|
1980
|
+
code: "provider_rate_limited",
|
|
1981
|
+
message: "Provider metadata request was rate limited.",
|
|
1982
|
+
retryAfterMs: retryAfter(response.headers)
|
|
1983
|
+
}]
|
|
1984
|
+
};
|
|
1985
|
+
if (!response.ok) return {
|
|
1986
|
+
records: [],
|
|
1987
|
+
complete: false,
|
|
1988
|
+
exclusions: [{
|
|
1989
|
+
code: "provider_feed_failed",
|
|
1990
|
+
message: `Provider metadata request failed with HTTP ${response.status}.`
|
|
1991
|
+
}]
|
|
1992
|
+
};
|
|
1993
|
+
if (response.url !== url) return {
|
|
1994
|
+
records: [],
|
|
1995
|
+
complete: false,
|
|
1996
|
+
exclusions: [{
|
|
1997
|
+
code: "unexpected_provider_redirect",
|
|
1998
|
+
message: "Provider metadata response URL did not match the configured URL."
|
|
1999
|
+
}]
|
|
2000
|
+
};
|
|
2001
|
+
let body;
|
|
2002
|
+
try {
|
|
2003
|
+
body = await bounded(response.text());
|
|
2004
|
+
} catch (error) {
|
|
2005
|
+
return {
|
|
2006
|
+
records: [],
|
|
2007
|
+
complete: false,
|
|
2008
|
+
paused: true,
|
|
2009
|
+
exclusions: [{
|
|
2010
|
+
code: "provider_feed_failed",
|
|
2011
|
+
message: error instanceof Error ? error.message : "Metadata read failed."
|
|
2012
|
+
}]
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
if (new TextEncoder().encode(body).byteLength > 2e6) return {
|
|
2016
|
+
records: [],
|
|
2017
|
+
complete: false,
|
|
2018
|
+
exclusions: [{
|
|
2019
|
+
code: "provider_metadata_oversized",
|
|
2020
|
+
message: "Provider metadata response exceeds 2 MB."
|
|
2021
|
+
}]
|
|
2022
|
+
};
|
|
2023
|
+
const revision = response.headers.get("etag") ?? response.headers.get("last-modified");
|
|
2024
|
+
if (!revision) return {
|
|
2025
|
+
records: [],
|
|
2026
|
+
complete: false,
|
|
2027
|
+
exclusions: [{
|
|
2028
|
+
code: "provider_revision_unavailable",
|
|
2029
|
+
message: "Provider metadata response supplied no ETag or Last-Modified revision."
|
|
2030
|
+
}]
|
|
2031
|
+
};
|
|
2032
|
+
return {
|
|
2033
|
+
body,
|
|
2034
|
+
revision
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
function validRecord(providerId, record) {
|
|
2038
|
+
return record.providerId === providerId && record.stableId.trim().length > 0 && record.revision.trim().length > 0 && record.title.trim().length > 0 && /^https:\/\//.test(record.canonicalUrl) && typeof record.licenseEvidence.state === "string";
|
|
2039
|
+
}
|
|
2040
|
+
var AuthorizedCatalogFeedAdapter = class {
|
|
2041
|
+
constructor(providerId, feed) {
|
|
2042
|
+
this.providerId = providerId;
|
|
2043
|
+
this.feed = feed;
|
|
2044
|
+
}
|
|
2045
|
+
providerId;
|
|
2046
|
+
feed;
|
|
2047
|
+
async enumerate(input) {
|
|
2048
|
+
if (!this.feed) return unavailablePage(this.providerId);
|
|
2049
|
+
if (this.feed.providerId !== this.providerId || this.feed.termsAccepted !== true || !/^https:\/\//.test(this.feed.metadataFeedUrl)) return unavailablePage(this.providerId);
|
|
2050
|
+
try {
|
|
2051
|
+
const page = await this.feed.enumerate(input);
|
|
2052
|
+
const malformed = page.records.filter((record) => !validRecord(this.providerId, record));
|
|
2053
|
+
if (malformed.length) return {
|
|
2054
|
+
records: page.records.filter((record) => validRecord(this.providerId, record)),
|
|
2055
|
+
complete: false,
|
|
2056
|
+
nextCursor: input.cursor,
|
|
2057
|
+
exclusions: [...page.exclusions ?? [], {
|
|
2058
|
+
cursor: input.cursor,
|
|
2059
|
+
code: "invalid_provider_metadata",
|
|
2060
|
+
message: `${malformed.length} provider records lack required stable metadata or license evidence.`
|
|
2061
|
+
}]
|
|
2062
|
+
};
|
|
2063
|
+
if (page.complete && page.nextCursor) return {
|
|
2064
|
+
records: page.records,
|
|
2065
|
+
complete: false,
|
|
2066
|
+
nextCursor: page.nextCursor,
|
|
2067
|
+
exclusions: [...page.exclusions ?? [], {
|
|
2068
|
+
cursor: input.cursor,
|
|
2069
|
+
code: "contradictory_terminal_page",
|
|
2070
|
+
message: "A terminal provider page cannot also declare a next cursor."
|
|
2071
|
+
}]
|
|
2072
|
+
};
|
|
2073
|
+
return page;
|
|
2074
|
+
} catch (error) {
|
|
2075
|
+
return {
|
|
2076
|
+
records: [],
|
|
2077
|
+
complete: false,
|
|
2078
|
+
nextCursor: input.cursor,
|
|
2079
|
+
exclusions: [{
|
|
2080
|
+
cursor: input.cursor,
|
|
2081
|
+
code: "provider_feed_failed",
|
|
2082
|
+
message: error instanceof Error ? error.message : "Provider metadata feed failed without an error message."
|
|
2083
|
+
}]
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
function createIllustrativeMathematicsAdapter(feed) {
|
|
2089
|
+
return new AuthorizedCatalogFeedAdapter("illustrative-mathematics", feed);
|
|
2090
|
+
}
|
|
2091
|
+
function createPublicIllustrativeMathematicsK5Adapter(config) {
|
|
2092
|
+
return { async enumerate(input) {
|
|
2093
|
+
if (input.cursor) return {
|
|
2094
|
+
records: [],
|
|
2095
|
+
complete: false,
|
|
2096
|
+
exclusions: [{
|
|
2097
|
+
cursor: input.cursor,
|
|
2098
|
+
code: "unsupported_provider_cursor",
|
|
2099
|
+
message: "The public IM K-5 index has no pagination cursor."
|
|
2100
|
+
}]
|
|
2101
|
+
};
|
|
2102
|
+
const url = httpsUrl(config.indexUrl ?? "https://im.kendallhunt.com/k5/curriculum.html", "Illustrative Mathematics");
|
|
2103
|
+
const result = await fetchMetadata(config.fetch, url, "text/html,application/xhtml+xml", config.timeoutMs);
|
|
2104
|
+
if ("complete" in result) return result;
|
|
2105
|
+
try {
|
|
2106
|
+
return {
|
|
2107
|
+
records: parseIllustrativeMathematicsK5Index(result.body, result.revision, url),
|
|
2108
|
+
complete: false,
|
|
2109
|
+
exclusions: [{
|
|
2110
|
+
code: "partial_provider_coverage",
|
|
2111
|
+
message: "The public IM K-5 index is not a complete IM catalog feed."
|
|
2112
|
+
}]
|
|
2113
|
+
};
|
|
2114
|
+
} catch (error) {
|
|
2115
|
+
return {
|
|
2116
|
+
records: [],
|
|
2117
|
+
complete: false,
|
|
2118
|
+
exclusions: [{
|
|
2119
|
+
code: "provider_metadata_invalid",
|
|
2120
|
+
message: error instanceof Error ? error.message : "IM metadata parsing failed."
|
|
2121
|
+
}]
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
} };
|
|
2125
|
+
}
|
|
2126
|
+
function createCk12Adapter(feed) {
|
|
2127
|
+
return new AuthorizedCatalogFeedAdapter("ck12", feed);
|
|
2128
|
+
}
|
|
2129
|
+
function createOerCommonsAdapter(feed) {
|
|
2130
|
+
return new AuthorizedCatalogFeedAdapter("oer-commons", feed);
|
|
2131
|
+
}
|
|
2132
|
+
function createOerCommonsRssAdapter(config) {
|
|
2133
|
+
return { async enumerate(input) {
|
|
2134
|
+
if (input.cursor) return {
|
|
2135
|
+
records: [],
|
|
2136
|
+
complete: false,
|
|
2137
|
+
exclusions: [{
|
|
2138
|
+
cursor: input.cursor,
|
|
2139
|
+
code: "unsupported_provider_cursor",
|
|
2140
|
+
message: "Configured OER RSS/DC feeds are snapshot feeds without cursors."
|
|
2141
|
+
}]
|
|
2142
|
+
};
|
|
2143
|
+
const url = httpsUrl(config.feedUrl, "OER Commons");
|
|
2144
|
+
const result = await fetchMetadata(config.fetch, url, "application/rss+xml, application/xml, text/xml", config.timeoutMs);
|
|
2145
|
+
if ("complete" in result) return result;
|
|
2146
|
+
try {
|
|
2147
|
+
return {
|
|
2148
|
+
records: parseOerCommonsRssFeed(result.body, result.revision),
|
|
2149
|
+
complete: config.terminalSnapshot === true,
|
|
2150
|
+
...config.terminalSnapshot === true ? {} : { exclusions: [{
|
|
2151
|
+
code: "partial_provider_coverage",
|
|
2152
|
+
message: "Configured OER feed scope has not been confirmed as terminal."
|
|
2153
|
+
}] }
|
|
2154
|
+
};
|
|
2155
|
+
} catch (error) {
|
|
2156
|
+
return {
|
|
2157
|
+
records: [],
|
|
2158
|
+
complete: false,
|
|
2159
|
+
exclusions: [{
|
|
2160
|
+
code: "provider_metadata_invalid",
|
|
2161
|
+
message: error instanceof Error ? error.message : "OER RSS/DC parsing failed."
|
|
2162
|
+
}]
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2165
|
+
} };
|
|
2166
|
+
}
|
|
2167
|
+
//#endregion
|
|
2168
|
+
//#region src/hearth-easy-peasy-grade4.ts
|
|
2169
|
+
var easyPeasyGrade4Courses = [
|
|
2170
|
+
{
|
|
2171
|
+
courseRef: "grade-4-math",
|
|
2172
|
+
title: "Math 4",
|
|
2173
|
+
subject: "math",
|
|
2174
|
+
pageUrl: "https://allinonehomeschool.com/ep-math-4/",
|
|
2175
|
+
variant: "core",
|
|
2176
|
+
enrollmentMode: "individual"
|
|
2177
|
+
},
|
|
2178
|
+
{
|
|
2179
|
+
courseRef: "grade-4-reading",
|
|
2180
|
+
title: "Reading 4",
|
|
2181
|
+
subject: "reading",
|
|
2182
|
+
pageUrl: "https://allinonehomeschool.com/reading-4/",
|
|
2183
|
+
variant: "core",
|
|
2184
|
+
enrollmentMode: "individual"
|
|
2185
|
+
},
|
|
2186
|
+
{
|
|
2187
|
+
courseRef: "grade-4-language-arts",
|
|
2188
|
+
title: "Language Arts 4",
|
|
2189
|
+
subject: "language-arts",
|
|
2190
|
+
pageUrl: "https://allinonehomeschool.com/language-arts-4/",
|
|
2191
|
+
variant: "core",
|
|
2192
|
+
enrollmentMode: "individual"
|
|
2193
|
+
},
|
|
2194
|
+
{
|
|
2195
|
+
courseRef: "lower-elementary-theme-1",
|
|
2196
|
+
title: "Theme 1: biology and ancient history",
|
|
2197
|
+
subject: "shared-theme",
|
|
2198
|
+
pageUrl: "https://allinonehomeschool.com/history-and-science-themes/",
|
|
2199
|
+
variant: "shared-theme-choice",
|
|
2200
|
+
enrollmentMode: "choose-one-theme"
|
|
2201
|
+
},
|
|
2202
|
+
{
|
|
2203
|
+
courseRef: "lower-elementary-theme-2",
|
|
2204
|
+
title: "Theme 2: zoology and early American history",
|
|
2205
|
+
subject: "shared-theme",
|
|
2206
|
+
pageUrl: "https://allinonehomeschool.com/history-and-science-themes/",
|
|
2207
|
+
variant: "shared-theme-choice",
|
|
2208
|
+
enrollmentMode: "choose-one-theme"
|
|
2209
|
+
},
|
|
2210
|
+
{
|
|
2211
|
+
courseRef: "lower-elementary-theme-3",
|
|
2212
|
+
title: "Theme 3: earth science and geography/cultures",
|
|
2213
|
+
subject: "shared-theme",
|
|
2214
|
+
pageUrl: "https://allinonehomeschool.com/history-and-science-themes/",
|
|
2215
|
+
variant: "shared-theme-choice",
|
|
2216
|
+
enrollmentMode: "choose-one-theme"
|
|
2217
|
+
},
|
|
2218
|
+
{
|
|
2219
|
+
courseRef: "lower-elementary-theme-4",
|
|
2220
|
+
title: "Theme 4: physics/chemistry and modern history",
|
|
2221
|
+
subject: "shared-theme",
|
|
2222
|
+
pageUrl: "https://allinonehomeschool.com/history-and-science-themes/",
|
|
2223
|
+
variant: "shared-theme-choice",
|
|
2224
|
+
enrollmentMode: "choose-one-theme"
|
|
2225
|
+
},
|
|
2226
|
+
{
|
|
2227
|
+
courseRef: "lower-elementary-computer",
|
|
2228
|
+
title: "Computer: Level 4 placement",
|
|
2229
|
+
subject: "computer",
|
|
2230
|
+
pageUrl: "https://allinonehomeschool.com/computer-fourth-level/",
|
|
2231
|
+
variant: "once-weekly",
|
|
2232
|
+
enrollmentMode: "individual"
|
|
2233
|
+
},
|
|
2234
|
+
{
|
|
2235
|
+
courseRef: "lower-elementary-thinking",
|
|
2236
|
+
title: "Thinking",
|
|
2237
|
+
subject: "thinking",
|
|
2238
|
+
pageUrl: "https://allinonehomeschool.com/thinking-levels-4-6/",
|
|
2239
|
+
variant: "once-weekly",
|
|
2240
|
+
enrollmentMode: "individual"
|
|
2241
|
+
}
|
|
2242
|
+
];
|
|
2243
|
+
function grade4Courses(variant) {
|
|
2244
|
+
return variant ? easyPeasyGrade4Courses.filter((course) => course.variant === variant) : easyPeasyGrade4Courses;
|
|
2245
|
+
}
|
|
2246
|
+
function themeCourse(themeId, subject, title, pageUrl) {
|
|
2247
|
+
return {
|
|
2248
|
+
courseRef: `${themeId}-${subject}`,
|
|
2249
|
+
title,
|
|
2250
|
+
subject,
|
|
2251
|
+
pageUrl,
|
|
2252
|
+
variant: "shared-theme-choice",
|
|
2253
|
+
enrollmentMode: "individual"
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
var easyPeasyGrade4Themes = [
|
|
2257
|
+
{
|
|
2258
|
+
themeId: "theme-1",
|
|
2259
|
+
title: "Theme 1: biology and ancient history",
|
|
2260
|
+
courses: [
|
|
2261
|
+
themeCourse("theme-1", "science", "Science — Biology", "https://allinonehomeschool.com/science-year-1/"),
|
|
2262
|
+
themeCourse("theme-1", "history", "History — Ancient", "https://allinonehomeschool.com/history-year-1/"),
|
|
2263
|
+
themeCourse("theme-1", "bible", "Bible — Old Testament", "https://allinonehomeschool.com/bible-year-1-2/"),
|
|
2264
|
+
themeCourse("theme-1", "art", "Art — Ancient", "https://allinonehomeschool.com/art-year-1/"),
|
|
2265
|
+
themeCourse("theme-1", "music", "Music — Ancient", "https://allinonehomeschool.com/1908-wright-brothers-flight-demonstration/music-year-1/"),
|
|
2266
|
+
themeCourse("theme-1", "pe-health", "PE/Health — Strong and Healthy", "https://allinonehomeschool.com/pehealth-odds/")
|
|
2267
|
+
]
|
|
2268
|
+
},
|
|
2269
|
+
{
|
|
2270
|
+
themeId: "theme-2",
|
|
2271
|
+
title: "Theme 2: zoology and early American history",
|
|
2272
|
+
courses: [
|
|
2273
|
+
themeCourse("theme-2", "science", "Science — Zoology", "https://allinonehomeschool.com/zoology/"),
|
|
2274
|
+
themeCourse("theme-2", "history", "History — Early American", "https://allinonehomeschool.com/early-american-history/"),
|
|
2275
|
+
themeCourse("theme-2", "bible", "Bible — New Testament", "https://allinonehomeschool.com/bible-newtestament/"),
|
|
2276
|
+
themeCourse("theme-2", "art", "Art — Early American", "https://allinonehomeschool.com/art-early-american/"),
|
|
2277
|
+
themeCourse("theme-2", "music", "Music — Early American", "https://allinonehomeschool.com/music-year-2/"),
|
|
2278
|
+
themeCourse("theme-2", "pe-health", "PE/Health — Exercise and Nutrition", "https://allinonehomeschool.com/pe-health/")
|
|
2279
|
+
]
|
|
2280
|
+
},
|
|
2281
|
+
{
|
|
2282
|
+
themeId: "theme-3",
|
|
2283
|
+
title: "Theme 3: earth science and geography/cultures",
|
|
2284
|
+
courses: [
|
|
2285
|
+
themeCourse("theme-3", "science", "Science — Earth Science", "https://allinonehomeschool.com/science-earth-science/"),
|
|
2286
|
+
themeCourse("theme-3", "history", "History — Geography and Cultures", "https://allinonehomeschool.com/geography-and-cultures/"),
|
|
2287
|
+
themeCourse("theme-3", "bible", "Bible — Poetry", "https://allinonehomeschool.com/bible-geography-and-cultures/"),
|
|
2288
|
+
themeCourse("theme-3", "art", "Art — Geography and Cultures", "https://allinonehomeschool.com/art-geography-and-cultures/"),
|
|
2289
|
+
themeCourse("theme-3", "music", "Music — Geography and Cultures", "https://allinonehomeschool.com/individual-courses-of-study/music-geography-and-cultures/"),
|
|
2290
|
+
themeCourse("theme-3", "pe-health", "PE/Health — Strong and Healthy", "https://allinonehomeschool.com/pehealth-odds/")
|
|
2291
|
+
]
|
|
2292
|
+
},
|
|
2293
|
+
{
|
|
2294
|
+
themeId: "theme-4",
|
|
2295
|
+
title: "Theme 4: physics/chemistry and modern history",
|
|
2296
|
+
courses: [
|
|
2297
|
+
themeCourse("theme-4", "science", "Science — Physics and Chemistry", "https://allinonehomeschool.com/science-year-4/"),
|
|
2298
|
+
themeCourse("theme-4", "history", "History — Modern", "https://allinonehomeschool.com/history-year-4/"),
|
|
2299
|
+
themeCourse("theme-4", "bible", "Bible — Prophets", "https://allinonehomeschool.com/bible/"),
|
|
2300
|
+
themeCourse("theme-4", "art", "Art — Modern", "https://allinonehomeschool.com/art-year-4/"),
|
|
2301
|
+
themeCourse("theme-4", "music", "Music — Modern", "https://allinonehomeschool.com/music/"),
|
|
2302
|
+
themeCourse("theme-4", "pe-health", "PE/Health — Exercise and Nutrition", "https://allinonehomeschool.com/pe-health/")
|
|
2303
|
+
]
|
|
2304
|
+
}
|
|
2305
|
+
];
|
|
2306
|
+
function grade4Theme(themeId) {
|
|
2307
|
+
const theme = easyPeasyGrade4Themes.find((candidate) => candidate.themeId === themeId);
|
|
2308
|
+
if (!theme) throw new Error("Unknown Easy Peasy Grade 4 theme.");
|
|
2309
|
+
return theme;
|
|
2310
|
+
}
|
|
2311
|
+
async function importEasyPeasyGrade4Course(service, request) {
|
|
2312
|
+
const course = easyPeasyGrade4Courses.find((candidate) => candidate.courseRef === request.courseRef);
|
|
2313
|
+
if (!course) throw new Error("Unknown Easy Peasy Grade 4 course reference.");
|
|
2314
|
+
if (course.enrollmentMode === "choose-one-theme") throw new Error("Theme choices are catalog pointers; select an official constituent course before importing lessons.");
|
|
2315
|
+
return service.importCourse({
|
|
2316
|
+
...request,
|
|
2317
|
+
pageUrl: course.pageUrl,
|
|
2318
|
+
subject: course.subject
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
async function importEasyPeasyGrade4Theme(service, request) {
|
|
2322
|
+
const theme = grade4Theme(request.themeId);
|
|
2323
|
+
const results = await Promise.all(theme.courses.map((course) => service.importCourse({
|
|
2324
|
+
...request,
|
|
2325
|
+
courseRef: course.courseRef,
|
|
2326
|
+
pageUrl: course.pageUrl,
|
|
2327
|
+
subject: course.subject
|
|
2328
|
+
})));
|
|
2329
|
+
return {
|
|
2330
|
+
themeId: theme.themeId,
|
|
2331
|
+
courseIds: results.map((result) => result.courseId)
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
//#endregion
|
|
1063
2335
|
//#region src/learner-service.ts
|
|
1064
2336
|
var LearnerAuthorizationError = class extends Error {
|
|
1065
2337
|
constructor() {
|
|
@@ -1112,6 +2384,8 @@ var LearnerService = class LearnerService {
|
|
|
1112
2384
|
writeFailure;
|
|
1113
2385
|
writeTail = Promise.resolve();
|
|
1114
2386
|
postgres;
|
|
2387
|
+
/** A trusted host transaction; capture/storage is forbidden on this scope. */
|
|
2388
|
+
executorBorrowed = false;
|
|
1115
2389
|
/** Compose planning/start operations atomically using the callback's service. */
|
|
1116
2390
|
async withTransaction(operation) {
|
|
1117
2391
|
if (this.transactionState) {
|
|
@@ -1142,6 +2416,26 @@ var LearnerService = class LearnerService {
|
|
|
1142
2416
|
throw error;
|
|
1143
2417
|
});
|
|
1144
2418
|
}
|
|
2419
|
+
/**
|
|
2420
|
+
* Compose database-only education operations with a host-owned transaction.
|
|
2421
|
+
* The callback service is closed when it returns and never opens a nested unit.
|
|
2422
|
+
*/
|
|
2423
|
+
async withExecutor(executor, operation) {
|
|
2424
|
+
if (this.transactionState) throw new Error("Learner transaction scope is already active");
|
|
2425
|
+
const service = this.createTransactionService(executor);
|
|
2426
|
+
service.transactionState = "active";
|
|
2427
|
+
service.executorBorrowed = true;
|
|
2428
|
+
service.postgres = isPostgresDatabase(executor);
|
|
2429
|
+
try {
|
|
2430
|
+
const result = await operation(service);
|
|
2431
|
+
service.transactionState = "draining";
|
|
2432
|
+
await service.writeTail;
|
|
2433
|
+
if (service.writeFailure) throw service.writeFailure.error;
|
|
2434
|
+
return result;
|
|
2435
|
+
} finally {
|
|
2436
|
+
service.transactionState = "closed";
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
1145
2439
|
/** Internal subclass hook: preserve the concrete owning service in a transaction. */
|
|
1146
2440
|
createTransactionService(db) {
|
|
1147
2441
|
return new LearnerService(db, this.authorize, this.authorizeCatalog);
|
|
@@ -1207,6 +2501,7 @@ var LearnerService = class LearnerService {
|
|
|
1207
2501
|
].includes(action)) throw new LearnerAuthorizationError();
|
|
1208
2502
|
if (await this.authorize({
|
|
1209
2503
|
...request,
|
|
2504
|
+
executor: this.db,
|
|
1210
2505
|
action,
|
|
1211
2506
|
own,
|
|
1212
2507
|
relationshipTypeId
|
|
@@ -1225,6 +2520,138 @@ var LearnerService = class LearnerService {
|
|
|
1225
2520
|
settings: row.getSettings()
|
|
1226
2521
|
};
|
|
1227
2522
|
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Checks whether an active tenant member still needs a Learner row.
|
|
2525
|
+
* The host authorizer decides who may run this onboarding probe; no learner
|
|
2526
|
+
* metadata or guardian relationship is returned or created here.
|
|
2527
|
+
*/
|
|
2528
|
+
async getLearnerSetupStatus(request) {
|
|
2529
|
+
const context = getCurrentTenant();
|
|
2530
|
+
if (!context || !request.tenantId || !request.actorUserId || !request.learnerUserId || context.tenantId !== request.tenantId || context.userId !== request.actorUserId || context.superAdminBypass) throw new LearnerAuthorizationError();
|
|
2531
|
+
if (await this.authorize({
|
|
2532
|
+
tenantId: request.tenantId,
|
|
2533
|
+
actorUserId: request.actorUserId,
|
|
2534
|
+
learnerId: "",
|
|
2535
|
+
learnerUserId: request.learnerUserId,
|
|
2536
|
+
executor: this.db,
|
|
2537
|
+
action: "education.learner.setup",
|
|
2538
|
+
own: request.learnerUserId === request.actorUserId
|
|
2539
|
+
}) !== true) throw new LearnerAuthorizationError();
|
|
2540
|
+
if (!await this.db.get("memberships", {
|
|
2541
|
+
tenant_id: request.tenantId,
|
|
2542
|
+
user_id: request.actorUserId,
|
|
2543
|
+
status: "active"
|
|
2544
|
+
}) || !await this.db.get("memberships", {
|
|
2545
|
+
tenant_id: request.tenantId,
|
|
2546
|
+
user_id: request.learnerUserId,
|
|
2547
|
+
status: "active"
|
|
2548
|
+
}) || !await this.db.get("users", { id: request.learnerUserId })) throw new LearnerAuthorizationError();
|
|
2549
|
+
return { needsSetup: !await (await LearnerCollection.create({ db: this.db })).get({
|
|
2550
|
+
tenantId: request.tenantId,
|
|
2551
|
+
userId: request.learnerUserId
|
|
2552
|
+
}, { cache: false }) };
|
|
2553
|
+
}
|
|
2554
|
+
/** Resolve the authenticated learner identity without exposing collections. */
|
|
2555
|
+
async getOwnLearner(request) {
|
|
2556
|
+
const context = getCurrentTenant();
|
|
2557
|
+
if (!context || context.tenantId !== request.tenantId || context.userId !== request.actorUserId || context.superAdminBypass) throw new LearnerAuthorizationError();
|
|
2558
|
+
const row = await (await LearnerCollection.create({ db: this.db })).get({
|
|
2559
|
+
tenantId: request.tenantId,
|
|
2560
|
+
userId: request.actorUserId
|
|
2561
|
+
}, { cache: false });
|
|
2562
|
+
return row ? this.getLearner({
|
|
2563
|
+
...request,
|
|
2564
|
+
learnerId: String(row.id)
|
|
2565
|
+
}) : null;
|
|
2566
|
+
}
|
|
2567
|
+
/** Resolve a relationship-selected Person Profile through the normal guard. */
|
|
2568
|
+
async getLearnerForProfile(request) {
|
|
2569
|
+
const row = await (await LearnerCollection.create({ db: this.db })).get({
|
|
2570
|
+
tenantId: request.tenantId,
|
|
2571
|
+
profileId: request.learnerProfileId
|
|
2572
|
+
}, { cache: false });
|
|
2573
|
+
if (!row) throw new LearnerAuthorizationError();
|
|
2574
|
+
return this.getLearner({
|
|
2575
|
+
tenantId: request.tenantId,
|
|
2576
|
+
actorUserId: request.actorUserId,
|
|
2577
|
+
guardianRelationshipId: request.guardianRelationshipId,
|
|
2578
|
+
learnerId: String(row.id)
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Trusted host provisioning after it has created the User, Person Profile and
|
|
2583
|
+
* active Membership. It never creates credentials or relationship grants.
|
|
2584
|
+
*/
|
|
2585
|
+
async provisionLearner(request) {
|
|
2586
|
+
return this.mutate(async (service) => {
|
|
2587
|
+
const context = getCurrentTenant();
|
|
2588
|
+
if (!context || context.tenantId !== request.tenantId || context.userId !== request.actorUserId || context.superAdminBypass) throw new LearnerAuthorizationError();
|
|
2589
|
+
const actor = await service.db.get("users", { id: request.actorUserId });
|
|
2590
|
+
if (!actor || !await service.db.get("memberships", {
|
|
2591
|
+
tenant_id: request.tenantId,
|
|
2592
|
+
user_id: request.actorUserId,
|
|
2593
|
+
status: "active"
|
|
2594
|
+
}) || !await service.db.get("memberships", {
|
|
2595
|
+
tenant_id: request.tenantId,
|
|
2596
|
+
user_id: request.learnerUserId,
|
|
2597
|
+
status: "active"
|
|
2598
|
+
}) || !await service.db.get("users", {
|
|
2599
|
+
id: request.learnerUserId,
|
|
2600
|
+
profile_id: request.learnerProfileId
|
|
2601
|
+
}) || !await service.db.get("profiles", {
|
|
2602
|
+
id: request.learnerProfileId,
|
|
2603
|
+
_meta_type: "@happyvertical/smrt-profiles:Person"
|
|
2604
|
+
})) throw new LearnerAuthorizationError();
|
|
2605
|
+
const relationship = request.guardianRelationshipId && actor.profile_id ? await service.db.get("profile_relationships", {
|
|
2606
|
+
id: request.guardianRelationshipId,
|
|
2607
|
+
tenant_id: request.tenantId,
|
|
2608
|
+
from_profile_id: actor.profile_id,
|
|
2609
|
+
to_profile_id: request.learnerProfileId
|
|
2610
|
+
}) : null;
|
|
2611
|
+
if (!relationship || await service.authorize({
|
|
2612
|
+
...request,
|
|
2613
|
+
learnerId: "",
|
|
2614
|
+
executor: service.db,
|
|
2615
|
+
action: "education.learner.plan",
|
|
2616
|
+
own: false,
|
|
2617
|
+
relationshipTypeId: String(relationship.type_id)
|
|
2618
|
+
}) !== true) throw new LearnerAuthorizationError();
|
|
2619
|
+
const learners = await LearnerCollection.create({ db: service.db });
|
|
2620
|
+
let row = await learners.get({
|
|
2621
|
+
tenantId: request.tenantId,
|
|
2622
|
+
userId: request.learnerUserId
|
|
2623
|
+
}, { cache: false });
|
|
2624
|
+
if (row) {
|
|
2625
|
+
if (row.profileId !== request.learnerProfileId) throw new LearnerAuthorizationError();
|
|
2626
|
+
return {
|
|
2627
|
+
id: String(row.id),
|
|
2628
|
+
tenantId: row.tenantId,
|
|
2629
|
+
userId: row.userId,
|
|
2630
|
+
profileId: row.profileId,
|
|
2631
|
+
displayName: row.displayName,
|
|
2632
|
+
level: row.level,
|
|
2633
|
+
settings: row.getSettings()
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2636
|
+
row = await learners.create({
|
|
2637
|
+
tenantId: request.tenantId,
|
|
2638
|
+
userId: request.learnerUserId,
|
|
2639
|
+
profileId: request.learnerProfileId,
|
|
2640
|
+
displayName: request.displayName,
|
|
2641
|
+
level: request.level ?? 0,
|
|
2642
|
+
_insertOnly: true
|
|
2643
|
+
});
|
|
2644
|
+
return {
|
|
2645
|
+
id: String(row.id),
|
|
2646
|
+
tenantId: row.tenantId,
|
|
2647
|
+
userId: row.userId,
|
|
2648
|
+
profileId: row.profileId,
|
|
2649
|
+
displayName: row.displayName,
|
|
2650
|
+
level: row.level,
|
|
2651
|
+
settings: row.getSettings()
|
|
2652
|
+
};
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
1228
2655
|
async createCurriculum(request) {
|
|
1229
2656
|
return this.mutate(async (service) => {
|
|
1230
2657
|
const learner = await service.requireActor(request, "education.learner.plan");
|
|
@@ -1304,6 +2731,79 @@ var LearnerService = class LearnerService {
|
|
|
1304
2731
|
tenantId: request.tenantId
|
|
1305
2732
|
} })).filter((progress) => lessons.has(progress.lessonId)).map(progressView);
|
|
1306
2733
|
}
|
|
2734
|
+
/** All exact-tenant enrollments visible to this learner or authorized guardian. */
|
|
2735
|
+
async listEnrollments(request) {
|
|
2736
|
+
const learner = await this.requireActor(request, "education.learner.read");
|
|
2737
|
+
return (await (await EnrollmentCollection.create({ db: this.db })).list({
|
|
2738
|
+
where: {
|
|
2739
|
+
tenantId: request.tenantId,
|
|
2740
|
+
learnerId: String(learner.id)
|
|
2741
|
+
},
|
|
2742
|
+
orderBy: "sortOrder"
|
|
2743
|
+
})).map(enrollmentView);
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Projects the first unfinished lesson in each authorized enrollment. No
|
|
2747
|
+
* catalog assets, answer material, model handles, or source locators escape.
|
|
2748
|
+
*/
|
|
2749
|
+
async nextLessons(request) {
|
|
2750
|
+
const enrollments = request.enrollmentId ? [await this.requireEnrollment(request, "education.learner.read")] : await (await EnrollmentCollection.create({ db: this.db })).list({
|
|
2751
|
+
where: {
|
|
2752
|
+
tenantId: request.tenantId,
|
|
2753
|
+
learnerId: request.learnerId
|
|
2754
|
+
},
|
|
2755
|
+
orderBy: "sortOrder"
|
|
2756
|
+
});
|
|
2757
|
+
await this.requireActor(request, "education.learner.read");
|
|
2758
|
+
const result = [];
|
|
2759
|
+
for (const enrollment of enrollments) {
|
|
2760
|
+
const catalog = await this.catalog(request, enrollment.courseId);
|
|
2761
|
+
const progresses = await (await LessonProgressCollection.create({ db: this.db })).list({ where: {
|
|
2762
|
+
tenantId: request.tenantId,
|
|
2763
|
+
enrollmentId: String(enrollment.id),
|
|
2764
|
+
learnerId: request.learnerId
|
|
2765
|
+
} });
|
|
2766
|
+
const byLesson = new Map(progresses.map((row) => [row.lessonId, row]));
|
|
2767
|
+
const lesson = catalog.sections.flatMap((section) => section.lessons.map(({ lesson: lesson2 }) => lesson2)).find((candidate) => {
|
|
2768
|
+
const progress2 = byLesson.get(String(candidate.id));
|
|
2769
|
+
return !progress2 || !["complete", "skipped"].includes(progress2.status);
|
|
2770
|
+
});
|
|
2771
|
+
if (!lesson) continue;
|
|
2772
|
+
const progress = byLesson.get(String(lesson.id));
|
|
2773
|
+
result.push({
|
|
2774
|
+
enrollment: enrollmentView(enrollment),
|
|
2775
|
+
progress: progress ? progressView(progress) : null,
|
|
2776
|
+
lesson: {
|
|
2777
|
+
id: String(lesson.id),
|
|
2778
|
+
title: lesson.title,
|
|
2779
|
+
kind: lesson.constructor.name,
|
|
2780
|
+
requiresOnlineResource: catalog.course.modality === "online" || lesson.constructor.name === "Video"
|
|
2781
|
+
}
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
return result;
|
|
2785
|
+
}
|
|
2786
|
+
/** All lesson summaries for one authorized assignment/enrollment. */
|
|
2787
|
+
async assignmentLessons(request) {
|
|
2788
|
+
const enrollment = await this.requireEnrollment(request, "education.learner.read");
|
|
2789
|
+
const catalog = await this.catalog(request, enrollment.courseId);
|
|
2790
|
+
const progresses = await (await LessonProgressCollection.create({ db: this.db })).list({ where: {
|
|
2791
|
+
tenantId: request.tenantId,
|
|
2792
|
+
enrollmentId: String(enrollment.id),
|
|
2793
|
+
learnerId: request.learnerId
|
|
2794
|
+
} });
|
|
2795
|
+
const byLesson = new Map(progresses.map((row) => [row.lessonId, row]));
|
|
2796
|
+
return catalog.sections.flatMap((section) => section.lessons.map(({ lesson }) => ({
|
|
2797
|
+
enrollment: enrollmentView(enrollment),
|
|
2798
|
+
progress: byLesson.get(String(lesson.id)) ? progressView(byLesson.get(String(lesson.id))) : null,
|
|
2799
|
+
lesson: {
|
|
2800
|
+
id: String(lesson.id),
|
|
2801
|
+
title: lesson.title,
|
|
2802
|
+
kind: lesson.constructor.name,
|
|
2803
|
+
requiresOnlineResource: catalog.course.modality === "online" || lesson.constructor.name === "Video"
|
|
2804
|
+
}
|
|
2805
|
+
})));
|
|
2806
|
+
}
|
|
1307
2807
|
/** Start once. Caller-supplied learner/status/score/counter patches are never applied. */
|
|
1308
2808
|
async startLesson(request) {
|
|
1309
2809
|
return this.mutate(async (service) => {
|
|
@@ -1433,21 +2933,113 @@ function attribute(attributes, name) {
|
|
|
1433
2933
|
const match = new RegExp(`(?:^|[\\t\\n\\f\\r ])${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, "i").exec(attributes);
|
|
1434
2934
|
return match?.[1] ?? match?.[2] ?? match?.[3];
|
|
1435
2935
|
}
|
|
2936
|
+
function closingDivEnd(html, start) {
|
|
2937
|
+
const tags = /<\/?div\b[^>]*>/gi;
|
|
2938
|
+
tags.lastIndex = start;
|
|
2939
|
+
let depth = 1;
|
|
2940
|
+
for (let tag = tags.exec(html); tag; tag = tags.exec(html)) {
|
|
2941
|
+
if (/^<\/div\b/i.test(tag[0])) depth -= 1;
|
|
2942
|
+
else depth += 1;
|
|
2943
|
+
if (depth === 0) return tag.index + tag[0].length;
|
|
2944
|
+
}
|
|
2945
|
+
return start;
|
|
2946
|
+
}
|
|
1436
2947
|
function lessonHeadings(html) {
|
|
1437
|
-
const
|
|
1438
|
-
for (const match of html.matchAll(/<
|
|
2948
|
+
const containers = [];
|
|
2949
|
+
for (const match of html.matchAll(/<div\b([^>]*)>/gi)) {
|
|
2950
|
+
const id = attribute(match[1], "id");
|
|
2951
|
+
const marker = /^(day|week)(\d+)$/i.exec(id ?? "");
|
|
2952
|
+
if (!marker) continue;
|
|
2953
|
+
const number = Number.parseInt(marker[2], 10);
|
|
2954
|
+
if (!Number.isSafeInteger(number) || number < 1) throw new Error(`Easy Peasy daily container is malformed: ${id}`);
|
|
2955
|
+
const weekly = marker[1].toLowerCase() === "week";
|
|
2956
|
+
const index = match.index ?? 0;
|
|
2957
|
+
const end = index + match[0].length;
|
|
2958
|
+
const containerEnd = closingDivEnd(html, end);
|
|
2959
|
+
const body = html.slice(end, containerEnd);
|
|
2960
|
+
const level = /\blevels?\s+(\d+)\s*[-–]\s*(\d+)\b/i.exec(textContent(body));
|
|
2961
|
+
containers.push({
|
|
2962
|
+
index,
|
|
2963
|
+
end,
|
|
2964
|
+
text: weekly ? `Week ${number}` : `Lesson ${number}`,
|
|
2965
|
+
day: number,
|
|
2966
|
+
fragment: `${weekly ? "week" : "day"}${number}`,
|
|
2967
|
+
...weekly ? { title: `Week ${number}` } : {},
|
|
2968
|
+
containerEnd,
|
|
2969
|
+
...level ? { levelRange: [Number.parseInt(level[1], 10), Number.parseInt(level[2], 10)] } : {},
|
|
2970
|
+
hasLessonMarker: new RegExp(`\\bLessons?\\s+${number}(?:\\D|$)`, "i").test(textContent(body))
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2973
|
+
containers.sort((left, right) => left.index - right.index);
|
|
2974
|
+
const grade = 4;
|
|
2975
|
+
const gradeContainers = containers.filter((container) => !container.levelRange || container.levelRange[0] <= grade && grade <= container.levelRange[1]);
|
|
2976
|
+
const selectedContainers = [];
|
|
2977
|
+
for (const container of gradeContainers) {
|
|
2978
|
+
const peers = gradeContainers.filter((candidate) => candidate.day === container.day);
|
|
2979
|
+
if (peers[0] !== container) continue;
|
|
2980
|
+
if (peers.length === 1) selectedContainers.push(container);
|
|
2981
|
+
else {
|
|
2982
|
+
const marked = peers.filter((candidate) => candidate.hasLessonMarker);
|
|
2983
|
+
if (marked.length !== 1) throw new Error(`Easy Peasy lesson day is invalid or duplicated: ${container.day}`);
|
|
2984
|
+
selectedContainers.push(marked[0]);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
const isInDailyContainer = (index) => selectedContainers.some((container) => container.index <= index && index < (container.containerEnd ?? container.end));
|
|
2988
|
+
const isDetachedDailyMarker = (index, text) => {
|
|
2989
|
+
const day = /^lesson\s+(\d+)\b/i.exec(text)?.[1];
|
|
2990
|
+
if (!day) return false;
|
|
2991
|
+
return selectedContainers.some((container) => container.day === Number.parseInt(day, 10) && (container.containerEnd ?? container.end) <= index && !textContent(html.slice(container.containerEnd ?? container.end, index)));
|
|
2992
|
+
};
|
|
2993
|
+
const matches = [...selectedContainers];
|
|
2994
|
+
const directDivLessons = /<div\b[^>]*>\s*<(?:strong|b)\b[^>]*>([\s\S]*?)<\/(?:strong|b)\s*>/gi;
|
|
2995
|
+
const bareDivMarkers = [];
|
|
2996
|
+
for (const match of html.matchAll(directDivLessons)) {
|
|
2997
|
+
if (isInDailyContainer(match.index ?? 0)) continue;
|
|
1439
2998
|
const text = textContent(match[1]);
|
|
1440
|
-
|
|
2999
|
+
if (!/^lesson\b/i.test(text)) continue;
|
|
3000
|
+
if (isDetachedDailyMarker(match.index ?? 0, text)) continue;
|
|
3001
|
+
bareDivMarkers.push({
|
|
1441
3002
|
index: match.index ?? 0,
|
|
1442
3003
|
end: (match.index ?? 0) + match[0].length,
|
|
1443
3004
|
text
|
|
1444
3005
|
});
|
|
1445
3006
|
}
|
|
3007
|
+
if (bareDivMarkers.length > 1) matches.push(...bareDivMarkers);
|
|
3008
|
+
for (const match of html.matchAll(/<(?:strong|b)\b([^>]*)>([\s\S]*?)<\/(?:strong|b)\s*>/gi)) {
|
|
3009
|
+
if (isInDailyContainer(match.index ?? 0)) continue;
|
|
3010
|
+
const id = attribute(match[1], "id");
|
|
3011
|
+
const day = /^week(\d+)$/i.exec(id ?? "")?.[1];
|
|
3012
|
+
const text = textContent(match[2]);
|
|
3013
|
+
if (!day || !/^week\s+\d+\s*$/i.test(text)) continue;
|
|
3014
|
+
const number = Number.parseInt(day, 10);
|
|
3015
|
+
if (!Number.isSafeInteger(number) || number < 1) throw new Error(`Easy Peasy weekly marker is malformed: ${id}`);
|
|
3016
|
+
const statedMatch = /^week\s+(\d+)/i.exec(text);
|
|
3017
|
+
if (!statedMatch) continue;
|
|
3018
|
+
if (Number.parseInt(statedMatch[1], 10) !== number) throw new Error("Easy Peasy weekly container and marker disagree.");
|
|
3019
|
+
matches.push({
|
|
3020
|
+
index: match.index ?? 0,
|
|
3021
|
+
end: (match.index ?? 0) + match[0].length,
|
|
3022
|
+
text,
|
|
3023
|
+
day: number,
|
|
3024
|
+
fragment: `week${number}`,
|
|
3025
|
+
title: `Week ${number}`
|
|
3026
|
+
});
|
|
3027
|
+
}
|
|
3028
|
+
for (const match of html.matchAll(/<h[1-6]\b[^>]*>([\s\S]*?)<\/h[1-6]\s*>/gi)) {
|
|
3029
|
+
if (isInDailyContainer(match.index ?? 0)) continue;
|
|
3030
|
+
matches.push({
|
|
3031
|
+
index: match.index ?? 0,
|
|
3032
|
+
end: (match.index ?? 0) + match[0].length,
|
|
3033
|
+
text: textContent(match[1])
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
1446
3036
|
for (const block of html.matchAll(/<(p|li)\b[^>]*>([\s\S]*?)<\/\1\s*>/gi)) {
|
|
3037
|
+
if (isInDailyContainer(block.index ?? 0)) continue;
|
|
1447
3038
|
const bold = /<(?:strong|b)\b[^>]*>([\s\S]*?)<\/(?:strong|b)\s*>/i.exec(block[2]);
|
|
1448
3039
|
if (!bold) continue;
|
|
1449
3040
|
const text = textContent(bold[1]);
|
|
1450
3041
|
if (!/^lesson\b/i.test(text)) continue;
|
|
3042
|
+
if (isDetachedDailyMarker(block.index ?? 0, text)) continue;
|
|
1451
3043
|
const boldIndex = (block.index ?? 0) + block[0].indexOf(bold[0]);
|
|
1452
3044
|
matches.push({
|
|
1453
3045
|
index: block.index ?? 0,
|
|
@@ -1460,10 +3052,24 @@ function lessonHeadings(html) {
|
|
|
1460
3052
|
if (!/^lesson\b/i.test(textContent(marker[2]))) continue;
|
|
1461
3053
|
const index = marker.index ?? 0;
|
|
1462
3054
|
const end = index + marker[0].length;
|
|
1463
|
-
if (!sorted.some((heading) => /^lesson\b/i.test(heading.text) && (heading.index === index || heading.index <= index && end <= heading.end))) throw new Error("Easy Peasy lesson marker layout is unsupported.");
|
|
3055
|
+
if (!(isInDailyContainer(index) || isDetachedDailyMarker(index, textContent(marker[2])) || sorted.some((heading) => /^lesson\b/i.test(heading.text) && (heading.index === index || heading.index <= index && end <= heading.end)))) throw new Error("Easy Peasy lesson marker layout is unsupported.");
|
|
1464
3056
|
}
|
|
1465
3057
|
return sorted;
|
|
1466
3058
|
}
|
|
3059
|
+
function lessonMarkerText(segment) {
|
|
3060
|
+
for (const marker of segment.matchAll(/<(?:strong|b)\b[^>]*>([\s\S]*?)<\/(?:strong|b)\s*>/gi)) {
|
|
3061
|
+
const text = textContent(marker[1]);
|
|
3062
|
+
if (/^lesson\b/i.test(text)) return text;
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
function parseLessonMarker(value) {
|
|
3066
|
+
const match = /^lesson\s+(\d+)\s*(\*|\(\*\))?(?:\s*\(|\s*$)/i.exec(value);
|
|
3067
|
+
if (!match) throw new Error(`Easy Peasy lesson heading is malformed: ${value}`);
|
|
3068
|
+
return {
|
|
3069
|
+
day: Number.parseInt(match[1], 10),
|
|
3070
|
+
worksheet: match[2] === "*" || match[2] === "(*)"
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
1467
3073
|
function rejectUnterminatedLessonTags(html) {
|
|
1468
3074
|
const candidates = [...html.matchAll(/<(h[1-6]|strong|b)\b[^>]*>\s*(?:<[^>]*>\s*)*lesson\b/gi)];
|
|
1469
3075
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
@@ -1495,13 +3101,25 @@ function isAnswerLink(link) {
|
|
|
1495
3101
|
function isPacketLink(link) {
|
|
1496
3102
|
return /\b(?:packet|whole course|complete course)\b/i.test(link.text);
|
|
1497
3103
|
}
|
|
3104
|
+
function isSupplementalPdfLink(link) {
|
|
3105
|
+
return /\b(?:directions?|instructions?)\b/i.test(`${link.href} ${link.text}`);
|
|
3106
|
+
}
|
|
1498
3107
|
function courseTitle(html) {
|
|
3108
|
+
const entryTitle = /<h1\b(?=[^>]*\bclass=(?:"[^"]*\bentry-title\b[^"]*"|'[^']*\bentry-title\b[^']*'|[^\s>]*\bentry-title\b))[^>]*>([\s\S]*?)<\/h1\s*>/i.exec(html)?.[1];
|
|
1499
3109
|
const h1 = /<h1\b[^>]*>([\s\S]*?)<\/h1\s*>/i.exec(html)?.[1];
|
|
1500
3110
|
const title = /<title\b[^>]*>([\s\S]*?)<\/title\s*>/i.exec(html)?.[1];
|
|
1501
|
-
const value = textContent(h1 ?? title ?? "");
|
|
3111
|
+
const value = textContent(entryTitle ?? h1 ?? title ?? "");
|
|
1502
3112
|
if (!value) throw new Error("Easy Peasy page has no title.");
|
|
1503
3113
|
return value;
|
|
1504
3114
|
}
|
|
3115
|
+
function isApprovedSourcePdf(link, pageUrl) {
|
|
3116
|
+
try {
|
|
3117
|
+
sourcePdf(link, pageUrl);
|
|
3118
|
+
return true;
|
|
3119
|
+
} catch {
|
|
3120
|
+
return false;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
1505
3123
|
function sourcePdf(link, pageUrl) {
|
|
1506
3124
|
const url = validateEasyPeasyUrl(link.href, pageUrl);
|
|
1507
3125
|
const parsed = new URL(url);
|
|
@@ -1509,10 +3127,30 @@ function sourcePdf(link, pageUrl) {
|
|
|
1509
3127
|
if (!/\.pdf$/i.test(parsed.pathname)) throw new Error("Easy Peasy printable URL must be a PDF.");
|
|
1510
3128
|
return url;
|
|
1511
3129
|
}
|
|
1512
|
-
function
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
3130
|
+
function canonicalWorksheetLink(links2, pageUrl) {
|
|
3131
|
+
if (links2.length < 2) return links2;
|
|
3132
|
+
const stem = (link) => {
|
|
3133
|
+
const pathname = new URL(link.href, pageUrl).pathname;
|
|
3134
|
+
return pathname.slice(pathname.lastIndexOf("/") + 1).replace(/\.pdf$/i, "").toLowerCase();
|
|
3135
|
+
};
|
|
3136
|
+
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3137
|
+
const canonical = links2.filter((candidate) => {
|
|
3138
|
+
const candidateStem = stem(candidate);
|
|
3139
|
+
const variant = new RegExp(`^${escapeRegex(candidateStem)}(?:[-_ ]?\\d+)$`);
|
|
3140
|
+
return links2.every((link) => link === candidate || variant.test(stem(link)));
|
|
3141
|
+
});
|
|
3142
|
+
return canonical.length === 1 ? canonical : links2;
|
|
3143
|
+
}
|
|
3144
|
+
function printableFor(segment, pageUrl, day) {
|
|
3145
|
+
const pdfLinks = links(segment).filter(isPdfLink).filter((link) => !isAnswerLink(link) && !isPacketLink(link) && !isSupplementalPdfLink(link) && isApprovedSourcePdf(link, pageUrl));
|
|
3146
|
+
const ownDay = new RegExp(`(?:day|lesson)[\\s_-]*${day}(?:\\D|$)`, "i");
|
|
3147
|
+
const dayLinks = pdfLinks.filter((link) => ownDay.test(`${link.href} ${link.text}`));
|
|
3148
|
+
const candidates = canonicalWorksheetLink(dayLinks.length ? dayLinks : pdfLinks, pageUrl);
|
|
3149
|
+
if (candidates.length > 1) return { hasSourcePdf: true };
|
|
3150
|
+
return candidates[0] ? {
|
|
3151
|
+
printableUrl: sourcePdf(candidates[0], pageUrl),
|
|
3152
|
+
hasSourcePdf: true
|
|
3153
|
+
} : { hasSourcePdf: false };
|
|
1516
3154
|
}
|
|
1517
3155
|
function packetFor(html, pageUrl) {
|
|
1518
3156
|
const packets = links(html).filter((link) => isPdfLink(link) && !isAnswerLink(link) && isPacketLink(link));
|
|
@@ -1529,24 +3167,30 @@ function parseEasyPeasyPage(html, pageUrl) {
|
|
|
1529
3167
|
const days = /* @__PURE__ */ new Set();
|
|
1530
3168
|
for (let index = 0; index < headings.length; index += 1) {
|
|
1531
3169
|
const heading = headings[index];
|
|
1532
|
-
if (!/^lesson\b/i.test(heading.text)) continue;
|
|
1533
|
-
const
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
if (
|
|
3170
|
+
if (heading.day === void 0 && !/^lesson\b/i.test(heading.text)) continue;
|
|
3171
|
+
const segment = source.slice(heading.end, headings[index + 1]?.index);
|
|
3172
|
+
const marker = heading.day === void 0 ? heading.text : lessonMarkerText(segment);
|
|
3173
|
+
let parsedMarker;
|
|
3174
|
+
if (marker) try {
|
|
3175
|
+
parsedMarker = parseLessonMarker(marker);
|
|
3176
|
+
} catch (error) {
|
|
3177
|
+
if (heading.day === void 0) throw error;
|
|
3178
|
+
}
|
|
3179
|
+
const day = heading.day ?? parsedMarker?.day;
|
|
3180
|
+
if (!day || heading.day !== void 0 && parsedMarker && parsedMarker.day !== day) throw new Error("Easy Peasy daily container and lesson marker disagree.");
|
|
3181
|
+
if (!Number.isSafeInteger(day) || day < 1 || days.has(day)) throw new Error(`Easy Peasy lesson day is invalid or duplicated: ${day}`);
|
|
1537
3182
|
days.add(day);
|
|
1538
|
-
const
|
|
1539
|
-
const worksheet =
|
|
1540
|
-
if (printableUrl && !worksheet) throw new Error(`Easy Peasy lesson ${day} has a printable PDF without a worksheet marker.`);
|
|
3183
|
+
const printable = printableFor(segment, normalizedPageUrl, day);
|
|
3184
|
+
const worksheet = parsedMarker?.worksheet ?? printable.hasSourcePdf;
|
|
1541
3185
|
const page = new URL(normalizedPageUrl);
|
|
1542
|
-
page.hash = `lesson-${day}`;
|
|
3186
|
+
page.hash = heading.fragment ?? `lesson-${day}`;
|
|
1543
3187
|
lessons.push({
|
|
1544
3188
|
day,
|
|
1545
|
-
title: `Lesson ${day}`,
|
|
3189
|
+
title: heading.title ?? `Lesson ${day}`,
|
|
1546
3190
|
externalRef: `lesson:${day}`,
|
|
1547
3191
|
externalUrl: page.toString(),
|
|
1548
3192
|
worksheet,
|
|
1549
|
-
...printableUrl ? { printableUrl } : {}
|
|
3193
|
+
...printable.printableUrl ? { printableUrl: printable.printableUrl } : {}
|
|
1550
3194
|
});
|
|
1551
3195
|
}
|
|
1552
3196
|
if (!lessons.length) throw new Error("Easy Peasy page has no lesson headings.");
|
|
@@ -1601,11 +3245,53 @@ var SourceImportService = class {
|
|
|
1601
3245
|
fetchPage: registration.fetchPage
|
|
1602
3246
|
});
|
|
1603
3247
|
}
|
|
3248
|
+
async ensureEasyPeasyCourseSource(input) {
|
|
3249
|
+
const request = Object.freeze({ ...input });
|
|
3250
|
+
const active = getCurrentTenant();
|
|
3251
|
+
if (!request.tenantId?.trim() || !request.actorUserId?.trim() || active?.tenantId !== request.tenantId || active.userId !== request.actorUserId || active.superAdminBypass) throw new CatalogAuthorizationError();
|
|
3252
|
+
if (!this.easyPeasy) throw new Error("Easy Peasy is not registered by this host.");
|
|
3253
|
+
const sourceId = await identity(request.tenantId, "easy-peasy");
|
|
3254
|
+
if (await this.authorize({
|
|
3255
|
+
action: "education.source.ensure",
|
|
3256
|
+
actorTenantId: request.tenantId,
|
|
3257
|
+
actorUserId: request.actorUserId,
|
|
3258
|
+
sourceId,
|
|
3259
|
+
sourceSharing: "private",
|
|
3260
|
+
destination: "private",
|
|
3261
|
+
licenseEvidence: ""
|
|
3262
|
+
}) !== true) throw new CatalogAuthorizationError();
|
|
3263
|
+
if (!this.db.transaction) throw new Error("Source provisioning requires real transactions.");
|
|
3264
|
+
const transaction = this.db.transaction;
|
|
3265
|
+
return withEmbeddedWriteQueue(this.db, isEmbeddedDatabase(this.db), async () => {
|
|
3266
|
+
for (let attempt = 0;; attempt++) try {
|
|
3267
|
+
return await transaction(async (tx) => {
|
|
3268
|
+
if (isPostgresDatabase(this.db)) await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", sourceId);
|
|
3269
|
+
const sources = await CourseSourceCollection.create({ db: tx });
|
|
3270
|
+
const existing = await sources.get(sourceId);
|
|
3271
|
+
if (existing) {
|
|
3272
|
+
if (existing.tenantId !== request.tenantId || existing.key !== "easy-peasy" || existing.kind !== "adapter" || existing.adapterKey !== "easy-peasy" || existing.sharing !== "private") throw new Error("Existing Easy Peasy source policy cannot be changed by provisioning.");
|
|
3273
|
+
} else await sources.create({
|
|
3274
|
+
id: sourceId,
|
|
3275
|
+
tenantId: request.tenantId,
|
|
3276
|
+
key: "easy-peasy",
|
|
3277
|
+
name: "Easy Peasy All-in-One Homeschool",
|
|
3278
|
+
kind: "adapter",
|
|
3279
|
+
adapterKey: "easy-peasy",
|
|
3280
|
+
sharing: "private"
|
|
3281
|
+
});
|
|
3282
|
+
return { sourceId };
|
|
3283
|
+
});
|
|
3284
|
+
} catch (error) {
|
|
3285
|
+
if (attempt >= 3 || !retryable(error)) throw error;
|
|
3286
|
+
await new Promise((resolve) => setTimeout(resolve, 5 * 2 ** attempt));
|
|
3287
|
+
}
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
1604
3290
|
actor(request) {
|
|
1605
3291
|
const active = getCurrentTenant();
|
|
1606
3292
|
if (!request.tenantId?.trim() || !request.actorUserId?.trim() || active?.tenantId !== request.tenantId || active.userId !== request.actorUserId || active.superAdminBypass) throw new CatalogAuthorizationError();
|
|
1607
3293
|
if (request.destination !== "private" && request.destination !== "group") throw new Error("Only private-family or licensed-group destinations are supported.");
|
|
1608
|
-
if (!request.sourceId?.trim() || !request.courseRef?.trim() || request.courseRef.length > 256) throw new Error("A source and stable course reference are required.");
|
|
3294
|
+
if (!request.sourceId?.trim() || !request.courseRef?.trim() || request.courseRef.length > 256 || request.subject !== void 0 && (!request.subject.trim() || request.subject.length > 256)) throw new Error("A source and stable course reference are required.");
|
|
1609
3295
|
}
|
|
1610
3296
|
async source(db, request) {
|
|
1611
3297
|
this.actor(request);
|
|
@@ -1660,11 +3346,13 @@ var SourceImportService = class {
|
|
|
1660
3346
|
if (existing) {
|
|
1661
3347
|
existing.title = parsed.title;
|
|
1662
3348
|
existing.sourceUrl = pageUrl;
|
|
3349
|
+
if (request.subject) existing.subject = request.subject.trim();
|
|
1663
3350
|
await existing.save();
|
|
1664
3351
|
} else await courses.create({
|
|
1665
3352
|
id: courseId,
|
|
1666
3353
|
tenantId: request.tenantId,
|
|
1667
3354
|
title: parsed.title,
|
|
3355
|
+
subject: request.subject?.trim() ?? "",
|
|
1668
3356
|
sourceKey: source.key,
|
|
1669
3357
|
sourceRef: request.courseRef,
|
|
1670
3358
|
sourceUrl: pageUrl,
|
|
@@ -1721,6 +3409,275 @@ var SourceImportService = class {
|
|
|
1721
3409
|
}
|
|
1722
3410
|
};
|
|
1723
3411
|
//#endregion
|
|
3412
|
+
//#region src/sources/google-drive-adapter.ts
|
|
3413
|
+
var DRIVE_CURRICULUM_SEED_ROOTS = [
|
|
3414
|
+
{
|
|
3415
|
+
id: "1K_au1upSRuakX55PF3aZzQLHWI70KwZd",
|
|
3416
|
+
title: "Carnival of Collisions (Contact Forces)"
|
|
3417
|
+
},
|
|
3418
|
+
{
|
|
3419
|
+
id: "1N1xFJ2omm-M1TuAxDOXQxZndkw70-KxM",
|
|
3420
|
+
title: "Mission Maglev (Electric and Magnetic Forces)"
|
|
3421
|
+
},
|
|
3422
|
+
{
|
|
3423
|
+
id: "1v0LXs7iky2R4FsiqZkWOjWyJmRu-aVIa",
|
|
3424
|
+
title: "Rocks vs. Big Rigs, Unit-Wide View"
|
|
3425
|
+
},
|
|
3426
|
+
{
|
|
3427
|
+
id: "1sFDNXdw8q-xrhgy8HFAMELXUoqKgp9tu",
|
|
3428
|
+
title: "Operation Space Jump (Gravitational Forces)"
|
|
3429
|
+
}
|
|
3430
|
+
];
|
|
3431
|
+
var GOOGLE_FOLDER_MIME = "application/vnd.google-apps.folder";
|
|
3432
|
+
function uniqueRoots(roots) {
|
|
3433
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3434
|
+
return roots.filter((root) => {
|
|
3435
|
+
if (!root.id || seen.has(root.id)) return false;
|
|
3436
|
+
seen.add(root.id);
|
|
3437
|
+
return true;
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
function assertPageSize(pageSize) {
|
|
3441
|
+
if (pageSize === void 0) return void 0;
|
|
3442
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 1e3) throw new Error("Drive metadata page size must be between 1 and 1000.");
|
|
3443
|
+
return pageSize;
|
|
3444
|
+
}
|
|
3445
|
+
function classify(name, inherited) {
|
|
3446
|
+
const normalized = name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_./-]+/g, " ").toLowerCase();
|
|
3447
|
+
if (/\b(answers?|solutions?|keys?)\b/.test(normalized)) return "teacher";
|
|
3448
|
+
if (/\b(teachers?|educators?|instructors?|facilitators?)\b/.test(normalized)) return "teacher";
|
|
3449
|
+
return inherited;
|
|
3450
|
+
}
|
|
3451
|
+
function isFolder(metadata) {
|
|
3452
|
+
return metadata.mimeType === GOOGLE_FOLDER_MIME;
|
|
3453
|
+
}
|
|
3454
|
+
function nativeExportFormats(mimeType) {
|
|
3455
|
+
if ([
|
|
3456
|
+
"application/vnd.google-apps.document",
|
|
3457
|
+
"application/vnd.google-apps.spreadsheet",
|
|
3458
|
+
"application/vnd.google-apps.presentation"
|
|
3459
|
+
].includes(mimeType)) return ["application/pdf"];
|
|
3460
|
+
return [mimeType];
|
|
3461
|
+
}
|
|
3462
|
+
function encodeCheckpoint(checkpoint) {
|
|
3463
|
+
return Buffer.from(JSON.stringify(checkpoint)).toString("base64url");
|
|
3464
|
+
}
|
|
3465
|
+
function decodeCheckpoint(cursor) {
|
|
3466
|
+
if (!cursor) return void 0;
|
|
3467
|
+
try {
|
|
3468
|
+
const decoded = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
3469
|
+
if (!decoded || typeof decoded !== "object") throw new Error("not an object");
|
|
3470
|
+
return decoded;
|
|
3471
|
+
} catch {
|
|
3472
|
+
throw new Error("Invalid Google Drive discovery cursor.");
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
function toRecord(metadata, parentStableId, inheritedVisibility, studentSafeFileIds) {
|
|
3476
|
+
const revision = metadata.revisionId ?? metadata.md5Checksum ?? metadata.modifiedTime?.toISOString();
|
|
3477
|
+
if (!revision) throw new Error(`Drive metadata for ${metadata.id} has no revision.`);
|
|
3478
|
+
return {
|
|
3479
|
+
providerId: "google-drive",
|
|
3480
|
+
stableId: metadata.id,
|
|
3481
|
+
revision,
|
|
3482
|
+
...metadata.md5Checksum ? { checksum: metadata.md5Checksum } : {},
|
|
3483
|
+
canonicalUrl: `https://drive.google.com/open?id=${encodeURIComponent(metadata.id)}`,
|
|
3484
|
+
title: metadata.name,
|
|
3485
|
+
resourceType: metadata.shortcutDetails ? "shortcut" : isFolder(metadata) ? "folder" : "file",
|
|
3486
|
+
parentStableId,
|
|
3487
|
+
availableFormats: nativeExportFormats(metadata.mimeType),
|
|
3488
|
+
mimeType: metadata.mimeType,
|
|
3489
|
+
...metadata.size === void 0 ? {} : { size: metadata.size },
|
|
3490
|
+
...metadata.modifiedTime ? { modifiedTime: metadata.modifiedTime.toISOString() } : {},
|
|
3491
|
+
visibility: !isFolder(metadata) && !metadata.shortcutDetails && studentSafeFileIds.has(metadata.id) && classify(metadata.name, inheritedVisibility) === "catalog" ? "catalog" : "teacher",
|
|
3492
|
+
licenseEvidence: { state: "unknown" }
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
function normalizedCheckpoint(options) {
|
|
3496
|
+
const roots = uniqueRoots(options.roots ?? DRIVE_CURRICULUM_SEED_ROOTS).map((root) => root.id);
|
|
3497
|
+
if (!roots.length) throw new Error("At least one Drive root is required.");
|
|
3498
|
+
const checkpoint = options.checkpoint;
|
|
3499
|
+
if (!checkpoint) return {
|
|
3500
|
+
version: 1,
|
|
3501
|
+
roots,
|
|
3502
|
+
pendingFolders: roots.map((folderId) => ({
|
|
3503
|
+
folderId,
|
|
3504
|
+
visibility: "catalog"
|
|
3505
|
+
})),
|
|
3506
|
+
visitedFolders: []
|
|
3507
|
+
};
|
|
3508
|
+
if (checkpoint.version !== 1 || checkpoint.roots.length !== roots.length || checkpoint.roots.some((root, index) => root !== roots[index])) throw new Error("Drive checkpoint does not match configured roots.");
|
|
3509
|
+
return {
|
|
3510
|
+
version: 1,
|
|
3511
|
+
roots,
|
|
3512
|
+
pendingFolders: checkpoint.pendingFolders.map((folder) => ({ ...folder })),
|
|
3513
|
+
visitedFolders: [...checkpoint.visitedFolders]
|
|
3514
|
+
};
|
|
3515
|
+
}
|
|
3516
|
+
var GoogleDriveCurriculumAdapter = class GoogleDriveCurriculumAdapter {
|
|
3517
|
+
constructor(drive, configuration = {}) {
|
|
3518
|
+
this.drive = drive;
|
|
3519
|
+
this.configuration = configuration;
|
|
3520
|
+
}
|
|
3521
|
+
drive;
|
|
3522
|
+
configuration;
|
|
3523
|
+
static providerId = "google-drive";
|
|
3524
|
+
async enumerate(input) {
|
|
3525
|
+
const page = await this.discover({
|
|
3526
|
+
...this.configuration,
|
|
3527
|
+
checkpoint: decodeCheckpoint(input.cursor)
|
|
3528
|
+
});
|
|
3529
|
+
return {
|
|
3530
|
+
records: page.records,
|
|
3531
|
+
complete: page.complete,
|
|
3532
|
+
...page.checkpoint ? { nextCursor: encodeCheckpoint(page.checkpoint) } : {},
|
|
3533
|
+
...page.exclusions.length ? { exclusions: page.exclusions.map((exclusion) => ({
|
|
3534
|
+
code: exclusion.reason,
|
|
3535
|
+
message: `Google Drive resource ${exclusion.stableId} was excluded: ${exclusion.reason}.`
|
|
3536
|
+
})) } : {}
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3539
|
+
async discover(options = {}) {
|
|
3540
|
+
const checkpoint = normalizedCheckpoint(options);
|
|
3541
|
+
const pageSize = assertPageSize(options.pageSize);
|
|
3542
|
+
const current = checkpoint.pendingFolders.shift();
|
|
3543
|
+
if (!current) return {
|
|
3544
|
+
records: [],
|
|
3545
|
+
exclusions: [],
|
|
3546
|
+
complete: true
|
|
3547
|
+
};
|
|
3548
|
+
const page = await this.drive.listMetadataPage({
|
|
3549
|
+
folderId: current.folderId,
|
|
3550
|
+
...current.pageToken ? { pageToken: current.pageToken } : {},
|
|
3551
|
+
...pageSize === void 0 ? {} : { pageSize }
|
|
3552
|
+
});
|
|
3553
|
+
const records = [];
|
|
3554
|
+
const exclusions = [];
|
|
3555
|
+
const seenRecordIds = /* @__PURE__ */ new Set();
|
|
3556
|
+
const seenFolders = new Set(checkpoint.visitedFolders);
|
|
3557
|
+
seenFolders.add(current.folderId);
|
|
3558
|
+
const studentSafeFileIds = new Set(options.studentSafeFileIds ?? []);
|
|
3559
|
+
for (const entry of page.entries) {
|
|
3560
|
+
if (!entry.id || seenRecordIds.has(entry.id)) continue;
|
|
3561
|
+
seenRecordIds.add(entry.id);
|
|
3562
|
+
if (entry.shortcutDetails && entry.shortcutDetails.targetMimeType !== GOOGLE_FOLDER_MIME) {
|
|
3563
|
+
exclusions.push({
|
|
3564
|
+
stableId: entry.shortcutDetails.targetId,
|
|
3565
|
+
reason: "shortcut-target-unsupported"
|
|
3566
|
+
});
|
|
3567
|
+
continue;
|
|
3568
|
+
}
|
|
3569
|
+
const inheritedVisibility = classify(entry.name, current.visibility);
|
|
3570
|
+
try {
|
|
3571
|
+
records.push(toRecord(entry, current.folderId, inheritedVisibility, studentSafeFileIds));
|
|
3572
|
+
} catch {
|
|
3573
|
+
exclusions.push({
|
|
3574
|
+
stableId: entry.id,
|
|
3575
|
+
reason: "missing-revision"
|
|
3576
|
+
});
|
|
3577
|
+
continue;
|
|
3578
|
+
}
|
|
3579
|
+
const visibility = inheritedVisibility;
|
|
3580
|
+
if (entry.shortcutDetails) await this.resolveShortcut(entry.shortcutDetails, current.folderId, visibility, checkpoint, seenFolders, options, exclusions);
|
|
3581
|
+
else if (isFolder(entry) && !seenFolders.has(entry.id)) {
|
|
3582
|
+
seenFolders.add(entry.id);
|
|
3583
|
+
checkpoint.pendingFolders.push({
|
|
3584
|
+
folderId: entry.id,
|
|
3585
|
+
visibility
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
if (page.nextPageToken) checkpoint.pendingFolders.unshift({
|
|
3590
|
+
...current,
|
|
3591
|
+
pageToken: page.nextPageToken
|
|
3592
|
+
});
|
|
3593
|
+
else seenFolders.add(current.folderId);
|
|
3594
|
+
checkpoint.visitedFolders = [...seenFolders].sort();
|
|
3595
|
+
return {
|
|
3596
|
+
records,
|
|
3597
|
+
exclusions,
|
|
3598
|
+
...checkpoint.pendingFolders.length ? { checkpoint } : {},
|
|
3599
|
+
complete: checkpoint.pendingFolders.length === 0
|
|
3600
|
+
};
|
|
3601
|
+
}
|
|
3602
|
+
async resolveShortcut(shortcut, sourceFolderId, inheritedVisibility, checkpoint, seenFolders, options, exclusions) {
|
|
3603
|
+
if (shortcut.targetMimeType !== GOOGLE_FOLDER_MIME) {
|
|
3604
|
+
exclusions.push({
|
|
3605
|
+
stableId: shortcut.targetId,
|
|
3606
|
+
reason: "shortcut-target-unsupported"
|
|
3607
|
+
});
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
let target;
|
|
3611
|
+
try {
|
|
3612
|
+
target = await this.drive.getMetadataById(shortcut.targetId);
|
|
3613
|
+
} catch {
|
|
3614
|
+
exclusions.push({
|
|
3615
|
+
stableId: shortcut.targetId,
|
|
3616
|
+
reason: "shortcut-target-inaccessible"
|
|
3617
|
+
});
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3620
|
+
if (!isFolder(target) || seenFolders.has(target.id) || await options.authorizeShortcutTarget?.(target, {
|
|
3621
|
+
sourceFolderId,
|
|
3622
|
+
configuredRootIds: checkpoint.roots
|
|
3623
|
+
}) !== true) {
|
|
3624
|
+
exclusions.push({
|
|
3625
|
+
stableId: target.id,
|
|
3626
|
+
reason: isFolder(target) ? "shortcut-target-outside-boundary" : "shortcut-target-unsupported"
|
|
3627
|
+
});
|
|
3628
|
+
return;
|
|
3629
|
+
}
|
|
3630
|
+
seenFolders.add(target.id);
|
|
3631
|
+
checkpoint.pendingFolders.push({
|
|
3632
|
+
folderId: target.id,
|
|
3633
|
+
visibility: classify(target.name, inheritedVisibility)
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Content is available only after explicit selection and policy approval.
|
|
3638
|
+
* A revision is mandatory so retries cannot silently acquire changed bytes.
|
|
3639
|
+
*/
|
|
3640
|
+
async acquireSelected(authorization, options) {
|
|
3641
|
+
if (!authorization.allowed) throw new Error("Drive content selection is not authorized by license policy.");
|
|
3642
|
+
if (!authorization.stableId || !authorization.revision) throw new Error("Drive selection requires a stable ID and exact revision.");
|
|
3643
|
+
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 1) throw new Error("Drive acquisition requires a positive maximum byte limit.");
|
|
3644
|
+
const acquisition = await this.drive.acquireById(authorization.stableId, {
|
|
3645
|
+
...options,
|
|
3646
|
+
expectedRevision: authorization.revision,
|
|
3647
|
+
...authorization.exportMimeType ? { exportMimeType: authorization.exportMimeType } : {}
|
|
3648
|
+
});
|
|
3649
|
+
if (acquisition.metadata.id !== authorization.stableId) throw new Error("Drive acquisition returned a different resource.");
|
|
3650
|
+
if (acquisition.metadata.revisionId && acquisition.metadata.revisionId !== authorization.revision) throw new Error("Drive acquisition revision changed before content was pinned.");
|
|
3651
|
+
if (acquisition.bytes.byteLength > options.maxBytes) throw new Error("Drive acquisition exceeds its byte limit.");
|
|
3652
|
+
const hash = await globalThis.crypto.subtle.digest("SHA-256", Uint8Array.from(acquisition.bytes));
|
|
3653
|
+
const sha256 = Buffer.from(hash).toString("hex");
|
|
3654
|
+
if (acquisition.contentHash.toLowerCase() !== sha256) throw new Error("Drive acquisition content hash did not match returned bytes.");
|
|
3655
|
+
return {
|
|
3656
|
+
stableId: acquisition.metadata.id,
|
|
3657
|
+
...acquisition.metadata.revisionId ? { revision: acquisition.metadata.revisionId } : {},
|
|
3658
|
+
...acquisition.metadata.md5Checksum ? { checksum: acquisition.metadata.md5Checksum } : {},
|
|
3659
|
+
sha256,
|
|
3660
|
+
bytes: acquisition.bytes,
|
|
3661
|
+
...acquisition.exportMimeType ? { exportMimeType: acquisition.exportMimeType } : {}
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
3664
|
+
async acquire(input) {
|
|
3665
|
+
if (input.providerId !== GoogleDriveCurriculumAdapter.providerId) throw new Error("Google Drive adapter cannot acquire another provider.");
|
|
3666
|
+
const acquired = await this.acquireSelected({
|
|
3667
|
+
stableId: input.stableId,
|
|
3668
|
+
revision: input.revision,
|
|
3669
|
+
exportMimeType: input.format,
|
|
3670
|
+
allowed: true
|
|
3671
|
+
}, { maxBytes: this.configuration.maxAcquisitionBytes ?? 25e6 });
|
|
3672
|
+
if (acquired.revision !== input.revision) throw new Error("Drive acquisition did not return the selected revision.");
|
|
3673
|
+
return {
|
|
3674
|
+
bytes: acquired.bytes,
|
|
3675
|
+
sha256: acquired.sha256,
|
|
3676
|
+
revision: input.revision
|
|
3677
|
+
};
|
|
3678
|
+
}
|
|
3679
|
+
};
|
|
3680
|
+
//#endregion
|
|
1724
3681
|
//#region ../../node_modules/.pnpm/tslib@1.14.1/node_modules/tslib/tslib.es6.js
|
|
1725
3682
|
/*! *****************************************************************************
|
|
1726
3683
|
Copyright (c) Microsoft Corporation.
|
|
@@ -24514,6 +26471,9 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
24514
26471
|
status: row.status,
|
|
24515
26472
|
submittedAt: row.submittedAt,
|
|
24516
26473
|
notes: row.notes,
|
|
26474
|
+
kind: row.kind,
|
|
26475
|
+
previousSubmissionId: row.previousSubmissionId,
|
|
26476
|
+
intakeItemId: row.intakeItemId,
|
|
24517
26477
|
pages,
|
|
24518
26478
|
grade: grade ? {
|
|
24519
26479
|
id: String(grade.id),
|
|
@@ -24523,12 +26483,21 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
24523
26483
|
} : null
|
|
24524
26484
|
};
|
|
24525
26485
|
}
|
|
26486
|
+
/** Recheck the exact assignment without creating progress or accepting bytes.
|
|
26487
|
+
* Trusted intake bridges call this before reading retained evidence.
|
|
26488
|
+
*/
|
|
26489
|
+
async assertLessonAssignment(request) {
|
|
26490
|
+
const enrollment = await this.requireEnrollment(request, "education.learner.submit");
|
|
26491
|
+
if (!(await this.catalog(request, enrollment.courseId)).sections.some((section) => section.lessons.some(({ lesson }) => lesson.id === request.lessonId))) throw new LearnerAuthorizationError();
|
|
26492
|
+
}
|
|
24526
26493
|
async captureSubmission(request) {
|
|
24527
26494
|
return this.mutate(async (service) => {
|
|
24528
26495
|
service.validatePages(request.pages);
|
|
24529
26496
|
if (request.notes !== void 0 && typeof request.notes !== "string") throw new Error("Notes must be text");
|
|
26497
|
+
if (request.intake && (!request.intake.intakeItemId?.trim() || !["blank_reference", "completed_work"].includes(request.intake.kind))) throw new Error("Invalid submission intake binding");
|
|
24530
26498
|
if (typeof request.requestKey !== "string" || !request.requestKey.trim() || request.requestKey.length > 200) throw new Error("Capture requestKey is required");
|
|
24531
26499
|
const progress = await service.progress(request, "education.learner.submit");
|
|
26500
|
+
const kind = request.intake?.kind ?? "completed_work";
|
|
24532
26501
|
const crypto = typeof process !== "undefined" && process.getBuiltinModule?.("node:crypto");
|
|
24533
26502
|
if (!crypto) throw new Error("Submission capture requires a Node server");
|
|
24534
26503
|
const digest = crypto.createHash("sha256");
|
|
@@ -24538,8 +26507,57 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
24538
26507
|
page.data.length
|
|
24539
26508
|
])).update(page.data);
|
|
24540
26509
|
digest.update(JSON.stringify(request.notes ?? ""));
|
|
26510
|
+
digest.update(JSON.stringify(request.intake ?? null));
|
|
24541
26511
|
const captureDigest = digest.digest("hex");
|
|
24542
26512
|
const submissions = await ScannedSubmissionCollection.create({ db: service.db });
|
|
26513
|
+
const intakeRequest = request.intake;
|
|
26514
|
+
if (intakeRequest) {
|
|
26515
|
+
const bindings = await SubmissionIntakeBindingCollection.create({ db: service.db });
|
|
26516
|
+
const binding = await bindings.get({
|
|
26517
|
+
tenantId: request.tenantId,
|
|
26518
|
+
intakeItemId: intakeRequest.intakeItemId
|
|
26519
|
+
}, { cache: false });
|
|
26520
|
+
const intake = binding ? await submissions.get({
|
|
26521
|
+
id: binding.submissionId,
|
|
26522
|
+
tenantId: request.tenantId
|
|
26523
|
+
}, { cache: false }) : await (async () => {
|
|
26524
|
+
const legacy = await submissions.list({
|
|
26525
|
+
where: {
|
|
26526
|
+
tenantId: request.tenantId,
|
|
26527
|
+
intakeItemId: intakeRequest.intakeItemId
|
|
26528
|
+
},
|
|
26529
|
+
limit: 2
|
|
26530
|
+
});
|
|
26531
|
+
if (legacy.length > 1) throw new Error("Legacy intake item has ambiguous submissions");
|
|
26532
|
+
return legacy[0];
|
|
26533
|
+
})();
|
|
26534
|
+
if (binding && !intake) throw new Error("Submission intake binding is corrupt");
|
|
26535
|
+
if (intake) {
|
|
26536
|
+
if (!binding) await bindings.create({
|
|
26537
|
+
tenantId: request.tenantId,
|
|
26538
|
+
intakeItemId: intakeRequest.intakeItemId,
|
|
26539
|
+
submissionId: String(intake.id),
|
|
26540
|
+
_insertOnly: true
|
|
26541
|
+
});
|
|
26542
|
+
if (intake.kind !== kind || intake.progressId !== String(progress.id) || intake.learnerId !== request.learnerId || intake.lessonId !== request.lessonId || intake.previousSubmissionId !== (intakeRequest.previousSubmissionId ?? null)) throw new Error("Intake item is already bound to another assignment");
|
|
26543
|
+
if (intake.captureDigest !== captureDigest) throw new Error("Capture retry changed its content");
|
|
26544
|
+
return service.view(intake);
|
|
26545
|
+
}
|
|
26546
|
+
}
|
|
26547
|
+
if (request.intake?.previousSubmissionId) {
|
|
26548
|
+
const predecessor = await (await SubmissionCollection.create({ db: service.db })).get({
|
|
26549
|
+
id: request.intake.previousSubmissionId,
|
|
26550
|
+
tenantId: request.tenantId,
|
|
26551
|
+
progressId: String(progress.id),
|
|
26552
|
+
learnerId: request.learnerId,
|
|
26553
|
+
lessonId: request.lessonId
|
|
26554
|
+
}, { cache: false });
|
|
26555
|
+
if (predecessor?.kind !== "completed_work") throw new Error("Submission predecessor is not current progress evidence");
|
|
26556
|
+
if ((await (await SubmissionCollection.create({ db: service.db })).list({ where: {
|
|
26557
|
+
tenantId: request.tenantId,
|
|
26558
|
+
previousSubmissionId: String(predecessor.id)
|
|
26559
|
+
} })).length) throw new Error("Submission predecessor is stale");
|
|
26560
|
+
}
|
|
24543
26561
|
const prior = await submissions.get({
|
|
24544
26562
|
progressId: String(progress.id),
|
|
24545
26563
|
tenantId: request.tenantId,
|
|
@@ -24549,7 +26567,7 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
24549
26567
|
if (prior.captureDigest !== captureDigest || prior.learnerId !== request.learnerId || prior.lessonId !== request.lessonId) throw new Error("Capture retry changed its content");
|
|
24550
26568
|
return service.view(prior);
|
|
24551
26569
|
}
|
|
24552
|
-
if ([
|
|
26570
|
+
if (kind === "completed_work" && !request.intake?.previousSubmissionId && [
|
|
24553
26571
|
"submitted",
|
|
24554
26572
|
"needs_review",
|
|
24555
26573
|
"complete",
|
|
@@ -24564,7 +26582,16 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
24564
26582
|
pageCount: request.pages.length,
|
|
24565
26583
|
notes: request.notes ?? "",
|
|
24566
26584
|
captureKey: request.requestKey,
|
|
24567
|
-
captureDigest
|
|
26585
|
+
captureDigest,
|
|
26586
|
+
kind,
|
|
26587
|
+
previousSubmissionId: request.intake?.previousSubmissionId ?? null,
|
|
26588
|
+
intakeItemId: request.intake?.intakeItemId ?? null
|
|
26589
|
+
});
|
|
26590
|
+
if (request.intake) await (await SubmissionIntakeBindingCollection.create({ db: service.db })).create({
|
|
26591
|
+
tenantId: request.tenantId,
|
|
26592
|
+
intakeItemId: request.intake.intakeItemId,
|
|
26593
|
+
submissionId: String(row.id),
|
|
26594
|
+
_insertOnly: true
|
|
24568
26595
|
});
|
|
24569
26596
|
const links = await SubmissionAssetCollection.create({ db: service.db });
|
|
24570
26597
|
for (const [sortOrder, page] of request.pages.entries()) {
|
|
@@ -24577,12 +26604,35 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
24577
26604
|
sortOrder
|
|
24578
26605
|
});
|
|
24579
26606
|
}
|
|
24580
|
-
|
|
24581
|
-
|
|
24582
|
-
|
|
26607
|
+
if (kind === "completed_work") {
|
|
26608
|
+
progress.status = "submitted";
|
|
26609
|
+
progress.attempts += 1;
|
|
26610
|
+
await progress.save();
|
|
26611
|
+
}
|
|
24583
26612
|
return service.view(row);
|
|
24584
26613
|
});
|
|
24585
26614
|
}
|
|
26615
|
+
/** Capture a blank workbook scan without claiming completion or a grade. */
|
|
26616
|
+
async captureBlankReference(request) {
|
|
26617
|
+
return this.captureSubmission({
|
|
26618
|
+
...request,
|
|
26619
|
+
intake: {
|
|
26620
|
+
intakeItemId: request.intakeItemId,
|
|
26621
|
+
kind: "blank_reference"
|
|
26622
|
+
}
|
|
26623
|
+
});
|
|
26624
|
+
}
|
|
26625
|
+
/** Rescan corrections as a new immutable completed-work attempt. */
|
|
26626
|
+
async resubmitSubmission(request) {
|
|
26627
|
+
return this.captureSubmission({
|
|
26628
|
+
...request,
|
|
26629
|
+
intake: {
|
|
26630
|
+
intakeItemId: request.intakeItemId,
|
|
26631
|
+
kind: "completed_work",
|
|
26632
|
+
previousSubmissionId: request.previousSubmissionId
|
|
26633
|
+
}
|
|
26634
|
+
});
|
|
26635
|
+
}
|
|
24586
26636
|
async getSubmission(request) {
|
|
24587
26637
|
return this.view((await this.requireSubmission(request, "education.learner.read")).row);
|
|
24588
26638
|
}
|
|
@@ -24620,9 +26670,39 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
24620
26670
|
await service.requireEnrollment(request, "education.learner.grade");
|
|
24621
26671
|
await service.lock("enrollments", request.enrollmentId, request.tenantId);
|
|
24622
26672
|
const { row, progress } = await service.requireSubmission(request, "education.learner.grade");
|
|
26673
|
+
if (row.kind !== "completed_work") throw new Error("Blank workbook references cannot be graded");
|
|
26674
|
+
if ((await (await SubmissionCollection.create({ db: service.db })).list({ where: {
|
|
26675
|
+
tenantId: request.tenantId,
|
|
26676
|
+
previousSubmissionId: String(row.id)
|
|
26677
|
+
} })).some((successor) => successor.kind === "completed_work")) throw new Error("Only the current submission attempt can be graded");
|
|
24623
26678
|
return service.grade(request, row, progress, request.score ?? null, request.feedback ?? "");
|
|
24624
26679
|
});
|
|
24625
26680
|
}
|
|
26681
|
+
/** Bridge-only alias: uses the same guardian authorization and current-attempt checks. */
|
|
26682
|
+
async applyReviewedSubmission(request) {
|
|
26683
|
+
if (!this.executorBorrowed) return this.reviewSubmission(request);
|
|
26684
|
+
return this.reviewSubmission(request);
|
|
26685
|
+
}
|
|
26686
|
+
/** Record a non-authoritative automated proposal; it never completes progress. */
|
|
26687
|
+
async proposeSubmissionGrade(request) {
|
|
26688
|
+
return this.mutate(async (service) => {
|
|
26689
|
+
const { row } = await service.requireSubmission(request, "education.learner.grade");
|
|
26690
|
+
if (row.kind !== "completed_work") throw new Error("Blank workbook references cannot be proposed for grading");
|
|
26691
|
+
const score = request.score ?? null;
|
|
26692
|
+
if (score !== null && (typeof score !== "number" || !Number.isFinite(score) || score < 0 || score > 1)) throw new Error("Score must be null or a number from zero to one");
|
|
26693
|
+
if (request.feedback !== void 0 && typeof request.feedback !== "string") throw new Error("Feedback must be text");
|
|
26694
|
+
await (await GradeCollection.create({ db: service.db })).create({
|
|
26695
|
+
tenantId: request.tenantId,
|
|
26696
|
+
submissionId: String(row.id),
|
|
26697
|
+
graderKind: "ai",
|
|
26698
|
+
score,
|
|
26699
|
+
feedback: request.feedback ?? ""
|
|
26700
|
+
});
|
|
26701
|
+
row.status = "needs_review";
|
|
26702
|
+
await row.save();
|
|
26703
|
+
return service.view(row);
|
|
26704
|
+
});
|
|
26705
|
+
}
|
|
24626
26706
|
async attestLesson(request) {
|
|
24627
26707
|
return this.mutate(async (service) => {
|
|
24628
26708
|
const progress = await service.progress(request, "education.learner.grade");
|
|
@@ -24673,6 +26753,30 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
24673
26753
|
expiresAt
|
|
24674
26754
|
};
|
|
24675
26755
|
}
|
|
26756
|
+
/** Sign only an exact authorized printable join; answer keys never qualify. */
|
|
26757
|
+
async signLessonPrintable(request) {
|
|
26758
|
+
const enrollment = await this.requireEnrollment(request, "education.learner.read");
|
|
26759
|
+
if (!(await this.catalog(request, enrollment.courseId)).sections.some((section) => section.lessons.some(({ lesson }) => String(lesson.id) === request.lessonId))) throw new LearnerAuthorizationError();
|
|
26760
|
+
if (!await (await LessonAssetCollection.create({ db: this.db })).get({
|
|
26761
|
+
tenantId: request.tenantId,
|
|
26762
|
+
lessonId: request.lessonId,
|
|
26763
|
+
assetId: request.assetId,
|
|
26764
|
+
relationship: "printable"
|
|
26765
|
+
}, { cache: false }) || !this.options.signAsset) throw new LearnerAuthorizationError();
|
|
26766
|
+
const expiresAt = new Date(Date.now() + 300 * 1e3);
|
|
26767
|
+
return {
|
|
26768
|
+
url: await this.options.signAsset(Object.freeze({
|
|
26769
|
+
enrollmentId: request.enrollmentId,
|
|
26770
|
+
tenantId: request.tenantId,
|
|
26771
|
+
actorUserId: request.actorUserId,
|
|
26772
|
+
learnerId: request.learnerId,
|
|
26773
|
+
submissionId: `lesson:${request.lessonId}`,
|
|
26774
|
+
assetId: request.assetId,
|
|
26775
|
+
expiresAt
|
|
26776
|
+
})),
|
|
26777
|
+
expiresAt
|
|
26778
|
+
};
|
|
26779
|
+
}
|
|
24676
26780
|
/** Extract inclusive one-based, disjoint ranges into private worksheet PDFs. */
|
|
24677
26781
|
async splitWorksheetPacket(request) {
|
|
24678
26782
|
return this.mutate(async (service) => {
|
|
@@ -24728,6 +26832,6 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
24728
26832
|
}
|
|
24729
26833
|
};
|
|
24730
26834
|
//#endregion
|
|
24731
|
-
export { Attestation, AttestationCollection, AuthoredCourse, AuthoredCourseCollection, CatalogAuthorizationError, CatalogService, Course, CourseCollection, CourseSection, CourseSectionCollection, CourseSource, CourseSourceCollection, Curriculum, CurriculumCollection, EducationCatalogRecipe, EducationLearnerRecipe, Enrollment, EnrollmentCollection, Exercise, ExerciseCollection, ExternalCompletion, ExternalCompletionCollection, ExternalCourse, ExternalCourseCollection, Grade, GradeCollection, Learner, LearnerAuthorizationError, LearnerCollection, LearnerService, Lesson, LessonAsset, LessonAssetCollection, LessonCollection, LessonProgress, LessonProgressCollection, Reading, ReadingCollection, ScannedSubmission, ScannedSubmissionCollection, SourceImportService, Submission, SubmissionAsset, SubmissionAssetCollection, SubmissionCollection, SubmissionService, SubmissionStorageCleanupError, Video, VideoCollection, Worksheet, WorksheetCollection, parseEasyPeasyPage, validateEasyPeasyUrl };
|
|
26835
|
+
export { Attestation, AttestationCollection, AuthoredCourse, AuthoredCourseCollection, CatalogAcquisition, CatalogAcquisitionCollection, CatalogAcquisitionService, CatalogAuthorizationError, CatalogDatabaseSyncStore, CatalogMaterializationService, CatalogPolicy, CatalogProvider, CatalogProviderCollection, CatalogReadService, CatalogResource, CatalogResourceCollection, CatalogSelection, CatalogSelectionCollection, CatalogService, CatalogSyncService, CatalogSyncState, CatalogSyncStateCollection, Course, CourseCollection, CourseSection, CourseSectionCollection, CourseSource, CourseSourceCollection, Curriculum, CurriculumCollection, DRIVE_CURRICULUM_SEED_ROOTS, EducationCatalogRecipe, EducationLearnerRecipe, Enrollment, EnrollmentCollection, Exercise, ExerciseCollection, ExternalCompletion, ExternalCompletionCollection, ExternalCourse, ExternalCourseCollection, GoogleDriveCurriculumAdapter, Grade, GradeCollection, Learner, LearnerAuthorizationError, LearnerCollection, LearnerService, Lesson, LessonAsset, LessonAssetCollection, LessonCollection, LessonProgress, LessonProgressCollection, Reading, ReadingCollection, ScannedSubmission, ScannedSubmissionCollection, SourceImportService, Submission, SubmissionAsset, SubmissionAssetCollection, SubmissionCollection, SubmissionIntakeBinding, SubmissionIntakeBindingCollection, SubmissionService, SubmissionStorageCleanupError, Video, VideoCollection, Worksheet, WorksheetCollection, createCk12Adapter, createIllustrativeMathematicsAdapter, createOerCommonsAdapter, createOerCommonsRssAdapter, createPublicIllustrativeMathematicsK5Adapter, easyPeasyGrade4Courses, easyPeasyGrade4Themes, grade4Courses, grade4Theme, importEasyPeasyGrade4Course, importEasyPeasyGrade4Theme, parseEasyPeasyPage, parseIllustrativeMathematicsK5Index, parseOerCommonsRssFeed, providerFeedRequirements, validateEasyPeasyUrl };
|
|
24732
26836
|
|
|
24733
26837
|
//# sourceMappingURL=index.js.map
|