@happyvertical/smrt-education 0.57.51 → 0.58.0

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 (93) hide show
  1. package/AGENTS.md +13 -8
  2. package/README.md +36 -2
  3. package/agents/course-bundle-import.md +80 -2
  4. package/agents/course-work-design.md +144 -0
  5. package/agents/group-sharing.md +5 -1
  6. package/agents/learner-plan.md +3 -3
  7. package/agents/learner-service.md +1 -1
  8. package/agents/lesson-materials.md +52 -0
  9. package/agents/{workbook-service.md → lesson-work-service.md} +164 -95
  10. package/agents/{workbook-test-matrix.md → lesson-work-test-matrix.md} +3 -3
  11. package/agents/photo-intake.md +37 -50
  12. package/agents/question-records.md +1 -1
  13. package/agents/submission-service.md +22 -6
  14. package/dist/catalog-service.d.ts +1 -1
  15. package/dist/catalog-service.d.ts.map +1 -1
  16. package/dist/catalog.d.ts +11 -0
  17. package/dist/catalog.d.ts.map +1 -1
  18. package/dist/chunks/{workbook-photo-service-BtMQsbhq.js → workbook-photo-service-COtyQL1y.js} +5927 -4963
  19. package/dist/chunks/workbook-photo-service-COtyQL1y.js.map +1 -0
  20. package/dist/course-authoring-contracts.d.ts +5 -1
  21. package/dist/course-authoring-contracts.d.ts.map +1 -1
  22. package/dist/course-authoring-service.d.ts.map +1 -1
  23. package/dist/course-bundle-contracts.d.ts +23 -3
  24. package/dist/course-bundle-contracts.d.ts.map +1 -1
  25. package/dist/course-bundle-models.d.ts +3 -0
  26. package/dist/course-bundle-models.d.ts.map +1 -1
  27. package/dist/course-bundle-service.d.ts +45 -1
  28. package/dist/course-bundle-service.d.ts.map +1 -1
  29. package/dist/course-bundle.d.ts +32 -0
  30. package/dist/course-bundle.d.ts.map +1 -1
  31. package/dist/course-work-migration.d.ts +27 -0
  32. package/dist/course-work-migration.d.ts.map +1 -0
  33. package/dist/document-converter.d.ts +138 -0
  34. package/dist/document-converter.d.ts.map +1 -0
  35. package/dist/group-sharing-contracts.d.ts +3 -3
  36. package/dist/group-sharing-contracts.d.ts.map +1 -1
  37. package/dist/group-sharing-policy.d.ts +4 -4
  38. package/dist/group-sharing-policy.d.ts.map +1 -1
  39. package/dist/group-sharing-reader.d.ts.map +1 -1
  40. package/dist/index.d.ts +15 -12
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +737 -49
  43. package/dist/index.js.map +1 -1
  44. package/dist/ingestion.d.ts +12 -11
  45. package/dist/ingestion.d.ts.map +1 -1
  46. package/dist/ingestion.js +63 -56
  47. package/dist/ingestion.js.map +1 -1
  48. package/dist/learner-plan-service.d.ts +1 -1
  49. package/dist/learner-plan-service.d.ts.map +1 -1
  50. package/dist/learner-service.d.ts +19 -5
  51. package/dist/learner-service.d.ts.map +1 -1
  52. package/dist/lesson-assets.d.ts +66 -2
  53. package/dist/lesson-assets.d.ts.map +1 -1
  54. package/dist/lesson-pages.d.ts +36 -0
  55. package/dist/lesson-pages.d.ts.map +1 -0
  56. package/dist/{workbook-completion.d.ts → lesson-work-completion.d.ts} +10 -10
  57. package/dist/lesson-work-completion.d.ts.map +1 -0
  58. package/dist/{workbook-contracts.d.ts → lesson-work-contracts.d.ts} +100 -52
  59. package/dist/lesson-work-contracts.d.ts.map +1 -0
  60. package/dist/lesson-work-models.d.ts +118 -0
  61. package/dist/lesson-work-models.d.ts.map +1 -0
  62. package/dist/lesson-work-service.d.ts +239 -0
  63. package/dist/lesson-work-service.d.ts.map +1 -0
  64. package/dist/manifest.json +6420 -5299
  65. package/dist/smrt-knowledge.json +2156 -1740
  66. package/dist/submission-service.d.ts +135 -5
  67. package/dist/submission-service.d.ts.map +1 -1
  68. package/dist/submissions.d.ts +2 -0
  69. package/dist/submissions.d.ts.map +1 -1
  70. package/dist/work-answer-regions.d.ts +11 -0
  71. package/dist/work-answer-regions.d.ts.map +1 -0
  72. package/dist/work-layout.d.ts +13 -0
  73. package/dist/work-layout.d.ts.map +1 -0
  74. package/dist/workbook-photo-contracts.d.ts +9 -7
  75. package/dist/workbook-photo-contracts.d.ts.map +1 -1
  76. package/dist/workbook-photo-ingestion.d.ts +1 -1
  77. package/dist/workbook-photo-ingestion.d.ts.map +1 -1
  78. package/dist/workbook-photo-models.d.ts +5 -5
  79. package/dist/workbook-photo-models.d.ts.map +1 -1
  80. package/dist/workbook-photo-service.d.ts +13 -10
  81. package/dist/workbook-photo-service.d.ts.map +1 -1
  82. package/package.json +8 -8
  83. package/dist/chunks/workbook-photo-service-BtMQsbhq.js.map +0 -1
  84. package/dist/workbook-answer-regions.d.ts +0 -11
  85. package/dist/workbook-answer-regions.d.ts.map +0 -1
  86. package/dist/workbook-completion.d.ts.map +0 -1
  87. package/dist/workbook-contracts.d.ts.map +0 -1
  88. package/dist/workbook-layout.d.ts +0 -13
  89. package/dist/workbook-layout.d.ts.map +0 -1
  90. package/dist/workbook-models.d.ts +0 -129
  91. package/dist/workbook-models.d.ts.map +0 -1
  92. package/dist/workbook-service.d.ts +0 -163
  93. package/dist/workbook-service.d.ts.map +0 -1
