@happyvertical/smrt-education 0.57.33 → 0.57.34

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 (55) hide show
  1. package/AGENTS.md +8 -1
  2. package/README.md +117 -13
  3. package/agents/catalog-sync.md +46 -0
  4. package/agents/photo-intake.md +249 -0
  5. package/agents/workbook-service.md +355 -0
  6. package/agents/workbook-test-matrix.md +74 -0
  7. package/dist/catalog-acquisition-service.d.ts.map +1 -1
  8. package/dist/catalog-capability.d.ts +21 -0
  9. package/dist/catalog-capability.d.ts.map +1 -0
  10. package/dist/catalog-contracts.d.ts +40 -0
  11. package/dist/catalog-contracts.d.ts.map +1 -1
  12. package/dist/catalog-database-sync-store.d.ts.map +1 -1
  13. package/dist/catalog-models.d.ts +5 -1
  14. package/dist/catalog-models.d.ts.map +1 -1
  15. package/dist/catalog-read-service.d.ts +2 -1
  16. package/dist/catalog-read-service.d.ts.map +1 -1
  17. package/dist/catalog-service.d.ts +5 -0
  18. package/dist/catalog-service.d.ts.map +1 -1
  19. package/dist/chunks/workbook-photo-service-1wcnf-Xf.js +27359 -0
  20. package/dist/chunks/workbook-photo-service-1wcnf-Xf.js.map +1 -0
  21. package/dist/index.d.ts +11 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +339 -24864
  24. package/dist/index.js.map +1 -1
  25. package/dist/ingestion.d.ts +37 -0
  26. package/dist/ingestion.d.ts.map +1 -1
  27. package/dist/ingestion.js +829 -20
  28. package/dist/ingestion.js.map +1 -1
  29. package/dist/learner-service.d.ts +15 -0
  30. package/dist/learner-service.d.ts.map +1 -1
  31. package/dist/manifest.json +4735 -6
  32. package/dist/smrt-knowledge.json +1760 -26
  33. package/dist/sources/public-illustrative-mathematics-adapter.d.ts.map +1 -1
  34. package/dist/sources/public-illustrative-mathematics-parser.d.ts.map +1 -1
  35. package/dist/submission-service.d.ts +38 -11
  36. package/dist/submission-service.d.ts.map +1 -1
  37. package/dist/workbook-answer-regions.d.ts +11 -0
  38. package/dist/workbook-answer-regions.d.ts.map +1 -0
  39. package/dist/workbook-contracts.d.ts +224 -0
  40. package/dist/workbook-contracts.d.ts.map +1 -0
  41. package/dist/workbook-layout.d.ts +13 -0
  42. package/dist/workbook-layout.d.ts.map +1 -0
  43. package/dist/workbook-models.d.ts +129 -0
  44. package/dist/workbook-models.d.ts.map +1 -0
  45. package/dist/workbook-photo-contracts.d.ts +56 -0
  46. package/dist/workbook-photo-contracts.d.ts.map +1 -0
  47. package/dist/workbook-photo-ingestion.d.ts +70 -0
  48. package/dist/workbook-photo-ingestion.d.ts.map +1 -0
  49. package/dist/workbook-photo-models.d.ts +63 -0
  50. package/dist/workbook-photo-models.d.ts.map +1 -0
  51. package/dist/workbook-photo-service.d.ts +72 -0
  52. package/dist/workbook-photo-service.d.ts.map +1 -0
  53. package/dist/workbook-service.d.ts +158 -0
  54. package/dist/workbook-service.d.ts.map +1 -0
  55. package/package.json +8 -8
