Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
6.8 KiB
6.8 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | duration | completed | |||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-shared-lists-live-sync | 07 | api |
|
|
|
|
|
|
|
|
|
6min | 2026-06-09 |
Phase 04 Plan 07: Gap-Closure (LIST-03 Rank Collation + T-04-08 Owner Guard) Summary
Closed LIST-03 drag-to-top bug via utf8mb4_bin migration on list_items.rank, and closed T-04-08/T-04-05 elevation-of-privilege by adding an owner-only guard before the isShared reconciliation block.
Performance
- Duration: ~6 min
- Started: 2026-06-09T18:18:10Z
- Completed: 2026-06-09T18:23:42Z
- Tasks: 2 (each TDD: RED commit + GREEN commit)
- Files modified: 4 (schema.ts, migration SQL, lists.ts, lists.test.ts)
Accomplishments
list_items.ranknow carriesCOLLATE utf8mb4_bin— uppercase fractional-indexing ranks (Zz) sort before lowercase ranks (a0) in DBORDER BY, matching JS string order. Drag-to-top persists across refetch.- Migration
0002_yielding_mattie_franklin.sqlis a single non-destructiveALTER TABLE list_items MODIFY COLUMN rank varchar(255) COLLATE utf8mb4_bin NOT NULL— no DROP, no TRUNCATE, no length or nullability change. Applied viadb:migrate(neverdb:push). PATCH /api/lists/:idnow returns403when a non-owner sharee sends{ isShared }, andlist_sharesis never mutated by a sharee. Threats T-04-08 and T-04-05 closed.- 3 new regression tests added (collation regression + 2 sharee-403 paths). Full suite: 184 tests, 0 failures (was 181).
Task Commits
- Task 1 RED — collation regression test -
ece663d(test) - Task 1 GREEN — schema + migration -
9b86061(feat) - Task 2 RED — sharee-403 tests -
931f767(test) - Task 2 GREEN — owner-only guard -
c0bd6d7(fix)
Files Created/Modified
apps/api/src/db/migrations/0002_yielding_mattie_franklin.sql— New additive migration: ALTER TABLE list_items MODIFY rank to COLLATE utf8mb4_binapps/api/src/db/schema.ts— AddedvarcharBincustomType factory; replacedlistItems.rankfromvarchar('rank', { length: 255 })tovarcharBin('rank').notNull(); addedcustomTypeto importsapps/api/src/routes/lists.ts— Added owner-only guard (if (patch.isShared !== undefined && !access.isOwner) return 403) after access check; updated stale comment on the reconciliation blockapps/api/tests/routes/lists.test.ts— Added collation regression test in reorder describe block; added two T-04-08 tests in PATCH describe block
Decisions Made
- D-04-07-collation: Drizzle 0.45.x does not expose a
collationoption onvarchar. UsedcustomTypefromdrizzle-orm/mysql-coreto define avarcharBinfactory that emitsvarchar(255) COLLATE utf8mb4_binas the SQL type string. This keeps schema-as-code and letsdb:generateproduce the correctMODIFY COLUMNstatement. - D-04-07-guard-placement: The guard is placed immediately after the
if (!access.allowed)block and beforeupdateValuesconstruction — ensuring neither theisSharedwrite nor the reconciliation block runs for non-owners.
Deviations from Plan
None — plan executed exactly as written. The customType approach for collation was anticipated by the plan's guidance ("add the utf8mb4_bin collation via drizzle's column collation option"), and customType is the correct mechanism when drizzle's built-in types lack a first-class option.
Must-Haves Verification
| Must-Have | Status |
|---|---|
| listItems.rank gets explicit COLLATE utf8mb4_bin with a migration | PASS — migration 0002; DB reports utf8mb4_bin via information_schema |
| PATCH isShared reconciliation runs ONLY for the list owner (access.isOwner === true) | PASS — guard at lists.ts:336 |
| Non-owner sharee sending { isShared } receives 403, list_shares never mutated | PASS — T-04-08 tests assert 403 + unchanged shares |
| Regression test for rank collation + negative sharee-403 test | PASS — 3 new tests in lists.test.ts |
Issues Encountered
- MySQL client (
mysql) is not installed on the dev host. Verified live DB collation vianode --input-type=modulewith directmysql2connection instead of the CLI. Result was confirmed:[{"COLUMN_NAME":"rank","COLLATION_NAME":"utf8mb4_bin"}].
User Setup Required
None — migration is applied automatically via db:migrate. The dev MariaDB was migrated in-place during execution.
Next Phase Readiness
- Phase 4 is now complete: all 14 security threats closed, LIST-03 gap resolved, full suite green (184/184).
- 04-SECURITY.md can be updated to
threats_open: 0. - 04-VERIFICATION.md LIST-03 gap entry can be marked resolved.
- Phase 5 (push notifications) is unblocked.
Self-Check: PASSED
All files found. All commits verified.
Phase: 04-shared-lists-live-sync Completed: 2026-06-09