The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit c95a8380814fCommit detailsView latest

apps/api/src/app.ts 112 lines · 4.7 KB · typescript
1
import Fastify from 'fastify';
2
import cors from '@fastify/cors';
3
import swagger from '@fastify/swagger';
4
import { z } from 'zod';
5
import {
6
  AgentIdentitySchema,
7
  DelegationCredentialSchema,
8
  FeedbackSchema,
9
  InteractionContractSchema,
10
  InteractionEventSchema,
11
  ReceiptSchema,
12
} from '../../../packages/protocol/src/index.js';
13
import { FixtureFactCheckProvider, TrustEngine } from '../../../packages/core/src/index.js';
14
15
export function buildApi(engine = new TrustEngine(), factChecker = new FixtureFactCheckProvider()) {
16
  const app = Fastify({
17
    logger: { redact: ['req.headers.authorization', 'req.body.rawMessage', 'req.body.privateKey'] },
18
  });
19
  void app.register(cors, { origin: true });
20
  void app.register(swagger, { openapi: { info: { title: 'OpenClasp API', version: '0.1.0' } } });
21
22
  app.setErrorHandler((error, _request, reply) =>
23
    reply.status(400).send({ error: error instanceof Error ? error.message : 'Unknown error' }),
24
  );
25
  void app.register(async (router) => {
26
    router.get('/health', async () => ({ status: 'ok' }));
27
    router.get('/ready', async () => ({ status: 'ready' }));
28
    router.get('/openapi.json', async () => app.swagger());
29
    router.get('/v0.1/dashboard', async () => ({
30
      agents: [...engine.agents.values()],
31
      events: [...engine.events.values()],
32
      conflicts: [...engine.conflicts.values()],
33
      receipts: [...engine.receipts.values()],
34
      profiles: [...engine.profiles.values()],
35
    }));
36
    router.post('/v0.1/agents', async (request) =>
37
      engine.registerAgent(AgentIdentitySchema.parse(request.body)),
38
    );
39
    router.get('/v0.1/agents/:id', async (request) => {
40
      const { id } = request.params as { id: string };
41
      const value = engine.agents.get(id);
42
      if (!value) throw new Error('Agent not found');
43
      return value;
44
    });
45
    router.post('/v0.1/delegations', async (request) => {
46
      const value = DelegationCredentialSchema.parse(request.body);
47
      engine.delegations.set(value.delegationId, value);
48
      return { valid: engine.verifyDelegation(value.delegationId) };
49
    });
50
    router.post('/v0.1/delegations/:id/verify', async (request) => ({
51
      valid: engine.verifyDelegation((request.params as { id: string }).id),
52
    }));
53
    router.post('/v0.1/interactions/contracts', async (request) =>
54
      engine.saveContract(InteractionContractSchema.parse(request.body)),
55
    );
56
    router.post('/v0.1/events', async (request) =>
57
      engine.recordEvent(InteractionEventSchema.parse(request.body)),
58
    );
59
    router.post('/v0.1/risk/assess', async (request) => engine.assess(request.body as any));
60
    router.get('/v0.1/profiles/:id', async (request) => {
61
      const { id } = request.params as { id: string };
62
      const query = request.query as { version?: string; taskCategory?: string };
63
      return engine.getRisk(id, query.version ?? '1.0.0', query.taskCategory ?? 'general');
64
    });
65
    router.post('/v0.1/claims/check', async (request) =>
66
      ((value) => factChecker.check(value.claim, value.permission))(
67
        z.object({ claim: z.string(), permission: z.boolean().optional() }).parse(request.body),
68
      ),
69
    );
70
    router.post('/v0.1/receipts', async (request) =>
71
      engine.submitReceipt(ReceiptSchema.parse(request.body)),
72
    );
73
    router.post('/v0.1/receipts/verify', async (request) => {
74
      try {
75
        engine.submitReceipt(ReceiptSchema.parse(request.body));
76
        return { valid: true };
77
      } catch {
78
        return { valid: false };
79
      }
80
    });
81
    router.post('/v0.1/feedback', async (request) =>
82
      engine.submitFeedback(FeedbackSchema.parse(request.body)),
83
    );
84
    router.post('/v0.1/conflicts', async (request) => engine.createConflict(request.body as any));
85
    router.post('/v0.1/conflicts/:id/permit', async (request) =>
86
      engine.permitMediation(
87
        (request.params as { id: string }).id,
88
        (request.body as { agentId: string }).agentId,
89
      ),
90
    );
91
    router.post('/v0.1/conflicts/:id/resolve', async (request) =>
92
      engine.resolveConflict(
93
        (request.params as { id: string }).id,
94
        (request.body as { resolution: string }).resolution,
95
      ),
96
    );
97
    router.post('/v0.1/revocations/agents/:id', async (request) => {
98
      engine.revokeAgent((request.params as { id: string }).id);
99
      return { revoked: true };
100
    });
101
    router.post('/v0.1/revocations/delegations/:id', async (request) => {
102
      engine.revokeDelegation((request.params as { id: string }).id);
103
      return { revoked: true };
104
    });
105
    router.post('/v0.1/contributions/consent', async (request) => {
106
      const value = z.object({ agentId: z.string(), enabled: z.boolean() }).parse(request.body);
107
      engine.setContributionConsent(value.agentId, value.enabled);
108
      return value;
109
    });
110
  });
111
  return app;
112
}