@vorionsys/proof-plane 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +9 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/routes.d.ts +88 -0
- package/dist/api/routes.d.ts.map +1 -0
- package/dist/api/routes.js +399 -0
- package/dist/api/routes.js.map +1 -0
- package/dist/events/event-emitter.d.ts +1 -1
- package/dist/events/event-emitter.d.ts.map +1 -1
- package/dist/events/event-signatures.d.ts +1 -1
- package/dist/events/event-signatures.d.ts.map +1 -1
- package/dist/events/event-store.d.ts +1 -1
- package/dist/events/event-store.d.ts.map +1 -1
- package/dist/events/hash-chain.d.ts +1 -1
- package/dist/events/hash-chain.d.ts.map +1 -1
- package/dist/events/memory-store.d.ts +1 -1
- package/dist/events/memory-store.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/proof-plane/logger.d.ts +1 -1
- package/dist/proof-plane/logger.d.ts.map +1 -1
- package/dist/proof-plane/proof-plane.d.ts +1 -1
- package/dist/proof-plane/proof-plane.d.ts.map +1 -1
- package/dist/proof-plane/proof-plane.js +1 -1
- package/dist/proof-plane/proof-plane.js.map +1 -1
- package/openapi.yaml +571 -0
- package/package.json +30 -4
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# @vorionsys/proof-plane
|
|
2
|
+
|
|
3
|
+
Immutable audit trail for AI agent operations. Provides hash-chained, cryptographically signed event logging for compliance, debugging, and trust verification.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @vorionsys/proof-plane
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createProofPlane, createInMemoryEventStore } from '@vorionsys/proof-plane';
|
|
15
|
+
|
|
16
|
+
const store = createInMemoryEventStore();
|
|
17
|
+
const proofPlane = createProofPlane({
|
|
18
|
+
signedBy: 'my-service',
|
|
19
|
+
store,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Log events
|
|
23
|
+
await proofPlane.logIntentReceived(intent);
|
|
24
|
+
await proofPlane.logDecisionMade(decision);
|
|
25
|
+
|
|
26
|
+
// Query events by correlation ID
|
|
27
|
+
const trace = await proofPlane.getTrace(correlationId);
|
|
28
|
+
|
|
29
|
+
// Verify hash chain integrity
|
|
30
|
+
const verification = await proofPlane.verifyChain();
|
|
31
|
+
console.log(verification.valid); // true
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **Hash-Chained Events**: Every event links to the previous via SHA-256, forming a tamper-evident chain
|
|
37
|
+
- **Cryptographic Signatures**: Ed25519 event signing with key pair generation and batch verification
|
|
38
|
+
- **Pluggable Storage**: Abstract `ProofEventStore` interface - bring your own database
|
|
39
|
+
- **Event Emitter**: Typed event system with batch emit support
|
|
40
|
+
- **Trace Queries**: Retrieve full event traces by correlation ID
|
|
41
|
+
- **Chain Verification**: Verify integrity of the entire hash chain with detailed reports
|
|
42
|
+
- **API Routes**: Pre-built Express/Fastify route handlers for proof endpoints
|
|
43
|
+
|
|
44
|
+
## Subpath Imports
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Core proof plane
|
|
48
|
+
import { createProofPlane, ProofPlane } from '@vorionsys/proof-plane';
|
|
49
|
+
|
|
50
|
+
// Event stores
|
|
51
|
+
import { InMemoryEventStore } from '@vorionsys/proof-plane/events';
|
|
52
|
+
|
|
53
|
+
// Proof plane internals
|
|
54
|
+
import { ProofPlane } from '@vorionsys/proof-plane/proof-plane';
|
|
55
|
+
|
|
56
|
+
// API route handlers
|
|
57
|
+
import { createProofRoutes } from '@vorionsys/proof-plane/api';
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API Reference
|
|
61
|
+
|
|
62
|
+
### ProofPlane
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const plane = createProofPlane({
|
|
66
|
+
signedBy: 'service-name', // Identifier for the signing service
|
|
67
|
+
store: eventStore, // ProofEventStore implementation
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Log lifecycle events
|
|
71
|
+
await plane.logIntentReceived(intent);
|
|
72
|
+
await plane.logDecisionMade(decision);
|
|
73
|
+
|
|
74
|
+
// Query
|
|
75
|
+
const trace = await plane.getTrace(correlationId);
|
|
76
|
+
const events = await plane.queryEvents({ limit: 100, offset: 0 });
|
|
77
|
+
|
|
78
|
+
// Verify chain
|
|
79
|
+
const result = await plane.verifyChain();
|
|
80
|
+
// { valid: boolean, checkedCount: number, errors: string[] }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Hash Chain
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import {
|
|
87
|
+
sha256,
|
|
88
|
+
computeEventHash,
|
|
89
|
+
verifyEventHash,
|
|
90
|
+
verifyChain,
|
|
91
|
+
verifyChainWithDetails,
|
|
92
|
+
} from '@vorionsys/proof-plane';
|
|
93
|
+
|
|
94
|
+
const hash = sha256(data);
|
|
95
|
+
const eventHash = computeEventHash(event);
|
|
96
|
+
const isValid = verifyEventHash(event);
|
|
97
|
+
const chainResult = verifyChainWithDetails(events);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Event Signatures
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import {
|
|
104
|
+
generateSigningKeyPair,
|
|
105
|
+
signEvent,
|
|
106
|
+
verifyEventSignature,
|
|
107
|
+
EventSigningService,
|
|
108
|
+
} from '@vorionsys/proof-plane';
|
|
109
|
+
|
|
110
|
+
// Generate keys
|
|
111
|
+
const keyPair = await generateSigningKeyPair();
|
|
112
|
+
|
|
113
|
+
// Sign an event
|
|
114
|
+
const signature = await signEvent(event, keyPair.privateKey);
|
|
115
|
+
|
|
116
|
+
// Verify
|
|
117
|
+
const isValid = await verifyEventSignature(event, signature, keyPair.publicKey);
|
|
118
|
+
|
|
119
|
+
// Or use the service
|
|
120
|
+
const signer = createSigningService({ keyPair });
|
|
121
|
+
const signed = await signer.sign(event);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Event Store
|
|
125
|
+
|
|
126
|
+
Implement the `ProofEventStore` interface for custom storage:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import type { ProofEventStore, EventQueryOptions } from '@vorionsys/proof-plane';
|
|
130
|
+
|
|
131
|
+
class MyEventStore implements ProofEventStore {
|
|
132
|
+
async append(event) { /* ... */ }
|
|
133
|
+
async getByCorrelationId(id) { /* ... */ }
|
|
134
|
+
async query(options: EventQueryOptions) { /* ... */ }
|
|
135
|
+
async getStats() { /* ... */ }
|
|
136
|
+
async getChain(options) { /* ... */ }
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### API Routes
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { createProofRoutes, registerProofRoutes } from '@vorionsys/proof-plane';
|
|
144
|
+
|
|
145
|
+
// Fastify
|
|
146
|
+
registerProofRoutes(fastifyApp, { store });
|
|
147
|
+
|
|
148
|
+
// Express
|
|
149
|
+
import { createProofExpressRouter } from '@vorionsys/proof-plane';
|
|
150
|
+
app.use('/proof', createProofExpressRouter({ store }));
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## TypeScript
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import type {
|
|
157
|
+
ProofPlaneConfig,
|
|
158
|
+
ProofPlaneLogger,
|
|
159
|
+
ProofEventStore,
|
|
160
|
+
EventQueryOptions,
|
|
161
|
+
EventQueryResult,
|
|
162
|
+
EventStats,
|
|
163
|
+
EventEmitterConfig,
|
|
164
|
+
EventListener,
|
|
165
|
+
EmitResult,
|
|
166
|
+
ChainVerificationResult,
|
|
167
|
+
SigningKeyPair,
|
|
168
|
+
SignatureVerificationResult,
|
|
169
|
+
} from '@vorionsys/proof-plane';
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof Plane API Module
|
|
3
|
+
*
|
|
4
|
+
* Provides REST API routes for the Vorion audit system.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
export { createProofRoutes, registerProofRoutes, createProofExpressRouter, type ProofRoute, } from './routes.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,UAAU,GAChB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,GAEzB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof Plane API Routes
|
|
3
|
+
*
|
|
4
|
+
* Provides REST API endpoints for the Vorion audit system:
|
|
5
|
+
* - POST /proof - Submit a proof event
|
|
6
|
+
* - GET /proof/:id - Retrieve proof event by ID
|
|
7
|
+
* - GET /proof/verify/:id - Verify a single proof event
|
|
8
|
+
* - GET /proof/chain/:correlationId - Get event trace by correlation ID
|
|
9
|
+
* - POST /proof/chain/verify - Verify chain integrity
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import type { ProofPlane } from '../proof-plane/proof-plane.js';
|
|
15
|
+
/**
|
|
16
|
+
* Route handler context - generic interface for Fastify-like frameworks
|
|
17
|
+
*/
|
|
18
|
+
interface RouteContext {
|
|
19
|
+
request: {
|
|
20
|
+
params?: unknown;
|
|
21
|
+
query?: unknown;
|
|
22
|
+
body?: unknown;
|
|
23
|
+
id?: string;
|
|
24
|
+
};
|
|
25
|
+
reply: {
|
|
26
|
+
status(code: number): RouteContext['reply'];
|
|
27
|
+
send(data: unknown): void;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Route definition for registration
|
|
32
|
+
*/
|
|
33
|
+
export interface ProofRoute {
|
|
34
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
35
|
+
path: string;
|
|
36
|
+
handler: (ctx: RouteContext, proofPlane: ProofPlane) => Promise<void>;
|
|
37
|
+
schema?: {
|
|
38
|
+
params?: z.ZodSchema;
|
|
39
|
+
query?: z.ZodSchema;
|
|
40
|
+
body?: z.ZodSchema;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Define all proof API routes
|
|
45
|
+
*/
|
|
46
|
+
export declare function createProofRoutes(proofPlane: ProofPlane): ProofRoute[];
|
|
47
|
+
/**
|
|
48
|
+
* Fastify plugin registration helper
|
|
49
|
+
*
|
|
50
|
+
* Usage with Fastify:
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import Fastify from 'fastify';
|
|
53
|
+
* import { createProofPlane } from '@vorionsys/proof-plane';
|
|
54
|
+
* import { registerProofRoutes } from '@vorionsys/proof-plane/api';
|
|
55
|
+
*
|
|
56
|
+
* const app = Fastify();
|
|
57
|
+
* const proofPlane = createProofPlane({ signedBy: 'my-service' });
|
|
58
|
+
*
|
|
59
|
+
* await app.register(async (instance) => {
|
|
60
|
+
* registerProofRoutes(instance, proofPlane);
|
|
61
|
+
* }, { prefix: '/v1' });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function registerProofRoutes(fastify: {
|
|
65
|
+
get: (path: string, handler: (request: any, reply: any) => Promise<void>) => void;
|
|
66
|
+
post: (path: string, handler: (request: any, reply: any) => Promise<void>) => void;
|
|
67
|
+
}, proofPlane: ProofPlane): void;
|
|
68
|
+
/**
|
|
69
|
+
* Express middleware adapter
|
|
70
|
+
*
|
|
71
|
+
* Usage with Express:
|
|
72
|
+
* ```typescript
|
|
73
|
+
* import express from 'express';
|
|
74
|
+
* import { createProofPlane } from '@vorionsys/proof-plane';
|
|
75
|
+
* import { createProofExpressRouter } from '@vorionsys/proof-plane/api';
|
|
76
|
+
*
|
|
77
|
+
* const app = express();
|
|
78
|
+
* const proofPlane = createProofPlane({ signedBy: 'my-service' });
|
|
79
|
+
*
|
|
80
|
+
* app.use('/v1', createProofExpressRouter(proofPlane));
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function createProofExpressRouter(proofPlane: ProofPlane): {
|
|
84
|
+
routes: ProofRoute[];
|
|
85
|
+
handler: (req: any, res: any, next: any) => Promise<void>;
|
|
86
|
+
};
|
|
87
|
+
export {};
|
|
88
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAiEhE;;GAEG;AACH,UAAU,YAAY;IACpB,OAAO,EAAE;QACP,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,CAAC;IACF,KAAK,EAAE;QACL,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;QACrB,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;QACpB,IAAI,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;KACpB,CAAC;CACH;AA4BD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,EAAE,CAuPtE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE;IACP,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IAClF,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;CACpF,EACD,UAAU,EAAE,UAAU,GACrB,IAAI,CA0CN;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG;IAChE,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D,CA+CA"}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof Plane API Routes
|
|
3
|
+
*
|
|
4
|
+
* Provides REST API endpoints for the Vorion audit system:
|
|
5
|
+
* - POST /proof - Submit a proof event
|
|
6
|
+
* - GET /proof/:id - Retrieve proof event by ID
|
|
7
|
+
* - GET /proof/verify/:id - Verify a single proof event
|
|
8
|
+
* - GET /proof/chain/:correlationId - Get event trace by correlation ID
|
|
9
|
+
* - POST /proof/chain/verify - Verify chain integrity
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { ProofEventType } from '@vorionsys/contracts';
|
|
15
|
+
/**
|
|
16
|
+
* Zod schema for proof event submission
|
|
17
|
+
*/
|
|
18
|
+
const submitProofSchema = z.object({
|
|
19
|
+
eventType: z.nativeEnum(ProofEventType),
|
|
20
|
+
correlationId: z.string().uuid(),
|
|
21
|
+
agentId: z.string().uuid().optional(),
|
|
22
|
+
payload: z.record(z.unknown()),
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Zod schema for event ID parameter
|
|
26
|
+
*/
|
|
27
|
+
const eventIdParamsSchema = z.object({
|
|
28
|
+
id: z.string().uuid(),
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Zod schema for correlation ID parameter
|
|
32
|
+
*/
|
|
33
|
+
const correlationIdParamsSchema = z.object({
|
|
34
|
+
correlationId: z.string().uuid(),
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Zod schema for chain verification request
|
|
38
|
+
*/
|
|
39
|
+
const verifyChainBodySchema = z.object({
|
|
40
|
+
fromEventId: z.string().uuid().optional(),
|
|
41
|
+
limit: z.number().int().min(1).max(10000).optional(),
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Zod schema for query options
|
|
45
|
+
*/
|
|
46
|
+
const queryOptionsSchema = z.object({
|
|
47
|
+
limit: z.coerce.number().int().min(1).max(1000).optional(),
|
|
48
|
+
offset: z.coerce.number().int().min(0).optional(),
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Create success response
|
|
52
|
+
*/
|
|
53
|
+
function success(data, requestId) {
|
|
54
|
+
return {
|
|
55
|
+
data,
|
|
56
|
+
meta: {
|
|
57
|
+
requestId,
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create error response
|
|
64
|
+
*/
|
|
65
|
+
function error(code, message, details) {
|
|
66
|
+
return {
|
|
67
|
+
error: {
|
|
68
|
+
code,
|
|
69
|
+
message,
|
|
70
|
+
details,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Define all proof API routes
|
|
76
|
+
*/
|
|
77
|
+
export function createProofRoutes(proofPlane) {
|
|
78
|
+
return [
|
|
79
|
+
// POST /proof - Submit a new proof event
|
|
80
|
+
{
|
|
81
|
+
method: 'POST',
|
|
82
|
+
path: '/proof',
|
|
83
|
+
schema: { body: submitProofSchema },
|
|
84
|
+
handler: async (ctx) => {
|
|
85
|
+
const body = submitProofSchema.parse(ctx.request.body ?? {});
|
|
86
|
+
try {
|
|
87
|
+
const result = await proofPlane.logEvent(body.eventType, body.correlationId, body.payload, body.agentId);
|
|
88
|
+
ctx.reply.status(201).send(success({
|
|
89
|
+
eventId: result.event.eventId,
|
|
90
|
+
eventType: result.event.eventType,
|
|
91
|
+
correlationId: result.event.correlationId,
|
|
92
|
+
eventHash: result.event.eventHash,
|
|
93
|
+
previousHash: result.event.previousHash,
|
|
94
|
+
occurredAt: result.event.occurredAt,
|
|
95
|
+
recordedAt: result.event.recordedAt,
|
|
96
|
+
}, ctx.request.id));
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
ctx.reply.status(500).send(error('EMIT_FAILED', 'Failed to emit proof event', {
|
|
100
|
+
message: err instanceof Error ? err.message : String(err),
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
// GET /proof/:id - Get proof event by ID
|
|
106
|
+
{
|
|
107
|
+
method: 'GET',
|
|
108
|
+
path: '/proof/:id',
|
|
109
|
+
schema: { params: eventIdParamsSchema },
|
|
110
|
+
handler: async (ctx) => {
|
|
111
|
+
const params = eventIdParamsSchema.parse(ctx.request.params ?? {});
|
|
112
|
+
const event = await proofPlane.getEvent(params.id);
|
|
113
|
+
if (!event) {
|
|
114
|
+
ctx.reply.status(404).send(error('EVENT_NOT_FOUND', `Proof event ${params.id} not found`));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
ctx.reply.status(200).send(success(event, ctx.request.id));
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
// GET /proof/verify/:id - Verify a single proof event
|
|
121
|
+
{
|
|
122
|
+
method: 'GET',
|
|
123
|
+
path: '/proof/verify/:id',
|
|
124
|
+
schema: { params: eventIdParamsSchema },
|
|
125
|
+
handler: async (ctx) => {
|
|
126
|
+
const params = eventIdParamsSchema.parse(ctx.request.params ?? {});
|
|
127
|
+
const event = await proofPlane.getEvent(params.id);
|
|
128
|
+
if (!event) {
|
|
129
|
+
ctx.reply.status(404).send(error('EVENT_NOT_FOUND', `Proof event ${params.id} not found`));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Verify hash integrity
|
|
133
|
+
const { computeEventHash } = await import('../events/hash-chain.js');
|
|
134
|
+
const computedHash = await computeEventHash(event);
|
|
135
|
+
const hashValid = computedHash === event.eventHash;
|
|
136
|
+
// Verify signature if present
|
|
137
|
+
let signatureResult = null;
|
|
138
|
+
if (event.signature && proofPlane.isSignatureVerificationEnabled()) {
|
|
139
|
+
signatureResult = await proofPlane.verifyEventSignature(event);
|
|
140
|
+
}
|
|
141
|
+
ctx.reply.status(200).send(success({
|
|
142
|
+
eventId: event.eventId,
|
|
143
|
+
verification: {
|
|
144
|
+
hashValid,
|
|
145
|
+
computedHash,
|
|
146
|
+
storedHash: event.eventHash,
|
|
147
|
+
signatureValid: signatureResult?.valid ?? null,
|
|
148
|
+
signatureError: signatureResult?.error,
|
|
149
|
+
signer: signatureResult?.signer,
|
|
150
|
+
verifiedAt: new Date().toISOString(),
|
|
151
|
+
},
|
|
152
|
+
}, ctx.request.id));
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
// GET /proof/chain/:correlationId - Get event trace by correlation ID
|
|
156
|
+
{
|
|
157
|
+
method: 'GET',
|
|
158
|
+
path: '/proof/chain/:correlationId',
|
|
159
|
+
schema: {
|
|
160
|
+
params: correlationIdParamsSchema,
|
|
161
|
+
query: queryOptionsSchema,
|
|
162
|
+
},
|
|
163
|
+
handler: async (ctx) => {
|
|
164
|
+
const params = correlationIdParamsSchema.parse(ctx.request.params ?? {});
|
|
165
|
+
const query = queryOptionsSchema.parse(ctx.request.query ?? {});
|
|
166
|
+
const events = await proofPlane.getTrace(params.correlationId);
|
|
167
|
+
if (events.length === 0) {
|
|
168
|
+
ctx.reply.status(404).send(error('TRACE_NOT_FOUND', `No events found for correlation ${params.correlationId}`));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Apply pagination
|
|
172
|
+
const offset = query.offset ?? 0;
|
|
173
|
+
const limit = query.limit ?? 100;
|
|
174
|
+
const paginatedEvents = events.slice(offset, offset + limit);
|
|
175
|
+
ctx.reply.status(200).send(success({
|
|
176
|
+
correlationId: params.correlationId,
|
|
177
|
+
events: paginatedEvents,
|
|
178
|
+
total: events.length,
|
|
179
|
+
pagination: {
|
|
180
|
+
offset,
|
|
181
|
+
limit,
|
|
182
|
+
hasMore: offset + limit < events.length,
|
|
183
|
+
},
|
|
184
|
+
}, ctx.request.id));
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
// POST /proof/chain/verify - Verify chain integrity
|
|
188
|
+
{
|
|
189
|
+
method: 'POST',
|
|
190
|
+
path: '/proof/chain/verify',
|
|
191
|
+
schema: { body: verifyChainBodySchema },
|
|
192
|
+
handler: async (ctx) => {
|
|
193
|
+
const body = verifyChainBodySchema.parse(ctx.request.body ?? {});
|
|
194
|
+
const chainResult = await proofPlane.verifyChain(body.fromEventId, body.limit);
|
|
195
|
+
// Optionally verify signatures
|
|
196
|
+
let signaturesResult = null;
|
|
197
|
+
if (proofPlane.isSignatureVerificationEnabled()) {
|
|
198
|
+
const fullResult = await proofPlane.verifyChainAndSignatures(body.fromEventId, body.limit);
|
|
199
|
+
signaturesResult = fullResult.signatures;
|
|
200
|
+
}
|
|
201
|
+
ctx.reply.status(200).send(success({
|
|
202
|
+
chain: {
|
|
203
|
+
valid: chainResult.valid,
|
|
204
|
+
verifiedCount: chainResult.verifiedCount,
|
|
205
|
+
totalEvents: chainResult.totalEvents,
|
|
206
|
+
firstEventId: chainResult.firstEventId,
|
|
207
|
+
lastEventId: chainResult.lastEventId,
|
|
208
|
+
brokenAtEventId: chainResult.brokenAtEventId,
|
|
209
|
+
brokenAtIndex: chainResult.brokenAtIndex,
|
|
210
|
+
error: chainResult.error,
|
|
211
|
+
},
|
|
212
|
+
signatures: signaturesResult
|
|
213
|
+
? {
|
|
214
|
+
totalEvents: signaturesResult.totalEvents,
|
|
215
|
+
validCount: signaturesResult.validCount,
|
|
216
|
+
invalidCount: signaturesResult.invalidCount,
|
|
217
|
+
unsignedCount: signaturesResult.unsignedCount,
|
|
218
|
+
success: signaturesResult.success,
|
|
219
|
+
}
|
|
220
|
+
: null,
|
|
221
|
+
fullyVerified: chainResult.valid && (signaturesResult?.success ?? true),
|
|
222
|
+
verifiedAt: new Date().toISOString(),
|
|
223
|
+
}, ctx.request.id));
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
// GET /proof/stats - Get event statistics
|
|
227
|
+
{
|
|
228
|
+
method: 'GET',
|
|
229
|
+
path: '/proof/stats',
|
|
230
|
+
handler: async (ctx) => {
|
|
231
|
+
const stats = await proofPlane.getStats();
|
|
232
|
+
ctx.reply.status(200).send(success({
|
|
233
|
+
totalEvents: stats.totalEvents,
|
|
234
|
+
eventsByType: stats.byType,
|
|
235
|
+
eventsByAgent: stats.byAgent,
|
|
236
|
+
oldestEvent: stats.oldestEvent,
|
|
237
|
+
newestEvent: stats.newestEvent,
|
|
238
|
+
shadowModeStats: stats.byShadowMode,
|
|
239
|
+
}, ctx.request.id));
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
// GET /proof/latest - Get most recent event
|
|
243
|
+
{
|
|
244
|
+
method: 'GET',
|
|
245
|
+
path: '/proof/latest',
|
|
246
|
+
handler: async (ctx) => {
|
|
247
|
+
const event = await proofPlane.getLatestEvent();
|
|
248
|
+
if (!event) {
|
|
249
|
+
ctx.reply.status(404).send(error('NO_EVENTS', 'No proof events recorded yet'));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
ctx.reply.status(200).send(success(event, ctx.request.id));
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Fastify plugin registration helper
|
|
259
|
+
*
|
|
260
|
+
* Usage with Fastify:
|
|
261
|
+
* ```typescript
|
|
262
|
+
* import Fastify from 'fastify';
|
|
263
|
+
* import { createProofPlane } from '@vorionsys/proof-plane';
|
|
264
|
+
* import { registerProofRoutes } from '@vorionsys/proof-plane/api';
|
|
265
|
+
*
|
|
266
|
+
* const app = Fastify();
|
|
267
|
+
* const proofPlane = createProofPlane({ signedBy: 'my-service' });
|
|
268
|
+
*
|
|
269
|
+
* await app.register(async (instance) => {
|
|
270
|
+
* registerProofRoutes(instance, proofPlane);
|
|
271
|
+
* }, { prefix: '/v1' });
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
export function registerProofRoutes(fastify, proofPlane) {
|
|
275
|
+
const routes = createProofRoutes(proofPlane);
|
|
276
|
+
for (const route of routes) {
|
|
277
|
+
const handler = async (request, reply) => {
|
|
278
|
+
const ctx = {
|
|
279
|
+
request: {
|
|
280
|
+
params: request.params,
|
|
281
|
+
query: request.query,
|
|
282
|
+
body: request.body,
|
|
283
|
+
id: request.id,
|
|
284
|
+
},
|
|
285
|
+
reply: {
|
|
286
|
+
status(code) {
|
|
287
|
+
reply.status(code);
|
|
288
|
+
return this;
|
|
289
|
+
},
|
|
290
|
+
send(data) {
|
|
291
|
+
reply.send(data);
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
try {
|
|
296
|
+
await route.handler(ctx, proofPlane);
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
if (err instanceof z.ZodError) {
|
|
300
|
+
reply.status(400).send(error('VALIDATION_ERROR', 'Request validation failed', err.errors));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
if (route.method === 'GET') {
|
|
307
|
+
fastify.get(route.path, handler);
|
|
308
|
+
}
|
|
309
|
+
else if (route.method === 'POST') {
|
|
310
|
+
fastify.post(route.path, handler);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Express middleware adapter
|
|
316
|
+
*
|
|
317
|
+
* Usage with Express:
|
|
318
|
+
* ```typescript
|
|
319
|
+
* import express from 'express';
|
|
320
|
+
* import { createProofPlane } from '@vorionsys/proof-plane';
|
|
321
|
+
* import { createProofExpressRouter } from '@vorionsys/proof-plane/api';
|
|
322
|
+
*
|
|
323
|
+
* const app = express();
|
|
324
|
+
* const proofPlane = createProofPlane({ signedBy: 'my-service' });
|
|
325
|
+
*
|
|
326
|
+
* app.use('/v1', createProofExpressRouter(proofPlane));
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
export function createProofExpressRouter(proofPlane) {
|
|
330
|
+
const routes = createProofRoutes(proofPlane);
|
|
331
|
+
const handler = async (req, res, next) => {
|
|
332
|
+
const matchedRoute = routes.find((r) => r.method === req.method &&
|
|
333
|
+
matchPath(r.path, req.path));
|
|
334
|
+
if (!matchedRoute) {
|
|
335
|
+
next();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const ctx = {
|
|
339
|
+
request: {
|
|
340
|
+
params: extractParams(matchedRoute.path, req.path),
|
|
341
|
+
query: req.query,
|
|
342
|
+
body: req.body,
|
|
343
|
+
id: req.headers['x-request-id'],
|
|
344
|
+
},
|
|
345
|
+
reply: {
|
|
346
|
+
status(code) {
|
|
347
|
+
res.status(code);
|
|
348
|
+
return this;
|
|
349
|
+
},
|
|
350
|
+
send(data) {
|
|
351
|
+
res.json(data);
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
try {
|
|
356
|
+
await matchedRoute.handler(ctx, proofPlane);
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
if (err instanceof z.ZodError) {
|
|
360
|
+
res.status(400).json(error('VALIDATION_ERROR', 'Request validation failed', err.errors));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
next(err);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
return { routes, handler };
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Simple path matching for Express adapter
|
|
370
|
+
*/
|
|
371
|
+
function matchPath(pattern, path) {
|
|
372
|
+
const patternParts = pattern.split('/');
|
|
373
|
+
const pathParts = path.split('/');
|
|
374
|
+
if (patternParts.length !== pathParts.length) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
return patternParts.every((part, i) => {
|
|
378
|
+
if (part.startsWith(':')) {
|
|
379
|
+
return true; // Parameter matches anything
|
|
380
|
+
}
|
|
381
|
+
return part === pathParts[i];
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Extract parameters from path
|
|
386
|
+
*/
|
|
387
|
+
function extractParams(pattern, path) {
|
|
388
|
+
const params = {};
|
|
389
|
+
const patternParts = pattern.split('/');
|
|
390
|
+
const pathParts = path.split('/');
|
|
391
|
+
patternParts.forEach((part, i) => {
|
|
392
|
+
if (part.startsWith(':')) {
|
|
393
|
+
const paramName = part.slice(1);
|
|
394
|
+
params[paramName] = pathParts[i];
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
return params;
|
|
398
|
+
}
|
|
399
|
+
//# sourceMappingURL=routes.js.map
|