CLUE-610: rename the generic document type from "group" to "axes" - #2952
Open
scytacki wants to merge 13 commits into
Open
CLUE-610: rename the generic document type from "group" to "axes"#2952scytacki wants to merge 13 commits into
scytacki wants to merge 13 commits into
Conversation
…LUE-610] Both tests reused whatever `db` the previous test left behind instead of opening their own studentAuth client, so the caller identity under test was an accident of declaration order and either test would throw undefined `db` if run in isolation.
…l ones [CLUE-610] The merged-write comments claimed a half-swept document (type:"axes" without kind:"group") matches neither getDocumentTitle branch and renders with no title. That branch checks isAxesType(type) && groupId, and isAxesType accepts both "axes" and "group", so a half-swept group document still matches and still renders correctly — the claimed hazard doesn't exist on this branch. Replace it with the two hazards that are real: a group document missing concurrent+kind loses its history manager and canonical-pointer slot label; a class-wide document missing curriculum scope is invisible to Sort Work's unit-scoped query. Also brings the function docstring in line with the three-part (not two-pass) behavior the header already describes.
…LUE-610] Ten no-behavior-change fixes from the final whole-branch review: - Replace the sweep script's merged-write rationale in all four places (design spec, script header, script body comment, test comment) with the one argument that survives verification: type is the script's own work-queue key (where("type", "==", "group")), so committing it separately from the axis fields would make write order load-bearing and could permanently strand a half-migrated document with no way to find it again. Two earlier rationales in the same spots were checked against the code and found false. - Name and accept the deploy-window collision in the design spec (§7) and the plan's deploy-sequencing section: old clients meeting a brand-new "axes"-typed document at the moment 7.5.0's writers ship, before any drain is possible. Mitigation: deploy outside class hours. - Strengthen the sweep script's test mock so it can catch a silent under-migration: pin the query predicate (collectionGroup + where args) instead of discarding them, and give db.batch() a fresh recorder per call so a 401-document case can assert the 400/1 commit split. - Reword the db.ts kind-lookup comment to state the actual constraint (no "axes" registry entry, so the group kind's `concurrent: true` is what's being borrowed) instead of a narrower and partly inaccurate one. - Delete a redundant db.test.ts case whose assertions are a strict subset of the amended create-path test just above it. - Note above both widened firestore.rules clauses that "group" and "axes" are both live until CLUE-604's sweep has run everywhere, so neither branch is dead code. - Correct "all ten call sites" to "every call site" in the isAxesType comment (an eleventh site was added after that comment was written). - Align the design spec with shipped code: DBGroupDocMetadata.type is "group" | "axes", not narrowed to "axes"; the isAxesType snippet now shows its type-guard return annotation. - Remove the design spec's link to an untracked session-handoff note so the committed doc doesn't 404 once that note is gone. - Note in the sweep script header that write volume now scales with every matched document, not just the ones needing a concurrent/scope backfill. Covering tests, npm run check:types, and npm run lint:build are clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ree comments [CLUE-610] The 401-document test counted batch allocations, so deleting the tail commit still passed it. The mock now records whether each batch was committed, and the test asserts on that — verified by deleting the tail commit and watching it fail. Also folds the plan-only deploy steps into the design spec, since the plan is transient: the rules deploy is now its own sequencing step with its command.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## CLUE-610-deferred-open-and-create-rules #2952 +/- ##
===========================================================================
+ Coverage 69.89% 69.90% +0.01%
===========================================================================
Files 973 973
Lines 55835 55849 +14
Branches 14736 14739 +3
===========================================================================
+ Hits 39025 39042 +17
+ Misses 16776 16773 -3
Partials 34 34
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:
|
collaborative-learning
|
||||||||||||||||||||||||||||
| Project |
collaborative-learning
|
| Branch Review |
CLUE-610-generic-type-rename
|
| Run status |
|
| Run duration | 03m 37s |
| Commit |
|
| Committer | Scott Cytacki |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
4
|
| View all changes introduced in this branch ↗︎ | |
…and schedule the cleanup [CLUE-610]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Renames the generic document
typefrom the stored value"group"to"axes", across the app, the Firestore rules, and the one-time sweep script.Stacked on #2951 (
CLUE-610-deferred-open-and-create-rules) — review that first. The diff shown against this base is only this work.Why
type: "group"no longer means "this is a group document". Regular group documents and class-wide collaborative documents both store it, and they are told apart only by guards over their stored axis fields. What the value actually marks is a document whose behavior is read from its axes rather than from its type — but it is still named for the first such document. That costs us twice:type. A freshDocumentTypeenum value reintroduces the per-type branching this refactor exists to remove; storing"group"says something false about a document that is not a group's.GroupDocumentmean "a group's document", some mean "a concurrent document", some mean "an axis-native document". The shared literal makes them look identical.Why now, and the deadline
typeis a stored value, so changing it needs an admin sweep, and a sweep needs a full client drain in front of it. CLUE-604 already schedules exactly that sweep — addingtypeis one more field in a write that is already happening to these documents.This means the app change has to ship in 7.5.0. Landing it in 7.6.0 instead would make the sweep wait for 7.6.0 to drain, but 7.6.0's own content is the cleanup the sweep unblocks — so 7.6.0 would have to be split, or the sweep run twice. v7.4.0 is still the newest tag as of 2026-08-08, so the window is open; if 7.5.0 has been cut by the time this is picked up, this needs re-planning rather than merging.
The shape of it: a transitional accept-both window
Nothing rewrites stored data here. Until CLUE-604's sweep runs and old clients drain, both values exist, so every reader accepts both and
GroupDocumentstays exported.Readers widen before writers flip, so no commit in the middle can write a value the app cannot read. The commits are task-sized and land in that order:
1768c35c2AxesDocument = "axes"and theisAxesTypepredicate2fc53129258dbd1a6d"axes"; RTDB declarations widened9ed6aad23,cb80176666d03bba57,0065dbfdfisAxesTypeis deliberately a type guard, not aboolean—db.ts:727narrowstypeto a literal to build a discriminated-union member, and a plain boolean return breaks that. It also means the post-sweep cleanup is one edit to one function rather than one per call site.Two things worth a careful look
The sweep script now makes one merged write per document rather than committing two disjoint write lists. This matters more than it looks:
typeis the script's own work-queue key — the driving query iswhere("type", "==", "group"), so once a document'stypeflips it stops matching and can never be returned by a re-run. Committingtypeseparately from the axis fields would make write order load-bearing across the 400-document chunk boundary, and a document whosetypelanded while its axis-field write failed would be permanently half-migrated with no recovery path.The rules widening is monotone — an added
||term at each of the two clauses, no operand removed — so every document that reached a decision before reaches the identical decision now, and exactly one additional type literal is granted.isValidDocumentCreateRequestneeded no change: it constrains only thattypeis present, never its value, because the create-time rules key on owner instead.Deploy sequencing
This fits the release plan already agreed for the CLUE-550 line of work — 7.5.0 CLUE-610, 7.6.0 CLUE-604, 7.7.0 CLUE-612:
npm run deploy:firestore:rules. Rules first, as usual, and here required rather than merely conventional. Step 7 goes the other way round, for the reason below."axes"on creation. Deadline-bound, see above. Deploy outside class hours — see the caveat below.APPLY=1, against each environment in turn. An operational step gated on step 3 having actually happened, deliberately separate from any version going out. Shipping the script is not running it — this PR ships it; the run is CLUE-604's."group"inisAxesType,isSortableType, andDocumentTypeEnumValues. They share CLUE-604's gate exactly (safe once the sweep has run everywhere, unsafe before), so there is no reason to leave them as unscheduled cleanup. Follows step 4 rather than accompanying it."group"branch from the canonical-race delete. (concurrentChangeOkis deleted wholesale by CLUE-612, so its own branch needs no separate handling.)Steps 1 and 4 are the two that are not a release going out.
Two things survive that cleanup rather than being dropped with it. The
GroupDocumentconstant stays — it is still the group kind's registered name, and thekindaxis is not renamed. And"group"stays in the RTDB type declarations, because RTDB is never swept and keeps a permanent mix of both values.Why the rules move first here and last in 7.7.0
Both are rules changes and they sit on opposite sides of their releases. The rule is direction:
typevalue and takes nothing away, so deploying it against the currently-live app changes no decision. It must precede the app that starts writing the new value, or that app's writes hit rules that don't know the value yet.For step 7 the write in question is the on-open
concurrentbackfill. Two independent things retire it: CLUE-604's step 3 deletes the code in 7.6.0, and the sweep (step 4) removes the unmigrated documents that would trigger it in the first place. Neither is enough alone, because a browser still running the 7.5.0 bundle contains the backfill — hence step 6's drain, and hence 7.6.0 and 7.7.0 being separate releases at all.And why step 1's rules must precede the app rather than merely accompany it. The widening is monotone — an added
||term at each clause, no operand removed — so deploying it against the currently-live app changes no decision, which is what makes rules-first safe. The reverse order is not: once the app writes"axes", two class members can race to create the same canonical document, and the loser's cleanup delete is governed by a rules clause that under un-widened rules matches only"group". That delete runs throughdeleteOrphanDocument, which is best-effort and swallows its own error — so the failure is silent, and the symptom is Firestore metadata quietly accumulating for documents nobody can reach.Deploy 7.5.0 outside class hours. From the instant the writers ship, a student still on the old bundle who opens a brand-new shared document — one a classmate on the new bundle just created — fails to open it, and no drain is possible because the window opens when the release does. Only a group's or class's first document created in that window is exposed; everything already stored is untouched. Accepted rather than designed around, since deferring the writers forces the second sweep this work exists to prevent.
Deliberately not here
typeis not swept. Existing records keep"group"forever — onlycreatedAtis read back from that tree, and the tree is slated for removal. The declarations widen so both values are representable; the data is not touched.concurrentbackfill andgetDocumentTitleare widened here and removed by CLUE-604, so the two stories do not both claim those sites.Testing
Full unit suite (3613) and the emulator-backed rules suite (430) both pass;
check:typesandlint:buildclean. Each widened reader has a case for both values — the transitional window is the only time both occur, and exactly when a regression would ship. The rules tests cover both values at both clauses.A literal-string sweep across
src,shared,functions-v2,scripts, andfirestore.rulesfound no reader left comparing against a bare"group"; the remaining hits are type declarations that legitimately admit both values, the owner axis (DocumentOwnerType), the kind registry, and unrelated uses of the word — a CSS class, drawing-object types, ARIA roles, a Sort Work section key, a sticky-note audience.The sweep script's tests pin the query predicate (
type == "group", not the value it writes) and the 400-document chunk boundary, including that the final partial batch is actually committed.Jira
CLUE-610 and CLUE-604 have been updated: CLUE-610 gained the rename plus its release requirements, and CLUE-604's step 2 was restated around the merged write. CLUE-612 was re-read and needs nothing — it is written entirely in terms of the shared marker, never the type value.
🤖 Generated with Claude Code