@cello-protocol/daemon 0.0.229 → 0.0.231

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 (77) 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 +36 -0
  6. package/dist/channel-collect-tick.d.ts.map +1 -0
  7. package/dist/channel-collect-tick.js +133 -0
  8. package/dist/channel-collect-tick.js.map +1 -0
  9. package/dist/channel-collector.d.ts +99 -0
  10. package/dist/channel-collector.d.ts.map +1 -0
  11. package/dist/channel-collector.js +249 -0
  12. package/dist/channel-collector.js.map +1 -0
  13. package/dist/channel-config-store.d.ts +36 -0
  14. package/dist/channel-config-store.d.ts.map +1 -0
  15. package/dist/channel-config-store.js +84 -0
  16. package/dist/channel-config-store.js.map +1 -0
  17. package/dist/channel-inbox-store.d.ts +46 -0
  18. package/dist/channel-inbox-store.d.ts.map +1 -0
  19. package/dist/channel-inbox-store.js +88 -0
  20. package/dist/channel-inbox-store.js.map +1 -0
  21. package/dist/channel-join-exchange.d.ts +87 -0
  22. package/dist/channel-join-exchange.d.ts.map +1 -0
  23. package/dist/channel-join-exchange.js +289 -0
  24. package/dist/channel-join-exchange.js.map +1 -0
  25. package/dist/channel-log-store.d.ts +14 -0
  26. package/dist/channel-log-store.d.ts.map +1 -1
  27. package/dist/channel-log-store.js +58 -0
  28. package/dist/channel-log-store.js.map +1 -1
  29. package/dist/channel-membership-store.d.ts +118 -0
  30. package/dist/channel-membership-store.d.ts.map +1 -0
  31. package/dist/channel-membership-store.js +226 -0
  32. package/dist/channel-membership-store.js.map +1 -0
  33. package/dist/channel-membership-wiring.d.ts +90 -0
  34. package/dist/channel-membership-wiring.d.ts.map +1 -0
  35. package/dist/channel-membership-wiring.js +387 -0
  36. package/dist/channel-membership-wiring.js.map +1 -0
  37. package/dist/channel-publish-handlers.d.ts +42 -0
  38. package/dist/channel-publish-handlers.d.ts.map +1 -0
  39. package/dist/channel-publish-handlers.js +212 -0
  40. package/dist/channel-publish-handlers.js.map +1 -0
  41. package/dist/channel-publish-wiring.d.ts +52 -0
  42. package/dist/channel-publish-wiring.d.ts.map +1 -0
  43. package/dist/channel-publish-wiring.js +143 -0
  44. package/dist/channel-publish-wiring.js.map +1 -0
  45. package/dist/channel-publisher.d.ts +221 -0
  46. package/dist/channel-publisher.d.ts.map +1 -0
  47. package/dist/channel-publisher.js +430 -0
  48. package/dist/channel-publisher.js.map +1 -0
  49. package/dist/channel-relay-client.d.ts +88 -0
  50. package/dist/channel-relay-client.d.ts.map +1 -0
  51. package/dist/channel-relay-client.js +180 -0
  52. package/dist/channel-relay-client.js.map +1 -0
  53. package/dist/channel-subscription-store.d.ts +137 -0
  54. package/dist/channel-subscription-store.d.ts.map +1 -0
  55. package/dist/channel-subscription-store.js +250 -0
  56. package/dist/channel-subscription-store.js.map +1 -0
  57. package/dist/daemon.d.ts.map +1 -1
  58. package/dist/daemon.js +54 -0
  59. package/dist/daemon.js.map +1 -1
  60. package/dist/inbound-sessions.d.ts +6 -0
  61. package/dist/inbound-sessions.d.ts.map +1 -1
  62. package/dist/inbound-sessions.js +27 -1
  63. package/dist/inbound-sessions.js.map +1 -1
  64. package/dist/refusal-reasons.d.ts +7 -0
  65. package/dist/refusal-reasons.d.ts.map +1 -1
  66. package/dist/refusal-reasons.js +15 -0
  67. package/dist/refusal-reasons.js.map +1 -1
  68. package/dist/session-content-context.d.ts +8 -0
  69. package/dist/session-content-context.d.ts.map +1 -1
  70. package/dist/session-content-ingest.d.ts.map +1 -1
  71. package/dist/session-content-ingest.js +28 -0
  72. package/dist/session-content-ingest.js.map +1 -1
  73. package/dist/session-node-manager.d.ts +4 -0
  74. package/dist/session-node-manager.d.ts.map +1 -1
  75. package/dist/session-node-manager.js +11 -0
  76. package/dist/session-node-manager.js.map +1 -1
  77. package/package.json +5 -5