@@ -1,47 +1,90 @@
1
- # Physical workbook contract
2
-
3
- `WorkbookService` extends `SubmissionService` with the same actor-bound authorization,
4
- private asset runtime, transaction executor and guardian Grade boundary. Both
5
- LearnerAuthorizer and CatalogAuthorizer receive the active executor. Host session
6
- heartbeats and current permission checks must use that supplied executor, including
7
- nested guardian review; a captured outer pool can wait on its own transaction.
8
- CatalogService obtains the executor through the public collection.db getter and
9
- fails closed when its four catalog collections do not share the same handle. It is an
10
- additive API; existing Submission/Grade consumers retain their contracts. The six
11
- closed `Workbook*` models are package-manifest models. Apply normal application
12
- schema migrations before using them; the service never creates runtime schema.
13
-
14
- A guardian calls `resolveWorkbookActivity` with an explicit learner, workbook key,
15
- title, edition (empty means unknown), page key and printed page label. Keys identify
16
- the physical copy/edition and page occurrence, including section/side where needed.
17
- Names and photo hashes do not establish identity. A conflicting reuse fails. One
18
- private personal-use-copy course/enrollment per learner/workbook supplies internal
19
- submission scaffolding. The public page API needs no course-building workflow.
20
-
21
- `captureWorkbookWork` owns original files through SubmissionAsset and creates an
1
+ # Lesson work contract
2
+
3
+ `LessonWorkService` (formerly `WorkbookService`) extends `SubmissionService` with
4
+ the same actor-bound authorization, private asset runtime, transaction executor and
5
+ guardian Grade boundary. Both LearnerAuthorizer and CatalogAuthorizer receive the
6
+ active executor. Host session heartbeats and current permission checks must use that
7
+ supplied executor, including nested guardian review; a captured outer pool can wait
8
+ on its own transaction. CatalogService obtains the executor through the public
9
+ collection.db getter and fails closed when its four catalog collections do not
10
+ share the same handle. The closed models (`LessonActivity`, `WorkAttempt`,
11
+ `WorkAssessmentRevision`, `WorkFeedbackRelease`, `WorkFeedbackDelivery`,
12
+ `WorkCompletionDecision`, `LessonWorkFiling`, `LessonPage`) are package-manifest
13
+ models; their storage tables keep the historical `workbook_*` names. Apply normal
14
+ application schema migrations before using them; the service never creates runtime
15
+ schema. The design record is [course work](course-work-design.md).
16
+
17
+ ## One pipeline
18
+
19
+ Every completed-work hand-in on every lesson is lesson work. `SubmissionService`
20
+ capture and correction record the learner's `LessonActivity` (one per enrollment
21
+ and lesson, created on first capture) and an immutable `WorkAttempt` bound to the
22
+ submission in the same transaction; a correction continues its predecessor's
23
+ episode. Blank references create no attempt. Timeline, analysis, guardian review,
24
+ release, delivery, concept evidence and completion work for every lesson.
25
+
26
+ Two completion rules apply. **Ordinary lessons** (a course without book identity):
27
+ a guardian grade (`reviewSubmission`, `attestLesson`, or guardian review of the
28
+ current capture) records the `WorkCompletionDecision` on the graded current attempt
29
+ and the learner sees the grade. **Book and loose-page work** (a course with
30
+ `bookKey`, or unfiled photo work): marks reach the learner only through an explicit
31
+ release, grading does not complete, and only `completeLessonActivity` does.
32
+ Generic `captureSubmission`/`resubmitSubmission` refuse such work with
33
+ `LessonWorkWorkflowRequiredError`, judged by the enrollment's course (book key or
34
+ unfiled routing) as well as an existing activity, so an untouched book page is
35
+ refused too; hosts call `assertOrdinarySubmissionScope` (enrollment, lesson,
36
+ predecessor) before creating a generic intake receipt.
37
+
38
+ ## Books and pages
39
+
40
+ A physical book is a course: `Course.bookKey` (one course per family per key,
41
+ immutable once set), `bookEdition`, and the course work policy
42
+ `workReviewRequired`/`workPolicyVersion`. A guardian calls `resolveBookPageActivity`
43
+ with an explicit learner, book key, title, edition (empty means unknown), page key
44
+ and printed label. Keys identify the physical copy/edition and page occurrence,
45
+ including section/side. The family's book course is found or created
46
+ (deterministic id, so concurrent first use collides), the learner is enrolled on
47
+ first use, and the page key matches a `LessonPage` in the course; a new page gets a
48
+ one-page Worksheet lesson. A learner keeps the page lesson their earlier work used
49
+ (merged books may hold two lessons for one key). A conflicting title/edition for a
50
+ key, or a page key naming another label, fails. Names and photo hashes never
51
+ establish identity.
52
+
53
+ `captureLessonWork` owns original files through SubmissionAsset and creates an
22
54
  immutable attempt with source SHA-256 hashes, actual page bounds, actor, capture
23
55
  time, assistance, correction predecessor/episode, policy version and effective
