@cello-protocol/daemon 0.0.3 → 0.0.4

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 +1 -1
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Daemon registration persistence — CELLO-M7-REGISTRATION.
3
+ *
4
+ * The daemon owns each agent's registration material (ML-DSA keypair, FROST key
5
+ * share, registration state) as per-agent files under ~/.cello/agents/<name>/.
6
+ * This mirrors the daemon's existing `key`-file model (see agent-loader.ts) and
7
+ * deliberately does NOT drag the client's SQLCipher ClientStatePersistence into
8
+ * the daemon — that layer is keyed by agent_pubkey in one shared DB, whereas the
9
+ * daemon isolates each agent in its own directory (cleaner for multi-agent).
10
+ *
11
+ * Security posture: the FROST signing share and ML-DSA secret key are written
12
+ * with 0o600 permissions, atomically (write-to-tmp + rename), consistent with the
13
+ * daemon's existing plaintext K_local `key` file. Encryption-at-rest for the
14
+ * daemon is a separate future concern and is intentionally NOT introduced
15
+ * piecemeal here. The signing share (SI-001) and secret key blob (SI-002) must
16
+ * never appear in a log event — only the files hold them.
17
+ */
18
+ import { mkdir, readFile, rename, chmod, unlink, open as fsOpen } from "node:fs/promises";
19
+ import { randomBytes } from "node:crypto";
20
+ import { join, dirname } from "node:path";
21
+ // ─── On-disk file names (relative to the agent directory) ────────────────────
22
+ const FILE_REGISTRATION_STATE = "registration-state.json";
23
+ const FILE_MLDSA_KEYPAIR = "ml-dsa-keypair.json";
24
+ const FILE_FROST_SHARE = "frost-share.json";
25
+ const FILE_AGENT_USER_LINK = "agent-user-link.json";
26
+ const SECRET_FILE_MODE = 0o600;
27
+ const hex = (b) => Buffer.from(b).toString("hex");
28
+ const unhex = (s) => new Uint8Array(Buffer.from(s, "hex"));
29
+ /**
30
+ * Required-field accessors. A registration file that exists but is missing or
31
+ * mistypes a field is corrupt — surface it loudly rather than silently coercing
32
+ * to NaN / an empty buffer (which would read as "unregistered" or hand crypto a
33
+ * zero-length key). Atomic writes make this unreachable in normal operation.
34
+ */
35
+ function reqStr(obj, key, file) {
36
+ const v = obj[key];
37
+ if (typeof v !== "string") {
38
+ throw new Error(`registration file '${file}' is corrupt: missing or non-string field '${key}'`);
39
+ }
40
+ return v;
41
+ }
42
+ function reqNum(obj, key, file) {
43
+ const v = obj[key];
44
+ if (typeof v !== "number" || !Number.isFinite(v)) {
45
+ throw new Error(`registration file '${file}' is corrupt: missing or non-numeric field '${key}'`);
46
+ }
47
+ return v;
48
+ }
49
+ /**
50
+ * File-backed implementation. One instance is scoped to a single agent's
51
+ * directory (e.g. ~/.cello/agents/alice/). The directory is created on first
52
+ * write; load methods on a fresh agent return null (ENOENT is not an error).
53
+ */
54
+ export class FileRegistrationPersistence {
55
+ #agentDir;
56
+ #logger;
57
+ #tmpCounter = 0;
58
+ constructor(opts) {
59
+ this.#agentDir = opts.agentDir;
60
+ this.#logger = opts.logger;
61
+ }
62
+ // ─── Persist ───────────────────────────────────────────────────────────────
63
+ async persistMlDsaKeypair(opts) {
64
+ // SI-002: secretKeyBlob is written to disk only — never logged.
65
+ await this.#writeJsonAtomic(FILE_MLDSA_KEYPAIR, {
66
+ mlDsaPubkey: opts.mlDsaPubkey,
67
+ secretKeyBlob: hex(opts.secretKeyBlob),
68
+ algorithm: "ML-DSA-44",
69
+ });
70
+ this.#logger.info("registration.mldsa.persisted", { mlDsaPubkey: opts.mlDsaPubkey });
71
+ }
72
+ async persistRegistrationState(opts) {
73
+ await this.#writeJsonAtomic(FILE_REGISTRATION_STATE, {
74
+ agentId: opts.agentId,
75
+ primaryPubkey: opts.primaryPubkey,
76
+ mlDsaPubkey: opts.mlDsaPubkey,
77
+ registeredAt: opts.registeredAt,
78
+ status: "active",
79
+ });
80
+ this.#logger.info("registration.state.persisted", {
81
+ agentId: opts.agentId,
82
+ primaryPubkey: opts.primaryPubkey,
83
+ });
84
+ }
85
+ async persistFrostKeyShare(opts) {
86
+ // A daemon agent holds exactly one active FROST share; a new DKG replaces it.
87
+ await this.#writeJsonAtomic(FILE_FROST_SHARE, {
88
+ epochId: opts.epochId,
89
+ primaryPubkey: opts.primaryPubkey,
90
+ identifier: opts.identifier,
91
+ signingShare: hex(opts.signingShare), // SI-001: on disk only, never logged
92
+ threshold: opts.threshold,
93
+ participants: opts.participants,
94
+ commitmentsCbor: hex(opts.commitmentsCbor),
95
+ verifyingSharesCbor: hex(opts.verifyingSharesCbor),
96
+ dkgMethod: opts.dkgMethod,
97
+ });
98
+ // SI-001: signingShare MUST NOT appear in this event.
99
+ this.#logger.info("registration.frost.share.persisted", {
100
+ epochId: opts.epochId,
101
+ threshold: opts.threshold,
102
+ participants: opts.participants,
103
+ dkgMethod: opts.dkgMethod,
104
+ });
105
+ }
106
+ /**
107
+ * Capture the agent→user link at registration (M7 capture-now-or-lose-it). Not
108
+ * part of the RegistrationManager seam — called by the cello_register handler
109
+ * after a successful register(), since only the handler holds the pre-auth ticket.
110
+ */
111
+ async persistAgentUserLink(opts) {
112
+ // SI: the preAuthToken is a bearer ticket — written to disk only, never logged.
113
+ await this.#writeJsonAtomic(FILE_AGENT_USER_LINK, {
114
+ agentId: opts.agentId,
115
+ preAuthToken: opts.preAuthToken,
116
+ linkedAt: opts.linkedAt,
117
+ });
118
+ this.#logger.info("registration.user_link.persisted", { agentId: opts.agentId });
119
+ }
120
+ // ─── Load (restart rehydration) ──────────────────────────────────────────────
121
+ async loadRegistrationState() {
122
+ const obj = await this.#readJson(FILE_REGISTRATION_STATE);
123
+ if (!obj)
124
+ return null;
125
+ return {
126
+ agentId: reqStr(obj, "agentId", FILE_REGISTRATION_STATE),
127
+ primaryPubkey: reqStr(obj, "primaryPubkey", FILE_REGISTRATION_STATE),
128
+ mlDsaPubkey: reqStr(obj, "mlDsaPubkey", FILE_REGISTRATION_STATE),
129
+ registeredAt: reqNum(obj, "registeredAt", FILE_REGISTRATION_STATE),
130
+ status: reqStr(obj, "status", FILE_REGISTRATION_STATE),
131
+ };
132
+ }
133
+ async loadMlDsaKeypair() {
134
+ const obj = await this.#readJson(FILE_MLDSA_KEYPAIR);
135
+ if (!obj)
136
+ return null;
137
+ return {
138
+ mlDsaPubkey: reqStr(obj, "mlDsaPubkey", FILE_MLDSA_KEYPAIR),
139
+ secretKeyBlob: unhex(reqStr(obj, "secretKeyBlob", FILE_MLDSA_KEYPAIR)),
140
+ algorithm: reqStr(obj, "algorithm", FILE_MLDSA_KEYPAIR),
141
+ };
142
+ }
143
+ async loadActiveFrostKeyShare() {
144
+ const obj = await this.#readJson(FILE_FROST_SHARE);
145
+ if (!obj)
146
+ return null;
147
+ return {
148
+ epochId: reqStr(obj, "epochId", FILE_FROST_SHARE),
149
+ primaryPubkey: reqStr(obj, "primaryPubkey", FILE_FROST_SHARE),
150
+ identifier: reqStr(obj, "identifier", FILE_FROST_SHARE),
151
+ signingShare: unhex(reqStr(obj, "signingShare", FILE_FROST_SHARE)),
152
+ threshold: reqNum(obj, "threshold", FILE_FROST_SHARE),
153
+ participants: reqNum(obj, "participants", FILE_FROST_SHARE),
154
+ commitmentsCbor: unhex(reqStr(obj, "commitmentsCbor", FILE_FROST_SHARE)),
155
+ verifyingSharesCbor: unhex(reqStr(obj, "verifyingSharesCbor", FILE_FROST_SHARE)),
156
+ dkgMethod: reqStr(obj, "dkgMethod", FILE_FROST_SHARE),
157
+ };
158
+ }
159
+ async loadAgentUserLink() {
160
+ const obj = await this.#readJson(FILE_AGENT_USER_LINK);
161
+ if (!obj)
162
+ return null;
163
+ return {
164
+ agentId: reqStr(obj, "agentId", FILE_AGENT_USER_LINK),
165
+ preAuthToken: reqStr(obj, "preAuthToken", FILE_AGENT_USER_LINK),
166
+ linkedAt: reqNum(obj, "linkedAt", FILE_AGENT_USER_LINK),
167
+ };
168
+ }
169
+ // ─── Internals ───────────────────────────────────────────────────────────────
170
+ async #readJson(name) {
171
+ let raw;
172
+ try {
173
+ raw = await readFile(join(this.#agentDir, name));
174
+ }
175
+ catch (err) {
176
+ if (err.code === "ENOENT")
177
+ return null;
178
+ throw err;
179
+ }
180
+ return JSON.parse(raw.toString("utf8"));
181
+ }
182
+ /** Write JSON atomically (tmp + rename) with 0o600 so partial writes never surface. */
183
+ async #writeJsonAtomic(name, value) {
184
+ await mkdir(this.#agentDir, { recursive: true });
185
+ const finalPath = join(this.#agentDir, name);
186
+ // Random suffix (not just pid+counter) so two FileRegistrationPersistence
187
+ // instances sharing an agentDir in one process can never collide on the tmp
188
+ // path and truncate each other's write.
189
+ const tmpPath = join(dirname(finalPath), `.cello-reg-tmp-${process.pid}-${++this.#tmpCounter}-${randomBytes(6).toString("hex")}-${name}`);
190
+ const body = Buffer.from(JSON.stringify(value), "utf8");
191
+ try {
192
+ const fd = await fsOpen(tmpPath, "w", SECRET_FILE_MODE);
193
+ try {
194
+ await fd.write(body);
195
+ await fd.chmod(SECRET_FILE_MODE);
196
+ // fsync the data before rename so a crash between write and rename can't
197
+ // surface an empty/partial key file on recovery.
198
+ await fd.sync();
199
+ }
200
+ finally {
201
+ await fd.close();
202
+ }
203
+ await rename(tmpPath, finalPath);
204
+ // rename preserves the tmp file's mode; chmod the final path defensively in
205
+ // case it pre-existed with looser permissions.
206
+ await chmod(finalPath, SECRET_FILE_MODE);
207
+ // Best-effort durability of the rename itself (parent-dir fsync). Not all
208
+ // platforms/filesystems support directory fsync — ignore failures.
209
+ await this.#fsyncDir(this.#agentDir);
210
+ }
211
+ catch (err) {
212
+ // Never leave plaintext secret material (ML-DSA secret / FROST share) as
213
+ // orphaned tmp litter if the write or rename failed partway.
214
+ await unlink(tmpPath).catch(() => { });
215
+ throw err;
216
+ }
217
+ }
218
+ async #fsyncDir(dir) {
219
+ try {
220
+ const dh = await fsOpen(dir, "r");
221
+ try {
222
+ await dh.sync();
223
+ }
224
+ finally {
225
+ await dh.close();
226
+ }
227
+ }
228
+ catch {
229
+ // Directory fsync is best-effort; some platforms reject opening a dir for sync.
230
+ }
231
+ }
232
+ }
233
+ //# sourceMappingURL=registration-persistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registration-persistence.js","sourceRoot":"","sources":["../src/registration-persistence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,144 @@
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
+ import type { DatabaseSync } from "node:sqlite";
55
+ import type { Logger } from "./types.js";
56
+ import type { TranscriptCipher } from "./transcript-cipher.js";
57
+ /** Cap per outline.md Resource Caps. */
58
+ export declare const RETRY_QUEUE_CAP = 1000;
59
+ export interface RetryQueueEntry {
60
+ sessionId: string;
61
+ nonceHex: string;
62
+ contentBlob: Uint8Array;
63
+ queuedAt: number;
64
+ attempts: number;
65
+ position: number;
66
+ }
67
+ /** Result of a resend attempt during drain. */
68
+ export type ResendResult = {
69
+ delivered: true;
70
+ } | {
71
+ delivered: false;
72
+ error: string;
73
+ };
74
+ export type ResendFn = (contentBlob: Uint8Array) => Promise<ResendResult>;
75
+ /**
76
+ * CELLO-M7-MSG-001: an un-acked content entry awaiting a `persisted` delivery ACK.
77
+ * Stored in the SAME retry_queue table (awaiting_ack = 1) — no new table. Drains to
78
+ * the relay store-and-forward queue (park target) instead of direct-P2P resend.
79
+ */
80
+ export interface AwaitingContentEntry {
81
+ /** DOD-LOOP-1: the OWNING agent — the original sender. Two of the operator's agents can hold
82
+ * awaiting content for the SAME session_id on one daemon (loopback), so the entry is keyed by
83
+ * (agentName, sessionId), not sessionId alone. */
84
+ agentName: string;
85
+ sessionId: string;
86
+ contentHashHex: string;
87
+ contentBlob: Uint8Array;
88
+ queuedAt: number;
89
+ position: number;
90
+ }
91
+ /** Result of a park attempt during the awaiting-ACK drain. */
92
+ export type ParkResult = {
93
+ parked: true;
94
+ } | {
95
+ parked: false;
96
+ error: string;
97
+ };
98
+ export type ParkFn = (entry: AwaitingContentEntry) => Promise<ParkResult>;
99
+ export declare class RetryQueue {
100
+ #private;
101
+ constructor(db: DatabaseSync, logger: Logger, cipher?: TranscriptCipher);
102
+ /**
103
+ * Load all retry queue entries from SQLCipher into memory.
104
+ * Must complete BEFORE the IPC socket opens (AC-007).
105
+ */
106
+ loadFromDb(): void;
107
+ enqueue(sessionId: string, nonce: Uint8Array, contentBlob: Uint8Array): void;
108
+ /**
109
+ * Drain the session's retry queue in FIFO order.
110
+ * Halts immediately on first failure (FIFO invariant: no out-of-order delivery).
111
+ * Returns the number of successfully delivered messages.
112
+ */
113
+ drainSession(sessionId: string, sendFn: ResendFn): Promise<number>;
114
+ /** Total retry queue depth across all sessions. */
115
+ getTotalDepth(): number;
116
+ /** Retry queue depth for a specific session. */
117
+ getSessionDepth(sessionId: string): number;
118
+ /** Get all entries for a session (FIFO ordered, defensive copy). Used by drain_session IPC. */
119
+ getSessionEntries(sessionId: string): RetryQueueEntry[];
120
+ /**
121
+ * Record un-acked content awaiting a `persisted` delivery ACK (TTF-trigger path,
122
+ * AC-005). Persisted to the SAME retry_queue table (awaiting_ack = 1) so a crash
123
+ * before the relay park confirms is recoverable at startup (AC-004). Idempotent on
124
+ * (sessionId, contentHash).
125
+ */
126
+ enqueueAwaitingContent(agentName: string, sessionId: string, contentHash: Uint8Array, contentBlob: Uint8Array): void;
127
+ /** Remove an awaiting-ACK entry once its `persisted` ACK arrives. */
128
+ markContentAcked(agentName: string, sessionId: string, contentHash: Uint8Array): void;
129
+ /** Awaiting-ACK depth for a session (un-acked content count). */
130
+ getAwaitingDepth(agentName: string, sessionId: string): number;
131
+ /** All (agent, session) pairs that currently hold awaiting-ACK content (for the startup flush). */
132
+ getAwaitingSessions(): Array<{
133
+ agentName: string;
134
+ sessionId: string;
135
+ }>;
136
+ /**
137
+ * Drain a session's awaiting-ACK content to the relay park target (AC-005). Each item
138
+ * is independent — unlike the direct FIFO resend, a park failure does NOT halt the
139
+ * rest; the failed item stays queued for the next reconnect / startup flush (DB-001,
140
+ * AC-019). Returns the number of entries successfully parked.
141
+ */
142
+ drainAwaitingToPark(agentName: string, sessionId: string, parkFn: ParkFn): Promise<number>;
143
+ }
144
+ //# sourceMappingURL=retry-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-queue.d.ts","sourceRoot":"","sources":["../src/retry-queue.ts"],"names":[],"mappings":"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"}