keri-ts 2.1.0 → 2.2.0
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/README.md +30 -2
- package/esm/keri/npm/src/acdc/authoring.js +195 -0
- package/esm/keri/npm/src/acdc/custody.js +164 -0
- package/esm/keri/npm/src/acdc/dhking.js +244 -32
- package/esm/keri/npm/src/acdc/regbasing.js +10 -0
- package/esm/keri/npm/src/acdc/salting.js +16 -1
- package/esm/keri/npm/src/acdc/scheming.js +93 -54
- package/esm/keri/npm/src/acdc/unblinding.js +118 -95
- package/esm/keri/npm/src/app/bootstrap/authenticating.js +4 -6
- package/esm/keri/npm/src/app/bootstrap/oobi-resolving.js +41 -0
- package/esm/keri/npm/src/app/bootstrap/oobiery.js +4 -6
- package/esm/keri/npm/src/app/credentials/credential-revoking.js +1 -0
- package/esm/keri/npm/src/app/credentials/credential-workflows.js +2 -0
- package/esm/keri/npm/src/app/credentials/registry-settling.js +44 -0
- package/esm/keri/npm/src/app/credentials/root-replicating.js +68 -60
- package/esm/keri/npm/src/app/credentials/root-replicator.js +198 -0
- package/esm/keri/npm/src/app/credentials/tel-completion.js +36 -0
- package/esm/keri/npm/src/app/delivery/forwarding.js +16 -3
- package/esm/keri/npm/src/app/exchanges/exchanging.js +14 -4
- package/esm/keri/npm/src/app/identity/challenging.js +50 -0
- package/esm/keri/npm/src/app/identity/delegating.js +77 -77
- package/esm/keri/npm/src/app/identity/delegation-approval.js +333 -0
- package/esm/keri/npm/src/app/identity/event-completion.js +154 -0
- package/esm/keri/npm/src/app/identity/habbing.js +129 -6
- package/esm/keri/npm/src/app/identity/keeping.js +77 -4
- package/esm/keri/npm/src/app/ipex/group-ipex.js +327 -0
- package/esm/keri/npm/src/app/ipex/group-issuance.js +902 -0
- package/esm/keri/npm/src/app/ipex/ipex-admitting.js +120 -0
- package/esm/keri/npm/src/app/ipex/ipex-conversation.js +197 -0
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +591 -0
- package/esm/keri/npm/src/app/ipex/ipex-granting.js +93 -0
- package/esm/keri/npm/src/app/ipex/ipex-grouping.js +34 -11
- package/esm/keri/npm/src/app/ipex/issuance.js +888 -0
- package/esm/keri/npm/src/app/ipex/pending-presentations.js +46 -0
- package/esm/keri/npm/src/app/ipex/presentation-files.js +200 -0
- package/esm/keri/npm/src/app/ipex/presenting.js +401 -0
- package/esm/keri/npm/src/app/ipex/receive-issuance.js +86 -0
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +660 -0
- package/esm/keri/npm/src/app/mailbox/ipex-arrival.js +103 -0
- package/esm/keri/npm/src/app/mailbox/kel-publisher.js +322 -0
- package/esm/keri/npm/src/app/mailbox/kel-publishing.js +65 -0
- package/esm/keri/npm/src/app/mailbox/kel-submission.js +24 -0
- package/esm/keri/npm/src/app/mailbox/mailbox-admin.js +208 -0
- package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +212 -47
- package/esm/keri/npm/src/app/mailbox/mailboxing.js +10 -5
- package/esm/keri/npm/src/app/multisig/counseling.js +482 -0
- package/esm/keri/npm/src/app/multisig/group-members.js +18 -0
- package/esm/keri/npm/src/app/multisig/multisig-workflows.js +94 -139
- package/esm/keri/npm/src/app/multisig/proposals.js +281 -0
- package/esm/keri/npm/src/app/multisig/proposing.js +122 -0
- package/esm/keri/npm/src/app/notifications/signaling.js +5 -5
- package/esm/keri/npm/src/app/protocol/parsering.js +26 -1
- package/esm/keri/npm/src/app/protocol/protocol-processing.js +9 -3
- package/esm/keri/npm/src/app/queries/query-workflows.js +98 -0
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +106 -55
- package/esm/keri/npm/src/app/runtime/cue-runtime.js +3 -6
- package/esm/keri/npm/src/app/runtime/delivery-outbox.js +87 -0
- package/esm/keri/npm/src/app/runtime/doing.js +17 -0
- package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +207 -0
- package/esm/keri/npm/src/app/runtime/operation-owners.js +151 -0
- package/esm/keri/npm/src/app/runtime/retry-backoff.js +93 -0
- package/esm/keri/npm/src/app/runtime/workflow-contract.js +80 -0
- package/esm/keri/npm/src/app/transport/http-client.js +25 -6
- package/esm/keri/npm/src/app/witness/http-endpoints.js +4 -4
- package/esm/keri/npm/src/cli/common/event-completion.js +49 -0
- package/esm/keri/npm/src/cli/common/runtime-workers.js +23 -0
- package/esm/keri/npm/src/cli/credentials/ipex.js +157 -190
- package/esm/keri/npm/src/cli/credentials/vc.js +53 -72
- package/esm/keri/npm/src/cli/endpoints/did.js +12 -3
- package/esm/keri/npm/src/cli/endpoints/oobi.js +10 -13
- package/esm/keri/npm/src/cli/identity/challenge.js +8 -34
- package/esm/keri/npm/src/cli/identity/delegate.js +81 -258
- package/esm/keri/npm/src/cli/identity/incept.js +15 -32
- package/esm/keri/npm/src/cli/identity/interact.js +5 -17
- package/esm/keri/npm/src/cli/identity/rotate.js +8 -36
- package/esm/keri/npm/src/cli/index.js +3 -1
- package/esm/keri/npm/src/cli/messaging/group-join.js +79 -0
- package/esm/keri/npm/src/cli/messaging/group-wait.js +88 -0
- package/esm/keri/npm/src/cli/messaging/multisig.js +163 -284
- package/esm/keri/npm/src/cli/messaging/root-copy-lines.js +31 -0
- package/esm/keri/npm/src/core/kel/eventing.js +66 -10
- package/esm/keri/npm/src/core/mailbox/kram-policy.js +39 -6
- package/esm/keri/npm/src/core/mailbox/kraming.js +2 -0
- package/esm/keri/npm/src/core/records.js +23 -0
- package/esm/keri/npm/src/core/routing.js +13 -0
- package/esm/keri/npm/src/db/basing.js +34 -2
- package/esm/keri/npm/src/db/core/indexeddb-persistence.js +221 -0
- package/esm/keri/npm/src/db/indexeddb.js +98 -0
- package/esm/keri/npm/src/db/keeping.js +11 -2
- package/esm/keri/npm/src/db/store-lock.js +150 -0
- package/esm/keri/npm/src/npm/db-indexeddb.browser.js +2 -0
- package/esm/keri/npm/src/npm/db-indexeddb.js +3 -0
- package/esm/keri/npm/src/npm/db.browser.js +2 -0
- package/esm/keri/npm/src/npm/index.browser.js +9 -0
- package/esm/keri/npm/src/npm/runtime-web.browser.js +2 -0
- package/esm/keri/npm/src/npm/runtime-web.js +3 -0
- package/esm/keri/npm/src/npm/runtime.browser.js +2 -0
- package/esm/keri/npm/src/runtime/index.js +51 -4
- package/esm/keri/npm/src/runtime/web/output-barrier.js +47 -0
- package/esm/keri/npm/src/runtime/web/persistent-storage.js +37 -0
- package/esm/keri/npm/src/runtime/web.js +10 -0
- package/esm/keri/npm/src/vdr/tel-publication.js +16 -0
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +15 -2
- package/types/acdc/authoring.d.ts +54 -0
- package/types/acdc/authoring.d.ts.map +1 -0
- package/types/acdc/custody.d.ts +77 -0
- package/types/acdc/custody.d.ts.map +1 -0
- package/types/acdc/dhking.d.ts +94 -3
- package/types/acdc/dhking.d.ts.map +1 -1
- package/types/acdc/regbasing.d.ts +4 -0
- package/types/acdc/regbasing.d.ts.map +1 -1
- package/types/acdc/salting.d.ts +6 -1
- package/types/acdc/salting.d.ts.map +1 -1
- package/types/acdc/scheming.d.ts +2 -0
- package/types/acdc/scheming.d.ts.map +1 -1
- package/types/acdc/unblinding.d.ts +58 -19
- package/types/acdc/unblinding.d.ts.map +1 -1
- package/types/app/bootstrap/authenticating.d.ts +3 -1
- package/types/app/bootstrap/authenticating.d.ts.map +1 -1
- package/types/app/bootstrap/oobi-resolving.d.ts +92 -0
- package/types/app/bootstrap/oobi-resolving.d.ts.map +1 -0
- package/types/app/bootstrap/oobiery.d.ts +3 -1
- package/types/app/bootstrap/oobiery.d.ts.map +1 -1
- package/types/app/credentials/credential-revoking.d.ts +3 -0
- package/types/app/credentials/credential-revoking.d.ts.map +1 -1
- package/types/app/credentials/credential-workflows.d.ts +5 -0
- package/types/app/credentials/credential-workflows.d.ts.map +1 -1
- package/types/app/credentials/registry-settling.d.ts +85 -0
- package/types/app/credentials/registry-settling.d.ts.map +1 -0
- package/types/app/credentials/root-replicating.d.ts +56 -44
- package/types/app/credentials/root-replicating.d.ts.map +1 -1
- package/types/app/credentials/root-replicator.d.ts +105 -0
- package/types/app/credentials/root-replicator.d.ts.map +1 -0
- package/types/app/credentials/tel-completion.d.ts +55 -0
- package/types/app/credentials/tel-completion.d.ts.map +1 -0
- package/types/app/delivery/forwarding.d.ts +6 -0
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/exchanges/exchanging.d.ts.map +1 -1
- package/types/app/identity/challenging.d.ts +54 -0
- package/types/app/identity/challenging.d.ts.map +1 -1
- package/types/app/identity/delegating.d.ts +21 -4
- package/types/app/identity/delegating.d.ts.map +1 -1
- package/types/app/identity/delegation-approval.d.ts +228 -0
- package/types/app/identity/delegation-approval.d.ts.map +1 -0
- package/types/app/identity/event-completion.d.ts +151 -0
- package/types/app/identity/event-completion.d.ts.map +1 -0
- package/types/app/identity/habbing.d.ts +67 -2
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/identity/keeping.d.ts +49 -3
- package/types/app/identity/keeping.d.ts.map +1 -1
- package/types/app/ipex/group-ipex.d.ts +247 -0
- package/types/app/ipex/group-ipex.d.ts.map +1 -0
- package/types/app/ipex/group-issuance.d.ts +281 -0
- package/types/app/ipex/group-issuance.d.ts.map +1 -0
- package/types/app/ipex/ipex-admitting.d.ts +47 -0
- package/types/app/ipex/ipex-admitting.d.ts.map +1 -0
- package/types/app/ipex/ipex-conversation.d.ts +195 -0
- package/types/app/ipex/ipex-conversation.d.ts.map +1 -0
- package/types/app/ipex/ipex-discloser.d.ts +156 -0
- package/types/app/ipex/ipex-discloser.d.ts.map +1 -0
- package/types/app/ipex/ipex-granting.d.ts +145 -0
- package/types/app/ipex/ipex-granting.d.ts.map +1 -0
- package/types/app/ipex/ipex-grouping.d.ts +18 -0
- package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
- package/types/app/ipex/issuance.d.ts +443 -0
- package/types/app/ipex/issuance.d.ts.map +1 -0
- package/types/app/ipex/pending-presentations.d.ts +25 -0
- package/types/app/ipex/pending-presentations.d.ts.map +1 -0
- package/types/app/ipex/presentation-files.d.ts +47 -0
- package/types/app/ipex/presentation-files.d.ts.map +1 -0
- package/types/app/ipex/presenting.d.ts +298 -0
- package/types/app/ipex/presenting.d.ts.map +1 -0
- package/types/app/ipex/receive-issuance.d.ts +99 -0
- package/types/app/ipex/receive-issuance.d.ts.map +1 -0
- package/types/app/ipex/unblinding-workflow.d.ts +255 -0
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -0
- package/types/app/mailbox/ipex-arrival.d.ts +104 -0
- package/types/app/mailbox/ipex-arrival.d.ts.map +1 -0
- package/types/app/mailbox/kel-publisher.d.ts +141 -0
- package/types/app/mailbox/kel-publisher.d.ts.map +1 -0
- package/types/app/mailbox/kel-publishing.d.ts +70 -0
- package/types/app/mailbox/kel-publishing.d.ts.map +1 -0
- package/types/app/mailbox/kel-submission.d.ts +18 -0
- package/types/app/mailbox/kel-submission.d.ts.map +1 -0
- package/types/app/mailbox/mailbox-admin.d.ts +113 -0
- package/types/app/mailbox/mailbox-admin.d.ts.map +1 -0
- package/types/app/mailbox/mailbox-poller.d.ts +92 -3
- package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
- package/types/app/mailbox/mailboxing.d.ts +3 -0
- package/types/app/mailbox/mailboxing.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +196 -0
- package/types/app/multisig/counseling.d.ts.map +1 -0
- package/types/app/multisig/group-members.d.ts +11 -0
- package/types/app/multisig/group-members.d.ts.map +1 -1
- package/types/app/multisig/grouping.d.ts +39 -20
- package/types/app/multisig/grouping.d.ts.map +1 -1
- package/types/app/multisig/multisig-workflows.d.ts +91 -22
- package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
- package/types/app/multisig/proposals.d.ts +129 -0
- package/types/app/multisig/proposals.d.ts.map +1 -0
- package/types/app/multisig/proposing.d.ts +125 -0
- package/types/app/multisig/proposing.d.ts.map +1 -0
- package/types/app/notifications/signaling.d.ts +3 -1
- package/types/app/notifications/signaling.d.ts.map +1 -1
- package/types/app/protocol/parsering.d.ts +13 -0
- package/types/app/protocol/parsering.d.ts.map +1 -1
- package/types/app/protocol/protocol-processing.d.ts +7 -1
- package/types/app/protocol/protocol-processing.d.ts.map +1 -1
- package/types/app/queries/query-workflows.d.ts +98 -0
- package/types/app/queries/query-workflows.d.ts.map +1 -0
- package/types/app/runtime/agent-runtime.d.ts +72 -25
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/cue-runtime.d.ts +2 -1
- package/types/app/runtime/cue-runtime.d.ts.map +1 -1
- package/types/app/runtime/delivery-outbox.d.ts +68 -0
- package/types/app/runtime/delivery-outbox.d.ts.map +1 -0
- package/types/app/runtime/doing.d.ts +27 -0
- package/types/app/runtime/doing.d.ts.map +1 -0
- package/types/app/runtime/ipex-auxiliaries.d.ts +74 -0
- package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -0
- package/types/app/runtime/operation-owners.d.ts +137 -0
- package/types/app/runtime/operation-owners.d.ts.map +1 -0
- package/types/app/runtime/retry-backoff.d.ts +49 -0
- package/types/app/runtime/retry-backoff.d.ts.map +1 -0
- package/types/app/runtime/workflow-contract.d.ts +79 -0
- package/types/app/runtime/workflow-contract.d.ts.map +1 -0
- package/types/app/transport/http-client.d.ts.map +1 -1
- package/types/cli/common/event-completion.d.ts +26 -0
- package/types/cli/common/event-completion.d.ts.map +1 -0
- package/types/cli/common/runtime-workers.d.ts +23 -0
- package/types/cli/common/runtime-workers.d.ts.map +1 -0
- package/types/cli/credentials/ipex.d.ts.map +1 -1
- package/types/cli/credentials/vc.d.ts +1 -1
- package/types/cli/credentials/vc.d.ts.map +1 -1
- package/types/cli/endpoints/did.d.ts.map +1 -1
- package/types/cli/endpoints/oobi.d.ts +3 -1
- package/types/cli/endpoints/oobi.d.ts.map +1 -1
- package/types/cli/identity/challenge.d.ts +1 -1
- package/types/cli/identity/challenge.d.ts.map +1 -1
- package/types/cli/identity/delegate.d.ts +14 -1
- package/types/cli/identity/delegate.d.ts.map +1 -1
- package/types/cli/identity/incept.d.ts.map +1 -1
- package/types/cli/identity/interact.d.ts.map +1 -1
- package/types/cli/identity/rotate.d.ts.map +1 -1
- package/types/cli/index.d.ts +3 -1
- package/types/cli/index.d.ts.map +1 -1
- package/types/cli/messaging/group-join.d.ts +53 -0
- package/types/cli/messaging/group-join.d.ts.map +1 -0
- package/types/cli/messaging/group-wait.d.ts +34 -0
- package/types/cli/messaging/group-wait.d.ts.map +1 -0
- package/types/cli/messaging/multisig.d.ts +28 -5
- package/types/cli/messaging/multisig.d.ts.map +1 -1
- package/types/cli/messaging/root-copy-lines.d.ts +39 -0
- package/types/cli/messaging/root-copy-lines.d.ts.map +1 -0
- package/types/core/cues.d.ts +11 -0
- package/types/core/cues.d.ts.map +1 -1
- package/types/core/kel/eventing.d.ts.map +1 -1
- package/types/core/mailbox/kram-policy.d.ts +14 -0
- package/types/core/mailbox/kram-policy.d.ts.map +1 -1
- package/types/core/mailbox/kraming.d.ts +1 -1
- package/types/core/mailbox/kraming.d.ts.map +1 -1
- package/types/core/records.d.ts +61 -0
- package/types/core/records.d.ts.map +1 -1
- package/types/core/routing.d.ts +6 -0
- package/types/core/routing.d.ts.map +1 -1
- package/types/db/basing.d.ts +8 -2
- package/types/db/basing.d.ts.map +1 -1
- package/types/db/core/indexeddb-persistence.d.ts +59 -0
- package/types/db/core/indexeddb-persistence.d.ts.map +1 -0
- package/types/db/indexeddb.d.ts +37 -0
- package/types/db/indexeddb.d.ts.map +1 -0
- package/types/db/keeping.d.ts +5 -2
- package/types/db/keeping.d.ts.map +1 -1
- package/types/db/store-lock.d.ts +72 -0
- package/types/db/store-lock.d.ts.map +1 -0
- package/types/npm/db-indexeddb.d.ts +4 -0
- package/types/npm/db-indexeddb.d.ts.map +1 -0
- package/types/npm/runtime-web.d.ts +4 -0
- package/types/npm/runtime-web.d.ts.map +1 -0
- package/types/runtime/index.d.ts +41 -6
- package/types/runtime/index.d.ts.map +1 -1
- package/types/runtime/web/output-barrier.d.ts +30 -0
- package/types/runtime/web/output-barrier.d.ts.map +1 -0
- package/types/runtime/web/persistent-storage.d.ts +36 -0
- package/types/runtime/web/persistent-storage.d.ts.map +1 -0
- package/types/runtime/web.d.ts +11 -0
- package/types/runtime/web.d.ts.map +1 -0
- package/types/vdr/tel-publication.d.ts +6 -0
- package/types/vdr/tel-publication.d.ts.map +1 -1
- package/types/version.d.ts +2 -2
- package/esm/keri/npm/src/cli/common/tel-completion.js +0 -24
- package/types/cli/common/tel-completion.d.ts +0 -8
- package/types/cli/common/tel-completion.d.ts.map +0 -1
|
@@ -2,33 +2,35 @@ import { Organizer } from "../identity/organizing.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Multisig proposal and approval workflows.
|
|
4
4
|
*
|
|
5
|
-
* CLI orchestration owns timed waits, worker lifetimes, prompts and output.
|
|
6
5
|
* This module publishes proposals and approves stored KEL/RPY/VDR proposals
|
|
7
|
-
* without starting or driving background workers.
|
|
6
|
+
* without starting or driving background workers. `approveGroupProposal` is the
|
|
7
|
+
* only caller that approves; it never asks, because calling it is the consent.
|
|
8
8
|
*/
|
|
9
|
-
import { attachmentPayload, collectPayload, concatBytes, Counter, Diger, Ilks, NumberPrimitive, NumDex, parseBytes, Prefixer, SerderACDC, SerderKERI, TraitDex, Vrsn_2_0, } from "cesr-ts";
|
|
9
|
+
import { attachmentPayload, collectPayload, concatBytes, Counter, Diger, Ilks, NumberPrimitive, NumDex, parseBytes, Prefixer, SerderACDC, SerderKERI, TraitDex, Verfer, Vrsn_2_0, } from "cesr-ts";
|
|
10
10
|
import { verifyRegistryAnchor, vetBlind, } from "../../acdc/regeventing.js";
|
|
11
11
|
import { publicOpeningAttachment } from "../../acdc/registering.js";
|
|
12
12
|
import { ValidationError } from "../../core/errors.js";
|
|
13
|
-
import { isV2RegistryRoute } from "../../core/exchange/protocol-exchanging.js";
|
|
14
13
|
import { Kever } from "../../core/kel/kever.js";
|
|
15
14
|
import { messagize } from "../../core/protocol-serialization.js";
|
|
16
15
|
import { RegistryRecord } from "../../core/records.js";
|
|
16
|
+
import { dgKey } from "../../db/core/keys.js";
|
|
17
17
|
import { loadNests } from "../../db/exchange/exchange-nests.js";
|
|
18
18
|
import { Credentialer, Registry, serializeCredential } from "../../vdr/credentialing.js";
|
|
19
19
|
import { envelopeFromFrame } from "../protocol/parsering.js";
|
|
20
20
|
import { groupCredentialAnchorReady, validateCredentialIssueProposal, validateCredentialRevocationProposal, validateCredentialSource, validateRegistryName, validateRegistryProposal, } from "../credentials/credential-proposals.js";
|
|
21
|
+
import { telEventStatus } from "../credentials/tel-completion.js";
|
|
21
22
|
import { endpointRoleAccepted, groupEndorseReply, groupEventKeys, groupSigningMembers, localGroupMember, signLocalGroupEvent, } from "../identity/endpoint-roleing.js";
|
|
22
23
|
import { Reactor } from "../protocol/reactor.js";
|
|
23
24
|
import { abortable } from "../runtime/abortable.js";
|
|
24
25
|
import { collectBytes } from "../transport/byte-source.js";
|
|
25
26
|
import { findLocalGroupMember as findLocalMember, groupMemberHasKey, uniqueMembers } from "./group-members.js";
|
|
26
27
|
import { acdcV2RegistryProposal, MULTISIG_ICP_ROUTE, MULTISIG_ISS_ROUTE, MULTISIG_IXN_ROUTE, MULTISIG_REV_ROUTE, MULTISIG_ROT_ROUTE, MULTISIG_RPY_ROUTE, MULTISIG_VCP_ROUTE, multisigChild, multisigChildEvidence, multisigChildGenusAttrs, prepareAcdcV2RegistryExn, prepareMultisigEventExn, prepareMultisigVdrExn, registryProposalEvent, } from "./grouping.js";
|
|
27
|
-
import { admitAndSendGroupMessage, groupSendResult,
|
|
28
|
+
import { admitAndSendGroupMessage, groupSendResult, prepareGroupMessage, remoteGroupMembers, signGroupMessage, storedGroupMessage, } from "./multisig-messaging.js";
|
|
28
29
|
/** Approve one exact ACDC v2 TEL/anchor through existing member signing and ordinary processors.
|
|
29
30
|
* A returned wrapper records consent, not quorum/witness/TEL completion; retry the accepted-state owners.
|
|
31
|
+
* `resume` reuses this member's recorded anchor signatures after a crash instead of signing again.
|
|
30
32
|
*/
|
|
31
|
-
export function* approveAcdcV2Registry(hby, protocol, group, event, { anchor, opening } = {}) {
|
|
33
|
+
export function* approveAcdcV2Registry(hby, protocol, group, event, { anchor, opening, resume } = {}) {
|
|
32
34
|
const registry = protocol.registry, member = localGroupMember(hby, group.pre);
|
|
33
35
|
if (!registry || group.db !== hby.db || registry.db !== hby.db) {
|
|
34
36
|
throw new ValidationError("ACDC v2 group registry owner is unavailable.");
|
|
@@ -105,7 +107,10 @@ export function* approveAcdcV2Registry(hby, protocol, group, event, { anchor, op
|
|
|
105
107
|
throw new ValidationError("Registry anchor does not extend the current group event.");
|
|
106
108
|
}
|
|
107
109
|
const keys = groupEventKeys(hby, group.pre, anchor);
|
|
108
|
-
const
|
|
110
|
+
const members = groupSigningMembers(hby, group.pre);
|
|
111
|
+
// A resuming caller reuses the signatures the first attempt recorded in `sigs.` instead of signing the anchor again.
|
|
112
|
+
const sigers = (resume ? recordedLocalSignatures(hby, anchor, group.pre, members) : null)
|
|
113
|
+
?? signLocalGroupEvent(hby, anchor, members, keys);
|
|
109
114
|
const own = messagize(anchor, { sigers, gvrsn: Vrsn_2_0 });
|
|
110
115
|
const tel = concatBytes(event.raw, Counter.enclose({
|
|
111
116
|
code: "AttachmentGroup",
|
|
@@ -222,109 +227,34 @@ export function routeForKelEvent(serder) {
|
|
|
222
227
|
}
|
|
223
228
|
}
|
|
224
229
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* Notices discover candidates; their attributes do not authorize a route or group.
|
|
228
|
-
* An explicit accepted wrapper missing its handler index is replayed by the caller.
|
|
230
|
+
* Approve and publish this member's contribution to one `rip` or `bup` proposal.
|
|
231
|
+
* Null when the proposal is not a well-formed one for a joined group; it then stays inert.
|
|
229
232
|
*/
|
|
230
|
-
function*
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (group && group !== proposalGroup) {
|
|
256
|
-
if (options.said) {
|
|
257
|
-
throw new ValidationError(`Multisig exchange ${said} belongs to group ${proposalGroup}, not ${group}.`);
|
|
258
|
-
}
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
selected.push({ exn, route, indexed });
|
|
262
|
-
}
|
|
263
|
-
return selected;
|
|
264
|
-
}
|
|
265
|
-
/** Approve matching stored evidence; host progress and deadlines belong to the caller. */
|
|
266
|
-
export function* approvePendingMultisigProposal(hby, runtime, options) {
|
|
267
|
-
const notifier = runtime.notifier;
|
|
268
|
-
if (!notifier && !options.said) {
|
|
269
|
-
throw new ValidationError("Multisig join requires notification storage or an explicit exchange SAID.");
|
|
270
|
-
}
|
|
271
|
-
const notices = (notifier?.list(0, 100) ?? []);
|
|
272
|
-
for (const { exn, route, indexed } of yield* pendingMultisigProposals(hby, runtime, notices, options)) {
|
|
273
|
-
if (!indexed) {
|
|
274
|
-
// Acceptance commits before the asynchronous handler. Resume that ordinary
|
|
275
|
-
// authenticated intake after interruption; never synthesize approval indexes.
|
|
276
|
-
const message = yield* abortable(signal => collectBytes(runtime.exc.replay(exn.said, signal)));
|
|
277
|
-
yield* runtime.reactor.processBytes(message, { local: true });
|
|
278
|
-
const payloadSaid = multisigPayloadSaid(exn);
|
|
279
|
-
if (!hby.db.meids.get([payloadSaid]).some(index => index.qb64 === exn.said))
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
const rgy = isVdrRoute(route) ? runtime.vdr : null;
|
|
283
|
-
let result;
|
|
284
|
-
if (isKelRoute(route)) {
|
|
285
|
-
result = yield* approveKelProposal(hby, runtime, exn, route, options);
|
|
286
|
-
}
|
|
287
|
-
else if (isVdrRoute(route)) {
|
|
288
|
-
result = yield* approveVdrProposal(hby, rgy, runtime, exn, route, options);
|
|
289
|
-
}
|
|
290
|
-
else if (isV2RegistryRoute(route)) {
|
|
291
|
-
const nests = loadNests(hby.db, exn.said);
|
|
292
|
-
const proposal = acdcV2RegistryProposal(exn, nests, runtime.acdc?.store ?? null);
|
|
293
|
-
const group = proposal?.anchor.pre ? hby.habs.get(proposal.anchor.pre) : null;
|
|
294
|
-
if (!proposal || !group)
|
|
295
|
-
continue;
|
|
296
|
-
const head = group.kever?.serder.said;
|
|
297
|
-
if (!head)
|
|
298
|
-
throw new ValidationError("Registry approval requires an accepted local group.");
|
|
299
|
-
if (!options.callbacks.approveProposal({ route, group: group.pre, embedded: proposal.tel.said }))
|
|
300
|
-
continue;
|
|
301
|
-
if (group.kever?.serder.said !== head || !acdcV2RegistryProposal(exn, nests, runtime.acdc?.store ?? null)) {
|
|
302
|
-
throw new ValidationError("Group or registry changed while approval was pending.");
|
|
303
|
-
}
|
|
304
|
-
const envelope = envelopeFromFrame(nests[0]), opening = [...envelope.bsqs, ...envelope.bsss];
|
|
305
|
-
if (opening.length > 1)
|
|
306
|
-
throw new ValidationError("Registry proposal carries multiple openings.");
|
|
307
|
-
const approval = yield* approveAcdcV2Registry(hby, runtime.protocol, group, proposal.tel, {
|
|
308
|
-
anchor: proposal.anchor,
|
|
309
|
-
opening: opening[0],
|
|
310
|
-
});
|
|
311
|
-
const published = yield* publishAcdcV2Registry(hby, runtime.postman, group, approval);
|
|
312
|
-
const regid = proposal.tel.ilk === "rip" ? proposal.tel.said : proposal.tel.regid;
|
|
313
|
-
const accepted = runtime.acdc.store.seqEvent(regid, Number(BigInt(`0x${proposal.tel.sad.n}`)))?.said === proposal.tel.said;
|
|
314
|
-
result = { route, said: exn.said, embedded: proposal.tel.said, group: group.pre, accepted, ...published };
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
result = yield* approveRpyProposal(hby, runtime, exn, route, options);
|
|
318
|
-
}
|
|
319
|
-
if (result) {
|
|
320
|
-
for (const note of notices) {
|
|
321
|
-
if (note.attrs.d === exn.said)
|
|
322
|
-
notifier?.remove(note.rid);
|
|
323
|
-
}
|
|
324
|
-
return result;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return null;
|
|
233
|
+
export function* approveRegistryV2Proposal(hby, runtime, exn, route, options) {
|
|
234
|
+
const nests = loadNests(hby.db, exn.said);
|
|
235
|
+
const proposal = acdcV2RegistryProposal(exn, nests, runtime.registryStore);
|
|
236
|
+
const group = proposal?.anchor.pre ? hby.habs.get(proposal.anchor.pre) : null;
|
|
237
|
+
if (!proposal || !group)
|
|
238
|
+
return null;
|
|
239
|
+
const head = group.kever?.serder.said;
|
|
240
|
+
if (!head)
|
|
241
|
+
throw new ValidationError("Registry approval requires an accepted local group.");
|
|
242
|
+
if (group.kever?.serder.said !== head || !acdcV2RegistryProposal(exn, nests, runtime.registryStore)) {
|
|
243
|
+
throw new ValidationError("Group or registry changed while approval was pending.");
|
|
244
|
+
}
|
|
245
|
+
const envelope = envelopeFromFrame(nests[0]), opening = [...envelope.bsqs, ...envelope.bsss];
|
|
246
|
+
if (opening.length > 1)
|
|
247
|
+
throw new ValidationError("Registry proposal carries multiple openings.");
|
|
248
|
+
const approval = yield* approveAcdcV2Registry(hby, runtime.protocol, group, proposal.tel, {
|
|
249
|
+
anchor: proposal.anchor,
|
|
250
|
+
opening: opening[0],
|
|
251
|
+
resume: options.resume,
|
|
252
|
+
});
|
|
253
|
+
const published = yield* publishAcdcV2Registry(hby, runtime.postman, group, approval);
|
|
254
|
+
const regid = proposal.tel.ilk === "rip" ? proposal.tel.said : proposal.tel.regid;
|
|
255
|
+
const accepted = runtime.registryStore?.seqEvent(regid, Number(BigInt(`0x${proposal.tel.sad.n}`)))?.said
|
|
256
|
+
=== proposal.tel.said;
|
|
257
|
+
return { route, said: exn.said, embedded: proposal.tel.said, group: group.pre, accepted, ...published };
|
|
328
258
|
}
|
|
329
259
|
/**
|
|
330
260
|
* Approve and publish a local contribution to one endpoint-role reply proposal.
|
|
@@ -335,10 +265,10 @@ export function* approvePendingMultisigProposal(hby, runtime, options) {
|
|
|
335
265
|
* @param runtime Borrowed composed runtime; caller owns its worker and store lifetime.
|
|
336
266
|
* @param exn Stored multisig exchange wrapper.
|
|
337
267
|
* @param route Selected multisig route identifying the proposal kind.
|
|
338
|
-
* @param options
|
|
268
|
+
* @param options Alias and name choices plus `resume`; a proposal that is not well formed is left inert.
|
|
339
269
|
* @returns Approval result with protocol acceptance and deliveries, or null for declined/incomplete input.
|
|
340
270
|
*/
|
|
341
|
-
function* approveRpyProposal(hby, runtime, exn, route, options) {
|
|
271
|
+
export function* approveRpyProposal(hby, runtime, exn, route, options) {
|
|
342
272
|
const wrapperSaid = exn.said;
|
|
343
273
|
if (!wrapperSaid) {
|
|
344
274
|
return null;
|
|
@@ -356,9 +286,6 @@ function* approveRpyProposal(hby, runtime, exn, route, options) {
|
|
|
356
286
|
const { event: rpySerder, attachment, genus } = child;
|
|
357
287
|
const embeddedSaid = rpySerder.said ?? "";
|
|
358
288
|
const prior = storedGroupMessage(hby.db, member.pre, exn);
|
|
359
|
-
if (!prior && !options.callbacks.approveProposal({ route, group: groupPre, embedded: embeddedSaid || "<unknown>" })) {
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
289
|
yield* runtime.reactor.processBytes(concatBytes(Counter.makeGVC(genus), rpySerder.raw, attachment), { local: true });
|
|
363
290
|
let localRpy;
|
|
364
291
|
if (prior) {
|
|
@@ -386,10 +313,10 @@ function* approveRpyProposal(hby, runtime, exn, route, options) {
|
|
|
386
313
|
* @param runtime Borrowed composed runtime; caller owns its worker and store lifetime.
|
|
387
314
|
* @param exn Stored multisig exchange wrapper.
|
|
388
315
|
* @param route Selected multisig route identifying the proposal kind.
|
|
389
|
-
* @param options
|
|
316
|
+
* @param options Alias and name choices plus `resume`; a proposal that is not well formed is left inert.
|
|
390
317
|
* @returns Approval result with protocol acceptance and deliveries, or null for declined/incomplete input.
|
|
391
318
|
*/
|
|
392
|
-
function* approveKelProposal(hby, runtime, exn, route, options) {
|
|
319
|
+
export function* approveKelProposal(hby, runtime, exn, route, options) {
|
|
393
320
|
const wrapperSaid = exn.said;
|
|
394
321
|
if (!wrapperSaid) {
|
|
395
322
|
return null;
|
|
@@ -429,17 +356,26 @@ function* approveKelProposal(hby, runtime, exn, route, options) {
|
|
|
429
356
|
if (eventAccepted(hby, serder) && !smids.some(mid => hby.habs.has(mid))) {
|
|
430
357
|
return null;
|
|
431
358
|
}
|
|
432
|
-
|
|
433
|
-
|
|
359
|
+
// Signing writes the member's signatures to `sigs.` when the event is first
|
|
360
|
+
// processed, before any wrapper exists. A resuming caller (explicit consent
|
|
361
|
+
// already given) finds them and reuses them: no second signature and no
|
|
362
|
+
// second join.
|
|
363
|
+
const signed = options.resume ? recordedLocalSignatures(hby, serder, groupPre, smids) : null;
|
|
364
|
+
if (signed) {
|
|
365
|
+
localMessage = messagize(serder, { sigers: signed, pipelined: false, gvrsn: approval.genus });
|
|
434
366
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
367
|
+
else {
|
|
368
|
+
if (proposal.route === MULTISIG_ICP_ROUTE || (proposal.route === MULTISIG_ROT_ROUTE && !hby.habs.has(groupPre))) {
|
|
369
|
+
const rmids = proposal.attrs.rmids;
|
|
370
|
+
const alias = options.groupAlias;
|
|
371
|
+
if (!alias)
|
|
372
|
+
throw new ValidationError(`A group alias is required to join ${groupPre}.`);
|
|
373
|
+
hby.joinGroupHab(groupPre, alias, member, smids, rmids.length > 0 ? rmids : smids);
|
|
374
|
+
}
|
|
375
|
+
const keys = groupEventKeys(hby, groupPre, serder);
|
|
376
|
+
const sigers = signLocalGroupEvent(hby, serder, smids, keys);
|
|
377
|
+
localMessage = messagize(serder, { sigers, pipelined: false, gvrsn: approval.genus });
|
|
439
378
|
}
|
|
440
|
-
const keys = groupEventKeys(hby, groupPre, serder);
|
|
441
|
-
const sigers = signLocalGroupEvent(hby, serder, smids, keys);
|
|
442
|
-
localMessage = messagize(serder, { sigers, pipelined: false, gvrsn: approval.genus });
|
|
443
379
|
}
|
|
444
380
|
// Each contribution may already own a counted attachment enclosure. Admit
|
|
445
381
|
// complete messages separately and let the ordinary KEL owner union proofs.
|
|
@@ -466,10 +402,10 @@ function* approveKelProposal(hby, runtime, exn, route, options) {
|
|
|
466
402
|
* @param runtime Borrowed composed runtime; caller owns its worker and store lifetime.
|
|
467
403
|
* @param exn Stored multisig exchange wrapper.
|
|
468
404
|
* @param route Selected multisig route identifying the proposal kind.
|
|
469
|
-
* @param options
|
|
405
|
+
* @param options Alias and name choices plus `resume`; a proposal that is not well formed is left inert.
|
|
470
406
|
* @returns Approval result with protocol acceptance and deliveries, or null for declined/incomplete input.
|
|
471
407
|
*/
|
|
472
|
-
function* approveVdrProposal(hby, rgy, runtime, exn, route, options) {
|
|
408
|
+
export function* approveVdrProposal(hby, rgy, runtime, exn, route, options) {
|
|
473
409
|
const wrapperSaid = exn.said;
|
|
474
410
|
if (!wrapperSaid) {
|
|
475
411
|
return null;
|
|
@@ -527,15 +463,14 @@ function* approveVdrProposal(hby, rgy, runtime, exn, route, options) {
|
|
|
527
463
|
const prior = storedGroupMessage(hby.db, member.pre, exn);
|
|
528
464
|
if (!prior && !groupCredentialAnchorReady(hby, groupPre, anchorSerder))
|
|
529
465
|
return null;
|
|
530
|
-
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
// Declined or malformed proposals remain inert; require VDR before signing or intake.
|
|
466
|
+
// Malformed proposals remain inert; require VDR before signing or intake.
|
|
534
467
|
if (!rgy)
|
|
535
468
|
throw new ValidationError("Legacy credential registry is unavailable in this runtime.");
|
|
536
469
|
let registryName;
|
|
537
470
|
if (proposal.route === MULTISIG_VCP_ROUTE && !prior) {
|
|
538
|
-
registryName = options.
|
|
471
|
+
registryName = options.registryName;
|
|
472
|
+
if (!registryName)
|
|
473
|
+
throw new ValidationError(`A registry name is required for ${embeddedSerder.pre}.`);
|
|
539
474
|
validateRegistryName(rgy.reger, registryName, embeddedSerder.pre);
|
|
540
475
|
}
|
|
541
476
|
const smids = groupSigningMembers(hby, groupPre);
|
|
@@ -545,7 +480,8 @@ function* approveVdrProposal(hby, rgy, runtime, exn, route, options) {
|
|
|
545
480
|
}
|
|
546
481
|
else {
|
|
547
482
|
const keys = groupEventKeys(hby, groupPre, anchorSerder);
|
|
548
|
-
const sigers =
|
|
483
|
+
const sigers = (options.resume ? recordedLocalSignatures(hby, anchorSerder, groupPre, smids) : null)
|
|
484
|
+
?? signLocalGroupEvent(hby, anchorSerder, smids, keys);
|
|
549
485
|
localAnchor = messagize(anchorSerder, { sigers, pipelined: false, gvrsn: genus });
|
|
550
486
|
}
|
|
551
487
|
yield* runtime.reactor.processBytes(concatBytes(Counter.makeGVC(genus), localAnchor), { local: true });
|
|
@@ -600,13 +536,32 @@ function* approveVdrProposal(hby, rgy, runtime, exn, route, options) {
|
|
|
600
536
|
tel: {
|
|
601
537
|
pre: embeddedSerder.pre,
|
|
602
538
|
sn: embeddedSerder.sn,
|
|
603
|
-
publicationComplete: rgy.publication
|
|
539
|
+
publicationComplete: telEventStatus({ publication: rgy.publication }, embeddedSerder.pre, embeddedSerder.sn).state === "complete",
|
|
604
540
|
},
|
|
605
541
|
...published,
|
|
606
542
|
};
|
|
607
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* This process's verified signatures for a group event, one per local signing
|
|
546
|
+
* member, read from `sigs.`; null unless every local member's key index has one.
|
|
547
|
+
* Remote members' signatures in the same row never count.
|
|
548
|
+
*/
|
|
549
|
+
function recordedLocalSignatures(hby, serder, groupPre, smids) {
|
|
550
|
+
if (!hby.habs.has(groupPre) || !serder.pre || !serder.said)
|
|
551
|
+
return null;
|
|
552
|
+
const local = smids.flatMap((mid, index) => hby.habs.has(mid) ? [index] : []);
|
|
553
|
+
const stored = hby.db.sigs.get(dgKey(serder.pre, serder.said));
|
|
554
|
+
if (local.length === 0 || stored.length === 0)
|
|
555
|
+
return null;
|
|
556
|
+
const verfers = groupEventKeys(hby, groupPre, serder).map(qb64 => new Verfer({ qb64 }));
|
|
557
|
+
const { sigers } = Kever.verifyIndexedSignatures(serder.raw, stored, verfers);
|
|
558
|
+
const own = sigers.filter(siger => local.includes(siger.index));
|
|
559
|
+
return local.every(index => own.some(siger => siger.index === index))
|
|
560
|
+
? own.sort((left, right) => left.index - right.index)
|
|
561
|
+
: null;
|
|
562
|
+
}
|
|
608
563
|
/** Preserve the recorded signing/rotation participant order during approval and resume. */
|
|
609
|
-
function kelApprovalMembers({ proposal }) {
|
|
564
|
+
export function kelApprovalMembers({ proposal }) {
|
|
610
565
|
if (proposal.route === MULTISIG_IXN_ROUTE)
|
|
611
566
|
return [...proposal.attrs.smids];
|
|
612
567
|
return uniqueMembers([...proposal.attrs.smids, ...proposal.attrs.rmids]);
|
|
@@ -662,7 +617,7 @@ function kelProposalLabel(route) {
|
|
|
662
617
|
}
|
|
663
618
|
}
|
|
664
619
|
/** Decode the fields used by approval, preserving existing missing-embed and member-list behavior. */
|
|
665
|
-
function kelApproval(exn, route, child) {
|
|
620
|
+
export function kelApproval(exn, route, child) {
|
|
666
621
|
if (!child)
|
|
667
622
|
return null;
|
|
668
623
|
const { event } = child;
|
|
@@ -791,7 +746,7 @@ function rpyAttrs(serder) {
|
|
|
791
746
|
}
|
|
792
747
|
return { cid: attrs.cid, role: attrs.role, eid: attrs.eid };
|
|
793
748
|
}
|
|
794
|
-
function payloadSection(ked) {
|
|
749
|
+
export function payloadSection(ked) {
|
|
795
750
|
return isRecord(ked.a) ? ked.a : {};
|
|
796
751
|
}
|
|
797
752
|
function embeddedSection(ked) {
|
|
@@ -812,10 +767,10 @@ function stringField(record, field) {
|
|
|
812
767
|
function isRecord(value) {
|
|
813
768
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
814
769
|
}
|
|
815
|
-
function isKelRoute(route) {
|
|
770
|
+
export function isKelRoute(route) {
|
|
816
771
|
return route === MULTISIG_ICP_ROUTE || route === MULTISIG_ROT_ROUTE || route === MULTISIG_IXN_ROUTE;
|
|
817
772
|
}
|
|
818
|
-
function isVdrRoute(route) {
|
|
773
|
+
export function isVdrRoute(route) {
|
|
819
774
|
return route === MULTISIG_VCP_ROUTE || route === MULTISIG_ISS_ROUTE || route === MULTISIG_REV_ROUTE;
|
|
820
775
|
}
|
|
821
776
|
function requireText(value, label) {
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group proposal reads, owned by the library so that a CLI, a browser wallet and
|
|
3
|
+
* a service all see and dismiss the same proposals. Operations follow the
|
|
4
|
+
* workflow contract (`app/runtime/workflow-contract.ts`):
|
|
5
|
+
* - `listGroupProposals` and `findGroupProposal` are pure reads of stored
|
|
6
|
+
* exchange messages, the multisig embed index (`meids.`) and the notifier.
|
|
7
|
+
* - `dismissGroupProposal` removes local notices only.
|
|
8
|
+
*
|
|
9
|
+
* A proposal is identified by the SAID of the outer exchange wrapper that
|
|
10
|
+
* announced it. Approval, which is the only step that signs, names that SAID
|
|
11
|
+
* explicitly; nothing here or in the notice store selects a proposal to approve.
|
|
12
|
+
*
|
|
13
|
+
* Boundaries:
|
|
14
|
+
* - Dependencies are declared collaborators, not an AgentRuntime (ADR-0013).
|
|
15
|
+
* - Notices discover candidates only. Their attributes never authorize a route
|
|
16
|
+
* or a group; every field below is read from the stored, indexed wrapper.
|
|
17
|
+
*/
|
|
18
|
+
import { Vrsn_2_0 } from "cesr-ts";
|
|
19
|
+
import { ValidationError } from "../../core/errors.js";
|
|
20
|
+
import { isV2RegistryRoute } from "../../core/exchange/protocol-exchanging.js";
|
|
21
|
+
import { loadNests } from "../../db/exchange/exchange-nests.js";
|
|
22
|
+
import { abortable } from "../runtime/abortable.js";
|
|
23
|
+
import { collectBytes } from "../transport/byte-source.js";
|
|
24
|
+
import { findLocalGroupMember, groupSigningMembers, uniqueMembers } from "./group-members.js";
|
|
25
|
+
import { acdcV2RegistryProposal, MULTISIG_RPY_ROUTE, multisigChild } from "./grouping.js";
|
|
26
|
+
import { multisigEmbeddedSection, multisigPayloadSaid, storedGroupMessage } from "./multisig-messaging.js";
|
|
27
|
+
import { approveKelProposal, approveRegistryV2Proposal, approveRpyProposal, approveVdrProposal, eventAccepted, isKelRoute, isVdrRoute, kelApproval, payloadSection, } from "./multisig-workflows.js";
|
|
28
|
+
/** Page size when reading the notice store; no proposal is hidden behind the first page. */
|
|
29
|
+
const NOTICE_PAGE = 100;
|
|
30
|
+
/** Wrapper route to proposal kind, or null for routes this API does not cover. */
|
|
31
|
+
function proposalKind(route) {
|
|
32
|
+
if (!isKelRoute(route) && !isVdrRoute(route) && route !== MULTISIG_RPY_ROUTE && !isV2RegistryRoute(route)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return route.slice("/multisig/".length);
|
|
36
|
+
}
|
|
37
|
+
/** Page through every notice; `fn` sees each in durable order. */
|
|
38
|
+
function eachNotice(notifier, fn) {
|
|
39
|
+
for (let start = 0;; start += NOTICE_PAGE) {
|
|
40
|
+
const page = notifier.list(start, NOTICE_PAGE);
|
|
41
|
+
for (const note of page)
|
|
42
|
+
fn({ rid: note.pad.i, attrs: note.pad.a });
|
|
43
|
+
if (page.length < NOTICE_PAGE)
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Wrapper SAIDs the notice store holds for multisig proposals, oldest first and without repeats. */
|
|
48
|
+
function noticedWrappers(notifier) {
|
|
49
|
+
const saids = new Set();
|
|
50
|
+
eachNotice(notifier, ({ attrs }) => {
|
|
51
|
+
if (typeof attrs.d === "string")
|
|
52
|
+
saids.add(attrs.d);
|
|
53
|
+
});
|
|
54
|
+
return [...saids];
|
|
55
|
+
}
|
|
56
|
+
/** The local member that would approve, without throwing for a group not yet joined. */
|
|
57
|
+
function approvingMember(hby, group, members) {
|
|
58
|
+
const mid = hby.db.getHab(group)?.mid;
|
|
59
|
+
return (mid ? hby.habs.get(mid) : null) ?? findLocalGroupMember(hby, members);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Event body of a wrapper's named child. v1 reads the pathed embed; v2 reads the
|
|
63
|
+
* first nest of a wrapper that authenticates through its own route. Proofs are
|
|
64
|
+
* not read: a listing never signs, so only the body is needed.
|
|
65
|
+
*/
|
|
66
|
+
function childBody(deps, wrapper, route, label) {
|
|
67
|
+
if (wrapper.pvrsn.major === 1)
|
|
68
|
+
return multisigChild(deps.hby, wrapper, label);
|
|
69
|
+
if (!wrapper.said || !deps.exc.verifyAccepted(wrapper.said, route))
|
|
70
|
+
return null;
|
|
71
|
+
const body = loadNests(deps.hby.db, wrapper.said)[0]?.body;
|
|
72
|
+
const event = body;
|
|
73
|
+
if (!event || typeof event.pre !== "string")
|
|
74
|
+
return null;
|
|
75
|
+
return { event, attachment: new Uint8Array(), genus: event.gvrsn ?? event.pvrsn ?? Vrsn_2_0 };
|
|
76
|
+
}
|
|
77
|
+
function eventRef(event, pre) {
|
|
78
|
+
return event.said && event.sn !== null ? { pre, sn: event.sn, said: event.said } : undefined;
|
|
79
|
+
}
|
|
80
|
+
function decode(deps, exn, route, kind) {
|
|
81
|
+
const { hby } = deps;
|
|
82
|
+
const payload = payloadSection(exn.ked ?? {});
|
|
83
|
+
if (isKelRoute(route)) {
|
|
84
|
+
const child = childBody(deps, exn, route, kind);
|
|
85
|
+
const approval = child && kelApproval(exn, route, child);
|
|
86
|
+
if (!approval)
|
|
87
|
+
return null;
|
|
88
|
+
const { smids, rmids } = approval.proposal.attrs;
|
|
89
|
+
return {
|
|
90
|
+
kind,
|
|
91
|
+
group: approval.group,
|
|
92
|
+
embedded: approval.event.said ?? "",
|
|
93
|
+
smids,
|
|
94
|
+
rmids: rmids ?? [],
|
|
95
|
+
event: eventRef(approval.event, approval.group),
|
|
96
|
+
inapplicable: eventAccepted(hby, approval.event) && !smids.some(mid => hby.habs.has(mid)),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (isV2RegistryRoute(route)) {
|
|
100
|
+
const nests = loadNests(hby.db, exn.said);
|
|
101
|
+
const registry = acdcV2RegistryProposal(exn, nests, deps.registryStore);
|
|
102
|
+
if (!registry?.anchor.pre)
|
|
103
|
+
return null;
|
|
104
|
+
const group = registry.anchor.pre;
|
|
105
|
+
return {
|
|
106
|
+
kind,
|
|
107
|
+
group,
|
|
108
|
+
embedded: registry.tel.said ?? "",
|
|
109
|
+
smids: groupSigningMembers(hby, group),
|
|
110
|
+
rmids: [],
|
|
111
|
+
event: eventRef(registry.anchor, group),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const group = typeof payload.gid === "string" ? payload.gid : "";
|
|
115
|
+
if (!group)
|
|
116
|
+
return null;
|
|
117
|
+
const smids = groupSigningMembers(hby, group);
|
|
118
|
+
if (route === MULTISIG_RPY_ROUTE) {
|
|
119
|
+
const child = childBody(deps, exn, route, "rpy");
|
|
120
|
+
return child?.event.said ? { kind, group, embedded: child.event.said, smids, rmids: [] } : null;
|
|
121
|
+
}
|
|
122
|
+
// vcp, iss and rev: the named TEL event, sealed by the group event in `anc`.
|
|
123
|
+
const anchor = multisigChild(hby, exn, "anc");
|
|
124
|
+
const tel = multisigEmbeddedSection(exn.ked ?? {})?.[kind];
|
|
125
|
+
const embedded = tel && typeof tel === "object" ? tel.d : undefined;
|
|
126
|
+
if (!anchor || anchor.event.pre !== group || typeof embedded !== "string")
|
|
127
|
+
return null;
|
|
128
|
+
return { kind, group, embedded, smids, rmids: [], event: eventRef(anchor.event, group) };
|
|
129
|
+
}
|
|
130
|
+
/** Read one stored wrapper; null when it is not a well-formed proposal of a supported kind. */
|
|
131
|
+
function readProposal(deps, said) {
|
|
132
|
+
const { hby } = deps;
|
|
133
|
+
const exn = hby.db.exns.get([said]);
|
|
134
|
+
if (!exn?.ked || !exn.said || !exn.pre)
|
|
135
|
+
return null;
|
|
136
|
+
const route = exn.route ?? "";
|
|
137
|
+
const kind = proposalKind(route);
|
|
138
|
+
if (!kind || !multisigPayloadSaid(exn))
|
|
139
|
+
return null;
|
|
140
|
+
let decoded;
|
|
141
|
+
try {
|
|
142
|
+
decoded = decode(deps, exn, route, kind);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (error instanceof ValidationError)
|
|
146
|
+
return null; // one malformed row never hides the others
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
if (!decoded)
|
|
150
|
+
return null;
|
|
151
|
+
const member = approvingMember(hby, decoded.group, uniqueMembers([...decoded.smids, ...decoded.rmids]));
|
|
152
|
+
const approved = !!member && storedGroupMessage(hby.db, member.pre, exn) !== null;
|
|
153
|
+
const inapplicable = !approved && decoded.inapplicable === true;
|
|
154
|
+
return {
|
|
155
|
+
said: exn.said,
|
|
156
|
+
kind,
|
|
157
|
+
group: decoded.group,
|
|
158
|
+
embedded: decoded.embedded,
|
|
159
|
+
proposer: exn.pre,
|
|
160
|
+
smids: decoded.smids,
|
|
161
|
+
rmids: decoded.rmids,
|
|
162
|
+
local: approved ? "approved" : inapplicable ? "inapplicable" : "unapproved",
|
|
163
|
+
needs: {
|
|
164
|
+
groupAlias: !approved && !inapplicable && (kind === "icp" || (kind === "rot" && !hby.habs.has(decoded.group))),
|
|
165
|
+
registryName: !approved && !inapplicable && kind === "vcp",
|
|
166
|
+
},
|
|
167
|
+
...(decoded.event ? { event: decoded.event } : {}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** Group prefix for a prefix or local alias. */
|
|
171
|
+
function resolveGroup(hby, group) {
|
|
172
|
+
return hby.habByName(group)?.pre ?? group;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* List the proposals the notice store announces, oldest notice first. Pure. Only
|
|
176
|
+
* wrappers that are stored, indexed in `meids.` and well formed are listed, one
|
|
177
|
+
* entry per wrapper; every notice page is read, so nothing hides behind the
|
|
178
|
+
* first 100. A dismissed proposal has no notice and is not listed; it remains
|
|
179
|
+
* available through {@link findGroupProposal}.
|
|
180
|
+
*/
|
|
181
|
+
export function listGroupProposals(deps, { group } = {}) {
|
|
182
|
+
if (!deps.notifier)
|
|
183
|
+
return [];
|
|
184
|
+
const wanted = group === undefined ? undefined : resolveGroup(deps.hby, group);
|
|
185
|
+
const proposals = [];
|
|
186
|
+
const seen = new Set();
|
|
187
|
+
for (const said of noticedWrappers(deps.notifier)) {
|
|
188
|
+
const exn = deps.hby.db.exns.get([said]);
|
|
189
|
+
const payloadSaid = exn && multisigPayloadSaid(exn);
|
|
190
|
+
if (!payloadSaid || !deps.hby.db.meids.get([payloadSaid]).some(index => index.qb64 === said))
|
|
191
|
+
continue;
|
|
192
|
+
const proposal = readProposal(deps, said);
|
|
193
|
+
if (!proposal || (wanted !== undefined && proposal.group !== wanted) || seen.has(said))
|
|
194
|
+
continue;
|
|
195
|
+
seen.add(said);
|
|
196
|
+
proposals.push(proposal);
|
|
197
|
+
}
|
|
198
|
+
return proposals;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Read one stored proposal by its wrapper SAID, whether or not a notice remains.
|
|
202
|
+
* Pure. Null when no such well-formed proposal is stored.
|
|
203
|
+
*/
|
|
204
|
+
export function findGroupProposal(deps, said) {
|
|
205
|
+
return readProposal(deps, said);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Dismiss a proposal locally: remove the notices that announce it, including
|
|
209
|
+
* those other members' wrappers of the same payload raised. The stored wrapper
|
|
210
|
+
* and the multisig index stay, so the proposal can still be found and approved
|
|
211
|
+
* by SAID. KERI has no reject message: nothing is sent, and the proposer and the
|
|
212
|
+
* other members learn nothing from a dismissal.
|
|
213
|
+
*
|
|
214
|
+
* @returns Number of notices removed.
|
|
215
|
+
*/
|
|
216
|
+
export function dismissGroupProposal(deps, said) {
|
|
217
|
+
if (!deps.notifier)
|
|
218
|
+
return 0;
|
|
219
|
+
const exn = deps.hby.db.exns.get([said]);
|
|
220
|
+
const payloadSaid = exn && multisigPayloadSaid(exn);
|
|
221
|
+
const wrappers = new Set([said]);
|
|
222
|
+
if (payloadSaid)
|
|
223
|
+
for (const index of deps.hby.db.meids.get([payloadSaid]))
|
|
224
|
+
wrappers.add(index.qb64);
|
|
225
|
+
const stale = [];
|
|
226
|
+
eachNotice(deps.notifier, ({ rid, attrs }) => {
|
|
227
|
+
if (typeof attrs.d === "string" && wrappers.has(attrs.d))
|
|
228
|
+
stale.push(rid);
|
|
229
|
+
});
|
|
230
|
+
return stale.filter(rid => deps.notifier.remove(rid)).length;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Approve one proposal, named by its wrapper SAID: sign this member's
|
|
234
|
+
* contribution to the embedded event, send it to the other members and hand the
|
|
235
|
+
* group event to the Counselor. Returns without waiting for other members; read
|
|
236
|
+
* progress with `counselor.status(result.event)`. This is the only call that
|
|
237
|
+
* signs, and it acts on exactly the SAID given: nothing is selected from the
|
|
238
|
+
* notice store, so a proposal the caller did not name cannot be approved.
|
|
239
|
+
*
|
|
240
|
+
* Calling it is the user's consent, so it never prompts. Repeating it after a
|
|
241
|
+
* crash resumes: this member's recorded signatures and stored wrapper are
|
|
242
|
+
* replayed, never replaced, and the group is not joined twice.
|
|
243
|
+
*
|
|
244
|
+
* Supports every proposal kind: KEL (`icp`, `rot`, `ixn`), reply (`rpy`), legacy registry and credential (`vcp`, `iss`, `rev`) and
|
|
245
|
+
* ACDC v2 registry (`rip`, `bup`). For the registry kinds `event` is the group anchor that seals the TEL event.
|
|
246
|
+
*
|
|
247
|
+
* @throws ValidationError for a SAID that is not a stored supported proposal, a missing `groupAlias` when the proposal {@link GroupProposal.needs} one, or a proposal that cannot be approved.
|
|
248
|
+
*/
|
|
249
|
+
export function* approveGroupProposal(deps, said, choices = {}) {
|
|
250
|
+
const { hby } = deps;
|
|
251
|
+
const proposal = findGroupProposal(deps, said);
|
|
252
|
+
const exn = hby.db.exns.get([said]);
|
|
253
|
+
if (!proposal || !exn)
|
|
254
|
+
throw new ValidationError(`No group proposal ${said} is stored.`);
|
|
255
|
+
const route = exn.route ?? "";
|
|
256
|
+
const payloadSaid = multisigPayloadSaid(exn);
|
|
257
|
+
if (!hby.db.meids.get([payloadSaid]).some(index => index.qb64 === said)) {
|
|
258
|
+
// Acceptance commits before the asynchronous handler; resume that ordinary intake.
|
|
259
|
+
const message = yield* abortable(signal => collectBytes(deps.exc.replay(said, signal)));
|
|
260
|
+
yield* deps.reactor.processBytes(message, { local: true });
|
|
261
|
+
if (!hby.db.meids.get([payloadSaid]).some(index => index.qb64 === said)) {
|
|
262
|
+
throw new ValidationError(`Group proposal ${said} is not indexed after replay.`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const options = { resume: true, groupAlias: choices.groupAlias, registryName: choices.registryName };
|
|
266
|
+
const result = isKelRoute(route)
|
|
267
|
+
? yield* approveKelProposal(hby, deps, exn, route, options)
|
|
268
|
+
: isVdrRoute(route)
|
|
269
|
+
? yield* approveVdrProposal(hby, deps.vdr, deps, exn, route, options)
|
|
270
|
+
: isV2RegistryRoute(route)
|
|
271
|
+
? yield* approveRegistryV2Proposal(hby, deps, exn, route, options)
|
|
272
|
+
: yield* approveRpyProposal(hby, deps, exn, route, options);
|
|
273
|
+
if (!result)
|
|
274
|
+
throw new ValidationError(`Group proposal ${said} cannot be approved.`);
|
|
275
|
+
const { event } = proposal;
|
|
276
|
+
if (event) {
|
|
277
|
+
const { auths, fallbackAfterMs, proxy } = choices;
|
|
278
|
+
deps.counselor.start(event, { auths, fallbackAfterMs, proxy });
|
|
279
|
+
}
|
|
280
|
+
return { ...result, ...(event ? { event } : {}) };
|
|
281
|
+
}
|