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
|
@@ -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,23 @@ 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
|
}
|
|
269
|
+
/**
|
|
270
|
+
* End, as abandoned with `reason`, every live operation of local group `group` whose party member or frozen
|
|
271
|
+
* DH custodian is `member`. Releasing the approval drops the DH owner's held query. Returns the Grants ended.
|
|
272
|
+
*/
|
|
273
|
+
endBoundToMember(group, member, reason) {
|
|
274
|
+
const bound = [...this.live.values()].filter(op => op.group?.party.group.pre === group
|
|
275
|
+
&& (op.group.party.member.pre === member || op.group.party.custodian === member));
|
|
276
|
+
for (const op of bound)
|
|
277
|
+
this.end(op, "abandoned", reason);
|
|
278
|
+
return bound.map(op => op.grant);
|
|
279
|
+
}
|
|
225
280
|
/** Drop the live entry and the DH approval; a missing approval means the owner already closed. */
|
|
226
281
|
release(op) {
|
|
227
|
-
this.live.delete(op.grant);
|
|
282
|
+
this.live.delete(partyKey(op.grant, op.role));
|
|
228
283
|
try {
|
|
229
284
|
op.dh.abandon(op.xid);
|
|
230
285
|
}
|
|
@@ -239,7 +294,8 @@ export class IssuanceOperations {
|
|
|
239
294
|
* dropped and reads from the stores. False means the Keeper is locked and the Grant is retried.
|
|
240
295
|
*/
|
|
241
296
|
*settle(found) {
|
|
242
|
-
|
|
297
|
+
const key = partyKey(found.grant, found.role);
|
|
298
|
+
if (this.live.has(key))
|
|
243
299
|
return true;
|
|
244
300
|
try {
|
|
245
301
|
found.hab.mgr.requireRegistryRootAccess();
|
|
@@ -251,18 +307,18 @@ export class IssuanceOperations {
|
|
|
251
307
|
}
|
|
252
308
|
// Another operations table over this runtime reached the Grant first (a command's own, beside the host's).
|
|
253
309
|
if (this.deps.dhkes.dhke(found.hab).holds(found.grant)) {
|
|
254
|
-
this.refused.add(
|
|
310
|
+
this.refused.add(key);
|
|
255
311
|
return true;
|
|
256
312
|
}
|
|
257
313
|
try {
|
|
258
314
|
yield* this.adopt(found);
|
|
259
315
|
}
|
|
260
316
|
catch (error) {
|
|
261
|
-
this.refused.add(
|
|
317
|
+
this.refused.add(key);
|
|
262
318
|
// A refusal means the stores no longer prove it resumable and status reads from them; anything else is
|
|
263
319
|
// this Grant's failure alone and must not stop the other operations.
|
|
264
320
|
if (!(error instanceof ValidationError)) {
|
|
265
|
-
this.ended.set(
|
|
321
|
+
this.ended.set(key, { role: found.role, state: "failed", reason: message(error) });
|
|
266
322
|
this.deps.changes?.push({ kind: "issuance", id: found.grant });
|
|
267
323
|
}
|
|
268
324
|
}
|
|
@@ -278,8 +334,8 @@ export class IssuanceOperations {
|
|
|
278
334
|
}
|
|
279
335
|
const wires = role === "issuer" ? yield* grantWires(retained) : null;
|
|
280
336
|
const dh = this.deps.dhkes.dhke(hab), xid = dh.resume(grant);
|
|
281
|
-
this.ended.delete(grant);
|
|
282
|
-
this.live.set(grant, {
|
|
337
|
+
this.ended.delete(partyKey(grant, role));
|
|
338
|
+
this.live.set(partyKey(grant, role), {
|
|
283
339
|
grant,
|
|
284
340
|
xid,
|
|
285
341
|
role,
|
|
@@ -308,8 +364,8 @@ export class IssuanceOperations {
|
|
|
308
364
|
const party = GroupParty.resolve(this.deps, hab, election.coordinator.said);
|
|
309
365
|
const group = new GroupIssuance(party, role, retained.serder, retained.nests ?? [], { mode: "resumed" });
|
|
310
366
|
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"));
|
|
367
|
+
this.ended.delete(partyKey(grant, role));
|
|
368
|
+
this.live.set(partyKey(grant, role), this.liveGroup(group, dh, xid, "rootRetained"));
|
|
313
369
|
this.deps.changes?.push({ kind: "issuance", id: grant });
|
|
314
370
|
}
|
|
315
371
|
/** The live entry of a group operation; its lifetime is the Grant's once KRAM has accepted it, else unknown. */
|
|
@@ -336,6 +392,20 @@ export class IssuanceOperations {
|
|
|
336
392
|
yield* waitOnClock(this.deps.clock, milliseconds);
|
|
337
393
|
return "expired";
|
|
338
394
|
}
|
|
395
|
+
*deliverToParty(message) {
|
|
396
|
+
const { hby, reactor, delivery } = this.deps, recipient = hby.habs.get(message.dest);
|
|
397
|
+
if (!recipient)
|
|
398
|
+
return yield* delivery.deliver(message);
|
|
399
|
+
yield* reactor.processBytes(message.msg);
|
|
400
|
+
const reached = [`local:${recipient.pre}`];
|
|
401
|
+
if (!isLocalGroupHab(hby, recipient))
|
|
402
|
+
return reached;
|
|
403
|
+
const member = localGroupMember(hby, recipient.pre);
|
|
404
|
+
for (const dest of remoteGroupMembers(hby, member, groupSigningMembers(hby, recipient.pre))) {
|
|
405
|
+
reached.push(...yield* delivery.deliver({ ...message, dest }));
|
|
406
|
+
}
|
|
407
|
+
return reached;
|
|
408
|
+
}
|
|
339
409
|
/**
|
|
340
410
|
* Queue signed bytes for delivery from the identifier that signed them. Steps decide what to send; {@link drain}
|
|
341
411
|
* sends it, so a member that is briefly offline delays the operation instead of ending it.
|
|
@@ -349,7 +419,7 @@ export class IssuanceOperations {
|
|
|
349
419
|
* lasting {@link ISSUANCE_RETRY_BOUND_MS} end one that has none yet. True when the outbox is empty.
|
|
350
420
|
*/
|
|
351
421
|
*drain(op) {
|
|
352
|
-
return yield* op.outbox.drain(this.
|
|
422
|
+
return yield* op.outbox.drain(this.transport, this.deps.clock, op.expires === null ? this.deps.retry?.boundMs ?? ISSUANCE_RETRY_BOUND_MS : undefined);
|
|
353
423
|
}
|
|
354
424
|
/** One pass: send what is queued, step only once the queue is empty, send what the step queued. */
|
|
355
425
|
*advance(op) {
|
|
@@ -388,14 +458,16 @@ export class IssuanceOperations {
|
|
|
388
458
|
: group.role === "issuer"
|
|
389
459
|
? "awaitingGrantSignatures"
|
|
390
460
|
: "exchangingKeys";
|
|
391
|
-
this.ended.delete(group.said);
|
|
392
|
-
this.live.set(group.said, this.liveGroup(group, dh, xid, phase));
|
|
461
|
+
this.ended.delete(partyKey(group.said, group.role));
|
|
462
|
+
this.live.set(partyKey(group.said, group.role), this.liveGroup(group, dh, xid, phase));
|
|
393
463
|
deps.changes?.push({ kind: "issuance", id: group.said });
|
|
394
464
|
return { grant: group.said, xid };
|
|
395
465
|
}
|
|
396
|
-
/** Whether
|
|
397
|
-
owes(grant) {
|
|
398
|
-
return
|
|
466
|
+
/** Whether a live operation for `grant` (or only its party `role`) still holds a message the transport has not taken. */
|
|
467
|
+
owes(grant, role) {
|
|
468
|
+
return role
|
|
469
|
+
? (this.liveOf(grant, role)?.outbox.length ?? 0) > 0
|
|
470
|
+
: this.owes(grant, "issuer") || this.owes(grant, "holder");
|
|
399
471
|
}
|
|
400
472
|
/** One pass of the issuer loop, in the order of the CLI loop it replaces. */
|
|
401
473
|
*step(op) {
|
|
@@ -525,7 +597,7 @@ function resumableGrants(deps) {
|
|
|
525
597
|
if (parties.issuer && resumable(deps, parties.issuer, grant, "issuer")) {
|
|
526
598
|
found.push({ hab: parties.issuer, grant, role: "issuer" });
|
|
527
599
|
}
|
|
528
|
-
|
|
600
|
+
if (parties.holder && resumable(deps, parties.holder, grant, "holder")) {
|
|
529
601
|
found.push({ hab: parties.holder, grant, role: "holder" });
|
|
530
602
|
}
|
|
531
603
|
}
|
|
@@ -575,7 +647,7 @@ function selectGrant(ops, hab, grant, role, group = false) {
|
|
|
575
647
|
const { deps } = ops;
|
|
576
648
|
if (!group)
|
|
577
649
|
requireIndividual(deps, hab);
|
|
578
|
-
if (ops.
|
|
650
|
+
if (ops.liveOf(grant, role) || partyComplete(deps, grant, role)) {
|
|
579
651
|
throw new ValidationError("Issuance was already begun for this Grant");
|
|
580
652
|
}
|
|
581
653
|
const retained = deps.exc.retainedInput(grant, { pending: true });
|
|
@@ -594,6 +666,16 @@ function selectGrant(ops, hab, grant, role, group = false) {
|
|
|
594
666
|
throw new Error("Authenticated pending Grant has a missing or corrupt deadline");
|
|
595
667
|
return { retained, credential, deadline, expires };
|
|
596
668
|
}
|
|
669
|
+
/**
|
|
670
|
+
* Whether one party's side of a Grant is finished. The accepted Grant is one fact of the Habery, but a Habery that
|
|
671
|
+
* holds both parties finishes the holder's side only when the holder's own exchange has retained its root.
|
|
672
|
+
*/
|
|
673
|
+
export function partyComplete(deps, grant, role) {
|
|
674
|
+
if (!deps.exc.complete(grant))
|
|
675
|
+
return false;
|
|
676
|
+
const parties = grantParties(deps.hby, grant);
|
|
677
|
+
return role === "issuer" || !parties?.issuer || !parties.holder || heldRoot(deps, parties.holder, grant);
|
|
678
|
+
}
|
|
597
679
|
/** The Grant body with its nested disclosure (the input of proof support) and the Grant as sent to the holder. */
|
|
598
680
|
function* grantWires(retained) {
|
|
599
681
|
const raw = concatBytes(Counter.makeGVC(Vrsn_2_0), ...yield* call(() => Promise.all((retained.nests ?? []).map((nest) => collectPayload(framePayload(nest))))));
|
|
@@ -616,8 +698,7 @@ export function* beginIssuance(ops, grant, auths) {
|
|
|
616
698
|
const { retained, credential, deadline, expires } = selectGrant(ops, hab, grant, "issuer");
|
|
617
699
|
const { raw, grantWire } = yield* grantWires(retained);
|
|
618
700
|
const dh = deps.dhkes.dhke(hab), xid = dh.approve(grant), query = dh.start(grant);
|
|
619
|
-
ops.
|
|
620
|
-
ops.live.set(grant, {
|
|
701
|
+
ops.begin({
|
|
621
702
|
grant,
|
|
622
703
|
xid,
|
|
623
704
|
role: "issuer",
|
|
@@ -651,8 +732,10 @@ export function* proposeIssuance(ops, draft, auths) {
|
|
|
651
732
|
*
|
|
652
733
|
* @throws ValidationError when `grant` is not a v2 Grant this runtime's identifiers are party to.
|
|
653
734
|
*/
|
|
654
|
-
export function issuanceStatus(ops, grant) {
|
|
655
|
-
const { deps } = ops,
|
|
735
|
+
export function issuanceStatus(ops, grant, party = {}) {
|
|
736
|
+
const { deps } = ops, parties = grantParties(deps.hby, grant);
|
|
737
|
+
const chosen = chooseParty(parties, party);
|
|
738
|
+
const ended = ops.endedOf(grant, chosen), live = ops.liveOf(grant, chosen);
|
|
656
739
|
if (ended)
|
|
657
740
|
return { state: ended.state, role: ended.role, reason: ended.reason };
|
|
658
741
|
if (live && !reached(deps.clock, live.expires)) {
|
|
@@ -664,19 +747,18 @@ export function issuanceStatus(ops, grant) {
|
|
|
664
747
|
...(live.outbox.failure && { failure: live.outbox.failure }),
|
|
665
748
|
};
|
|
666
749
|
}
|
|
667
|
-
const parties = grantParties(deps.hby, grant);
|
|
668
750
|
if (!parties)
|
|
669
751
|
throw new ValidationError("No issuance was begun for this Grant");
|
|
670
752
|
const { serder, issuer, holder } = parties, db = deps.hby.db;
|
|
671
|
-
if (deps
|
|
753
|
+
if (partyComplete(deps, grant, chosen)) {
|
|
672
754
|
const admitted = !!db.erpy.get([grant]);
|
|
673
|
-
if (
|
|
755
|
+
if (chosen === "issuer")
|
|
674
756
|
return { state: "done", role: "issuer", phase: "issued" };
|
|
675
757
|
if (admitted)
|
|
676
758
|
return { state: "done", role: "holder", phase: "admitted" };
|
|
677
759
|
return { state: "active", role: "holder", phase: "awaitingAdmitConsent", needs: "admit" };
|
|
678
760
|
}
|
|
679
|
-
const role =
|
|
761
|
+
const role = chosen, hab = (role === "issuer" ? issuer : holder);
|
|
680
762
|
const group = isLocalGroupHab(deps.hby, hab);
|
|
681
763
|
// A group issuer's partial Grant has no accepted lifetime until its members' contributions authenticate it.
|
|
682
764
|
const lifetime = deps.exc.exchangeDeadline(serder) ?? undefined;
|
|
@@ -701,6 +783,24 @@ export function issuanceStatus(ops, grant) {
|
|
|
701
783
|
const [state, reason] = orphaned(deps, hab, grant);
|
|
702
784
|
return { state, role, reason };
|
|
703
785
|
}
|
|
786
|
+
/**
|
|
787
|
+
* The party a read is for: the one asked for, else the Habery's only party, else (it holds both) the issuer. Null
|
|
788
|
+
* when the Grant has no local party.
|
|
789
|
+
*
|
|
790
|
+
* @throws ValidationError when `choice` names a party this Habery does not hold.
|
|
791
|
+
*/
|
|
792
|
+
function chooseParty(parties, choice) {
|
|
793
|
+
if (!parties)
|
|
794
|
+
return choice.role;
|
|
795
|
+
const held = [
|
|
796
|
+
...(parties.issuer ? ["issuer"] : []),
|
|
797
|
+
...(parties.holder ? ["holder"] : []),
|
|
798
|
+
];
|
|
799
|
+
if (choice.role && !held.includes(choice.role)) {
|
|
800
|
+
throw new ValidationError(`This Habery holds no ${choice.role} of this Grant`);
|
|
801
|
+
}
|
|
802
|
+
return choice.role ?? held[0];
|
|
803
|
+
}
|
|
704
804
|
/**
|
|
705
805
|
* Pure read of the Grants waiting for the holder's exchange consent: authenticated pending v2 Grants addressed
|
|
706
806
|
* to a local identifier (to `hab` when given) for which {@link approveIssuanceExchange} would be accepted.
|
|
@@ -713,7 +813,7 @@ export function pendingIssuanceGrants(ops, { hab } = {}) {
|
|
|
713
813
|
if (!holder || (hab && holder.pre !== hab.pre))
|
|
714
814
|
continue;
|
|
715
815
|
try {
|
|
716
|
-
const status = issuanceStatus(ops, grant);
|
|
816
|
+
const status = issuanceStatus(ops, grant, { role: "holder" });
|
|
717
817
|
if (status.state !== "active" || status.needs !== "exchange")
|
|
718
818
|
continue;
|
|
719
819
|
const { retained, credential, deadline } = selectGrant(ops, holder, grant, "holder");
|
|
@@ -746,7 +846,7 @@ export function pendingGroupGrants(ops, { group } = {}) {
|
|
|
746
846
|
if (!parties || !holder || !isLocalGroupHab(deps.hby, holder) || (group && holder.pre !== group.pre))
|
|
747
847
|
continue;
|
|
748
848
|
try {
|
|
749
|
-
const status = issuanceStatus(ops, grant);
|
|
849
|
+
const status = issuanceStatus(ops, grant, { role: "holder" });
|
|
750
850
|
if (status.state !== "active" || status.needs !== "exchange")
|
|
751
851
|
continue;
|
|
752
852
|
const credential = deps.exc.retainedInput(grant, { pending: true })?.nests?.[0]?.body;
|
|
@@ -779,13 +879,13 @@ function heldRoot(deps, hab, grant) {
|
|
|
779
879
|
* Observe until the issuance is done or ended, or the budget expires. Starts no
|
|
780
880
|
* worker and drives nothing: someone else must call `processOnce`.
|
|
781
881
|
*/
|
|
782
|
-
export function* waitForIssuance(ops, grant, { timeoutMs, clock = ops.deps.clock, until = (status) => status.state !== "active" }) {
|
|
882
|
+
export function* waitForIssuance(ops, grant, { timeoutMs, clock = ops.deps.clock, until = (status) => status.state !== "active", role }) {
|
|
783
883
|
// 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), {
|
|
884
|
+
const settled = yield* observeUntil(() => until(issuanceStatus(ops, grant, { role })) && !ops.owes(grant, role), {
|
|
785
885
|
timeoutMs,
|
|
786
886
|
clock,
|
|
787
887
|
});
|
|
788
|
-
const status = issuanceStatus(ops, grant);
|
|
888
|
+
const status = issuanceStatus(ops, grant, { role });
|
|
789
889
|
return settled ? { outcome: "settled", status } : { outcome: "timed-out", status, timeoutMs };
|
|
790
890
|
}
|
|
791
891
|
/**
|
|
@@ -799,8 +899,7 @@ export function approveIssuanceExchange(ops, grant) {
|
|
|
799
899
|
throw new ValidationError("Select the exact unanchored pending issuance Grant for this local party");
|
|
800
900
|
const { retained, credential, deadline, expires } = selectGrant(ops, hab, grant, "holder");
|
|
801
901
|
const dh = deps.dhkes.dhke(hab), xid = dh.approve(grant);
|
|
802
|
-
ops.
|
|
803
|
-
ops.live.set(grant, {
|
|
902
|
+
ops.begin({
|
|
804
903
|
grant,
|
|
805
904
|
xid,
|
|
806
905
|
role: "holder",
|
|
@@ -882,15 +981,15 @@ export function* rebuildIssuanceAdmit(deps, admit) {
|
|
|
882
981
|
*
|
|
883
982
|
* @throws ValidationError when no issuance was begun for `grant`.
|
|
884
983
|
*/
|
|
885
|
-
export function abandonIssuance(ops, grant, reason = "Abandoned by the user") {
|
|
886
|
-
const status = issuanceStatus(ops, grant);
|
|
984
|
+
export function abandonIssuance(ops, grant, reason = "Abandoned by the user", party = {}) {
|
|
985
|
+
const status = issuanceStatus(ops, grant, party);
|
|
887
986
|
if (status.state !== "active")
|
|
888
987
|
return;
|
|
889
|
-
const live = ops.
|
|
988
|
+
const live = ops.liveOf(grant, status.role);
|
|
890
989
|
if (live)
|
|
891
990
|
ops.end(live, "abandoned", reason);
|
|
892
991
|
else {
|
|
893
|
-
ops.
|
|
992
|
+
ops.remember(grant, { role: status.role, state: "abandoned", reason });
|
|
894
993
|
ops.deps.changes?.push({ kind: "issuance", id: grant });
|
|
895
994
|
}
|
|
896
995
|
}
|
|
@@ -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
|
+
}
|
|
@@ -184,7 +184,7 @@ export function* preparePresentation(deps, input) {
|
|
|
184
184
|
break;
|
|
185
185
|
case "agree":
|
|
186
186
|
case "spurn":
|
|
187
|
-
prepared = yield* discloser.prepareReply(sender, input.verb, input.
|
|
187
|
+
prepared = yield* discloser.prepareReply(sender, input.verb, input.priorSaid, input.message ?? "", input.recipient, input.auths);
|
|
188
188
|
break;
|
|
189
189
|
case "offer":
|
|
190
190
|
prepared = yield* discloser.prepareOffer(sender, input.selection, input.options);
|
|
@@ -259,6 +259,41 @@ export function* proposeGroupPresentation(deps, groupIpex, { hab, selection, opt
|
|
|
259
259
|
},
|
|
260
260
|
});
|
|
261
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* A group's consent to send an Apply, Offer, Agree or Spurn: freezes the unsigned body (the same body a single
|
|
264
|
+
* identifier would sign) and proposes it as a group-authored message with {@link proposeGroupIpex}. The other members
|
|
265
|
+
* list and approve it with `approveGroupIpex` and the elected lead delivers the completed message. A Grant has its own
|
|
266
|
+
* call, {@link proposeGroupPresentation}; an Admit has `proposeGroupAdmit`.
|
|
267
|
+
*
|
|
268
|
+
* @throws ValidationError for an identifier that is not a local group, a selection or predecessor the discloser
|
|
269
|
+
* refuses, an exchange that requires anchoring (a group message carries member contributions, not one source seal),
|
|
270
|
+
* or a message awaiting authenticating dependencies.
|
|
271
|
+
*/
|
|
272
|
+
export function* proposeGroupNegotiation(deps, groupIpex, input) {
|
|
273
|
+
const { hab, deliver } = input;
|
|
274
|
+
const sender = hab.pre;
|
|
275
|
+
let exn, nests, gvrsn;
|
|
276
|
+
switch (input.verb) {
|
|
277
|
+
case "apply":
|
|
278
|
+
gvrsn = input.options.gvrsn;
|
|
279
|
+
exn = discloserFor(deps, hab).applyBody(sender, input.options);
|
|
280
|
+
break;
|
|
281
|
+
case "offer": {
|
|
282
|
+
gvrsn = input.options.gvrsn;
|
|
283
|
+
const offer = yield* discloserFor(deps, hab, input.selection.derive).prepareOfferBody(sender, input.selection, input.options);
|
|
284
|
+
({ exn, nests } = offer);
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
case "agree":
|
|
288
|
+
case "spurn":
|
|
289
|
+
exn = yield* discloserFor(deps, hab).replyBody(sender, input.verb, input.priorSaid, input.message ?? "", input.recipient);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
if (exn.sad.a.ax?.[0] === true) {
|
|
293
|
+
throw new ValidationError("A group cannot send an exchange that requires anchoring.");
|
|
294
|
+
}
|
|
295
|
+
return yield* proposeGroupIpex(groupIpex, { hab, exn, nests, gvrsn: gvrsn ?? undefined, deliver });
|
|
296
|
+
}
|
|
262
297
|
/**
|
|
263
298
|
* Deliver a group's completed prepared Grant ({@link resumePresentationFile}): queues the delivery bytes for
|
|
264
299
|
* `processOnce` when the local member is the elected lead, with the retry and deadline of any presentation. Signs
|