Skip to content

Commit 4f7aa6b

Browse files
chitcommitclaude
andcommitted
fix: add missing source modules (meta, beacon, context, ledger)
These files were present locally but not included in the original PR commit, causing tsc --noEmit to fail in CI with TS2307 errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e1388cd commit 4f7aa6b

4 files changed

Lines changed: 283 additions & 0 deletions

File tree

src/lib/beacon.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { Env } from '../index';
2+
3+
export async function sendBeacon(env: Env) {
4+
try {
5+
if (!env.CHITTYREGISTER_URL) return;
6+
const url = `${env.CHITTYREGISTER_URL.replace(/\/$/, '')}/v1/beacon`;
7+
const payload = {
8+
name: 'ChittyCommand',
9+
version: '0.1.0',
10+
environment: env.ENVIRONMENT || 'production',
11+
canonicalUri: 'chittycanon://core/services/chittycommand',
12+
timestamp: new Date().toISOString(),
13+
};
14+
const headers: Record<string, string> = { 'Content-Type': 'application/json', 'X-Source-Service': 'chittycommand' };
15+
if (env.CHITTY_CONNECT_TOKEN) headers['Authorization'] = `Bearer ${env.CHITTY_CONNECT_TOKEN}`;
16+
const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify(payload) });
17+
const ok = res.ok;
18+
const now = new Date().toISOString();
19+
await env.COMMAND_KV.put('register:last_beacon_at', now);
20+
await env.COMMAND_KV.put('register:last_beacon_status', ok ? 'ok' : `http_${res.status}`);
21+
22+
// Also emit beacon to ChittyConnect if configured (topology tracking)
23+
if (env.CHITTYCONNECT_URL) {
24+
try {
25+
const cu = `${env.CHITTYCONNECT_URL.replace(/\/$/, '')}/v1/beacon`;
26+
const ch = { 'Content-Type': 'application/json', 'X-Source-Service': 'chittycommand' } as Record<string, string>;
27+
if (env.CHITTY_CONNECT_TOKEN) ch['Authorization'] = `Bearer ${env.CHITTY_CONNECT_TOKEN}`;
28+
const cres = await fetch(cu, { method: 'POST', headers: ch, body: JSON.stringify(payload) });
29+
await env.COMMAND_KV.put('connect:last_beacon_at', now);
30+
await env.COMMAND_KV.put('connect:last_beacon_status', cres.ok ? 'ok' : `http_${cres.status}`);
31+
} catch {
32+
await env.COMMAND_KV.put('connect:last_beacon_status', 'error');
33+
}
34+
}
35+
} catch (e) {
36+
// Best-effort; record failure without throwing
37+
await env.COMMAND_KV.put('register:last_beacon_status', 'error');
38+
}
39+
}

src/routes/context.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Hono } from 'hono';
2+
import type { Env } from '../index';
3+
import { z } from 'zod';
4+
import { contextUpdateSchema } from '../lib/validators';
5+
6+
export const contextRoutes = new Hono<{ Bindings: Env }>();
7+
8+
type StoredContext = {
9+
label?: string | null;
10+
persona?: string | null;
11+
tags?: string[];
12+
updated_at: string;
13+
};
14+
15+
function ensureScopes(scopes: unknown, required: string[]): boolean {
16+
if (!Array.isArray(scopes)) return false;
17+
return required.some((r) => (scopes as string[]).includes(r));
18+
}
19+
20+
// Get current user's context
21+
contextRoutes.get('/context', async (c) => {
22+
// @ts-expect-error app-level variables
23+
const userId = c.get('userId') as string | undefined;
24+
if (!userId) return c.json({ error: 'Unauthorized' }, 401);
25+
const key = `context:user:${userId}`;
26+
const raw = await c.env.COMMAND_KV.get(key);
27+
const payload: StoredContext | null = raw ? JSON.parse(raw) : null;
28+
return c.json({ userId, ...(payload || { label: null, persona: null, tags: [], updated_at: null }) });
29+
});
30+
31+
// Update current user's context (admin or mcp permitted)
32+
contextRoutes.post('/context', async (c) => {
33+
// @ts-expect-error app-level variables
34+
const userId = c.get('userId') as string | undefined;
35+
// @ts-expect-error app-level variables
36+
const scopes = (c.get('scopes') as string[] | undefined) || [];
37+
if (!userId) return c.json({ error: 'Unauthorized' }, 401);
38+
if (!ensureScopes(scopes, ['admin', 'mcp'])) {
39+
return c.json({ error: 'Forbidden' }, 403);
40+
}
41+
const body = await c.req.json().catch(() => ({}));
42+
const parsed = contextUpdateSchema.safeParse(body);
43+
if (!parsed.success) {
44+
return c.json({ error: 'Invalid payload', issues: parsed.error.issues }, 400);
45+
}
46+
const now = new Date().toISOString();
47+
const key = `context:user:${userId}`;
48+
const currentRaw = await c.env.COMMAND_KV.get(key);
49+
const current: StoredContext = currentRaw ? JSON.parse(currentRaw) : { updated_at: now };
50+
const next: StoredContext = {
51+
label: parsed.data.label ?? current.label ?? null,
52+
persona: parsed.data.persona ?? current.persona ?? null,
53+
tags: parsed.data.tags ?? current.tags ?? [],
54+
updated_at: now,
55+
};
56+
await c.env.COMMAND_KV.put(key, JSON.stringify(next));
57+
return c.json({ userId, ...next });
58+
});
59+
60+
// Global context (admin only) for shared clients like MCP
61+
contextRoutes.get('/context/global', async (c) => {
62+
const raw = await c.env.COMMAND_KV.get('context:global');
63+
const payload: StoredContext | null = raw ? JSON.parse(raw) : null;
64+
return c.json({ scope: 'global', ...(payload || { label: null, persona: null, tags: [], updated_at: null }) });
65+
});
66+
67+
contextRoutes.post('/context/global', async (c) => {
68+
// @ts-expect-error app-level variables
69+
const scopes = (c.get('scopes') as string[] | undefined) || [];
70+
if (!ensureScopes(scopes, ['admin'])) return c.json({ error: 'Forbidden' }, 403);
71+
const body = await c.req.json().catch(() => ({}));
72+
const parsed = contextUpdateSchema.safeParse(body);
73+
if (!parsed.success) {
74+
return c.json({ error: 'Invalid payload', issues: parsed.error.issues }, 400);
75+
}
76+
const now = new Date().toISOString();
77+
const currentRaw = await c.env.COMMAND_KV.get('context:global');
78+
const current: StoredContext = currentRaw ? JSON.parse(currentRaw) : { updated_at: now };
79+
const next: StoredContext = {
80+
label: parsed.data.label ?? current.label ?? null,
81+
persona: parsed.data.persona ?? current.persona ?? null,
82+
tags: parsed.data.tags ?? current.tags ?? [],
83+
updated_at: now,
84+
};
85+
await c.env.COMMAND_KV.put('context:global', JSON.stringify(next));
86+
return c.json({ scope: 'global', ...next });
87+
});

