@fedify/fedify 2.4.0-dev.2256 → 2.4.0-dev.2260

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 (105) hide show
  1. package/dist/{builder-DvSQqk2l.mjs → builder-BK8HfSUm.mjs} +8 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-5bg_j_ye.mjs → compound-proof-D--HeIbV.mjs} +41 -4
  6. package/dist/{context-CtI307_A.d.ts → context-7NyiqIcg.d.ts} +111 -2
  7. package/dist/{context-B6X-t8gI.d.cts → context-tsYPJ2Ew.d.cts} +111 -2
  8. package/dist/{deno-AmcRMq5z.mjs → deno-DVP7DGJC.mjs} +1 -1
  9. package/dist/{docloader-CIP_SG39.mjs → docloader-Dv1JJl37.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  13. package/dist/federation/handler.test.mjs +76 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/inbox-report.test.d.mts +2 -0
  17. package/dist/federation/inbox-report.test.mjs +475 -0
  18. package/dist/federation/metrics.test.mjs +1 -1
  19. package/dist/federation/middleware.test.mjs +7 -7
  20. package/dist/federation/mod.cjs +1 -1
  21. package/dist/federation/mod.d.cts +4 -3
  22. package/dist/federation/mod.d.ts +4 -3
  23. package/dist/federation/mod.js +1 -1
  24. package/dist/federation/outgoing-proof.test.mjs +54 -10
  25. package/dist/federation/portable-actor.test.mjs +3 -3
  26. package/dist/federation/portable-chain.test.mjs +3 -3
  27. package/dist/federation/portable-collection.test.mjs +66 -4
  28. package/dist/federation/portable-delivery.test.mjs +2 -2
  29. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  30. package/dist/federation/portable-inbox.test.mjs +67 -6
  31. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  32. package/dist/federation/portable-route-activity.test.mjs +2 -2
  33. package/dist/federation/portable.test.mjs +3 -3
  34. package/dist/federation/send.test.mjs +3 -3
  35. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  36. package/dist/federation/tasks/tasks.test.mjs +2 -2
  37. package/dist/federation/temporal.test.mjs +1 -1
  38. package/dist/federation/webfinger.test.mjs +1 -1
  39. package/dist/{federation-BDzIgqOd.cjs → federation-Bo5z395X.cjs} +552 -122
  40. package/dist/{http-1fZ83jj7.cjs → http-BIhWdDf5.cjs} +447 -110
  41. package/dist/{http-BGb3QioZ.js → http-DRMu1C2T.js} +430 -111
  42. package/dist/{http-DkqWu-sm.mjs → http-DvpuyDJB.mjs} +141 -98
  43. package/dist/{key-CEsu7oaD.mjs → key-B7N8mNoY.mjs} +30 -3
  44. package/dist/{http-BrwKINQn.d.cts → key-C1nv7_jD.d.ts} +638 -229
  45. package/dist/{http-DGx0KOws.d.ts → key-DYxuyd-s.d.cts} +638 -229
  46. package/dist/{kv-cache-CJS4mImS.mjs → kv-cache-D0kA0rml.mjs} +1 -1
  47. package/dist/{kv-cache-CGhEgt98.js → kv-cache-DZLpqhW9.js} +1 -1
  48. package/dist/{kv-cache-BEWuZSB4.cjs → kv-cache-mF8Zmjix.cjs} +1 -1
  49. package/dist/{ld-Cf4CPWN_.mjs → ld-BDMNyM2M.mjs} +40 -5
  50. package/dist/{metrics-CocsZP0E.mjs → metrics-BbBj-VZN.mjs} +2 -1
  51. package/dist/{middleware-Cne_jMZo.mjs → middleware-CE1X7-UM.mjs} +1 -1
  52. package/dist/{middleware-Dxn0E9ES.mjs → middleware-DAA7wDdS.mjs} +520 -131
  53. package/dist/{middleware-BfYke3YQ.js → middleware-DfUWPAa-.js} +552 -122
  54. package/dist/{mod-C7_2j7VT.d.cts → mod-BojP2Bw3.d.cts} +1 -1
  55. package/dist/{mod-BlLwewYe.d.ts → mod-CavhAgLw.d.cts} +5 -255
  56. package/dist/{mod-VHYh1lTK.d.cts → mod-CtpF2BU5.d.ts} +5 -255
  57. package/dist/{mod-B8MI1mpi.d.ts → mod-DEZXwV6J.d.ts} +1 -1
  58. package/dist/{mod-DFL6IxYQ.d.cts → mod-Dw-nPxQh.d.cts} +1 -1
  59. package/dist/{mod-BhTaDR5H.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
  60. package/dist/mod.cjs +4 -4
  61. package/dist/mod.d.cts +7 -7
  62. package/dist/mod.d.ts +7 -7
  63. package/dist/mod.js +4 -4
  64. package/dist/nodeinfo/handler.test.mjs +1 -1
  65. package/dist/{owner-DSJ7nFgy.mjs → owner-CzUycuhd.mjs} +3 -3
  66. package/dist/{portable-collection-BuBV6xTo.js → portable-collection-COcJxZg_.js} +1 -1
  67. package/dist/{portable-collection-qskqXJrH.mjs → portable-collection-CTcNJiAa.mjs} +2 -2
  68. package/dist/{portable-collection-r-mHg1PV.cjs → portable-collection-cPMffcHJ.cjs} +1 -1
  69. package/dist/{portable-key-woGH_1nn.mjs → portable-key-B4QN3OzR.mjs} +1 -1
  70. package/dist/{proof-8WiH_-Gq.mjs → proof-nce4pf5S.mjs} +133 -16
  71. package/dist/{send-JKzZBgPU.mjs → send-4Idpe2z0.mjs} +3 -3
  72. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  73. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  74. package/dist/sig/compound-proof-verification.test.mjs +19 -2
  75. package/dist/sig/compound-proof.test.mjs +2 -2
  76. package/dist/sig/http.test.mjs +2 -2
  77. package/dist/sig/key.test.mjs +1 -1
  78. package/dist/sig/ld.test.mjs +2 -2
  79. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  80. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  81. package/dist/sig/mod.cjs +2 -2
  82. package/dist/sig/mod.d.cts +2 -2
  83. package/dist/sig/mod.d.ts +2 -2
  84. package/dist/sig/mod.js +2 -2
  85. package/dist/sig/owner.test.mjs +1 -1
  86. package/dist/sig/portable-collection.test.mjs +1 -1
  87. package/dist/sig/portable-interop.test.mjs +3 -3
  88. package/dist/sig/portable-key.test.mjs +6 -6
  89. package/dist/sig/proof.test.mjs +76 -3
  90. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  91. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  92. package/dist/sig/verification.test.d.mts +2 -0
  93. package/dist/sig/verification.test.mjs +310 -0
  94. package/dist/{temporal-CT3fRaMH.mjs → temporal-BbjmPHYn.mjs} +1 -1
  95. package/dist/testing/kv.test.mjs +1 -1
  96. package/dist/testing/mod.d.mts +315 -28
  97. package/dist/utils/docloader.test.mjs +2 -2
  98. package/dist/utils/kv-cache.test.mjs +1 -1
  99. package/dist/utils/mod.cjs +1 -1
  100. package/dist/utils/mod.d.cts +1 -1
  101. package/dist/utils/mod.d.ts +1 -1
  102. package/dist/utils/mod.js +1 -1
  103. package/dist/verification-C3954hQh.mjs +5 -0
  104. package/dist/verification-Dpfcw8ml.mjs +105 -0
  105. package/package.json +7 -7
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as getNormalizationContextLoader } from "./ld-Cf4CPWN_.mjs";
4
+ import { c as getNormalizationContextLoader } from "./ld-BDMNyM2M.mjs";
5
5
  import jsonld from "@fedify/vocab-runtime/jsonld";
6
6
  //#region src/federation/temporal.ts
7
7
  function isPlainObject(value) {
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
7
7
  import { n as baseOptions, o as stringSchema, t as MockQueue } from "../tasks-IG3pHfvZ.mjs";
8
8
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
9
- import { o as createFederation } from "../middleware-Dxn0E9ES.mjs";
9
+ import { o as createFederation } from "../middleware-DAA7wDdS.mjs";
10
10
  //#region src/testing/kv.test.ts
11
11
  test("ManualClockKvStore keeps an entry at its exact expiration instant", async () => {
12
12
  const kv = new ManualClockKvStore();
@@ -2,7 +2,7 @@ import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { ExportResultCode } from "@opentelemetry/core";
4
4
  import { Router } from "@fedify/uri-template";
5
- import { Activity, Actor, Collection, CryptographicKey, Hashtag, Link, LookupObjectOptions, Multikey, Note, Object as Object$1, Recipient, Tombstone, TraverseCollectionOptions } from "@fedify/vocab";
5
+ import { Activity, Actor, Collection, CryptographicKey, DataIntegrityProof, Hashtag, Link, LookupObjectOptions, Multikey, Note, Object as Object$1, Recipient, Tombstone, TraverseCollectionOptions } from "@fedify/vocab";
6
6
  import { Link as Link$1, LookupWebFingerOptions, ResourceDescriptor } from "@fedify/webfinger";
7
7
  import { MeterProvider, TracerProvider } from "@opentelemetry/api";
8
8
  import { AuthenticatedDocumentLoaderFactory, DocumentLoader, DocumentLoaderFactory, GetUserAgentOptions, PortableObjectVerifier } from "@fedify/vocab-runtime";
@@ -169,6 +169,215 @@ interface GetNodeInfoOptions {
169
169
  userAgent?: GetUserAgentOptions | string;
170
170
  }
171
171
  //#endregion
172
+ //#region src/sig/proof.d.ts
173
+ /**
174
+ * The reason why {@link verifyPortableObjectProof} could not verify a portable
175
+ * object proof.
176
+ * @since 2.4.0
177
+ */
178
+ type VerifyPortableObjectProofFailureReason = {
179
+ /**
180
+ * The document has neither a portable `ap:` or `ap+ef61:` ID nor an
181
+ * FEP-ef61 compatible identifier.
182
+ */
183
+ readonly type: "notPortableObject";
184
+ } | {
185
+ /**
186
+ * The document is a portable collection without an Object Integrity
187
+ * Proof. Its trust policy is outside this verifier.
188
+ */
189
+ readonly type: "unsecuredCollection";
190
+ } | {
191
+ /** The portable document is a core type outside FEP-ef61 proof policy. */readonly type: "unsupportedObjectType"; /** The FEP-2277 core type of the document. */
192
+ readonly objectType: "verificationMethod" | "publicKey" | "link";
193
+ } | {
194
+ /** A portable actor, activity, or object has no proof. */readonly type: "missingProof";
195
+ } | {
196
+ /**
197
+ * A portable actor's `gateways` is missing or empty, or has an item that
198
+ * is not an HTTP(S) URI with an empty path, query, and fragment.
199
+ * The document is rejected before its proofs are verified, so this
200
+ * reason does not mean that the proofs are valid.
201
+ */
202
+ readonly type: "invalidGateways";
203
+ } | {
204
+ /** The proof is malformed, unsupported, or cryptographically invalid. */readonly type: "invalidProof"; /** The zero-based index of the invalid proof. */
205
+ readonly proofIndex: number;
206
+ } | {
207
+ /** The proof's verification method is not a valid DID URL. */readonly type: "unsupportedVerificationMethod"; /** The zero-based index of the proof. */
208
+ readonly proofIndex: number; /** The unsupported verification method. */
209
+ readonly verificationMethod: URL;
210
+ } | {
211
+ /** The verification method DID does not match the portable ID authority. */readonly type: "verificationMethodMismatch"; /** The zero-based index of the proof. */
212
+ readonly proofIndex: number; /** The portable object ID. */
213
+ readonly objectId: URL; /** The mismatching verification method. */
214
+ readonly verificationMethod: URL;
215
+ };
216
+ //#endregion
217
+ //#region src/sig/compound-proof.d.ts
218
+ /** Why compound-proof discovery could not produce an atomic result. */
219
+ type CompoundProofDiscoveryFailureReason = {
220
+ readonly type: "invalidJsonTree";
221
+ readonly path: string;
222
+ } | {
223
+ readonly type: "unsupportedProofShape";
224
+ readonly path: string;
225
+ } | {
226
+ readonly type: "limitExceeded";
227
+ readonly limit: "depth" | "maps" | "proofs" | "bytes";
228
+ readonly maximum: number;
229
+ readonly actual: number;
230
+ readonly path: string;
231
+ };
232
+ /** Why a discovered map-local proof did not verify. */
233
+ type CompoundProofVerificationFailureReason = {
234
+ /** A nested secured map does not carry its own JSON-LD context. */readonly type: "missingContext";
235
+ } | {
236
+ /** The direct proof is malformed, unsupported, or invalid. */readonly type: "invalidProof";
237
+ };
238
+ /** Why one portable map did not pass compound proof policy. */
239
+ type CompoundPortableObjectFailureReason = CompoundProofVerificationFailureReason | VerifyPortableObjectProofFailureReason | {
240
+ /** The portable map could not be interpreted by the policy layer. */readonly type: "invalidPortableObject";
241
+ };
242
+ //#endregion
243
+ //#region src/sig/http.d.ts
244
+ /**
245
+ * The standard to use for signing and verifying HTTP signatures.
246
+ * @since 1.6.0
247
+ */
248
+ type HttpMessageSignaturesSpec =
249
+ /**
250
+ * The Signing HTTP Messages (draft-cavage-http-signatures-12) specification,
251
+ * which is widely adopted and used in the fediverse (as of May 2025).
252
+ */
253
+ "draft-cavage-http-signatures-12"
254
+ /**
255
+ * The HTTP Message Signatures (RFC 9421) specification, which is the
256
+ * finalized standard but not widely adopted yet (as of May 2025).
257
+ */
258
+ | "rfc9421";
259
+ /**
260
+ * The reason why {@link verifyRequestDetailed} could not verify a request.
261
+ * @since 2.1.0
262
+ */
263
+ type VerifyRequestFailureReason = {
264
+ readonly type: "keyFetchError";
265
+ readonly keyId: URL;
266
+ readonly result: FetchKeyErrorResult;
267
+ } | {
268
+ readonly type: "invalidSignature";
269
+ readonly keyId?: URL;
270
+ } | {
271
+ readonly type: "noSignature";
272
+ };
273
+ //#endregion
274
+ //#region src/sig/verification.d.ts
275
+ /**
276
+ * A snapshot of a key actually used to check a signature.
277
+ * @since 2.4.0
278
+ */
279
+ type InboxVerificationKey = {
280
+ readonly type: "cryptographicKey";
281
+ readonly id: URL | null;
282
+ readonly ownerId: URL | null;
283
+ readonly publicKey: CryptoKey;
284
+ } | {
285
+ readonly type: "multikey";
286
+ readonly id: URL | null;
287
+ readonly controllerId: URL | null;
288
+ readonly publicKey: CryptoKey;
289
+ };
290
+ /**
291
+ * Why a cryptographic check failed.
292
+ * @since 2.4.0
293
+ */
294
+ type InboxSignatureFailureReason = {
295
+ readonly type: "invalidSignature";
296
+ } | {
297
+ readonly type: "keyFetchError";
298
+ readonly keyId: URL;
299
+ readonly result: FetchKeyErrorResult;
300
+ };
301
+ /**
302
+ * The signature or proof being checked.
303
+ * @since 2.4.0
304
+ */
305
+ type InboxSignatureIdentity = {
306
+ readonly mechanism: "http";
307
+ readonly spec: HttpMessageSignaturesSpec;
308
+ readonly label: string | null;
309
+ } | {
310
+ readonly mechanism: "linkedData";
311
+ } | {
312
+ readonly mechanism: "objectIntegrity";
313
+ readonly proofId: string | null; /** Index within this evaluation, not a global proof identifier. */
314
+ readonly proofIndex: number | null;
315
+ };
316
+ /**
317
+ * Evidence from one signature/proof, including cached-key retries.
318
+ * @since 2.4.0
319
+ */
320
+ type InboxSignatureCheck = InboxSignatureIdentity & {
321
+ /** The declaration as received, including invalid IRIs. */readonly declaredKeyId: string | null; /** Keys actually tried, in use order; refresh details are not a retry API. */
322
+ readonly triedKeys: readonly InboxVerificationKey[];
323
+ } & ({
324
+ readonly status: "verified";
325
+ readonly key: InboxVerificationKey;
326
+ } | {
327
+ readonly status: "rejected";
328
+ readonly reason: InboxSignatureFailureReason;
329
+ } | {
330
+ readonly status: "error";
331
+ readonly error: unknown;
332
+ });
333
+ /**
334
+ * A bounded proof-policy diagnostic.
335
+ * @since 2.4.0
336
+ */
337
+ type InboxProofPolicyFailureReason = VerifyPortableObjectProofFailureReason | CompoundPortableObjectFailureReason | CompoundProofDiscoveryFailureReason | {
338
+ readonly type: "invalidObjectId" | "invalidDocument" | "subjectMismatch";
339
+ };
340
+ /**
341
+ * Why a complete verification evaluation was rejected.
342
+ * @since 2.4.0
343
+ */
344
+ type InboxVerificationFailureReason = {
345
+ readonly type: "noSignature";
346
+ } | {
347
+ readonly type: "signatureVerificationFailed";
348
+ } | {
349
+ readonly type: "uncoveredAttribution";
350
+ readonly attributionIds: readonly URL[];
351
+ } | {
352
+ readonly type: "missingOwner";
353
+ } | {
354
+ readonly type: "proofPolicy";
355
+ readonly reason: InboxProofPolicyFailureReason;
356
+ };
357
+ /**
358
+ * A logical verification evaluation, separate from inbox authentication.
359
+ * @since 2.4.0
360
+ */
361
+ type InboxVerificationAttempt = {
362
+ readonly mechanism: "http" | "linkedData" | "objectIntegrity";
363
+ readonly subject: {
364
+ readonly id: URL | null; /** RFC 6901 pointer; empty for the root, null when unavailable. */
365
+ readonly pointer: string | null;
366
+ };
367
+ readonly checks: readonly InboxSignatureCheck[];
368
+ } & ({
369
+ readonly status: "verified"; /** Direct references to successful members of checks. */
370
+ readonly signatures: readonly Extract<InboxSignatureCheck, {
371
+ status: "verified";
372
+ }>[];
373
+ } | {
374
+ readonly status: "rejected";
375
+ readonly reason: InboxVerificationFailureReason;
376
+ } | {
377
+ readonly status: "error";
378
+ readonly error: unknown;
379
+ });
380
+ //#endregion
172
381
  //#region src/sig/key.d.ts
173
382
  /**
174
383
  * Detailed fetch failure information from {@link fetchKeyDetailed}.
@@ -203,43 +412,111 @@ interface GetKeyOwnerOptions {
203
412
  tracerProvider?: TracerProvider;
204
413
  }
205
414
  //#endregion
206
- //#region src/compat/types.d.ts
415
+ //#region src/federation/inbox-report.d.ts
207
416
  /**
208
- * A function that transforms an activity object.
209
- * @since 1.4.0
417
+ * A JSON value retained from the incoming request.
418
+ * @since 2.4.0
210
419
  */
211
- type ActivityTransformer<TContextData> = (activity: Activity, context: Context<TContextData>) => Activity;
212
- //#endregion
213
- //#region src/sig/http.d.ts
420
+ type InboxJsonValue = null | boolean | number | string | readonly InboxJsonValue[] | {
421
+ readonly [key: string]: InboxJsonValue;
422
+ };
214
423
  /**
215
- * The standard to use for signing and verifying HTTP signatures.
216
- * @since 1.6.0
424
+ * The final inbox authentication decision.
425
+ * @since 2.4.0
217
426
  */
218
- type HttpMessageSignaturesSpec =
427
+ type InboxAuthentication = {
428
+ readonly status: "verified"; /** Direct references to the attempts that authenticated this delivery. */
429
+ readonly attempts: readonly Extract<InboxVerificationAttempt, {
430
+ status: "verified";
431
+ }>[];
432
+ } | {
433
+ readonly status: "rejected";
434
+ readonly reason: {
435
+ readonly type: "verificationFailed";
436
+ } | {
437
+ readonly type: "actorKeyMismatch";
438
+ readonly key: InboxVerificationKey;
439
+ readonly actorIds: readonly URL[];
440
+ } | {
441
+ readonly type: "proofPolicy";
442
+ readonly policy: "portableActor" | "portableActivity" | "compound";
443
+ readonly detail?: InboxProofPolicyFailureReason;
444
+ } | {
445
+ readonly type: "invalidNonce";
446
+ };
447
+ } | {
448
+ readonly status: "skipped";
449
+ } | {
450
+ readonly status: "notDetermined";
451
+ };
219
452
  /**
220
- * The Signing HTTP Messages (draft-cavage-http-signatures-12) specification,
221
- * which is widely adopted and used in the fediverse (as of May 2025).
453
+ * How an inbox request finished.
454
+ * @since 2.4.0
222
455
  */
223
- "draft-cavage-http-signatures-12"
456
+ type InboxRequestOutcome = {
457
+ readonly type: "response";
458
+ readonly status: number;
459
+ readonly disposition: "processed" | "enqueued" | "duplicate" | "unhandled" | "customResponse";
460
+ } | {
461
+ readonly type: "response";
462
+ readonly status: number;
463
+ readonly disposition: "rejected";
464
+ readonly reason: "recipientNotFound" | "invalidJson" | "invalidJsonLd" | "invalidActivity" | "bodyTooLarge" | "missingActor" | "authentication";
465
+ } | {
466
+ readonly type: "response";
467
+ readonly status: number;
468
+ readonly disposition: "failed";
469
+ readonly reason: "bodyUnavailable" | "enqueueError" | "listenerError";
470
+ readonly error?: unknown;
471
+ } | {
472
+ readonly type: "exception";
473
+ readonly stage: "prepare" | "parse" | "verify" | "policy" | "dispatch" | "respond"; /** The original thrown value, including null/undefined. */
474
+ readonly error: unknown;
475
+ };
224
476
  /**
225
- * The HTTP Message Signatures (RFC 9421) specification, which is the
226
- * finalized standard but not widely adopted yet (as of May 2025).
477
+ * Observations of a single inbox delivery request.
478
+ * @since 2.4.0
227
479
  */
228
- | "rfc9421";
480
+ interface InboxRequestReport {
481
+ /** The delivery route, including a portable gateway route. */
482
+ readonly inbox: {
483
+ readonly kind: "personal" | "shared" | "portable";
484
+ readonly recipient: string | null;
485
+ };
486
+ /** Unavailable differs from a successfully parsed JSON null. */
487
+ readonly payload: {
488
+ readonly status: "unavailable";
489
+ } | {
490
+ readonly status: "parsed";
491
+ readonly value: InboxJsonValue;
492
+ };
493
+ /** The Activity obtained by the normal processing flow, if any. */
494
+ readonly activity: Activity | null;
495
+ /** Logical evaluations, including unsuccessful fallback paths. */
496
+ readonly attempts: readonly InboxVerificationAttempt[];
497
+ /** Cryptographic success alone does not imply this decision is verified. */
498
+ readonly authentication: InboxAuthentication;
499
+ /** Response status snapshot or the original exception. */
500
+ readonly outcome: InboxRequestOutcome;
501
+ }
229
502
  /**
230
- * The reason why {@link verifyRequestDetailed} could not verify a request.
231
- * @since 2.1.0
503
+ * Observes an inbox delivery after processing, before fetch resolves/rethrows.
504
+ * The callback is awaited. Its errors are logged and swallowed, preserving
505
+ * the original response/exception. The last registered callback replaces the
506
+ * previous one. It excludes inbox collection GETs, unmatched routes,
507
+ * programmatic routing, and queue workers. This is not an atomic persistence
508
+ * guarantee. Keys/owner/controller declarations remain untrusted unless the
509
+ * final authentication decision verifies them.
510
+ * @since 2.4.0
232
511
  */
233
- type VerifyRequestFailureReason = {
234
- readonly type: "keyFetchError";
235
- readonly keyId: URL;
236
- readonly result: FetchKeyErrorResult;
237
- } | {
238
- readonly type: "invalidSignature";
239
- readonly keyId?: URL;
240
- } | {
241
- readonly type: "noSignature";
242
- };
512
+ type InboxRequestFinishedHandler<TContextData> = (context: RequestContext<TContextData>, report: InboxRequestReport) => void | Promise<void>;
513
+ //#endregion
514
+ //#region src/compat/types.d.ts
515
+ /**
516
+ * A function that transforms an activity object.
517
+ * @since 1.4.0
518
+ */
519
+ type ActivityTransformer<TContextData> = (activity: Activity, context: Context<TContextData>) => Activity;
243
520
  //#endregion
244
521
  //#region src/federation/collection.d.ts
245
522
  /**
@@ -2755,6 +3032,16 @@ interface InboxListenerSetters<TContextData> {
2755
3032
  * @returns The setters object so that settings can be chained.
2756
3033
  */
2757
3034
  onError(handler: InboxErrorHandler<TContextData>): InboxListenerSetters<TContextData>;
3035
+ /**
3036
+ * Observes each configured inbox delivery once, before fetch finishes.
3037
+ * The callback is awaited and its errors do not alter delivery processing.
3038
+ * Calling this again replaces the previous callback. Built federations
3039
+ * retain the callback registered at build time.
3040
+ * @param handler The request completion observer.
3041
+ * @returns This setter for chaining.
3042
+ * @since 2.4.0
3043
+ */
3044
+ onRequestFinished(handler: InboxRequestFinishedHandler<TContextData>): InboxListenerSetters<TContextData>;
2758
3045
  /**
2759
3046
  * Registers a callback for incoming activities whose HTTP signatures could
2760
3047
  * not be verified.
@@ -5,9 +5,9 @@ import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
7
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
8
- import { f as verifyRequest } from "../http-DkqWu-sm.mjs";
8
+ import { f as verifyRequest } from "../http-DvpuyDJB.mjs";
9
9
  import { i as rsaPrivateKey2 } from "../keys-DIJNHW-Y.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-CIP_SG39.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-Dv1JJl37.mjs";
11
11
  import { configure, reset } from "@logtape/logtape";
12
12
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
13
13
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as kvCache, t as MockKvStore } from "../kv-cache-CJS4mImS.mjs";
4
+ import { n as kvCache, t as MockKvStore } from "../kv-cache-D0kA0rml.mjs";
5
5
  import { preloadedContexts } from "@fedify/vocab-runtime";
6
6
  import { deepStrictEqual, throws } from "node:assert";
7
7
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_kv_cache = require("../kv-cache-BEWuZSB4.cjs");
3
+ const require_kv_cache = require("../kv-cache-mF8Zmjix.cjs");
4
4
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
5
5
  exports.kvCache = require_kv_cache.kvCache;
@@ -1,3 +1,3 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-DFL6IxYQ.cjs";
2
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-Dw-nPxQh.cjs";
3
3
  export { getAuthenticatedDocumentLoader, kvCache };
@@ -1,3 +1,3 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-B8MI1mpi.js";
2
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-DEZXwV6J.js";
3
3
  export { getAuthenticatedDocumentLoader, kvCache };
package/dist/utils/mod.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-CGhEgt98.js";
3
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-DZLpqhW9.js";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };
@@ -0,0 +1,5 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { a as verificationObservation, r as snapshotKey } from "./verification-Dpfcw8ml.mjs";
5
+ export { snapshotKey, verificationObservation };
@@ -0,0 +1,105 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { CryptographicKey } from "@fedify/vocab";
5
+ //#region src/sig/verification.ts
6
+ const verificationObservation = Symbol("verificationObservation");
7
+ function snapshotKey(key) {
8
+ if (key.publicKey == null) throw new TypeError("Verification key has no public key.");
9
+ const id = key.id == null ? null : new URL(key.id.href);
10
+ return key instanceof CryptographicKey ? {
11
+ type: "cryptographicKey",
12
+ id,
13
+ ownerId: key.ownerId == null ? null : new URL(key.ownerId.href),
14
+ publicKey: key.publicKey
15
+ } : {
16
+ type: "multikey",
17
+ id,
18
+ controllerId: key.controllerId == null ? null : new URL(key.controllerId.href),
19
+ publicKey: key.publicKey
20
+ };
21
+ }
22
+ function triedKey(options, key) {
23
+ const check = options[verificationObservation]?.check;
24
+ if (check != null) check.triedKeys.push(snapshotKey(key));
25
+ }
26
+ async function observeCheck(options, identity, declaredKeyId, operation) {
27
+ const observation = options[verificationObservation];
28
+ if (observation?.attempt == null) return await operation(observation ?? { attempts: [] });
29
+ const check = {
30
+ identity,
31
+ declaredKeyId,
32
+ triedKeys: []
33
+ };
34
+ const base = {
35
+ ...identity,
36
+ declaredKeyId,
37
+ triedKeys: check.triedKeys
38
+ };
39
+ try {
40
+ const key = await operation({
41
+ ...observation,
42
+ check
43
+ });
44
+ observation.attempt.checks.push(key == null ? {
45
+ ...base,
46
+ status: "rejected",
47
+ reason: check.reason ?? { type: "invalidSignature" }
48
+ } : {
49
+ ...base,
50
+ status: "verified",
51
+ key: check.triedKeys.at(-1) ?? snapshotKey(key)
52
+ });
53
+ return key;
54
+ } catch (error) {
55
+ observation.attempt.checks.push({
56
+ ...base,
57
+ status: "error",
58
+ error
59
+ });
60
+ throw error;
61
+ }
62
+ }
63
+ async function observeAttempt(options, mechanism, operation, isVerified) {
64
+ const observation = options[verificationObservation];
65
+ if (observation == null) return await operation({ attempts: [] });
66
+ const attempt = { checks: [] };
67
+ const base = {
68
+ mechanism,
69
+ subject: observation.subject == null ? {
70
+ id: null,
71
+ pointer: ""
72
+ } : {
73
+ id: observation.subject.id == null ? null : new URL(observation.subject.id.href),
74
+ pointer: observation.subject.pointer
75
+ },
76
+ checks: attempt.checks
77
+ };
78
+ try {
79
+ const value = await operation({
80
+ ...observation,
81
+ subject: base.subject,
82
+ attempt,
83
+ check: void 0
84
+ });
85
+ observation.attempts.push(isVerified(value) ? {
86
+ ...base,
87
+ status: "verified",
88
+ signatures: attempt.checks.filter((check) => check.status === "verified")
89
+ } : {
90
+ ...base,
91
+ status: "rejected",
92
+ reason: attempt.reason ?? { type: "signatureVerificationFailed" }
93
+ });
94
+ return value;
95
+ } catch (error) {
96
+ observation.attempts.push({
97
+ ...base,
98
+ status: "error",
99
+ error
100
+ });
101
+ throw error;
102
+ }
103
+ }
104
+ //#endregion
105
+ export { verificationObservation as a, triedKey as i, observeCheck as n, snapshotKey as r, observeAttempt as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "2.4.0-dev.2256+683c50ba",
3
+ "version": "2.4.0-dev.2260+42356e7d",
4
4
  "description": "An ActivityPub server framework",
5
5
  "keywords": [
6
6
  "ActivityPub",
@@ -151,10 +151,10 @@
151
151
  "structured-field-values": "^2.0.4",
152
152
  "temporal-polyfill": "^1.0.1",
153
153
  "urlpattern-polyfill": "^10.1.0",
154
- "@fedify/uri-template": "2.4.0-dev.2256+683c50ba",
155
- "@fedify/vocab": "2.4.0-dev.2256+683c50ba",
156
- "@fedify/webfinger": "2.4.0-dev.2256+683c50ba",
157
- "@fedify/vocab-runtime": "2.4.0-dev.2256+683c50ba"
154
+ "@fedify/uri-template": "2.4.0-dev.2260+42356e7d",
155
+ "@fedify/vocab": "2.4.0-dev.2260+42356e7d",
156
+ "@fedify/webfinger": "2.4.0-dev.2260+42356e7d",
157
+ "@fedify/vocab-runtime": "2.4.0-dev.2260+42356e7d"
158
158
  },
159
159
  "devDependencies": {
160
160
  "@std/assert": "npm:@jsr/std__assert@^0.226.0",
@@ -166,8 +166,8 @@
166
166
  "tsx": "^4.21.0",
167
167
  "typescript": "^6.0.0",
168
168
  "wrangler": "^4.17.0",
169
- "@fedify/vocab-tools": "^2.4.0-dev.2256+683c50ba",
170
- "@fedify/fixture": "2.0.0"
169
+ "@fedify/fixture": "2.0.0",
170
+ "@fedify/vocab-tools": "^2.4.0-dev.2260+42356e7d"
171
171
  },
172
172
  "scripts": {
173
173
  "build:self": "tsdown",