@cello-protocol/daemon 0.0.53 → 0.0.54

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 (173) 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 +0 -11
  36. package/dist/daemon.d.ts.map +1 -1
  37. package/dist/daemon.js +407 -4552
  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-content-handlers.d.ts +25 -0
  130. package/dist/session-content-handlers.d.ts.map +1 -0
  131. package/dist/session-content-handlers.js +436 -0
  132. package/dist/session-content-handlers.js.map +1 -0
  133. package/dist/session-name.d.ts +35 -0
  134. package/dist/session-name.d.ts.map +1 -0
  135. package/dist/session-name.js +60 -0
  136. package/dist/session-name.js.map +1 -0
  137. package/dist/session-node-manager.d.ts +26 -62
  138. package/dist/session-node-manager.d.ts.map +1 -1
  139. package/dist/session-node-manager.js +115 -92
  140. package/dist/session-node-manager.js.map +1 -1
  141. package/dist/session-read-handlers.d.ts +38 -0
  142. package/dist/session-read-handlers.d.ts.map +1 -0
  143. package/dist/session-read-handlers.js +233 -0
  144. package/dist/session-read-handlers.js.map +1 -0
  145. package/dist/session-relay-client.d.ts +8 -8
  146. package/dist/session-relay-client.d.ts.map +1 -1
  147. package/dist/session-relay-client.js +44 -46
  148. package/dist/session-relay-client.js.map +1 -1
  149. package/dist/session-tree.d.ts +11 -23
  150. package/dist/session-tree.d.ts.map +1 -1
  151. package/dist/session-tree.js +14 -26
  152. package/dist/session-tree.js.map +1 -1
  153. package/dist/signaling-connect.d.ts +2 -5
  154. package/dist/signaling-connect.d.ts.map +1 -1
  155. package/dist/signaling-connect.js +7 -12
  156. package/dist/signaling-connect.js.map +1 -1
  157. package/dist/sqlcipher-db.d.ts +18 -19
  158. package/dist/sqlcipher-db.d.ts.map +1 -1
  159. package/dist/sqlcipher-db.js +30 -30
  160. package/dist/sqlcipher-db.js.map +1 -1
  161. package/dist/telegram-doorbell.d.ts +38 -0
  162. package/dist/telegram-doorbell.d.ts.map +1 -0
  163. package/dist/telegram-doorbell.js +139 -0
  164. package/dist/telegram-doorbell.js.map +1 -0
  165. package/dist/types.d.ts +86 -79
  166. package/dist/types.d.ts.map +1 -1
  167. package/dist/types.js +0 -1
  168. package/dist/types.js.map +1 -1
  169. package/dist/vocabulary.d.ts +30 -30
  170. package/dist/vocabulary.d.ts.map +1 -1
  171. package/dist/vocabulary.js +41 -49
  172. package/dist/vocabulary.js.map +1 -1
  173. package/package.json +5 -5
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Normalize the persisted FROST blobs to the canonical CBOR encoding.
3
+ *
4
+ * `frost_commitments` and `frost_verifying_shares` may hold an older encoding (cbor-x tag-64 or its
5
+ * record extension) alongside the canonical one; cbor-x decodes all three, which is why a mixed
6
+ * column reads correctly and can go unnoticed. This makes the column hold ONE encoding, so a reader
7
+ * that is not cbor-x can parse it.
8
+ *
9
+ * Decode, re-encode canonically, rewrite only if the bytes differ. Self-checking and idempotent: a
10
+ * canonical blob re-encodes to itself and is skipped, so there is no "have I run yet?" flag to get
11
+ * wrong and a second run is a no-op.
12
+ *
13
+ * FAILS SAFE, PER ROW. These blobs are FROST key material. A row that cannot be decoded is LEFT
14
+ * EXACTLY AS IT WAS and reported — never dropped, never half-written, never guessed at. Destroying a
15
+ * share makes the agent permanently unable to sign or seal, which is strictly worse than leaving it
16
+ * in an old encoding that still decodes. Nobody may lose a key to this migration.
17
+ */
18
+ import { encodeCbor, decodeCbor } from "@cello-protocol/protocol-types";
19
+ /** The columns written by both producers. Both hold CBOR; both must hold the canonical encoding. */
20
+ const CBOR_BLOB_COLUMNS = ["frost_commitments", "frost_verifying_shares"];
21
+ function toBytes(v) {
22
+ if (v instanceof Uint8Array)
23
+ return v;
24
+ if (Buffer.isBuffer(v))
25
+ return new Uint8Array(v);
26
+ return null;
27
+ }
28
+ /**
29
+ * Do two decoded CBOR values carry the same DATA?
30
+ *
31
+ * Compares byte CONTENT, never container class. cbor-x decodes a byte string to a Node Buffer and a
32
+ * tag-64 to a Uint8Array — identical bytes, different wrapper. A class-sensitive compare
33
+ * (`JSON.stringify`, `toEqual`) calls those unequal and refuses every genuine migration.
34
+ */
35
+ function sameDecodedValue(a, b) {
36
+ const ab = toBytes(a);
37
+ const bb = toBytes(b);
38
+ if (ab || bb) {
39
+ return ab !== null && bb !== null && Buffer.from(ab).equals(Buffer.from(bb));
40
+ }
41
+ if (Array.isArray(a) || Array.isArray(b)) {
42
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
43
+ return false;
44
+ return a.every((v, i) => sameDecodedValue(v, b[i]));
45
+ }
46
+ if (a && b && typeof a === "object" && typeof b === "object") {
47
+ const ao = a;
48
+ const bo = b;
49
+ const ak = Object.keys(ao).sort();
50
+ const bk = Object.keys(bo).sort();
51
+ if (ak.length !== bk.length || ak.some((k, i) => k !== bk[i]))
52
+ return false;
53
+ return ak.every((k) => sameDecodedValue(ao[k], bo[k]));
54
+ }
55
+ return Object.is(a, b);
56
+ }
57
+ /**
58
+ * Rewrite any non-canonically-encoded FROST blob in `agents` to the canonical encoding.
59
+ * Idempotent. Safe to run on every daemon start.
60
+ */
61
+ export function migrateCborBlobsToCanonical(db, logger) {
62
+ // The table may predate these columns (fresh DB, or an install that never registered).
63
+ const columns = new Set(db.prepare("PRAGMA table_info(agents)").all().map((c) => c.name));
64
+ const present = CBOR_BLOB_COLUMNS.filter((c) => columns.has(c));
65
+ if (present.length === 0)
66
+ return;
67
+ const rows = db
68
+ .prepare(`SELECT agent_id, ${present.join(", ")} FROM agents`)
69
+ .all();
70
+ let rewritten = 0;
71
+ let skipped = 0;
72
+ for (const row of rows) {
73
+ const agentId = String(row["agent_id"]);
74
+ for (const column of present) {
75
+ const stored = toBytes(row[column]);
76
+ if (!stored || stored.length === 0)
77
+ continue; // never registered / no share — nothing to do
78
+ let canonical;
79
+ try {
80
+ canonical = encodeCbor(decodeCbor(stored));
81
+ }
82
+ catch (err) {
83
+ // Undecodable key material — leave it alone.
84
+ skipped++;
85
+ logger.warn("daemon.cbor.migration.undecodable", {
86
+ agentId,
87
+ column,
88
+ bytes: stored.length,
89
+ error: err instanceof Error ? err.message : String(err),
90
+ });
91
+ continue;
92
+ }
93
+ if (Buffer.from(canonical).equals(Buffer.from(stored)))
94
+ continue; // already canonical
95
+ // Verify BEFORE writing: re-encoding must preserve the VALUE, not merely produce bytes. If it
96
+ // ever altered the decoded content, refuse rather than persist a corrupted share.
97
+ if (!sameDecodedValue(decodeCbor(canonical), decodeCbor(stored))) {
98
+ skipped++;
99
+ logger.error("daemon.cbor.migration.value_changed", {
100
+ agentId,
101
+ column,
102
+ guidance: "re-encoding altered the decoded value; the blob was NOT rewritten",
103
+ });
104
+ continue;
105
+ }
106
+ db.prepare(`UPDATE agents SET ${column} = ? WHERE agent_id = ?`).run(Buffer.from(canonical), agentId);
107
+ rewritten++;
108
+ logger.info("daemon.cbor.migration.rewritten", { agentId, column, bytes: canonical.length });
109
+ }
110
+ }
111
+ if (rewritten > 0 || skipped > 0) {
112
+ logger.info("daemon.cbor.migration.complete", { rewritten, skipped, rows: rows.length });
113
+ }
114
+ }
115
+ //# sourceMappingURL=cbor-blob-migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cbor-blob-migration.js","sourceRoot":"","sources":["../src/cbor-blob-migration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { SignalingManager } from "@cello-protocol/transport";
2
+ import type { KeyProvider } from "@cello-protocol/crypto";
3
+ import type { IpcHandler } from "./ipc-server.js";
4
+ import type { SessionNodeManager } from "./session-node-manager.js";
5
+ import type { Logger, SessionRecord } from "./types.js";
6
+ import type { ConnState } from "./contact-handlers.js";
7
+ import type { SealFlowResult, ActiveSealResult } from "./seal-flows.js";
8
+ import type { SealCompletion, UnilateralResult } from "./seal-coordinator.js";
9
+ import type { DirectoryEndpoint } from "./signaling-connect.js";
10
+ import type { ConsortiumEndpoint } from "./directory-bootstrap.js";
11
+ export interface CloseSessionDeps {
12
+ handlers: Map<string, IpcHandler>;
13
+ logger: Logger;
14
+ sessionNodeManager: SessionNodeManager;
15
+ getConnState: (connectionId: string) => ConnState | undefined;
16
+ resolveCurrentAgent: (connState: ConnState | undefined, explicitAgent?: string) => string | null;
17
+ NO_CURRENT_AGENT_RESPONSE: unknown;
18
+ getKeyProvider: (agentName: string) => KeyProvider | undefined;
19
+ signalingFor: (agentName: string) => SignalingManager | undefined;
20
+ sendOver: (agentName: string, frame: Record<string, unknown>) => Promise<{
21
+ ok: boolean;
22
+ reason?: string;
23
+ }>;
24
+ waitForSignalingConnected: (mgr: SignalingManager, timeoutMs: number) => Promise<boolean>;
25
+ openVisitingConnection: (agentName: string, agentKeyProvider: KeyProvider, agentPubkeyHex: string, endpoint: DirectoryEndpoint, correlationId: string, nodeId: string) => {
26
+ mgr: SignalingManager;
27
+ stop: (reason: string) => Promise<void>;
28
+ };
29
+ /** sessionId → the broker node id that brokered this cross-node session. */
30
+ crossNodeBrokerBySession: Map<string, string>;
31
+ sealKey: (agentName: string, sessionId: string) => string;
32
+ sealInterruptedInProgress: Set<string>;
33
+ pendingSealWaiters: Map<string, (completion: SealCompletion) => void>;
34
+ pendingUnilateralWaiters: Map<string, (r: UnilateralResult) => void>;
35
+ /** The verified consortium roster, re-resolved at ceremony time. */
36
+ resolveConsortiumRoster: () => Promise<ConsortiumEndpoint[] | null>;
37
+ handleSealInterruptedFlow: (sessionId: string, record: SessionRecord, correlationId: string, merkleRootAtInterruption: string) => Promise<SealFlowResult>;
38
+ handleActiveSealFlow: (sessionId: string, record: SessionRecord, correlationId: string) => Promise<ActiveSealResult>;
39
+ }
40
+ export declare function registerCloseSessionHandler(deps: CloseSessionDeps): void;
41
+ //# sourceMappingURL=close-session-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close-session-handler.d.ts","sourceRoot":"","sources":["../src/close-session-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,399 @@
1
+ /**
2
+ * cello_close_session — the bilateral close, and every way it can fail.
3
+ *
4
+ * M7 error discipline: each distinct failure cause produces a DISTINCT error code
5
+ * (session_already_sealed, seal_interrupted_in_progress, seal_interrupted_counterparty_unavailable,
6
+ * seal_interrupted_rejected_by_counterparty, signaling_reconnecting). A close that fails must tell
7
+ * the operator WHY, not merely that it failed — this handler exists as much for its error paths as
8
+ * for its happy path.
9
+ *
10
+ * SI-001: there is NO auto-seal on a session_interrupted receipt. The operator must close
11
+ * explicitly. A daemon that sealed on its own would notarize a conversation nobody chose to end.
12
+ *
13
+ * This was on my DO-NOT-CUT list. It has fifteen dependencies — a long list, but a KNOWN one, and
14
+ * that is the entire difference from a closure over 73 shared locals.
15
+ */
16
+ import { randomUUID } from "node:crypto";
17
+ import { validateSessionName } from "./session-name.js";
18
+ export function registerCloseSessionHandler(deps) {
19
+ const { resolveConsortiumRoster, handlers, logger, sessionNodeManager, getConnState, resolveCurrentAgent, NO_CURRENT_AGENT_RESPONSE, getKeyProvider, signalingFor, sendOver, waitForSignalingConnected, openVisitingConnection, crossNodeBrokerBySession, sealKey, sealInterruptedInProgress, pendingSealWaiters, pendingUnilateralWaiters, handleSealInterruptedFlow, handleActiveSealFlow, } = deps;
20
+ // ─── M7-SESSION-001: cello_close_session ────────────────────────────────────
21
+ // M7 error discipline: each distinct failure cause produces a distinct error code.
22
+ // AC-010: session_already_sealed
23
+ // AC-011: seal_interrupted_in_progress
24
+ // AC-012: seal_interrupted_counterparty_unavailable
25
+ // AC-013: seal_interrupted_rejected_by_counterparty
26
+ // DB-001: signaling_reconnecting
27
+ // SI-001: no auto-seal on session_interrupted receipt; operator must call explicitly
28
+ handlers.set("cello_close_session", async (params, connectionId) => {
29
+ const connState = getConnState(connectionId);
30
+ // CC-3 / M8C-AUTOSTART-1 F18: explicit { agent } > this connection's current > sole online agent.
31
+ const agentName = resolveCurrentAgent(connState, params?.agent);
32
+ if (!agentName) {
33
+ return NO_CURRENT_AGENT_RESPONSE;
34
+ }
35
+ // round-2 BLOCKING: the public IPC contract field is snake_case `session_id`
36
+ // (this is what cello-mcp.ts forwards verbatim through IpcProxy, matching the
37
+ // rest of the public MCP tool surface — target_pubkey, content_hash, timeout_ms).
38
+ // Reading camelCase `sessionId` here meant every real proxy invocation produced
39
+ // undefined → missing_params. Consume the field the producer actually sends.
40
+ const sessionId = params?.session_id;
41
+ if (!sessionId) {
42
+ return {
43
+ ok: false,
44
+ reason: "missing_params",
45
+ guidance: "Provide 'session_id' parameter with the hex session ID to close.",
46
+ };
47
+ }
48
+ // DOD-SESSION-NAME-1 (AC-A6): THE NAME MUST NEVER BREAK THE CLOSE. A close is a seal ceremony
49
+ // and the seal is the valuable thing — a notarized receipt both parties keep. So a bad name is
50
+ // refused HERE, before any of it starts: not half-closed-then-failed, and not silently dropped
51
+ // and sealed anyway. Validate, then close. The name is a sticky note; it does not get a vote on
52
+ // whether the notarization happens.
53
+ const nameCheck = validateSessionName(params?.session_name);
54
+ if (!nameCheck.ok) {
55
+ logger.warn("session.name.rejected", {
56
+ // agentId, not agentName — session.name.set/.cleared key on agentId (AC-A16), and an
57
+ // operator correlating rejections against sets on two different keys gets nothing.
58
+ agentId: sessionNodeManager.resolveAgentId(agentName), sessionId, reason: nameCheck.reason, source: "close",
59
+ nameLength: typeof params?.session_name === "string" ? params.session_name.length : null,
60
+ });
61
+ return { ok: false, reason: nameCheck.reason, guidance: nameCheck.guidance };
62
+ }
63
+ // DOD-LOOP-1: scope the lookup to the current agent — the composite (agent, session_id) key IS
64
+ // the ownership scope. A session_id owned only by a DIFFERENT agent does not exist in this
65
+ // agent's namespace (returns null → session_not_found), which is correct for loopback (two
66
+ // agents can hold the same session_id on one daemon).
67
+ const record = sessionNodeManager.getSessionRecord(agentName, sessionId);
68
+ if (!record) {
69
+ return {
70
+ ok: false,
71
+ reason: "session_not_found",
72
+ guidance: "No session found with this ID. Check cello_sessions for active and interrupted sessions.",
73
+ };
74
+ }
75
+ // Ownership: redundant now that the lookup is agent-scoped (record.agent_name === currentAgent),
76
+ // kept as a defensive invariant.
77
+ if (record.agent_name !== agentName) {
78
+ return {
79
+ ok: false,
80
+ reason: "session_not_owned",
81
+ guidance: "This session belongs to a different agent. Call cello_use_agent to switch to the agent that owns it (see cello_sessions), then retry.",
82
+ };
83
+ }
84
+ // DOD-SESSION-NAME-1 (AC-A7): the name is written HERE — once, on the way in — rather than at
85
+ // each of the terminal exits (bilateral seal, unilateral seal, force-abandon). Threading it
86
+ // through all of them is how one silently ends up without it, and a name dropped on the very
87
+ // path where it is most useful (a force-abandoned ghost is the session you most want to identify
88
+ // later) is a defect nothing would fail on.
89
+ //
90
+ // It sits ABOVE the status gates deliberately. A name is not close-scoped: renaming is legal in
91
+ // ANY status (AC-A9), sealed included. Below the already-sealed gate, a RETRIED close carrying a
92
+ // name — the seal completed but the agent's call was interrupted, so it calls again — would be
93
+ // answered "already sealed, no further action is needed" and the name would be silently dropped,
94
+ // on exactly the path where the agent believes it just named the session.
95
+ //
96
+ // Writing it before the close COMPLETES is safe for the same reason: if the seal below fails, the
97
+ // operator has a named open session — a state they could produce with cello_name_session anyway.
98
+ // Nothing about the name gates, delays, or alters the ceremony.
99
+ if (nameCheck.value !== null) {
100
+ sessionNodeManager.setSessionName(agentName, sessionId, nameCheck.value);
101
+ logger.info("session.name.set", {
102
+ agentId: record.agent_id, sessionId, nameLength: nameCheck.value.length, source: "close",
103
+ });
104
+ }
105
+ // AC-010: already sealed
106
+ if (record.status === "sealed") {
107
+ return {
108
+ ok: false,
109
+ reason: "session_already_sealed",
110
+ // A name given here WAS applied (above) — say so, rather than let "no further action is
111
+ // needed" imply the name went nowhere.
112
+ ...(nameCheck.value !== null ? { session_name: nameCheck.value } : {}),
113
+ guidance: nameCheck.value !== null
114
+ ? "This session is already sealed, so it was not closed again — but the name you gave WAS applied. Check cello_sessions to view its sealed record and the FROST notarization."
115
+ : "This session is already sealed. No further action is needed — check cello_sessions to view its sealed record and the FROST notarization.",
116
+ };
117
+ }
118
+ // An ABANDONED session is TERMINAL, and a close without --force must say so rather than try to
119
+ // seal it. The already-abandoned early-return below lives INSIDE the force branch, so a plain
120
+ // `cello close-session <abandoned-id>` fell through to the seal flow and fired a bilateral seal
121
+ // ceremony at a counterparty that, by definition, was never there — the exact unsealable hang
122
+ // that force exists to escape, re-entered from the other side. Found by the unit reviewer on this
123
+ // diff; pre-existing, fixed here rather than left for someone to hit in the dark.
124
+ // Scoped to the NON-force path: `force` on an already-abandoned session stays idempotent
125
+ // (ok:true / already_abandoned, below) — that contract is tested and must not change.
126
+ if (record.status === "abandoned" && params?.force !== true) {
127
+ return {
128
+ ok: false,
129
+ reason: "session_abandoned",
130
+ guidance: "This session was force-abandoned — it is terminal and has no counterparty to seal with, so it cannot be closed. It is already off the open list; check cello_sessions.",
131
+ };
132
+ }
133
+ // CC-5/F21 terminal-escape: force-abandon a session that can never be bilaterally sealed — a
134
+ // half-open handshake the counterparty never joined, whose normal close fires a seal the absent/
135
+ // rejecting counterparty can't complete (seal_interrupted_rejected_by_counterparty / timeout). Marks
136
+ // it locally-terminal ("abandoned") with NO seal, so it leaves the open list. Additive: placed BEFORE
137
+ // the seal branches, it never touches the seal flow. Owner-scoped (the lookup above is agent-scoped)
138
+ // and idempotent. NOT for a healthy session — a normal close (no force) still seals so both parties
139
+ // get a notarized receipt; force is the escape hatch for a provably unsealable ghost.
140
+ if (params?.force === true) {
141
+ if (record.status === "abandoned") {
142
+ return { ok: true, status: "abandoned", reason: "already_abandoned", guidance: "This session was already force-abandoned." };
143
+ }
144
+ await sessionNodeManager.abandonSession(record.agent_name, sessionId);
145
+ logger.info("session.force_abandoned", { agentName: record.agent_name, sessionId, priorStatus: record.status });
146
+ return {
147
+ ok: true,
148
+ status: "abandoned",
149
+ reason: "force_abandoned",
150
+ guidance: `Session ${sessionId} was force-abandoned — marked terminal locally with no bilateral seal. Use force only for a half-open session that cannot be sealed; a normal close (no force) still attempts the seal so both parties get a notarized receipt.`,
151
+ };
152
+ }
153
+ // AC-011: seal-interrupted already in progress
154
+ if (sealInterruptedInProgress.has(sealKey(record.agent_name, sessionId))) {
155
+ return {
156
+ ok: false,
157
+ reason: "seal_interrupted_in_progress",
158
+ guidance: "A seal-interrupted attempt is already in progress for this session. Wait for session.interrupted.sealed to appear in the daemon logs before retrying. Do not call cello_close_session again until the current attempt completes or times out.",
159
+ };
160
+ }
161
+ // DB-001: signaling stream reconnecting
162
+ if (record.status === "interrupted" && signalingFor(record.agent_name)?.status === "reconnecting") {
163
+ return {
164
+ ok: false,
165
+ reason: "signaling_reconnecting",
166
+ guidance: "The directory signaling stream is reconnecting. Wait for directory_signaling to show connected in cello status before initiating seal-interrupted. The daemon reconnects automatically — no manual intervention required.",
167
+ };
168
+ }
169
+ // AC-012 / AC-013: seal-interrupted bilateral flow for interrupted sessions.
170
+ // BLOCKING-1 fix: await the flow synchronously so the caller receives the real result
171
+ // (counterparty_unavailable, rejected_by_counterparty, or sealed).
172
+ // The sealInterruptedInProgress Set still guards concurrent calls (AC-011).
173
+ if (record.status === "interrupted") {
174
+ // H-1: the Merkle root at interruption is held by the client (the daemon
175
+ // does not maintain the session Merkle tree). The client supplies it here
176
+ // so both parties co-sign over the same root. Absent → empty string, in
177
+ // which case the bilateral commitment binds leafCount only.
178
+ const merkleRootAtInterruption = typeof params?.merkleRootAtInterruption === "string" ? params.merkleRootAtInterruption : "";
179
+ sealInterruptedInProgress.add(sealKey(record.agent_name, sessionId));
180
+ const correlationId = randomUUID();
181
+ try {
182
+ return await handleSealInterruptedFlow(sessionId, record, correlationId, merkleRootAtInterruption);
183
+ }
184
+ finally {
185
+ sealInterruptedInProgress.delete(sealKey(record.agent_name, sessionId));
186
+ }
187
+ }
188
+ // CELLO-M7-DAEMON-004 (AC-003): ACTIVE session — initiate the active-session
189
+ // seal over the daemon's OWN tree root. SI-001: any caller-supplied
190
+ // merkleRoot is IGNORED; the daemon signs only the root it built itself.
191
+ //
192
+ // round-2 finding #4: the active path must take the SAME concurrency guard as
193
+ // the interrupted path (the top-of-handler check at sealInterruptedInProgress.has
194
+ // rejects re-entry). Without adding to the set here, two concurrent active closes
195
+ // would both send a seal_interrupted_request and both await acks (double seal).
196
+ if (record.status === "active") {
197
+ sealInterruptedInProgress.add(sealKey(record.agent_name, sessionId));
198
+ const correlationId = randomUUID();
199
+ // Fix #1 (cross-node seal-liveness): if this session was brokered by another node, the seal_verified
200
+ // + session_sealed frames are pushed by that BROKER — but the initiator released its visiting
201
+ // connection after setup, so on the home stream they never arrive and close times out. Re-open a
202
+ // transient visiting connection to the broker (seal-capable — openVisitingConnection now wires the
203
+ // seal handlers) for the duration of the seal, then release it in finally. Same-node sessions have
204
+ // no entry here and use the home stream unchanged.
205
+ let sealBrokerConn = null;
206
+ try {
207
+ const brokerNodeForSeal = crossNodeBrokerBySession.get(`${record.agent_name}:${sessionId}`);
208
+ if (brokerNodeForSeal) {
209
+ const sealKp = getKeyProvider(record.agent_name);
210
+ if (!sealKp) {
211
+ // Fallback-finder #1: never skip the cross-node reconnect silently — without a key provider
212
+ // we cannot open the broker connection, so the seal will revert to the pre-fix timeout. Log
213
+ // WHY so it is not indistinguishable from a normal counterparty-didn't-close timeout.
214
+ logger.warn("session.seal.broker.no_keyprovider", { agentName: record.agent_name, brokerNode: brokerNodeForSeal, correlationId });
215
+ }
216
+ else {
217
+ const sealPubHex = Buffer.from(await sealKp.getPublicKey()).toString("hex");
218
+ const roster = await resolveConsortiumRoster();
219
+ const brokerTarget = roster?.find((e) => e.nodeId === brokerNodeForSeal) ?? null;
220
+ if (!brokerTarget) {
221
+ logger.warn("session.seal.broker.unresolved", { agentName: record.agent_name, brokerNode: brokerNodeForSeal, correlationId });
222
+ }
223
+ else {
224
+ const conn = openVisitingConnection(record.agent_name, sealKp, sealPubHex, { peerId: brokerTarget.peerId, multiaddr: brokerTarget.multiaddr }, correlationId, brokerNodeForSeal);
225
+ if (await waitForSignalingConnected(conn.mgr, 10_000)) {
226
+ sealBrokerConn = conn;
227
+ logger.info("session.seal.broker.reconnected", { agentName: record.agent_name, brokerNode: brokerNodeForSeal, correlationId });
228
+ }
229
+ else {
230
+ await conn.stop("seal-broker-unreachable");
231
+ logger.warn("session.seal.broker.unreachable", { agentName: record.agent_name, brokerNode: brokerNodeForSeal, correlationId });
232
+ }
233
+ }
234
+ }
235
+ }
236
+ // M7 DOD-SPINE-7: relay-mediated bilateral seal. Submit our SEAL ctrl leaf to the
237
+ // relay witness; when the counterparty ALSO closes, the relay's #maybeProcessSeal
238
+ // fires → directory processSeal rebuilds + verifies the signed chain → FROST
239
+ // notarization → session_sealed to BOTH parties. Register the waiter BEFORE
240
+ // submitting so the notification can never race ahead of us.
241
+ let resolveSeal;
242
+ const sealedP = new Promise((r) => { resolveSeal = r; });
243
+ pendingSealWaiters.set(sealKey(record.agent_name, sessionId), resolveSeal);
244
+ const submit = await sessionNodeManager.submitSealLeaf(record.agent_name, sessionId, correlationId);
245
+ // M7-UPGRADE-002: the auto-acknowledge path may have already submitted THIS party's
246
+ // responder SEAL leaf (it won the race against this explicit close). That is success, not
247
+ // failure — keep the waiter registered and fall through to await session_sealed (the
248
+ // auto-ack's submission drives the same bilateral seal).
249
+ if (!submit.ok && submit.reason !== "responder_seal_already_submitted") {
250
+ pendingSealWaiters.delete(sealKey(record.agent_name, sessionId));
251
+ if (submit.reason === "relay_unavailable") {
252
+ // No relay witness for this session (direct/interrupted) — fall back to the
253
+ // directory-mediated bilateral-ack seal.
254
+ return await handleActiveSealFlow(sessionId, record, correlationId);
255
+ }
256
+ return {
257
+ ok: false,
258
+ reason: submit.reason,
259
+ guidance: "The SEAL leaf could not be submitted to the relay witness. Retry once the relay is reachable (cello status).",
260
+ };
261
+ }
262
+ // Both parties must close for the directory to notarize. Await session_sealed; a
263
+ // timeout means the counterparty has not closed yet (our leaf is recorded — the
264
+ // session seals when they call cello_close_session). CELLO_SEAL_BILATERAL_TIMEOUT_MS
265
+ // tunes how long to wait for the counterparty before escalating to a unilateral seal.
266
+ const bilateralTimeoutMs = Number(process.env["CELLO_SEAL_BILATERAL_TIMEOUT_MS"]) || 30_000;
267
+ let timer;
268
+ const timeoutP = new Promise((r) => { timer = setTimeout(() => r(null), bilateralTimeoutMs); });
269
+ const sealedCompletion = await Promise.race([sealedP, timeoutP]);
270
+ clearTimeout(timer);
271
+ pendingSealWaiters.delete(sealKey(record.agent_name, sessionId));
272
+ if (sealedCompletion !== null) {
273
+ logger.info("session.seal.completed", { sessionId, sealedRoot: sealedCompletion.rootHex, role: "bilateral", correlationId });
274
+ crossNodeBrokerBySession.delete(`${record.agent_name}:${sessionId}`); // Fix #1 review: evict on terminal seal success (a FAILED close keeps the entry so a retry can still reconnect).
275
+ // M7-SESSION-004 (AC-006): return the legibility certificate on the seal completion so
276
+ // a reader gets it on the same surface that proves the seal — receipt-not-assent,
277
+ // per-party frontiers, attestation modes, and final_message.answered.
278
+ return { ok: true, sealed_root: sealedCompletion.rootHex, legibility: sealedCompletion.legibility };
279
+ }
280
+ // M8B FINDING-1: `responder_seal_already_submitted` has two producers — (a) the auto-ack
281
+ // path submitted our leaf because the COUNTERPARTY's SEAL arrived, or (b) our OWN earlier
282
+ // close submitted it and the counterparty never co-closed. The result now carries the
283
+ // first submit's reportedRootHex/sequenceNumber, so a retry close can still escalate to a
284
+ // unilateral seal (case b — the live-deadlock path). We deliberately do NOT distinguish
285
+ // (a) from (b) client-side: the bilateral wait above already gave case (a) its window, and
286
+ // the directory's grace/already-sealed gates arbitrate a redundant seal_unilateral — that
287
+ // is the sovereign-node-correct shape. Only when the not-ok result carries NO root (the
288
+ // first submit is still in flight) is "pending" the honest terminal answer here.
289
+ const escalation = submit.ok
290
+ ? { reportedRootHex: submit.reportedRootHex, sequenceNumber: submit.sequenceNumber }
291
+ : submit.reason === "responder_seal_already_submitted" &&
292
+ typeof submit.reportedRootHex === "string" &&
293
+ typeof submit.sequenceNumber === "number"
294
+ ? { reportedRootHex: submit.reportedRootHex, sequenceNumber: submit.sequenceNumber }
295
+ : null;
296
+ if (!escalation) {
297
+ return {
298
+ ok: false,
299
+ reason: "seal_pending_bilateral",
300
+ guidance: "Your SEAL leaf is recorded (auto-acknowledged) and the bilateral seal is completing, but it did not finalize within the wait window. Check cello status and the daemon logs; retry cello_close_session if the session remains unsealed.",
301
+ };
302
+ }
303
+ // SESSION-002 (DOD-SEAL): the counterparty did not co-close. Escalate to a UNILATERAL
304
+ // seal — submit a seal_unilateral request carrying our reported_root (the content-hash
305
+ // root the directory rebuilds from the relay chain and verifies). The directory enforces
306
+ // the delivery-grace gate; if grace has not elapsed it replies seal_unilateral_too_early.
307
+ let resolveUni;
308
+ const uniP = new Promise((r) => { resolveUni = r; });
309
+ pendingUnilateralWaiters.set(sessionId, resolveUni);
310
+ // FED-OPTIONB-SEAL-001 (Option B): carry the full leaf chain (both parties) + the relay receipts so
311
+ // the directory rebuilds + verifies the tree OFFLINE — no directory→relay getSealLeaves dial. The
312
+ // store is keyed by the agent's K_local pubkey (the same key the relay client recorded under).
313
+ const sealAgentKp = getKeyProvider(record.agent_name);
314
+ const sealAgentPubkeyHex = sealAgentKp ? Buffer.from(await sealAgentKp.getPublicKey()).toString("hex") : "";
315
+ const sealCarry = sealAgentPubkeyHex ? sessionNodeManager.getSealCarry(sealAgentPubkeyHex, sessionId) : [];
316
+ const seal_leaves = sealCarry.map((l) => ({
317
+ sequence_number: l.sequenceNumber,
318
+ leaf_kind: l.leafKind,
319
+ structure2_cbor: l.structure2Cbor,
320
+ structure1_cbor: l.structure1Cbor,
321
+ // Relay receipt (present only for the present party's OWN leaves — the seq-pinning teeth).
322
+ relay_id: l.relayId,
323
+ relay_timestamp: l.relayTimestamp,
324
+ relay_signature: l.relaySignatureHex ? new Uint8Array(Buffer.from(l.relaySignatureHex, "hex")) : undefined,
325
+ }));
326
+ const sent = await sendOver(record.agent_name, {
327
+ type: "seal_unilateral",
328
+ session_id: new Uint8Array(Buffer.from(sessionId, "hex")),
329
+ reported_root: new Uint8Array(Buffer.from(escalation.reportedRootHex, "hex")),
330
+ reported_seq: escalation.sequenceNumber,
331
+ seal_leaves,
332
+ });
333
+ if (!sent.ok) {
334
+ pendingUnilateralWaiters.delete(sessionId);
335
+ return {
336
+ ok: false,
337
+ reason: "seal_unilateral_send_failed",
338
+ guidance: "The unilateral seal request could not be sent to the directory. Check the directory connection (cello status) and retry cello_close_session.",
339
+ };
340
+ }
341
+ let uniTimer;
342
+ const uniTimeoutP = new Promise((r) => {
343
+ uniTimer = setTimeout(() => r({ ok: false, reason: "seal_unilateral_timeout" }), 30_000);
344
+ });
345
+ const uniResult = await Promise.race([uniP, uniTimeoutP]);
346
+ clearTimeout(uniTimer);
347
+ pendingUnilateralWaiters.delete(sessionId);
348
+ if (uniResult.ok) {
349
+ logger.info("session.seal.completed", { sessionId, sealedRoot: uniResult.sealedRootHex, role: "unilateral", correlationId });
350
+ crossNodeBrokerBySession.delete(`${record.agent_name}:${sessionId}`); // Fix #1 review: evict on terminal seal success (a FAILED close keeps the entry so a retry can still reconnect).
351
+ // M8B FINDING-3 (cascade-2): return the legibility certificate inline — same shape as the
352
+ // bilateral close (AC-006) — so the operator gets the receipt on the surface that proves
353
+ // the seal, not just a bare root. It is also persisted (above) for cello_get_sealed_receipt.
354
+ return {
355
+ ok: true,
356
+ sealed_root: uniResult.sealedRootHex,
357
+ seal_type: "unilateral",
358
+ ...(uniResult.legibility !== undefined ? { legibility: uniResult.legibility } : {}),
359
+ };
360
+ }
361
+ if (uniResult.reason === "seal_unilateral_too_early") {
362
+ // F20: tell the operator WHEN the unilateral seal becomes available, not just "later".
363
+ const when = typeof uniResult.remainingSeconds === "number"
364
+ ? `A unilateral seal becomes available in ~${uniResult.remainingSeconds}s. `
365
+ : "";
366
+ return {
367
+ ok: false,
368
+ reason: "seal_counterparty_pending",
369
+ guidance: `Your SEAL leaf is recorded, but the counterparty has not closed and the directory's delivery-grace window has not yet elapsed, so a unilateral seal is not yet allowed. ${when}Retry cello_close_session after the grace period, or once the counterparty closes.`,
370
+ };
371
+ }
372
+ return {
373
+ ok: false,
374
+ reason: uniResult.reason,
375
+ guidance: "The unilateral seal did not complete (the directory could not verify the reported root, or the certificate failed verification). Confirm your messages reached the relay (cello_sessions) before retrying cello_close_session.",
376
+ };
377
+ }
378
+ finally {
379
+ // Fix #1: release the transient broker seal-connection (best-effort; the seal result stands).
380
+ if (sealBrokerConn) {
381
+ try {
382
+ await sealBrokerConn.stop("seal-complete");
383
+ }
384
+ catch (err) {
385
+ logger.warn("session.seal.broker.release_failed", { sessionId, reason: err instanceof Error ? err.message : String(err) });
386
+ }
387
+ }
388
+ sealInterruptedInProgress.delete(sealKey(record.agent_name, sessionId));
389
+ }
390
+ }
391
+ // Any other status (e.g. seal_interrupted_pending) — nothing to do.
392
+ return {
393
+ ok: false,
394
+ reason: "session_not_closeable",
395
+ guidance: `Session is in status '${record.status}', which cannot be closed via cello_close_session. Check cello_sessions; a seal_interrupted_pending session is awaiting FROST notarization.`,
396
+ };
397
+ });
398
+ }
399
+ //# sourceMappingURL=close-session-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close-session-handler.js","sourceRoot":"","sources":["../src/close-session-handler.ts"],"names":[],"mappings":"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"}