@scopeblind/passport 0.3.0 → 0.4.0

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 CHANGED
@@ -1,14 +1,16 @@
1
1
  # @scopeblind/passport
2
2
 
3
- Portable cryptographic identity for AI agents and coaches. Ed25519 keypairs, immutable manifests, ownership attestations, and verifiable evidence receipts.
3
+ Portable cryptographic identity and local agent-pack builder for AI agents, MCP runtimes, and BlindLLM/OpenClaw-style flows.
4
4
 
5
- ## Why
5
+ ## What it does
6
6
 
7
- Machine decisions need portable, verifiable proof. This SDK provides the identity layer: agents and coaches get Ed25519 keypairs, sign immutable manifests declaring their capabilities, and produce receipts that anyone can verify without calling ScopeBlind.
7
+ - Generate Ed25519 passports for agents and coaches
8
+ - Sign immutable manifests locally
9
+ - Export portable passport bundles
10
+ - Create a new local agent pack with `create`
11
+ - Wrap an existing OpenClaw / MCP config with `wrap`
8
12
 
9
- - **Portable** Export a passport bundle to any platform. No vendor lock-in.
10
- - **Verifiable** — Ed25519 signatures over canonical JSON. Verify offline, no API calls.
11
- - **Privacy-preserving** — No PII required. Identity is a key fingerprint.
13
+ The CLI is local-first. It does not upload prompts, context, or keys to ScopeBlind.
12
14
 
13
15
  ## Install
14
16
 
@@ -18,115 +20,85 @@ npm install @scopeblind/passport
18
20
 
19
21
  Requires Node.js >= 18. Browser entrypoint available at `@scopeblind/passport/browser`.
20
22
 
21
- ## Quick Start
23
+ ## CLI First
22
24
 
23
- ```typescript
24
- import {
25
- generatePassportKey,
26
- createCoachManifest,
27
- verifyManifest,
28
- } from '@scopeblind/passport';
25
+ Create a new agent pack:
29
26
 
30
- // 1. Generate an Ed25519 keypair
31
- const key = generatePassportKey('coach');
32
- console.log(key.kid); // sb:coach:7Xq9...
33
-
34
- // 2. Create an immutable manifest
35
- const manifest = createCoachManifest(key, {
36
- display_name: 'Atlas Nova',
37
- model_family: 'claude',
38
- system_prompt_hash: 'sha256:a1b2c3...',
39
- capabilities: ['debate', 'analysis'],
40
- });
41
-
42
- // 3. Verify the signature
43
- const result = verifyManifest(manifest);
44
- console.log(result.valid); // true
27
+ ```bash
28
+ npx @scopeblind/passport create \
29
+ --name "Luna" \
30
+ --runtime openclaw \
31
+ --policy shadow
45
32
  ```
46
33
 
47
- ## API
48
-
49
- ### Key Generation
34
+ Wrap an existing OpenClaw or MCP config:
50
35
 
51
- ```typescript
52
- // Generate a new Ed25519 keypair for a coach or agent
53
- const coachKey = generatePassportKey('coach');
54
- const agentKey = generatePassportKey('agent');
55
-
56
- // Derive a passport ID from an existing public key
57
- const kid = derivePassportId('coach', publicKeyBytes);
58
- // => "sb:coach:7Xq9kM..."
36
+ ```bash
37
+ npx @scopeblind/passport wrap \
38
+ --runtime openclaw \
39
+ --config ./openclaw.json \
40
+ --policy email-safe
59
41
  ```
60
42
 
61
- ### Manifests
43
+ Both commands generate:
62
44
 
63
- Manifests are **immutable once signed**. Config changes produce a new version.
45
+ - `manifest.json`
46
+ - `passport.bundle.json`
47
+ - `protect-mcp.json`
48
+ - `keys/gateway.json`
49
+ - runtime-specific config guidance
50
+ - `VERIFY.md`
64
51
 
