fix: a trashed note file leaves its note, quieter MCP sweep, bounded compose logs

A note's attachment moved to the trash from the file manager kept
showing on the note; the hydration read now skips it, as the chat one
already did. The MCP session sweep logged a line every minute while any
session was open; it logs only when it cleaned something. The compose
file caps the container log, and three test mocks that pointed at
modules long gone are removed.
This commit is contained in:
Maurice
2026-10-07 16:47:53 +02:00
parent 76df44d8de
commit 72fbbd55d4
9 changed files with 21 additions and 13 deletions
@@ -192,12 +192,18 @@ export class CollabNotesRepository extends TrekRepository<CollabNotes> {
// `TripFilesRepository`.
// -------------------------------------------------------------------------
/** CB6 (`formatNote`'s hydration read) — `SELECT id, filename, original_name, file_size, mime_type FROM trip_files WHERE note_id = ?`. */
/**
* CB6 (`formatNote`'s hydration read) — `SELECT id, filename, original_name,
* file_size, mime_type FROM trip_files WHERE note_id = ? AND deleted_at IS
* NULL`. A note's file can be moved to the trash from the file manager; it
* leaves the note then, as a chat attachment leaves its message.
*/
async listAttachmentsForNote(note_id: number): Promise<NoteAttachmentRow[]> {
return await this.kysely<NoteAttachmentsKyselyDB>()
.selectFrom('trip_files')
.select(['id', 'filename', 'original_name', 'file_size', 'mime_type'])
.where('note_id', '=', note_id)
.where('deleted_at', 'is', null)
.execute();
}
+3 -1
View File
@@ -56,7 +56,9 @@ const sessionSweepInterval = setInterval(() => {
for (const [key, entry] of rateLimitMap) {
if (entry.windowStart < rateCutoff) rateLimitMap.delete(key);
}
if (cleaned > 0 || sessions.size > 0) {
// Only when something was cleaned: logging every minute while any session
// is open wrote 1,440 lines a day into a log nothing rotates.
if (cleaned > 0) {
console.log(`[MCP] Session sweep: cleaned ${cleaned}, active ${sessions.size}`);
}
}, 60 * 1000); // sweep every 1 minute
+1 -1
View File
@@ -4,7 +4,7 @@
* The implementation moved to nest/realtime: the connection lifecycle and the
* handshake are RealtimeGateway, the wire protocol is TrekWsAdapter, and the
* socket registry is ws-state.ts. This file stays because it is the import path
* the rest of the tree knows, including 115 test files that `vi.mock` it to
* the rest of the tree knows, including the more than 160 test files that `vi.mock` it to
* assert on broadcasts. Repointing them all would be a large diff that proves
* nothing, and this re-export keeps every one of those mocks working.
*
-4
View File
@@ -28,10 +28,6 @@ vi.mock('../../src/websocket', () => ({
broadcastToUser: vi.fn(),
getOnlineUserIds: vi.fn(() => []),
}));
vi.mock('../../src/services/memories/immichService', () => ({
uploadToImmich: vi.fn(async () => null),
getImmichCredentials: vi.fn(() => null),
}));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -874,6 +874,10 @@ describe('repository parity', () => {
// CB6's narrow attachment projection.
const legacyAttachments = testDb.prepare('SELECT id, filename, original_name, file_size, mime_type FROM trip_files WHERE note_id = ? ORDER BY id ASC').all(note.id);
expect(await notesRepo.listAttachmentsForNote(note.id)).toEqual(legacyAttachments);
// A file moved to the trash from the file manager leaves the note.
testDb.prepare("UPDATE trip_files SET deleted_at = CURRENT_TIMESTAMP WHERE note_id = ? AND original_name = 'b.png'").run(note.id);
expect((await notesRepo.listAttachmentsForNote(note.id)).map(a => a.original_name)).toEqual(['a.pdf']);
});
it('COLLAB-REPO-002: CollabPollsRepository/CollabPollVotesRepository match the legacy queries for a multiple-choice poll voted on every option', async () => {