@happyvertical/smrt-education 0.57.49 → 0.57.51

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 (45) hide show
  1. package/README.md +18 -1
  2. package/agents/course-authoring-service.md +39 -0
  3. package/agents/course-generation-review.md +53 -0
  4. package/agents/learner-service.md +20 -0
  5. package/agents/tutor-context.md +2 -1
  6. package/dist/chunks/{workbook-photo-service-WwDDC5GF.js → workbook-photo-service-BtMQsbhq.js} +1036 -402
  7. package/dist/chunks/workbook-photo-service-BtMQsbhq.js.map +1 -0
  8. package/dist/course-authoring-contracts.d.ts +23 -1
  9. package/dist/course-authoring-contracts.d.ts.map +1 -1
  10. package/dist/course-authoring-models.d.ts +2 -0
  11. package/dist/course-authoring-models.d.ts.map +1 -1
  12. package/dist/course-authoring-service.d.ts +56 -2
  13. package/dist/course-authoring-service.d.ts.map +1 -1
  14. package/dist/course-generation-contracts.d.ts +91 -0
  15. package/dist/course-generation-contracts.d.ts.map +1 -1
  16. package/dist/course-generation-models.d.ts +29 -0
  17. package/dist/course-generation-models.d.ts.map +1 -1
  18. package/dist/course-generation-output.d.ts +9 -3
  19. package/dist/course-generation-output.d.ts.map +1 -1
  20. package/dist/course-generation-proposal-generator.d.ts +171 -1
  21. package/dist/course-generation-proposal-generator.d.ts.map +1 -1
  22. package/dist/course-generation-review-service.d.ts +15 -1
  23. package/dist/course-generation-review-service.d.ts.map +1 -1
  24. package/dist/index.d.ts +6 -4
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +364 -327
  27. package/dist/index.js.map +1 -1
  28. package/dist/ingestion.js +1 -1
  29. package/dist/learner-history.d.ts +23 -0
  30. package/dist/learner-history.d.ts.map +1 -1
  31. package/dist/learner-interest-theme.d.ts +25 -0
  32. package/dist/learner-interest-theme.d.ts.map +1 -0
  33. package/dist/learner-service.d.ts +31 -2
  34. package/dist/learner-service.d.ts.map +1 -1
  35. package/dist/learners.d.ts +4 -0
  36. package/dist/learners.d.ts.map +1 -1
  37. package/dist/manifest.json +3699 -2802
  38. package/dist/question-authoring-service.d.ts +12 -0
  39. package/dist/question-authoring-service.d.ts.map +1 -1
  40. package/dist/smrt-knowledge.json +363 -13
  41. package/dist/tutor-context-service.d.ts.map +1 -1
  42. package/dist/tutor-contracts.d.ts +8 -0
  43. package/dist/tutor-contracts.d.ts.map +1 -1
  44. package/package.json +8 -8
  45. package/dist/chunks/workbook-photo-service-WwDDC5GF.js.map +0 -1
package/README.md CHANGED
@@ -109,7 +109,12 @@ host setting and optional evidence submission; guardian `attestLesson` is
109
109
  unchanged. `setEnrollmentPace` sets or clears an enrollment's planned lessons
110
110
  per week (1 to 14) under the host's `education.enrollment.set-pace` decision with
111
111
  an immutable change row, and `nextLessons({ count })` returns a week's set of
112
- unfinished lessons (default one).
112
+ unfinished lessons (default one). `setInterestTheme` stores a learner's interest
113
+ theme (a short plain label and an optional stable key such as `minecraft`; the
114
+ host owns its starter list) under `education.learner.set-interest-theme`: a
115
+ guardian may set any of their learners' themes, a learner only their own
116
+ (the host decides), nobody another learner's. Each change appends an immutable
117
+ row; `getLearner` and the tutor context read it back.
113
118
  `TutorContextService.compose` builds a bounded, learner-safe `TutorContext`
