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.
- 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/habbing.js +4 -1
- 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 +131 -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 +140 -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/grouping.js +120 -1
- package/esm/keri/npm/src/app/multisig/proposals.js +1 -1
- 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 +8 -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/habbing.d.ts +10 -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 +41 -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 +46 -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/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/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 +10 -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
|
@@ -50,7 +50,7 @@ import { proposeRegistryEvent } from "../multisig/proposing.js";
|
|
|
50
50
|
import { envelopeFromFrame } from "../protocol/parsering.js";
|
|
51
51
|
import { exchangeWire } from "./ipex-discloser.js";
|
|
52
52
|
import { acceptedRegistryEvidence, isIssuanceOrigin } from "./ipex-node-authentication.js";
|
|
53
|
-
import { ISSUANCE_QUERY_RESEND_MS, } from "./issuance.js";
|
|
53
|
+
import { ISSUANCE_QUERY_RESEND_MS, partyComplete, } from "./issuance.js";
|
|
54
54
|
/** Milliseconds between scans of the stored wrappers for the final proof. */
|
|
55
55
|
const PROOF_SCAN_MS = 1000;
|
|
56
56
|
/** How many of the group's latest accepted events {@link deriveGroupCustody} considers as coordinators. */
|
|
@@ -327,7 +327,7 @@ function* proposalOf(ops, grant, proposal) {
|
|
|
327
327
|
*/
|
|
328
328
|
export function* approveGroupIssuance(ops, grantSaid, options = {}) {
|
|
329
329
|
const { deps, discloser } = ops;
|
|
330
|
-
if (ops.
|
|
330
|
+
if (ops.liveOf(grantSaid, "issuer") || partyComplete(deps, grantSaid, "issuer")) {
|
|
331
331
|
throw new ValidationError("Issuance was already begun for this Grant");
|
|
332
332
|
}
|
|
333
333
|
const { child } = yield* proposalOf(ops, grantSaid, options.proposal);
|
|
@@ -413,7 +413,7 @@ function pendingGrant(deps, said) {
|
|
|
413
413
|
*/
|
|
414
414
|
export function approveGroupIssuanceExchange(ops, said, coordinator) {
|
|
415
415
|
const { deps } = ops;
|
|
416
|
-
if (ops.
|
|
416
|
+
if (ops.liveOf(said, "holder") || partyComplete(deps, said, "holder")) {
|
|
417
417
|
throw new ValidationError("Issuance was already begun for this Grant");
|
|
418
418
|
}
|
|
419
419
|
const { grant, nodes } = pendingGrant(deps, said);
|
|
@@ -430,7 +430,7 @@ export function approveGroupIssuanceExchange(ops, said, coordinator) {
|
|
|
430
430
|
*/
|
|
431
431
|
export function* followGroupIssuanceProof(ops, said, options = {}) {
|
|
432
432
|
const { deps } = ops;
|
|
433
|
-
if (ops.
|
|
433
|
+
if (ops.liveOf(said, "issuer") || partyComplete(deps, said, "issuer")) {
|
|
434
434
|
throw new ValidationError("Issuance was already begun for this Grant");
|
|
435
435
|
}
|
|
436
436
|
const { grant, nodes } = pendingGrant(deps, said);
|
|
@@ -647,7 +647,7 @@ export function registerGroupRoutes(ops) {
|
|
|
647
647
|
acceptsNests: true,
|
|
648
648
|
verify: (args) => structural(deps.hby, args.serder, args.nests ?? []),
|
|
649
649
|
handle: (args) => {
|
|
650
|
-
for (const op of ops.
|
|
650
|
+
for (const op of ops.liveOps()) {
|
|
651
651
|
if (op.group?.accepts(args.serder, args.nests)) {
|
|
652
652
|
op.group.pending.set(args.serder.said, { wrapper: args.serder, child: args.nests[0] });
|
|
653
653
|
}
|
|
@@ -761,7 +761,10 @@ export function* stepGroup(ops, op) {
|
|
|
761
761
|
for (const peer of party.others())
|
|
762
762
|
yield* send(peer, msg);
|
|
763
763
|
};
|
|
764
|
-
|
|
764
|
+
// The accepted Grant is one fact of the Habery. A holder in the issuer's own Habery is not finished by the issuer
|
|
765
|
+
// finishing it: its key exchange completes, and its proof goes to its members, in its own steps.
|
|
766
|
+
const issuerHere = op.role === "holder" && deps.hby.habs.has(g.grant.pre);
|
|
767
|
+
if (deps.exc.complete(grant) && (!issuerHere || g.mode === "resumed"))
|
|
765
768
|
return true;
|
|
766
769
|
if (op.expires === null) {
|
|
767
770
|
const deadline = deps.exc.exchangeDeadline(g.grant);
|
|
@@ -40,7 +40,8 @@ export function ipexExnBody(sender, route, nodes, options) {
|
|
|
40
40
|
throw new Error("IPEX reply parties do not match the prior.");
|
|
41
41
|
}
|
|
42
42
|
if (verb === "spurn" && prior.route === "/ipex/grant" && prior.sad.p) {
|
|
43
|
-
throw new Error("Only
|
|
43
|
+
throw new Error("Only a flow-starting Grant (one with no prior) may be spurned; a Grant that answers an Apply or Agree has no "
|
|
44
|
+
+ "rejection message, so do not Admit it and keep it as evidence.");
|
|
44
45
|
}
|
|
45
46
|
recipient = prior.pre;
|
|
46
47
|
xid = prior.ilk === "xip" ? prior.said : prior.sad.x;
|
|
@@ -25,6 +25,12 @@ import { ipexExnBody, prepareIpexExn } from "./ipex-building.js";
|
|
|
25
25
|
import { walkIpexGraph } from "./ipex-graph.js";
|
|
26
26
|
import { acceptedRegistryEvidence, authenticateIpexNode, issuedNodeVerdict } from "./ipex-node-authentication.js";
|
|
27
27
|
import { ipexGrantWalk } from "./ipex-v2.js";
|
|
28
|
+
/** Refuse the builder's `prior` where the disclosure API names the prior exchange by `priorSaid`. */
|
|
29
|
+
function refuseBuilderPrior(options) {
|
|
30
|
+
if (options.prior !== undefined) {
|
|
31
|
+
throw new ValidationError("Name the prior exchange with priorSaid; prior is not a disclosure option.");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
28
34
|
/** Reuse a matching accepted KEL seal, or author once and gather real witness receipts. */
|
|
29
35
|
export function* anchorExchange(hby, receiptor, hab, body, auths) {
|
|
30
36
|
if (receiptor.hby !== hby)
|
|
@@ -61,8 +67,10 @@ export function exchangeWire(hab, exn, nested = new Uint8Array(), source, gvrsn
|
|
|
61
67
|
return concatBytes(Counter.makeGVC(gvrsn), exn.raw, Counter.enclose({ qb64: concatBytes(proof, nested), version: gvrsn }));
|
|
62
68
|
}
|
|
63
69
|
/**
|
|
64
|
-
* Re-encode a signed exchange from its retained, authenticated factors: the
|
|
65
|
-
* from the stored signatures or source seal and the stored nests.
|
|
70
|
+
* Re-encode a signed exchange from its retained, authenticated factors: the exact bytes its signer presented,
|
|
71
|
+
* rebuilt from the stored signatures or source seal and the stored nests (`runtime.exc.retainedInput(said)` supplies
|
|
72
|
+
* them). Signs nothing, so it needs no keys. Public: a host that must hand the presented bytes to a third party
|
|
73
|
+
* reads them with this instead of re-signing.
|
|
66
74
|
*/
|
|
67
75
|
export function* storedExchangeWire(retained, gvrsn = Vrsn_2_0) {
|
|
68
76
|
const nests = [...retained.nests ?? []];
|
|
@@ -367,22 +375,30 @@ export class IpexDiscloser {
|
|
|
367
375
|
}
|
|
368
376
|
/** Create an application request; no credential-use approval is invented for negotiation. */
|
|
369
377
|
*prepareApply(sender, options) {
|
|
370
|
-
const hab = this.signer(sender, options.gvrsn ?? Vrsn_2_0), exn =
|
|
378
|
+
const hab = this.signer(sender, options.gvrsn ?? Vrsn_2_0), exn = this.applyBody(sender, options);
|
|
371
379
|
return {
|
|
372
380
|
exn,
|
|
373
381
|
wire: exchangeWire(hab, exn, undefined, undefined, options.gvrsn ?? Vrsn_2_0),
|
|
374
382
|
support: new Uint8Array(),
|
|
375
383
|
};
|
|
376
384
|
}
|
|
385
|
+
/** The unsigned Apply body, for a sender that signs elsewhere (a group's members each sign their share). */
|
|
386
|
+
applyBody(sender, options) {
|
|
387
|
+
return ipexExnBody(sender, "/ipex/apply", [], options);
|
|
388
|
+
}
|
|
377
389
|
/** Accept or decline the exact authenticated predecessor and its negotiated anchoring choice. */
|
|
378
390
|
*prepareReply(sender, verb, priorSaid, message = "", recipient, auths) {
|
|
379
|
-
const hab = this.signer(sender),
|
|
380
|
-
const exn = ipexExnBody(sender, `/ipex/${verb}`, [], { message, prior, recipient });
|
|
391
|
+
const hab = this.signer(sender), exn = yield* this.replyBody(sender, verb, priorSaid, message, recipient);
|
|
381
392
|
const source = verb === "agree" && exn.sad.a.ax?.[0]
|
|
382
393
|
? yield* anchorExchange(this.hby, this.receiptor, hab, exn, auths)
|
|
383
394
|
: undefined;
|
|
384
395
|
return { exn, wire: exchangeWire(hab, exn, undefined, source), support: source ? this.kel(hab) : new Uint8Array() };
|
|
385
396
|
}
|
|
397
|
+
/** The unsigned Agree or Spurn body answering an accepted, authenticated predecessor. */
|
|
398
|
+
*replyBody(sender, verb, priorSaid, message = "", recipient) {
|
|
399
|
+
const prior = yield* this.prior(priorSaid);
|
|
400
|
+
return ipexExnBody(sender, `/ipex/${verb}`, [], { message, prior, recipient });
|
|
401
|
+
}
|
|
386
402
|
/** Extend the frozen Grant's exact origin proof; ordinary intake still authenticates the supplied presentation evidence. */
|
|
387
403
|
*attachPresentationProof(prepared, opening, source) {
|
|
388
404
|
const frames = parseBytes(prepared.raw), node = frames[0].body;
|
|
@@ -463,15 +479,22 @@ export class IpexDiscloser {
|
|
|
463
479
|
}
|
|
464
480
|
/** Offer only the selected commitment/view without issuer proof attachments. */
|
|
465
481
|
*prepareOffer(sender, selection, options) {
|
|
466
|
-
const hab = this.signer(sender, options.gvrsn ?? Vrsn_2_0)
|
|
467
|
-
const
|
|
468
|
-
const [exn, nests] = yield* prepareIpexExn(sender, "/ipex/offer", raw, { ...options, prior });
|
|
482
|
+
const hab = this.signer(sender, options.gvrsn ?? Vrsn_2_0);
|
|
483
|
+
const { exn, nests } = yield* this.prepareOfferBody(sender, selection, options);
|
|
469
484
|
return {
|
|
470
485
|
exn,
|
|
471
486
|
wire: exchangeWire(hab, exn, nests, undefined, options.gvrsn ?? Vrsn_2_0),
|
|
472
487
|
support: new Uint8Array(),
|
|
473
488
|
};
|
|
474
489
|
}
|
|
490
|
+
/** Freeze an Offer body and its nested evidence without signing, for a sender that signs elsewhere. */
|
|
491
|
+
*prepareOfferBody(sender, selection, options) {
|
|
492
|
+
refuseBuilderPrior(options);
|
|
493
|
+
const prior = yield* this.prior(options.priorSaid);
|
|
494
|
+
const raw = yield* this.selected(selection, true);
|
|
495
|
+
const [exn, nests] = yield* prepareIpexExn(sender, "/ipex/offer", raw, { ...options, prior });
|
|
496
|
+
return { exn, nests };
|
|
497
|
+
}
|
|
475
498
|
/** Select and authorize once before freezing a Grant body, without signing or committing group approval. */
|
|
476
499
|
*prepareGrantBody(sender, selection, options) {
|
|
477
500
|
return yield* this.grantBody(sender, selection, options, false);
|
|
@@ -482,6 +505,7 @@ export class IpexDiscloser {
|
|
|
482
505
|
}
|
|
483
506
|
/** Both public preparation profiles preserve the same selected view and body construction. */
|
|
484
507
|
*grantBody(sender, selection, options, pendingPresentation) {
|
|
508
|
+
refuseBuilderPrior(options);
|
|
485
509
|
const hab = this.hby.habs.get(sender);
|
|
486
510
|
if (!hab?.kever || (options.gvrsn ?? Vrsn_2_0).major < 2) {
|
|
487
511
|
throw new ValidationError("ACDC v2 Grant preparation requires an accepted local sender and attachment genus 2.");
|
|
@@ -68,7 +68,8 @@ import { grantTransactionId, registryUncommitted, resumableExchange } from "../.
|
|
|
68
68
|
import { ValidationError } from "../../core/errors.js";
|
|
69
69
|
import { CREDENTIAL_MAILBOX_TOPIC } from "../../core/mailbox/mailbox-topics.js";
|
|
70
70
|
import { timestampMicros } from "../../time/mod.js";
|
|
71
|
-
import { isLocalGroupHab } from "../multisig/group-members.js";
|
|
71
|
+
import { groupSigningMembers, isLocalGroupHab, localGroupMember } from "../multisig/group-members.js";
|
|
72
|
+
import { remoteGroupMembers } from "../multisig/multisig-messaging.js";
|
|
72
73
|
import { DeliveryOutbox, } from "../runtime/delivery-outbox.js";
|
|
73
74
|
import { observeUntil, waitOnClock } from "../runtime/workflow-contract.js";
|
|
74
75
|
import { deriveGroupCustody, GroupIssuance, GroupParty, registerGroupRoutes, stepGroup, } from "./group-issuance.js";
|
|
@@ -87,6 +88,10 @@ export const ISSUANCE_QUERY_RESEND_MS = 1000;
|
|
|
87
88
|
* other members approve it).
|
|
88
89
|
*/
|
|
89
90
|
export const ISSUANCE_SWEEP_MS = 5_000;
|
|
91
|
+
/** The key of one party's operation on a Grant. */
|
|
92
|
+
export function partyKey(grant, role) {
|
|
93
|
+
return `${role}:${grant}`;
|
|
94
|
+
}
|
|
90
95
|
/**
|
|
91
96
|
* Adapt a `Poster`; its delivery worker must be running (a one-shot CLI runs it under `withRuntimeWorkers`).
|
|
92
97
|
* A wire of several complete messages (proof support plus Grant) goes as one ordered stream, which mailbox
|
|
@@ -128,14 +133,14 @@ export class IssuanceOperations {
|
|
|
128
133
|
writable: true,
|
|
129
134
|
value: deps
|
|
130
135
|
});
|
|
131
|
-
/** Operations live in this process.
|
|
136
|
+
/** Operations live in this process, by {@link partyKey}. Read them through {@link liveOf}. */
|
|
132
137
|
Object.defineProperty(this, "live", {
|
|
133
138
|
enumerable: true,
|
|
134
139
|
configurable: true,
|
|
135
140
|
writable: true,
|
|
136
141
|
value: new Map()
|
|
137
142
|
});
|
|
138
|
-
/** Operations that ended in this process with their reasons; not durable, see the module doc. */
|
|
143
|
+
/** Operations that ended in this process with their reasons, by {@link partyKey}; not durable, see the module doc. */
|
|
139
144
|
Object.defineProperty(this, "ended", {
|
|
140
145
|
enumerable: true,
|
|
141
146
|
configurable: true,
|
|
@@ -176,8 +181,44 @@ export class IssuanceOperations {
|
|
|
176
181
|
writable: true,
|
|
177
182
|
value: []
|
|
178
183
|
});
|
|
184
|
+
/**
|
|
185
|
+
* The delivery the outboxes use. A recipient that is an identifier of this Habery takes the bytes through the
|
|
186
|
+
* ordinary reactor path, so the checks a remote delivery meets run too; for a local group its members that are
|
|
187
|
+
* not held here are still delivered to, each through the host's delivery.
|
|
188
|
+
*/
|
|
189
|
+
Object.defineProperty(this, "transport", {
|
|
190
|
+
enumerable: true,
|
|
191
|
+
configurable: true,
|
|
192
|
+
writable: true,
|
|
193
|
+
value: { deliver: (message) => this.deliverToParty(message) }
|
|
194
|
+
});
|
|
179
195
|
this.discloser = new IpexDiscloser(deps.hby, deps.rgy.store, deps.exc, deps.receiptor);
|
|
180
196
|
}
|
|
197
|
+
/** The live operation of one party of `grant`; without `role`, the issuer's when this Habery holds both. */
|
|
198
|
+
liveOf(grant, role) {
|
|
199
|
+
return role
|
|
200
|
+
? this.live.get(partyKey(grant, role))
|
|
201
|
+
: this.live.get(partyKey(grant, "issuer")) ?? this.live.get(partyKey(grant, "holder"));
|
|
202
|
+
}
|
|
203
|
+
/** The operation of one party of `grant` that ended in this process; without `role`, as {@link liveOf}. */
|
|
204
|
+
endedOf(grant, role) {
|
|
205
|
+
return role
|
|
206
|
+
? this.ended.get(partyKey(grant, role))
|
|
207
|
+
: this.ended.get(partyKey(grant, "issuer")) ?? this.ended.get(partyKey(grant, "holder"));
|
|
208
|
+
}
|
|
209
|
+
/** Every live operation, for the module functions that look across parties. */
|
|
210
|
+
liveOps() {
|
|
211
|
+
return [...this.live.values()];
|
|
212
|
+
}
|
|
213
|
+
/** Record a consented operation as live, replacing what an earlier one of the same party left behind. */
|
|
214
|
+
begin(op) {
|
|
215
|
+
this.ended.delete(partyKey(op.grant, op.role));
|
|
216
|
+
this.live.set(partyKey(op.grant, op.role), op);
|
|
217
|
+
}
|
|
218
|
+
/** Remember how a party's operation on `grant` ended without a live operation to end. */
|
|
219
|
+
remember(grant, ended) {
|
|
220
|
+
this.ended.set(partyKey(grant, ended.role), ended);
|
|
221
|
+
}
|
|
181
222
|
/** Whether a pass has work: a live operation, or the one-time scan for resumable Grants has not run. */
|
|
182
223
|
active() {
|
|
183
224
|
return this.live.size > 0 || this.stale.length > 0 || this.deps.clock.now() >= this.nextSweep;
|
|
@@ -190,15 +231,18 @@ export class IssuanceOperations {
|
|
|
190
231
|
*processOnce() {
|
|
191
232
|
if (this.deps.clock.now() >= this.nextSweep) {
|
|
192
233
|
this.nextSweep = this.deps.clock.now() + ISSUANCE_SWEEP_MS;
|
|
193
|
-
const known = new Set(this.stale.map(({ grant }) => grant));
|
|
194
|
-
this.stale.push(...resumableGrants(this.deps).filter(({ grant }) =>
|
|
234
|
+
const known = new Set(this.stale.map(({ grant, role }) => partyKey(grant, role)));
|
|
235
|
+
this.stale.push(...resumableGrants(this.deps).filter(({ grant, role }) => {
|
|
236
|
+
const key = partyKey(grant, role);
|
|
237
|
+
return !this.live.has(key) && !known.has(key) && !this.refused.has(key) && !this.ended.has(key);
|
|
238
|
+
}));
|
|
195
239
|
}
|
|
196
240
|
for (const found of [...this.stale]) {
|
|
197
241
|
if (yield* this.settle(found))
|
|
198
242
|
this.stale.splice(this.stale.indexOf(found), 1);
|
|
199
243
|
}
|
|
200
244
|
for (const op of [...this.live.values()]) {
|
|
201
|
-
if (op.busy ||
|
|
245
|
+
if (op.busy || this.live.get(partyKey(op.grant, op.role)) !== op)
|
|
202
246
|
continue;
|
|
203
247
|
op.busy = true;
|
|
204
248
|
try {
|
|
@@ -219,12 +263,12 @@ export class IssuanceOperations {
|
|
|
219
263
|
/** End a live operation: release its DH approval, remember why until the process ends, tell the sink. */
|
|
220
264
|
end(op, state, reason) {
|
|
221
265
|
this.release(op);
|
|
222
|
-
this.ended.set(op.grant, { role: op.role, state, reason });
|
|
266
|
+
this.ended.set(partyKey(op.grant, op.role), { role: op.role, state, reason });
|
|
223
267
|
this.deps.changes?.push({ kind: "issuance", id: op.grant });
|
|
224
268
|
}
|
|
225
269
|
/** Drop the live entry and the DH approval; a missing approval means the owner already closed. */
|
|
226
270
|
release(op) {
|
|
227
|
-
this.live.delete(op.grant);
|
|
271
|
+
this.live.delete(partyKey(op.grant, op.role));
|
|
228
272
|
try {
|
|
229
273
|
op.dh.abandon(op.xid);
|
|
230
274
|
}
|
|
@@ -239,7 +283,8 @@ export class IssuanceOperations {
|
|
|
239
283
|
* dropped and reads from the stores. False means the Keeper is locked and the Grant is retried.
|
|
240
284
|
*/
|
|
241
285
|
*settle(found) {
|
|
242
|
-
|
|
286
|
+
const key = partyKey(found.grant, found.role);
|
|
287
|
+
if (this.live.has(key))
|
|
243
288
|
return true;
|
|
244
289
|
try {
|
|
245
290
|
found.hab.mgr.requireRegistryRootAccess();
|
|
@@ -251,18 +296,18 @@ export class IssuanceOperations {
|
|
|
251
296
|
}
|
|
252
297
|
// Another operations table over this runtime reached the Grant first (a command's own, beside the host's).
|
|
253
298
|
if (this.deps.dhkes.dhke(found.hab).holds(found.grant)) {
|
|
254
|
-
this.refused.add(
|
|
299
|
+
this.refused.add(key);
|
|
255
300
|
return true;
|
|
256
301
|
}
|
|
257
302
|
try {
|
|
258
303
|
yield* this.adopt(found);
|
|
259
304
|
}
|
|
260
305
|
catch (error) {
|
|
261
|
-
this.refused.add(
|
|
306
|
+
this.refused.add(key);
|
|
262
307
|
// A refusal means the stores no longer prove it resumable and status reads from them; anything else is
|
|
263
308
|
// this Grant's failure alone and must not stop the other operations.
|
|
264
309
|
if (!(error instanceof ValidationError)) {
|
|
265
|
-
this.ended.set(
|
|
310
|
+
this.ended.set(key, { role: found.role, state: "failed", reason: message(error) });
|
|
266
311
|
this.deps.changes?.push({ kind: "issuance", id: found.grant });
|
|
267
312
|
}
|
|
268
313
|
}
|
|
@@ -278,8 +323,8 @@ export class IssuanceOperations {
|
|
|
278
323
|
}
|
|
279
324
|
const wires = role === "issuer" ? yield* grantWires(retained) : null;
|
|
280
325
|
const dh = this.deps.dhkes.dhke(hab), xid = dh.resume(grant);
|
|
281
|
-
this.ended.delete(grant);
|
|
282
|
-
this.live.set(grant, {
|
|
326
|
+
this.ended.delete(partyKey(grant, role));
|
|
327
|
+
this.live.set(partyKey(grant, role), {
|
|
283
328
|
grant,
|
|
284
329
|
xid,
|
|
285
330
|
role,
|
|
@@ -308,8 +353,8 @@ export class IssuanceOperations {
|
|
|
308
353
|
const party = GroupParty.resolve(this.deps, hab, election.coordinator.said);
|
|
309
354
|
const group = new GroupIssuance(party, role, retained.serder, retained.nests ?? [], { mode: "resumed" });
|
|
310
355
|
const dh = this.deps.dhkes.dhke(hab), xid = dh.resume(grant, party);
|
|
311
|
-
this.ended.delete(grant);
|
|
312
|
-
this.live.set(grant, this.liveGroup(group, dh, xid, "rootRetained"));
|
|
356
|
+
this.ended.delete(partyKey(grant, role));
|
|
357
|
+
this.live.set(partyKey(grant, role), this.liveGroup(group, dh, xid, "rootRetained"));
|
|
313
358
|
this.deps.changes?.push({ kind: "issuance", id: grant });
|
|
314
359
|
}
|
|
315
360
|
/** The live entry of a group operation; its lifetime is the Grant's once KRAM has accepted it, else unknown. */
|
|
@@ -336,6 +381,20 @@ export class IssuanceOperations {
|
|
|
336
381
|
yield* waitOnClock(this.deps.clock, milliseconds);
|
|
337
382
|
return "expired";
|
|
338
383
|
}
|
|
384
|
+
*deliverToParty(message) {
|
|
385
|
+
const { hby, reactor, delivery } = this.deps, recipient = hby.habs.get(message.dest);
|
|
386
|
+
if (!recipient)
|
|
387
|
+
return yield* delivery.deliver(message);
|
|
388
|
+
yield* reactor.processBytes(message.msg);
|
|
389
|
+
const reached = [`local:${recipient.pre}`];
|
|
390
|
+
if (!isLocalGroupHab(hby, recipient))
|
|
391
|
+
return reached;
|
|
392
|
+
const member = localGroupMember(hby, recipient.pre);
|
|
393
|
+
for (const dest of remoteGroupMembers(hby, member, groupSigningMembers(hby, recipient.pre))) {
|
|
394
|
+
reached.push(...yield* delivery.deliver({ ...message, dest }));
|
|
395
|
+
}
|
|
396
|
+
return reached;
|
|
397
|
+
}
|
|
339
398
|
/**
|
|
340
399
|
* Queue signed bytes for delivery from the identifier that signed them. Steps decide what to send; {@link drain}
|
|
341
400
|
* sends it, so a member that is briefly offline delays the operation instead of ending it.
|
|
@@ -349,7 +408,7 @@ export class IssuanceOperations {
|
|
|
349
408
|
* lasting {@link ISSUANCE_RETRY_BOUND_MS} end one that has none yet. True when the outbox is empty.
|
|
350
409
|
*/
|
|
351
410
|
*drain(op) {
|
|
352
|
-
return yield* op.outbox.drain(this.
|
|
411
|
+
return yield* op.outbox.drain(this.transport, this.deps.clock, op.expires === null ? this.deps.retry?.boundMs ?? ISSUANCE_RETRY_BOUND_MS : undefined);
|
|
353
412
|
}
|
|
354
413
|
/** One pass: send what is queued, step only once the queue is empty, send what the step queued. */
|
|
355
414
|
*advance(op) {
|
|
@@ -388,14 +447,16 @@ export class IssuanceOperations {
|
|
|
388
447
|
: group.role === "issuer"
|
|
389
448
|
? "awaitingGrantSignatures"
|
|
390
449
|
: "exchangingKeys";
|
|
391
|
-
this.ended.delete(group.said);
|
|
392
|
-
this.live.set(group.said, this.liveGroup(group, dh, xid, phase));
|
|
450
|
+
this.ended.delete(partyKey(group.said, group.role));
|
|
451
|
+
this.live.set(partyKey(group.said, group.role), this.liveGroup(group, dh, xid, phase));
|
|
393
452
|
deps.changes?.push({ kind: "issuance", id: group.said });
|
|
394
453
|
return { grant: group.said, xid };
|
|
395
454
|
}
|
|
396
|
-
/** Whether
|
|
397
|
-
owes(grant) {
|
|
398
|
-
return
|
|
455
|
+
/** Whether a live operation for `grant` (or only its party `role`) still holds a message the transport has not taken. */
|
|
456
|
+
owes(grant, role) {
|
|
457
|
+
return role
|
|
458
|
+
? (this.liveOf(grant, role)?.outbox.length ?? 0) > 0
|
|
459
|
+
: this.owes(grant, "issuer") || this.owes(grant, "holder");
|
|
399
460
|
}
|
|
400
461
|
/** One pass of the issuer loop, in the order of the CLI loop it replaces. */
|
|
401
462
|
*step(op) {
|
|
@@ -525,7 +586,7 @@ function resumableGrants(deps) {
|
|
|
525
586
|
if (parties.issuer && resumable(deps, parties.issuer, grant, "issuer")) {
|
|
526
587
|
found.push({ hab: parties.issuer, grant, role: "issuer" });
|
|
527
588
|
}
|
|
528
|
-
|
|
589
|
+
if (parties.holder && resumable(deps, parties.holder, grant, "holder")) {
|
|
529
590
|
found.push({ hab: parties.holder, grant, role: "holder" });
|
|
530
591
|
}
|
|
531
592
|
}
|
|
@@ -575,7 +636,7 @@ function selectGrant(ops, hab, grant, role, group = false) {
|
|
|
575
636
|
const { deps } = ops;
|
|
576
637
|
if (!group)
|
|
577
638
|
requireIndividual(deps, hab);
|
|
578
|
-
if (ops.
|
|
639
|
+
if (ops.liveOf(grant, role) || partyComplete(deps, grant, role)) {
|
|
579
640
|
throw new ValidationError("Issuance was already begun for this Grant");
|
|
580
641
|
}
|
|
581
642
|
const retained = deps.exc.retainedInput(grant, { pending: true });
|
|
@@ -594,6 +655,16 @@ function selectGrant(ops, hab, grant, role, group = false) {
|
|
|
594
655
|
throw new Error("Authenticated pending Grant has a missing or corrupt deadline");
|
|
595
656
|
return { retained, credential, deadline, expires };
|
|
596
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Whether one party's side of a Grant is finished. The accepted Grant is one fact of the Habery, but a Habery that
|
|
660
|
+
* holds both parties finishes the holder's side only when the holder's own exchange has retained its root.
|
|
661
|
+
*/
|
|
662
|
+
export function partyComplete(deps, grant, role) {
|
|
663
|
+
if (!deps.exc.complete(grant))
|
|
664
|
+
return false;
|
|
665
|
+
const parties = grantParties(deps.hby, grant);
|
|
666
|
+
return role === "issuer" || !parties?.issuer || !parties.holder || heldRoot(deps, parties.holder, grant);
|
|
667
|
+
}
|
|
597
668
|
/** The Grant body with its nested disclosure (the input of proof support) and the Grant as sent to the holder. */
|
|
598
669
|
function* grantWires(retained) {
|
|
599
670
|
const raw = concatBytes(Counter.makeGVC(Vrsn_2_0), ...yield* call(() => Promise.all((retained.nests ?? []).map((nest) => collectPayload(framePayload(nest))))));
|
|
@@ -616,8 +687,7 @@ export function* beginIssuance(ops, grant, auths) {
|
|
|
616
687
|
const { retained, credential, deadline, expires } = selectGrant(ops, hab, grant, "issuer");
|
|
617
688
|
const { raw, grantWire } = yield* grantWires(retained);
|
|
618
689
|
const dh = deps.dhkes.dhke(hab), xid = dh.approve(grant), query = dh.start(grant);
|
|
619
|
-
ops.
|
|
620
|
-
ops.live.set(grant, {
|
|
690
|
+
ops.begin({
|
|
621
691
|
grant,
|
|
622
692
|
xid,
|
|
623
693
|
role: "issuer",
|
|
@@ -651,8 +721,10 @@ export function* proposeIssuance(ops, draft, auths) {
|
|
|
651
721
|
*
|
|
652
722
|
* @throws ValidationError when `grant` is not a v2 Grant this runtime's identifiers are party to.
|
|
653
723
|
*/
|
|
654
|
-
export function issuanceStatus(ops, grant) {
|
|
655
|
-
const { deps } = ops,
|
|
724
|
+
export function issuanceStatus(ops, grant, party = {}) {
|
|
725
|
+
const { deps } = ops, parties = grantParties(deps.hby, grant);
|
|
726
|
+
const chosen = chooseParty(parties, party);
|
|
727
|
+
const ended = ops.endedOf(grant, chosen), live = ops.liveOf(grant, chosen);
|
|
656
728
|
if (ended)
|
|
657
729
|
return { state: ended.state, role: ended.role, reason: ended.reason };
|
|
658
730
|
if (live && !reached(deps.clock, live.expires)) {
|
|
@@ -664,19 +736,18 @@ export function issuanceStatus(ops, grant) {
|
|
|
664
736
|
...(live.outbox.failure && { failure: live.outbox.failure }),
|
|
665
737
|
};
|
|
666
738
|
}
|
|
667
|
-
const parties = grantParties(deps.hby, grant);
|
|
668
739
|
if (!parties)
|
|
669
740
|
throw new ValidationError("No issuance was begun for this Grant");
|
|
670
741
|
const { serder, issuer, holder } = parties, db = deps.hby.db;
|
|
671
|
-
if (deps
|
|
742
|
+
if (partyComplete(deps, grant, chosen)) {
|
|
672
743
|
const admitted = !!db.erpy.get([grant]);
|
|
673
|
-
if (
|
|
744
|
+
if (chosen === "issuer")
|
|
674
745
|
return { state: "done", role: "issuer", phase: "issued" };
|
|
675
746
|
if (admitted)
|
|
676
747
|
return { state: "done", role: "holder", phase: "admitted" };
|
|
677
748
|
return { state: "active", role: "holder", phase: "awaitingAdmitConsent", needs: "admit" };
|
|
678
749
|
}
|
|
679
|
-
const role =
|
|
750
|
+
const role = chosen, hab = (role === "issuer" ? issuer : holder);
|
|
680
751
|
const group = isLocalGroupHab(deps.hby, hab);
|
|
681
752
|
// A group issuer's partial Grant has no accepted lifetime until its members' contributions authenticate it.
|
|
682
753
|
const lifetime = deps.exc.exchangeDeadline(serder) ?? undefined;
|
|
@@ -701,6 +772,24 @@ export function issuanceStatus(ops, grant) {
|
|
|
701
772
|
const [state, reason] = orphaned(deps, hab, grant);
|
|
702
773
|
return { state, role, reason };
|
|
703
774
|
}
|
|
775
|
+
/**
|
|
776
|
+
* The party a read is for: the one asked for, else the Habery's only party, else (it holds both) the issuer. Null
|
|
777
|
+
* when the Grant has no local party.
|
|
778
|
+
*
|
|
779
|
+
* @throws ValidationError when `choice` names a party this Habery does not hold.
|
|
780
|
+
*/
|
|
781
|
+
function chooseParty(parties, choice) {
|
|
782
|
+
if (!parties)
|
|
783
|
+
return choice.role;
|
|
784
|
+
const held = [
|
|
785
|
+
...(parties.issuer ? ["issuer"] : []),
|
|
786
|
+
...(parties.holder ? ["holder"] : []),
|
|
787
|
+
];
|
|
788
|
+
if (choice.role && !held.includes(choice.role)) {
|
|
789
|
+
throw new ValidationError(`This Habery holds no ${choice.role} of this Grant`);
|
|
790
|
+
}
|
|
791
|
+
return choice.role ?? held[0];
|
|
792
|
+
}
|
|
704
793
|
/**
|
|
705
794
|
* Pure read of the Grants waiting for the holder's exchange consent: authenticated pending v2 Grants addressed
|
|
706
795
|
* to a local identifier (to `hab` when given) for which {@link approveIssuanceExchange} would be accepted.
|
|
@@ -713,7 +802,7 @@ export function pendingIssuanceGrants(ops, { hab } = {}) {
|
|
|
713
802
|
if (!holder || (hab && holder.pre !== hab.pre))
|
|
714
803
|
continue;
|
|
715
804
|
try {
|
|
716
|
-
const status = issuanceStatus(ops, grant);
|
|
805
|
+
const status = issuanceStatus(ops, grant, { role: "holder" });
|
|
717
806
|
if (status.state !== "active" || status.needs !== "exchange")
|
|
718
807
|
continue;
|
|
719
808
|
const { retained, credential, deadline } = selectGrant(ops, holder, grant, "holder");
|
|
@@ -746,7 +835,7 @@ export function pendingGroupGrants(ops, { group } = {}) {
|
|
|
746
835
|
if (!parties || !holder || !isLocalGroupHab(deps.hby, holder) || (group && holder.pre !== group.pre))
|
|
747
836
|
continue;
|
|
748
837
|
try {
|
|
749
|
-
const status = issuanceStatus(ops, grant);
|
|
838
|
+
const status = issuanceStatus(ops, grant, { role: "holder" });
|
|
750
839
|
if (status.state !== "active" || status.needs !== "exchange")
|
|
751
840
|
continue;
|
|
752
841
|
const credential = deps.exc.retainedInput(grant, { pending: true })?.nests?.[0]?.body;
|
|
@@ -779,13 +868,13 @@ function heldRoot(deps, hab, grant) {
|
|
|
779
868
|
* Observe until the issuance is done or ended, or the budget expires. Starts no
|
|
780
869
|
* worker and drives nothing: someone else must call `processOnce`.
|
|
781
870
|
*/
|
|
782
|
-
export function* waitForIssuance(ops, grant, { timeoutMs, clock = ops.deps.clock, until = (status) => status.state !== "active" }) {
|
|
871
|
+
export function* waitForIssuance(ops, grant, { timeoutMs, clock = ops.deps.clock, until = (status) => status.state !== "active", role }) {
|
|
783
872
|
// A phase is not reached while the step that produced it still owes a delivery: the host would stop its workers first.
|
|
784
|
-
const settled = yield* observeUntil(() => until(issuanceStatus(ops, grant)) && !ops.owes(grant), {
|
|
873
|
+
const settled = yield* observeUntil(() => until(issuanceStatus(ops, grant, { role })) && !ops.owes(grant, role), {
|
|
785
874
|
timeoutMs,
|
|
786
875
|
clock,
|
|
787
876
|
});
|
|
788
|
-
const status = issuanceStatus(ops, grant);
|
|
877
|
+
const status = issuanceStatus(ops, grant, { role });
|
|
789
878
|
return settled ? { outcome: "settled", status } : { outcome: "timed-out", status, timeoutMs };
|
|
790
879
|
}
|
|
791
880
|
/**
|
|
@@ -799,8 +888,7 @@ export function approveIssuanceExchange(ops, grant) {
|
|
|
799
888
|
throw new ValidationError("Select the exact unanchored pending issuance Grant for this local party");
|
|
800
889
|
const { retained, credential, deadline, expires } = selectGrant(ops, hab, grant, "holder");
|
|
801
890
|
const dh = deps.dhkes.dhke(hab), xid = dh.approve(grant);
|
|
802
|
-
ops.
|
|
803
|
-
ops.live.set(grant, {
|
|
891
|
+
ops.begin({
|
|
804
892
|
grant,
|
|
805
893
|
xid,
|
|
806
894
|
role: "holder",
|
|
@@ -882,15 +970,15 @@ export function* rebuildIssuanceAdmit(deps, admit) {
|
|
|
882
970
|
*
|
|
883
971
|
* @throws ValidationError when no issuance was begun for `grant`.
|
|
884
972
|
*/
|
|
885
|
-
export function abandonIssuance(ops, grant, reason = "Abandoned by the user") {
|
|
886
|
-
const status = issuanceStatus(ops, grant);
|
|
973
|
+
export function abandonIssuance(ops, grant, reason = "Abandoned by the user", party = {}) {
|
|
974
|
+
const status = issuanceStatus(ops, grant, party);
|
|
887
975
|
if (status.state !== "active")
|
|
888
976
|
return;
|
|
889
|
-
const live = ops.
|
|
977
|
+
const live = ops.liveOf(grant, status.role);
|
|
890
978
|
if (live)
|
|
891
979
|
ops.end(live, "abandoned", reason);
|
|
892
980
|
else {
|
|
893
|
-
ops.
|
|
981
|
+
ops.remember(grant, { role: status.role, state: "abandoned", reason });
|
|
894
982
|
ops.deps.changes?.push({ kind: "issuance", id: grant });
|
|
895
983
|
}
|
|
896
984
|
}
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { SerderACDC } from "cesr-ts";
|
|
11
11
|
import { ValidationError } from "../../core/errors.js";
|
|
12
|
+
import { timestampMicros } from "../../time/mod.js";
|
|
13
|
+
import { singleDagDisclosurePlan } from "./ipex-graph.js";
|
|
12
14
|
import { unblindingStatus } from "./unblinding-workflow.js";
|
|
13
15
|
/**
|
|
14
16
|
* The pending v2 Grants addressed to a local identifier (to `hab` when given) that are inside their deadline.
|
|
@@ -44,3 +46,51 @@ export function pendingPresentations(ops, { hab } = {}) {
|
|
|
44
46
|
}
|
|
45
47
|
return found;
|
|
46
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The accepted v2 Applies addressed to a local identifier (to `hab` when given) that no exchange has answered
|
|
51
|
+
* (no `erpy` pointer) and whose deadline has not passed; with `includeAnswered`, the answered ones too. Mutates
|
|
52
|
+
* nothing. A group member lists the Applies of its group once the group has accepted them.
|
|
53
|
+
*/
|
|
54
|
+
export function pendingApplies(deps, { hab, includeAnswered = false } = {}) {
|
|
55
|
+
const { hby, exc, clock } = deps, found = [];
|
|
56
|
+
const now = BigInt(clock.now()) * 1000n;
|
|
57
|
+
for (const [[apply], serder] of [...hby.db.exns.getTopItemIter()]) {
|
|
58
|
+
const holder = serder.sad?.ri;
|
|
59
|
+
if (serder.route !== "/ipex/apply" || serder.pvrsn.major < 2 || typeof holder !== "string")
|
|
60
|
+
continue;
|
|
61
|
+
if (!hby.habs.has(holder) || (hab && hab.pre !== holder))
|
|
62
|
+
continue;
|
|
63
|
+
if (!exc.complete(apply))
|
|
64
|
+
continue;
|
|
65
|
+
const answer = hby.db.erpy.get([apply])?.qb64 ?? null;
|
|
66
|
+
if (answer && !includeAnswered)
|
|
67
|
+
continue;
|
|
68
|
+
const deadline = exc.exchangeDeadline(serder), expiry = deadline ? timestampMicros(deadline) : null;
|
|
69
|
+
if (!answer && expiry !== null && now >= expiry)
|
|
70
|
+
continue;
|
|
71
|
+
const q = serder.sad?.q;
|
|
72
|
+
found.push({
|
|
73
|
+
apply,
|
|
74
|
+
holder,
|
|
75
|
+
applicant: serder.pre,
|
|
76
|
+
message: String(serder.sad?.a?.m ?? ""),
|
|
77
|
+
plan: singleDagDisclosurePlan(q?.dp) ?? [],
|
|
78
|
+
deadline,
|
|
79
|
+
state: answer ? "answered" : "pending",
|
|
80
|
+
answer,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return found;
|
|
84
|
+
}
|
|
85
|
+
/** What answered the exchange `said`, from the answer pointer the stores keep; null when nothing has. Mutates nothing. */
|
|
86
|
+
export function exchangeAnswer(deps, said) {
|
|
87
|
+
const answer = deps.hby.db.erpy.get([said])?.qb64, serder = answer ? deps.hby.db.exns.get([answer]) : null;
|
|
88
|
+
if (!answer || !serder?.route)
|
|
89
|
+
return null;
|
|
90
|
+
return { answer, route: serder.route, sender: serder.pre, accepted: deps.exc.complete(answer) };
|
|
91
|
+
}
|
|
92
|
+
/** The Agree that answers the Offer `offer`; null while nothing, or something other than an Agree (a Spurn), answered it. */
|
|
93
|
+
export function agreeFor(deps, offer) {
|
|
94
|
+
const answer = exchangeAnswer(deps, offer);
|
|
95
|
+
return answer?.route === "/ipex/agree" ? answer : null;
|
|
96
|
+
}
|