@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.
- package/dist/agent-handlers.d.ts +57 -0
- package/dist/agent-handlers.d.ts.map +1 -0
- package/dist/agent-handlers.js +360 -0
- package/dist/agent-handlers.js.map +1 -0
- package/dist/agent-id-migration.d.ts.map +1 -1
- package/dist/agent-id-migration.js +1 -0
- package/dist/agent-id-migration.js.map +1 -1
- package/dist/agent-loader.d.ts +3 -4
- package/dist/agent-loader.d.ts.map +1 -1
- package/dist/agent-loader.js +3 -4
- package/dist/agent-loader.js.map +1 -1
- package/dist/cbor-blob-migration.d.ts +8 -0
- package/dist/cbor-blob-migration.d.ts.map +1 -0
- package/dist/cbor-blob-migration.js +115 -0
- package/dist/cbor-blob-migration.js.map +1 -0
- package/dist/close-session-handler.d.ts +41 -0
- package/dist/close-session-handler.d.ts.map +1 -0
- package/dist/close-session-handler.js +399 -0
- package/dist/close-session-handler.js.map +1 -0
- package/dist/consortium-bootstrap.d.ts +83 -0
- package/dist/consortium-bootstrap.d.ts.map +1 -0
- package/dist/consortium-bootstrap.js +120 -0
- package/dist/consortium-bootstrap.js.map +1 -0
- package/dist/contact-handlers.d.ts +56 -0
- package/dist/contact-handlers.d.ts.map +1 -0
- package/dist/contact-handlers.js +303 -0
- package/dist/contact-handlers.js.map +1 -0
- package/dist/content-park-client.d.ts.map +1 -1
- package/dist/content-park-client.js +7 -7
- package/dist/content-park-client.js.map +1 -1
- package/dist/content-park.d.ts +15 -0
- package/dist/content-park.d.ts.map +1 -0
- package/dist/content-park.js +227 -0
- package/dist/content-park.js.map +1 -0
- package/dist/daemon.d.ts +4 -10
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +430 -4563
- package/dist/daemon.js.map +1 -1
- package/dist/db-identity-store.d.ts +31 -35
- package/dist/db-identity-store.d.ts.map +1 -1
- package/dist/db-identity-store.js +62 -66
- package/dist/db-identity-store.js.map +1 -1
- package/dist/directory-bootstrap.d.ts +27 -31
- package/dist/directory-bootstrap.d.ts.map +1 -1
- package/dist/directory-bootstrap.js +18 -21
- package/dist/directory-bootstrap.js.map +1 -1
- package/dist/frame-values.d.ts +3 -0
- package/dist/frame-values.d.ts.map +1 -0
- package/dist/frame-values.js +73 -0
- package/dist/frame-values.js.map +1 -0
- package/dist/identity-migration.d.ts +8 -8
- package/dist/identity-migration.d.ts.map +1 -1
- package/dist/identity-migration.js +30 -31
- package/dist/identity-migration.js.map +1 -1
- package/dist/inbound-seal-request.d.ts +17 -0
- package/dist/inbound-seal-request.d.ts.map +1 -0
- package/dist/inbound-seal-request.js +155 -0
- package/dist/inbound-seal-request.js.map +1 -0
- package/dist/inbound-sessions.d.ts +54 -0
- package/dist/inbound-sessions.d.ts.map +1 -0
- package/dist/inbound-sessions.js +603 -0
- package/dist/inbound-sessions.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -10
- package/dist/index.js.map +1 -1
- package/dist/initiate-session-handler.d.ts +22 -0
- package/dist/initiate-session-handler.d.ts.map +1 -0
- package/dist/initiate-session-handler.js +150 -0
- package/dist/initiate-session-handler.js.map +1 -0
- package/dist/network-directory-node.d.ts +6 -6
- package/dist/network-directory-node.d.ts.map +1 -1
- package/dist/network-directory-node.js +29 -28
- package/dist/network-directory-node.js.map +1 -1
- package/dist/notification-handlers.d.ts +32 -0
- package/dist/notification-handlers.d.ts.map +1 -0
- package/dist/notification-handlers.js +73 -0
- package/dist/notification-handlers.js.map +1 -0
- package/dist/outbound-sessions.d.ts +54 -0
- package/dist/outbound-sessions.d.ts.map +1 -0
- package/dist/outbound-sessions.js +429 -0
- package/dist/outbound-sessions.js.map +1 -0
- package/dist/park-envelope.d.ts.map +1 -1
- package/dist/park-envelope.js +3 -2
- package/dist/park-envelope.js.map +1 -1
- package/dist/register-handler.d.ts +35 -0
- package/dist/register-handler.d.ts.map +1 -0
- package/dist/register-handler.js +179 -0
- package/dist/register-handler.js.map +1 -0
- package/dist/registration-manager.d.ts.map +1 -1
- package/dist/registration-manager.js +3 -6
- package/dist/registration-manager.js.map +1 -1
- package/dist/relay-receipt-store.d.ts.map +1 -1
- package/dist/relay-receipt-store.js +1 -2
- package/dist/relay-receipt-store.js.map +1 -1
- package/dist/retry-queue.d.ts +18 -60
- package/dist/retry-queue.d.ts.map +1 -1
- package/dist/retry-queue.js +46 -97
- package/dist/retry-queue.js.map +1 -1
- package/dist/seal-coordinator.d.ts +47 -0
- package/dist/seal-coordinator.d.ts.map +1 -0
- package/dist/seal-coordinator.js +551 -0
- package/dist/seal-coordinator.js.map +1 -0
- package/dist/seal-flows.d.ts +50 -0
- package/dist/seal-flows.d.ts.map +1 -0
- package/dist/seal-flows.js +454 -0
- package/dist/seal-flows.js.map +1 -0
- package/dist/seal-frontier-verify.d.ts +9 -9
- package/dist/seal-frontier-verify.d.ts.map +1 -1
- package/dist/seal-frontier-verify.js +11 -12
- package/dist/seal-frontier-verify.js.map +1 -1
- package/dist/seal-leaf.d.ts +58 -0
- package/dist/seal-leaf.d.ts.map +1 -0
- package/dist/seal-leaf.js +111 -0
- package/dist/seal-leaf.js.map +1 -0
- package/dist/seal-receipt-upgrade.d.ts +1 -1
- package/dist/seal-receipt-upgrade.js +1 -1
- package/dist/seal-upgrade.d.ts +10 -10
- package/dist/seal-upgrade.d.ts.map +1 -1
- package/dist/seal-upgrade.js +23 -24
- package/dist/seal-upgrade.js.map +1 -1
- package/dist/session-assignment-parser.d.ts +16 -19
- package/dist/session-assignment-parser.d.ts.map +1 -1
- package/dist/session-assignment-parser.js +30 -29
- package/dist/session-assignment-parser.js.map +1 -1
- package/dist/session-ceremony.d.ts +3 -6
- package/dist/session-ceremony.d.ts.map +1 -1
- package/dist/session-ceremony.js +7 -9
- package/dist/session-ceremony.js.map +1 -1
- package/dist/session-connection-gater.d.ts +4 -2
- package/dist/session-connection-gater.d.ts.map +1 -1
- package/dist/session-connection-gater.js +16 -10
- package/dist/session-connection-gater.js.map +1 -1
- package/dist/session-content-handlers.d.ts +25 -0
- package/dist/session-content-handlers.d.ts.map +1 -0
- package/dist/session-content-handlers.js +436 -0
- package/dist/session-content-handlers.js.map +1 -0
- package/dist/session-name.d.ts +35 -0
- package/dist/session-name.d.ts.map +1 -0
- package/dist/session-name.js +60 -0
- package/dist/session-name.js.map +1 -0
- package/dist/session-node-manager.d.ts +47 -62
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +297 -97
- package/dist/session-node-manager.js.map +1 -1
- package/dist/session-read-handlers.d.ts +38 -0
- package/dist/session-read-handlers.d.ts.map +1 -0
- package/dist/session-read-handlers.js +233 -0
- package/dist/session-read-handlers.js.map +1 -0
- package/dist/session-relay-client.d.ts +8 -8
- package/dist/session-relay-client.d.ts.map +1 -1
- package/dist/session-relay-client.js +44 -46
- package/dist/session-relay-client.js.map +1 -1
- package/dist/session-tree.d.ts +11 -23
- package/dist/session-tree.d.ts.map +1 -1
- package/dist/session-tree.js +14 -26
- package/dist/session-tree.js.map +1 -1
- package/dist/signaling-connect.d.ts +13 -5
- package/dist/signaling-connect.d.ts.map +1 -1
- package/dist/signaling-connect.js +55 -13
- package/dist/signaling-connect.js.map +1 -1
- package/dist/sqlcipher-db.d.ts +18 -19
- package/dist/sqlcipher-db.d.ts.map +1 -1
- package/dist/sqlcipher-db.js +30 -30
- package/dist/sqlcipher-db.js.map +1 -1
- package/dist/telegram-doorbell.d.ts +38 -0
- package/dist/telegram-doorbell.d.ts.map +1 -0
- package/dist/telegram-doorbell.js +139 -0
- package/dist/telegram-doorbell.js.map +1 -0
- package/dist/types.d.ts +86 -79
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/dist/vocabulary.d.ts +30 -30
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +41 -49
- package/dist/vocabulary.js.map +1 -1
- 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":"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"}
|
|
@@ -28,7 +28,7 @@ export interface LeafDeliverFrame {
|
|
|
28
28
|
structure1_cbor: Uint8Array;
|
|
29
29
|
structure2_cbor: Uint8Array;
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
72
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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":"AAkCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;
|
|
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
|
-
*
|
|
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).
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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 {
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
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
|
-
*
|
|
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
|
|
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 =
|
|
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
|
-
*
|
|
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
|
|
359
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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(
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
707
|
-
//
|
|
708
|
-
//
|
|
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 =
|
|
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
|
-
//
|
|
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
|
-
//
|
|
776
|
+
// Settle any in-flight record so #doRecord resolves promptly.
|
|
779
777
|
{
|
|
780
778
|
const r = this.#pendingRecord;
|
|
781
779
|
this.#pendingRecord = null;
|