24
- review-required flag. Assistance defaults to `unknown`. Capture retries reuse the
25
- same Submission/attempt and reject changed bytes or help claims. A correction
26
- requires the same learner/activity/current predecessor and stays in the same
27
- practice episode. Analysis/reanalysis adds revisions, never practice attempts.
28
- Existing LessonProgress.attempts remains a compatibility capture counter.
29
-
30
- The workbook review-required policy defaults on and is shared by its pages.
31
- `setWorkbookFeedbackPolicy` uses expected policy version; existing capture snapshots
32
- remain unchanged. Policy changes cannot erase prior disclosure. Guarded capture,
33
- policy and decision writes serialize on the learner within the active transaction.
34
- Ingestion target resolution acquires that learner lock before the enrollment lock,
35
- matching capture/review order. Guardian apply follows authorized timeline cursors
36
- when locating a preserved assessment on an older attempt.
37
- Composite mutations also use the education transaction's serialized composite
38
- queue, allowing inherited queued capture/review writes without queue recursion.
39
- Any swallowed mutation error still aborts the complete transaction and file rollback
40
- uses the inherited fresh-file compensation ledger.
56
+ review-required flag. Each page may carry a `pageKey`; a correction may carry only
57
+ the pages it replaces, and `WorkAttemptView.pages` folds the versions over the
58
+ correction chain (`current` plus every version oldest first, through the same
59
+ submission privacy projection). Assistance defaults to `unknown`. Capture retries
60
+ reuse the same Submission/attempt and reject changed bytes or help claims. A
61
+ correction requires the same learner/activity/current predecessor and stays in the
62
+ same practice episode. Analysis/reanalysis adds revisions, never practice attempts.
63
+
64
+ `setBookCourseSubject({courseId, subject})` sets or clears (`null`) a family book
65
+ course's subject (text up to 200 characters) with the same plan authority; shared
66
+ catalog and plain courses are refused. `fileLessonWork({newBook:{subject?}})` sets it
67
+ on a new book; the workbook migration leaves it unset.
68
+
69
+ `setCourseWorkPolicy({courseId, expectedPolicyVersion, reviewRequired})` needs plan
70
+ authority over every learner enrolled in the course; capture snapshots never change.
71
+ Filed work snapshots the policy of the course it is placed in. Policy changes cannot
72
+ erase prior disclosure. Guarded capture, policy and decision writes serialize on the
73
+ learner within the active transaction. Ingestion target resolution acquires that
74
+ learner lock before the enrollment lock, matching capture/review order. Composite
75
+ mutations use the education transaction's serialized composite queue; any swallowed
76
+ mutation error still aborts the transaction and file rollback uses the inherited
77
+ fresh-file compensation ledger.
78
+
79
+ `getLessonActivity`/`listLessonActivities({courseId?, filing?})` return the routing
80
+ (`enrollmentId`, `lessonId`: captures and submission reads use these and they never
81
+ move) and the effective placement: `courseId`, `courseTitle`, `bookKey`,
82
+ `bookEdition`, `lessonTitle`, `pageKey`/`pageLabel`, the lesson's `pages` and the
83
+ course policy.
41
84
 
42
85
  ## Structured assessment and real ingestion
43
86
 
44
- `WorkbookAssessment` version 1 has stable question IDs, original/corrected prompt
87
+ `WorkAssessment` version 1 has stable question IDs, original/corrected prompt
45
88
  and answer text, explicit result/reason, nullable points, feedback, retained source
46
89
  anchors and curated concept mappings. An anchor names an owned asset plus SHA-256,
47
90
  one-based verified page (or null for a whole-file PDF source) and normalized
@@ -58,14 +101,14 @@ is explicitly persisted as unknown. Reviewed overrides append new bodies and kee
58
101
  original AI text inspectable.
59
102
 
60
103
  The configurable vocabulary is trusted server configuration, not AI-created labels.
61
- The default `WORKBOOK_MATH_VOCABULARY` has addition, subtraction and place value.
62
- Other subjects may use an explicit curated `workbookVocabulary` on WorkbookService,
104
+ The default `WORK_MATH_VOCABULARY` has addition, subtraction and place value.
105
+ Other subjects may use an explicit curated `workVocabulary` on LessonWorkService,
63
106
  the ingestion handler factory and preservation helper. Unknown concept IDs fail;
64
107
  zero mapped concepts is valid. AI mappings are proposed, guardian mappings reviewed.
65
108
  Known question IDs are pinned across captures; exact prompts are pinned within a
66
109
  capture's reanalysis only. Correction transcriptions come from the new capture,
67
110
  so earlier answer-bearing prompts cannot override corrected work. Immutable
68
- historical prompts remain unchanged. The public `getWorkbookQuestionIdentity`
111
+ historical prompts remain unchanged. The public `getWorkQuestionIdentity`
69
112
  returns IDs only: historical prompt text can itself include a learner response.
70
113
  Handlers use printed number/occurrence/subpart when present; unnumbered tasks use
71
114
  answer-free printed-task slugs with visible-task occurrence/subpart, never output
@@ -79,10 +122,10 @@ semantic equivalence of printed tasks; uncertain correspondence requires human
79
122
  review rather than an invented match.
80
123
  A different page/question requires explicit new activity identity, never AI retargeting.
81
124
 
82
- `captureEducationIntake` accepts optional `workbookActivityId`/`assistance` when its
83
- service is WorkbookService. It preserves the existing return SubmissionView and
84
- binds the workbook attempt/policy before the host queues processing.
85
- `educationIntakeHandlers({workbookActivityId,...})` advertises a bounded JSON question
125
+ `captureEducationIntake` accepts optional `lessonActivityId`/`assistance` when its
126
+ service is LessonWorkService. It preserves the existing return SubmissionView and
127
+ binds the lesson work attempt/policy before the host queues processing.
128
+ `educationIntakeHandlers({lessonActivityId,...})` advertises a bounded JSON question
86
129
  schema to real ingestion generation. Assessed results require reason none;
87
130
  unassessed reasons require null points. Per-question outcomes must agree with
88
131
  points. Correctness is independent of scoring: readable objectively solvable
@@ -94,8 +137,8 @@ Incorrect earns zero only when the possible-point allocation is known.
94
137
  Model aggregate scores/narratives are not requested or trusted: exact
95
138
  count summaries and points scores are computed from validated questions without
96
139
  rounding or invented partial credit. Default legacy handlers remain compatible.
97
- `preserveWorkbookIntakeAnalysis` reads the public immutable completed generation,
98
- checks its exact submission and offered workbook schema, maps cited original
140
+ `preserveWorkIntakeAnalysis` reads the public immutable completed generation,
141
+ checks its exact submission and offered marking schema, maps cited original
99
142
  ingestion evidence by hash/order to retained education-owned files and imports one
