Skip to content

CLUE-550 Stage 1: concurrent/kind stored axes + rebase group behaviors - #2923

Open
scytacki wants to merge 19 commits into
CLUE-550-document-axes-roadmapfrom
CLUE-550-concurrent-kind-axes
Open

CLUE-550 Stage 1: concurrent/kind stored axes + rebase group behaviors#2923
scytacki wants to merge 19 commits into
CLUE-550-document-axes-roadmapfrom
CLUE-550-concurrent-kind-axes

Conversation

@scytacki

@scytacki scytacki commented Jul 22, 2026

Copy link
Copy Markdown
Member

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) and kind (registry key) added to IDocumentMetadata, the DocumentMetadataModel mirror, and DocumentModel (stored props). Both use | null / types.maybeNull so the metadata parity check holds.
  • A kind registry (src/models/document/document-kinds.ts): registerDocumentKind / getDocumentKindInfo, built-in group = { concurrent: true }. Each kind declares the axis fields it stamps (metadataFields); getDocumentKindMetadataFields merges in the kind key so the stamp sites splat a single object. Creation-time source only — never consulted to read a live document's concurrency.
  • openDocument sources concurrent/kind onto the model from Firestore metadata (the existing context_idcontextId template); creation stamps them onto the Firestore doc for registered kinds.

Behaviors split by axis

  • Concurrency machinery → concurrent: concurrent-history-manager selection (documents.ts) and the non-owner write-sync warning (use-document-sync-to-firebase.ts) read the stored concurrent.
  • Permissions → group type: class-wide student read access (isDocumentAccessibleToUser) and the Firestore non-canonical delete rule stay keyed on type === "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' concurrent field

Only the history manager needs concurrent on pre-existing (collaborated) group docs. Two backfills converge it:

  • On open (transitional): openDocument derives concurrent from 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, canonical untouched). Clearly marked transitional — removed a release after the batch script runs.
  • Batch (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=1 to write.

Firestore index

firestore.indexes.json adds a COLLECTION_GROUP single-field index on documents.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 the documents collection group, so the index has no effect on the running application. It is inert until firebase deploy --only firestore:indexes is 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 type value is unchanged (group docs keep type === "group"). DQB / new kinds / the full permissions axis are later stages.

Testing

  • Jest unit suite green (3083 passed, 0 failed), incl. new tests for the metadata fields, the registry, the DocumentModel props + metadata getter, openDocument sourcing, 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).
  • Firestore rules (emulator): canonical-pointers-rules green incl. non-canonical delete keyed on type; full firebase-test suite 347 passed.
  • check:types clean; lint:build 0 errors.

Follow-up

  • After the batch script runs on staging + production, remove the transitional on-open derive/write-back in openDocument (restores the pure "read concurrent from storage" path).

scytacki added 6 commits July 22, 2026 08:00
…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.
@cypress

cypress Bot commented Jul 22, 2026

Copy link
Copy Markdown

collaborative-learning    Run #19559

Run Properties:  status check passed Passed #19559  •  git commit f681050f76: CLUE-550 De-duplicate document-kinds comments
Project collaborative-learning
Branch Review CLUE-550-concurrent-kind-axes
Run status status check passed Passed #19559
Run duration 10m 02s
Commit git commit f681050f76: CLUE-550 De-duplicate document-kinds comments
Committer Scott Cytacki
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 1
Tests that did not run due to a developer annotating a test with .skip  Pending 5
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 220
View all changes introduced in this branch ↗︎

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.88136% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.14%. Comparing base (55742f7) to head (f681050).

Files with missing lines Patch % Lines
scripts/backfill-group-concurrent.ts 60.00% 14 Missing ⚠️
src/lib/db.ts 77.77% 2 Missing ⚠️
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     
Flag Coverage Δ
cypress-regression 73.00% <81.81%> (?)
cypress-smoke 41.99% <81.81%> (+0.01%) ⬆️
jest 55.62% <71.18%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…LUE-550]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scytacki and others added 3 commits July 23, 2026 11:06
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 concurrent and kind fields 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.concurrent while keeping permission behavior keyed on type === "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.

Comment thread src/models/document/document-utils.ts Outdated
Comment thread firestore.rules Outdated
Comment thread src/lib/db.ts
Comment thread scripts/backfill-group-concurrent.ts
…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>
scytacki and others added 2 commits July 26, 2026 12:47
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants