@happyvertical/smrt-education 0.57.52 → 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.
- package/AGENTS.md +12 -7
- package/README.md +36 -2
- package/agents/course-work-design.md +144 -0
- package/agents/group-sharing.md +5 -1
- package/agents/learner-plan.md +3 -3
- package/agents/learner-service.md +1 -1
- package/agents/lesson-materials.md +52 -0
- package/agents/{workbook-service.md → lesson-work-service.md} +164 -95
- package/agents/{workbook-test-matrix.md → lesson-work-test-matrix.md} +3 -3
- package/agents/photo-intake.md +37 -50
- package/agents/question-records.md +1 -1
- package/agents/submission-service.md +22 -6
- package/dist/catalog-service.d.ts +1 -1
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog.d.ts +11 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/chunks/{workbook-photo-service-BtMQsbhq.js → workbook-photo-service-COtyQL1y.js} +5927 -4963
- package/dist/chunks/workbook-photo-service-COtyQL1y.js.map +1 -0
- package/dist/course-authoring-contracts.d.ts +5 -1
- package/dist/course-authoring-contracts.d.ts.map +1 -1
- package/dist/course-authoring-service.d.ts.map +1 -1
- package/dist/course-work-migration.d.ts +27 -0
- package/dist/course-work-migration.d.ts.map +1 -0
- package/dist/group-sharing-contracts.d.ts +3 -3
- package/dist/group-sharing-contracts.d.ts.map +1 -1
- package/dist/group-sharing-policy.d.ts +4 -4
- package/dist/group-sharing-policy.d.ts.map +1 -1
- package/dist/group-sharing-reader.d.ts.map +1 -1
- package/dist/index.d.ts +13 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +351 -36
- package/dist/index.js.map +1 -1
- package/dist/ingestion.d.ts +12 -11
- package/dist/ingestion.d.ts.map +1 -1
- package/dist/ingestion.js +63 -56
- package/dist/ingestion.js.map +1 -1
- package/dist/learner-plan-service.d.ts +1 -1
- package/dist/learner-plan-service.d.ts.map +1 -1
- package/dist/learner-service.d.ts +19 -5
- package/dist/learner-service.d.ts.map +1 -1
- package/dist/lesson-assets.d.ts +66 -2
- package/dist/lesson-assets.d.ts.map +1 -1
- package/dist/lesson-pages.d.ts +36 -0
- package/dist/lesson-pages.d.ts.map +1 -0
- package/dist/{workbook-completion.d.ts → lesson-work-completion.d.ts} +10 -10
- package/dist/lesson-work-completion.d.ts.map +1 -0
- package/dist/{workbook-contracts.d.ts → lesson-work-contracts.d.ts} +100 -52
- package/dist/lesson-work-contracts.d.ts.map +1 -0
- package/dist/lesson-work-models.d.ts +118 -0
- package/dist/lesson-work-models.d.ts.map +1 -0
- package/dist/lesson-work-service.d.ts +239 -0
- package/dist/lesson-work-service.d.ts.map +1 -0
- package/dist/manifest.json +6504 -5419
- package/dist/smrt-knowledge.json +2147 -1744
- package/dist/submission-service.d.ts +135 -5
- package/dist/submission-service.d.ts.map +1 -1
- package/dist/submissions.d.ts +2 -0
- package/dist/submissions.d.ts.map +1 -1
- package/dist/work-answer-regions.d.ts +11 -0
- package/dist/work-answer-regions.d.ts.map +1 -0
- package/dist/work-layout.d.ts +13 -0
- package/dist/work-layout.d.ts.map +1 -0
- package/dist/workbook-photo-contracts.d.ts +9 -7
- package/dist/workbook-photo-contracts.d.ts.map +1 -1
- package/dist/workbook-photo-ingestion.d.ts +1 -1
- package/dist/workbook-photo-ingestion.d.ts.map +1 -1
- package/dist/workbook-photo-models.d.ts +5 -5
- package/dist/workbook-photo-models.d.ts.map +1 -1
- package/dist/workbook-photo-service.d.ts +13 -10
- package/dist/workbook-photo-service.d.ts.map +1 -1
- package/package.json +8 -8
- package/dist/chunks/workbook-photo-service-BtMQsbhq.js.map +0 -1
- package/dist/workbook-answer-regions.d.ts +0 -11
- package/dist/workbook-answer-regions.d.ts.map +0 -1
- package/dist/workbook-completion.d.ts.map +0 -1
- package/dist/workbook-contracts.d.ts.map +0 -1
- package/dist/workbook-layout.d.ts +0 -13
- package/dist/workbook-layout.d.ts.map +0 -1
- package/dist/workbook-models.d.ts +0 -129
- package/dist/workbook-models.d.ts.map +0 -1
- package/dist/workbook-service.d.ts +0 -163
- package/dist/workbook-service.d.ts.map +0 -1
|
@@ -1,47 +1,90 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
`WorkbookService` extends `SubmissionService` with
|
|
4
|
-
private asset runtime, transaction executor and
|
|
5
|
-
LearnerAuthorizer and CatalogAuthorizer receive the
|
|
6
|
-
heartbeats and current permission checks must use that
|
|
7
|
-
nested guardian review; a captured outer pool can wait
|
|
8
|
-
CatalogService obtains the executor through the public
|
|
9
|
-
fails closed when its four catalog collections do not
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
`
|
|
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 `
|
|
62
|
-
Other subjects may use an explicit curated `
|
|
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 `
|
|
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 `
|
|
83
|
-
service is
|
|
84
|
-
binds the
|
|
85
|
-
`educationIntakeHandlers({
|
|
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
|
-
`
|
|
98
|
-
checks its exact submission and offered
|
|
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
|
-
`
|
|
116
|
-
AI revisions only. `
|
|
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
|
-
`
|
|
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.
|
|
131
|
-
hosts must route
|
|
132
|
-
serving
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
|
|
188
|
-
`SubmissionService` recognizes authoritative
|
|
189
|
-
including calls through a plain service instance. Own-learner compatibility
|
|
190
|
-
withhold Grade and bestScore.
|
|
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
|
|
193
|
-
|
|
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
|
-
`
|
|
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
|
|
250
|
+
Generic guardian grading of book or loose-page submissions cannot complete work.
|
|
208
251
|
|
|
209
|
-
`
|
|
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
|
-
`
|
|
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
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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__/
|
|
244
|
-
`
|
|
245
|
-
an available isolated PostgreSQL database.
|
|
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
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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 `
|
|
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 `
|
|
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__/
|
|
341
|
-
`
|
|
342
|
-
`
|
|
343
|
-
`
|
|
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
|
-
`
|
|
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. `
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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
|
package/agents/photo-intake.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
# Photo-first
|
|
1
|
+
# Photo-first book page intake
|
|
2
2
|
|
|
3
|
-
`WorkbookPhotoService` extends `
|
|
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 `
|
|
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
|
-
`
|
|
75
|
-
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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. `
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
`filing.
|
|
201
|
-
|
|
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. `
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
captures cannot be
|
|
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
|
|
239
|
-
retained resolution's exact
|
|
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 `
|
|
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
|
-
`
|
|
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).
|