motir-ai: GET /v1/lessons — list and detail for ONE project's tenant lessons, paged
The lessons live in motir-ai's database and motir-core has no way to read them: there is no /v1/lessons route — git grep '/v1/lessons' over src returns only the seed's import. This adds it.
Two shapes on the /v1 surface motirAiClient already talks to:
- list — this project's lessons,
lastOccurredAtdescending, paged. The repository already defines the admin paging convention (ADMIN_PAGE_DEFAULT = 50,ADMIN_PAGE_MAX = 100); reuse it rather than inventing a second. - detail — one lesson, in full:
title,body,why,howToApply, its axes,sourceRef,createdAt,lastOccurredAt, recurrence state.
Scoped to ONE project, at the query. The caller supplies the core project id and the route resolves it to an AiProject, exactly as getJob(jobId, coreProjectId) does. The WHERE is aiProjectId = <resolved> — not the injection clause scope = 'global' OR aiProjectId = …, which would leak the shipped global corpus into a project's own list.
Global rows never appear here. That is the single assertion most worth writing: the natural instinct is to reuse the injection read, and the injection read is deliberately wider.
Return retired and expired lessons too — this is an inspection surface, not the injection path. What a project is no longer being taught is exactly what someone opening this screen wants to see; the read-time filters belong to injection.
Acceptance criteria
GET /v1/lessonsreturns one project's lessons,lastOccurredAtdescending, paged on the existing admin convention.- A detail shape returns one lesson in full.
- The project is resolved from the caller's core project id; a caller cannot ask for another project's lessons, asserted.
- No
scope = 'global'row is ever returned, asserted directly against a fixture holding both. - Retired and expired lessons ARE returned, and carry enough state for a client to render them differently.
- The route follows the existing
/v1envelope, auth and error conventions — no new pattern. - Unit + integration tests against the real test database.
Context refs
motir-aisrc/repositories/lessonRepository.ts— the row shape,ADMIN_PAGE_DEFAULT/ADMIN_PAGE_MAX, and the injection clause this must NOT reuse.motir-aisrc/routes/— the/v1surface, its envelope and auth.motir-corelib/ai/motirAiClient.ts—getJob(jobId, coreProjectId), the project-scoping pattern to mirror.- MOTIR-3294 — the axes each row carries.