@cello-protocol/daemon 0.0.230 → 0.0.232

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 (76) hide show
  1. package/dist/channel-admin-lookup.d.ts +54 -0
  2. package/dist/channel-admin-lookup.d.ts.map +1 -0
  3. package/dist/channel-admin-lookup.js +122 -0
  4. package/dist/channel-admin-lookup.js.map +1 -0
  5. package/dist/channel-collect-tick.d.ts +50 -4
  6. package/dist/channel-collect-tick.d.ts.map +1 -1
  7. package/dist/channel-collect-tick.js +103 -7
  8. package/dist/channel-collect-tick.js.map +1 -1
  9. package/dist/channel-config-store.d.ts +3 -0
  10. package/dist/channel-config-store.d.ts.map +1 -1
  11. package/dist/channel-config-store.js +28 -5
  12. package/dist/channel-config-store.js.map +1 -1
  13. package/dist/channel-join-exchange.d.ts +111 -0
  14. package/dist/channel-join-exchange.d.ts.map +1 -0
  15. package/dist/channel-join-exchange.js +296 -0
  16. package/dist/channel-join-exchange.js.map +1 -0
  17. package/dist/channel-membership-store.d.ts +118 -0
  18. package/dist/channel-membership-store.d.ts.map +1 -0
  19. package/dist/channel-membership-store.js +226 -0
  20. package/dist/channel-membership-store.js.map +1 -0
  21. package/dist/channel-membership-wiring.d.ts +98 -0
  22. package/dist/channel-membership-wiring.d.ts.map +1 -0
  23. package/dist/channel-membership-wiring.js +393 -0
  24. package/dist/channel-membership-wiring.js.map +1 -0
  25. package/dist/channel-publish-handlers.d.ts +8 -0
  26. package/dist/channel-publish-handlers.d.ts.map +1 -1
  27. package/dist/channel-publish-handlers.js +3 -0
  28. package/dist/channel-publish-handlers.js.map +1 -1
  29. package/dist/channel-publish-wiring.d.ts +21 -0
  30. package/dist/channel-publish-wiring.d.ts.map +1 -1
  31. package/dist/channel-publish-wiring.js +39 -3
  32. package/dist/channel-publish-wiring.js.map +1 -1
  33. package/dist/channel-publisher.d.ts +34 -2
  34. package/dist/channel-publisher.d.ts.map +1 -1
  35. package/dist/channel-publisher.js +46 -10
  36. package/dist/channel-publisher.js.map +1 -1
  37. package/dist/channel-subscription-store.d.ts +71 -0
  38. package/dist/channel-subscription-store.d.ts.map +1 -1
  39. package/dist/channel-subscription-store.js +150 -9
  40. package/dist/channel-subscription-store.js.map +1 -1
  41. package/dist/channel-wake-sender.d.ts +35 -0
  42. package/dist/channel-wake-sender.d.ts.map +1 -0
  43. package/dist/channel-wake-sender.js +61 -0
  44. package/dist/channel-wake-sender.js.map +1 -0
  45. package/dist/daemon.d.ts.map +1 -1
  46. package/dist/daemon.js +76 -1
  47. package/dist/daemon.js.map +1 -1
  48. package/dist/inbound-sessions.d.ts +6 -0
  49. package/dist/inbound-sessions.d.ts.map +1 -1
  50. package/dist/inbound-sessions.js +27 -1
  51. package/dist/inbound-sessions.js.map +1 -1
  52. package/dist/refusal-reasons.d.ts +7 -0
  53. package/dist/refusal-reasons.d.ts.map +1 -1
  54. package/dist/refusal-reasons.js +15 -0
  55. package/dist/refusal-reasons.js.map +1 -1
  56. package/dist/register-handler.d.ts.map +1 -1
  57. package/dist/register-handler.js +18 -1
  58. package/dist/register-handler.js.map +1 -1
  59. package/dist/registration-manager.d.ts +6 -0
  60. package/dist/registration-manager.d.ts.map +1 -1
  61. package/dist/registration-manager.js +9 -1
  62. package/dist/registration-manager.js.map +1 -1
  63. package/dist/session-content-context.d.ts +8 -0
  64. package/dist/session-content-context.d.ts.map +1 -1
  65. package/dist/session-content-ingest.d.ts.map +1 -1
  66. package/dist/session-content-ingest.js +28 -0
  67. package/dist/session-content-ingest.js.map +1 -1
  68. package/dist/session-node-manager.d.ts +4 -0
  69. package/dist/session-node-manager.d.ts.map +1 -1
  70. package/dist/session-node-manager.js +11 -0
  71. package/dist/session-node-manager.js.map +1 -1
  72. package/dist/signaling-wiring.d.ts +5 -0
  73. package/dist/signaling-wiring.d.ts.map +1 -1
  74. package/dist/signaling-wiring.js +14 -0
  75. package/dist/signaling-wiring.js.map +1 -1
  76. package/package.json +5 -5
