docs: record H34-H36 audit closure

This commit is contained in:
Siavash Sameni
2026-06-07 16:40:44 +04:00
parent af9bce3967
commit ffc0d83c60
2 changed files with 19 additions and 6 deletions

View File

@@ -12,6 +12,16 @@ entries on top. Maintained by agents per the rule in `../AGENTS.md`.
---
### 2026-06-07 — backend@04de406, frontend@c2a5e4a — DB audit H34-H36 normalized chat/dispute messages
**Commits:** `04de406` `c2a5e4a`
**Touched:** backend `src/db/schema/chat.ts`, `src/db/schema/dispute.ts`, `src/db/migrations/0028_chat_dispute_message_tables.sql`, `src/db/repositories/drizzle/DrizzleChatRepo.ts`, `src/db/repositories/drizzle/DrizzleDisputeRepo.ts`, `src/db/repositories/interfaces/IChatRepo.ts`, `src/services/chat/ChatService.ts`, targeted regression tests, `package.json`, `package-lock.json`; frontend `package.json`, `Dockerfile`; docs `09 - Audits/DB Query & Schema Audit - 2026-06-06.md`, `09 - Audits/Activity Log.md`
**Why:** Close High H34-H36 from the DB Query & Schema Audit. Chat messages, participants, and unread counts now have normalized relational tables with migration backfills while the old JSONB columns remain compatibility mirrors. Chat message send/page/read/edit/delete paths use row-level repository methods and relational list/count predicates. Dispute messages now hydrate from `dispute_messages` rows with JSON fallback only during transition.
**Verification:** backend `npm run typecheck`; backend `npx jest __tests__/drizzle-chat-repo.test.ts __tests__/db-audit-dispute-integrity.test.ts __tests__/db-audit-chat-dispute-normalized-messages.test.ts --runInBand` (3 suites / 12 tests); backend/frontend scoped `git diff --check`; frontend/backend version metadata confirmed at v2.10.3.
**Linked docs updated:** [[09 - Audits/DB Query & Schema Audit - 2026-06-06]]
---
### 2026-06-07 — backend@05b402c, frontend@169fe69 — DB audit bounded high cleanup and blog status enum
**Commits:** `e8cb64c` `05b402c` `169fe69`

View File

@@ -91,6 +91,9 @@ updated: 2026-06-07
| H17: template payment completion per-request status loop → one serializable bulk status update with an all-rows-updated guard | `957c356` v2.9.38 |
| H18: email-code registration split referrer/user/temp writes → one serializable auth-store registration transaction, with referral bonus left post-commit best-effort | `957c356` v2.9.38 |
| H38: `blog_posts.status` stored as text instead of pgEnum → runtime schema and migration `0027_blog_post_status_enum.sql` enforce `blog_post_status` | `e8cb64c` / `05b402c` v2.10.1 |
| H34: chat `messages` JSONB array hot path → normalized `chat_messages` table, backfill migration, row-level send/read/edit/delete methods, DB pagination | `04de406` v2.10.3 |
| H35: chat `participants`/`unreadCounts` JSONB arrays → normalized `chat_participants` + `chat_unread_counts` tables and relational list/count predicates | `04de406` v2.10.3 |
| H36: dispute `messages` JSONB array → normalized `dispute_messages` table with migration backfill and relational hydration | `04de406` v2.10.3 |
---
@@ -520,33 +523,33 @@ When `input.limit` is not supplied (the notification fan-out path), `findSellers
---
### 34. Chat messages stored as a JSONB array column — full-document rewrite on every message send, no DB-level pagination
### 34. Chat messages stored as a JSONB array column — full-document rewrite on every message send, no DB-level pagination | **FIXED** `04de406` v2.10.3
> **Category:** Wrong Schema | **File:** `src/db/schema/chat.ts:100-103`
All messages for a chat are in a single `messages jsonb` column. Every `sendMessage()`, `editMessage()`, `deleteMessage()`, or `markMessagesAsRead()` does a full read-modify-write of the entire array. For a chat with 10,000 messages this means writing ~10 MB of JSON on each send. `getChatMessages()` in ChatService.ts (lines 444-451) fetches all messages then sorts and slices in JavaScript — there is no way to paginate at the DB level.
**Fix:** Migrate messages to a separate `chat_messages` table (chat_id FK, sender_id, content, message_type, timestamp, is_read, is_edited, reply_to_id). This enables server-side cursor/keyset pagination, targeted UPDATE for mark-as-read, atomic INSERT for new messages, and GIN index on content for search. Move unread_counts to a `chat_unread_counts` table similarly.
**Fix:** `04de406` v2.10.3 adds `chat_messages` with chat/sender/type/timestamp/read/edit/reply indexes plus migration backfill from `chats.messages`. `ChatService` now calls row-level repo methods for send, page reads, mark-read, edit, and delete; the JSON column remains as a compatibility mirror only.
---
### 35. Chat participants and unread counts stored as JSONB arrays instead of relational tables
### 35. Chat participants and unread counts stored as JSONB arrays instead of relational tables | **FIXED** `04de406` v2.10.3
> **Category:** Wrong Schema | **File:** `src/db/schema/chat.ts:95-114`
The `participants` and `unreadCounts` JSONB columns are queried for specific userIds and counts on every user-facing chat request. These are fundamentally relational: each participant belongs to one chat, has a userId FK, role, and isActive flag. Storing them as JSONB means membership lookups are full table scans and the DB cannot enforce FK integrity or use B-tree indexes on userId.
**Fix:** Create a `chat_participants` table (chat_id, user_id, role, is_active, joined_at, last_seen) and a `chat_unread_counts` table (chat_id, user_id, count). Index `(user_id, is_active)` on participants and `(user_id, count)` on unread_counts. This converts O(N) JSONB scans to O(1) indexed lookups.
**Fix:** `04de406` v2.10.3 adds `chat_participants` and `chat_unread_counts` with backfill from the old JSONB arrays. `DrizzleChatRepo` hydrates returned chats from those rows, uses relational `EXISTS` predicates for membership/unread list/count queries, and syncs normalized rows on create/save.
---
### 36. Dispute messages stored as JSONB array — unbounded growth, no sender/read-status filtering without full scan
### 36. Dispute messages stored as JSONB array — unbounded growth, no sender/read-status filtering without full scan | **FIXED** `04de406` v2.10.3
> **Category:** Wrong Schema | **File:** `src/db/schema/dispute.ts:99-102`
The `messages` column stores all dispute messages in a single JSONB array. Any filter by `senderId`, `isRead`, or timestamp must deserialise the entire array. There is no GIN index. Every message append requires a full read-modify-write of the blob.
**Fix:** Create a `dispute_messages` table (dispute_id, sender_id, content, timestamp, is_read, attachments). Index on `(dispute_id, timestamp)`. This enables efficient per-dispute pagination and sender filtering.
**Fix:** `04de406` v2.10.3 adds `dispute_messages` with `(dispute_id, timestamp)`, sender/timestamp, and read-status indexes plus migration backfill from `disputes.messages`. `DrizzleDisputeRepo` batch-hydrates dispute messages from normalized rows, falling back to JSON only when rows do not exist yet.
---