@treeship/verify 0.9.2

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 ADDED
@@ -0,0 +1,143 @@
1
+ # @treeship/verify
2
+
3
+ Zero-dependency cryptographic verification for [Treeship](https://treeship.dev) Session Receipts and Agent Certificates. Runs anywhere WebAssembly and `fetch` are available.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @treeship/verify
9
+ ```
10
+
11
+ The only dependency is `@treeship/core-wasm` (the compiled Rust core, under 170 KB gzipped). No transitive dependency on `@treeship/sdk`, so you can ship this to an edge worker, browser dashboard, or audit tool without pulling the subprocess code path in at all.
12
+
13
+ ## API
14
+
15
+ Three functions. Each accepts a parsed object, a JSON string, or a URL.
16
+
17
+ ### `verifyReceipt(target)`
18
+
19
+ Runs the JSON-level checks a Treeship Session Receipt carries: Merkle root recomputation, inclusion proof verification, leaf-count parity, timeline ordering, chain linkage.
20
+
21
+ ```typescript
22
+ import { verifyReceipt } from '@treeship/verify';
23
+
24
+ const result = await verifyReceipt('https://treeship.dev/receipt/ssn_abc');
25
+
26
+ if (result.outcome === 'pass') {
27
+ console.log(`session ${result.session.id} verified`);
28
+ } else {
29
+ console.log(`verification failed:`, result.checks.filter(c => c.status === 'fail'));
30
+ }
31
+ ```
32
+
33
+ ### `verifyCertificate(target, now?)`
34
+
35
+ Verifies the Ed25519 signature on an Agent Certificate against the public key embedded in the certificate. With `now` supplied (Date or RFC 3339 string), also classifies the validity window.
36
+
37
+ ```typescript
38
+ import { verifyCertificate } from '@treeship/verify';
39
+
40
+ const result = await verifyCertificate('./researcher.agent/certificate.json', new Date());
41
+
42
+ if (result.outcome === 'pass' && result.validity === 'valid') {
43
+ console.log(`certificate valid for ${result.certificate.agent_name}`);
44
+ }
45
+ ```
46
+
47
+ ### `crossVerify(receipt, certificate, now?)`
48
+
49
+ Answers three questions: do the receipt and certificate reference the same ship, was the certificate valid at `now`, was every tool the session called authorized by the certificate. The `ok` field is the roll-up.
50
+
51
+ ```typescript
52
+ import { crossVerify } from '@treeship/verify';
53
+
54
+ const result = await crossVerify(
55
+ 'https://treeship.dev/receipt/ssn_abc',
56
+ 'https://example.com/researcher.agent.json',
57
+ );
58
+
59
+ if (result.ok) {
60
+ console.log('complete trust loop verified');
61
+ } else {
62
+ console.log('ship_id_status:', result.ship_id_status);
63
+ console.log('unauthorized:', result.unauthorized_tool_calls);
64
+ }
65
+ ```
66
+
67
+ ## Runtime compatibility
68
+
69
+ | Runtime | Supported |
70
+ |---------|-----------|
71
+ | Node.js 18+ | yes |
72
+ | Node.js 20+ | yes |
73
+ | Deno | yes |
74
+ | Browser (bundler) | yes |
75
+ | Vercel Edge | yes |
76
+ | Cloudflare Workers | yes |
77
+ | AWS Lambda (Node) | yes |
78
+
79
+ The package ships `@treeship/core-wasm` with `--target bundler`. Runtimes that need a different WASM wrapper (plain Node without a bundler) can still consume the core via the subprocess-backed `@treeship/sdk`.
80
+
81
+ ## Examples
82
+
83
+ ### Vercel Edge Function
84
+
85
+ ```typescript
86
+ import { verifyReceipt } from '@treeship/verify';
87
+
88
+ export const config = { runtime: 'edge' };
89
+
90
+ export default async function handler(req: Request) {
91
+ const { url } = await req.json();
92
+ const result = await verifyReceipt(url);
93
+ return Response.json(result);
94
+ }
95
+ ```
96
+
97
+ ### Cloudflare Worker
98
+
99
+ ```typescript
100
+ import { verifyReceipt } from '@treeship/verify';
101
+
102
+ export default {
103
+ async fetch(request: Request): Promise<Response> {
104
+ const { url } = await request.json();
105
+ const result = await verifyReceipt(url);
106
+ return Response.json(result);
107
+ },
108
+ };
109
+ ```
110
+
111
+ ### AWS Lambda (Node runtime)
112
+
113
+ ```typescript
114
+ import { verifyReceipt } from '@treeship/verify';
115
+
116
+ export const handler = async (event: { body: string }) => {
117
+ const { url } = JSON.parse(event.body);
118
+ const result = await verifyReceipt(url);
119
+ return { statusCode: 200, body: JSON.stringify(result) };
120
+ };
121
+ ```
122
+
123
+ ### Browser dashboard
124
+
125
+ ```typescript
126
+ import { verifyReceipt } from '@treeship/verify';
127
+
128
+ const file = await fileInput.files[0];
129
+ const text = await file.text();
130
+ const result = await verifyReceipt(text);
131
+
132
+ renderChecks(result.checks);
133
+ ```
134
+
135
+ ## What this package is NOT
136
+
137
+ - **Not an attestation SDK.** For signing artifacts, session management, Hub push/pull, or agent registration, use [`@treeship/sdk`](../sdk-ts/) which shells out to the `treeship` CLI.
138
+ - **Not a trust anchor.** The embedded Ed25519 signature on an Agent Certificate is verified against the certificate's own public key. Chaining a certificate to a trusted issuer is the caller's responsibility.
139
+ - **Not a drop-in for local-chain verification.** Some signature verification needs the original envelope bytes, which a URL-fetched receipt does not carry. Use `treeship verify <artifact-id>` on the CLI for that.
140
+
141
+ ## License
142
+
143
+ Apache-2.0
@@ -0,0 +1,80 @@
1
+ /** Accepted input shapes across all exported functions. */
2
+ export type VerifyTarget = string | URL | Record<string, unknown>;
3
+ export interface VerifyCheck {
4
+ step: string;
5
+ status: 'pass' | 'fail' | 'warn';
6
+ detail: string;
7
+ }
8
+ export interface VerifyReceiptResult {
9
+ outcome: 'pass' | 'fail' | 'error';
10
+ checks: VerifyCheck[];
11
+ session: {
12
+ id: string;
13
+ ship_id?: string;
14
+ schema_version?: string;
15
+ agent: string;
16
+ duration_ms?: number;
17
+ actions: number;
18
+ };
19
+ error_code?: string;
20
+ message?: string;
21
+ }
22
+ export interface VerifyCertificateResult {
23
+ outcome: 'pass' | 'fail' | 'error';
24
+ signature_valid: boolean;
25
+ validity: 'valid' | 'expired' | 'not_yet_valid' | 'not_checked';
26
+ certificate: {
27
+ ship_id: string;
28
+ agent_name: string;
29
+ issued_at: string;
30
+ valid_until: string;
31
+ schema_version?: string;
32
+ };
33
+ error_code?: string;
34
+ message?: string;
35
+ }
36
+ export interface CrossVerifyResult {
37
+ outcome: 'pass' | 'fail' | 'error';
38
+ ok: boolean;
39
+ ship_id_status: 'match' | 'mismatch' | 'unknown';
40
+ certificate_status: 'valid' | 'expired' | 'not_yet_valid';
41
+ certificate_signature_valid: boolean;
42
+ authorized_tool_calls: string[];
43
+ unauthorized_tool_calls: string[];
44
+ authorized_tools_never_called: string[];
45
+ error_code?: string;
46
+ message?: string;
47
+ }
48
+ /**
49
+ * Verify a Treeship Session Receipt. Runs the checks derivable from the
50
+ * receipt JSON alone (Merkle root recomputation, inclusion proofs, leaf
51
+ * count, timeline ordering, chain linkage). Signature verification on
52
+ * individual envelopes requires the original envelope bytes and is out of
53
+ * scope for URL-fetched receipts; use the `treeship verify` CLI for that.
54
+ *
55
+ * Accepts:
56
+ * - a parsed receipt object (best for callers that already have the JSON)
57
+ * - a JSON string
58
+ * - a URL string (fetched with the runtime's global fetch)
59
+ * - a URL object
60
+ */
61
+ export declare function verifyReceipt(target: VerifyTarget): Promise<VerifyReceiptResult>;
62
+ /**
63
+ * Verify an Agent Certificate. Checks the embedded Ed25519 signature
64
+ * against the certificate's embedded public key, then optionally
65
+ * classifies the validity window relative to `now`.
66
+ *
67
+ * Omit `now` (or pass `undefined`) to defer validity classification
68
+ * (signature-only). Pass a `Date` or RFC 3339 string to check expiry.
69
+ */
70
+ export declare function verifyCertificate(target: VerifyTarget, now?: Date | string): Promise<VerifyCertificateResult>;
71
+ /**
72
+ * Cross-verify a Session Receipt against an Agent Certificate. Answers
73
+ * three questions in one call: do the receipt and certificate reference
74
+ * the same ship? Was the certificate valid at `now`? Was every tool the
75
+ * session called authorized by the certificate?
76
+ *
77
+ * The `ok` field is the roll-up: true iff all three checks pass. Defaults
78
+ * `now` to `Date.now()` if omitted.
79
+ */
80
+ export declare function crossVerify(receipt: VerifyTarget, certificate: VerifyTarget, now?: Date | string): Promise<CrossVerifyResult>;
package/dist/index.js ADDED
@@ -0,0 +1,89 @@
1
+ // @treeship/verify -- zero-dependency cryptographic verification.
2
+ //
3
+ // Install this package alone to verify Treeship Session Receipts and Agent
4
+ // Certificates in any runtime with WebAssembly and fetch. It is deliberately
5
+ // tiny: the only dependency is @treeship/core-wasm (the compiled Rust core,
6
+ // ~170 KB gzipped). There is no transitive dependency on @treeship/sdk,
7
+ // so shipping this to an edge worker, browser dashboard, or Witness doesn't
8
+ // pull the subprocess code path in at all.
9
+ //
10
+ // Same rules `treeship verify` applies from the CLI, same result shape.
11
+ // If a new schema version lands in core, this package picks it up via
12
+ // core-wasm without an API change here.
13
+ let wasmBindings = null;
14
+ async function loadWasm() {
15
+ if (wasmBindings)
16
+ return wasmBindings;
17
+ const mod = (await import('@treeship/core-wasm'));
18
+ wasmBindings = mod;
19
+ return mod;
20
+ }
21
+ async function normalizeToJson(target) {
22
+ if (typeof target === 'object' && !(target instanceof URL)) {
23
+ return JSON.stringify(target);
24
+ }
25
+ const raw = target instanceof URL ? target.toString() : target;
26
+ if (raw.startsWith('http://') || raw.startsWith('https://')) {
27
+ // Accept both the Hub JSON API path and the human-readable mirror.
28
+ const apiUrl = raw.replace('/receipt/', '/v1/receipt/');
29
+ const res = await fetch(apiUrl, { headers: { accept: 'application/json' } });
30
+ if (!res.ok)
31
+ throw new Error(`fetch ${apiUrl} returned HTTP ${res.status}`);
32
+ return await res.text();
33
+ }
34
+ return raw;
35
+ }
36
+ /**
37
+ * Verify a Treeship Session Receipt. Runs the checks derivable from the
38
+ * receipt JSON alone (Merkle root recomputation, inclusion proofs, leaf
39
+ * count, timeline ordering, chain linkage). Signature verification on
40
+ * individual envelopes requires the original envelope bytes and is out of
41
+ * scope for URL-fetched receipts; use the `treeship verify` CLI for that.
42
+ *
43
+ * Accepts:
44
+ * - a parsed receipt object (best for callers that already have the JSON)
45
+ * - a JSON string
46
+ * - a URL string (fetched with the runtime's global fetch)
47
+ * - a URL object
48
+ */
49
+ export async function verifyReceipt(target) {
50
+ const json = await normalizeToJson(target);
51
+ const wasm = await loadWasm();
52
+ return JSON.parse(wasm.verify_receipt(json));
53
+ }
54
+ /**
55
+ * Verify an Agent Certificate. Checks the embedded Ed25519 signature
56
+ * against the certificate's embedded public key, then optionally
57
+ * classifies the validity window relative to `now`.
58
+ *
59
+ * Omit `now` (or pass `undefined`) to defer validity classification
60
+ * (signature-only). Pass a `Date` or RFC 3339 string to check expiry.
61
+ */
62
+ export async function verifyCertificate(target, now) {
63
+ const json = await normalizeToJson(target);
64
+ const nowStr = now === undefined ? '' : now instanceof Date ? now.toISOString() : now;
65
+ const wasm = await loadWasm();
66
+ return JSON.parse(wasm.verify_certificate(json, nowStr));
67
+ }
68
+ /**
69
+ * Cross-verify a Session Receipt against an Agent Certificate. Answers
70
+ * three questions in one call: do the receipt and certificate reference
71
+ * the same ship? Was the certificate valid at `now`? Was every tool the
72
+ * session called authorized by the certificate?
73
+ *
74
+ * The `ok` field is the roll-up: true iff all three checks pass. Defaults
75
+ * `now` to `Date.now()` if omitted.
76
+ */
77
+ export async function crossVerify(receipt, certificate, now) {
78
+ const [receiptJson, certJson] = await Promise.all([
79
+ normalizeToJson(receipt),
80
+ normalizeToJson(certificate),
81
+ ]);
82
+ const nowStr = now === undefined
83
+ ? new Date().toISOString()
84
+ : now instanceof Date
85
+ ? now.toISOString()
86
+ : now;
87
+ const wasm = await loadWasm();
88
+ return JSON.parse(wasm.cross_verify(receiptJson, certJson, nowStr));
89
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@treeship/verify",
3
+ "version": "0.9.2",
4
+ "description": "Zero-dependency cryptographic verification for Treeship receipts and certificates. Runs anywhere WASM runs: Node, browser, Vercel Edge, Cloudflare Workers, AWS Lambda.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/zerkerlabs/treeship",
9
+ "directory": "packages/verify-js"
10
+ },
11
+ "homepage": "https://treeship.dev",
12
+ "keywords": [
13
+ "treeship",
14
+ "verification",
15
+ "verify",
16
+ "attestation",
17
+ "receipts",
18
+ "certificate",
19
+ "cross-verification",
20
+ "wasm",
21
+ "edge",
22
+ "serverless"
23
+ ],
24
+ "type": "module",
25
+ "main": "dist/index.js",
26
+ "types": "dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "import": "./dist/index.js",
30
+ "types": "./dist/index.d.ts"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md"
36
+ ],
37
+ "sideEffects": false,
38
+ "scripts": {
39
+ "build": "tsc",
40
+ "test": "vitest run"
41
+ },
42
+ "dependencies": {
43
+ "@treeship/core-wasm": "0.9.2"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^25.5.0",
47
+ "typescript": "^5.7.0",
48
+ "vitest": "^3.0.0"
49
+ }
50
+ }