100
143
  validated proposal. Provider/model/version, configuration/rubric, source analysis
101
144
  pin and proposal digest are durable provenance. Pending/failed processing produces
@@ -112,8 +155,8 @@ configuration digest; question anchors always use honest source-only grounding.
112
155
 
113
156
  ## Authority, visibility and history
114
157
 
115
- `recordWorkbookAnalysis` accepts authorized submit context and writes provisional
116
- AI revisions only. `reviewWorkbookAssessment` requires guardian authority, exact
158
+ `recordWorkAnalysis` accepts authorized submit context and writes provisional
159
+ AI revisions only. `reviewWorkAssessment` requires guardian authority, exact
117
160
  source revision, expected current guardian head and stable request key. It appends
118
161
  an immutable guardian revision recording sourceRevisionId, superseded head, actor,
119
162
  time and decision reason. Changed retries conflict. Review of the current capture
@@ -122,22 +165,22 @@ its evidence authority only and cannot grade or reopen its successor. Reanalysis
122
165
  after a guardian decision remains provisional. A guardian can invalidate an outcome
123
166
  with an unassessed replacement and a stated reason; old AI does not regain authority.
124
167
 
125
- `releaseWorkbookFeedback` separately releases an exact revision using expected
168
+ `releaseWorkFeedback` separately releases an exact revision using expected
126
169
  current guardian head. New stale releases fail; accepted retry requests replay their
127
170
  original result after later decisions. Release creates no Grade. With the gate off,
128
171
  AI feedback is visible as provisional while authoritative grading still requires a
129
172
  guardian. Gate-on learners receive no unreleased assessment payloads, scores or
130
- hints. WorkbookService's ordinary getSubmission also redacts Grade for own learners;
131
- hosts must route workbook reads through this service and prevent legacy routes from
132
- serving workbook grades. Guardian history is complete; learners see only their own
173
+ hints. LessonWorkService's ordinary getSubmission also redacts Grade for own learners;
174
+ hosts must route lesson work reads through this service and prevent legacy routes from
175
+ serving gated grades. Guardian history is complete; learners see only their own
133
176
  work and visible revisions. Answer keys/source storage URIs never enter these DTOs.
134
177
 
135
- `acknowledgeWorkbookFeedbackDelivery` is an authenticated own-learner acknowledgment
178
+ `acknowledgeWorkFeedbackDelivery` is an authenticated own-learner acknowledgment
136
179
  for an exact currently visible feedback revision. It deduplicates per actor/revision
137
180
  and rejects withheld, foreign, sibling and guardian acknowledgments. Immutable
138
181
  delivery events record actor/time and mean rendered/delivered, never read or understood.
139
182
 
140
- `getWorkbookTimeline` uses education rows/assets without reading ingestion. It lists
183
+ `getWorkTimeline` uses education rows/assets without reading ingestion. It lists
141
184
  newest captures/revisions first, includes all releases/delivery events and provides
142
185
  cursor pagination (1–100, default 25). A guardian revisionId selector locates its
143
186
  containing capture for evidence links. Guardian source IDs/actors/reasons remain
@@ -154,13 +197,13 @@ percentage, generic score comparison, hidden threshold or predictive label is us
154
197
 
155
198
  ## Preserved processing status and retention
156
199
 
157
- `getWorkbookPreservationState` authorizes exact actor/learner/activity/submission
200
+ `getWorkPreservationState` authorizes exact actor/learner/activity/submission
158
201
  and returns only the intake ID plus retained assessment/analysis IDs and the
159
202
  interpretation revision. It contains no questions, scores, feedback or hidden
160
203
  assessment payloads. New AI provenance records `analysisRevision` from the
161
204
  completed interpretation snapshot, separately from its extraction-source pin.
162
205
 
163
- `preserveWorkbookIntakeAnalysis` checks this education-owned state and the public
206
+ `preserveWorkIntakeAnalysis` checks this education-owned state and the public
164
207
  content-free ingestion item revision before requesting raw completed analysis.
165
208
  An exact already-preserved revision returns `retained` immediately. Guardian
166
209
  review, release, corrections and receipt expiry therefore do not force a stale
@@ -184,19 +227,19 @@ the authorized education timeline independently of optional scratch processing.
184
227
 
185
228
  ## Compatibility API privacy boundary
186
229
 
187
- Workbook feedback is projected only from the revision/release workflow. The base
188
- `SubmissionService` recognizes authoritative `WorkbookActivity` lesson ownership,
189
- including calls through a plain service instance. Own-learner compatibility views
190
- withhold Grade and bestScore. Every workbook progress projection uses the explicit
230
+ Book and loose-page feedback is projected only from the revision/release workflow.
231
+ The base `SubmissionService` recognizes it from the authoritative activity and its
232
+ course, including calls through a plain service instance. Own-learner compatibility
233
+ views withhold Grade and bestScore. Their progress projection uses the explicit
191
234
  parent decision for status and completedAt; historical grade-derived completion
192
- alone is Doing with no completion date. Ordinary non-workbook projections retain
193
- their existing behavior.
235
+ alone is Doing with no completion date. Ordinary lessons keep grade-visible,
236
+ grade-completes behaviour, recorded as the completion decision.
194
237
  This applies in both feedback policy modes; direct feedback remains provisional
195
238
  and does not authorize compatibility Grade disclosure.
196
239
 
197
240
  ## Explicit parent completion
198
241
 
199
- `completeWorkbookActivity` requires the current verified guardian, exact activity,
242
+ `completeLessonActivity` requires the current verified guardian, exact activity,
200
243
  submissionId, expectedAttemptId, explicit expectedHeadId (including null), and a
201
244
  bounded requestKey. The SDK independently denies own learners for
202
245
  `education.learner.complete`, even when a host callback grants it. The head token
@@ -204,12 +247,12 @@ is the latest guardian revision, otherwise latest AI revision, otherwise null;
204
247
  null permits a parent's judgment without any assessment. The decision and progress
205
248
  update share the authorized transaction and learner serialization lock. No grade,
