keri-ts 0.8.0 → 0.9.1

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.
Files changed (99) hide show
  1. package/README.md +10 -0
  2. package/esm/cesr/src/primitives/counter.js +64 -9
  3. package/esm/cesr/src/version.js +2 -2
  4. package/esm/keri/src/app/cesr-http.js +4 -3
  5. package/esm/keri/src/app/cli/delegate.js +106 -10
  6. package/esm/keri/src/app/cli/did.js +182 -0
  7. package/esm/keri/src/app/cli/ends.js +40 -0
  8. package/esm/keri/src/app/cli/index.js +2 -0
  9. package/esm/keri/src/app/cli/ipex.js +365 -22
  10. package/esm/keri/src/app/cli/multisig.js +1089 -0
  11. package/esm/keri/src/app/cli/vc.js +259 -2
  12. package/esm/keri/src/app/delegating.js +26 -11
  13. package/esm/keri/src/app/endpoint-roleing.js +140 -0
  14. package/esm/keri/src/app/exchanging.js +14 -15
  15. package/esm/keri/src/app/forwarding.js +90 -36
  16. package/esm/keri/src/app/habbing.js +189 -36
  17. package/esm/keri/src/app/ipex-credentialing.js +41 -23
  18. package/esm/keri/src/app/oobiery.js +13 -2
  19. package/esm/keri/src/app/version.js +2 -2
  20. package/esm/keri/src/app/witnessing.js +82 -49
  21. package/esm/keri/src/core/attachment-countering.js +105 -0
  22. package/esm/keri/src/core/protocol-exchanging.js +13 -11
  23. package/esm/keri/src/core/protocol-serialization.js +102 -76
  24. package/esm/keri/src/db/basing.js +32 -43
  25. package/esm/keri/src/db/reger.js +18 -24
  26. package/esm/keri/src/did/index.js +7 -0
  27. package/esm/keri/src/did/keri/resolving.js +26 -0
  28. package/esm/keri/src/did/webs/artifacts.js +104 -0
  29. package/esm/keri/src/did/webs/designated-aliases-public-schema.js +156 -0
  30. package/esm/keri/src/did/webs/designated-aliases.js +235 -0
  31. package/esm/keri/src/did/webs/dids.js +186 -0
  32. package/esm/keri/src/did/webs/documenting.js +190 -0
  33. package/esm/keri/src/did/webs/resolving.js +115 -0
  34. package/esm/keri/src/runtime/index.js +2 -0
  35. package/esm/keri/src/vdr/credentialing.js +9 -8
  36. package/package.json +2 -2
  37. package/types/cesr/src/primitives/counter.d.ts +8 -0
  38. package/types/cesr/src/primitives/counter.d.ts.map +1 -1
  39. package/types/cesr/src/version.d.ts +2 -2
  40. package/types/keri/src/app/cesr-http.d.ts +2 -1
  41. package/types/keri/src/app/cesr-http.d.ts.map +1 -1
  42. package/types/keri/src/app/cli/delegate.d.ts.map +1 -1
  43. package/types/keri/src/app/cli/did.d.ts +10 -0
  44. package/types/keri/src/app/cli/did.d.ts.map +1 -0
  45. package/types/keri/src/app/cli/ends.d.ts.map +1 -1
  46. package/types/keri/src/app/cli/index.d.ts +2 -0
  47. package/types/keri/src/app/cli/index.d.ts.map +1 -1
  48. package/types/keri/src/app/cli/ipex.d.ts +9 -0
  49. package/types/keri/src/app/cli/ipex.d.ts.map +1 -1
  50. package/types/keri/src/app/cli/multisig.d.ts +12 -0
  51. package/types/keri/src/app/cli/multisig.d.ts.map +1 -0
  52. package/types/keri/src/app/cli/vc.d.ts +9 -0
  53. package/types/keri/src/app/cli/vc.d.ts.map +1 -1
  54. package/types/keri/src/app/delegating.d.ts +1 -0
  55. package/types/keri/src/app/delegating.d.ts.map +1 -1
  56. package/types/keri/src/app/endpoint-roleing.d.ts +46 -0
  57. package/types/keri/src/app/endpoint-roleing.d.ts.map +1 -0
  58. package/types/keri/src/app/exchanging.d.ts.map +1 -1
  59. package/types/keri/src/app/forwarding.d.ts +7 -1
  60. package/types/keri/src/app/forwarding.d.ts.map +1 -1
  61. package/types/keri/src/app/habbing.d.ts +51 -4
  62. package/types/keri/src/app/habbing.d.ts.map +1 -1
  63. package/types/keri/src/app/ipex-credentialing.d.ts +11 -4
  64. package/types/keri/src/app/ipex-credentialing.d.ts.map +1 -1
  65. package/types/keri/src/app/oobiery.d.ts +1 -0
  66. package/types/keri/src/app/oobiery.d.ts.map +1 -1
  67. package/types/keri/src/app/protocol-host-policy.d.ts +8 -0
  68. package/types/keri/src/app/protocol-host-policy.d.ts.map +1 -1
  69. package/types/keri/src/app/version.d.ts +2 -2
  70. package/types/keri/src/app/witnessing.d.ts.map +1 -1
  71. package/types/keri/src/core/attachment-countering.d.ts +39 -0
  72. package/types/keri/src/core/attachment-countering.d.ts.map +1 -0
  73. package/types/keri/src/core/protocol-exchanging.d.ts +2 -1
  74. package/types/keri/src/core/protocol-exchanging.d.ts.map +1 -1
  75. package/types/keri/src/core/protocol-serialization.d.ts +38 -4
  76. package/types/keri/src/core/protocol-serialization.d.ts.map +1 -1
  77. package/types/keri/src/db/basing.d.ts.map +1 -1
  78. package/types/keri/src/db/reger.d.ts +2 -2
  79. package/types/keri/src/db/reger.d.ts.map +1 -1
  80. package/types/keri/src/did/index.d.ts +8 -0
  81. package/types/keri/src/did/index.d.ts.map +1 -0
  82. package/types/keri/src/did/keri/resolving.d.ts +19 -0
  83. package/types/keri/src/did/keri/resolving.d.ts.map +1 -0
  84. package/types/keri/src/did/webs/artifacts.d.ts +19 -0
  85. package/types/keri/src/did/webs/artifacts.d.ts.map +1 -0
  86. package/types/keri/src/did/webs/designated-aliases-public-schema.d.ts +147 -0
  87. package/types/keri/src/did/webs/designated-aliases-public-schema.d.ts.map +1 -0
  88. package/types/keri/src/did/webs/designated-aliases.d.ts +46 -0
  89. package/types/keri/src/did/webs/designated-aliases.d.ts.map +1 -0
  90. package/types/keri/src/did/webs/dids.d.ts +65 -0
  91. package/types/keri/src/did/webs/dids.d.ts.map +1 -0
  92. package/types/keri/src/did/webs/documenting.d.ts +29 -0
  93. package/types/keri/src/did/webs/documenting.d.ts.map +1 -0
  94. package/types/keri/src/did/webs/resolving.d.ts +22 -0
  95. package/types/keri/src/did/webs/resolving.d.ts.map +1 -0
  96. package/types/keri/src/runtime/index.d.ts +2 -0
  97. package/types/keri/src/runtime/index.d.ts.map +1 -1
  98. package/types/keri/src/vdr/credentialing.d.ts +1 -1
  99. package/types/keri/src/vdr/credentialing.d.ts.map +1 -1
