@interop/wallet-core 0.48.0 → 0.50.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.
Files changed (163) hide show
  1. package/README.md +5 -2
  2. package/dist/clientAnnex/credentialAnchoredGenesis.d.ts +118 -0
  3. package/dist/clientAnnex/credentialAnchoredGenesis.d.ts.map +1 -0
  4. package/dist/clientAnnex/credentialAnchoredGenesis.js +174 -0
  5. package/dist/clientAnnex/credentialAnchoredGenesis.js.map +1 -0
  6. package/dist/clientAnnex/forget.d.ts +138 -0
  7. package/dist/clientAnnex/forget.d.ts.map +1 -0
  8. package/dist/clientAnnex/forget.js +133 -0
  9. package/dist/clientAnnex/forget.js.map +1 -0
  10. package/dist/clientAnnex/forgetLast.d.ts +267 -0
  11. package/dist/clientAnnex/forgetLast.d.ts.map +1 -0
  12. package/dist/clientAnnex/forgetLast.js +374 -0
  13. package/dist/clientAnnex/forgetLast.js.map +1 -0
  14. package/dist/clientAnnex/gc.d.ts +255 -0
  15. package/dist/clientAnnex/gc.d.ts.map +1 -0
  16. package/dist/clientAnnex/gc.js +445 -0
  17. package/dist/clientAnnex/gc.js.map +1 -0
  18. package/dist/clientAnnex/index.d.ts +60 -0
  19. package/dist/clientAnnex/index.d.ts.map +1 -0
  20. package/dist/clientAnnex/index.js +60 -0
  21. package/dist/clientAnnex/index.js.map +1 -0
  22. package/dist/{unlock → clientAnnex}/ladder.d.ts +99 -23
  23. package/dist/clientAnnex/ladder.d.ts.map +1 -0
  24. package/dist/clientAnnex/ladder.js +474 -0
  25. package/dist/clientAnnex/ladder.js.map +1 -0
  26. package/dist/clientAnnex/ladderAnchored.d.ts +276 -0
  27. package/dist/clientAnnex/ladderAnchored.d.ts.map +1 -0
  28. package/dist/clientAnnex/ladderAnchored.js +631 -0
  29. package/dist/clientAnnex/ladderAnchored.js.map +1 -0
  30. package/dist/{webvh/companion.d.ts → clientAnnex/log.d.ts} +373 -152
  31. package/dist/clientAnnex/log.d.ts.map +1 -0
  32. package/dist/{webvh/companion.js → clientAnnex/log.js} +654 -261
  33. package/dist/clientAnnex/log.js.map +1 -0
  34. package/dist/clientAnnex/recoveryLadderAnchored.d.ts +103 -0
  35. package/dist/clientAnnex/recoveryLadderAnchored.d.ts.map +1 -0
  36. package/dist/clientAnnex/recoveryLadderAnchored.js +263 -0
  37. package/dist/clientAnnex/recoveryLadderAnchored.js.map +1 -0
  38. package/dist/{unlock → clientAnnex}/selfEnroll.d.ts +1 -1
  39. package/dist/clientAnnex/selfEnroll.d.ts.map +1 -0
  40. package/dist/{unlock → clientAnnex}/selfEnroll.js +1 -1
  41. package/dist/clientAnnex/selfEnroll.js.map +1 -0
  42. package/dist/clientAnnex/zcap.d.ts +49 -0
  43. package/dist/clientAnnex/zcap.d.ts.map +1 -0
  44. package/dist/clientAnnex/zcap.js +101 -0
  45. package/dist/clientAnnex/zcap.js.map +1 -0
  46. package/dist/clients/index.d.ts +9 -6
  47. package/dist/clients/index.d.ts.map +1 -1
  48. package/dist/clients/index.js +8 -5
  49. package/dist/clients/index.js.map +1 -1
  50. package/dist/clients/listing.d.ts +32 -0
  51. package/dist/clients/listing.d.ts.map +1 -1
  52. package/dist/clients/listing.js +47 -1
  53. package/dist/clients/listing.js.map +1 -1
  54. package/dist/clients/revocation.d.ts +33 -1
  55. package/dist/clients/revocation.d.ts.map +1 -1
  56. package/dist/clients/revocation.js +16 -4
  57. package/dist/clients/revocation.js.map +1 -1
  58. package/dist/enrollment/index.d.ts +6 -4
  59. package/dist/enrollment/index.d.ts.map +1 -1
  60. package/dist/enrollment/index.js +6 -4
  61. package/dist/enrollment/index.js.map +1 -1
  62. package/dist/enrollment/onboardingInvite.d.ts +5 -88
  63. package/dist/enrollment/onboardingInvite.d.ts.map +1 -1
  64. package/dist/enrollment/onboardingInvite.js +5 -168
  65. package/dist/enrollment/onboardingInvite.js.map +1 -1
  66. package/dist/genesis/index.d.ts +5 -0
  67. package/dist/genesis/index.d.ts.map +1 -1
  68. package/dist/genesis/index.js +5 -0
  69. package/dist/genesis/index.js.map +1 -1
  70. package/dist/keys/index.d.ts +1 -1
  71. package/dist/keys/index.d.ts.map +1 -1
  72. package/dist/keys/index.js +1 -1
  73. package/dist/keys/index.js.map +1 -1
  74. package/dist/keys/spaceEpochs.d.ts +7 -2
  75. package/dist/keys/spaceEpochs.d.ts.map +1 -1
  76. package/dist/keys/spaceEpochs.js +8 -2
  77. package/dist/keys/spaceEpochs.js.map +1 -1
  78. package/dist/keys/userKeyRoster.d.ts +39 -0
  79. package/dist/keys/userKeyRoster.d.ts.map +1 -1
  80. package/dist/keys/userKeyRoster.js +43 -1
  81. package/dist/keys/userKeyRoster.js.map +1 -1
  82. package/dist/recovery/index.d.ts +5 -3
  83. package/dist/recovery/index.d.ts.map +1 -1
  84. package/dist/recovery/index.js +4 -2
  85. package/dist/recovery/index.js.map +1 -1
  86. package/dist/recovery/recoveryDelegation.d.ts +35 -12
  87. package/dist/recovery/recoveryDelegation.d.ts.map +1 -1
  88. package/dist/recovery/recoveryDelegation.js +84 -86
  89. package/dist/recovery/recoveryDelegation.js.map +1 -1
  90. package/dist/recovery/recoveryWebvh.d.ts +32 -1
  91. package/dist/recovery/recoveryWebvh.d.ts.map +1 -1
  92. package/dist/recovery/recoveryWebvh.js +2 -2
  93. package/dist/recovery/recoveryWebvh.js.map +1 -1
  94. package/dist/request/capabilityRequest.d.ts +33 -0
  95. package/dist/request/capabilityRequest.d.ts.map +1 -0
  96. package/dist/request/capabilityRequest.js +35 -0
  97. package/dist/request/capabilityRequest.js.map +1 -0
  98. package/dist/request/ephemeralExchange.d.ts +118 -0
  99. package/dist/request/ephemeralExchange.d.ts.map +1 -0
  100. package/dist/request/ephemeralExchange.js +224 -0
  101. package/dist/request/ephemeralExchange.js.map +1 -0
  102. package/dist/request/index.d.ts +6 -0
  103. package/dist/request/index.d.ts.map +1 -1
  104. package/dist/request/index.js +6 -0
  105. package/dist/request/index.js.map +1 -1
  106. package/dist/space/activity.d.ts +38 -0
  107. package/dist/space/activity.d.ts.map +1 -1
  108. package/dist/space/activity.js +41 -1
  109. package/dist/space/activity.js.map +1 -1
  110. package/dist/space/index.d.ts +1 -1
  111. package/dist/space/index.d.ts.map +1 -1
  112. package/dist/space/index.js +1 -1
  113. package/dist/space/index.js.map +1 -1
  114. package/dist/unlock/index.d.ts +11 -23
  115. package/dist/unlock/index.d.ts.map +1 -1
  116. package/dist/unlock/index.js +10 -21
  117. package/dist/unlock/index.js.map +1 -1
  118. package/dist/unlock/retire.d.ts +47 -3
  119. package/dist/unlock/retire.d.ts.map +1 -1
  120. package/dist/unlock/retire.js +21 -5
  121. package/dist/unlock/retire.js.map +1 -1
  122. package/dist/unlock/standingWebvh.d.ts +37 -82
  123. package/dist/unlock/standingWebvh.d.ts.map +1 -1
  124. package/dist/unlock/standingWebvh.js +64 -247
  125. package/dist/unlock/standingWebvh.js.map +1 -1
  126. package/dist/unlock/unlockRecord.d.ts +13 -6
  127. package/dist/unlock/unlockRecord.d.ts.map +1 -1
  128. package/dist/unlock/unlockRecord.js +14 -8
  129. package/dist/unlock/unlockRecord.js.map +1 -1
  130. package/dist/webvh/delegatedLogStore.d.ts +5 -5
  131. package/dist/webvh/delegatedLogStore.js +1 -1
  132. package/dist/webvh/didWebvh.d.ts +20 -10
  133. package/dist/webvh/didWebvh.d.ts.map +1 -1
  134. package/dist/webvh/didWebvh.js +17 -17
  135. package/dist/webvh/didWebvh.js.map +1 -1
  136. package/dist/webvh/index.d.ts +9 -23
  137. package/dist/webvh/index.d.ts.map +1 -1
  138. package/dist/webvh/index.js +9 -22
  139. package/dist/webvh/index.js.map +1 -1
  140. package/dist/webvh/revokeClient.d.ts +77 -5
  141. package/dist/webvh/revokeClient.d.ts.map +1 -1
  142. package/dist/webvh/revokeClient.js +154 -61
  143. package/dist/webvh/revokeClient.js.map +1 -1
  144. package/dist/webvh/standingZcap.d.ts +11 -1
  145. package/dist/webvh/standingZcap.d.ts.map +1 -1
  146. package/dist/webvh/standingZcap.js +13 -14
  147. package/dist/webvh/standingZcap.js.map +1 -1
  148. package/dist/webvh/wasIdStore.d.ts +13 -7
  149. package/dist/webvh/wasIdStore.d.ts.map +1 -1
  150. package/dist/webvh/wasIdStore.js +8 -4
  151. package/dist/webvh/wasIdStore.js.map +1 -1
  152. package/dist/webvh/zcap.d.ts +0 -24
  153. package/dist/webvh/zcap.d.ts.map +1 -1
  154. package/dist/webvh/zcap.js +0 -42
  155. package/dist/webvh/zcap.js.map +1 -1
  156. package/package.json +40 -34
  157. package/dist/unlock/ladder.d.ts.map +0 -1
  158. package/dist/unlock/ladder.js +0 -254
  159. package/dist/unlock/ladder.js.map +0 -1
  160. package/dist/unlock/selfEnroll.d.ts.map +0 -1
  161. package/dist/unlock/selfEnroll.js.map +0 -1
  162. package/dist/webvh/companion.d.ts.map +0 -1
  163. package/dist/webvh/companion.js.map +0 -1
@@ -2,25 +2,25 @@
2
2
  * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
3
  */
4
4
  /**
5
- * The companion did:webvh: the disposable sidecar log holding transient
5
+ * The client annex did:webvh: the disposable sidecar log holding transient
6
6
  * per-visit verification methods, one generation per flat `gen-` collection
7
- * inside the account's stable auxiliary companion Space -- so per-visit facts
7
+ * inside the account's stable auxiliary annex Space -- so per-visit facts
8
8
  * stay out of the account's identity log entirely. This module is the
9
9
  * generation's identity, genesis, and enrollment machinery: the
10
- * `gen-<random>` segment convention, the typed auxiliary Space ensure, the
11
- * genesis parameters, the pin-slot key for companion continuity, the atomic
10
+ * `gen-<random>` generation id convention, the typed auxiliary Space ensure,
11
+ * the genesis parameters, the pin-slot key for annex continuity, the atomic
12
12
  * transient-enrollment entry, and the account document's delegated-clients
13
13
  * service entry (the pointer at the current generation).
14
14
  *
15
- * The companion's posture differs from the account log's on purpose:
15
+ * The annex's posture differs from the account log's on purpose:
16
16
  *
17
- * - Update authority is each standing credential's static companion rung 0
17
+ * - Update authority is each standing credential's static annex rung 0
18
18
  * (chain length one, no rung advancement, no attribution scan). Genesis
19
19
  * states the minting credential's rung-0 key in `updateKeys` and commits
20
20
  * every standing credential's rung-0 hash in `nextKeyHashes` -- the minting
21
21
  * key's own carry-over hash included, or no later entry could re-state it.
22
22
  * - Prerotation stays on (the rung-0 hashes are the commitment chain),
23
- * witnesses stay off, and portability is off: a companion is
23
+ * witnesses stay off, and portability is off: an annex is
24
24
  * generation-scoped and host-bound, and replacement is a GC swap, never a
25
25
  * portability move.
26
26
  * - The genesis document is bare -- no verification methods, no service
@@ -32,20 +32,20 @@
32
32
  * - No `did:web` projection exists: the generation collection holds only its
33
33
  * `did.jsonl`, capability-gated rather than world-readable.
34
34
  *
35
- * Ordering rule: the companion log publishes FIRST; only then does the
35
+ * Ordering rule: the annex log publishes FIRST; only then does the
36
36
  * caller re-point the account document's `#DelegatedClients` service entry at
37
- * the new companion DID. A companion nobody points at is authorization-inert
37
+ * the new annex DID. An annex nobody points at is authorization-inert
38
38
  * (no delegation ever names it), so a tear or a double-genesis race leaks
39
39
  * storage, never authority -- and the standing orphan discovery is a plain
40
40
  * `gen-` prefix match over the auxiliary Space's collection listing, with no
41
41
  * registry of generations anywhere.
42
42
  *
43
- * Generation identity is random, never a counter: a reused segment would
43
+ * Generation identity is random, never a counter: a reused generation id would
44
44
  * re-derive the same rung-0 update key for a new generation, and no counter
45
- * carrier survives GC deleting the old collection. The segment is also the
46
- * generation-identifying half of the companion rung HKDF labels
47
- * (`<segment>/rung/<k>` under the unlock ladder's one salt), so there is
48
- * exactly one spelling of a generation's identity -- the one the companion
45
+ * carrier survives GC deleting the old collection. The generation id is also
46
+ * the generation-identifying half of the annex rung HKDF labels
47
+ * (`<generationId>/rung/<k>` under the unlock ladder's one salt), so there is
48
+ * exactly one spelling of a generation's identity -- the one the annex
49
49
  * DID string already embeds.
50
50
  */
