@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.
Files changed (57) hide show
  1. package/agents/catalog-sync.md +35 -0
  2. package/agents/ingestion.md +93 -0
  3. package/agents/learner-service.md +9 -0
  4. package/agents/source-import.md +9 -0
  5. package/agents/submission-service.md +18 -0
  6. package/dist/catalog-acquisition-service.d.ts +58 -0
  7. package/dist/catalog-acquisition-service.d.ts.map +1 -0
  8. package/dist/catalog-collections.d.ts +18 -0
  9. package/dist/catalog-collections.d.ts.map +1 -0
  10. package/dist/catalog-contracts.d.ts +88 -0
  11. package/dist/catalog-contracts.d.ts.map +1 -0
  12. package/dist/catalog-database-sync-store.d.ts +13 -0
  13. package/dist/catalog-database-sync-store.d.ts.map +1 -0
  14. package/dist/catalog-materialization-service.d.ts +78 -0
  15. package/dist/catalog-materialization-service.d.ts.map +1 -0
  16. package/dist/catalog-models.d.ts +63 -0
  17. package/dist/catalog-models.d.ts.map +1 -0
  18. package/dist/catalog-policy.d.ts +12 -0
  19. package/dist/catalog-policy.d.ts.map +1 -0
  20. package/dist/catalog-read-service.d.ts +44 -0
  21. package/dist/catalog-read-service.d.ts.map +1 -0
  22. package/dist/catalog-sync-service.d.ts +19 -0
  23. package/dist/catalog-sync-service.d.ts.map +1 -0
  24. package/dist/collections.d.ts +4 -1
  25. package/dist/collections.d.ts.map +1 -1
  26. package/dist/hearth-catalog-providers.d.ts +74 -0
  27. package/dist/hearth-catalog-providers.d.ts.map +1 -0
  28. package/dist/hearth-easy-peasy-grade4.d.ts +40 -0
  29. package/dist/hearth-easy-peasy-grade4.d.ts.map +1 -0
  30. package/dist/index.d.ts +28 -6
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2279 -175
  33. package/dist/index.js.map +1 -1
  34. package/dist/ingestion-isolation.d.ts +8 -0
  35. package/dist/ingestion-isolation.d.ts.map +1 -0
  36. package/dist/ingestion.d.ts +103 -0
  37. package/dist/ingestion.d.ts.map +1 -0
  38. package/dist/ingestion.js +364 -0
  39. package/dist/ingestion.js.map +1 -0
  40. package/dist/learner-service.d.ts +74 -1
  41. package/dist/learner-service.d.ts.map +1 -1
  42. package/dist/manifest.json +2686 -481
  43. package/dist/smrt-knowledge.json +713 -14
  44. package/dist/source-import-service.d.ts +11 -1
  45. package/dist/source-import-service.d.ts.map +1 -1
  46. package/dist/sources/easy-peasy-parser.d.ts.map +1 -1
  47. package/dist/sources/google-drive-adapter.d.ts +125 -0
  48. package/dist/sources/google-drive-adapter.d.ts.map +1 -0
  49. package/dist/sources/illustrative-mathematics-parser.d.ts +8 -0
  50. package/dist/sources/illustrative-mathematics-parser.d.ts.map +1 -0
  51. package/dist/sources/oer-commons-rss-parser.d.ts +8 -0
  52. package/dist/sources/oer-commons-rss-parser.d.ts.map +1 -0
  53. package/dist/submission-service.d.ts +48 -1
  54. package/dist/submission-service.d.ts.map +1 -1
  55. package/dist/submissions.d.ts +18 -0
  56. package/dist/submissions.d.ts.map +1 -1
  57. package/package.json +16 -10