114
119
  (lesson text, learner-visible questions, own progress, released feedback; for the
115
120
  learner audience only `answerKeyAvailable`, never keys, rubrics, notes or storage
@@ -123,6 +128,18 @@ or file set under hard size, ratio, path and entry bounds, keeps unsupported and
123
128
  duplicate members as append-only evidence, hands accepted members to ingestion and
124
129
  proposes sections and lessons through `CourseGenerationReviewService`; see
125
130
  [bundle import](agents/course-bundle-import.md).
131
+ `requestGeneration` also accepts a structured `brief` (subject, category, area,
132
+ grade, objective, a `course` or one-`lesson` target and a per-request interest
133
+ theme that beats the learner's saved theme for that request only); the theme used
134
+ and whether it was an override show on the guardian's review, and `release`
135
+ accepts a `policyBasis` so a host can honour its own approve-requested-lessons
136
+ setting without bypassing the review gate; see
137
+ [generation and review](agents/course-generation-review.md).
138
+ `CourseAuthoringService.appendLesson` adds one lesson (materials and question
139
+ records included) to a released course as a new, optionally released revision:
140
+ append-only, idempotent by request key, refused on archived, unreleased or
141
+ foreign courses; enrollments stay on their pinned release until the host
142
+ upgrades them; see [course authoring](agents/course-authoring-service.md).
126
143
  Repeated starts preserve the existing progress, including attempts and scores.
127
144
  A new curriculum/year creates a distinct enrollment and fresh progress history.
128
145
  An unscored lesson has `bestScore: null`, distinct from zero. The service does
@@ -78,6 +78,45 @@ lesson identity; `Lesson.externalRef` and `ReleasedLesson` carry the stable
78
78
  lesson identity for progress/reporting. Learners on different releases never
79
79
  share mutable rows. Personal-use releases stay `draft` status (the existing rule).
80
80
 
81
+ ## Append a lesson to a released course
82
+
83
+ `appendLesson({ courseId, expectedHeadRevisionId, requestKey, sectionKey,
84
+ sectionTitle?, lesson, release?, policyBasis? })` adds exactly one lesson (a
85
+ `CourseLessonInput` with a required `lessonKey`, its `materials` and `questions`
86
+ exactly as `saveDraft` takes them) to a course that already has a release, as one
87
+ new revision, and releases that revision in the same transaction (`release:
88
+ false` only appends). Returns `CourseAppendLessonView` (`revisionId`, `version`,
89
+ `lessonKey`, `release | null`). Host mapping: action `education.course.author`,
90
+ operation `append-lesson` (also `generate` for generated provenance), and, when
91
+ releasing, `education.course.release` with the usual `policyBasis` and delegation
92
+ rules. Hosts that grant `author` but not `release` use `release: false`.
93
+
94
+ - **Append-only.** The new revision is the head plus the lesson at the end of
95
+ `sectionKey` (a new section needs `sectionTitle`). No lesson, revision or release
96
+ is changed; the release is a new Course row with the next release number.
97
+ - **Refused.** Unknown or foreign-tenant course (`CourseNotFoundError`), discarded,
98
+ archived (`CourseArchivedError`), no release yet (`CourseAppendRefusedError`
99
+ `not-released`), head with changes that were never released (`unreleased-changes`:
100
+ nothing unreviewed rides along; release or discard it first), `lessonKey` already
101
+ in the course (`duplicate-lesson`), stale `expectedHeadRevisionId`
102
+ (`CourseStaleHeadError`).
103
+ - **Review gate.** Releasing goes through the injected `releaseReviewGate` unchanged.
104
+ The default review-backed gate needs a current review of the new revision, which
105
+ cannot exist before it does, so with that gate use `release: false`, `recordReview`,
106
+ then `release`. A host whose gate honours its own approval (the guardian already
107
+ approved the requested lesson) can release in the one call. A gate that is not
108
+ current rolls the append back entirely.
109
+ - **Idempotent.** The same `requestKey` and request replays the original result, also
110
+ after the head moved (a no-op: no revision, release or lesson rows); a changed lesson
111
+ or section or release intent (`release` true versus false) under the same key is
112
+ `CourseRequestKeyConflictError`; publish a `release: false` append later with
113
+ `release` and its own key.
114
+ - **Enrollment pins.** Existing enrollments keep their pinned release and so never see
115
+ the new lesson implicitly. The host advances them with `upgradeEnrollment` on the new
116
+ release (explicit, recorded), or an enrollment/course that opted in with
117
+ `setAutoUpgrade` is moved by the release exactly as for any release (unstarted
118
+ lessons follow, started lessons stay on their row). Nothing else moves a pin.
119
+
81
120
  ## Enrollment pin and upgrades
82
121
 
83
122
  `Enrollment` is untouched (its `courseId` stays immutable): enroll through
@@ -40,6 +40,59 @@ a revision on `expectedHeadRevisionId` and records the decision; supplied conten
40
40
  is replaced only that way. Never write generated content to a released revision or
41
41
  assign it to a learner.
42
42
 
43
+ ## Structured requests and one-lesson generation
44
+
45
+ `requestGeneration` also takes an optional `brief` (`CourseGenerationBriefInput`)
46
+ next to the free `instruction`: `target` (`'course'`, the default and today's
47
+ behaviour, or `'lesson'`), `subject`, `category`, `area`, `grade` (whole 0-12),
48
+ `objective`, `learningObjectiveId`, `learnerId` and a per-request `theme`
49
+ (`{ label, key? }`). Text is bounded and sent to the provider as data. A request
50
+ without `brief` behaves exactly as before and `brief` reads back `null`.
51
+
52
+ - **Objective.** A `lesson` target needs an objective (`objective`, else `area`);
53
+ without one `CourseGenerationBriefError` is thrown before anything is written.
54
+ The objective is stored on the job's `CourseGenerationBriefRecord`, sent in the
55
+ generation brief as fixed, and copied verbatim onto the proposed lesson. The
56
+ theme changes story and examples only: output that states a different
57
+ objective fails the job (`CourseGenerationOutputError`).
58
+ - **Theme.** The per-request `theme` is an override that wins for that request
59
+ only. Without one the learner's saved `interestTheme` (read-only, from
60
+ `brief.learnerId`) applies; with neither there is no theme. The brief records
61
+ `theme` and `themeSource` (`'override' | 'saved' | 'none'`) at request time, and
62
+ `CourseGenerationView.brief` / `CourseProposalView.brief` show it to the
63
+ guardian reviewing. Nothing in generation writes to the learner, so the saved
64
+ preference is never changed, on success or failure.
65
+ - **Authority.** The host authorizer is asked for `education.course.generate`
66
+ with `learnerId` in the context so it can enforce its own "may this learner ask
67
+ for a lesson" policy; an unknown or foreign-tenant learner is `CourseNotFoundError`.
68
+ The package does not check the requester's relationship to `brief.learnerId` (the
69
+ brief echoes that learner's saved theme): the host authorizer MUST deny `generate`
70
+ when the actor is not that learner or a guardian with an effective relationship, as
71
+ it does for `assign`.
72
+ Replaying a request key with the same brief returns the same job; a changed
73
+ brief is `CourseRequestKeyConflictError`.
74
+ - **Output.** For `lesson` the generator is given `request.brief` and
75
+ `COURSE_LESSON_PROPOSAL_ARGS_SCHEMA` (via `createProposalCourseGenerator`):
76
+ exactly one new lesson with `title`, `body` (the themed explanation) and
77
+ `questions` (worksheet; every question needs an `answerKey`), and no course-level
78
+ fields. The worksheet becomes immutable question records, linked to
79
+ `learningObjectiveId`, in the same transaction as the draft revision; answer keys
80
+ stay out of learner views. Review, `decideProposal` and release are unchanged.
81
+ - **Approving requested lessons.** The package has no per-learner setting. To honour
82
+ the host's own "approve requested lessons" setting without bypassing authority,
83
+ the host calls `recordReview` then `release` under the guardian delegation as for
84
+ any course, passing `policyBasis` (for example `'auto-approve-requested-lessons'`)
85
+ to `release`, or returning `policyBasis` from its authorizer decision (the two
86
+ must agree). The basis is recorded on `CourseRelease.policyBasis` and
87
+ `CourseReleaseView.policyBasis`; it grants nothing and the review gate, the
88
+ `education.course.release` grant and the delegation check still apply. The
89
+ explicit alternative, a guardian approving each lesson, is the existing
90
+ `recordReview` plus `release` by the guardian.
91
+
92
+ Additive schema: table `course_generation_briefs` and column
93
+ `course_releases.policy_basis`. On a host without the table, plain requests still
94
+ work and structured ones fail closed with `schema-unavailable`.
95
+
43
96
  ## Review
44
97
 
45
98
  `recordReview` (action `education.course.review`) binds to an exact revision and a
@@ -131,6 +131,26 @@ Same-tenant membership alone never grants sibling access.
131
131
  without `enrollment_pace_changes` gets `LearnerHistoryUnavailableError`.
132
132
  Apply the additive migration (new column plus table) before deploying writes;
133
133
  reads of an un-migrated table report no pace.
134
+ - `setInterestTheme({ theme: { label, key? } | null, policyBasis })` stores the
135
+ learner's interest theme under `education.learner.set-interest-theme`. The
136
+ label is a short plain string (1-60 characters after whitespace is collapsed;
137
+ control characters, `@`, `://` and `www.` are refused so it cannot carry
138
+ contact details) and the optional key is lowercase `[a-z0-9-]` up to 40
139
+ characters. The package ships no starter list: the host owns its keys
140
+ (for Hearth `minecraft`, `roman`, `hockey`, `baseball`, `football`, `soccer`,
141
+ `cars`) and family-added themes are free labels without a key. A guardian with
142
+ an effective relationship may set any of their learners' themes; the learner
143
+ may set their own when the host authorizer answers `own: true` as allowed;
144
+ another learner's theme needs the actor's own guardian relationship, so a
145
+ learner, sibling or stranger is refused by the same actor/tenant checks as
146
+ every other operation. It is a skin for stories and examples only; it never
147
+ changes an objective. Columns `learners.interest_theme_label` and
148
+ `interest_theme_key` (additive; empty = none) hold the value and each change
149
+ appends a `LearnerInterestThemeChange` (actor, `policyBasis`, previous and new
150
+ value). An unchanged value is `{ changed: false }`; a bad value is
151
+ `InterestThemeError`; a host without `learner_interest_theme_changes` gets
152
+ `LearnerHistoryUnavailableError`. `getLearner` returns `interestTheme`
153
+ (`{ label, key } | null`). Apply the additive migration before deploying.
134
154
  - `nextLessons({ enrollmentId?, count? })`: `count` is an integer from 1 to
135
155
  14 (`MAX_NEXT_LESSONS_COUNT`, default 1, else `LearnerValidationError`) and
136
156
  returns up to that many unfinished (not complete or skipped) lessons per
@@ -15,7 +15,8 @@ enrollment's effective set; a focus question must belong to that lesson
15
15
  (otherwise `LearnerAuthorizationError`). The guardian audience is refused for
16
16
  the learner's own actor whatever the host answers.
17
17
 
18
- Result: learner (name, level), enrollment (status, pace; no notes), course,
18
+ Result: learner (name, level, `interestTheme`: the learner-safe label and key or
19
+ null, read-only; never the free `settings`), enrollment (status, pace; no notes), course,
19
20
  section and lesson titles, learner-facing lesson body (withheld for
20
21
  personal-use-copy), objectives, own progress status, the learner-visible
21
22
  question rendering (prompt, response type, choices, subpart prompts, points; no