51
51
  import { createDID, deriveNextKeyHash, updateDID } from '@interop/did-method-webvh';
@@ -53,92 +53,94 @@ import { rootCapabilityId, spaceItems, spacePath, toUrl } from '@interop/was-cli
53
53
  import { base64urlnopad } from '@scure/base';
54
54
  import { DID_LOG_RESOURCE } from '../space/collections.js';
55
55
  import { resourceLogPinId } from '../resourceLog/pin.js';
56
- import { companionRung } from '../unlock/ladder.js';
57
- import { assertCarryOverCommitments, concludeWithPublishedLog, didWebvhControllerTemplate, MULTIKEY_VM_TYPE, publishUpdatedLog, putLogResource, readPublishedLog, relationIds, updateKeyMultibase, updateKeySigner, withLogConflictRetry } from './didWebvh.js';
58
- import { STANDING_ZCAP_TTL_MS, zcapExpiring } from './standingZcap.js';
59
- import { wasWebvhLogStore } from './wasIdStore.js';
56
+ import { clientAnnexRung } from './ladder.js';
57
+ import { assertCarryOverCommitments, concludeWithPublishedLog, didWebvhControllerTemplate, MULTIKEY_VM_TYPE, publishUpdatedLog, putLogResource, readPublishedLog, relationIds, updateKeyMultibase, updateKeySigner, withLogConflictRetry } from '../webvh/didWebvh.js';
58
+ import { delegationKeyInDocument } from '../webvh/listClients.js';
59
+ import { delegationProofKeyId, STANDING_ZCAP_TTL_MS, zcapExpiring } from '../webvh/standingZcap.js';
60
+ import { wasWebvhLogStore } from '../webvh/wasIdStore.js';
60
61
  /**
61
- * The Space Description `type` array of the auxiliary companion Space, set at
62
+ * The Space Description `type` array of the auxiliary annex Space, set at
62
63
  * creation (the server treats a Space's `type` as immutable afterwards).
63
64
  * Wire-level and permanent: the server's inspector clause recognizes the
64
65
  * `DelegatedClientsSpace` member, and user-data surfaces exclude auxiliary
65
66
  * Spaces by it.
66
67
  */