@@ -0,0 +1,35 @@
1
+ # Catalog synchronization contract
2
+
3
+ `CatalogResource` is a global provider-native metadata record. Its identity is
4
+ `providerId + stableId`; do not merge or overwrite it with a matching record
5
+ from another provider. `CatalogSelection` and `CatalogAcquisition` are required
6
+ tenant-scoped records and are the only catalog records that express family use.
7
+
8
+ Use `CatalogSyncService` with a durable `CatalogSyncStore`. Adapters enumerate
9
+ metadata through `CatalogSyncAdapter`; this API has no byte or download method.
10
+ Persist the cursor after every partial page. Only a terminal successful page may
11
+ mark a checkpoint complete and withdraw source records not seen in that run.
12
+ A failed, restricted, or incomplete enumeration remains incomplete and retains
13
+ its cursor and failures for resumption.
14
+
15
+ Use `CatalogPolicy` for server-side visibility and operation decisions. Unknown,
16
+ conflicting, and restricted license evidence denies content operations. Teacher
17
+ material is omitted for non-teachers before any DTO is formed. Acquisitions are
18
+ explicit selected-content requests through `CatalogAcquisitionService`; it
19
+ requires a currently pinned revision and available format, verifies the returned
20
+ revision and SHA-256, and returns an existing acquisition before contacting a
21
+ provider on retry. Catalog browsing and synchronization must never call it.
22
+
23
+ `CatalogMaterializationService` is the only framework path from an acquired
24
+ selection to learner material. It requires a tenant-owned acquisition whose
25
+ resource revision still matches, current host-derived guardian authorization
26
+ (`education.catalog.materialize`), and a host storage adapter that creates an
27
+ authorized private Asset from the pinned bytes. Caller-provided roles are never
28
+ authorized. Before private storage side effects it re-runs `CatalogPolicy` on
29
+ the current resource and its current license evidence (`print` for PDF,
30
+ `download` otherwise); restricted or conflicting evidence denies promotion. It
31
+ creates a `personal-use-copy` external course, its section and lesson, and the
32
+ sole `LessonAsset` join; it never makes a browsed resource assignable. A host
33
+ may then supply curriculum and learner identifiers so the service calls the
34
+ existing guardian-authorized learner enrollment API. Missing provider subject
35
+ classification must be supplied explicitly by the guardian.
@@ -0,0 +1,93 @@
1
+ # Paper intake bridge
2
+
3
+ Import server integration from `@happyvertical/smrt-education/ingestion`, never
4
+ from browser code. Build IngestionService, SubmissionService and the supplied
5
+ scope from one authenticated server session. Derive learner/enrollment selection
6
+ from authorized application state, not OCR output or uploaded metadata.
7
+
8
+ 1. Receive retained bytes through IngestionService.receive with bounded request
9
+ sizes, captured authority and retention. Accepted/duplicate receipts share the
10
+ existing durable item identity. Retry/unavailable is not a successful upload.
11
+ 2. `queueEducationExtraction(service,itemId,configuration)` allocates the immutable
12
+ extraction revision. The registered ingestion job dispatch invokes
13
+ `processEducationAnalysis` with the saved item/revision and its authenticated
14
+ worker service. Extraction runs outside transactions; success allocates a
15
+ separately dispatched interpretation revision with ingestion's frozen source
16
+ pin. Ingestion's outbox repair/recovery APIs remain the owner of missing jobs.
17
+ 3. `educationIntakeHandlers({submissions,request})` discovers only the authorized
18
+ learner/enrollment's assignment lessons. Unknown/ambiguous proposal outcomes
19
+ remain unresolved. Use ingestion's preview/decision/apply APIs for the human
20
+ identification choice; do not select the first candidate automatically.
21
+ 4. After explicit assignment confirmation, `captureEducationIntake` reads original
22
+ evidence through the authenticated ingestion service and copies it through
23
+ SubmissionService private storage. Supply `kind: 'blank_reference'` before work
24
+ begins or `kind: 'completed_work'` for an attempt. A correction supplies the
25
+ previous submission ID and a NEW intake item. Durable tenant/item uniqueness
26
+ prohibits rebinding captured bytes to another learner/lesson/kind/predecessor.
27
+ Retrying a capture preserves its identity; storage failures propagate with the
28
+ owning service's compensation/reconciliation contract.
29
+ 5. Reconfigure the authoritative catalog with the captured `submissionId`, then
30
+ queue fresh extraction with `queueEducationExtraction(service,itemId,config,
31
+ submissionId)`, then generate a fresh interpretation/preview for grading. The grade handler binds
32
+ the exact captured intake and attempt. Every policy must require human review;
33
+ the adapter rejects an effective policy without `requireReview`. Marks remain
34
+ proposals until ingestion records an approved guardian decision. A blank
35
+ reference cannot be graded. Applying uses `withExecutor(context.db)` and
36
+ `applyReviewedSubmission`, with no provider or blob I/O. Ingestion owns action
37
+ retries/decision idempotency; education owns current-attempt checks.
38
+
39
+ The host execution authorizer must serialize current grant checks/revocation with
40
+ writes. Use `educationIntakeTargetResolver({submissions,request,submissionId?})`
41
+ for its target resolver: it locks the enrollment and reauthorizes on the supplied
42
+ executor. It uses these revision formats: lessons use
43
+ `assignment:<enrollmentId>:<lessonId>`; submissions use
44
+ `attempt:<submissionId>:<status>`. The latter must also recheck the current attempt
45
+ and intake binding. These identities do not authorize arbitrary catalog access.
46
+ Answer keys and private source locators never enter candidate labels.
47
+
48
+ ## Decoder deployment
49
+
50
+ The SDK adapter uses disposable child processes with deadlines and a Node heap
51
+ cap. Native decoder allocations require a real operating-system ceiling. Call
52
+ `requireIngestionMemoryLimit(maximumBytes)` **in the decoder worker at startup**
53
+ before constructing `createSDKExtractionAdapter({nativeMemoryIsolation:
54
+ 'host-enforced', ...})`. The check requires Linux cgroup v2, reads actual process
55
+ membership and all visible ancestor `memory.max` values, and fails closed for
56
+ missing/unlimited/over-policy limits or inaccessible files. Children inherit the
57
+ worker cgroup. Deploy an unprivileged container with a finite memory limit, no
58
+ writable cgroup mount, and no permission to escape/migrate cgroups. A configuration
59
+ string alone is insufficient. Container termination/OOM remains a retry/recovery
60
+ condition in ingestion, never a successful extraction.
61
+
62
+ The web server may run on macOS or another platform while jobs run in that Linux
63
+ worker. Local mock extraction tests establish sequencing and authorization,
64
+ not live OCR quality or production container enforcement. An actual memory-limited
65
+ worker launch and live provider smoke test are deployment acceptance prerequisites.
66
+ Provider credentials belong only in trusted worker configuration; do not put them
67
+ in receipts, job arguments, scope metadata or logs.
68
+
69
+ Selected catalog content must come from the explicit acquisition service, carrying
70
+ resource/revision/hash and attribution/notices. Catalog discovery is metadata
71
+ only and cannot establish download permission. Pin the acquired reference to the
72
+ assignment; do not replace it when the source catalog changes.
73
+
74
+ ## Learner uploads and guardian execution
75
+
76
+ `capturedCeiling.execution.principalId` is mandatory and immutable. A learner
77
+ upload that will later be reviewed by a guardian must capture a trusted,
78
+ server-provisioned execution principal, distinct from the requesting person.
79
+ The host explicitly provisions that principal's maximum scoped operations and
80
+ rechecks its current grants on the supplied executor. Uploading never grants
81
+ those permissions to the learner. Missing principal provisioning is a closed
82
+ configuration error; existing learner-principal receipts cannot be silently
83
+ upgraded or have their captured ceiling rewritten.
84
+
85
+ Ingestion already executes as that stable principal **on behalf of** the current
86
+ authenticated `scope.actorId`. The bridge preserves the principal permission
87
+ runtime and its intersected permission snapshot while entering a nested tenant
88
+ context for the actual requesting human. Education then verifies this actor's
89
+ membership, learner ownership or guardian relationship on `context.db`; its
90
+ existing own-learner grading prohibition still applies. Both authorizations are
91
+ required: principal grants alone cannot authorize guardian grading, and a
92
+ legitimate guardian cannot exceed the receipt's principal ceiling. Current grant
93
+ and guardian relationship revocation must serialize with execution, as before.
@@ -29,10 +29,19 @@ actor's User Profile to the learner's Profile. `authorizeLearner` receives
29
29
  that the type represents guardianship, its terms are effective, and that
