@cello-protocol/daemon 0.0.53 → 0.0.55

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 (177) hide show
  1. package/dist/agent-handlers.d.ts +57 -0
  2. package/dist/agent-handlers.d.ts.map +1 -0
  3. package/dist/agent-handlers.js +360 -0
  4. package/dist/agent-handlers.js.map +1 -0
  5. package/dist/agent-id-migration.d.ts.map +1 -1
  6. package/dist/agent-id-migration.js +1 -0
  7. package/dist/agent-id-migration.js.map +1 -1
  8. package/dist/agent-loader.d.ts +3 -4
  9. package/dist/agent-loader.d.ts.map +1 -1
  10. package/dist/agent-loader.js +3 -4
  11. package/dist/agent-loader.js.map +1 -1
  12. package/dist/cbor-blob-migration.d.ts +8 -0
  13. package/dist/cbor-blob-migration.d.ts.map +1 -0
  14. package/dist/cbor-blob-migration.js +115 -0
  15. package/dist/cbor-blob-migration.js.map +1 -0
  16. package/dist/close-session-handler.d.ts +41 -0
  17. package/dist/close-session-handler.d.ts.map +1 -0
  18. package/dist/close-session-handler.js +399 -0
  19. package/dist/close-session-handler.js.map +1 -0
  20. package/dist/consortium-bootstrap.d.ts +83 -0
  21. package/dist/consortium-bootstrap.d.ts.map +1 -0
  22. package/dist/consortium-bootstrap.js +120 -0
  23. package/dist/consortium-bootstrap.js.map +1 -0
  24. package/dist/contact-handlers.d.ts +56 -0
  25. package/dist/contact-handlers.d.ts.map +1 -0
  26. package/dist/contact-handlers.js +303 -0
  27. package/dist/contact-handlers.js.map +1 -0
  28. package/dist/content-park-client.d.ts.map +1 -1
  29. package/dist/content-park-client.js +7 -7
  30. package/dist/content-park-client.js.map +1 -1
  31. package/dist/content-park.d.ts +15 -0
  32. package/dist/content-park.d.ts.map +1 -0
  33. package/dist/content-park.js +227 -0
  34. package/dist/content-park.js.map +1 -0
  35. package/dist/daemon.d.ts +4 -10
  36. package/dist/daemon.d.ts.map +1 -1
  37. package/dist/daemon.js +430 -4563
  38. package/dist/daemon.js.map +1 -1
  39. package/dist/db-identity-store.d.ts +31 -35
  40. package/dist/db-identity-store.d.ts.map +1 -1
  41. package/dist/db-identity-store.js +62 -66
  42. package/dist/db-identity-store.js.map +1 -1
  43. package/dist/directory-bootstrap.d.ts +27 -31
  44. package/dist/directory-bootstrap.d.ts.map +1 -1
  45. package/dist/directory-bootstrap.js +18 -21
  46. package/dist/directory-bootstrap.js.map +1 -1
  47. package/dist/frame-values.d.ts +3 -0
  48. package/dist/frame-values.d.ts.map +1 -0
  49. package/dist/frame-values.js +73 -0
  50. package/dist/frame-values.js.map +1 -0
  51. package/dist/identity-migration.d.ts +8 -8
  52. package/dist/identity-migration.d.ts.map +1 -1
  53. package/dist/identity-migration.js +30 -31
  54. package/dist/identity-migration.js.map +1 -1
  55. package/dist/inbound-seal-request.d.ts +17 -0
  56. package/dist/inbound-seal-request.d.ts.map +1 -0
  57. package/dist/inbound-seal-request.js +155 -0
  58. package/dist/inbound-seal-request.js.map +1 -0
  59. package/dist/inbound-sessions.d.ts +54 -0
  60. package/dist/inbound-sessions.d.ts.map +1 -0
  61. package/dist/inbound-sessions.js +603 -0
  62. package/dist/inbound-sessions.js.map +1 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +8 -10
  65. package/dist/index.js.map +1 -1
  66. package/dist/initiate-session-handler.d.ts +22 -0
  67. package/dist/initiate-session-handler.d.ts.map +1 -0
  68. package/dist/initiate-session-handler.js +150 -0
  69. package/dist/initiate-session-handler.js.map +1 -0
  70. package/dist/network-directory-node.d.ts +6 -6
  71. package/dist/network-directory-node.d.ts.map +1 -1
  72. package/dist/network-directory-node.js +29 -28
  73. package/dist/network-directory-node.js.map +1 -1
  74. package/dist/notification-handlers.d.ts +32 -0
  75. package/dist/notification-handlers.d.ts.map +1 -0
  76. package/dist/notification-handlers.js +73 -0
  77. package/dist/notification-handlers.js.map +1 -0
  78. package/dist/outbound-sessions.d.ts +54 -0
  79. package/dist/outbound-sessions.d.ts.map +1 -0
  80. package/dist/outbound-sessions.js +429 -0
  81. package/dist/outbound-sessions.js.map +1 -0
  82. package/dist/park-envelope.d.ts.map +1 -1
  83. package/dist/park-envelope.js +3 -2
  84. package/dist/park-envelope.js.map +1 -1
  85. package/dist/register-handler.d.ts +35 -0
  86. package/dist/register-handler.d.ts.map +1 -0
  87. package/dist/register-handler.js +179 -0
  88. package/dist/register-handler.js.map +1 -0
  89. package/dist/registration-manager.d.ts.map +1 -1
  90. package/dist/registration-manager.js +3 -6
  91. package/dist/registration-manager.js.map +1 -1
  92. package/dist/relay-receipt-store.d.ts.map +1 -1
  93. package/dist/relay-receipt-store.js +1 -2
  94. package/dist/relay-receipt-store.js.map +1 -1
  95. package/dist/retry-queue.d.ts +18 -60
  96. package/dist/retry-queue.d.ts.map +1 -1
  97. package/dist/retry-queue.js +46 -97
  98. package/dist/retry-queue.js.map +1 -1
  99. package/dist/seal-coordinator.d.ts +47 -0
  100. package/dist/seal-coordinator.d.ts.map +1 -0
  101. package/dist/seal-coordinator.js +551 -0
  102. package/dist/seal-coordinator.js.map +1 -0
  103. package/dist/seal-flows.d.ts +50 -0
  104. package/dist/seal-flows.d.ts.map +1 -0
  105. package/dist/seal-flows.js +454 -0
  106. package/dist/seal-flows.js.map +1 -0
  107. package/dist/seal-frontier-verify.d.ts +9 -9
  108. package/dist/seal-frontier-verify.d.ts.map +1 -1
  109. package/dist/seal-frontier-verify.js +11 -12
  110. package/dist/seal-frontier-verify.js.map +1 -1
  111. package/dist/seal-leaf.d.ts +58 -0
  112. package/dist/seal-leaf.d.ts.map +1 -0
  113. package/dist/seal-leaf.js +111 -0
  114. package/dist/seal-leaf.js.map +1 -0
  115. package/dist/seal-receipt-upgrade.d.ts +1 -1
  116. package/dist/seal-receipt-upgrade.js +1 -1
  117. package/dist/seal-upgrade.d.ts +10 -10
  118. package/dist/seal-upgrade.d.ts.map +1 -1
  119. package/dist/seal-upgrade.js +23 -24
  120. package/dist/seal-upgrade.js.map +1 -1
  121. package/dist/session-assignment-parser.d.ts +16 -19
  122. package/dist/session-assignment-parser.d.ts.map +1 -1
  123. package/dist/session-assignment-parser.js +30 -29
  124. package/dist/session-assignment-parser.js.map +1 -1
  125. package/dist/session-ceremony.d.ts +3 -6
  126. package/dist/session-ceremony.d.ts.map +1 -1
  127. package/dist/session-ceremony.js +7 -9
  128. package/dist/session-ceremony.js.map +1 -1
  129. package/dist/session-connection-gater.d.ts +4 -2
  130. package/dist/session-connection-gater.d.ts.map +1 -1
  131. package/dist/session-connection-gater.js +16 -10
  132. package/dist/session-connection-gater.js.map +1 -1
  133. package/dist/session-content-handlers.d.ts +25 -0
  134. package/dist/session-content-handlers.d.ts.map +1 -0
  135. package/dist/session-content-handlers.js +436 -0
  136. package/dist/session-content-handlers.js.map +1 -0
  137. package/dist/session-name.d.ts +35 -0
  138. package/dist/session-name.d.ts.map +1 -0
  139. package/dist/session-name.js +60 -0
  140. package/dist/session-name.js.map +1 -0
  141. package/dist/session-node-manager.d.ts +47 -62
  142. package/dist/session-node-manager.d.ts.map +1 -1
  143. package/dist/session-node-manager.js +297 -97
  144. package/dist/session-node-manager.js.map +1 -1
  145. package/dist/session-read-handlers.d.ts +38 -0
  146. package/dist/session-read-handlers.d.ts.map +1 -0
  147. package/dist/session-read-handlers.js +233 -0
  148. package/dist/session-read-handlers.js.map +1 -0
  149. package/dist/session-relay-client.d.ts +8 -8
  150. package/dist/session-relay-client.d.ts.map +1 -1
  151. package/dist/session-relay-client.js +44 -46
  152. package/dist/session-relay-client.js.map +1 -1
  153. package/dist/session-tree.d.ts +11 -23
  154. package/dist/session-tree.d.ts.map +1 -1
  155. package/dist/session-tree.js +14 -26
  156. package/dist/session-tree.js.map +1 -1
  157. package/dist/signaling-connect.d.ts +13 -5
  158. package/dist/signaling-connect.d.ts.map +1 -1
  159. package/dist/signaling-connect.js +55 -13
  160. package/dist/signaling-connect.js.map +1 -1
  161. package/dist/sqlcipher-db.d.ts +18 -19
  162. package/dist/sqlcipher-db.d.ts.map +1 -1
  163. package/dist/sqlcipher-db.js +30 -30
  164. package/dist/sqlcipher-db.js.map +1 -1
  165. package/dist/telegram-doorbell.d.ts +38 -0
  166. package/dist/telegram-doorbell.d.ts.map +1 -0
  167. package/dist/telegram-doorbell.js +139 -0
  168. package/dist/telegram-doorbell.js.map +1 -0
  169. package/dist/types.d.ts +86 -79
  170. package/dist/types.d.ts.map +1 -1
  171. package/dist/types.js +0 -1
  172. package/dist/types.js.map +1 -1
  173. package/dist/vocabulary.d.ts +30 -30
  174. package/dist/vocabulary.d.ts.map +1 -1
  175. package/dist/vocabulary.js +41 -49
  176. package/dist/vocabulary.js.map +1 -1
  177. package/package.json +5 -5
