From 64ee0a9af9299cfeeedb15213076d9588c910710 Mon Sep 17 00:00:00 2001 From: vswaroop04 Date: Fri, 15 May 2026 01:46:49 +0530 Subject: [PATCH] fix(storage): enforce cache proposal constraints at application level Expression-based partial unique indexes are silently skipped on some SQLite builds in CI, leaving the constraints unenforced. Mirror the CHECK and UNIQUE constraints in createCacheProposal so the behaviour is consistent regardless of SQLite version. --- .../src/storage/adapters/sqlite.adapter.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/apps/api/src/storage/adapters/sqlite.adapter.ts b/apps/api/src/storage/adapters/sqlite.adapter.ts index 636083dd..6b68af0d 100644 --- a/apps/api/src/storage/adapters/sqlite.adapter.ts +++ b/apps/api/src/storage/adapters/sqlite.adapter.ts @@ -3364,6 +3364,43 @@ export class SqliteAdapter implements StoragePort { async createCacheProposal(input: CreateCacheProposalInput): Promise { if (!this.db) throw new Error('Database not initialized'); + + const validTypes: Record = { + semantic_cache: ['threshold_adjust', 'invalidate'], + agent_cache: ['tool_ttl_adjust', 'invalidate'], + }; + if (!validTypes[input.cache_type]?.includes(input.proposal_type)) { + throw new Error( + `CHECK constraint failed: invalid combination cache_type='${input.cache_type}' proposal_type='${input.proposal_type}'`, + ); + } + + const payload = input.proposal_payload as Record; + if (input.proposal_type === 'threshold_adjust') { + const category = payload.category ?? null; + const dupe = this.db + .prepare( + `SELECT id FROM cache_proposals + WHERE connection_id = ? AND cache_name = ? AND proposal_type = 'threshold_adjust' + AND status = 'pending' + AND COALESCE(json_extract(proposal_payload, '$.category'), '__betterdb_null__') = ?`, + ) + .get(input.connection_id, input.cache_name, category ?? '__betterdb_null__'); + if (dupe) throw new Error('UNIQUE constraint failed: duplicate pending threshold_adjust'); + } + if (input.proposal_type === 'tool_ttl_adjust') { + const toolName = payload.tool_name ?? null; + const dupe = this.db + .prepare( + `SELECT id FROM cache_proposals + WHERE connection_id = ? AND cache_name = ? AND proposal_type = 'tool_ttl_adjust' + AND status = 'pending' + AND COALESCE(json_extract(proposal_payload, '$.tool_name'), '__betterdb_null__') = ?`, + ) + .get(input.connection_id, input.cache_name, toolName ?? '__betterdb_null__'); + if (dupe) throw new Error('UNIQUE constraint failed: duplicate pending tool_ttl_adjust'); + } + const proposedAt = input.proposed_at ?? Date.now(); const expiresAt = input.expires_at ?? proposedAt + PROPOSAL_DEFAULT_EXPIRY_MS; this.db