30
30
  its permission policy allows the action. An arbitrary relationship is not a
31
31
  guardian grant. Missing relationships and host denials/exceptions fail closed.
32
+ The authorizer also receives `executor`, the service's active database
33
+ executor. Host policy reads, including relationship and revocation checks, must
34
+ use that executor so they share a `withTransaction` or `withExecutor` boundary.
32
35
  Same-tenant membership alone never grants sibling access.
33
36
 
34
37
  ## Supported workflow
35
38
 
39
+ - `getLearnerSetupStatus({ tenantId, actorUserId, learnerUserId })` is a
40
+ host-authorized onboarding probe for an active tenant member. It returns only
41
+ `{ needsSetup }`; it neither returns learner or relationship data nor grants
42
+ relationships. The host authorizer receives `education.learner.setup`, the
43
+ target user id, and the active executor. `provisionLearner` remains the write
44
+ path and still requires a directed guardian relationship.
36
45
  - `getLearner` returns authorized learner metadata and parsed settings.
37
46
  - `createCurriculum` creates a **planned** curriculum for an authorized guardian.
38
47
  A learner cannot plan their own curriculum. Title is required.
@@ -58,6 +58,15 @@ ON UPDATE CASCADE/self-referential schema limitations documented in
58
58
  [catalog-service.md](catalog-service.md); no alternate schema is substituted.
59
59
  Tests use synthetic local HTML and an injected fetch callback, never the network.
60
60
 
61
+ Google Drive discovery defaults every record to `teacher`. A trusted host may
62
+ make only an exact reviewed file ID visible by passing `studentSafeFileIds`;
63
+ folder IDs and descendant membership never grant visibility. A teacher-marked
64
+ folder or name still wins over that allowlist. File shortcuts are excluded: a
65
+ shortcut does not prove the target remains under a reviewed root or retains the
66
+ target's teacher-path classification. Resync after changing the allowlist: the
67
+ durable sync upsert rewrites existing visibility rather than retaining a
68
+ previous learner classification.
69
+
61
70
  Generic “Printable” and “Printables” labels identify lesson PDFs; course packets
62
71
  require explicit packet/whole-course/complete-course wording. Only actual href
63
72
  attributes supply pointers; data-href metadata does not.
@@ -28,6 +28,20 @@ the existing submission; altered retries fail. Submitted/review/complete/skipped
28
28
  progress rejects new captures. Content includes ordered filenames, MIME types,
29
29
  bytes and notes. Hosts should impose tighter request/body limits as needed.
30
30
 
31
+ `captureBlankReference` records a workbook-page scan as immutable
32
+ `blank_reference` evidence without advancing completion or creating a grade.
33
+ Trusted ingestion scopes each receipt to the exact `enrollmentId` and calls
34
+ `assertLessonAssignment` before reading retained evidence; it proves the active
35
+ tenant, actor, learner, enrollment, and lesson without creating progress. A
36
+ stable `intakeItemId` is claimed once in the durable tenant/item binding table,
37
+ then bound to the tenant, learner, lesson, progress, kind and predecessor; an
38
+ altered retry fails. The binding is database-unique across enrollments while
39
+ ordinary uploads remain unbound. `resubmitSubmission` requires the current completed-work predecessor and
40
+ creates a new immutable `completed_work` attempt. Old pages, grades and feedback
41
+ remain history; stale predecessors cannot be graded or replaced. A correction may
42
+ reopen submitted, review or complete progress, while an AI proposal remains a
43
+ non-authoritative Grade until guardian review.
44
+
31
45
  `reviewSubmission` accepts an existing authorized submission and optional score