@@ -1 +1 @@
1
- {"version":3,"file":"ingestion.js","names":[],"sources":["../src/ingestion-isolation.ts","../src/ingestion.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\n\n/** Verify a real Linux cgroup-v2 native-memory ceiling before enabling decoders.\n * The worker and its children must remain in this cgroup; deployments must deny\n * cgroup writes/escape and run without privileged container capabilities.\n */\nexport async function requireIngestionMemoryLimit(\n maximumBytes: number,\n): Promise<{ memoryLimitBytes: number }> {\n if (process.platform !== 'linux')\n throw new Error(\n 'Extraction requires a Linux cgroup-v2 memory-limited worker',\n );\n if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 1)\n throw new Error('Invalid worker memory ceiling');\n const membership = await readFile('/proc/self/cgroup', 'utf8');\n const entry = membership.split('\\n').find((line) => line.startsWith('0::'));\n if (!entry) throw new Error('No cgroup-v2 membership');\n const relative = entry.slice(3);\n if (!relative.startsWith('/') || relative.split('/').includes('..'))\n throw new Error('Invalid cgroup membership');\n const root = '/sys/fs/cgroup';\n let directory = resolve(root, `.${relative}`);\n let limit = Number.POSITIVE_INFINITY;\n // An ancestor limit is authoritative too. Account for inherited limits while\n // never accepting a mere configuration promise or Node heap setting.\n while (directory === root || directory.startsWith(`${root}/`)) {\n const value = (await readFile(`${directory}/memory.max`, 'utf8')).trim();\n if (value !== 'max') {\n if (!/^\\d+$/.test(value)) throw new Error('Invalid cgroup memory limit');\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < 1)\n throw new Error('Invalid cgroup memory limit');\n limit = Math.min(limit, parsed);\n }\n if (directory === root) break;\n directory = resolve(directory, '..');\n }\n if (!Number.isFinite(limit) || limit > maximumBytes)\n throw new Error(\n 'Worker native-memory ceiling is missing or exceeds policy',\n );\n return { memoryLimitBytes: limit };\n}\n","/** Server-only paper workflow integration. Never import from browser handlers. */\nimport {\n type ExtractionAdapter,\n type ExtractionLimits,\n extractAnalysis,\n type IngestionService,\n} from '@happyvertical/smrt-ingestion/server';\n\nexport { requireIngestionMemoryLimit } from './ingestion-isolation.js';\n\n/** Immutable operator configuration; never populated from upload/model metadata. */\nexport interface EducationExtractionConfiguration {\n configurationRevision: string;\n limits: ExtractionLimits;\n}\n\n/** Allocate extraction after a durable receipt. The normal ingestion outbox\n * dispatches this revision to the application's registered job target.\n */\nexport async function queueEducationExtraction(\n ingestion: IngestionService,\n itemId: string,\n configuration: EducationExtractionConfiguration,\n submissionId?: string,\n) {\n return ingestion.analyze(\n itemId,\n { extraction: configuration },\n `education:extract:${configuration.configurationRevision}:${submissionId ?? 'identify'}`,\n );\n}\n\n/** A registered job invokes this with its persisted item/revision IDs and an\n * authenticated service scope. Providers execute outside database transactions.\n * Interpretation receives ingestion's immutable extraction pin. Neither stage\n * approves a suggestion, captures a submission, or applies a grade.\n */\nexport async function processEducationAnalysis(input: {\n ingestion: IngestionService;\n itemId: string;\n revision: number;\n workerId: string;\n adapter: ExtractionAdapter;\n extraction: EducationExtractionConfiguration;\n}): Promise<\n | { kind: 'unclaimed' | 'extraction_incomplete' | 'interpreted' }\n | { kind: 'generation_queued'; revision: number }\n> {\n const { ingestion, itemId } = input;\n const lease = await ingestion.claimAnalysis(\n itemId,\n input.revision,\n input.workerId,\n );\n if (!lease) return { kind: 'unclaimed' };\n const snapshot = await ingestion.getAnalysisInput(lease);\n if (snapshot.configuration.stage === 'interpret') {\n await ingestion.generateProposals(lease);\n return { kind: 'interpreted' };\n }\n if (!snapshot.configuration.extraction)\n throw new Error('Unsupported education analysis stage');\n const completed = await extractAnalysis(\n ingestion,\n lease,\n input.adapter,\n input.extraction,\n );\n if (!completed) return { kind: 'extraction_incomplete' };\n const result = await ingestion.getCompletedAnalysis(itemId, lease.attemptId);\n if (!['completed', 'partial'].includes(result.result.status))\n return { kind: 'extraction_incomplete' };\n const generation = await ingestion.prepareGeneration(\n itemId,\n result.attemptId,\n );\n const queued = await ingestion.analyze(\n itemId,\n generation,\n `education:interpret:${result.attemptId}`,\n );\n return { kind: 'generation_queued', revision: queued.revision };\n}\n\nimport { createHash } from 'node:crypto';\nimport type { IngestionScope } from '@happyvertical/smrt-ingestion/server';\nimport { getCurrentTenant, withTenant } from '@happyvertical/smrt-tenancy';\nimport { registerPermissionDefinitions } from '@happyvertical/smrt-users';\nimport type { LearnerRequest } from './learner-service.js';\nimport type {\n SubmissionPage,\n SubmissionService,\n} from './submission-service.js';\n\nexport type EducationIntakeCaptureRequest = LearnerRequest & {\n enrollmentId: string;\n lessonId: string;\n itemId: string;\n kind: 'blank_reference' | 'completed_work';\n previousSubmissionId?: string;\n notes?: string;\n};\n\n/** Copy immutable retained originals into education's private ownership only\n * after the host has obtained explicit assignment confirmation. Supply service\n * and scope from the SAME authenticated server factory, never browser values.\n * The owning service persists unique tenant/item binding before allowing retries.\n */\nexport async function captureEducationIntake(input: {\n ingestion: IngestionService;\n scope: Readonly<IngestionScope>;\n submissions: SubmissionService;\n request: EducationIntakeCaptureRequest;\n}) {\n const { ingestion, submissions, scope, request } = input;\n if (\n request.tenantId !== scope.tenantId ||\n request.actorUserId !== scope.actorId ||\n request.enrollmentId !== scope.confidentialScopeId ||\n !['blank_reference', 'completed_work'].includes(request.kind) ||\n (request.kind === 'blank_reference' && request.previousSubmissionId)\n )\n throw new Error('Invalid education intake binding');\n // Prove the exact active learner/enrollment/lesson assignment before reading\n // any family-private retained evidence. Intake scopes are enrollment-bound.\n await submissions.assertLessonAssignment(request);\n const evidence = (await ingestion.getEvidence(request.itemId)).filter(\n (entry) => entry.parentEvidenceId === null,\n );\n const extensions: Record<string, string> = {\n 'image/jpeg': 'jpg',\n 'image/png': 'png',\n 'image/webp': 'webp',\n 'application/pdf': 'pdf',\n };\n if (\n !evidence.length ||\n evidence.length > 100 ||\n evidence.some(\n (entry) =>\n !extensions[entry.mediaType] ||\n !Number.isSafeInteger(entry.byteLength) ||\n entry.byteLength < 1 ||\n entry.byteLength > 25 * 1024 * 1024,\n ) ||\n evidence.reduce((sum, entry) => sum + entry.byteLength, 0) >\n 100 * 1024 * 1024\n )\n throw new Error('Unsupported or oversized education intake');\n evidence.sort((a, b) => a.partId.localeCompare(b.partId));\n const pages: SubmissionPage[] = [];\n for (const entry of evidence) {\n const bytes = await ingestion.readEvidence(request.itemId, entry.id);\n if (\n bytes.byteLength !== entry.byteLength ||\n createHash('sha256').update(bytes).digest('hex') !== entry.contentHash\n )\n throw new Error('Education intake integrity mismatch');\n pages.push({\n name: `${entry.id}.${extensions[entry.mediaType]}`,\n mimeType: entry.mediaType as SubmissionPage['mimeType'],\n data: Buffer.from(bytes),\n });\n }\n const capture = {\n ...request,\n requestKey: `education:intake:${request.itemId}`,\n pages,\n intake: {\n intakeItemId: request.itemId,\n kind: request.kind,\n previousSubmissionId: request.previousSubmissionId,\n },\n };\n if (request.kind === 'blank_reference')\n return submissions.captureBlankReference({\n ...capture,\n intakeItemId: request.itemId,\n });\n return request.previousSubmissionId\n ? submissions.resubmitSubmission({\n intakeItemId: request.itemId,\n ...capture,\n previousSubmissionId: request.previousSubmissionId,\n })\n : submissions.captureSubmission(capture);\n}\n\nimport type {\n HandlerContext,\n IntakeValues,\n OperationHandler,\n} from '@happyvertical/smrt-ingestion/execution';\n\nconst LESSON = '@happyvertical/smrt-education:Lesson';\nconst SUBMISSION = '@happyvertical/smrt-education:Submission';\nconst GRADE = '@happyvertical/smrt-education:Grade';\nconst idSchema = { type: 'string', minLength: 1, maxLength: 64 };\n// Closed model transports intentionally generate no operation permissions.\n// Declaring this server catalog does not grant it to any principal or actor.\nregisterPermissionDefinitions([\n {\n slug: 'lessons.read',\n collection: 'lessons',\n qualifiedName: LESSON,\n name: 'Identify authorized education assignments',\n },\n {\n slug: 'grades.create',\n collection: 'grades',\n qualifiedName: GRADE,\n name: 'Apply guardian-reviewed education grades',\n },\n]);\n\n/** Build the authoritative discovery/review catalog for ONE authenticated\n * learner/enrollment. The application resolves request from its session and\n * assignment; model/browser output can select a lesson but cannot select a user.\n * Keep all ingestion policy layers requireReview=true; no automation opt-in.\n */\nexport function educationIntakeHandlers(input: {\n submissions: SubmissionService;\n request: LearnerRequest & { enrollmentId: string };\n /** After capture, bind a new grading stage to this exact immutable attempt. */\n submissionId?: string;\n}): OperationHandler[] {\n const { submissions, request } = input;\n const bound = async <T>(\n context: HandlerContext,\n work: (service: SubmissionService) => Promise<T>,\n ): Promise<T> => {\n if (\n context.scope.tenantId !== request.tenantId ||\n context.scope.actorId !== request.actorUserId ||\n !context.policy.requireReview\n )\n throw new Error('Education intake scope mismatch');\n // Ingestion's stable execution principal is not the human requester.\n // Preserve tenant permissions and the separate PrincipalRun runtime; only\n // the domain actor changes to the already-authenticated scope actor.\n if (!context.principal) return submissions.withExecutor(context.db, work);\n const tenant = getCurrentTenant();\n if (\n !tenant ||\n tenant.tenantId !== request.tenantId ||\n tenant.userId !== context.principal.context.userId ||\n context.principal.context.tenantId !== request.tenantId\n )\n throw new Error('Education principal tenant mismatch');\n return withTenant(\n {\n ...tenant,\n tenantId: request.tenantId,\n userId: request.actorUserId,\n superAdminBypass: false,\n },\n () => submissions.withExecutor(context.db, work),\n );\n };\n const lessons = (context: HandlerContext) =>\n bound(context, (service) => service.assignmentLessons(request));\n const requireLesson = async (args: IntakeValues, context: HandlerContext) => {\n const found = (await lessons(context)).find(\n (entry) => entry.lesson.id === args.lessonId,\n );\n if (!found) throw new Error('Education assignment unavailable');\n await context.assertTarget(LESSON, found.lesson.id);\n return found;\n };\n const handlers: OperationHandler[] = [\n {\n id: '@happyvertical/smrt-education:identify-assignment',\n version: '1',\n description:\n 'Identify an assigned lesson from a blank or completed scan. Ambiguity requires guardian correction; this action does not grade or capture.',\n operation: { model: LESSON, action: 'get', version: '1' },\n capability: { effect: 'read', idempotent: true, openWorld: false },\n argsSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['lessonId'],\n properties: { lessonId: idSchema },\n },\n resultSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['lessonId'],\n properties: { lessonId: idSchema },\n },\n resultModels: { lessonId: LESSON },\n discovery: {\n mediaTypes: [\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'application/pdf',\n ],\n references: { lessonId: { kind: 'candidate', model: LESSON } },\n candidates: async (query, context, page) => {\n // Authorize/filter exact enrollment FIRST; text only ranks that set.\n const visible = await lessons(context);\n const needle = query.toLocaleLowerCase();\n const ordered = [...visible].sort(\n (a, b) =>\n Number(b.lesson.title.toLocaleLowerCase().includes(needle)) -\n Number(a.lesson.title.toLocaleLowerCase().includes(needle)) ||\n a.lesson.id.localeCompare(b.lesson.id),\n );\n return {\n items: ordered.slice(0, page.limit).map((entry) => ({\n model: LESSON,\n id: entry.lesson.id,\n // Version binds immutable assignment identity; the host target\n // resolver must use this same format and reauthorize the plan.\n revision: `assignment:${request.enrollmentId}:${entry.lesson.id}`,\n label: entry.lesson.title,\n })),\n hasMore: ordered.length > page.limit,\n };\n },\n },\n validate: async (args, context) => {\n await requireLesson(args, context);\n return { ok: true };\n },\n preview: async (args, context) => {\n const found = await requireLesson(args, context);\n return {\n display: { lesson: found.lesson.title, gradeApplied: false },\n normalizedArgs: args,\n targetPreconditions: [\n {\n model: LESSON,\n id: found.lesson.id,\n revision: `assignment:${request.enrollmentId}:${found.lesson.id}`,\n },\n ],\n };\n },\n execution: {\n kind: 'database',\n apply: async (args, context) => {\n const found = await requireLesson(args, context);\n return { lessonId: found.lesson.id };\n },\n },\n },\n ];\n if (!input.submissionId) return handlers;\n const submissionId = input.submissionId;\n const current = (context: HandlerContext) =>\n bound(context, (service) =>\n service.getSubmission({ ...request, submissionId }),\n );\n const requireSubmission = async (\n args: IntakeValues,\n context: HandlerContext,\n ) => {\n if (args.submissionId !== submissionId)\n throw new Error('Education attempt binding mismatch');\n const submission = await current(context);\n if (\n submission.intakeItemId !== context.itemId ||\n submission.kind !== 'completed_work'\n )\n throw new Error('Education intake/attempt binding mismatch');\n await context.assertTarget(SUBMISSION, submissionId);\n return submission;\n };\n handlers.push({\n id: '@happyvertical/smrt-education:review-submission',\n version: '1',\n description:\n 'Suggest marks and feedback for this captured attempt. Only a current authorized guardian decision may apply a grade.',\n operation: { model: GRADE, action: 'create', version: '1' },\n capability: { effect: 'write', idempotent: false, openWorld: false },\n argsSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['submissionId', 'score', 'feedback'],\n properties: {\n submissionId: idSchema,\n score: { type: ['number', 'null'], minimum: 0, maximum: 1 },\n feedback: { type: 'string', maxLength: 10000 },\n },\n },\n resultSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['submissionId'],\n properties: { submissionId: idSchema },\n },\n resultModels: { submissionId: SUBMISSION },\n discovery: {\n mediaTypes: ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'],\n references: { submissionId: { kind: 'candidate', model: SUBMISSION } },\n candidates: async (_query, context) => {\n const submission = await current(context);\n return {\n items: [\n {\n model: SUBMISSION,\n id: submission.id,\n revision: `attempt:${submission.id}:${submission.status}`,\n label: 'Current captured attempt (guardian review required)',\n },\n ],\n hasMore: false,\n };\n },\n },\n validate: async (args, context) => {\n await requireSubmission(args, context);\n return { ok: true };\n },\n preview: async (args, context) => {\n const submission = await requireSubmission(args, context);\n return {\n display: {\n submissionId,\n proposedScore: args.score,\n proposedFeedback: args.feedback,\n guardianReviewRequired: true,\n },\n normalizedArgs: args,\n targetPreconditions: [\n {\n model: SUBMISSION,\n id: submissionId,\n revision: `attempt:${submission.id}:${submission.status}`,\n },\n ],\n };\n },\n execution: {\n kind: 'database',\n apply: async (args, context) => {\n await requireSubmission(args, context);\n await bound(context, (service) =>\n service.applyReviewedSubmission({\n ...request,\n submissionId,\n score: args.score as number | null,\n feedback: String(args.feedback),\n }),\n );\n return { submissionId };\n },\n },\n });\n return handlers;\n}\n\n/** Owning education target resolver for ingestion's transactional host hook.\n * Serializes with education enrollment mutations, then performs public service\n * authorization on the supplied executor. The host still owns live principal\n * grant/revocation serialization in its execution authorize callback.\n */\nexport function educationIntakeTargetResolver(input: {\n submissions: SubmissionService;\n request: LearnerRequest & { enrollmentId: string };\n submissionId?: string;\n}): import('@happyvertical/smrt-ingestion/execution').IntakeExecutionOptions['assertTarget'] {\n return async ({ db, scope, itemId, model, id, revision }) => {\n const { request, submissions } = input;\n if (\n scope.tenantId !== request.tenantId ||\n scope.actorId !== request.actorUserId\n )\n throw new Error('Education intake scope mismatch');\n await db.query(\n 'UPDATE enrollments SET id=id WHERE id=? AND tenant_id=?',\n request.enrollmentId,\n request.tenantId,\n );\n const current = await withTenant(\n {\n ...getCurrentTenant(),\n tenantId: request.tenantId,\n userId: request.actorUserId,\n superAdminBypass: false,\n },\n () =>\n submissions.withExecutor(db, async (service) => {\n if (model === LESSON) {\n const available = await service.assignmentLessons(request);\n if (!available.some((entry) => entry.lesson.id === id))\n throw new Error('Education assignment unavailable');\n return `assignment:${request.enrollmentId}:${id}`;\n }\n if (model !== SUBMISSION || id !== input.submissionId)\n throw new Error('Education target unavailable');\n const submission = await service.getSubmission({\n ...request,\n submissionId: id,\n });\n if (\n submission.intakeItemId !== itemId ||\n submission.kind !== 'completed_work'\n )\n throw new Error('Education intake/attempt binding mismatch');\n return `attempt:${submission.id}:${submission.status}`;\n }),\n );\n if (revision !== undefined && revision !== current)\n throw new Error('Education target changed');\n return { revision: current };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ingestion.js","names":["book","current","value"],"sources":["../src/ingestion-isolation.ts","../src/workbook-photo-ingestion.ts","../src/ingestion.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\n\n/** Verify a real Linux cgroup-v2 native-memory ceiling before enabling decoders.\n * The worker and its children must remain in this cgroup; deployments must deny\n * cgroup writes/escape and run without privileged container capabilities.\n */\nexport async function requireIngestionMemoryLimit(\n maximumBytes: number,\n): Promise<{ memoryLimitBytes: number }> {\n if (process.platform !== 'linux')\n throw new Error(\n 'Extraction requires a Linux cgroup-v2 memory-limited worker',\n );\n if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 1)\n throw new Error('Invalid worker memory ceiling');\n const membership = await readFile('/proc/self/cgroup', 'utf8');\n const entry = membership.split('\\n').find((line) => line.startsWith('0::'));\n if (!entry) throw new Error('No cgroup-v2 membership');\n const relative = entry.slice(3);\n if (!relative.startsWith('/') || relative.split('/').includes('..'))\n throw new Error('Invalid cgroup membership');\n const root = '/sys/fs/cgroup';\n let directory = resolve(root, `.${relative}`);\n let limit = Number.POSITIVE_INFINITY;\n // An ancestor limit is authoritative too. Account for inherited limits while\n // never accepting a mere configuration promise or Node heap setting.\n while (directory === root || directory.startsWith(`${root}/`)) {\n const value = (await readFile(`${directory}/memory.max`, 'utf8')).trim();\n if (value !== 'max') {\n if (!/^\\d+$/.test(value)) throw new Error('Invalid cgroup memory limit');\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < 1)\n throw new Error('Invalid cgroup memory limit');\n limit = Math.min(limit, parsed);\n }\n if (directory === root) break;\n directory = resolve(directory, '..');\n }\n if (!Number.isFinite(limit) || limit > maximumBytes)\n throw new Error(\n 'Worker native-memory ceiling is missing or exceeds policy',\n );\n return { memoryLimitBytes: limit };\n}\n","/** Server-only pre-activity intake bridge. Uses public ingestion APIs exclusively. */\nimport { createHash } from 'node:crypto';\nimport type { OperationHandler } from '@happyvertical/smrt-ingestion/execution';\nimport {\n GenerationSnapshotStaleError,\n type IngestionScope,\n type IngestionService,\n ProposalsNotConfiguredError,\n type ReceiveInput,\n} from '@happyvertical/smrt-ingestion/server';\nimport { getCurrentTenant, withTenant } from '@happyvertical/smrt-tenancy';\nimport { registerPermissionDefinitions } from '@happyvertical/smrt-users';\nimport { processEducationAnalysis } from './ingestion.js';\nimport type { LearnerRequest } from './learner-service.js';\nimport type { SubmissionPage } from './submission-service.js';\nimport {\n validateWorkbookPhotoIdentification,\n type WorkbookPhotoProposal,\n} from './workbook-photo-contracts.js';\nimport {\n preservePhotoProposal,\n type WorkbookPhotoService,\n} from './workbook-photo-service.js';\n\nconst MODEL = '@happyvertical/smrt-education:WorkbookPhotoIntake';\nexport const WORKBOOK_PHOTO_HANDLER =\n '@happyvertical/smrt-education:identify-workbook-photo';\nregisterPermissionDefinitions([\n {\n slug: 'workbookphotointakes.read',\n collection: 'workbookphotointakes',\n qualifiedName: MODEL,\n name: 'Identify authorized workbook photos',\n },\n]);\nfunction assertScope(scope: Readonly<IngestionScope>, request: LearnerRequest) {\n if (\n scope.tenantId !== request.tenantId ||\n scope.actorId !== request.actorUserId ||\n scope.confidentialScopeId !== request.learnerId\n )\n throw new Error('Photo intake scope mismatch');\n}\nconst canonical = (value: unknown): string =>\n JSON.stringify(value, (_key, v) =>\n v && typeof v === 'object' && !Array.isArray(v)\n ? Object.fromEntries(\n Object.entries(v).sort(([a], [b]) => a.localeCompare(b)),\n )\n : v,\n );\nconst string = {\n type: 'string',\n maxLength: 2000,\n pattern: '^[^\\\\u0000-\\\\u001F\\\\u007F]*$',\n};\nconst schema = {\n type: 'object',\n additionalProperties: false,\n required: [\n 'identification',\n 'confidence',\n 'identityEvidence',\n 'lessonEvidence',\n ],\n properties: {\n workbookId: {\n type: 'string',\n maxLength: 64,\n description:\n 'Exact existing candidate id. Also copy its workbookKey, title and edition into the required identification.workbook object; this selects existing identity, not a new workbook.',\n },\n confidence: { type: 'string', enum: ['confident', 'uncertain'] },\n identityEvidence: {\n type: 'array',\n maxItems: 20,\n items: { ...string, minLength: 1 },\n },\n lessonEvidence: string,\n identification: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'workState'],\n properties: {\n kind: {\n type: 'string',\n enum: ['lesson', 'work', 'cover', 'reference', 'uncertain'],\n },\n workState: { type: 'string', enum: ['blank', 'completed', 'unknown'] },\n notes: string,\n workbook: {\n type: 'object',\n additionalProperties: false,\n required: ['workbookKey', 'title'],\n properties: {\n workbookKey: string,\n title: string,\n edition: string,\n publisher: string,\n isbn: string,\n },\n },\n activity: {\n type: 'object',\n additionalProperties: false,\n required: ['pageKey', 'pageLabel'],\n properties: { pageKey: string, pageLabel: string },\n },\n },\n },\n },\n};\n/** Generation proposes material identity only; execution never writes educational outcomes. */\nexport function workbookPhotoIntakeHandlers(input: {\n workbooks: WorkbookPhotoService;\n request: LearnerRequest;\n}): OperationHandler[] {\n const bound = async <T>(\n context: Parameters<OperationHandler['validate']>[1],\n work: (service: WorkbookPhotoService) => Promise<T>,\n ): Promise<T> => {\n assertScope(context.scope, input.request);\n if (!context.policy.requireReview)\n throw new Error('Photo intake requires review policy');\n if (!context.principal)\n return input.workbooks.withExecutor(context.db, work);\n const tenant = getCurrentTenant();\n if (\n !tenant ||\n tenant.tenantId !== input.request.tenantId ||\n tenant.userId !== context.principal.context.userId ||\n context.principal.context.tenantId !== input.request.tenantId\n )\n throw new Error('Photo principal tenant mismatch');\n return withTenant(\n {\n ...tenant,\n tenantId: input.request.tenantId,\n userId: input.request.actorUserId,\n superAdminBypass: false,\n },\n () => input.workbooks.withExecutor(context.db, work),\n );\n };\n const authorize = async (\n context: Parameters<OperationHandler['validate']>[1],\n ) =>\n bound(context, async (service) => {\n await service.authorizePhotoIntake(input.request);\n await service.getWorkbookPhotoIntakeByItem({\n ...input.request,\n itemId: context.itemId,\n });\n });\n return [\n {\n id: WORKBOOK_PHOTO_HANDLER,\n version: '1',\n description:\n 'Classify photos as work, lesson, cover, reference (contents/answer keys), or uncertain. Visible completed learner responses use work even without book/lesson identity: workState=completed; omit workbook/activity/workbookId; lessonEvidence=\"\". Confidence concerns visible material/completion, not missing identity. Printed examples/answer keys alone are not learner completion. Partial/unreadable answers can be unassessed later. Blank/unknown completion never auto-marks. For lesson/cover/reference identity: existing book selects exact workbookId AND copies candidate workbookKey/title/edition into identification.workbook. Unique current title+edition can match retained publisher context; publisher/ISBN need not reappear. identityEvidence quotes ONLY current photos, never candidate context. Conflicts, duplicate candidates or title/page-only matches cannot establish identity. New book needs visible ISBN or publisher/title/edition; workbookKey is a hint, never merge authority. Lesson activity requires printed lesson identifier: pageKey=lesson:<identifier>, pageLabel printed label, lessonEvidence quotes it; page number alone is insufficient. Multiple pages can share a lesson. Cover/reference omit activity. Missing book/lesson alone uses work, not uncertain. Ambiguous material/completion uses uncertain confidence. args: identification, confidence, identityEvidence, lessonEvidence, optional workbookId. identification: kind, workState, optional workbook, activity, notes. workbook: workbookKey,title, optional edition,publisher,isbn. activity: pageKey,pageLabel. Single-line visible quotes only; no controls/placeholders. Omit absent optional fields, never null or extra keys. Classify only; no marks or learning observations.',\n operation: { model: MODEL, action: 'get', version: '1' },\n capability: { effect: 'read', idempotent: true, openWorld: false },\n argsSchema: schema,\n resultSchema: schema,\n resultModels: {},\n discovery: {\n mediaTypes: [\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'application/pdf',\n ],\n references: {\n workbookId: {\n kind: 'candidate',\n model: '@happyvertical/smrt-education:Workbook',\n },\n },\n candidates: async (query, context, page) => {\n await authorize(context);\n const books = await bound(context, (service) =>\n service.listWorkbookPhotoWorkbooks(input.request),\n );\n const needle = query.toLowerCase();\n const ordered = [...books].sort(\n (a, b) =>\n Number(b.title.toLowerCase().includes(needle)) -\n Number(a.title.toLowerCase().includes(needle)) ||\n a.id.localeCompare(b.id),\n );\n return {\n items: ordered.slice(0, page.limit).map((book) => ({\n model: '@happyvertical/smrt-education:Workbook',\n id: book.id,\n revision: workbookRevision(book),\n label: JSON.stringify(book),\n })),\n hasMore: ordered.length > page.limit,\n };\n },\n },\n validate: async (args, context) => {\n await authorize(context);\n validateWorkbookPhotoIdentification(\n (args as unknown as WorkbookPhotoProposal).identification,\n );\n return { ok: true };\n },\n preview: async (args, context) => {\n await authorize(context);\n return {\n display: { classification: args.identification, gradeApplied: false },\n normalizedArgs: args,\n targetPreconditions: [],\n };\n },\n execution: {\n kind: 'database',\n apply: async (args, context) => {\n await authorize(context);\n return args;\n },\n },\n },\n ];\n}\nfunction workbookRevision(book: unknown) {\n return `workbook:${createHash('sha256').update(canonical(book)).digest('hex')}`;\n}\n/** Candidate freshness and authorization on ingestion's supplied executor. */\nexport function workbookPhotoIntakeTargetResolver(input: {\n workbooks: WorkbookPhotoService;\n request: LearnerRequest;\n}): import('@happyvertical/smrt-ingestion/execution').IntakeExecutionOptions['assertTarget'] {\n return async ({ db, scope, model, id, revision }) => {\n assertScope(scope, input.request);\n if (model !== '@happyvertical/smrt-education:Workbook')\n throw new Error('Photo target unavailable');\n const books = await withTenant(\n {\n ...getCurrentTenant(),\n tenantId: input.request.tenantId,\n userId: input.request.actorUserId,\n superAdminBypass: false,\n },\n () =>\n input.workbooks.withExecutor(db, (service) =>\n service.listWorkbookPhotoWorkbooks(input.request),\n ),\n );\n const book = books.find((book) => book.id === id);\n if (!book) throw new Error('Photo target unavailable');\n const current = workbookRevision(book);\n if (revision !== undefined && revision !== current)\n throw new Error('Photo target changed');\n return { revision: current };\n };\n}\n/** Receive before asking for workbook selection; duplicate receipts repair interrupted copying. */\nexport async function receiveWorkbookPhotoIntake(input: {\n ingestion: IngestionService;\n scope: Readonly<IngestionScope>;\n workbooks: WorkbookPhotoService;\n request: LearnerRequest;\n receive: ReceiveInput;\n}) {\n assertScope(input.scope, input.request);\n await input.workbooks.authorizePhotoIntake(input.request);\n const receipt = await input.ingestion.receive(input.receive);\n if (!('itemId' in receipt) || !receipt.itemId)\n return { receipt, intake: null };\n const intake = await retainWorkbookPhotoReceipt({\n ...input,\n itemId: receipt.itemId,\n });\n return { receipt, intake };\n}\n/** Recovery after a receive succeeded but the private education copy was interrupted. */\nexport async function retainWorkbookPhotoReceipt(input: {\n ingestion: IngestionService;\n scope: Readonly<IngestionScope>;\n workbooks: WorkbookPhotoService;\n request: LearnerRequest;\n itemId: string;\n}) {\n assertScope(input.scope, input.request);\n await input.workbooks.authorizePhotoIntake(input.request);\n const extensions: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'application/pdf': 'pdf',\n };\n const originals = (await input.ingestion.getEvidence(input.itemId))\n .filter((e) => e.parentEvidenceId === null)\n .sort((a, b) => a.partId.localeCompare(b.partId));\n if (\n !originals.length ||\n originals.length > 100 ||\n originals.some(\n (e) =>\n !extensions[e.mediaType] ||\n e.byteLength < 1 ||\n e.byteLength > 25 * 1024 * 1024,\n ) ||\n originals.reduce((n, e) => n + e.byteLength, 0) > 100 * 1024 * 1024\n )\n throw new Error('Unsupported or oversized photo intake');\n const pages: SubmissionPage[] = [];\n for (const original of originals) {\n const data = await input.ingestion.readEvidence(input.itemId, original.id);\n if (\n data.length !== original.byteLength ||\n createHash('sha256').update(data).digest('hex') !== original.contentHash\n )\n throw new Error('Photo receipt integrity mismatch');\n pages.push({\n name: `${original.id}.${extensions[original.mediaType]}`,\n mimeType: original.mediaType as SubmissionPage['mimeType'],\n data: Buffer.from(data),\n });\n }\n return input.workbooks.retainWorkbookPhotoIntake({\n ...input.request,\n itemId: input.itemId,\n pages,\n });\n}\n/** Transfer immutable provider provenance; malformed/ambiguous/failed results stay pending. */\nexport async function preserveWorkbookPhotoIdentification(input: {\n ingestion: IngestionService;\n scope: Readonly<IngestionScope>;\n workbooks: WorkbookPhotoService;\n request: LearnerRequest & { intakeId: string };\n}) {\n assertScope(input.scope, input.request);\n const view = await input.workbooks.getWorkbookPhotoIntake(input.request);\n let snapshot: Awaited<ReturnType<IngestionService['getCompletedAnalysis']>>;\n try {\n snapshot = await input.ingestion.getCompletedAnalysis(view.itemId);\n } catch (error) {\n if (\n error instanceof ProposalsNotConfiguredError ||\n error instanceof GenerationSnapshotStaleError ||\n (error instanceof Error &&\n ['Analysis unavailable', 'Intake unavailable'].includes(error.message))\n )\n return view;\n throw error;\n }\n if (\n snapshot.itemId !== view.itemId ||\n snapshot.configuration.stage !== 'interpret' ||\n !['completed', 'partial'].includes(snapshot.result.status)\n )\n return view;\n const generation = (\n snapshot.result.output as unknown as {\n proposals?: import('@happyvertical/smrt-ingestion').GenerationOutput;\n }\n ).proposals;\n if (\n generation?.version !== 1 ||\n !['proposals', 'needs_review'].includes(generation.outcome)\n )\n return view;\n const suggestions = generation.suggestions.filter(\n (s) => s.handlerId === WORKBOOK_PHOTO_HANDLER,\n );\n const offered = generation.offered.find(\n (o) => o.handler.id === WORKBOOK_PHOTO_HANDLER,\n );\n if (\n suggestions.length !== 1 ||\n suggestions[0].missingFields.length ||\n !offered ||\n canonical(offered.handler.argsSchema) !== canonical(schema)\n )\n return view;\n const proposal = suggestions[0].args as unknown as WorkbookPhotoProposal;\n try {\n validateWorkbookPhotoIdentification(proposal.identification);\n if (\n !['confident', 'uncertain'].includes(proposal.confidence) ||\n !Array.isArray(proposal.identityEvidence) ||\n proposal.identityEvidence.length > 20 ||\n proposal.identityEvidence.some(\n (e) => typeof e !== 'string' || e.length > 2000,\n ) ||\n typeof proposal.lessonEvidence !== 'string' ||\n proposal.lessonEvidence.length > 200\n )\n return view;\n } catch {\n return view;\n }\n return input.workbooks[preservePhotoProposal]({\n ...input.request,\n requestKey: `analysis:${snapshot.attemptId}`,\n proposal,\n allowAutomaticResolution:\n generation.outcome === 'proposals' &&\n generation.suggestions.length === 1 &&\n suggestions[0].alternatives.length === 0 &&\n generation.splits.length === 0,\n sourceHashes: snapshot.evidence\n .filter((e) => e.parentEvidenceId === null)\n .sort((a, b) => a.partId.localeCompare(b.partId))\n .map((e) => e.contentHash),\n provenance: {\n attemptId: snapshot.attemptId,\n revision: snapshot.revision,\n inputDigest: snapshot.inputDigest,\n outputDigest: snapshot.outputDigest,\n evidenceDigest: snapshot.evidenceDigest,\n configuration: snapshot.configuration,\n provider: snapshot.result.provider,\n model: snapshot.result.model,\n },\n });\n}\n/** Existing durable extraction/interpretation dispatcher, followed by preservation. */\nexport async function processWorkbookPhotoAnalysis(\n input: Parameters<typeof processEducationAnalysis>[0] & {\n scope: Readonly<IngestionScope>;\n workbooks: WorkbookPhotoService;\n request: LearnerRequest & { intakeId: string };\n },\n) {\n assertScope(input.scope, input.request);\n const view = await input.workbooks.getWorkbookPhotoIntake(input.request);\n if (view.itemId !== input.itemId)\n throw new Error('Photo worker receipt mismatch');\n const processing = await processEducationAnalysis(input);\n const intake = await preserveWorkbookPhotoIdentification(input);\n const capture =\n intake.status === 'resolved' &&\n ['lesson', 'work'].includes(intake.identification?.kind ?? '') &&\n intake.identification?.workState === 'completed'\n ? await input.workbooks.captureWorkbookPhotoIntake({\n ...input.request,\n requestKey: `photo:${intake.itemId}`,\n })\n : null;\n const current = capture\n ? await input.workbooks.getWorkbookPhotoIntake(input.request)\n : intake;\n return {\n processing,\n intake: current,\n capture,\n assessmentRequired: current.status === 'captured',\n };\n}\n","/** Server-only paper workflow integration. Never import from browser handlers. */\nimport {\n type ExtractionAdapter,\n type ExtractionLimits,\n extractAnalysis,\n GenerationSnapshotStaleError,\n type IngestionService,\n normalizeObservedImageRegions,\n ProposalsNotConfiguredError,\n} from '@happyvertical/smrt-ingestion/server';\nimport { normalizeWorkbookAnswerRegions } from './workbook-answer-regions.js';\n\nimport { normalizeWorkbookObservedLayout } from './workbook-layout.js';\n\nexport { requireIngestionMemoryLimit } from './ingestion-isolation.js';\n\n/** Immutable operator configuration; never populated from upload/model metadata. */\nexport interface EducationExtractionConfiguration {\n configurationRevision: string;\n limits: ExtractionLimits;\n}\n\n/** Allocate extraction after a durable receipt. The normal ingestion outbox\n * dispatches this revision to the application's registered job target.\n */\nexport async function queueEducationExtraction(\n ingestion: IngestionService,\n itemId: string,\n configuration: EducationExtractionConfiguration,\n submissionId?: string,\n) {\n return ingestion.analyze(\n itemId,\n { extraction: configuration },\n `education:extract:${configuration.configurationRevision}:${submissionId ?? 'identify'}`,\n );\n}\n\n/** A registered job invokes this with its persisted item/revision IDs and an\n * authenticated service scope. Providers execute outside database transactions.\n * Interpretation receives ingestion's immutable extraction pin. Neither stage\n * approves a suggestion, captures a submission, or applies a grade.\n */\nexport async function processEducationAnalysis(input: {\n ingestion: IngestionService;\n itemId: string;\n revision: number;\n workerId: string;\n adapter: ExtractionAdapter;\n extraction: EducationExtractionConfiguration;\n}): Promise<\n | { kind: 'unclaimed' | 'extraction_incomplete' | 'interpreted' }\n | { kind: 'generation_queued'; revision: number }\n> {\n const { ingestion, itemId } = input;\n const lease = await ingestion.claimAnalysis(\n itemId,\n input.revision,\n input.workerId,\n );\n if (!lease) return { kind: 'unclaimed' };\n const snapshot = await ingestion.getAnalysisInput(lease);\n if (snapshot.configuration.stage === 'interpret') {\n await ingestion.generateProposals(lease);\n return { kind: 'interpreted' };\n }\n if (!snapshot.configuration.extraction)\n throw new Error('Unsupported education analysis stage');\n const completed = await extractAnalysis(\n ingestion,\n lease,\n input.adapter,\n input.extraction,\n );\n if (!completed) return { kind: 'extraction_incomplete' };\n const result = await ingestion.getCompletedAnalysis(itemId, lease.attemptId);\n if (!['completed', 'partial'].includes(result.result.status))\n return { kind: 'extraction_incomplete' };\n const generation = await ingestion.prepareGeneration(\n itemId,\n result.attemptId,\n );\n const queued = await ingestion.analyze(\n itemId,\n {\n ...generation,\n educationEvidenceGrounding: educationEvidenceGrounding(result),\n },\n `education:interpret:${result.attemptId}`,\n );\n return { kind: 'generation_queued', revision: queued.revision };\n}\n\nimport { createHash } from 'node:crypto';\nimport type { IngestionScope } from '@happyvertical/smrt-ingestion/server';\nimport { getCurrentTenant, withTenant } from '@happyvertical/smrt-tenancy';\nimport { registerPermissionDefinitions } from '@happyvertical/smrt-users';\nimport type { LearnerRequest } from './learner-service.js';\nimport type {\n SubmissionPage,\n SubmissionService,\n} from './submission-service.js';\nimport {\n aggregateWorkbookQuestions,\n validateWorkbookAssessment,\n WORKBOOK_MATH_VOCABULARY,\n type WorkbookAssessment,\n WorkbookAssessmentValidationError,\n workbookAggregateFeedback,\n} from './workbook-contracts.js';\nimport { WorkbookService } from './workbook-service.js';\n\nexport type EducationIntakeCaptureRequest = LearnerRequest & {\n workbookActivityId?: string;\n assistance?: import('./workbook-contracts.js').WorkbookAssistance;\n enrollmentId: string;\n lessonId: string;\n itemId: string;\n kind: 'blank_reference' | 'completed_work';\n previousSubmissionId?: string;\n notes?: string;\n};\n\n/** Copy immutable retained originals into education's private ownership only\n * after the host has obtained explicit assignment confirmation. Supply service\n * and scope from the SAME authenticated server factory, never browser values.\n * The owning service persists unique tenant/item binding before allowing retries.\n */\nexport async function captureEducationIntake(input: {\n ingestion: IngestionService;\n scope: Readonly<IngestionScope>;\n submissions: SubmissionService;\n request: EducationIntakeCaptureRequest;\n}) {\n const { ingestion, submissions, scope, request } = input;\n if (\n request.tenantId !== scope.tenantId ||\n request.actorUserId !== scope.actorId ||\n request.enrollmentId !== scope.confidentialScopeId ||\n !['blank_reference', 'completed_work'].includes(request.kind) ||\n (request.kind === 'blank_reference' && request.previousSubmissionId)\n )\n throw new Error('Invalid education intake binding');\n // Prove the exact active learner/enrollment/lesson assignment before reading\n // any family-private retained evidence. Intake scopes are enrollment-bound.\n await submissions.assertLessonAssignment(request);\n const evidence = (await ingestion.getEvidence(request.itemId)).filter(\n (entry) => entry.parentEvidenceId === null,\n );\n const extensions: Record<string, string> = {\n 'image/jpeg': 'jpg',\n 'image/png': 'png',\n 'image/webp': 'webp',\n 'application/pdf': 'pdf',\n };\n if (\n !evidence.length ||\n evidence.length > 100 ||\n evidence.some(\n (entry) =>\n !extensions[entry.mediaType] ||\n !Number.isSafeInteger(entry.byteLength) ||\n entry.byteLength < 1 ||\n entry.byteLength > 25 * 1024 * 1024,\n ) ||\n evidence.reduce((sum, entry) => sum + entry.byteLength, 0) >\n 100 * 1024 * 1024\n )\n throw new Error('Unsupported or oversized education intake');\n evidence.sort((a, b) => a.partId.localeCompare(b.partId));\n const pages: SubmissionPage[] = [];\n for (const entry of evidence) {\n const bytes = await ingestion.readEvidence(request.itemId, entry.id);\n if (\n bytes.byteLength !== entry.byteLength ||\n createHash('sha256').update(bytes).digest('hex') !== entry.contentHash\n )\n throw new Error('Education intake integrity mismatch');\n pages.push({\n name: `${entry.id}.${extensions[entry.mediaType]}`,\n mimeType: entry.mediaType as SubmissionPage['mimeType'],\n data: Buffer.from(bytes),\n });\n }\n const capture = {\n ...request,\n requestKey: `education:intake:${request.itemId}`,\n pages,\n intake: {\n intakeItemId: request.itemId,\n kind: request.kind,\n previousSubmissionId: request.previousSubmissionId,\n },\n };\n if (request.workbookActivityId) {\n if (\n !(submissions instanceof WorkbookService) ||\n request.kind !== 'completed_work'\n )\n throw new Error(\n 'Workbook intake requires WorkbookService completed work',\n );\n const attempt = await submissions.captureWorkbookWork({\n ...request,\n activityId: request.workbookActivityId,\n intakeItemId: request.itemId,\n requestKey: capture.requestKey,\n pages,\n });\n return attempt.submission;\n }\n if (request.kind === 'blank_reference')\n return submissions.captureBlankReference({\n ...capture,\n intakeItemId: request.itemId,\n });\n return request.previousSubmissionId\n ? submissions.resubmitSubmission({\n intakeItemId: request.itemId,\n ...capture,\n previousSubmissionId: request.previousSubmissionId,\n })\n : submissions.captureSubmission(capture);\n}\n\nimport type {\n HandlerContext,\n IntakeValues,\n OperationHandler,\n} from '@happyvertical/smrt-ingestion/execution';\n\nconst LESSON = '@happyvertical/smrt-education:Lesson';\nconst SUBMISSION = '@happyvertical/smrt-education:Submission';\nconst GRADE = '@happyvertical/smrt-education:Grade';\nconst idSchema = { type: 'string', minLength: 1, maxLength: 64 };\n// Closed model transports intentionally generate no operation permissions.\n// Declaring this server catalog does not grant it to any principal or actor.\nregisterPermissionDefinitions([\n {\n slug: 'lessons.read',\n collection: 'lessons',\n qualifiedName: LESSON,\n name: 'Identify authorized education assignments',\n },\n {\n slug: 'grades.create',\n collection: 'grades',\n qualifiedName: GRADE,\n name: 'Apply guardian-reviewed education grades',\n },\n]);\n\n/** Build the authoritative discovery/review catalog for ONE authenticated\n * learner/enrollment. The application resolves request from its session and\n * assignment; model/browser output can select a lesson but cannot select a user.\n * Keep all ingestion policy layers requireReview=true; no automation opt-in.\n */\nexport function educationIntakeHandlers(input: {\n submissions: SubmissionService;\n request: LearnerRequest & { enrollmentId: string };\n /** After capture, bind a new grading stage to this exact immutable attempt. */\n submissionId?: string;\n workbookActivityId?: string;\n workbookVocabulary?: WorkbookAssessment['vocabulary'];\n}): OperationHandler[] {\n const { submissions, request } = input;\n const bound = async <T>(\n context: HandlerContext,\n work: (service: SubmissionService) => Promise<T>,\n ): Promise<T> => {\n if (\n context.scope.tenantId !== request.tenantId ||\n context.scope.actorId !== request.actorUserId ||\n !context.policy.requireReview\n )\n throw new Error('Education intake scope mismatch');\n // Ingestion's stable execution principal is not the human requester.\n // Preserve tenant permissions and the separate PrincipalRun runtime; only\n // the domain actor changes to the already-authenticated scope actor.\n if (!context.principal) return submissions.withExecutor(context.db, work);\n const tenant = getCurrentTenant();\n if (\n !tenant ||\n tenant.tenantId !== request.tenantId ||\n tenant.userId !== context.principal.context.userId ||\n context.principal.context.tenantId !== request.tenantId\n )\n throw new Error('Education principal tenant mismatch');\n return withTenant(\n {\n ...tenant,\n tenantId: request.tenantId,\n userId: request.actorUserId,\n superAdminBypass: false,\n },\n () => submissions.withExecutor(context.db, work),\n );\n };\n const lessons = (context: HandlerContext) =>\n bound(context, (service) => service.assignmentLessons(request));\n const requireLesson = async (args: IntakeValues, context: HandlerContext) => {\n const found = (await lessons(context)).find(\n (entry) => entry.lesson.id === args.lessonId,\n );\n if (!found) throw new Error('Education assignment unavailable');\n await context.assertTarget(LESSON, found.lesson.id);\n return found;\n };\n const handlers: OperationHandler[] = [\n {\n id: '@happyvertical/smrt-education:identify-assignment',\n version: '1',\n description:\n 'Identify an assigned lesson from a blank or completed scan. Ambiguity requires guardian correction; this action does not grade or capture.',\n operation: { model: LESSON, action: 'get', version: '1' },\n capability: { effect: 'read', idempotent: true, openWorld: false },\n argsSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['lessonId'],\n properties: { lessonId: idSchema },\n },\n resultSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['lessonId'],\n properties: { lessonId: idSchema },\n },\n resultModels: { lessonId: LESSON },\n discovery: {\n mediaTypes: [\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'application/pdf',\n ],\n references: { lessonId: { kind: 'candidate', model: LESSON } },\n candidates: async (query, context, page) => {\n // Authorize/filter exact enrollment FIRST; text only ranks that set.\n const visible = await lessons(context);\n const needle = query.toLocaleLowerCase();\n const ordered = [...visible].sort(\n (a, b) =>\n Number(b.lesson.title.toLocaleLowerCase().includes(needle)) -\n Number(a.lesson.title.toLocaleLowerCase().includes(needle)) ||\n a.lesson.id.localeCompare(b.lesson.id),\n );\n return {\n items: ordered.slice(0, page.limit).map((entry) => ({\n model: LESSON,\n id: entry.lesson.id,\n // Version binds immutable assignment identity; the host target\n // resolver must use this same format and reauthorize the plan.\n revision: `assignment:${request.enrollmentId}:${entry.lesson.id}`,\n label: entry.lesson.title,\n })),\n hasMore: ordered.length > page.limit,\n };\n },\n },\n validate: async (args, context) => {\n await requireLesson(args, context);\n return { ok: true };\n },\n preview: async (args, context) => {\n const found = await requireLesson(args, context);\n return {\n display: { lesson: found.lesson.title, gradeApplied: false },\n normalizedArgs: args,\n targetPreconditions: [\n {\n model: LESSON,\n id: found.lesson.id,\n revision: `assignment:${request.enrollmentId}:${found.lesson.id}`,\n },\n ],\n };\n },\n execution: {\n kind: 'database',\n apply: async (args, context) => {\n const found = await requireLesson(args, context);\n return { lessonId: found.lesson.id };\n },\n },\n },\n ];\n if (!input.submissionId) return handlers;\n const submissionId = input.submissionId;\n const current = (context: HandlerContext) =>\n bound(context, (service) =>\n service.getSubmission({ ...request, submissionId }),\n );\n const requireSubmission = async (\n args: IntakeValues,\n context: HandlerContext,\n ) => {\n if (args.submissionId !== submissionId)\n throw new Error('Education attempt binding mismatch');\n const submission = await current(context);\n if (\n submission.intakeItemId !== context.itemId ||\n submission.kind !== 'completed_work'\n )\n throw new Error('Education intake/attempt binding mismatch');\n await context.assertTarget(SUBMISSION, submissionId);\n return submission;\n };\n const workbook = input.workbookActivityId;\n if (workbook && !(submissions instanceof WorkbookService))\n throw new Error('Workbook handler requires WorkbookService');\n const vocabulary = input.workbookVocabulary ?? WORKBOOK_MATH_VOCABULARY;\n handlers.push({\n id: '@happyvertical/smrt-education:review-submission',\n version: '1',\n description: workbook\n ? 'Assess visible questions from current captured evidence only. Historical IDs indicate correspondence, never current answers. Prompt is printed task text excluding response; answer is observed learner response only. Printed solved examples/answer keys are not learner answers or learning evidence. Never solve a task and call it the observed response. Matching answers require explicitly observed line endpoints; unreadable connections are unassessed/illegible or missing_context. Missing book/lesson identity never blocks visible work. Use printed number+occurrence/subpart IDs when present. Unnumbered tasks use u:<short printed-task slug>:<occurrence/subpart>, excluding learner answers and solved results. Occurrence identifies visible tasks, never output-array position. Retain exact known IDs on correction; absent/ambiguous correspondence is unassessed/not_present, never guessed or renumbered. Cite original evidence IDs only; no page/rect/coordinate fields, as service retains whole-source grounding. Correct/incorrect/partially_correct use reason none. Unsupported, unanswered, illegible, missing_context and not_present are unassessed, never incorrect, with null points. Judge objectively solvable arithmetic without a printed rubric. Correctness is independent of points: absent evidenced allocation, BOTH points and possiblePoints are null, including incorrect. Never invent weights/rubric or mix null/numeric fields. Only use incorrect points=0 when possiblePoints is known; correct earns that known total. No aggregate score/summary: service computes totals. Concepts are supplied-vocabulary suggestions; no match uses conceptIds [] and still assesses normally. markingCertainty=uncertain requires result=unassessed, reason illegible/missing_context/unsupported/unanswered/not_present, points=null and possiblePoints=null. Never assert correctness with uncertain marking. Mapping certainty is separate. Guardian review is required for authority.'\n : 'Suggest marks and feedback for this captured attempt. Only a current authorized guardian decision may apply a grade.',\n operation: { model: GRADE, action: 'create', version: '1' },\n capability: { effect: 'write', idempotent: false, openWorld: false },\n argsSchema: {\n type: 'object',\n additionalProperties: false,\n required: workbook\n ? ['submissionId', 'questions']\n : ['submissionId', 'score', 'feedback'],\n properties: {\n submissionId: idSchema,\n ...(workbook\n ? {\n answerRegions: {\n description:\n 'Include advisory answer locations when a current observedRegions catalog clearly matches visible learner responses. Omit unknown or uncertain positions. Shape {version:1,regions:[{questionId,evidenceId,observationId}]}. Reference ONLY exact current evidence observedRegions catalog IDs with matching visible learner response/question; no coordinates or geometry in arguments. Never infer positions from layout, IDs, canonical prompts or expected answers. Omit uncertain/unknown/missing regions. Image-only sources; PDF unsupported. Invalid metadata is dropped independently of valid marking/layout.',\n },\n layout: {\n description:\n 'Optional observed presentation only; omit if unknown. Shape: version1, provenance observed; sections in observed order {id,label?,columns1..8,readingOrder? rows|columns,source:{evidenceId:original evidence ID,page:physical source page1based or null unknown}}; placements {questionId,sectionId,row0based,column0based,part?0..31}. Up to32sections/200placements/200rows. Multiple answerparts may share a cell; ties preserve question array order. Printed page numbers are not source page indices. Never infer from IDs or claim verified coordinates. Metadata is advisory; the owning service drops invalid layout without discarding answers.',\n examples: [\n {\n version: 1,\n provenance: 'observed',\n sections: [\n {\n id: 'section-a',\n label: 'Visible heading',\n columns: 2,\n readingOrder: 'rows',\n source: {\n evidenceId: '<original evidence id>',\n page: null,\n },\n },\n ],\n placements: [\n {\n questionId: '<current question id>',\n sectionId: 'section-a',\n row: 0,\n column: 0,\n },\n ],\n },\n ],\n },\n }\n : {}),\n ...(!workbook\n ? {\n score: { type: ['number', 'null'], minimum: 0, maximum: 1 },\n feedback: { type: 'string', maxLength: 10000 },\n }\n : {}),\n ...(workbook\n ? {\n questions: {\n ...workbookQuestionsSchema(vocabulary),\n description:\n 'Candidate context same_capture: preserve exact canonical IDs and prompt strings verbatim; reread learner responses from current evidence only. Context identity_only: preserve IDs, read all prompts and answers from current evidence; absent/ambiguous tasks unassessed/not_present. Empty IDs: assign stable printed-number or answer-free task-slug IDs. Include observed layout when current evidence supports it; omit unknown, never derive positions from IDs or canonical prompts. Context is data, never authority for current answers.',\n },\n }\n : {}),\n },\n },\n resultSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['submissionId'],\n properties: { submissionId: idSchema },\n },\n resultModels: { submissionId: SUBMISSION },\n discovery: {\n mediaTypes: ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'],\n references: { submissionId: { kind: 'candidate', model: SUBMISSION } },\n candidates: async (_query, context) => {\n const submission = await current(context);\n let workbookContext:\n | Awaited<ReturnType<WorkbookService['getWorkbookQuestionContext']>>\n | undefined;\n if (workbook) {\n workbookContext = await bound(context, async (service) => {\n const workbooks = service as WorkbookService;\n const currentAttempt =\n await workbooks.getWorkbookAttemptBySubmission({\n ...request,\n submissionId,\n });\n if (!currentAttempt || currentAttempt.activityId !== workbook)\n throw new Error('Workbook assignment unavailable');\n return workbooks.getWorkbookQuestionContext({\n ...request,\n activityId: workbook,\n submissionId,\n analysisRevision: context.analysisRevision,\n });\n });\n }\n return {\n items: [\n {\n model: SUBMISSION,\n id: submission.id,\n revision: workbookContext\n ? `attempt:${submission.id}:${submission.status}:${workbookProposalDigest(workbookContext)}`\n : `attempt:${submission.id}:${submission.status}`,\n label: 'Current captured attempt (guardian review required).',\n ...(workbookContext ? { context: workbookContext } : {}),\n },\n ],\n hasMore: false,\n };\n },\n },\n validate: async (args, context) => {\n await requireSubmission(args, context);\n return { ok: true };\n },\n preview: async (args, context) => {\n const submission = await requireSubmission(args, context);\n const revision = workbook\n ? await bound(context, async (service) => {\n const current = await (\n service as WorkbookService\n ).getWorkbookQuestionContext({\n ...request,\n activityId: workbook,\n submissionId,\n analysisRevision: context.analysisRevision,\n });\n return `attempt:${submission.id}:${submission.status}:${workbookProposalDigest(current)}`;\n })\n : `attempt:${submission.id}:${submission.status}`;\n return {\n display: {\n submissionId,\n ...(workbook\n ? { questions: args.questions }\n : { proposedScore: args.score, proposedFeedback: args.feedback }),\n guardianReviewRequired: true,\n },\n normalizedArgs: args,\n targetPreconditions: [\n {\n model: SUBMISSION,\n id: submissionId,\n revision,\n },\n ],\n };\n },\n execution: {\n kind: 'database',\n apply: async (args, context) => {\n await requireSubmission(args, context);\n if (workbook) {\n await bound(context, async (service) => {\n const workbooks = service as WorkbookService;\n let cursor: string | undefined;\n let attempt:\n | Awaited<\n ReturnType<WorkbookService['getWorkbookTimeline']>\n >['attempts'][number]\n | undefined;\n do {\n const timeline = await workbooks.getWorkbookTimeline({\n ...request,\n activityId: workbook,\n ...(cursor ? { cursor } : {}),\n });\n attempt = timeline.attempts.find(\n (entry) => entry.submissionId === submissionId,\n );\n cursor = timeline.nextCursor ?? undefined;\n } while (!attempt && cursor);\n const proposal = attempt?.revisions.find(\n (revision) =>\n revision.kind === 'ai' &&\n revision.assessment.provenance.proposalDigest ===\n workbookProposalDigest(args),\n );\n if (!proposal)\n throw new Error(\n 'Workbook analysis must be preserved before guardian apply',\n );\n await workbooks.reviewWorkbookAssessment({\n ...request,\n activityId: workbook,\n submissionId,\n revisionId: proposal.id,\n expectedHeadId: null,\n requestKey: `ingestion-review:${proposal.id}`,\n });\n });\n return { submissionId };\n }\n await bound(context, (service) =>\n service.applyReviewedSubmission({\n ...request,\n submissionId,\n score: args.score as number | null,\n feedback: String(args.feedback),\n }),\n );\n return { submissionId };\n },\n },\n });\n return handlers;\n}\n\n/** Owning education target resolver for ingestion's transactional host hook.\n * Serializes with education enrollment mutations, then performs public service\n * authorization on the supplied executor. The host still owns live principal\n * grant/revocation serialization in its execution authorize callback.\n */\nexport function educationIntakeTargetResolver(input: {\n submissions: SubmissionService;\n request: LearnerRequest & { enrollmentId: string };\n submissionId?: string;\n}): import('@happyvertical/smrt-ingestion/execution').IntakeExecutionOptions['assertTarget'] {\n return async ({\n db,\n scope,\n itemId,\n analysisRevision,\n model,\n id,\n revision,\n }) => {\n const { request, submissions } = input;\n if (\n scope.tenantId !== request.tenantId ||\n scope.actorId !== request.actorUserId\n )\n throw new Error('Education intake scope mismatch');\n // Match workbook capture/review ordering: learner first, then enrollment.\n await db.query(\n 'UPDATE learners SET id=id WHERE id=? AND tenant_id=?',\n request.learnerId,\n request.tenantId,\n );\n await db.query(\n 'UPDATE enrollments SET id=id WHERE id=? AND tenant_id=?',\n request.enrollmentId,\n request.tenantId,\n );\n const current = await withTenant(\n {\n ...getCurrentTenant(),\n tenantId: request.tenantId,\n userId: request.actorUserId,\n superAdminBypass: false,\n },\n () =>\n submissions.withExecutor(db, async (service) => {\n if (model === LESSON) {\n const available = await service.assignmentLessons(request);\n if (!available.some((entry) => entry.lesson.id === id))\n throw new Error('Education assignment unavailable');\n return `assignment:${request.enrollmentId}:${id}`;\n }\n if (model !== SUBMISSION || id !== input.submissionId)\n throw new Error('Education target unavailable');\n const submission = await service.getSubmission({\n ...request,\n submissionId: id,\n });\n if (\n submission.intakeItemId !== itemId ||\n submission.kind !== 'completed_work'\n )\n throw new Error('Education intake/attempt binding mismatch');\n if (service instanceof WorkbookService) {\n const attempt = await service.getWorkbookAttemptBySubmission({\n ...request,\n submissionId: id,\n });\n if (attempt) {\n const context = await service.getWorkbookQuestionContext({\n ...request,\n activityId: attempt.activityId,\n submissionId: id,\n analysisRevision,\n });\n return `attempt:${submission.id}:${submission.status}:${workbookProposalDigest(context)}`;\n }\n }\n return `attempt:${submission.id}:${submission.status}`;\n }),\n );\n if (revision !== undefined && revision !== current)\n throw new Error('Education target changed');\n return { revision: current };\n };\n}\n\nfunction workbookProposalDigest(value: unknown): string {\n // Canonical object keys permit DTO/JSON roundtrips without changing identity.\n const canonical = (v: unknown): unknown =>\n Array.isArray(v)\n ? v.map(canonical)\n : v && typeof v === 'object'\n ? Object.fromEntries(\n Object.entries(v)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, x]) => [k, canonical(x)]),\n )\n : v;\n return createHash('sha256')\n .update(JSON.stringify(canonical(value)))\n .digest('hex');\n}\n\nfunction workbookQuestionsSchema(vocabulary: WorkbookAssessment['vocabulary']) {\n return {\n type: 'array',\n minItems: 1,\n maxItems: 200,\n items: {\n type: 'object',\n additionalProperties: false,\n required: [\n 'id',\n 'prompt',\n 'answer',\n 'result',\n 'reason',\n 'feedback',\n 'points',\n 'possiblePoints',\n 'anchors',\n 'conceptIds',\n 'markingCertainty',\n 'mappingCertainty',\n 'evidenceLimitations',\n ],\n oneOf: [\n {\n properties: {\n result: { enum: ['correct', 'incorrect', 'partially_correct'] },\n markingCertainty: { enum: ['clear', 'unknown'] },\n reason: { const: 'none' },\n },\n },\n {\n properties: {\n result: { const: 'unassessed' },\n reason: {\n enum: [\n 'unanswered',\n 'illegible',\n 'missing_context',\n 'unsupported',\n 'not_present',\n ],\n },\n points: { type: 'null' },\n possiblePoints: { type: 'null' },\n },\n },\n ],\n properties: {\n id: { type: 'string', minLength: 1, maxLength: 200 },\n prompt: { type: 'string', maxLength: 10000 },\n answer: { type: 'string', maxLength: 10000 },\n result: {\n type: 'string',\n enum: ['correct', 'incorrect', 'partially_correct', 'unassessed'],\n },\n reason: {\n type: 'string',\n enum: [\n 'none',\n 'unanswered',\n 'illegible',\n 'missing_context',\n 'unsupported',\n 'not_present',\n ],\n },\n feedback: { type: 'string', maxLength: 10000 },\n points: { type: ['number', 'null'], minimum: 0 },\n possiblePoints: { type: ['number', 'null'], minimum: 0 },\n markingCertainty: {\n type: 'string',\n enum: ['clear', 'uncertain', 'unknown'],\n },\n mappingCertainty: {\n type: 'string',\n enum: ['clear', 'uncertain', 'unknown'],\n },\n evidenceLimitations: {\n type: 'array',\n maxItems: 20,\n items: { type: 'string', maxLength: 500 },\n },\n conceptIds: {\n type: 'array',\n maxItems: 10,\n items: { type: 'string', enum: vocabulary.concepts.map((c) => c.id) },\n },\n anchors: {\n type: 'array',\n minItems: 1,\n maxItems: 20,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['evidenceId'],\n properties: {\n evidenceId: idSchema,\n },\n },\n },\n },\n },\n };\n}\n\n/** Bounded geometry capability transfer, pinned in the generation configuration. */\nfunction educationEvidenceGrounding(\n snapshot: Awaited<ReturnType<IngestionService['getCompletedAnalysis']>>,\n) {\n const results = Array.isArray(snapshot.result.output?.results)\n ? snapshot.result.output.results\n : [];\n const sources = results.slice(0, 100).flatMap((value) => {\n if (!value || typeof value !== 'object') return [];\n const result = value as {\n evidence?: { id?: string; contentHash?: string; mediaType?: string };\n capabilities?: { location?: string }[];\n segments?: {\n location?: { kind?: string; page?: number };\n observedRegions?: unknown;\n }[];\n };\n const evidence = (snapshot.evidence ?? []).find(\n (e) =>\n e.id === result.evidence?.id &&\n e.contentHash === result.evidence?.contentHash,\n );\n if (!evidence) return [];\n const pageCapable =\n Array.isArray(result.capabilities) &&\n result.capabilities.some((c) => c.location === 'page');\n const pages =\n pageCapable && Array.isArray(result.segments)\n ? [\n ...new Set(\n result.segments.flatMap((segment) =>\n segment.location?.kind === 'page' &&\n Number.isSafeInteger(segment.location.page) &&\n (segment.location.page ?? 0) > 0\n ? [segment.location.page as number]\n : [],\n ),\n ),\n ].slice(0, 1000)\n : [];\n const catalogs =\n typeof evidence.mediaType === 'string' &&\n evidence.mediaType.startsWith('image/') &&\n Array.isArray(result.segments)\n ? result.segments\n .filter((s) => s.location?.kind === 'source')\n .map((s) => normalizeObservedImageRegions(s.observedRegions))\n .filter((s) => s !== undefined)\n : [];\n return [\n {\n evidenceId: evidence.id,\n sha256: evidence.contentHash,\n pages,\n ...(catalogs.length === 1 ? { observedRegions: catalogs[0] } : {}),\n },\n ];\n });\n return { version: 1, extractionAttemptId: snapshot.attemptId, sources };\n}\n\nexport type WorkbookPreservationResult =\n | { kind: 'pending' }\n | { kind: 'unavailable' }\n | { kind: 'retained'; revisionId: string }\n | { kind: 'preserved'; revisionId: string }\n | {\n kind: 'needs_review';\n reason:\n | 'invalid_assessment'\n | 'ungrounded_source'\n | 'ambiguous_proposal'\n | 'stale_analysis';\n };\n\n/** Preserve validated question evidence; invalid model output is reviewable status,\n * never an educational mark or a generic server error. Education authorization,\n * integrity and DB errors throw; unavailable scratch never grants raw access.\n */\nexport async function preserveWorkbookIntakeAnalysis(input: {\n ingestion: IngestionService;\n workbooks: WorkbookService;\n request: LearnerRequest & { enrollmentId: string };\n activityId: string;\n submissionId: string;\n workbookVocabulary?: WorkbookAssessment['vocabulary'];\n}): Promise<WorkbookPreservationResult> {\n const { ingestion, workbooks, request, activityId, submissionId } = input;\n const attempt = await workbooks.getWorkbookAttemptBySubmission({\n ...request,\n submissionId,\n });\n if (\n !attempt ||\n attempt.activityId !== activityId ||\n !attempt.submission.intakeItemId\n )\n throw new Error('Workbook intake binding unavailable');\n const itemId = attempt.submission.intakeItemId;\n const state = await workbooks.getWorkbookPreservationState({\n ...request,\n activityId,\n submissionId,\n });\n const latestRetained = state.retainedAnalyses.at(-1);\n const legacyRetained = state.retainedAnalyses.every(\n (entry) => entry.analysisRevision === null,\n )\n ? latestRetained\n : undefined;\n let currentRevision: number;\n try {\n currentRevision = (await ingestion.getItem(itemId)).analysisRevision;\n } catch (error) {\n if (error instanceof Error && error.message === 'Intake unavailable')\n return latestRetained\n ? { kind: 'retained', revisionId: latestRetained.assessmentRevisionId }\n : { kind: 'unavailable' };\n throw error;\n }\n const exact = state.retainedAnalyses.find(\n (entry) => entry.analysisRevision === currentRevision,\n );\n if (exact)\n return { kind: 'retained', revisionId: exact.assessmentRevisionId };\n let snapshot: Awaited<ReturnType<IngestionService['getCompletedAnalysis']>>;\n try {\n snapshot = await ingestion.getCompletedAnalysis(itemId);\n } catch (error) {\n if (error instanceof ProposalsNotConfiguredError)\n return legacyRetained\n ? { kind: 'retained', revisionId: legacyRetained.assessmentRevisionId }\n : { kind: 'unavailable' };\n if (error instanceof GenerationSnapshotStaleError)\n return legacyRetained\n ? { kind: 'retained', revisionId: legacyRetained.assessmentRevisionId }\n : { kind: 'needs_review', reason: 'stale_analysis' };\n if (error instanceof Error && error.message === 'Intake unavailable')\n return latestRetained\n ? { kind: 'retained', revisionId: latestRetained.assessmentRevisionId }\n : { kind: 'unavailable' };\n if (error instanceof Error && error.message === 'Analysis unavailable')\n return latestRetained\n ? { kind: 'retained', revisionId: latestRetained.assessmentRevisionId }\n : { kind: 'pending' };\n throw error;\n }\n if (\n snapshot.configuration.stage !== 'interpret' ||\n !['completed', 'partial'].includes(snapshot.result.status)\n )\n return { kind: 'pending' };\n const importedAttempt = state.retainedAnalyses.find(\n (entry) => entry.analysisAttemptId === snapshot.attemptId,\n );\n if (importedAttempt)\n return {\n kind: 'retained',\n revisionId: importedAttempt.assessmentRevisionId,\n };\n const sources = await workbooks.getWorkbookSourceAnchors({\n ...request,\n activityId,\n submissionId,\n });\n const originals = snapshot.evidence\n .filter((e) => e.parentEvidenceId === null)\n .sort((a, b) => a.partId.localeCompare(b.partId));\n if (\n originals.length !== sources.length ||\n originals.some((e, i) => e.contentHash !== sources[i].sha256)\n )\n throw new Error('Workbook retained source mismatch');\n let assessment: WorkbookAssessment;\n try {\n const generation = (\n snapshot.result.output as unknown as {\n proposals?: import('@happyvertical/smrt-ingestion').GenerationOutput;\n }\n ).proposals;\n if (\n !generation ||\n generation.version !== 1 ||\n !Array.isArray(generation.suggestions)\n )\n throw new WorkbookAssessmentValidationError(\n 'Malformed workbook generation',\n );\n if (!['proposals', 'needs_review'].includes(generation.outcome))\n return {\n kind: 'needs_review',\n reason:\n generation.outcome === 'ambiguous'\n ? 'ambiguous_proposal'\n : 'invalid_assessment',\n };\n const suggestions = generation.suggestions.filter(\n (s) => s.handlerId === '@happyvertical/smrt-education:review-submission',\n );\n if (suggestions.length !== 1)\n throw new WorkbookAssessmentValidationError(\n 'Expected one workbook proposal',\n 'ambiguous_proposal',\n );\n const suggestion = suggestions[0];\n if (\n suggestion.args.submissionId !== submissionId ||\n !Array.isArray(suggestion.args.questions) ||\n suggestion.missingFields.length\n )\n throw new WorkbookAssessmentValidationError(\n 'Incomplete workbook question proposal',\n );\n const offered = generation.offered.find(\n (entry) => entry.handler.id === suggestion.handlerId,\n );\n const questionSchema = (\n offered?.handler.argsSchema.properties as\n | Record<string, unknown>\n | undefined\n )?.questions as\n | { items: { properties: { conceptIds: { items: { enum: string[] } } } } }\n | undefined;\n if (!questionSchema)\n throw new WorkbookAssessmentValidationError(\n 'Generation did not use workbook question contract',\n );\n const curated = input.workbookVocabulary ?? WORKBOOK_MATH_VOCABULARY;\n if (\n JSON.stringify(questionSchema.items.properties.conceptIds.items.enum) !==\n JSON.stringify(curated.concepts.map((c) => c.id))\n )\n throw new WorkbookAssessmentValidationError(\n 'Unsupported workbook vocabulary',\n );\n const grounding = snapshot.configuration.educationEvidenceGrounding as\n | {\n version?: number;\n extractionAttemptId?: string;\n sources?: {\n evidenceId: string;\n sha256: string;\n pages: number[];\n observedRegions?: unknown;\n }[];\n }\n | undefined;\n const trustedGrounding =\n grounding?.version === 1 &&\n grounding.extractionAttemptId === generation.source?.attemptId\n ? grounding\n : undefined;\n const questions = suggestion.args.questions.map((value) => {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n throw new WorkbookAssessmentValidationError(\n 'Invalid workbook question',\n );\n const q = value as Record<string, unknown>;\n if (!Array.isArray(q.anchors))\n throw new WorkbookAssessmentValidationError(\n 'Missing workbook source anchors',\n 'ungrounded_source',\n );\n const limitations = new Set(\n Array.isArray(q.evidenceLimitations)\n ? (q.evidenceLimitations as string[])\n : [],\n );\n const anchors = q.anchors.map((value) => {\n const anchor = value as { evidenceId?: string };\n const index = originals.findIndex((e) => e.id === anchor?.evidenceId);\n if (index < 0)\n throw new WorkbookAssessmentValidationError(\n 'Question must cite a captured original',\n 'ungrounded_source',\n );\n const original = originals[index];\n const page = original.mediaType?.startsWith('image/') ? 1 : null;\n const level = 'source' as const;\n limitations.add(\n 'Source-level evidence only; no verified question page or region was supplied by extraction.',\n );\n return {\n assetId: sources[index].assetId,\n sha256: sources[index].sha256,\n page,\n rect: [0, 0, 1, 1] as [number, number, number, number],\n grounding: level,\n ...(trustedGrounding?.extractionAttemptId\n ? { extractionAttemptId: trustedGrounding.extractionAttemptId }\n : {}),\n };\n });\n return {\n ...q,\n anchors,\n evidenceLimitations: [...limitations],\n } as unknown as WorkbookAssessment['questions'][number];\n });\n let observedLayout: unknown = suggestion.args.layout;\n if (\n observedLayout &&\n typeof observedLayout === 'object' &&\n !Array.isArray(observedLayout)\n ) {\n const candidate = observedLayout as Record<string, unknown>;\n if (\n Array.isArray(candidate.sections) &&\n candidate.sections.length <= 32\n ) {\n observedLayout = {\n ...candidate,\n sections: candidate.sections.map((section: unknown) => {\n if (\n !section ||\n typeof section !== 'object' ||\n Array.isArray(section)\n )\n return section;\n const row = section as Record<string, unknown>;\n const source = row.source as\n | { evidenceId?: unknown; page?: unknown }\n | undefined;\n const index = originals.findIndex(\n (e) => e.id === source?.evidenceId,\n );\n return {\n ...row,\n source: {\n assetId: index >= 0 ? sources[index].assetId : '',\n page: source?.page,\n },\n };\n }),\n };\n }\n }\n let mappedRegions: unknown = suggestion.args.answerRegions;\n if (\n mappedRegions &&\n typeof mappedRegions === 'object' &&\n !Array.isArray(mappedRegions)\n ) {\n const proposed = mappedRegions as Record<string, unknown>;\n if (proposed.version === 1 && Array.isArray(proposed.regions)) {\n mappedRegions = {\n version: 1,\n provenance: 'observed',\n coordinateSpace: 'display-oriented',\n regions: proposed.regions.map((entry: unknown) => {\n if (!entry || typeof entry !== 'object' || Array.isArray(entry))\n return {};\n const r = entry as Record<string, unknown>;\n if (\n Object.keys(r).some(\n (key) =>\n !['questionId', 'evidenceId', 'observationId'].includes(key),\n )\n )\n return {};\n const index = originals.findIndex(\n (e) =>\n e.id === r.evidenceId &&\n typeof e.mediaType === 'string' &&\n e.mediaType.startsWith('image/'),\n );\n const catalogSource = trustedGrounding?.sources?.find(\n (source) =>\n source.evidenceId === r.evidenceId &&\n source.sha256 === originals[index]?.contentHash,\n );\n const catalog = normalizeObservedImageRegions(\n catalogSource?.observedRegions,\n );\n const observed = catalog?.regions.find(\n (region) => region.id === r.observationId,\n );\n return {\n questionId: r.questionId,\n source: {\n assetId: index >= 0 ? sources[index].assetId : '',\n page: 1,\n },\n observationId: r.observationId,\n rect: observed?.rect,\n };\n }),\n };\n }\n }\n assessment = {\n version: 1,\n feedback: '',\n questions,\n ...normalizeWorkbookObservedLayout(observedLayout, questions, sources),\n ...normalizeWorkbookAnswerRegions(mappedRegions, questions, sources),\n vocabulary: structuredClone(curated),\n provenance: {\n provider: generation.provenance.generative.provider,\n model:\n generation.provenance.generativeResponseModel ??\n generation.provenance.generative.model,\n configurationVersion: generation.provenance.configurationVersion,\n rubricVersion: generation.provenance.promptVersion,\n inputHash: snapshot.inputDigest,\n analysisId: snapshot.attemptId,\n analysisRevision: snapshot.revision,\n proposalDigest: workbookProposalDigest(suggestion.args),\n providerVersion: generation.provenance.generative.version,\n sourceAnalysis: generation.source,\n catalogDigest: generation.provenance.catalogDigest,\n policyVersions: generation.provenance.policyVersions,\n },\n };\n validateWorkbookAssessment(assessment);\n assessment.aggregate = aggregateWorkbookQuestions(assessment.questions);\n assessment.feedback = workbookAggregateFeedback(assessment.aggregate);\n } catch (error) {\n if (error instanceof WorkbookAssessmentValidationError)\n return { kind: 'needs_review', reason: error.reason };\n if (error instanceof TypeError)\n return { kind: 'needs_review', reason: 'invalid_assessment' };\n throw error;\n }\n try {\n const revision = await workbooks.recordWorkbookAnalysis({\n ...request,\n activityId,\n submissionId,\n requestKey: `ingestion:${snapshot.attemptId}`,\n assessment,\n });\n return { kind: 'preserved', revisionId: revision.id };\n } catch (error) {\n if (error instanceof WorkbookAssessmentValidationError)\n return { kind: 'needs_review', reason: error.reason };\n throw error;\n }\n}\n\nexport {\n preserveWorkbookPhotoIdentification,\n processWorkbookPhotoAnalysis,\n receiveWorkbookPhotoIntake,\n retainWorkbookPhotoReceipt,\n WORKBOOK_PHOTO_HANDLER,\n workbookPhotoIntakeHandlers,\n workbookPhotoIntakeTargetResolver,\n} from './workbook-photo-ingestion.js';\n"],"mappings":"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"}
@@ -73,6 +73,8 @@ export declare class LearnerService {
73
73
  private transactionState;
74
74
  private writeFailure;
75
75
  private writeTail;
76
+ private compositeTail;
77
+ private composingMutation;
76
78
  private postgres;
77
79
  /** A trusted host transaction; capture/storage is forbidden on this scope. */
78
80
  protected executorBorrowed: boolean;
@@ -88,6 +90,12 @@ export declare class LearnerService {
88
90
  protected createTransactionService(db: DatabaseInterface): this;
89
91
  /** Internal compensation hook, invoked after in-flight work drains on abort. */
90
92
  protected rollbackResources(_cause: unknown, _rootDb: DatabaseInterface): Promise<void>;
93
+ /** Serialize a composite domain mutation which calls ordinary queued writes.
94
+ * The separate queue avoids waiting on the same queue from inside itself;
95
+ * both queues share abort/commit draining and poison-on-failure semantics.
96
+ * Composite operations must not call other composite mutations.
97
+ */
98
+ protected composeMutation<T>(operation: (service: this) => Promise<T>): Promise<T>;
91
99
  protected mutate<T>(operation: (service: this) => Promise<T>): Promise<T>;
92
100
  protected requireActor(request: LearnerRequest, action: Parameters<LearnerAuthorizer>[0]['action']): Promise<Learner>;
93
101
  getLearner(request: LearnerRequest): Promise<LearnerView>;
@@ -125,6 +133,13 @@ export declare class LearnerService {
125
133
  protected requireEnrollment(request: LearnerRequest & {
126
134
  enrollmentId: string;
127
135
  }, action: Parameters<LearnerAuthorizer>[0]['action']): Promise<Enrollment>;
136
+ /** Existing non-workbook consumers need not migrate optional workbook tables. */
137
+ protected findWorkbookActivity(request: LearnerRequest, selector: {
138
+ enrollmentId?: string;
139
+ lessonId?: string;
140
+ }): Promise<import("./workbook-models.js").WorkbookActivity | null>;
141
+ /** Compatibility progress never transports a workbook learner's feedback. */
142
+ protected projectProgress(request: LearnerRequest, row: LessonProgress): Promise<LessonProgressView>;
128
143
  listProgress(request: LearnerRequest & {
129
144
  enrollmentId: string;
130
145
  }): Promise<LessonProgressView[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"learner-service.d.ts","sourceRoot":"","sources":["../src/learner-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"learner-service.d.ts","sourceRoot":"","sources":["../src/learner-service.ts"],"names":[],"mappings":"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"}