@cello-protocol/daemon 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-loader.d.ts +41 -0
- package/dist/agent-loader.d.ts.map +1 -0
- package/dist/agent-loader.js +94 -0
- package/dist/agent-loader.js.map +1 -0
- package/dist/bin/cello-daemon.d.ts +13 -0
- package/dist/bin/cello-daemon.d.ts.map +1 -0
- package/dist/bin/cello-daemon.js +170 -0
- package/dist/bin/cello-daemon.js.map +1 -0
- package/dist/cello-node-transport-dialer.d.ts +59 -0
- package/dist/cello-node-transport-dialer.d.ts.map +1 -0
- package/dist/cello-node-transport-dialer.js +108 -0
- package/dist/cello-node-transport-dialer.js.map +1 -0
- package/dist/challenge-verifier.d.ts +12 -0
- package/dist/challenge-verifier.d.ts.map +1 -0
- package/dist/challenge-verifier.js +11 -0
- package/dist/challenge-verifier.js.map +1 -0
- package/dist/connect-or-start.d.ts +25 -0
- package/dist/connect-or-start.d.ts.map +1 -0
- package/dist/connect-or-start.js +117 -0
- package/dist/connect-or-start.js.map +1 -0
- package/dist/content-park-client.d.ts +49 -0
- package/dist/content-park-client.d.ts.map +1 -0
- package/dist/content-park-client.js +196 -0
- package/dist/content-park-client.js.map +1 -0
- package/dist/daemon.d.ts +65 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +3202 -0
- package/dist/daemon.js.map +1 -0
- package/dist/directory-bootstrap.d.ts +55 -0
- package/dist/directory-bootstrap.d.ts.map +1 -0
- package/dist/directory-bootstrap.js +102 -0
- package/dist/directory-bootstrap.js.map +1 -0
- package/dist/file-manifest-provider.d.ts +18 -0
- package/dist/file-manifest-provider.d.ts.map +1 -0
- package/dist/file-manifest-provider.js +72 -0
- package/dist/file-manifest-provider.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc-client.d.ts +31 -0
- package/dist/ipc-client.d.ts.map +1 -0
- package/dist/ipc-client.js +112 -0
- package/dist/ipc-client.js.map +1 -0
- package/dist/ipc-server.d.ts +49 -0
- package/dist/ipc-server.d.ts.map +1 -0
- package/dist/ipc-server.js +268 -0
- package/dist/ipc-server.js.map +1 -0
- package/dist/lock-file.d.ts +27 -0
- package/dist/lock-file.d.ts.map +1 -0
- package/dist/lock-file.js +84 -0
- package/dist/lock-file.js.map +1 -0
- package/dist/manifest-loader.d.ts +33 -0
- package/dist/manifest-loader.d.ts.map +1 -0
- package/dist/manifest-loader.js +70 -0
- package/dist/manifest-loader.js.map +1 -0
- package/dist/manifest-poll-scheduler.d.ts +31 -0
- package/dist/manifest-poll-scheduler.d.ts.map +1 -0
- package/dist/manifest-poll-scheduler.js +59 -0
- package/dist/manifest-poll-scheduler.js.map +1 -0
- package/dist/manifest-version-store-file.d.ts +18 -0
- package/dist/manifest-version-store-file.d.ts.map +1 -0
- package/dist/manifest-version-store-file.js +40 -0
- package/dist/manifest-version-store-file.js.map +1 -0
- package/dist/manifest-version-store.d.ts +14 -0
- package/dist/manifest-version-store.d.ts.map +1 -0
- package/dist/manifest-version-store.js +13 -0
- package/dist/manifest-version-store.js.map +1 -0
- package/dist/network-directory-node.d.ts +94 -0
- package/dist/network-directory-node.d.ts.map +1 -0
- package/dist/network-directory-node.js +626 -0
- package/dist/network-directory-node.js.map +1 -0
- package/dist/nonce-dedup.d.ts +68 -0
- package/dist/nonce-dedup.d.ts.map +1 -0
- package/dist/nonce-dedup.js +204 -0
- package/dist/nonce-dedup.js.map +1 -0
- package/dist/notification-dispatcher.d.ts +65 -0
- package/dist/notification-dispatcher.d.ts.map +1 -0
- package/dist/notification-dispatcher.js +138 -0
- package/dist/notification-dispatcher.js.map +1 -0
- package/dist/registration-context.d.ts +69 -0
- package/dist/registration-context.d.ts.map +1 -0
- package/dist/registration-context.js +118 -0
- package/dist/registration-context.js.map +1 -0
- package/dist/registration-manager.d.ts +72 -0
- package/dist/registration-manager.d.ts.map +1 -0
- package/dist/registration-manager.js +267 -0
- package/dist/registration-manager.js.map +1 -0
- package/dist/registration-persistence.d.ts +131 -0
- package/dist/registration-persistence.d.ts.map +1 -0
- package/dist/registration-persistence.js +233 -0
- package/dist/registration-persistence.js.map +1 -0
- package/dist/retry-queue.d.ts +144 -0
- package/dist/retry-queue.d.ts.map +1 -0
- package/dist/retry-queue.js +444 -0
- package/dist/retry-queue.js.map +1 -0
- package/dist/seal-frontier-verify.d.ts +58 -0
- package/dist/seal-frontier-verify.d.ts.map +1 -0
- package/dist/seal-frontier-verify.js +87 -0
- package/dist/seal-frontier-verify.js.map +1 -0
- package/dist/seal-legibility-tbs.d.ts +25 -0
- package/dist/seal-legibility-tbs.d.ts.map +1 -0
- package/dist/seal-legibility-tbs.js +78 -0
- package/dist/seal-legibility-tbs.js.map +1 -0
- package/dist/seal-upgrade.d.ts +90 -0
- package/dist/seal-upgrade.d.ts.map +1 -0
- package/dist/seal-upgrade.js +178 -0
- package/dist/seal-upgrade.js.map +1 -0
- package/dist/session-assignment-parser.d.ts +22 -0
- package/dist/session-assignment-parser.d.ts.map +1 -0
- package/dist/session-assignment-parser.js +139 -0
- package/dist/session-assignment-parser.js.map +1 -0
- package/dist/session-ceremony.d.ts +156 -0
- package/dist/session-ceremony.d.ts.map +1 -0
- package/dist/session-ceremony.js +447 -0
- package/dist/session-ceremony.js.map +1 -0
- package/dist/session-connection-gater.d.ts +91 -0
- package/dist/session-connection-gater.d.ts.map +1 -0
- package/dist/session-connection-gater.js +146 -0
- package/dist/session-connection-gater.js.map +1 -0
- package/dist/session-node-manager.d.ts +585 -0
- package/dist/session-node-manager.d.ts.map +1 -0
- package/dist/session-node-manager.js +2609 -0
- package/dist/session-node-manager.js.map +1 -0
- package/dist/session-relay-client.d.ts +101 -0
- package/dist/session-relay-client.d.ts.map +1 -0
- package/dist/session-relay-client.js +520 -0
- package/dist/session-relay-client.js.map +1 -0
- package/dist/session-tree.d.ts +80 -0
- package/dist/session-tree.d.ts.map +1 -0
- package/dist/session-tree.js +123 -0
- package/dist/session-tree.js.map +1 -0
- package/dist/signaling-connect.d.ts +83 -0
- package/dist/signaling-connect.d.ts.map +1 -0
- package/dist/signaling-connect.js +266 -0
- package/dist/signaling-connect.js.map +1 -0
- package/dist/transcript-cipher.d.ts +31 -0
- package/dist/transcript-cipher.d.ts.map +1 -0
- package/dist/transcript-cipher.js +74 -0
- package/dist/transcript-cipher.js.map +1 -0
- package/dist/transport-composition.d.ts +31 -0
- package/dist/transport-composition.d.ts.map +1 -0
- package/dist/transport-composition.js +55 -0
- package/dist/transport-composition.js.map +1 -0
- package/dist/transport-selector.d.ts +189 -0
- package/dist/transport-selector.d.ts.map +1 -0
- package/dist/transport-selector.js +195 -0
- package/dist/transport-selector.js.map +1 -0
- package/dist/types.d.ts +265 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +33 -0
- package/dist/types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M7 DOD-SPINE-6 / MSG-001-3b — daemon-side relay witness client (PER AGENT).
|
|
3
|
+
*
|
|
4
|
+
* In CELLO the relay is the ordering/witness authority (Structure 2): it never sees
|
|
5
|
+
* plaintext (content is peer↔peer), only the SIGNED content-hash leaves. It assigns
|
|
6
|
+
* the canonical `sequence_number` and forwards each witnessed leaf to the counterparty
|
|
7
|
+
* (`leaf_deliver`). A `cello_send` whose hash the relay never witnessed has no canonical
|
|
8
|
+
* sequence and is not a complete CELLO message — so the session submits the message leaf
|
|
9
|
+
* hash here, in parallel with the direct content delivery.
|
|
10
|
+
*
|
|
11
|
+
* ONE stream per AGENT, not per session. The relay authenticates a stream by the agent's
|
|
12
|
+
* K_local pubkey and keys its delivery/queue maps by that pubkey (relay-node
|
|
13
|
+
* #handleRelayStream / #processHashSubmit), so a second stream for the same pubkey would
|
|
14
|
+
* OVERWRITE the first's delivery stream and steal its queued `leaf_deliver`s. The protocol
|
|
15
|
+
* is designed for one relay connection per agent identity multiplexing all that agent's
|
|
16
|
+
* sessions — every wire frame carries `session_id`. So this client is shared across an
|
|
17
|
+
* agent's sessions: submits are globally FIFO-serialized on the stream (a `hash_submit_ack`
|
|
18
|
+
* carries NO session_id, so at most one submit may be outstanding at a time and acks match
|
|
19
|
+
* the queue head in order), while inbound `leaf_deliver` (which DOES carry `session_id`) is
|
|
20
|
+
* routed to the owning session's handler.
|
|
21
|
+
*
|
|
22
|
+
* The stream is (re)dialed from whatever live session node is current at submit time, so it
|
|
23
|
+
* survives individual session teardown (the relay treats a same-pubkey reconnect as a
|
|
24
|
+
* reconnect, re-auths, and re-drains). Reuses only proven wire shapes:
|
|
25
|
+
* - auth: mirror of the retired client's relay challenge-response (proven against this relay)
|
|
26
|
+
* - Structure 1 + hash_submit: mirror of the retired client's seal-leaf submit path
|
|
27
|
+
* - server contract: `packages/relay/src/relay-node.ts`
|
|
28
|
+
*
|
|
29
|
+
* Crypto: Ed25519 RFC 8032. Relay auth domain: "CELLO-RELAY-AUTH-v1".
|
|
30
|
+
*/
|
|
31
|
+
import { createHash } from "node:crypto";
|
|
32
|
+
import * as lp from "it-length-prefixed";
|
|
33
|
+
import { Encoder, decode } from "cbor-x";
|
|
34
|
+
const CBOR_ENC = new Encoder({ tagUint8Array: false });
|
|
35
|
+
export const RELAY_PROTOCOL_ID = "/cello/relay/1.0.0";
|
|
36
|
+
export const RELAY_AUTH_DOMAIN = "CELLO-RELAY-AUTH-v1";
|
|
37
|
+
/** Structure 1 leaf kind: 0x00 = message, 0x02 = control (matches the relay). */
|
|
38
|
+
export const LEAF_KIND_MSG = 0x00;
|
|
39
|
+
/** Control leaf (SEAL etc.) — two distinct-sender ctrl leaves trigger directory notarization. */
|
|
40
|
+
export const LEAF_KIND_CTRL = 0x02;
|
|
41
|
+
const RELAY_AUTH_TIMEOUT_MS = 5_000;
|
|
42
|
+
const HASH_SUBMIT_TIMEOUT_MS = 10_000;
|
|
43
|
+
/**
|
|
44
|
+
* The Ed25519-signed payload that proves K_local ownership to the relay:
|
|
45
|
+
* SHA-256("CELLO-RELAY-AUTH-v1" || nonce || pubkey). The relay verifies the
|
|
46
|
+
* signature against exactly this hash (relay-node #handleRelayStream).
|
|
47
|
+
*/
|
|
48
|
+
export function buildRelayAuthPayload(nonce, pubkey) {
|
|
49
|
+
const domain = Buffer.from(RELAY_AUTH_DOMAIN, "utf8");
|
|
50
|
+
const authMsg = Buffer.concat([domain, nonce, pubkey]);
|
|
51
|
+
return new Uint8Array(createHash("sha256").update(authMsg).digest());
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Canonical Structure 1 CBOR the sender signs and the relay re-verifies byte-for-byte:
|
|
55
|
+
* [1, content_hash(32), sender_pubkey(32), session_id(16), last_seen_seq, timestamp].
|
|
56
|
+
* The relay decodes this (decodeStructure1) AND verifies Ed25519 over these EXACT bytes,
|
|
57
|
+
* so the encoded bytes — not a re-encoding — are what gets signed and sent.
|
|
58
|
+
*/
|
|
59
|
+
export function encodeStructure1(contentHash, senderPubkey, sessionId, lastSeenSeq, timestamp) {
|
|
60
|
+
return CBOR_ENC.encode([1, contentHash, senderPubkey, sessionId, lastSeenSeq, timestamp]);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Extract a real message from a thrown value. libp2p / cross-package errors are not
|
|
64
|
+
* always `instanceof Error` in this realm (multi-version split), so fall back to a
|
|
65
|
+
* `message` property or JSON — never the useless "[object Object]".
|
|
66
|
+
*/
|
|
67
|
+
export function extractErrorMessage(err) {
|
|
68
|
+
if (err instanceof Error)
|
|
69
|
+
return err.message;
|
|
70
|
+
if (err && typeof err === "object" && typeof err.message === "string") {
|
|
71
|
+
return err.message;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return JSON.stringify(err);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return String(err);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function toU8(v) {
|
|
81
|
+
if (v instanceof Uint8Array)
|
|
82
|
+
return v;
|
|
83
|
+
if (Buffer.isBuffer(v))
|
|
84
|
+
return new Uint8Array(v);
|
|
85
|
+
if (v && typeof v.subarray === "function") {
|
|
86
|
+
return v.subarray();
|
|
87
|
+
}
|
|
88
|
+
if (v && typeof v.slice === "function") {
|
|
89
|
+
return v.slice();
|
|
90
|
+
}
|
|
91
|
+
return new Uint8Array();
|
|
92
|
+
}
|
|
93
|
+
async function nextWithTimeout(iter, ms) {
|
|
94
|
+
let timer;
|
|
95
|
+
const timeout = new Promise((resolve) => {
|
|
96
|
+
timer = setTimeout(() => resolve({ value: undefined, done: true }), ms);
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
const result = (await Promise.race([iter.next(), timeout]));
|
|
100
|
+
return { value: result.value, done: !!result.done };
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Per-AGENT relay witness client. Shared across all of an agent's sessions; one
|
|
108
|
+
* authenticated stream at a time (the relay keys by agent pubkey). Submits are FIFO and
|
|
109
|
+
* single-in-flight on the stream; `leaf_deliver` is routed by session_id.
|
|
110
|
+
*/
|
|
111
|
+
export class AgentRelayClient {
|
|
112
|
+
#relayPeerId;
|
|
113
|
+
#relayAddrs;
|
|
114
|
+
#keyProvider;
|
|
115
|
+
#senderPubkey;
|
|
116
|
+
#logger;
|
|
117
|
+
#stream = null;
|
|
118
|
+
#connecting = null;
|
|
119
|
+
#closed = false;
|
|
120
|
+
/**
|
|
121
|
+
* PER-SESSION highest relay-assigned sequence (session_id hex → seq). The relay's
|
|
122
|
+
* `seq_counter` is per session, and it rejects `last_seen_seq > seq_counter`, so each
|
|
123
|
+
* session's submit MUST carry that session's own high-water mark — NOT an agent-global
|
|
124
|
+
* one (which would make a newer session's first submit look ahead and get rejected).
|
|
125
|
+
*/
|
|
126
|
+
#lastSeen = new Map();
|
|
127
|
+
/** The one outstanding submit's resolver (global FIFO — ack carries no session_id). */
|
|
128
|
+
#pendingAck = null;
|
|
129
|
+
// DOD-MSG-4: the sender-signed structure1_cbor of the in-flight submit, paired with its ack so the
|
|
130
|
+
// SubmitResult can carry it (the ack itself only returns the relay's structure2_cbor).
|
|
131
|
+
#pendingStructure1 = null;
|
|
132
|
+
/** The session_id hex of the in-flight submit, so its ack updates the right #lastSeen. */
|
|
133
|
+
#pendingAckSessionHex = null;
|
|
134
|
+
/** Serializes submits so only one is in flight at a time across all sessions. */
|
|
135
|
+
#submitChain = Promise.resolve();
|
|
136
|
+
/** session_id hex → { the live node to (re)dial from, inbound leaf handler }. */
|
|
137
|
+
#sessions = new Map();
|
|
138
|
+
constructor(opts) {
|
|
139
|
+
this.#relayPeerId = opts.relayPeerId;
|
|
140
|
+
this.#relayAddrs = opts.relayAddrs;
|
|
141
|
+
this.#keyProvider = opts.keyProvider;
|
|
142
|
+
this.#senderPubkey = opts.senderPubkey;
|
|
143
|
+
this.#logger = opts.logger;
|
|
144
|
+
}
|
|
145
|
+
/** The agent's K_local public key as hex — the responder identity for auto-acknowledge (UPGRADE-002). */
|
|
146
|
+
get senderPubkeyHex() {
|
|
147
|
+
return Buffer.from(this.#senderPubkey).toString("hex");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Register a session's inbound leaf handler + a live node to (re)dial the relay from
|
|
151
|
+
* (idempotent). Storing the node per session lets a pure-receiver session re-establish
|
|
152
|
+
* the shared stream if the node that originally dialed is torn down (L5).
|
|
153
|
+
*/
|
|
154
|
+
registerSession(sessionIdHex, node, onLeafDeliver) {
|
|
155
|
+
this.#sessions.set(sessionIdHex, { node, onLeafDeliver: onLeafDeliver ?? (() => { }) });
|
|
156
|
+
}
|
|
157
|
+
/** Remove a session; caller closes the client when no sessions remain. */
|
|
158
|
+
unregisterSession(sessionIdHex) {
|
|
159
|
+
this.#sessions.delete(sessionIdHex);
|
|
160
|
+
this.#lastSeen.delete(sessionIdHex);
|
|
161
|
+
}
|
|
162
|
+
hasSessions() {
|
|
163
|
+
return this.#sessions.size > 0;
|
|
164
|
+
}
|
|
165
|
+
/** Settle the one outstanding submit (if any) exactly once. */
|
|
166
|
+
#settlePending(r) {
|
|
167
|
+
const resolve = this.#pendingAck;
|
|
168
|
+
this.#pendingAck = null;
|
|
169
|
+
this.#pendingAckSessionHex = null;
|
|
170
|
+
this.#pendingStructure1 = null;
|
|
171
|
+
if (resolve)
|
|
172
|
+
resolve(r);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Tear down the current stream (drops it so the next submit re-dials). Used on a submit
|
|
176
|
+
* timeout: because `hash_submit_ack` carries no session_id, ack↔submit matching is purely
|
|
177
|
+
* FIFO, so a LATE ack from a timed-out submit would settle the NEXT submit's resolver and
|
|
178
|
+
* shift every subsequent ack by one. Resetting the stream prevents that desync (the relay
|
|
179
|
+
* re-auths + re-drains on the reconnect).
|
|
180
|
+
*/
|
|
181
|
+
#resetStream() {
|
|
182
|
+
const stream = this.#stream;
|
|
183
|
+
this.#stream = null;
|
|
184
|
+
if (stream) {
|
|
185
|
+
try {
|
|
186
|
+
void stream.close();
|
|
187
|
+
}
|
|
188
|
+
catch { /* best-effort */ }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
#bumpLastSeen(sessionIdHex, seq) {
|
|
192
|
+
if (seq < 0)
|
|
193
|
+
return;
|
|
194
|
+
const prev = this.#lastSeen.get(sessionIdHex) ?? 0;
|
|
195
|
+
if (seq > prev)
|
|
196
|
+
this.#lastSeen.set(sessionIdHex, seq);
|
|
197
|
+
}
|
|
198
|
+
/** True if this Structure-1 leaf was authored by US (sender_pubkey === our K_local). */
|
|
199
|
+
#isOwnLeaf(structure1Cbor) {
|
|
200
|
+
try {
|
|
201
|
+
const arr = decode(structure1Cbor);
|
|
202
|
+
// Structure 1 = [1, content_hash, sender_pubkey, session_id, last_seen_seq, ts].
|
|
203
|
+
const senderPubkey = toU8(arr[2]);
|
|
204
|
+
return Buffer.from(senderPubkey).equals(Buffer.from(this.#senderPubkey));
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Undecodable → treat as NOT ours (conservative: don't suppress a real counterparty leaf).
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
#dispatch(frame) {
|
|
212
|
+
const type = frame["type"];
|
|
213
|
+
if (type === "hash_submit_ack") {
|
|
214
|
+
const seq = typeof frame["sequence_number"] === "number" ? frame["sequence_number"] : -1;
|
|
215
|
+
// DO NOT advance #lastSeen here: the ack is for OUR OWN leaf. last_seen_seq must track
|
|
216
|
+
// the highest COUNTERPARTY sequence we've observed (the directory's causal check —
|
|
217
|
+
// SESSION-003 SI-003 — rejects a leaf whose last_seen_seq exceeds the max sequence of
|
|
218
|
+
// OTHER-sender leaves before it). Advancing on our own ack would inflate it and trip
|
|
219
|
+
// causal_chain_violated on a subsequent submit (e.g. the SEAL leaf after a sent message).
|
|
220
|
+
// DOD-MSG-4: pair the relay's committed structure2_cbor with our in-flight structure1_cbor so
|
|
221
|
+
// the SubmitResult carries the full signed ordering record for the self-ordering content frame.
|
|
222
|
+
// Captured BEFORE #settlePending clears #pendingStructure1.
|
|
223
|
+
const s2 = frame["structure2_cbor"];
|
|
224
|
+
const structure2Cbor = s2 instanceof Uint8Array ? s2 : undefined;
|
|
225
|
+
const structure1Cbor = this.#pendingStructure1 ?? undefined;
|
|
226
|
+
this.#settlePending(seq >= 0
|
|
227
|
+
? { ok: true, sequence_number: seq, structure1_cbor: structure1Cbor, structure2_cbor: structure2Cbor }
|
|
228
|
+
: { ok: false, reason: "relay_ack_malformed" });
|
|
229
|
+
}
|
|
230
|
+
else if (type === "hash_submit_error") {
|
|
231
|
+
const reason = typeof frame["reason"] === "string" ? frame["reason"] : "relay_rejected";
|
|
232
|
+
this.#settlePending({ ok: false, reason });
|
|
233
|
+
}
|
|
234
|
+
else if (type === "leaf_deliver") {
|
|
235
|
+
const seq = typeof frame["sequence_number"] === "number" ? frame["sequence_number"] : -1;
|
|
236
|
+
const sidHex = Buffer.from(toU8(frame["session_id"])).toString("hex");
|
|
237
|
+
const s1 = toU8(frame["structure1_cbor"]);
|
|
238
|
+
// Advance last_seen_seq ONLY for a COUNTERPARTY leaf. The relay also echoes our OWN
|
|
239
|
+
// leaf back as a leaf_deliver — that must NOT advance it (same reason as the ack above).
|
|
240
|
+
const authoredByUs = this.#isOwnLeaf(s1);
|
|
241
|
+
if (seq >= 0 && !authoredByUs)
|
|
242
|
+
this.#bumpLastSeen(sidHex, seq);
|
|
243
|
+
const session = this.#sessions.get(sidHex);
|
|
244
|
+
if (session && seq >= 0) {
|
|
245
|
+
session.onLeafDeliver({
|
|
246
|
+
sequence_number: seq,
|
|
247
|
+
leaf_kind: typeof frame["leaf_kind"] === "number" ? frame["leaf_kind"] : LEAF_KIND_MSG,
|
|
248
|
+
structure1_cbor: s1,
|
|
249
|
+
structure2_cbor: toU8(frame["structure2_cbor"]),
|
|
250
|
+
// M7-UPGRADE-002: tell the consumer whether this is our own echoed leaf (so the
|
|
251
|
+
// auto-acknowledge gate never co-signs in response to its own SEAL ctrl leaf).
|
|
252
|
+
authored_by_us: authoredByUs,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// session_interrupted / content_park_notify are out of scope here — session interruption
|
|
257
|
+
// is handled by the session node manager's dedicated relay-stream watcher.
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Proactively establish the authenticated stream from `node`. The RECEIVER must connect
|
|
261
|
+
* before the counterparty submits, so the relay has its stream to deliver `leaf_deliver`
|
|
262
|
+
* to (otherwise the relay queues until the recipient connects). Best-effort.
|
|
263
|
+
*/
|
|
264
|
+
async connect(node) {
|
|
265
|
+
return this.#ensureConnected(node);
|
|
266
|
+
}
|
|
267
|
+
/** Ensure an authenticated stream exists, (re)dialing from `node` if needed. */
|
|
268
|
+
async #ensureConnected(node) {
|
|
269
|
+
if (this.#closed)
|
|
270
|
+
return false;
|
|
271
|
+
if (this.#stream)
|
|
272
|
+
return true;
|
|
273
|
+
if (this.#connecting)
|
|
274
|
+
return this.#connecting;
|
|
275
|
+
this.#connecting = this.#connect(node).finally(() => { this.#connecting = null; });
|
|
276
|
+
return this.#connecting;
|
|
277
|
+
}
|
|
278
|
+
async #connect(node) {
|
|
279
|
+
// Best-effort dial: newStream auto-dials a known peer, but the relay's addrs may not
|
|
280
|
+
// be in the peerstore yet, so dial each addr first. One success is enough.
|
|
281
|
+
let dialed = false;
|
|
282
|
+
let lastDialError = "";
|
|
283
|
+
for (const addr of this.#relayAddrs) {
|
|
284
|
+
try {
|
|
285
|
+
await node.dial(addr);
|
|
286
|
+
dialed = true;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
lastDialError = extractErrorMessage(err);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (!dialed && this.#relayAddrs.length > 0) {
|
|
294
|
+
this.#logger.warn("session.relay.dial.failed", {
|
|
295
|
+
relayPeerId: this.#relayPeerId,
|
|
296
|
+
relayAddrs: this.#relayAddrs,
|
|
297
|
+
error: lastDialError,
|
|
298
|
+
});
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
let stream;
|
|
302
|
+
try {
|
|
303
|
+
stream = await node.newStream(this.#relayPeerId, RELAY_PROTOCOL_ID);
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
this.#logger.warn("session.relay.stream.failed", { relayPeerId: this.#relayPeerId, error: extractErrorMessage(err) });
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
// ONE shared lp.decode iterator for the whole stream lifetime — splitting it signals
|
|
310
|
+
// EOF to the relay's single-iterator reader and breaks subsequent reads.
|
|
311
|
+
const iter = lp.decode(stream)[Symbol.asyncIterator]();
|
|
312
|
+
if (!(await this.#authenticate(stream, iter)))
|
|
313
|
+
return false;
|
|
314
|
+
this.#stream = stream;
|
|
315
|
+
this.#logger.info("session.relay.connected", { relayPeerId: this.#relayPeerId });
|
|
316
|
+
this.#startReader(stream, iter);
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
async #authenticate(stream, iter) {
|
|
320
|
+
const challengeRes = await nextWithTimeout(iter, RELAY_AUTH_TIMEOUT_MS);
|
|
321
|
+
if (challengeRes.done || challengeRes.value === undefined) {
|
|
322
|
+
this.#logger.warn("session.relay.auth.failed", { relayPeerId: this.#relayPeerId, reason: "no_challenge" });
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
let challenge;
|
|
326
|
+
try {
|
|
327
|
+
challenge = decode(toU8(challengeRes.value));
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
this.#logger.warn("session.relay.auth.failed", { relayPeerId: this.#relayPeerId, reason: "challenge_decode" });
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
if (challenge["type"] !== "relay_auth_challenge") {
|
|
334
|
+
this.#logger.warn("session.relay.auth.failed", { relayPeerId: this.#relayPeerId, reason: "not_challenge" });
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
const nonce = toU8(challenge["nonce"]);
|
|
338
|
+
if (nonce.length !== 32) {
|
|
339
|
+
this.#logger.warn("session.relay.auth.failed", { relayPeerId: this.#relayPeerId, reason: "bad_nonce" });
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
const authSig = await this.#keyProvider.sign(buildRelayAuthPayload(nonce, this.#senderPubkey));
|
|
343
|
+
try {
|
|
344
|
+
stream.send(lp.encode.single(CBOR_ENC.encode({ type: "relay_auth_response", pubkey: this.#senderPubkey, signature: authSig })));
|
|
345
|
+
}
|
|
346
|
+
catch (err) {
|
|
347
|
+
this.#logger.warn("session.relay.auth.failed", { relayPeerId: this.#relayPeerId, reason: "response_send", error: extractErrorMessage(err) });
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
const ackRes = await nextWithTimeout(iter, RELAY_AUTH_TIMEOUT_MS);
|
|
351
|
+
if (ackRes.done || ackRes.value === undefined) {
|
|
352
|
+
this.#logger.warn("session.relay.auth.failed", { relayPeerId: this.#relayPeerId, reason: "no_auth_ok" });
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
let ackFrame;
|
|
356
|
+
try {
|
|
357
|
+
ackFrame = decode(toU8(ackRes.value));
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
this.#logger.warn("session.relay.auth.failed", { relayPeerId: this.#relayPeerId, reason: "auth_ok_decode" });
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
if (ackFrame["type"] !== "relay_auth_ok") {
|
|
364
|
+
this.#logger.warn("session.relay.auth.failed", {
|
|
365
|
+
relayPeerId: this.#relayPeerId,
|
|
366
|
+
reason: ackFrame["type"] === "relay_auth_failed" ? "auth_rejected" : "unexpected_frame",
|
|
367
|
+
});
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
#startReader(stream, iter) {
|
|
373
|
+
void (async () => {
|
|
374
|
+
try {
|
|
375
|
+
while (!this.#closed && this.#stream === stream) {
|
|
376
|
+
const res = await iter.next();
|
|
377
|
+
if (res.done || res.value === undefined)
|
|
378
|
+
break;
|
|
379
|
+
// The await above can suspend across a #resetStream() (timeout) that supersedes this
|
|
380
|
+
// stream. Re-check identity before dispatching so a late frame from a stale stream
|
|
381
|
+
// (e.g. a buffered ack for a timed-out submit) can't bump/settle the wrong session.
|
|
382
|
+
if (this.#stream !== stream)
|
|
383
|
+
break;
|
|
384
|
+
let frame;
|
|
385
|
+
try {
|
|
386
|
+
frame = decode(toU8(res.value));
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
this.#dispatch(frame);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
this.#logger.debug?.("session.relay.reader.ended", { relayPeerId: this.#relayPeerId, error: extractErrorMessage(err) });
|
|
396
|
+
}
|
|
397
|
+
finally {
|
|
398
|
+
// Stream gone — clear it so the next submit re-dials, and fail any in-flight submit.
|
|
399
|
+
if (this.#stream === stream)
|
|
400
|
+
this.#stream = null;
|
|
401
|
+
this.#settlePending({ ok: false, reason: "relay_stream_closed" });
|
|
402
|
+
// L5: a pure-receiver session issues no submit, so it would never trigger a re-dial
|
|
403
|
+
// after the node that owned the stream is torn down. If sessions remain, proactively
|
|
404
|
+
// re-establish from any still-live registered session node so queued leaf_delivers
|
|
405
|
+
// are drained (the relay queues by pubkey and re-delivers on reconnect).
|
|
406
|
+
if (!this.#closed && this.#sessions.size > 0) {
|
|
407
|
+
void this.#reconnectFromAnySession();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
})();
|
|
411
|
+
}
|
|
412
|
+
/** Re-establish the shared stream from any registered session's node (first that dials). */
|
|
413
|
+
async #reconnectFromAnySession() {
|
|
414
|
+
if (this.#closed || this.#stream)
|
|
415
|
+
return;
|
|
416
|
+
for (const { node } of this.#sessions.values()) {
|
|
417
|
+
if (await this.#ensureConnected(node))
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Submit a session's MESSAGE-leaf (0x00) hash to the relay. Connects/re-connects from
|
|
423
|
+
* `node` if needed. Globally FIFO across the agent's sessions (the ack has no session_id).
|
|
424
|
+
*/
|
|
425
|
+
async submitMessageHash(node, sessionId, contentHash) {
|
|
426
|
+
return this.submitLeaf(node, sessionId, contentHash, LEAF_KIND_MSG);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Submit a leaf hash of a given kind (0x00 message / 0x02 control) to the relay. The SEAL
|
|
430
|
+
* ctrl leaf (DOD-SPINE-7) rides this path: two distinct-sender ctrl leaves in the relay's
|
|
431
|
+
* log trigger the directory's FROST notarization (relay `#maybeProcessSeal`).
|
|
432
|
+
*/
|
|
433
|
+
async submitLeaf(node, sessionId, contentHash, leafKind) {
|
|
434
|
+
// Chain on the prior submit so only one is outstanding at a time (FIFO). The ack
|
|
435
|
+
// carries no session_id, so concurrent submits on one stream would be ambiguous.
|
|
436
|
+
const run = this.#submitChain.then(() => this.#doSubmit(node, sessionId, contentHash, leafKind));
|
|
437
|
+
// Keep the chain alive regardless of this submit's outcome.
|
|
438
|
+
this.#submitChain = run.then(() => undefined, () => undefined);
|
|
439
|
+
return run;
|
|
440
|
+
}
|
|
441
|
+
async #doSubmit(node, sessionId, contentHash, leafKind) {
|
|
442
|
+
if (this.#closed)
|
|
443
|
+
return { ok: false, reason: "relay_client_closed" };
|
|
444
|
+
if (!(await this.#ensureConnected(node)))
|
|
445
|
+
return { ok: false, reason: "relay_unavailable" };
|
|
446
|
+
const stream = this.#stream;
|
|
447
|
+
if (!stream)
|
|
448
|
+
return { ok: false, reason: "relay_unavailable" };
|
|
449
|
+
const sessionIdHex = Buffer.from(sessionId).toString("hex");
|
|
450
|
+
// This session's OWN high-water mark (NOT an agent-global one) — the relay's seq_counter
|
|
451
|
+
// is per session and rejects last_seen_seq > seq_counter.
|
|
452
|
+
const lastSeenForSession = this.#lastSeen.get(sessionIdHex) ?? 0;
|
|
453
|
+
const structure1 = encodeStructure1(contentHash, this.#senderPubkey, sessionId, lastSeenForSession, Date.now());
|
|
454
|
+
const signature = await this.#keyProvider.sign(structure1);
|
|
455
|
+
const frame = CBOR_ENC.encode({
|
|
456
|
+
type: "hash_submit",
|
|
457
|
+
session_id: sessionId,
|
|
458
|
+
leaf_kind: leafKind,
|
|
459
|
+
structure1_cbor: structure1,
|
|
460
|
+
sender_signature: signature,
|
|
461
|
+
});
|
|
462
|
+
// Set the resolver synchronously (no await between the in-flight check and the set):
|
|
463
|
+
// the submit chain guarantees no other submit runs concurrently, so #pendingAck is null.
|
|
464
|
+
let resolveAck;
|
|
465
|
+
const ackPromise = new Promise((r) => { resolveAck = r; });
|
|
466
|
+
this.#pendingAck = resolveAck;
|
|
467
|
+
this.#pendingAckSessionHex = sessionIdHex;
|
|
468
|
+
// DOD-MSG-4: remember this submit's sender-signed structure1_cbor so its ack can return the full
|
|
469
|
+
// ordering record (the ack itself carries only the relay's structure2_cbor).
|
|
470
|
+
this.#pendingStructure1 = structure1;
|
|
471
|
+
try {
|
|
472
|
+
stream.send(lp.encode.single(frame));
|
|
473
|
+
}
|
|
474
|
+
catch (err) {
|
|
475
|
+
if (this.#pendingAck === resolveAck) {
|
|
476
|
+
this.#pendingAck = null;
|
|
477
|
+
this.#pendingAckSessionHex = null;
|
|
478
|
+
this.#pendingStructure1 = null;
|
|
479
|
+
}
|
|
480
|
+
this.#logger.warn("session.relay.submit.send.failed", { relayPeerId: this.#relayPeerId, error: extractErrorMessage(err) });
|
|
481
|
+
return { ok: false, reason: "relay_submit_send_failed" };
|
|
482
|
+
}
|
|
483
|
+
let timer;
|
|
484
|
+
const timeout = new Promise((r) => {
|
|
485
|
+
timer = setTimeout(() => r({ ok: false, reason: "relay_submit_timeout" }), HASH_SUBMIT_TIMEOUT_MS);
|
|
486
|
+
});
|
|
487
|
+
try {
|
|
488
|
+
const result = await Promise.race([ackPromise, timeout]);
|
|
489
|
+
// On timeout, reset the stream so a late ack can't settle a LATER submit (FIFO desync).
|
|
490
|
+
if (!result.ok && result.reason === "relay_submit_timeout")
|
|
491
|
+
this.#resetStream();
|
|
492
|
+
return result;
|
|
493
|
+
}
|
|
494
|
+
finally {
|
|
495
|
+
clearTimeout(timer);
|
|
496
|
+
if (this.#pendingAck === resolveAck) {
|
|
497
|
+
this.#pendingAck = null;
|
|
498
|
+
this.#pendingAckSessionHex = null;
|
|
499
|
+
this.#pendingStructure1 = null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/** The highest relay-assigned sequence observed for a given session (ack or deliver). */
|
|
504
|
+
lastSeenSeq(sessionIdHex) {
|
|
505
|
+
return this.#lastSeen.get(sessionIdHex) ?? 0;
|
|
506
|
+
}
|
|
507
|
+
close() {
|
|
508
|
+
this.#closed = true;
|
|
509
|
+
this.#settlePending({ ok: false, reason: "relay_client_closed" });
|
|
510
|
+
const stream = this.#stream;
|
|
511
|
+
this.#stream = null;
|
|
512
|
+
if (stream) {
|
|
513
|
+
try {
|
|
514
|
+
void stream.close();
|
|
515
|
+
}
|
|
516
|
+
catch { /* best-effort */ }
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
//# sourceMappingURL=session-relay-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-relay-client.js","sourceRoot":"","sources":["../src/session-relay-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CELLO Daemon — SessionTree (CELLO-M7-DAEMON-004, Option A)
|
|
3
|
+
*
|
|
4
|
+
* The DAEMON-OWNED per-session Merkle tree. This is the authoritative running
|
|
5
|
+
* transcript of every leaf (sent or received) for a session. Before this story,
|
|
6
|
+
* the daemon "did not maintain the session Merkle tree — the client supplied it"
|
|
7
|
+
* (daemon.ts:568). Option A re-homes ownership here: the daemon is the component
|
|
8
|
+
* that sends and receives every message, so the transcript belongs where the
|
|
9
|
+
* messages flow.
|
|
10
|
+
*
|
|
11
|
+
* Specification (SPARC Phase S):
|
|
12
|
+
* - A leaf is the 32-byte leaf hash of a message or control entry, in order.
|
|
13
|
+
* For a message leaf the leaf hash is content_hash = SHA-256(0x00 || content)
|
|
14
|
+
* (msgLeafHash, RFC 6962 §2.1 leaf hashing). For a control leaf (SEAL) the
|
|
15
|
+
* leaf hash is SHA-256(0x02 || canonical_bytes) (ctrlLeafHash). Both sides of
|
|
16
|
+
* a session compute the SAME leaf hash for the SAME message, so appending the
|
|
17
|
+
* SAME leaves in the SAME order yields the SAME root (AC-002).
|
|
18
|
+
* - The root is recomputed over all leaves after every append (RFC 6962 §2.1,
|
|
19
|
+
* left-balanced binary Merkle tree). Empty tree root = SHA-256("").
|
|
20
|
+
* - The tree is serializable to/from an ordered list of leaf-hash hex strings so
|
|
21
|
+
* it can be persisted to SQLCipher and reloaded across a daemon restart (AC-007).
|
|
22
|
+
*
|
|
23
|
+
* Pseudocode (SPARC Phase P):
|
|
24
|
+
* appendLeafHash(kind, hashHex):
|
|
25
|
+
* 1. push {kind, hashHex} onto leaves
|
|
26
|
+
* 2. return { leafIndex: leaves.length - 1, newRootHex: rootHex() }
|
|
27
|
+
* rootHex():
|
|
28
|
+
* 1. build = buildMerkleTree(leaves.map(l => {kind:"hash", data: bytes(l.hashHex)}))
|
|
29
|
+
* 2. return hex(merkleRoot(build)) // empty → hex(SHA-256(""))
|
|
30
|
+
* fromLeaves(leaves): reconstruct from persisted ordered list.
|
|
31
|
+
*
|
|
32
|
+
* Crypto refs: RFC 6962 §2.1 (Merkle hash trees), FIPS 180-4 (SHA-256).
|
|
33
|
+
*/
|
|
34
|
+
export type SessionTreeLeafKind = "msg" | "ctrl";
|
|
35
|
+
export interface SessionTreeLeaf {
|
|
36
|
+
/** Domain of the leaf — metadata only; the stored hash already encodes the prefix. */
|
|
37
|
+
readonly kind: SessionTreeLeafKind;
|
|
38
|
+
/** 32-byte leaf hash, hex-encoded. */
|
|
39
|
+
readonly hashHex: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A daemon-owned per-session Merkle tree. Leaves are stored as pre-computed
|
|
43
|
+
* 32-byte leaf hashes (hex), so reconstruction is exact regardless of the
|
|
44
|
+
* original payload — the daemon never needs the plaintext to recompute the root.
|
|
45
|
+
*/
|
|
46
|
+
export declare class SessionTree {
|
|
47
|
+
#private;
|
|
48
|
+
private constructor();
|
|
49
|
+
/** O(1) index of the first leaf with this content-hash, or -1 if absent (DOD-MSG-5 dedup). */
|
|
50
|
+
indexOfHash(hashHex: string): number;
|
|
51
|
+
/** Construct an empty tree. */
|
|
52
|
+
static empty(): SessionTree;
|
|
53
|
+
/** Reconstruct a tree from a persisted, ordered list of leaves (AC-007). */
|
|
54
|
+
static fromLeaves(leaves: readonly SessionTreeLeaf[]): SessionTree;
|
|
55
|
+
/** Number of leaves currently in the tree. */
|
|
56
|
+
size(): number;
|
|
57
|
+
/** Ordered copy of the leaves (for persistence / inspection). */
|
|
58
|
+
leaves(): SessionTreeLeaf[];
|
|
59
|
+
/**
|
|
60
|
+
* Append a leaf (by its pre-computed 32-byte leaf-hash hex) and advance the root.
|
|
61
|
+
* @returns the new leaf's index and the recomputed root hex.
|
|
62
|
+
*/
|
|
63
|
+
appendLeafHash(kind: SessionTreeLeafKind, hashHex: string): {
|
|
64
|
+
leafIndex: number;
|
|
65
|
+
newRootHex: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Recompute and return the current Merkle root as hex.
|
|
69
|
+
* Empty tree → hex(SHA-256("")) per RFC 6962 §2.1.
|
|
70
|
+
*/
|
|
71
|
+
rootHex(): string;
|
|
72
|
+
/**
|
|
73
|
+
* SESSION-002: the Merkle root this tree WOULD have if one more leaf (hashHex) were
|
|
74
|
+
* appended — WITHOUT mutating the tree. Used to compute the reported_root for a unilateral
|
|
75
|
+
* seal: the post-SEAL-ctrl-leaf root the directory rebuilds from the relay's content-hash
|
|
76
|
+
* chain and verifies, without advancing the durable tree / message_count.
|
|
77
|
+
*/
|
|
78
|
+
rootWithAppendedHex(hashHex: string): string;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=session-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-tree.d.ts","sourceRoot":"","sources":["../src/session-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,MAAM,CAAC;AAEjD,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,WAAW;;IAOtB,OAAO;IAQP,8FAA8F;IAC9F,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIpC,+BAA+B;IAC/B,MAAM,CAAC,KAAK,IAAI,WAAW;IAI3B,4EAA4E;IAC5E,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,WAAW;IAIlE,8CAA8C;IAC9C,IAAI,IAAI,MAAM;IAId,iEAAiE;IACjE,MAAM,IAAI,eAAe,EAAE;IAI3B;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAUrG;;;OAGG;IACH,OAAO,IAAI,MAAM;IASjB;;;;;OAKG;IACH,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;CAU7C"}
|