@vaultgraph/sdk 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 +110 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +237 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# VaultGraph SDK
|
|
2
|
+
|
|
3
|
+
VaultGraph client helpers for generating, signing, verifying, and submitting VaultGraph JobReceipts. Intended for server-side usage (Node 18+/edge runtimes with `fetch`).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @vaultgraph/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Generate a keypair (one-time, server-side)
|
|
14
|
+
|
|
15
|
+
**Supported algorithm (MVP): Ed25519.** The ingestion API verifies with `algorithm: null`, which assumes Ed25519/Ed448; RSA/ECDSA signatures are not accepted right now.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { generateKeyPairSync } from "crypto";
|
|
19
|
+
|
|
20
|
+
const { privateKey, publicKey } = generateKeyPairSync("ed25519", {
|
|
21
|
+
privateKeyEncoding: { format: "pem", type: "pkcs8" },
|
|
22
|
+
publicKeyEncoding: { format: "pem", type: "spki" },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
console.log("Private key (keep secret):\n", privateKey);
|
|
26
|
+
console.log("Public key (share with VaultGraph):\n", publicKey);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Store the private key in your secrets manager; never ship it to the browser. Publish the public key wherever you manage org settings or bundle it with exports.
|
|
30
|
+
|
|
31
|
+
### Create, sign, verify, and submit
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import {
|
|
35
|
+
createReceipt,
|
|
36
|
+
createSignedReceipt,
|
|
37
|
+
hashContext,
|
|
38
|
+
signReceipt,
|
|
39
|
+
submitReceipt,
|
|
40
|
+
verifyReceipt,
|
|
41
|
+
} from "@vaultgraph/sdk";
|
|
42
|
+
|
|
43
|
+
// 1) Hash any sensitive context before sending
|
|
44
|
+
const contextHash = hashContext({ transcript: "..." });
|
|
45
|
+
|
|
46
|
+
// 2) Build the canonical receipt payload
|
|
47
|
+
const receipt = createReceipt({
|
|
48
|
+
agentId: "agent-123",
|
|
49
|
+
consumerId: "consumer-456",
|
|
50
|
+
jobId: "job-789",
|
|
51
|
+
resolution: "resolved",
|
|
52
|
+
contextHash,
|
|
53
|
+
metadata: { channel: "email" },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 3) Sign the receipt (Ed25519, ECDSA, RSA supported)
|
|
57
|
+
const { signature } = createSignedReceipt({
|
|
58
|
+
receipt,
|
|
59
|
+
privateKey: process.env.VAULTGRAPH_VENDOR_PRIVATE_KEY!,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 4) Optionally verify locally
|
|
63
|
+
const ok = verifyReceipt({
|
|
64
|
+
receipt,
|
|
65
|
+
signature,
|
|
66
|
+
publicKey: process.env.VAULTGRAPH_VENDOR_PUBLIC_KEY!,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 5) Submit to your portal deployment
|
|
70
|
+
await submitReceipt({
|
|
71
|
+
apiUrl: "https://app.vaultgraph.com", // or your self-hosted URL
|
|
72
|
+
receipt,
|
|
73
|
+
signature,
|
|
74
|
+
publicKey: process.env.VAULTGRAPH_VENDOR_PUBLIC_KEY!,
|
|
75
|
+
apiKey: process.env.VAULTGRAPH_VENDOR_API_KEY!,
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Convenience: create + sign in one step
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { createSignedReceipt } from "@vaultgraph/sdk";
|
|
83
|
+
|
|
84
|
+
const { receipt, signature } = createSignedReceipt({
|
|
85
|
+
agentId: "agent-123",
|
|
86
|
+
consumerId: "consumer-456",
|
|
87
|
+
jobId: "job-789",
|
|
88
|
+
resolution: "resolved",
|
|
89
|
+
contextHash: "abc123",
|
|
90
|
+
privateKey: process.env.VAULTGRAPH_VENDOR_PRIVATE_KEY!,
|
|
91
|
+
metadata: { channel: "sms" },
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## API surface
|
|
96
|
+
|
|
97
|
+
- `hashContext(value, options?)` → sha256 hash of canonical JSON/bytes
|
|
98
|
+
- `createReceipt(input)` → normalized `JobReceipt`
|
|
99
|
+
- `serializeReceipt(receipt)` → canonical JSON string
|
|
100
|
+
- `signReceipt(options)` → signature string (base64 default)
|
|
101
|
+
- `verifyReceipt(options)` → boolean
|
|
102
|
+
- `createSignedReceipt(options)` → `{ receipt, signature }`
|
|
103
|
+
- `submitReceipt(options)` → POSTs to `/api/receipts` (requires `apiKey`)
|
|
104
|
+
- Types: `JobReceipt`, `JobResolution`, `ReceiptVersion`, `SubmitReceiptOptions`, `SubmitReceiptResponse`
|
|
105
|
+
|
|
106
|
+
## Notes
|
|
107
|
+
|
|
108
|
+
- Do not send raw conversation context; send `context_hash` instead.
|
|
109
|
+
- Keep your private key and vendor API key server-side only (API key is required for ingestion).
|
|
110
|
+
- Receipt versioning currently `v0`; breaking changes will bump the major version of this package.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { KeyLike, BinaryToTextEncoding } from 'crypto';
|
|
2
|
+
import { CreateReceiptInput, JobReceipt } from '@repo/lib/job-receipt';
|
|
3
|
+
export { CreateReceiptInput, JOB_RESOLUTIONS, JobReceipt, JobReceiptV0, JobResolution, ReceiptVersion, canonicalJSONStringify, createReceipt, hashContext, jobReceiptV0Schema, serializeReceipt, signReceipt, verifyReceipt } from '@repo/lib/job-receipt';
|
|
4
|
+
export { SubmitReceiptOptions, SubmitReceiptResponse, submitReceipt } from '@repo/lib/submit-receipt';
|
|
5
|
+
|
|
6
|
+
interface CreateSignedReceiptOptions extends CreateReceiptInput {
|
|
7
|
+
privateKey: KeyLike;
|
|
8
|
+
algorithm?: string | null;
|
|
9
|
+
encoding?: BinaryToTextEncoding;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Convenience helper to construct and sign a receipt in one step.
|
|
13
|
+
*/
|
|
14
|
+
declare function createSignedReceipt(options: CreateSignedReceiptOptions): {
|
|
15
|
+
receipt: JobReceipt;
|
|
16
|
+
signature: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export { type CreateSignedReceiptOptions, createSignedReceipt };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { createHash, sign, verify, createPrivateKey, createPublicKey } from 'crypto';
|
|
2
|
+
|
|
3
|
+
// ../lib/src/job-receipt.ts
|
|
4
|
+
var JOB_RESOLUTIONS = ["resolved", "partial", "failed"];
|
|
5
|
+
var VALID_JOB_RESOLUTIONS = JOB_RESOLUTIONS;
|
|
6
|
+
var jobReceiptV0Schema = {
|
|
7
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
8
|
+
$id: "https://vaultgraph.com/schemas/job-receipt-v0.json",
|
|
9
|
+
type: "object",
|
|
10
|
+
required: [
|
|
11
|
+
"version",
|
|
12
|
+
"agent_id",
|
|
13
|
+
"consumer_id",
|
|
14
|
+
"job_id",
|
|
15
|
+
"resolution",
|
|
16
|
+
"context_hash",
|
|
17
|
+
"issued_at",
|
|
18
|
+
"metadata"
|
|
19
|
+
],
|
|
20
|
+
properties: {
|
|
21
|
+
version: { type: "string", enum: ["v0"] },
|
|
22
|
+
agent_id: { type: "string", minLength: 1 },
|
|
23
|
+
consumer_id: { type: "string", minLength: 1 },
|
|
24
|
+
job_id: { type: "string", minLength: 1 },
|
|
25
|
+
resolution: { type: "string", enum: [...JOB_RESOLUTIONS] },
|
|
26
|
+
context_hash: { type: "string", minLength: 1 },
|
|
27
|
+
issued_at: { type: "string", format: "date-time" },
|
|
28
|
+
metadata: {
|
|
29
|
+
type: "object",
|
|
30
|
+
additionalProperties: true
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
additionalProperties: false
|
|
34
|
+
};
|
|
35
|
+
function canonicalJSONStringify(value) {
|
|
36
|
+
return canonicalize(value);
|
|
37
|
+
}
|
|
38
|
+
function hashContext(value, options = {}) {
|
|
39
|
+
const encoding = options.encoding ?? "hex";
|
|
40
|
+
const payload = typeof value === "string" || value instanceof Uint8Array ? value : canonicalJSONStringify(value);
|
|
41
|
+
return createHash("sha256").update(payload).digest(encoding);
|
|
42
|
+
}
|
|
43
|
+
function createReceipt(input) {
|
|
44
|
+
const receipt = {
|
|
45
|
+
version: "v0",
|
|
46
|
+
agent_id: normalizeNonEmpty(input.agentId, "agentId"),
|
|
47
|
+
consumer_id: normalizeNonEmpty(input.consumerId, "consumerId"),
|
|
48
|
+
job_id: normalizeNonEmpty(input.jobId, "jobId"),
|
|
49
|
+
resolution: validateResolution(input.resolution),
|
|
50
|
+
context_hash: normalizeNonEmpty(input.contextHash, "contextHash"),
|
|
51
|
+
issued_at: normalizeIssuedAt(input.issuedAt),
|
|
52
|
+
metadata: normalizeMetadata(input.metadata)
|
|
53
|
+
};
|
|
54
|
+
assertValidReceipt(receipt);
|
|
55
|
+
return receipt;
|
|
56
|
+
}
|
|
57
|
+
function serializeReceipt(receipt) {
|
|
58
|
+
assertValidReceipt(receipt);
|
|
59
|
+
return canonicalJSONStringify(receipt);
|
|
60
|
+
}
|
|
61
|
+
function signReceipt(options) {
|
|
62
|
+
const privateKey = normalizePrivateKey(options.privateKey);
|
|
63
|
+
const algorithm = resolveAlgorithm(privateKey, options.algorithm);
|
|
64
|
+
const payload = serializeReceipt(options.receipt);
|
|
65
|
+
const signature = sign(algorithm, Buffer.from(payload), privateKey);
|
|
66
|
+
return signature.toString(options.encoding ?? "base64");
|
|
67
|
+
}
|
|
68
|
+
function verifyReceipt(options) {
|
|
69
|
+
const publicKey = normalizePublicKey(options.publicKey);
|
|
70
|
+
const algorithm = resolveAlgorithm(publicKey, options.algorithm);
|
|
71
|
+
const payload = serializeReceipt(options.receipt);
|
|
72
|
+
const signature = typeof options.signature === "string" ? Buffer.from(options.signature, options.encoding ?? "base64") : options.signature;
|
|
73
|
+
return verify(algorithm, Buffer.from(payload), publicKey, signature);
|
|
74
|
+
}
|
|
75
|
+
function normalizeNonEmpty(value, field) {
|
|
76
|
+
const trimmed = (value ?? "").trim();
|
|
77
|
+
if (!trimmed) {
|
|
78
|
+
throw new Error(`${field} is required`);
|
|
79
|
+
}
|
|
80
|
+
return trimmed;
|
|
81
|
+
}
|
|
82
|
+
function validateResolution(resolution) {
|
|
83
|
+
if (!VALID_JOB_RESOLUTIONS.includes(resolution)) {
|
|
84
|
+
throw new Error(`Invalid resolution: ${resolution}`);
|
|
85
|
+
}
|
|
86
|
+
return resolution;
|
|
87
|
+
}
|
|
88
|
+
function normalizeIssuedAt(issuedAt) {
|
|
89
|
+
if (!issuedAt) {
|
|
90
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
91
|
+
}
|
|
92
|
+
if (issuedAt instanceof Date) {
|
|
93
|
+
return issuedAt.toISOString();
|
|
94
|
+
}
|
|
95
|
+
const parsed = new Date(issuedAt);
|
|
96
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"issuedAt must be a valid ISO date string or Date instance"
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return parsed.toISOString();
|
|
102
|
+
}
|
|
103
|
+
function normalizeMetadata(metadata) {
|
|
104
|
+
if (metadata === void 0) return {};
|
|
105
|
+
if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
106
|
+
throw new Error("metadata must be a plain object");
|
|
107
|
+
}
|
|
108
|
+
return metadata;
|
|
109
|
+
}
|
|
110
|
+
function assertValidReceipt(receipt) {
|
|
111
|
+
normalizeNonEmpty(receipt.agent_id, "agent_id");
|
|
112
|
+
normalizeNonEmpty(receipt.consumer_id, "consumer_id");
|
|
113
|
+
normalizeNonEmpty(receipt.job_id, "job_id");
|
|
114
|
+
normalizeNonEmpty(receipt.context_hash, "context_hash");
|
|
115
|
+
validateResolution(receipt.resolution);
|
|
116
|
+
normalizeIssuedAt(receipt.issued_at);
|
|
117
|
+
normalizeMetadata(receipt.metadata);
|
|
118
|
+
}
|
|
119
|
+
function normalizePrivateKey(key) {
|
|
120
|
+
return typeof key === "string" || Buffer.isBuffer(key) ? createPrivateKey(key) : key;
|
|
121
|
+
}
|
|
122
|
+
function normalizePublicKey(key) {
|
|
123
|
+
return typeof key === "string" || Buffer.isBuffer(key) ? createPublicKey(key) : key;
|
|
124
|
+
}
|
|
125
|
+
function resolveAlgorithm(key, algorithm) {
|
|
126
|
+
if (algorithm !== void 0) {
|
|
127
|
+
return algorithm;
|
|
128
|
+
}
|
|
129
|
+
const keyType = key.asymmetricKeyType;
|
|
130
|
+
if (keyType === "ed25519" || keyType === "ed448") {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
return "sha256";
|
|
134
|
+
}
|
|
135
|
+
function canonicalize(value) {
|
|
136
|
+
if (value === null) return "null";
|
|
137
|
+
const type = typeof value;
|
|
138
|
+
if (type === "number") {
|
|
139
|
+
if (!Number.isFinite(value)) {
|
|
140
|
+
throw new Error("Non-finite numbers are not supported in canonical JSON");
|
|
141
|
+
}
|
|
142
|
+
return JSON.stringify(value);
|
|
143
|
+
}
|
|
144
|
+
if (type === "boolean" || type === "string") {
|
|
145
|
+
return JSON.stringify(value);
|
|
146
|
+
}
|
|
147
|
+
if (value instanceof Date) {
|
|
148
|
+
return JSON.stringify(value.toISOString());
|
|
149
|
+
}
|
|
150
|
+
if (value instanceof Uint8Array) {
|
|
151
|
+
return JSON.stringify(Buffer.from(value).toString("base64"));
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(value)) {
|
|
154
|
+
const items = value.map((item) => canonicalize(item)).join(",");
|
|
155
|
+
return `[${items}]`;
|
|
156
|
+
}
|
|
157
|
+
if (type === "object") {
|
|
158
|
+
const obj = value;
|
|
159
|
+
const keys = Object.keys(obj).sort();
|
|
160
|
+
const entries = keys.map(
|
|
161
|
+
(key) => `${JSON.stringify(key)}:${canonicalize(obj[key])}`
|
|
162
|
+
);
|
|
163
|
+
return `{${entries.join(",")}}`;
|
|
164
|
+
}
|
|
165
|
+
throw new Error("Unsupported data type for canonical JSON");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ../lib/src/submit-receipt.ts
|
|
169
|
+
async function submitReceipt(options) {
|
|
170
|
+
const {
|
|
171
|
+
apiUrl,
|
|
172
|
+
receipt,
|
|
173
|
+
signature,
|
|
174
|
+
publicKey,
|
|
175
|
+
metadata,
|
|
176
|
+
apiKey,
|
|
177
|
+
fetchImpl
|
|
178
|
+
} = options;
|
|
179
|
+
if (!apiUrl || !apiUrl.trim()) {
|
|
180
|
+
throw new Error("apiUrl is required");
|
|
181
|
+
}
|
|
182
|
+
if (!apiKey || !apiKey.trim()) {
|
|
183
|
+
throw new Error("apiKey is required");
|
|
184
|
+
}
|
|
185
|
+
const target = new URL("/api/receipts", apiUrl).toString();
|
|
186
|
+
const fetcher = fetchImpl ?? fetch;
|
|
187
|
+
const res = await fetcher(target, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: {
|
|
190
|
+
"content-type": "application/json",
|
|
191
|
+
"x-api-key": apiKey
|
|
192
|
+
},
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
receipt,
|
|
195
|
+
signature,
|
|
196
|
+
public_key: publicKey,
|
|
197
|
+
metadata
|
|
198
|
+
})
|
|
199
|
+
});
|
|
200
|
+
const payload = await safeParseJson(res);
|
|
201
|
+
if (!res.ok) {
|
|
202
|
+
const detail = payload && typeof payload === "object" ? payload : null;
|
|
203
|
+
const message = detail?.detail || detail?.error || res.statusText || "Receipt submission failed";
|
|
204
|
+
throw new Error(message);
|
|
205
|
+
}
|
|
206
|
+
if (!payload || typeof payload !== "object") {
|
|
207
|
+
throw new Error("Invalid response payload");
|
|
208
|
+
}
|
|
209
|
+
const id = payload.id;
|
|
210
|
+
const status = payload.status;
|
|
211
|
+
if (!id || !status) {
|
|
212
|
+
throw new Error("Response missing id or status");
|
|
213
|
+
}
|
|
214
|
+
return { id, status };
|
|
215
|
+
}
|
|
216
|
+
async function safeParseJson(response) {
|
|
217
|
+
try {
|
|
218
|
+
return await response.json();
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/index.ts
|
|
225
|
+
function createSignedReceipt(options) {
|
|
226
|
+
const { privateKey, algorithm, encoding, ...receiptInput } = options;
|
|
227
|
+
const receipt = createReceipt(receiptInput);
|
|
228
|
+
const signature = signReceipt({
|
|
229
|
+
receipt,
|
|
230
|
+
privateKey,
|
|
231
|
+
algorithm,
|
|
232
|
+
encoding
|
|
233
|
+
});
|
|
234
|
+
return { receipt, signature };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export { JOB_RESOLUTIONS, canonicalJSONStringify, createReceipt, createSignedReceipt, hashContext, jobReceiptV0Schema, serializeReceipt, signReceipt, submitReceipt, verifyReceipt };
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vaultgraph/sdk",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": ["dist"],
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"vaultgraph",
|
|
22
|
+
"sdk",
|
|
23
|
+
"ai",
|
|
24
|
+
"agent",
|
|
25
|
+
"job-receipt",
|
|
26
|
+
"trust",
|
|
27
|
+
"trust-layer",
|
|
28
|
+
"trust-score",
|
|
29
|
+
"attestation",
|
|
30
|
+
"verification"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup --config tsup.config.ts",
|
|
34
|
+
"lint": "eslint . --max-warnings 0",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"check-types": "tsc --noEmit",
|
|
38
|
+
"release": "bash ./scripts/publish.sh"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@repo/lib": "workspace:*",
|
|
42
|
+
"@repo/eslint-config": "workspace:*",
|
|
43
|
+
"@repo/typescript-config": "workspace:*",
|
|
44
|
+
"@repo/vitest-config": "workspace:*",
|
|
45
|
+
"@types/node": "^22.15.3",
|
|
46
|
+
"eslint": "^9.39.1",
|
|
47
|
+
"tsup": "^8.3.6",
|
|
48
|
+
"typescript": "5.9.2",
|
|
49
|
+
"vitest": "^4.0.16"
|
|
50
|
+
}
|
|
51
|
+
}
|