32
46
  and feedback. `attestLesson` creates/reuses an Attestation for a matching progress
33
47
  item without requiring uploads. Both require a verified guardian and create a
@@ -73,6 +87,10 @@ returned. The host signs every claim and its serving endpoint must verify the
73
87
  signature, expiry, current actor/tenant and object authorization before serving.
74
88
  A bearer URL must never become a cross-family or sibling access bypass.
75
89
 
90
+ `signLessonPrintable` signs only the exact authorized `printable` LessonAsset
91
+ join for an enrolled lesson. It never accepts an answer-key join and carries the
92
+ same tenant, actor, learner and enrollment scope as a submission claim.
93
+
76
94
  ## Personal worksheet packets
77
95
 
78
96
  `splitWorksheetPacket` takes the guardian learner request, exact-owned
@@ -0,0 +1,58 @@
1
+ import type { CatalogAcquireAdapter, CatalogOperation, CatalogSourceRecord } from './catalog-contracts.js';
2
+ import { CatalogPolicy } from './catalog-policy.js';
3
+ export interface CatalogAcquisitionRecord {
4
+ id: string;
5
+ tenantId: string;
6
+ resourceId: string;
7
+ selectionId: string;
8
+ revision: string;
9
+ format: string;
10
+ sha256: string;
11
+ attribution?: string;
12
+ notices?: string;
13
+ }
14
+ export interface CatalogSelectionRecord {
15
+ id: string;
16
+ tenantId: string;
17
+ resourceId: string;
18
+ selectedRevision: string;
19
+ }
20
+ export interface CatalogAcquisitionStore {
21
+ getResource(resourceId: string): Promise<(CatalogSourceRecord & {
22
+ id: string;
23
+ withdrawnAt: Date | null;
24
+ }) | undefined>;
25
+ getSelection(input: {
26
+ tenantId: string;
27
+ selectionId: string;
28
+ }): Promise<CatalogSelectionRecord | undefined>;
29
+ findAcquisition(input: {
30
+ tenantId: string;
31
+ resourceId: string;
32
+ selectionId: string;
33
+ revision: string;
34
+ format: string;
35
+ }): Promise<CatalogAcquisitionRecord | undefined>;
36
+ createAcquisition(input: Omit<CatalogAcquisitionRecord, 'id'> & {
37
+ bytes: Uint8Array;
38
+ }): Promise<CatalogAcquisitionRecord>;
39
+ }
40
+ export interface CatalogAcquisitionRequest {
41
+ tenantId: string;
42
+ actorUserId: string;
43
+ roles: readonly string[];
44
+ resourceId: string;
45
+ selectionId: string;
46
+ revision: string;
47
+ format: string;
48
+ operation: CatalogOperation;
49
+ }
50
+ /** Explicit, policy-gated selected-content acquisition. Catalog discovery has no route here. */
51
+ export declare class CatalogAcquisitionService {
52
+ private readonly store;
53
+ private readonly adapter;
54
+ private readonly policy;
55
+ constructor(store: CatalogAcquisitionStore, adapter: CatalogAcquireAdapter, policy?: CatalogPolicy);
56
+ acquire(request: CatalogAcquisitionRequest): Promise<CatalogAcquisitionRecord>;
57
+ }
58
+ //# sourceMappingURL=catalog-acquisition-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-acquisition-service.d.ts","sourceRoot":"","sources":["../src/catalog-acquisition-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EAErB,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,CACT,UAAU,EAAE,MAAM,GACjB,OAAO,CACR,CAAC,mBAAmB,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;KAAE,CAAC,GAAG,SAAS,CAC7E,CAAC;IACF,YAAY,CAAC,KAAK,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAAC;IAChD,eAAe,CAAC,KAAK,EAAE;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAAC;IAClD,iBAAiB,CACf,KAAK,EAAE,IAAI,CAAC,wBAAwB,EAAE,IAAI,CAAC,GAAG;QAAE,KAAK,EAAE,UAAU,CAAA;KAAE,GAClE,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,CAAC;CAC7B;AASD,gGAAgG;AAChG,qBAAa,yBAAyB;IAElC,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAFN,KAAK,EAAE,uBAAuB,EAC9B,OAAO,EAAE,qBAAqB,EAC9B,MAAM,gBAAsB;IAGzC,OAAO,CACX,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC;CAgErC"}
@@ -0,0 +1,18 @@
1
+ import { SmrtCollection } from '@happyvertical/smrt-core';
2
+ import { CatalogAcquisition, CatalogProvider, CatalogResource, CatalogSelection, CatalogSyncState } from './catalog-models.js';
3
+ export declare class CatalogProviderCollection extends SmrtCollection<CatalogProvider> {
4
+ static readonly _itemClass: typeof CatalogProvider;
5
+ }
6
+ export declare class CatalogResourceCollection extends SmrtCollection<CatalogResource> {
7
+ static readonly _itemClass: typeof CatalogResource;
8
+ }
9
+ export declare class CatalogSyncStateCollection extends SmrtCollection<CatalogSyncState> {
10
+ static readonly _itemClass: typeof CatalogSyncState;
11
+ }
12
+ export declare class CatalogSelectionCollection extends SmrtCollection<CatalogSelection> {
13
+ static readonly _itemClass: typeof CatalogSelection;
14
+ }
15
+ export declare class CatalogAcquisitionCollection extends SmrtCollection<CatalogAcquisition> {
16
+ static readonly _itemClass: typeof CatalogAcquisition;
17
+ }
18
+ //# sourceMappingURL=catalog-collections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-collections.d.ts","sourceRoot":"","sources":["../src/catalog-collections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAE7B,qBAAa,yBAA0B,SAAQ,cAAc,CAAC,eAAe,CAAC;IAC5E,MAAM,CAAC,QAAQ,CAAC,UAAU,yBAAmB;CAC9C;AACD,qBAAa,yBAA0B,SAAQ,cAAc,CAAC,eAAe,CAAC;IAC5E,MAAM,CAAC,QAAQ,CAAC,UAAU,yBAAmB;CAC9C;AACD,qBAAa,0BAA2B,SAAQ,cAAc,CAAC,gBAAgB,CAAC;IAC9E,MAAM,CAAC,QAAQ,CAAC,UAAU,0BAAoB;CAC/C;AACD,qBAAa,0BAA2B,SAAQ,cAAc,CAAC,gBAAgB,CAAC;IAC9E,MAAM,CAAC,QAAQ,CAAC,UAAU,0BAAoB;CAC/C;AACD,qBAAa,4BAA6B,SAAQ,cAAc,CAAC,kBAAkB,CAAC;IAClF,MAAM,CAAC,QAAQ,CAAC,UAAU,4BAAsB;CACjD"}
@@ -0,0 +1,88 @@
1
+ /** Metadata-only resource visibility. Teacher material is never learner-visible. */
2
+ export type CatalogVisibility = 'catalog' | 'teacher';
3
+ export type CatalogLicenseState = 'verified' | 'unknown' | 'conflicting' | 'restricted';
4
+ export type CatalogOperation = 'link' | 'download' | 'print' | 'adapt' | 'redistribute' | 'commercial-use';
5
+ /** Exact source evidence; no provider-wide license inference is permitted. */
6
+ export interface CatalogLicenseEvidence {
7
+ state: CatalogLicenseState;
8
+ identifier?: string;
9
+ version?: string;
10
+ sourceUrl?: string;
11
+ attribution?: string;
12
+ exceptions?: string;
13
+ retrievedAt?: string;
14
+ /** Operations expressly authorized by the source evidence. */
15
+ permittedOperations?: CatalogOperation[];
16
+ }
17
+ /** A provider's durable source record. It remains distinct after deduplication. */
18
+ export interface CatalogSourceRecord {
19
+ providerId: string;
20
+ stableId: string;
21
+ revision: string;
22
+ canonicalUrl: string;
23
+ title: string;
24
+ suppliedDescription?: string;
25
+ author?: string;
26
+ subjects?: string[];
27
+ gradeRange?: {
28
+ min?: string;
29
+ max?: string;
30
+ };
31
+ language?: string;
32
+ resourceType?: string;
33
+ parentStableId?: string;
34
+ availableFormats?: string[];
35
+ licenseEvidence: CatalogLicenseEvidence;
36
+ visibility: CatalogVisibility;
37
+ }
38
+ export interface CatalogSyncFailure {
39
+ cursor?: string;
40
+ code: string;
41
+ message: string;
42
+ retryAfterMs?: number;
43
+ }
44
+ /** One metadata-only enumeration result. `complete` only means this page is terminal. */
45
+ export interface CatalogSyncPage {
46
+ records: CatalogSourceRecord[];
47
+ nextCursor?: string;
48
+ complete: boolean;
49
+ /** Honest non-terminal stop, e.g. missing source authorization. */
50
+ paused?: boolean;
51
+ exclusions?: CatalogSyncFailure[];
52
+ }
53
+ export interface CatalogSyncCheckpoint {
54
+ providerId: string;
55
+ /** Internal sync generation retained until its terminal page permits withdrawals. */
56
+ runId?: string;
57
+ cursor?: string;
58
+ lastSuccessfulAt?: string;
59
+ complete: boolean;
60
+ paused?: boolean;
61
+ pauseReason?: string;
62
+ importedCount: number;
63
+ failures: CatalogSyncFailure[];
64
+ }
65
+ export interface CatalogSyncAdapter {
66
+ enumerate(input: {
67
+ cursor?: string;
68
+ checkpoint: CatalogSyncCheckpoint;
69
+ }): Promise<CatalogSyncPage>;
70
+ }
71
+ export interface CatalogAcquireAdapter {
72
+ acquire(input: {
73
+ providerId: string;
74
+ stableId: string;
75
+ revision: string;
76
+ format: string;
77
+ }): Promise<{
78
+ bytes: Uint8Array;
79
+ sha256: string;
80
+ revision: string;
81
+ }>;
82
+ }
83
+ export interface CatalogActor {
84
+ tenantId: string;
85
+ actorUserId: string;
86
+ roles: readonly string[];
87
+ }
88
+ //# sourceMappingURL=catalog-contracts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-contracts.d.ts","sourceRoot":"","sources":["../src/catalog-contracts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { DatabaseInterface } from '@happyvertical/sql';
2
+ import type { CatalogSourceRecord, CatalogSyncCheckpoint } from './catalog-contracts.js';
3
+ import type { CatalogSyncStore } from './catalog-sync-service.js';
4
+ /** Default durable store for metadata-only provider synchronization. */
5
+ export declare class CatalogDatabaseSyncStore implements CatalogSyncStore {
6
+ private readonly db;
7
+ constructor(db: DatabaseInterface);
8
+ getCheckpoint(providerId: string): Promise<CatalogSyncCheckpoint | undefined>;
9
+ saveCheckpoint(value: CatalogSyncCheckpoint): Promise<void>;
10
+ upsert(record: CatalogSourceRecord, runId: string): Promise<void>;
11
+ withdrawUnseen(providerId: string, runId: string): Promise<number>;
12
+ }
13
+ //# sourceMappingURL=catalog-database-sync-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-database-sync-store.d.ts","sourceRoot":"","sources":["../src/catalog-database-sync-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAK5D,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AA2ClE,wEAAwE;AACxE,qBAAa,wBAAyB,YAAW,gBAAgB;IACnD,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,iBAAiB;IAE5C,aAAa,CACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC;IAQvC,cAAc,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB3D,MAAM,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkCjE,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAazE"}
@@ -0,0 +1,78 @@
1
+ import type { DatabaseInterface } from '@happyvertical/sql';
2
+ import type { CatalogAcquisitionRecord } from './catalog-acquisition-service.js';
3
+ import type { CatalogSourceRecord } from './catalog-contracts.js';
4
+ import { CatalogPolicy } from './catalog-policy.js';
5
+ import type { LearnerRequest } from './learner-service.js';
6
+ export interface CatalogMaterializationStore {
7
+ getResource(resourceId: string): Promise<(CatalogSourceRecord & {
8
+ id: string;
9
+ withdrawnAt: Date | null;
10
+ }) | undefined>;
11
+ getAcquisition(input: {
12
+ tenantId: string;
13
+ acquisitionId: string;
14
+ }): Promise<CatalogAcquisitionRecord | undefined>;
15
+ }
16
+ /** Host-owned copy operation; it must retain the pinned bytes and prove a private Asset capability. */
17
+ export interface CatalogMaterializationStorage {
18
+ ensurePrivateAsset(input: {
19
+ tenantId: string;
20
+ actorUserId: string;
21
+ acquisition: CatalogAcquisitionRecord;
22
+ name: string;
23
+ }): Promise<{
24
+ assetId: string;
25
+ }>;
26
+ }
27
+ export interface CatalogEnrollmentPort {
28
+ enroll(input: LearnerRequest & {
29
+ curriculumId: string;
30
+ courseId: string;
31
+ }): Promise<{
32
+ id: string;
33
+ }>;
34
+ }
35
+ export interface CatalogMaterializationRequest {
36
+ tenantId: string;
37
+ actorUserId: string;
38
+ acquisitionId: string;
39
+ /** Required when provider metadata does not classify a subject. */
40
+ subject?: string;
41
+ courseTitle?: string;
42
+ lessonTitle?: string;
43
+ curriculumId?: string;
44
+ learnerId?: string;
45
+ guardianRelationshipId?: string;
46
+ }
47
+ /** Host derives this from current memberships and guardian terms; callers never supply roles. */
48
+ export type CatalogMaterializationAuthorizer = (input: {
49
+ tenantId: string;
50
+ actorUserId: string;
51
+ executor: DatabaseInterface;
52
+ action: 'education.catalog.materialize';
53
+ }) => Promise<Readonly<{
54
+ allowed: boolean;
55
+ roles: readonly string[];
56
+ }>>;
57
+ export interface CatalogMaterializationResult {
58
+ courseId: string;
59
+ lessonId: string;
60
+ assetId: string;
61
+ revision: string;
62
+ sha256: string;
63
+ enrollmentId?: string;
64
+ }
65
+ /** Both adapter file extensions and MIME media types preserve PDF layout. */
66
+ export declare function isPrintableAcquisitionFormat(format: string): boolean;
67
+ /** Creates a private course from an already-authorized, pinned acquisition. */
68
+ export declare class CatalogMaterializationService {
69
+ private readonly db;
70
+ private readonly store;
71
+ private readonly storage;
72
+ private readonly learner;
73
+ private readonly authorize;
74
+ private readonly policy;
75
+ constructor(db: DatabaseInterface, store: CatalogMaterializationStore, storage: CatalogMaterializationStorage, learner: CatalogEnrollmentPort | undefined, authorize: CatalogMaterializationAuthorizer, policy?: CatalogPolicy);
76
+ materialize(request: CatalogMaterializationRequest): Promise<CatalogMaterializationResult>;
77
+ }
78
+ //# sourceMappingURL=catalog-materialization-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-materialization-service.d.ts","sourceRoot":"","sources":["../src/catalog-materialization-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { SmrtObject } from '@happyvertical/smrt-core';
2
+ import type { CatalogLicenseState, CatalogVisibility } from './catalog-contracts.js';
3
+ /** Globally visible provider registration. Hosts configure adapters and credentials out of band. */
4
+ export declare class CatalogProvider extends SmrtObject {
5
+ providerId: string;
6
+ name: string;
7
+ active: boolean;
8
+ }
9
+ /** A provider-native record. Rows are intentionally not merged across providers. */
10
+ export declare class CatalogResource extends SmrtObject {
11
+ providerId: string;
12
+ stableId: string;
13
+ revision: string;
14
+ canonicalUrl: string;
15
+ title: string;
16
+ suppliedDescription: string;
17
+ author: string;
18
+ subjects: string;
19
+ grades: string;
20
+ language: string;
21
+ resourceType: string;
22
+ parentStableId: string;
23
+ availableFormats: string;
24
+ licenseEvidence: string;
25
+ licenseState: CatalogLicenseState;
26
+ visibility: CatalogVisibility;
27
+ /** Last durable enumeration generation that observed this provider record. */
28
+ lastSeenRunId: string;
29
+ withdrawnAt: string;
30
+ }
31
+ /** Durable per-provider enumeration state. A failed or partial feed is never complete. */
32
+ export declare class CatalogSyncState extends SmrtObject {
33
+ /** Provider-owned stable key, not an internal provider row ID. */
34
+ providerId: string;
35
+ cursor: string;
36
+ /** Persists across interrupted pages so terminal withdrawal is safe on resume. */
37
+ runId: string;
38
+ complete: boolean;
39
+ paused: boolean;
40
+ pauseReason: string;
41
+ importedCount: number;
42
+ failures: string;
43
+ lastSuccessfulAt: string;
44
+ }
45
+ /** Tenant-owned intent to use a metadata record, distinct from acquiring bytes. */
46
+ export declare class CatalogSelection extends SmrtObject {
47
+ tenantId: string;
48
+ resourceId: string;
49
+ selectedByUserId: string;
50
+ selectedRevision: string;
51
+ }
52
+ /** A pinned, tenant-owned acquisition record. Bytes belong in a host storage service. */
53
+ export declare class CatalogAcquisition extends SmrtObject {
54
+ tenantId: string;
55
+ resourceId: string;
56
+ selectionId: string;
57
+ revision: string;
58
+ format: string;
59
+ sha256: string;
60
+ attribution: string;
61
+ notices: string;
62
+ }
63
+ //# sourceMappingURL=catalog-models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-models.d.ts","sourceRoot":"","sources":["../src/catalog-models.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAE/E,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAIhC,oGAAoG;AACpG,qBACa,eAAgB,SAAQ,UAAU;IAClB,UAAU,SAAM;IAChB,IAAI,SAAM;IACrC,MAAM,UAAQ;CACf;AAED,oFAAoF;AACpF,qBACa,eAAgB,SAAQ,UAAU;IAClB,UAAU,SAAM;IAChB,QAAQ,SAAM;IACd,QAAQ,SAAM;IACzC,YAAY,SAAM;IACS,KAAK,SAAM;IACtC,mBAAmB,SAAM;IACzB,MAAM,SAAM;IACZ,QAAQ,SAAQ;IAChB,MAAM,SAAQ;IACd,QAAQ,SAAM;IACd,YAAY,SAAM;IAClB,cAAc,SAAM;IACpB,gBAAgB,SAAQ;IACxB,eAAe,SAAQ;IACvB,YAAY,EAAE,mBAAmB,CAAa;IAC9C,UAAU,EAAE,iBAAiB,CAAa;IAC1C,8EAA8E;IAC9E,aAAa,SAAM;IACnB,WAAW,SAAM;CAClB;AAED,0FAA0F;AAC1F,qBACa,gBAAiB,SAAQ,UAAU;IAC9C,kEAAkE;IACvC,UAAU,SAAM;IAC3C,MAAM,SAAM;IACZ,kFAAkF;IAClF,KAAK,SAAM;IACX,QAAQ,UAAS;IACjB,MAAM,UAAS;IACf,WAAW,SAAM;IACjB,aAAa,SAAK;IAClB,QAAQ,SAAQ;IAChB,gBAAgB,SAAM;CACvB;AAED,mFAAmF;AACnF,qBAEa,gBAAiB,SAAQ,UAAU;IAClC,QAAQ,SAAM;IACyB,UAAU,SAAM;IACxC,gBAAgB,SAAM;IACtB,gBAAgB,SAAM;CAClD;AAED,yFAAyF;AACzF,qBAEa,kBAAmB,SAAQ,UAAU;IACpC,QAAQ,SAAM;IACyB,UAAU,SAAM;IACf,WAAW,SAAM;IAC1C,QAAQ,SAAM;IACd,MAAM,SAAM;IACZ,MAAM,SAAM;IACvC,WAAW,SAAM;IACjB,OAAO,SAAM;CACd"}
@@ -0,0 +1,12 @@
1
+ import type { CatalogActor, CatalogOperation, CatalogSourceRecord } from './catalog-contracts.js';
2
+ export interface CatalogPolicyDecision {
3
+ allowed: boolean;
4
+ reason?: 'teacher-material' | 'unverified-license' | 'operation-not-permitted';
5
+ }
6
+ /** Server-side resource policy. UI affordances are advisory and cannot bypass it. */
7
+ export declare class CatalogPolicy {
8
+ canView(record: Pick<CatalogSourceRecord, 'visibility'>, actor: CatalogActor): boolean;
9
+ authorize(record: Pick<CatalogSourceRecord, 'visibility' | 'licenseEvidence'>, operation: CatalogOperation, actor: CatalogActor): CatalogPolicyDecision;
10
+ private isVerified;
11
+ }
12
+ //# sourceMappingURL=catalog-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-policy.d.ts","sourceRoot":"","sources":["../src/catalog-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAEZ,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EACH,kBAAkB,GAClB,oBAAoB,GACpB,yBAAyB,CAAC;CAC/B;AAED,qFAAqF;AACrF,qBAAa,aAAa;IACxB,OAAO,CACL,MAAM,EAAE,IAAI,CAAC,mBAAmB,EAAE,YAAY,CAAC,EAC/C,KAAK,EAAE,YAAY,GAClB,OAAO;IAIV,SAAS,CACP,MAAM,EAAE,IAAI,CAAC,mBAAmB,EAAE,YAAY,GAAG,iBAAiB,CAAC,EACnE,SAAS,EAAE,gBAAgB,EAC3B,KAAK,EAAE,YAAY,GAClB,qBAAqB;IAWxB,OAAO,CAAC,UAAU;CAGnB"}
@@ -0,0 +1,44 @@
1
+ import type { CatalogActor, CatalogLicenseEvidence, CatalogSourceRecord } from './catalog-contracts.js';
2
+ import { CatalogPolicy } from './catalog-policy.js';
3
+ export interface CatalogReadStore {
4
+ search(input: {
5
+ query?: string;
6
+ subject?: string;
7
+ grade?: string;
8
+ }): Promise<readonly (CatalogSourceRecord & {
9
+ id: string;
10
+ })[]>;
11
+ }
12
+ /** Deliberately metadata-only DTO: credentials, byte locations, and teacher records stay server-side. */
13
+ export interface CatalogResourceView {
14
+ id: string;
15
+ providerId: string;
16
+ stableId: string;
17
+ revision: string;
18
+ canonicalUrl: string;
19
+ title: string;
20
+ suppliedDescription?: string;
21
+ author?: string;
22
+ subjects?: string[];
23
+ gradeRange?: {
24
+ min?: string;
25
+ max?: string;
26
+ };
27
+ language?: string;
28
+ resourceType?: string;
29
+ parentStableId?: string;
30
+ availableFormats?: string[];
31
+ license: Pick<CatalogLicenseEvidence, 'state' | 'identifier' | 'version' | 'attribution' | 'exceptions' | 'retrievedAt'>;
32
+ }
33
+ export declare class CatalogReadService {
34
+ private readonly store;
35
+ private readonly policy;
36
+ constructor(store: CatalogReadStore, policy?: CatalogPolicy);
37
+ search(actor: CatalogActor, filter?: {
38
+ query?: string;
39
+ subject?: string;
40
+ grade?: string;
41
+ }): Promise<CatalogResourceView[]>;
42
+ private view;
43
+ }
44
+ //# sourceMappingURL=catalog-read-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-read-service.d.ts","sourceRoot":"","sources":["../src/catalog-read-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,KAAK,EAAE;QACZ,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,SAAS,CAAC,mBAAmB,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC,CAAC;CAChE;AAED,yGAAyG;AACzG,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,OAAO,EAAE,IAAI,CACX,sBAAsB,EACpB,OAAO,GACP,YAAY,GACZ,SAAS,GACT,aAAa,GACb,YAAY,GACZ,aAAa,CAChB,CAAC;CACH;AAED,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,KAAK,EAAE,gBAAgB,EACvB,MAAM,gBAAsB;IAGzC,MAAM,CACV,KAAK,EAAE,YAAY,EACnB,MAAM,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAChE,OAAO,CAAC,mBAAmB,EAAE,CAAC;IASjC,OAAO,CAAC,IAAI;CA6Cb"}
@@ -0,0 +1,19 @@
1
+ import type { CatalogSourceRecord, CatalogSyncAdapter, CatalogSyncCheckpoint } from './catalog-contracts.js';
2
+ export interface CatalogSyncStore {
3
+ getCheckpoint(providerId: string): Promise<CatalogSyncCheckpoint | undefined>;
4
+ saveCheckpoint(checkpoint: CatalogSyncCheckpoint): Promise<void>;
5
+ upsert(record: CatalogSourceRecord, runId: string): Promise<void>;
6
+ withdrawUnseen(providerId: string, runId: string): Promise<number>;
7
+ }
8
+ export interface CatalogSyncReport extends CatalogSyncCheckpoint {
9
+ withdrawnCount: number;
10
+ }
11
+ /** Durable metadata synchronization. It never invokes a content acquisition adapter. */
12
+ export declare class CatalogSyncService {
13
+ private readonly store;
14
+ private readonly retries;
15
+ private readonly maxPages;
16
+ constructor(store: CatalogSyncStore, retries?: number, maxPages?: number);
17
+ sync(providerId: string, adapter: CatalogSyncAdapter): Promise<CatalogSyncReport>;
18
+ }
19
+ //# sourceMappingURL=catalog-sync-service.d.ts.map