@@ -0,0 +1,54 @@
1
+ /**
2
+ * M16 020-CHANADMIN — asking a directory who administers a channel.
3
+ *
4
+ * A subscriber that has been handed a group key must check the agent that handed it over against
5
+ * what the directory says about the channel. The session proves who the counterparty IS; it says
6
+ * nothing about whether they have any authority over this channel. Without the check, any agent that
7
+ * can open a session with you becomes your channel.
8
+ *
9
+ * 019 built the check and it could not run: the answer lives in the directory's profile and no
10
+ * client-facing frame carried it. `channel_admin_query` (020, directory side) does, on the
11
+ * authenticated signaling stream. This is the client half.
12
+ *
13
+ * ⚠️ **"NOT A CHANNEL" AND "I COULD NOT FIND OUT" ARE DIFFERENT ANSWERS, and the whole value of this
14
+ * module is keeping them apart.** A negative is authoritative — the subscriber acts on it. A
15
+ * timeout, a down stream, a malformed reply or a directory-side `lookup_failed` are all the absence
16
+ * of an answer, and they leave the join refused. Collapsed into the negative, a directory outage
17
+ * would read as a verdict about the channel, and the step after that is accepting a key from
18
+ * whoever happened to answer.
19
+ */
20
+ import type { Logger } from "./types.js";
21
+ /**
22
+ * The part of a `SignalingManager` this needs. Structural rather than the class, so the lookup can
23
+ * be exercised without standing up a directory connection.
24
+ */
25
+ export interface SignalingLike {
26
+ registerInboundHandler(handler: (frame: Record<string, unknown>) => void): () => void;
27
+ sendRaw(frame: unknown): Promise<{
28
+ ok: boolean;
29
+ reason?: string;
30
+ }>;
31
+ }
32
+ export type ChannelAdminOutcome =
33
+ /** A registered channel, and the agent the directory says administers it. */
34
+ {
35
+ kind: "admin";
36
+ adminPubkeyHex: string;
37
+ }
38
+ /** The directory answered, and this pubkey is not a channel — unregistered, or an ordinary agent. */
39
+ | {
40
+ kind: "not_a_channel";
41
+ }
42
+ /** No answer. NOT a verdict about the channel. */
43
+ | {
44
+ kind: "unavailable";
45
+ reason: string;
46
+ };
47
+ export interface ChannelAdminLookupDeps {
48
+ /** The asking agent's directory connection, or null when it has none. */
49
+ signalingFor: (agentId: string) => SignalingLike | null;
50
+ logger: Logger;
51
+ timeoutMs?: number;
52
+ }
53
+ export declare function createChannelAdminLookup(deps: ChannelAdminLookupDeps): (agentId: string, channelHex: string) => Promise<ChannelAdminOutcome>;
54
+ //# sourceMappingURL=channel-admin-lookup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-admin-lookup.d.ts","sourceRoot":"","sources":["../src/channel-admin-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGzC;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,sBAAsB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACtF,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE;AAED,MAAM,MAAM,mBAAmB;AAC7B,6EAA6E;AAC3E;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE;AAC3C,qGAAqG;GACnG;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE;AAC3B,kDAAkD;GAChD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C,MAAM,WAAW,sBAAsB;IACrC,yEAAyE;IACzE,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,aAAa,GAAG,IAAI,CAAC;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAmBD,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,sBAAsB,GAC3B,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,CA0GvE"}
@@ -0,0 +1,122 @@
1
+ import { extractErrorMessage } from "./error-message.js";
2
+ const DEFAULT_TIMEOUT_MS = 10_000;
3
+ /** 64 lowercase hex characters, or nothing. A partial admin key must not reach a comparison. */
4
+ function adminPubkeyOf(frame) {
5
+ const raw = frame["admin_pubkey"];
6
+ if (typeof raw !== "string" || !/^[0-9a-fA-F]{64}$/.test(raw))
7
+ return null;
8
+ return raw.toLowerCase();
9
+ }
10
+ function echoedChannelHex(frame) {
11
+ const raw = frame["channel_pubkey"];
12
+ if (raw instanceof Uint8Array)
13
+ return Buffer.from(raw).toString("hex");
14
+ if (Buffer.isBuffer(raw))
15
+ return raw.toString("hex");
16
+ if (typeof raw === "string" && /^[0-9a-fA-F]{64}$/.test(raw))
17
+ return raw.toLowerCase();
18
+ return null;
19
+ }
20
+ export function createChannelAdminLookup(deps) {
21
+ const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
22
+ return async function lookup(agentId, channelHexRaw) {
23
+ const channelHex = channelHexRaw.toLowerCase();
24
+ const signaling = deps.signalingFor(agentId);
25
+ if (!signaling) {
26
+ // Answer now rather than waiting out the timeout for a reply that was never coming — this runs
27
+ // on the inbound join path, once per frame.
28
+ return { kind: "unavailable", reason: "signaling_unavailable" };
29
+ }
30
+ let resolveFrame;
31
+ const pending = new Promise((r) => { resolveFrame = r; });
32
+ /**
33
+ * ⚠️ **MATCHED ON THE ECHOED PUBKEY, not on the frame type alone.** The signaling stream is one
34
+ * multiplexed stream shared by every lookup this agent has in flight. Taking the first
35
+ * `channel_admin_result` that arrives would let two concurrent joins each accept the other's
36
+ * answer — and then the admin check runs against a different channel's administrator, where a
37
+ * match proves nothing.
38
+ */
39
+ const unregister = signaling.registerInboundHandler((frame) => {
40
+ const t = frame["type"];
41
+ if (t !== "channel_admin_result" && t !== "channel_admin_error")
42
+ return;
43
+ /**
44
+ * ⚠️ **THE ERROR IS MATCHED THE SAME WAY, and it was not at first.** An unmatched error
45
+ * resolved whichever lookup happened to be waiting: two joins in flight, a database fault on
46
+ * the first channel, and the second was refused although the directory had answered it
47
+ * perfectly. It fails closed, so nothing unsafe — it just turns one channel's outage into
48
+ * every concurrent join's refusal, which is a bad trade for a field.
49
+ */
50
+ if (echoedChannelHex(frame) !== channelHex)
51
+ return;
52
+ resolveFrame(frame);
53
+ });
54
+ let timer;
55
+ try {
56
+ const sent = await signaling.sendRaw({
57
+ type: "channel_admin_query",
58
+ channel_pubkey: new Uint8Array(Buffer.from(channelHex, "hex")),
59
+ });
60
+ if (!sent.ok) {
61
+ // The transport's own reason, never a guess about the directory or the channel.
62
+ return { kind: "unavailable", reason: sent.reason ?? "signaling_unavailable" };
63
+ }
64
+ const timeoutP = new Promise((r) => {
65
+ timer = setTimeout(() => r({ type: "__timeout__" }), timeoutMs);
66
+ });
67
+ const frame = await Promise.race([pending, timeoutP]);
68
+ if (frame["type"] === "__timeout__") {
69
+ deps.logger.warn("directory.channel.admin.lookup.failed", { channel: channelHex.slice(0, 16), reason: "timeout" });
70
+ return { kind: "unavailable", reason: "timeout" };
71
+ }
72
+ if (frame["type"] === "channel_admin_error") {
73
+ const reason = typeof frame["reason"] === "string" ? frame["reason"] : "lookup_failed";
74
+ deps.logger.warn("directory.channel.admin.lookup.failed", { channel: channelHex.slice(0, 16), reason });
75
+ return { kind: "unavailable", reason };
76
+ }
77
+ /**
78
+ * ⚠️ **SHAPE FIRST, AND THIS WAS A DEFECT THE TEST CAUGHT.** `registered !== true` is true for
79
+ * a field that is ABSENT as well as one that is `false` — so a reply missing the field
80
+ * entirely read as an authoritative "not a channel", which is precisely the collapse this
81
+ * module exists to prevent. A frame that does not carry both booleans is a directory that
82
+ * replied with something we cannot read, and that is not knowing.
83
+ */
84
+ if (typeof frame["registered"] !== "boolean" || typeof frame["channel"] !== "boolean") {
85
+ deps.logger.warn("directory.channel.admin.lookup.failed", { channel: channelHex.slice(0, 16), reason: "malformed_reply" });
86
+ return { kind: "unavailable", reason: "malformed_reply" };
87
+ }
88
+ if (frame["registered"] !== true || frame["channel"] !== true) {
89
+ // An answer, and a settled one: the subscriber is looking at something that is not a channel.
90
+ deps.logger.info("directory.channel.admin.lookup", { channel: channelHex.slice(0, 16), answered: "not_a_channel" });
91
+ return { kind: "not_a_channel" };
92
+ }
93
+ const adminPubkeyHex = adminPubkeyOf(frame);
94
+ if (adminPubkeyHex === null) {
95
+ /**
96
+ * A registered channel whose answer carries no usable admin key. Reading a missing or
97
+ * malformed field as `""` would put an empty key into the comparison against whoever
98
+ * answered — which either matches nothing for ever or, worse, matches an empty counterparty.
99
+ * So it is `unavailable`: there is no admin to check against, and that is not knowing.
100
+ *
101
+ * ⚠️ **NAMED FOR THE REGISTRATION, NOT THE WIRE.** Calling this `malformed_reply` sent the
102
+ * operator to debug the protocol, when the directory answered exactly as designed about a
103
+ * channel that was registered without an administrator. The reason has to point at the
104
+ * thing that is actually wrong.
105
+ */
106
+ deps.logger.warn("directory.channel.admin.lookup.failed", { channel: channelHex.slice(0, 16), reason: "channel_without_admin" });
107
+ return { kind: "unavailable", reason: "channel_without_admin" };
108
+ }
109
+ deps.logger.info("directory.channel.admin.lookup", { channel: channelHex.slice(0, 16), answered: "admin" });
110
+ return { kind: "admin", adminPubkeyHex };
111
+ }
112
+ catch (err) {
113
+ return { kind: "unavailable", reason: extractErrorMessage(err) };
114
+ }
115
+ finally {
116
+ if (timer !== undefined)
117
+ clearTimeout(timer);
118
+ unregister();
119
+ }
120
+ };
121
+ }
122
+ //# sourceMappingURL=channel-admin-lookup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-admin-lookup.js","sourceRoot":"","sources":["../src/channel-admin-lookup.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,48 @@
1
1
  import type { Logger } from "./types.js";
