CLUE-550 Stage 1: concurrent/kind stored axes + rebase group behaviors - #2923
CLUE-550 Stage 1: concurrent/kind stored axes + rebase group behaviors#2923scytacki wants to merge 19 commits into
Conversation
…tory + write-sync [CLUE-550]
…E-550] isDocumentAccessibleToUser now grants student read access based on the stored metadata.concurrent flag instead of type === "group", so any concurrent document (not just group-typed ones) is class-wide readable. Also relocates the `concurrent` field from IDocumentMetadata up to IDocumentMetadataBase (shared/shared.ts) — the access check's documentMetadata param is typed as the narrower base interface, and concurrent must be readable there. Type-only relocation, both interfaces still expose the field identically.
collaborative-learning
|
||||||||||||||||||||||||||||
| Project |
collaborative-learning
|
| Branch Review |
CLUE-550-concurrent-kind-axes
|
| Run status |
|
| Run duration | 10m 02s |
| Commit |
|
| Committer | Scott Cytacki |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
1
|
|
|
5
|
|
|
0
|
|
|
220
|
| View all changes introduced in this branch ↗︎ | |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## CLUE-550-document-axes-roadmap #2923 +/- ##
===================================================================
+ Coverage 69.18% 86.14% +16.96%
===================================================================
Files 930 937 +7
Lines 53296 53374 +78
Branches 14088 14100 +12
===================================================================
+ Hits 36874 45981 +9107
+ Misses 16390 7377 -9013
+ Partials 32 16 -16
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… TypeScript [CLUE-550] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Kind registrations now carry a metadataFields object (the axis fields a kind stamps onto its documents); getDocumentKindMetadataFields merges in the kind key. Both the creation and on-open-backfill stamp sites splat that single object, so adding an axis to a kind's stamped set is a registry + type change with no edit to the stamp sites. Also fix a stale design-doc reference in the on-open backfill comment, flag the temporary type-keyed kind lookup, and update the Stage 1 spec to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Introduces the first slice of the “document axes” refactor by adding stored concurrent/kind metadata fields, a kind registry to stamp creation-time axis defaults, and updates key “group” behaviors to be driven by the stored concurrency axis rather than type.
Changes:
- Added stored
concurrentandkindfields across shared metadata types, MST models, and document open/creation flows. - Introduced a kind registry (
document-kinds.ts) and stamped/backfilled axis fields for group documents (including a one-shot backfill script + index). - Rebased concurrency behaviors (history manager selection + non-owner sync warning) to read
document.concurrentwhile keeping permission behavior keyed ontype === "group".
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/models/stores/documents.ts | Switches Firestore history manager selection to use document.concurrent. |
| src/models/document/document.ts | Adds concurrent/kind stored props and includes them in the metadata getter. |
| src/models/document/document.test.ts | Adds unit coverage for DocumentModel concurrent/kind props and metadata getter. |
| src/models/document/document-utils.ts | Updates group-access comment tied to type === "group" behavior. |
| src/models/document/document-utils.test.ts | Adds tests ensuring group docs remain accessible regardless of concurrent presence. |
| src/models/document/document-metadata-model.ts | Extends Firestore metadata MST model with concurrent/kind (nullable). |
| src/models/document/document-metadata-model.test.ts | Adds tests for new metadata fields defaulting/retention. |
| src/models/document/document-kinds.ts | Adds a simple kind registry and seeds group as { concurrent: true }. |
| src/models/document/document-kinds.test.ts | Adds tests for kind registry behavior and metadata field stamping. |
| src/lib/db.ts | Stamps axis fields at creation; sources/backfills axis fields on open (transitional). |
| src/lib/db.test.ts | Adds test coverage for stamping, sourcing, and backfill write-back behavior. |
| src/hooks/use-document-sync-to-firebase.ts | Gates the non-owner write-sync warning on !document.concurrent. |
| shared/shared.ts | Extends IDocumentMetadata with concurrent and kind fields. |
| scripts/backfill-group-concurrent.ts | Adds admin backfill script to stamp { concurrent: true, kind: "group" } for group docs. |
| scripts/backfill-group-concurrent.test.ts | Adds unit tests for dry-run/apply/idempotency of the backfill logic. |
| firestore.rules | Updates comment around non-canonical group-doc delete rule (no semantic change). |
| firestore.indexes.json | Adds a fieldOverrides entry to ensure a collection-group index for documents.type. |
| docs/superpowers/specs/2026-07-23-clue-550-stage-1-document-axes-design.md | Adds design spec documenting the stage-1 approach and migration plan. |
| docs/document-axes/README.md | Updates axis roadmap status to reflect Stage 1 delivery. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…tency - document-utils: the class-access comment described a class check this function doesn't do; clarify that the security rules scope group docs to the class. - firestore.rules: the non-canonical group-doc delete rule keys on class, not ownership; drop the misleading "their own" from its comment. - db.ts on-open backfill: derive `concurrent` by treating any non-`true` stored value as missing, matching the write-back gate, so a group doc never opens single-writer in the same session the code writes `concurrent: true` back. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment-only cleanup: expand compact multiline JSDoc blocks to the /**-on-its- own-line style in shared.ts and document-kinds.ts, and simplify a few inline notes. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The concurrent/kind field docs restated what the field names and the Stage 1 design already convey; removing them matches the interface's uncommented style. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Define the stamped-fields concept once on the type; the field and function comments now carry only their distinguishing detail instead of restating it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What this does
First code slice of the document-axes refactor: introduces stored document "axes" and splits the group-document behaviors by axis instead of switching on
type.New stored axes + registry
concurrent(multi-writer) andkind(registry key) added toIDocumentMetadata, theDocumentMetadataModelmirror, andDocumentModel(stored props). Both use| null/types.maybeNullso the metadata parity check holds.src/models/document/document-kinds.ts):registerDocumentKind/getDocumentKindInfo, built-ingroup={ concurrent: true }. Each kind declares the axis fields it stamps (metadataFields);getDocumentKindMetadataFieldsmerges in thekindkey so the stamp sites splat a single object. Creation-time source only — never consulted to read a live document's concurrency.openDocumentsourcesconcurrent/kindonto the model from Firestore metadata (the existingcontext_id→contextIdtemplate); creation stamps them onto the Firestore doc for registered kinds.Behaviors split by axis
concurrent: concurrent-history-manager selection (documents.ts) and the non-owner write-sync warning (use-document-sync-to-firebase.ts) read the storedconcurrent.type: class-wide student read access (isDocumentAccessibleToUser) and the Firestore non-canonical delete rule stay keyed ontype === "group". Read/delete are permissions (kind-derived in the target design), and every group doc already carries the type — so pre-existing group docs keep read access and stay deletable with no data migration, and the non-canonical race-loser orphans that the delete rule exists to clean up (never opened, so never backfilled) remain deletable.Backfill for pre-existing group docs'
concurrentfieldOnly the history manager needs
concurrenton pre-existing (collaborated) group docs. Two backfills converge it:openDocumentderivesconcurrentfrom the kind registry when the stored field is absent (so the opened model runs concurrent history immediately) and best-effort merge-writes{ concurrent: true, kind }back to the doc. Idempotent, fire-and-forget (never blocks/rejects the open), and rules-permitted for a class member (only non-read-only fields,canonicaluntouched). Clearly marked transitional — removed a release after the batch script runs.scripts/backfill-group-concurrent.ts, dry-run default): one-shot admin sweep (npx tsx) for never-opened docs. Run manually against staging then production after this ships;APPLY=1to write.Firestore index
firestore.indexes.jsonadds aCOLLECTION_GROUPsingle-field index ondocuments.type. It exists solely to serve the batch backfill script's collection-group query (collectionGroup("documents").where("type", "==", "group")) — no app code or cloud function queries thedocumentscollection group, so the index has no effect on the running application. It is inert untilfirebase deploy --only firestore:indexesis run (merging this PR does not activate it); it's bundled here to keep the backfill script self-contained for whenever it's run. Firestore also prints a one-click creation link on the query's first run if the index isn't deployed yet.The stored
typevalue is unchanged (group docs keeptype === "group"). DQB / new kinds / the full permissions axis are later stages.Testing
DocumentModelprops +metadatagetter,openDocumentsourcing, the on-open backfill (fires / doesn't-fire ×3), type-based read access, and Firestore creation stamping; batch script unit tests (dry-run / apply / idempotent).canonical-pointers-rulesgreen incl. non-canonical delete keyed on type; full firebase-test suite 347 passed.check:typesclean;lint:build0 errors.Follow-up
openDocument(restores the pure "readconcurrentfrom storage" path).