src/routes/ledger.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Hono } from 'hono';
2+
import type { Env } from '../index';
3+
import { ledgerClient } from '../lib/integrations';
4+
5+
export const ledgerRoutes = new Hono<{ Bindings: Env }>();
6+
7+
// GET /api/v1/ledger/evidence?case_id=...
8+
ledgerRoutes.get('/ledger/evidence', async (c) => {
9+
const caseId = c.req.query('case_id');
10+
if (!caseId) return c.json({ error: 'Missing query param: case_id' }, 400);
11+
const ledger = ledgerClient(c.env);
12+
if (!ledger) return c.json({ error: 'ChittyLedger not configured' }, 503);
13+
const items = await ledger.getEvidenceByCase(caseId);
14+
return c.json({ case_id: caseId, evidence: items });
15+
});
16+
17+
// POST /api/v1/ledger/record-custody { evidence_id, action, notes? }
18+
ledgerRoutes.post('/ledger/record-custody', async (c) => {
19+
const body = await c.req.json().catch(() => ({}));
20+
const evidenceId = String((body?.evidence_id ?? '')).trim();
21+
const action = String((body?.action ?? '')).trim();
22+
const notes = (body?.notes as string | undefined) || undefined;
23+
if (!evidenceId || !action) return c.json({ error: 'Missing fields: evidence_id, action' }, 400);
24+
// @ts-expect-error app-level vars
25+
const userId = (c.get('userId') as string | undefined) || 'api-client';
26+
const ledger = ledgerClient(c.env);
27+
if (!ledger) return c.json({ error: 'ChittyLedger not configured' }, 503);
28+
const result = await ledger.addCustodyEntry({ evidenceId, action, performedBy: userId, notes });
29+
return c.json({ ok: !!result, result });
30+
});
31+