2
2
  import type { ChannelCollector } from "./channel-collector.js";
3
3
  import type { ChannelSubscriptionStore } from "./channel-subscription-store.js";
4
- /** See the header: bounded by the shortest retention any channel may configure. */
4
+ /**
5
+ * ⚠️ **THIS IS A BACKSTOP, NOT THE DELIVERY MECHANISM — 021-WAKE changed what it is FOR.**
6
+ *
7
+ * At five minutes this timer WAS how a post reached anyone, and the feature was email with a
8
+ * five-minute inbox check. Delivery is now the wake (021): the publisher asks a directory to poke
9
+ * the members and they fetch at once.
10
+ *
11
+ * The timer keeps a job because push is best-effort, and it is the only thing that covers a post
12
+ * sitting on a relay with nobody told: the publisher's daemon never sent the wake, the frame was
13
+ * lost while the signaling stream rotated, or the peer forward never reached this subscriber's
14
+ * node. It does not go to zero, because the case it cannot otherwise repair is a channel that goes
15
+ * QUIET right after the message you missed — which is exactly the "relays going down now" post.
16
+ */
5
17
  export declare const COLLECT_TICK_INTERVAL_MS: number;
6
- /** The widest the pubkey-derived offset can push a channel's tick. */
7
- export declare const COLLECT_JITTER_MS = 60000;
8
- /** A failing channel is backed off to at most this, then stays there until it succeeds. */
18
+ /**
19
+ * The widest the pubkey-derived offset can push a channel's tick — **a proportion of the interval,
20
+ * which is why it is written as one.** A fixed 60 seconds was right for a 5-minute tick and is a
21
+ * narrow band inside an hour: the fleet would clump into the same minute every hour, which is the
22
+ * thundering herd this exists to prevent, arriving less often and so harder to notice.
23
+ */
24
+ export declare const COLLECT_JITTER_MS: number;
25
+ /**
26
+ * A failing channel is backed off to at most this, then stays there until it succeeds.
27
+ *
28
+ * ⚠️ **IT HAS TO EXCEED THE INTERVAL OR IT IS NOT A BACKOFF.** At 30 minutes against an hourly
29
+ * poll, a FAILING channel became due sooner than a healthy one — the first failure computed
30
+ * `min(30min, 60min×2)` = 30 minutes, and every subsequent one stayed there. This number was sized
31
+ * for a five-minute interval and not re-derived when the interval moved, which is the whole
32
+ * mistake: a constant that only makes sense relative to another constant should be written
33
+ * relative to it.
34
+ */
9
35
  export declare const COLLECT_MAX_BACKOFF_MS: number;
