@kumiai/hub-server 0.13.0 → 0.13.1

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/README.md CHANGED
@@ -5,8 +5,8 @@ client registry for push fan-out, and an in-memory `HubStore` for development an
5
5
 
6
6
  ## Exports
7
7
 
8
- - `createHub({ transport, store, identity, ... })` — the whole server: handlers, registry, access
9
- rules, rate limits, and an optional scheduled purge. Returns `{ registry, server }`.
8
+ - `createHub({ transport, store, identity, ... })` -- handlers, registry, access rules, rate limits and an optional scheduled purge.
9
+ Returns `{ registry, server, dispose }`.
10
10
  - `createHandlers({ registry, store, ... })` — the handlers alone, for a host assembling its own
11
11
  server.
12
12
  - `createMemoryStore({ maxDepth?, retention? })` — an in-memory `HubStore`. For testing and
@@ -160,3 +160,19 @@ rather than passing silently. Pass an empty handler to silence it deliberately.
160
160
  There is no throttling: a permanently broken store reports per request. logtape ships
161
161
  `getThrottlingFilter`, so rate control belongs in the app's sink configuration where an operator
162
162
  can tune it.
163
+
164
+ ## Invocation hooks and attribution
165
+
166
+ `createHub` accepts optional `onInvocation`, forwarded to the Enkaku server for authenticated invocation context and delegation verification.
167
+ `createHub` and `createHandlers` accept optional `resolveAttribution({ message, senderDID })`.
168
+ The resolver can return `HubAttribution`, `undefined`, or a promise of either.
169
+
170
+ Without a resolver result, publishes use `{ account: senderDID, actor: senderDID, via: 'direct' }`.
171
+ The sender DID still comes from the verified signature.
172
+ A resolver supplies host accounting metadata rather than changing the authenticated sender or authorisation rules.
173
+ Only a verified delegation yields `via: 'delegated'`. A claimed subject alone does not prove delegation.
174
+ Hosts can use `onInvocation` to verify delegation before resolving attribution.
175
+
176
+ The store receives the resolved attribution with the publish.
177
+ The SQL stores persist it. The development memory store reports publish outcomes but does not implement occupancy sampling.
178
+ Unconfigured hooks preserve existing routing, retention and delivery behaviour.
package/lib/handlers.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type ProcedureHandlers } from '@enkaku/server';
2
- import type { HubProtocol, HubStore, WakeRegistry } from '@kumiai/hub-protocol';
2
+ import type { HubAttribution, HubProtocol, HubStore, WakeRegistry } from '@kumiai/hub-protocol';
3
3
  import { type RateLimitConfig } from './rateLimit.js';
4
4
  import type { HubClientRegistry } from './registry.js';
5
5
  import type { WakeDispatcher } from './wake.js';
