keri-ts 2.2.1 → 2.2.2

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 (107) hide show
  1. package/esm/keri/npm/src/_dnt.polyfills.js +1 -127
  2. package/esm/keri/npm/src/acdc/dhking.js +6 -1
  3. package/esm/keri/npm/src/acdc/salting.js +18 -0
  4. package/esm/keri/npm/src/acdc/scheming.js +31 -18
  5. package/esm/keri/npm/src/acdc/unblinding.js +112 -6
  6. package/esm/keri/npm/src/app/delivery/forwarding.js +9 -0
  7. package/esm/keri/npm/src/app/delivery/respondant.js +21 -0
  8. package/esm/keri/npm/src/app/identity/habbing.js +4 -1
  9. package/esm/keri/npm/src/app/identity/keeping.js +38 -1
  10. package/esm/keri/npm/src/app/ipex/group-issuance.js +9 -6
  11. package/esm/keri/npm/src/app/ipex/ipex-building.js +2 -1
  12. package/esm/keri/npm/src/app/ipex/ipex-discloser.js +32 -8
  13. package/esm/keri/npm/src/app/ipex/issuance.js +131 -43
  14. package/esm/keri/npm/src/app/ipex/pending-presentations.js +50 -0
  15. package/esm/keri/npm/src/app/ipex/presenting.js +36 -1
  16. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +140 -36
  17. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +37 -9
  18. package/esm/keri/npm/src/app/mailbox/mailboxing.js +9 -3
  19. package/esm/keri/npm/src/app/multisig/counseling.js +76 -3
  20. package/esm/keri/npm/src/app/multisig/event-forwarding.js +27 -0
  21. package/esm/keri/npm/src/app/multisig/grouping.js +120 -1
  22. package/esm/keri/npm/src/app/multisig/proposals.js +1 -1
  23. package/esm/keri/npm/src/app/runtime/agent-runtime.js +43 -23
  24. package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +59 -25
  25. package/esm/keri/npm/src/app/runtime/operation-owners.js +1 -0
  26. package/esm/keri/npm/src/app/runtime/signer-conditions.js +51 -0
  27. package/esm/keri/npm/src/app/transport/cesr-egress.js +6 -0
  28. package/esm/keri/npm/src/core/errors.js +91 -3
  29. package/esm/keri/npm/src/core/json-schema-meta.js +688 -0
  30. package/esm/keri/npm/src/core/json-schema.js +147 -0
  31. package/esm/keri/npm/src/core/kel/eventing.js +20 -2
  32. package/esm/keri/npm/src/core/scheming.js +27 -26
  33. package/esm/keri/npm/src/db/replay/tel-replay.js +6 -0
  34. package/esm/keri/npm/src/db/store-lock.js +12 -0
  35. package/esm/keri/npm/src/runtime/index.js +8 -7
  36. package/esm/keri/npm/src/runtime/web/output-barrier.js +9 -0
  37. package/esm/keri/npm/src/version.js +2 -2
  38. package/package.json +2 -2
  39. package/types/_dnt.polyfills.d.ts +0 -99
  40. package/types/_dnt.polyfills.d.ts.map +1 -1
  41. package/types/acdc/dhking.d.ts.map +1 -1
  42. package/types/acdc/salting.d.ts +2 -0
  43. package/types/acdc/salting.d.ts.map +1 -1
  44. package/types/acdc/scheming.d.ts +2 -0
  45. package/types/acdc/scheming.d.ts.map +1 -1
  46. package/types/acdc/unblinding.d.ts +16 -5
  47. package/types/acdc/unblinding.d.ts.map +1 -1
  48. package/types/app/delivery/forwarding.d.ts +1 -0
  49. package/types/app/delivery/forwarding.d.ts.map +1 -1
  50. package/types/app/delivery/respondant.d.ts +7 -0
  51. package/types/app/delivery/respondant.d.ts.map +1 -1
  52. package/types/app/identity/habbing.d.ts +10 -1
  53. package/types/app/identity/habbing.d.ts.map +1 -1
  54. package/types/app/identity/keeping.d.ts +18 -0
  55. package/types/app/identity/keeping.d.ts.map +1 -1
  56. package/types/app/ipex/group-issuance.d.ts.map +1 -1
  57. package/types/app/ipex/ipex-building.d.ts.map +1 -1
  58. package/types/app/ipex/ipex-discloser.d.ts +31 -19
  59. package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
  60. package/types/app/ipex/issuance.d.ts +41 -10
  61. package/types/app/ipex/issuance.d.ts.map +1 -1
  62. package/types/app/ipex/pending-presentations.d.ts +62 -1
  63. package/types/app/ipex/pending-presentations.d.ts.map +1 -1
  64. package/types/app/ipex/presenting.d.ts +39 -4
  65. package/types/app/ipex/presenting.d.ts.map +1 -1
  66. package/types/app/ipex/unblinding-workflow.d.ts +46 -12
  67. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
  68. package/types/app/mailbox/mailbox-poller.d.ts +11 -2
  69. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  70. package/types/app/mailbox/mailboxing.d.ts +4 -1
  71. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  72. package/types/app/multisig/counseling.d.ts +22 -1
  73. package/types/app/multisig/counseling.d.ts.map +1 -1
  74. package/types/app/multisig/event-forwarding.d.ts +6 -0
  75. package/types/app/multisig/event-forwarding.d.ts.map +1 -0
  76. package/types/app/multisig/grouping.d.ts +26 -0
  77. package/types/app/multisig/grouping.d.ts.map +1 -1
  78. package/types/app/multisig/proposals.d.ts +3 -2
  79. package/types/app/multisig/proposals.d.ts.map +1 -1
  80. package/types/app/multisig/proposing.d.ts +8 -1
  81. package/types/app/multisig/proposing.d.ts.map +1 -1
  82. package/types/app/runtime/agent-runtime.d.ts +3 -0
  83. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  84. package/types/app/runtime/ipex-auxiliaries.d.ts +8 -3
  85. package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -1
  86. package/types/app/runtime/operation-owners.d.ts.map +1 -1
  87. package/types/app/runtime/signer-conditions.d.ts +32 -0
  88. package/types/app/runtime/signer-conditions.d.ts.map +1 -0
  89. package/types/app/transport/cesr-egress.d.ts +1 -0
  90. package/types/app/transport/cesr-egress.d.ts.map +1 -1
  91. package/types/core/errors.d.ts +20 -1
  92. package/types/core/errors.d.ts.map +1 -1
  93. package/types/core/json-schema-meta.d.ts +19 -0
  94. package/types/core/json-schema-meta.d.ts.map +1 -0
  95. package/types/core/json-schema.d.ts +29 -0
  96. package/types/core/json-schema.d.ts.map +1 -0
  97. package/types/core/scheming.d.ts +1 -0
  98. package/types/core/scheming.d.ts.map +1 -1
  99. package/types/db/replay/tel-replay.d.ts +1 -0
  100. package/types/db/replay/tel-replay.d.ts.map +1 -1
  101. package/types/db/store-lock.d.ts +2 -0
  102. package/types/db/store-lock.d.ts.map +1 -1
  103. package/types/runtime/index.d.ts +10 -9
  104. package/types/runtime/index.d.ts.map +1 -1
  105. package/types/runtime/web/output-barrier.d.ts +1 -0
  106. package/types/runtime/web/output-barrier.d.ts.map +1 -1
  107. package/types/version.d.ts +2 -2