36
+ /**
37
+ * Randomness added to a RETRY, on top of the stable per-channel jitter.
38
+ *
39
+ * ⚠️ **THE STEADY-STATE TIMER IS NOT THE STAMPEDE RISK — RECOVERY IS.** When a relay returns after
40
+ * an outage, every daemon that has been failing is on the retry path, not the timer, and
41
+ * `jitterForChannel` is stable per (agent, channel) so it does not stagger the returning WAVE: all
42
+ * of them back off to the same ceiling and come due on the same pass. This is the one place a
43
+ * re-rolled random belongs, because the thing being spread is a moment rather than a schedule.
44
+ */
45
+ export declare const COLLECT_RETRY_SPREAD_MS: number;
10
46
  export interface ChannelCollectTickDeps {
11
47
  logger: Logger;
12
48
  collector: ChannelCollector;
@@ -19,12 +55,22 @@ export interface ChannelCollectTickDeps {
19
55
  intervalMs?: number;
20
56
  jitterMs?: number;
21
57
  maxBackoffMs?: number;
58
+ /** M16 021-WAKE: random spread on the RETRY path only. Set to 0 in a test that needs determinism. */
59
+ retrySpreadMs?: number;
22
60
  }
23
61
  export interface ChannelCollectTicker {
24
62
  /** Begin collecting. Idempotent: a second call does not stack a second timer. */
25
63
  start: () => void;
26
64
  /** One pass over every active subscription. Exposed so a test drives it without a clock. */
27
65
  collectAllDue: (now: number) => Promise<void>;
66
+ /**
67
+ * M16 021-WAKE: the doorbell rang — collect this agent's channels NOW.
68
+ *
69
+ * ⚠️ **EVERY channel this agent follows, because the wake names none.** The frame is deliberately
70
+ * empty so that channel→member never travels a second wire; the daemon already knows what it
71
+ * subscribes to, and fetching all of them is what makes the empty frame sufficient.
72
+ */
73
+ collectNow: (agentId: string) => Promise<void>;
28
74
  stop: () => void;
29
75
  }
30
76
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"channel-collect-tick.d.ts","sourceRoot":"","sources":["../src/channel-collect-tick.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAGhF,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,QAAa,CAAC;AACnD,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,QAAS,CAAC;AACxC,2FAA2F;AAC3F,eAAO,MAAM,sBAAsB,QAAc,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,CAAC;IAC5B,aAAa,EAAE,wBAAwB,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,iFAAiF;IACjF,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,4FAA4F;IAC5F,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI9F;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,sBAAsB,GAAG,oBAAoB,CAgF7F"}
1
+ {"version":3,"file":"channel-collect-tick.d.ts","sourceRoot":"","sources":["../src/channel-collect-tick.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAGhF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,QAAc,CAAC;AACpD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAA+B,CAAC;AAC9D;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,QAA+B,CAAC;AAEnE;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,QAAc,CAAC;AAEnD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,CAAC;IAC5B,aAAa,EAAE,wBAAwB,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qGAAqG;IACrG,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,iFAAiF;IACjF,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,4FAA4F;IAC5F,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C;;;;;;OAMG;IACH,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI9F;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,sBAAsB,GAAG,oBAAoB,CAyI7F"}
@@ -32,12 +32,48 @@
32
32
  */
33
33
  import { createHash } from "node:crypto";
34
34
  import { extractErrorMessage } from "./error-message.js";
35
- /** See the header: bounded by the shortest retention any channel may configure. */
36
- export const COLLECT_TICK_INTERVAL_MS = 5 * 60_000;
37
- /** The widest the pubkey-derived offset can push a channel's tick. */
38
- export const COLLECT_JITTER_MS = 60_000;
39
- /** A failing channel is backed off to at most this, then stays there until it succeeds. */
40
- export const COLLECT_MAX_BACKOFF_MS = 30 * 60_000;
35
+ /**
36
+ * ⚠️ **THIS IS A BACKSTOP, NOT THE DELIVERY MECHANISM — 021-WAKE changed what it is FOR.**
37
+ *
38
+ * At five minutes this timer WAS how a post reached anyone, and the feature was email with a
39
+ * five-minute inbox check. Delivery is now the wake (021): the publisher asks a directory to poke
40
+ * the members and they fetch at once.
41
+ *
42
+ * The timer keeps a job because push is best-effort, and it is the only thing that covers a post
43
+ * sitting on a relay with nobody told: the publisher's daemon never sent the wake, the frame was
44
+ * lost while the signaling stream rotated, or the peer forward never reached this subscriber's
45
+ * node. It does not go to zero, because the case it cannot otherwise repair is a channel that goes
46
+ * QUIET right after the message you missed — which is exactly the "relays going down now" post.
47
+ */
48
+ export const COLLECT_TICK_INTERVAL_MS = 60 * 60_000;
49
+ /**
50
+ * The widest the pubkey-derived offset can push a channel's tick — **a proportion of the interval,
51
+ * which is why it is written as one.** A fixed 60 seconds was right for a 5-minute tick and is a
52
+ * narrow band inside an hour: the fleet would clump into the same minute every hour, which is the
53
+ * thundering herd this exists to prevent, arriving less often and so harder to notice.
54
+ */
55
+ export const COLLECT_JITTER_MS = COLLECT_TICK_INTERVAL_MS / 2;
56
+ /**
57
+ * A failing channel is backed off to at most this, then stays there until it succeeds.
58
+ *
59
+ * ⚠️ **IT HAS TO EXCEED THE INTERVAL OR IT IS NOT A BACKOFF.** At 30 minutes against an hourly
60
+ * poll, a FAILING channel became due sooner than a healthy one — the first failure computed
61
+ * `min(30min, 60min×2)` = 30 minutes, and every subsequent one stayed there. This number was sized
62
+ * for a five-minute interval and not re-derived when the interval moved, which is the whole
63
+ * mistake: a constant that only makes sense relative to another constant should be written
64
+ * relative to it.
65
+ */
66
+ export const COLLECT_MAX_BACKOFF_MS = 4 * COLLECT_TICK_INTERVAL_MS;
67
+ /**
68
+ * Randomness added to a RETRY, on top of the stable per-channel jitter.
69
+ *
70
+ * ⚠️ **THE STEADY-STATE TIMER IS NOT THE STAMPEDE RISK — RECOVERY IS.** When a relay returns after
71
+ * an outage, every daemon that has been failing is on the retry path, not the timer, and
72
+ * `jitterForChannel` is stable per (agent, channel) so it does not stagger the returning WAVE: all
73
+ * of them back off to the same ceiling and come due on the same pass. This is the one place a
74
+ * re-rolled random belongs, because the thing being spread is a moment rather than a schedule.
75
+ */
76
+ export const COLLECT_RETRY_SPREAD_MS = 10 * 60_000;
41
77
  /**
42
78
  * A stable offset in [0, jitterMs) for a channel, from its public key. Same channel, same
43
79
  * subscriber, same offset every time — see the header for why that beats a fresh random each tick.
@@ -53,6 +89,7 @@ export function createChannelCollectTicker(deps) {
53
89
  const intervalMs = deps.intervalMs ?? COLLECT_TICK_INTERVAL_MS;
54
90
  const jitterMs = deps.jitterMs ?? COLLECT_JITTER_MS;
55
91
  const maxBackoffMs = deps.maxBackoffMs ?? COLLECT_MAX_BACKOFF_MS;
92
+ const retrySpreadMs = deps.retrySpreadMs ?? COLLECT_RETRY_SPREAD_MS;
56
93
  /** When each subscription is next due, and how far it has been backed off. */
57
94
  const nextDue = new Map();
58
95
  const backoff = new Map();
@@ -95,7 +132,12 @@ export function createChannelCollectTicker(deps) {
95
132
  // and one channel's bad relay must not stop every other channel on this daemon.
96
133
  const next = Math.min(maxBackoffMs, (backoff.get(key) ?? intervalMs) * 2);
97
134
  backoff.set(key, next);
98
- nextDue.set(key, now + next + jitterForChannel(sub.channel_pubkey, sub.agent_id, jitterMs));
135
+ // Stable jitter spreads channels across subscribers; the RANDOM part spreads the moment a
136
+ // whole fleet returns after an outage, which stable jitter cannot do because every failing
137
+ // channel lands on the same ceiling.
138
+ nextDue.set(key, now + next
139
+ + jitterForChannel(sub.channel_pubkey, sub.agent_id, jitterMs)
140
+ + Math.floor(Math.random() * retrySpreadMs));
99
141
  logger.warn("channel.collect.tick_failed", {
100
142
  channel_pubkey: sub.channel_pubkey,
101
143
  reason: extractErrorMessage(err),
@@ -107,10 +149,64 @@ export function createChannelCollectTicker(deps) {
107
149
  }
108
150
  }
109
151
  }
152
+ /**
153
+ * M16 021-WAKE — a doorbell arrived for this agent. Fetch its channels at once.
154
+ *
155
+ * ⚠️ **IT DOES NOT TOUCH `nextDue`, and that is deliberate.** A wake that reset the schedule would
156
+ * let a chatty channel keep pushing the backstop out for every OTHER channel on this agent — and
157
+ * the backstop is the only thing that repairs a wake that never arrived.
158
+ *
159
+ * ⚠️ The online check is repeated here. The kill switch has to hold on this path too: collecting
160
+ * for an agent the operator switched off is the switch failing to switch something off, and a
161
+ * doorbell must not become the way around it.
162
+ */
163
+ async function collectNow(agentId) {
164
+ if (!isAgentOnline(agentId)) {
165
+ logger.debug("channel.collect.wake_ignored", { reason: "agent_offline" });
166
+ return;
167
+ }
168
+ for (const sub of subscriptions.active()) {
169
+ if (sub.agent_id !== agentId)
170
+ continue;
171
+ const key = keyOf(sub.agent_id, sub.channel_pubkey);
172
+ if (inFlight.has(key))
173
+ continue;
174
+ inFlight.add(key);
175
+ try {
176
+ await collector.collectOnce(sub.agent_id, sub.channel_pubkey);
177
+ await collector.repairGaps(sub.agent_id, sub.channel_pubkey);
178
+ backoff.delete(key);
179
+ }
180
+ catch (err) {
181
+ // One channel's bad relay must not stop the rest of this agent's channels collecting. The
182
+ // timer's own backoff is untouched: this pass is extra, not a replacement for it.
183
+ logger.warn("channel.collect.wake_failed", {
184
+ channel_pubkey: sub.channel_pubkey, reason: extractErrorMessage(err),
185
+ });
186
+ }
187
+ finally {
188
+ inFlight.delete(key);
189
+ }
190
+ }
191
+ }
110
192
  return {
193
+ collectNow,
111
194
  start() {
112
195
  if (timer !== null)
113
196
  return;
197
+ /**
198
+ * ⚠️ **ONE PASS NOW, BEFORE THE TIMER — and leaving this out made the order's own worst case
199
+ * twelve times worse.** At a five-minute interval a daemon that had just started waited five
200
+ * minutes for its first collection; at sixty it waits an hour. Your laptop was shut
201
+ * overnight, you open it, the daemon reconnects — and nothing is fetched until the next hour.
202
+ * The offline subscriber is exactly the case the backstop exists for.
203
+ *
204
+ * It is also what makes a NEW subscription arrive promptly: `collectAllDue` treats first
205
+ * sight as due immediately, but only a pass can notice it.
206
+ */
207
+ void collectAllDue(Date.now()).catch((err) => {
208
+ logger.warn("channel.collect.pass_failed", { reason: extractErrorMessage(err) });
209
+ });
114
210
  // The pass runs every interval and decides per subscription what is due; the jitter lives in
115
211
  // the due times rather than in the timer, so one slow channel cannot drag the others.
116
212
  timer = setInterval(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"channel-collect-tick.js","sourceRoot":"","sources":["../src/channel-collect-tick.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"channel-collect-tick.js","sourceRoot":"","sources":["../src/channel-collect-tick.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,9 @@ export interface ChannelConfig {
23
23
  relays: string[];
24
24
  guidance: string;
25
25
  retention_seconds: number;
26
+ members_visible?: boolean;
27
+ /** The agent that answers join requests. NOT the channel key — see the column comment. */
28
+ admin_pubkey?: string;
26
29
  }
27
30
  export declare class ChannelConfigStore {
28
31
  #private;
@@ -1 +1 @@
1
- {"version":3,"file":"channel-config-store.d.ts","sourceRoot":"","sources":["../src/channel-config-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAEpE,8DAA8D;AAC9D,eAAO,MAAM,yBAAyB,QAAmB,CAAC;AAE1D,eAAO,MAAM,yBAAyB,QAWrC,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,kBAAkB;;gBAIjB,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM;IAM9C,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IA0B7C,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;CAiBlE"}
1
+ {"version":3,"file":"channel-config-store.d.ts","sourceRoot":"","sources":["../src/channel-config-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAEpE,8DAA8D;AAC9D,eAAO,MAAM,yBAAyB,QAAmB,CAAC;AAE1D,eAAO,MAAM,yBAAyB,QA0BrC,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0FAA0F;IAC1F,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,kBAAkB;;gBAIjB,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM;IAM9C,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAkC7C,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;CAsBlE"}
@@ -9,6 +9,21 @@ export const CHANNEL_CONFIG_CREATE_SQL = `
9
9
  relays TEXT NOT NULL,
10
10
  guidance TEXT NOT NULL DEFAULT '',
11
11
  retention_seconds INTEGER NOT NULL DEFAULT ${String(DEFAULT_RETENTION_SECONDS)},
12
+ -- ─── M16 019-MEMBERSHIP: one table, not two ───────────────────────────────────────────────
13
+ -- 019 first added a channel_settings table duplicating four of these columns, and wired NO
14
+ -- writer to it — so every membership read came back empty and every join was refused on a
15
+ -- channel the daemon genuinely administered. Two tables for one fact, with the live path
16
+ -- reading the empty one. Collapsed here while the database is still empty, which makes it a
17
+ -- schema edit rather than a migration.
18
+ --
19
+ -- Whether members can see each other. A publisher's decision about its own channel.
20
+ members_visible INTEGER NOT NULL DEFAULT 0,
21
+ -- 0 means NO KEY HAS EVER BEEN ISSUED. The first join mints generation 1; each ejection
22
+ -- advances it, and that number is what every body and every fetch key is bound to.
23
+ key_generation INTEGER NOT NULL DEFAULT 0,
24
+ -- The AGENT that admits members and answers join requests. Distinct from the channel key: the
25
+ -- channel signs posts and never converses, the admin holds the sessions. Empty until set.
26
+ admin_pubkey TEXT NOT NULL DEFAULT '',
12
27
  updated_at INTEGER NOT NULL
13
28
  );
14
29
  `;
@@ -22,7 +37,8 @@ export class ChannelConfigStore {
22
37
  }
23
38
  get(channelHex) {
24
39
  const row = this.#db
25
- .prepare(`SELECT access, relays, guidance, retention_seconds FROM channel_config WHERE channel_pubkey = ?`)
40
+ .prepare(`SELECT access, relays, guidance, retention_seconds, members_visible, admin_pubkey
41
+ FROM channel_config WHERE channel_pubkey = ?`)
26
42
  .get(channelHex.toLowerCase());
27
43
  if (!row)
28
44
  return null;
@@ -43,16 +59,23 @@ export class ChannelConfigStore {
43
59
  relays,
44
60
  guidance: row.guidance,
45
61
  retention_seconds: Number(row.retention_seconds),
62
+ members_visible: Number(row.members_visible) === 1,
63
+ admin_pubkey: row.admin_pubkey,
46
64
  };
47
65
  }
48
66
  set(channelHex, config, now) {
49
67
  this.#db
50
- .prepare(`INSERT INTO channel_config (channel_pubkey, access, relays, guidance, retention_seconds, updated_at)
51
- VALUES (?, ?, ?, ?, ?, ?)
68
+ .prepare(
69
+ // ⚠️ `key_generation` is NOT in the update list, and must never be: it is advanced only by an
70
+ // ejection, and letting a settings edit touch it would silently re-key or un-re-key a channel.
71
+ `INSERT INTO channel_config
72
+ (channel_pubkey, access, relays, guidance, retention_seconds, members_visible, admin_pubkey, updated_at)
73
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
52
74
  ON CONFLICT (channel_pubkey) DO UPDATE SET
53
75
  access = excluded.access, relays = excluded.relays, guidance = excluded.guidance,
54
- retention_seconds = excluded.retention_seconds, updated_at = excluded.updated_at`)
55
- .run(channelHex.toLowerCase(), config.access, JSON.stringify(config.relays), config.guidance, config.retention_seconds, now);
76
+ retention_seconds = excluded.retention_seconds, members_visible = excluded.members_visible,
77
+ admin_pubkey = excluded.admin_pubkey, updated_at = excluded.updated_at`)
78
+ .run(channelHex.toLowerCase(), config.access, JSON.stringify(config.relays), config.guidance, config.retention_seconds, config.members_visible === true ? 1 : 0, config.admin_pubkey ?? "", now);
56
79
  this.#logger.info("channel.config.set", {
57
80
  channel_pubkey: channelHex, access: config.access, relays: config.relays.length,
58
81
  });
@@ -1 +1 @@
1
- {"version":3,"file":"channel-config-store.js","sourceRoot":"","sources":["../src/channel-config-store.ts"],"names":[],"mappings":"AAkBA,8DAA8D;AAC9D,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAE1D,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;kDAQS,MAAM,CAAC,yBAAyB,CAAC;;;CAGlF,CAAC;AASF,MAAM,OAAO,kBAAkB;IACpB,GAAG,CAAiB;IACpB,OAAO,CAAS;IAEzB,YAAY,EAAkB,EAAE,MAAc;QAC5C,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAC3C,CAAC;IAED,GAAG,CAAC,UAAkB;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG;aACjB,OAAO,CAAC,iGAAiG,CAAC;aAC1G,GAAG,CAAC,UAAU,CAAC,WAAW,EAAE,CAElB,CAAC;QACd,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QAEtB,IAAI,MAAM,GAAa,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAY,CAAC;YACjD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;QAC/F,CAAC;QAAC,MAAM,CAAC;YACP,8FAA8F;YAC9F,sCAAsC;YACtC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC,CAAC;YACpF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAuB;YACnC,MAAM;YACN,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,iBAAiB,EAAE,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC;SACjD,CAAC;IACJ,CAAC;IAED,GAAG,CAAC,UAAkB,EAAE,MAAqB,EAAE,GAAW;QACxD,IAAI,CAAC,GAAG;aACL,OAAO,CACN;;;;4FAIoF,CACrF;aACA,GAAG,CACF,UAAU,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EACtE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,iBAAiB,EAAE,GAAG,CAC/C,CAAC;QACJ,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE;YACtC,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;SAChF,CAAC,CAAC;IACL,CAAC;CACF"}
1
+ {"version":3,"file":"channel-config-store.js","sourceRoot":"","sources":["../src/channel-config-store.ts"],"names":[],"mappings":"AAkBA,8DAA8D;AAC9D,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAE1D,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;kDAQS,MAAM,CAAC,yBAAyB,CAAC;;;;;;;;;;;;;;;;;;CAkBlF,CAAC;AAYF,MAAM,OAAO,kBAAkB;IACpB,GAAG,CAAiB;IACpB,OAAO,CAAS;IAEzB,YAAY,EAAkB,EAAE,MAAc;QAC5C,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAC3C,CAAC;IAED,GAAG,CAAC,UAAkB;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG;aACjB,OAAO,CACN;wDACgD,CACjD;aACA,GAAG,CAAC,UAAU,CAAC,WAAW,EAAE,CAKlB,CAAC;QACd,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QAEtB,IAAI,MAAM,GAAa,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAY,CAAC;YACjD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;QAC/F,CAAC;QAAC,MAAM,CAAC;YACP,8FAA8F;YAC9F,sCAAsC;YACtC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC,CAAC;YACpF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAuB;YACnC,MAAM;YACN,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,iBAAiB,EAAE,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC;YAChD,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC;YAClD,YAAY,EAAE,GAAG,CAAC,YAAY;SAC/B,CAAC;IACJ,CAAC;IAED,GAAG,CAAC,UAAkB,EAAE,MAAqB,EAAE,GAAW;QACxD,IAAI,CAAC,GAAG;aACL,OAAO;QACN,8FAA8F;QAC9F,+FAA+F;QAC/F;;;;;;kFAM0E,CAC3E;aACA,GAAG,CACF,UAAU,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EACtE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,iBAAiB,EACzC,MAAM,CAAC,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,YAAY,IAAI,EAAE,EAAE,GAAG,CACxE,CAAC;QACJ,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE;YACtC,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;SAChF,CAAC,CAAC;IACL,CAAC;CACF"}