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
|
@@ -184,21 +184,6 @@ function* publishVdrProposal(hby, protocol, postman, member, recipients, proposa
|
|
|
184
184
|
return groupSendResult(outcomes);
|
|
185
185
|
}
|
|
186
186
|
/** True when a KEL event is accepted locally. */
|
|
187
|
-
/**
|
|
188
|
-
* Commit the members of a locally accepted group establishment event.
|
|
189
|
-
*
|
|
190
|
-
* Only the current latest establishment event commits; replaying an older
|
|
191
|
-
* approval must not restore historical group membership. Returns whether the
|
|
192
|
-
* members were committed.
|
|
193
|
-
*/
|
|
194
|
-
export function commitAcceptedGroupMembers(hby, serder, smids, rmids) {
|
|
195
|
-
const pre = serder.pre;
|
|
196
|
-
if (!pre || !eventAccepted(hby, serder) || hby.db.getKever(pre)?.lastEst.d !== serder.said) {
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
hby.commitGroupHabMembers(pre, smids, rmids.length > 0 ? rmids : smids);
|
|
200
|
-
return true;
|
|
201
|
-
}
|
|
202
187
|
export function eventAccepted(hby, serder) {
|
|
203
188
|
const pre = serder.pre;
|
|
204
189
|
const said = serder.said;
|
|
@@ -383,9 +368,9 @@ export function* approveKelProposal(hby, runtime, exn, route, options) {
|
|
|
383
368
|
// approval must reuse its proposal's wrapper version for the proposer to merge it.
|
|
384
369
|
exn.pvrsn);
|
|
385
370
|
const accepted = eventAccepted(hby, serder);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
371
|
+
// An establishment event this approval completed: the members follow accepted state now, not the proposal's lists.
|
|
372
|
+
if (accepted && proposal.route !== MULTISIG_IXN_ROUTE)
|
|
373
|
+
hby.alignGroupMembers(groupPre);
|
|
389
374
|
return { route, said: wrapperSaid, embedded: embeddedSaid ?? "", group: groupPre, accepted, ...publication };
|
|
390
375
|
}
|
|
391
376
|
/**
|
|
@@ -4,7 +4,7 @@ import { createCredential, inceptCredentialRegistry, } from "../credentials/cred
|
|
|
4
4
|
import { groupSigningMembers, localGroupMember, uniqueMembers } from "./group-members.js";
|
|
5
5
|
import { MULTISIG_ICP_ROUTE, MULTISIG_IXN_ROUTE, MULTISIG_ROT_ROUTE } from "./grouping.js";
|
|
6
6
|
import { remoteGroupMembers } from "./multisig-messaging.js";
|
|
7
|
-
import { approveAcdcV2Registry,
|
|
7
|
+
import { approveAcdcV2Registry, publishAcdcV2Registry, publishProposal, } from "./multisig-workflows.js";
|
|
8
8
|
/** Propose a new group: create this member's inception and publish it. */
|
|
9
9
|
export function* proposeGroupInception(deps, member, alias, { smids, rmids = smids, ...args }, inputs) {
|
|
10
10
|
// Publishing needs a route to every other member. Refuse before the group habitat exists, so a call that could
|
|
@@ -44,21 +44,6 @@ export function* proposeGroupInteraction(deps, group, data, inputs, version) {
|
|
|
44
44
|
const sent = yield* publishProposal(hby, deps.protocol, deps.postman, localGroupMember(hby, created.hab.pre), smids, { route: MULTISIG_IXN_ROUTE, attrs: { gid: created.hab.pre, smids } }, created.message);
|
|
45
45
|
return { ...sent, route: MULTISIG_IXN_ROUTE, group: created.hab.pre, event, smids, rmids: [] };
|
|
46
46
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Commit the members a proposed rotation named, once the rotation is accepted and
|
|
49
|
-
* is the group's latest establishment event. Idempotent; false until then, so a
|
|
50
|
-
* proposed newcomer is never adopted early. Other event kinds commit nothing.
|
|
51
|
-
*
|
|
52
|
-
* @throws ValidationError when the event is not an accepted event body held locally.
|
|
53
|
-
*/
|
|
54
|
-
export function settleProposedRotation(deps, result) {
|
|
55
|
-
if (result.route !== MULTISIG_ROT_ROUTE)
|
|
56
|
-
return false;
|
|
57
|
-
const serder = deps.hby.db.getEvtSerder(result.event.pre, result.event.said);
|
|
58
|
-
if (!serder)
|
|
59
|
-
throw new ValidationError(`No rotation ${result.event.said} is held for ${result.event.pre}.`);
|
|
60
|
-
return commitAcceptedGroupMembers(deps.hby, serder, result.smids, result.rmids);
|
|
61
|
-
}
|
|
62
47
|
/**
|
|
63
48
|
* Propose an ACDC v2 registry event (`rip` or `bup`): author the group anchor
|
|
64
49
|
* that seals it, process this member's own contribution, hand the anchor to the
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ValidationError } from "../../core/errors.js";
|
|
2
|
+
import { beginOobi, oobiStatus } from "../bootstrap/oobi-resolving.js";
|
|
3
|
+
import { observeUntil } from "../runtime/workflow-contract.js";
|
|
4
|
+
import { acceptedGroupMembers, isAcceptedGroupMember } from "./group-members.js";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve `input.oobi`, wait for the group's accepted key state, and record the group with `input.member` as
|
|
7
|
+
* its local member, using the accepted members. Then the mailbox poller, the KEL publisher and the root
|
|
8
|
+
* replicator are prompted for the group, so each acts for it on its next tock. Creates no event and signs
|
|
9
|
+
* nothing. Observe-only: the host must already run the runtime's workers (see `workflow-contract.ts`).
|
|
10
|
+
*
|
|
11
|
+
* A record that already names `member` returns `joined` at once with `written: false`, with no OOBI and no write.
|
|
12
|
+
*
|
|
13
|
+
* @throws ValidationError for misuse: `member` is not a local single-signature habitat, `group` is a local
|
|
14
|
+
* single-signature habitat, the group already has a local record naming a different member (use
|
|
15
|
+
* `replaceGroupMember`), or `alias` is bound to another prefix.
|
|
16
|
+
*/
|
|
17
|
+
export function* rejoinGroup(deps, input) {
|
|
18
|
+
const { hby } = deps, { group, member, alias, timeoutMs, domain } = input;
|
|
19
|
+
const recorded = rejoinedRecord(hby, input);
|
|
20
|
+
if (recorded)
|
|
21
|
+
return recorded;
|
|
22
|
+
beginOobi(deps, input.oobi);
|
|
23
|
+
const read = () => readRejoin(hby.db, input.oobi, group, member.pre);
|
|
24
|
+
yield* observeUntil(() => read().kind !== "waiting", { timeoutMs, clock: deps.clock });
|
|
25
|
+
const reading = read();
|
|
26
|
+
switch (reading.kind) {
|
|
27
|
+
case "waiting":
|
|
28
|
+
return { outcome: "timed-out", group, timeoutMs, waiting: reading.waiting };
|
|
29
|
+
case "oobi-failed":
|
|
30
|
+
return { outcome: "oobi-failed", group, reason: reading.reason };
|
|
31
|
+
case "not-member":
|
|
32
|
+
return { outcome: "not-member", group, member: member.pre };
|
|
33
|
+
}
|
|
34
|
+
const { smids, rmids } = reading.members;
|
|
35
|
+
if (!smids.includes(member.pre) && !rmids.includes(member.pre)) {
|
|
36
|
+
return { outcome: "not-member", group, member: member.pre };
|
|
37
|
+
}
|
|
38
|
+
hby.joinGroupHab(group, alias, member, smids, rmids, domain);
|
|
39
|
+
deps.mailboxPoller.prompt(group);
|
|
40
|
+
deps.kelPublisher.prompt(group);
|
|
41
|
+
deps.rootReplicator?.prompt(group);
|
|
42
|
+
deps.changes?.push({ kind: "group", id: group });
|
|
43
|
+
return { outcome: "joined", group, alias, smids, rmids, written: true };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Refuse misuse before any I/O, and return `joined` for a record that already names the member.
|
|
47
|
+
* Null when the group has no local record yet.
|
|
48
|
+
*/
|
|
49
|
+
function rejoinedRecord(hby, { group, member, alias, domain }) {
|
|
50
|
+
if (!group)
|
|
51
|
+
throw new ValidationError("Group prefix is required.");
|
|
52
|
+
if (!alias)
|
|
53
|
+
throw new ValidationError("Group alias is required.");
|
|
54
|
+
const pre = member.pre;
|
|
55
|
+
if (!pre || hby.habs.get(pre) !== member) {
|
|
56
|
+
throw new ValidationError(`Member ${pre} is not a habitat of this Habery.`);
|
|
57
|
+
}
|
|
58
|
+
if (hby.db.getHab(pre)?.mid) {
|
|
59
|
+
throw new ValidationError(`Member ${pre} is a group identifier; a group rejoins through a single-signature member.`);
|
|
60
|
+
}
|
|
61
|
+
const record = hby.db.getHab(group);
|
|
62
|
+
if (record && !record.mid) {
|
|
63
|
+
throw new ValidationError(`${group} is a local single-signature identifier, not a group.`);
|
|
64
|
+
}
|
|
65
|
+
if (record?.mid && record.mid !== pre) {
|
|
66
|
+
throw new ValidationError(`Group ${group} is already recorded with local member ${record.mid}; use replaceGroupMember to change it.`);
|
|
67
|
+
}
|
|
68
|
+
if (record?.mid) {
|
|
69
|
+
return {
|
|
70
|
+
outcome: "joined",
|
|
71
|
+
group,
|
|
72
|
+
alias: record.name ?? alias,
|
|
73
|
+
smids: [...(record.smids ?? [])],
|
|
74
|
+
rmids: [...(record.rmids ?? [])],
|
|
75
|
+
written: false,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const bound = hby.db.getName(domain ?? "", alias);
|
|
79
|
+
if (bound && bound !== group)
|
|
80
|
+
throw new ValidationError(`Alias ${alias} is already bound to ${bound}.`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/** Read the OOBI, the group's accepted key state and its members, in that order. */
|
|
84
|
+
function readRejoin(db, oobi, group, member) {
|
|
85
|
+
const status = oobiStatus({ db }, oobi);
|
|
86
|
+
if (status.state === "pending")
|
|
87
|
+
return { kind: "waiting", waiting: "oobi" };
|
|
88
|
+
if (!db.getKever(group)) {
|
|
89
|
+
return status.state === "failed"
|
|
90
|
+
? { kind: "oobi-failed", reason: status.reason }
|
|
91
|
+
: { kind: "waiting", waiting: "group-key-state" };
|
|
92
|
+
}
|
|
93
|
+
// Every list member is an accepted member, so this answers `not-member` before the other members' KELs arrive.
|
|
94
|
+
if (!isAcceptedGroupMember(db, group, member))
|
|
95
|
+
return { kind: "not-member" };
|
|
96
|
+
const candidates = [...db.states.getTopItemIter()].map(([keys]) => keys.join("."));
|
|
97
|
+
const members = acceptedGroupMembers(db, group, candidates);
|
|
98
|
+
return members ? { kind: "members", members } : { kind: "waiting", waiting: "member-key-state" };
|
|
99
|
+
}
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Replace a local group's recorded member after a rotation removed it,
|
|
2
|
+
* Replace a local group's recorded member after a rotation removed it, end the runtime's work bound to it, and
|
|
3
|
+
* retire it when nothing else needs it.
|
|
3
4
|
*
|
|
4
5
|
* `Habery.joinGroupHab` re-points the record and clears the durable rows addressed to the old member. The
|
|
5
6
|
* runtime owns the live operations, so this is the one call that also ends them: an issuance whose party
|
|
6
7
|
* member or frozen DH custodian is the old member, and an Unblind holding it. Each ends through its owner's
|
|
7
8
|
* own end path, which releases the approval and drops the held `heldq`/`heldu` rows. Group IPEX messages and
|
|
8
9
|
* presentations keep no member binding (their contributions are already signed and the member is read live),
|
|
9
|
-
* so they are left to run.
|
|
10
|
+
* so they are left to run. It then prompts the mailbox poller, the KEL publisher and the root replicator for the
|
|
11
|
+
* group, so each acts as the new member on its next tock instead of at its next sweep. Nothing is signed and no
|
|
12
|
+
* event is made.
|
|
13
|
+
*
|
|
14
|
+
* Last, the old member is retired with `retireIdentifier` (its stored keys deleted, its mailbox workers
|
|
15
|
+
* stopped, every KEL and BADA record about it kept) unless the caller set `keepPrevious` or
|
|
16
|
+
* `retireIdentifierRefusal` names a reason to keep it: it is a member of another local group, a delegator, a
|
|
17
|
+
* registry issuer, or has live work. A kept member does not fail the replacement; the result says why it was
|
|
18
|
+
* kept.
|
|
10
19
|
*/
|
|
11
20
|
import { ValidationError } from "../../core/errors.js";
|
|
21
|
+
import { retireIdentifier, retireIdentifierRefusal } from "../identity/retire-identifier.js";
|
|
12
22
|
/**
|
|
13
|
-
* Replace the local member of `input.group`,
|
|
23
|
+
* Replace the local member of `input.group`, end live operations bound to the previous one, then retire the
|
|
24
|
+
* previous one unless it is kept (see the module doc).
|
|
14
25
|
*
|
|
15
26
|
* @throws ValidationError when `joinGroupHab` refuses: the group has no accepted state, the previous member is
|
|
16
27
|
* still a member of it, or the new one is not.
|
|
@@ -25,10 +36,20 @@ export function replaceGroupMember(runtime, input) {
|
|
|
25
36
|
throw new ValidationError(`Member ${previous} is already the local member of ${group}.`);
|
|
26
37
|
hby.joinGroupHab(group, alias, member, input.smids, input.rmids ?? input.smids, record.domain);
|
|
27
38
|
const reason = `Local member ${previous} of group ${group} was replaced by ${member.pre}`;
|
|
39
|
+
runtime.mailboxPoller.prompt(group);
|
|
40
|
+
runtime.kelPublisher.prompt(group);
|
|
41
|
+
runtime.rootReplicator?.prompt(group);
|
|
28
42
|
const ops = runtime.operations;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
const issuance = ops?.issuance.endBoundToMember(group, previous, reason) ?? [];
|
|
44
|
+
const unblinding = ops?.unblinding.endBoundToMember(group, previous, reason) ?? [];
|
|
45
|
+
return { previous, issuance, unblinding, previousMember: retirePrevious(runtime, previous, input.keepPrevious) };
|
|
46
|
+
}
|
|
47
|
+
/** Retire the replaced member, or say why it is kept. */
|
|
48
|
+
function retirePrevious(runtime, previous, keep = false) {
|
|
49
|
+
if (keep)
|
|
50
|
+
return { kind: "kept", reason: "The caller asked to keep the previous member." };
|
|
51
|
+
const refusal = retireIdentifierRefusal(runtime, previous);
|
|
52
|
+
if (refusal)
|
|
53
|
+
return { kind: "kept", reason: refusal };
|
|
54
|
+
return { kind: "retired", retired: retireIdentifier(runtime, previous) };
|
|
34
55
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { BlindState, BoundState, Cigar, concatBytes, Counter, Dater, Diger, FirstSeen, Ilks, isCounterGroupLike, isGroupTuple, Labeler, Noncer, NumberPrimitive, parseBytes, parseSerder, Prefixer,
|
|
1
|
+
import { BlindState, BoundState, Cigar, concatBytes, Counter, Dater, Diger, FirstSeen, Ilks, isCounterGroupLike, isGroupTuple, Labeler, Noncer, NumberPrimitive, parseBytes, parseSerder, Prefixer, SealEvent, SealKind, SealSource, SerderACDC, SerderKERI, Siger, Texter, TypeMedia, Verfer, Verser, } from "cesr-ts";
|
|
2
2
|
import { KeriDispatchEnvelope, PathedMaterialGroup, TransIdxSigGroup, TransLastIdxSigGroup, TransReceiptQuadruple, } from "../../core/dispatch.js";
|
|
3
|
+
import { ValidationError } from "../../core/errors.js";
|
|
3
4
|
import { nestedIpexRoute, supportsNestedExchange } from "../../core/exchange/protocol-exchanging.js";
|
|
4
5
|
import { serializeMessage } from "../../core/protocol-serialization.js";
|
|
6
|
+
import { readPrefixer } from "../../core/wire-fields.js";
|
|
5
7
|
/**
|
|
6
8
|
* Convert one or more already-parsed CESR frames into KERI dispatch envelopes.
|
|
7
9
|
*
|
|
@@ -29,12 +31,14 @@ export function envelopesFromFrames(frames, local = false) {
|
|
|
29
31
|
* @param messageKevery Authoritative shared message-policy/KRAM processor when distinct from input Kevery.
|
|
30
32
|
* @param registry V2 registry intake over accepted public KEL state, without requiring a local alias.
|
|
31
33
|
* @param qvy Optional auxiliary query owner; receives full evidence after v2 message admission.
|
|
34
|
+
* @param retained Whether this is retained history from a finite local import (`DispatchOptions.retained`): a v2
|
|
35
|
+
* `exn`/`xip` then skips KRAM intake and reaches the Exchanger marked retained. Network intake never sets it.
|
|
32
36
|
* @returns The processed envelope and protocol-specific decision data.
|
|
33
37
|
* @throws Unsupported input and infrastructure/handler failures follow their owning processor contracts.
|
|
34
38
|
*/
|
|
35
39
|
export function* dispatchEnvelope(envelope, revery, kevery, exchanger, tevery, verifier, mailboxAid,
|
|
36
40
|
/** Long-lived v2 message policy/KRAM owner; input-specific Kevery handles KEL and legacy traffic. */
|
|
37
|
-
messageKevery = kevery, registry, qvy) {
|
|
41
|
+
messageKevery = kevery, registry, qvy, retained = false) {
|
|
38
42
|
const serder = envelope.serder;
|
|
39
43
|
if (serder instanceof SerderACDC) {
|
|
40
44
|
// Historical upd bytes remain decodable, but cannot enter live registry processing.
|
|
@@ -59,7 +63,7 @@ messageKevery = kevery, registry, qvy) {
|
|
|
59
63
|
}
|
|
60
64
|
if (serder.pvrsn.major >= 2
|
|
61
65
|
&& [Ilks.qry, Ilks.rpy, Ilks.exn, Ilks.xip, Ilks.pro, Ilks.bar].some((ilk) => ilk === serder.ilk)) {
|
|
62
|
-
return yield* dispatchMessage(envelope, revery, messageKevery, exchanger, tevery, mailboxAid, qvy);
|
|
66
|
+
return yield* dispatchMessage(envelope, revery, messageKevery, exchanger, tevery, mailboxAid, qvy, retained);
|
|
63
67
|
}
|
|
64
68
|
switch (serder.ilk) {
|
|
65
69
|
case Ilks.rpy:
|
|
@@ -132,8 +136,9 @@ messageKevery = kevery, registry, qvy) {
|
|
|
132
136
|
* processMsg boundary: sender policy precedes optional KRAM and proof
|
|
133
137
|
* prerequisites and delegation. Downstream processors authenticate their own
|
|
134
138
|
* evidence; this layer borrows the envelope and never owns its payloads.
|
|
139
|
+
* A retained exchange (finite local import) skips only the KRAM step.
|
|
135
140
|
*/
|
|
136
|
-
function* dispatchMessage(envelope, revery, kevery, exchanger, tevery, mailboxAid, qvy) {
|
|
141
|
+
function* dispatchMessage(envelope, revery, kevery, exchanger, tevery, mailboxAid, qvy, retained = false) {
|
|
137
142
|
const serder = envelope.serder;
|
|
138
143
|
const sender = serder.pre;
|
|
139
144
|
if (sender !== null) {
|
|
@@ -147,15 +152,18 @@ function* dispatchMessage(envelope, revery, kevery, exchanger, tevery, mailboxAi
|
|
|
147
152
|
&& !(qvy?.claims ? qvy.claims(serder) : qvy?.resource === serder.route)) {
|
|
148
153
|
return { envelope, decision: { kind: "drop", reason: "missingProcessor" } };
|
|
149
154
|
}
|
|
150
|
-
|
|
155
|
+
// Retained history was KRAM-admitted where it was first received; its window here has passed by design. Queries
|
|
156
|
+
// and replies are never retained history and always pass KRAM.
|
|
157
|
+
const history = retained && (serder.ilk === Ilks.exn || serder.ilk === Ilks.xip);
|
|
158
|
+
const result = history ? undefined : kevery.kramer?.intake(envelope);
|
|
151
159
|
if (result && result.kind !== "ready")
|
|
152
160
|
return { envelope, decision: result };
|
|
153
161
|
const ready = result?.kind === "ready" ? result.envelope : envelope;
|
|
154
|
-
const decision = yield* dispatchReadyMessage(ready, revery, kevery, exchanger, tevery, mailboxAid, result?.kind === "ready" ? result.receiverTime : undefined, qvy);
|
|
162
|
+
const decision = yield* dispatchReadyMessage(ready, revery, kevery, exchanger, tevery, mailboxAid, result?.kind === "ready" ? result.receiverTime : undefined, qvy, history);
|
|
155
163
|
return { envelope: ready, decision };
|
|
156
164
|
}
|
|
157
165
|
/** Delegate accepted evidence; downstream processors verify their own proof owners. */
|
|
158
|
-
function* dispatchReadyMessage(envelope, revery, kevery, exchanger, tevery, mailboxAid, receiverTime, qvy) {
|
|
166
|
+
function* dispatchReadyMessage(envelope, revery, kevery, exchanger, tevery, mailboxAid, receiverTime, qvy, retained = false) {
|
|
159
167
|
const serder = envelope.serder;
|
|
160
168
|
switch (serder.ilk) {
|
|
161
169
|
case Ilks.qry: {
|
|
@@ -186,7 +194,7 @@ function* dispatchReadyMessage(envelope, revery, kevery, exchanger, tevery, mail
|
|
|
186
194
|
}
|
|
187
195
|
if (!exchanger)
|
|
188
196
|
return { kind: "drop", reason: "missingProcessor" };
|
|
189
|
-
return yield* dispatchExchange(envelope, exchanger, mailboxAid, receiverTime);
|
|
197
|
+
return yield* dispatchExchange(envelope, exchanger, mailboxAid, receiverTime, retained);
|
|
190
198
|
default:
|
|
191
199
|
// processPro/processBar remain pass stubs at the selected reference.
|
|
192
200
|
return { kind: "drop", reason: "referenceStub" };
|
|
@@ -201,7 +209,7 @@ function dispatchTelQuery(query, tevery) {
|
|
|
201
209
|
return tevery.processQuery(query);
|
|
202
210
|
}
|
|
203
211
|
/** Await the route owner while preserving parent, child and mailbox provenance. */
|
|
204
|
-
function* dispatchExchange(envelope, exchanger, mailboxAid, receiverTime) {
|
|
212
|
+
function* dispatchExchange(envelope, exchanger, mailboxAid, receiverTime, retained = false) {
|
|
205
213
|
return yield* exchanger.processEvent({
|
|
206
214
|
serder: envelope.serder,
|
|
207
215
|
tsgs: envelope.tsgs,
|
|
@@ -216,6 +224,7 @@ function* dispatchExchange(envelope, exchanger, mailboxAid, receiverTime) {
|
|
|
216
224
|
nests: envelope.nests,
|
|
217
225
|
mailboxAid,
|
|
218
226
|
receiverTime,
|
|
227
|
+
...(retained ? { retained } : {}),
|
|
219
228
|
});
|
|
220
229
|
}
|
|
221
230
|
function telEnvelopeFromDispatch(envelope, serder) {
|
|
@@ -439,14 +448,32 @@ function collectAttachments(group, envelope) {
|
|
|
439
448
|
return;
|
|
440
449
|
}
|
|
441
450
|
}
|
|
442
|
-
/**
|
|
443
|
-
|
|
444
|
-
|
|
451
|
+
/**
|
|
452
|
+
* Whether a frame carries nested messages that no completed consumer owns.
|
|
453
|
+
*
|
|
454
|
+
* Only supported exchange routes, IPEX disclosure `xip`s and the v2 Unblind query retain parent-owned children; any
|
|
455
|
+
* other frame with children is unsupported input. A remote frame is refused as one invalid message
|
|
456
|
+
* (`ProtocolProcessor.dispatch`); {@link envelopeFromFrame} refuses it with a `ValidationError`.
|
|
457
|
+
*/
|
|
458
|
+
export function hasUnsupportedNests(message) {
|
|
459
|
+
if (!message.nests?.length)
|
|
460
|
+
return false;
|
|
445
461
|
const route = String(message.body.ked?.r ?? "");
|
|
446
462
|
const unblindQuery = message.body.ilk === "qry" && message.body.pvrsn.major === 2 && route === "/ipex/unblind";
|
|
447
|
-
|
|
448
|
-
|| !supportsNestedExchange(route, message.body.pvrsn.major))
|
|
449
|
-
|
|
463
|
+
return !unblindQuery && ((message.body.ilk !== "exn" && !(message.body.ilk === "xip" && nestedIpexRoute(route)))
|
|
464
|
+
|| !supportsNestedExchange(route, message.body.pvrsn.major));
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Project one complete frame for message routing without an intermediate event array.
|
|
468
|
+
*
|
|
469
|
+
* Only completed consumers may retain parent-owned children through normalization. A frame with children no consumer
|
|
470
|
+
* owns is usually a remote child nested inside a parent (a BUP inside an Unblind query, an event inside a multisig or
|
|
471
|
+
* IPEX exchange): it is refused with a `ValidationError`, which the parent's processor reports as its own typed
|
|
472
|
+
* refusal (an invalid-message drop, a rejected escrow row), never a fault that ends the runtime.
|
|
473
|
+
*/
|
|
474
|
+
export function envelopeFromFrame(message, local = false) {
|
|
475
|
+
if (hasUnsupportedNests(message)) {
|
|
476
|
+
throw new ValidationError(`KERI runtime normalization does not yet support nested message trees (${message.body.ilk ?? "unknown"} frame).`);
|
|
450
477
|
}
|
|
451
478
|
const serder = (message.body instanceof SerderKERI || message.body instanceof SerderACDC)
|
|
452
479
|
? message.body
|
|
@@ -516,8 +543,9 @@ export function restoreAuxiliaryQuery(wire, local = false) {
|
|
|
516
543
|
export function queryEnvelopeFromDispatch(envelope) {
|
|
517
544
|
const lastSsg = envelope.ssgs.at(-1);
|
|
518
545
|
// V2 processMsg falls back to the claimed sender only when raw sigers exist.
|
|
519
|
-
|
|
520
|
-
|
|
546
|
+
// A claimed sender that is not a prefix authenticates nothing; the query is then refused as unauthenticated.
|
|
547
|
+
const rawSource = !lastSsg && envelope.serder.pvrsn.major >= 2 && envelope.sigers.length
|
|
548
|
+
? readPrefixer(envelope.serder.pre) ?? undefined
|
|
521
549
|
: undefined;
|
|
522
550
|
return {
|
|
523
551
|
serder: envelope.serder,
|
|
@@ -6,9 +6,10 @@ import { consoleLogger } from "../../core/logger.js";
|
|
|
6
6
|
import { BasicReplyRouteHandler, Revery } from "../../core/routing.js";
|
|
7
7
|
import { Exchanger } from "../exchanges/exchanging.js";
|
|
8
8
|
import { QueryReplies } from "../queries/query-replies.js";
|
|
9
|
+
import { relayRefreshCue } from "../queries/relay-refresh.js";
|
|
9
10
|
import { doEveryOnWrite, PROTOCOL_PACING } from "../runtime/doing.js";
|
|
10
11
|
import { systemClock } from "../runtime/runtime-clock.js";
|
|
11
|
-
import { dispatchEnvelope, envelopeFromFrame, queryEnvelopeFromDispatch, } from "./parsering.js";
|
|
12
|
+
import { dispatchEnvelope, envelopeFromFrame, hasUnsupportedNests, queryEnvelopeFromDispatch, } from "./parsering.js";
|
|
12
13
|
/**
|
|
13
14
|
* Wait after every escrow pass, as KERIpy's Doist cycle (`tock` 0.03125 s, app/directing.py) paces its `escrowDo`,
|
|
14
15
|
* which runs one full pass per cycle. A pass is never skipped or shortened; this is the time others get between passes.
|
|
@@ -25,6 +26,24 @@ export const ESCROW_TOCK_MS = 31.25;
|
|
|
25
26
|
function isInvalidMessageError(error) {
|
|
26
27
|
return error instanceof ValidationError;
|
|
27
28
|
}
|
|
29
|
+
/** Why a remote frame whose nested children no consumer owns is refused. */
|
|
30
|
+
const NESTED_REFUSAL = "KERI runtime normalization does not yet support nested message trees";
|
|
31
|
+
/**
|
|
32
|
+
* Report and describe the refusal of one extracted message, so its input continues with the next frame.
|
|
33
|
+
*
|
|
34
|
+
* @param envelope The refused message's own body and attachments.
|
|
35
|
+
* @param detail Why validation refused it.
|
|
36
|
+
* @returns The `invalidMessage` drop for this envelope.
|
|
37
|
+
*/
|
|
38
|
+
function invalidMessage(envelope, detail) {
|
|
39
|
+
consoleLogger.warn("Skipping invalid message", {
|
|
40
|
+
said: envelope.serder.said,
|
|
41
|
+
ilk: envelope.serder.ilk,
|
|
42
|
+
route: envelope.serder.route,
|
|
43
|
+
detail,
|
|
44
|
+
});
|
|
45
|
+
return { envelope, decision: { kind: "drop", reason: "invalidMessage", detail } };
|
|
46
|
+
}
|
|
28
47
|
/**
|
|
29
48
|
* Dispatch to synchronous semantic owners and publish their committed output.
|
|
30
49
|
*
|
|
@@ -48,6 +67,8 @@ export class ProtocolProcessor {
|
|
|
48
67
|
* @param cues Application output Deck consumed by the composed cue router.
|
|
49
68
|
* @param replies Shared live request correlations captured before application publication.
|
|
50
69
|
* @param qvy Optional caller-owned auxiliary query receiver; owns its authorization and lifecycle.
|
|
70
|
+
* @param relaying Whether this domain is a relaying host's (the agent side of relay mode): a served sender's query
|
|
71
|
+
* about a remote subject then also publishes the host's own refresh query cue (see `relayRefreshCue`).
|
|
51
72
|
* @throws TypeError for inconsistent stores or cue bindings.
|
|
52
73
|
*/
|
|
53
74
|
constructor(
|
|
@@ -68,7 +89,9 @@ export class ProtocolProcessor {
|
|
|
68
89
|
/** Ordinary application cues; the composed cue router is the consumer. */
|
|
69
90
|
cues,
|
|
70
91
|
/** Shared live request correlations; each requesting connection owns its slots. */
|
|
71
|
-
replies, qvy
|
|
92
|
+
replies, qvy,
|
|
93
|
+
/** Relaying host: served senders' queries about remote subjects refresh this host's own view of them. */
|
|
94
|
+
relaying = false) {
|
|
72
95
|
Object.defineProperty(this, "kvy", {
|
|
73
96
|
enumerable: true,
|
|
74
97
|
configurable: true,
|
|
@@ -129,6 +152,12 @@ export class ProtocolProcessor {
|
|
|
129
152
|
writable: true,
|
|
130
153
|
value: qvy
|
|
131
154
|
});
|
|
155
|
+
Object.defineProperty(this, "relaying", {
|
|
156
|
+
enumerable: true,
|
|
157
|
+
configurable: true,
|
|
158
|
+
writable: true,
|
|
159
|
+
value: relaying
|
|
160
|
+
});
|
|
132
161
|
if (protocolCues === cues || kvy.cues !== protocolCues || rvy.cues !== protocolCues
|
|
133
162
|
|| exc.cues !== protocolCues || kvy.db !== rvy.db || exc.hby.db !== kvy.db || kvy.rvy !== rvy) {
|
|
134
163
|
throw new TypeError("Protocol processors require one store and a distinct shared protocol cue flow");
|
|
@@ -156,14 +185,15 @@ export class ProtocolProcessor {
|
|
|
156
185
|
* Process one frame and publish its committed protocol cues before returning decision data.
|
|
157
186
|
*
|
|
158
187
|
* @param frame Complete hydrated frame whose evidence remains valid for this operation.
|
|
159
|
-
* @param options Local intake policy, hosted mailbox provenance
|
|
188
|
+
* @param options Local intake policy, hosted mailbox provenance, whether to advance escrows, and whether the frame is
|
|
189
|
+
* retained history from a finite local import.
|
|
160
190
|
* @param kvy Input-specific Kevery in this domain; defaults to the shared owner.
|
|
161
191
|
* @returns Exact dispatch result and current mailbox authorization at its synchronous query decision.
|
|
162
192
|
* A message refused by validation after extraction is an `invalidMessage` drop, so the caller
|
|
163
193
|
* continues with the next frame as KERIpy's parser does.
|
|
164
194
|
* @throws Domain mismatch, infrastructure/handler or cleanup errors propagate; publication still runs in the dispatch finalizer.
|
|
165
195
|
*/
|
|
166
|
-
dispatch(frame, { local = this.kvy.local, mailboxAid, escrows = false } = {}, kvy = this.kvy) {
|
|
196
|
+
dispatch(frame, { local = this.kvy.local, mailboxAid, escrows = false, retained = false } = {}, kvy = this.kvy) {
|
|
167
197
|
// A short-lived input processor must publish into this owner's private cue flow.
|
|
168
198
|
if (kvy.db !== this.kvy.db || kvy.cues !== this.protocolCues || kvy.rvy !== this.rvy) {
|
|
169
199
|
throw new TypeError("Kevery belongs to another protocol processing domain");
|
|
@@ -172,27 +202,28 @@ export class ProtocolProcessor {
|
|
|
172
202
|
const self = this;
|
|
173
203
|
return call(function* () {
|
|
174
204
|
try {
|
|
175
|
-
|
|
205
|
+
// Children no consumer owns make the frame one invalid message; its own body and attachments remain evidence.
|
|
206
|
+
const nested = hasUnsupportedNests(frame);
|
|
207
|
+
const envelope = envelopeFromFrame(nested ? { ...frame, nests: [] } : frame, local);
|
|
176
208
|
let result;
|
|
177
209
|
let invalid = false;
|
|
178
210
|
try {
|
|
179
|
-
result = yield* dispatchEnvelope(envelope, self.rvy, kvy, self.exc, self.tvy ?? undefined, self.vry ?? undefined, mailboxAid, self.kvy, // One authoritative v2 sender policy and KRAM intake across all inputs.
|
|
180
|
-
self.registry ?? undefined, self.qvy);
|
|
211
|
+
result = nested ? invalidMessage(envelope, NESTED_REFUSAL) : yield* dispatchEnvelope(envelope, self.rvy, kvy, self.exc, self.tvy ?? undefined, self.vry ?? undefined, mailboxAid, self.kvy, // One authoritative v2 sender policy and KRAM intake across all inputs.
|
|
212
|
+
self.registry ?? undefined, self.qvy, retained);
|
|
213
|
+
invalid = nested;
|
|
181
214
|
}
|
|
182
215
|
catch (error) {
|
|
183
216
|
if (!isInvalidMessageError(error))
|
|
184
217
|
throw error;
|
|
185
218
|
invalid = true;
|
|
186
|
-
|
|
187
|
-
said: envelope.serder.said,
|
|
188
|
-
ilk: envelope.serder.ilk,
|
|
189
|
-
route: envelope.serder.route,
|
|
190
|
-
detail: error.message,
|
|
191
|
-
});
|
|
192
|
-
result = { envelope, decision: { kind: "drop", reason: "invalidMessage", detail: error.message } };
|
|
219
|
+
result = invalidMessage(envelope, error.message);
|
|
193
220
|
}
|
|
194
221
|
const mailboxQueryAuthorized = !invalid && result.envelope.serder.route === "mbx"
|
|
195
222
|
&& kvy.isMailboxQueryAuthorized(queryEnvelopeFromDispatch(result.envelope));
|
|
223
|
+
// Published with this query's own cues; the cue router hands it to this host's querier.
|
|
224
|
+
const refresh = self.relaying && !invalid ? relayRefreshCue(self.exc.hby, result, mailboxAid ?? null) : null;
|
|
225
|
+
if (refresh)
|
|
226
|
+
self.protocolCues.push(refresh);
|
|
196
227
|
// Capture this request's facts before optional escrows can suspend or change key state.
|
|
197
228
|
self.publishCues();
|
|
198
229
|
if (escrows)
|
|
@@ -206,14 +237,9 @@ export class ProtocolProcessor {
|
|
|
206
237
|
}
|
|
207
238
|
/** Capture direct replies before handing ordinary protocol records to the application consumer. */
|
|
208
239
|
publishCues() {
|
|
209
|
-
|
|
210
|
-
// batch before notifying them so nested publication cannot take its records.
|
|
211
|
-
const committed = new Deck();
|
|
240
|
+
this.replies.capture(this.protocolCues);
|
|
212
241
|
while (!this.protocolCues.empty)
|
|
213
|
-
|
|
214
|
-
this.replies.capture(committed);
|
|
215
|
-
while (!committed.empty)
|
|
216
|
-
this.cues.push(committed.pull());
|
|
242
|
+
this.cues.push(this.protocolCues.pull());
|
|
217
243
|
}
|
|
218
244
|
/** Advance configured escrow owners; each scan joins its own I/O and handler cleanup. */
|
|
219
245
|
*processEscrowsOnce() {
|
|
@@ -67,23 +67,6 @@ export class QueryReplies {
|
|
|
67
67
|
get ready() {
|
|
68
68
|
return slot.reply !== undefined;
|
|
69
69
|
},
|
|
70
|
-
/**
|
|
71
|
-
* Install the sole readiness observer for this slot.
|
|
72
|
-
*
|
|
73
|
-
* @param notify Synchronous wake-up callback; invoked immediately if ready or closed.
|
|
74
|
-
* @returns A function removing this observer without closing the request slot.
|
|
75
|
-
*/
|
|
76
|
-
observeReady(notify) {
|
|
77
|
-
if (slot.notify)
|
|
78
|
-
throw new Error("Reply slot already has a readiness observer");
|
|
79
|
-
slot.notify = notify;
|
|
80
|
-
if (slot.reply || closed)
|
|
81
|
-
notify();
|
|
82
|
-
return () => {
|
|
83
|
-
if (slot.notify === notify)
|
|
84
|
-
slot.notify = undefined;
|
|
85
|
-
};
|
|
86
|
-
},
|
|
87
70
|
/**
|
|
88
71
|
* Inspect evidence without consuming it so callers can finish construction first.
|
|
89
72
|
*
|
|
@@ -112,8 +95,6 @@ export class QueryReplies {
|
|
|
112
95
|
group.delete(slot);
|
|
113
96
|
if (group.size === 0)
|
|
114
97
|
this.active.delete(said);
|
|
115
|
-
slot.notify?.();
|
|
116
|
-
slot.notify = undefined;
|
|
117
98
|
},
|
|
118
99
|
};
|
|
119
100
|
}
|
|
@@ -142,9 +123,6 @@ export class QueryReplies {
|
|
|
142
123
|
&& cue.kin === "reply" && cue.route === "/tsn/credential"
|
|
143
124
|
|| slot.reply.kin === "receipt" && cue.kin === "witness"))
|
|
144
125
|
slot.companion = cue;
|
|
145
|
-
else
|
|
146
|
-
continue;
|
|
147
|
-
slot.notify?.();
|
|
148
126
|
}
|
|
149
127
|
}
|
|
150
128
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent side of relay mode: refresh the agent's own view of a subject when a served edge asks about it.
|
|
3
|
+
*
|
|
4
|
+
* An edge's only view of third-party key state is its agent's: every edge query is addressed to the agent, which
|
|
5
|
+
* answers from its own store. That store may be stale for an identifier the agent has no reason of its own to
|
|
6
|
+
* follow. So when a relaying agent receives a `logs` or `ksn` query from a sender it serves about a subject that is
|
|
7
|
+
* not one of its own, it also issues its own ordinary `logs` query for that subject, as an ordinary `query` cue its
|
|
8
|
+
* own querier consumes. The agent is a forwarder, so that query goes directly to the subject's controller, agent or
|
|
9
|
+
* witness endpoint. This is on demand only, as KERIA queries for its Signify clients: no watching, no subscription
|
|
10
|
+
* list, no new store.
|
|
11
|
+
*
|
|
12
|
+
* What the edge sees: the answer to the query that caused the refresh comes from what the agent held then, unless
|
|
13
|
+
* the query names a sequence number or anchor the agent lacks, in which case the agent escrows it (query-not-found)
|
|
14
|
+
* and answers once the refresh lands. A later edge query returns the refreshed state.
|
|
15
|
+
*/
|
|
16
|
+
import { Ilks } from "cesr-ts";
|
|
17
|
+
import { Kever } from "../../core/kel/kever.js";
|
|
18
|
+
import { servingHost } from "../mailbox/forward-handler.js";
|
|
19
|
+
import { queryEnvelopeFromDispatch } from "../protocol/parsering.js";
|
|
20
|
+
import { addressableAttesters } from "./witness-inquisitor.js";
|
|
21
|
+
/**
|
|
22
|
+
* The requester of a query whose attached signatures satisfy its current signing threshold, or null.
|
|
23
|
+
*
|
|
24
|
+
* `logs` and `ksn` answers are public, so the KEL query path does not authenticate the requester (nor does KERIpy).
|
|
25
|
+
* A refresh spends the agent's own traffic on the requester's behalf, so it needs the requester proven: a
|
|
26
|
+
* transferable source whose KEL the agent holds and whose current keys signed this query to threshold.
|
|
27
|
+
*
|
|
28
|
+
* @param hby Borrowed accepted-state store holding the requester's KEL.
|
|
29
|
+
* @param query Normalized query envelope with its requester source and indexed signatures.
|
|
30
|
+
* @returns The authenticated requester AID, or null.
|
|
31
|
+
*/
|
|
32
|
+
function authenticatedRequester(hby, query) {
|
|
33
|
+
const source = query.source?.qb64;
|
|
34
|
+
if (!source)
|
|
35
|
+
return null;
|
|
36
|
+
const kever = hby.db.getKever(source);
|
|
37
|
+
if (!kever?.tholder)
|
|
38
|
+
return null;
|
|
39
|
+
const verified = Kever.verifyIndexedSignatures(query.serder.raw, query.sigers ?? [], kever.verfers);
|
|
40
|
+
return kever.tholder.satisfy(verified.indices) ? source : null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Decide whether one processed query makes a relaying host refresh its view of the subject, and return the query cue
|
|
44
|
+
* that does it.
|
|
45
|
+
*
|
|
46
|
+
* Every condition must hold:
|
|
47
|
+
* - the message is a remote (not local) `logs` or `ksn` query the KEL owner accepted or escrowed;
|
|
48
|
+
* - its subject `q.i` is not a prefix of this Habery;
|
|
49
|
+
* - its requester is authenticated (see {@link authenticatedRequester}) and served by a local host under the shared
|
|
50
|
+
* `servingHost` rule (an accepted mailbox or agent end role naming the host);
|
|
51
|
+
* - the host can address an attester for the subject now ({@link addressableAttesters}: one with an accepted
|
|
52
|
+
* location), so the request is sent on the querier's next pass instead of waiting in its queue.
|
|
53
|
+
*
|
|
54
|
+
* The cue is `{ route: "logs", q: {} }` for the subject, signed by the serving host: one full replay from the
|
|
55
|
+
* attester covers both routes, since `ksn` is answered from the same key state. A `tels` query is not refreshed:
|
|
56
|
+
* its wire body names the registry and credential but not the issuer the querier selects attesters by.
|
|
57
|
+
*
|
|
58
|
+
* @param hby Borrowed habitats and accepted-state store of the relaying host.
|
|
59
|
+
* @param result Processed envelope and the decision its owner made.
|
|
60
|
+
* @param mailboxAid Receiving hosted endpoint AID, or null when the request named none.
|
|
61
|
+
* @returns The refresh query cue, or null when this query refreshes nothing.
|
|
62
|
+
*/
|
|
63
|
+
export function relayRefreshCue(hby, result, mailboxAid) {
|
|
64
|
+
const { envelope, decision } = result;
|
|
65
|
+
const serder = envelope.serder;
|
|
66
|
+
if (envelope.local || serder.ilk !== Ilks.qry || (serder.route !== "logs" && serder.route !== "ksn"))
|
|
67
|
+
return null;
|
|
68
|
+
if (!decision || (decision.kind !== "accept" && decision.kind !== "escrow"))
|
|
69
|
+
return null;
|
|
70
|
+
const subject = serder.ked?.q?.i;
|
|
71
|
+
if (typeof subject !== "string" || hby.db.prefixes.has(subject))
|
|
72
|
+
return null;
|
|
73
|
+
const requester = authenticatedRequester(hby, queryEnvelopeFromDispatch(envelope));
|
|
74
|
+
if (!requester)
|
|
75
|
+
return null;
|
|
76
|
+
const host = servingHost(hby, { sender: requester, origin: null }, mailboxAid);
|
|
77
|
+
const hab = host ? hby.habs.get(host) : undefined;
|
|
78
|
+
if (!hab || addressableAttesters(hab, subject).length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
return { kin: "query", pre: subject, route: "logs", q: {}, signer: hab.pre };
|
|
81
|
+
}
|