@cello-protocol/daemon 0.0.52 → 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.
- 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 +0 -11
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +407 -4552
- 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-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 +26 -62
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +115 -92
- 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 +2 -5
- package/dist/signaling-connect.d.ts.map +1 -1
- package/dist/signaling-connect.js +7 -12
- 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,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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"}
|