midsummer-sol 0.1.4 → 0.2.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.
Files changed (5) hide show
  1. package/index.js +818 -51
  2. package/package.json +9 -33
  3. package/sol-mcp.js +373 -28
  4. package/sol-secret-mcp.js +3251 -0
  5. package/sol.js +9644 -2065
package/index.js CHANGED
@@ -11,7 +11,10 @@ function hashNode(node) {
11
11
  canon = node.encoding ? `blob\x00${node.encoding}\x00${node.content}` : `blob\x00${node.content}`;
12
12
  } else if (node.kind === "sealed")
13
13
  canon = `sealed\x00${node.box}`;
14
- else
14
+ else if (node.kind === "prov") {
15
+ const f = (k, v) => v === undefined ? "" : `\x00${k}\x00${v}`;
16
+ canon = "prov" + f("agentId", node.agentId) + f("sessionId", node.sessionId) + f("model", node.model) + f("intent", node.intent) + f("tool", node.tool) + f("rationale", node.rationale) + f("parent", node.parent);
17
+ } else
15
18
  canon = "tree\x00" + Object.keys(node.entries).sort().map((k) => {
16
19
  const e = node.entries[k];
17
20
  return e.mode === undefined ? `${k}\x00${e.kind}\x00${e.hash}` : `${k}\x00${e.kind}\x00${e.hash}\x00${e.mode}`;
@@ -44,13 +47,161 @@ class Store {
44
47
  return this.objects.size;
45
48
  }
46
49
  }
50
+ // src/struct.ts
51
+ import { randomBytes } from "node:crypto";
52
+
53
+ // src/async-store.ts
54
+ async function getTree(store, hash) {
55
+ const node = await store.get(hash);
56
+ if (!node || node.kind !== "tree")
57
+ throw new Error(`not a tree: ${hash}`);
58
+ return node;
59
+ }
60
+
61
+ class MemoryAsyncStore {
62
+ nodes = new Map;
63
+ async put(node) {
64
+ const h = hashNode(node);
65
+ if (!this.nodes.has(h))
66
+ this.nodes.set(h, node);
67
+ return h;
68
+ }
69
+ async get(hash) {
70
+ return this.nodes.get(hash);
71
+ }
72
+ async has(hash) {
73
+ return this.nodes.has(hash);
74
+ }
75
+ get size() {
76
+ return this.nodes.size;
77
+ }
78
+ }
79
+
80
+ // src/struct.ts
81
+ var SLOT_PREFIX = "\x00slot\x00";
82
+ var HIDDEN_KEY = "\x00hidden";
83
+ function isReservedKey(key) {
84
+ return key.startsWith("\x00");
85
+ }
86
+ function slotKey(slotId) {
87
+ return SLOT_PREFIX + slotId;
88
+ }
89
+ function slotIdFromKey(key) {
90
+ return key.startsWith(SLOT_PREFIX) ? key.slice(SLOT_PREFIX.length) : undefined;
91
+ }
92
+ function mintSlotId() {
93
+ return randomBytes(16).toString("base64url");
94
+ }
95
+ var STRUCT_HEADER = "__solStruct";
96
+ function parseStruct(plaintext) {
97
+ if (!plaintext.startsWith("{"))
98
+ return;
99
+ let v;
100
+ try {
101
+ v = JSON.parse(plaintext);
102
+ } catch {
103
+ return;
104
+ }
105
+ if (!v || typeof v !== "object")
106
+ return;
107
+ const tag = v[STRUCT_HEADER];
108
+ if (tag === "subtree" || tag === "name" || tag === "entries")
109
+ return v;
110
+ return;
111
+ }
112
+ function nextBucket(n) {
113
+ let b = 256;
114
+ while (b < n)
115
+ b *= 2;
116
+ return b;
117
+ }
118
+ function padPlaintext(p) {
119
+ const bare = JSON.stringify(p);
120
+ const target = nextBucket(bare.length);
121
+ if (bare.length >= target)
122
+ return bare;
123
+ const overhead = `,"${"__pad"}":""`.length;
124
+ const fill = Math.max(0, target - bare.length - overhead);
125
+ return JSON.stringify({ ...p, __pad: " ".repeat(fill) });
126
+ }
127
+ async function collectAsync(store, hash, into) {
128
+ if (into[hash])
129
+ return;
130
+ const node = await store.get(hash);
131
+ if (!node)
132
+ throw new Error(`sealSubtree: node missing from store: ${hash}`);
133
+ into[hash] = node;
134
+ if (node.kind === "tree")
135
+ for (const e of Object.values(node.entries))
136
+ await collectAsync(store, e.hash, into);
137
+ }
138
+ async function serializeSubtreeAsync(store, dirRoot) {
139
+ const nodes = {};
140
+ await collectAsync(store, dirRoot, nodes);
141
+ return padPlaintext({ __solStruct: "subtree", bundle: { root: dirRoot, nodes } });
142
+ }
143
+ async function subtreeRootAt(store, root, dirPath) {
144
+ const segs = dirPath.split("/").filter(Boolean);
145
+ let cur = root;
146
+ for (let i = 0;i < segs.length; i++) {
147
+ const entry = (await getTree(store, cur)).entries[segs[i]];
148
+ if (!entry || entry.kind !== "tree")
149
+ return;
150
+ cur = entry.hash;
151
+ }
152
+ return cur;
153
+ }
154
+ async function entryAt(store, root, path) {
155
+ const segs = path.split("/").filter(Boolean);
156
+ let cur = root;
157
+ for (let i = 0;i < segs.length; i++) {
158
+ const entry = (await getTree(store, cur)).entries[segs[i]];
159
+ if (!entry)
160
+ return;
161
+ if (i === segs.length - 1)
162
+ return entry;
163
+ if (entry.kind !== "tree")
164
+ return;
165
+ cur = entry.hash;
166
+ }
167
+ return;
168
+ }
169
+ async function serializeNameAsync(store, name, entry) {
170
+ const nodes = {};
171
+ await collectAsync(store, entry.hash, nodes);
172
+ const payload = {
173
+ __solStruct: "name",
174
+ name,
175
+ entryKind: entry.kind,
176
+ ...entry.mode === undefined ? {} : { entryMode: entry.mode },
177
+ bundle: { root: entry.hash, nodes }
178
+ };
179
+ return padPlaintext(payload);
180
+ }
181
+ async function hiddenEntryAsync(store, entry) {
182
+ const nodes = {};
183
+ await collectAsync(store, entry.hash, nodes);
184
+ return { entryKind: entry.kind, ...entry.mode === undefined ? {} : { entryMode: entry.mode }, bundle: { root: entry.hash, nodes } };
185
+ }
186
+ function serializeHiddenEntries(slots) {
187
+ return padPlaintext({ __solStruct: "entries", slots });
188
+ }
189
+
47
190
  // src/tree.ts
48
191
  var EMPTY_TREE = { kind: "tree", entries: {} };
49
192
  function emptyRoot(store) {
50
193
  return store.put(EMPTY_TREE);
51
194
  }
52
195
  function segments(path) {
53
- return path.split("/").filter(Boolean);
196
+ const segs = path.split("/").filter(Boolean);
197
+ for (const s of segs) {
198
+ if (!s.includes("\x00"))
199
+ continue;
200
+ const reserved = s.startsWith("\x00slot\x00") ? s.slice("\x00slot\x00".length) : s === "\x00hidden" ? "" : undefined;
201
+ if (reserved === undefined || reserved.includes("\x00"))
202
+ throw new Error("illegal NUL byte in path");
203
+ }
204
+ return segs;
54
205
  }
55
206
  function writeFile(store, root, path, content, opts = {}) {
56
207
  const blob = opts.encoding ? { kind: "blob", content, encoding: opts.encoding } : { kind: "blob", content };
@@ -84,6 +235,21 @@ function fileAt(store, root, path) {
84
235
  }
85
236
  return;
86
237
  }
238
+ function sealedBoxAt(store, root, path) {
239
+ const segs = segments(path);
240
+ let cur = root;
241
+ for (let i = 0;i < segs.length; i++) {
242
+ const entry = store.getTree(cur).entries[segs[i]];
243
+ if (!entry)
244
+ return;
245
+ if (i === segs.length - 1)
246
+ return entry.kind === "sealed" ? store.get(entry.hash).box : undefined;
247
+ if (entry.kind !== "tree")
248
+ return;
249
+ cur = entry.hash;
250
+ }
251
+ return;
252
+ }
87
253
  function modeAt(store, root, path) {
88
254
  const segs = segments(path);
89
255
  let cur = root;
@@ -135,6 +301,10 @@ function listAll(store, root, prefix = "") {
135
301
  const tree = store.getTree(root);
136
302
  const out = [];
137
303
  for (const [name, entry] of Object.entries(tree.entries)) {
304
+ if (isReservedKey(name)) {
305
+ out.push(prefix ? `${prefix}/${name}` : name);
306
+ continue;
307
+ }
138
308
  const p = prefix ? `${prefix}/${name}` : name;
139
309
  if (entry.kind === "tree")
140
310
  out.push(...listAll(store, entry.hash, p));
@@ -198,7 +368,7 @@ function covers(prefix, path) {
198
368
  return path === prefix || path.startsWith(prefix + "/");
199
369
  }
200
370
  // src/crypto.ts
201
- import { createCipheriv, createDecipheriv, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes, timingSafeEqual } from "node:crypto";
371
+ import { createCipheriv, createDecipheriv, createHash as createHash2, createPrivateKey, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes as randomBytes2, scryptSync, sign as edSign, timingSafeEqual, verify as edVerify } from "node:crypto";
202
372
  var UNREADABLE = "<<unreadable>>";
203
373
 
204
374
  class KeyRing {
@@ -206,7 +376,7 @@ class KeyRing {
206
376
  ensure(actor) {
207
377
  let k = this.keys.get(actor);
208
378
  if (!k) {
209
- k = randomBytes(32);
379
+ k = randomBytes2(32);
210
380
  this.keys.set(actor, k);
211
381
  }
212
382
  return k;
@@ -226,7 +396,7 @@ class KeyRing {
226
396
  }
227
397
  }
228
398
  function aeadSeal(key, plain) {
229
- const iv = randomBytes(12);
399
+ const iv = randomBytes2(12);
230
400
  const c = createCipheriv("aes-256-gcm", key, iv);
231
401
  const ct = Buffer.concat([c.update(plain), c.final()]);
232
402
  return { iv: iv.toString("base64"), ct: ct.toString("base64"), tag: c.getAuthTag().toString("base64") };
@@ -237,14 +407,25 @@ function aeadOpen(key, box) {
237
407
  return Buffer.concat([d.update(Buffer.from(box.ct, "base64")), d.final()]);
238
408
  }
239
409
  function sealContent(ring, content, recipients, epoch = 1) {
240
- const cek = randomBytes(32);
410
+ const cek = randomBytes2(32);
241
411
  const body = aeadSeal(cek, Buffer.from(content, "utf8"));
242
412
  const lockboxes = {};
243
413
  for (const r of recipients)
244
414
  lockboxes[r] = aeadSeal(ring.ensure(r), cek);
245
415
  return { body, lockboxes, epoch };
246
416
  }
247
- function openContent(ring, box, actor) {
417
+ function openContent(ring, box, actor, self) {
418
+ if (self) {
419
+ const alb = box.asym?.[self.accountId];
420
+ if (alb) {
421
+ try {
422
+ const cek = unwrapCekWith(self.privateKey, alb);
423
+ return aeadOpen(cek, box.body).toString("utf8");
424
+ } catch {
425
+ return UNREADABLE;
426
+ }
427
+ }
428
+ }
248
429
  const lb = box.lockboxes[actor];
249
430
  const key = ring.key(actor);
250
431
  if (!lb || !key)
@@ -257,7 +438,7 @@ function openContent(ring, box, actor) {
257
438
  }
258
439
  }
259
440
  function isRecipient(box, actor) {
260
- return actor in box.lockboxes;
441
+ return actor in box.lockboxes || !!box.asym?.[actor];
261
442
  }
262
443
  function ciphertextOf(box) {
263
444
  return box.body.ct;
@@ -295,7 +476,7 @@ function unwrapCekWith(privateKey, lb) {
295
476
  return aeadOpen(deriveWrapKey(shared, lb.epk, recipientPubB64), lb.box);
296
477
  }
297
478
  function sealWithRecovery(ring, content, recipients, recoveryPubKeys = {}, epoch = 1) {
298
- const cek = randomBytes(32);
479
+ const cek = randomBytes2(32);
299
480
  const body = aeadSeal(cek, Buffer.from(content, "utf8"));
300
481
  const lockboxes = {};
301
482
  for (const r of recipients)
@@ -316,6 +497,68 @@ function openWithRecovery(privateKey, box, recoveryId) {
316
497
  return UNREADABLE;
317
498
  }
318
499
  }
500
+ function sealToAccounts(content, recipientPubKeys, opts = {}) {
501
+ const cek = randomBytes2(32);
502
+ const body = aeadSeal(cek, Buffer.from(content, "utf8"));
503
+ const asym = {};
504
+ for (const [acct, pub] of Object.entries(recipientPubKeys))
505
+ asym[acct] = wrapCekTo(pub, cek);
506
+ const lockboxes = {};
507
+ if (opts.ring && opts.localRecipients)
508
+ for (const r of opts.localRecipients)
509
+ lockboxes[r] = aeadSeal(opts.ring.ensure(r), cek);
510
+ const recovery = {};
511
+ for (const [id, pub] of Object.entries(opts.recoveryPubKeys ?? {}))
512
+ recovery[id] = wrapCekTo(pub, cek);
513
+ const box = { body, lockboxes, asym, epoch: opts.epoch ?? 1 };
514
+ if (Object.keys(recovery).length)
515
+ box.recovery = recovery;
516
+ return box;
517
+ }
518
+ var X25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b656e04220420", "hex");
519
+ var ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
520
+ var KDF_PARAMS = { N: 1 << 15, r: 8, p: 1, maxmem: 64 * 1024 * 1024 };
521
+ function x25519FromSeed(seed) {
522
+ const privateKey = createPrivateKey({ key: Buffer.concat([X25519_PKCS8_PREFIX, seed]), format: "der", type: "pkcs8" });
523
+ return { publicKey: createPublicKey(privateKey).export({ type: "spki", format: "der" }).toString("base64"), privateKey };
524
+ }
525
+ function ed25519FromSeed(seed) {
526
+ const privateKey = createPrivateKey({ key: Buffer.concat([ED25519_PKCS8_PREFIX, seed]), format: "der", type: "pkcs8" });
527
+ return { publicKey: createPublicKey(privateKey).export({ type: "spki", format: "der" }).toString("base64"), privateKey };
528
+ }
529
+ var WORDLIST = "abandon ability able about above absorb abstract access accident account accuse achieve acid acoustic acquire across action actor actual adapt add address adjust admit adult advance advice aerobic affair afford afraid again age agent agree ahead aim air airport aisle alarm album alert alien all alley allow almost alone alpha already also alter always amateur amazing among amount amused anchor ancient anger angle angry animal ankle announce annual another answer antenna antique anxiety apart apology appear apple approve april arch arctic area arena argue arm armed armor army around arrange arrest arrive arrow art artist artwork ask aspect assault asset assist assume asthma athlete atom attack attend attitude attract auction audit august aunt author auto autumn average avocado avoid awake aware away awesome awful awkward axis".split(" ");
530
+ function generateRecoveryCode(words = 12) {
531
+ const out = [];
532
+ for (let i = 0;i < words; i++)
533
+ out.push(WORDLIST[randomBytes2(2).readUInt16BE(0) % WORDLIST.length]);
534
+ return out.join(" ");
535
+ }
536
+ function deriveIdentity(accountId, recoveryCode, keyEpoch = 1) {
537
+ const salt = Buffer.from(`sol/identity/v1\x00${accountId}\x00epoch=${keyEpoch}`);
538
+ const xSeed = scryptSync(recoveryCode.normalize("NFKD"), Buffer.concat([salt, Buffer.from("\x00x25519")]), 32, KDF_PARAMS);
539
+ const eSeed = scryptSync(recoveryCode.normalize("NFKD"), Buffer.concat([salt, Buffer.from("\x00ed25519")]), 32, KDF_PARAMS);
540
+ const x = x25519FromSeed(xSeed);
541
+ const e = ed25519FromSeed(eSeed);
542
+ return { accountId, x25519Pub: x.publicKey, x25519Priv: x.privateKey, edPub: e.publicKey, edPriv: e.privateKey, keyEpoch };
543
+ }
544
+ function pubkeyBindingBytes(accountId, x25519Pub, keyEpoch) {
545
+ return Buffer.from(`sol/keydir/v1\x00${accountId}\x00${x25519Pub}\x00epoch=${keyEpoch}`);
546
+ }
547
+ function signPubkey(id) {
548
+ return edSign(null, pubkeyBindingBytes(id.accountId, id.x25519Pub, id.keyEpoch), id.edPriv).toString("base64");
549
+ }
550
+ function verifyPubkeySig(entry) {
551
+ try {
552
+ const edPub = createPublicKey({ key: Buffer.from(entry.edPub, "base64"), type: "spki", format: "der" });
553
+ return edVerify(null, pubkeyBindingBytes(entry.accountId, entry.x25519Pub, entry.keyEpoch), edPub, Buffer.from(entry.sig, "base64"));
554
+ } catch {
555
+ return false;
556
+ }
557
+ }
558
+ function pubFingerprint(x25519Pub) {
559
+ const h = createHash2("sha256").update(Buffer.from(x25519Pub, "base64")).digest("hex");
560
+ return h.slice(0, 32).match(/.{4}/g).join("-");
561
+ }
319
562
  // src/log.ts
320
563
  function groupIntoUnits(ops) {
321
564
  const units = [];
@@ -354,7 +597,7 @@ function filterLog(ops, opts = {}) {
354
597
  }
355
598
  function formatLog(ops) {
356
599
  return ops.map((op) => {
357
- const by = op.by ?? "?";
600
+ const by = op.kind === "agent" ? `agent:${op.by ?? "?"}` : op.by ?? "?";
358
601
  const head = `${op.seq} ${by} ${op.type} ${op.path}`;
359
602
  return op.message ? `${head}: ${op.message}` : head;
360
603
  });
@@ -600,11 +843,11 @@ function resolvePath(base, ours, theirs) {
600
843
  const m = merge3(base, ours, theirs);
601
844
  if (m.clean)
602
845
  return { content: m.text };
603
- return { content: m.text, conflict: { path: "", ours, theirs } };
846
+ return { content: m.text, conflict: { path: "", base, ours, theirs } };
604
847
  }
605
848
  return {
606
849
  content: ours,
607
- conflict: { path: "", ours, theirs }
850
+ conflict: { path: "", base, ours, theirs }
608
851
  };
609
852
  }
610
853
  function merge(repo, baseHead, oursHead, theirsHead) {
@@ -617,6 +860,14 @@ function merge(repo, baseHead, oursHead, theirsHead) {
617
860
  const conflicts = [];
618
861
  let root = emptyRoot(store);
619
862
  for (const path of [...paths].sort()) {
863
+ if (entryKindAt(store, baseHead, path) === "sealed" || entryKindAt(store, oursHead, path) === "sealed" || entryKindAt(store, theirsHead, path) === "sealed") {
864
+ const { box, conflict: conflict2 } = resolveSealed(store, baseHead, oursHead, theirsHead, path);
865
+ if (conflict2)
866
+ conflicts.push({ ...conflict2, path });
867
+ if (box !== undefined)
868
+ root = writeSealedLeaf(store, root, path, box);
869
+ continue;
870
+ }
620
871
  const base = readFile(store, baseHead, path);
621
872
  const ours = readFile(store, oursHead, path);
622
873
  const theirs = readFile(store, theirsHead, path);
@@ -629,6 +880,40 @@ function merge(repo, baseHead, oursHead, theirsHead) {
629
880
  }
630
881
  return { head: root, conflicts };
631
882
  }
883
+ function resolveSealed(store, baseHead, oursHead, theirsHead, path) {
884
+ const base = sealedBoxAt(store, baseHead, path);
885
+ const ours = sealedBoxAt(store, oursHead, path);
886
+ const theirs = sealedBoxAt(store, theirsHead, path);
887
+ const oursChanged = ours !== base;
888
+ const theirsChanged = theirs !== base;
889
+ if (!oursChanged && !theirsChanged)
890
+ return { box: base };
891
+ if (oursChanged && !theirsChanged)
892
+ return { box: ours };
893
+ if (!oursChanged && theirsChanged)
894
+ return { box: theirs };
895
+ if (ours === theirs)
896
+ return { box: ours };
897
+ return { box: ours, conflict: { path: "", base, ours, theirs, sealed: true } };
898
+ }
899
+ function writeSealedLeaf(store, root, path, box) {
900
+ const segs = path.split("/").filter(Boolean);
901
+ const hash = store.put({ kind: "sealed", box });
902
+ const go = (treeHash, segments2) => {
903
+ const tree = store.getTree(treeHash);
904
+ const entries = { ...tree.entries };
905
+ const [head, ...rest] = segments2;
906
+ if (rest.length === 0)
907
+ entries[head] = { kind: "sealed", hash };
908
+ else {
909
+ const child = entries[head];
910
+ const childHash = child?.kind === "tree" ? child.hash : emptyRoot(store);
911
+ entries[head] = { kind: "tree", hash: go(childHash, rest) };
912
+ }
913
+ return store.put({ kind: "tree", entries });
914
+ };
915
+ return go(root, segs);
916
+ }
632
917
  // src/labels.ts
633
918
  class Labels {
634
919
  labels = new Map;
@@ -763,32 +1048,6 @@ function cmdFork(view) {
763
1048
  function short(hash) {
764
1049
  return hash.slice(0, 12);
765
1050
  }
766
- // src/async-store.ts
767
- async function getTree(store, hash) {
768
- const node = await store.get(hash);
769
- if (!node || node.kind !== "tree")
770
- throw new Error(`not a tree: ${hash}`);
771
- return node;
772
- }
773
-
774
- class MemoryAsyncStore {
775
- nodes = new Map;
776
- async put(node) {
777
- const h = hashNode(node);
778
- if (!this.nodes.has(h))
779
- this.nodes.set(h, node);
780
- return h;
781
- }
782
- async get(hash) {
783
- return this.nodes.get(hash);
784
- }
785
- async has(hash) {
786
- return this.nodes.has(hash);
787
- }
788
- get size() {
789
- return this.nodes.size;
790
- }
791
- }
792
1051
  // src/attest.ts
793
1052
  import { createHmac, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
794
1053
  function canonicalOp(op) {
@@ -798,7 +1057,9 @@ function canonicalOp(op) {
798
1057
  path: op.path,
799
1058
  rootAfter: op.rootAfter,
800
1059
  by: op.by ?? null,
801
- message: op.message ?? null
1060
+ message: op.message ?? null,
1061
+ ...op.kind !== undefined ? { kind: op.kind } : {},
1062
+ ...op.prov !== undefined ? { prov: op.prov } : {}
802
1063
  });
803
1064
  }
804
1065
  function signOp(key, op) {
@@ -816,6 +1077,31 @@ function verifyOp(key, op, sig) {
816
1077
  return false;
817
1078
  return timingSafeEqual2(expected, claimed);
818
1079
  }
1080
+ function canonicalAtt(op) {
1081
+ return JSON.stringify({
1082
+ entryHash: op.entryHash ?? null,
1083
+ prov: op.prov ?? null,
1084
+ pushedBy: op.pushedBy ?? null,
1085
+ at: op.at ?? null
1086
+ });
1087
+ }
1088
+ function attestOp(key, op) {
1089
+ return createHmac("sha256", key).update(canonicalAtt(op)).digest("base64");
1090
+ }
1091
+ function verifyAtt(key, op, sig = op.att) {
1092
+ if (!sig)
1093
+ return false;
1094
+ const expected = Buffer.from(attestOp(key, op), "base64");
1095
+ let claimed;
1096
+ try {
1097
+ claimed = Buffer.from(sig, "base64");
1098
+ } catch {
1099
+ return false;
1100
+ }
1101
+ if (expected.length !== claimed.length)
1102
+ return false;
1103
+ return timingSafeEqual2(expected, claimed);
1104
+ }
819
1105
 
820
1106
  // src/errors.ts
821
1107
  class CorruptRepoError extends Error {
@@ -835,7 +1121,15 @@ function chainOp(prevHash, op) {
835
1121
 
836
1122
  // src/async-tree.ts
837
1123
  function segments2(path) {
838
- return path.split("/").filter(Boolean);
1124
+ const segs = path.split("/").filter(Boolean);
1125
+ for (const s of segs) {
1126
+ if (!s.includes("\x00"))
1127
+ continue;
1128
+ const reserved = s.startsWith("\x00slot\x00") ? s.slice("\x00slot\x00".length) : s === "\x00hidden" ? "" : undefined;
1129
+ if (reserved === undefined || reserved.includes("\x00"))
1130
+ throw new Error("illegal NUL byte in path");
1131
+ }
1132
+ return segs;
839
1133
  }
840
1134
  async function emptyRoot2(store) {
841
1135
  return store.put({ kind: "tree", entries: {} });
@@ -849,6 +1143,70 @@ async function writeSealed(store, root, path, box) {
849
1143
  const hash = await store.put({ kind: "sealed", box });
850
1144
  return writeInto2(store, root, segments2(path), hash, "sealed");
851
1145
  }
1146
+ async function hideName(store, root, path, box, slotId) {
1147
+ const hash = await store.put({ kind: "sealed", box });
1148
+ return hideInto(store, root, segments2(path), hash, slotKey(slotId));
1149
+ }
1150
+ async function reHideName(store, root, dirSegs, key, box) {
1151
+ const hash = await store.put({ kind: "sealed", box });
1152
+ if (dirSegs.length === 0) {
1153
+ const tree2 = await getTree(store, root);
1154
+ return store.put({ kind: "tree", entries: { ...tree2.entries, [key]: { kind: "sealed", hash } } });
1155
+ }
1156
+ const tree = await getTree(store, root);
1157
+ const entries = { ...tree.entries };
1158
+ const [head, ...rest] = dirSegs;
1159
+ const child = entries[head];
1160
+ if (!child || child.kind !== "tree")
1161
+ return root;
1162
+ entries[head] = { kind: "tree", hash: await reHideName(store, child.hash, rest, key, box) };
1163
+ return store.put({ kind: "tree", entries });
1164
+ }
1165
+ async function hideInto(store, treeHash, segs, sealedHash, key) {
1166
+ const tree = await getTree(store, treeHash);
1167
+ const entries = { ...tree.entries };
1168
+ const [head, ...rest] = segs;
1169
+ if (rest.length === 0) {
1170
+ delete entries[head];
1171
+ entries[key] = { kind: "sealed", hash: sealedHash };
1172
+ } else {
1173
+ const child = entries[head];
1174
+ if (!child || child.kind !== "tree")
1175
+ return treeHash;
1176
+ entries[head] = { kind: "tree", hash: await hideInto(store, child.hash, rest, sealedHash, key) };
1177
+ }
1178
+ return store.put({ kind: "tree", entries });
1179
+ }
1180
+ async function hideExistence(store, root, path, hiddenBox) {
1181
+ const hash = await store.put({ kind: "sealed", box: hiddenBox });
1182
+ return hideExistenceInto(store, root, segments2(path), hash);
1183
+ }
1184
+ async function hideExistenceInto(store, treeHash, segs, sidecarHash) {
1185
+ const tree = await getTree(store, treeHash);
1186
+ const entries = { ...tree.entries };
1187
+ const [head, ...rest] = segs;
1188
+ if (rest.length === 0) {
1189
+ delete entries[head];
1190
+ entries[HIDDEN_KEY] = { kind: "sealed", hash: sidecarHash };
1191
+ } else {
1192
+ const child = entries[head];
1193
+ if (!child || child.kind !== "tree")
1194
+ return treeHash;
1195
+ entries[head] = { kind: "tree", hash: await hideExistenceInto(store, child.hash, rest, sidecarHash) };
1196
+ }
1197
+ return store.put({ kind: "tree", entries });
1198
+ }
1199
+ async function dirEntriesAt(store, root, dirPath) {
1200
+ const segs = segments2(dirPath);
1201
+ let cur = root;
1202
+ for (const seg of segs) {
1203
+ const entry = (await getTree(store, cur)).entries[seg];
1204
+ if (!entry || entry.kind !== "tree")
1205
+ return;
1206
+ cur = entry.hash;
1207
+ }
1208
+ return (await getTree(store, cur)).entries;
1209
+ }
852
1210
  async function writeInto2(store, treeHash, segs, hash, leafKind, mode) {
853
1211
  const tree = await getTree(store, treeHash);
854
1212
  const entries = { ...tree.entries };
@@ -976,6 +1334,10 @@ async function listAll2(store, root, prefix = "") {
976
1334
  const tree = await getTree(store, root);
977
1335
  const out = [];
978
1336
  for (const [name, entry] of Object.entries(tree.entries)) {
1337
+ if (isReservedKey(name)) {
1338
+ out.push(prefix ? `${prefix}/${name}` : name);
1339
+ continue;
1340
+ }
979
1341
  const p = prefix ? `${prefix}/${name}` : name;
980
1342
  if (entry.kind === "tree")
981
1343
  out.push(...await listAll2(store, entry.hash, p));
@@ -985,6 +1347,224 @@ async function listAll2(store, root, prefix = "") {
985
1347
  return out.sort();
986
1348
  }
987
1349
 
1350
+ // src/sign.ts
1351
+ import { createHash as createHash3, generateKeyPairSync as generateKeyPairSync2, createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2, sign as edSign2, verify as edVerify2 } from "node:crypto";
1352
+ function canonicalAuthor(op) {
1353
+ return JSON.stringify({
1354
+ type: op.type,
1355
+ path: op.path,
1356
+ rootAfter: op.rootAfter,
1357
+ by: op.by ?? null,
1358
+ message: op.message ?? null,
1359
+ ...op.kind !== undefined ? { kind: op.kind } : {},
1360
+ ...op.prov !== undefined ? { prov: op.prov } : {}
1361
+ });
1362
+ }
1363
+ function fingerprintOf(pub) {
1364
+ const der = Buffer.from(pub, "base64");
1365
+ return "sol1:" + createHash3("sha256").update(der).digest("hex").slice(0, 16);
1366
+ }
1367
+ var FINGERPRINT_RE = /^sol1:[0-9a-f]{16}$/;
1368
+ function normalizeFingerprint(fp) {
1369
+ const t = fp.trim();
1370
+ const body = (t.toLowerCase().startsWith("sol1:") ? t.slice(5) : t).replace(/[-:\s]/g, "").toLowerCase();
1371
+ const candidate = "sol1:" + body;
1372
+ return FINGERPRINT_RE.test(candidate) ? candidate : undefined;
1373
+ }
1374
+ function generateKeypair() {
1375
+ const { publicKey, privateKey } = generateKeyPairSync2("ed25519");
1376
+ const pub = publicKey.export({ type: "spki", format: "der" }).toString("base64");
1377
+ const seed = privateKey.export({ type: "pkcs8", format: "der" }).subarray(16).toString("base64");
1378
+ return { seed, pub, fingerprint: fingerprintOf(pub) };
1379
+ }
1380
+ var ED25519_PKCS8_PREFIX2 = Buffer.from("302e020100300506032b657004220420", "hex");
1381
+ function loadPrivateKey(secret) {
1382
+ const s = secret.trim();
1383
+ if (s.includes("-----BEGIN"))
1384
+ return createPrivateKey2(s);
1385
+ const raw = Buffer.from(s, "base64");
1386
+ if (raw.length === 32)
1387
+ return createPrivateKey2({ key: Buffer.concat([ED25519_PKCS8_PREFIX2, raw]), format: "der", type: "pkcs8" });
1388
+ return createPrivateKey2({ key: raw, format: "der", type: "pkcs8" });
1389
+ }
1390
+ function publicOf(priv) {
1391
+ return createPublicKey2(priv).export({ type: "spki", format: "der" }).toString("base64");
1392
+ }
1393
+ function signerFrom(secret) {
1394
+ const priv = loadPrivateKey(secret);
1395
+ const pub = publicOf(priv);
1396
+ return { priv, pub, fingerprint: fingerprintOf(pub) };
1397
+ }
1398
+ function signAuthor(signer, op) {
1399
+ const sig = edSign2(null, Buffer.from(canonicalAuthor(op)), signer.priv).toString("base64");
1400
+ return { sig, pub: signer.pub };
1401
+ }
1402
+ function verifyAuthor(op) {
1403
+ if (!op.sig || !op.pub)
1404
+ return { signed: false, valid: false };
1405
+ try {
1406
+ const ok = edVerify2(null, Buffer.from(canonicalAuthor(op)), createPublicKey2({ key: Buffer.from(op.pub, "base64"), format: "der", type: "spki" }), Buffer.from(op.sig, "base64"));
1407
+ return ok ? { signed: true, valid: true, fingerprint: fingerprintOf(op.pub) } : { signed: true, valid: false };
1408
+ } catch {
1409
+ return { signed: true, valid: false };
1410
+ }
1411
+ }
1412
+ function trustAuthor(op, trust = {}) {
1413
+ const v = verifyAuthor(op);
1414
+ const claimedActor = op.by ?? undefined;
1415
+ if (!v.signed)
1416
+ return { ...v, claimedActor, state: "unsigned" };
1417
+ if (!v.valid)
1418
+ return { ...v, claimedActor, state: "invalid" };
1419
+ const expected = claimedActor ? trust[claimedActor] : undefined;
1420
+ if (!expected)
1421
+ return { ...v, claimedActor, state: "untrusted" };
1422
+ return { ...v, claimedActor, state: expected === v.fingerprint ? "trusted" : "forged" };
1423
+ }
1424
+
1425
+ // src/prov.ts
1426
+ function provenanceFromEnv(env = process.env) {
1427
+ const kind = env.SOL_KIND === "agent" ? "agent" : "human";
1428
+ const node = buildProvNode({
1429
+ agentId: env.SOL_AGENT_ID || (kind === "agent" ? env.SOL_ACTOR : undefined),
1430
+ sessionId: env.SOL_SESSION,
1431
+ model: env.SOL_MODEL,
1432
+ intent: env.SOL_INTENT,
1433
+ tool: env.SOL_TOOL
1434
+ });
1435
+ return node ? { kind, node } : { kind };
1436
+ }
1437
+ function buildProvNode(fields) {
1438
+ const clean = { kind: "prov" };
1439
+ let any = false;
1440
+ for (const k of ["agentId", "sessionId", "model", "intent", "tool", "rationale", "parent"]) {
1441
+ const v = fields[k];
1442
+ if (v) {
1443
+ clean[k] = v;
1444
+ any = true;
1445
+ }
1446
+ }
1447
+ return any ? clean : undefined;
1448
+ }
1449
+ function provOf(op, reader) {
1450
+ if (!op.prov)
1451
+ return;
1452
+ const n = reader.get(op.prov);
1453
+ return n && n.kind === "prov" ? n : undefined;
1454
+ }
1455
+ function authorLabel(op, reader) {
1456
+ const name = op.by ?? "?";
1457
+ const p = reader ? provOf(op, reader) : undefined;
1458
+ if (op.kind !== "agent" && !p)
1459
+ return name;
1460
+ const who = op.kind === "agent" ? `agent ${p?.agentId ?? p?.model ?? name}` : name;
1461
+ const bits = [];
1462
+ if (p?.sessionId)
1463
+ bits.push(`session ${p.sessionId}`);
1464
+ const tail = p?.intent ? ` — intent: ${p.intent}` : "";
1465
+ return `${who}${bits.length ? ` (${bits.join(", ")})` : ""}${tail}`;
1466
+ }
1467
+ function provJson(op, reader, key, trust) {
1468
+ const p = reader ? provOf(op, reader) : undefined;
1469
+ if (op.kind === undefined && !p && op.att === undefined && op.pushedBy === undefined && op.sig === undefined)
1470
+ return;
1471
+ const out = { kind: op.kind ?? "human" };
1472
+ if (p) {
1473
+ for (const k of ["agentId", "sessionId", "model", "intent", "tool", "rationale", "parent"])
1474
+ if (p[k])
1475
+ out[k] = p[k];
1476
+ }
1477
+ if (op.sig !== undefined) {
1478
+ const t = trustAuthor(op, trust);
1479
+ out.signed = true;
1480
+ out.verified = t.valid;
1481
+ if (t.fingerprint)
1482
+ out.fingerprint = t.fingerprint;
1483
+ if (t.claimedActor !== undefined)
1484
+ out.claimedActor = t.claimedActor;
1485
+ out.trust = t.state;
1486
+ }
1487
+ if (op.pushedBy !== undefined)
1488
+ out.pushedBy = op.pushedBy;
1489
+ if (op.att !== undefined) {
1490
+ out.att = op.att;
1491
+ out.attested = key ? verifyAtt(key, op) : true;
1492
+ } else if (op.pushedBy !== undefined) {
1493
+ out.attested = false;
1494
+ }
1495
+ return out;
1496
+ }
1497
+ function attTag(op, key) {
1498
+ if (op.att === undefined)
1499
+ return "[unattested/local]";
1500
+ if (key && !verifyAtt(key, op))
1501
+ return "[att INVALID]";
1502
+ return `[attested pushedBy=${op.pushedBy ?? "?"}]`;
1503
+ }
1504
+ function signTag(op, trust) {
1505
+ const t = trustAuthor(op, trust);
1506
+ switch (t.state) {
1507
+ case "unsigned":
1508
+ return "unsigned";
1509
+ case "invalid":
1510
+ return "INVALID ✗";
1511
+ case "forged":
1512
+ return `FORGED ${t.claimedActor ?? "?"}!=${t.fingerprint} ✗`;
1513
+ case "trusted":
1514
+ return `signed ${t.fingerprint} ✓ (trusted ${t.claimedActor})`;
1515
+ default:
1516
+ return `signed ${t.fingerprint} ✓`;
1517
+ }
1518
+ }
1519
+ function signTrailerValue(op, trust) {
1520
+ const t = trustAuthor(op, trust);
1521
+ switch (t.state) {
1522
+ case "unsigned":
1523
+ return;
1524
+ case "invalid":
1525
+ return "INVALID";
1526
+ case "forged":
1527
+ return `${t.fingerprint} FORGED (claimed ${t.claimedActor ?? "?"})`;
1528
+ case "trusted":
1529
+ return `${t.fingerprint} verified (trusted ${t.claimedActor})`;
1530
+ default:
1531
+ return `${t.fingerprint} verified`;
1532
+ }
1533
+ }
1534
+ function provTrailers(op, reader, key, trust) {
1535
+ const p = reader ? provOf(op, reader) : undefined;
1536
+ const lines2 = [];
1537
+ const signed = trustAuthor(op, trust);
1538
+ const isAgent = op.kind === "agent" || signed.signed && signed.valid;
1539
+ if (isAgent)
1540
+ lines2.push("Sol-Kind: agent");
1541
+ const agentId = p?.agentId || (isAgent ? signed.fingerprint || op.by : undefined);
1542
+ if (agentId)
1543
+ lines2.push(`Sol-Agent: ${agentId}`);
1544
+ if (p?.sessionId)
1545
+ lines2.push(`Sol-Session: ${p.sessionId}`);
1546
+ if (p?.model)
1547
+ lines2.push(`Sol-Model: ${p.model}`);
1548
+ const intent = p?.intent || (isAgent ? op.message?.trim() || undefined : undefined);
1549
+ if (intent)
1550
+ lines2.push(`Sol-Intent: ${intent}`);
1551
+ const sign = signTrailerValue(op, trust);
1552
+ if (sign)
1553
+ lines2.push(`Sol-Sign: ${sign}`);
1554
+ if (op.att !== undefined && !(key && !verifyAtt(key, op)))
1555
+ lines2.push(`Sol-Attested: pushedBy=${op.pushedBy ?? "?"}`);
1556
+ return lines2;
1557
+ }
1558
+ async function captureProv(sink, env = process.env) {
1559
+ const { kind, node } = provenanceFromEnv(env);
1560
+ const out = {};
1561
+ if (kind === "agent")
1562
+ out.kind = "agent";
1563
+ if (node)
1564
+ out.prov = await sink.put(node);
1565
+ return out;
1566
+ }
1567
+
988
1568
  // src/async-repo.ts
989
1569
  function pageOps(ops, opts = {}) {
990
1570
  let out = opts.from !== undefined ? ops.filter((o) => o.seq >= opts.from) : ops.slice();
@@ -1016,15 +1596,27 @@ class MemoryOpLog {
1016
1596
  return pageOps(this.entries, opts);
1017
1597
  }
1018
1598
  }
1599
+ var noSign = (op) => op;
1019
1600
 
1020
1601
  class AsyncRepo {
1021
1602
  store;
1022
1603
  log;
1023
1604
  actor;
1024
- constructor(store, log, actor = "anon") {
1605
+ sign;
1606
+ constructor(store, log, actor = "anon", sign = noSign) {
1025
1607
  this.store = store;
1026
1608
  this.log = log;
1027
1609
  this.actor = actor;
1610
+ this.sign = sign;
1611
+ }
1612
+ async appendAuthored(entry, by) {
1613
+ await this.log.append(by === this.actor ? this.sign(entry) : entry);
1614
+ }
1615
+ provFor;
1616
+ async provenance(by) {
1617
+ if (by !== this.actor)
1618
+ return {};
1619
+ return this.provFor ??= captureProv(this.store);
1028
1620
  }
1029
1621
  async currentRoot() {
1030
1622
  const head = await this.log.head();
@@ -1041,7 +1633,7 @@ class AsyncRepo {
1041
1633
  }
1042
1634
  async writeFile(path, content, at = Date.now(), by = this.actor) {
1043
1635
  const rootAfter = await writeFile2(this.store, await this.currentRoot(), path, content);
1044
- await this.log.append({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by });
1636
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by, ...await this.provenance(by) }, by);
1045
1637
  return rootAfter;
1046
1638
  }
1047
1639
  async applyBatch(changes) {
@@ -1060,7 +1652,7 @@ class AsyncRepo {
1060
1652
  }
1061
1653
  async writeBytes(path, data, at = Date.now(), by = this.actor) {
1062
1654
  const rootAfter = await writeFile2(this.store, await this.currentRoot(), path, Buffer.from(data).toString("base64"), { encoding: "base64" });
1063
- await this.log.append({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by });
1655
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by, ...await this.provenance(by) }, by);
1064
1656
  return rootAfter;
1065
1657
  }
1066
1658
  async readBytes(path) {
@@ -1079,7 +1671,7 @@ class AsyncRepo {
1079
1671
  if (!f)
1080
1672
  return this.currentRoot();
1081
1673
  const rootAfter = await writeFile2(this.store, await this.currentRoot(), path, f.content, { encoding: f.encoding, mode });
1082
- await this.log.append({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by: this.actor });
1674
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by: this.actor, ...await this.provenance(this.actor) }, this.actor);
1083
1675
  return rootAfter;
1084
1676
  }
1085
1677
  async mode(path) {
@@ -1087,7 +1679,96 @@ class AsyncRepo {
1087
1679
  }
1088
1680
  async writeSealed(path, box, at = Date.now()) {
1089
1681
  const rootAfter = await writeSealed(this.store, await this.currentRoot(), path, box);
1090
- await this.log.append({ seq: await this.log.seq() + 1, type: "seal", path, rootAfter, at, by: this.actor });
1682
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "seal", path, rootAfter, at, by: this.actor, ...await this.provenance(this.actor) }, this.actor);
1683
+ return rootAfter;
1684
+ }
1685
+ async sealSubtree(dirPath, seal, at = Date.now()) {
1686
+ const subRoot = await subtreeRootAt(this.store, await this.currentRoot(), dirPath);
1687
+ if (subRoot === undefined)
1688
+ return;
1689
+ const plaintext = await serializeSubtreeAsync(this.store, subRoot);
1690
+ return this.writeSealed(dirPath, seal(plaintext), at);
1691
+ }
1692
+ async hideName(path, seal, opts = {}, at = Date.now()) {
1693
+ const root = await this.currentRoot();
1694
+ const segs = path.split("/").filter(Boolean);
1695
+ const name = segs[segs.length - 1];
1696
+ let entry = await entryAt(this.store, root, path);
1697
+ if (!entry) {
1698
+ if (opts.contentSeal && opts.absentContent !== undefined) {
1699
+ const sealedHash = await this.store.put({ kind: "sealed", box: opts.contentSeal(opts.absentContent) });
1700
+ entry = { kind: "sealed", hash: sealedHash };
1701
+ } else {
1702
+ return;
1703
+ }
1704
+ }
1705
+ if (slotIdFromKey(name) !== undefined)
1706
+ return slotIdFromKey(name);
1707
+ if (opts.contentSeal && entry.kind === "blob") {
1708
+ const blob = await this.store.get(entry.hash);
1709
+ if (blob && blob.kind === "blob") {
1710
+ const sealedHash = await this.store.put({ kind: "sealed", box: opts.contentSeal(blob.content) });
1711
+ entry = entry.mode === undefined ? { kind: "sealed", hash: sealedHash } : { kind: "sealed", hash: sealedHash, mode: entry.mode };
1712
+ }
1713
+ }
1714
+ const slotId = opts.slotId ?? mintSlotId();
1715
+ const plaintext = await serializeNameAsync(this.store, name, entry);
1716
+ const rootAfter = await hideName(this.store, root, path, seal(plaintext), slotId);
1717
+ const slotPath = segs.length > 1 ? `${segs.slice(0, -1).join("/")}/${slotKey(slotId)}` : slotKey(slotId);
1718
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "seal", path: slotPath, rootAfter, at, by: this.actor, ...await this.provenance(this.actor) }, this.actor);
1719
+ return slotId;
1720
+ }
1721
+ async reHideName(realDir, name, slotId, newContent, seal, contentSeal, at = Date.now()) {
1722
+ const root = await this.currentRoot();
1723
+ const sealedHash = await this.store.put({ kind: "sealed", box: contentSeal(newContent) });
1724
+ const childEntry = { kind: "sealed", hash: sealedHash };
1725
+ const plaintext = await serializeNameAsync(this.store, name, childEntry);
1726
+ const dirSegs = realDir.split("/").filter(Boolean);
1727
+ const rootAfter = await reHideName(this.store, root, dirSegs, slotKey(slotId), seal(plaintext));
1728
+ const slotPath = dirSegs.length ? `${dirSegs.join("/")}/${slotKey(slotId)}` : slotKey(slotId);
1729
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "seal", path: slotPath, rootAfter, at, by: this.actor, ...await this.provenance(this.actor) }, this.actor);
1730
+ return rootAfter;
1731
+ }
1732
+ async hideExistence(path, seal, opts = {}, at = Date.now()) {
1733
+ const root = await this.currentRoot();
1734
+ const segs = path.split("/").filter(Boolean);
1735
+ const name = segs[segs.length - 1];
1736
+ const dirPath = segs.slice(0, -1).join("/");
1737
+ let entry = await entryAt(this.store, root, path);
1738
+ if (name === HIDDEN_KEY)
1739
+ return root;
1740
+ if (!entry) {
1741
+ if (opts.contentSeal && opts.absentContent !== undefined) {
1742
+ const sealedHash = await this.store.put({ kind: "sealed", box: opts.contentSeal(opts.absentContent) });
1743
+ entry = { kind: "sealed", hash: sealedHash };
1744
+ } else {
1745
+ return;
1746
+ }
1747
+ }
1748
+ if (opts.contentSeal && entry.kind === "blob") {
1749
+ const blob = await this.store.get(entry.hash);
1750
+ if (blob && blob.kind === "blob") {
1751
+ const sealedHash = await this.store.put({ kind: "sealed", box: opts.contentSeal(blob.content) });
1752
+ entry = entry.mode === undefined ? { kind: "sealed", hash: sealedHash } : { kind: "sealed", hash: sealedHash, mode: entry.mode };
1753
+ }
1754
+ }
1755
+ const slots = {};
1756
+ const dirEntries = await dirEntriesAt(this.store, root, dirPath);
1757
+ const prior = dirEntries?.[HIDDEN_KEY];
1758
+ if (prior && prior.kind === "sealed" && opts.openPrior) {
1759
+ const priorNode = await this.store.get(prior.hash);
1760
+ if (priorNode && priorNode.kind === "sealed") {
1761
+ const opened = opts.openPrior(priorNode.box);
1762
+ const payload = opened === undefined ? undefined : parseStruct(opened);
1763
+ if (payload?.__solStruct === "entries")
1764
+ Object.assign(slots, payload.slots);
1765
+ }
1766
+ }
1767
+ slots[name] = await hiddenEntryAsync(this.store, entry);
1768
+ const plaintext = serializeHiddenEntries(slots);
1769
+ const rootAfter = await hideExistence(this.store, root, path, seal(plaintext));
1770
+ const hiddenPath = dirPath ? `${dirPath}/${HIDDEN_KEY}` : HIDDEN_KEY;
1771
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "seal", path: hiddenPath, rootAfter, at, by: this.actor, ...await this.provenance(this.actor) }, this.actor);
1091
1772
  return rootAfter;
1092
1773
  }
1093
1774
  async read(path) {
@@ -1095,7 +1776,7 @@ class AsyncRepo {
1095
1776
  }
1096
1777
  async deleteFile(path, at = Date.now(), by = this.actor) {
1097
1778
  const rootAfter = await deleteFile2(this.store, await this.currentRoot(), path);
1098
- await this.log.append({ seq: await this.log.seq() + 1, type: "delete", path, rootAfter, at, by });
1779
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "delete", path, rootAfter, at, by, ...await this.provenance(by) }, by);
1099
1780
  return rootAfter;
1100
1781
  }
1101
1782
  async readFile(path) {
@@ -1232,14 +1913,33 @@ class SealedClient {
1232
1913
  const box = sealContent(this.ring, content, recipients);
1233
1914
  return this.repo.writeSealed(path, JSON.stringify(box));
1234
1915
  }
1235
- async open(path, actor) {
1916
+ async sealToAccounts(path, content, recipientPubKeys, opts = {}) {
1917
+ const box = sealToAccounts(content, recipientPubKeys, { ring: this.ring, localRecipients: opts.localRecipients, recoveryPubKeys: opts.recoveryPubKeys, epoch: opts.epoch });
1918
+ return this.repo.writeSealed(path, JSON.stringify(box));
1919
+ }
1920
+ async sealSubtree(dirPath, recipientPubKeys, opts = {}) {
1921
+ return this.repo.sealSubtree(dirPath, (plaintext) => JSON.stringify(sealToAccounts(plaintext, recipientPubKeys, { ring: this.ring, localRecipients: opts.localRecipients, recoveryPubKeys: opts.recoveryPubKeys, epoch: opts.epoch })));
1922
+ }
1923
+ async hideName(path, recipientPubKeys, opts = {}) {
1924
+ const sealOne = (plaintext) => JSON.stringify(sealToAccounts(plaintext, recipientPubKeys, { ring: this.ring, localRecipients: opts.localRecipients, recoveryPubKeys: opts.recoveryPubKeys, epoch: opts.epoch }));
1925
+ return this.repo.hideName(path, sealOne, { slotId: opts.slotId, ...opts.contentSeal ? { contentSeal: sealOne } : {}, ...opts.absentContent !== undefined ? { absentContent: opts.absentContent } : {} });
1926
+ }
1927
+ async hideExistence(path, recipientPubKeys, opts = {}) {
1928
+ const sealOne = (plaintext) => JSON.stringify(sealToAccounts(plaintext, recipientPubKeys, { ring: this.ring, localRecipients: opts.localRecipients, recoveryPubKeys: opts.recoveryPubKeys, epoch: opts.epoch }));
1929
+ return this.repo.hideExistence(path, sealOne, { openPrior: opts.openPrior, ...opts.contentSeal ? { contentSeal: sealOne } : {}, ...opts.absentContent !== undefined ? { absentContent: opts.absentContent } : {} });
1930
+ }
1931
+ async reHideName(realDir, name, slotId, newContent, recipientPubKeys, opts = {}) {
1932
+ const sealOne = (plaintext) => JSON.stringify(sealToAccounts(plaintext, recipientPubKeys, { ring: this.ring, localRecipients: opts.localRecipients, recoveryPubKeys: opts.recoveryPubKeys, epoch: opts.epoch }));
1933
+ return this.repo.reHideName(realDir, name, slotId, newContent, sealOne, sealOne);
1934
+ }
1935
+ async open(path, actor, self) {
1236
1936
  const leaf = await this.repo.read(path);
1237
1937
  if (!leaf)
1238
1938
  return;
1239
1939
  if (leaf.kind === "blob")
1240
1940
  return leaf.content;
1241
1941
  const box = JSON.parse(leaf.box);
1242
- return openContent(this.ring, box, actor);
1942
+ return openContent(this.ring, box, actor, self);
1243
1943
  }
1244
1944
  }
1245
1945
  // src/sync.ts
@@ -1542,15 +2242,32 @@ function diffTrees(store, aHead, bHead) {
1542
2242
  const added = [];
1543
2243
  const removed = [];
1544
2244
  const modified = [];
2245
+ const sealedPaths = new Set;
2246
+ const sealedAt = (head, p) => entryKindAt(store, head, p) === "sealed";
1545
2247
  for (const p of bPaths)
1546
- if (!aPaths.has(p))
2248
+ if (!aPaths.has(p)) {
1547
2249
  added.push(p);
2250
+ if (sealedAt(bHead, p))
2251
+ sealedPaths.add(p);
2252
+ }
1548
2253
  for (const p of aPaths)
1549
- if (!bPaths.has(p))
2254
+ if (!bPaths.has(p)) {
1550
2255
  removed.push(p);
2256
+ if (sealedAt(aHead, p))
2257
+ sealedPaths.add(p);
2258
+ }
1551
2259
  for (const p of aPaths) {
1552
2260
  if (!bPaths.has(p))
1553
2261
  continue;
2262
+ if (sealedAt(aHead, p) || sealedAt(bHead, p)) {
2263
+ const ah = blobHashAtAny(store, aHead, p);
2264
+ const bh = blobHashAtAny(store, bHead, p);
2265
+ if (ah !== bh) {
2266
+ modified.push({ path: p, hunks: "", sealed: true });
2267
+ sealedPaths.add(p);
2268
+ }
2269
+ continue;
2270
+ }
1554
2271
  const a = fileAt(store, aHead, p);
1555
2272
  const b = fileAt(store, bHead, p);
1556
2273
  if (!a || !b)
@@ -1564,7 +2281,28 @@ function diffTrees(store, aHead, bHead) {
1564
2281
  added.sort();
1565
2282
  removed.sort();
1566
2283
  modified.sort((x, y) => x.path < y.path ? -1 : x.path > y.path ? 1 : 0);
1567
- return { added, removed, modified };
2284
+ const out = { added, removed, modified };
2285
+ if (sealedPaths.size)
2286
+ out.sealedPaths = [...sealedPaths].sort();
2287
+ return out;
2288
+ }
2289
+ function blobHashAtAny(store, root, path) {
2290
+ const b = blobHashAt(store, root, path);
2291
+ if (b !== undefined)
2292
+ return b;
2293
+ const segs = path.split("/").filter(Boolean);
2294
+ let cur = root;
2295
+ for (let i = 0;i < segs.length; i++) {
2296
+ const entry = store.getTree(cur).entries[segs[i]];
2297
+ if (!entry)
2298
+ return;
2299
+ if (i === segs.length - 1)
2300
+ return entry.kind === "sealed" ? entry.hash : undefined;
2301
+ if (entry.kind !== "tree")
2302
+ return;
2303
+ cur = entry.hash;
2304
+ }
2305
+ return;
1568
2306
  }
1569
2307
  function diffFile(store, aHead, bHead, path) {
1570
2308
  const a = readFile(store, aHead, path);
@@ -2133,14 +2871,24 @@ function toAttestation(runner, r) {
2133
2871
  export {
2134
2872
  writeFile,
2135
2873
  wrapCekTo,
2874
+ verifyPubkeySig,
2136
2875
  verifyOp,
2876
+ verifyAuthor,
2137
2877
  verifyAttestation,
2878
+ verifyAtt,
2138
2879
  unwrapCekWith,
2880
+ trustAuthor,
2139
2881
  toAttestation,
2882
+ signerFrom,
2883
+ signTag,
2884
+ signPubkey,
2140
2885
  signOp,
2886
+ signAuthor,
2141
2887
  signAttestation,
2142
2888
  semanticMerge,
2889
+ sealedBoxAt,
2143
2890
  sealWithRecovery,
2891
+ sealToAccounts,
2144
2892
  sealContent,
2145
2893
  sameActor,
2146
2894
  safePath,
@@ -2153,14 +2901,22 @@ export {
2153
2901
  pullSubtree,
2154
2902
  pull,
2155
2903
  publishPaths,
2904
+ publicOf,
2905
+ pubFingerprint,
2906
+ provenanceFromEnv,
2907
+ provTrailers,
2908
+ provOf,
2909
+ provJson,
2156
2910
  pageOps,
2157
2911
  openWithRecovery,
2158
2912
  openContent,
2159
2913
  openChange,
2914
+ normalizeFingerprint,
2160
2915
  modeAt,
2161
2916
  merge3,
2162
2917
  merge,
2163
2918
  localTarget,
2919
+ loadPrivateKey,
2164
2920
  listAll,
2165
2921
  lineHunks,
2166
2922
  isRecipient,
@@ -2169,8 +2925,11 @@ export {
2169
2925
  hashNode,
2170
2926
  groupIntoUnits,
2171
2927
  getTree,
2928
+ generateRecoveryCode,
2929
+ generateKeypair,
2172
2930
  garbage,
2173
2931
  formatLog,
2932
+ fingerprintOf,
2174
2933
  filterLog,
2175
2934
  fileAt,
2176
2935
  exportFiles,
@@ -2179,16 +2938,24 @@ export {
2179
2938
  duplicateExportCheck,
2180
2939
  diffTrees,
2181
2940
  diffFile,
2941
+ deriveIdentity,
2182
2942
  deleteFile,
2183
2943
  converge,
2184
2944
  compact,
2185
2945
  clone,
2186
2946
  ciphertextOf,
2947
+ captureProv,
2187
2948
  canonicalOp,
2949
+ canonicalAuthor,
2188
2950
  canonicalAttestation,
2951
+ canonicalAtt,
2952
+ buildProvNode,
2189
2953
  blobHashAt,
2190
2954
  blame,
2955
+ authorLabel,
2191
2956
  attestSigned,
2957
+ attestOp,
2958
+ attTag,
2192
2959
  View,
2193
2960
  UNREADABLE,
2194
2961
  Store,