65
- ```typescript
66
- // Coach manifest (for AI coaches/tutors)
67
- const manifest = createCoachManifest(key, {
68
- display_name: 'Atlas Nova',
69
- model_family: 'claude',
70
- system_prompt_hash: 'sha256:...',
71
- capabilities: ['debate', 'analysis', 'coaching'],
72
- });
52
+ ## What the CLI is for
73
53
 
74
- // Agent manifest (for autonomous AI agents)
75
- const agentManifest = createAgentManifest(key, {
76
- display_name: 'Research Bot',
77
- model_family: 'gpt-4',
78
- tool_names: ['web_search', 'file_read'],
79
- max_actions_per_turn: 10,
80
- });
81
-
82
- // Verify any manifest
83
- const result = verifyManifest(manifest);
84
- // { valid: true, kid: 'sb:coach:...', type: 'scopeblind:coach-manifest' }
85
- ```
54
+ This is not a no-code agent builder.
86
55
 
87
- ### Ownership Attestations
56
+ It is for:
88
57
 
89
- Prove a coach owns an agent (or vice versa):
58
+ - giving an agent a portable identity
59
+ - applying a `protect-mcp` policy pack
60
+ - generating runtime glue for OpenClaw / Claude Desktop / Cursor / generic MCP
61
+ - making the resulting pack independently verifiable
90
62
 
91
- ```typescript
92
- const attestation = createOwnershipAttestation(coachKey, {
93
- subject_kid: agentKey.kid,
94
- relationship: 'owns',
95
- });
96
- ```
63
+ It is not for:
97
64
 
98
- ### Evidence Receipts
65
+ - hosted prompt management
66
+ - agent marketplaces
67
+ - non-local storage of context or secrets
99
68
 
100
- Sign verifiable evidence of machine decisions:
69
+ ## SDK Quick Start
101
70
 
102
71
  ```typescript
103
72
  import {
104
- generateIssuerKey,
105
- signReceipt,
106
- createArenaBattleReceipt,
73
+ generatePassportKey,
74
+ createAgentManifest,
75
+ verifyManifest,
76
+ hashString,
107
77
  } from '@scopeblind/passport';
108
78
 
109
- // Generate an issuer keypair (server-side)
110
- const issuer = generateIssuerKey();
111
-
112
- // Sign a battle receipt
113
- const receipt = createArenaBattleReceipt(issuer, {
114
- battle_id: 'battle_abc123',
115
- arena_id: 'blindllm',
116
- participants: [
117
- { kid: coach1.kid, role: 'coach', model_family: 'claude' },
118
- { kid: coach2.kid, role: 'coach', model_family: 'gpt-4' },
119
- ],
120
- outcome: { winner_kid: coach1.kid, method: 'judge_decision' },
79
+ const key = generatePassportKey('agent');
80
+
81
+ const manifest = createAgentManifest(key, {
82
+ public_profile: {
83
+ name: 'Research Bot',
84
+ description: 'Fetches and summarizes MCP-backed research tasks',
85
+ domain_lanes: ['analysis:research'],
86
+ },
87
+ configuration_attestations: {
88
+ model_family_hash: hashString('runtime-managed'),
89
+ memory_mode: 'isolated',
90
+ system_prompt: 'Runtime-managed prompt placeholder',
91
+ },
92
+ capability_declarations: {
93
+ lease_template_compatibility: ['shadow'],
94
+ requested_tool_classes: ['mcp:general'],
95
+ },
121
96
  });
122
97
 
123
- // Anyone can verify the receipt
124
- const verified = verifyEnvelope(receipt);
98
+ console.log(verifyManifest(manifest).valid); // true
125
99
  ```
126
100
 
127
- ### Portable Export/Import
128
-
129
- Export a passport for migration to another platform:
101
+ ## Portable Export / Import
130
102
 
