@fedify/fedify 2.5.0-dev.2609 → 2.5.0-dev.2614
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/{builder-BLCvt0zp.mjs → builder-B2H8GsyI.mjs} +82 -3
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-DNhLvscb.mjs → compound-proof-BavXW-ow.mjs} +2 -2
- package/dist/{context-ByflxsY9.d.cts → context-C1NqcIct.d.cts} +1061 -443
- package/dist/{context-uQUY5fQT.d.ts → context-xrqq4Hrq.d.ts} +1064 -446
- package/dist/{deno-DYkYmKp0.mjs → deno-CRXki9-m.mjs} +1 -1
- package/dist/{docloader-B9RO5nh9.mjs → docloader-Ca6i6ddp.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +4 -4
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-actorless.test.mjs +5 -5
- package/dist/federation/inbox-report.test.mjs +220 -5
- package/dist/federation/inbox-view.test.mjs +1 -1
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +4 -4
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +1 -1
- package/dist/federation/outbound-body.test.d.mts +2 -0
- package/dist/federation/outbound-body.test.mjs +149 -0
- package/dist/federation/outbound-capture.test.d.mts +2 -0
- package/dist/federation/outbound-capture.test.mjs +394 -0
- package/dist/federation/outbound-dispatch.test.d.mts +2 -0
- package/dist/federation/outbound-dispatch.test.mjs +368 -0
- package/dist/federation/outbound-document.test.d.mts +2 -0
- package/dist/federation/outbound-document.test.mjs +171 -0
- package/dist/federation/outbound-provenance.test.d.mts +2 -0
- package/dist/federation/outbound-provenance.test.mjs +543 -0
- package/dist/federation/outbound-report.test.d.mts +2 -0
- package/dist/federation/outbound-report.test.mjs +5475 -0
- package/dist/federation/outbound-workers.test.d.mts +2 -0
- package/dist/federation/outbound-workers.test.mjs +64 -0
- package/dist/federation/outbound-workflow.test.d.mts +2 -0
- package/dist/federation/outbound-workflow.test.mjs +154 -0
- package/dist/federation/outgoing-proof.test.mjs +4 -4
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-delivery.test.mjs +106 -7
- package/dist/federation/portable-gateway-keys.test.mjs +5 -5
- package/dist/federation/portable-inbox.test.mjs +430 -5
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.mjs +2 -2
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +44 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-DzMdu4zU.cjs → federation-DRl81KbJ.cjs} +2358 -405
- package/dist/{http-EoFzzh1s.js → http-C2H4DT6t.js} +62 -32
- package/dist/{http-BdTJujR0.cjs → http-DHSIMipE.cjs} +62 -32
- package/dist/{http-TgeyuTDM.mjs → http-DqOXkYbX.mjs} +64 -34
- package/dist/{inbox-ChkbRxCN.mjs → inbox-C4iW1g0F.mjs} +90 -3
- package/dist/{inbox-view-BosBZdlt.mjs → inbox-view-Bu_aSwFJ.mjs} +1 -1
- package/dist/{key-DbLNT-vA.d.ts → key-dwwAg5bA.d.ts} +1 -1
- package/dist/{kv-cache-CMgOYiLU.js → kv-cache-B-x_OrBo.js} +1 -1
- package/dist/{kv-cache-B0o18_Ni.cjs → kv-cache-CHR_BVyM.cjs} +1 -1
- package/dist/{kv-cache-CTA2i08a.mjs → kv-cache-DJsxu9nI.mjs} +1 -1
- package/dist/{ld-CZsMAfOw.mjs → ld-C5JOBBFj.mjs} +2 -2
- package/dist/{ld-Inagww4R.d.ts → ld-o4LnhCYY.d.ts} +2 -2
- package/dist/{metrics-Kc9mP1LD.mjs → metrics-CTteRnEr.mjs} +1 -1
- package/dist/{metrics-DoRtI0qU.d.ts → metrics-DVKYY6K1.d.ts} +1 -1
- package/dist/{middleware-B-pjIvQ8.js → middleware-B-tMVSUA.js} +2303 -351
- package/dist/{middleware-CIRoIVSU.mjs → middleware-BkRqMGPU.mjs} +1573 -325
- package/dist/{middleware-C1coPz8l.mjs → middleware-CPigKLnt.mjs} +1 -1
- package/dist/{mod-B7j30IQf.d.ts → mod-CQC9xlLZ.d.ts} +3 -3
- package/dist/{mod-B4kBw1-8.d.ts → mod-D22ZsiKi.d.ts} +2 -2
- package/dist/{mod-BN0TTfyQ.d.ts → mod-DO0epJGH.d.ts} +1 -1
- package/dist/{mod-DF2gbN_d.d.cts → mod-TwiQgEf3.d.cts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +4 -4
- package/dist/mod.d.ts +8 -8
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/outbound-body-CckPA5gc.mjs +83 -0
- package/dist/outbound-capture-DVgnRzlf.mjs +184 -0
- package/dist/outbound-document-BMgwLXTY.mjs +179 -0
- package/dist/{owner-Dwz4fGUW.mjs → owner-CKlRIkeF.mjs} +3 -3
- package/dist/{portable-collection-Oxfavx8f.mjs → portable-collection-B7RBHVGl.mjs} +2 -2
- package/dist/{portable-collection-BikiijSk.js → portable-collection-CByyPjLc.js} +2 -2
- package/dist/{portable-collection-Ci6vIhbR.cjs → portable-collection-CmLjeKsP.cjs} +2 -2
- package/dist/{portable-key--fWpTNDN.mjs → portable-key-Clg0IH8C.mjs} +1 -1
- package/dist/{proof-D6Kb0Qq9.mjs → proof-tAb5_Dq4.mjs} +3 -3
- package/dist/{send-BFti0L3I.mjs → send-DzfFREML.mjs} +153 -40
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +38 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +1 -1
- package/dist/sig/mitra-receive-interop.test.mjs +4 -4
- package/dist/sig/mitra-send-interop.test.mjs +3 -3
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-interop.test.mjs +3 -3
- package/dist/sig/portable-key.test.mjs +5 -5
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/sig/tootik-request-interop.test.mjs +4 -4
- package/dist/sig/tootik-send-interop.test.mjs +3 -3
- package/dist/sig/verification.test.mjs +3 -3
- package/dist/{temporal-DlYL_raW.mjs → temporal-D9Nt8Fm4.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +1030 -412
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
- package/skills/fedify/SKILL.md +60 -1
|
@@ -7,15 +7,16 @@ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
|
7
7
|
import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
|
|
8
8
|
import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
|
|
9
9
|
import "../std__assert-DvMDRPes.mjs";
|
|
10
|
+
import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
|
|
10
11
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
11
|
-
import { m as signJsonLd } from "../ld-
|
|
12
|
-
import { s as signObject } from "../proof-
|
|
13
|
-
import { m as verifyRequestDetailed, u as signRequest } from "../http-
|
|
12
|
+
import { m as signJsonLd } from "../ld-C5JOBBFj.mjs";
|
|
13
|
+
import { s as signObject } from "../proof-tAb5_Dq4.mjs";
|
|
14
|
+
import { m as verifyRequestDetailed, u as signRequest } from "../http-DqOXkYbX.mjs";
|
|
14
15
|
import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
15
|
-
import { _ as coversDelivery, o as createFederation, v as forwardPortableInboxActivity, y as resolvePortableInboxForwardingOptions } from "../middleware-
|
|
16
|
+
import { M as observeOutboundBroadcast, _ as coversDelivery, o as createFederation, v as forwardPortableInboxActivity, y as resolvePortableInboxForwardingOptions } from "../middleware-BkRqMGPU.mjs";
|
|
16
17
|
import { Create, CryptographicKey, Follow, Note, Person, Tombstone } from "@fedify/vocab";
|
|
17
18
|
import { exportDidKey, formatIri, parseIri } from "@fedify/vocab-runtime";
|
|
18
|
-
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
19
|
+
import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
19
20
|
import { encodeBase64 } from "byte-encodings/base64";
|
|
20
21
|
//#region src/federation/portable-inbox.test.ts
|
|
21
22
|
const did = await exportDidKey(ed25519PublicKey.publicKey);
|
|
@@ -1531,5 +1532,429 @@ test("onRequestFinished covers portable ingress and recipient lookup failures",
|
|
|
1531
1532
|
error: failure
|
|
1532
1533
|
});
|
|
1533
1534
|
});
|
|
1535
|
+
function replicationResult(events) {
|
|
1536
|
+
assertEquals(events.map((event) => event.stage), ["started", "preparation-finished"]);
|
|
1537
|
+
assertEquals(events.map((event) => event.operation), ["portable-replication", "portable-replication"]);
|
|
1538
|
+
assertEquals(events[0].broadcastId, events[1].broadcastId);
|
|
1539
|
+
const event = events[1];
|
|
1540
|
+
assert(event.stage === "preparation-finished");
|
|
1541
|
+
return event.result;
|
|
1542
|
+
}
|
|
1543
|
+
test("portable replication observers distinguish disabled, unavailable, and unclaimed work", async (t) => {
|
|
1544
|
+
for (const scenario of [
|
|
1545
|
+
"disabled",
|
|
1546
|
+
"unavailable",
|
|
1547
|
+
"no-targets"
|
|
1548
|
+
]) await t.step(scenario, async () => {
|
|
1549
|
+
const memory = new MemoryKvStore();
|
|
1550
|
+
const kv = scenario === "unavailable" ? {
|
|
1551
|
+
get: (key) => memory.get(key),
|
|
1552
|
+
set: (key, value, options) => memory.set(key, value, options),
|
|
1553
|
+
delete: (key) => memory.delete(key),
|
|
1554
|
+
list: (prefix) => memory.list(prefix)
|
|
1555
|
+
} : memory;
|
|
1556
|
+
const queue = new RecordingQueue();
|
|
1557
|
+
const { federation } = setup({
|
|
1558
|
+
kv,
|
|
1559
|
+
queue,
|
|
1560
|
+
keyPairs: false,
|
|
1561
|
+
gateways: scenario === "no-targets" ? [LOCAL] : [LOCAL, GATEWAY2],
|
|
1562
|
+
options: { portableInboxForwarding: { maxTargets: scenario === "disabled" ? 0 : 10 } }
|
|
1563
|
+
});
|
|
1564
|
+
const events = [];
|
|
1565
|
+
const deliveries = [];
|
|
1566
|
+
const contexts = [];
|
|
1567
|
+
federation.setOutboundObservers({
|
|
1568
|
+
onDelivery(_ctx, event) {
|
|
1569
|
+
deliveries.push(event);
|
|
1570
|
+
},
|
|
1571
|
+
onBroadcast(ctx, event) {
|
|
1572
|
+
contexts.push(ctx);
|
|
1573
|
+
events.push(event);
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
assertEquals((await federation.fetch(post(inboxUrl(), await signedFollow()), { contextData: void 0 })).status, 202);
|
|
1577
|
+
assertEquals(replicationResult(events), {
|
|
1578
|
+
type: "skipped",
|
|
1579
|
+
reason: scenario === "disabled" ? "forwarding-disabled" : scenario === "unavailable" ? "forwarding-unavailable" : "no-forwarding-targets"
|
|
1580
|
+
});
|
|
1581
|
+
assertEquals(events[0].activity, {});
|
|
1582
|
+
assertEquals(events[1].activity.type, Follow.typeId.href);
|
|
1583
|
+
assert(events[1].activity.id?.startsWith("ap+ef61://"));
|
|
1584
|
+
assert(Object.isFrozen(events[1].activity));
|
|
1585
|
+
assertStrictEquals(contexts[0], contexts[1]);
|
|
1586
|
+
assertEquals(deliveries, []);
|
|
1587
|
+
assertEquals(queue.outbox.length, 0);
|
|
1588
|
+
});
|
|
1589
|
+
});
|
|
1590
|
+
test("portable replication observers preserve lazy enqueue, unsigned fallback, and failed claims", async (t) => {
|
|
1591
|
+
await t.step("readiness precedes acceptance without delaying earlier handoffs", async () => {
|
|
1592
|
+
const queue = new RecordingQueue();
|
|
1593
|
+
let accept;
|
|
1594
|
+
const acceptance = new Promise((resolve) => {
|
|
1595
|
+
accept = resolve;
|
|
1596
|
+
});
|
|
1597
|
+
queue.enqueue = (message) => {
|
|
1598
|
+
queue.messages.push(message);
|
|
1599
|
+
return queue.messages.length === 1 ? acceptance.then(() => {
|
|
1600
|
+
throw new Error("acceptance unknown");
|
|
1601
|
+
}) : Promise.resolve();
|
|
1602
|
+
};
|
|
1603
|
+
const { federation, reports } = setup({
|
|
1604
|
+
queue,
|
|
1605
|
+
keyPairs: false
|
|
1606
|
+
});
|
|
1607
|
+
const events = [];
|
|
1608
|
+
const deliveries = [];
|
|
1609
|
+
let ready;
|
|
1610
|
+
const prepared = new Promise((resolve) => {
|
|
1611
|
+
ready = resolve;
|
|
1612
|
+
});
|
|
1613
|
+
let release;
|
|
1614
|
+
const observerGate = new Promise((resolve) => {
|
|
1615
|
+
release = resolve;
|
|
1616
|
+
});
|
|
1617
|
+
federation.setOutboundObservers({
|
|
1618
|
+
onDelivery(_ctx, event) {
|
|
1619
|
+
deliveries.push(event);
|
|
1620
|
+
},
|
|
1621
|
+
async onBroadcast(_ctx, event) {
|
|
1622
|
+
events.push(event);
|
|
1623
|
+
if (event.stage === "preparation-finished") {
|
|
1624
|
+
ready();
|
|
1625
|
+
await observerGate;
|
|
1626
|
+
}
|
|
1627
|
+
throw new Error("recorder failed");
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1630
|
+
const json = await signedFollow();
|
|
1631
|
+
let settled = false;
|
|
1632
|
+
const pending = federation.fetch(post(inboxUrl(), json), { contextData: void 0 }).finally(() => {
|
|
1633
|
+
settled = true;
|
|
1634
|
+
});
|
|
1635
|
+
await prepared;
|
|
1636
|
+
assertEquals(queue.outbox.length, 1);
|
|
1637
|
+
assertFalse(settled);
|
|
1638
|
+
assertEquals(replicationResult(events), {
|
|
1639
|
+
type: "prepared",
|
|
1640
|
+
preparedFor: "delivery",
|
|
1641
|
+
destinationCount: 2
|
|
1642
|
+
});
|
|
1643
|
+
accept();
|
|
1644
|
+
release();
|
|
1645
|
+
assertEquals((await pending).status, 202);
|
|
1646
|
+
assertEquals(queue.outbox.length, 2);
|
|
1647
|
+
for (const message of queue.outbox) {
|
|
1648
|
+
assertEquals(message.activity, json);
|
|
1649
|
+
assertEquals(message.keys, []);
|
|
1650
|
+
assertFalse("broadcastId" in message);
|
|
1651
|
+
const plan = deliveries.find((event) => event.stage === "planned" && event.destinationInbox === message.inbox);
|
|
1652
|
+
assert(plan);
|
|
1653
|
+
assertEquals(message.outbound, {
|
|
1654
|
+
broadcast: plan.broadcast,
|
|
1655
|
+
deliveryId: plan.deliveryId,
|
|
1656
|
+
destinationInbox: plan.destinationInbox,
|
|
1657
|
+
portableInbox: plan.portableInbox
|
|
1658
|
+
});
|
|
1659
|
+
assertEquals(JSON.parse(JSON.stringify(message.outbound)), message.outbound);
|
|
1660
|
+
assertEquals(structuredClone(message.outbound), message.outbound);
|
|
1661
|
+
}
|
|
1662
|
+
assertEquals(deliveries.filter((event) => event.stage === "planned").length, 2);
|
|
1663
|
+
const handoffs = deliveries.filter((event) => event.stage === "handoff");
|
|
1664
|
+
assertEquals(handoffs.length, 2);
|
|
1665
|
+
assertEquals(handoffs.filter((event) => event.stage === "handoff" && event.result.type === "enqueued").length, 1);
|
|
1666
|
+
assertEquals(handoffs.filter((event) => event.stage === "handoff" && event.result.type === "enqueue-failed" && event.result.acceptance === "unknown").length, 1);
|
|
1667
|
+
for (const event of deliveries) {
|
|
1668
|
+
assertEquals(event.portableInbox, `ap+ef61://${did}/users/alice/inbox`);
|
|
1669
|
+
if (event.stage === "planned") assertEquals(event.mode, "portable-replication");
|
|
1670
|
+
}
|
|
1671
|
+
assertEquals(events.length, 2);
|
|
1672
|
+
const source = events[0].provenance.inboxRequest;
|
|
1673
|
+
assert(source);
|
|
1674
|
+
assertEquals(source.inboxRequestId, reports[0].inboxRequestId);
|
|
1675
|
+
assertEquals(source.requestId, reports[0].requestId);
|
|
1676
|
+
assertEquals(source.context.origin, LOCAL);
|
|
1677
|
+
for (const message of queue.outbox) assertEquals(message.provenance?.inboxRequest, source);
|
|
1678
|
+
for (const event of [...events, ...deliveries]) assertEquals(event.provenance.inboxRequest, source);
|
|
1679
|
+
federation.setOutboundObservers({
|
|
1680
|
+
onDelivery(_ctx, event) {
|
|
1681
|
+
deliveries.push(event);
|
|
1682
|
+
},
|
|
1683
|
+
onBroadcast(_ctx, event) {
|
|
1684
|
+
events.push(event);
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
assertEquals((await federation.fetch(post(inboxUrl(), json), { contextData: void 0 })).status, 202);
|
|
1688
|
+
assertEquals(replicationResult(events.slice(2)), {
|
|
1689
|
+
type: "skipped",
|
|
1690
|
+
reason: "no-forwarding-targets"
|
|
1691
|
+
});
|
|
1692
|
+
assert(events[0].broadcastId !== events[2].broadcastId);
|
|
1693
|
+
assertEquals(queue.outbox.length, 2);
|
|
1694
|
+
});
|
|
1695
|
+
await t.step("partial claim failure is incomplete planning and preserves ingress acceptance", async () => {
|
|
1696
|
+
const memory = new MemoryKvStore();
|
|
1697
|
+
const cas = memory.cas.bind(memory);
|
|
1698
|
+
const failure = { failed: "second claim" };
|
|
1699
|
+
let claims = 0;
|
|
1700
|
+
memory.cas = (...args) => {
|
|
1701
|
+
if (args[0][1] === "portableInboxForwarding" && ++claims === 2) return Promise.reject(failure);
|
|
1702
|
+
return cas(...args);
|
|
1703
|
+
};
|
|
1704
|
+
const queue = new RecordingQueue();
|
|
1705
|
+
const { federation } = setup({
|
|
1706
|
+
kv: memory,
|
|
1707
|
+
queue,
|
|
1708
|
+
keyPairs: false
|
|
1709
|
+
});
|
|
1710
|
+
const events = [];
|
|
1711
|
+
const deliveries = [];
|
|
1712
|
+
federation.setOutboundObservers({
|
|
1713
|
+
onDelivery(_ctx, event) {
|
|
1714
|
+
deliveries.push(event);
|
|
1715
|
+
},
|
|
1716
|
+
onBroadcast(_ctx, event) {
|
|
1717
|
+
events.push(event);
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
assertEquals((await federation.fetch(post(inboxUrl(), await signedFollow()), { contextData: void 0 })).status, 202);
|
|
1721
|
+
assertEquals(replicationResult(events), {
|
|
1722
|
+
type: "failed",
|
|
1723
|
+
failure: {
|
|
1724
|
+
kind: "local",
|
|
1725
|
+
phase: "destination-planning",
|
|
1726
|
+
error: failure
|
|
1727
|
+
},
|
|
1728
|
+
planning: "incomplete",
|
|
1729
|
+
plannedDestinationCount: 1
|
|
1730
|
+
});
|
|
1731
|
+
assertEquals(deliveries.map((event) => event.stage), ["planned"]);
|
|
1732
|
+
assertEquals(queue.outbox.length, 0);
|
|
1733
|
+
});
|
|
1734
|
+
await t.step("a handled key failure prepares unsigned queue messages", async () => {
|
|
1735
|
+
const queue = new RecordingQueue();
|
|
1736
|
+
const parameters = forwardingParameters({
|
|
1737
|
+
outboxQueue: queue,
|
|
1738
|
+
getKeys: () => Promise.reject(/* @__PURE__ */ new Error("no keys"))
|
|
1739
|
+
});
|
|
1740
|
+
const ctx = createFederation({ kv: parameters.kv }).createContext(new URL(LOCAL));
|
|
1741
|
+
const events = [];
|
|
1742
|
+
const deliveries = [];
|
|
1743
|
+
await observeOutboundBroadcast(ctx, {
|
|
1744
|
+
onDelivery(_ctx, event) {
|
|
1745
|
+
deliveries.push(event);
|
|
1746
|
+
},
|
|
1747
|
+
onBroadcast: (_ctx, event) => {
|
|
1748
|
+
events.push(event);
|
|
1749
|
+
}
|
|
1750
|
+
}, (observation) => forwardPortableInboxActivity(parameters, observation), "portable-replication");
|
|
1751
|
+
assertEquals(replicationResult(events), {
|
|
1752
|
+
type: "prepared",
|
|
1753
|
+
preparedFor: "delivery",
|
|
1754
|
+
destinationCount: 1
|
|
1755
|
+
});
|
|
1756
|
+
assertEquals(deliveries.map((event) => event.stage), ["planned", "handoff"]);
|
|
1757
|
+
const accepted = deliveries[1];
|
|
1758
|
+
assert(accepted.stage === "handoff" && accepted.result.type === "enqueued");
|
|
1759
|
+
assertEquals(queue.outbox[0].keys, []);
|
|
1760
|
+
assertStrictEquals(queue.outbox[0].activity, parameters.activity);
|
|
1761
|
+
});
|
|
1762
|
+
});
|
|
1763
|
+
test("portable replication attempts settle independently after the forwarding deadline", async () => {
|
|
1764
|
+
const [tracerProvider, exporter] = createTestTracerProvider();
|
|
1765
|
+
let release;
|
|
1766
|
+
const gate = new Promise((resolve) => {
|
|
1767
|
+
release = resolve;
|
|
1768
|
+
});
|
|
1769
|
+
let fetched;
|
|
1770
|
+
const fetching = new Promise((resolve) => {
|
|
1771
|
+
fetched = resolve;
|
|
1772
|
+
});
|
|
1773
|
+
let failed;
|
|
1774
|
+
const failureReported = new Promise((resolve) => {
|
|
1775
|
+
failed = resolve;
|
|
1776
|
+
});
|
|
1777
|
+
let succeeded;
|
|
1778
|
+
const successReported = new Promise((resolve) => {
|
|
1779
|
+
succeeded = resolve;
|
|
1780
|
+
});
|
|
1781
|
+
let ended;
|
|
1782
|
+
const concreteSendsEnded = new Promise((resolve) => {
|
|
1783
|
+
ended = resolve;
|
|
1784
|
+
});
|
|
1785
|
+
const exportSpans = exporter.export.bind(exporter);
|
|
1786
|
+
exporter.export = (spans, callback) => {
|
|
1787
|
+
exportSpans(spans, callback);
|
|
1788
|
+
if (exporter.getSpans("activitypub.send_activity").length === 2) ended();
|
|
1789
|
+
};
|
|
1790
|
+
const attempts = [];
|
|
1791
|
+
const submitted = [];
|
|
1792
|
+
esm_default.spyGlobal().post(`begin:${GATEWAY2}/`, (call) => {
|
|
1793
|
+
submitted.push(JSON.parse(String(call.options.body)));
|
|
1794
|
+
return 503;
|
|
1795
|
+
}).post(`begin:${GATEWAY3}/`, async (call) => {
|
|
1796
|
+
submitted.push(JSON.parse(String(call.options.body)));
|
|
1797
|
+
fetched();
|
|
1798
|
+
await gate;
|
|
1799
|
+
return 202;
|
|
1800
|
+
});
|
|
1801
|
+
try {
|
|
1802
|
+
const { federation, reports } = setup({
|
|
1803
|
+
keyPairs: false,
|
|
1804
|
+
options: {
|
|
1805
|
+
allowPrivateAddress: true,
|
|
1806
|
+
tracerProvider,
|
|
1807
|
+
portableInboxForwarding: { deadline: { milliseconds: 0 } }
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1810
|
+
let replacements = 0;
|
|
1811
|
+
federation.setOutboundObservers({
|
|
1812
|
+
senderPayload: true,
|
|
1813
|
+
onAttempt(_ctx, report) {
|
|
1814
|
+
attempts.push(report);
|
|
1815
|
+
if (report.outcome.type === "failed") failed();
|
|
1816
|
+
else succeeded();
|
|
1817
|
+
throw new Error("replication observer failure must be isolated");
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
const json = await signedFollow();
|
|
1821
|
+
assertEquals((await federation.fetch(post(inboxUrl(), json), { contextData: void 0 })).status, 202);
|
|
1822
|
+
await Promise.all([fetching, failureReported]);
|
|
1823
|
+
assertEquals(attempts.length, 1);
|
|
1824
|
+
assertEquals(attempts[0].outcome.type, "failed");
|
|
1825
|
+
assert(attempts[0].outcome.response.type === "final");
|
|
1826
|
+
assertEquals(attempts[0].outcome.response.metadata.status, 503);
|
|
1827
|
+
assertEquals(attempts[0].httpExchangeCount, 1);
|
|
1828
|
+
federation.setOutboundObservers({ onAttempt() {
|
|
1829
|
+
replacements++;
|
|
1830
|
+
} });
|
|
1831
|
+
release();
|
|
1832
|
+
await Promise.all([successReported, concreteSendsEnded]);
|
|
1833
|
+
assertEquals(attempts.length, 2);
|
|
1834
|
+
assertEquals(attempts[1].outcome.type, "succeeded");
|
|
1835
|
+
assert(attempts[1].outcome.response.type === "final");
|
|
1836
|
+
assertEquals(attempts[1].outcome.response.metadata.status, 202);
|
|
1837
|
+
assertEquals(attempts[1].httpExchangeCount, 1);
|
|
1838
|
+
assertEquals(replacements, 0);
|
|
1839
|
+
assert(attempts[0].attemptId !== attempts[1].attemptId);
|
|
1840
|
+
assert(attempts[0].deliveryId !== attempts[1].deliveryId);
|
|
1841
|
+
assertEquals(attempts[0].broadcast, attempts[1].broadcast);
|
|
1842
|
+
for (const report of attempts) {
|
|
1843
|
+
assertEquals(report.senderPayload.stage, "sender-payload");
|
|
1844
|
+
if (report.senderPayload.type === "unavailable") assertEquals(report.senderPayload.reason, "privacy-filter-unavailable");
|
|
1845
|
+
else {
|
|
1846
|
+
assertEquals(report.senderPayload.representation, "privacy-projection");
|
|
1847
|
+
assert(Object.isFrozen(report.senderPayload.value));
|
|
1848
|
+
}
|
|
1849
|
+
assertEquals(report.destinationInbox, report.inboxUrl);
|
|
1850
|
+
assertEquals(report.portableInbox, `ap+ef61://${did}/users/alice/inbox`);
|
|
1851
|
+
assertEquals(report.provenance.inboxRequest?.inboxRequestId, reports[0].inboxRequestId);
|
|
1852
|
+
assert(report.provenance.inboxRequest);
|
|
1853
|
+
const span = exporter.spans.find((span) => span.spanContext().spanId === report.trace?.spanId);
|
|
1854
|
+
assert(span);
|
|
1855
|
+
assertEquals(span.name, "activitypub.send_activity");
|
|
1856
|
+
}
|
|
1857
|
+
assertEquals(submitted, [json, json]);
|
|
1858
|
+
assertEquals((await federation.fetch(post(inboxUrl(), json), { contextData: void 0 })).status, 202);
|
|
1859
|
+
assertEquals(submitted.length, 2);
|
|
1860
|
+
assertEquals(replacements, 0);
|
|
1861
|
+
} finally {
|
|
1862
|
+
release();
|
|
1863
|
+
esm_default.hardReset();
|
|
1864
|
+
await tracerProvider.shutdown();
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
test("portable replication observers retain preparation after the forwarding deadline", async (t) => {
|
|
1868
|
+
for (const failed of [false, true]) await t.step(failed ? "late preparation failure" : "late unsigned readiness", async () => {
|
|
1869
|
+
let release;
|
|
1870
|
+
const keyGate = new Promise((resolve) => {
|
|
1871
|
+
release = resolve;
|
|
1872
|
+
});
|
|
1873
|
+
const [tracerProvider, exporter] = createTestTracerProvider();
|
|
1874
|
+
let ended;
|
|
1875
|
+
const concreteSendEnded = new Promise((resolve) => {
|
|
1876
|
+
ended = resolve;
|
|
1877
|
+
});
|
|
1878
|
+
const exportSpans = exporter.export.bind(exporter);
|
|
1879
|
+
exporter.export = (spans, callback) => {
|
|
1880
|
+
exportSpans(spans, callback);
|
|
1881
|
+
if (spans.some((span) => span.name === "activitypub.send_activity")) ended();
|
|
1882
|
+
};
|
|
1883
|
+
const failure = { failed: "late local preparation" };
|
|
1884
|
+
const parameters = forwardingParameters({
|
|
1885
|
+
tracerProvider,
|
|
1886
|
+
options: resolvePortableInboxForwardingOptions({ deadline: { milliseconds: 0 } }),
|
|
1887
|
+
getKeys: async () => {
|
|
1888
|
+
await keyGate;
|
|
1889
|
+
if (failed) Object.defineProperty(parameters.activityId, "href", { get() {
|
|
1890
|
+
throw failure;
|
|
1891
|
+
} });
|
|
1892
|
+
return [];
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
const ctx = createFederation({ kv: parameters.kv }).createContext(new URL(LOCAL));
|
|
1896
|
+
const events = [];
|
|
1897
|
+
const deliveries = [];
|
|
1898
|
+
let finished;
|
|
1899
|
+
const preparation = new Promise((resolve) => {
|
|
1900
|
+
finished = resolve;
|
|
1901
|
+
});
|
|
1902
|
+
let fetched;
|
|
1903
|
+
const fetching = new Promise((resolve) => {
|
|
1904
|
+
fetched = resolve;
|
|
1905
|
+
});
|
|
1906
|
+
esm_default.spyGlobal().post(`begin:${GATEWAY2}/`, () => {
|
|
1907
|
+
fetched();
|
|
1908
|
+
return new Response(null, { status: 503 });
|
|
1909
|
+
});
|
|
1910
|
+
try {
|
|
1911
|
+
assertEquals((await observeOutboundBroadcast(ctx, {
|
|
1912
|
+
onDelivery(_ctx, event) {
|
|
1913
|
+
deliveries.push(event);
|
|
1914
|
+
},
|
|
1915
|
+
onBroadcast: (_ctx, event) => {
|
|
1916
|
+
events.push(event);
|
|
1917
|
+
if (event.stage === "preparation-finished") finished();
|
|
1918
|
+
}
|
|
1919
|
+
}, (observation) => forwardPortableInboxActivity(parameters, observation), "portable-replication")).length, 1);
|
|
1920
|
+
assertEquals(events.map((event) => event.stage), ["started"]);
|
|
1921
|
+
release();
|
|
1922
|
+
await preparation;
|
|
1923
|
+
assertEquals(deliveries.map((event) => event.stage), ["planned", failed ? "preparation-failed" : "handoff"]);
|
|
1924
|
+
assertEquals(deliveries[0].deliveryId, deliveries[1].deliveryId);
|
|
1925
|
+
if (failed) {
|
|
1926
|
+
const failedDelivery = deliveries[1];
|
|
1927
|
+
assert(failedDelivery.stage === "preparation-failed");
|
|
1928
|
+
assertStrictEquals(failedDelivery.failure.error, failure);
|
|
1929
|
+
assertEquals(replicationResult(events), {
|
|
1930
|
+
type: "failed",
|
|
1931
|
+
failure: {
|
|
1932
|
+
kind: "local",
|
|
1933
|
+
phase: "request-preparation",
|
|
1934
|
+
error: failure
|
|
1935
|
+
},
|
|
1936
|
+
planning: "complete",
|
|
1937
|
+
plannedDestinationCount: 1
|
|
1938
|
+
});
|
|
1939
|
+
assertEquals(esm_default.callHistory.calls().length, 0);
|
|
1940
|
+
} else {
|
|
1941
|
+
assertEquals(replicationResult(events), {
|
|
1942
|
+
type: "prepared",
|
|
1943
|
+
preparedFor: "delivery",
|
|
1944
|
+
destinationCount: 1
|
|
1945
|
+
});
|
|
1946
|
+
await fetching;
|
|
1947
|
+
await concreteSendEnded;
|
|
1948
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1949
|
+
assertEquals(events.length, 2);
|
|
1950
|
+
assertEquals(await forwardPortableInboxActivity(parameters), []);
|
|
1951
|
+
}
|
|
1952
|
+
} finally {
|
|
1953
|
+
release();
|
|
1954
|
+
esm_default.hardReset();
|
|
1955
|
+
await tracerProvider.shutdown();
|
|
1956
|
+
}
|
|
1957
|
+
});
|
|
1958
|
+
});
|
|
1534
1959
|
//#endregion
|
|
1535
1960
|
export {};
|
|
@@ -7,7 +7,7 @@ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
|
|
|
7
7
|
import "../std__assert-DvMDRPes.mjs";
|
|
8
8
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
9
9
|
import { r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
|
|
10
|
-
import { o as createFederation } from "../middleware-
|
|
10
|
+
import { o as createFederation } from "../middleware-BkRqMGPU.mjs";
|
|
11
11
|
import { Note, Object as Object$1, Person } from "@fedify/vocab";
|
|
12
12
|
import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
|
|
13
13
|
import { test } from "@fedify/fixture";
|
|
@@ -6,9 +6,9 @@ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
|
6
6
|
import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
|
|
7
7
|
import "../std__assert-DvMDRPes.mjs";
|
|
8
8
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
9
|
-
import { s as signObject } from "../proof-
|
|
9
|
+
import { s as signObject } from "../proof-tAb5_Dq4.mjs";
|
|
10
10
|
import { n as ed25519PrivateKey, r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
|
|
11
|
-
import { o as createFederation } from "../middleware-
|
|
11
|
+
import { o as createFederation } from "../middleware-BkRqMGPU.mjs";
|
|
12
12
|
import { Create, Note } from "@fedify/vocab";
|
|
13
13
|
import { exportDidKey, formatIri, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
|
|
14
14
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
@@ -8,10 +8,10 @@ import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
|
|
|
8
8
|
import "../std__assert-DvMDRPes.mjs";
|
|
9
9
|
import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
|
|
10
10
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
11
|
-
import { d as verifyPortableObjectProof, s as signObject } from "../proof-
|
|
12
|
-
import { u as signRequest } from "../http-
|
|
11
|
+
import { d as verifyPortableObjectProof, s as signObject } from "../proof-tAb5_Dq4.mjs";
|
|
12
|
+
import { u as signRequest } from "../http-DqOXkYbX.mjs";
|
|
13
13
|
import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
14
|
-
import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-
|
|
14
|
+
import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-BkRqMGPU.mjs";
|
|
15
15
|
import { configure, reset } from "@logtape/logtape";
|
|
16
16
|
import { Collection, Create, Note, Object as Object$1, PUBLIC_COLLECTION, Person, Tombstone, lookupObject } from "@fedify/vocab";
|
|
17
17
|
import { exportDidKey, formatIri, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
|
|
@@ -11,10 +11,10 @@ import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
|
|
|
11
11
|
import "../std__assert-DvMDRPes.mjs";
|
|
12
12
|
import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
|
|
13
13
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
14
|
-
import { p as verifyRequest } from "../http-
|
|
14
|
+
import { p as verifyRequest } from "../http-DqOXkYbX.mjs";
|
|
15
15
|
import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
|
|
16
|
-
import { t as doesActorOwnKey } from "../owner-
|
|
17
|
-
import { i as sendActivity, n as extractInboxes, r as fetchWithValidatedRedirects, t as SendActivityError } from "../send-
|
|
16
|
+
import { t as doesActorOwnKey } from "../owner-CKlRIkeF.mjs";
|
|
17
|
+
import { i as sendActivity, n as extractInboxes, r as fetchWithValidatedRedirects, t as SendActivityError } from "../send-DzfFREML.mjs";
|
|
18
18
|
import { t as FedifySpanExporter } from "../exporter-DVG68ozH.mjs";
|
|
19
19
|
import { configure, getLogger, reset, withContext } from "@logtape/logtape";
|
|
20
20
|
import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
|
|
@@ -1376,5 +1376,46 @@ function recordHops(respond) {
|
|
|
1376
1376
|
}
|
|
1377
1377
|
};
|
|
1378
1378
|
}
|
|
1379
|
+
test("sendActivity() bounds a stalled diagnostic preview without changing HTTP failure", async () => {
|
|
1380
|
+
const inbox = new URL("https://example.com/lifecycle/stalled-error");
|
|
1381
|
+
let reads = 0;
|
|
1382
|
+
let cancels = 0;
|
|
1383
|
+
let closed = 0;
|
|
1384
|
+
let submissions = 0;
|
|
1385
|
+
const body = new ReadableStream({
|
|
1386
|
+
pull(controller) {
|
|
1387
|
+
if (reads++ === 0) controller.enqueue(new TextEncoder().encode("partial error"));
|
|
1388
|
+
},
|
|
1389
|
+
cancel() {
|
|
1390
|
+
cancels++;
|
|
1391
|
+
return new Promise(() => {});
|
|
1392
|
+
}
|
|
1393
|
+
}, { highWaterMark: 0 });
|
|
1394
|
+
const error = await assertRejects(() => sendActivity({
|
|
1395
|
+
activity: lifecycleActivity,
|
|
1396
|
+
keys: unsignedKeys,
|
|
1397
|
+
inbox
|
|
1398
|
+
}, { openHop: () => Promise.resolve({
|
|
1399
|
+
url: inbox,
|
|
1400
|
+
fetch: () => {
|
|
1401
|
+
submissions++;
|
|
1402
|
+
return Promise.resolve(new Response(body, {
|
|
1403
|
+
status: 503,
|
|
1404
|
+
headers: { "Retry-After": "1" }
|
|
1405
|
+
}));
|
|
1406
|
+
},
|
|
1407
|
+
close: () => {
|
|
1408
|
+
closed++;
|
|
1409
|
+
assertFalse(body.locked);
|
|
1410
|
+
return Promise.resolve();
|
|
1411
|
+
}
|
|
1412
|
+
}) }), SendActivityError);
|
|
1413
|
+
assertEquals(error.statusCode, 503);
|
|
1414
|
+
assertEquals(error.responseBody, "partial error… (timed out)");
|
|
1415
|
+
assertEquals(error.responseHeaders.get("Retry-After"), "1");
|
|
1416
|
+
assertEquals(submissions, 1);
|
|
1417
|
+
assertEquals(cancels, 1);
|
|
1418
|
+
assertEquals(closed, 1);
|
|
1419
|
+
});
|
|
1379
1420
|
//#endregion
|
|
1380
1421
|
export {};
|
|
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
|
|
5
5
|
import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-IG3pHfvZ.mjs";
|
|
6
6
|
import "../../testing-_9kJHvqh.mjs";
|
|
7
|
-
import { o as createFederation } from "../../middleware-
|
|
7
|
+
import { o as createFederation } from "../../middleware-BkRqMGPU.mjs";
|
|
8
8
|
import { n as ParallelMessageQueue } from "../../mq-eF96QxUT.mjs";
|
|
9
9
|
import { configure, reset } from "@logtape/logtape";
|
|
10
10
|
import { delay } from "es-toolkit";
|
|
@@ -4,8 +4,8 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
|
|
5
5
|
import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-IG3pHfvZ.mjs";
|
|
6
6
|
import "../../testing-_9kJHvqh.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { r as createFederationBuilder } from "../../builder-B2H8GsyI.mjs";
|
|
8
|
+
import { o as createFederation } from "../../middleware-BkRqMGPU.mjs";
|
|
9
9
|
import { t as TaskCodec } from "../../codec-C6rTrpIp.mjs";
|
|
10
10
|
import { t as InProcessMessageQueue } from "../../mq-eF96QxUT.mjs";
|
|
11
11
|
import { W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
|
|
5
5
|
import "../std__assert-DvMDRPes.mjs";
|
|
6
6
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
7
|
-
import { t as hasMalformedKnownTemporalLiteral } from "../temporal-
|
|
7
|
+
import { t as hasMalformedKnownTemporalLiteral } from "../temporal-D9Nt8Fm4.mjs";
|
|
8
8
|
import { test } from "@fedify/fixture";
|
|
9
9
|
//#region src/federation/temporal.test.ts
|
|
10
10
|
test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
|
|
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
|
6
6
|
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
7
7
|
import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
|
|
8
8
|
import "../std__assert-DvMDRPes.mjs";
|
|
9
|
-
import { o as createFederation, s as handleWebFinger } from "../middleware-
|
|
9
|
+
import { o as createFederation, s as handleWebFinger } from "../middleware-BkRqMGPU.mjs";
|
|
10
10
|
import { Image, Link, Person, Tombstone } from "@fedify/vocab";
|
|
11
11
|
import { parseIri } from "@fedify/vocab-runtime";
|
|
12
12
|
import { createTestMeterProvider, test } from "@fedify/fixture";
|