diff --git a/src/gateway.ts b/src/gateway.ts index 81f2a2f..5c5f2b3 100644 --- a/src/gateway.ts +++ b/src/gateway.ts @@ -236,24 +236,35 @@ async function proxyRestToolCall( return { content: [{ type: 'text', text: `Receipt lookup failed: HTTP ${response.status}` }], isError: true }; } - const receipt = await response.json() as Record; + // Worker envelope: { verified, receipt: { hash, createdAt, governance, facts, ... } } + const envelope = await response.json() as { + verified?: boolean; + receipt?: Record; + }; + const r = envelope.receipt ?? {}; // Return subset based on which flow tool was called if (toolName === 'flow_status') { return { content: [{ type: 'text', text: JSON.stringify({ - verified: receipt.verified, - createdAt: receipt.createdAt, + verified: envelope.verified ?? false, + createdAt: r.createdAt, hash, }, null, 2) }] }; } if (toolName === 'flow_quality' || toolName === 'flow_governance') { + const facts = (r.facts ?? {}) as Record; return { content: [{ type: 'text', text: JSON.stringify({ - governance: receipt.governance, + governance: r.governance ?? null, + quality: { + confidence: facts.scaffold_confidence ?? null, + shadow_density: facts.shadow_density ?? null, + position_count: facts.position_count ?? null, + }, hash, }, null, 2) }] }; } - // flow_summary — full receipt - return { content: [{ type: 'text', text: JSON.stringify(receipt, null, 2) }] }; + // flow_summary — full envelope (verified + receipt) + return { content: [{ type: 'text', text: JSON.stringify(envelope, null, 2) }] }; } if (toolName === 'scaffold_status') { diff --git a/test/gateway.test.ts b/test/gateway.test.ts index 34f36a4..0516551 100644 --- a/test/gateway.test.ts +++ b/test/gateway.test.ts @@ -267,7 +267,8 @@ describe('handleMcpRequest', () => { tenantId: req.headers.get('X-Gateway-Tenant-Id') ?? '', }; return new Response(JSON.stringify({ - verified: true, createdAt: '2026-04-03', + verified: true, + receipt: { hash: 'abc', createdAt: '2026-04-03' }, }), { headers: { 'Content-Type': 'application/json' } }); }, connect: () => { throw new Error('not implemented'); }, @@ -280,6 +281,57 @@ describe('handleMcpRequest', () => { expect(capturedHeaders.tenantId).toBe('tenant-1'); }); + + // Regression: gateway previously read receipt fields off the envelope root, + // silently returning `{ hash }`-only echoes when nothing happened to live there. + it('flow_governance unwraps the worker envelope', async () => { + const env = makeEnv({ + TAROTSCRIPT: { + fetch: async () => new Response(JSON.stringify({ + verified: true, + receipt: { + hash: 'abc', + createdAt: '2026-04-03', + governance: { determinism: 'locked', capped: true }, + facts: { scaffold_confidence: 'high', shadow_density: 0.2, position_count: 6 }, + }, + }), { headers: { 'Content-Type': 'application/json' } }), + connect: () => { throw new Error('not implemented'); }, + } as unknown as Fetcher, + }); + + const sessionId = await getSession(env); + const req = rpcRequest('tools/call', { name: 'flow_governance', arguments: { hash: 'abc' } }, { 'MCP-Session-Id': sessionId }); + const res = await handleMcpRequest(req, env); + const body = await res.json() as any; + const payload = JSON.parse(body.result.content[0].text); + + expect(payload.governance).toEqual({ determinism: 'locked', capped: true }); + expect(payload.quality.confidence).toBe('high'); + expect(payload.hash).toBe('abc'); + }); + + it('flow_status surfaces createdAt from the receipt envelope', async () => { + const env = makeEnv({ + TAROTSCRIPT: { + fetch: async () => new Response(JSON.stringify({ + verified: true, + receipt: { hash: 'abc', createdAt: '2026-04-17T00:00:00Z' }, + }), { headers: { 'Content-Type': 'application/json' } }), + connect: () => { throw new Error('not implemented'); }, + } as unknown as Fetcher, + }); + + const sessionId = await getSession(env); + const req = rpcRequest('tools/call', { name: 'flow_status', arguments: { hash: 'abc' } }, { 'MCP-Session-Id': sessionId }); + const res = await handleMcpRequest(req, env); + const body = await res.json() as any; + const payload = JSON.parse(body.result.content[0].text); + + expect(payload.verified).toBe(true); + expect(payload.createdAt).toBe('2026-04-17T00:00:00Z'); + expect(payload.hash).toBe('abc'); + }); }); describe('security', () => {