131
103
  ```typescript
132
104
  import {
@@ -135,50 +107,27 @@ import {
135
107
  importPassportBundle,
136
108
  } from '@scopeblind/passport';
137
109
 
138
- // Export (includes secret key — handle securely)
139
- const bundle = exportPassportBundle(key, manifest);
140
- const json = serializeBundle(bundle);
110
+ const bundle = exportPassportBundle({
111
+ key,
112
+ manifests: [manifest],
113
+ ownership_attestations: [],
114
+ status_records: [],
115
+ });
141
116
 
142
- // Import on another platform
117
+ const json = serializeBundle(bundle);
143
118
  const imported = importPassportBundle(json);
144
- console.log(imported.key.kid); // same kid, different machine
145
- ```
146
-
147
- ### Display Helpers
148
119
 
149
- ```typescript
150
- import { formatKidShort, getCoachSummary } from '@scopeblind/passport';
151
-
152
- formatKidShort('sb:coach:7Xq9kMvR...'); // "7Xq9kM"
153
- getCoachSummary(manifest); // "Atlas Nova (claude) — sb:coach:7Xq9kM"
120
+ console.log(imported.valid); // true
154
121
  ```
155
122
 
156
- ### Browser Entrypoint
157
-
158
- For browser environments (uses IndexedDB for key storage):
159
-
160
- ```typescript
161
- import { ... } from '@scopeblind/passport/browser';
162
- ```
163
-
164
- ## Types
165
-
166
- All types are exported and fully documented:
167
-
168
- - `PassportKeyPair` — Ed25519 keypair with kid and role
169
- - `CoachManifest` / `AgentManifest` — Immutable signed manifests
170
- - `OwnershipAttestation` — Cross-key ownership proof
171
- - `SignedEnvelope` — Canonical JSON wrapper with Ed25519 signature
172
- - `ArenaBattleReceipt` / `CoachUpliftReceipt` / `FormalDebateReceipt` — Evidence types
173
- - `PortablePassportBundle` — Exportable passport + manifest bundle
123
+ ## Why it fits the current stack
174
124
 
175
- ## Design Decisions
125
+ - `protect-mcp` handles tool policy and signed receipts
126
+ - `@veritasacta/verify` handles independent verification
127
+ - `@scopeblind/passport` handles portable identity + pack generation
176
128
 
177
- - **Ed25519 only** Single algorithm, no negotiation. P-256 DPoP bindings reserved for future lease validation.
178
- - **Canonical JSON** — Deterministic serialization (ASCII-only keys, sorted). Same input always produces same bytes.
179
- - **Immutable manifests** — Config changes produce a new version with `previous_version` link. No in-place mutation.
180
- - **No blockchain** — Verification is pure cryptography. No tokens, no chain, no consensus.
129
+ That split keeps the verifier MIT, keeps the local trust primitives local, and leaves the managed control plane as the paid product.
181
130
 
182
131
  ## License
183
132
 
