You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Give the backend an authoritative, storage-agnostic view of each collection's shape (board rosters, sizing, release status) via a build-generated manifest that both Baker Bridge and Practice-Bidding-Scenarios publish in one shared format, consumed behind a pluggable LibrarySource abstraction.
Design docs (this issue tracks their implementation):
ADR: documentation/adr/0002-collection-manifest-and-library-source.md (amends the Collection Producer Contract with a new R5)
Today the backend has no authoritative board count and no server-side view of release status:
Lesson size is inferred as COUNT(DISTINCT deal_number) across all users (lesson_mastery.rs) — so a lesson's "size" is only as big as the union of boards anyone has played. Denominators understate until coverage fills in.
prerelease is client-asserted: the browser computes prerelease = deal.stable !== true (observationSchema.js) and the backend trusts it. The producer's stable flag lives in the PBN, but nothing server-side reads the PBN.
Three parallel, non-authoritative sizing sources exist: static dealCount table (bakerBridgeTaxonomy.js), runtime PBN regex parsing (useBoardMastery.js), and backend COUNT(DISTINCT).
The frontend already fetches a whole-collection manifest for PBS (fetchScenarioManifest, carries generatedAtCommit) — but it has no sizing, and Baker Bridge has no equivalent (uses a differently-shaped toc.json).
Scope
Content producers
Agree manifest schema v2 (adds a lessons board roster) — Rick + David
PBS: add the lessons roster to David's manifest generator
Baker Bridge: new manifest generator in CSVtoPBN (companion to the R3 board-version-token stamping step)
Cut lesson_mastery.rs denominators over to the roster (verify vs current values before removing COUNT(DISTINCT))
Make prerelease server-authoritative: reconcile observations.prerelease against the roster in student_summary.rs stats
Frontend
Delete runtime PBN parsing for counts (useBoardMastery.js) and the static dealCount table (bakerBridgeTaxonomy.js) once the backend is authoritative
(later) reconcile COLLECTIONS[] location fields against the backend registry (single source)
Key decisions (see ADR)
Backend pulls, frontend does not push. Client-pushed sizing would re-send per session and re-introduce the client-trust problem. Backend fetches the manifest itself.
Collection location is backend-owned config. The backend must know where to fetch regardless; consolidate the reports.rs hardcode + frontend duplication into one registry. Client keeps stamping collection_id per observation (cheap, self-contained), but the backend stops trusting the client for sizing/release status.
Reconciliation, not rejection. An observation for a board not in the roster is accepted + flagged, never rejected — ADR-0001 observations stay self-contained; history is never mutated.
Summary
Give the backend an authoritative, storage-agnostic view of each collection's shape (board rosters, sizing, release status) via a build-generated manifest that both Baker Bridge and Practice-Bidding-Scenarios publish in one shared format, consumed behind a pluggable
LibrarySourceabstraction.Design docs (this issue tracks their implementation):
documentation/adr/0002-collection-manifest-and-library-source.md(amends the Collection Producer Contract with a new R5)documentation/design/collection-manifest-and-library-source.mdWhy
Today the backend has no authoritative board count and no server-side view of release status:
COUNT(DISTINCT deal_number)across all users (lesson_mastery.rs) — so a lesson's "size" is only as big as the union of boards anyone has played. Denominators understate until coverage fills in.prereleaseis client-asserted: the browser computesprerelease = deal.stable !== true(observationSchema.js) and the backend trusts it. The producer'sstableflag lives in the PBN, but nothing server-side reads the PBN.dealCounttable (bakerBridgeTaxonomy.js), runtime PBN regex parsing (useBoardMastery.js), and backendCOUNT(DISTINCT).The frontend already fetches a whole-collection manifest for PBS (
fetchScenarioManifest, carriesgeneratedAtCommit) — but it has no sizing, and Baker Bridge has no equivalent (uses a differently-shapedtoc.json).Scope
Content producers
lessonsboard roster) — Rick + Davidlessonsroster to David's manifest generatorCSVtoPBN(companion to the R3 board-version-token stamping step)Backend (Bridge-Classroom-api)
LibrarySourcetrait +GitHubRawSourceimpl (conditional GET / ETag /generatedAtCommit)CollectionRegistryconfig (collection → source + location); fold inreports.rs::repo_for_collection(collection_id, version_token)lesson_mastery.rsdenominators over to the roster (verify vs current values before removingCOUNT(DISTINCT))observations.prereleaseagainst the roster instudent_summary.rsstatsFrontend
useBoardMastery.js) and the staticdealCounttable (bakerBridgeTaxonomy.js) once the backend is authoritativeCOLLECTIONS[]location fields against the backend registry (single source)Key decisions (see ADR)
reports.rshardcode + frontend duplication into one registry. Client keeps stampingcollection_idper observation (cheap, self-contained), but the backend stops trusting the client for sizing/release status.GoogleDocsSource/ObjectStoreSource/LocalFileSourcedrop in behind the trait without touching routes.generatedAtCommit+If-None-Matchconditional GETs; a304is free. Webhook is an optional later accelerator.Open questions
toc.jsonalongside the manifest, or subsume it?