@@ -198,6 +198,10 @@ export type CreateHandlersParams = {
198
198
  registry: HubClientRegistry;
199
199
  store: HubStore;
200
200
  authorize?: AuthorizeHook;
201
+ resolveAttribution?: (params: {
202
+ message: unknown;
203
+ senderDID: string;
204
+ }) => HubAttribution | undefined | Promise<HubAttribution | undefined>;
201
205
  rateLimits?: Partial<HubRateLimits>;
202
206
  keyPackageFetchLimits?: Partial<KeyPackageFetchLimits>;
203
207
  /** Max frames queued-but-unflushed on a receive channel. See {@link DEFAULT_RECEIVE_BUFFER_LIMIT}
package/lib/handlers.js CHANGED
@@ -287,8 +287,17 @@ export function createHandlers(params) {
287
287
  let sequenceID;
288
288
  let deduped;
289
289
  try {
290
+ const attribution = await params.resolveAttribution?.({
291
+ message: ctx.message,
292
+ senderDID
293
+ }) ?? {
294
+ account: senderDID,
295
+ actor: senderDID,
296
+ via: 'direct'
297
+ };
290
298
  const result = await store.publish({
291
299
  senderDID,
300
+ attribution,
292
301
  topicID,
293
302
  payload: payloadBytes,
294
303
  retain: ctx.param.retain,
package/lib/hub.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { ServerTransportOf } from '@enkaku/protocol';
2
- import type { AccessRules, ReplayOptions, ResourceLimits, Server } from '@enkaku/server';
2
+ import type { AccessRules, InvocationHook, ReplayOptions, ResourceLimits, Server } from '@enkaku/server';
3
3
  import type { VerifyTokenHook } from '@kokuin/capability';
4
4
  import type { CryptoOptions, Identity } from '@kokuin/token';
5
5
  import type { HubProtocol, HubStore, WakeRegistry, WakeSender } from '@kumiai/hub-protocol';
6
- import { type AuthorizeHook, type HubRateLimits, type HubStoreErrorHook, type KeyPackageFetchLimits } from './handlers.js';
6
+ import type { AuthorizeHook, CreateHandlersParams, HubRateLimits, HubStoreErrorHook, KeyPackageFetchLimits } from './handlers.js';
7
7
  import { HubClientRegistry } from './registry.js';
8
8
  /**
9
9
  * Default access rules: any authenticated DID may call hub procedures.
@@ -53,6 +53,9 @@ export type CreateHubParams = CryptoOptions & {
53
53
  * delegated capabilities.
54
54
  */
55
55
  verifyToken?: VerifyTokenHook;
56
+ onInvocation?: InvocationHook;
57
+ /** An absent result attributes directly to the sender. */
58
+ resolveAttribution?: CreateHandlersParams['resolveAttribution'];
56
59
  /** Publish rate limits. Merged over {@link DEFAULT_RATE_LIMITS}. */
57
60
  rateLimits?: Partial<HubRateLimits>;
58
61
  /**
package/lib/hub.js CHANGED
@@ -32,6 +32,7 @@ export function createHub(params) {
32
32
  registry,
33
33
  store: params.store,
34
34
  authorize: params.authorize,
35
+ resolveAttribution: params.resolveAttribution,
35
36
  rateLimits: params.rateLimits,
36
37
  keyPackageFetchLimits: params.keyPackageFetchLimits,
37
38
  receiveAuthCacheTTL: params.receiveAuthCacheTTL,
@@ -58,6 +59,7 @@ export function createHub(params) {
58
59
  crypto: params.crypto,
59
60
  accessRules: params.accessRules ?? DEFAULT_HUB_ACCESS_RULES,
60
61
  verifyToken: params.verifyToken,
62
+ onInvocation: params.onInvocation,
61
63
  replay: {
62
64
  ...params.replay,
63
65
  enabled: true,
@@ -137,7 +137,9 @@ function isLive(entry, nowSeconds) {
137
137
  if (accepted !== undefined) {
138
138
  return {
139
139
  sequenceID: accepted,
140
- deduped: true
140
+ deduped: true,
141
+ outcome: 'deduplicated',
142
+ storedBytes: 0
141
143
  };
142
144
  }
143
145
  }
@@ -174,7 +176,9 @@ function isLive(entry, nowSeconds) {
174
176
  if (retain === 'mailbox' && recipients.size === 0) {
175
177
  return {
176
178
  sequenceID,
177
- deduped: false
179
+ deduped: false,
180
+ outcome: 'no-recipients',
181
+ storedBytes: 0
178
182
  };
179
183
  }
180
184
  const entry = {
@@ -219,7 +223,9 @@ function isLive(entry, nowSeconds) {
219
223
  }
220
224
  return {
221
225
  sequenceID,
222
- deduped: false
226
+ deduped: false,
227
+ outcome: 'inserted',
228
+ storedBytes: params.payload.byteLength
223
229
  };
224
230
  },
225
231
  async fetch (params) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kumiai/hub-server",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "Hub server for Enkaku E2EE group message routing",
5
5
  "keywords": [
6
6
  "hub",
@@ -25,19 +25,19 @@
25
25
  "lib/*"
26
26
  ],
27
27
  "dependencies": {
28
- "@enkaku/protocol": "^0.23.0",
29
- "@enkaku/server": "^0.23.0",
28
+ "@enkaku/protocol": "^0.23.1",
29
+ "@enkaku/server": "^0.23.1",
30
30
  "@kokuin/capability": "^0.7.0",
31
31
  "@kokuin/token": "^0.7.0",
32
- "@kumiai/hub-protocol": "^0.13.0",
32
+ "@kumiai/hub-protocol": "^0.13.1",
33
33
  "@sozai/codec": "^0.4.4",
34
34
  "@sozai/event": "^0.1.6",
35
35
  "@sozai/log": "^0.4.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@enkaku/client": "^0.23.0",
39
- "@enkaku/transport": "^0.23.0",
40
- "@kokuin/crypto": "^0.7.0",
38
+ "@enkaku/client": "^0.23.1",
39
+ "@enkaku/transport": "^0.23.1",
40
+ "@kokuin/crypto": "^0.7.1",
41
41
  "@kumiai/hub-conformance": "^0.13.0",
42
42
  "@kumiai/hub-wake": "^0.13.0"
43
43
  },