184
- FSL-1.1-MIT — Source-available, converts to MIT after 2 years.
133
+ FSL-1.1-MIT — source-available, free to use, converts to MIT after 2 years.
@@ -0,0 +1,401 @@
1
+ // src/display.ts
2
+ function formatKidShort(kid) {
3
+ const parts = kid.split(":");
4
+ const fingerprint = parts[2] || kid;
5
+ if (fingerprint.length <= 8) return fingerprint;
6
+ return `${fingerprint.slice(0, 4)}\u2026${fingerprint.slice(-4)}`;
7
+ }
8
+ function formatKidLabeled(kid) {
9
+ const parts = kid.split(":");
10
+ const role = parts[1] === "coach" ? "Coach" : parts[1] === "agent" ? "Agent" : "Unknown";
11
+ return `${role} ${formatKidShort(kid)}`;
12
+ }
13
+ function getCoachSummary(envelope) {
14
+ const m = envelope.payload;
15
+ return {
16
+ kid: m.id,
17
+ displayName: m.display_name,
18
+ shortId: formatKidShort(m.id),
19
+ version: m.version,
20
+ createdAt: m.created_at
21
+ };
22
+ }
23
+ function getAgentSummary(envelope) {
24
+ const m = envelope.payload;
25
+ return {
26
+ kid: m.id,
27
+ name: m.public_profile.name,
28
+ shortId: formatKidShort(m.id),
29
+ version: m.version,
30
+ domainLanes: m.public_profile.domain_lanes,
31
+ createdAt: m.created_at
32
+ };
33
+ }
34
+ function isCoachManifest(manifest) {
35
+ return manifest.type === "scopeblind:coach-manifest";
36
+ }
37
+ function isAgentManifest(manifest) {
38
+ return manifest.type === "scopeblind:agent-manifest";
39
+ }
40
+
41
+ // src/keys.ts
42
+ import { ed25519 } from "@noble/curves/ed25519";
43
+ import { sha256 } from "@noble/hashes/sha256";
44
+ import { bytesToHex } from "@noble/hashes/utils";
45
+ var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
46
+ function base58Encode(bytes) {
47
+ let num = BigInt("0x" + bytesToHex(bytes));
48
+ const chars = [];
49
+ while (num > 0n) {
50
+ const remainder = Number(num % 58n);
51
+ chars.unshift(BASE58_ALPHABET[remainder]);
52
+ num = num / 58n;
53
+ }
54
+ for (const b of bytes) {
55
+ if (b === 0) chars.unshift("1");
56
+ else break;
57
+ }
58
+ return chars.join("") || "1";
59
+ }
60
+ function derivePassportId(publicKey, role) {
61
+ const fingerprint = base58Encode(publicKey).slice(0, 12);
62
+ return `sb:${role}:${fingerprint}`;
63
+ }
64
+ function generatePassportKey(role) {
65
+ const secretKeyRaw = ed25519.utils.randomPrivateKey();
66
+ const publicKey = ed25519.getPublicKey(secretKeyRaw);
67
+ const secretKey = new Uint8Array(64);
68
+ secretKey.set(secretKeyRaw, 0);
69
+ secretKey.set(publicKey, 32);
70
+ const kid = derivePassportId(publicKey, role);
71
+ return {
72
+ publicKey,
73
+ secretKey,
74
+ kid,
75
+ role,
76
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
77
+ };
78
+ }
79
+ function getSigningKey(keyPair) {
80
+ return bytesToHex(keyPair.secretKey.slice(0, 32));
81
+ }
82
+ function getVerifyKey(keyPair) {
83
+ return bytesToHex(keyPair.publicKey);
84
+ }
85
+ function hashString(value) {
86
+ const encoder = new TextEncoder();
87
+ return bytesToHex(sha256(encoder.encode(value)));
88
+ }
89
+
90
+ // src/manifest.ts
91
+ import { ed25519 as ed255192 } from "@noble/curves/ed25519";
92
+ import { sha256 as sha2562 } from "@noble/hashes/sha256";
93
+ import { bytesToHex as bytesToHex2, utf8ToBytes } from "@noble/hashes/utils";
94
+ function canonicalize(obj) {
95
+ return JSON.stringify(obj, (_key, value) => {
96
+ if (value && typeof value === "object" && !Array.isArray(value)) {
97
+ const sorted = {};
98
+ for (const k of Object.keys(value).sort()) {
99
+ sorted[k] = value[k];
100
+ }
101
+ return sorted;
102
+ }
103
+ return value;
104
+ });
105
+ }
106
+ function canonicalHash(obj) {
107
+ return bytesToHex2(sha2562(utf8ToBytes(canonicalize(obj))));
108
+ }
109
+ function signPayload(payload, keyPair) {
110
+ const message = utf8ToBytes(canonicalize(payload));
111
+ const sigBytes = ed255192.sign(message, keyPair.secretKey.slice(0, 32));
112
+ return {
113
+ payload,
114
+ signature: {
115
+ alg: "EdDSA",
116
+ kid: keyPair.kid,
117
+ sig: bytesToHex2(sigBytes)
118
+ }
119
+ };
120
+ }
121
+ function createCoachManifest(keyPair, input, previousVersion, version) {
122
+ const manifest = {
123
+ type: "scopeblind:coach-manifest",
124
+ id: keyPair.kid,
125
+ version: version || "1.0.0",
126
+ previous_version: previousVersion ?? null,
127
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
128
+ public_key: base58Encode(keyPair.publicKey),
129
+ display_name: input.display_name.trim().slice(0, 32)
130
+ };
131
+ return signPayload(manifest, keyPair);
132
+ }
133
+ function createAgentManifest(keyPair, input, previousVersion, version) {
134
+ const promptHash = hashString(input.configuration_attestations.system_prompt);
135
+ const manifest = {
136
+ type: "scopeblind:agent-manifest",
137
+ id: keyPair.kid,
138
+ version: version || "1.0.0",
139
+ previous_version: previousVersion ?? null,
140
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
141
+ public_key: base58Encode(keyPair.publicKey),
142
+ public_profile: input.public_profile,
143
+ configuration_attestations: {
144
+ model_family_hash: input.configuration_attestations.model_family_hash,
145
+ memory_mode: input.configuration_attestations.memory_mode,
146
+ prompt_hash: promptHash
147
+ },
148
+ capability_declarations: input.capability_declarations,
149
+ evidence_pointers: input.evidence_pointers || {}
150
+ };
151
+ return signPayload(manifest, keyPair);
152
+ }
153
+ function createOwnershipAttestation(coachKeyPair, agentId, agentManifestVersion) {
154
+ const attestation = {
155
+ type: "scopeblind:ownership-attestation",
156
+ coach_id: coachKeyPair.kid,
157
+ agent_id: agentId,
158
+ agent_manifest_version: agentManifestVersion,
159
+ granted_at: (/* @__PURE__ */ new Date()).toISOString()
160
+ };
161
+ return signPayload(attestation, coachKeyPair);
162
+ }
163
+ function createStatusRecord(signerKeyPair, targetId, status, reason) {
164
+ const record = {
165
+ type: "scopeblind:status-record",
166
+ target_id: targetId,
167
+ status,
168
+ changed_at: (/* @__PURE__ */ new Date()).toISOString(),
169
+ issuer_id: signerKeyPair.kid,
170
+ ...reason ? { reason } : {}
171
+ };
172
+ return signPayload(record, signerKeyPair);
173
+ }
174
+ function createCoachContribution(coachKeyPair, battleId, coachedSide, noteHash, noteLength, upliftVerdict) {
175
+ const contribution = {
176
+ type: "scopeblind:coach-contribution",
177
+ battle_id: battleId,
178
+ coach_id: coachKeyPair.kid,
179
+ coached_side: coachedSide,
180
+ note_hash: noteHash,
181
+ note_length: noteLength,
182
+ ...upliftVerdict ? { uplift_verdict: upliftVerdict } : {}
183
+ };
184
+ return signPayload(contribution, coachKeyPair);
185
+ }
186
+
187
+ // src/verify.ts
188
+ import { ed25519 as ed255193 } from "@noble/curves/ed25519";
189
+ import { hexToBytes as hexToBytes2, utf8ToBytes as utf8ToBytes2 } from "@noble/hashes/utils";
190
+ function verifyEnvelope(envelope, expectedPublicKey) {
191
+ try {
192
+ if (!envelope.signature?.sig || !envelope.signature?.kid) {
193
+ return { valid: false, error: "missing_signature" };
194
+ }
195
+ const message = utf8ToBytes2(canonicalize(envelope.payload));
196
+ const hash = canonicalHash(envelope.payload);
197
+ const payload = envelope.payload;
198
+ let publicKeyBytes;
199
+ if (expectedPublicKey) {
200
+ publicKeyBytes = expectedPublicKey;
201
+ } else if (typeof payload.public_key === "string") {
202
+ publicKeyBytes = base58Decode(payload.public_key);
203
+ } else {
204
+ return { valid: false, error: "no_public_key_available" };
205
+ }
206
+ if (expectedPublicKey) {
207
+ const expectedKid = base58Encode(expectedPublicKey);
208
+ const signerFingerprint = envelope.signature.kid.split(":")[2];
209
+ const expectedFingerprint = expectedKid.slice(0, 12);
210
+ if (signerFingerprint !== expectedFingerprint) {
211
+ return { valid: false, hash, error: "kid_mismatch" };
212
+ }
213
+ }
214
+ const valid = ed255193.verify(
215
+ hexToBytes2(envelope.signature.sig),
216
+ message,
217
+ publicKeyBytes
218
+ );
219
+ return valid ? { valid: true, hash } : { valid: false, hash, error: "invalid_signature" };
220
+ } catch (err) {
221
+ return {
222
+ valid: false,
223
+ error: `verification_error: ${err instanceof Error ? err.message : "unknown"}`
224
+ };
225
+ }
226
+ }
227
+ function verifyManifest(envelope) {
228
+ return verifyEnvelope(envelope);
229
+ }
230
+ function verifyOwnership(attestation, coachPublicKey) {
231
+ const result = verifyEnvelope(attestation, coachPublicKey);
232
+ if (!result.valid) return result;
233
+ return result;
234
+ }
235
+ function base58Decode(str) {
236
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
237
+ let num = 0n;
238
+ for (const char of str) {
239
+ const idx = ALPHABET.indexOf(char);
240
+ if (idx === -1) throw new Error(`Invalid base58 character: ${char}`);
241
+ num = num * 58n + BigInt(idx);
242
+ }
243
+ const hex = num.toString(16).padStart(64, "0");
244
+ const bytes = new Uint8Array(32);
245
+ for (let i = 0; i < 32; i++) {
246
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
247
+ }
248
+ return bytes;
249
+ }
250
+
251
+ // src/portable.ts
252
+ import { bytesToHex as bytesToHex3, hexToBytes as hexToBytes3 } from "@noble/hashes/utils";
253
+ import { ed25519 as ed255194 } from "@noble/curves/ed25519";
254
+ function exportPassportBundle(bundle) {
255
+ return {
256
+ v: 1,
257
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
258
+ passport: {
259
+ kid: bundle.key.kid,
260
+ role: bundle.key.role,
261
+ created_at: bundle.key.created_at,
262
+ publicKeyHex: bytesToHex3(bundle.key.publicKey),
263
+ secretKeyHex: bytesToHex3(bundle.key.secretKey)
264
+ },
265
+ manifests: bundle.manifests,
266
+ ownership_attestations: bundle.ownership_attestations,
267
+ status_records: bundle.status_records
268
+ };
269
+ }
270
+ function serializeBundle(bundle) {
271
+ return JSON.stringify(bundle, null, 2);
272
+ }
273
+ function importPassportBundle(json) {
274
+ const errors = [];
275
+ const warnings = [];
276
+ let raw;
277
+ try {
278
+ raw = JSON.parse(json);
279
+ } catch {
280
+ return { valid: false, errors: ["Invalid JSON"], warnings };
281
+ }
282
+ const data = raw;
283
+ if (data.v !== 1) {
284
+ errors.push(`Unsupported bundle version: ${data.v}`);
285
+ return { valid: false, errors, warnings };
286
+ }
287
+ const passport = data.passport;
288
+ if (!passport) {
289
+ errors.push("Missing passport field");
290
+ return { valid: false, errors, warnings };
291
+ }
292
+ if (typeof passport.publicKeyHex !== "string" || typeof passport.secretKeyHex !== "string") {
293
+ errors.push("Missing or invalid key hex values");
294
+ return { valid: false, errors, warnings };
295
+ }
296
+ if (typeof passport.kid !== "string" || !passport.kid.startsWith("sb:")) {
297
+ errors.push("Invalid passport kid format");
298
+ return { valid: false, errors, warnings };
299
+ }
300
+ const role = passport.role;
301
+ if (role !== "coach" && role !== "agent") {
302
+ errors.push(`Invalid role: ${role}`);
303
+ return { valid: false, errors, warnings };
304
+ }
305
+ let publicKey;
306
+ let secretKey;
307
+ try {
308
+ publicKey = hexToBytes3(passport.publicKeyHex);
309
+ secretKey = hexToBytes3(passport.secretKeyHex);
310
+ } catch {
311
+ errors.push("Failed to decode key hex values");
312
+ return { valid: false, errors, warnings };
313
+ }
314
+ if (publicKey.length !== 32) {
315
+ errors.push(`Invalid public key length: ${publicKey.length} (expected 32)`);
316
+ return { valid: false, errors, warnings };
317
+ }
318
+ if (secretKey.length !== 64) {
319
+ errors.push(`Invalid secret key length: ${secretKey.length} (expected 64)`);
320
+ return { valid: false, errors, warnings };
321
+ }
322
+ const seed = secretKey.slice(0, 32);
323
+ let derivedPublicKey;
324
+ try {
325
+ derivedPublicKey = ed255194.getPublicKey(seed);
326
+ } catch {
327
+ errors.push("Secret key seed is not a valid Ed25519 private key");
328
+ return { valid: false, errors, warnings };
329
+ }
330
+ if (bytesToHex3(derivedPublicKey) !== bytesToHex3(publicKey)) {
331
+ errors.push("Key coherence failure: secret key does not derive to the claimed public key");
332
+ return { valid: false, errors, warnings };
333
+ }
334
+ const expectedKid = derivePassportId(publicKey, role);
335
+ if (passport.kid !== expectedKid) {
336
+ errors.push(`Kid mismatch: bundle claims ${passport.kid} but public key derives to ${expectedKid}`);
337
+ return { valid: false, errors, warnings };
338
+ }
339
+ const key = {
340
+ publicKey,
341
+ secretKey,
342
+ kid: passport.kid,
343
+ role,
344
+ created_at: passport.created_at || (/* @__PURE__ */ new Date()).toISOString()
345
+ };
346
+ const rawManifests = Array.isArray(data.manifests) ? data.manifests : [];
347
+ const validManifests = [];
348
+ for (const m of rawManifests) {
349
+ const result = verifyManifest(m);
350
+ if (!result.valid) {
351
+ errors.push(`Manifest ${m.payload?.id || "unknown"} has invalid signature: ${result.error}`);
352
+ continue;
353
+ }
354
+ if (m.payload?.id && m.payload.id !== key.kid) {
355
+ warnings.push(`Dropped manifest ${m.payload.id}: does not belong to passport ${key.kid}`);
356
+ continue;
357
+ }
358
+ validManifests.push(m);
359
+ }
360
+ const bundle = {
361
+ key,
362
+ manifests: validManifests,
363
+ ownership_attestations: Array.isArray(data.ownership_attestations) ? data.ownership_attestations : [],
364
+ status_records: Array.isArray(data.status_records) ? data.status_records : []
365
+ };
366
+ return {
367
+ valid: errors.length === 0,
368
+ bundle,
369
+ errors,
370
+ warnings
371
+ };
372
+ }
373
+
374
+ export {
375
+ formatKidShort,
376
+ formatKidLabeled,
377
+ getCoachSummary,
378
+ getAgentSummary,
379
+ isCoachManifest,
380
+ isAgentManifest,
381
+ base58Encode,
382
+ derivePassportId,
383
+ generatePassportKey,
384
+ getSigningKey,
385
+ getVerifyKey,
386
+ hashString,
387
+ canonicalize,
388
+ canonicalHash,
389
+ signPayload,
390
+ createCoachManifest,
391
+ createAgentManifest,
392
+ createOwnershipAttestation,
393
+ createStatusRecord,
394
+ createCoachContribution,
395
+ verifyEnvelope,
396
+ verifyManifest,
397
+ verifyOwnership,
398
+ exportPassportBundle,
399
+ serializeBundle,
400
+ importPassportBundle
401
+ };
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node