@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
|
@@ -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.
|
package/agents/source-import.md
CHANGED
|
@@ -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
|