keri-ts 2.2.4 → 2.2.5
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 +14 -14
- package/esm/keri/npm/src/acdc/registering.js +19 -0
- package/esm/keri/npm/src/acdc/salting.js +4 -0
- package/esm/keri/npm/src/app/bootstrap/authenticating.js +32 -10
- package/esm/keri/npm/src/app/bootstrap/oobiery.js +363 -30
- package/esm/keri/npm/src/app/credentials/credential-importing.js +33 -4
- package/esm/keri/npm/src/app/credentials/root-replicator.js +27 -15
- package/esm/keri/npm/src/app/credentials/schema-resolving.js +5 -1
- package/esm/keri/npm/src/app/credentials/tel-publishing.js +16 -7
- package/esm/keri/npm/src/app/delivery/forwarding.js +177 -43
- package/esm/keri/npm/src/app/delivery/relaying.js +232 -0
- package/esm/keri/npm/src/app/delivery/respondant.js +8 -5
- package/esm/keri/npm/src/app/exchanges/exchange-messaging.js +1 -1
- package/esm/keri/npm/src/app/exchanges/exchanging.js +65 -9
- package/esm/keri/npm/src/app/identity/delegating.js +66 -9
- package/esm/keri/npm/src/app/identity/habbing.js +190 -71
- package/esm/keri/npm/src/app/identity/keeping.js +111 -39
- package/esm/keri/npm/src/app/identity/retire-identifier.js +98 -0
- package/esm/keri/npm/src/app/ipex/disclosure-replaying.js +116 -0
- package/esm/keri/npm/src/app/ipex/group-ipex.js +122 -25
- package/esm/keri/npm/src/app/ipex/group-issuance.js +109 -23
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +36 -22
- package/esm/keri/npm/src/app/ipex/ipex-grouping.js +53 -3
- package/esm/keri/npm/src/app/ipex/ipex-v2.js +7 -2
- package/esm/keri/npm/src/app/ipex/issuance.js +131 -35
- package/esm/keri/npm/src/app/ipex/pending-presentations.js +4 -2
- package/esm/keri/npm/src/app/ipex/presenting.js +3 -18
- package/esm/keri/npm/src/app/ipex/receive-issuance.js +2 -1
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +16 -3
- package/esm/keri/npm/src/app/mailbox/agent-inbox.js +292 -0
- package/esm/keri/npm/src/app/mailbox/forward-handler.js +298 -52
- package/esm/keri/npm/src/app/mailbox/kel-publisher.js +26 -31
- package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +70 -62
- package/esm/keri/npm/src/app/mailbox/mailboxing.js +20 -4
- package/esm/keri/npm/src/app/multisig/counseling.js +71 -27
- package/esm/keri/npm/src/app/multisig/group-members.js +41 -5
- package/esm/keri/npm/src/app/multisig/grouping.js +50 -2
- package/esm/keri/npm/src/app/multisig/multisig-workflows.js +3 -18
- package/esm/keri/npm/src/app/multisig/proposing.js +1 -16
- package/esm/keri/npm/src/app/multisig/rejoin-group.js +99 -0
- package/esm/keri/npm/src/app/multisig/replace-member.js +29 -8
- package/esm/keri/npm/src/app/protocol/parsering.js +45 -17
- package/esm/keri/npm/src/app/protocol/protocol-processing.js +47 -21
- package/esm/keri/npm/src/app/queries/query-replies.js +0 -22
- package/esm/keri/npm/src/app/queries/relay-refresh.js +81 -0
- package/esm/keri/npm/src/app/queries/witness-inquisitor.js +180 -34
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +41 -72
- package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
- package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
- package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
- package/esm/keri/npm/src/app/transport/http-client.js +20 -2
- package/esm/keri/npm/src/app/transport/message-source.js +2 -1
- package/esm/keri/npm/src/app/transport/messengering.js +39 -8
- package/esm/keri/npm/src/app/witness/ingress.js +7 -0
- package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
- package/esm/keri/npm/src/app/witness/receipting.js +62 -20
- package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
- package/esm/keri/npm/src/core/errors.js +73 -0
- package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
- package/esm/keri/npm/src/core/kel/eventing.js +25 -28
- package/esm/keri/npm/src/core/kel/kever.js +15 -9
- package/esm/keri/npm/src/core/mailbox/kram-policy.js +8 -0
- package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
- package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
- package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
- package/esm/keri/npm/src/core/routing.js +18 -25
- package/esm/keri/npm/src/core/scheming.js +12 -5
- package/esm/keri/npm/src/core/wire-fields.js +126 -0
- package/esm/keri/npm/src/db/basing.js +4 -13
- package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
- package/esm/keri/npm/src/db/core/webdber.js +85 -27
- package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
- package/esm/keri/npm/src/db/subing.js +4 -0
- package/esm/keri/npm/src/runtime/index.js +11 -5
- package/esm/keri/npm/src/time/mod.js +5 -0
- package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
- package/esm/keri/npm/src/vdr/eventing.js +28 -19
- package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +1 -1
- package/types/_dnt.polyfills.d.ts +9 -9
- package/types/_dnt.polyfills.d.ts.map +1 -1
- package/types/acdc/registering.d.ts +6 -0
- package/types/acdc/registering.d.ts.map +1 -1
- package/types/acdc/salting.d.ts.map +1 -1
- package/types/app/bootstrap/authenticating.d.ts +12 -2
- package/types/app/bootstrap/authenticating.d.ts.map +1 -1
- package/types/app/bootstrap/oobiery.d.ts +144 -7
- package/types/app/bootstrap/oobiery.d.ts.map +1 -1
- package/types/app/credentials/credential-importing.d.ts +8 -0
- package/types/app/credentials/credential-importing.d.ts.map +1 -1
- package/types/app/credentials/root-replicator.d.ts +19 -8
- package/types/app/credentials/root-replicator.d.ts.map +1 -1
- package/types/app/credentials/schema-resolving.d.ts.map +1 -1
- package/types/app/credentials/tel-publishing.d.ts +4 -2
- package/types/app/credentials/tel-publishing.d.ts.map +1 -1
- package/types/app/delivery/forwarding.d.ts +69 -5
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/delivery/relaying.d.ts +144 -0
- package/types/app/delivery/relaying.d.ts.map +1 -0
- package/types/app/delivery/respondant.d.ts +9 -2
- package/types/app/delivery/respondant.d.ts.map +1 -1
- package/types/app/exchanges/exchanging.d.ts +36 -0
- package/types/app/exchanges/exchanging.d.ts.map +1 -1
- package/types/app/identity/delegating.d.ts +6 -0
- package/types/app/identity/delegating.d.ts.map +1 -1
- package/types/app/identity/habbing.d.ts +106 -19
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/identity/keeping.d.ts +54 -19
- package/types/app/identity/keeping.d.ts.map +1 -1
- package/types/app/identity/retire-identifier.d.ts +24 -0
- package/types/app/identity/retire-identifier.d.ts.map +1 -0
- package/types/app/ipex/disclosure-replaying.d.ts +61 -0
- package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
- package/types/app/ipex/group-ipex.d.ts +60 -14
- package/types/app/ipex/group-ipex.d.ts.map +1 -1
- package/types/app/ipex/group-issuance.d.ts +48 -6
- package/types/app/ipex/group-issuance.d.ts.map +1 -1
- package/types/app/ipex/ipex-discloser.d.ts +14 -2
- package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
- package/types/app/ipex/ipex-grouping.d.ts +23 -2
- package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
- package/types/app/ipex/ipex-v2.d.ts +5 -2
- package/types/app/ipex/ipex-v2.d.ts.map +1 -1
- package/types/app/ipex/issuance.d.ts +35 -11
- package/types/app/ipex/issuance.d.ts.map +1 -1
- package/types/app/ipex/pending-presentations.d.ts +4 -2
- package/types/app/ipex/pending-presentations.d.ts.map +1 -1
- package/types/app/ipex/presenting.d.ts.map +1 -1
- package/types/app/ipex/receive-issuance.d.ts +2 -1
- package/types/app/ipex/receive-issuance.d.ts.map +1 -1
- package/types/app/ipex/unblinding-workflow.d.ts +4 -1
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
- package/types/app/mailbox/agent-inbox.d.ts +126 -0
- package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
- package/types/app/mailbox/forward-handler.d.ts +114 -14
- package/types/app/mailbox/forward-handler.d.ts.map +1 -1
- package/types/app/mailbox/kel-publisher.d.ts +12 -22
- package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
- package/types/app/mailbox/mailbox-poller.d.ts +40 -24
- package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
- package/types/app/mailbox/mailboxing.d.ts +19 -5
- package/types/app/mailbox/mailboxing.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +32 -11
- package/types/app/multisig/counseling.d.ts.map +1 -1
- package/types/app/multisig/group-members.d.ts +17 -1
- package/types/app/multisig/group-members.d.ts.map +1 -1
- package/types/app/multisig/grouping.d.ts +17 -1
- package/types/app/multisig/grouping.d.ts.map +1 -1
- package/types/app/multisig/multisig-workflows.d.ts +0 -8
- package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
- package/types/app/multisig/proposing.d.ts +4 -19
- package/types/app/multisig/proposing.d.ts.map +1 -1
- package/types/app/multisig/rejoin-group.d.ts +119 -0
- package/types/app/multisig/rejoin-group.d.ts.map +1 -0
- package/types/app/multisig/replace-member.d.ts +15 -1
- package/types/app/multisig/replace-member.d.ts.map +1 -1
- package/types/app/protocol/parsering.d.ts +21 -2
- package/types/app/protocol/parsering.d.ts.map +1 -1
- package/types/app/protocol/protocol-processing.d.ts +25 -5
- package/types/app/protocol/protocol-processing.d.ts.map +1 -1
- package/types/app/queries/query-replies.d.ts +4 -8
- package/types/app/queries/query-replies.d.ts.map +1 -1
- package/types/app/queries/relay-refresh.d.ts +26 -0
- package/types/app/queries/relay-refresh.d.ts.map +1 -0
- package/types/app/queries/witness-inquisitor.d.ts +56 -5
- package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
- package/types/app/runtime/agent-runtime.d.ts +34 -3
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/cue-runtime.d.ts +15 -3
- package/types/app/runtime/cue-runtime.d.ts.map +1 -1
- package/types/app/runtime/operation-owners.d.ts +16 -26
- package/types/app/runtime/operation-owners.d.ts.map +1 -1
- package/types/app/runtime/workflow-contract.d.ts +21 -6
- package/types/app/runtime/workflow-contract.d.ts.map +1 -1
- package/types/app/transport/http-client.d.ts +9 -0
- package/types/app/transport/http-client.d.ts.map +1 -1
- package/types/app/transport/message-source.d.ts +6 -3
- package/types/app/transport/message-source.d.ts.map +1 -1
- package/types/app/transport/messengering.d.ts +10 -1
- package/types/app/transport/messengering.d.ts.map +1 -1
- package/types/app/witness/ingress.d.ts +1 -0
- package/types/app/witness/ingress.d.ts.map +1 -1
- package/types/app/witness/receipt-client.d.ts +19 -1
- package/types/app/witness/receipt-client.d.ts.map +1 -1
- package/types/app/witness/receipting.d.ts +24 -1
- package/types/app/witness/receipting.d.ts.map +1 -1
- package/types/cli/messaging/multisig.d.ts.map +1 -1
- package/types/core/cues.d.ts +53 -1
- package/types/core/cues.d.ts.map +1 -1
- package/types/core/errors.d.ts +32 -0
- package/types/core/errors.d.ts.map +1 -1
- package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
- package/types/core/kel/eventing.d.ts.map +1 -1
- package/types/core/kel/kever-decisions.d.ts +3 -1
- package/types/core/kel/kever-decisions.d.ts.map +1 -1
- package/types/core/kel/kever.d.ts +7 -1
- package/types/core/kel/kever.d.ts.map +1 -1
- package/types/core/mailbox/kram-policy.d.ts +6 -0
- package/types/core/mailbox/kram-policy.d.ts.map +1 -1
- package/types/core/mailbox/kram-pruning.d.ts +5 -1
- package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
- package/types/core/mailbox/kram-time.d.ts +26 -2
- package/types/core/mailbox/kram-time.d.ts.map +1 -1
- package/types/core/mailbox/kraming.d.ts +14 -1
- package/types/core/mailbox/kraming.d.ts.map +1 -1
- package/types/core/routing.d.ts +3 -7
- package/types/core/routing.d.ts.map +1 -1
- package/types/core/scheming.d.ts.map +1 -1
- package/types/core/wire-fields.d.ts +56 -0
- package/types/core/wire-fields.d.ts.map +1 -0
- package/types/db/basing.d.ts.map +1 -1
- package/types/db/core/indexeddb-persistence.d.ts +3 -2
- package/types/db/core/indexeddb-persistence.d.ts.map +1 -1
- package/types/db/core/webdber.d.ts +32 -6
- package/types/db/core/webdber.d.ts.map +1 -1
- package/types/db/exchange/exchange-records.d.ts +4 -0
- package/types/db/exchange/exchange-records.d.ts.map +1 -1
- package/types/db/mailbox/mailboxing.d.ts +10 -1
- package/types/db/mailbox/mailboxing.d.ts.map +1 -1
- package/types/db/subing.d.ts +2 -0
- package/types/db/subing.d.ts.map +1 -1
- package/types/runtime/index.d.ts +20 -14
- package/types/runtime/index.d.ts.map +1 -1
- package/types/time/mod.d.ts +2 -0
- package/types/time/mod.d.ts.map +1 -1
- package/types/vdr/credential-tsn-record.d.ts.map +1 -1
- package/types/vdr/eventing.d.ts.map +1 -1
- package/types/vdr/registry-tsn-record.d.ts.map +1 -1
- package/types/version.d.ts +2 -2
- package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
- package/types/app/runtime/signer-conditions.d.ts +0 -32
- package/types/app/runtime/signer-conditions.d.ts.map +0 -1
|
@@ -88,6 +88,32 @@ export function* storedExchangeWire(retained, gvrsn = Vrsn_2_0) {
|
|
|
88
88
|
frame.nests = nests;
|
|
89
89
|
return yield* call(() => collectPayload(framePayload(frame)));
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Extend a frozen Grant's exact origin proof with its presented-state opening and the KEL seal of the registry update
|
|
93
|
+
* that commits to it; the Grant body and its SAID are unchanged. Ordinary intake still authenticates the supplied
|
|
94
|
+
* presentation evidence. Reads no store and signs nothing.
|
|
95
|
+
*
|
|
96
|
+
* @throws ValidationError when the opening does not target this exact Grant's disclosed Issuee in the `presented` state.
|
|
97
|
+
*/
|
|
98
|
+
export function* attachPresentationProof(prepared, opening, source) {
|
|
99
|
+
const frames = parseBytes(prepared.raw), node = frames[0].body;
|
|
100
|
+
if (!node.issuee || opening.td.nonce !== prepared.exn.said || opening.ts.label !== "presented") {
|
|
101
|
+
throw new ValidationError("Presentation proof must target this exact Grant and disclosed Issuee.");
|
|
102
|
+
}
|
|
103
|
+
const seal = Counter.enclose({
|
|
104
|
+
code: "SealSourceTriples",
|
|
105
|
+
qb64: concatBytes(new Prefixer({ qb64: node.issuee }).qb64b, source.s.qb64b, source.d.qb64b),
|
|
106
|
+
version: Vrsn_2_0,
|
|
107
|
+
});
|
|
108
|
+
const first = concatBytes(node.raw, Counter.enclose({
|
|
109
|
+
qb64: concatBytes(...frames[0].attachments.map(group => group.raw), publicOpeningAttachment(opening), seal),
|
|
110
|
+
version: Vrsn_2_0,
|
|
111
|
+
}));
|
|
112
|
+
const rest = yield* call(() => Promise.all(frames.slice(1).map(frame => collectPayload(nestedPayload(frame, Vrsn_2_0)))));
|
|
113
|
+
const raw = concatBytes(Counter.makeGVC(Vrsn_2_0), first, ...rest);
|
|
114
|
+
const payloads = yield* call(() => Promise.all(parseBytes(raw).map(frame => collectPayload(nestedPayload(frame, Vrsn_2_0)))));
|
|
115
|
+
return { exn: prepared.exn, raw, nests: concatBytes(...payloads) };
|
|
116
|
+
}
|
|
91
117
|
/** Borrow concrete owners only. No constructor opens a store, starts a worker or loads registry secrets. */
|
|
92
118
|
export class IpexDiscloser {
|
|
93
119
|
constructor(hby, registry, exc, receiptor, custody, views) {
|
|
@@ -201,7 +227,15 @@ export class IpexDiscloser {
|
|
|
201
227
|
}
|
|
202
228
|
/** Carry selected public evidence and root-first delegation ancestry; receiver intake still verifies every approval. */
|
|
203
229
|
proofSupport(raw, hab) {
|
|
204
|
-
|
|
230
|
+
return this.proofSupportFor(raw, [hab.pre]);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* `proofSupport` for explicitly named parties instead of one habitat: the KELs of `parties` and of every identifier
|
|
234
|
+
* the ACDC frames in `raw` name, each after its delegation ancestry, then the accepted TEL of every registry they
|
|
235
|
+
* name. Reads accepted state only and signs nothing, so it needs no keys.
|
|
236
|
+
*/
|
|
237
|
+
proofSupportFor(raw, parties) {
|
|
238
|
+
const aids = new Set(parties), registries = new Set();
|
|
205
239
|
for (const frame of parseBytes(raw)) {
|
|
206
240
|
if (!(frame.body instanceof SerderACDC))
|
|
207
241
|
continue;
|
|
@@ -399,26 +433,6 @@ export class IpexDiscloser {
|
|
|
399
433
|
const prior = yield* this.prior(priorSaid);
|
|
400
434
|
return ipexExnBody(sender, `/ipex/${verb}`, [], { message, prior, recipient });
|
|
401
435
|
}
|
|
402
|
-
/** Extend the frozen Grant's exact origin proof; ordinary intake still authenticates the supplied presentation evidence. */
|
|
403
|
-
*attachPresentationProof(prepared, opening, source) {
|
|
404
|
-
const frames = parseBytes(prepared.raw), node = frames[0].body;
|
|
405
|
-
if (!node.issuee || opening.td.nonce !== prepared.exn.said || opening.ts.label !== "presented") {
|
|
406
|
-
throw new ValidationError("Presentation proof must target this exact Grant and disclosed Issuee.");
|
|
407
|
-
}
|
|
408
|
-
const seal = Counter.enclose({
|
|
409
|
-
code: "SealSourceTriples",
|
|
410
|
-
qb64: concatBytes(new Prefixer({ qb64: node.issuee }).qb64b, source.s.qb64b, source.d.qb64b),
|
|
411
|
-
version: Vrsn_2_0,
|
|
412
|
-
});
|
|
413
|
-
const first = concatBytes(node.raw, Counter.enclose({
|
|
414
|
-
qb64: concatBytes(...frames[0].attachments.map(group => group.raw), publicOpeningAttachment(opening), seal),
|
|
415
|
-
version: Vrsn_2_0,
|
|
416
|
-
}));
|
|
417
|
-
const rest = yield* call(() => Promise.all(frames.slice(1).map(frame => collectPayload(nestedPayload(frame, Vrsn_2_0)))));
|
|
418
|
-
const raw = concatBytes(Counter.makeGVC(Vrsn_2_0), first, ...rest);
|
|
419
|
-
const payloads = yield* call(() => Promise.all(parseBytes(raw).map(frame => collectPayload(nestedPayload(frame, Vrsn_2_0)))));
|
|
420
|
-
return { exn: prepared.exn, raw, nests: concatBytes(...payloads) };
|
|
421
|
-
}
|
|
422
436
|
/** Select immutable admitted/transferred nests; proof refresh changes attachments only when explicitly requested. */
|
|
423
437
|
*selected(selection, offer = false) {
|
|
424
438
|
if ([selection.said, selection.fromGrant, selection.transferred].filter(value => value !== undefined).length !== 1) {
|
|
@@ -540,7 +554,7 @@ export class IpexDiscloser {
|
|
|
540
554
|
if (reg.store.seqEvent(reg.regk, Number(BigInt(`0x${update.sad.n}`)))?.said !== update.said) {
|
|
541
555
|
throw new ValidationError("Presentation BUP remains pending authenticated acceptance.");
|
|
542
556
|
}
|
|
543
|
-
({ raw, nests } = yield*
|
|
557
|
+
({ raw, nests } = yield* attachPresentationProof({ exn, raw, nests }, proof, seal));
|
|
544
558
|
}
|
|
545
559
|
const source = exn.sad.a.ax?.[0]
|
|
546
560
|
? yield* anchorExchange(this.hby, this.receiptor, hab, exn, options.auths)
|
|
@@ -12,7 +12,7 @@ import { call } from "effection";
|
|
|
12
12
|
import { evaluateChain } from "../../acdc/chaining.js";
|
|
13
13
|
import { resolveAttachmentGvrsn } from "../../core/attachment-countering.js";
|
|
14
14
|
import { TransIdxSigGroup } from "../../core/dispatch.js";
|
|
15
|
-
import { ValidationError } from "../../core/errors.js";
|
|
15
|
+
import { KramRefusedError, ValidationError } from "../../core/errors.js";
|
|
16
16
|
import { verifyExchangeProofs } from "../../core/exchange/exchange-proofing.js";
|
|
17
17
|
import { exchange, serializeMessage } from "../../core/exchange/protocol-exchanging.js";
|
|
18
18
|
import { Kever } from "../../core/kel/kever.js";
|
|
@@ -74,7 +74,19 @@ export function requireAcdcV2GroupUse(hby, protocol, embedded) {
|
|
|
74
74
|
throw new ValidationError("ACDC v2 group graph is unavailable for completed use.");
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
/**
|
|
77
|
+
/** KRAM refusals after which the message can no longer be accepted; a `replay` is routine, not one of them. */
|
|
78
|
+
const KRAM_EXPIRY_REASONS = new Set(["timestamp", "timeliness", "unknownThread", "lifetime"]);
|
|
79
|
+
/** Whether `decision` is this receiver's KRAM refusing a message for its time, its thread or its lifetime. */
|
|
80
|
+
function isKramExpiry(decision) {
|
|
81
|
+
return !!decision && decision.kind === "drop" && "reason" in decision && KRAM_EXPIRY_REASONS.has(decision.reason);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Approve exactly one ACDC v2 IPEX stream before adding this member's signature; no new acceptance rows are manufactured.
|
|
85
|
+
*
|
|
86
|
+
* @returns This member's accepted wire, or null while its evidence or dependencies are missing.
|
|
87
|
+
* @throws KramRefusedError when this member's own KRAM refuses the contribution for its time, thread or exchange
|
|
88
|
+
* lifetime: the message can no longer be accepted here. A `replay` refusal is routine and is not thrown.
|
|
89
|
+
*/
|
|
78
90
|
export function* prepareAcdcV2GroupIpex(hby, protocol, group, raw) {
|
|
79
91
|
const frames = parseBytes(concatBytes(Counter.makeGVC(Vrsn_2_0), raw));
|
|
80
92
|
const frame = frames[0], embedded = frame?.body;
|
|
@@ -136,7 +148,11 @@ export function* prepareAcdcV2GroupIpex(hby, protocol, group, raw) {
|
|
|
136
148
|
};
|
|
137
149
|
const attachments = yield* call(() => collectPayload(attachmentPayload(approved, { gvrsn: Vrsn_2_0 })));
|
|
138
150
|
const wire = concatBytes(embedded.raw, attachments);
|
|
139
|
-
|
|
151
|
+
// Dispatch the frame itself, not its bytes, so this member's own KRAM decision is visible. A refusal here is
|
|
152
|
+
// not a remote outcome to skip: the contribution was never accepted and nothing else will complete the message.
|
|
153
|
+
const { decision } = yield* protocol.dispatch(parseBytes(wire)[0], { local: true }, new Reactor(protocol).kvy);
|
|
154
|
+
if (isKramExpiry(decision))
|
|
155
|
+
throw new KramRefusedError(embedded.said, decision);
|
|
140
156
|
yield* protocol.processEscrowsOnce();
|
|
141
157
|
return wire;
|
|
142
158
|
}
|
|
@@ -457,6 +473,40 @@ export function* approveStoredMultisigIpexWrappers(hby, protocol, embeddedSaid,
|
|
|
457
473
|
yield* approveIpexChild(hby, protocol, groupHab, child, wrapper, reger);
|
|
458
474
|
}
|
|
459
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* The copies this member received of each v2 group IPEX message it has not accepted: per message, the child of each
|
|
478
|
+
* stored, authenticated `/multisig/exn` wrapper that carries it, with its signatures and nests. A pure read; whether a
|
|
479
|
+
* message may be taken from its copies is {@link groupIpexFollowable}'s rule.
|
|
480
|
+
*/
|
|
481
|
+
export function unacceptedGroupIpexCopies(hby, exchanger) {
|
|
482
|
+
const copies = new Map();
|
|
483
|
+
for (const [, wrapper] of hby.db.exns.getTopItemIter()) {
|
|
484
|
+
if (wrapper.route !== MULTISIG_EXN_ROUTE || wrapper.pvrsn.major !== 2)
|
|
485
|
+
continue;
|
|
486
|
+
const said = storedMultisigIpexBody(hby.db, wrapper)?.said;
|
|
487
|
+
if (!said || exchanger.complete(said) || !exchanger.verifyAccepted(wrapper.said, MULTISIG_EXN_ROUTE))
|
|
488
|
+
continue;
|
|
489
|
+
copies.set(said, [...copies.get(said) ?? [], loadNests(hby.db, wrapper.said)[0]]);
|
|
490
|
+
}
|
|
491
|
+
return [...copies.values()];
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Accept a group IPEX message from the copies this member received, adding no signature: dispatch each copy with its
|
|
495
|
+
* own proofs, then advance escrows so that copies whose signatures only together meet the threshold complete. The
|
|
496
|
+
* caller has checked {@link groupIpexFollowable}.
|
|
497
|
+
*
|
|
498
|
+
* @throws KramRefusedError when this member's own KRAM refuses a copy for its time, thread or exchange lifetime: the
|
|
499
|
+
* message can no longer be accepted here.
|
|
500
|
+
*/
|
|
501
|
+
export function* acceptGroupIpexCopies(protocol, copies) {
|
|
502
|
+
const kvy = new Reactor(protocol).kvy;
|
|
503
|
+
for (const copy of copies) {
|
|
504
|
+
const { decision } = yield* protocol.dispatch(copy, { local: true }, kvy);
|
|
505
|
+
if (isKramExpiry(decision))
|
|
506
|
+
throw new KramRefusedError(copy.body.said, decision);
|
|
507
|
+
}
|
|
508
|
+
yield* protocol.processEscrowsOnce();
|
|
509
|
+
}
|
|
460
510
|
/**
|
|
461
511
|
* Return the first incomplete stored `/multisig/exn` wrapping `/ipex/*`.
|
|
462
512
|
*
|
|
@@ -464,8 +464,13 @@ export class IpexV2Handler {
|
|
|
464
464
|
}
|
|
465
465
|
return { pending, registries };
|
|
466
466
|
}
|
|
467
|
-
/**
|
|
468
|
-
|
|
467
|
+
/**
|
|
468
|
+
* Notify only after Exchanger has durably accepted the parent and its evidence. A retained exchange (history a
|
|
469
|
+
* local import replayed, such as an already admitted Grant and its Admit) is not news: no notice asks anyone to act.
|
|
470
|
+
*/
|
|
471
|
+
handle({ serder, retained }) {
|
|
472
|
+
if (retained)
|
|
473
|
+
return;
|
|
469
474
|
this.notifier?.add({ r: `/exn${serder.route}`, d: serder.said, m: serder.sad.a.m });
|
|
470
475
|
}
|
|
471
476
|
/** Every disclosed own schema vets before presentation and issuer graph reduction. */
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* | holder: pending Grant, no root held | `awaitingExchangeConsent` |
|
|
23
23
|
* | pending Grant past its deadline | `expired` |
|
|
24
24
|
* | holder: root held, not committed otherwise; issuer: update accepted | `active`, `rootRetained` (adopted) |
|
|
25
|
+
* | group member: root copy received or the custodian's final proof stored | `active`, `awaitingAnchor` (followed) |
|
|
25
26
|
* | issuer: pending Grant, update not accepted | `interrupted` |
|
|
26
27
|
* | root held and the registry committed to another exchange | `interrupted` |
|
|
27
28
|
* | holder: Grant accepted, no accepted Admit | `active`, `awaitingAdmitConsent` |
|
|
@@ -54,9 +55,12 @@
|
|
|
54
55
|
* `{kind: "issuance", id: <grant>}` and the embedder records it.
|
|
55
56
|
*
|
|
56
57
|
* A local group issues and receives through the same table, status and wait: its consent calls and per-operation
|
|
57
|
-
* steps are in `group-issuance.ts` and run inside the same `processOnce`. A group exchange is not
|
|
58
|
-
* restart (its frozen coordinator and custodian are not stored): a group pending Grant with no root
|
|
59
|
-
* `awaitingExchangeConsent`, and a member consents again with the same call.
|
|
58
|
+
* steps are in `group-issuance.ts` and run inside the same `processOnce`. A group exchange in progress is not
|
|
59
|
+
* resumed after a restart (its frozen coordinator and custodian are not stored): a group pending Grant with no root
|
|
60
|
+
* reads `awaitingExchangeConsent`, and a member consents again with the same call. The custodian that retained the
|
|
61
|
+
* root is adopted once the stores allow it, and a member the group completed without is followed: the same sweep
|
|
62
|
+
* that finds resumable Grants begins the proof-only follow of each group Grant whose custodian's final proof is
|
|
63
|
+
* stored, signing nothing, so a restart begins it again.
|
|
60
64
|
*
|
|
61
65
|
* Collaborators are narrow concrete owners (ADR-0013); no `AgentRuntime`.
|
|
62
66
|
*/
|
|
@@ -65,14 +69,14 @@ import { call, race } from "effection";
|
|
|
65
69
|
import { RegistryAuthor } from "../../acdc/authoring.js";
|
|
66
70
|
import { RegistryCustody } from "../../acdc/custody.js";
|
|
67
71
|
import { grantTransactionId, registryUncommitted, resumableExchange } from "../../acdc/dhking.js";
|
|
68
|
-
import { ValidationError } from "../../core/errors.js";
|
|
72
|
+
import { KramRefusedError, ValidationError } from "../../core/errors.js";
|
|
69
73
|
import { CREDENTIAL_MAILBOX_TOPIC } from "../../core/mailbox/mailbox-topics.js";
|
|
70
74
|
import { timestampMicros } from "../../time/mod.js";
|
|
71
75
|
import { groupSigningMembers, isLocalGroupHab, localGroupMember } from "../multisig/group-members.js";
|
|
72
76
|
import { remoteGroupMembers } from "../multisig/multisig-messaging.js";
|
|
73
77
|
import { DeliveryOutbox, } from "../runtime/delivery-outbox.js";
|
|
74
78
|
import { observeUntil, waitOnClock } from "../runtime/workflow-contract.js";
|
|
75
|
-
import { deriveGroupCustody, GroupIssuance, GroupParty, registerGroupRoutes, stepGroup, } from "./group-issuance.js";
|
|
79
|
+
import { awaitsGroupProof, beginGroupProofFollow, deriveGroupCustody, GroupIssuance, GroupParty, registerGroupRoutes, stepGroup, storedGroupProof, } from "./group-issuance.js";
|
|
76
80
|
import { IpexAdmitter } from "./ipex-admitting.js";
|
|
77
81
|
import { exchangeWire, IpexDiscloser, storedExchangeWire } from "./ipex-discloser.js";
|
|
78
82
|
/**
|
|
@@ -83,9 +87,9 @@ import { exchangeWire, IpexDiscloser, storedExchangeWire } from "./ipex-disclose
|
|
|
83
87
|
*/
|
|
84
88
|
export const ISSUANCE_QUERY_RESEND_MS = 1000;
|
|
85
89
|
/**
|
|
86
|
-
* Milliseconds between scans of the pending exchange escrow for Grants a restarted process can finish
|
|
87
|
-
* repeats because what makes a Grant resumable can arrive after
|
|
88
|
-
* other members approve it).
|
|
90
|
+
* Milliseconds between scans of the pending exchange escrow for Grants a restarted process can finish and group
|
|
91
|
+
* Grants whose final proof a member follows. The scan repeats because what makes a Grant resumable can arrive after
|
|
92
|
+
* the restart (an issuer's update accepted once the other members approve it), and a custodian's proof arrives late.
|
|
89
93
|
*/
|
|
90
94
|
export const ISSUANCE_SWEEP_MS = 5_000;
|
|
91
95
|
/** The key of one party's operation on a Grant. */
|
|
@@ -219,23 +223,27 @@ export class IssuanceOperations {
|
|
|
219
223
|
remember(grant, ended) {
|
|
220
224
|
this.ended.set(partyKey(grant, ended.role), ended);
|
|
221
225
|
}
|
|
222
|
-
/** Whether a pass has work: a live operation, or
|
|
226
|
+
/** Whether a pass has work: a live operation, an adoption still owed, or a sweep of the pending Grants is due. */
|
|
223
227
|
active() {
|
|
224
228
|
return this.live.size > 0 || this.stale.length > 0 || this.deps.clock.now() >= this.nextSweep;
|
|
225
229
|
}
|
|
226
230
|
/**
|
|
227
231
|
* Advance every live operation by one pass. Each pass is bounded by the operation's Grant deadline.
|
|
228
|
-
*
|
|
229
|
-
*
|
|
232
|
+
* Every {@link ISSUANCE_SWEEP_MS} a pass also scans the pending exchange escrow once ({@link sweptGrants}): it
|
|
233
|
+
* adopts the Grants the stores prove resumable and begins the proof-only follow of the group Grants whose
|
|
234
|
+
* custodian's proof is stored. Neither signs nor sends anything.
|
|
230
235
|
*/
|
|
231
236
|
*processOnce() {
|
|
232
237
|
if (this.deps.clock.now() >= this.nextSweep) {
|
|
233
238
|
this.nextSweep = this.deps.clock.now() + ISSUANCE_SWEEP_MS;
|
|
234
239
|
const known = new Set(this.stale.map(({ grant, role }) => partyKey(grant, role)));
|
|
235
|
-
|
|
240
|
+
const swept = sweptGrants(this.deps, (grant, role) => {
|
|
236
241
|
const key = partyKey(grant, role);
|
|
237
|
-
return
|
|
238
|
-
})
|
|
242
|
+
return this.live.has(key) || known.has(key) || this.refused.has(key) || this.ended.has(key);
|
|
243
|
+
});
|
|
244
|
+
this.stale.push(...swept.resumable);
|
|
245
|
+
for (const found of swept.follows)
|
|
246
|
+
this.follow(found);
|
|
239
247
|
}
|
|
240
248
|
for (const found of [...this.stale]) {
|
|
241
249
|
if (yield* this.settle(found))
|
|
@@ -249,6 +257,10 @@ export class IssuanceOperations {
|
|
|
249
257
|
const left = op.expires === null ? null : op.expires - BigInt(this.deps.clock.now()) * 1000n;
|
|
250
258
|
if (left !== null && left <= 0n)
|
|
251
259
|
this.end(op, "expired", EXPIRED);
|
|
260
|
+
// A habitat that cannot sign yet waits for its signer; only its Grant deadline ends the operation meanwhile.
|
|
261
|
+
// A proof-only follow signs nothing, so it proceeds without one.
|
|
262
|
+
else if (!op.hab.canSign() && op.group?.mode !== "proofOnly")
|
|
263
|
+
continue;
|
|
252
264
|
else if ((yield* race([this.advance(op), ...(left === null ? [] : [this.timer(Number(left) / 1000)])])) === "expired")
|
|
253
265
|
this.end(op, "expired", EXPIRED);
|
|
254
266
|
}
|
|
@@ -263,7 +275,8 @@ export class IssuanceOperations {
|
|
|
263
275
|
/** End a live operation: release its DH approval, remember why until the process ends, tell the sink. */
|
|
264
276
|
end(op, state, reason) {
|
|
265
277
|
this.release(op);
|
|
266
|
-
|
|
278
|
+
const deadline = state === "expired" ? op.deadline : null;
|
|
279
|
+
this.ended.set(partyKey(op.grant, op.role), { role: op.role, state, reason, ...(deadline && { deadline }) });
|
|
267
280
|
this.deps.changes?.push({ kind: "issuance", id: op.grant });
|
|
268
281
|
}
|
|
269
282
|
/**
|
|
@@ -277,6 +290,14 @@ export class IssuanceOperations {
|
|
|
277
290
|
this.end(op, "abandoned", reason);
|
|
278
291
|
return bound.map(op => op.grant);
|
|
279
292
|
}
|
|
293
|
+
/** Grants of the live operations, and of those found resumable and not yet adopted, whose habitat is `pre`. */
|
|
294
|
+
grantsOf(pre) {
|
|
295
|
+
const grants = [...this.live.values()].filter(op => op.hab.pre === pre).map(op => op.grant);
|
|
296
|
+
for (const found of this.stale)
|
|
297
|
+
if (found.hab.pre === pre)
|
|
298
|
+
grants.push(found.grant);
|
|
299
|
+
return [...new Set(grants)];
|
|
300
|
+
}
|
|
280
301
|
/** Drop the live entry and the DH approval; a missing approval means the owner already closed. */
|
|
281
302
|
release(op) {
|
|
282
303
|
this.live.delete(partyKey(op.grant, op.role));
|
|
@@ -324,6 +345,24 @@ export class IssuanceOperations {
|
|
|
324
345
|
}
|
|
325
346
|
return true;
|
|
326
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* Begin the proof-only follow a sweep found, under the stored proof's election; it signs nothing, so it needs no
|
|
350
|
+
* unlocked Keeper. A refusal means the stores no longer support it: the Grant then reads from them and is not tried
|
|
351
|
+
* again in this process. Any other failure is this Grant's alone and ends it `failed`, as for an adoption.
|
|
352
|
+
*/
|
|
353
|
+
follow(found) {
|
|
354
|
+
const key = partyKey(found.grant, found.role);
|
|
355
|
+
try {
|
|
356
|
+
beginGroupProofFollow(this, found.grant, found.role, found.election.coordinator.said);
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
this.refused.add(key);
|
|
360
|
+
if (!(error instanceof ValidationError)) {
|
|
361
|
+
this.ended.set(key, { role: found.role, state: "failed", reason: message(error) });
|
|
362
|
+
this.deps.changes?.push({ kind: "issuance", id: found.grant });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
327
366
|
/** Rebuild the live entry of a resumable Grant from the stored Grant and the held roots. */
|
|
328
367
|
*adopt({ hab, grant, role }) {
|
|
329
368
|
const group = isLocalGroupHab(this.deps.hby, hab);
|
|
@@ -442,7 +481,8 @@ export class IssuanceOperations {
|
|
|
442
481
|
}
|
|
443
482
|
/**
|
|
444
483
|
* Record one group operation the user consented to ({@link approveGroupIssuance},
|
|
445
|
-
* {@link approveGroupIssuanceExchange},
|
|
484
|
+
* {@link approveGroupIssuanceExchange}), or a proof-only follow, which needs no consent ({@link beginGroupProofFollow},
|
|
485
|
+
* begun by the sweep or by {@link followGroupIssuanceProof}). A holder approves DH here, with its
|
|
446
486
|
* consent; an issuer approves once the group has authenticated the Grant.
|
|
447
487
|
*/
|
|
448
488
|
beginGroup(group) {
|
|
@@ -559,6 +599,20 @@ export class IssuanceOperations {
|
|
|
559
599
|
function reached(clock, expires) {
|
|
560
600
|
return expires !== null && BigInt(clock.now()) * 1000n >= expires;
|
|
561
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* Refuse a Grant whose exchange deadline has passed with the KRAM lifetime refusal that names that deadline,
|
|
604
|
+
* so a caller learns why the Grant can no longer be acted on rather than that it is unknown. The deadline
|
|
605
|
+
* outlives the Grant's KRAM cache row and its ejection from escrow ({@link Exchanger.exchangeExpiry}). Returns
|
|
606
|
+
* when the Grant has no known deadline or is still inside it; the caller then raises its own refusal.
|
|
607
|
+
*
|
|
608
|
+
* @throws KramRefusedError (reason `lifetime`) once the deadline has passed.
|
|
609
|
+
*/
|
|
610
|
+
function refuseLapsedGrant(deps, grant) {
|
|
611
|
+
const deadline = deps.exc.exchangeExpiry(grant);
|
|
612
|
+
if (deadline && reached(deps.clock, timestampMicros(deadline))) {
|
|
613
|
+
throw new KramRefusedError(grant, { kind: "drop", reason: "lifetime", deadline });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
562
616
|
/**
|
|
563
617
|
* Whether a restarted process can complete this Grant without a new key exchange: it is pending inside its
|
|
564
618
|
* deadline, `hab` is the party `role` names, and {@link resumableExchange} finds the roots. An issuer
|
|
@@ -585,23 +639,43 @@ function grantParties(hby, grant) {
|
|
|
585
639
|
return issuer || holder ? { serder, issuer, holder } : null;
|
|
586
640
|
}
|
|
587
641
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
642
|
+
* One scan of the pending exchange escrow, made every {@link ISSUANCE_SWEEP_MS}: each local party of a pending Grant
|
|
643
|
+
* that `skip` does not exclude (the owner skips what it holds live, ended, refused or already found) is either
|
|
644
|
+
* resumable, or a group member that follows the custodian's stored proof ({@link followable}). A pure read.
|
|
590
645
|
*/
|
|
591
|
-
function
|
|
592
|
-
const
|
|
646
|
+
function sweptGrants(deps, skip) {
|
|
647
|
+
const swept = { resumable: [], follows: [] };
|
|
593
648
|
for (const [[grant]] of [...deps.hby.db.epse.getTopItemIter()]) {
|
|
594
649
|
const parties = grantParties(deps.hby, grant);
|
|
595
650
|
if (!parties)
|
|
596
651
|
continue;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
652
|
+
for (const [hab, role] of [[parties.issuer, "issuer"], [parties.holder, "holder"]]) {
|
|
653
|
+
if (!hab || skip(grant, role))
|
|
654
|
+
continue;
|
|
655
|
+
if (resumable(deps, hab, grant, role)) {
|
|
656
|
+
swept.resumable.push({ hab, grant, role });
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
const election = followable(deps, hab, grant, role, parties.serder);
|
|
660
|
+
if (election)
|
|
661
|
+
swept.follows.push({ hab, grant, role, election });
|
|
602
662
|
}
|
|
603
663
|
}
|
|
604
|
-
return
|
|
664
|
+
return swept;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* The election under which the local member of group `hab` follows `grant` to its final proof, or null: the party's
|
|
668
|
+
* side is not complete, the Grant is inside its exchange deadline, and the custodian's proof is stored
|
|
669
|
+
* ({@link storedGroupProof}). Only the proof decides it, never the member's weight: a member that should sign is not
|
|
670
|
+
* followed, because the proof exists only once the key exchange no longer wants any signature.
|
|
671
|
+
*/
|
|
672
|
+
function followable(deps, hab, grant, role, serder) {
|
|
673
|
+
if (!isLocalGroupHab(deps.hby, hab) || partyComplete(deps, grant, role))
|
|
674
|
+
return null;
|
|
675
|
+
const deadline = deps.exc.exchangeDeadline(serder), expires = deadline ? timestampMicros(deadline) : null;
|
|
676
|
+
if (expires === null || reached(deps.clock, expires))
|
|
677
|
+
return null;
|
|
678
|
+
return storedGroupProof(deps, hab, grant);
|
|
605
679
|
}
|
|
606
680
|
/** The terminal reading of a pending Grant no process can complete: ended by a restart, or by another exchange. */
|
|
607
681
|
function orphaned(deps, hab, grant) {
|
|
@@ -641,7 +715,8 @@ function requireIndividual(deps, hab) {
|
|
|
641
715
|
}
|
|
642
716
|
/**
|
|
643
717
|
* The exact unanchored pending Grant a local party may consent to, with its deadline. Refuses a group
|
|
644
|
-
* identifier, an issuance already begun, a Grant that is not this party's, and an issuer without its registry
|
|
718
|
+
* identifier, an issuance already begun, a Grant that is not this party's, and an issuer without its registry;
|
|
719
|
+
* a Grant past its deadline is refused with `KramRefusedError`.
|
|
645
720
|
*/
|
|
646
721
|
function selectGrant(ops, hab, grant, role, group = false) {
|
|
647
722
|
const { deps } = ops;
|
|
@@ -655,6 +730,7 @@ function selectGrant(ops, hab, grant, role, group = false) {
|
|
|
655
730
|
if (!retained || !(credential instanceof SerderACDC) || !credential.regid
|
|
656
731
|
|| (role === "issuer" ? retained.serder.pre : retained.serder.sad.ri) !== hab.pre
|
|
657
732
|
|| retained.sourceSeals?.length || retained.ptds?.length || retained.essrs?.length) {
|
|
733
|
+
refuseLapsedGrant(deps, grant);
|
|
658
734
|
throw new ValidationError("Select the exact unanchored pending issuance Grant for this local party");
|
|
659
735
|
}
|
|
660
736
|
const reg = deps.rgy.regs.get(credential.regid);
|
|
@@ -689,12 +765,15 @@ function* grantWires(retained) {
|
|
|
689
765
|
*
|
|
690
766
|
* @throws ValidationError for a Grant that is not this identifier's exact
|
|
691
767
|
* unanchored pending issuance, an unmanaged registry, a group identifier, or
|
|
692
|
-
* an issuance already begun or completed
|
|
768
|
+
* an issuance already begun or completed; its subclass `KramRefusedError`
|
|
769
|
+
* (reason `lifetime`, with the deadline) for a Grant past its exchange deadline.
|
|
693
770
|
*/
|
|
694
771
|
export function* beginIssuance(ops, grant, auths) {
|
|
695
772
|
const { deps } = ops, hab = grantParties(deps.hby, grant)?.issuer;
|
|
696
|
-
if (!hab)
|
|
773
|
+
if (!hab) {
|
|
774
|
+
refuseLapsedGrant(deps, grant);
|
|
697
775
|
throw new ValidationError("Select the exact unanchored pending issuance Grant for this local party");
|
|
776
|
+
}
|
|
698
777
|
const { retained, credential, deadline, expires } = selectGrant(ops, hab, grant, "issuer");
|
|
699
778
|
const { raw, grantWire } = yield* grantWires(retained);
|
|
700
779
|
const dh = deps.dhkes.dhke(hab), xid = dh.approve(grant), query = dh.start(grant);
|
|
@@ -730,14 +809,18 @@ export function* proposeIssuance(ops, draft, auths) {
|
|
|
730
809
|
* operation holds reads `interrupted` (or `expired` once the Grant deadline has passed); that is a
|
|
731
810
|
* statement, not a write.
|
|
732
811
|
*
|
|
812
|
+
* @throws KramRefusedError (reason `lifetime`, with the deadline) when the Grant's deadline has passed and the
|
|
813
|
+
* stores no longer hold what a status needs: KRAM pruned its cache row, or the escrow sweep ejected the Grant.
|
|
733
814
|
* @throws ValidationError when `grant` is not a v2 Grant this runtime's identifiers are party to.
|
|
734
815
|
*/
|
|
735
816
|
export function issuanceStatus(ops, grant, party = {}) {
|
|
736
817
|
const { deps } = ops, parties = grantParties(deps.hby, grant);
|
|
737
818
|
const chosen = chooseParty(parties, party);
|
|
738
819
|
const ended = ops.endedOf(grant, chosen), live = ops.liveOf(grant, chosen);
|
|
739
|
-
if (ended)
|
|
740
|
-
|
|
820
|
+
if (ended) {
|
|
821
|
+
const { state, role, reason, deadline } = ended;
|
|
822
|
+
return { state, role, reason, ...(deadline && { deadline }) };
|
|
823
|
+
}
|
|
741
824
|
if (live && !reached(deps.clock, live.expires)) {
|
|
742
825
|
return {
|
|
743
826
|
state: "active",
|
|
@@ -747,8 +830,10 @@ export function issuanceStatus(ops, grant, party = {}) {
|
|
|
747
830
|
...(live.outbox.failure && { failure: live.outbox.failure }),
|
|
748
831
|
};
|
|
749
832
|
}
|
|
750
|
-
if (!parties)
|
|
833
|
+
if (!parties) {
|
|
834
|
+
refuseLapsedGrant(deps, grant);
|
|
751
835
|
throw new ValidationError("No issuance was begun for this Grant");
|
|
836
|
+
}
|
|
752
837
|
const { serder, issuer, holder } = parties, db = deps.hby.db;
|
|
753
838
|
if (partyComplete(deps, grant, chosen)) {
|
|
754
839
|
const admitted = !!db.erpy.get([grant]);
|
|
@@ -763,16 +848,22 @@ export function issuanceStatus(ops, grant, party = {}) {
|
|
|
763
848
|
// A group issuer's partial Grant has no accepted lifetime until its members' contributions authenticate it.
|
|
764
849
|
const lifetime = deps.exc.exchangeDeadline(serder) ?? undefined;
|
|
765
850
|
const deadline = lifetime, expires = lifetime ? timestampMicros(lifetime) : null;
|
|
766
|
-
if (!group && (!deadline || expires === null))
|
|
851
|
+
if (!group && (!deadline || expires === null)) {
|
|
852
|
+
// KRAM pruned the Grant's cache row at its deadline, before the escrow sweep ejected the Grant.
|
|
853
|
+
refuseLapsedGrant(deps, grant);
|
|
767
854
|
throw new ValidationError("No issuance was begun for this Grant");
|
|
855
|
+
}
|
|
768
856
|
if (reached(deps.clock, expires)) {
|
|
769
857
|
const credential = deps.exc.retainedInput(grant)?.nests?.[0]?.body;
|
|
770
858
|
const registry = credential instanceof SerderACDC ? credential.regid : undefined;
|
|
771
859
|
const late = role === "holder" && registry && deps.rgy.store.headEvent(registry)?.ilk === "bup";
|
|
772
|
-
return { state: "expired", role, reason: late ? EXPIRED_LATE : EXPIRED };
|
|
860
|
+
return { state: "expired", role, reason: late ? EXPIRED_LATE : EXPIRED, deadline };
|
|
773
861
|
}
|
|
774
862
|
if (resumable(deps, hab, grant, role))
|
|
775
863
|
return { state: "active", role, phase: "rootRetained", deadline };
|
|
864
|
+
// The group completed without this member: the owner follows the custodian's proof once it is stored.
|
|
865
|
+
if (group && awaitsGroupProof(deps, hab, grant))
|
|
866
|
+
return { state: "active", role, phase: "awaitingAnchor", deadline };
|
|
776
867
|
if ((role === "holder" || group) && !heldRoot(deps, hab, grant)) {
|
|
777
868
|
// A group issuer's Grant may still be a partial one that only its members' contributions authenticate.
|
|
778
869
|
if (!group && !deps.exc.retainedInput(grant, { pending: true })) {
|
|
@@ -892,11 +983,16 @@ export function* waitForIssuance(ops, grant, { timeoutMs, clock = ops.deps.clock
|
|
|
892
983
|
* The holder's consent to the DH exchange for one pending Grant addressed to a local identifier. Approves DH and records the
|
|
893
984
|
* consent; `processOnce` then answers the issuer's query, waits for its registry update and finishes the Grant.
|
|
894
985
|
* Admitting the credential is a separate consent ({@link admitIssuance}).
|
|
986
|
+
*
|
|
987
|
+
* @throws KramRefusedError (reason `lifetime`, with the deadline) for a Grant past its exchange deadline, and
|
|
988
|
+
* ValidationError for any other Grant this holder cannot consent to.
|
|
895
989
|
*/
|
|
896
990
|
export function approveIssuanceExchange(ops, grant) {
|
|
897
991
|
const { deps } = ops, hab = grantParties(deps.hby, grant)?.holder;
|
|
898
|
-
if (!hab)
|
|
992
|
+
if (!hab) {
|
|
993
|
+
refuseLapsedGrant(deps, grant);
|
|
899
994
|
throw new ValidationError("Select the exact unanchored pending issuance Grant for this local party");
|
|
995
|
+
}
|
|
900
996
|
const { retained, credential, deadline, expires } = selectGrant(ops, hab, grant, "holder");
|
|
901
997
|
const dh = deps.dhkes.dhke(hab), xid = dh.approve(grant);
|
|
902
998
|
ops.begin({
|
|
@@ -49,7 +49,9 @@ export function pendingPresentations(ops, { hab } = {}) {
|
|
|
49
49
|
/**
|
|
50
50
|
* The accepted v2 Applies addressed to a local identifier (to `hab` when given) that no exchange has answered
|
|
51
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.
|
|
52
|
+
* nothing. A group member lists the Applies of its group once the group has accepted them. The deadline is the
|
|
53
|
+
* Apply's own stamp plus the exchange window once KRAM has pruned its cache row, so an expired Apply stays
|
|
54
|
+
* omitted after the prune.
|
|
53
55
|
*/
|
|
54
56
|
export function pendingApplies(deps, { hab, includeAnswered = false } = {}) {
|
|
55
57
|
const { hby, exc, clock } = deps, found = [];
|
|
@@ -65,7 +67,7 @@ export function pendingApplies(deps, { hab, includeAnswered = false } = {}) {
|
|
|
65
67
|
const answer = hby.db.erpy.get([apply])?.qb64 ?? null;
|
|
66
68
|
if (answer && !includeAnswered)
|
|
67
69
|
continue;
|
|
68
|
-
const deadline = exc.
|
|
70
|
+
const deadline = exc.exchangeExpiry(apply), expiry = deadline ? timestampMicros(deadline) : null;
|
|
69
71
|
if (!answer && expiry !== null && now >= expiry)
|
|
70
72
|
continue;
|
|
71
73
|
const q = serder.sad?.q;
|
|
@@ -47,7 +47,6 @@ import { DeliveryOutbox } from "../runtime/delivery-outbox.js";
|
|
|
47
47
|
import { observeUntil, waitOnClock } from "../runtime/workflow-contract.js";
|
|
48
48
|
import { proposeGroupIpex, proposeGroupIpexAfter as groupIpexAfter, } from "./group-ipex.js";
|
|
49
49
|
import { IpexDiscloser, storedExchangeWire, } from "./ipex-discloser.js";
|
|
50
|
-
import { prepareAcdcV2GroupIpex } from "./ipex-grouping.js";
|
|
51
50
|
/** How long delivery failures may last on an exchange with no deadline (an Apply, an Agree) before it reads `failed`. */
|
|
52
51
|
export const PRESENTATION_RETRY_BOUND_MS = 600_000;
|
|
53
52
|
const EXPIRED = "The presentation reached its original exchange deadline";
|
|
@@ -220,7 +219,7 @@ export function* proposeGroupPresentation(deps, groupIpex, { hab, selection, opt
|
|
|
220
219
|
const { presentationRegistry, ...body } = options;
|
|
221
220
|
const gvrsn = body.gvrsn ?? undefined;
|
|
222
221
|
const discloser = discloserFor(deps, hab, selection.derive);
|
|
223
|
-
|
|
222
|
+
const prepared = yield* discloser.prepareGrantBody(hab.pre, selection, body);
|
|
224
223
|
if (!presentationRegistry) {
|
|
225
224
|
return yield* proposeGroupIpex(groupIpex, { hab, exn: prepared.exn, nests: prepared.nests, gvrsn, deliver });
|
|
226
225
|
}
|
|
@@ -237,26 +236,12 @@ export function* proposeGroupPresentation(deps, groupIpex, { hab, selection, opt
|
|
|
237
236
|
// The Grant SAID is frozen before the update is anchored; only its nested proof is extended afterwards.
|
|
238
237
|
const [proof, update] = reg.present(prepared.exn, "presented", { stamp: body.stamp });
|
|
239
238
|
const proposed = yield* proposeRegistryEvent(proposals, hab, update, proof, counsel);
|
|
240
|
-
const exn = prepared.exn;
|
|
241
239
|
return groupIpexAfter(groupIpex, {
|
|
242
240
|
hab,
|
|
243
|
-
exn,
|
|
241
|
+
exn: prepared.exn,
|
|
244
242
|
gvrsn,
|
|
245
243
|
deliver,
|
|
246
|
-
prerequisite: {
|
|
247
|
-
anchor: registryEventRef(update, proposed.event),
|
|
248
|
-
*proceed() {
|
|
249
|
-
const seal = deps.rgy.store.anchor(update.said);
|
|
250
|
-
if (!seal)
|
|
251
|
-
throw new ValidationError("The presentation update is accepted but its anchor is unavailable");
|
|
252
|
-
prepared = yield* discloser.attachPresentationProof(prepared, proof, { s: seal[0], d: seal[1] });
|
|
253
|
-
const candidate = concatBytes(Counter.makeGVC(gvrsn ?? Vrsn_2_0), prepared.exn.raw, Counter.enclose({ qb64: prepared.nests, version: gvrsn ?? Vrsn_2_0 }));
|
|
254
|
-
const own = yield* prepareAcdcV2GroupIpex(deps.hby, groupIpex.deps.protocol, hab, candidate);
|
|
255
|
-
if (!own)
|
|
256
|
-
throw new ValidationError("ACDC v2 group disclosure awaits authenticating dependencies.");
|
|
257
|
-
return own;
|
|
258
|
-
},
|
|
259
|
-
},
|
|
244
|
+
prerequisite: { anchor: registryEventRef(update, proposed.event), grant: prepared, opening: proof },
|
|
260
245
|
});
|
|
261
246
|
}
|
|
262
247
|
/**
|
|
@@ -48,7 +48,8 @@ export function* admitAndDeliver(deps, input) {
|
|
|
48
48
|
* wait for the issuer to finish it, then admit and deliver ({@link admitAndDeliver}).
|
|
49
49
|
*
|
|
50
50
|
* @throws ValidationError for a Grant that is not addressed to a local identifier, one this runtime issued, or a
|
|
51
|
-
* group identifier
|
|
51
|
+
* group identifier; its subclass `KramRefusedError` (reason `lifetime`, with the deadline) for a Grant whose
|
|
52
|
+
* deadline passed before the stores could say more (its cache row pruned or the Grant ejected from escrow).
|
|
52
53
|
*/
|
|
53
54
|
export function* receiveIssuance(ops, input) {
|
|
54
55
|
const { grant, timeoutMs } = input, { deps } = ops;
|