@@ -0,0 +1,27 @@
1
+ import { acceptedEventReplayMessage } from "../identity/habbing.js";
2
+ import { localGroupMember } from "./group-members.js";
3
+ import { failedGroupDelivery } from "./multisig-messaging.js";
4
+ /** The Counselor's forwarding port over a Habery and its Poster; the Poster's delivery worker must run. */
5
+ export function groupEventForwarding(hby, postman) {
6
+ return {
7
+ *forward(event, members) {
8
+ const { serder } = acceptedEventReplayMessage(hby, event.pre, event.sn);
9
+ const { message } = acceptedEventReplayMessage(hby, event.pre, event.sn, serder.pvrsn);
10
+ const sender = localGroupMember(hby, event.pre);
11
+ const outcomes = [];
12
+ for (const recipient of members) {
13
+ const ref = { recipient, exnSaid: event.said, payloadSaid: event.said };
14
+ try {
15
+ const outcome = yield* postman.wait(postman.send({ src: sender.pre, dest: recipient, topic: "/replay", msg: message }, { notify: true }));
16
+ outcomes.push(outcome.kin === "failed"
17
+ ? failedGroupDelivery(ref, outcome.error)
18
+ : { ...ref, outcome: "delivered", deliveries: [...outcome.deliveries] });
19
+ }
20
+ catch (error) {
21
+ outcomes.push(failedGroupDelivery(ref, error));
22
+ }
23
+ }
24
+ return outcomes;
25
+ },
26
+ };
27
+ }
@@ -12,12 +12,13 @@ import { validateRip, validateUpdate } from "../../acdc/regeventing.js";
12
12
  import { ValidationError } from "../../core/errors.js";
13
13
  import { exchange, isIpexVerb, isV2RegistryRoute, multisigChildField, nestedIpexRoute, nestedMultisigIlks, } from "../../core/exchange/protocol-exchanging.js";
14
14
  import { Kevery } from "../../core/kel/eventing.js";
15
+ import { Kever } from "../../core/kel/kever.js";
15
16
  import { BasicReplyRouteHandler, Revery, Router } from "../../core/routing.js";
16
17
  import { Exchanger } from "../exchanges/exchanging.js";
17
18
  import { IpexV2Handler } from "../ipex/ipex-v2.js";
18
19
  import { dispatchEnvelope, envelopeFromFrame } from "../protocol/parsering.js";
19
20
  import { abortable, rethrowCleanupFailure } from "../runtime/abortable.js";
20
- import { groupSigningMembers, localIpexContribution } from "./group-members.js";
21
+ import { groupSigningMembers, isLocalGroupHab, localIpexContribution } from "./group-members.js";
21
22
  import { multisigEmbeddedSection, multisigPayloadSaid } from "./multisig-messaging.js";
