From b25fee58014311d51e98fceafd90e564119a8ff0 Mon Sep 17 00:00:00 2001 From: chitcommit <208086304+chitcommit@users.noreply.github.com> Date: Wed, 25 Mar 2026 23:41:48 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20address=20PR=20review=20=E2=80=94=20erro?= =?UTF-8?q?r=20logging,=20evidence=20auth,=20partial=20data=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add console.error/warn to all empty catch blocks in ledgerClient, connectClient.discover, and MCP connect_discover handler - Add CHITTYEVIDENCE_TOKEN to Env type and evidence client auth headers - Return error field (not empty data) from ledger_facts/contradictions when ChittyEvidence is unconfigured - Wrap get_case_timeline DB queries in try-catch for partial data collection - Add warnings/partial fields to timeline response when data sources fail - Fix stale test description (38 → 48 tools) Co-Authored-By: Claude Opus 4.6 --- src/index.ts | 1 + src/lib/integrations.ts | 31 ++++++++++++++------------ src/routes/mcp.ts | 48 ++++++++++++++++++++++------------------- src/routes/timeline.ts | 5 +++++ tests/mcp.test.ts | 2 +- 5 files changed, 50 insertions(+), 37 deletions(-) diff --git a/src/index.ts b/src/index.ts index fd39863..7947184 100644 --- a/src/index.ts +++ b/src/index.ts @@ -54,6 +54,7 @@ export type Env = { CHITTYSCHEMA_URL?: string; CHITTYCERT_URL?: string; CHITTYEVIDENCE_URL?: string; + CHITTYEVIDENCE_TOKEN?: string; CHITTY_CONNECT_TOKEN?: string; CHITTYLEDGER_TOKEN?: string; PLAID_CLIENT_ID?: string; diff --git a/src/lib/integrations.ts b/src/lib/integrations.ts index 94c5d29..bbc315f 100644 --- a/src/lib/integrations.ts +++ b/src/lib/integrations.ts @@ -51,36 +51,36 @@ export function ledgerClient(env: Env) { Object.entries(params).filter(([, v]) => v !== undefined).map(([k, v]) => [k, String(v)] as [string, string]) ).toString(); const res = await fetch(`${baseUrl}/entries${qs ? `?${qs}` : ''}`, { headers }); - if (!res.ok) return []; + if (!res.ok) { console.error(`[ledger] GET /entries failed: ${res.status}`); return []; } return await res.json() as Record[]; - } catch { return []; } + } catch (err) { console.error('[ledger] GET /entries error:', err); return []; } }, /** Get chain of custody for an entity */ getChainOfCustody: async (entityId: string): Promise[]> => { try { const res = await fetch(`${baseUrl}/custody/${encodeURIComponent(entityId)}`, { headers }); - if (!res.ok) return []; + if (!res.ok) { console.error(`[ledger] GET /custody failed: ${res.status}`); return []; } return await res.json() as Record[]; - } catch { return []; } + } catch (err) { console.error('[ledger] GET /custody error:', err); return []; } }, /** Verify ledger chain integrity */ verifyChain: async (): Promise<{ valid: boolean; errors: string[] } | null> => { try { const res = await fetch(`${baseUrl}/verify`, { headers }); - if (!res.ok) return null; + if (!res.ok) { console.error(`[ledger] GET /verify failed: ${res.status}`); return null; } return await res.json() as { valid: boolean; errors: string[] }; - } catch { return null; } + } catch (err) { console.error('[ledger] GET /verify error:', err); return null; } }, /** Get ledger statistics */ getStatistics: async (): Promise | null> => { try { const res = await fetch(`${baseUrl}/statistics`, { headers }); - if (!res.ok) return null; + if (!res.ok) { console.error(`[ledger] GET /statistics failed: ${res.status}`); return null; } return await res.json() as Record; - } catch { return null; } + } catch (err) { console.error('[ledger] GET /statistics error:', err); return null; } }, }; } @@ -119,11 +119,14 @@ export function evidenceClient(env: Env) { if (!baseUrl) return null; const headers: Record = { 'X-Source-Service': 'chittycommand' }; + if (env.CHITTYEVIDENCE_TOKEN) { + headers['Authorization'] = `Bearer ${env.CHITTYEVIDENCE_TOKEN}`; + } async function get(path: string): Promise { try { const res = await fetch(`${baseUrl}${path}`, { headers }); - if (!res.ok) return null; + if (!res.ok) { console.error(`[evidence] GET ${path} failed: ${res.status}`); return null; } return await res.json() as T; } catch (err) { console.error(`[evidence] ${path} error:`, err); @@ -458,20 +461,20 @@ export function connectClient(env: Env) { try { const obj = JSON.parse(cached) as { url?: string }; if (obj?.url) return obj.url; - } catch { /* ignore */ } + } catch (err) { console.warn('[connect/discover] KV cache parse failed:', err); } } - } catch { /* ignore */ } + } catch (err) { console.warn('[connect/discover] KV read failed:', err); } try { const res = await fetch(`${baseUrl}/api/discover/${encodeURIComponent(serviceName)}`, { headers: { 'X-Source-Service': 'chittycommand' }, }); - if (!res.ok) return null; + if (!res.ok) { console.error(`[connect/discover] ${serviceName} failed: ${res.status}`); return null; } const data = await res.json() as { url: string }; // Store in KV with TTL - try { await env.COMMAND_KV.put(key, JSON.stringify({ url: data.url }), { expirationTtl: 300 }); } catch { /* ignore */ } + try { await env.COMMAND_KV.put(key, JSON.stringify({ url: data.url }), { expirationTtl: 300 }); } catch (err) { console.warn('[connect/discover] KV write failed:', err); } return data.url; - } catch { return null; } + } catch (err) { console.error('[connect/discover] fetch error:', err); return null; } }, // ── Prompt Registry (ContextConsciousness) ───────────────── diff --git a/src/routes/mcp.ts b/src/routes/mcp.ts index 3b3f9cb..f350b4b 100644 --- a/src/routes/mcp.ts +++ b/src/routes/mcp.ts @@ -621,7 +621,7 @@ async function executeTool(env: Env, sql: NeonQueryFunction, toolN const caseId = String(args.case_id || '').trim(); if (!caseId) throw new Error('Missing argument: case_id'); const ev = evidenceClient(env); - if (!ev) return { facts: [] }; + if (!ev) return { error: 'ChittyEvidence not configured', facts: [] }; const facts = await ev.getStatementOfFacts(caseId); return { case_id: caseId, facts: facts || [] }; } @@ -630,7 +630,7 @@ async function executeTool(env: Env, sql: NeonQueryFunction, toolN const caseId = String(args.case_id || '').trim(); if (!caseId) throw new Error('Missing argument: case_id'); const ev = evidenceClient(env); - if (!ev) return { contradictions: [] }; + if (!ev) return { error: 'ChittyEvidence not configured', contradictions: [] }; const contradictions = await ev.getContradictions(caseId); return { case_id: caseId, contradictions: contradictions || [] }; } @@ -798,9 +798,9 @@ async function executeTool(env: Env, sql: NeonQueryFunction, toolN try { const cached = await env.COMMAND_KV.get(key); if (cached) { - try { const obj = JSON.parse(cached) as { url?: string }; if (obj?.url) return { service, url: obj.url, cached: true }; } catch {} + try { const obj = JSON.parse(cached) as { url?: string }; if (obj?.url) return { service, url: obj.url, cached: true }; } catch (err) { console.warn('[mcp/connect_discover] KV cache parse failed:', err); } } - } catch {} + } catch (err) { console.warn('[mcp/connect_discover] KV read failed:', err); } try { const res = await fetch(`${baseUrl}/api/discover/${encodeURIComponent(service)}`, { headers: { 'X-Source-Service': 'chittycommand' }, @@ -808,7 +808,7 @@ async function executeTool(env: Env, sql: NeonQueryFunction, toolN }); if (!res.ok) return { error: `Service not found`, code: res.status }; const data = await res.json() as { url: string }; - try { await env.COMMAND_KV.put(key, JSON.stringify({ url: data.url }), { expirationTtl: 300 }); } catch {} + try { await env.COMMAND_KV.put(key, JSON.stringify({ url: data.url }), { expirationTtl: 300 }); } catch (err) { console.warn('[mcp/connect_discover] KV write failed:', err); } return { service, url: data.url }; } catch (err) { return { error: String(err) }; @@ -1228,25 +1228,29 @@ async function executeTool(env: Env, sql: NeonQueryFunction, toolN } // Deadlines from DB (with date filtering) - let deadlines; - if (startDate && endDate) { - deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date >= ${startDate} AND deadline_date <= ${endDate} ORDER BY deadline_date ASC`; - } else if (startDate) { - deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date >= ${startDate} ORDER BY deadline_date ASC`; - } else if (endDate) { - deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date <= ${endDate} ORDER BY deadline_date ASC`; - } else { - deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} ORDER BY deadline_date ASC`; - } - for (const d of deadlines) { - events.push({ id: `deadline:${d.id}`, date: d.deadline_date, type: 'deadline', title: d.title, deadlineType: d.deadline_type, status: d.status }); - } + try { + let deadlines; + if (startDate && endDate) { + deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date >= ${startDate} AND deadline_date <= ${endDate} ORDER BY deadline_date ASC`; + } else if (startDate) { + deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date >= ${startDate} ORDER BY deadline_date ASC`; + } else if (endDate) { + deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date <= ${endDate} ORDER BY deadline_date ASC`; + } else { + deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} ORDER BY deadline_date ASC`; + } + for (const d of deadlines) { + events.push({ id: `deadline:${d.id}`, date: d.deadline_date, type: 'deadline', title: d.title, deadlineType: d.deadline_type, status: d.status }); + } + } catch (err) { console.error('[mcp/get_case_timeline] deadlines query error:', err); } // Disputes from DB - const disputes = await sql`SELECT id, title, status, dispute_type, created_at FROM cc_disputes WHERE metadata->>'case_ref' = ${caseId} OR metadata->>'ledger_case_id' = ${caseId}`; - for (const d of disputes) { - events.push({ id: `dispute:${d.id}`, date: d.created_at, type: 'dispute', title: d.title, status: d.status, disputeType: d.dispute_type }); - } + try { + const disputes = await sql`SELECT id, title, status, dispute_type, created_at FROM cc_disputes WHERE metadata->>'case_ref' = ${caseId} OR metadata->>'ledger_case_id' = ${caseId}`; + for (const d of disputes) { + events.push({ id: `dispute:${d.id}`, date: d.created_at, type: 'dispute', title: d.title, status: d.status, disputeType: d.dispute_type }); + } + } catch (err) { console.error('[mcp/get_case_timeline] disputes query error:', err); } // Documents already covered by ChittyEvidence facts above diff --git a/src/routes/timeline.ts b/src/routes/timeline.ts index 7f3983a..41f29cf 100644 --- a/src/routes/timeline.ts +++ b/src/routes/timeline.ts @@ -23,6 +23,7 @@ timelineRoutes.get('/cases/:caseId/timeline', async (c) => { const endDate = c.req.query('end'); const events: TimelineEvent[] = []; + const warnings: string[] = []; // 1. Fetch enriched facts from ChittyEvidence const evidence = evidenceClient(c.env); @@ -55,6 +56,7 @@ timelineRoutes.get('/cases/:caseId/timeline', async (c) => { } } catch (err) { console.error('[timeline] evidence facts error:', err); + warnings.push('Evidence facts unavailable: service error'); } } @@ -86,6 +88,7 @@ timelineRoutes.get('/cases/:caseId/timeline', async (c) => { } } catch (err) { console.error('[timeline] deadlines error:', err); + warnings.push('Legal deadlines unavailable: database error'); } // 3. Fetch dispute milestones @@ -114,6 +117,7 @@ timelineRoutes.get('/cases/:caseId/timeline', async (c) => { } } catch (err) { console.error('[timeline] disputes error:', err); + warnings.push('Dispute milestones unavailable: database error'); } // 4. Evidence documents already fetched in section 1 via evidenceClient @@ -135,6 +139,7 @@ timelineRoutes.get('/cases/:caseId/timeline', async (c) => { documents: events.filter(e => e.type === 'document').length, }, events, + ...(warnings.length > 0 ? { warnings, partial: true } : {}), }); }); diff --git a/tests/mcp.test.ts b/tests/mcp.test.ts index aae586b..1f536b2 100644 --- a/tests/mcp.test.ts +++ b/tests/mcp.test.ts @@ -141,7 +141,7 @@ describe('MCP — tools/list', () => { expect(tools.length).toBeGreaterThanOrEqual(1); }); - it('exposes exactly 38 tools', async () => { + it('exposes exactly 48 tools', async () => { const { post } = buildApp(); const res = await post({ jsonrpc: '2.0', id: 1, method: 'tools/list' }); const json = await res.json() as Record;