src/routes/meta.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { Hono } from 'hono';
2+
import type { ContentfulStatusCode } from 'hono/utils/http-status';
3+
import type { Env } from '../index';
4+
5+
export const metaPublicRoutes = new Hono<{ Bindings: Env }>();
6+
export const metaRoutes = new Hono<{ Bindings: Env }>();
7+
8+
// Public: Canon info and lightweight schema refs
9+
metaPublicRoutes.get('/canon', async (c) => {
10+
const env = c.env;
11+
const canonicalUri = 'chittycanon://core/services/chittycommand';
12+
const serviceId = await env.COMMAND_KV.get('register:service_id');
13+
const lastBeaconAt = await env.COMMAND_KV.get('register:last_beacon_at');
14+
const lastBeaconStatus = await env.COMMAND_KV.get('register:last_beacon_status');
15+
return c.json({
16+
name: 'ChittyCommand',
17+
version: '0.1.0',
18+
environment: env.ENVIRONMENT || 'production',
19+
canonicalUri,
20+
namespace: 'chittycanon://core/services',
21+
tier: 5,
22+
registered_with: env.CHITTYREGISTER_URL || null,
23+
registration: { service_id: serviceId || null, last_beacon_at: lastBeaconAt || null, last_status: lastBeaconStatus || null },
24+
});
25+
});
26+
27+
metaPublicRoutes.get('/schema', (c) => {
28+
// Lightweight schema refs + canonical links (ChittySchema)
29+
return c.json({
30+
schemaVersion: '0.1.0',
31+
generatedAt: new Date().toISOString(),
32+
endpoints: [
33+
'/api/dashboard',
34+
'/api/accounts',
35+
'/api/obligations',
36+
'/api/disputes',
37+
'/api/recommendations',
38+
'/api/cashflow',
39+
],
40+
db_tables: [
41+
'cc_accounts',
42+
'cc_obligations',
43+
'cc_transactions',
44+
'cc_recommendations',
45+
'cc_cashflow_projections',
46+
'cc_disputes',
47+
'cc_dispute_correspondence',
48+
'cc_legal_deadlines',
49+
'cc_documents',
50+
'cc_actions_log',
51+
'cc_sync_log',
52+
'cc_properties',
53+
],
54+
canonicalRefs: {
55+
chittyschema_base: 'https://schema.chitty.cc/api/v1',
56+
list_schemas: 'https://schema.chitty.cc/api/v1/schemas',
57+
get_schema: 'https://schema.chitty.cc/api/v1/schemas/{type}',
58+
validate: 'https://schema.chitty.cc/api/v1/validate',
59+
drift: 'https://schema.chitty.cc/api/v1/drift',
60+
catalog_repo: 'https://github.com/chittyfoundation/chittyschema',
61+
},
62+
notes: 'Zod schemas are used server-side; canonical JSON Schemas are governed by chittyfoundation/chittyschema.',
63+
});
64+
});
65+
66+
metaPublicRoutes.get('/beacon', async (c) => {
67+
const regAt = await c.env.COMMAND_KV.get('register:last_beacon_at');
68+
const regSt = await c.env.COMMAND_KV.get('register:last_beacon_status');
69+
const conAt = await c.env.COMMAND_KV.get('connect:last_beacon_at');
70+
const conSt = await c.env.COMMAND_KV.get('connect:last_beacon_status');
71+
return c.json({
72+
register: { last_beacon_at: regAt || null, last_status: regSt || null },
73+
connect: { last_beacon_at: conAt || null, last_status: conSt || null },
74+
});
75+
});
76+
77+
// Certificate verification passthrough (public)
78+
metaPublicRoutes.post('/cert/verify', async (c) => {
79+
try {
80+
const body = await c.req.json().catch(() => ({}));
81+
const certificateId = String(body?.certificate_id || '').trim();
82+
if (!certificateId) return c.json({ error: 'Missing field: certificate_id' }, 400);
83+
const base = c.env.CHITTYCERT_URL || 'https://cert.chitty.cc';
84+
const res = await fetch(`${base}/api/v1/verify`, {
85+
method: 'POST',
86+
headers: { 'Content-Type': 'application/json', 'X-Source-Service': 'chittycommand' },
87+
body: JSON.stringify({ certificate_id: certificateId }),
88+
});
89+
const out = await res.json().catch(() => ({}));
90+
if (!res.ok) return c.json({ valid: false, result: out, code: res.status }, res.status as ContentfulStatusCode);
91+
return c.json(out);
92+
} catch (err) {
93+
return c.json({ error: String(err) }, 500);
94+
}
95+
});
96+
97+
// Certificate fetch (public)
98+
metaPublicRoutes.get('/cert/:id', async (c) => {
99+
const id = c.req.param('id');
100+
if (!id) return c.json({ error: 'Missing certificate id' }, 400);
101+
try {
102+
const base = c.env.CHITTYCERT_URL || 'https://cert.chitty.cc';
103+
const res = await fetch(`${base}/api/v1/certificate/${encodeURIComponent(id)}`);
104+
const out = await res.json().catch(() => ({}));
105+
if (!res.ok) return c.json({ error: 'Not found', code: res.status, result: out }, res.status as ContentfulStatusCode);
106+
return c.json(out);
107+
} catch (err) {
108+
return c.json({ error: String(err) }, 500);
109+
}
110+
});
111+
112+
// Authenticated: identity resolution
113+
metaRoutes.get('/whoami', (c) => {
114+
// authMiddleware populates userId/scopes into variables
115+
// @ts-expect-error hono types for Variables are on app-level
116+
const userId = c.get('userId') as string | undefined;
117+
// @ts-expect-error see above
118+
const scopes = (c.get('scopes') as string[] | undefined) || [];
119+
if (!userId) return c.json({ error: 'Unauthorized' }, 401);
120+
return c.json({
121+
userId,
122+
subjectUri: `chittyid://users/${userId}`,
123+
scopes,
124+
environment: c.env.ENVIRONMENT || 'production',
125+
});
126+
});

0 commit comments

Comments
 (0)