22
23
  /** KERIpy multisig route constants. */
23
24
  export const MULTISIG_ICP_ROUTE = "/multisig/icp";
@@ -579,6 +580,14 @@ export class Multiplexor {
579
580
  else
580
581
  yield* this.processApproved(nests);
581
582
  }
583
+ else if (!unreplayed && this.alreadyComplete(serder, nests, route)) {
584
+ // Consent only adds this member's signature; an event the other members' signatures already complete needs
585
+ // none, so a member that only receives it takes it on receipt instead of waiting for an approval it need not give.
586
+ if (serder.pvrsn.major === 1)
587
+ yield* this.processLegacyApproved(serder);
588
+ else
589
+ yield* this.processApproved(nests);
590
+ }
582
591
  else if (serder.pvrsn.major === 2 || firstSubmission) {
583
592
  // Legacy approval polling consumes its first notice; repeated v1 notices would re-propose it.
584
593
  this.notifier?.add(firstSubmission ? { r: route, d: said } : { r: route, d: said, e: embeddedSaid });
@@ -598,6 +607,71 @@ export class Multiplexor {
598
607
  sender,
599
608
  };
600
609
  }
610
+ /**
611
+ * Whether the proposed event is already signed by enough keys to be the group's next event or reply, so that a
612
+ * member that only receives it need not approve it. An interaction (or the anchor of a registry proposal) is judged
613
+ * against the group's current keys and threshold; a rotation against its own keys and threshold, when it names the
614
+ * members the group already has (the prior next-key commitments are checked by ordinary processing, and a
615
+ * membership change waits for approval); a reply against the group's current establishment.
616
+ */
617
+ alreadyComplete(serder, nests, route) {
618
+ let event, frame;
619
+ if (serder.pvrsn.major === 1) {
620
+ const label = route === MULTISIG_IXN_ROUTE
621
+ ? "ixn"
622
+ : route === MULTISIG_ROT_ROUTE
623
+ ? "rot"
624
+ : route === MULTISIG_RPY_ROUTE
625
+ ? "rpy"
626
+ : null;
627
+ if (!label)
628
+ return false;
629
+ const child = multisigChild(this.hby, serder, label);
630
+ const attrs = isRecord(serder.ked?.a) ? serder.ked.a : {};
631
+ if (!child?.attachment.length || attrs.gid !== child.event.pre)
632
+ return false;
633
+ if (route === MULTISIG_ROT_ROUTE && !this.namesCurrentMembers(attrs))
634
+ return false;
635
+ frame = parseBytes(concatBytes(Counter.makeGVC(child.genus), child.event.raw, child.attachment))[0];
636
+ event = child.event;
637
+ }
638
+ else if (isV2RegistryRoute(route)) {
639
+ const anchor = acdcV2RegistryProposal(serder, nests, this.registry?.store ?? null)?.anchor;
640
+ frame = nests.find((nest) => nest.body.said === anchor?.said);
641
+ event = anchor;
642
+ }
643
+ if (!event || !frame || !event.pre || !this.isLocalGroup(event.pre))
644
+ return false;
645
+ const kever = this.hby.db.getKever(event.pre);
646
+ if (!kever?.tholder)
647
+ return false;
648
+ const envelope = envelopeFromFrame(frame);
649
+ if (route === MULTISIG_RPY_ROUTE) {
650
+ const proof = envelope.tsgs.find((group) => group.pre === event.pre && group.sn === BigInt(`0x${kever.lastEst.s}`) && group.said === kever.lastEst.d);
651
+ return !!proof
652
+ && kever.tholder.satisfy(Kever.verifyIndexedSignatures(event.raw, proof.sigers, kever.verfers).indices);
653
+ }
654
+ if (event.sn !== kever.sn + 1 || event.prior !== kever.said)
655
+ return false;
656
+ if (event.ilk === "rot") {
657
+ return !!event.tholder
658
+ && event.tholder.satisfy(Kever.verifyIndexedSignatures(event.raw, envelope.sigers, event.verfers).indices);
659
+ }
660
+ return event.ilk === "ixn"
661
+ && kever.tholder.satisfy(Kever.verifyIndexedSignatures(event.raw, envelope.sigers, kever.verfers).indices);
662
+ }
663
+ /** Whether a rotation proposal names exactly the group's current signing members and rotation members. */
664
+ namesCurrentMembers(attrs) {
665
+ const group = typeof attrs.gid === "string" ? attrs.gid : "";
666
+ const same = (named, current) => {
667
+ const set = new Set(named);
668
+ return set.size === current.length && current.every((member) => set.has(member));
669
+ };
670
+ const signing = memberPrefixes(attrs.smids, []);
671
+ const rotation = attrs.rmids === undefined ? signing : memberPrefixes(attrs.rmids, []);
672
+ return same(signing, groupSigningMembers(this.hby, group))
673
+ && same(rotation, this.hby.ks.getRmids(group).map((tuple) => tuple[0].qb64));
674
+ }
601
675
  /** Return true when this wrapper can be accepted for local group work. */