package/README.md CHANGED
@@ -18,6 +18,16 @@ npm install keri-ts
18
18
  Only those three entrypoints are supported public surfaces. Runnable CLI/server
19
19
  ownership lives in the separate `tufa` package.
20
20
 
21
+ ## Maintainer Docs
22
+
23
+ - `docs/ARCHITECTURE_MAP.md`: package and module ownership map
24
+ - `docs/design-docs/keri/ACDC_TEL_IPEX_VERIFIER_MAINTAINER_GUIDE.md`:
25
+ credential stack ownership and failure modes
26
+ - `docs/design-docs/keri/DELEGATION_MULTISIG_ENDPOINT_ROLES_MAINTAINER_GUIDE.md`:
27
+ delegation, group coordination, and endpoint-role mental model
28
+ - `docs/design-docs/keri/ATTACHMENT_COUNTER_GVRSN_MAINTAINER_GUIDE.md`:
29
+ attachment counter versioning and replay boundaries
30
+
21
31
  ## License
22
32
 
23
33
  Licensed under the Apache License, Version 2.0 (`Apache-2.0`). See the top-level
@@ -1,5 +1,6 @@
1
- import { b, b64ToInt, codeB2ToB64, codeB64ToB2, intToB64, nabSextets, sceil } from "../core/bytes.js";
1
+ import { b, b64ToInt, codeB2ToB64, codeB64ToB2, concatBytes, intToB64, nabSextets, sceil } from "../core/bytes.js";
2
2
  import { DeserializeError, ShortageError, UnknownCodeError } from "../core/errors.js";
3
+ import { CtrDexV2 } from "../tables/counter-codex.js";
3
4
  import { resolveCounterCodeNameTable, resolveCounterSizeTable } from "../tables/counter-version-registry.js";
4
5
  import { COUNTER_HARDS } from "../tables/counter.tables.generated.js";
