@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.
- package/dist/channel-admin-lookup.d.ts +54 -0
- package/dist/channel-admin-lookup.d.ts.map +1 -0
- package/dist/channel-admin-lookup.js +122 -0
- package/dist/channel-admin-lookup.js.map +1 -0
- package/dist/channel-collect-tick.d.ts +36 -0
- package/dist/channel-collect-tick.d.ts.map +1 -0
- package/dist/channel-collect-tick.js +133 -0
- package/dist/channel-collect-tick.js.map +1 -0
- package/dist/channel-collector.d.ts +99 -0
- package/dist/channel-collector.d.ts.map +1 -0
- package/dist/channel-collector.js +249 -0
- package/dist/channel-collector.js.map +1 -0
- package/dist/channel-config-store.d.ts +36 -0
- package/dist/channel-config-store.d.ts.map +1 -0
- package/dist/channel-config-store.js +84 -0
- package/dist/channel-config-store.js.map +1 -0
- package/dist/channel-inbox-store.d.ts +46 -0
- package/dist/channel-inbox-store.d.ts.map +1 -0
- package/dist/channel-inbox-store.js +88 -0
- package/dist/channel-inbox-store.js.map +1 -0
- package/dist/channel-join-exchange.d.ts +87 -0
- package/dist/channel-join-exchange.d.ts.map +1 -0
- package/dist/channel-join-exchange.js +289 -0
- package/dist/channel-join-exchange.js.map +1 -0
- package/dist/channel-log-store.d.ts +14 -0
- package/dist/channel-log-store.d.ts.map +1 -1
- package/dist/channel-log-store.js +58 -0
- package/dist/channel-log-store.js.map +1 -1
- package/dist/channel-membership-store.d.ts +118 -0
- package/dist/channel-membership-store.d.ts.map +1 -0
- package/dist/channel-membership-store.js +226 -0
- package/dist/channel-membership-store.js.map +1 -0
- package/dist/channel-membership-wiring.d.ts +90 -0
- package/dist/channel-membership-wiring.d.ts.map +1 -0
- package/dist/channel-membership-wiring.js +387 -0
- package/dist/channel-membership-wiring.js.map +1 -0
- package/dist/channel-publish-handlers.d.ts +42 -0
- package/dist/channel-publish-handlers.d.ts.map +1 -0
- package/dist/channel-publish-handlers.js +212 -0
- package/dist/channel-publish-handlers.js.map +1 -0
- package/dist/channel-publish-wiring.d.ts +52 -0
- package/dist/channel-publish-wiring.d.ts.map +1 -0
- package/dist/channel-publish-wiring.js +143 -0
- package/dist/channel-publish-wiring.js.map +1 -0
- package/dist/channel-publisher.d.ts +221 -0
- package/dist/channel-publisher.d.ts.map +1 -0
- package/dist/channel-publisher.js +430 -0
- package/dist/channel-publisher.js.map +1 -0
- package/dist/channel-relay-client.d.ts +88 -0
- package/dist/channel-relay-client.d.ts.map +1 -0
- package/dist/channel-relay-client.js +180 -0
- package/dist/channel-relay-client.js.map +1 -0
- package/dist/channel-subscription-store.d.ts +137 -0
- package/dist/channel-subscription-store.d.ts.map +1 -0
- package/dist/channel-subscription-store.js +250 -0
- package/dist/channel-subscription-store.js.map +1 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +54 -0
- package/dist/daemon.js.map +1 -1
- package/dist/inbound-sessions.d.ts +6 -0
- package/dist/inbound-sessions.d.ts.map +1 -1
- package/dist/inbound-sessions.js +27 -1
- package/dist/inbound-sessions.js.map +1 -1
- package/dist/refusal-reasons.d.ts +7 -0
- package/dist/refusal-reasons.d.ts.map +1 -1
- package/dist/refusal-reasons.js +15 -0
- package/dist/refusal-reasons.js.map +1 -1
- package/dist/session-content-context.d.ts +8 -0
- package/dist/session-content-context.d.ts.map +1 -1
- package/dist/session-content-ingest.d.ts.map +1 -1
- package/dist/session-content-ingest.js +28 -0
- package/dist/session-content-ingest.js.map +1 -1
- package/dist/session-node-manager.d.ts +4 -0
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +11 -0
- package/dist/session-node-manager.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { channelJoinFrameType, encodeChannelRekey, buildChannelFetchKeyTbs, JOIN_REQUEST_TYPE, } from "@cello-protocol/protocol-types";
|
|
2
|
+
import { generateGroupKey, wrapGroupKeyFor, deriveFetchKey } from "@cello-protocol/crypto";
|
|
3
|
+
import { ChannelMembershipStore } from "./channel-membership-store.js";
|
|
4
|
+
import { ChannelSubscriptionStore } from "./channel-subscription-store.js";
|
|
5
|
+
import { createChannelJoinExchange } from "./channel-join-exchange.js";
|
|
6
|
+
import { createChannelAdminLookup, } from "./channel-admin-lookup.js";
|
|
7
|
+
import { extractErrorMessage } from "./error-message.js";
|
|
8
|
+
/**
|
|
9
|
+
* M16 020-CHANADMIN — where the subscriber's admin key comes from.
|
|
10
|
+
*
|
|
11
|
+
* The join exchange compares the agent that answered against this. It is deliberately the ONLY
|
|
12
|
+
* source: the session proves who the counterparty is and says nothing about their authority over a
|
|
13
|
+
* channel, so "whoever answered" is exactly the hole the check exists to close.
|
|
14
|
+
*
|
|
15
|
+
* Two sources, in order:
|
|
16
|
+
* 1. **This daemon's own settings**, when we publish the channel. No round trip, and no directory
|
|
17
|
+
* outage in the path of a join to a channel running on this very machine.
|
|
18
|
+
* 2. **The directory**, for everything else. Before 020 there was no step 2 and this returned null
|
|
19
|
+
* for every channel not published here — so nobody could join anybody else's channel, or their
|
|
20
|
+
* own from a second device.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ **`null` MEANS REFUSE, AND EVERY UNKNOWN STILL ENDS HERE.** A directory that cannot be reached,
|
|
23
|
+
* one that faults, a pubkey that is not a channel, and a lookup that throws all answer `null`, which
|
|
24
|
+
* the exchange refuses as `admin_unresolved`. 020 removes a refusal that was firing on every
|
|
25
|
+
* channel; it must not weaken the one that remains.
|
|
26
|
+
*/
|
|
27
|
+
export function createProfileAdminPubkey(deps) {
|
|
28
|
+
return async function profileAdminPubkey(channelHex, agentId) {
|
|
29
|
+
// The ADMIN this daemon recorded, not the channel's own key — the two are different agents, and
|
|
30
|
+
// comparing a key with itself is what the first version of this did.
|
|
31
|
+
const settings = deps.members.settings(channelHex);
|
|
32
|
+
if (settings && settings.admin_pubkey.length > 0)
|
|
33
|
+
return settings.admin_pubkey;
|
|
34
|
+
try {
|
|
35
|
+
const outcome = await deps.lookup(agentId, channelHex);
|
|
36
|
+
if (outcome.kind === "admin")
|
|
37
|
+
return outcome.adminPubkeyHex;
|
|
38
|
+
deps.logger.info("channel.join.admin_unresolved", {
|
|
39
|
+
channel_pubkey: channelHex,
|
|
40
|
+
reason: outcome.kind === "not_a_channel" ? "not_a_channel" : outcome.reason,
|
|
41
|
+
});
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
// This runs inside the inbound content path. An exception escaping would surface as a broken
|
|
46
|
+
// session rather than a refused join, which is a worse answer to the same question.
|
|
47
|
+
deps.logger.warn("channel.join.admin_unresolved", {
|
|
48
|
+
channel_pubkey: channelHex, reason: extractErrorMessage(err),
|
|
49
|
+
});
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function needAgent(deps, params, connectionId) {
|
|
55
|
+
const agentName = deps.resolveCurrentAgent(connectionId, params?.["agent"]);
|
|
56
|
+
if (agentName === null) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
answer: { ok: false, reason: "no_current_agent", guidance: "Name the agent, or select one with cello_use_agent." },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return { ok: true, agentName };
|
|
63
|
+
}
|
|
64
|
+
function needChannel(params) {
|
|
65
|
+
const raw = params?.["channel"];
|
|
66
|
+
if (typeof raw !== "string" || !/^[0-9a-fA-F]{64}$/.test(raw)) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
answer: { ok: false, reason: "bad_channel", guidance: "Pass the channel's 64-character hex public key as `channel`." },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { ok: true, channelHex: raw.toLowerCase() };
|
|
73
|
+
}
|
|
74
|
+
export function wireChannelMembership(deps) {
|
|
75
|
+
const { handlers, logger } = deps;
|
|
76
|
+
const members = new ChannelMembershipStore(deps.getDb(), logger);
|
|
77
|
+
const subscriptions = new ChannelSubscriptionStore(deps.getDb(), logger);
|
|
78
|
+
/**
|
|
79
|
+
* ⚠️ A CHANNEL IS AN AGENT THIS DAEMON HOLDS, looked up BY PUBKEY — the same rule the publisher
|
|
80
|
+
* follows, because the pubkey is the identity and the name is a mutable label. In this release the
|
|
81
|
+
* publisher and the admin are the SAME daemon; that is ASSERTED by returning null when the key is
|
|
82
|
+
* not here, rather than assumed anywhere downstream.
|
|
83
|
+
*/
|
|
84
|
+
const localChannelAdmin = (channelHex) => {
|
|
85
|
+
const channel = deps.loadedAgents.find((a) => a.pubkey.toLowerCase() === channelHex.toLowerCase());
|
|
86
|
+
if (!channel)
|
|
87
|
+
return null;
|
|
88
|
+
/**
|
|
89
|
+
* ⚠️ **THE ADMIN IS A DIFFERENT AGENT FROM THE CHANNEL, and reading the channel's own key here
|
|
90
|
+
* was a defect.** The channel signs posts and never converses; the ADMIN holds the sessions and
|
|
91
|
+
* answers join requests. A subscriber compares the agent that answered against the admin key, so
|
|
92
|
+
* returning the channel's key made that comparison a key against itself — it could never
|
|
93
|
+
* succeed, in either direction, and the check was safe only by never passing.
|
|
94
|
+
*
|
|
95
|
+
* The admin is whichever agent ran the channel's setup, recorded then. No row, or an agent this
|
|
96
|
+
* daemon no longer holds, means this daemon cannot answer for the channel — which is the truth.
|
|
97
|
+
*/
|
|
98
|
+
const settings = members.settings(channelHex);
|
|
99
|
+
if (!settings || settings.admin_pubkey.length === 0)
|
|
100
|
+
return null;
|
|
101
|
+
const adminAgent = deps.loadedAgents.find((a) => a.pubkey.toLowerCase() === settings.admin_pubkey.toLowerCase());
|
|
102
|
+
if (!adminAgent)
|
|
103
|
+
return null;
|
|
104
|
+
const adminKp = deps.keyProviders.get(adminAgent.name);
|
|
105
|
+
if (!adminKp)
|
|
106
|
+
return null;
|
|
107
|
+
return {
|
|
108
|
+
agentId: deps.resolveAgentId(adminAgent.name),
|
|
109
|
+
adminPubkeyHex: adminAgent.pubkey,
|
|
110
|
+
channelKeyProvider: channel.keyProvider,
|
|
111
|
+
adminKeyProvider: adminKp,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* The join path carries an agent ID; `signalingFor` is keyed by the daemon's agent NAME. Resolved
|
|
116
|
+
* the same way `keyProviderFor` does it just below — by walking the loaded agents, because the ID
|
|
117
|
+
* is the stable key and the name is a mutable label.
|
|
118
|
+
*/
|
|
119
|
+
const signalingForAgentId = (agentId) => {
|
|
120
|
+
const agent = deps.loadedAgents.find((a) => deps.resolveAgentId(a.name) === agentId);
|
|
121
|
+
return agent ? deps.signalingFor(agent.name) : null;
|
|
122
|
+
};
|
|
123
|
+
const profileAdminPubkey = createProfileAdminPubkey({
|
|
124
|
+
members,
|
|
125
|
+
logger,
|
|
126
|
+
lookup: createChannelAdminLookup({ signalingFor: signalingForAgentId, logger }),
|
|
127
|
+
});
|
|
128
|
+
const keyProviderFor = (agentId) => {
|
|
129
|
+
const agent = deps.loadedAgents.find((a) => deps.resolveAgentId(a.name) === agentId);
|
|
130
|
+
return agent ? (deps.keyProviders.get(agent.name) ?? null) : null;
|
|
131
|
+
};
|
|
132
|
+
const openSessionWith = (agentName, memberPubkeyHex) => {
|
|
133
|
+
const open = deps.activeSessionsFor(agentName)
|
|
134
|
+
.find((s) => s.counterpartyPubkeyHex.toLowerCase() === memberPubkeyHex.toLowerCase());
|
|
135
|
+
return open ? open.sessionId : null;
|
|
136
|
+
};
|
|
137
|
+
const raiseNotice = (event, channelHex, subscriberHex) => {
|
|
138
|
+
logger.info(event, { channel_pubkey: channelHex, subscriber_pubkey: subscriberHex });
|
|
139
|
+
};
|
|
140
|
+
/** Which agent a session belongs to, so the hook's answers go back down the right one. */
|
|
141
|
+
const exchangeFor = (agentName) => createChannelJoinExchange({
|
|
142
|
+
logger,
|
|
143
|
+
members,
|
|
144
|
+
subscriptions,
|
|
145
|
+
sendInSession: (sessionId, content) => deps.sendInSession(agentName, sessionId, content),
|
|
146
|
+
localChannelAdmin,
|
|
147
|
+
profileAdminPubkey,
|
|
148
|
+
keyProviderFor,
|
|
149
|
+
raiseNotice,
|
|
150
|
+
});
|
|
151
|
+
/**
|
|
152
|
+
* ⚠️ CLASSIFY SYNCHRONOUSLY, HANDLE ASYNCHRONOUSLY. The ingest path needs an immediate answer to
|
|
153
|
+
* decide whether these bytes are conversation; the admin and subscriber work — key wrapping,
|
|
154
|
+
* a directory lookup — cannot be done in that window.
|
|
155
|
+
*/
|
|
156
|
+
deps.setOnChannelJoinFrame((agentName, sessionId, content, senderPubkey, correlationId) => {
|
|
157
|
+
/**
|
|
158
|
+
* ⚠️ **ROUTED BY FRAME TYPE, ONCE — trying the admin half first and falling through was a bug
|
|
159
|
+
* that made joining impossible.** `onAdminFrame` answers `consumed: true` for any join frame it
|
|
160
|
+
* cannot read as a REQUEST, so an acceptance or a re-key arriving at a SUBSCRIBER was absorbed
|
|
161
|
+
* there and the subscriber half was never called. A member sent a request, the admin admitted
|
|
162
|
+
* them and replied with the key, and their daemon appended a leaf and discarded it: no
|
|
163
|
+
* subscription, no key, and not one line saying anything had gone wrong. Every re-key after an
|
|
164
|
+
* ejection went the same way. The unit tests missed it because they call the two halves by hand.
|
|
165
|
+
*/
|
|
166
|
+
const kind = channelJoinFrameType(content);
|
|
167
|
+
if (kind === null)
|
|
168
|
+
return { consumed: false };
|
|
169
|
+
const exchange = exchangeFor(agentName);
|
|
170
|
+
const agentId = deps.resolveAgentId(agentName);
|
|
171
|
+
void (async () => {
|
|
172
|
+
if (kind === JOIN_REQUEST_TYPE) {
|
|
173
|
+
await exchange.onAdminFrame(sessionId, senderPubkey, content);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// An acceptance, a refusal or a re-key: all answers TO us, all the subscriber's business.
|
|
177
|
+
const asSubscriber = await exchange.onSubscriberFrame(agentId, sessionId, senderPubkey, content);
|
|
178
|
+
if (!asSubscriber.ok) {
|
|
179
|
+
logger.warn("channel.join.refused", {
|
|
180
|
+
...(correlationId !== undefined ? { correlationId } : {}),
|
|
181
|
+
reason: asSubscriber.reason, sender: senderPubkey, frame_type: kind,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
})().catch((err) => {
|
|
185
|
+
// A rejected promise here must not take down the content path for every other session.
|
|
186
|
+
logger.error("channel.join.handling_failed", {
|
|
187
|
+
...(correlationId !== undefined ? { correlationId } : {}),
|
|
188
|
+
reason: extractErrorMessage(err),
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
return { consumed: true };
|
|
192
|
+
});
|
|
193
|
+
// ─── Operator verbs ───────────────────────────────────────────────────────────────────────────
|
|
194
|
+
handlers.set("cello_channels", async (params, connectionId) => {
|
|
195
|
+
const agent = needAgent(deps, params, connectionId);
|
|
196
|
+
if (!agent.ok)
|
|
197
|
+
return agent.answer;
|
|
198
|
+
const agentId = deps.resolveAgentId(agent.agentName);
|
|
199
|
+
const rows = subscriptions.active().filter((s) => s.agent_id === agentId);
|
|
200
|
+
return Promise.resolve({
|
|
201
|
+
ok: true,
|
|
202
|
+
channels: rows.map((s) => ({
|
|
203
|
+
channel: s.channel_pubkey,
|
|
204
|
+
moniker: s.moniker,
|
|
205
|
+
access: s.access,
|
|
206
|
+
delivered_through: s.delivered_through,
|
|
207
|
+
processed_through: s.processed_through,
|
|
208
|
+
// What the operator actually wants to know: how much is waiting.
|
|
209
|
+
unread: Math.max(0, s.delivered_through - s.processed_through),
|
|
210
|
+
})),
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
handlers.set("cello_channel_set_moniker", async (params, connectionId) => {
|
|
214
|
+
const agent = needAgent(deps, params, connectionId);
|
|
215
|
+
if (!agent.ok)
|
|
216
|
+
return agent.answer;
|
|
217
|
+
const channel = needChannel(params);
|
|
218
|
+
if (!channel.ok)
|
|
219
|
+
return channel.answer;
|
|
220
|
+
const moniker = params?.["moniker"];
|
|
221
|
+
if (typeof moniker !== "string" || moniker.length === 0) {
|
|
222
|
+
return { ok: false, reason: "bad_moniker", guidance: "Pass a `moniker`: what you want to call this channel." };
|
|
223
|
+
}
|
|
224
|
+
subscriptions.setMoniker(deps.resolveAgentId(agent.agentName), channel.channelHex, moniker);
|
|
225
|
+
return Promise.resolve({ ok: true, channel: channel.channelHex, moniker });
|
|
226
|
+
});
|
|
227
|
+
handlers.set("cello_channel_leave", async (params, connectionId) => {
|
|
228
|
+
const agent = needAgent(deps, params, connectionId);
|
|
229
|
+
if (!agent.ok)
|
|
230
|
+
return agent.answer;
|
|
231
|
+
const channel = needChannel(params);
|
|
232
|
+
if (!channel.ok)
|
|
233
|
+
return channel.answer;
|
|
234
|
+
try {
|
|
235
|
+
subscriptions.markLeft(deps.resolveAgentId(agent.agentName), channel.channelHex);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
return { ok: false, reason: extractErrorMessage(err) };
|
|
239
|
+
}
|
|
240
|
+
return Promise.resolve({
|
|
241
|
+
ok: true,
|
|
242
|
+
channel: channel.channelHex,
|
|
243
|
+
// ⚠️ LEAVING IS LOCAL, and saying so matters: an operator who thinks the publisher was told
|
|
244
|
+
// may expect to be removed from a member list they are still on.
|
|
245
|
+
guidance: "You have stopped collecting this channel. Nothing was sent — the publisher does not know, and your keys are kept so old posts stay readable.",
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
/**
|
|
249
|
+
* Eject a member and re-key the channel.
|
|
250
|
+
*
|
|
251
|
+
* ⚠️ **THE GENERATION BUMP AND THE STATUS FLIP ARE ONE TRANSACTION** (in the store). What happens
|
|
252
|
+
* HERE is the delivery, and a delivery that fails does NOT undo the ejection: the member is out at
|
|
253
|
+
* the relay the moment the next deposit carries the new fetch key, whether or not every remaining
|
|
254
|
+
* member has collected their new key yet. A member left behind hits `unknown_generation` and asks.
|
|
255
|
+
*/
|
|
256
|
+
handlers.set("cello_channel_eject", async (params, connectionId) => {
|
|
257
|
+
const agent = needAgent(deps, params, connectionId);
|
|
258
|
+
if (!agent.ok)
|
|
259
|
+
return agent.answer;
|
|
260
|
+
const channel = needChannel(params);
|
|
261
|
+
if (!channel.ok)
|
|
262
|
+
return channel.answer;
|
|
263
|
+
const subscriber = params?.["subscriber"];
|
|
264
|
+
if (typeof subscriber !== "string" || !/^[0-9a-fA-F]{64}$/.test(subscriber)) {
|
|
265
|
+
return { ok: false, reason: "bad_subscriber", guidance: "Pass the member's 64-character hex public key as `subscriber`." };
|
|
266
|
+
}
|
|
267
|
+
const admin = localChannelAdmin(channel.channelHex);
|
|
268
|
+
if (!admin) {
|
|
269
|
+
return {
|
|
270
|
+
ok: false, reason: "channel_not_local",
|
|
271
|
+
guidance: "This daemon does not hold that channel's key, and ejecting is the admin's own act. Cross-daemon admin is not in this release.",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
let outcome;
|
|
275
|
+
try {
|
|
276
|
+
outcome = members.eject(channel.channelHex, subscriber.toLowerCase());
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
return { ok: false, reason: extractErrorMessage(err) };
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The new key, wrapped once per REMAINING member — the ejected one is simply not in this list.
|
|
283
|
+
*
|
|
284
|
+
* ⚠️ STORED FIRST, against the admin's own agent id. If the process died between minting and
|
|
285
|
+
* storing, the channel's settings would say generation N while no key for N existed anywhere:
|
|
286
|
+
* every subsequent publish would encrypt under a key no member was ever given, and the channel
|
|
287
|
+
* would go permanently silent for everyone rather than for the one person ejected.
|
|
288
|
+
*/
|
|
289
|
+
const gk = generateGroupKey(outcome.generation);
|
|
290
|
+
subscriptions.addKey(admin.agentId, channel.channelHex, gk, Date.now());
|
|
291
|
+
const channelPubkey = await admin.channelKeyProvider.getPublicKey();
|
|
292
|
+
let delivered = 0;
|
|
293
|
+
const unreached = [];
|
|
294
|
+
for (const member of outcome.remaining) {
|
|
295
|
+
const sessionId = openSessionWith(agent.agentName, member);
|
|
296
|
+
if (sessionId === null) {
|
|
297
|
+
// Not a failure of the ejection. Recorded by name so an operator can see who is behind.
|
|
298
|
+
logger.warn("channel.rekey.member_unreached", {
|
|
299
|
+
channel_pubkey: channel.channelHex, member_pubkey: member, generation: outcome.generation,
|
|
300
|
+
});
|
|
301
|
+
unreached.push(member);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const bundle = await wrapGroupKeyFor(gk, channelPubkey, new Uint8Array(Buffer.from(member, "hex")), admin.adminKeyProvider);
|
|
305
|
+
await deps.sendInSession(agent.agentName, sessionId, encodeChannelRekey({
|
|
306
|
+
channel_pubkey: channelPubkey, key_bundle: bundle, generation: outcome.generation,
|
|
307
|
+
}));
|
|
308
|
+
delivered += 1;
|
|
309
|
+
}
|
|
310
|
+
logger.info("channel.rekey.completed", {
|
|
311
|
+
channel_pubkey: channel.channelHex,
|
|
312
|
+
generation: outcome.generation,
|
|
313
|
+
member_count: outcome.remaining.length,
|
|
314
|
+
delivered_count: delivered,
|
|
315
|
+
failed_count: unreached.length,
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
ok: true,
|
|
319
|
+
channel: channel.channelHex,
|
|
320
|
+
generation: outcome.generation,
|
|
321
|
+
delivered,
|
|
322
|
+
unreached,
|
|
323
|
+
guidance: unreached.length > 0
|
|
324
|
+
? `${String(unreached.length)} member(s) were not reachable and still hold the old key. They will ask for the new one when they next read; the ejection itself is done.`
|
|
325
|
+
: undefined,
|
|
326
|
+
};
|
|
327
|
+
});
|
|
328
|
+
handlers.set("cello_channel_approve", async (params, connectionId) => {
|
|
329
|
+
const agent = needAgent(deps, params, connectionId);
|
|
330
|
+
if (!agent.ok)
|
|
331
|
+
return agent.answer;
|
|
332
|
+
const channel = needChannel(params);
|
|
333
|
+
if (!channel.ok)
|
|
334
|
+
return channel.answer;
|
|
335
|
+
const subscriber = params?.["subscriber"];
|
|
336
|
+
if (typeof subscriber !== "string" || !/^[0-9a-fA-F]{64}$/.test(subscriber)) {
|
|
337
|
+
return { ok: false, reason: "bad_subscriber" };
|
|
338
|
+
}
|
|
339
|
+
const sessionId = openSessionWith(agent.agentName, subscriber.toLowerCase());
|
|
340
|
+
if (sessionId === null) {
|
|
341
|
+
// The approval is recorded either way: the next request from an approved member is accepted
|
|
342
|
+
// immediately, so an admin approving somebody who has gone offline is not wasted work.
|
|
343
|
+
return { ok: false, reason: "no_open_session", guidance: "They are not currently reachable. Approve again when they next ask, or the approval applies to their next request." };
|
|
344
|
+
}
|
|
345
|
+
const result = await exchangeFor(agent.agentName).approve(channel.channelHex, subscriber.toLowerCase(), sessionId);
|
|
346
|
+
return result.ok ? { ok: true, channel: channel.channelHex } : { ok: false, reason: result.reason };
|
|
347
|
+
});
|
|
348
|
+
handlers.set("cello_channel_refuse", async (params, connectionId) => {
|
|
349
|
+
const agent = needAgent(deps, params, connectionId);
|
|
350
|
+
if (!agent.ok)
|
|
351
|
+
return agent.answer;
|
|
352
|
+
const channel = needChannel(params);
|
|
353
|
+
if (!channel.ok)
|
|
354
|
+
return channel.answer;
|
|
355
|
+
const subscriber = params?.["subscriber"];
|
|
356
|
+
if (typeof subscriber !== "string" || !/^[0-9a-fA-F]{64}$/.test(subscriber)) {
|
|
357
|
+
return { ok: false, reason: "bad_subscriber" };
|
|
358
|
+
}
|
|
359
|
+
const sessionId = openSessionWith(agent.agentName, subscriber.toLowerCase());
|
|
360
|
+
if (sessionId === null)
|
|
361
|
+
return { ok: false, reason: "no_open_session" };
|
|
362
|
+
const result = await exchangeFor(agent.agentName).refuse(channel.channelHex, subscriber.toLowerCase(), sessionId);
|
|
363
|
+
return result.ok ? { ok: true, channel: channel.channelHex } : { ok: false, reason: result.reason };
|
|
364
|
+
});
|
|
365
|
+
return {
|
|
366
|
+
currentFetchKey: async (channelHex) => {
|
|
367
|
+
const admin = localChannelAdmin(channelHex);
|
|
368
|
+
if (!admin)
|
|
369
|
+
return undefined;
|
|
370
|
+
// The NEWEST generation this daemon holds for its own channel. `keysFor` returns them newest
|
|
371
|
+
// first, so a re-key is picked up by the next publish without anything else being told.
|
|
372
|
+
const newest = subscriptions.keysFor(admin.agentId, channelHex)[0];
|
|
373
|
+
if (!newest)
|
|
374
|
+
return undefined;
|
|
375
|
+
const fetchKey = await deriveFetchKey(newest, new Uint8Array(Buffer.from(channelHex, "hex")));
|
|
376
|
+
const timeMs = Date.now();
|
|
377
|
+
/**
|
|
378
|
+
* ⚠️ SIGNED WITH THE CHANNEL KEY, and it must be: the post's signature does not cover the
|
|
379
|
+
* fetch key, so without one of its own anyone who could read a post could replay its bytes
|
|
380
|
+
* inside the clock window, attach their own key and take the channel over.
|
|
381
|
+
*/
|
|
382
|
+
const signature = await admin.channelKeyProvider.sign(buildChannelFetchKeyTbs(new Uint8Array(Buffer.from(channelHex, "hex")), fetchKey.publicKey, timeMs));
|
|
383
|
+
return { pubkey: fetchKey.publicKey, time_ms: timeMs, signature };
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=channel-membership-wiring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-membership-wiring.js","sourceRoot":"","sources":["../src/channel-membership-wiring.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M16 018-PUBCOLLECT — the operator's IPC verbs for publishing on a channel.
|
|
3
|
+
*
|
|
4
|
+
* cello_channel_publish publish a post to the channel's relays
|
|
5
|
+
* cello_channel_info_set sign and deposit the channel's info record
|
|
6
|
+
* cello_channel_prune drop everything through a post number, oldest end only
|
|
7
|
+
* cello_channel_resend refill a relay that lost content, or fill a newly added one
|
|
8
|
+
*
|
|
9
|
+
* **IPC only — no MCP tools.** These are the publisher's own administration of a channel it holds
|
|
10
|
+
* the key to, and the caller is a human at a terminal, not an agent mid-conversation. The MCP
|
|
11
|
+
* surface for channels is a later tier; adding one here would put a publishing verb in reach of
|
|
12
|
+
* anything that can drive an agent's tools.
|
|
13
|
+
*
|
|
14
|
+
* Every verb answers `{ ok: false, reason, guidance }` rather than throwing, because the far side is
|
|
15
|
+
* a CLI that has to print something a person can act on.
|
|
16
|
+
*/
|
|
17
|
+
import type { Logger } from "./types.js";
|
|
18
|
+
import type { ChannelPublisher } from "./channel-publisher.js";
|
|
19
|
+
import { type ChannelConfig } from "./channel-config-store.js";
|
|
20
|
+
type Handler = (params: Record<string, unknown> | undefined, connectionId: string) => Promise<unknown>;
|
|
21
|
+
export interface ChannelPublishDeps {
|
|
22
|
+
handlers: Map<string, Handler>;
|
|
23
|
+
logger: Logger;
|
|
24
|
+
getPublisher: (agentName: string) => ChannelPublisher | null;
|
|
25
|
+
/**
|
|
26
|
+
* Record the publisher's decisions for a channel it holds the key to. REFUSES when this daemon
|
|
27
|
+
* does not hold that channel's key — otherwise an operator could record relays for somebody
|
|
28
|
+
* else's channel and every later verb would fail with a key error instead of the real reason.
|
|
29
|
+
*/
|
|
30
|
+
setChannelConfig: (agentName: string, channelHex: string, config: ChannelConfig) => {
|
|
31
|
+
ok: true;
|
|
32
|
+
} | {
|
|
33
|
+
ok: false;
|
|
34
|
+
reason: string;
|
|
35
|
+
guidance?: string;
|
|
36
|
+
};
|
|
37
|
+
/** The daemon's single agent-selection rule, injected rather than re-implemented here. */
|
|
38
|
+
resolveCurrentAgent: (connectionId: string, explicitAgent?: string) => string | null;
|
|
39
|
+
}
|
|
40
|
+
export declare function registerChannelPublishHandlers(deps: ChannelPublishDeps): void;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=channel-publish-handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-publish-handlers.d.ts","sourceRoot":"","sources":["../src/channel-publish-handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1F,KAAK,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEvG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,gBAAgB,GAAG,IAAI,CAAC;IAC7D;;;;OAIG;IACH,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,KAC7E;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,0FAA0F;IAC1F,mBAAmB,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACtF;AAkCD,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAuL7E"}
|