@cello-protocol/daemon 0.0.3 → 0.0.5

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 (153) hide show
  1. package/dist/agent-loader.d.ts +41 -0
  2. package/dist/agent-loader.d.ts.map +1 -0
  3. package/dist/agent-loader.js +94 -0
  4. package/dist/agent-loader.js.map +1 -0
  5. package/dist/bin/cello-daemon.d.ts +13 -0
  6. package/dist/bin/cello-daemon.d.ts.map +1 -0
  7. package/dist/bin/cello-daemon.js +170 -0
  8. package/dist/bin/cello-daemon.js.map +1 -0
  9. package/dist/cello-node-transport-dialer.d.ts +59 -0
  10. package/dist/cello-node-transport-dialer.d.ts.map +1 -0
  11. package/dist/cello-node-transport-dialer.js +108 -0
  12. package/dist/cello-node-transport-dialer.js.map +1 -0
  13. package/dist/challenge-verifier.d.ts +12 -0
  14. package/dist/challenge-verifier.d.ts.map +1 -0
  15. package/dist/challenge-verifier.js +11 -0
  16. package/dist/challenge-verifier.js.map +1 -0
  17. package/dist/connect-or-start.d.ts +25 -0
  18. package/dist/connect-or-start.d.ts.map +1 -0
  19. package/dist/connect-or-start.js +117 -0
  20. package/dist/connect-or-start.js.map +1 -0
  21. package/dist/content-park-client.d.ts +49 -0
  22. package/dist/content-park-client.d.ts.map +1 -0
  23. package/dist/content-park-client.js +196 -0
  24. package/dist/content-park-client.js.map +1 -0
  25. package/dist/daemon.d.ts +65 -0
  26. package/dist/daemon.d.ts.map +1 -0
  27. package/dist/daemon.js +3202 -0
  28. package/dist/daemon.js.map +1 -0
  29. package/dist/directory-bootstrap.d.ts +55 -0
  30. package/dist/directory-bootstrap.d.ts.map +1 -0
  31. package/dist/directory-bootstrap.js +102 -0
  32. package/dist/directory-bootstrap.js.map +1 -0
  33. package/dist/file-manifest-provider.d.ts +18 -0
  34. package/dist/file-manifest-provider.d.ts.map +1 -0
  35. package/dist/file-manifest-provider.js +72 -0
  36. package/dist/file-manifest-provider.js.map +1 -0
  37. package/dist/index.d.ts +18 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +18 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/ipc-client.d.ts +31 -0
  42. package/dist/ipc-client.d.ts.map +1 -0
  43. package/dist/ipc-client.js +112 -0
  44. package/dist/ipc-client.js.map +1 -0
  45. package/dist/ipc-server.d.ts +49 -0
  46. package/dist/ipc-server.d.ts.map +1 -0
  47. package/dist/ipc-server.js +268 -0
  48. package/dist/ipc-server.js.map +1 -0
  49. package/dist/lock-file.d.ts +27 -0
  50. package/dist/lock-file.d.ts.map +1 -0
  51. package/dist/lock-file.js +84 -0
  52. package/dist/lock-file.js.map +1 -0
  53. package/dist/manifest-loader.d.ts +33 -0
  54. package/dist/manifest-loader.d.ts.map +1 -0
  55. package/dist/manifest-loader.js +70 -0
  56. package/dist/manifest-loader.js.map +1 -0
  57. package/dist/manifest-poll-scheduler.d.ts +31 -0
  58. package/dist/manifest-poll-scheduler.d.ts.map +1 -0
  59. package/dist/manifest-poll-scheduler.js +59 -0
  60. package/dist/manifest-poll-scheduler.js.map +1 -0
  61. package/dist/manifest-version-store-file.d.ts +18 -0
  62. package/dist/manifest-version-store-file.d.ts.map +1 -0
  63. package/dist/manifest-version-store-file.js +40 -0
  64. package/dist/manifest-version-store-file.js.map +1 -0
  65. package/dist/manifest-version-store.d.ts +14 -0
  66. package/dist/manifest-version-store.d.ts.map +1 -0
  67. package/dist/manifest-version-store.js +13 -0
  68. package/dist/manifest-version-store.js.map +1 -0
  69. package/dist/network-directory-node.d.ts +94 -0
  70. package/dist/network-directory-node.d.ts.map +1 -0
  71. package/dist/network-directory-node.js +626 -0
  72. package/dist/network-directory-node.js.map +1 -0
  73. package/dist/nonce-dedup.d.ts +68 -0
  74. package/dist/nonce-dedup.d.ts.map +1 -0
  75. package/dist/nonce-dedup.js +204 -0
  76. package/dist/nonce-dedup.js.map +1 -0
  77. package/dist/notification-dispatcher.d.ts +65 -0
  78. package/dist/notification-dispatcher.d.ts.map +1 -0
  79. package/dist/notification-dispatcher.js +138 -0
  80. package/dist/notification-dispatcher.js.map +1 -0
  81. package/dist/registration-context.d.ts +69 -0
  82. package/dist/registration-context.d.ts.map +1 -0
  83. package/dist/registration-context.js +118 -0
  84. package/dist/registration-context.js.map +1 -0
  85. package/dist/registration-manager.d.ts +72 -0
  86. package/dist/registration-manager.d.ts.map +1 -0
  87. package/dist/registration-manager.js +267 -0
  88. package/dist/registration-manager.js.map +1 -0
  89. package/dist/registration-persistence.d.ts +131 -0
  90. package/dist/registration-persistence.d.ts.map +1 -0
  91. package/dist/registration-persistence.js +233 -0
  92. package/dist/registration-persistence.js.map +1 -0
  93. package/dist/retry-queue.d.ts +144 -0
  94. package/dist/retry-queue.d.ts.map +1 -0
  95. package/dist/retry-queue.js +444 -0
  96. package/dist/retry-queue.js.map +1 -0
  97. package/dist/seal-frontier-verify.d.ts +58 -0
  98. package/dist/seal-frontier-verify.d.ts.map +1 -0
  99. package/dist/seal-frontier-verify.js +87 -0
  100. package/dist/seal-frontier-verify.js.map +1 -0
  101. package/dist/seal-legibility-tbs.d.ts +25 -0
  102. package/dist/seal-legibility-tbs.d.ts.map +1 -0
  103. package/dist/seal-legibility-tbs.js +78 -0
  104. package/dist/seal-legibility-tbs.js.map +1 -0
  105. package/dist/seal-upgrade.d.ts +90 -0
  106. package/dist/seal-upgrade.d.ts.map +1 -0
  107. package/dist/seal-upgrade.js +178 -0
  108. package/dist/seal-upgrade.js.map +1 -0
  109. package/dist/session-assignment-parser.d.ts +22 -0
  110. package/dist/session-assignment-parser.d.ts.map +1 -0
  111. package/dist/session-assignment-parser.js +139 -0
  112. package/dist/session-assignment-parser.js.map +1 -0
  113. package/dist/session-ceremony.d.ts +156 -0
  114. package/dist/session-ceremony.d.ts.map +1 -0
  115. package/dist/session-ceremony.js +447 -0
  116. package/dist/session-ceremony.js.map +1 -0
  117. package/dist/session-connection-gater.d.ts +91 -0
  118. package/dist/session-connection-gater.d.ts.map +1 -0
  119. package/dist/session-connection-gater.js +146 -0
  120. package/dist/session-connection-gater.js.map +1 -0
  121. package/dist/session-node-manager.d.ts +585 -0
  122. package/dist/session-node-manager.d.ts.map +1 -0
  123. package/dist/session-node-manager.js +2609 -0
  124. package/dist/session-node-manager.js.map +1 -0
  125. package/dist/session-relay-client.d.ts +101 -0
  126. package/dist/session-relay-client.d.ts.map +1 -0
  127. package/dist/session-relay-client.js +520 -0
  128. package/dist/session-relay-client.js.map +1 -0
  129. package/dist/session-tree.d.ts +80 -0
  130. package/dist/session-tree.d.ts.map +1 -0
  131. package/dist/session-tree.js +123 -0
  132. package/dist/session-tree.js.map +1 -0
  133. package/dist/signaling-connect.d.ts +83 -0
  134. package/dist/signaling-connect.d.ts.map +1 -0
  135. package/dist/signaling-connect.js +266 -0
  136. package/dist/signaling-connect.js.map +1 -0
  137. package/dist/transcript-cipher.d.ts +31 -0
  138. package/dist/transcript-cipher.d.ts.map +1 -0
  139. package/dist/transcript-cipher.js +74 -0
  140. package/dist/transcript-cipher.js.map +1 -0
  141. package/dist/transport-composition.d.ts +31 -0
  142. package/dist/transport-composition.d.ts.map +1 -0
  143. package/dist/transport-composition.js +55 -0
  144. package/dist/transport-composition.js.map +1 -0
  145. package/dist/transport-selector.d.ts +189 -0
  146. package/dist/transport-selector.d.ts.map +1 -0
  147. package/dist/transport-selector.js +195 -0
  148. package/dist/transport-selector.js.map +1 -0
  149. package/dist/types.d.ts +265 -0
  150. package/dist/types.d.ts.map +1 -0
  151. package/dist/types.js +33 -0
  152. package/dist/types.js.map +1 -0
  153. package/package.json +4 -4
