Skip to content

Commit fccaf40

Browse files
chitcommitclaude
andcommitted
fix: redirect phantom ledger API calls to correct services
ledgerClient was calling /api/evidence, /api/cases/*, and /api/evidence/*/custody — none of which exist on ChittyLedger. These endpoints belong to ChittyEvidence. - Rewrite ledgerClient to only expose real ChittyLedger endpoints: POST /entries, GET /entries, GET /custody/:id, GET /verify, GET /statistics - Add submitDocument and addCustodyEntry to evidenceClient - Update all callers (bridge, mcp, documents, timeline, dispute-sync) to use evidenceClient for evidence/case operations - Remove duplicate evidence fetch in timeline route - All operations include Bearer auth via CHITTYLEDGER_TOKEN Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 23cecc0 commit fccaf40

7 files changed

Lines changed: 170 additions & 175 deletions

File tree

src/lib/dispute-sync.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type { NeonQueryFunction } from '@neondatabase/serverless';
1616
import type { Env } from '../index';
1717
import { notionClient, routerClient, ledgerClient } from './integrations';
1818

19+
1920
// ── Types ─────────────────────────────────────────────────────
2021

2122
interface DisputeCore {
@@ -302,21 +303,27 @@ async function linkDisputeToLedger(
302303
const ledger = ledgerClient(env);
303304
if (!ledger) return;
304305

305-
const caseResult = await ledger.createCase({
306-
caseNumber: `CC-DISPUTE-${disputeId.slice(0, 8)}`,
307-
title: dispute.title,
308-
caseType: 'CIVIL',
309-
description: dispute.description || undefined,
306+
const entryResult = await ledger.addEntry({
307+
entityType: 'audit',
308+
entityId: `CC-DISPUTE-${disputeId.slice(0, 8)}`,
309+
action: 'dispute:created',
310+
actor: 'chittycommand',
311+
actorType: 'service',
312+
metadata: {
313+
title: dispute.title,
314+
description: dispute.description,
315+
caseType: 'CIVIL',
316+
},
310317
});
311318

312-
if (caseResult?.id) {
319+
if (entryResult?.id) {
313320
await sql`
314321
UPDATE cc_disputes
315-
SET metadata = COALESCE(metadata, '{}'::jsonb) || ${JSON.stringify({ ledger_case_id: caseResult.id })}::jsonb,
322+
SET metadata = COALESCE(metadata, '{}'::jsonb) || ${JSON.stringify({ ledger_case_id: entryResult.id })}::jsonb,
316323
updated_at = NOW()
317324
WHERE id = ${disputeId}
318325
`;
319-
console.log(`[dispute-sync:ledger] Linked dispute ${disputeId}case ${caseResult.id}`);
326+
console.log(`[dispute-sync:ledger] Linked dispute ${disputeId}ledger entry ${entryResult.id}`);
320327
}
321328
} catch (err) {
322329
console.error(`[dispute-sync:ledger] Failed for dispute ${disputeId}:`, err);

src/lib/integrations.ts

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,17 @@ import type { Env } from '../index';
77
*/
88

99
// ── ChittyLedger ─────────────────────────────────────────────
10-
// Evidence pipeline: documents → evidence, disputes → cases, actions → custody log
10+
// Audit trail: entries, chain verification, custody queries
11+
// NOTE: Evidence/case operations live on ChittyEvidence, not ChittyLedger.
1112

12-
export interface LedgerEvidencePayload {
13-
filename: string;
14-
fileType: string;
15-
fileSize?: string;
16-
description?: string;
17-
evidenceTier: string;
18-
caseId?: string;
19-
}
20-
21-
export interface LedgerCustodyEntry {
22-
evidenceId: string;
13+
export interface LedgerEntryPayload {
14+
entityType: 'transaction' | 'evidence' | 'custody' | 'audit';
15+
entityId?: string;
2316
action: string;
24-
performedBy: string;
25-
location?: string;
26-
notes?: string;
17+
actor?: string;
18+
actorType?: 'user' | 'service' | 'system';
19+
metadata?: Record<string, unknown>;
20+
status?: 'pending' | 'confirmed' | 'rejected';
2721
}
2822

2923
export function ledgerClient(env: Env) {
@@ -38,63 +32,55 @@ export function ledgerClient(env: Env) {
3832
headers['Authorization'] = `Bearer ${env.CHITTYLEDGER_TOKEN}`;
3933
}
4034

41-
async function post<T>(path: string, body: unknown): Promise<T | null> {
42-
try {
43-
const res = await fetch(`${baseUrl}${path}`, {
44-
method: 'POST',
45-
headers,
46-
body: JSON.stringify(body),
47-
});
48-
if (!res.ok) {
49-
console.error(`[ledger] ${path} failed: ${res.status}`);
50-
return null;
51-
}
52-
return await res.json() as T;
53-
} catch (err) {
54-
console.error(`[ledger] ${path} error:`, err);
55-
return null;
56-
}
57-
}
58-
5935
return {
60-
/** Push a document into ChittyLedger as evidence */
61-
createEvidence: (payload: LedgerEvidencePayload) =>
62-
post<{ id: string }>('/api/evidence', payload),
63-
64-
/** Record a chain-of-custody event */
65-
addCustodyEntry: (entry: LedgerCustodyEntry) =>
66-
post('/api/evidence/' + entry.evidenceId + '/custody', entry),
67-
68-
/** Create or link a case in ChittyLedger */
69-
createCase: (payload: { caseNumber: string; title: string; caseType: string; description?: string }) =>
70-
post<{ id: string }>('/api/cases', payload),
36+
/** Add an audit/custody/evidence entry to the ledger */
37+
addEntry: async (entry: LedgerEntryPayload): Promise<{ id: string; sequenceNumber: number; hash: string } | null> => {
38+
try {
39+
const res = await fetch(`${baseUrl}/entries`, {
40+
method: 'POST', headers, body: JSON.stringify(entry),
41+
});
42+
if (!res.ok) { console.error(`[ledger] POST /entries failed: ${res.status}`); return null; }
43+
return await res.json() as { id: string; sequenceNumber: number; hash: string };
44+
} catch (err) { console.error('[ledger] POST /entries error:', err); return null; }
45+
},
7146

72-
/** Get evidence by case */
73-
getEvidenceByCase: async (caseId: string): Promise<Record<string, unknown>[]> => {
47+
/** Search ledger entries */
48+
searchEntries: async (params: { entityType?: string; entityId?: string; actor?: string; status?: string; limit?: number }): Promise<Record<string, unknown>[]> => {
7449
try {
75-
const qs = new URLSearchParams({ caseId }).toString();
76-
const res = await fetch(`${baseUrl}/api/evidence?${qs}`, { headers });
50+
const qs = new URLSearchParams(
51+
Object.entries(params).filter(([, v]) => v !== undefined).map(([k, v]) => [k, String(v)] as [string, string])
52+
).toString();
53+
const res = await fetch(`${baseUrl}/entries${qs ? `?${qs}` : ''}`, { headers });
7754
if (!res.ok) return [];
7855
return await res.json() as Record<string, unknown>[];
7956
} catch { return []; }
8057
},
8158

82-
/** Get facts for a case (if supported) */
83-
getFactsForCase: async (caseId: string): Promise<Record<string, unknown>[]> => {
59+
/** Get chain of custody for an entity */
60+
getChainOfCustody: async (entityId: string): Promise<Record<string, unknown>[]> => {
8461
try {
85-
const res = await fetch(`${baseUrl}/api/cases/${encodeURIComponent(caseId)}/facts`, { headers });
62+
const res = await fetch(`${baseUrl}/custody/${encodeURIComponent(entityId)}`, { headers });
8663
if (!res.ok) return [];
8764
return await res.json() as Record<string, unknown>[];
8865
} catch { return []; }
8966
},
9067

91-
/** Get contradictions for a case (if supported) */
92-
getContradictionsForCase: async (caseId: string): Promise<Record<string, unknown>[]> => {
68+
/** Verify ledger chain integrity */
69+
verifyChain: async (): Promise<{ valid: boolean; errors: string[] } | null> => {
9370
try {
94-
const res = await fetch(`${baseUrl}/api/cases/${encodeURIComponent(caseId)}/contradictions`, { headers });
95-
if (!res.ok) return [];
96-
return await res.json() as Record<string, unknown>[];
97-
} catch { return []; }
71+
const res = await fetch(`${baseUrl}/verify`, { headers });
72+
if (!res.ok) return null;
73+
return await res.json() as { valid: boolean; errors: string[] };
74+
} catch { return null; }
75+
},
76+
77+
/** Get ledger statistics */
78+
getStatistics: async (): Promise<Record<string, unknown> | null> => {
79+
try {
80+
const res = await fetch(`${baseUrl}/statistics`, { headers });
81+
if (!res.ok) return null;
82+
return await res.json() as Record<string, unknown>;
83+
} catch { return null; }
9884
},
9985
};
10086
}
@@ -132,7 +118,7 @@ export function evidenceClient(env: Env) {
132118
const baseUrl = env.CHITTYEVIDENCE_URL;
133119
if (!baseUrl) return null;
134120

135-
const headers = { 'X-Source-Service': 'chittycommand' };
121+
const headers: Record<string, string> = { 'X-Source-Service': 'chittycommand' };
136122

137123
async function get<T>(path: string): Promise<T | null> {
138124
try {
@@ -145,7 +131,35 @@ export function evidenceClient(env: Env) {
145131
}
146132
}
147133

134+
async function post<T>(path: string, body: unknown): Promise<T | null> {
135+
try {
136+
const res = await fetch(`${baseUrl}${path}`, {
137+
method: 'POST',
138+
headers: { ...headers, 'Content-Type': 'application/json' },
139+
body: JSON.stringify(body),
140+
});
141+
if (!res.ok) { console.error(`[evidence] POST ${path} failed: ${res.status}`); return null; }
142+
return await res.json() as T;
143+
} catch (err) {
144+
console.error(`[evidence] POST ${path} error:`, err);
145+
return null;
146+
}
147+
}
148+
148149
return {
150+
/** Submit a document to the evidence pipeline */
151+
submitDocument: (payload: { filename: string; fileType: string; fileSize?: string; description?: string; evidenceTier?: string; caseId?: string }) =>
152+
post<{ id: string; submission_id?: string }>('/collect', {
153+
file_name: payload.filename,
154+
document_type: payload.fileType,
155+
description: payload.description,
156+
case_id: payload.caseId,
157+
}),
158+
159+
/** Record a chain-of-custody event on a document */
160+
addCustodyEntry: (documentId: string, entry: { action: string; performedBy: string; location?: string; notes?: string }) =>
161+
post<Record<string, unknown>>(`/legal/documents/${encodeURIComponent(documentId)}/custody`, entry),
162+
149163
/** Get enriched facts for a case (facts + entities + amounts) */
150164
getEnrichedFacts: (caseId: string) =>
151165
get<EvidenceFact[]>(`/facts/cases/${encodeURIComponent(caseId)}/enriched`),

src/routes/bridge/ledger.ts

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@ import { Hono } from 'hono';
22
import type { Env } from '../../index';
33
import type { AuthVariables } from '../../middleware/auth';
44
import { getDb } from '../../lib/db';
5-
import { ledgerClient } from '../../lib/integrations';
5+
import { evidenceClient, ledgerClient } from '../../lib/integrations';
66
import { recordActionSchema } from '../../lib/validators';
77

88
export const ledgerBridgeRoutes = new Hono<{ Bindings: Env; Variables: AuthVariables }>();
99

10-
// ── ChittyLedger Sync ────────────────────────────────────────
10+
// ── Evidence Sync (via ChittyEvidence) ────────────────────────
1111

12-
/** Push all unsynced documents to ChittyLedger as evidence */
12+
/** Push all unsynced documents to ChittyEvidence pipeline */
1313
ledgerBridgeRoutes.post('/sync-documents', async (c) => {
14-
const ledger = ledgerClient(c.env);
15-
if (!ledger) return c.json({ error: 'ChittyLedger not configured' }, 503);
14+
const evidence = evidenceClient(c.env);
15+
if (!evidence) return c.json({ error: 'ChittyEvidence not configured' }, 503);
1616

1717
const sql = getDb(c.env);
1818
const unsynced = await sql`
@@ -25,28 +25,40 @@ ledgerBridgeRoutes.post('/sync-documents', async (c) => {
2525

2626
let synced = 0;
2727
for (const doc of unsynced) {
28-
const evidence = await ledger.createEvidence({
28+
const result = await evidence.submitDocument({
2929
filename: doc.filename || 'unknown',
3030
fileType: doc.doc_type || 'upload',
3131
description: `Uploaded via ChittyCommand: ${doc.filename}`,
3232
evidenceTier: 'BUSINESS_RECORDS',
3333
});
3434

35-
if (evidence?.id) {
35+
if (result?.id) {
3636
await sql`
3737
UPDATE cc_documents SET
38-
metadata = COALESCE(metadata, '{}'::jsonb) || ${JSON.stringify({ ledger_evidence_id: evidence.id })}::jsonb,
38+
metadata = COALESCE(metadata, '{}'::jsonb) || ${JSON.stringify({ ledger_evidence_id: result.id })}::jsonb,
3939
processing_status = 'synced'
4040
WHERE id = ${doc.id}
4141
`;
4242
synced++;
4343
}
4444
}
4545

46-
return c.json({ total: unsynced.length, synced, message: `Synced ${synced} documents to ChittyLedger` });
46+
// Also log the sync event to ChittyLedger audit trail
47+
const ledger = ledgerClient(c.env);
48+
if (ledger && synced > 0) {
49+
ledger.addEntry({
50+
entityType: 'audit',
51+
action: 'evidence:sync-documents',
52+
actor: 'chittycommand',
53+
actorType: 'service',
54+
metadata: { total: unsynced.length, synced },
55+
}).catch(() => {});
56+
}
57+
58+
return c.json({ total: unsynced.length, synced, message: `Synced ${synced} documents to ChittyEvidence` });
4759
});
4860

49-
/** Push disputes to ChittyLedger as cases */
61+
/** Push disputes to ChittyLedger as audit entries */
5062
ledgerBridgeRoutes.post('/sync-disputes', async (c) => {
5163
const ledger = ledgerClient(c.env);
5264
if (!ledger) return c.json({ error: 'ChittyLedger not configured' }, 503);
@@ -60,17 +72,23 @@ ledgerBridgeRoutes.post('/sync-disputes', async (c) => {
6072

6173
let synced = 0;
6274
for (const dispute of unsynced) {
63-
const caseResult = await ledger.createCase({
64-
caseNumber: `CC-DISPUTE-${(dispute.id as string).slice(0, 8)}`,
65-
title: dispute.title as string,
66-
caseType: 'CIVIL',
67-
description: dispute.description as string || undefined,
75+
const entryResult = await ledger.addEntry({
76+
entityType: 'audit',
77+
entityId: `CC-DISPUTE-${(dispute.id as string).slice(0, 8)}`,
78+
action: 'dispute:created',
79+
actor: 'chittycommand',
80+
actorType: 'service',
81+
metadata: {
82+
title: dispute.title,
83+
description: dispute.description,
84+
caseType: 'CIVIL',
85+
},
6886
});
6987

70-
if (caseResult?.id) {
88+
if (entryResult?.id) {
7189
await sql`
7290
UPDATE cc_disputes SET
73-
metadata = COALESCE(metadata, '{}'::jsonb) || ${JSON.stringify({ ledger_case_id: caseResult.id })}::jsonb
91+
metadata = COALESCE(metadata, '{}'::jsonb) || ${JSON.stringify({ ledger_case_id: entryResult.id })}::jsonb
7492
WHERE id = ${dispute.id}
7593
`;
7694
synced++;
@@ -80,17 +98,16 @@ ledgerBridgeRoutes.post('/sync-disputes', async (c) => {
8098
return c.json({ total: unsynced.length, synced, message: `Synced ${synced} disputes to ChittyLedger` });
8199
});
82100

83-
/** Record an action in ChittyLedger chain of custody */
101+
/** Record an action in ChittyEvidence chain of custody */
84102
ledgerBridgeRoutes.post('/record-action', async (c) => {
85-
const ledger = ledgerClient(c.env);
86-
if (!ledger) return c.json({ error: 'ChittyLedger not configured' }, 503);
103+
const evidence = evidenceClient(c.env);
104+
if (!evidence) return c.json({ error: 'ChittyEvidence not configured' }, 503);
87105

88106
const parsed = recordActionSchema.safeParse(await c.req.json());
89107
if (!parsed.success) return c.json({ error: 'Invalid request', details: parsed.error.issues }, 400);
90108

91109
const body = parsed.data;
92-
const result = await ledger.addCustodyEntry({
93-
evidenceId: body.evidence_id,
110+
const result = await evidence.addCustodyEntry(body.evidence_id, {
94111
action: body.action,
95112
performedBy: 'chittycommand',
96113
location: 'ChittyCommand Dashboard',

src/routes/documents.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Hono } from 'hono';
22
import type { Env } from '../index';
33
import { getDb } from '../lib/db';
4-
import { ledgerClient } from '../lib/integrations';
4+
import { evidenceClient } from '../lib/integrations';
55

66
export const documentRoutes = new Hono<{ Bindings: Env }>();
77
const UUID_V4ISH = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -56,10 +56,10 @@ documentRoutes.post('/upload', async (c) => {
5656
RETURNING *
5757
`;
5858

59-
// Fire-and-forget: push to ChittyLedger evidence pipeline
60-
const ledger = ledgerClient(c.env);
61-
if (ledger) {
62-
ledger.createEvidence({
59+
// Fire-and-forget: push to ChittyEvidence pipeline
60+
const evidence = evidenceClient(c.env);
61+
if (evidence) {
62+
evidence.submitDocument({
6363
filename: safeName,
6464
fileType: file.type,
6565
fileSize: String(file.size),

0 commit comments

Comments
 (0)