keri-ts 2.2.1 → 2.2.3
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/esm/keri/npm/src/_dnt.polyfills.js +1 -127
- package/esm/keri/npm/src/acdc/dhking.js +6 -1
- package/esm/keri/npm/src/acdc/salting.js +18 -0
- package/esm/keri/npm/src/acdc/scheming.js +31 -18
- package/esm/keri/npm/src/acdc/unblinding.js +112 -6
- package/esm/keri/npm/src/app/delivery/forwarding.js +9 -0
- package/esm/keri/npm/src/app/delivery/respondant.js +21 -0
- package/esm/keri/npm/src/app/identity/delegating.js +2 -5
- package/esm/keri/npm/src/app/identity/habbing.js +52 -6
- package/esm/keri/npm/src/app/identity/keeping.js +38 -1
- package/esm/keri/npm/src/app/ipex/group-issuance.js +9 -6
- package/esm/keri/npm/src/app/ipex/ipex-building.js +2 -1
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +32 -8
- package/esm/keri/npm/src/app/ipex/issuance.js +142 -43
- package/esm/keri/npm/src/app/ipex/pending-presentations.js +50 -0
- package/esm/keri/npm/src/app/ipex/presenting.js +36 -1
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +150 -36
- package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +37 -9
- package/esm/keri/npm/src/app/mailbox/mailboxing.js +9 -3
- package/esm/keri/npm/src/app/multisig/counseling.js +76 -3
- package/esm/keri/npm/src/app/multisig/event-forwarding.js +27 -0
- package/esm/keri/npm/src/app/multisig/group-members.js +22 -0
- package/esm/keri/npm/src/app/multisig/grouping.js +120 -1
- package/esm/keri/npm/src/app/multisig/proposals.js +1 -1
- package/esm/keri/npm/src/app/multisig/replace-member.js +34 -0
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +43 -23
- package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +59 -25
- package/esm/keri/npm/src/app/runtime/operation-owners.js +1 -0
- package/esm/keri/npm/src/app/runtime/signer-conditions.js +51 -0
- package/esm/keri/npm/src/app/transport/cesr-egress.js +6 -0
- package/esm/keri/npm/src/core/errors.js +91 -3
- package/esm/keri/npm/src/core/json-schema-meta.js +688 -0
- package/esm/keri/npm/src/core/json-schema.js +147 -0
- package/esm/keri/npm/src/core/kel/eventing.js +20 -2
- package/esm/keri/npm/src/core/scheming.js +27 -26
- package/esm/keri/npm/src/db/replay/tel-replay.js +6 -0
- package/esm/keri/npm/src/db/store-lock.js +12 -0
- package/esm/keri/npm/src/runtime/index.js +9 -7
- package/esm/keri/npm/src/runtime/web/output-barrier.js +9 -0
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +2 -2
- package/types/_dnt.polyfills.d.ts +0 -99
- package/types/_dnt.polyfills.d.ts.map +1 -1
- package/types/acdc/dhking.d.ts.map +1 -1
- package/types/acdc/salting.d.ts +2 -0
- package/types/acdc/salting.d.ts.map +1 -1
- package/types/acdc/scheming.d.ts +2 -0
- package/types/acdc/scheming.d.ts.map +1 -1
- package/types/acdc/unblinding.d.ts +16 -5
- package/types/acdc/unblinding.d.ts.map +1 -1
- package/types/app/delivery/forwarding.d.ts +1 -0
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/delivery/respondant.d.ts +7 -0
- package/types/app/delivery/respondant.d.ts.map +1 -1
- package/types/app/identity/delegating.d.ts.map +1 -1
- package/types/app/identity/habbing.d.ts +21 -1
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/identity/keeping.d.ts +18 -0
- package/types/app/identity/keeping.d.ts.map +1 -1
- package/types/app/ipex/group-issuance.d.ts.map +1 -1
- package/types/app/ipex/ipex-building.d.ts.map +1 -1
- package/types/app/ipex/ipex-discloser.d.ts +31 -19
- package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
- package/types/app/ipex/issuance.d.ts +46 -10
- package/types/app/ipex/issuance.d.ts.map +1 -1
- package/types/app/ipex/pending-presentations.d.ts +62 -1
- package/types/app/ipex/pending-presentations.d.ts.map +1 -1
- package/types/app/ipex/presenting.d.ts +39 -4
- package/types/app/ipex/presenting.d.ts.map +1 -1
- package/types/app/ipex/unblinding-workflow.d.ts +51 -12
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
- package/types/app/mailbox/mailbox-poller.d.ts +11 -2
- package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
- package/types/app/mailbox/mailboxing.d.ts +4 -1
- package/types/app/mailbox/mailboxing.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +22 -1
- package/types/app/multisig/counseling.d.ts.map +1 -1
- package/types/app/multisig/event-forwarding.d.ts +6 -0
- package/types/app/multisig/event-forwarding.d.ts.map +1 -0
- package/types/app/multisig/group-members.d.ts +6 -0
- package/types/app/multisig/group-members.d.ts.map +1 -1
- package/types/app/multisig/grouping.d.ts +26 -0
- package/types/app/multisig/grouping.d.ts.map +1 -1
- package/types/app/multisig/proposals.d.ts +3 -2
- package/types/app/multisig/proposals.d.ts.map +1 -1
- package/types/app/multisig/proposing.d.ts +8 -1
- package/types/app/multisig/proposing.d.ts.map +1 -1
- package/types/app/multisig/replace-member.d.ts +30 -0
- package/types/app/multisig/replace-member.d.ts.map +1 -0
- package/types/app/runtime/agent-runtime.d.ts +3 -0
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/ipex-auxiliaries.d.ts +8 -3
- package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -1
- package/types/app/runtime/operation-owners.d.ts.map +1 -1
- package/types/app/runtime/signer-conditions.d.ts +32 -0
- package/types/app/runtime/signer-conditions.d.ts.map +1 -0
- package/types/app/transport/cesr-egress.d.ts +1 -0
- package/types/app/transport/cesr-egress.d.ts.map +1 -1
- package/types/core/errors.d.ts +20 -1
- package/types/core/errors.d.ts.map +1 -1
- package/types/core/json-schema-meta.d.ts +19 -0
- package/types/core/json-schema-meta.d.ts.map +1 -0
- package/types/core/json-schema.d.ts +29 -0
- package/types/core/json-schema.d.ts.map +1 -0
- package/types/core/scheming.d.ts +1 -0
- package/types/core/scheming.d.ts.map +1 -1
- package/types/db/replay/tel-replay.d.ts +1 -0
- package/types/db/replay/tel-replay.d.ts.map +1 -1
- package/types/db/store-lock.d.ts +2 -0
- package/types/db/store-lock.d.ts.map +1 -1
- package/types/runtime/index.d.ts +11 -9
- package/types/runtime/index.d.ts.map +1 -1
- package/types/runtime/web/output-barrier.d.ts +1 -0
- package/types/runtime/web/output-barrier.d.ts.map +1 -1
- package/types/version.d.ts +2 -2
|
@@ -128,6 +128,28 @@ export function groupMemberHasKey(db, member, key) {
|
|
|
128
128
|
}
|
|
129
129
|
return false;
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* True when `aid` is a signing or rotation member of `group`'s latest accepted establishment event: one of its
|
|
133
|
+
* establishment events supplied a key in that event's key list or committed to a digest in its next list.
|
|
134
|
+
* Decided from accepted key state only. False when the group has no accepted state.
|
|
135
|
+
*/
|
|
136
|
+
export function isAcceptedGroupMember(db, group, aid) {
|
|
137
|
+
const kever = db.getKever(group);
|
|
138
|
+
const event = kever ? db.getEvtSerder(group, kever.lastEst.d) : null;
|
|
139
|
+
if (!event)
|
|
140
|
+
return false;
|
|
141
|
+
const keys = new Set((event.verfers ?? []).map(verfer => verfer.qb64));
|
|
142
|
+
const digs = new Set((event.ndigers ?? []).map(diger => diger.qb64));
|
|
143
|
+
for (const [, said] of db.getKelItemIter(aid)) {
|
|
144
|
+
const member = db.getEvtSerder(aid, said);
|
|
145
|
+
if (!member?.estive)
|
|
146
|
+
continue;
|
|
147
|
+
if ((member.verfers ?? []).some(verfer => keys.has(verfer.qb64))
|
|
148
|
+
|| (member.ndigers ?? []).some(diger => digs.has(diger.qb64)))
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
131
153
|
/**
|
|
132
154
|
* Select only a cryptographically valid local partial at an accepted group establishment.
|
|
133
155
|
* The shared esigs table can hold unverified escrow candidates, so a matching
|
|
@@ -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)) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replace a local group's recorded member after a rotation removed it, and end the runtime's work bound to it.
|
|
3
|
+
*
|
|
4
|
+
* `Habery.joinGroupHab` re-points the record and clears the durable rows addressed to the old member. The
|
|
5
|
+
* runtime owns the live operations, so this is the one call that also ends them: an issuance whose party
|
|
6
|
+
* member or frozen DH custodian is the old member, and an Unblind holding it. Each ends through its owner's
|
|
7
|
+
* own end path, which releases the approval and drops the held `heldq`/`heldu` rows. Group IPEX messages and
|
|
8
|
+
* presentations keep no member binding (their contributions are already signed and the member is read live),
|
|
9
|
+
* so they are left to run. Nothing is signed and no event is made.
|
|
10
|
+
*/
|
|
11
|
+
import { ValidationError } from "../../core/errors.js";
|
|
12
|
+
/**
|
|
13
|
+
* Replace the local member of `input.group`, then end live operations bound to the previous one.
|
|
14
|
+
*
|
|
15
|
+
* @throws ValidationError when `joinGroupHab` refuses: the group has no accepted state, the previous member is
|
|
16
|
+
* still a member of it, or the new one is not.
|
|
17
|
+
*/
|
|
18
|
+
export function replaceGroupMember(runtime, input) {
|
|
19
|
+
const { hby } = runtime, { group, member } = input;
|
|
20
|
+
const record = hby.db.getHab(group), alias = record?.name;
|
|
21
|
+
if (!record?.mid || !alias)
|
|
22
|
+
throw new ValidationError(`Group ${group} is not a local group habitat.`);
|
|
23
|
+
const previous = record.mid;
|
|
24
|
+
if (previous === member.pre)
|
|
25
|
+
throw new ValidationError(`Member ${previous} is already the local member of ${group}.`);
|
|
26
|
+
hby.joinGroupHab(group, alias, member, input.smids, input.rmids ?? input.smids, record.domain);
|
|
27
|
+
const reason = `Local member ${previous} of group ${group} was replaced by ${member.pre}`;
|
|
28
|
+
const ops = runtime.operations;
|
|
29
|
+
return {
|
|
30
|
+
previous,
|
|
31
|
+
issuance: ops?.issuance.endBoundToMember(group, previous, reason) ?? [],
|
|
32
|
+
unblinding: ops?.unblinding.endBoundToMember(group, previous, reason) ?? [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -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*
|
|
627
|
-
yield*
|
|
635
|
+
yield* alive("querying", () => runtime.querying.deliverDo()),
|
|
636
|
+
yield* alive("postman", () => runtime.postman.deliverDo()),
|
|
628
637
|
...(runtime.vdr && telReceiptor
|
|
629
638
|
? [
|
|
630
|
-
yield*
|
|
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*
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
yield*
|
|
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*
|
|
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*
|
|
660
|
-
yield*
|
|
661
|
-
yield*
|
|
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*
|
|
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)
|
|
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
|
-
|
|
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
|
|
153
|
-
if (!
|
|
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
|
-
|
|
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
|
|
188
|
+
return kind ? this.claimants(kind, serder.pre, serder.sad?.q?.x) : [];
|
|
160
189
|
}
|
|
161
190
|
/**
|
|
162
|
-
* The
|
|
163
|
-
*
|
|
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
|
-
|
|
195
|
+
claimants(kind, sender, xid) {
|
|
166
196
|
if (!sender || typeof xid !== "string")
|
|
167
|
-
return
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
}
|
|
@@ -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
|
/**
|