@@ -0,0 +1,233 @@
1
+ import { classifySession } from "./session-category.js";
2
+ import { validateSessionName } from "./session-name.js";
3
+ export function registerSessionReadHandlers(deps) {
4
+ const { handlers, logger, sessionNodeManager, loadedAgents, getConnState, resolveCurrentAgent, NO_CURRENT_AGENT_RESPONSE, resolveWho, safeCursorAdvance, safeWatermarkAdvance, reapDeadHalfOpenSessions, } = deps;
5
+ // ─── M7-SESSION-004 (AC-005/AC-006): read the sealed certificate's legibility ───
6
+ // The cert-read surface: returns the receipt-not-assent certificate for a sealed session —
7
+ // per-party content frontiers, attestation modes, and whether the final message was answered.
8
+ // Reads the PERSISTED record, so it works after a daemon restart and from a DIFFERENT process
9
+ // than the one that built the certificate (an arbitrator reading the receiving side). The
10
+ // legibility states, as a first-class machine-readable property, that a signature attests
11
+ // receipt — never assent (implies_assent: false); a malicious unanswered tail reads as
12
+ // delivered-but-unanswered (final_message.answered: false), never agreed.
13
+ handlers.set("cello_get_sealed_receipt", async (params, connectionId) => {
14
+ // cello-mcp forwards this as { session_id } (snake_case, matching the other session tools).
15
+ const sessionId = params?.["session_id"];
16
+ if (!sessionId || typeof sessionId !== "string") {
17
+ return { ok: false, reason: "missing_session_id", guidance: "Provide the session_id (hex) of the sealed session. Check cello_sessions for sealed sessions." };
18
+ }
19
+ // DOD-LOOP-1: the certificate is keyed by (agent, session_id) — read the current agent's.
20
+ const connState = getConnState(connectionId);
21
+ // CC-3 / M8C-AUTOSTART-1 F18: resolve the target agent — explicit { agent } wins, else this
22
+ // connection's current agent, else the sole online agent (removes the no_current_agent papercut
23
+ // after a /mcp reconnect when exactly one agent is online). 2+ online with none selected → null.
24
+ const agentName = resolveCurrentAgent(connState, params?.agent);
25
+ if (!agentName) {
26
+ return NO_CURRENT_AGENT_RESPONSE;
27
+ }
28
+ const cert = sessionNodeManager.getSealCertificate(agentName, sessionId);
29
+ if (cert) {
30
+ // DOD-SESSION-NAME-1 (AC-A12): echo the name so the output is self-describing — you already
31
+ // hold the id, so the name is free context. It is display only: nothing in the certificate,
32
+ // the sealed root, or the legibility object is derived from it.
33
+ const sessionName = sessionNodeManager.getSessionRecord(agentName, sessionId)?.session_name ?? null;
34
+ return { ok: true, session_id: sessionId, session_name: sessionName, sealed_root: cert.sealed_root, legibility: cert.legibility };
35
+ }
36
+ // M8C-INBOX-1 (F4): the single `sealed_receipt_not_found` conflated four distinct causes, so a
37
+ // caller could not tell a typo from a not-sealed-yet session from a wrong-agent selection.
38
+ // Split them (full session ids on cello_list_sessions / cello_status already let a pasted id match).
39
+ if (sessionNodeManager.getSessionRecord(agentName, sessionId)) {
40
+ // The session is THIS agent's — it simply has no seal certificate yet.
41
+ return {
42
+ ok: false,
43
+ reason: "not_sealed_yet",
44
+ guidance: "This session exists but is not sealed yet. Close it with cello_close_session and confirm it reports sealed (or wait for the counterparty to co-seal), then retry.",
45
+ };
46
+ }
47
+ // Owned by a DIFFERENT loaded agent → the caller has the wrong current agent selected.
48
+ const owner = loadedAgents.find((a) => a.name !== agentName && sessionNodeManager.getSessionRecord(a.name, sessionId));
49
+ if (owner) {
50
+ return {
51
+ ok: false,
52
+ reason: "wrong_agent",
53
+ guidance: `This session belongs to agent '${owner.name}', not the current agent '${agentName}'. Call cello_use_agent('${owner.name}') then retry cello_sealed_receipt.`,
54
+ };
55
+ }
56
+ // A truncated paste: the id is a strict prefix of one of this agent's real session ids.
57
+ const truncated = sessionNodeManager
58
+ .getSessionsForAgent(agentName)
59
+ .some((s) => s.session_id.startsWith(sessionId) && s.session_id.length > sessionId.length);
60
+ if (truncated) {
61
+ return {
62
+ ok: false,
63
+ reason: "session_id_too_short",
64
+ guidance: "That looks like a truncated session id. cello_sessions and cello status show the FULL id — copy the complete id and retry.",
65
+ };
66
+ }
67
+ return {
68
+ ok: false,
69
+ reason: "unknown_session",
70
+ guidance: "No session with this id belongs to the current agent. Run cello_sessions to see the full ids of this agent's sessions.",
71
+ };
72
+ });
73
+ // DOD-LOG-1 (PERSIST-LOG-001): read the durable, decrypted conversation transcript for a session —
74
+ // the readable sent+received messages in canonical-sequence order, recovered AFTER a daemon restart
75
+ // (not just the opaque hash chain). The plaintext is decrypted from the encrypted-at-rest store here,
76
+ // in the daemon; the relay/directory never held it (INV-3).
77
+ handlers.set("cello_get_transcript", async (params, connectionId) => {
78
+ const sessionId = params?.["session_id"];
79
+ if (!sessionId || typeof sessionId !== "string") {
80
+ return { ok: false, reason: "missing_session_id", guidance: "Provide the session_id (hex) whose transcript to read. See cello_sessions." };
81
+ }
82
+ const connState = getConnState(connectionId);
83
+ // CC-3 / M8C-AUTOSTART-1 F18: explicit { agent } > this connection's current > sole online agent.
84
+ const agentName = resolveCurrentAgent(connState, params?.agent);
85
+ if (!agentName) {
86
+ return NO_CURRENT_AGENT_RESPONSE;
87
+ }
88
+ const { messages, undecryptable } = sessionNodeManager.readTranscript(agentName, sessionId);
89
+ // undecryptable > 0 means some rows failed GCM auth (tamper / wrong key) — surfaced, not hidden,
90
+ // so the reader can tell a real gap from an empty transcript.
91
+ // M8C-CURSOR-1: this is the ONLY reader that covers BOTH directions (sent + received), so it's
92
+ // the correct general catch-up path — e.g. a second attended connection on the same agent
93
+ // catching up on a message a DIFFERENT local connection sent (since_seq is received-only and
94
+ // would never surface it). Route through safeCursorAdvance rather than trusting the raw max —
95
+ // reviewer HIGH finding (aa5928e2/a9099571): recordTranscriptMessage swallows a DB write
96
+ // failure without rolling back the tree's leaf count, so a hole in `messages` is possible in
97
+ // principle; the contiguous-run walk refuses to vault the cursor past such a hole even here.
98
+ const deliveredSeqs = new Set(messages.map((m) => m.sequence));
99
+ safeCursorAdvance(connectionId, sessionId, deliveredSeqs);
100
+ // DOD-CURSOR-DURABLE-1 (AC3): reading the full history IS reading — so this must advance the
101
+ // PERSISTED watermark too, not just this connection's in-memory cursor. Previously it advanced
102
+ // only the cursor, which made the gate's own guidance ("call cello_get_transcript, then retry")
103
+ // a dead end for any stateless client: a fresh process reads the transcript, exits, and the next
104
+ // process still presents an unread count. Same contiguous-run safety as the cursor — never vault
105
+ // past a gap (an undecryptable row is absent from `messages`, so the walk stops there and those
106
+ // messages correctly stay unread).
107
+ safeWatermarkAdvance(agentName, sessionId, deliveredSeqs);
108
+ // DOD-SESSION-NAME-1 (AC-A12): the name rides along so a transcript dump says what it is of.
109
+ const transcriptName = sessionNodeManager.getSessionRecord(agentName, sessionId)?.session_name ?? null;
110
+ return { ok: true, session_id: sessionId, session_name: transcriptName, messages, undecryptable };
111
+ });
112
+ // cello_list_sessions: the discovery surface — every persisted session for the
113
+ // current agent (active, interrupted, sealed, seal_interrupted_pending), newest
114
+ // updated first. This is where cello_get_transcript / cello_get_sealed_receipt
115
+ // get their session ids; without it those by-id reads have no starting point,
116
+ // and the guidance strings that point here ("See cello_list_sessions") dead-end.
117
+ // Read from the persisted SQLite store, so it works after a daemon restart and
118
+ // from a fresh MCP connection (no in-memory session-node required).
119
+ // Classify → filter → cap a set of session rows into a SessionListResponse. Shared by the
120
+ // per-agent MCP surface (cello_list_sessions) and the daemon-wide CLI surface (list_sessions),
121
+ // so the operator-facing categories (open/closed/failed) and the default cap never drift.
122
+ // Default filter = "open" (live/resumable only — the common case); default limit bounds the
123
+ // response so a long-lived agent's history can't balloon it. `all` includes failed/closed.
124
+ const DEFAULT_LIST_LIMIT = 50;
125
+ const MAX_LIST_LIMIT = 500;
126
+ function selectSessions(rows, params) {
127
+ const rawFilter = typeof params?.filter === "string" ? params.filter : "open";
128
+ const filter = rawFilter === "closed" || rawFilter === "failed" || rawFilter === "all" ? rawFilter : "open";
129
+ let limit = Number(params?.limit);
130
+ if (!Number.isFinite(limit) || limit < 1)
131
+ limit = DEFAULT_LIST_LIMIT;
132
+ limit = Math.min(Math.floor(limit), MAX_LIST_LIMIT);
133
+ const classified = rows.map((row) => {
134
+ const messageCount = row.message_count ?? 0;
135
+ const category = classifySession(row.status, messageCount);
136
+ return { row, messageCount, category };
137
+ });
138
+ const matched = filter === "all" ? classified : classified.filter((c) => c.category === filter);
139
+ const sessions = matched.slice(0, limit).map(({ row, messageCount, category }) => ({
140
+ sessionId: row.session_id,
141
+ // DOD-SESSION-NAME-1 (AC-A13): SECOND, immediately after the id — not last. Every list surface
142
+ // renders as JSON, and JSON.stringify preserves insertion order, so key position IS layout: at
143
+ // the end of the entry the name prints ~11 lines below the id it belongs to, and scanning a
144
+ // 50-session dump for "which one was the deploy" is the exact problem this column exists to
145
+ // solve. Alongside the id, never instead of it — the id is what you paste into the next command.
146
+ sessionName: row.session_name ?? null,
147
+ agentName: row.agent_name,
148
+ counterpartyPubkey: row.counterparty_pubkey,
149
+ // MONIKER-5 AC1: the same resolution the doorbell uses — pet name ?? offered ?? fingerprint.
150
+ ...resolveWho(row.agent_name, row.counterparty_pubkey, row.session_id),
151
+ status: row.status,
152
+ category,
153
+ messageCount,
154
+ createdAt: new Date(row.created_at).toISOString(),
155
+ updatedAt: new Date(row.updated_at).toISOString(),
156
+ // interrupted_at is the canonical ISO interruption stamp; null for any
157
+ // session that was never interrupted (active/sealed).
158
+ interruptedAt: row.interrupted_at ?? null,
159
+ }));
160
+ return { ok: true, filter, limit, totalMatched: matched.length, sessions };
161
+ }
162
+ // cello_list_sessions (MCP, per current agent): the discovery surface for the by-id reads
163
+ // (cello_get_transcript / cello_get_sealed_receipt). Accepts { filter?: open|closed|failed|all,
164
+ // limit?: number } — defaults to open + DEFAULT_LIST_LIMIT so failed/dead handshakes don't drown
165
+ // the live ones. Read from persisted SQLite, so it survives a daemon restart / fresh connection.
166
+ handlers.set("cello_list_sessions", async (params, connectionId) => {
167
+ const connState = getConnState(connectionId);
168
+ // CC-3 / M8C-AUTOSTART-1 F18: explicit { agent } > this connection's current > sole online agent.
169
+ const agentName = resolveCurrentAgent(connState, params?.agent);
170
+ if (!agentName) {
171
+ return NO_CURRENT_AGENT_RESPONSE;
172
+ }
173
+ reapDeadHalfOpenSessions(agentName); // CC-5/F21: drop provably-dead half-open sessions before listing
174
+ return selectSessions(sessionNodeManager.getSessionsForAgent(agentName), params);
175
+ });
176
+ // ─── DOD-SESSION-NAME-1: cello_name_session — set or clear THIS agent's label for a session ───
177
+ // Set-or-clear-by-null, mirroring cello_contact_set_moniker. Works in ANY status: naming a
178
+ // long-sealed session for archival clarity is the point of the tool, not an edge case. The only
179
+ // scope is ownership — the (agent_id, session_id) row must be this agent's, which for the loopback
180
+ // case correctly means each of the operator's two agents renames only its OWN row.
181
+ handlers.set("cello_name_session", async (params, connectionId) => {
182
+ const connState = getConnState(connectionId);
183
+ const agentName = resolveCurrentAgent(connState, params?.agent);
184
+ if (!agentName)
185
+ return NO_CURRENT_AGENT_RESPONSE;
186
+ const sessionId = params?.session_id;
187
+ // `session_name` must be PRESENT (null is how you clear it) — an omitted key is a caller that
188
+ // forgot the argument, not a caller asking to clear. Same distinction cello_contact_set_moniker
189
+ // draws with its `"moniker" in params` check.
190
+ if (!sessionId || !params || !("session_name" in params)) {
191
+ return {
192
+ ok: false,
193
+ reason: "missing_params",
194
+ guidance: "Provide 'session_id' (hex) and 'session_name' — a string to name the session, or null to clear it.",
195
+ };
196
+ }
197
+ const check = validateSessionName(params.session_name);
198
+ if (!check.ok) {
199
+ logger.warn("session.name.rejected", {
200
+ agentId: sessionNodeManager.resolveAgentId(agentName), sessionId, reason: check.reason, source: "rename",
201
+ nameLength: typeof params.session_name === "string" ? params.session_name.length : null,
202
+ });
203
+ return { ok: false, reason: check.reason, guidance: check.guidance };
204
+ }
205
+ const written = sessionNodeManager.setSessionName(agentName, sessionId, check.value);
206
+ if (!written) {
207
+ // The UPDATE matched no row: this agent holds no session with that id. Fail loud rather than
208
+ // report a success for a write that landed nowhere.
209
+ return {
210
+ ok: false,
211
+ reason: "session_not_found",
212
+ guidance: "No session with this ID belongs to this agent. Check cello_sessions for its sessions and their IDs.",
213
+ };
214
+ }
215
+ // AC-A16: the LENGTH, never the text. A session name carries the subject of a private
216
+ // conversation ("Acquisition of Northwind Traders"), and daemon logs are not confidential.
217
+ const agentId = sessionNodeManager.getSessionRecord(agentName, sessionId)?.agent_id;
218
+ if (check.value === null) {
219
+ logger.info("session.name.cleared", { agentId, sessionId, source: "rename" });
220
+ }
221
+ else {
222
+ logger.info("session.name.set", { agentId, sessionId, nameLength: check.value.length, source: "rename" });
223
+ }
224
+ return { ok: true, session_id: sessionId, session_name: check.value };
225
+ });
226
+ // list_sessions (daemon-wide, for the `cello sessions` CLI which has no current agent): same
227
+ // filter/limit semantics, across ALL agents.
228
+ handlers.set("list_sessions", async (params) => {
229
+ return selectSessions(sessionNodeManager.getAllSessions(), params);
230
+ });
231
+ // DAEMON-003 IPC handlers: queue_failed_send and check_nonce (AC-010)
232
+ }
233
+ //# sourceMappingURL=session-read-handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-read-handlers.js","sourceRoot":"","sources":["../src/session-read-handlers.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,eAAe,EAAwB,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAkBxD,MAAM,UAAU,2BAA2B,CAAC,IAAqB;IAC/D,MAAM,EACJ,QAAQ,EAAE,MAAM,EAAE,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EACrF,yBAAyB,EAAE,UAAU,EAAE,iBAAiB,EAAE,oBAAoB,EAC9E,wBAAwB,GACzB,GAAG,IAAI,CAAC;IAET,mFAAmF;IACnF,2FAA2F;IAC3F,8FAA8F;IAC9F,8FAA8F;IAC9F,0FAA0F;IAC1F,0FAA0F;IAC1F,uFAAuF;IACvF,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,CAAC,0BAA0B,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;QACtE,4FAA4F;QAC5F,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,YAAY,CAAuB,CAAC;QAC/D,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,+FAA+F,EAAE,CAAC;QAChK,CAAC;QACD,0FAA0F;QAC1F,MAAM,SAAS,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;QAC7C,4FAA4F;QAC5F,gGAAgG;QAChG,iGAAiG;QACjG,MAAM,SAAS,GAAG,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAA2B,CAAC,CAAC;QACtF,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,yBAAyB,CAAC;QACnC,CAAC;QACD,MAAM,IAAI,GAAG,kBAAkB,CAAC,kBAAkB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACzE,IAAI,IAAI,EAAE,CAAC;YACT,4FAA4F;YAC5F,4FAA4F;YAC5F,gEAAgE;YAChE,MAAM,WAAW,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,YAAY,IAAI,IAAI,CAAC;YACpG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;QACpI,CAAC;QACD,+FAA+F;QAC/F,2FAA2F;QAC3F,qGAAqG;QACrG,IAAI,kBAAkB,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;YAC9D,uEAAuE;YACvE,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,gBAAgB;gBACxB,QAAQ,EAAE,mKAAmK;aAC9K,CAAC;QACJ,CAAC;QACD,uFAAuF;QACvF,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,kBAAkB,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;QACvH,IAAI,KAAK,EAAE,CAAC;YACV,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,aAAa;gBACrB,QAAQ,EAAE,kCAAkC,KAAK,CAAC,IAAI,6BAA6B,SAAS,4BAA4B,KAAK,CAAC,IAAI,qCAAqC;aACxK,CAAC;QACJ,CAAC;QACD,wFAAwF;QACxF,MAAM,SAAS,GAAG,kBAAkB;aACjC,mBAAmB,CAAC,SAAS,CAAC;aAC9B,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QAC7F,IAAI,SAAS,EAAE,CAAC;YACd,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,sBAAsB;gBAC9B,QAAQ,EAAE,4HAA4H;aACvI,CAAC;QACJ,CAAC;QACD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,iBAAiB;YACzB,QAAQ,EAAE,wHAAwH;SACnI,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,mGAAmG;IACnG,oGAAoG;IACpG,sGAAsG;IACtG,4DAA4D;IAC5D,QAAQ,CAAC,GAAG,CAAC,sBAAsB,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;QAClE,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,YAAY,CAAuB,CAAC;QAC/D,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,4EAA4E,EAAE,CAAC;QAC7I,CAAC;QACD,MAAM,SAAS,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;QAC7C,kGAAkG;QAClG,MAAM,SAAS,GAAG,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAA2B,CAAC,CAAC;QACtF,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,yBAAyB,CAAC;QACnC,CAAC;QACD,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAG,kBAAkB,CAAC,cAAc,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAC5F,iGAAiG;QACjG,8DAA8D;QAC9D,+FAA+F;QAC/F,0FAA0F;QAC1F,6FAA6F;QAC7F,8FAA8F;QAC9F,yFAAyF;QACzF,6FAA6F;QAC7F,6FAA6F;QAC7F,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC/D,iBAAiB,CAAC,YAAY,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;QAC1D,6FAA6F;QAC7F,+FAA+F;QAC/F,gGAAgG;QAChG,iGAAiG;QACjG,iGAAiG;QACjG,gGAAgG;QAChG,mCAAmC;QACnC,oBAAoB,CAAC,SAAS,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;QAC1D,6FAA6F;QAC7F,MAAM,cAAc,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,YAAY,IAAI,IAAI,CAAC;QACvG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;IACpG,CAAC,CAAC,CAAC;IAEH,+EAA+E;IAC/E,gFAAgF;IAChF,+EAA+E;IAC/E,8EAA8E;IAC9E,iFAAiF;IACjF,+EAA+E;IAC/E,oEAAoE;IACpE,0FAA0F;IAC1F,+FAA+F;IAC/F,0FAA0F;IAC1F,4FAA4F;IAC5F,2FAA2F;IAC3F,MAAM,kBAAkB,GAAG,EAAE,CAAC;IAC9B,MAAM,cAAc,GAAG,GAAG,CAAC;IAC3B,SAAS,cAAc,CACrB,IAAqB,EACrB,MAA2C;QAE3C,MAAM,SAAS,GAAG,OAAO,MAAM,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;QAC9E,MAAM,MAAM,GACV,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;QAC/F,IAAI,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;YAAE,KAAK,GAAG,kBAAkB,CAAC;QACrE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC,CAAC;QAEpD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YAClC,MAAM,YAAY,GAAG,GAAG,CAAC,aAAa,IAAI,CAAC,CAAC;YAC5C,MAAM,QAAQ,GAAoB,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;YAC5E,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;QACzC,CAAC,CAAC,CAAC;QACH,MAAM,OAAO,GACX,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC;QAClF,MAAM,QAAQ,GAAuB,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;YACrG,SAAS,EAAE,GAAG,CAAC,UAAU;YACzB,+FAA+F;YAC/F,+FAA+F;YAC/F,4FAA4F;YAC5F,4FAA4F;YAC5F,iGAAiG;YACjG,WAAW,EAAE,GAAG,CAAC,YAAY,IAAI,IAAI;YACrC,SAAS,EAAE,GAAG,CAAC,UAAU;YACzB,kBAAkB,EAAE,GAAG,CAAC,mBAAmB;YAC3C,6FAA6F;YAC7F,GAAG,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,mBAAmB,EAAE,GAAG,CAAC,UAAU,CAAC;YACtE,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,QAAQ;YACR,YAAY;YACZ,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE;YACjD,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE;YACjD,uEAAuE;YACvE,sDAAsD;YACtD,aAAa,EAAE,GAAG,CAAC,cAAc,IAAI,IAAI;SAC1C,CAAC,CAAC,CAAC;QACJ,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC7E,CAAC;IAED,0FAA0F;IAC1F,gGAAgG;IAChG,iGAAiG;IACjG,iGAAiG;IACjG,QAAQ,CAAC,GAAG,CAAC,qBAAqB,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;QACjE,MAAM,SAAS,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;QAC7C,kGAAkG;QAClG,MAAM,SAAS,GAAG,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAA2B,CAAC,CAAC;QACtF,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,yBAAyB,CAAC;QACnC,CAAC;QACD,wBAAwB,CAAC,SAAS,CAAC,CAAC,CAAC,iEAAiE;QACtG,OAAO,cAAc,CACnB,kBAAkB,CAAC,mBAAmB,CAAC,SAAS,CAAC,EACjD,MAA6C,CAC9C,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,iGAAiG;IACjG,2FAA2F;IAC3F,gGAAgG;IAChG,mGAAmG;IACnG,mFAAmF;IACnF,QAAQ,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;QAChE,MAAM,SAAS,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAA2B,CAAC,CAAC;QACtF,IAAI,CAAC,SAAS;YAAE,OAAO,yBAAyB,CAAC;QAEjD,MAAM,SAAS,GAAG,MAAM,EAAE,UAAgC,CAAC;QAC3D,8FAA8F;QAC9F,gGAAgG;QAChG,8CAA8C;QAC9C,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,cAAc,IAAI,MAAM,CAAC,EAAE,CAAC;YACzD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,gBAAgB;gBACxB,QAAQ,EAAE,oGAAoG;aAC/G,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,mBAAmB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE;gBACnC,OAAO,EAAE,kBAAkB,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ;gBACxG,UAAU,EAAE,OAAO,MAAM,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI;aACxF,CAAC,CAAC;YACH,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;QACvE,CAAC;QAED,MAAM,OAAO,GAAG,kBAAkB,CAAC,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACrF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,6FAA6F;YAC7F,oDAAoD;YACpD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,mBAAmB;gBAC3B,QAAQ,EAAE,qGAAqG;aAChH,CAAC;QACJ,CAAC;QAED,sFAAsF;QACtF,2FAA2F;QAC3F,MAAM,OAAO,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,QAAQ,CAAC;QACpF,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAChF,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC5G,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,6FAA6F;IAC7F,6CAA6C;IAC7C,QAAQ,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAC7C,OAAO,cAAc,CACnB,kBAAkB,CAAC,cAAc,EAAE,EACnC,MAA6C,CAC9C,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,sEAAsE;AACxE,CAAC"}
@@ -28,7 +28,7 @@ export interface LeafDeliverFrame {
28
28
  structure1_cbor: Uint8Array;
29
29
  structure2_cbor: Uint8Array;
30
30
  /**
31
- * M7-UPGRADE-002: true when the relay echoed back OUR OWN submitted leaf (sender_pubkey ===
31
+ * True when the relay echoed back OUR OWN submitted leaf (sender_pubkey ===
32
32
  * our K_local), false when it is a genuine COUNTERPARTY leaf. The auto-acknowledge gate uses
33
33
  * this to never auto-co-sign in response to its own SEAL ctrl leaf (which would loop). An
34
34
  * explicit field — the consumer must not have to re-decode structure1_cbor to learn it.
@@ -54,13 +54,13 @@ export interface AgentRelayClientOpts {
54
54
  senderPubkey: Uint8Array;
55
55
  logger: Logger;
56
56
  /**
57
- * DOD-RELAYSIG-1: durable store for the relay's signed ordering-record receipts. When present, each
57
+ * Durable store for the relay's signed ordering-record receipts. When present, each
58
58
  * verified `hash_submit_ack` is recorded immutably (the client's evidence of the relay's sequence
59
59
  * attestation, carried to the directory at seal time). Optional — when absent, ACKs are not recorded.
60
60
  */
61
61
  receiptStore?: RelayReceiptStore;
62
62
  /**
63
- * FED-OPTIONB-SEAL-001: the per-session leaf log a unilateral seal carries to the directory. When
63
+ * The per-session leaf log a unilateral seal carries to the directory. When
64
64
  * present, every leaf this client sees — its OWN submits (with the relay receipt) and the COUNTERPARTY's
65
65
  * delivered leaves (no receipt) — is recorded so a later unilateral seal presents the full chain for the
66
66
  * directory's OFFLINE tree rebuild. Optional — when absent, no leaf log is kept.
@@ -68,8 +68,8 @@ export interface AgentRelayClientOpts {
68
68
  sealLeafStore?: SessionSealLeafStore;
69
69
  }
70
70
  /**
71
- * FED-OPTIONB-SETUP-001 (Option B): the directory-signed relay assignment a client presents to its
72
- * chosen relay (a `client_record_assignment` frame), replacing the old directory→relay dial. The relay
71
+ * The directory-signed relay assignment a client presents to its chosen relay (a
72
+ * `client_record_assignment` frame). The relay
73
73
  * reconstructs the relay TBS from these fields and verifies `assignmentSignature` (the directory's
74
74
  * per-node relayDirSig) against any consortium directory pubkey. Field order/presence MUST match the
75
75
  * directory producer (directory-node.ts) and the relay verifier (relay-node.ts recordAssignment).
@@ -96,12 +96,12 @@ export declare function extractErrorMessage(err: unknown): string;
96
96
  export declare class AgentRelayClient {
97
97
  #private;
98
98
  constructor(opts: AgentRelayClientOpts);
99
- /** The agent's K_local public key as hex — the responder identity for auto-acknowledge (UPGRADE-002). */
99
+ /** The agent's K_local public key as hex — the responder identity for auto-acknowledge. */
100
100
  get senderPubkeyHex(): string;
101
101
  /**
102
102
  * Register a session's inbound leaf handler + a live node to (re)dial the relay from
103
103
  * (idempotent). Storing the node per session lets a pure-receiver session re-establish
104
- * the shared stream if the node that originally dialed is torn down (L5).
104
+ * the shared stream if the node that originally dialed is torn down.
105
105
  */
106
106
  registerSession(sessionIdHex: string, node: CelloNode, onLeafDeliver?: (frame: LeafDeliverFrame) => void, assignment?: RelayAssignmentCarry): void;
107
107
  /** Remove a session; caller closes the client when no sessions remain. */
@@ -120,7 +120,7 @@ export declare class AgentRelayClient {
120
120
  submitMessageHash(node: CelloNode, sessionId: Uint8Array, contentHash: Uint8Array): Promise<SubmitResult>;
121
121
  /**
122
122
  * Submit a leaf hash of a given kind (0x00 message / 0x02 control) to the relay. The SEAL
123
- * ctrl leaf (DOD-SPINE-7) rides this path: two distinct-sender ctrl leaves in the relay's
123
+ * ctrl leaf rides this path: two distinct-sender ctrl leaves in the relay's
124
124
  * log trigger the directory's FROST notarization (relay `#maybeProcessSeal`).
125
125
  */
126
126
  submitLeaf(node: CelloNode, sessionId: Uint8Array, contentHash: Uint8Array, leafKind: number): Promise<SubmitResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"session-relay-client.d.ts","sourceRoot":"","sources":["../src/session-relay-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-relay-client.d.ts","sourceRoot":"","sources":["../src/session-relay-client.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * M7 DOD-SPINE-6 / MSG-001-3b — daemon-side relay witness client (PER AGENT).
2
+ * Daemon-side relay witness client (PER AGENT).
3
3
  *
4
4
  * In CELLO the relay is the ordering/witness authority (Structure 2): it never sees
5
5
  * plaintext (content is peer↔peer), only the SIGNED content-hash leaves. It assigns
@@ -21,18 +21,17 @@
21
21
  *
22
22
  * The stream is (re)dialed from whatever live session node is current at submit time, so it
23
23
  * survives individual session teardown (the relay treats a same-pubkey reconnect as a
24
- * reconnect, re-auths, and re-drains). Reuses only proven wire shapes:
25
- * - auth: mirror of the retired client's relay challenge-response (proven against this relay)
26
- * - Structure 1 + hash_submit: mirror of the retired client's seal-leaf submit path
27
- * - server contract: `packages/relay/src/relay-node.ts`
24
+ * reconnect, re-auths, and re-drains).
25
+ *
26
+ * The server contract this must match: `packages/relay/src/relay-node.ts`.
28
27
  *
29
28
  * Crypto: Ed25519 RFC 8032. Relay auth domain: "CELLO-RELAY-AUTH-v1".
30
29
  */
31
30
  import { createHash } from "node:crypto";
32
31
  import * as lp from "it-length-prefixed";
33
- import { Encoder, decode } from "cbor-x";
32
+ import { decode } from "cbor-x";
33
+ import { encodeCbor } from "@cello-protocol/protocol-types";
34
34
  import { evaluateRelayAck } from "./relay-receipt-store.js";
35
- const CBOR_ENC = new Encoder({ tagUint8Array: false });
36
35
  export const RELAY_PROTOCOL_ID = "/cello/relay/1.0.0";
37
36
  export const RELAY_AUTH_DOMAIN = "CELLO-RELAY-AUTH-v1";
38
37
  /** Structure 1 leaf kind: 0x00 = message, 0x02 = control (matches the relay). */
@@ -58,7 +57,7 @@ export function buildRelayAuthPayload(nonce, pubkey) {
58
57
  * so the encoded bytes — not a re-encoding — are what gets signed and sent.
59
58
  */
60
59
  export function encodeStructure1(contentHash, senderPubkey, sessionId, lastSeenSeq, timestamp) {
61
- return CBOR_ENC.encode([1, contentHash, senderPubkey, sessionId, lastSeenSeq, timestamp]);
60
+ return encodeCbor([1, contentHash, senderPubkey, sessionId, lastSeenSeq, timestamp]);
62
61
  }
63
62
  /**
64
63
  * Extract a real message from a thrown value. libp2p / cross-package errors are not
@@ -129,16 +128,16 @@ export class AgentRelayClient {
129
128
  #lastSeen = new Map();
130
129
  /** The one outstanding submit's resolver (global FIFO — ack carries no session_id). */
131
130
  #pendingAck = null;
132
- // DOD-MSG-4: the sender-signed structure1_cbor of the in-flight submit, paired with its ack so the
131
+ // The sender-signed structure1_cbor of the in-flight submit, paired with its ack so the
133
132
  // SubmitResult can carry it (the ack itself only returns the relay's structure2_cbor).
134
133
  #pendingStructure1 = null;
135
- // FED-OPTIONB-SEAL-001: the in-flight submit's leaf kind (0x00 msg / 0x02 ctrl), paired with its ack so
134
+ // The in-flight submit's leaf kind (0x00 msg / 0x02 ctrl), paired with its ack so
136
135
  // #captureReceipt can persist it alongside the Structure2/Structure1 carry bytes for the unilateral seal.
137
136
  #pendingLeafKind = null;
138
137
  /** The session_id hex of the in-flight submit, so its ack updates the right #lastSeen. */
139
138
  #pendingAckSessionHex = null;
140
139
  /**
141
- * FED-OPTIONB-SETUP-001: resolver for the in-flight `client_record_assignment` ack. The ack carries
140
+ * Resolver for the in-flight `client_record_assignment` ack. The ack carries
142
141
  * no session_id (like hash_submit_ack), so at most one record is in flight; records are serialized on
143
142
  * the same `#submitChain` as submits, guaranteeing no overlap.
144
143
  */
@@ -156,14 +155,14 @@ export class AgentRelayClient {
156
155
  this.#receiptStore = opts.receiptStore;
157
156
  this.#sealLeafStore = opts.sealLeafStore;
158
157
  }
159
- /** The agent's K_local public key as hex — the responder identity for auto-acknowledge (UPGRADE-002). */
158
+ /** The agent's K_local public key as hex — the responder identity for auto-acknowledge. */
160
159
  get senderPubkeyHex() {
161
160
  return Buffer.from(this.#senderPubkey).toString("hex");
162
161
  }
163
162
  /**
164
163
  * Register a session's inbound leaf handler + a live node to (re)dial the relay from
165
164
  * (idempotent). Storing the node per session lets a pure-receiver session re-establish
166
- * the shared stream if the node that originally dialed is torn down (L5).
165
+ * the shared stream if the node that originally dialed is torn down.
167
166
  */
168
167
  registerSession(sessionIdHex, node, onLeafDeliver, assignment) {
169
168
  const existing = this.#sessions.get(sessionIdHex);
@@ -175,10 +174,10 @@ export class AgentRelayClient {
175
174
  recorded: existing?.recorded ?? false,
176
175
  recordRejected: existing?.recordRejected ?? false,
177
176
  });
178
- // FED-OPTIONB-SETUP-001 (Option B): eagerly present the assignment so the relay records the session
179
- // (binds peer IDs, creates the session entry) BEFORE the first hash_submit or the counterparty's
180
- // leaves arrive matching the timing the old directory→relay recordAssignment dial provided.
181
- // Best-effort + serialized on the submit chain (the ack carries no session_id, so no overlap).
177
+ // Eagerly present the assignment so the relay records the session (binds peer IDs, creates the
178
+ // session entry) BEFORE the first hash_submit or the counterparty's leaves arrive — the relay
179
+ // rejects frames for a session it has not recorded. Best-effort + serialized on the submit chain
180
+ // (the ack carries no session_id, so no overlap).
182
181
  if (assignment) {
183
182
  this.#submitChain = this.#submitChain
184
183
  .then(() => this.#doRecord(node, sessionIdHex))
@@ -186,7 +185,7 @@ export class AgentRelayClient {
186
185
  }
187
186
  }
188
187
  /**
189
- * FED-OPTIONB-SETUP-001: present the directory-signed assignment to the relay (Option B). Idempotent
188
+ * Present the directory-signed assignment to the relay. Idempotent
190
189
  * (no-op once `recorded`, or when the session has no assignment — direct/persisted/legacy sessions).
191
190
  * The relay reconstructs the TBS and verifies the per-node directory signature against any consortium
192
191
  * key. On success the session is recorded; the send/ack is single-in-flight (mirrors #doSubmit).
@@ -199,7 +198,7 @@ export class AgentRelayClient {
199
198
  return true;
200
199
  if (sess.recorded)
201
200
  return true;
202
- // Terminal rejection (code-review M1): a relay that cleanly rejected this assignment will reject it
201
+ // Terminal rejection: a relay that cleanly rejected this assignment will reject it
203
202
  // again — stop re-presenting so a misconfigured/forged case can't storm the shared stream.
204
203
  if (sess.recordRejected)
205
204
  return false;
@@ -209,7 +208,7 @@ export class AgentRelayClient {
209
208
  if (!stream)
210
209
  return false;
211
210
  const a = sess.assignment;
212
- const frame = CBOR_ENC.encode({
211
+ const frame = encodeCbor({
213
212
  type: "client_record_assignment",
214
213
  session_id: new Uint8Array(Buffer.from(sessionIdHex, "hex")),
215
214
  participant_a: a.participantA,
@@ -322,7 +321,7 @@ export class AgentRelayClient {
322
321
  }
323
322
  }
324
323
  /**
325
- * RELAYSIG-1: verify a relay `hash_submit_ack`'s signed ordering record and durably store the receipt.
324
+ * Verify a relay `hash_submit_ack`'s signed ordering record and durably store the receipt.
326
325
  * The relay signs TBS = SHA-256(content_hash || seq_BE4 || ts_BE8) with its ack-signing key, whose hex is
327
326
  * `relay_id`. We verify SELF-CONSISTENCY (the signature binds the sequence ⇒ a FORGED sequence fails) and
328
327
  * record the immutable receipt. The authoritative registered-relay check is the directory's at seal
@@ -355,8 +354,8 @@ export class AgentRelayClient {
355
354
  });
356
355
  switch (ev.kind) {
357
356
  case "unsigned":
358
- // A relay that SHOULD sign (PERSIST-012) but didn't → no durable witness for this message. Not an
359
- // error per the DoD (unsigned ACKs are optional), but make it diagnosable instead of invisible.
357
+ // A relay that SHOULD sign but didn't → no durable witness for this message. Unsigned ACKs are
358
+ // tolerated, but make it diagnosable instead of invisible.
360
359
  this.#logger.debug("relay.receipt.unsigned", { seq, hashShort: Buffer.from(contentHash).toString("hex").slice(0, 16) });
361
360
  return false;
362
361
  case "bad_relay_id":
@@ -365,7 +364,7 @@ export class AgentRelayClient {
365
364
  case "invalid_signature":
366
365
  // FORGED / corrupt ACK — the signature does not bind (hash, seq, ts). REJECT the submit so the send
367
366
  // does NOT settle ok on an unverified sequence (a forged ordering record must not drive ordering),
368
- // and store nothing (DoD: "a forged sequence is rejected"). The send still completes via the direct
367
+ // and store nothing. The send still completes via the direct
369
368
  // content path — the relay witness simply degrades to absent for this leaf.
370
369
  this.#logger.warn("relay.receipt.signature_invalid", { seq });
371
370
  return true;
@@ -377,7 +376,7 @@ export class AgentRelayClient {
377
376
  if (wrote) {
378
377
  this.#logger.info("relay.receipt.stored", { seq, hashShort: ev.receipt.hashHex.slice(0, 16), relayShort: ev.receipt.relayId.slice(0, 16) });
379
378
  }
380
- // FED-OPTIONB-SEAL-001: record this OWN leaf in the seal-leaf log WITH its relay receipt (the
379
+ // Record this OWN leaf in the seal-leaf log WITH its relay receipt (the
381
380
  // relay's signature pins content_hash→seq — the teeth that stop a supplier reordering its own
382
381
  // leaves). structure2_cbor rides the ack we just verified; structure1Cbor + the leaf kind are this
383
382
  // submit's paired in-flight values. Best-effort + separate from the receipt write.
@@ -397,7 +396,7 @@ export class AgentRelayClient {
397
396
  }
398
397
  catch (err) {
399
398
  // A durable-evidence write failure must be LOUD — the relay will not re-emit this ack, so a
400
- // swallowed write permanently loses a verified receipt (fallback #4).
399
+ // swallowed write permanently loses a verified receipt.
401
400
  this.#logger.error("relay.receipt.store_failed", { seq, error: err instanceof Error ? err.message : String(err) });
402
401
  }
403
402
  return false;
@@ -409,17 +408,17 @@ export class AgentRelayClient {
409
408
  if (type === "hash_submit_ack") {
410
409
  const seq = typeof frame["sequence_number"] === "number" ? frame["sequence_number"] : -1;
411
410
  // DO NOT advance #lastSeen here: the ack is for OUR OWN leaf. last_seen_seq must track
412
- // the highest COUNTERPARTY sequence we've observed (the directory's causal check
413
- // SESSION-003 SI-003 — rejects a leaf whose last_seen_seq exceeds the max sequence of
414
- // OTHER-sender leaves before it). Advancing on our own ack would inflate it and trip
415
- // causal_chain_violated on a subsequent submit (e.g. the SEAL leaf after a sent message).
416
- // DOD-MSG-4: pair the relay's committed structure2_cbor with our in-flight structure1_cbor so
411
+ // the highest COUNTERPARTY sequence we've observed the directory's causal check rejects a leaf
412
+ // whose last_seen_seq exceeds the max sequence of OTHER-sender leaves before it. Advancing on our
413
+ // own ack would inflate it and trip causal_chain_violated on a subsequent submit (e.g. the SEAL
414
+ // leaf after a sent message).
415
+ // Pair the relay's committed structure2_cbor with our in-flight structure1_cbor so
417
416
  // the SubmitResult carries the full signed ordering record for the self-ordering content frame.
418
417
  // Captured BEFORE #settlePending clears #pendingStructure1.
419
418
  const s2 = frame["structure2_cbor"];
420
419
  const structure2Cbor = s2 instanceof Uint8Array ? s2 : undefined;
421
420
  const structure1Cbor = this.#pendingStructure1 ?? undefined;
422
- // RELAYSIG-1: verify the relay's signed ordering record and durably store the receipt BEFORE
421
+ // Verify the relay's signed ordering record and durably store the receipt BEFORE
423
422
  // settling (which clears #pendingStructure1, the source of the content hash + session id). A
424
423
  // signed-but-INVALID ACK rejects the submit so the send does not settle ok on an unverified sequence.
425
424
  const rejectSubmit = this.#captureReceipt(frame, structure1Cbor, seq);
@@ -434,7 +433,7 @@ export class AgentRelayClient {
434
433
  this.#settlePending({ ok: false, reason });
435
434
  }
436
435
  else if (type === "assignment_ok") {
437
- // FED-OPTIONB-SETUP-001: the relay verified + recorded our client-presented assignment.
436
+ // The relay verified + recorded our client-presented assignment.
438
437
  const r = this.#pendingRecord;
439
438
  this.#pendingRecord = null;
440
439
  if (r)
@@ -458,7 +457,7 @@ export class AgentRelayClient {
458
457
  const authoredByUs = this.#isOwnLeaf(s1);
459
458
  if (seq >= 0 && !authoredByUs)
460
459
  this.#bumpLastSeen(sidHex, seq);
461
- // FED-OPTIONB-SEAL-001: record the COUNTERPARTY's delivered leaf in the seal-leaf log (no relay
460
+ // Record the COUNTERPARTY's delivered leaf in the seal-leaf log (no relay
462
461
  // receipt — the relay does not ack-sign a delivery to the recipient). It is pinned at seal by the
463
462
  // absent party's sender_signature (unforgeable) + sequence contiguity against our receipt-pinned own
464
463
  // leaves. Our OWN echoed leaf is skipped here (it is recorded WITH its receipt on the ack path).
@@ -497,7 +496,7 @@ export class AgentRelayClient {
497
496
  leaf_kind: typeof frame["leaf_kind"] === "number" ? frame["leaf_kind"] : LEAF_KIND_MSG,
498
497
  structure1_cbor: s1,
499
498
  structure2_cbor: toU8(frame["structure2_cbor"]),
500
- // M7-UPGRADE-002: tell the consumer whether this is our own echoed leaf (so the
499
+ // Tell the consumer whether this is our own echoed leaf (so the
501
500
  // auto-acknowledge gate never co-signs in response to its own SEAL ctrl leaf).
502
501
  authored_by_us: authoredByUs,
503
502
  });
@@ -591,7 +590,7 @@ export class AgentRelayClient {
591
590
  }
592
591
  const authSig = await this.#keyProvider.sign(buildRelayAuthPayload(nonce, this.#senderPubkey));
593
592
  try {
594
- stream.send(lp.encode.single(CBOR_ENC.encode({ type: "relay_auth_response", pubkey: this.#senderPubkey, signature: authSig })));
593
+ stream.send(lp.encode.single(encodeCbor({ type: "relay_auth_response", pubkey: this.#senderPubkey, signature: authSig })));
595
594
  }
596
595
  catch (err) {
597
596
  this.#logger.warn("session.relay.auth.failed", { relayPeerId: this.#relayPeerId, reason: "response_send", error: extractErrorMessage(err) });
@@ -649,7 +648,7 @@ export class AgentRelayClient {
649
648
  if (this.#stream === stream)
650
649
  this.#stream = null;
651
650
  this.#settlePending({ ok: false, reason: "relay_stream_closed" });
652
- // FED-OPTIONB-SETUP-001 (code-review L4): settle an in-flight record too, so #doRecord doesn't
651
+ // Settle an in-flight record too, so #doRecord doesn't
653
652
  // wait the full timeout on a dropped stream. "closed" is transient (not a directory rejection) ⇒
654
653
  // recorded stays false and it is retried after reconnect.
655
654
  {
@@ -658,7 +657,7 @@ export class AgentRelayClient {
658
657
  if (r)
659
658
  r("closed");
660
659
  }
661
- // L5: a pure-receiver session issues no submit, so it would never trigger a re-dial
660
+ // A pure-receiver session issues no submit, so it would never trigger a re-dial
662
661
  // after the node that owned the stream is torn down. If sessions remain, proactively
663
662
  // re-establish from any still-live registered session node so queued leaf_delivers
664
663
  // are drained (the relay queues by pubkey and re-delivers on reconnect).
@@ -686,7 +685,7 @@ export class AgentRelayClient {
686
685
  }
687
686
  /**
688
687
  * Submit a leaf hash of a given kind (0x00 message / 0x02 control) to the relay. The SEAL
689
- * ctrl leaf (DOD-SPINE-7) rides this path: two distinct-sender ctrl leaves in the relay's
688
+ * ctrl leaf rides this path: two distinct-sender ctrl leaves in the relay's
690
689
  * log trigger the directory's FROST notarization (relay `#maybeProcessSeal`).
691
690
  */
692
691
  async submitLeaf(node, sessionId, contentHash, leafKind) {
@@ -703,10 +702,9 @@ export class AgentRelayClient {
703
702
  if (!(await this.#ensureConnected(node)))
704
703
  return { ok: false, reason: "relay_unavailable" };
705
704
  const sessionIdHex = Buffer.from(sessionId).toString("hex");
706
- // FED-OPTIONB-SETUP-001 (Option B): the relay records the session from the CLIENT-presented
707
- // assignment (the directory no longer dials the relay). Ensure it is recorded BEFORE the first
708
- // hash_submit the relay rejects a submit for an unknown session. Idempotent + no-op when there is
709
- // no assignment to present (direct/persisted/legacy). Runs inline on the submit chain (no re-chaining
705
+ // The relay records the session from the CLIENT-presented assignment. It MUST be recorded BEFORE
706
+ // the first hash_submit the relay rejects a submit for an unknown session. Idempotent + no-op when
707
+ // there is no assignment to present (direct/persisted sessions). Runs inline on the submit chain (no re-chaining
710
708
  // — #doSubmit is already a chain link), and may reset the stream on failure, so capture #stream after.
711
709
  await this.#doRecord(node, sessionIdHex);
712
710
  const stream = this.#stream;
@@ -717,7 +715,7 @@ export class AgentRelayClient {
717
715
  const lastSeenForSession = this.#lastSeen.get(sessionIdHex) ?? 0;
718
716
  const structure1 = encodeStructure1(contentHash, this.#senderPubkey, sessionId, lastSeenForSession, Date.now());
719
717
  const signature = await this.#keyProvider.sign(structure1);
720
- const frame = CBOR_ENC.encode({
718
+ const frame = encodeCbor({
721
719
  type: "hash_submit",
722
720
  session_id: sessionId,
723
721
  leaf_kind: leafKind,
@@ -730,7 +728,7 @@ export class AgentRelayClient {
730
728
  const ackPromise = new Promise((r) => { resolveAck = r; });
731
729
  this.#pendingAck = resolveAck;
732
730
  this.#pendingAckSessionHex = sessionIdHex;
733
- // DOD-MSG-4: remember this submit's sender-signed structure1_cbor so its ack can return the full
731
+ // Remember this submit's sender-signed structure1_cbor so its ack can return the full
734
732
  // ordering record (the ack itself carries only the relay's structure2_cbor).
735
733
  this.#pendingStructure1 = structure1;
736
734
  this.#pendingLeafKind = leafKind;
@@ -775,7 +773,7 @@ export class AgentRelayClient {
775
773
  close() {
776
774
  this.#closed = true;
777
775
  this.#settlePending({ ok: false, reason: "relay_client_closed" });
778
- // FED-OPTIONB-SETUP-001 (code-review L4): settle any in-flight record so #doRecord resolves promptly.
776
+ // Settle any in-flight record so #doRecord resolves promptly.
779
777
  {
780
778
  const r = this.#pendingRecord;
781
779
  this.#pendingRecord = null;