@@ -0,0 +1,249 @@
1
+ import { broadcastPostHash, decodeBroadcastArtifact, validateBroadcastTitle, verifyBroadcastArtifact, } from "@cello-protocol/protocol-types";
2
+ /** At most this many agent keys ride a fetch, per the relay's own cap (017). */
3
+ const MAX_AGENT_KEYS_PER_FETCH = 32;
4
+ export class ChannelCollector {
5
+ #opts;
6
+ #now;
7
+ /** Relay-reported `first_held_seq` per channel — the floor below which absence is not a gap. */
8
+ #firstHeld = new Map();
9
+ /**
10
+ * Gaps already reported to the publisher, so a survivor is not re-requested every tick.
11
+ *
12
+ * ⚠️ KEYED ON `agentId:channel`, NOT on the channel. Two agents on this daemon can subscribe to
13
+ * the same channel, and one key per channel means the second agent's genuine gap is silently never
14
+ * requested once the first has asked — its posts simply never arrive, with nothing logged.
15
+ */
16
+ #repairRequested = new Map();
17
+ /** The one place the repair key is built, so the two halves cannot drift apart. */
18
+ static #repairKey(agentId, channelHex) {
19
+ return `${agentId}:${channelHex}`;
20
+ }
21
+ constructor(opts) {
22
+ this.#opts = opts;
23
+ this.#now = opts.now ?? (() => Date.now());
24
+ }
25
+ /** One pass over one channel: fetch both relays, verify, store, advance. */
26
+ async collectOnce(agentId, channelHex, correlationId) {
27
+ const { logger, subscriptions, inbox } = this.#opts;
28
+ const sub = subscriptions.get(agentId, channelHex);
29
+ if (!sub || sub.status !== "active")
30
+ return;
31
+ const since = sub.delivered_through + 1;
32
+ const auth = await this.#opts.fetchAuth(sub.access, channelHex, since);
33
+ const agentKeys = this.#opts.localAgentKeys(channelHex).slice(0, MAX_AGENT_KEYS_PER_FETCH);
34
+ const channelPubkey = new Uint8Array(Buffer.from(channelHex, "hex"));
35
+ let lowestFirstHeld = null;
36
+ for (const relay of sub.relays) {
37
+ let answer;
38
+ try {
39
+ answer = await this.#opts.fetch(relay, {
40
+ channel_pubkey: channelPubkey,
41
+ since_seq: since,
42
+ max_bytes: this.#opts.maxBytesPerFetch ?? 4 * 1024 * 1024,
43
+ ...(auth ? { auth } : {}),
44
+ ...(agentKeys.length > 0 ? { agent_pubkeys: agentKeys } : {}),
45
+ });
46
+ }
47
+ catch (err) {
48
+ // ⚠️ ONE RELAY DOWN IS NOT A FAILED COLLECTION. The other still has the posts, which is the
49
+ // entire reason a channel publishes to two.
50
+ logger.warn("channel.fetch.failed", {
51
+ ...(correlationId !== undefined ? { correlationId } : {}),
52
+ channel_pubkey: channelHex, relay, reason: extract(err),
53
+ });
54
+ continue;
55
+ }
56
+ if (!answer.ok) {
57
+ logger.warn("channel.fetch.failed", {
58
+ ...(correlationId !== undefined ? { correlationId } : {}),
59
+ channel_pubkey: channelHex, relay, reason: answer.reason,
60
+ });
61
+ continue;
62
+ }
63
+ if (answer.first_held_seq !== null) {
64
+ lowestFirstHeld = lowestFirstHeld === null ? answer.first_held_seq : Math.min(lowestFirstHeld, answer.first_held_seq);
65
+ }
66
+ let stored = 0;
67
+ for (const entry of answer.posts) {
68
+ const post = this.#verify(agentId, channelHex, sub.admin_pubkey, entry.post_cbor, relay, correlationId);
69
+ if (!post)
70
+ continue;
71
+ const result = inbox.store(agentId, channelHex, post, {
72
+ ...(entry.receipt_cbor ? { receiptCbor: entry.receipt_cbor } : {}),
73
+ fromRelay: relay,
74
+ collectedAt: this.#now(),
75
+ });
76
+ if (result.stored)
77
+ stored += 1;
78
+ }
79
+ logger.info("channel.fetch.completed", {
80
+ ...(correlationId !== undefined ? { correlationId } : {}),
81
+ // `sub` was read BEFORE the fetch, so its position is the one we started from. Naming it
82
+ // `delivered_through` made the log say the position had not moved on every successful
83
+ // collection — the advance happens below, after every relay has been read.
84
+ channel_pubkey: channelHex, relay, count: stored, delivered_through_before: sub.delivered_through,
85
+ });
86
+ }
87
+ if (lowestFirstHeld !== null)
88
+ this.#firstHeld.set(channelHex, lowestFirstHeld);
89
+ this.#advance(agentId, channelHex, correlationId);
90
+ this.#reportGaps(agentId, channelHex, correlationId);
91
+ }
92
+ /**
93
+ * Verify a post before it is stored, and name the check that failed.
94
+ *
95
+ * The admin check is against the admin THIS SUBSCRIBER knows from its subscription — not against
96
+ * anything the relay said. A relay that swapped a post for one signed by another agent is exactly
97
+ * what this refuses.
98
+ */
99
+ #verify(agentId, channelHex, adminPubkeyHex, cbor, relay, correlationId) {
100
+ const drop = (check, detail) => {
101
+ this.#opts.logger.warn("channel.post.invalid", {
102
+ ...(correlationId !== undefined ? { correlationId } : {}),
103
+ channel_pubkey: channelHex, relay, check, detail,
104
+ });
105
+ return null;
106
+ };
107
+ const decoded = decodeBroadcastArtifact(cbor);
108
+ if (!decoded.ok)
109
+ return drop("decode", decoded.reason);
110
+ const post = decoded.artifact;
111
+ if (Buffer.from(post.channel_pubkey).toString("hex") !== channelHex.toLowerCase()) {
112
+ return drop("channel_pubkey", "the post belongs to a different channel");
113
+ }
114
+ const verdict = verifyBroadcastArtifact(post);
115
+ if (!verdict.ok)
116
+ return drop("signature", verdict.reason);
117
+ const title = validateBroadcastTitle(post.title);
118
+ if (!title.ok)
119
+ return drop("title", title.detail);
120
+ if (Buffer.from(post.agent_pubkey).toString("hex") !== adminPubkeyHex.toLowerCase()) {
121
+ return drop("admin", "the publishing agent is not the admin this subscriber knows for the channel");
122
+ }
123
+ return post;
124
+ }
125
+ /** Advance over the contiguous run, stopping below any gap and below any fork. */
126
+ #advance(agentId, channelHex, correlationId) {
127
+ const { subscriptions, inbox, logger } = this.#opts;
128
+ const sub = subscriptions.get(agentId, channelHex);
129
+ if (!sub)
130
+ return;
131
+ const held = new Set(inbox.heldSeqs(agentId, channelHex));
132
+ const forked = new Set(inbox.forkedSeqs(agentId, channelHex));
133
+ for (const seq of forked) {
134
+ const versions = inbox.forksFor(agentId, channelHex, seq);
135
+ logger.warn("channel.fork.detected", {
136
+ ...(correlationId !== undefined ? { correlationId } : {}),
137
+ channel_pubkey: channelHex,
138
+ seq,
139
+ // Both hashes, so the publisher can be confronted with exactly what it signed.
140
+ hashes: versions.map((v) => Buffer.from(broadcastPostHash(v)).toString("hex")),
141
+ });
142
+ }
143
+ // Start from the first post this subscriber could possibly hold: either the next one it expects,
144
+ // or the relay's floor when everything below has been pruned away.
145
+ const floor = this.#firstHeld.get(channelHex);
146
+ let next = sub.delivered_through + 1;
147
+ if (floor !== undefined && floor > next && !held.has(next))
148
+ next = floor;
149
+ let advanced = sub.delivered_through;
150
+ while (held.has(next) && !forked.has(next)) {
151
+ advanced = next;
152
+ next += 1;
153
+ }
154
+ if (advanced > sub.delivered_through) {
155
+ subscriptions.setDeliveredThrough(agentId, channelHex, advanced);
156
+ }
157
+ }
158
+ /**
159
+ * What is missing between the relays' floor and the highest post seen.
160
+ *
161
+ * ⚠️ **BELOW `first_held_seq` IS NEVER A GAP.** Those posts were pruned or aged out on purpose.
162
+ * Reporting them would have every subscriber demand content that no longer exists, for ever.
163
+ */
164
+ gapsFor(agentId, channelHex) {
165
+ const { subscriptions, inbox } = this.#opts;
166
+ const sub = subscriptions.get(agentId, channelHex);
167
+ const held = inbox.heldSeqs(agentId, channelHex);
168
+ /**
169
+ * ⚠️ **THE RELAY'S REPORTED FLOOR WINS OUTRIGHT, and is never lowered by anything local.**
170
+ *
171
+ * The floor used to be the minimum of the relay's floor, the lowest post held, and
172
+ * `delivered_through + 1`. Take a relay that pruned 1–4 and serves 5, 6, 7, where post 5 fails
173
+ * verification: nothing is held below 6, `delivered_through` stays 0, and the minimum is 1 — so
174
+ * the subscriber demands posts 1 to 5 from a publisher that pruned four of them months ago, and
175
+ * keeps demanding them for ever. That is precisely what "below the floor is never a gap" forbids.
176
+ *
177
+ * Only when NO relay has reported a floor do the local positions stand in for one.
178
+ */
179
+ const reported = this.#firstHeld.get(channelHex);
180
+ const localCandidates = [held[0], (sub?.delivered_through ?? 0) + 1]
181
+ .filter((n) => typeof n === "number" && n > 0);
182
+ const first_held_seq = reported !== undefined && reported > 0
183
+ ? reported
184
+ : (localCandidates.length > 0 ? Math.min(...localCandidates) : 1);
185
+ if (held.length === 0)
186
+ return { missing: [], first_held_seq };
187
+ const highest = held[held.length - 1];
188
+ const have = new Set(held);
189
+ const missing = [];
190
+ for (let seq = first_held_seq; seq < highest; seq++) {
191
+ if (!have.has(seq))
192
+ missing.push(seq);
193
+ }
194
+ return { missing, first_held_seq };
195
+ }
196
+ #reportGaps(agentId, channelHex, correlationId) {
197
+ const { missing, first_held_seq } = this.gapsFor(agentId, channelHex);
198
+ if (missing.length === 0)
199
+ return;
200
+ this.#opts.logger.warn("channel.gap.detected", {
201
+ ...(correlationId !== undefined ? { correlationId } : {}),
202
+ channel_pubkey: channelHex, missing, first_held_seq,
203
+ });
204
+ }
205
+ /**
206
+ * Repair what is still missing: the OTHER relay first, then the publisher.
207
+ *
208
+ * The other relay is asked first because it costs nothing and usually has it — the two queues
209
+ * disagree by design. The publisher is a last resort and is asked ONCE per gap: a gap that
210
+ * survives both is reported, not re-requested every tick, or a permanently pruned post becomes a
211
+ * permanent request loop.
212
+ */
213
+ async repairGaps(agentId, channelHex, correlationId) {
214
+ const sub = this.#opts.subscriptions.get(agentId, channelHex);
215
+ if (!sub || sub.status !== "active")
216
+ return;
217
+ // A fresh pass over both relays is the "ask the other relay" step: whatever either holds now is
218
+ // collected and the gap closes without troubling the publisher.
219
+ await this.collectOnce(agentId, channelHex, correlationId);
220
+ const { missing } = this.gapsFor(agentId, channelHex);
221
+ if (missing.length === 0)
222
+ return;
223
+ const repairKey = ChannelCollector.#repairKey(agentId, channelHex);
224
+ let asked = this.#repairRequested.get(repairKey);
225
+ if (!asked) {
226
+ asked = new Set();
227
+ this.#repairRequested.set(repairKey, asked);
228
+ }
229
+ const fresh = missing.filter((seq) => !asked.has(seq));
230
+ if (fresh.length === 0)
231
+ return;
232
+ for (const seq of fresh)
233
+ asked.add(seq);
234
+ const from = Math.min(...fresh);
235
+ const to = Math.max(...fresh);
236
+ this.#opts.logger.info("channel.repair.requested", {
237
+ ...(correlationId !== undefined ? { correlationId } : {}),
238
+ channel_pubkey: channelHex, from, to,
239
+ });
240
+ await this.#opts.requestRepair(agentId, channelHex, from, to);
241
+ }
242
+ }
243
+ function extract(err) {
244
+ if (err instanceof Error)
245
+ return err.message;
246
+ const m = err?.message;
247
+ return typeof m === "string" ? m : JSON.stringify(err);
248
+ }
249
+ //# sourceMappingURL=channel-collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-collector.js","sourceRoot":"","sources":["../src/channel-collector.ts"],"names":[],"mappings":"AA8BA,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,GAGxB,MAAM,gCAAgC,CAAC;AAIxC,gFAAgF;AAChF,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAoCpC,MAAM,OAAO,gBAAgB;IAClB,KAAK,CAA0B;IAC/B,IAAI,CAAe;IAC5B,gGAAgG;IACvF,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAChD;;;;;;OAMG;IACM,gBAAgB,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE3D,mFAAmF;IACnF,MAAM,CAAC,UAAU,CAAC,OAAe,EAAE,UAAkB;QACnD,OAAO,GAAG,OAAO,IAAI,UAAU,EAAE,CAAC;IACpC,CAAC;IAED,YAAY,IAA6B;QACvC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,UAAkB,EAAE,aAAsB;QAC3E,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QACpD,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QACnD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO;QAE5C,MAAM,KAAK,GAAG,GAAG,CAAC,iBAAiB,GAAG,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,wBAAwB,CAAC,CAAC;QAC3F,MAAM,aAAa,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;QAErE,IAAI,eAAe,GAAkB,IAAI,CAAC;QAC1C,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YAC/B,IAAI,MAA2C,CAAC;YAChD,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE;oBACrC,cAAc,EAAE,aAAa;oBAC7B,SAAS,EAAE,KAAK;oBAChB,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,gBAAgB,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI;oBACzD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACzB,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC9D,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,4FAA4F;gBAC5F,4CAA4C;gBAC5C,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE;oBAClC,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACzD,cAAc,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC;iBACxD,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE;oBAClC,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACzD,cAAc,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM;iBACzD,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,IAAI,MAAM,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;gBACnC,eAAe,GAAG,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;YACxH,CAAC;YAED,IAAI,MAAM,GAAG,CAAC,CAAC;YACf,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC;gBACxG,IAAI,CAAC,IAAI;oBAAE,SAAS;gBACpB,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE;oBACpD,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAClE,SAAS,EAAE,KAAK;oBAChB,WAAW,EAAE,IAAI,CAAC,IAAI,EAAE;iBACzB,CAAC,CAAC;gBACH,IAAI,MAAM,CAAC,MAAM;oBAAE,MAAM,IAAI,CAAC,CAAC;YACjC,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE;gBACrC,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzD,yFAAyF;gBACzF,sFAAsF;gBACtF,2EAA2E;gBAC3E,cAAc,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAE,GAAG,CAAC,iBAAiB;aAClG,CAAC,CAAC;QACL,CAAC;QAED,IAAI,eAAe,KAAK,IAAI;YAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAC/E,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;QAClD,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;IACvD,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CACL,OAAe,EAAE,UAAkB,EAAE,cAAsB,EAAE,IAAgB,EAAE,KAAa,EAAE,aAAsB;QAEpH,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,MAAc,EAAQ,EAAE;YACnD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE;gBAC7C,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzD,cAAc,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM;aACjD,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;QAE9B,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC,WAAW,EAAE,EAAE,CAAC;YAClF,OAAO,IAAI,CAAC,gBAAgB,EAAE,yCAAyC,CAAC,CAAC;QAC3E,CAAC;QACD,MAAM,OAAO,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,cAAc,CAAC,WAAW,EAAE,EAAE,CAAC;YACpF,OAAO,IAAI,CAAC,OAAO,EAAE,6EAA6E,CAAC,CAAC;QACtG,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kFAAkF;IAClF,QAAQ,CAAC,OAAe,EAAE,UAAkB,EAAE,aAAsB;QAClE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QACpD,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QACnD,IAAI,CAAC,GAAG;YAAE,OAAO;QAEjB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;QAC1D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;QAC9D,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE;gBACnC,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzD,cAAc,EAAE,UAAU;gBAC1B,GAAG;gBACH,+EAA+E;gBAC/E,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;aAC/E,CAAC,CAAC;QACL,CAAC;QAED,iGAAiG;QACjG,mEAAmE;QACnE,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,IAAI,GAAG,GAAG,CAAC,iBAAiB,GAAG,CAAC,CAAC;QACrC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,KAAK,CAAC;QAEzE,IAAI,QAAQ,GAAG,GAAG,CAAC,iBAAiB,CAAC;QACrC,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,QAAQ,GAAG,IAAI,CAAC;YAChB,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;QACD,IAAI,QAAQ,GAAG,GAAG,CAAC,iBAAiB,EAAE,CAAC;YACrC,aAAa,CAAC,mBAAmB,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,OAAO,CAAC,OAAe,EAAE,UAAkB;QACzC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAC5C,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QACjD;;;;;;;;;;WAUG;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACjD,MAAM,eAAe,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;aACjE,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9D,MAAM,cAAc,GAAG,QAAQ,KAAK,SAAS,IAAI,QAAQ,GAAG,CAAC;YAC3D,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC;QAE9D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3B,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,IAAI,GAAG,GAAG,cAAc,EAAE,GAAG,GAAG,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC;YACpD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IACrC,CAAC;IAED,WAAW,CAAC,OAAe,EAAE,UAAkB,EAAE,aAAsB;QACrE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QACtE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE;YAC7C,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc;SACpD,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,UAAU,CAAC,OAAe,EAAE,UAAkB,EAAE,aAAsB;QAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QAC9D,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO;QAE5C,gGAAgG;QAChG,gEAAgE;QAChE,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;QAE3D,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QACtD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEjC,MAAM,SAAS,GAAG,gBAAgB,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QACnE,IAAI,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;YAC1B,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC9C,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACvD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAE/B,KAAK,MAAM,GAAG,IAAI,KAAK;YAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;QAChC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE;YACjD,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,cAAc,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE;SACrC,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAChE,CAAC;CACF;AAED,SAAS,OAAO,CAAC,GAAY;IAC3B,IAAI,GAAG,YAAY,KAAK;QAAE,OAAO,GAAG,CAAC,OAAO,CAAC;IAC7C,MAAM,CAAC,GAAI,GAAoC,EAAE,OAAO,CAAC;IACzD,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ChannelConfigStore — what a PUBLISHER has decided about its own channel (M16 018-PUBCOLLECT).
3
+ *
4
+ * The mirror image of `channel-subscription-store.ts`: that one is what a subscriber knows about
5
+ * somebody else's channel, this is what a publisher has chosen for its own — which two relays it
6
+ * publishes to, whether the channel is public, what it is for, how long posts are kept.
7
+ *
8
+ * ⚠️ **THE RELAY PAIR LIVES HERE, NOT IN THE INFO RECORD.** The record is the SIGNED STATEMENT of
9
+ * these facts, deposited for subscribers to read; this is the source it is signed from. Deriving the
10
+ * pair from the last record instead would mean a publisher that lost its record could no longer
11
+ * publish, and a relay serving a stale record could quietly redirect one.
12
+ *
13
+ * Keyed on the channel's own pubkey.
14
+ */
15
+ import type { DaemonDatabase } from "./sqlcipher-db.js";
16
+ import type { Logger } from "./types.js";
17
+ import type { ChannelAccess } from "@cello-protocol/protocol-types";
18
+ /** Seven days, matching the relay's own default retention. */
19
+ export declare const DEFAULT_RETENTION_SECONDS: number;
20
+ export declare const CHANNEL_CONFIG_CREATE_SQL: string;
21
+ export interface ChannelConfig {
22
+ access: ChannelAccess;
23
+ relays: string[];
24
+ guidance: string;
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;
29
+ }
30
+ export declare class ChannelConfigStore {
31
+ #private;
32
+ constructor(db: DaemonDatabase, logger: Logger);
33
+ get(channelHex: string): ChannelConfig | null;
34
+ set(channelHex: string, config: ChannelConfig, now: number): void;
35
+ }
36
+ //# sourceMappingURL=channel-config-store.d.ts.map
@@ -0,0 +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,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"}
@@ -0,0 +1,84 @@
1
+ /** Seven days, matching the relay's own default retention. */
2
+ export const DEFAULT_RETENTION_SECONDS = 7 * 24 * 60 * 60;
3
+ export const CHANNEL_CONFIG_CREATE_SQL = `
4
+ CREATE TABLE IF NOT EXISTS channel_config (
5
+ channel_pubkey TEXT NOT NULL PRIMARY KEY,
6
+ access TEXT NOT NULL,
7
+ -- JSON array of multiaddrs, in the order the publisher chose them. Two is the design; the column
8
+ -- is a list because the count is the publisher's decision, not the schema's.
9
+ relays TEXT NOT NULL,
10
+ guidance TEXT NOT NULL DEFAULT '',
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 '',
27
+ updated_at INTEGER NOT NULL
28
+ );
29
+ `;
30
+ export class ChannelConfigStore {
31
+ #db;
32
+ #logger;
33
+ constructor(db, logger) {
34
+ this.#db = db;
35
+ this.#logger = logger;
36
+ this.#db.exec(CHANNEL_CONFIG_CREATE_SQL);
37
+ }
38
+ get(channelHex) {
39
+ const row = this.#db
40
+ .prepare(`SELECT access, relays, guidance, retention_seconds, members_visible, admin_pubkey
41
+ FROM channel_config WHERE channel_pubkey = ?`)
42
+ .get(channelHex.toLowerCase());
43
+ if (!row)
44
+ return null;
45
+ let relays = [];
46
+ try {
47
+ const parsed = JSON.parse(row.relays);
48
+ if (Array.isArray(parsed))
49
+ relays = parsed.filter((r) => typeof r === "string");
50
+ }
51
+ catch {
52
+ // A corrupt relay list makes the channel unpublishable and says so, rather than publishing to
53
+ // an empty set and reporting success.
54
+ this.#logger.error("channel.config.relays_corrupt", { channel_pubkey: channelHex });
55
+ return null;
56
+ }
57
+ return {
58
+ access: row.access,
59
+ relays,
60
+ guidance: row.guidance,
61
+ retention_seconds: Number(row.retention_seconds),
62
+ members_visible: Number(row.members_visible) === 1,
63
+ admin_pubkey: row.admin_pubkey,
64
+ };
65
+ }
66
+ set(channelHex, config, now) {
67
+ this.#db
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 (?, ?, ?, ?, ?, ?, ?, ?)
74
+ ON CONFLICT (channel_pubkey) DO UPDATE SET
75
+ access = excluded.access, relays = excluded.relays, guidance = excluded.guidance,
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);
79
+ this.#logger.info("channel.config.set", {
80
+ channel_pubkey: channelHex, access: config.access, relays: config.relays.length,
81
+ });
82
+ }
83
+ }
84
+ //# sourceMappingURL=channel-config-store.js.map
@@ -0,0 +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;;;;;;;;;;;;;;;;;;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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * ChannelInboxStore — the posts a SUBSCRIBER has collected and verified (M16 018-PUBCOLLECT).
3
+ *
4
+ * Distinct from `channel-log-store.ts`, which is the PUBLISHER's copy of what it sent. This is the
5
+ * reader's side: what arrived, from which relay, and — when two relays disagree — both versions.
6
+ *
7
+ * ⚠️ **A FORK IS STORED, NOT RESOLVED.** Two different posts at one number means the channel signed
8
+ * both, which is the one thing a channel key cannot take back. Keeping only the first seen would
9
+ * make whichever relay answered first the arbiter of what the channel said, and would destroy the
10
+ * evidence that it said two things. So both rows survive, and the position stops below them.
11
+ *
12
+ * Keyed on `agent_id`, never `agent_name`.
13
+ */
14
+ import type { DaemonDatabase } from "./sqlcipher-db.js";
15
+ import type { Logger } from "./types.js";
16
+ import { type BroadcastArtifact } from "@cello-protocol/protocol-types";
17
+ export declare const CHANNEL_INBOX_CREATE_SQL = "\n CREATE TABLE IF NOT EXISTS channel_inbox (\n agent_id TEXT NOT NULL,\n channel_pubkey TEXT NOT NULL,\n seq INTEGER NOT NULL,\n -- The post's own hash is part of the key, which is what lets a FORK keep both rows rather than\n -- one overwriting the other. A (agent, channel, seq) key would silently drop the evidence.\n post_hash TEXT NOT NULL,\n post_cbor BLOB NOT NULL,\n receipt_cbor BLOB,\n from_relay TEXT NOT NULL,\n collected_at INTEGER NOT NULL,\n PRIMARY KEY (agent_id, channel_pubkey, seq, post_hash)\n );\n";
18
+ export type ChannelInboxErrorCode = "post_invalid" | "inbox_row_corrupt";
19
+ export declare class ChannelInboxError extends Error {
20
+ readonly code: ChannelInboxErrorCode;
21
+ constructor(code: ChannelInboxErrorCode, message: string);
22
+ }
23
+ export declare class ChannelInboxStore {
24
+ #private;
25
+ constructor(db: DaemonDatabase, logger: Logger);
26
+ /**
27
+ * Store one verified post. Idempotent on (agent, channel, seq, hash): the same post arriving from
28
+ * the second relay is the ordinary case, not a conflict.
29
+ */
30
+ store(agentId: string, channelHex: string, post: BroadcastArtifact, opts: {
31
+ receiptCbor?: Uint8Array;
32
+ fromRelay: string;
33
+ collectedAt: number;
34
+ }): {
35
+ stored: boolean;
36
+ };
37
+ /** Posts in [fromSeq, toSeq], seq order. A forked number contributes every version it holds. */
38
+ range(agentId: string, channelHex: string, fromSeq: number, toSeq: number): BroadcastArtifact[];
39
+ /** Every version held at one number. Length > 1 is a fork. */
40
+ forksFor(agentId: string, channelHex: string, seq: number): BroadcastArtifact[];
41
+ /** The post numbers this subscriber holds, ascending and deduplicated. */
42
+ heldSeqs(agentId: string, channelHex: string): number[];
43
+ /** The numbers at which this subscriber holds more than one distinct post. */
44
+ forkedSeqs(agentId: string, channelHex: string): number[];
45
+ }
46
+ //# sourceMappingURL=channel-inbox-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-inbox-store.d.ts","sourceRoot":"","sources":["../src/channel-inbox-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,gCAAgC,CAAC;AAExC,eAAO,MAAM,wBAAwB,ymBAcpC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,cAAc,GAAG,mBAAmB,CAAC;AAEzE,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;gBACzB,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM;CAKzD;AAED,qBAAa,iBAAiB;;gBAIhB,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM;IAM9C;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;QACxE,WAAW,CAAC,EAAE,UAAU,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;KAClE,GAAG;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE;IAgBvB,gGAAgG;IAChG,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAW/F,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAI/E,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAUvD,8EAA8E;IAC9E,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;CAqB1D"}
@@ -0,0 +1,88 @@
1
+ import { broadcastPostHash, decodeBroadcastArtifact, encodeBroadcastArtifact, } from "@cello-protocol/protocol-types";
2
+ export const CHANNEL_INBOX_CREATE_SQL = `
3
+ CREATE TABLE IF NOT EXISTS channel_inbox (
4
+ agent_id TEXT NOT NULL,
5
+ channel_pubkey TEXT NOT NULL,
6
+ seq INTEGER NOT NULL,
7
+ -- The post's own hash is part of the key, which is what lets a FORK keep both rows rather than
8
+ -- one overwriting the other. A (agent, channel, seq) key would silently drop the evidence.
9
+ post_hash TEXT NOT NULL,
10
+ post_cbor BLOB NOT NULL,
11
+ receipt_cbor BLOB,
12
+ from_relay TEXT NOT NULL,
13
+ collected_at INTEGER NOT NULL,
14
+ PRIMARY KEY (agent_id, channel_pubkey, seq, post_hash)
15
+ );
16
+ `;
17
+ export class ChannelInboxError extends Error {
18
+ code;
19
+ constructor(code, message) {
20
+ super(`${code}: ${message}`);
21
+ this.name = "ChannelInboxError";
22
+ this.code = code;
23
+ }
24
+ }
25
+ export class ChannelInboxStore {
26
+ #db;
27
+ #logger;
28
+ constructor(db, logger) {
29
+ this.#db = db;
30
+ this.#logger = logger;
31
+ this.#db.exec(CHANNEL_INBOX_CREATE_SQL);
32
+ }
33
+ /**
34
+ * Store one verified post. Idempotent on (agent, channel, seq, hash): the same post arriving from
35
+ * the second relay is the ordinary case, not a conflict.
36
+ */
37
+ store(agentId, channelHex, post, opts) {
38
+ const cbor = encodeBroadcastArtifact(post);
39
+ const hashHex = Buffer.from(broadcastPostHash(post)).toString("hex");
40
+ const inserted = this.#db
41
+ .prepare(`INSERT OR IGNORE INTO channel_inbox
42
+ (agent_id, channel_pubkey, seq, post_hash, post_cbor, receipt_cbor, from_relay, collected_at)
43
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
44
+ .run(agentId, channelHex.toLowerCase(), post.seq, hashHex, Buffer.from(cbor), opts.receiptCbor ? Buffer.from(opts.receiptCbor) : null, opts.fromRelay, opts.collectedAt);
45
+ return { stored: Number(inserted.changes) > 0 };
46
+ }
47
+ /** Posts in [fromSeq, toSeq], seq order. A forked number contributes every version it holds. */
48
+ range(agentId, channelHex, fromSeq, toSeq) {
49
+ const rows = this.#db
50
+ .prepare(`SELECT seq, post_cbor FROM channel_inbox
51
+ WHERE agent_id = ? AND channel_pubkey = ? AND seq BETWEEN ? AND ?
52
+ ORDER BY seq ASC, post_hash ASC`)
53
+ .all(agentId, channelHex.toLowerCase(), fromSeq, toSeq);
54
+ return rows.map((r) => this.#decode(Number(r.seq), new Uint8Array(r.post_cbor)));
55
+ }
56
+ /** Every version held at one number. Length > 1 is a fork. */
57
+ forksFor(agentId, channelHex, seq) {
58
+ return this.range(agentId, channelHex, seq, seq);
59
+ }
60
+ /** The post numbers this subscriber holds, ascending and deduplicated. */
61
+ heldSeqs(agentId, channelHex) {
62
+ const rows = this.#db
63
+ .prepare(`SELECT DISTINCT seq FROM channel_inbox
64
+ WHERE agent_id = ? AND channel_pubkey = ? ORDER BY seq ASC`)
65
+ .all(agentId, channelHex.toLowerCase());
66
+ return rows.map((r) => Number(r.seq));
67
+ }
68
+ /** The numbers at which this subscriber holds more than one distinct post. */
69
+ forkedSeqs(agentId, channelHex) {
70
+ const rows = this.#db
71
+ .prepare(`SELECT seq FROM channel_inbox
72
+ WHERE agent_id = ? AND channel_pubkey = ?
73
+ GROUP BY seq HAVING COUNT(DISTINCT post_hash) > 1 ORDER BY seq ASC`)
74
+ .all(agentId, channelHex.toLowerCase());
75
+ return rows.map((r) => Number(r.seq));
76
+ }
77
+ #decode(seq, cbor) {
78
+ const decoded = decodeBroadcastArtifact(cbor);
79
+ if (!decoded.ok) {
80
+ // A stored row that no longer decodes is corruption, never something to skip past: the caller
81
+ // is about to show these to an agent.
82
+ this.#logger.error("channel.inbox.row_corrupt", { seq, reason: decoded.reason });
83
+ throw new ChannelInboxError("inbox_row_corrupt", `stored seq ${String(seq)} does not decode: ${decoded.reason}`);
84
+ }
85
+ return decoded.artifact;
86
+ }
87
+ }
88
+ //# sourceMappingURL=channel-inbox-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-inbox-store.js","sourceRoot":"","sources":["../src/channel-inbox-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * ChannelJoinExchange — M16 019-MEMBERSHIP Part B, both sides of joining a channel.
3
+ *
4
+ * A channel never converses. Its ADMIN is an ordinary agent, so a join is a typed exchange inside a
5
+ * normal sealed session with that admin — no new transport, no relay frames, no special case in the
6
+ * session layer.
7
+ *
8
+ * ─── The two identity checks, and why each is load-bearing ───────────────────────────────────
9
+ *
10
+ * **ADMIN side: the session counterparty must BE the subscriber the frame names.** The frame says
11
+ * who is joining and the session says who is talking; if they may differ, anyone can enrol a third
12
+ * party — and, worse, receive that third party's key bundle themselves, because the bundle is
13
+ * wrapped for the pubkey in the frame and sent down the session they are holding.
14
+ *
15
+ * **SUBSCRIBER side: the answering agent must be the admin in the channel's DIRECTORY PROFILE.**
16
+ * The session proves who the counterparty is. It does not prove they are this channel's admin. Drop
17
+ * this check and any agent that can open a session with you hands you a key bundle and a relay pair
18
+ * and becomes your channel: you read their posts believing them to be somebody else's, and the
19
+ * immutable `admin_pubkey` the design rests on protects nobody.
20
+ *
21
+ * ─── What is NOT decided here ────────────────────────────────────────────────────────────────
22
+ *
23
+ * Invite-only admission is the admin AGENT's decision. A request lands as `pending` and raises a
24
+ * notice; nothing in this file approves one. There is no heuristic, no allowlist, no auto-approve.
25
+ */
26
+ import { encodeChannelRekey } from "@cello-protocol/protocol-types";
27
+ import type { KeyProvider } from "@cello-protocol/crypto";
28
+ import type { Logger } from "./types.js";
29
+ import type { ChannelMembershipStore } from "./channel-membership-store.js";
30
+ import type { ChannelSubscriptionStore } from "./channel-subscription-store.js";
31
+ /** What this daemon knows about a channel it administers. `null` means it does not administer one. */
32
+ export interface LocalChannelAdmin {
33
+ agentId: string;
34
+ adminPubkeyHex: string;
35
+ channelKeyProvider: KeyProvider;
36
+ adminKeyProvider: KeyProvider;
37
+ }
38
+ export interface ChannelJoinExchangeDeps {
39
+ logger: Logger;
40
+ members: ChannelMembershipStore;
41
+ subscriptions: ChannelSubscriptionStore;
42
+ /** Put a frame on an open session. The session layer owns delivery; this owns the decisions. */
43
+ sendInSession: (sessionId: string, content: Uint8Array) => Promise<void>;
44
+ localChannelAdmin: (channelHex: string) => LocalChannelAdmin | null;
45
+ /**
46
+ * The channel's admin AS THE DIRECTORY REPORTS IT. `null` means the lookup could not be resolved,
47
+ * which FAILS CLOSED — an unreachable directory must not become "whoever answered is the admin".
48
+ */
49
+ profileAdminPubkey: (channelHex: string, agentId: string) => Promise<string | null>;
50
+ keyProviderFor: (agentId: string) => KeyProvider | null;
51
+ raiseNotice: (event: string, channelHex: string, subscriberHex: string) => void;
52
+ now?: () => number;
53
+ }
54
+ export type SubscriberJoinResult = {
55
+ ok: true;
56
+ channelHex: string;
57
+ generation: number;
58
+ } | {
59
+ ok: false;
60
+ reason: "not_a_join_frame" | "malformed" | "not_admin_of_channel" | "admin_unresolved" | "key_unwrap_failed" | "no_key_provider";
61
+ };
62
+ export interface ChannelJoinExchange {
63
+ /** An inbound frame on the ADMIN's daemon. `consumed: false` means it was not a join frame. */
64
+ onAdminFrame: (sessionId: string, counterpartyHex: string, content: Uint8Array) => Promise<{
65
+ consumed: boolean;
66
+ }>;
67
+ /** An inbound acceptance or re-key on the SUBSCRIBER's daemon. */
68
+ onSubscriberFrame: (agentId: string, sessionId: string, counterpartyHex: string, content: Uint8Array) => Promise<SubscriberJoinResult>;
69
+ /** The admin agent's explicit decision on a pending invite-only request. */
70
+ approve: (channelHex: string, subscriberHex: string, sessionId: string) => Promise<{
71
+ ok: true;
72
+ } | {
73
+ ok: false;
74
+ reason: string;
75
+ }>;
76
+ /** Refuse a pending request by name. */
77
+ refuse: (channelHex: string, subscriberHex: string, sessionId: string) => Promise<{
78
+ ok: true;
79
+ } | {
80
+ ok: false;
81
+ reason: string;
82
+ }>;
83
+ }
84
+ export declare function createChannelJoinExchange(deps: ChannelJoinExchangeDeps): ChannelJoinExchange;
85
+ /** Re-exported so the caller does not need a second import to build a re-key frame. */
86
+ export { encodeChannelRekey };
87
+ //# sourceMappingURL=channel-join-exchange.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-join-exchange.d.ts","sourceRoot":"","sources":["../src/channel-join-exchange.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAEgD,kBAAkB,EAGxE,MAAM,gCAAgC,CAAC;AAIxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAGhF,sGAAsG;AACtG,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,WAAW,CAAC;IAChC,gBAAgB,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,sBAAsB,CAAC;IAChC,aAAa,EAAE,wBAAwB,CAAC;IACxC,gGAAgG;IAChG,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,iBAAiB,GAAG,IAAI,CAAC;IACpE;;;OAGG;IACH,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACpF,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,WAAW,GAAG,IAAI,CAAC;IACxD,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAChF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,GAAG,sBAAsB,GAAG,kBAAkB,GAAG,mBAAmB,GAAG,iBAAiB,CAAA;CAAE,CAAC;AAEpJ,MAAM,WAAW,mBAAmB;IAClC,+FAA+F;IAC/F,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAClH,kEAAkE;IAClE,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACvI,4EAA4E;IAC5E,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjI,wCAAwC;IACxC,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjI;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,uBAAuB,GAAG,mBAAmB,CAgR5F;AAED,uFAAuF;AACvF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}