@@ -0,0 +1,444 @@
1
+ /**
2
+ * CELLO Daemon — RetryQueue
3
+ *
4
+ * Per-session FIFO queue of messages awaiting delivery. When a send over a
5
+ * session node stream fails, the message envelope is added to the queue. When
6
+ * the peer reconnects, the daemon drains the queue in FIFO order.
7
+ *
8
+ * Persistence: SQLCipher table `retry_queue`.
9
+ * Cap: 1,000 messages per session. On overflow the OLDEST is evicted.
10
+ *
11
+ * Pseudocode (SPARC Phase P):
12
+ *
13
+ * 1. constructor(db, logger):
14
+ * - Store db handle and logger reference
15
+ * - Initialize empty per-session Map<sessionId, RetryQueueEntry[]>
16
+ * - Create retry_queue table IF NOT EXISTS
17
+ * - Create index on (session_id, position ASC)
18
+ *
19
+ * 2. loadFromDb():
20
+ * - SELECT all rows from retry_queue ordered by session_id, position ASC
21
+ * - Group into per-session arrays of RetryQueueEntry
22
+ * - Store in in-memory Map
23
+ * - Track per-session position counters (MAX position per session)
24
+ *
25
+ * 3. enqueue(sessionId, nonce: Uint8Array, contentBlob: Uint8Array):
26
+ * - Convert nonce to hex via Buffer.from(nonce).toString('hex')
27
+ * - If session queue size >= 1000: evict oldest (lowest position)
28
+ * - DELETE from retry_queue WHERE session_id AND position = min
29
+ * - Log message.retry.evicted WARN with evicted nonce hex
30
+ * - Remove from in-memory array
31
+ * - Compute next position: max(existing positions) + 1
32
+ * - INSERT into retry_queue (session_id, nonce_hex, content_blob, queued_at, attempts, position)
33
+ * - Add to in-memory array
34
+ * - Log message.retry.queued INFO
35
+ * - Return queueDepth
36
+ *
37
+ * 4. drainSession(sessionId, sendFn):
38
+ * - Get entries for session in FIFO order (ascending position)
39
+ * - For each entry:
40
+ * a. Call sendFn(entry.contentBlob) → await result
41
+ * b. If success: DELETE from retry_queue, remove from memory,
42
+ * log message.retry.delivered INFO with attemptsTotal
43
+ * c. If failure: HALT immediately. Do not attempt remaining entries.
44
+ * FIFO invariant: M3 must not arrive before M2.
45
+ * - Return count of successfully delivered messages
46
+ *
47
+ * 5. getTotalDepth():
48
+ * - Sum sizes of all per-session arrays
49
+ * - Return integer >= 0
50
+ *
51
+ * 6. getSessionDepth(sessionId):
52
+ * - Return length of session's array (0 if absent)
53
+ */
54
+ /** Cap per outline.md Resource Caps. */
55
+ export const RETRY_QUEUE_CAP = 1000;
56
+ export class RetryQueue {
57
+ #db;
58
+ #logger;
59
+ // DOD-LOG-1: when provided, content_blob is AES-256-GCM encrypted at rest with the SAME key as
60
+ // the transcript — closing the gap where queued message plaintext sat in cleartext in the DB.
61
+ // Optional for backward-compat with direct-construction tests (they store plaintext, as before).
62
+ #cipher;
63
+ /** Per-session FIFO arrays of DIRECT-resend entries, ordered by position ascending. */
64
+ #queues = new Map();
65
+ /** Per-session position counter for monotonic increment. */
66
+ #positionCounters = new Map();
67
+ /** CELLO-M7-MSG-001: per-session FIFO arrays of awaiting-ACK content (park target). */
68
+ #awaiting = new Map();
69
+ constructor(db, logger, cipher) {
70
+ this.#db = db;
71
+ this.#logger = logger;
72
+ this.#cipher = cipher;
73
+ // Create table + index if not exists (inline migration — not Flyway)
74
+ this.#db.exec(`
75
+ CREATE TABLE IF NOT EXISTS retry_queue (
76
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
77
+ session_id TEXT NOT NULL,
78
+ nonce_hex TEXT NOT NULL,
79
+ content_blob BLOB NOT NULL,
80
+ queued_at INTEGER NOT NULL,
81
+ attempts INTEGER NOT NULL DEFAULT 1,
82
+ position INTEGER NOT NULL,
83
+ UNIQUE(session_id, nonce_hex)
84
+ )
85
+ `);
86
+ this.#db.exec(`
87
+ CREATE INDEX IF NOT EXISTS retry_queue_by_session_position
88
+ ON retry_queue(session_id, position ASC)
89
+ `);
90
+ // CELLO-M7-MSG-001 (AC-005): extend the SAME table with awaiting-ACK content state.
91
+ // No new table — guarded ALTERs make this idempotent across client upgrades.
92
+ const cols = this.#db.prepare("PRAGMA table_info(retry_queue)").all();
93
+ const hasCol = (n) => cols.some((c) => c.name === n);
94
+ if (!hasCol("awaiting_ack")) {
95
+ this.#db.exec("ALTER TABLE retry_queue ADD COLUMN awaiting_ack INTEGER NOT NULL DEFAULT 0");
96
+ }
97
+ if (!hasCol("content_hash_hex")) {
98
+ this.#db.exec("ALTER TABLE retry_queue ADD COLUMN content_hash_hex TEXT");
99
+ }
100
+ // DOD-LOOP-1: the OWNING agent for awaiting-ACK content, so two of the operator's agents can
101
+ // hold awaiting content for the SAME session_id on one daemon without colliding. NULL for
102
+ // legacy direct-retry rows; awaiting rows always set it.
103
+ if (!hasCol("agent_name")) {
104
+ this.#db.exec("ALTER TABLE retry_queue ADD COLUMN agent_name TEXT");
105
+ }
106
+ }
107
+ /** DOD-LOOP-1: composite key for the awaiting-ACK map — (agentName, sessionId). */
108
+ #ak(agentName, sessionId) {
109
+ return `${agentName}\x1f${sessionId}`;
110
+ }
111
+ /**
112
+ * Load all retry queue entries from SQLCipher into memory.
113
+ * Must complete BEFORE the IPC socket opens (AC-007).
114
+ */
115
+ loadFromDb() {
116
+ const rows = this.#db
117
+ .prepare("SELECT session_id, agent_name, nonce_hex, content_blob, queued_at, attempts, position, awaiting_ack, content_hash_hex FROM retry_queue ORDER BY session_id, position ASC")
118
+ .all();
119
+ this.#queues.clear();
120
+ this.#positionCounters.clear();
121
+ this.#awaiting.clear();
122
+ for (const row of rows) {
123
+ // Track max position per session across BOTH direct and awaiting entries.
124
+ const currentMax = this.#positionCounters.get(row.session_id) ?? 0;
125
+ if (row.position > currentMax)
126
+ this.#positionCounters.set(row.session_id, row.position);
127
+ if (row.awaiting_ack === 1) {
128
+ // CELLO-M7-MSG-001 (AC-004): un-acked content to re-park at startup. DOD-LOOP-1: keyed by
129
+ // the OWNING agent (legacy rows with no agent_name fall back to "" — single-agent behavior).
130
+ const agentName = row.agent_name ?? "";
131
+ const entry = {
132
+ agentName,
133
+ sessionId: row.session_id,
134
+ contentHashHex: row.content_hash_hex ?? row.nonce_hex,
135
+ contentBlob: this.#openBlob(Uint8Array.from(row.content_blob)),
136
+ queuedAt: row.queued_at,
137
+ position: row.position,
138
+ };
139
+ const ak = this.#ak(agentName, entry.sessionId);
140
+ let q = this.#awaiting.get(ak);
141
+ if (!q) {
142
+ q = [];
143
+ this.#awaiting.set(ak, q);
144
+ }
145
+ q.push(entry);
146
+ continue;
147
+ }
148
+ const entry = {
149
+ sessionId: row.session_id,
150
+ nonceHex: row.nonce_hex,
151
+ contentBlob: this.#openBlob(Uint8Array.from(row.content_blob)),
152
+ queuedAt: row.queued_at,
153
+ attempts: row.attempts,
154
+ position: row.position,
155
+ };
156
+ let sessionQueue = this.#queues.get(entry.sessionId);
157
+ if (!sessionQueue) {
158
+ sessionQueue = [];
159
+ this.#queues.set(entry.sessionId, sessionQueue);
160
+ }
161
+ sessionQueue.push(entry);
162
+ }
163
+ }
164
+ /**
165
+ * Enqueue a failed message for later retry.
166
+ * Evicts the oldest entry if cap is reached.
167
+ */
168
+ /** DOD-LOG-1: encrypt the content blob at rest when a cipher is configured (else passthrough). */
169
+ #sealBlob(b) {
170
+ return this.#cipher ? this.#cipher.encrypt(b) : b;
171
+ }
172
+ /**
173
+ * DOD-LOG-1: decrypt a stored content blob. If no cipher is configured, or the blob is a legacy
174
+ * PLAINTEXT row written before at-rest encryption (decrypt returns null), fall back to the raw
175
+ * bytes — so an upgrade reads old rows without crashing while new rows are encrypted.
176
+ */
177
+ #openBlob(b) {
178
+ if (!this.#cipher)
179
+ return b;
180
+ return this.#cipher.decrypt(b) ?? b;
181
+ }
182
+ enqueue(sessionId, nonce, contentBlob) {
183
+ const nonceHex = Buffer.from(nonce).toString("hex");
184
+ let sessionQueue = this.#queues.get(sessionId);
185
+ if (!sessionQueue) {
186
+ sessionQueue = [];
187
+ this.#queues.set(sessionId, sessionQueue);
188
+ }
189
+ // Cap enforcement: evict oldest if at limit
190
+ if (sessionQueue.length >= RETRY_QUEUE_CAP) {
191
+ const oldest = sessionQueue[0];
192
+ // Delete from DB
193
+ try {
194
+ this.#db
195
+ .prepare("DELETE FROM retry_queue WHERE session_id = ? AND position = ?")
196
+ .run(sessionId, oldest.position);
197
+ }
198
+ catch (err) {
199
+ this.#logger.error("message.retry.persist.failed", {
200
+ sessionId,
201
+ nonce: oldest.nonceHex,
202
+ error: err instanceof Error ? err.message : String(err),
203
+ });
204
+ }
205
+ // Log eviction BEFORE the new entry is logged (per story spec)
206
+ this.#logger.warn("message.retry.evicted", {
207
+ sessionId,
208
+ nonce: oldest.nonceHex,
209
+ queueDepth: RETRY_QUEUE_CAP,
210
+ });
211
+ // Remove from in-memory
212
+ sessionQueue.shift();
213
+ }
214
+ // Compute next position
215
+ const currentMax = this.#positionCounters.get(sessionId) ?? 0;
216
+ const nextPosition = currentMax + 1;
217
+ this.#positionCounters.set(sessionId, nextPosition);
218
+ const queuedAt = Date.now();
219
+ const entry = {
220
+ sessionId,
221
+ nonceHex,
222
+ contentBlob,
223
+ queuedAt,
224
+ attempts: 1,
225
+ position: nextPosition,
226
+ };
227
+ // Persist to SQLCipher
228
+ try {
229
+ this.#db
230
+ .prepare(`INSERT INTO retry_queue (session_id, nonce_hex, content_blob, queued_at, attempts, position)
231
+ VALUES (?, ?, ?, ?, ?, ?)`)
232
+ .run(sessionId, nonceHex, Buffer.from(this.#sealBlob(contentBlob)), queuedAt, 1, nextPosition);
233
+ }
234
+ catch (err) {
235
+ this.#logger.error("message.retry.persist.failed", {
236
+ sessionId,
237
+ nonce: nonceHex,
238
+ error: err instanceof Error ? err.message : String(err),
239
+ });
240
+ // Message stays in memory only (DB-001 fallback)
241
+ }
242
+ sessionQueue.push(entry);
243
+ this.#logger.info("message.retry.queued", {
244
+ sessionId,
245
+ nonce: nonceHex,
246
+ queueDepth: sessionQueue.length,
247
+ });
248
+ }
249
+ /**
250
+ * Drain the session's retry queue in FIFO order.
251
+ * Halts immediately on first failure (FIFO invariant: no out-of-order delivery).
252
+ * Returns the number of successfully delivered messages.
253
+ */
254
+ async drainSession(sessionId, sendFn) {
255
+ const sessionQueue = this.#queues.get(sessionId);
256
+ if (!sessionQueue || sessionQueue.length === 0) {
257
+ return 0;
258
+ }
259
+ let delivered = 0;
260
+ // Drain in FIFO order (array is already sorted by position ascending)
261
+ while (sessionQueue.length > 0) {
262
+ const entry = sessionQueue[0];
263
+ const result = await sendFn(entry.contentBlob);
264
+ if (!result.delivered) {
265
+ // Increment attempts on failure so attemptsTotal reflects actual tries
266
+ entry.attempts++;
267
+ try {
268
+ this.#db
269
+ .prepare("UPDATE retry_queue SET attempts = ? WHERE session_id = ? AND nonce_hex = ?")
270
+ .run(entry.attempts, sessionId, entry.nonceHex);
271
+ }
272
+ catch (err) {
273
+ this.#logger.error("message.retry.persist.failed", {
274
+ sessionId,
275
+ nonce: entry.nonceHex,
276
+ error: err instanceof Error ? err.message : String(err),
277
+ });
278
+ }
279
+ // Halt immediately — FIFO invariant (AC-015)
280
+ break;
281
+ }
282
+ // Success: delete from DB
283
+ try {
284
+ this.#db
285
+ .prepare("DELETE FROM retry_queue WHERE session_id = ? AND nonce_hex = ?")
286
+ .run(sessionId, entry.nonceHex);
287
+ }
288
+ catch (err) {
289
+ this.#logger.error("message.retry.persist.failed", {
290
+ sessionId,
291
+ nonce: entry.nonceHex,
292
+ error: err instanceof Error ? err.message : String(err),
293
+ });
294
+ }
295
+ // Remove from memory
296
+ sessionQueue.shift();
297
+ // Increment attempts for logging
298
+ const attemptsTotal = entry.attempts + 1;
299
+ this.#logger.info("message.retry.delivered", {
300
+ sessionId,
301
+ nonce: entry.nonceHex,
302
+ attemptsTotal,
303
+ });
304
+ delivered++;
305
+ }
306
+ // Clean up empty queue entry
307
+ if (sessionQueue.length === 0) {
308
+ this.#queues.delete(sessionId);
309
+ }
310
+ return delivered;
311
+ }
312
+ /** Total retry queue depth across all sessions. */
313
+ getTotalDepth() {
314
+ let total = 0;
315
+ for (const queue of this.#queues.values()) {
316
+ total += queue.length;
317
+ }
318
+ return total;
319
+ }
320
+ /** Retry queue depth for a specific session. */
321
+ getSessionDepth(sessionId) {
322
+ return this.#queues.get(sessionId)?.length ?? 0;
323
+ }
324
+ /** Get all entries for a session (FIFO ordered, defensive copy). Used by drain_session IPC. */
325
+ getSessionEntries(sessionId) {
326
+ const queue = this.#queues.get(sessionId);
327
+ return queue ? [...queue] : [];
328
+ }
329
+ // ─── CELLO-M7-MSG-001 (AC-004/AC-005/AC-019): awaiting-ACK content (park target) ──
330
+ /**
331
+ * Record un-acked content awaiting a `persisted` delivery ACK (TTF-trigger path,
332
+ * AC-005). Persisted to the SAME retry_queue table (awaiting_ack = 1) so a crash
333
+ * before the relay park confirms is recoverable at startup (AC-004). Idempotent on
334
+ * (sessionId, contentHash).
335
+ */
336
+ enqueueAwaitingContent(agentName, sessionId, contentHash, contentBlob) {
337
+ const contentHashHex = Buffer.from(contentHash).toString("hex");
338
+ const ak = this.#ak(agentName, sessionId);
339
+ let q = this.#awaiting.get(ak);
340
+ if (!q) {
341
+ q = [];
342
+ this.#awaiting.set(ak, q);
343
+ }
344
+ if (q.some((e) => e.contentHashHex === contentHashHex))
345
+ return; // idempotent
346
+ const currentMax = this.#positionCounters.get(ak) ?? 0;
347
+ const position = currentMax + 1;
348
+ this.#positionCounters.set(ak, position);
349
+ const queuedAt = Date.now();
350
+ try {
351
+ this.#db
352
+ .prepare(`INSERT INTO retry_queue (session_id, agent_name, nonce_hex, content_blob, queued_at, attempts, position, awaiting_ack, content_hash_hex)
353
+ VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?)`)
354
+ .run(sessionId, agentName, contentHashHex, Buffer.from(this.#sealBlob(contentBlob)), queuedAt, 1, position, contentHashHex);
355
+ }
356
+ catch (err) {
357
+ this.#logger.error("message.retry.persist.failed", {
358
+ sessionId,
359
+ nonce: contentHashHex,
360
+ error: err instanceof Error ? err.message : String(err),
361
+ });
362
+ // In-memory only (DB-001 fallback) — still re-parkable this run.
363
+ }
364
+ q.push({ agentName, sessionId, contentHashHex, contentBlob, queuedAt, position });
365
+ }
366
+ /** Remove an awaiting-ACK entry once its `persisted` ACK arrives. */
367
+ markContentAcked(agentName, sessionId, contentHash) {
368
+ const contentHashHex = Buffer.from(contentHash).toString("hex");
369
+ const ak = this.#ak(agentName, sessionId);
370
+ const q = this.#awaiting.get(ak);
371
+ if (q) {
372
+ const idx = q.findIndex((e) => e.contentHashHex === contentHashHex);
373
+ if (idx !== -1)
374
+ q.splice(idx, 1);
375
+ if (q.length === 0)
376
+ this.#awaiting.delete(ak);
377
+ }
378
+ try {
379
+ this.#db
380
+ .prepare("DELETE FROM retry_queue WHERE agent_name = ? AND session_id = ? AND content_hash_hex = ? AND awaiting_ack = 1")
381
+ .run(agentName, sessionId, contentHashHex);
382
+ }
383
+ catch (err) {
384
+ this.#logger.error("message.retry.persist.failed", {
385
+ sessionId, nonce: contentHashHex, error: err instanceof Error ? err.message : String(err),
386
+ });
387
+ }
388
+ }
389
+ /** Awaiting-ACK depth for a session (un-acked content count). */
390
+ getAwaitingDepth(agentName, sessionId) {
391
+ return this.#awaiting.get(this.#ak(agentName, sessionId))?.length ?? 0;
392
+ }
393
+ /** All (agent, session) pairs that currently hold awaiting-ACK content (for the startup flush). */
394
+ getAwaitingSessions() {
395
+ const out = [];
396
+ for (const q of this.#awaiting.values()) {
397
+ if (q.length > 0)
398
+ out.push({ agentName: q[0].agentName, sessionId: q[0].sessionId });
399
+ }
400
+ return out;
401
+ }
402
+ /**
403
+ * Drain a session's awaiting-ACK content to the relay park target (AC-005). Each item
404
+ * is independent — unlike the direct FIFO resend, a park failure does NOT halt the
405
+ * rest; the failed item stays queued for the next reconnect / startup flush (DB-001,
406
+ * AC-019). Returns the number of entries successfully parked.
407
+ */
408
+ async drainAwaitingToPark(agentName, sessionId, parkFn) {
409
+ const ak = this.#ak(agentName, sessionId);
410
+ const q = this.#awaiting.get(ak);
411
+ if (!q || q.length === 0)
412
+ return 0;
413
+ let parked = 0;
414
+ for (const entry of [...q]) {
415
+ let result;
416
+ try {
417
+ result = await parkFn(entry);
418
+ }
419
+ catch (err) {
420
+ result = { parked: false, error: err instanceof Error ? err.message : String(err) };
421
+ }
422
+ if (!result.parked)
423
+ continue; // keep for next reconnect / startup flush (AC-019)
424
+ const idx = q.indexOf(entry);
425
+ if (idx !== -1)
426
+ q.splice(idx, 1);
427
+ try {
428
+ this.#db
429
+ .prepare("DELETE FROM retry_queue WHERE agent_name = ? AND session_id = ? AND content_hash_hex = ? AND awaiting_ack = 1")
430
+ .run(agentName, sessionId, entry.contentHashHex);
431
+ }
432
+ catch (err) {
433
+ this.#logger.error("message.retry.persist.failed", {
434
+ sessionId, nonce: entry.contentHashHex, error: err instanceof Error ? err.message : String(err),
435
+ });
436
+ }
437
+ parked += 1;
438
+ }
439
+ if (q.length === 0)
440
+ this.#awaiting.delete(ak);
441
+ return parked;
442
+ }
443
+ }
444
+ //# sourceMappingURL=retry-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-queue.js","sourceRoot":"","sources":["../src/retry-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * DOD-LEG-2 (SESSION-004 SI-002) — client-side content-frontier re-derivation.
3
+ *
4
+ * The directory builds + signs the seal legibility, including each party's
5
+ * `content_frontier_seq` ("the highest message the party provably received"). The client
6
+ * must NOT trust that published value: a buggy or malicious directory could inflate it.
7
+ * The client independently re-derives each party's frontier from the SIGNED leaves the
8
+ * directory ships on the session_sealed frame, and rejects the certificate
9
+ * (`certificate_frontier_unverifiable`) if any published frontier EXCEEDS what the signed
10
+ * leaves support.
11
+ *
12
+ * Why signature-verification alone is sufficient to catch inflation: the directory cannot
13
+ * forge a leaf signed by a participant (it holds no participant key), so it can only ship
14
+ * REAL signed leaves. The maximum real signed last_seen_seq for a party IS that party's
15
+ * honest frontier — there is no way to fabricate a higher one. (A malicious directory could
16
+ * OMIT leaves, lowering the re-derived value and DoS-ing its own seal, but that is a refusal
17
+ * to seal, not an inflation attack — and Merkle-binding the shipped leaves to the sealed_root
18
+ * is a further hardening tracked separately.)
19
+ *
20
+ * Mirrors the directory's buildSealLegibility frontier derivation
21
+ * (trustless-cello/packages/directory/src/seal-legibility.ts): max signed last_seen_seq
22
+ * (Structure 1 index 4) per sender, over that sender's OWN signed leaves.
23
+ */
24
+ export interface SealFrontierLeaf {
25
+ structure1_cbor: Uint8Array;
26
+ sender_pubkey: Uint8Array;
27
+ sender_signature: Uint8Array;
28
+ }
29
+ export type ReDeriveResult = {
30
+ ok: true;
31
+ frontiers: Map<string, number>;
32
+ } | {
33
+ ok: false;
34
+ reason: "leaf_signature_invalid" | "leaf_malformed" | "leaf_session_mismatch";
35
+ };
36
+ /**
37
+ * Re-derive each party's content_frontier_seq from the signed leaves. Verifies every leaf's
38
+ * Ed25519 signature over its Structure 1 bytes AND that the leaf's SIGNED session_id matches
39
+ * the session being sealed — otherwise a malicious directory could replay a party's genuinely
40
+ * signed leaves from a DIFFERENT session (where it reached a higher frontier) to inflate this
41
+ * session's frontier. An unverifiable or wrong-session leaf fails the whole re-derivation.
42
+ * Returns a map of lowercase-hex sender pubkey → max signed last_seen_seq.
43
+ */
44
+ export declare function reDeriveFrontiers(leaves: SealFrontierLeaf[], expectedSessionId: Uint8Array): ReDeriveResult;
45
+ /**
46
+ * Compare each published per-party content_frontier_seq against the re-derived value. Returns
47
+ * the first party whose PUBLISHED frontier exceeds what its signed leaves support (inflation —
48
+ * the certificate_frontier_unverifiable case), or null when every published frontier is honest.
49
+ */
50
+ export declare function findInflatedFrontier(participants: ReadonlyArray<{
51
+ pubkey: string;
52
+ content_frontier_seq: number;
53
+ }>, derived: Map<string, number>): {
54
+ party: string;
55
+ publishedFrontier: number;
56
+ derivedFrontier: number;
57
+ } | null;
58
+ //# sourceMappingURL=seal-frontier-verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal-frontier-verify.d.ts","sourceRoot":"","sources":["../src/seal-frontier-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,UAAU,CAAC;IAC5B,aAAa,EAAE,UAAU,CAAC;IAC1B,gBAAgB,EAAE,UAAU,CAAC;CAC9B;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC5C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,wBAAwB,GAAG,gBAAgB,GAAG,uBAAuB,CAAA;CAAE,CAAC;AAQjG;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,EAAE,iBAAiB,EAAE,UAAU,GAAG,cAAc,CA0B3G;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,aAAa,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAA;CAAE,CAAC,EAC7E,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQ9E"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * DOD-LEG-2 (SESSION-004 SI-002) — client-side content-frontier re-derivation.
3
+ *
4
+ * The directory builds + signs the seal legibility, including each party's
5
+ * `content_frontier_seq` ("the highest message the party provably received"). The client
6
+ * must NOT trust that published value: a buggy or malicious directory could inflate it.
7
+ * The client independently re-derives each party's frontier from the SIGNED leaves the
8
+ * directory ships on the session_sealed frame, and rejects the certificate
9
+ * (`certificate_frontier_unverifiable`) if any published frontier EXCEEDS what the signed
10
+ * leaves support.
11
+ *
12
+ * Why signature-verification alone is sufficient to catch inflation: the directory cannot
13
+ * forge a leaf signed by a participant (it holds no participant key), so it can only ship
14
+ * REAL signed leaves. The maximum real signed last_seen_seq for a party IS that party's
15
+ * honest frontier — there is no way to fabricate a higher one. (A malicious directory could
16
+ * OMIT leaves, lowering the re-derived value and DoS-ing its own seal, but that is a refusal
17
+ * to seal, not an inflation attack — and Merkle-binding the shipped leaves to the sealed_root
18
+ * is a further hardening tracked separately.)
19
+ *
20
+ * Mirrors the directory's buildSealLegibility frontier derivation
21
+ * (trustless-cello/packages/directory/src/seal-legibility.ts): max signed last_seen_seq
22
+ * (Structure 1 index 4) per sender, over that sender's OWN signed leaves.
23
+ */
24
+ import { verify } from "@cello-protocol/crypto";
25
+ import { decode } from "cbor-x";
26
+ function bytesEqual(a, b) {
27
+ if (a.length !== b.length)
28
+ return false;
29
+ for (let i = 0; i < a.length; i++)
30
+ if (a[i] !== b[i])
31
+ return false;
32
+ return true;
33
+ }
34
+ /**
35
+ * Re-derive each party's content_frontier_seq from the signed leaves. Verifies every leaf's
36
+ * Ed25519 signature over its Structure 1 bytes AND that the leaf's SIGNED session_id matches
37
+ * the session being sealed — otherwise a malicious directory could replay a party's genuinely
38
+ * signed leaves from a DIFFERENT session (where it reached a higher frontier) to inflate this
39
+ * session's frontier. An unverifiable or wrong-session leaf fails the whole re-derivation.
40
+ * Returns a map of lowercase-hex sender pubkey → max signed last_seen_seq.
41
+ */
42
+ export function reDeriveFrontiers(leaves, expectedSessionId) {
43
+ const frontiers = new Map();
44
+ for (const leaf of leaves) {
45
+ if (!verify(leaf.sender_pubkey, leaf.structure1_cbor, leaf.sender_signature)) {
46
+ return { ok: false, reason: "leaf_signature_invalid" };
47
+ }
48
+ let arr;
49
+ try {
50
+ arr = decode(leaf.structure1_cbor);
51
+ }
52
+ catch {
53
+ return { ok: false, reason: "leaf_malformed" };
54
+ }
55
+ // Structure 1 = [1, content_hash(32), sender_pubkey(32), session_id(16), last_seen_seq, ts]
56
+ if (!Array.isArray(arr) || arr.length < 5)
57
+ return { ok: false, reason: "leaf_malformed" };
58
+ const sid = arr[3];
59
+ if (!(sid instanceof Uint8Array) || !bytesEqual(sid, expectedSessionId)) {
60
+ return { ok: false, reason: "leaf_session_mismatch" };
61
+ }
62
+ const raw = arr[4];
63
+ const lss = typeof raw === "bigint" ? Number(raw) : raw;
64
+ if (typeof lss !== "number" || !Number.isFinite(lss))
65
+ continue; // leaf carries no signed last_seen
66
+ const senderHex = Buffer.from(leaf.sender_pubkey).toString("hex").toLowerCase();
67
+ const cur = frontiers.get(senderHex) ?? 0;
68
+ if (lss > cur)
69
+ frontiers.set(senderHex, lss);
70
+ }
71
+ return { ok: true, frontiers };
72
+ }
73
+ /**
74
+ * Compare each published per-party content_frontier_seq against the re-derived value. Returns
75
+ * the first party whose PUBLISHED frontier exceeds what its signed leaves support (inflation —
76
+ * the certificate_frontier_unverifiable case), or null when every published frontier is honest.
77
+ */
78
+ export function findInflatedFrontier(participants, derived) {
79
+ for (const p of participants) {
80
+ const d = derived.get(p.pubkey.toLowerCase()) ?? 0;
81
+ if (p.content_frontier_seq > d) {
82
+ return { party: p.pubkey, publishedFrontier: p.content_frontier_seq, derivedFrontier: d };
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ //# sourceMappingURL=seal-frontier-verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal-frontier-verify.js","sourceRoot":"","sources":["../src/seal-frontier-verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /** The legibility shape this hash reads — pubkeys may be Uint8Array or Buffer (CBOR-decoded). */
2
+ export interface LegibilityForHash {
3
+ participants: Array<{
4
+ pubkey: unknown;
5
+ content_frontier_seq: unknown;
6
+ last_authored_seq: unknown;
7
+ attestation_mode: unknown;
8
+ }>;
9
+ final_message: {
10
+ sender_pubkey: unknown;
11
+ seq: unknown;
12
+ answered: unknown;
13
+ };
14
+ }
15
+ export declare function canonicalLegibilityBytes(legibility: LegibilityForHash): Buffer;
16
+ /** SHA-256 of the canonical legibility bytes (32 bytes). */
17
+ export declare function canonicalLegibilityHash(legibility: LegibilityForHash): Uint8Array;
18
+ /**
19
+ * Build the seal TBS bytes WITH the legibility binding: the plain seal TBS concatenated with
20
+ * the 32-byte legibility hash. When `legibility` is absent (the unilateral seal carries none),
21
+ * returns the plain TBS unchanged — so the unilateral path keeps signing/verifying the plain TBS
22
+ * and only the bilateral path (which always carries legibility) is bound.
23
+ */
24
+ export declare function bindLegibilityToTbs(tbs: Uint8Array, legibility: LegibilityForHash | null | undefined): Uint8Array;
25
+ //# sourceMappingURL=seal-legibility-tbs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal-legibility-tbs.d.ts","sourceRoot":"","sources":["../src/seal-legibility-tbs.ts"],"names":[],"mappings":"AAuBA,iGAAiG;AACjG,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,KAAK,CAAC;QAClB,MAAM,EAAE,OAAO,CAAC;QAChB,oBAAoB,EAAE,OAAO,CAAC;QAC9B,iBAAiB,EAAE,OAAO,CAAC;QAC3B,gBAAgB,EAAE,OAAO,CAAC;KAC3B,CAAC,CAAC;IACH,aAAa,EAAE;QACb,aAAa,EAAE,OAAO,CAAC;QACvB,GAAG,EAAE,OAAO,CAAC;QACb,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AA0BD,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAY9E;AAED,4DAA4D;AAC5D,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,iBAAiB,GAAG,UAAU,CAEjF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,GAAG,UAAU,CAOjH"}