206
249
  assessment, feedback release, or learner visibility is created by completion.
207
- Generic guardian grading of workbook submissions also cannot complete work.
250
+ Generic guardian grading of book or loose-page submissions cannot complete work.
208
251
 
209
- `WorkbookCompletionDecision` retains immutable server actor/time, attempt,
252
+ `WorkCompletionDecision` retains immutable server actor/time, attempt,
210
253
  submission, head provenance, and retry key. Exact retries return that historical
211
254
  decision even after a correction; changed retries and stale selectors reject with
212
- `WorkbookCompletionConflictError` (changed_retry, stale_attempt, stale_head, or
255
+ `WorkCompletionConflictError` (changed_retry, stale_attempt, stale_head, or
213
256
  already_complete). Hosts should acknowledge recording and reread current state,
214
257
  never interpret a historical retry as completing a newer attempt.
215
258
 
@@ -229,36 +272,62 @@ The existing attempt headId continues to mean guardian review head.
229
272
  Deploy the ordinary additive manifest migration before recording decisions. Reads
230
273
  of an unmigrated decision table report Doing; runtime handlers never create it.
231
274
 
232
- Generic capture/resubmission rejects workbook enrollment or lesson scaffolding,
233
- including idempotent retries. Only the protected exact-activity owning workflow
234
- can capture workbook work. Hosts must call public
235
- `assertNonWorkbookSubmissionScope` before legacy intake receipt creation and when
236
- reading an old legacy binding, using authoritative enrollment, lesson and
237
- predecessor selectors. `WorkbookWorkflowRequiredError` is a controlled denial.
238
- The browser's optional workbook flag is never authority. Authenticated original
239
- asset delivery remains a separate read authorization path.
275
+ Generic capture/resubmission rejects book and loose-page work, including
276
+ idempotent retries; only the protected exact-activity workflow captures it. Hosts
277
+ call `assertOrdinarySubmissionScope` before generic intake receipt creation and
278
+ when reading an old binding, using authoritative enrollment, lesson and predecessor
279
+ selectors. `LessonWorkWorkflowRequiredError` is a controlled denial. A browser flag
280
+ is never authority. Authenticated original asset delivery remains a separate read
281
+ authorization path.
240
282
 
241
283
  ## Validation and limits
242
284
 
243
- `src/__tests__/workbook-workflows.ts` is the shared SQLite/PostgreSQL contract suite;
244
- `workbook-service.test.ts` runs SQLite, and `workbook.optional.test.ts` runs against
245
- an available isolated PostgreSQL database. Workers never provision service stacks.
285
+ `src/__tests__/lesson-work-workflows.ts` is the shared SQLite/PostgreSQL contract
286
+ suite; `lesson-work-service.test.ts` runs SQLite, and `lesson-work.optional.test.ts`
287
+ runs against an available isolated PostgreSQL database. `lesson-work-pipeline.test.ts`
288
+ covers ordinary lessons and per-page corrections, `lesson-work-filing.test.ts`
289
+ placement filing, and `course-work-migration.test.ts` (with its optional PostgreSQL
290
+ twin) the workbook migration. Workers never provision service stacks.
246
291
  Run package test/typecheck/build; the integration owner runs test:postgres and the
247
292
  integrated review. Synthetic provider DTO tests establish transfer integrity, not
248
293
  real handwriting accuracy. Actual provider/sample and app restart/retention
249
294
  acceptance remain separate integration evidence. Family deletion/export/backup
250
295
  policy and automatic lesson generation are not implemented by this milestone.
251
296
 
252
- ## Identity-independent work
297
+ ## Identity-independent work and filing
253
298
 
254
299
  [Photo intake](photo-intake.md#marking-before-filing) may own an explicit unfiled
255
- work unit. Its nullable workbook FK is constrained to an exact retained receipt;
256
- private enrollment/worksheet routing is technical rather than bibliographic.
257
- The same attempt/assessment/review/release and concept-evidence contracts apply.
258
- `filing` is optional on legacy-shaped DTOs and explicit on current projections.
259
- Append-only filing changes display association only; it never moves immutable
260
- learning evidence or changes captured review policy. Ordinary identified workbook
261
- identity and generated-surface restrictions remain unchanged.
300
+ work unit bound to an exact retained receipt; its private enrollment/worksheet
301
+ routing is technical rather than bibliographic. The same attempt, assessment,
302
+ review, release and concept-evidence contracts apply.
303
+
304
+ `fileLessonWork({activityId, courseId | newBook:{title, edition?, bookKey?},
305
+ lessonId?, pageKey?, pageLabel?, expectedRevisionId, requestKey, reason})` (`newBook`
306
+ may carry `subject`) is the
307
+ guardian's placement (plan authority). Exactly one of `courseId`/`newBook`. The
308
+ target lesson is the given lesson (which must be in the learner's enrollment of that
309
+ course; a page key matches or adds a `LessonPage` on it, adding only on a
310
+ family-owned lesson, else `shared-lesson-page`), else the course's page with
311
+ `pageKey`, else a new one-page Worksheet lesson in a family book or plain course.
312
+ An authored course needs the lesson first: append it with
313
+ `CourseAuthoringService.appendLesson` and file with its `lessonId`
314
+ (`LessonWorkPlacementError` `lesson-required`). A course the learner is not enrolled
315
+ in fails `not-enrolled` unless it is the family's own book course (enrolled on first
316
+ use); another family's course is denied. An omitted new-book key is an opaque manual
317
+ key for this work/request, never a title match.
318
+
319
+ A filing appends a `LessonWorkFiling` naming the course, enrollment, lesson and
320
+ page; the head is the work's placement. Lists by course include it, `filing:
321
+ 'unfiled'` no longer does, and the target lesson's progress becomes Doing (Done once
322
+ the work's current attempt is complete). Completion decisions, new captures and
323
+ corrections keep it in step in the same transaction: a filing never lowers it, and
324
+ a correction reopens only a completion this placed work produced (its completedAt
325
+ is one of the work's own decisions); attested, legacy and other completions stay.
326
+ A new destination row starts at the work's first capture. Progress reads
327
+ (`listProgress`, plans) count completed work filed onto the lesson. Activity, attempt, submission, assessment,
328
+ release and correction-episode IDs never move, and captured policy snapshots never
329
+ change. Stable request-key retries replay; changed retries and stale heads conflict;
330
+ book creation and filing share one transaction. Re-filing names the current head.
262
331
 
263
332
  ### Unnumbered visible questions
264
333
 
@@ -312,7 +381,7 @@ other clearly visible questions are assessed in the same valid proposal.
312
381
 
313
382
  ### Observed assessment layout
314
383
 
315
- Optional `WorkbookAssessment.layout` version 1 describes observed sections in source
384
+ Optional `WorkAssessment.layout` version 1 describes observed sections in source
316
385
  order and zero-based row/column question placements. It is advisory, with fixed
317
386
  `provenance: 'observed'`; it never establishes identity or verified geometry.
318
387
  Sections reference a retained source asset and a physical one-based observed page,
@@ -324,7 +393,7 @@ original question array index for ties. If any placement in that cell omits
324
393
  `part`, the whole cell follows original question array order. Partial placement is valid. Missing `readingOrder` preserves original
325
394
  question order for a flattened display.
326
395
 
327
- The browser-safe `normalizeWorkbookObservedLayout` copies only bounded known
396
+ The browser-safe `normalizeWorkObservedLayout` copies only bounded known
328
397
  fields: at most 32 sections and 200 placements, 1–8 columns, rows 0–199, parts
329
398
  0–31, section IDs up to 64 characters and labels up to 200. Duplicate section IDs
330
399
  or question placements, invalid source/page/placement references and malformed
@@ -337,10 +406,10 @@ inside existing revision privacy and exact release boundaries; no migration or
337
406
  additional learner visibility is introduced.
338
407
 
339
408
  Focused shared SQLite/PostgreSQL cases live in
340
- `src/__tests__/workbook-layout-workflows.ts`, with SQLite in
341
- `workbook-layout-service.test.ts` and isolated PostgreSQL in
342
- `workbook-layout.optional.test.ts`. Pure bounds/reference cases are in
343
- `workbook-layout.test.ts`. Integration runs the PostgreSQL wrapper under its
409
+ `src/__tests__/lesson-work-layout-workflows.ts`, with SQLite in
410
+ `lesson-work-layout-service.test.ts` and isolated PostgreSQL in
411
+ `lesson-work-layout.optional.test.ts`. Pure bounds/reference cases are in
412
+ `lesson-work-layout.test.ts`. Integration runs the PostgreSQL wrapper under its
344
413
  existing isolated database lifecycle.
345
414
 
346
415
  Generation carries optional `layout` as advisory JSON so malformed presentation
@@ -354,7 +423,7 @@ never establishes a question ID, a source authorization or a verified region.
354
423
 
355
424
  ### Same-capture reread context
356
425
 
357
- `getWorkbookQuestionContext` returns authorized canonical `{id,prompt}` only
426
+ `getWorkQuestionContext` returns authorized canonical `{id,prompt}` only
358
427
  from the exact current attempt's identity revision. Generation preserves those
359
428
  strings verbatim for the existing strict same-capture prompt invariant, while
360
429
  rereading learner responses and any observed layout from current evidence. This
@@ -362,7 +431,7 @@ context is not an answer key or authority for answers. A correction without its
362
431
  own revision receives predecessor IDs only; predecessor prompts are never copied.
363
432
  Exact source/revision and full correction-ancestry visibility apply to both paths,
364
433
  including own-learner reads. No historical rows are rewritten and absent layout
365
- remains valid. `getWorkbookQuestionIdentity` remains the IDs-only public API.
434
+ remains valid. `getWorkQuestionIdentity` remains the IDs-only public API.
366
435
 
367
436
  Canonical prompts follow ordinary assessment payload visibility, including
368
437
  captured review policy and exact guardian revision release even for learner-owned
@@ -1,4 +1,4 @@
1
- # Workbook behavior/test matrix
1
+ # Lesson work behavior/test matrix
2
2
 
3
3
  Design derived from the approved Hearth workbook plan (2026-10-22), learning-record
4
4
  contract and WB-001–WB-019 preparation packet (2026-10-23). Feature-class additive
@@ -54,7 +54,7 @@ SessionService heartbeat regression belongs to the host integration lane; synthe
54
54
  SQLite affinity alone is not proof that a pooled session lock can never deadlock.
55
55
 
56
56
  Preservation regression uses actual public IngestionService generation with an
57
- injected synthetic generator and actual WorkbookService persistence. It proves
57
+ injected synthetic generator and actual LessonWorkService persistence. It proves
58
58
  the raw stale-snapshot guard still rejects after grading while preservation skips
59
59
  the imported revision, allows a genuinely new provisional reanalysis, and keeps
60
60
  review/release/correction history and child feedback gates intact after public
@@ -63,7 +63,7 @@ is not imported; unexpected database errors propagate. This is service-integrity
63
63
  evidence, not live-provider accuracy evidence.
64
64
 
65
65
  Independent-review privacy regression (WB-PRIVACY-1/2): the shared suite exercises
66
- plain LearnerService/SubmissionService and WorkbookService after guardian review
66
+ plain LearnerService/SubmissionService and LessonWorkService after guardian review
67
67
  before release, in both policy modes. getSubmission, capture retry, listProgress,
68
68
  assignmentLessons, startLesson and nextLessons do not disclose Grade, score or
69
69
  grade-derived completion to the own learner. Generic capture/resubmission and
@@ -1,10 +1,13 @@
1
- # Photo-first workbook intake
1
+ # Photo-first book page intake
2
2
 
3
- `WorkbookPhotoService` extends `WorkbookService` with the same constructor,
3
+ `WorkbookPhotoService` extends `LessonWorkService` with the same constructor,
4
4
  transaction executor, actor/tenant checks, private storage and rollback ledger.
5
5
  Import server integration from `@happyvertical/smrt-education/ingestion`.
6
6
  Apply manifest migrations for `WorkbookPhotoIntake`, `WorkbookPhotoAsset`,
7
- `WorkbookPhotoDecision` and `WorkbookContextRevision` before deploying. There is
7
+ `WorkbookPhotoDecision` and `BookContextRevision` before deploying. The photo
8
+ models keep their `Workbook*` names: they derive the persisted ingestion operation
9
+ IDs and grants captured in receipt ceilings. The book they identify is a course
10
+ with book identity ([course work](course-work-design.md)). There is
8
11
  no runtime schema creation and these models expose no generated operations. Each
9
12
  concrete table declares its own tenant/learner fields; a custom abstract model
10
13
  base does not supply persisted columns to independent manifest tables.
@@ -71,24 +74,26 @@ authorized contextual record with retained publisher provenance; that publisher
71
74
  is context, never a fabricated current quotation. Conflicting or ambiguous
72
75
  matches stay pending. Provider
73
76
  candidates contain book metadata only, never reference text or answer keys.
74
- `listWorkbookPhotoWorkbooks(request)` includes cover-only books for the provider
75
- and guardian picker. A printed lesson identity supplies one stable activity key;
77
+ `listPhotoBookCourses(request)` returns the family's book courses (including
78
+ cover-only books) for the provider and guardian picker, with publisher/ISBN context
79
+ from this learner's own decisions. Provider candidates are `Course` targets; the
80
+ wire candidate id (`workbookId`) is the course id and its `workbookKey` the book key. A printed lesson identity supplies one stable activity key;
76
81
  multiple page files may belong to that same lesson. Page number alone cannot
77
82
  automatically establish lesson identity.
78
83
 
79
84
  A guardian may call `resolveWorkbookPhotoIntake({...request,intakeId,requestKey,
80
85
  identification})` after upload. It requires planning authority and appends a new
81
86
  decision. Stable request-key retries replay; changed retries conflict. Covers and
82
- references create/reuse the Workbook and append contextual metadata only: no
83
- Course, Lesson, progress, attempt, Grade or concept observations. Existing
84
- Workbook title/edition identity never changes; context versions retain original
87
+ references create/reuse the family's book course and append contextual metadata
88
+ (`BookContextRevision`) only: no Lesson, enrollment, progress, attempt, Grade or
89
+ concept observations. Existing book title/edition identity never changes; context versions retain original
85
90
  metadata and source receipt identity. A captured intake cannot be retargeted.
86
91
 
87
92
  ## Existing marking pipeline
88
93
 
89
94
  `captureWorkbookPhotoIntake({...request,intakeId,requestKey,assistance?,
90
95
  previousSubmissionId?,completedWork?})` copies the retained originals through
91
- `captureWorkbookWork` and returns its immutable attempt. Blank or unknown work
96
+ `captureLessonWork` and returns its immutable attempt. Blank or unknown work
92
97
  requires explicit `completedWork:true`; no blank exercise is automatically marked
93
98
  as unanswered learner work. Corrections require the explicit current predecessor
94
99
  and preserve the existing practice episode. Existing attempts reject automatic
@@ -99,7 +104,7 @@ After capture, configure the existing `educationIntakeHandlers` with the returne
99
104
  submission ID, `view.activity.id`, and authoritative activity enrollment ID.
100
105
  Queue `queueEducationExtraction(ingestion,view.itemId,configuration,submissionId)`
101
106
  on the **same learner-scoped receipt**, process normally, then use
102
- `preserveWorkbookIntakeAnalysis`. That bridge already verifies exact education
107
+ `preserveWorkIntakeAnalysis`. That bridge already verifies exact education
103
108
  ownership and source binding independently of the receipt's confidential scope.
104
109
  Guardian Grade/release policy and immutable assessment history are unchanged.
105
110
 
@@ -126,7 +131,7 @@ work; a base regression run is N/A because no pre-activity capture API existed.
126
131
  The immutable photo receipt actor, compared with the persisted learner user,
127
132
  determines source origin. Classification as a lesson does not grant learner
128
133
  access. `Submission.intakeItemId` joins the exact tenant/learner receipt; only a
129
- `WorkbookFeedbackRelease` on that submission's exact `WorkbookAttempt` releases
134
+ `WorkFeedbackRelease` on that submission's exact `WorkAttempt` releases
130
135
  its source. Another attempt or context-only page cannot borrow that release.
131
136
 
132
137
  The base `SubmissionService` applies this policy too: own learners receive no
@@ -135,7 +140,7 @@ a guessed source asset is denied. The DTO exposes `awaitingParentRelease:true`
135
140
  for an own learner awaiting release, and `sourceRequiresParentRelease:true` to
136
141
  all authorized actors for guardian-origin material. The latter remains true
137
142
  after release: every new derived revision still needs its own explicit release.
138
- `WorkbookAttemptView.reviewRequired` and persisted capture policy snapshots
143
+ `WorkAttemptView.reviewRequired` and persisted capture policy snapshots
139
144
  retain their historical workbook setting; combine that setting with
140
145
  `submission.sourceRequiresParentRelease` when describing effective visibility. Workbook timeline and
141
146
  feedback delivery acknowledgment require exact revision release for this source,
@@ -152,7 +157,7 @@ tables.
152
157
 
153
158
  ## Existing candidate identification
154
159
 
155
- `workbookId` selects an exact authorized candidate; it does not replace the
160
+ The provider's `workbookId` selects an exact authorized candidate (a book course id); it does not replace the
156
161
  required `identification.workbook` contract. Copy that candidate's `workbookKey`,
157
162
  `title` and `edition` into the required object. Those are catalog identity fields,
158
163
  not invented IDs or claims about what is printed on the new photograph.
@@ -194,56 +199,38 @@ printed examples separate from learner marks and reports matching endpoints only
194
199
  when observed. Adopt it with a new extraction configuration revision.
195
200
 
196
201
  The internal private Course/Enrollment/Worksheet are routing records, not a claim
197
- about a canonical book or lesson. `WorkbookActivity.workbookId` is null only for
198
- explicit `captureKind:'unfiled'` with an exact owned `photoIntakeId`; all ordinary
199
- identified activity invariants remain. No Workbook is invented. Activity DTO
200
- `filing.state` is `unfiled`, `filed` or `identified`; empty workbook title/key/edition
201
- on unfiled work are intentional. Use the work label as the primary display.
202
- `listWorkbookActivities` includes these work units. Exact
202
+ about a canonical book or lesson. Only `captureKind:'unfiled'` carries an exact owned
203
+ `photoIntakeId`; all identified activity invariants remain. No book is invented.
204
+ Activity DTO `filing.state` is `unfiled`, `filed` or `identified`; unfiled work has
205
+ `filing.placement: null`, empty course/book fields and the `Captured work` label.
206
+ `listLessonActivities` includes these work units. Exact
203
207
  `getWorkbookPhotoTimeline({...request,intakeId,limit?,cursor?,revisionId?})`
204
208
  returns existing authorized feedback/history; the normal activity timeline also
205
- works. `getWorkbookActivity` refreshes exact filing presentation without loading
209
+ works. `getLessonActivity` refreshes exact filing presentation without loading
206
210
  history. The receipt view refreshes filing rather than replaying its old label.
207
211
 
208
- Optional guardian `fileWorkbookWork({...request,activityId,workbookId,
209
- canonicalActivityId?,expectedRevisionId,requestKey,reason})` appends an association
210
- to a real authorized workbook and optionally a real identified activity. Initial
211
- expected revision is null. Stable retries replay their original accepted result;
212
- changed retries/stale heads conflict. Filing does not move or recreate activity,
213
- submission, attempt, assessment, release, source or correction episode IDs. It
214
- cannot change captured review policy, create a Grade, duplicate concept evidence,
215
- or release guardian-origin material. Future corrections continue against the
216
- original work-unit ID and explicit current predecessor. Existing identified
217
- captures cannot be retargeted through filing.
218
-
219
- Apply normal manifest migrations before using this contract. Existing activities
220
- receive `capture_kind='identified'` through the declared database default; the
221
- workbook FK becomes nullable and photo-intake FK plus filing table are added.
222
- Old populated SQLite requires the explicit supported nullability-relaxation
223
- migration described by #4413; no runtime DDL or manual SQL workaround is allowed.
224
-
225
- When no book record exists, the same optional filing operation accepts
226
- `newWorkbook:{title,edition?,workbookKey?}` instead of `workbookId`. Exactly one is
227
- required. Parents need only title/edition; an omitted key becomes an opaque stable
228
- manual identity for this work/request, never an automatic title-based match.
229
- An explicit existing key must retain its exact title/edition. New metadata cannot
230
- also claim `canonicalActivityId`. Book creation and filing share the guardian's
231
- serialized transaction: stale/unauthorized/conflicting requests create nothing,
232
- and a later failure rolls both back. No cover re-upload, canonical lesson,
233
- submission replacement or second assessment is needed. New books keep the default
234
- review policy; filing never changes the source attempt's captured policy.
212
+ Guardian `fileLessonWork` places unfiled work in a course (an existing book course,
213
+ a new book, or another enrolled course), matching or appending the lesson page; see
214
+ [lesson work filing](lesson-work-service.md#identity-independent-work-and-filing).
215
+ The work leaves the unfiled list at once and its target lesson follows its
216
+ completion. Filing does not move or recreate activity, submission, attempt,
217
+ assessment, release, source or correction episode IDs, cannot change captured
218
+ review policy, create a Grade, duplicate concept evidence, or release
219
+ guardian-origin material. Future corrections continue against the original
220
+ work-unit ID and explicit current predecessor and snapshot the placed course's
221
+ policy. Identified captures cannot be refiled.
235
222
 
236
223
  ## Exact cover context for header selection
237
224
 
238
- `getWorkbookPhotoIntake` adds optional nullable `workbookId`, loaded from the latest
239
- retained resolution's exact WorkbookContextRevision under the same tenant/learner
225
+ `getWorkbookPhotoIntake` adds nullable `bookCourseId`, loaded from the latest
226
+ retained resolution's exact BookContextRevision under the same tenant/learner
240
227
  and decision ID. Pending/uncertain or unfiled-work resolution returns null; this is
241
228
  independent of a later activity filing. It never guesses from title or an inbox
242
229
  list. Existing guardian-origin receipt privacy still applies before lookup.
243
230
 
244
231
  Header cover camera/upload uses the normal receive/process photo flow. A confident
245
232
  cover resolves book/context only, with no attempt, lesson or Grade. Read that exact
246
- intake, then let the guardian explicitly choose `fileWorkbookWork({workbookId,...})`.
233
+ intake, then let the guardian explicitly choose `fileLessonWork({courseId: bookCourseId,...})`.
247
234
  An uncertain cover can use the existing guardian `resolveWorkbookPhotoIntake` with
248
235
  manual cover metadata, followed by the same exact getter and explicit filing.
249
236
  Neither cover identification nor context alone publishes learner source/feedback.
@@ -49,7 +49,7 @@ page/rect anchor) and flat `layoutParams`; they are part of the lesson revision
49
49
  digest, and saving requires each id to be an existing question revision of the
50
50
  tenant (`QuestionReferenceError`). Newer question revisions never move a pin.
51
51
 
52
- `WorkbookQuestion.questionRevisionId`/`lessonRevisionId` and the nullable
52
+ `WorkQuestion.questionRevisionId`/`lessonRevisionId` and the nullable
53
53
  `Grade.questionRevisionId` are additive. A referenced revision must exist in the
54
54
  tenant; `Grade.rubricResults` must then fit its rubric (criterion id, number or
55
55
  `{points}` up to `maxPoints`; empty stays unscored; results need a rubric).