67
- export const COMPANION_SPACE_TYPE = [
68
+ export const CLIENT_ANNEX_SPACE_TYPE = [
68
69
  'Space',
69
70
  'AuxiliarySpace',
70
71
  'DelegatedClientsSpace'
71
72
  ];
72
73
  /**
73
74
  * The `type` member that marks a Space as the delegated-clients auxiliary
74
- * Space (the last entry of {@link COMPANION_SPACE_TYPE}).
75
+ * Space (the last entry of {@link CLIENT_ANNEX_SPACE_TYPE}).
75
76
  */
76
77
  const DELEGATED_CLIENTS_SPACE_TYPE = 'DelegatedClientsSpace';
77
78
  /**
78
79
  * The literal prefix of every generation collection's name. Wire-level and
79
80
  * permanent: orphan discovery is a plain prefix match over the auxiliary
80
- * Space's collection listing, and the segment embeds in every companion DID
81
- * string ever published.
81
+ * Space's collection listing, and the generation id embeds in every annex
82
+ * DID string ever published.
82
83
  */
83
- export const GENERATION_SEGMENT_PREFIX = 'gen-';
84
+ export const GENERATION_ID_PREFIX = 'gen-';
84
85
  /**
85
86
  * The random suffix: 12 bytes, base64url-no-pad (16 characters), for 20
86
87
  * characters total. Every character is inside the server's `[A-Za-z0-9._~-]+`
87
- * id allowlist, so `encodeURIComponent` is the identity on the segment and
88
- * the DID path encoding round-trips it.
88
+ * id allowlist, so `encodeURIComponent` is the identity on the generation id
89
+ * and the DID path encoding round-trips it.
89
90
  */
90
- const GENERATION_SEGMENT_SUFFIX_BYTES = 12;
91
+ const GENERATION_ID_SUFFIX_BYTES = 12;
91
92
  /**
92
- * The full segment shape: the literal prefix plus 16 base64url characters.
93
+ * The full generation id shape: the literal prefix plus 16 base64url
94
+ * characters.
93
95
  */
94
- const GENERATION_SEGMENT_PATTERN = /^gen-[A-Za-z0-9_-]{16}$/;
96
+ const GENERATION_ID_PATTERN = /^gen-[A-Za-z0-9_-]{16}$/;
95
97
  /**
96
- * Mints a fresh generation segment -- the generation collection's name, e.g.
98
+ * Mints a fresh generation id -- the generation collection's name, e.g.
97
99
  * `gen-Ux3v0kQf9aPmB2hZ`. Random rather than a counter on purpose: never-reuse
98
100
  * is structural (nothing durable survives GC to carry a counter), at the same
99
101
  * probabilistic order as every other random-id convention in the system.
100
102
  *
101
103
  * @returns {string}
102
104
  */
103
- export function mintGenerationSegment() {
104
- return (GENERATION_SEGMENT_PREFIX +
105
- base64urlnopad.encode(crypto.getRandomValues(new Uint8Array(GENERATION_SEGMENT_SUFFIX_BYTES))));
105
+ export function mintGenerationId() {
106
+ return (GENERATION_ID_PREFIX +
107
+ base64urlnopad.encode(crypto.getRandomValues(new Uint8Array(GENERATION_ID_SUFFIX_BYTES))));
106
108
  }
107
109
  /**
108
- * Refuses anything that is not a well-formed generation segment. Run by every
109
- * companion builder that takes a segment, so a malformed one is refused
110
+ * Refuses anything that is not a well-formed generation id. Run by every
111
+ * annex builder that takes a generation id, so a malformed one is refused
110
112
  * before it can reach a DID string, an HKDF label, or a collection id.
111
113
  *
112
- * @param segment {string}
114
+ * @param generationId {string}
113
115
  */
114
- export function assertGenerationSegment(segment) {
115
- if (!GENERATION_SEGMENT_PATTERN.test(segment)) {
116
- throw new Error(`Not a generation segment: "${segment}" (expected "gen-" plus 16 ` +
116
+ export function assertGenerationId(generationId) {
117
+ if (!GENERATION_ID_PATTERN.test(generationId)) {
118
+ throw new Error(`Not a generation id: "${generationId}" (expected "gen-" plus 16 ` +
117
119
  'base64url characters).');
118
120
  }
119
121
  }
120
122
  /**
121
- * The pin-slot key for one companion generation's log -- host-free like every
122
- * pin-slot key, keyed by the auxiliary Space id and the generation segment.
123
+ * The pin-slot key for one annex generation's log -- host-free like every
124
+ * pin-slot key, keyed by the auxiliary Space id and the generation id.
123
125
  * A transient session keeps this slot in an in-memory pin store (a durable
124
126
  * pin is the wrong lifetime for a disposable log, and a transient session
125
127
  * must not durably create the pin store on a read); a durable client's store
126
- * clears companion slots when the generation is collected.
128
+ * clears annex slots when the generation is collected.
127
129
  *
128
130
  * @param options {object}
129
- * @param options.spaceId {string} the auxiliary companion Space's id
130
- * @param options.segment {string} the generation collection's name
131
+ * @param options.spaceId {string} the auxiliary annex Space's id
132
+ * @param options.generationId {string} the generation collection's name
131
133
  * @returns {string}
132
134
  */
133
- export function companionLogPinId({ spaceId, segment }) {
135
+ export function clientAnnexLogPinId({ spaceId, generationId }) {
134
136
  return resourceLogPinId({
135
137
  spaceId,
136
- collectionId: segment,
138
+ collectionId: generationId,
137
139
  resourceId: DID_LOG_RESOURCE
138
140
  });
139
141
  }
140
142
  /**
141
- * The WAS-backed store a companion generation's ceremonies read and publish
143
+ * The WAS-backed store an annex generation's ceremonies read and publish
142
144
  * through with controller-tier signing (an enrolled client). A transient
143
145
  * session writes through the delegated store instead
144
146
  * (`delegatedWebvhLogStore`, invoking the credential's sibling delegation);
@@ -146,16 +148,24 @@ export function companionLogPinId({ spaceId, segment }) {
146
148
  *
147
149
  * @param options {object}
148
150
  * @param options.was {WasClient}
149
- * @param options.spaceId {string} the auxiliary companion Space's id
150
- * @param options.segment {string} the generation collection's name
151
+ * @param options.spaceId {string} the auxiliary annex Space's id
152
+ * @param options.generationId {string} the generation collection's name
153
+ * @param [options.capability] {IZcap} an invocation capability every request
154
+ * rides (the sibling delegation, where the caller is not an enrolled
155
+ * invoker); absent, requests invoke the root capability
151
156
  * @returns {WebvhLogResourceStore}
152
157
  */
153
- export function companionLogStore({ was, spaceId, segment }) {
154
- assertGenerationSegment(segment);
155
- return wasWebvhLogStore({ was, spaceId, collectionId: segment });
158
+ export function clientAnnexLogStore({ was, spaceId, generationId, capability }) {
159
+ assertGenerationId(generationId);
160
+ return wasWebvhLogStore({
161
+ was,
162
+ spaceId,
163
+ collectionId: generationId,
164
+ ...(capability !== undefined ? { capability } : {})
165
+ });
156
166
  }
157
167
  /**
158
- * The DID core context -- the companion genesis document's whole `@context`.
168
+ * The DID core context -- the annex genesis document's whole `@context`.
159
169
  * The document carries no verification methods and no service entries at
160
170
  * genesis, so no other vocabulary is in scope; the entry that first publishes
161
171
  * a typed member extends the context then (a did:webvh entry replaces the
@@ -163,23 +173,23 @@ export function companionLogStore({ was, spaceId, segment }) {
163
173
  */
164
174
  const DID_CORE_CONTEXT = 'https://www.w3.org/ns/did/v1';
165
175
  /**
166
- * The Multikey context, appended to the companion document's `@context` by
176
+ * The Multikey context, appended to the annex document's `@context` by
167
177
  * the entry that first publishes a transient verification method (genesis
168
178
  * carries the DID core context only, having no typed members to define).
169
179
  */
170
180
  const MULTIKEY_CONTEXT_URL = 'https://w3id.org/security/multikey/v1';
171
181
  /**
172
- * Creates the one-entry companion generation log. The genesis parameters are
173
- * the companion posture (see the module doc): prerotation on via the rung-0
182
+ * Creates the one-entry annex generation log. The genesis parameters are
183
+ * the annex posture (see the module doc): prerotation on via the rung-0
174
184
  * hash commitments, no witnesses, portability off (the library's default,
175
185
  * stated explicitly in the emitted entry), and a bare document -- id and the
176
186
  * DID core context, nothing else.
177
187
  *
178
188
  * The caller supplies the update authority: the minting credential's
179
- * companion rung-0 key as the sole `updateKeys` member, `nextKeyHashes` as
189
+ * annex rung-0 key as the sole `updateKeys` member, `nextKeyHashes` as
180
190
  * every standing credential's rung-0 hash (restated explicitly on every later
181
191
  * entry, never inherited), and rung 0's signer. The minting key's own
182
- * carry-over hash MUST be among the commitments -- every companion entry
192
+ * carry-over hash MUST be among the commitments -- every annex entry
183
193
  * re-states `updateKeys` containing the revealed rung-0 keys, and the
184
194
  * resolver checks the re-statement against the previous entry's commitments
185
195
  * -- so a `nextKeyHashes` that omits it is refused here rather than
@@ -187,20 +197,20 @@ const MULTIKEY_CONTEXT_URL = 'https://w3id.org/security/multikey/v1';
187
197
  *
188
198
  * @param options {object}
189
199
  * @param options.wasServerUrl {string}
190
- * @param options.spaceId {string} the auxiliary companion Space's id
191
- * @param options.segment {string} the generation collection's name
200
+ * @param options.spaceId {string} the auxiliary annex Space's id
201
+ * @param options.generationId {string} the generation collection's name
192
202
  * @param options.updateKeyPublicKeyMultibase {string} the minting
193
- * credential's companion rung-0 key
203
+ * credential's annex rung-0 key
194
204
  * @param options.nextKeyHashes {string[]} every standing credential's
195
205
  * rung-0 hash, the minting credential's included
196
206
  * @param options.signer {Signer} the minting credential's rung-0 signer
197
207
  * @returns {Promise<{ log: DIDLog; did: string; doc: DIDDoc }>}
198
208
  */
199
- export async function createCompanionLog({ wasServerUrl, spaceId, segment, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
200
- assertGenerationSegment(segment);
209
+ export async function createClientAnnexLog({ wasServerUrl, spaceId, generationId, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
210
+ assertGenerationId(generationId);
201
211
  const carryOverHash = await deriveNextKeyHash(updateKeyPublicKeyMultibase);
202
212
  if (!nextKeyHashes.includes(carryOverHash)) {
203
- throw new Error('companion genesis: `nextKeyHashes` must include the minting ' +
213
+ throw new Error('client annex genesis: `nextKeyHashes` must include the minting ' +
204
214
  "credential's own rung-0 hash (the carry-over commitment), or no " +
205
215
  'later entry could ever re-state the revealed key.');
206
216
  }
@@ -208,24 +218,24 @@ export async function createCompanionLog({ wasServerUrl, spaceId, segment, updat
208
218
  const controllerTemplate = didWebvhControllerTemplate({
209
219
  wasServerUrl,
210
220
  spaceId,
211
- collectionId: segment
221
+ collectionId: generationId
212
222
  });
213
223
  const result = await createDID({
214
224
  address: host,
215
- paths: ['space', spaceId, segment],
225
+ paths: ['space', spaceId, generationId],
216
226
  signer,
217
227
  updateKeys: [updateKeyPublicKeyMultibase],
218
228
  nextKeyHashes,
219
229
  didDocument: { '@context': [DID_CORE_CONTEXT], id: controllerTemplate }
220
230
  });
221
231
  if (!result.did || !result.doc) {
222
- throw new Error('companion genesis: createDID returned no DID document.');
232
+ throw new Error('client annex genesis: createDID returned no DID document.');
223
233
  }
224
234
  return { log: result.log, did: result.did, doc: result.doc };
225
235
  }
226
236
  /**
227
- * Ensures the auxiliary companion Space exists: created with the typed
228
- * Description ({@link COMPANION_SPACE_TYPE}) under the given controller when
237
+ * Ensures the auxiliary annex Space exists: created with the typed
238
+ * Description ({@link CLIENT_ANNEX_SPACE_TYPE}) under the given controller when
229
239
  * absent, verified when present. The `type` array must ride the create -- the
230
240
  * server accepts it at creation only and treats it as immutable afterwards --
231
241
  * which is also why an existing Space at this id that is NOT typed as the
@@ -241,19 +251,19 @@ export async function createCompanionLog({ wasServerUrl, spaceId, segment, updat
241
251
  *
242
252
  * @param options {object}
243
253
  * @param options.was {WasClient}
244
- * @param options.spaceId {string} the auxiliary companion Space's id
254
+ * @param options.spaceId {string} the auxiliary annex Space's id
245
255
  * @param options.controller {string} the Space controller (the account
246
- * did:webvh where it exists; a bootstrap did:key on a client-less signup,
256
+ * did:webvh where it exists; a bootstrap did:key on a ladder-anchored signup,
247
257
  * promoted the same way the account Space's controller is)
248
258
  * @returns {Promise<void>}
249
259
  */
250
- export async function ensureCompanionSpace({ was, spaceId, controller }) {
260
+ export async function ensureClientAnnexSpace({ was, spaceId, controller }) {
251
261
  const space = was.space(spaceId);
252
262
  const current = await space.describe();
253
263
  if (current === null) {
254
264
  await space.configure({
255
265
  controller,
256
- type: COMPANION_SPACE_TYPE,
266
+ type: CLIENT_ANNEX_SPACE_TYPE,
257
267
  force: true
258
268
  });
259
269
  return;
@@ -261,18 +271,19 @@ export async function ensureCompanionSpace({ was, spaceId, controller }) {
261
271
  if (!current.type?.includes(DELEGATED_CLIENTS_SPACE_TYPE)) {
262
272
  throw new Error(`The Space "${spaceId}" exists but is not typed as the ` +
263
273
  'delegated-clients auxiliary Space; its type is immutable, so it ' +
264
- 'cannot hold companion generations.');
274
+ 'cannot hold client-annex generations.');
265
275
  }
266
276
  }
267
277
  /**
268
- * Mints a fresh companion generation with controller-tier signing: ensures
269
- * the typed auxiliary Space, mints a fresh random segment, creates the
278
+ * Mints a fresh annex generation with controller-tier signing: ensures
279
+ * the typed auxiliary Space, mints a fresh random generation id, creates the
270
280
  * generation collection, and publishes the genesis `did.jsonl` as a
271
281
  * create-if-absent -- the same conditional-publish discipline as every log
272
- * write, though a fresh random segment makes a create collision negligible.
282
+ * write, though a fresh random generation id makes a create collision
283
+ * negligible.
273
284
  *
274
285
  * The account document's `#DelegatedClients` service entry is deliberately
275
- * NOT written here: the companion log publishes first, and the caller
286
+ * NOT written here: the annex log publishes first, and the caller
276
287
  * re-points the account document at the returned DID afterwards. A run torn
277
288
  * between the two leaves an unpointed generation -- authorization-inert (no
278
289
  * delegation names it), collected by the standing `gen-` prefix orphan
@@ -285,27 +296,27 @@ export async function ensureCompanionSpace({ was, spaceId, controller }) {
285
296
  *
286
297
  * @param options {object}
287
298
  * @param options.was {WasClient} the storage client, signing as an enrolled
288
- * client (or the bootstrap controller on a client-less signup)
299
+ * client (or the bootstrap controller on a ladder-anchored signup)
289
300
  * @param options.wasServerUrl {string}
290
- * @param options.spaceId {string} the auxiliary companion Space's id
301
+ * @param options.spaceId {string} the auxiliary annex Space's id
291
302
  * @param options.controller {string} the auxiliary Space's controller, used
292
303
  * only when the Space does not exist yet
293
304
  * @param options.updateKeyPublicKeyMultibase {string} the minting
294
- * credential's companion rung-0 key
305
+ * credential's annex rung-0 key
295
306
  * @param options.nextKeyHashes {string[]} every standing credential's
296
307
  * rung-0 hash, the minting credential's included
297
308
  * @param options.signer {Signer} the minting credential's rung-0 signer
298
- * @returns {Promise<{ did: string; segment: string; log: DIDLog;
309
+ * @returns {Promise<{ did: string; generationId: string; log: DIDLog;
299
310
  * doc: DIDDoc }>}
300
311
  */
301
- export async function mintCompanionGeneration({ was, wasServerUrl, spaceId, controller, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
302
- await ensureCompanionSpace({ was, spaceId, controller });
303
- const segment = mintGenerationSegment();
304
- return publishCompanionGenesis({
312
+ export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, controller, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
313
+ await ensureClientAnnexSpace({ was, spaceId, controller });
314
+ const generationId = mintGenerationId();
315
+ return publishClientAnnexGenesis({
305
316
  was,
306
317
  wasServerUrl,
307
318
  spaceId,
308
- segment,
319
+ generationId,
309
320
  updateKeyPublicKeyMultibase,
310
321
  nextKeyHashes,
311
322
  signer
@@ -319,87 +330,106 @@ export async function mintCompanionGeneration({ was, wasServerUrl, spaceId, cont
319
330
  * @param options {object}
320
331
  * @param options.was {WasClient}
321
332
  * @param options.wasServerUrl {string}
322
- * @param options.spaceId {string} the auxiliary companion Space's id
323
- * @param options.segment {string} the freshly minted generation segment
333
+ * @param options.spaceId {string} the auxiliary annex Space's id
334
+ * @param options.generationId {string} the freshly minted generation id
324
335
  * @param options.updateKeyPublicKeyMultibase {string}
325
336
  * @param options.nextKeyHashes {string[]}
326
337
  * @param options.signer {Signer}
327
- * @returns {Promise<{ did: string; segment: string; log: DIDLog;
338
+ * @param [options.capability] {IZcap} an invocation capability the
339
+ * collection create and the genesis publish ride (a delegated minter)
340
+ * @returns {Promise<{ did: string; generationId: string; log: DIDLog;
328
341
  * doc: DIDDoc }>}
329
342
  */
330
- async function publishCompanionGenesis({ was, wasServerUrl, spaceId, segment, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
343
+ async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generationId, updateKeyPublicKeyMultibase, nextKeyHashes, signer, capability }) {
331
344
  // The generation collection must exist before its first resource PUT; a
332
- // fresh random segment means this is always a create. Plaintext on purpose:
333
- // the server resolves the companion DID out of its own storage, and the
345
+ // fresh random generation id means this is always a create. Plaintext on
346
+ // purpose:
347
+ // the server resolves the annex DID out of its own storage, and the
334
348
  // collection is capability-gated rather than encrypted.
335
349
  await was
336
- .space(spaceId)
337
- .collection(segment, { encryption: 'plaintext' })
338
- .configure({ name: segment, force: true });
339
- const created = await createCompanionLog({
350
+ .space(spaceId, capability !== undefined ? { capability } : {})
351
+ .collection(generationId, { encryption: 'plaintext' })
352
+ .configure({ name: generationId, force: true });
353
+ const created = await createClientAnnexLog({
340
354
  wasServerUrl,
341
355
  spaceId,
342
- segment,
356
+ generationId,
343
357
  updateKeyPublicKeyMultibase,
344
358
  nextKeyHashes,
345
359
  signer
346
360
  });
347
361
  await putLogResource({
348
- store: companionLogStore({ was, spaceId, segment }),
362
+ store: clientAnnexLogStore({
363
+ was,
364
+ spaceId,
365
+ generationId,
366
+ ...(capability !== undefined ? { capability } : {})
367
+ }),
349
368
  log: created.log,
350
369
  ifNoneMatch: true
351
370
  });
352
- return { ...created, segment };
371
+ return { ...created, generationId };
353
372
  }
354
373
  /**
355
- * Mints a fresh companion generation signed by a standing CREDENTIAL's
356
- * companion rung 0 -- the mint a ladder-seed holder runs (a credential-in-hand
357
- * login, or a test harness standing in for one). The segment must exist
374
+ * Mints a fresh annex generation signed by a standing CREDENTIAL's
375
+ * annex rung 0 -- the mint a ladder-seed holder runs (a credential-in-hand
376
+ * login, or a test harness standing in for one). The generation id must exist
358
377
  * before the update authority can: the rung-0 key derives from the ladder
359
- * seed AND the segment (`companionRung`), so this helper mints the segment
378
+ * seed AND the generation id (`clientAnnexRung`), so this helper mints the
379
+ * generation id
360
380
  * first, derives the rung, and states its own carry-over hash in
361
- * `nextKeyHashes` -- {@link mintCompanionGeneration}'s caller-supplied-key
381
+ * `nextKeyHashes` -- {@link mintClientAnnexGeneration}'s caller-supplied-key
362
382
  * shape cannot express that ordering. Everything else matches it: the typed
363
383
  * Space ensure, the collection create, the create-if-absent genesis publish,
364
384
  * and the pointer deliberately left to the caller.
365
385
  *
366
386
  * @param options {object}
367
387
  * @param options.was {WasClient} the storage client, signing as an enrolled
368
- * client (or the bootstrap controller on a client-less signup)
388
+ * client (or the bootstrap controller on a ladder-anchored signup)
369
389
  * @param options.wasServerUrl {string}
370
- * @param options.spaceId {string} the auxiliary companion Space's id
390
+ * @param options.spaceId {string} the auxiliary annex Space's id
371
391
  * @param options.controller {string} the auxiliary Space's controller, used
372
392
  * only when the Space does not exist yet
373
393
  * @param options.ladderSeed {Uint8Array} the minting credential's ladder
374
394
  * seed, from its unlock record
375
395
  * @param [options.extraNextKeyHashes] {string[]} the OTHER standing
376
- * credentials' rung-0 hashes for this segment, when the account has more
396
+ * credentials' rung-0 hashes for this generation id, when the account has
397
+ * more
377
398
  * than one; the minting credential's own carry-over hash is always included
378
- * @returns {Promise<{ did: string; segment: string; log: DIDLog;
399
+ * @param [options.capability] {IZcap} an invocation capability the mint
400
+ * rides -- the transient-recovery continuation minting its fresh generation
401
+ * through the credential's sibling delegation (the auxiliary Space's items
402
+ * subtree). The typed-Space ensure is then skipped: the delegation's target
403
+ * covers the collections beneath the Space, never the Space Description,
404
+ * and a standing sibling delegation presupposes the auxiliary Space
405
+ * @returns {Promise<{ did: string; generationId: string; log: DIDLog;
379
406
  * doc: DIDDoc }>}
380
407
  */
381
- export async function mintCredentialCompanionGeneration({ was, wasServerUrl, spaceId, controller, ladderSeed, extraNextKeyHashes = [] }) {
382
- await ensureCompanionSpace({ was, spaceId, controller });
383
- const segment = mintGenerationSegment();
384
- const rung = await companionRung({ ladderSeed, segment });
385
- return publishCompanionGenesis({
408
+ export async function mintCredentialClientAnnexGeneration({ was, wasServerUrl, spaceId, controller, ladderSeed, extraNextKeyHashes = [], capability }) {
409
+ if (capability === undefined) {
410
+ await ensureClientAnnexSpace({ was, spaceId, controller });
411
+ }
412
+ const generationId = mintGenerationId();
413
+ const rung = await clientAnnexRung({ ladderSeed, generationId });
414
+ return publishClientAnnexGenesis({
386
415
  was,
387
416
  wasServerUrl,
388
417
  spaceId,
389
- segment,
418
+ generationId,
390
419
  updateKeyPublicKeyMultibase: rung.keyMultibase,
391
420
  nextKeyHashes: [
392
421
  await deriveNextKeyHash(rung.keyMultibase),
393
422
  ...extraNextKeyHashes
394
423
  ],
395
- signer: await updateKeySigner({ seed: rung.seed })
424
+ signer: await updateKeySigner({ seed: rung.seed }),
425
+ ...(capability !== undefined ? { capability } : {})
396
426
  });
397
427
  }
398
428
  /**
399
429
  * The type IRI of the account document's delegated-clients service entry --
400
- * the pointer at the current companion generation's DID. Wire-level and
430
+ * the pointer at the current annex generation's DID. Wire-level and
401
431
  * permanent: readers (this module's {@link delegatedClientsPointer}, the
402
- * server's companion-chain inspector clause) dispatch on this IRI, never on
432
+ * server's annex-chain inspector clause) dispatch on this IRI, never on
403
433
  * the entry's fragment id, which is non-semantic by convention.
404
434
  */
405
435
  export const DELEGATED_CLIENTS_SERVICE_TYPE = 'https://w3id.org/byoe#DelegatedClients';
@@ -412,25 +442,25 @@ export const DELEGATED_CLIENTS_SERVICE_TYPE = 'https://w3id.org/byoe#DelegatedCl
412
442
  const DELEGATED_CLIENTS_SERVICE_FRAGMENT = 'delegated-clients';
413
443
  /**
414
444
  * Builds a fresh delegated-clients service entry for the account document.
415
- * The `serviceEndpoint` is the companion DID STRING, deliberately not a URL:
445
+ * The `serviceEndpoint` is the annex DID STRING, deliberately not a URL:
416
446
  * the DID is self-certifying and host-independent, and the account pointer
417
447
  * already carries the host.
418
448
  *
419
449
  * @param options {object}
420
450
  * @param options.accountDid {string} the account did:webvh
421
- * @param options.companionDid {string} the current generation's companion
451
+ * @param options.clientAnnexDid {string} the current generation's annex
422
452
  * DID
423
453
  * @returns {ServiceEndpoint}
424
454
  */
425
- export function delegatedClientsServiceEntry({ accountDid, companionDid }) {
455
+ export function delegatedClientsServiceEntry({ accountDid, clientAnnexDid }) {
426
456
  return {
427
457
  id: `${accountDid}#${DELEGATED_CLIENTS_SERVICE_FRAGMENT}`,
428
458
  type: DELEGATED_CLIENTS_SERVICE_TYPE,
429
- serviceEndpoint: companionDid
459
+ serviceEndpoint: clientAnnexDid
430
460
  };
431
461
  }
432
462
  /**
433
- * The companion DID the account document currently points at: the
463
+ * The annex DID the account document currently points at: the
434
464
  * `serviceEndpoint` of the service entry whose `type` names (or includes)
435
465
  * {@link DELEGATED_CLIENTS_SERVICE_TYPE}. Only a bare DID-string endpoint
436
466
  * counts -- the same predicate the server's inspector clause evaluates, so
@@ -450,9 +480,42 @@ export function delegatedClientsPointer({ doc }) {
450
480
  }
451
481
  return undefined;
452
482
  }
483
+ /**
484
+ * The account document's `service` array with the delegated-clients pointer
485
+ * set to `clientAnnexDid`. An existing pointer entry is re-pointed in place,
486
+ * its fragment id preserved verbatim (the id is non-semantic and stable);
487
+ * absent one, a fresh entry is appended. Every other service entry is carried
488
+ * through untouched.
489
+ *
490
+ * Shared by the two writers of the pointer: the standalone
491
+ * {@link setDelegatedClientsPointer} entry, and the transient-recovery
492
+ * continuation, which folds the pointer into its own add-and-retire entry so
493
+ * the pointer can never lag the entry that retires the standing ladder VMs.
494
+ *
495
+ * @param options {object}
496
+ * @param options.doc {DIDDoc} the current account document
497
+ * @param options.accountDid {string} the account did:webvh
498
+ * @param options.clientAnnexDid {string} the generation to point at
499
+ * @returns {ServiceEndpoint[]}
500
+ */
501
+ export function servicesPointedAtClientAnnex({ doc, accountDid, clientAnnexDid }) {
502
+ const existing = (doc.service ?? []);
503
+ const isPointerEntry = (entry) => {
504
+ const types = Array.isArray(entry.type) ? entry.type : [entry.type];
505
+ return types.includes(DELEGATED_CLIENTS_SERVICE_TYPE);
506
+ };
507
+ return existing.some(isPointerEntry)
508
+ ? existing.map(entry => isPointerEntry(entry)
509
+ ? { ...entry, serviceEndpoint: clientAnnexDid }
510
+ : entry)
511
+ : [
512
+ ...existing,
513
+ delegatedClientsServiceEntry({ accountDid, clientAnnexDid })
514
+ ];
515
+ }
453
516
  /**
454
517
  * The unlock-record sibling delegation's `allowedAction` set: GET beside PUT,
455
- * so an enrolling transient client can read the companion head it appends to.
518
+ * so an enrolling transient client can read the annex head it appends to.
456
519
  * Wire-level and permanent (wallet-core decision 0005): the server's
457
520
  * inspector clause admits a delegated-clients delegation with `allowedAction`
458
521
  * a subset of exactly this pair.
@@ -466,16 +529,17 @@ export const DELEGATED_CLIENTS_DELEGATION_ACTIONS = ['GET', 'PUT'];
466
529
  */
467
530
  export const DELEGATED_CLIENTS_DELEGATION_TTL_MS = STANDING_ZCAP_TTL_MS;
468
531
  /**
469
- * Mints one delegated-clients (companion-Space) delegation: the pre-minted
532
+ * Mints one delegated-clients (annex Space) delegation: the pre-minted
470
533
  * zcap sealed into a standing credential's unlock record beside the account
471
- * bridge, which is what lets a transient login reach the companion log with
534
+ * bridge, which is what lets a transient login reach the annex log with
472
535
  * nothing but the credential. The shape is a permanent wire artifact
473
536
  * (wallet-core decision 0005):
474
537
  *
475
- * - `invocationTarget` is the AUXILIARY companion Space's items subtree --
538
+ * - `invocationTarget` is the AUXILIARY annex Space's items subtree --
476
539
  * the Space URL with a trailing slash, built with was-client's paths
477
540
  * helpers so the bytes match the server's target check on a sub-path
478
- * deployment. Generation coverage comes from segment-bounded attenuation
541
+ * deployment. Generation coverage comes from generation-id-bounded
542
+ * attenuation
479
543
  * over the flat `gen-` collection names, so no GC cycle rewrites the
480
544
  * record or the registry.
481
545
  * - `controller` is the credential-derived signing DID (the same grantee
@@ -489,22 +553,22 @@ export const DELEGATED_CLIENTS_DELEGATION_TTL_MS = STANDING_ZCAP_TTL_MS;
489
553
  * enrolled client's promoted signer, or the account ladder VM)
490
554
  * @param options.wasServerUrl {string} the auxiliary Space's storage
491
555
  * server (the account pointer's host)
492
- * @param options.companionSpaceId {string} the auxiliary companion
556
+ * @param options.clientAnnexSpaceId {string} the auxiliary annex
493
557
  * Space's id
494
558
  * @param options.controller {string} the credential-derived signing DID
495
559
  * @param [options.now] {number} epoch milliseconds, for tests
496
560
  * @returns {Promise<IZcap>}
497
561
  */
498
- export async function mintDelegatedClientsDelegation({ zcapClient, wasServerUrl, companionSpaceId, controller, now = Date.now() }) {
562
+ export async function mintDelegatedClientsDelegation({ zcapClient, wasServerUrl, clientAnnexSpaceId, controller, now = Date.now() }) {
499
563
  const spaceUrl = toUrl({
500
564
  serverUrl: wasServerUrl,
501
- path: spacePath(companionSpaceId)
565
+ path: spacePath(clientAnnexSpaceId)
502
566
  });
503
567
  return (await zcapClient.delegate({
504
568
  capability: rootCapabilityId(spaceUrl),
505
569
  invocationTarget: toUrl({
506
570
  serverUrl: wasServerUrl,
507
- path: spaceItems(companionSpaceId)
571
+ path: spaceItems(clientAnnexSpaceId)
508
572
  }),
509
573
  controller,
510
574
  allowedActions: [...DELEGATED_CLIENTS_DELEGATION_ACTIONS],
@@ -512,9 +576,51 @@ export async function mintDelegatedClientsDelegation({ zcapClient, wasServerUrl,
512
576
  }));
513
577
  }
514
578
  /**
515
- * The auxiliary companion Space id a delegated-clients delegation targets,
579
+ * Builds the annex-side sibling-delegation minter the durable record re-mint
580
+ * orchestrator (`recovery/remintRecoveryDelegations`) takes as an injected
581
+ * closure -- the boundary keeping that base orchestrator free of annex
582
+ * imports. The returned closure reads the auxiliary annex Space id off the
583
+ * verified document's delegated-clients service entry (the annex DID string
584
+ * embeds it) and mints a fresh {@link mintDelegatedClientsDelegation} to the
585
+ * named controller; it resolves `undefined` while the document points at no
586
+ * generation, which the orchestrator reads as "carry the old sealed member
587
+ * verbatim".
588
+ *
589
+ * @param options {object}
590
+ * @param options.doc {object} the locally verified account document
591
+ * @param options.zcapClient {ZcapClient} the acting client's promoted
592
+ * signer, which mints the fresh delegations
593
+ * @param options.wasServerUrl {string} the auxiliary Space's storage
594
+ * server (the account pointer's host)
595
+ * @returns {Function} `({ controller }) => Promise<IZcap | undefined>`
596
+ */
597
+ export function delegatedClientsDelegationMinter({ doc, zcapClient, wasServerUrl }) {
598
+ return async ({ controller }) => {
599
+ const clientAnnexDid = delegatedClientsPointer({
600
+ doc: doc
601
+ });
602
+ if (!clientAnnexDid) {
603
+ return undefined;
604
+ }
605
+ let clientAnnexSpaceId;
606
+ try {
607
+ clientAnnexSpaceId = clientAnnexDidParts({ did: clientAnnexDid }).spaceId;
608
+ }
609
+ catch {
610
+ return undefined;
611
+ }
612
+ return mintDelegatedClientsDelegation({
613
+ zcapClient,
614
+ wasServerUrl,
615
+ clientAnnexSpaceId,
616
+ controller
617
+ });
618
+ };
619
+ }
620
+ /**
621
+ * The auxiliary annex Space id a delegated-clients delegation targets,
516
622
  * read out of its `invocationTarget` (the items-subtree URL,
517
- * `.../space/<companionSpaceId>/`). The id has no other home -- a transient
623
+ * `.../space/<clientAnnexSpaceId>/`). The id has no other home -- a transient
518
624
  * login learns the Space from the delegation it unwraps, and a refresh pass
519
625
  * that holds the old delegation rebuilds the target from it -- so this parse
520
626
  * is the one reader. Returns `undefined` on anything that is not an
@@ -548,7 +654,7 @@ export function delegatedClientsDelegationSpaceId({ delegation }) {
548
654
  return spaceId ? decodeURIComponent(spaceId) : undefined;
549
655
  }
550
656
  /**
551
- * The type IRI of the companion document's generation-delegation service
657
+ * The type IRI of the annex document's generation-delegation service
552
658
  * entry -- the generation's standing Space-scoped zcap, embedded where an
553
659
  * enrolling transient client can reach it before it holds any other
554
660
  * authority. Wire-level and permanent: readers (this module's
@@ -601,8 +707,8 @@ export const GENERATION_DELEGATION_TTL_MS = STANDING_ZCAP_TTL_MS;
601
707
  * bytes match the server's target check on a sub-path deployment. The
602
708
  * bare Space URL sits outside the capability bytes (see
603
709
  * {@link GENERATION_DELEGATION_ACTIONS} for what that excludes).
604
- * - `controller` is the bare companion DID string. Transient keys invoke as
605
- * `<companionDid>#<vm>`, and the server's inspector clause compares this
710
+ * - `controller` is the bare annex DID string. Transient keys invoke as
711
+ * `<clientAnnexDid>#<vm>`, and the server's inspector clause compares this
606
712
  * string against the account document's delegated-clients pointer.
607
713
  * - The chain is rooted directly in the account Space's root zcap, so an
608
714
  * App Connect grant delegated under it forms the depth-3 chain
@@ -618,12 +724,12 @@ export const GENERATION_DELEGATION_TTL_MS = STANDING_ZCAP_TTL_MS;
618
724
  * or a durable client's promoted signer)
619
725
  * @param options.wasServerUrl {string} the ACCOUNT Space's storage server
620
726
  * @param options.spaceId {string} the ACCOUNT Space's id
621
- * @param options.companionDid {string} the generation's companion DID
727
+ * @param options.clientAnnexDid {string} the generation's annex DID
622
728
  * @param [options.now] {number} epoch milliseconds, for tests
623
729
  * @returns {Promise<IZcap>}
624
730
  */
625
- export async function mintGenerationDelegation({ zcapClient, wasServerUrl, spaceId, companionDid, now = Date.now() }) {
626
- companionDidParts({ did: companionDid });
731
+ export async function mintGenerationDelegation({ zcapClient, wasServerUrl, spaceId, clientAnnexDid, now = Date.now() }) {
732
+ clientAnnexDidParts({ did: clientAnnexDid });
627
733
  const spaceUrl = toUrl({ serverUrl: wasServerUrl, path: spacePath(spaceId) });
628
734
  return (await zcapClient.delegate({
629
735
  capability: rootCapabilityId(spaceUrl),
@@ -631,7 +737,7 @@ export async function mintGenerationDelegation({ zcapClient, wasServerUrl, space
631
737
  serverUrl: wasServerUrl,
632
738
  path: spaceItems(spaceId)
633
739
  }),
634
- controller: companionDid,
740
+ controller: clientAnnexDid,
635
741
  allowedActions: [...GENERATION_DELEGATION_ACTIONS],
636
742
  expires: new Date(now + GENERATION_DELEGATION_TTL_MS)
637
743
  }));
@@ -661,33 +767,33 @@ export function clampGrantExpires({ ttlMs, delegation, now = Date.now() }) {
661
767
  return new Date(Math.min(now + ttlMs, parentExpires));
662
768
  }
663
769
  /**
664
- * Builds a fresh generation-delegation service entry for a companion
770
+ * Builds a fresh generation-delegation service entry for an annex
665
771
  * document. The `serviceEndpoint` is the full delegated-zcap JSON as a
666
772
  * single map, byte-identical to what `zcapClient.delegate` produced -- the
667
- * companion entry proof (JCS canonicalization) then covers it byte for byte,
773
+ * annex entry proof (JCS canonicalization) then covers it byte for byte,
668
774
  * so host tampering with the stored delegation is client-visible.
669
775
  *
670
776
  * @param options {object}
671
- * @param options.companionDid {string} the generation's companion DID
777
+ * @param options.clientAnnexDid {string} the generation's annex DID
672
778
  * @param options.delegation {IZcap} the minted generation delegation
673
779
  * @returns {ServiceEndpoint}
674
780
  */
675
- export function generationDelegationServiceEntry({ companionDid, delegation }) {
781
+ export function generationDelegationServiceEntry({ clientAnnexDid, delegation }) {
676
782
  return {
677
- id: `${companionDid}#${GENERATION_DELEGATION_SERVICE_FRAGMENT}`,
783
+ id: `${clientAnnexDid}#${GENERATION_DELEGATION_SERVICE_FRAGMENT}`,
678
784
  type: GENERATION_DELEGATION_SERVICE_TYPE,
679
785
  serviceEndpoint: delegation
680
786
  };
681
787
  }
682
788
  /**
683
- * The generation delegation a companion document carries: the
789
+ * The generation delegation an annex document carries: the
684
790
  * `serviceEndpoint` map of the service entry whose `type` names (or
685
791
  * includes) {@link GENERATION_DELEGATION_SERVICE_TYPE}. Only a map-form
686
792
  * endpoint counts (the delegation is embedded as the zcap JSON itself,
687
793
  * never as a URL or an encoded string).
688
794
  *
689
795
  * @param options {object}
690
- * @param options.doc {DIDDoc} the resolved (and verified) companion
796
+ * @param options.doc {DIDDoc} the resolved (and verified) annex
691
797
  * document
692
798
  * @returns {IZcap | undefined}
693
799
  */
@@ -706,18 +812,77 @@ export function embeddedGenerationDelegation({ doc }) {
706
812
  return undefined;
707
813
  }
708
814
  /**
709
- * The companion document's service list with the generation delegation
815
+ * Every generation delegation a generation's log has ever embedded, in log
816
+ * order and deduplicated by zcap id -- the annex-log HISTORY WALK the
817
+ * last-durable-client forget revokes from (decision 0004's 2026-08-19
818
+ * amendment): a renewal replaces the head service entry's endpoint in place,
819
+ * so a superseded delegation's bytes survive only in earlier entries'
820
+ * re-stated full state, and a renewal inside the 30-day window can leave TWO
821
+ * still-unexpired ladder-signed delegations. The caller filters (signer,
822
+ * expiry) and revokes; this walk only recovers the bytes.
823
+ *
824
+ * @param options {object}
825
+ * @param options.log {DIDLog} the generation's VERIFIED log
826
+ * @returns {IZcap[]}
827
+ */
828
+ export function generationDelegationHistory({ log }) {
829
+ const seen = new Set();
830
+ const delegations = [];
831
+ for (const entry of log) {
832
+ const embedded = embeddedGenerationDelegation({
833
+ doc: entry.state
834
+ });
835
+ if (embedded === undefined) {
836
+ continue;
837
+ }
838
+ const id = embedded.id;
839
+ if (typeof id !== 'string' || seen.has(id)) {
840
+ continue;
841
+ }
842
+ seen.add(id);
843
+ delegations.push(embedded);
844
+ }
845
+ return delegations;
846
+ }
847
+ /**
848
+ * Submits the revocation of a generation delegation, reading the server's
849
+ * 400 answer as success: an already-revoked chain (a resumed ceremony's
850
+ * blind re-POST) and an expired delegation (which no longer needs revoking)
851
+ * both land there, and the revocation protocol exposes no read endpoint to
852
+ * distinguish them beforehand. Matched on `err.name` -- error classes do not
853
+ * survive crossing package copies. The `revoke` seam is was-client's
854
+ * `WasClient#revoke`, bound by the caller.
855
+ *
856
+ * @param options {object}
857
+ * @param options.revoke {Function} `(delegation) => Promise<void>` --
858
+ * POSTs the revocation (`was.revoke`)
859
+ * @param options.delegation {IZcap}
860
+ * @returns {Promise<void>}
861
+ */
862
+ export async function revokeTreatingAlreadyRevokedAsSuccess({ revoke, delegation }) {
863
+ try {
864
+ await revoke(delegation);
865
+ }
866
+ catch (err) {
867
+ if (err.name === 'ValidationError') {
868
+ return;
869
+ }
870
+ throw err;
871
+ }
872
+ }
873
+ /**
874
+ * The annex document's service list with the generation delegation
710
875
  * installed: an existing entry's endpoint is replaced in place, its fragment
711
876
  * id preserved verbatim (the id is non-semantic and stable); absent one, a
712
877
  * fresh entry is appended. Every other service entry is preserved untouched.
713
878
  *
714
879
  * @param options {object}
715
- * @param options.doc {DIDDoc} the companion document as published
716
- * @param options.companionDid {string}
880
+ * @param options.doc {DIDDoc} the annex document as published
881
+ * @param options.clientAnnexDid {string}
717
882
  * @param options.delegation {IZcap}
718
883
  * @returns {ServiceEndpoint[]}
719
884
  */
720
- function withGenerationDelegationEntry({ doc, companionDid, delegation }) {
885
+ function withGenerationDelegationEntry({ doc, clientAnnexDid, delegation }) {
721
886
  const existing = (doc.service ?? []);
722
887
  const isDelegationEntry = (entry) => {
723
888
  const types = Array.isArray(entry.type) ? entry.type : [entry.type];
@@ -732,65 +897,66 @@ function withGenerationDelegationEntry({ doc, companionDid, delegation }) {
732
897
  : entry)
733
898
  : [
734
899
  ...existing,
735
- generationDelegationServiceEntry({ companionDid, delegation })
900
+ generationDelegationServiceEntry({ clientAnnexDid, delegation })
736
901
  ];
737
902
  }
738
903
  /**
739
- * Parses the auxiliary Space id and generation segment out of a companion DID
740
- * string. Both are permanent substrings of every companion DID by
741
- * construction (`did:webvh:<scid>:<host>:...:space:<spaceId>:<segment>`), and
742
- * the segment is the generation-identifying half of the companion rung HKDF
904
+ * Parses the auxiliary Space id and generation id out of an annex DID
905
+ * string. Both are permanent substrings of every annex DID by
906
+ * construction: the generation id is the final path segment of the annex
907
+ * DID (`did:webvh:<scid>:<host>:...:space:<spaceId>:<generationId>`), and it
908
+ * is the generation-identifying half of the annex rung HKDF
743
909
  * labels, so this parse is what lets an enrollee derive its writing key from
744
910
  * the pointer alone -- no log read, no registry.
745
911
  *
746
912
  * @param options {object}
747
- * @param options.did {string} a companion did:webvh string
748
- * @returns {{ spaceId: string, segment: string }}
913
+ * @param options.did {string} an annex did:webvh string
914
+ * @returns {{ spaceId: string, generationId: string }}
749
915
  */
750
- export function companionDidParts({ did }) {
916
+ export function clientAnnexDidParts({ did }) {
751
917
  const parts = did.split(':');
752
- const segment = parts[parts.length - 1];
918
+ const generationId = parts[parts.length - 1];
753
919
  const spaceId = parts[parts.length - 2];
754
920
  if (parts.length < 7 ||
755
921
  parts[0] !== 'did' ||
756
922
  parts[1] !== 'webvh' ||
757
923
  parts[parts.length - 3] !== 'space' ||
758
- segment === undefined ||
924
+ generationId === undefined ||
759
925
  spaceId === undefined ||
760
926
  spaceId.length === 0) {
761
- throw new Error(`Not a companion did:webvh: "${did}".`);
927
+ throw new Error(`Not a client annex did:webvh: "${did}".`);
762
928
  }
763
- assertGenerationSegment(segment);
764
- return { spaceId, segment };
929
+ assertGenerationId(generationId);
930
+ return { spaceId, generationId };
765
931
  }
766
932
  /**
767
- * Thrown when the published companion log commits neither the writing
933
+ * Thrown when the published annex log commits neither the writing
768
934
  * credential's rung-0 key nor its hash -- the mid-generation lockout: a
769
- * credential bound after the generation's genesis cannot write the companion
935
+ * credential bound after the generation's genesis cannot write the annex
770
936
  * until an existing writer commits its rung-0 hash or the next GC swap's
771
937
  * genesis does. Typed so callers can map it to the fresh-generation path
772
938
  * where one is licensed (the transient-recovery continuation) or to honest
773
939
  * copy where none is.
774
940
  */
775
- export class CompanionRungUncommittedError extends Error {
941
+ export class ClientAnnexRungUncommittedError extends Error {
776
942
  constructor(message) {
777
943
  super(message);
778
- this.name = 'CompanionRungUncommittedError';
944
+ this.name = 'ClientAnnexRungUncommittedError';
779
945
  }
780
946
  }
781
947
  /**
782
- * Reads and resolves the published companion log through the narrow seam, or
948
+ * Reads and resolves the published annex log through the narrow seam, or
783
949
  * throws when the generation's `did.jsonl` is missing (an unpointed or
784
950
  * deleted generation -- nothing to enroll into).
785
951
  *
786
952
  * @param options {object}
787
- * @param options.store {CompanionWriteStore}
953
+ * @param options.store {ClientAnnexWriteStore}
788
954
  * @param [options.expectedDid] {string}
789
955
  * @param [options.pinStore] {ResourceLogPinStore}
790
956
  * @param [options.logId] {string}
791
957
  * @returns {Promise<PublishedWebvhLog>}
792
958
  */
793
- async function readCompanionLogOrThrow({ store, expectedDid, pinStore, logId }) {
959
+ async function readClientAnnexLogOrThrow({ store, expectedDid, pinStore, logId }) {
794
960
  // readPublishedLog only calls getIdResourceRaw, so the narrow seam is safe.
795
961
  const published = await readPublishedLog({
796
962
  idStore: store,
@@ -799,18 +965,19 @@ async function readCompanionLogOrThrow({ store, expectedDid, pinStore, logId })
799
965
  ...(logId !== undefined ? { logId } : {})
800
966
  });
801
967
  if (!published) {
802
- throw new Error('companion: did.jsonl is missing; the generation was never minted or ' +
968
+ throw new Error('client annex: did.jsonl is missing; the generation was never minted or ' +
803
969
  'has been collected.');
804
970
  }
805
971
  return published;
806
972
  }
807
973
  /**
808
974
  * TRANSIENT ENROLLMENT: publishes one per-visit verification method into a
809
- * companion generation's log -- one atomic entry, signed by the writing
810
- * credential's static rung 0 (derived from the ladder seed and the segment;
811
- * see `companionRung`). The entry:
975
+ * annex generation's log -- one atomic entry, signed by the writing
976
+ * credential's static rung 0 (derived from the ladder seed and the generation
977
+ * id;
978
+ * see `clientAnnexRung`). The entry:
812
979
  *
813
- * - reveals the writer's rung-0 key into `updateKeys` at its first companion
980
+ * - reveals the writer's rung-0 key into `updateKeys` at its first annex
814
981
  * write (later writes re-state it unchanged);
815
982
  * - re-states `nextKeyHashes` verbatim -- every standing credential's rung-0
816
983
  * hash, the writer's own carry-over hash included -- explicitly on the
@@ -819,7 +986,7 @@ async function readCompanionLogOrThrow({ store, expectedDid, pinStore, logId })
819
986
  * relationship arrays stated explicitly (no `authentication`, no
820
987
  * `assertionMethod`, no `keyAgreement` twin -- the DIDAuth path signs as
821
988
  * the bare did:key, and the controller-marker convention does not arise in
822
- * the companion at all).
989
+ * the annex at all).
823
990
  *
824
991
  * The transient key set carries no update key, and nothing here touches the
825
992
  * ACCOUNT log's `updateKeys` or `nextKeyHashes`. There is no two-entry
@@ -828,54 +995,54 @@ async function readCompanionLogOrThrow({ store, expectedDid, pinStore, logId })
828
995
  * published document's own state -- a VM already present is a no-op.
829
996
  *
830
997
  * A writer whose rung-0 key is neither revealed nor committed is refused
831
- * ({@link CompanionRungUncommittedError}): companion entries verify against
998
+ * ({@link ClientAnnexRungUncommittedError}): annex entries verify against
832
999
  * the log's own hash-commitment chain, so no admission rule can make an
833
1000
  * uncommitted key verify mid-log.
834
1001
  *
835
1002
  * @param options {object}
836
- * @param options.store {CompanionWriteStore} the generation's log store
1003
+ * @param options.store {ClientAnnexWriteStore} the generation's log store
837
1004
  * (delegated through the credential's sibling delegation, or
838
1005
  * controller-tier)
839
1006
  * @param options.ladderSeed {Uint8Array} the credential's ladder seed, from
840
1007
  * its unlock record
841
- * @param options.segment {string} the generation collection's name
1008
+ * @param options.generationId {string} the generation collection's name
842
1009
  * @param options.transientKeyMultibase {string} the visit's in-memory
843
1010
  * Ed25519 signing key, public multibase
844
- * @param [options.services] {ServiceEndpoint[]} the companion document's
1011
+ * @param [options.services] {ServiceEndpoint[]} the annex document's
845
1012
  * full service-entry list, replacing the published one wholesale; omitted,
846
1013
  * the prior entries are preserved verbatim (or extended by
847
1014
  * `mintGenerationDelegation` below). Supplying both is refused in favor of
848
1015
  * the explicit list
849
1016
  * @param [options.mintGenerationDelegation] {Function}
850
- * `({ companionDid }) => Promise<IZcap>` -- mints the generation
1017
+ * `({ clientAnnexDid }) => Promise<IZcap>` -- mints the generation
851
1018
  * delegation this entry installs when it publishes the generation's FIRST
852
1019
  * transient verification method (and the document carries no delegation
853
1020
  * entry yet). Never invoked otherwise: the delegation is installed with
854
1021
  * the first transient VM or by the GC ceremony's own install stage, never
855
1022
  * by genesis (a genesis-embedded signed zcap can never verify -- its
856
1023
  * `controller` embeds the SCID the genesis hash derives from)
857
- * @param [options.expectedDid] {string} the companion DID the log must
1024
+ * @param [options.expectedDid] {string} the annex DID the log must
858
1025
  * resolve to, from the account document's pointer
859
1026
  * @param [options.pinStore] {ResourceLogPinStore} chain-head pins (a
860
1027
  * transient session passes an in-memory store)
861
1028
  * @param [options.logId] {string} the generation's pin-slot key, from
862
- * {@link companionLogPinId}; required whenever a `pinStore` is supplied
1029
+ * {@link clientAnnexLogPinId}; required whenever a `pinStore` is supplied
863
1030
  * @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog }>}
864
1031
  */
865
- export async function enrollCompanionTransientClient(options) {
866
- return withLogConflictRetry(() => enrollCompanionTransientClientOnce(options));
1032
+ export async function enrollClientAnnexTransientClient(options) {
1033
+ return withLogConflictRetry(() => enrollClientAnnexTransientClientOnce(options));
867
1034
  }
868
1035
  /**
869
- * One attempt of {@link enrollCompanionTransientClient}, re-invoked by the
1036
+ * One attempt of {@link enrollClientAnnexTransientClient}, re-invoked by the
870
1037
  * conflict retry (with the same signing key -- static rung 0 has no
871
1038
  * advanced-rung retry shape).
872
1039
  *
873
- * @param options {object} see {@link enrollCompanionTransientClient}
1040
+ * @param options {object} see {@link enrollClientAnnexTransientClient}
874
1041
  * @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog }>}
875
1042
  */
876
- async function enrollCompanionTransientClientOnce({ store, ladderSeed, segment, transientKeyMultibase, services, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId }) {
877
- assertGenerationSegment(segment);
878
- const published = await readCompanionLogOrThrow({
1043
+ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generationId, transientKeyMultibase, services, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId }) {
1044
+ assertGenerationId(generationId);
1045
+ const published = await readClientAnnexLogOrThrow({
879
1046
  store,
880
1047
  ...(expectedDid !== undefined ? { expectedDid } : {}),
881
1048
  ...(pinStore !== undefined ? { pinStore } : {}),
@@ -890,13 +1057,13 @@ async function enrollCompanionTransientClientOnce({ store, ladderSeed, segment,
890
1057
  if (existingMethods.some(method => method.id === vmId)) {
891
1058
  return { did, doc, log: published.log };
892
1059
  }
893
- const rung = await companionRung({ ladderSeed, segment });
1060
+ const rung = await clientAnnexRung({ ladderSeed, generationId });
894
1061
  const rungHash = await deriveNextKeyHash(rung.keyMultibase);
895
1062
  const revealed = published.updateKeys.includes(rung.keyMultibase);
896
1063
  if (!revealed && !published.nextKeyHashes.includes(rungHash)) {
897
- throw new CompanionRungUncommittedError("companion: the log commits neither this credential's rung-0 key nor " +
1064
+ throw new ClientAnnexRungUncommittedError("client annex: the log commits neither this credential's rung-0 key nor " +
898
1065
  'its hash; a credential bound mid-generation cannot write the ' +
899
- 'companion until a writer commits its hash or the next GC swap does.');
1066
+ 'annex until a writer commits its hash or the next GC swap does.');
900
1067
  }
901
1068
  // A non-rotating entry re-states `updateKeys`, which the resolver checks
902
1069
  // against the previous entry's commitments -- genesis enforces the
@@ -910,10 +1077,10 @@ async function enrollCompanionTransientClientOnce({ store, ladderSeed, segment,
910
1077
  mintDelegation !== undefined &&
911
1078
  existingMethods.length === 0 &&
912
1079
  embeddedGenerationDelegation({ doc }) === undefined) {
913
- const delegation = await mintDelegation({ companionDid: did });
1080
+ const delegation = await mintDelegation({ clientAnnexDid: did });
914
1081
  services = withGenerationDelegationEntry({
915
1082
  doc,
916
- companionDid: did,
1083
+ clientAnnexDid: did,
917
1084
  delegation
918
1085
  });
919
1086
  }
@@ -947,17 +1114,17 @@ async function enrollCompanionTransientClientOnce({ store, ladderSeed, segment,
947
1114
  capabilityDelegation: relationIds(doc.capabilityDelegation),
948
1115
  ...(services !== undefined ? { services } : {})
949
1116
  });
950
- // The log only -- a companion has no did:web projection -- conditional on
1117
+ // The log only -- an annex has no did:web projection -- conditional on
951
1118
  // the read this entry was built on.
952
1119
  await putLogResource({ store, log: updated.log, ifMatch: published.etag });
953
1120
  return { did: updated.did, doc: updated.doc, log: updated.log };
954
1121
  }
955
1122
  /**
956
1123
  * Points the account document's delegated-clients service entry at a
957
- * companion DID -- the first install after a generation's genesis, and the GC
1124
+ * annex DID -- the first install after a generation's genesis, and the GC
958
1125
  * swap's re-point alike. One ordinary document-update entry, signed by an
959
- * enrolled durable client's active update key; the companion log always
960
- * publishes FIRST (see {@link mintCompanionGeneration}), so a tear leaves an
1126
+ * enrolled durable client's active update key; the annex log always
1127
+ * publishes FIRST (see {@link mintClientAnnexGeneration}), so a tear leaves an
961
1128
  * unpointed, authorization-inert generation, never a dangling pointer.
962
1129
  *
963
1130
  * An existing delegated-clients entry is re-pointed in place, its fragment id
@@ -968,10 +1135,14 @@ async function enrollCompanionTransientClientOnce({ store, ladderSeed, segment,
968
1135
  * a no-op on the log (it still heals a lagging `did.json`).
969
1136
  *
970
1137
  * @param options {object}
971
- * @param options.idStore {WebvhIdStore} the ACCOUNT log's store
1138
+ * @param options.idStore {WebvhIdStore} the ACCOUNT log's store; with
1139
+ * `logOnly`, only its log read and `did.jsonl` PUT are used, so the narrow
1140
+ * delegated seam satisfies it
972
1141
  * @param options.updateKeys {ClientWebvhUpdateKeys} this durable client's
973
- * update-key seeds
974
- * @param options.companionDid {string} the generation to point at
1142
+ * update-key seeds -- or the ladder-rung idiom on a ladder-anchored
1143
+ * account (`{ updateSeed: rung0.seed, stagedSeed: rung1.seed }`), as the
1144
+ * credential-anchored genesis and the transient-recovery continuation pass
1145
+ * @param options.clientAnnexDid {string} the generation to point at
975
1146
  * @param [options.expectedDid] {string} the account DID the log must
976
1147
  * resolve to, from the account pointer
977
1148
  * @param [options.pinStore] {ResourceLogPinStore} this client's chain-head
@@ -979,6 +1150,11 @@ async function enrollCompanionTransientClientOnce({ store, ladderSeed, segment,
979
1150
  * @param [options.logId] {string} the account log's pin-slot key, from
980
1151
  * `accountLogPinId({ spaceId })`; required whenever a `pinStore` is
981
1152
  * supplied
1153
+ * @param [options.logOnly] {boolean} publish `did.jsonl` only, never the
1154
+ * `did.json` projection -- the transient-recovery continuation writing
1155
+ * through the record's bridge delegation, whose narrow scope covers nothing
1156
+ * but the log. The projection heals at the next authorized write (the log
1157
+ * is the source of truth)
982
1158
  * @returns {Promise<{ did: string, doc: DIDDoc }>}
983
1159
  */
984
1160
  export async function setDelegatedClientsPointer(options) {
@@ -991,9 +1167,9 @@ export async function setDelegatedClientsPointer(options) {
991
1167
  * @param options {object} see {@link setDelegatedClientsPointer}
992
1168
  * @returns {Promise<{ did: string, doc: DIDDoc }>}
993
1169
  */
994
- async function setDelegatedClientsPointerOnce({ idStore, updateKeys, companionDid, expectedDid, pinStore, logId }) {
1170
+ async function setDelegatedClientsPointerOnce({ idStore, updateKeys, clientAnnexDid, expectedDid, pinStore, logId, logOnly = false }) {
995
1171
  // Refuses a malformed target before anything is read or written.
996
- companionDidParts({ did: companionDid });
1172
+ clientAnnexDidParts({ did: clientAnnexDid });
997
1173
  const published = await readPublishedLog({
998
1174
  idStore,
999
1175
  ...(expectedDid !== undefined ? { expectedDid } : {}),
@@ -1001,11 +1177,13 @@ async function setDelegatedClientsPointerOnce({ idStore, updateKeys, companionDi
1001
1177
  ...(logId !== undefined ? { logId } : {})
1002
1178
  });
1003
1179
  if (!published) {
1004
- throw new Error('did:webvh: did.jsonl is missing; nothing to point at a companion.');
1180
+ throw new Error('did:webvh: did.jsonl is missing; nothing to point at a client annex.');
1005
1181
  }
1006
1182
  const { did, doc } = published;
1007
- if (delegatedClientsPointer({ doc }) === companionDid) {
1008
- await concludeWithPublishedLog({ idStore, published });
1183
+ if (delegatedClientsPointer({ doc }) === clientAnnexDid) {
1184
+ if (!logOnly) {
1185
+ await concludeWithPublishedLog({ idStore, published });
1186
+ }
1009
1187
  return { did, doc };
1010
1188
  }
1011
1189
  // The entry is signed by this client's active update key; a log that does
@@ -1017,19 +1195,11 @@ async function setDelegatedClientsPointerOnce({ idStore, updateKeys, companionDi
1017
1195
  'delegated-clients entry.');
1018
1196
  }
1019
1197
  await assertCarryOverCommitments({ published });
1020
- const existing = (doc.service ?? []);
1021
- const isPointerEntry = (entry) => {
1022
- const types = Array.isArray(entry.type) ? entry.type : [entry.type];
1023
- return types.includes(DELEGATED_CLIENTS_SERVICE_TYPE);
1024
- };
1025
- const services = existing.some(isPointerEntry)
1026
- ? existing.map(entry => isPointerEntry(entry)
1027
- ? { ...entry, serviceEndpoint: companionDid }
1028
- : entry)
1029
- : [
1030
- ...existing,
1031
- delegatedClientsServiceEntry({ accountDid: did, companionDid })
1032
- ];
1198
+ const services = servicesPointedAtClientAnnex({
1199
+ doc,
1200
+ accountDid: did,
1201
+ clientAnnexDid
1202
+ });
1033
1203
  const signer = await updateKeySigner({ seed: updateKeys.updateSeed });
1034
1204
  const updated = await updateDID({
1035
1205
  log: published.log,
@@ -1042,7 +1212,16 @@ async function setDelegatedClientsPointerOnce({ idStore, updateKeys, companionDi
1042
1212
  nextKeyHashes: published.nextKeyHashes,
1043
1213
  services
1044
1214
  });
1045
- await publishUpdatedLog({ idStore, updated, ifMatch: published.etag });
1215
+ if (logOnly) {
1216
+ await putLogResource({
1217
+ store: idStore,
1218
+ log: updated.log,
1219
+ ifMatch: published.etag
1220
+ });
1221
+ }
1222
+ else {
1223
+ await publishUpdatedLog({ idStore, updated, ifMatch: published.etag });
1224
+ }
1046
1225
  return { did: updated.did, doc: updated.doc };
1047
1226
  }
1048
1227
  /**
@@ -1060,76 +1239,88 @@ async function setDelegatedClientsPointerOnce({ idStore, updateKeys, companionDi
1060
1239
  * @param options.readAccountDocument {Function} reads the VERIFIED account
1061
1240
  * document (the caller's `verifyAccountLog` read, pins and `expectedDid`
1062
1241
  * applied there); called once per round
1063
- * @param options.storeForSegment {Function} builds the generation's log
1064
- * store for a segment (the delegated store over the credential's sibling
1242
+ * @param options.storeForGenerationId {Function} builds the generation's log
1243
+ * store for a generation id (the delegated store over the credential's
1244
+ * sibling
1065
1245
  * delegation, or a controller-tier store)
1066
1246
  * @param options.ladderSeed {Uint8Array} the credential's ladder seed
1067
1247
  * @param options.transientKeyMultibase {string} the visit's in-memory
1068
1248
  * signing key, public multibase
1069
1249
  * @param [options.mintGenerationDelegation] {Function}
1070
- * `({ companionDid }) => Promise<IZcap>` -- forwarded to the enrollment
1250
+ * `({ clientAnnexDid }) => Promise<IZcap>` -- forwarded to the enrollment
1071
1251
  * entry, which installs the minted delegation when it publishes the
1072
1252
  * generation's first transient VM (see
1073
- * {@link enrollCompanionTransientClient}). The closure receives whichever
1074
- * companion DID the round enrolls into, so a GC-race re-enroll mints for
1253
+ * {@link enrollClientAnnexTransientClient}). The closure receives whichever
1254
+ * annex DID the round enrolls into, so a GC-race re-enroll mints for
1075
1255
  * the fresh generation
1076
1256
  * @param [options.pinStore] {ResourceLogPinStore} chain-head pins for the
1077
1257
  * generation logs (a transient session passes an in-memory store); slot
1078
- * keys are derived per generation with {@link companionLogPinId}
1258
+ * keys are derived per generation with {@link clientAnnexLogPinId}
1079
1259
  * @param [options.maxRounds] {number} how many pointer moves to chase
1080
1260
  * before giving up (a GC pass is quarterly, so more than one mid-ceremony
1081
1261
  * move means something else is wrong)
1082
- * @returns {Promise<{ companionDid: string, doc: DIDDoc, log: DIDLog }>}
1262
+ * @returns {Promise<{ clientAnnexDid: string, doc: DIDDoc, log: DIDLog }>}
1083
1263
  */
1084
- export async function enrollTransientClient({ readAccountDocument, storeForSegment, ladderSeed, transientKeyMultibase, mintGenerationDelegation: mintDelegation, pinStore, maxRounds = 3 }) {
1264
+ export async function enrollTransientClient({ readAccountDocument, storeForGenerationId, ladderSeed, transientKeyMultibase, mintGenerationDelegation: mintDelegation, pinStore, maxRounds = 3 }) {
1085
1265
  let accountDoc = await readAccountDocument();
1086
1266
  for (let round = 0; round < maxRounds; round++) {
1087
- const companionDid = delegatedClientsPointer({ doc: accountDoc });
1088
- if (companionDid === undefined) {
1089
- throw new Error('companion: the account document carries no delegated-clients ' +
1267
+ const clientAnnexDid = delegatedClientsPointer({ doc: accountDoc });
1268
+ if (clientAnnexDid === undefined) {
1269
+ throw new Error('client annex: the account document carries no delegated-clients ' +
1090
1270
  'service entry; no generation exists to enroll into.');
1091
1271
  }
1092
- const { spaceId, segment } = companionDidParts({ did: companionDid });
1093
- const enrolled = await enrollCompanionTransientClient({
1094
- store: storeForSegment(segment),
1272
+ const { spaceId, generationId } = clientAnnexDidParts({
1273
+ did: clientAnnexDid
1274
+ });
1275
+ const enrolled = await enrollClientAnnexTransientClient({
1276
+ store: storeForGenerationId(generationId),
1095
1277
  ladderSeed,
1096
- segment,
1278
+ generationId,
1097
1279
  transientKeyMultibase,
1098
- expectedDid: companionDid,
1280
+ expectedDid: clientAnnexDid,
1099
1281
  ...(mintDelegation !== undefined
1100
1282
  ? { mintGenerationDelegation: mintDelegation }
1101
1283
  : {}),
1102
1284
  ...(pinStore !== undefined
1103
- ? { pinStore, logId: companionLogPinId({ spaceId, segment }) }
1285
+ ? { pinStore, logId: clientAnnexLogPinId({ spaceId, generationId }) }
1104
1286
  : {})
1105
1287
  });
1106
1288
  // The GC-race re-read: an unchanged pointer means the enrollment stands
1107
1289
  // in the pointed generation; a moved one means a concurrent GC abandoned
1108
1290
  // it, and the next round enrolls into the fresh generation.
1109
1291
  accountDoc = await readAccountDocument();
1110
- if (delegatedClientsPointer({ doc: accountDoc }) === companionDid) {
1111
- return { companionDid, doc: enrolled.doc, log: enrolled.log };
1292
+ if (delegatedClientsPointer({ doc: accountDoc }) === clientAnnexDid) {
1293
+ return { clientAnnexDid, doc: enrolled.doc, log: enrolled.log };
1112
1294
  }
1113
1295
  }
1114
- throw new Error('companion: the delegated-clients pointer kept moving across ' +
1296
+ throw new Error('client annex: the delegated-clients pointer kept moving across ' +
1115
1297
  `${String(maxRounds)} enrollment rounds; giving up.`);
1116
1298
  }
1117
1299
  /**
1118
1300
  * RENEW PRECEDES MINT: the blocking pre-mint stage a transient App Connect
1119
- * approval runs before delegating any grant. Reads the companion document
1301
+ * approval runs before delegating any grant. Reads the annex document
1120
1302
  * and hands back its embedded generation delegation -- renewing it first
1121
1303
  * when it is expired or inside the 30-day renewal window ({@link
1122
1304
  * zcapExpiring}): a fresh delegation is minted through the caller's closure
1123
1305
  * (ladder-signed -- the renewal must not depend on the very delegation it
1124
1306
  * replaces; published through the store, which in a transient session is
1125
1307
  * the credential's sibling delegation, so even a hard-expired delegation is
1126
- * recoverable), and one companion entry replaces the service entry's
1308
+ * recoverable), and one annex entry replaces the service entry's
1127
1309
  * endpoint in place, signed by the writing credential's static rung 0.
1128
1310
  *
1129
- * A companion document carrying no delegation entry at all installs one the
1311
+ * An annex document carrying no delegation entry at all installs one the
1130
1312
  * same way (the GC ceremony's own install stage and the first-VM install
1131
1313
  * make this rare; a heal, not a policy).
1132
1314
  *
1315
+ * Beside the expiry axis, an `accountDoc` adds the SIGNER-DEATH axis: a
1316
+ * standing delegation whose proof key is no longer in the supplied verified
1317
+ * account document has rotted under the current-key-set rule (the durable
1318
+ * client that minted it was revoked, or the ladder VM that signed it left
1319
+ * with the first durable self-enrollment) and is replaced the same way. No
1320
+ * revocation POST accompanies the replacement: a rotted chain no longer
1321
+ * verifies at the revocation endpoint, and the expiry-renewal path never
1322
+ * revoked either.
1323
+ *
1133
1324
  * Failure is the caller's failure: a renewal that cannot complete throws,
1134
1325
  * and the App Connect approval fails with the standard retryable-ceremony
1135
1326
  * posture -- deliberately no clamp-on-failure fallback, which would deliver
@@ -1139,21 +1330,28 @@ export async function enrollTransientClient({ readAccountDocument, storeForSegme
1139
1330
  * 30 or more days remaining ({@link clampGrantExpires}).
1140
1331
  *
1141
1332
  * @param options {object}
1142
- * @param options.store {CompanionWriteStore} the generation's log store
1333
+ * @param options.store {ClientAnnexWriteStore} the generation's log store
1143
1334
  * (delegated through the credential's sibling delegation, or
1144
1335
  * controller-tier)
1145
1336
  * @param options.ladderSeed {Uint8Array} the credential's ladder seed, from
1146
1337
  * its unlock record
1147
- * @param options.segment {string} the generation collection's name
1338
+ * @param options.generationId {string} the generation collection's name
1148
1339
  * @param options.mintGenerationDelegation {Function}
1149
- * `({ companionDid }) => Promise<IZcap>` -- mints the replacement
1340
+ * `({ clientAnnexDid }) => Promise<IZcap>` -- mints the replacement
1150
1341
  * delegation (ladder-signed in a transient session)
1151
- * @param [options.expectedDid] {string} the companion DID the log must
1342
+ * @param [options.expectedDid] {string} the annex DID the log must
1152
1343
  * resolve to, from the account document's pointer
1153
1344
  * @param [options.pinStore] {ResourceLogPinStore} chain-head pins (a
1154
1345
  * transient session passes an in-memory store)
1155
1346
  * @param [options.logId] {string} the generation's pin-slot key, from
1156
- * {@link companionLogPinId}; required whenever a `pinStore` is supplied
1347
+ * {@link clientAnnexLogPinId}; required whenever a `pinStore` is supplied
1348
+ * @param [options.accountDoc] {PublishedKeyDocument} the locally VERIFIED
1349
+ * account document; supplied, a standing delegation whose proof key it no
1350
+ * longer lists is replaced (the signer-death axis above)
1351
+ * @param [options.force] {boolean} replace the embedded delegation
1352
+ * unconditionally, however healthy it looks -- the last-durable-client
1353
+ * forget's replacement stage, where the standing delegation has just been
1354
+ * revoked server-side (a state no client-side predicate can read)
1157
1355
  * @param [options.now] {number} epoch milliseconds, for tests
1158
1356
  * @returns {Promise<{ delegation: IZcap, renewed: boolean }>}
1159
1357
  */
@@ -1168,9 +1366,9 @@ export async function ensureGenerationDelegationCurrent(options) {
1168
1366
  * @param options {object} see {@link ensureGenerationDelegationCurrent}
1169
1367
  * @returns {Promise<{ delegation: IZcap, renewed: boolean }>}
1170
1368
  */
1171
- async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, segment, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId, now }) {
1172
- assertGenerationSegment(segment);
1173
- const published = await readCompanionLogOrThrow({
1369
+ async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, generationId, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId, accountDoc, force = false, now }) {
1370
+ assertGenerationId(generationId);
1371
+ const published = await readClientAnnexLogOrThrow({
1174
1372
  store,
1175
1373
  ...(expectedDid !== undefined ? { expectedDid } : {}),
1176
1374
  ...(pinStore !== undefined ? { pinStore } : {}),
@@ -1178,7 +1376,17 @@ async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, segmen
1178
1376
  });
1179
1377
  const { did, doc } = published;
1180
1378
  const standing = embeddedGenerationDelegation({ doc });
1181
- if (standing !== undefined &&
1379
+ const signerRotted = standing !== undefined &&
1380
+ accountDoc !== undefined &&
1381
+ !delegationKeyInDocument({
1382
+ doc: accountDoc,
1383
+ ...(delegationProofKeyId(standing) !== undefined
1384
+ ? { delegationKeyId: delegationProofKeyId(standing) }
1385
+ : {})
1386
+ });
1387
+ if (!force &&
1388
+ standing !== undefined &&
1389
+ !signerRotted &&
1182
1390
  !zcapExpiring({
1183
1391
  ...(standing.expires !== undefined
1184
1392
  ? { expires: standing.expires }
@@ -1189,22 +1397,22 @@ async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, segmen
1189
1397
  }
1190
1398
  // The rung refusal precedes the mint: nothing is delegated for a writer
1191
1399
  // who cannot publish the entry that would carry it.
1192
- const rung = await companionRung({ ladderSeed, segment });
1400
+ const rung = await clientAnnexRung({ ladderSeed, generationId });
1193
1401
  const rungHash = await deriveNextKeyHash(rung.keyMultibase);
1194
1402
  const revealed = published.updateKeys.includes(rung.keyMultibase);
1195
1403
  if (!revealed && !published.nextKeyHashes.includes(rungHash)) {
1196
- throw new CompanionRungUncommittedError("companion: the log commits neither this credential's rung-0 key nor " +
1404
+ throw new ClientAnnexRungUncommittedError("client annex: the log commits neither this credential's rung-0 key nor " +
1197
1405
  'its hash; a credential bound mid-generation cannot renew the ' +
1198
1406
  'generation delegation until a writer commits its hash or the next ' +
1199
1407
  'GC swap does.');
1200
1408
  }
1201
1409
  await assertCarryOverCommitments({ published });
1202
- const fresh = await mintDelegation({ companionDid: did });
1410
+ const fresh = await mintDelegation({ clientAnnexDid: did });
1203
1411
  const signer = await updateKeySigner({ seed: rung.seed });
1204
1412
  const updated = await updateDID({
1205
1413
  log: published.log,
1206
1414
  signer,
1207
- // The writer's rung-0 key reveals at its first companion write, exactly
1415
+ // The writer's rung-0 key reveals at its first annex write, exactly
1208
1416
  // as the enrollment entry does; `nextKeyHashes` is re-stated verbatim,
1209
1417
  // never inherited. Verification methods, relationship arrays, and every
1210
1418
  // other service entry ride the library's prior-state clone untouched.
@@ -1212,11 +1420,196 @@ async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, segmen
1212
1420
  nextKeyHashes: [...published.nextKeyHashes],
1213
1421
  services: withGenerationDelegationEntry({
1214
1422
  doc,
1215
- companionDid: did,
1423
+ clientAnnexDid: did,
1216
1424
  delegation: fresh
1217
1425
  })
1218
1426
  });
1219
1427
  await putLogResource({ store, log: updated.log, ifMatch: published.etag });
1220
1428
  return { delegation: fresh, renewed: true };
1221
1429
  }
1222
- //# sourceMappingURL=companion.js.map
1430
+ /**
1431
+ * THE CLIENT-ANNEX RUNG STRIKE: drops a retired credential's annex posture
1432
+ * from a generation's log -- its revealed rung-0 key out of `updateKeys` and
1433
+ * its standing rung-0 hash out of `nextKeyHashes` -- in one atomic entry
1434
+ * signed by ANOTHER credential's committed rung 0 (an annex entry cannot
1435
+ * remove its own signing key: the entry verifies against its own re-stated
1436
+ * `updateKeys`). The credential-rotation ceremony's annex reach.
1437
+ *
1438
+ * A log committing neither the retired rung's key nor its hash is already
1439
+ * clean and the strike no-ops (`struck: false`) -- the resumable shape, and
1440
+ * the common one: a credential that never minted or wrote this generation
1441
+ * has no posture in it. An acting rung the log does not commit (after the
1442
+ * retired members are excluded -- so the retired credential can never sign
1443
+ * its own strike) is refused with {@link ClientAnnexRungUncommittedError},
1444
+ * which the caller maps to the generation-swap fallback: a fresh generation
1445
+ * minted from a surviving credential's seed retires the rung with the whole
1446
+ * generation.
1447
+ *
1448
+ * @param options {object}
1449
+ * @param options.store {ClientAnnexWriteStore} the pointed generation's log
1450
+ * store (controller-tier, or delegated through a sibling delegation)
1451
+ * @param options.retiredLadderSeed {Uint8Array} the RETIRED credential's
1452
+ * ladder seed (its rung is derived per generation, so the seed is the only
1453
+ * way to name what to strike)
1454
+ * @param options.actingLadderSeed {Uint8Array} a surviving credential's
1455
+ * ladder seed, whose committed rung 0 signs the strike entry
1456
+ * @param options.generationId {string} the generation collection's name
1457
+ * @param [options.expectedDid] {string} the annex DID the log must
1458
+ * resolve to, from the account document's pointer
1459
+ * @param [options.pinStore] {ResourceLogPinStore}
1460
+ * @param [options.logId] {string} the generation's pin-slot key, from
1461
+ * {@link clientAnnexLogPinId}; required whenever a `pinStore` is supplied
1462
+ * @returns {Promise<{ struck: boolean }>}
1463
+ */
1464
+ export async function retireClientAnnexRung(options) {
1465
+ return withLogConflictRetry(() => retireClientAnnexRungOnce(options));
1466
+ }
1467
+ /**
1468
+ * One attempt of {@link retireClientAnnexRung}, re-invoked by the conflict
1469
+ * retry (with the same signing key -- static rung 0 has no advanced-rung
1470
+ * retry shape).
1471
+ *
1472
+ * @param options {object} see {@link retireClientAnnexRung}
1473
+ * @returns {Promise<{ struck: boolean }>}
1474
+ */
1475
+ async function retireClientAnnexRungOnce({ store, retiredLadderSeed, actingLadderSeed, generationId, expectedDid, pinStore, logId }) {
1476
+ assertGenerationId(generationId);
1477
+ const published = await readClientAnnexLogOrThrow({
1478
+ store,
1479
+ ...(expectedDid !== undefined ? { expectedDid } : {}),
1480
+ ...(pinStore !== undefined ? { pinStore } : {}),
1481
+ ...(logId !== undefined ? { logId } : {})
1482
+ });
1483
+ const retired = await clientAnnexRung({
1484
+ ladderSeed: retiredLadderSeed,
1485
+ generationId
1486
+ });
1487
+ const retiredHash = await deriveNextKeyHash(retired.keyMultibase);
1488
+ const remainingKeys = published.updateKeys.filter(key => key !== retired.keyMultibase);
1489
+ const remainingHashes = published.nextKeyHashes.filter(hash => hash !== retiredHash);
1490
+ if (remainingKeys.length === published.updateKeys.length &&
1491
+ remainingHashes.length === published.nextKeyHashes.length) {
1492
+ // Already clean: the retired credential holds no posture in this
1493
+ // generation (never committed, or a completed earlier strike).
1494
+ return { struck: false };
1495
+ }
1496
+ // The acting rung must be committed AFTER the retired members are
1497
+ // excluded, so the retired credential can never sign its own strike.
1498
+ const acting = await clientAnnexRung({
1499
+ ladderSeed: actingLadderSeed,
1500
+ generationId
1501
+ });
1502
+ const actingHash = await deriveNextKeyHash(acting.keyMultibase);
1503
+ const revealed = remainingKeys.includes(acting.keyMultibase);
1504
+ if (!revealed && !remainingHashes.includes(actingHash)) {
1505
+ throw new ClientAnnexRungUncommittedError("client annex: the log commits neither the acting credential's rung-0 " +
1506
+ 'key nor its hash (or it is the retired rung itself); the strike ' +
1507
+ 'needs a distinct committed writer -- swap the generation instead.');
1508
+ }
1509
+ await assertCarryOverCommitments({ published });
1510
+ const signer = await updateKeySigner({ seed: acting.seed });
1511
+ const updated = await updateDID({
1512
+ log: published.log,
1513
+ signer,
1514
+ // The acting rung reveals at its first annex write, exactly as the
1515
+ // enrollment entry does; the retired rung's key and hash are dropped by
1516
+ // explicit re-statement (never parameter inheritance). Verification
1517
+ // methods, relationship arrays, and the service entries ride the
1518
+ // library's prior-state clone untouched.
1519
+ updateKeys: [...new Set([...remainingKeys, acting.keyMultibase])],
1520
+ nextKeyHashes: [...remainingHashes]
1521
+ });
1522
+ await putLogResource({ store, log: updated.log, ifMatch: published.etag });
1523
+ return { struck: true };
1524
+ }
1525
+ /**
1526
+ * THE CLIENT-ANNEX RUNG COMMIT: adds a freshly bound credential's rung-0
1527
+ * hash to a generation's `nextKeyHashes` -- one atomic hash-restating entry
1528
+ * signed by an already-committed credential's rung 0. The bind ceremonies'
1529
+ * annex reach (passkey add, passphrase change): a bind runs from a logged-in
1530
+ * session whose own login credential's rung 0 is committed, so committing the
1531
+ * new credential's hash here is what keeps it out of the mid-generation
1532
+ * lockout ({@link ClientAnnexRungUncommittedError} at its first transient
1533
+ * login, otherwise standing until the next GC swap's genesis).
1534
+ *
1535
+ * A log already committing the bound rung's hash (or carrying its revealed
1536
+ * key) is a no-op (`committed: false`) -- the resumable shape. An acting rung
1537
+ * the log does not commit is refused with
1538
+ * {@link ClientAnnexRungUncommittedError}: the bind ceremony maps that to an
1539
+ * honest skip (nothing licenses it to mint a generation), and the lockout
1540
+ * consequence stands as documented.
1541
+ *
1542
+ * @param options {object}
1543
+ * @param options.store {ClientAnnexWriteStore} the pointed generation's log
1544
+ * store (controller-tier, or delegated through a sibling delegation)
1545
+ * @param options.boundLadderSeed {Uint8Array} the freshly bound
1546
+ * credential's ladder seed (its rung is derived per generation, so the seed
1547
+ * is the only way to name what to commit)
1548
+ * @param options.actingLadderSeed {Uint8Array} the logged-in session's
1549
+ * login credential's ladder seed, whose committed rung 0 signs the entry
1550
+ * @param options.generationId {string} the generation collection's name
1551
+ * @param [options.expectedDid] {string} the annex DID the log must
1552
+ * resolve to, from the account document's pointer
1553
+ * @param [options.pinStore] {ResourceLogPinStore}
1554
+ * @param [options.logId] {string} the generation's pin-slot key, from
1555
+ * {@link clientAnnexLogPinId}; required whenever a `pinStore` is supplied
1556
+ * @returns {Promise<{ committed: boolean }>}
1557
+ */
1558
+ export async function commitClientAnnexRung(options) {
1559
+ return withLogConflictRetry(() => commitClientAnnexRungOnce(options));
1560
+ }
1561
+ /**
1562
+ * One attempt of {@link commitClientAnnexRung}, re-invoked by the conflict
1563
+ * retry (with the same signing key -- static rung 0 has no advanced-rung
1564
+ * retry shape).
1565
+ *
1566
+ * @param options {object} see {@link commitClientAnnexRung}
1567
+ * @returns {Promise<{ committed: boolean }>}
1568
+ */
1569
+ async function commitClientAnnexRungOnce({ store, boundLadderSeed, actingLadderSeed, generationId, expectedDid, pinStore, logId }) {
1570
+ assertGenerationId(generationId);
1571
+ const published = await readClientAnnexLogOrThrow({
1572
+ store,
1573
+ ...(expectedDid !== undefined ? { expectedDid } : {}),
1574
+ ...(pinStore !== undefined ? { pinStore } : {}),
1575
+ ...(logId !== undefined ? { logId } : {})
1576
+ });
1577
+ const bound = await clientAnnexRung({
1578
+ ladderSeed: boundLadderSeed,
1579
+ generationId
1580
+ });
1581
+ const boundHash = await deriveNextKeyHash(bound.keyMultibase);
1582
+ if (published.updateKeys.includes(bound.keyMultibase) ||
1583
+ published.nextKeyHashes.includes(boundHash)) {
1584
+ // Already committed (or even revealed): a completed earlier run, or a
1585
+ // generation the bound credential itself minted.
1586
+ return { committed: false };
1587
+ }
1588
+ const acting = await clientAnnexRung({
1589
+ ladderSeed: actingLadderSeed,
1590
+ generationId
1591
+ });
1592
+ const actingHash = await deriveNextKeyHash(acting.keyMultibase);
1593
+ const revealed = published.updateKeys.includes(acting.keyMultibase);
1594
+ if (!revealed && !published.nextKeyHashes.includes(actingHash)) {
1595
+ throw new ClientAnnexRungUncommittedError("client annex: the log commits neither the acting credential's rung-0 " +
1596
+ 'key nor its hash; a commit entry needs a committed writer -- the ' +
1597
+ 'bound credential stays locked out until the next GC swap.');
1598
+ }
1599
+ await assertCarryOverCommitments({ published });
1600
+ const signer = await updateKeySigner({ seed: acting.seed });
1601
+ const updated = await updateDID({
1602
+ log: published.log,
1603
+ signer,
1604
+ // The acting rung reveals at its first annex write, exactly as the
1605
+ // enrollment entry does; the bound rung's hash is added by explicit
1606
+ // re-statement (never parameter inheritance). Verification methods,
1607
+ // relationship arrays, and the service entries ride the library's
1608
+ // prior-state clone untouched.
1609
+ updateKeys: [...new Set([...published.updateKeys, acting.keyMultibase])],
1610
+ nextKeyHashes: [...published.nextKeyHashes, boundHash]
1611
+ });
1612
+ await putLogResource({ store, log: updated.log, ifMatch: published.etag });
1613
+ return { committed: true };
1614
+ }
1615
+ //# sourceMappingURL=log.js.map