5
6
  const COUNTER_BARDS = new Map([...COUNTER_HARDS.entries()].map(([code, hs]) => [
@@ -126,6 +127,38 @@ function encodeCounterFromFields(code, count, version) {
126
127
  version,
127
128
  };
128
129
  }
130
+ function semanticNameForCode(code, version) {
131
+ const nameTable = resolveCounterCodeNameTable(version);
132
+ return nameTable[code] ?? null;
133
+ }
134
+ function codeForSemanticName(name, version) {
135
+ const nameTable = resolveCounterCodeNameTable(version);
136
+ for (const [code, candidate] of Object.entries(nameTable)) {
137
+ if (candidate === name) {
138
+ return code;
139
+ }
140
+ }
141
+ return null;
142
+ }
143
+ function promoteCounterCodeForCount(code, count, version) {
144
+ const sizeTable = resolveCounterSizeTable(version);
145
+ const sizage = sizeTable.get(code);
146
+ if (!sizage) {
147
+ throw new UnknownCodeError(`Unsupported counter code for version: ${code}`);
148
+ }
149
+ if (count <= (64 ** sizage.ss) - 1) {
150
+ return code;
151
+ }
152
+ const name = semanticNameForCode(code, version);
153
+ if (!name || name.startsWith("Big")) {
154
+ throw new DeserializeError(`Invalid count=${count} for code=${code}`);
155
+ }
156
+ const bigCode = codeForSemanticName(`Big${name}`, version);
157
+ if (!bigCode) {
158
+ throw new DeserializeError(`Counter code=${code} has no Big${name} form.`);
159
+ }
160
+ return bigCode;
161
+ }
129
162
  /** Normalize the supported constructor variants into one shared counter payload. */
130
163
  function parseCounterInit(init) {
131
164
  const version = init.version ?? { major: 2, minor: 0 };
@@ -152,6 +185,34 @@ function parseCounterInit(init) {
152
185
  * payload groups and carry versioned code-name semantics.
153
186
  */
154
187
  export class Counter {
188
+ static makeGVC(version) {
189
+ return new Counter({
190
+ code: CtrDexV2.KERIACDCGenusVersion,
191
+ countB64: `${intToB64(version.major, 1)}${intToB64(version.minor, 2)}`,
192
+ version,
193
+ }).qb64b;
194
+ }
195
+ static enclose({ qb64 = undefined, qb2 = undefined, code = "AttachmentGroup", version = { major: 2, minor: 0 }, } = {}) {
196
+ const actualQb64 = typeof qb64 === "string" ? b(qb64) : qb64;
197
+ if (actualQb64 !== undefined && actualQb64 !== null) {
198
+ if (actualQb64.length % 4 !== 0) {
199
+ throw new DeserializeError(`Bad enclosed qb64 length=${actualQb64.length}`);
200
+ }
201
+ const count = actualQb64.length / 4;
202
+ const counterCode = promoteCounterCodeForCount(codeForSemanticName(code, version) ?? code, count, version);
203
+ return concatBytes(new Counter({ code: counterCode, count, version }).qb64b, actualQb64);
204
+ }
205
+ if (qb2 !== undefined && qb2 !== null) {
206
+ if (qb2.length % 3 !== 0) {
207
+ throw new DeserializeError(`Bad enclosed qb2 length=${qb2.length}`);
208
+ }
209
+ const count = qb2.length / 3;
210
+ const counterCode = promoteCounterCodeForCount(codeForSemanticName(code, version) ?? code, count, version);
211
+ return concatBytes(new Counter({ code: counterCode, count, version }).qb2, qb2);
212
+ }
213
+ const counterCode = promoteCounterCodeForCount(codeForSemanticName(code, version) ?? code, 0, version);
214
+ return new Counter({ code: counterCode, count: 0, version }).qb64b;
215
+ }
155
216
  constructor(init) {
156
217
  Object.defineProperty(this, "_code", {
157
218
  enumerable: true,
@@ -195,11 +256,7 @@ export class Counter {
195
256
  writable: true,
196
257
  value: void 0
197
258
  });
198
- const data = init instanceof Counter
199
- ? init.toCounterData()
200
- : isCounterData(init)
201
- ? init
202
- : parseCounterInit(init);
259
+ const data = init instanceof Counter ? init.toCounterData() : isCounterData(init) ? init : parseCounterInit(init);
203
260
  this._code = data.code;
204
261
  this._count = data.count;
205
262
  this._fullSize = data.fullSize;
@@ -287,7 +344,5 @@ export function parseCounterFromBinary(input, version) {
287
344
  }
288
345
  /** Parse counter using domain hint (`txt` or `bny`) and versioned codex tables. */
289
346
  export function parseCounter(input, version, cold) {
290
- return cold === "bny"
291
- ? parseCounterFromBinary(input, version)
292
- : parseCounterFromText(input, version);
347
+ return cold === "bny" ? parseCounterFromBinary(input, version) : parseCounterFromText(input, version);
293
348
  }
@@ -6,9 +6,9 @@
6
6
  * `src/version.ts` files by hand; edit this template instead.
7
7
  */
8
8
  /** Package semantic version copied from the owning package manifest. */
9
- export const PACKAGE_VERSION = "0.8.0";
9
+ export const PACKAGE_VERSION = "0.9.0";
10
10
  /** Optional build metadata stamp injected by release/CI workflows. */
11
- export const BUILD_METADATA = "build.13.5884299463b061ec0fe2847a35a28b4189dee3f8";
11
+ export const BUILD_METADATA = "build.15.488d4554e9b5462734e921e29385b9655b3c1e44";
12
12
  /** User-facing version string with build metadata appended when present. */
13
13
  export const DISPLAY_VERSION = BUILD_METADATA
14
14
  ? `${PACKAGE_VERSION}+${BUILD_METADATA}`
@@ -62,10 +62,10 @@ export function cesrBodyModeFromGlobal(value) {
62
62
  * Body mode:
63
63
  * - the full payload is sent in the HTTP body
64
64
  */
65
- export function buildCesrRequest(message, { bodyMode = DEFAULT_CESR_BODY_MODE, destination, } = {}) {
65
+ export function buildCesrRequest(message, { bodyMode = DEFAULT_CESR_BODY_MODE, contentType = CESR_CONTENT_TYPE, destination, } = {}) {
66
66
  const mode = normalizeCesrBodyMode(bodyMode);
67
67
  const headers = {
68
- "Content-Type": CESR_CONTENT_TYPE,
68
+ "Content-Type": contentType,
69
69
  };
70
70
  if (destination) {
71
71
  headers[CESR_DESTINATION_HEADER] = destination;
@@ -76,7 +76,8 @@ export function buildCesrRequest(message, { bodyMode = DEFAULT_CESR_BODY_MODE, d
76
76
  body: arrayBufferFromBytes(message),
77
77
  };
78
78
  }
79
- const serder = new SerderKERI({ raw: message });
79
+ const { smellage } = smell(message);
80
+ const serder = parseSerder(message.slice(0, smellage.size), smellage);
80
81
  headers[CESR_ATTACHMENT_HEADER] = textDecoder.decode(message.slice(serder.size));
81
82
  return {
82
83
  headers,
@@ -17,6 +17,7 @@ import { ValidationError } from "../../core/errors.js";
17
17
  import { dgKey } from "../../db/core/keys.js";
18
18
  import { makeNowIso8601 } from "../../time/mod.js";
19
19
  import { createAgentRuntime, processRuntimeTurn, processRuntimeUntil, } from "../agent-runtime.js";
20
+ import { MULTISIG_IXN_ROUTE } from "../grouping.js";
20
21
  import { queryTransportSink } from "../query-transport.js";
21
22
  import { WitnessReceiptor } from "../witnessing.js";
22
23
  import { setupHby } from "./common/existing.js";
@@ -86,6 +87,9 @@ function anchorData(serder) {
86
87
  }
87
88
  return { i: serder.pre, s: serder.snh, d: serder.said };
88
89
  }
90
+ function approvingEvent(hby, serder, delegator) {
91
+ return hby.db.fetchLastSealingEventByEventSeal(delegator.pre, anchorData(serder));
92
+ }
89
93
  /** Resolve delegate witnesses for either delegated inception or rotation. */
90
94
  function delegateWitnesses(hby, serder) {
91
95
  if ((serder.sn ?? 0) === 0) {
@@ -123,6 +127,57 @@ function delegateWitnessLogsQuery(hab, serder, witness) {
123
127
  msgs: [hab.query(serder.pre, witness, query, "logs")],
124
128
  };
125
129
  }
130
+ function isGroupHab(hby, hab) {
131
+ return !!hab.pre && !!hby.db.getHab(hab.pre)?.mid;
132
+ }
133
+ function groupSigningMembers(hby, groupPre) {
134
+ const stored = hby.ks.getSmids(groupPre).map((tuple) => tuple[0].qb64);
135
+ if (stored.length > 0) {
136
+ return stored;
137
+ }
138
+ return hby.db.getHab(groupPre)?.smids ?? [];
139
+ }
140
+ function localGroupMember(hby, groupPre) {
141
+ const record = hby.db.getHab(groupPre);
142
+ const member = record?.mid ? hby.habs.get(record.mid) : null;
143
+ if (!member) {
144
+ throw new ValidationError(`Group ${groupPre} is missing local member metadata.`);
145
+ }
146
+ return member;
147
+ }
148
+ function* publishGroupDelegationApproval(runtime, hby, groupHab, message) {
149
+ const member = localGroupMember(hby, groupHab.pre);
150
+ const smids = groupSigningMembers(hby, groupHab.pre);
151
+ const deliveries = [];
152
+ for (const recipient of smids) {
153
+ if (recipient === member.pre || hby.habs.has(recipient)) {
154
+ continue;
155
+ }
156
+ const result = yield* runtime.poster.sendExchange(member, {
157
+ recipient,
158
+ route: MULTISIG_IXN_ROUTE,
159
+ payload: { gid: groupHab.pre, smids },
160
+ embeds: { ixn: message },
161
+ topic: "multisig",
162
+ });
163
+ deliveries.push(...result.deliveries, ...result.queued);
164
+ }
165
+ return deliveries;
166
+ }
167
+ function eventAccepted(hby, serder) {
168
+ return !!serder.pre && serder.said !== undefined
169
+ && hby.db.kels.getLast(serder.pre, serder.sn ?? 0) === serder.said;
170
+ }
171
+ function pinApprovalSeal(hby, hab, delegated, approving) {
172
+ if (!approving.sner || !approving.said || !delegated.pre || !delegated.said) {
173
+ throw new ValidationError("Approving event material is incomplete.");
174
+ }
175
+ hby.db.aess.pin(dgKey(delegated.pre, delegated.said), [
176
+ approving.sner,
177
+ new Diger({ qb64: approving.said }),
178
+ ]);
179
+ hab.kvy.processEscrowDelegables();
180
+ }
126
181
  /** Route query cues through query transport and other cues through Respondant. */
127
182
  function delegateConfirmSink(runtime, hby, hab) {
128
183
  const querySink = queryTransportSink(runtime, hby, hab);
@@ -188,6 +243,54 @@ export function* delegateConfirmCommand(args) {
188
243
  const selected = confirmArgs.auto ? pending : [pending[0]];
189
244
  for (const serder of selected) {
190
245
  const anchor = anchorData(serder);
246
+ if (isGroupHab(hby, hab)) {
247
+ if (!interactionApproval) {
248
+ throw new ValidationError("Multisig delegated approval currently requires --interact.");
249
+ }
250
+ const approving = approvingEvent(hby, serder, hab);
251
+ if (!approving) {
252
+ const created = hby.interactGroupHab(confirmArgs.alias, undefined, { data: [anchor] });
253
+ const deliveries = yield* publishGroupDelegationApproval(runtime, hby, created.hab, created.message);
254
+ console.log(JSON.stringify({
255
+ status: eventAccepted(hby, created.serder) ? "accepted" : "multisig-pending",
256
+ route: MULTISIG_IXN_ROUTE,
257
+ group: created.hab.pre,
258
+ delegated: serder.pre,
259
+ said: serder.said,
260
+ anchor: created.serder.said,
261
+ deliveries,
262
+ }));
263
+ continue;
264
+ }
265
+ const witnesses = delegateWitnesses(hby, serder).sort();
266
+ const selectedWitness = witnesses[0];
267
+ const member = localGroupMember(hby, hab.pre);
268
+ const memberSink = delegateConfirmSink(runtime, hby, member);
269
+ pinApprovalSeal(hby, hab, serder, approving);
270
+ yield* processRuntimeTurn(runtime, {
271
+ hab: member,
272
+ pollMailbox: true,
273
+ sink: memberSink,
274
+ });
275
+ if (!delegateCommitted(hby, serder) && selectedWitness) {
276
+ yield* memberSink.send(delegateWitnessLogsQuery(member, serder, selectedWitness));
277
+ yield* processRuntimeUntil(runtime, () => delegateCommitted(hby, serder), { hab: member, maxTurns: 128, pollMailbox: true, sink: memberSink });
278
+ }
279
+ pinApprovalSeal(hby, hab, serder, approving);
280
+ yield* processRuntimeTurn(runtime, {
281
+ hab: member,
282
+ pollMailbox: true,
283
+ sink: memberSink,
284
+ });
285
+ if (serder.pre && serder.said) {
286
+ const stillPending = hby.db.delegables.get([serder.pre]).includes(serder.said);
287
+ if (stillPending) {
288
+ throw new ValidationError(`Delegated event ${serder.said} remained in delegables escrow after approval.`);
289
+ }
290
+ }
291
+ console.log(`Approved delegated ${serder.ilk} ${serder.said ?? ""} for ${serder.pre ?? ""} using multisig ixn.`);
292
+ continue;
293
+ }
191
294
  // KERIpy permits either interaction or rotation approval. Keep this
192
295
  // explicit because the chosen approving event type affects later
193
296
  // replay, but the embedded anchor seal is the same.
@@ -205,16 +308,9 @@ export function* delegateConfirmCommand(args) {
205
308
  });
206
309
  }
207
310
  const approving = hby.db.getEvtSerder(hab.pre, hab.kever.said);
208
- if (!approving?.sner || !approving.said || !serder.pre || !serder.said) {
311
+ if (!approving) {
209
312
  throw new ValidationError("Approving event material is incomplete.");
210
313
  }
211
- const pinApprovalSeal = () => {
212
- hby.db.aess.pin(dgKey(serder.pre, serder.said), [
213
- approving.sner,
214
- new Diger({ qb64: approving.said }),
215
- ]);
216
- hab.kvy.processEscrowDelegables();
217
- };
218
314
  const witnesses = delegateWitnesses(hby, serder).sort();
219
315
  const selectedWitness = witnesses[0];
220
316
  if (selectedWitness) {
@@ -229,7 +325,7 @@ export function* delegateConfirmCommand(args) {
229
325
  // No delegate witness exists to query. Process the local delegable
230
326
  // after the delegator has anchored approval; the delegate still
231
327
  // discovers approval through its own delegator-KEL query path.
232
- pinApprovalSeal();
328
+ pinApprovalSeal(hby, hab, serder, approving);
233
329
  yield* processRuntimeUntil(runtime, () => querier.done, { hab, maxTurns: 128, pollMailbox: true, sink });
234
330
  }
235
331
  yield* processRuntimeTurn(runtime, { hab, pollMailbox: true, sink });
@@ -237,7 +333,7 @@ export function* delegateConfirmCommand(args) {
237
333
  // delegated unescrow after the delegate event has been observed as
238
334
  // locally committed through the witness-backed query/replay path.
239
335
  if (selectedWitness) {
240
- pinApprovalSeal();
336
+ pinApprovalSeal(hby, hab, serder, approving);
241
337
  }
242
338
  if (serder.pre && serder.said) {
243
339
  const stillPending = hby.db.delegables.get([serder.pre]).includes(serder.said);
@@ -0,0 +1,182 @@
1
+ /**
2
+ * DID-related CLI operations shared by the Tufa command dispatcher.
3
+ */
4
+ import * as dntShim from "../../../../_dnt.shims.js";
5
+ import { ValidationError } from "../../core/errors.js";
6
+ import { Reger } from "../../db/reger.js";
7
+ import { bindDesignatedAliases, DEFAULT_DESIGNATED_ALIASES_REGISTRY_NAME, generateDidWebsArtifacts, parseDidWebs, resolveDidKeri, resolveDidWebs, } from "../../did/index.js";
8
+ import { createAgentRuntime } from "../agent-runtime.js";
9
+ import { WitnessReceiptor } from "../witnessing.js";
10
+ import { setupHby } from "./common/existing.js";
11
+ /** Implement `tufa dws bind`. */
12
+ export function* dwsBindCommand(args) {
13
+ const commandArgs = {
14
+ ...baseArgs(args),
15
+ alias: args.alias,
16
+ dids: asStringList(args.did),
17
+ registryName: args.registryName,
18
+ createRegistry: args.createRegistry,
19
+ allowExternalDid: args.allowExternalDid,
20
+ };
21
+ requireNonEmpty(commandArgs.alias, "Alias");
22
+ const { hby, runtime } = yield* openRuntime(commandArgs);
23
+ try {
24
+ const hab = hby.habByName(commandArgs.alias);
25
+ const priorSn = hab?.kever?.sn ?? 0;
26
+ const result = bindDesignatedAliases(runtime, {
27
+ alias: commandArgs.alias,
28
+ dids: commandArgs.dids,
29
+ registryName: commandArgs.registryName ?? DEFAULT_DESIGNATED_ALIASES_REGISTRY_NAME,
30
+ createRegistry: commandArgs.createRegistry ?? true,
31
+ allowExternalDid: commandArgs.allowExternalDid ?? false,
32
+ });
33
+ if (hab?.pre) {
34
+ yield* receiptNewWitnessEvents(hby, hab.pre, priorSn);
35
+ }
36
+ console.log(JSON.stringify(result));
37
+ }
38
+ finally {
39
+ yield* closeRuntime(hby, runtime);
40
+ }
41
+ }
42
+ /** Implement `tufa dws generate`. */
43
+ export function* dwsGenerateCommand(args) {
44
+ const commandArgs = {
45
+ ...baseArgs(args),
46
+ alias: args.alias,
47
+ did: args.did,
48
+ outputDir: args.outputDir,
49
+ meta: args.meta,
50
+ };
51
+ requireNonEmpty(commandArgs.alias, "Alias");
52
+ requireNonEmpty(commandArgs.did, "DID");
53
+ requireNonEmpty(commandArgs.outputDir, "Output directory");
54
+ const { hby, runtime } = yield* openRuntime(commandArgs);
55
+ try {
56
+ const artifacts = generateDidWebsArtifacts(runtime, {
57
+ alias: commandArgs.alias,
58
+ did: commandArgs.did,
59
+ metadata: commandArgs.meta ?? false,
60
+ });
61
+ const parsed = parseDidWebs(commandArgs.did);
62
+ const dir = artifactOutputDir(commandArgs.outputDir, parsed.path, artifacts.aid);
63
+ dntShim.Deno.mkdirSync(dir, { recursive: true });
64
+ dntShim.Deno.writeFileSync(`${dir}/did.json`, artifacts.didJson);
65
+ dntShim.Deno.writeFileSync(`${dir}/keri.cesr`, artifacts.keriCesr);
66
+ console.log(JSON.stringify({
67
+ aid: artifacts.aid,
68
+ did: commandArgs.did,
69
+ didJson: `${dir}/did.json`,
70
+ keriCesr: `${dir}/keri.cesr`,
71
+ }));
72
+ }
73
+ finally {
74
+ yield* closeRuntime(hby, runtime);
75
+ }
76
+ }
77
+ /** Implement `tufa dws resolve`. */
78
+ export function* dwsResolveCommand(args) {
79
+ const commandArgs = {
80
+ ...baseArgs(args),
81
+ did: args.did,
82
+ meta: args.meta,
83
+ insecureHttp: args.insecureHttp,
84
+ };
85
+ requireNonEmpty(commandArgs.did, "DID");
86
+ const { hby, runtime } = yield* openRuntime(commandArgs);
87
+ try {
88
+ const result = yield* resolveDidWebs(runtime, {
89
+ did: commandArgs.did,
90
+ metadata: commandArgs.meta ?? false,
91
+ insecureHttp: commandArgs.insecureHttp ?? false,
92
+ });
93
+ console.log(JSON.stringify(commandArgs.meta ? result.resolution : result.document, null, 2));
94
+ }
95
+ finally {
96
+ yield* closeRuntime(hby, runtime);
97
+ }
98
+ }
99
+ /** Implement `tufa dkr resolve`. */
100
+ export function* dkrResolveCommand(args) {
101
+ const commandArgs = {
102
+ ...baseArgs(args),
103
+ did: args.did,
104
+ oobis: asStringList(args.oobi),
105
+ meta: args.meta,
106
+ };
107
+ requireNonEmpty(commandArgs.did, "DID");
108
+ const { hby, runtime } = yield* openRuntime(commandArgs);
109
+ try {
110
+ const result = yield* resolveDidKeri(runtime, {
111
+ did: commandArgs.did,
112
+ oobis: commandArgs.oobis,
113
+ metadata: commandArgs.meta ?? false,
114
+ });
115
+ console.log(JSON.stringify(commandArgs.meta ? result.resolution : result.document, null, 2));
116
+ }
117
+ finally {
118
+ yield* closeRuntime(hby, runtime);
119
+ }
120
+ }
121
+ function baseArgs(args) {
122
+ return {
123
+ name: args.name,
124
+ base: args.base,
125
+ headDirPath: args.headDirPath,
126
+ passcode: args.passcode,
127
+ compat: args.compat,
128
+ };
129
+ }
130
+ function* openRuntime(args) {
131
+ requireNonEmpty(args.name, "Name");
132
+ const hby = yield* setupHby(args.name, args.base ?? "", args.passcode, false, args.headDirPath, {
133
+ compat: args.compat ?? false,
134
+ skipConfig: true,
135
+ });
136
+ const runtime = yield* createAgentRuntime(hby, { mode: "local" });
137
+ const reger = runtime.vdr.reger;
138
+ if (!(reger instanceof Reger)) {
139
+ throw new ValidationError("VDR runtime did not open Reger.");
140
+ }
141
+ return { hby, runtime, reger };
142
+ }
143
+ function* closeRuntime(hby, runtime) {
144
+ yield* runtime.close();
145
+ yield* hby.close();
146
+ }
147
+ function requireNonEmpty(value, label) {
148
+ if (!value || value.trim().length === 0) {
149
+ throw new ValidationError(`${label} is required.`);
150
+ }
151
+ }
152
+ function* receiptNewWitnessEvents(hby, pre, priorSn) {
153
+ const hab = hby.habs.get(pre);
154
+ const latestSn = hab?.kever?.sn;
155
+ if (!hab?.kever || latestSn === undefined || latestSn <= priorSn || hab.kever.wits.length === 0) {
156
+ return;
157
+ }
158
+ const receiptor = new WitnessReceiptor(hby);
159
+ for (let sn = priorSn + 1; sn <= latestSn; sn += 1) {
160
+ yield* receiptor.submit(pre, { sn });
161
+ }
162
+ }
163
+ function asStringList(value) {
164
+ if (Array.isArray(value)) {
165
+ return value.filter((item) => typeof item === "string");
166
+ }
167
+ return typeof value === "string" ? [value] : [];
168
+ }
169
+ function artifactOutputDir(root, didPath, aid) {
170
+ const normalizedRoot = root.replace(/\/+$/u, "");
171
+ return [
172
+ normalizedRoot,
173
+ ...didPath.map(safePathSegment),
174
+ safePathSegment(aid),
175
+ ].filter((part) => part.length > 0).join("/");
176
+ }
177
+ function safePathSegment(segment) {
178
+ if (segment.includes("/") || segment === ".." || segment.includes("\0")) {
179
+ throw new ValidationError(`Unsafe DID path segment ${segment}.`);
180
+ }
181
+ return segment;
182
+ }
@@ -1,6 +1,7 @@
1
1
  import { ValidationError } from "../../core/errors.js";
2
2
  import { isEndpointRole } from "../../core/roles.js";
3
3
  import { createAgentRuntime, ingestKeriBytes, processRuntimeTurn } from "../agent-runtime.js";
4
+ import { endpointRoleAccepted, isLocalGroupHab, proposeGroupEndpointRole, } from "../endpoint-roleing.js";
4
5
  import { setupHby } from "./common/existing.js";
5
6
  /**
6
7
  * Implement `tufa ends add` on top of the shared local runtime.
@@ -23,6 +24,7 @@ export function* endsAddCommand(args) {
23
24
  alias: args.alias,
24
25
  role: args.role,
25
26
  eid: args.eid,
27
+ multisigMode: parseMultisigMode(args.multisigMode),
26
28
  compat: args.compat,
27
29
  };
28
30
  if (!commandArgs.name) {
@@ -49,6 +51,35 @@ export function* endsAddCommand(args) {
49
51
  throw new ValidationError(`No local AID found for alias ${commandArgs.alias}`);
50
52
  }
51
53
  const runtime = yield* createAgentRuntime(hby, { mode: "local" });
54
+ if (isLocalGroupHab(hby, hab)) {
55
+ if (!commandArgs.multisigMode) {
56
+ throw new ValidationError("Group endpoint role authorization requires --multisig-mode propose or --multisig-mode complete.");
57
+ }
58
+ if (commandArgs.multisigMode === "propose") {
59
+ const result = yield* proposeGroupEndpointRole(runtime, hab, {
60
+ eid: commandArgs.eid,
61
+ role: commandArgs.role,
62
+ allow: true,
63
+ });
64
+ console.log(JSON.stringify({
65
+ route: result.route,
66
+ said: result.said,
67
+ group: result.group,
68
+ accepted: result.accepted,
69
+ deliveries: result.deliveries,
70
+ attachmentBytes: result.attachmentBytes,
71
+ }));
72
+ return;
73
+ }
74
+ if (!endpointRoleAccepted(hby, hab.pre, commandArgs.role, commandArgs.eid)) {
75
+ throw new ValidationError(`Endpoint role ${commandArgs.role} for ${commandArgs.eid} is not yet approved for group ${hab.pre}.`);
76
+ }
77
+ console.log(`${commandArgs.role} ${commandArgs.eid}`);
78
+ return;
79
+ }
80
+ if (commandArgs.multisigMode) {
81
+ throw new ValidationError("--multisig-mode is only valid for local group aliases.");
82
+ }
52
83
  ingestKeriBytes(runtime, hab.makeEndRole(commandArgs.eid, commandArgs.role, true));
53
84
  yield* processRuntimeTurn(runtime, { hab, pollMailbox: false });
54
85
  const end = hby.db.ends.get([hab.pre, commandArgs.role, commandArgs.eid]);
@@ -61,3 +92,12 @@ export function* endsAddCommand(args) {
61
92
  yield* hby.close();
62
93
  }
63
94
  }
95
+ function parseMultisigMode(value) {
96
+ if (value === undefined) {
97
+ return undefined;
98
+ }
99
+ if (value === "propose" || value === "complete") {
100
+ return value;
101
+ }
102
+ throw new ValidationError("--multisig-mode must be propose or complete.");
103
+ }
@@ -10,6 +10,7 @@ export { benchmarkCommand } from "./benchmark.js";
10
10
  export { challengeGenerateCommand, challengeRespondCommand, challengeVerifyCommand } from "./challenge.js";
11
11
  export { dumpEvts } from "./db-dump.js";
12
12
  export { delegateConfirmCommand } from "./delegate.js";
13
+ export { dkrResolveCommand, dwsBindCommand, dwsGenerateCommand, dwsResolveCommand } from "./did.js";
13
14
  export { endsAddCommand } from "./ends.js";
14
15
  export { exchangeSendCommand } from "./exchange.js";
15
16
  export { exportCommand } from "./export.js";
@@ -19,6 +20,7 @@ export { interactCommand } from "./interact.js";
19
20
  export { ipexAdmitCommand, ipexAgreeCommand, ipexApplyCommand, ipexGrantCommand, ipexJoinCommand, ipexListCommand, ipexOfferCommand, ipexPollCommand, ipexSpurnCommand, } from "./ipex.js";
20
21
  export { listCommand } from "./list.js";
21
22
  export { locAddCommand } from "./loc.js";
23
+ export { multisigInceptCommand, multisigInteractCommand, multisigJoinCommand, multisigRotateCommand, multisigRpyCommand, } from "./multisig.js";
22
24
  export { notificationsListCommand, notificationsMarkReadCommand, notificationsRemoveCommand } from "./notifications.js";
23
25
  export { oobiGenerateCommand, oobiRequestCommand, oobiResolveCommand } from "./oobi.js";
24
26
  export { queryCommand } from "./query.js";