602
676
  verify(serder, nests = []) {
603
677
  return this.inspect(serder, nests).ok;
@@ -765,6 +839,9 @@ export class Multiplexor {
765
839
  if (membership) {
766
840
  return { ok: false, said, reason: membership };
767
841
  }
842
+ const stray = this.strayReason(serder, request, embeddedSaid);
843
+ if (stray)
844
+ return { ok: false, said, reason: stray };
768
845
  return {
769
846
  ok: true,
770
847
  said,
@@ -788,6 +865,44 @@ export class Multiplexor {
788
865
  }
789
866
  return exns;
790
867
  }
868
+ /**
869
+ * Why a remote wrapper is not for this runtime's members, or null. It must be addressed to a member held here and,
870
+ * for a proposal about a group, come from one of that group's signing members: a wrapper for a member the receiver
871
+ * does not hold, or from an identifier that is not in the group, is not stored, noticed or listed. The one exception
872
+ * is a rotation's proposed new member, accepted only once a current member's stored wrapper for the same event names
873
+ * it ({@link proposedByMember}).
874
+ */
875
+ strayReason(serder, request, embeddedSaid) {
876
+ const sender = serder.pre ?? "";
877
+ if (this.isLocalHab(sender))
878
+ return null;
879
+ const recipient = serder.sad?.rp;
880
+ if (typeof recipient === "string" && recipient.length > 0 && !this.isLocalHab(recipient)) {
881
+ return `Multisig wrapper is addressed to ${recipient}, which is not a local identifier.`;
882
+ }
883
+ const proposed = request.kind === "rotate" && request.members.includes(sender)
884
+ && this.proposedByMember(embeddedSaid, sender);
885
+ if (request.kind !== "reject" && request.kind !== "join" && this.isLocalGroup(request.group)
886
+ && !proposed && !groupSigningMembers(this.hby, request.group).includes(sender)) {
887
+ return `Multisig wrapper sender ${sender} is not a signing member of group ${request.group}.`;
888
+ }
889
+ return null;
890
+ }
891
+ /**
892
+ * Whether a current member of the group (or a local identifier) already proposed or approved this exact rotation
893
+ * in a stored wrapper that names `sender` among its members. A rotation's proposed members answer it before they
894
+ * are signing members of the group, but a sender cannot certify itself: the wrapper it names itself in is not
895
+ * enough, a wrapper for the same event from a member the group already trusts must name it too.
896
+ */
897
+ proposedByMember(embeddedSaid, sender) {
898
+ return this.get(embeddedSaid).some(({ exn }) => {
899
+ const attrs = isRecord(exn.a) ? exn.a : {};
900
+ const from = typeof exn.i === "string" ? exn.i : "";
901
+ const gid = typeof attrs.gid === "string" ? attrs.gid : "";
902
+ const named = memberPrefixes(attrs.smids, attrs.rmids).includes(sender);
903
+ return named && (this.isLocalHab(from) || groupSigningMembers(this.hby, gid).includes(from));
904
+ });
905
+ }
791
906
  /** Apply local habitat membership to an already decoded request. */
792
907
  verifyMembership(request) {
793
908
  if (request.kind === "participate") {
@@ -802,6 +917,10 @@ export class Multiplexor {
802
917
  ? null
803
918
  : `Invalid request to join group; no local member in mids=${request.members.join(",")}.`;
804
919
  }
920
+ isLocalGroup(pre) {
921
+ const hab = this.hby.habs.get(pre);
922
+ return !!hab && isLocalGroupHab(this.hby, hab);
923
+ }
805
924
  isLocalHab(pre) {
806
925
  return pre.length > 0 && this.hby.habs.has(pre);
807
926
  }
@@ -93,7 +93,7 @@ function decode(deps, exn, route, kind) {
93
93
  smids,
94
94
  rmids: rmids ?? [],
95
95
  event: eventRef(approval.event, approval.group),
96
- inapplicable: eventAccepted(hby, approval.event) && !smids.some(mid => hby.habs.has(mid)),
96
+ inapplicable: eventAccepted(hby, approval.event) && (kind !== "icp" || !smids.some(mid => hby.habs.has(mid))),
97
97
  };
98
98
  }
99
99
  if (isV2RegistryRoute(route)) {
@@ -25,6 +25,7 @@ import { Respondant } from "../delivery/respondant.js";
25
25
  import { Exchanger, RouteRefuser } from "../exchanges/exchanging.js";
26
26
  import { loadChallengeHandlers } from "../identity/challenging.js";
27
27
  import { Anchorer, loadDelegationHandlers } from "../identity/delegating.js";
28
+ import { SignerNotHeldError } from "../identity/keeping.js";
28
29
  import { loadIpexHandlers } from "../ipex/ipexing.js";
29
30
  import { ForwardHandler } from "../mailbox/forward-handler.js";
30
31
  import { KEL_PUBLISHER_PACING, KelPublisher, kelPublisherDo } from "../mailbox/kel-publisher.js";
@@ -33,6 +34,7 @@ import { MAILBOX_POLLER_PACING, MailboxPoller, } from "../mailbox/mailbox-poller
33
34
  import { mailboxerOptionsForHabery } from "../mailbox/mailboxing.js";
34
35
  import { mailboxTopicForRoute } from "../mailbox/topic-routing.js";
35
36
  import { Counselor } from "../multisig/counseling.js";
37
+ import { groupEventForwarding } from "../multisig/event-forwarding.js";
36
38
  import { loadMultisigHandlers } from "../multisig/grouping.js";
37
39
  import { noterOptionsForHabery, Notifier } from "../notifications/notifying.js";
38
40
  import { Signaler } from "../notifications/signaling.js";
@@ -45,6 +47,7 @@ import { cueDo } from "./cue-runtime.js";
45
47
  import { doEvery, PROTOCOL_PACING } from "./doing.js";
46
48
  import { IpexAuxiliaries } from "./ipex-auxiliaries.js";
47
49
  import { composeOwners, operationOwnersDo, OWNER_PACING, } from "./operation-owners.js";
50
+ import { SignerConditions } from "./signer-conditions.js";
48
51
  import { signalerChangeSink } from "./workflow-contract.js";
49
52
  import { observeUntil } from "./workflow-contract.js";
50
53
  /**
@@ -203,7 +206,9 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
203
206
  changes: signaler ? signalerChangeSink(signaler) : undefined,
204
207
  retry: options.kelPublishRetry,
205
208
  });
209
+ const signerConditions = new SignerConditions();
206
210
  const mailboxPoller = new MailboxPoller(hby, mailboxDirector, http, clock, {
211
+ signerConditions,
207
212
  onPolled: (pre, mailbox) => kelPublisher.prompt(pre, mailbox),
208
213
  onUnauthorized: (pre, mailbox) => kelPublisher.repair(pre, mailbox),
209
214
  timeouts: options.mailboxPolling,
@@ -244,7 +249,7 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
244
249
  kelPublisher.rotated.push(cue);
245
250
  owned?.rootReplicator?.prompt(cue.pre);
246
251
  },
247
- }, options.counselorReceiptRetry);
252
+ }, options.counselorReceiptRetry, groupEventForwarding(hby, postman));
248
253
  const owned = acdc && ipex
249
254
  ? composeOwners({
250
255
  hby,
@@ -291,6 +296,7 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
291
296
  delegating,
292
297
  counselor,
293
298
  kelPublisher,
299
+ signerConditions,
294
300
  ownerPacing: options.ownerPacing ?? OWNER_PACING,
295
301
  publisherPacing: options.publisherPacing ?? KEL_PUBLISHER_PACING,
296
302
  protocolPacing: options.protocolPacing ?? PROTOCOL_PACING,
@@ -622,31 +628,24 @@ export function* runAgentRuntime(runtime, options = {}) {
622
628
  runtime.querying.configure({ hab: options.hab });
623
629
  const pacing = runtime.protocolPacing;
624
630
  const telReceiptor = runtime.vdr ? new Receiptor(runtime.hby, runtime.protocol, runtime.http, runtime.clock) : null;
631
+ // A loop that needs a signer the host released waits for it; it never ends the runtime (see `surviving`).
632
+ const alive = (loop, make) => spawn(() => surviving(runtime, loop, make));
633
+ const stopHoldWatch = runtime.hby.mgr.onHold(() => runtime.signerConditions.held());
625
634
  const tasks = [
626
- yield* spawn(() => runtime.querying.deliverDo()),
627
- yield* spawn(() => runtime.postman.deliverDo()),
635
+ yield* alive("querying", () => runtime.querying.deliverDo()),
636
+ yield* alive("postman", () => runtime.postman.deliverDo()),
628
637
  ...(runtime.vdr && telReceiptor
629
638
  ? [
630
- yield* spawn(() => doEvery(runtime.clock, pacing.tockMs, () => publishPendingTel(runtime.hby, runtime.vdr.publication, telReceiptor, runtime.http, runtime.clock))),
639
+ yield* alive("tel-publication", () => doEvery(runtime.clock, pacing.tockMs, () => publishPendingTel(runtime.hby, runtime.vdr.publication, telReceiptor, runtime.http, runtime.clock))),
631
640
  ]
632
641
  : []),
633
- yield* spawn(function* () {
634
- yield* cueDo(runtime, options, pacing);
635
- }),
636
- yield* spawn(function* () {
637
- yield* runtime.protocol.escrowDo(runtime.clock);
638
- }),
639
- yield* spawn(function* () {
640
- yield* runtime.oobiery.oobiDo(runtime.clock, pacing);
641
- }),
642
- yield* spawn(function* () {
643
- yield* runtime.authenticator.authDo(runtime.clock, pacing);
644
- }),
642
+ yield* alive("cues", () => cueDo(runtime, options, pacing)),
643
+ yield* alive("escrows", () => runtime.protocol.escrowDo(runtime.clock)),
644
+ yield* alive("oobi", () => runtime.oobiery.oobiDo(runtime.clock, pacing)),
645
+ yield* alive("authentication", () => runtime.authenticator.authDo(runtime.clock, pacing)),
645
646
  ...(runtime.signaler
646
647
  ? [
647
- yield* spawn(function* () {
648
- yield* runtime.signaler.signalDo(runtime.clock, pacing);
649
- }),
648
+ yield* alive("signals", () => runtime.signaler.signalDo(runtime.clock, pacing)),
650
649
  ]
651
650
  : []),
652
651
  ...(options.pollMailbox ?? true
@@ -656,12 +655,12 @@ export function* runAgentRuntime(runtime, options = {}) {
656
655
  }),
657
656
  ]
658
657
  : []),
659
- yield* spawn(() => doEvery(runtime.clock, pacing.tockMs, () => runtime.delegating.processAllOnce())),
660
- yield* spawn(() => doEvery(runtime.clock, pacing.tockMs, () => runtime.counselor.processOnce())),
661
- yield* spawn(() => kelPublisherDo(runtime.kelPublisher, runtime.clock, runtime.publisherPacing)),
658
+ yield* alive("delegation", () => doEvery(runtime.clock, pacing.tockMs, () => runtime.delegating.processAllOnce())),
659
+ yield* alive("counselor", () => doEvery(runtime.clock, pacing.tockMs, () => runtime.counselor.processOnce())),
660
+ yield* alive("kel-publisher", () => kelPublisherDo(runtime.kelPublisher, runtime.clock, runtime.publisherPacing)),
662
661
  ...(runtime.operations && runtime.ipex
663
662
  ? [
664
- yield* spawn(() => operationOwnersDo({
663
+ yield* alive("operation-owners", () => operationOwnersDo({
665
664
  operations: runtime.operations,
666
665
  rootReplicator: runtime.rootReplicator,
667
666
  ipex: runtime.ipex,
@@ -674,8 +673,29 @@ export function* runAgentRuntime(runtime, options = {}) {
674
673
  return yield* suspend();
675
674
  }
676
675
  finally {
676
+ stopHoldWatch();
677
677
  for (const task of tasks.reverse()) {
678
678
  yield* task.halt();
679
679
  }
680
680
  }
681
681
  }
682
+ /** How long a loop waits for a held signer before trying again, in milliseconds. */
683
+ const SIGNER_RETRY_MS = 1_000;
684
+ /**
685
+ * Run a background loop so that a signer the host released does not end the runtime: the loop's
686
+ * `SignerNotHeldError` is recorded in `runtime.signerConditions`, the loop waits for a signer to be held (or a
687
+ * second) and starts again. Every other failure still ends it.
688
+ */
689
+ function* surviving(runtime, loop, make) {
690
+ while (true) {
691
+ try {
692
+ return yield* make();
693
+ }
694
+ catch (error) {
695
+ if (!(error instanceof SignerNotHeldError))
696
+ throw error;
697
+ runtime.signerConditions.report(`runtime:${loop}`, error.pub, runtime.clock.now());
698
+ yield* runtime.signerConditions.waitForHold(runtime.clock, SIGNER_RETRY_MS);
699
+ }
700
+ }
701
+ }
@@ -143,28 +143,58 @@ export class IpexAuxiliaries {
143
143
  this.unblinds.clear();
144
144
  }
145
145
  claims(serder) {
146
- return this.select(serder) !== null;
146
+ return this.select(serder).length > 0;
147
147
  }
148
+ /**
149
+ * Every local party to the transaction takes the query: a Habery that holds both sides of a Grant has the sender's
150
+ * owner (its own threshold query) and the recipient's (the query received), and each decides by its own role.
151
+ */
148
152
  processQuery(envelope) {
149
- return this.select(envelope.serder)?.processQuery(envelope) ?? { kind: "drop", reason: "missingProcessor" };
153
+ const owners = this.select(envelope.serder);
154
+ const decisions = owners.map((owner) => {
155
+ try {
156
+ return owner.processQuery(envelope);
157
+ }
158
+ catch (error) {
159
+ // With two parties, the one that is not the query's audience may refuse it; the other still decides.
160
+ if (owners.length > 1 && error instanceof ValidationError)
161
+ return { kind: "drop", reason: "missingProcessor" };
162
+ throw error;
163
+ }
164
+ });
165
+ return decisions.find((decision) => decision && decision.kind === "accept") ?? decisions[0]
166
+ ?? { kind: "drop", reason: "missingProcessor" };
150
167
  }
151
168
  reply(kind, args) {
152
- const owner = this.claimant(kind, args.serder.pre, args.serder.sad?.a?.x);
153
- if (!owner)
169
+ const owners = this.claimants(kind, args.serder.pre, args.serder.sad?.a?.x);
170
+ if (!owners.length)
154
171
  throw new ValidationError(`No resource is registered to handle route ${args.route}`);
155
- return owner.processReply(args);
172
+ let accepted, failure;
173
+ for (const owner of owners) {
174
+ try {
175
+ const decision = owner.processReply(args);
176
+ accepted ??= decision;
177
+ }
178
+ catch (error) {
179
+ failure ??= error;
180
+ }
181
+ }
182
+ if (accepted === undefined)
183
+ throw failure;
184
+ return accepted;
156
185
  }
157
186
  select(serder) {
158
187
  const kind = serder.route === Dhke.Route ? "dhke" : serder.route === Unblind.Route ? "unblind" : null;
159
- return kind && this.claimant(kind, serder.pre, serder.sad?.q?.x);
188
+ return kind ? this.claimants(kind, serder.pre, serder.sad?.q?.x) : [];
160
189
  }
161
190
  /**
162
- * The owner of the one local identifier party to the transaction, created now if needed; absent or
163
- * ambiguous means unowned. Parties come from live owners (approved operations) and from the store.
191
+ * The owners of the local identifiers party to the transaction, created now if needed; none means unowned.
192
+ * Parties come from live owners (approved operations) and from the store. Usually one; two when this Habery
193
+ * holds both the sender and the recipient.
164
194
  */
165
- claimant(kind, sender, xid) {
195
+ claimants(kind, sender, xid) {
166
196
  if (!sender || typeof xid !== "string")
167
- return null;
197
+ return [];
168
198
  const owners = kind === "dhke" ? this.dhkes : this.unblinds;
169
199
  const habs = this.exc.hby.habs;
170
200
  const parties = new Set();
@@ -186,22 +216,26 @@ export class IpexAuxiliaries {
186
216
  const group = habs.get(sender);
187
217
  if (group && isLocalGroupHab(this.exc.hby, group))
188
218
  parties.add(sender);
189
- if (parties.size !== 1)
190
- return null;
191
- const [pre] = parties;
192
- const hab = habs.get(pre);
193
- if (!hab) {
194
- this.release(pre);
195
- return null;
196
- }
197
- try {
198
- return kind === "dhke" ? this.dhke(hab) : this.unblind(hab);
219
+ // Two parties are one transaction only when the sender is itself local: its owner and the recipient's.
220
+ if (parties.size < 1 || (parties.size > 1 && !(parties.size === 2 && parties.has(sender)))) {
221
+ return [];
199
222
  }
200
- catch (error) {
201
- // A group without a local signing member cannot own the route.
202
- if (error instanceof ValidationError)
203
- return null;
204
- throw error;
223
+ const owned = [];
224
+ for (const pre of parties) {
225
+ const hab = habs.get(pre);
226
+ if (!hab) {
227
+ this.release(pre);
228
+ continue;
229
+ }
230
+ try {
231
+ owned.push(kind === "dhke" ? this.dhke(hab) : this.unblind(hab));
232
+ }
233
+ catch (error) {
234
+ // A group without a local signing member cannot own the route.
235
+ if (!(error instanceof ValidationError))
236
+ throw error;
237
+ }
205
238
  }
239
+ return owned;
206
240
  }
207
241
  }
@@ -89,6 +89,7 @@ export function composeOwners(parts, stores = {}) {
89
89
  unblinds: ipex,
90
90
  group: { mux, protocol },
91
91
  delivery,
92
+ reactor,
92
93
  clock,
93
94
  retry: parts.retry,
94
95
  changes,
@@ -0,0 +1,51 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var _SignerConditions_conditions, _SignerConditions_waiters;
7
+ /**
8
+ * Background loops that sign with a local key meet a missing key when the host has released it
9
+ * (`Manager.release`), which a custodial host does on purpose. That is a condition to wait out, not a failure
10
+ * of the runtime: the loop reports it here, backs off and resumes when a signer is held again
11
+ * (`Manager.hold`), and an application reads the list to tell its user which identifier cannot act.
12
+ */
13
+ import { action } from "effection";
14
+ /** The runtime's record of loops waiting for a signer; process-local, rebuilt by the loops themselves. */
15
+ export class SignerConditions {
16
+ constructor() {
17
+ _SignerConditions_conditions.set(this, new Map());
18
+ _SignerConditions_waiters.set(this, new Set());
19
+ }
20
+ /** `source` cannot sign because `pub` is not held; keeps the first time it was reported. */
21
+ report(source, pub, now) {
22
+ const known = __classPrivateFieldGet(this, _SignerConditions_conditions, "f").get(source);
23
+ __classPrivateFieldGet(this, _SignerConditions_conditions, "f").set(source, known?.pub === pub ? known : { source, pub, since: now });
24
+ }
25
+ /** `source` no longer waits for a signer. */
26
+ clear(source) {
27
+ __classPrivateFieldGet(this, _SignerConditions_conditions, "f").delete(source);
28
+ }
29
+ /** The loops waiting for a signer now. */
30
+ list() {
31
+ return [...__classPrivateFieldGet(this, _SignerConditions_conditions, "f").values()];
32
+ }
33
+ /** A signer was held: forget every condition (a loop that still lacks its key reports again) and wake the waiters. */
34
+ held() {
35
+ __classPrivateFieldGet(this, _SignerConditions_conditions, "f").clear();
36
+ for (const wake of [...__classPrivateFieldGet(this, _SignerConditions_waiters, "f")])
37
+ wake();
38
+ }
39
+ /** Wait up to `ms` on the host clock, or until a signer is held, whichever comes first. */
40
+ *waitForHold(clock, ms) {
41
+ yield* action((resolve) => {
42
+ const timer = clock.setTimeout(resolve, ms);
43
+ __classPrivateFieldGet(this, _SignerConditions_waiters, "f").add(resolve);
44
+ return () => {
45
+ clock.clearTimeout(timer);
46
+ __classPrivateFieldGet(this, _SignerConditions_waiters, "f").delete(resolve);
47
+ };
48
+ });
49
+ }
50
+ }
51
+ _SignerConditions_conditions = new WeakMap(), _SignerConditions_waiters = new WeakMap();
@@ -34,6 +34,12 @@ export class CesrHttpRefusal extends ValidationError {
34
34
  writable: true,
35
35
  value: status
36
36
  });
37
+ Object.defineProperty(this, "name", {
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true,
41
+ value: "CesrHttpRefusal"
42
+ });
37
43
  }
38
44
  }
39
45
  /**
@@ -6,29 +6,117 @@
6
6
  * opaque; domain processing never inspects them for instructions.
7
7
  */
8
8
  export class AppError extends Error {
9
- constructor(message, options) {
10
- super(message, options);
11
- this.name = globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).name;
9
+ constructor() {
10
+ super(...arguments);
11
+ /**
12
+ * Every class names itself with a literal. `new.target.name` and `constructor.name` are not used: they are
13
+ * mangled by a minifier, and the dnt transform of `new.target` needs a polyfill the browser entries do not load.
14
+ */
15
+ Object.defineProperty(this, "name", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: "AppError"
20
+ });
12
21
  }
13
22
  }
14
23
  /** Invalid caller input or state transition at an application boundary. */
15
24
  export class ValidationError extends AppError {
25
+ constructor() {
26
+ super(...arguments);
27
+ Object.defineProperty(this, "name", {
28
+ enumerable: true,
29
+ configurable: true,
30
+ writable: true,
31
+ value: "ValidationError"
32
+ });
33
+ }
16
34
  }
17
35
  /** Filesystem or path-resolution failure from path-managed resources. */
18
36
  export class PathError extends AppError {
37
+ constructor() {
38
+ super(...arguments);
39
+ Object.defineProperty(this, "name", {
40
+ enumerable: true,
41
+ configurable: true,
42
+ writable: true,
43
+ value: "PathError"
44
+ });
45
+ }
46
+ }
47
+ /**
48
+ * An Unblind was asked of a presentation whose exchange already has its terminal response (an Admit or a Spurn). The
49
+ * discloser consented to answer queries about a live presentation; the response ends that authority, so the
50
+ * Unblind must be requested before the response is made.
51
+ */
52
+ export class PresentationClosedError extends ValidationError {
53
+ constructor() {
54
+ super(...arguments);
55
+ Object.defineProperty(this, "name", {
56
+ enumerable: true,
57
+ configurable: true,
58
+ writable: true,
59
+ value: "PresentationClosedError"
60
+ });
61
+ }
19
62
  }
20
63
  /** Rejected path name because it violates relative-name constraints. */
21
64
  export class InvalidPathNameError extends ValidationError {
65
+ constructor() {
66
+ super(...arguments);
67
+ Object.defineProperty(this, "name", {
68
+ enumerable: true,
69
+ configurable: true,
70
+ writable: true,
71
+ value: "InvalidPathNameError"
72
+ });
73
+ }
22
74
  }
23
75
  /** Base error for LMDB or higher-level database operations. */
24
76
  export class DatabaseError extends AppError {
77
+ constructor() {
78
+ super(...arguments);
79
+ Object.defineProperty(this, "name", {
80
+ enumerable: true,
81
+ configurable: true,
82
+ writable: true,
83
+ value: "DatabaseError"
84
+ });
85
+ }
25
86
  }
26
87
  /** Database-dependent operation attempted before the resource was opened. */
27
88
  export class DatabaseNotOpenError extends DatabaseError {
89
+ constructor() {
90
+ super(...arguments);
91
+ Object.defineProperty(this, "name", {
92
+ enumerable: true,
93
+ configurable: true,
94
+ writable: true,
95
+ value: "DatabaseNotOpenError"
96
+ });
97
+ }
28
98
  }
29
99
  /** Key encoding/shape failure while reading or writing database state. */
30
100
  export class DatabaseKeyError extends DatabaseError {
101
+ constructor() {
102
+ super(...arguments);
103
+ Object.defineProperty(this, "name", {
104
+ enumerable: true,
105
+ configurable: true,
106
+ writable: true,
107
+ value: "DatabaseKeyError"
108
+ });
109
+ }
31
110
  }
32
111
  /** Operational LMDB/path failure during a database lifecycle step. */
33
112
  export class DatabaseOperationError extends DatabaseError {
113
+ constructor() {
114
+ super(...arguments);
115
+ Object.defineProperty(this, "name", {
116
+ enumerable: true,
117
+ configurable: true,
118
+ writable: true,
119
+ value: "DatabaseOperationError"
120
+ });
121
+ }
34
122
  }