@serfab/cadre-core 1.15.0 → 1.17.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 (199) hide show
  1. package/README.md +1 -1
  2. package/dist/bootstrap-peer-store-file.js +1 -1
  3. package/dist/bootstrap-peer-store.d.ts +1 -1
  4. package/dist/bootstrap-peer-store.js +4 -4
  5. package/dist/cadre-invite-protocol.d.ts +55 -138
  6. package/dist/cadre-invite-protocol.js +94 -449
  7. package/dist/cadre-invite-protocol.js.map +1 -1
  8. package/dist/cadre-join-request.d.ts +15 -2
  9. package/dist/cadre-join-request.js +22 -3
  10. package/dist/cadre-join-request.js.map +1 -1
  11. package/dist/cadre-node.d.ts +530 -247
  12. package/dist/cadre-node.js +1046 -525
  13. package/dist/cadre-node.js.map +1 -1
  14. package/dist/control-authorization.d.ts +36 -14
  15. package/dist/control-authorization.js +20 -3
  16. package/dist/control-authorization.js.map +1 -1
  17. package/dist/control-cohort.d.ts +22 -23
  18. package/dist/control-cohort.js +24 -24
  19. package/dist/control-cohort.js.map +1 -1
  20. package/dist/control-database.d.ts +416 -136
  21. package/dist/control-database.js +757 -378
  22. package/dist/control-database.js.map +1 -1
  23. package/dist/control-protocol-guard.d.ts +5 -4
  24. package/dist/control-protocol-guard.js +6 -3
  25. package/dist/control-protocol-guard.js.map +1 -1
  26. package/dist/control-schema-version.d.ts +80 -0
  27. package/dist/control-schema-version.js +203 -0
  28. package/dist/control-schema-version.js.map +1 -0
  29. package/dist/control-schema.d.ts +45 -5
  30. package/dist/control-schema.js +254 -70
  31. package/dist/control-schema.js.map +1 -1
  32. package/dist/control-write-retry.d.ts +5 -5
  33. package/dist/control-write-retry.js +10 -8
  34. package/dist/control-write-retry.js.map +1 -1
  35. package/dist/delegate-admission.d.ts +13 -0
  36. package/dist/delegate-admission.js +27 -0
  37. package/dist/delegate-admission.js.map +1 -1
  38. package/dist/ed25519-key.d.ts +10 -7
  39. package/dist/ed25519-key.js +23 -20
  40. package/dist/ed25519-key.js.map +1 -1
  41. package/dist/edge-node/config.d.ts +122 -0
  42. package/dist/edge-node/config.js +199 -0
  43. package/dist/edge-node/config.js.map +1 -0
  44. package/dist/edge-node/dissolve.d.ts +95 -0
  45. package/dist/edge-node/dissolve.js +184 -0
  46. package/dist/edge-node/dissolve.js.map +1 -0
  47. package/dist/edge-node/edge-join.d.ts +44 -0
  48. package/dist/edge-node/edge-join.js +227 -0
  49. package/dist/edge-node/edge-join.js.map +1 -0
  50. package/dist/edge-node/edge-node.d.ts +295 -0
  51. package/dist/edge-node/edge-node.js +704 -0
  52. package/dist/edge-node/edge-node.js.map +1 -0
  53. package/dist/edge-node/index.d.ts +18 -0
  54. package/dist/edge-node/index.js +18 -0
  55. package/dist/edge-node/index.js.map +1 -0
  56. package/dist/edge-node/join-credential.d.ts +11 -0
  57. package/dist/edge-node/join-credential.js +11 -0
  58. package/dist/edge-node/join-credential.js.map +1 -0
  59. package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
  60. package/dist/edge-node/lazy-raw-storage.js +134 -0
  61. package/dist/edge-node/lazy-raw-storage.js.map +1 -0
  62. package/dist/edge-node/node-local.d.ts +60 -0
  63. package/dist/edge-node/node-local.js +58 -0
  64. package/dist/edge-node/node-local.js.map +1 -0
  65. package/dist/edge-node/options.d.ts +83 -0
  66. package/dist/edge-node/options.js +110 -0
  67. package/dist/edge-node/options.js.map +1 -0
  68. package/dist/edge-node/owner-grant.d.ts +41 -0
  69. package/dist/edge-node/owner-grant.js +38 -0
  70. package/dist/edge-node/owner-grant.js.map +1 -0
  71. package/dist/edge-node/platform.d.ts +140 -0
  72. package/dist/edge-node/platform.js +12 -0
  73. package/dist/edge-node/platform.js.map +1 -0
  74. package/dist/edge-node/strand-move.d.ts +115 -0
  75. package/dist/edge-node/strand-move.js +223 -0
  76. package/dist/edge-node/strand-move.js.map +1 -0
  77. package/dist/edge-node/strands.d.ts +28 -0
  78. package/dist/edge-node/strands.js +60 -0
  79. package/dist/edge-node/strands.js.map +1 -0
  80. package/dist/index.d.ts +16 -7
  81. package/dist/index.js +26 -7
  82. package/dist/index.js.map +1 -1
  83. package/dist/joined-strand-store.d.ts +3 -3
  84. package/dist/joined-strand-store.js +3 -3
  85. package/dist/joined-strand-store.js.map +1 -1
  86. package/dist/key-store.d.ts +9 -4
  87. package/dist/key-store.js +9 -4
  88. package/dist/key-store.js.map +1 -1
  89. package/dist/machine-removal.d.ts +19 -39
  90. package/dist/machine-removal.js +24 -98
  91. package/dist/machine-removal.js.map +1 -1
  92. package/dist/member-redemption.d.ts +250 -0
  93. package/dist/member-redemption.js +485 -0
  94. package/dist/member-redemption.js.map +1 -0
  95. package/dist/owner-anchor-sync.d.ts +4 -4
  96. package/dist/owner-anchor-sync.js +16 -15
  97. package/dist/owner-anchor-sync.js.map +1 -1
  98. package/dist/owner-grant-protocol.d.ts +247 -0
  99. package/dist/owner-grant-protocol.js +330 -0
  100. package/dist/owner-grant-protocol.js.map +1 -0
  101. package/dist/owner-key-export.d.ts +70 -0
  102. package/dist/owner-key-export.js +279 -0
  103. package/dist/owner-key-export.js.map +1 -0
  104. package/dist/owner-key-flows.d.ts +55 -0
  105. package/dist/owner-key-flows.js +60 -0
  106. package/dist/owner-key-flows.js.map +1 -0
  107. package/dist/owner-key-store.d.ts +31 -0
  108. package/dist/owner-key-store.js +81 -0
  109. package/dist/owner-key-store.js.map +1 -0
  110. package/dist/owner-keys.d.ts +78 -0
  111. package/dist/owner-keys.js +133 -0
  112. package/dist/owner-keys.js.map +1 -0
  113. package/dist/owner-signer.d.ts +54 -0
  114. package/dist/owner-signer.js +51 -0
  115. package/dist/owner-signer.js.map +1 -0
  116. package/dist/peer-authorization.d.ts +83 -18
  117. package/dist/peer-authorization.js +123 -53
  118. package/dist/peer-authorization.js.map +1 -1
  119. package/dist/provider/rent-node.js +1 -1
  120. package/dist/provider/rent-node.js.map +1 -1
  121. package/dist/seed-bootstrap.d.ts +131 -109
  122. package/dist/seed-bootstrap.js +260 -248
  123. package/dist/seed-bootstrap.js.map +1 -1
  124. package/dist/seed-trust-policy.js +4 -4
  125. package/dist/strand-addr-protocol.d.ts +4 -1
  126. package/dist/strand-addr-protocol.js +8 -2
  127. package/dist/strand-addr-protocol.js.map +1 -1
  128. package/dist/strand-cohort.d.ts +3 -0
  129. package/dist/strand-cohort.js.map +1 -1
  130. package/dist/strand-database.d.ts +1 -0
  131. package/dist/strand-database.js +3 -0
  132. package/dist/strand-database.js.map +1 -1
  133. package/dist/strand-first-sync-gate.d.ts +4 -3
  134. package/dist/strand-first-sync-gate.js +4 -3
  135. package/dist/strand-first-sync-gate.js.map +1 -1
  136. package/dist/strand-instance-manager.d.ts +1 -1
  137. package/dist/strand-membership-writer.d.ts +13 -2
  138. package/dist/strand-membership-writer.js +52 -16
  139. package/dist/strand-membership-writer.js.map +1 -1
  140. package/dist/strand-move.d.ts +6 -5
  141. package/dist/strand-move.js +31 -30
  142. package/dist/strand-move.js.map +1 -1
  143. package/dist/strand-peer-redial.js +2 -3
  144. package/dist/strand-peer-redial.js.map +1 -1
  145. package/dist/types.d.ts +286 -103
  146. package/dist/types.js.map +1 -1
  147. package/package.json +16 -7
  148. package/src/bootstrap-peer-store-file.ts +44 -44
  149. package/src/bootstrap-peer-store.ts +214 -214
  150. package/src/cadre-invite-protocol.ts +131 -531
  151. package/src/cadre-join-request.ts +188 -167
  152. package/src/cadre-node.ts +1166 -541
  153. package/src/control-authorization.ts +47 -14
  154. package/src/control-cohort.ts +127 -128
  155. package/src/control-database.ts +940 -456
  156. package/src/control-protocol-guard.ts +7 -4
  157. package/src/control-schema-version.ts +251 -0
  158. package/src/control-schema.ts +265 -70
  159. package/src/control-write-retry.ts +569 -567
  160. package/src/delegate-admission.ts +37 -0
  161. package/src/ed25519-key.ts +174 -171
  162. package/src/edge-node/config.ts +277 -0
  163. package/src/edge-node/dissolve.ts +251 -0
  164. package/src/edge-node/edge-join.ts +319 -0
  165. package/src/edge-node/edge-node.ts +1045 -0
  166. package/src/edge-node/index.ts +59 -0
  167. package/src/edge-node/join-credential.ts +16 -0
  168. package/src/edge-node/lazy-raw-storage.ts +178 -0
  169. package/src/edge-node/node-local.ts +84 -0
  170. package/src/edge-node/options.ts +179 -0
  171. package/src/edge-node/owner-grant.ts +63 -0
  172. package/src/edge-node/platform.ts +151 -0
  173. package/src/edge-node/strand-move.ts +270 -0
  174. package/src/edge-node/strands.ts +72 -0
  175. package/src/index.ts +102 -5
  176. package/src/joined-strand-store.ts +4 -4
  177. package/src/key-store.ts +10 -4
  178. package/src/machine-removal.ts +26 -111
  179. package/src/member-redemption.ts +642 -0
  180. package/src/owner-anchor-sync.ts +19 -18
  181. package/src/owner-grant-protocol.ts +532 -0
  182. package/src/owner-key-export.ts +348 -0
  183. package/src/owner-key-flows.ts +109 -0
  184. package/src/owner-key-store.ts +85 -0
  185. package/src/owner-keys.ts +178 -0
  186. package/src/owner-signer.ts +94 -0
  187. package/src/peer-authorization.ts +558 -488
  188. package/src/provider/rent-node.ts +1 -1
  189. package/src/seed-bootstrap.ts +301 -312
  190. package/src/seed-trust-policy.ts +4 -4
  191. package/src/strand-addr-protocol.ts +547 -538
  192. package/src/strand-cohort.ts +4 -0
  193. package/src/strand-database.ts +3 -0
  194. package/src/strand-first-sync-gate.ts +4 -3
  195. package/src/strand-instance-manager.ts +1 -1
  196. package/src/strand-membership-writer.ts +61 -16
  197. package/src/strand-move.ts +45 -36
  198. package/src/strand-peer-redial.ts +2 -3
  199. package/src/types.ts +304 -103
@@ -1,17 +1,19 @@
1
1
  import type { Libp2p, PeerId } from '@libp2p/interface';
2
2
  import type { Multiaddr } from '@multiformats/multiaddr';
3
- import type { CadreNodeConfig, StrandInstance, StrandRow, StrandConfig, FoundStrandConfig, FoundStrandResult, SAppConfig, CadreNodeEvents, ControlNetworkSeed, ApplySeedResult, SeedAckMessage, SeedDeliveryTarget, AddDroneOptions, DroneInitResult, OpenInvitation, FormStrandResult, StrandFormationDisclosure, StrandMembershipInvite, ResolveOpts, SelfRegistrationOutcome, ServiceWakeResult, PushPlatform, DeviceTokenRecord, CadreInviteStatus, CadreMachine, MachineRemovalPlan, MachineRemovalResult, RemoveMachineOptions, CadreRemoval, CadreInvitationReach, CreateCadreInvitationOptions, CreateCadreInvitationResult, RedeemCadreInvitationOptions, RedeemCadreInvitationResult, ResolveDeviceTokenOpts, PendingJoinStatus } from './types.js';
4
- import { type Ed25519KeyPair } from './ed25519-key.js';
3
+ import type { CadreNodeConfig, StrandInstance, StrandRow, StrandConfig, FoundStrandConfig, FoundStrandResult, FoundStrandOptions, FoundStrandBackgroundResult, SAppConfig, CadreNodeEvents, ControlNetworkSeed, ApplySeedResult, SeedAckMessage, SeedDeliveryTarget, AddDroneOptions, DroneInitResult, OpenInvitation, FormStrandResult, StrandFormationDisclosure, StrandMembershipInvite, ResolveOpts, SelfRegistrationOutcome, ServiceWakeResult, PushPlatform, DeviceTokenRecord, CadreInviteStatus, CadreMachine, CadreOwnerKey, MachineRemovalPlan, MachineRemovalResult, OwnerKeyRemovalPlan, OwnerKeyRemovalResult, RemoveOwnerOptions, CadreRemoval, CadreInvitationReach, CreateCadreInvitationOptions, CreateCadreInvitationResult, RedeemCadreInvitationOptions, RedeemCadreInvitationResult, CreateOwnerGrantOptions, CreateOwnerGrantResult, OwnerGrantStatus, RedeemOwnerGrantOptions, RedeemOwnerGrantResult, JoinWithOwnerGrantResult, ResolveDeviceTokenOpts, PendingJoinStatus } from './types.js';
5
4
  import { type TrustedOwnerStore, type TrustSource } from './trusted-owner-store.js';
5
+ import { type OwnerSign, type OwnerSigner } from './owner-signer.js';
6
6
  import { type BootstrapPeerStore } from './bootstrap-peer-store.js';
7
7
  import { type StrandNetworkStateStore } from './strand-network-state.js';
8
8
  import { type EnrolledMachineStore } from './enrolled-machine-store.js';
9
9
  import { type JoinedStrandRecord } from './joined-strand-store.js';
10
10
  import { type SAppIdLookup } from './strand-watcher.js';
11
+ import type { CohortPeerRow } from './strand-cohort.js';
11
12
  import { type ControlCohortReconcileResult } from './control-cohort.js';
12
13
  import { EnrollmentService } from './enrollment.js';
13
14
  import { ControlDatabase } from './control-database.js';
14
15
  import { type CadreInvitation } from './cadre-invite-protocol.js';
16
+ import { type OwnerGrantBundle } from './owner-grant-protocol.js';
15
17
  import { SeedBootstrapService } from './seed-bootstrap.js';
16
18
  import { type SeedTrustPolicy } from './seed-trust-policy.js';
17
19
  import { StrandSolicitationService, type StrandSolicitationServiceOptions } from './strand-solicitation.js';
@@ -41,13 +43,13 @@ export declare const STRAND_PEER_ADDR_REFRESH_MS: number;
41
43
  */
42
44
  export declare const STRAND_PEER_ADDR_RETRY_MS: number;
43
45
  /**
44
- * Default lifetime of an UNTARGETED, OWNER-GRANTING cadre invitation
45
- * ({@link CadreNode.createCadreInvitation}): whoever holds the bundle becomes an owner, so
46
- * it is a bearer credential for admin rights and lives only long enough to be pasted or
47
- * scanned by the device it was minted for.
46
+ * Default lifetime of an owner grant ({@link CadreNode.createOwnerGrant}): whoever holds the
47
+ * bundle can become an owner, so it is a bearer credential for admin rights and lives only
48
+ * long enough to be pasted or scanned by the device it was minted for. A backup grant is minted
49
+ * with no expiry, by explicit choice.
48
50
  */
49
- export declare const OWNER_INVITATION_DEFAULT_TTL_MS: number;
50
- /** Default lifetime of every other cadre invitation: targeted, or granting membership only. */
51
+ export declare const OWNER_GRANT_DEFAULT_TTL_MS: number;
52
+ /** Default lifetime of a cadre invitation ({@link CadreNode.createCadreInvitation}): it admits a machine and grants nothing else, so it need not be short. */
51
53
  export declare const CADRE_INVITATION_DEFAULT_TTL_MS: number;
52
54
  type EventHandler<T> = (data: T) => void;
53
55
  /**
@@ -134,6 +136,13 @@ export declare class CadreNode implements SAppIdLookup {
134
136
  * once that database is up, on every node.
135
137
  */
136
138
  private cadreInviteHandler;
139
+ /**
140
+ * Owner grant redemption handler (`/sereus/owner-grant/1.0.0`): a holder of a grant dials
141
+ * this machine and proves possession of the grant and of the key it seats; the handler seats
142
+ * the row and writes the `OwnerKey` row against this node's control database. Registered by
143
+ * {@link start} beside the invitation handler, on every node.
144
+ */
145
+ private ownerGrantHandler;
137
146
  /**
138
147
  * Server-side push-wake fan-out. Constructed by {@link start} only when
139
148
  * `config.push` (an injected `PushNotifier` + policy) is present — without it
@@ -335,6 +344,25 @@ export declare class CadreNode implements SAppIdLookup {
335
344
  * strand is no longer running are pruned on each reconcile pass.
336
345
  */
337
346
  private readonly delegateAnnounceAt;
347
+ /**
348
+ * Relay peerIds whose protocol negotiation refused the delegate announcement
349
+ * (strand-addr outcome `unsupported`), recorded and cleared by
350
+ * {@link recordDelegateAnnounces} from every announce pass's outcomes. Only a
351
+ * party-run relay has a membership gate that needs a grant, and it serves
352
+ * strand-addr, so a refusal marks a dedicated relay: {@link circuitRelayTargets}
353
+ * leaves it out of the founding/resume and periodic announcements, which would
354
+ * otherwise spend a protocol negotiation on it every time (gotchoices/sereus#49). Not
355
+ * pruned: one entry per relay this node ever named.
356
+ *
357
+ * {@link announceDelegateToRelay} still asks every relay, remembered or not, and
358
+ * its reply is what clears an entry. A party-run relay registers its strand-addr
359
+ * handler late in its own `start()`, after its libp2p node and relay server are
360
+ * up, so an announcement landing in that window is refused by a relay that serves
361
+ * the protocol a moment later. If the re-drive hook skipped remembered relays too,
362
+ * that relay would stay skipped until this node restarts and its gate would deny
363
+ * every reservation through it.
364
+ */
365
+ private readonly relaysRefusingStrandAddr;
338
366
  /**
339
367
  * When each connected sibling is next due a strand-addr RPC for each running
340
368
  * strand, keyed `peerStrandKey(siblingControlPeerId, strandId)` → epoch ms; a
@@ -460,7 +488,7 @@ export declare class CadreNode implements SAppIdLookup {
460
488
  /**
461
489
  * Dial targets learned out of band, keyed by peer id, valued by the multiaddr
462
490
  * strings this node was handed (parsed lazily, at dial time). Two writers:
463
- * {@link recordSeedBootstrapPeers} retains the owner-flagged peers of every seed
491
+ * {@link recordSeedBootstrapPeers} retains the preferred peers of every seed
464
492
  * this node applies, and {@link addDrone} retains the addresses of every node
465
493
  * this node adds. Two readers: {@link dialColdStartBootstrap} dials every entry
466
494
  * while the control database still has no siblings, and
@@ -777,7 +805,7 @@ export declare class CadreNode implements SAppIdLookup {
777
805
  /**
778
806
  * Construct (or adopt) the node-local trusted-owner anchor and seed the
779
807
  * out-of-band pinned keys from `config.trustedOwners`. The anchor's BASE comes
780
- * only from out of band — genesis self-trust ({@link initializeSeedBootstrap}),
808
+ * only from out of band — genesis self-trust ({@link useOwnerSigner}),
781
809
  * config pins (here), runtime enrollment pins ({@link trustOwnerKeys}) and a
782
810
  * claim; everything else in it is derived from that base along verifiable
783
811
  * chains through the replicated `OwnerKey` table ({@link syncOwnerAnchor}),
@@ -943,12 +971,13 @@ export declare class CadreNode implements SAppIdLookup {
943
971
  getTrustedOwnerStore(): TrustedOwnerStore | null;
944
972
  /**
945
973
  * Persist out-of-band-established owner keys into the node-local anchor —
946
- * the runtime enrollment seam: {@link redeemCadreInvitation} calls it with the
947
- * cadre invitation's `ownerKeys` BEFORE dialing a member (so the anchor already
948
- * holds the pins when the reply and the rows that follow are judged), and an
974
+ * the runtime enrollment seam: {@link redeemCadreInvitation} and
975
+ * {@link redeemOwnerGrant} call it with their bundle's `ownerKeys` BEFORE dialing
976
+ * a member (so the anchor already holds the pins when the reply and the rows
977
+ * that follow are judged), and an
949
978
  * embedder calls it with an operator-supplied pin. Idempotent. ('genesis'
950
979
  * provenance is reserved for the node's own founding key, seeded internally by
951
- * {@link initializeSeedBootstrap}; 'chain' is written only by {@link syncOwnerAnchor}.)
980
+ * {@link useOwnerSigner}; 'chain' is written only by {@link syncOwnerAnchor}.)
952
981
  *
953
982
  * Validates every key's shape before trusting any of them (all-or-nothing):
954
983
  * a malformed entry anywhere in `keys` rejects the whole call before a
@@ -1095,10 +1124,18 @@ export declare class CadreNode implements SAppIdLookup {
1095
1124
  * own strand node, so a NAT'd member's strand node can hold a
1096
1125
  * circuit-relay reservation here (see `delegate-admission.ts`).
1097
1126
  * Connection only: the per-stream gate below never honors a grant;
1098
- * 3. the authorized-member set is empty — cold start: the rows that would
1127
+ * 3. the peer is a device whose invitation or owner-grant redemption this
1128
+ * node has verified and is writing ({@link admittingDevices}): its
1129
+ * connection carries the exchange, which must not be cut at the
1130
+ * provisional deadline. A LIVE OWNER GRANT does not count as expecting a
1131
+ * stranger the way check 7's invitation does: a backup grant has no
1132
+ * expiry, and counting it would admit every stranger outright for the
1133
+ * party's life, so the holder rides the provisional admission until its
1134
+ * redemption verifies, then this;
1135
+ * 4. the authorized-member set is empty — cold start: the rows that would
1099
1136
  * authorize anyone arrive by replication over these very connections;
1100
- * 4. the peer IS an authorized member; or
1101
- * 5. an open invitation is OUTSTANDING — at least one unexpired,
1137
+ * 5. the peer IS an authorized member; or
1138
+ * 6. an open invitation is OUTSTANDING — at least one unexpired,
1102
1139
  * not-fully-consumed invitation this node minted or persisted (see
1103
1140
  * `StrandSolicitationService.hasOutstandingInvitation`). A formation
1104
1141
  * initiator is another party's peer by design and its token is only
@@ -1106,19 +1143,19 @@ export declare class CadreNode implements SAppIdLookup {
1106
1143
  * "does this node expect a stranger at all?". REGISTERING the responder
1107
1144
  * does not count: every node registers one at {@link start}, and
1108
1145
  * registering mints no invitation; or
1109
- * 6. a LIVE cadre invitation exists — a `CadreInvite` row this node holds
1146
+ * 7. a LIVE cadre invitation exists — a `CadreInvite` row this node holds
1110
1147
  * that is not withdrawn, unexpired, has uses left and whose issuer is
1111
1148
  * still an owner (`ControlDatabase.hasLiveCadreInvite`). The device that
1112
1149
  * redeems it is a stranger until the redemption writes its row, and its
1113
1150
  * proof of possession is only checkable inside `/sereus/cadre-invite/1.0.0`;
1114
- * same reasoning as check 5, and the same expectation-of-a-stranger key.
1151
+ * same reasoning as check 6, and the same expectation-of-a-stranger key.
1115
1152
  *
1116
1153
  * Ordering is semantically free (the checks are OR'd) but decides who pays:
1117
- * checks 1-2 are in-memory, 3/4 share one control-DB read, and only a peer
1118
- * that is otherwise headed for a provisional admission reaches check 5's and
1119
- * 6's invitation lookups.
1154
+ * checks 1-3 are in-memory, 4/5 share one control-DB read, and only a peer
1155
+ * that is otherwise headed for a provisional admission reaches check 6's and
1156
+ * 7's invitation lookups.
1120
1157
  *
1121
- * Caveats of check 5:
1158
+ * Caveats of check 6:
1122
1159
  * - the in-memory mint registry dies with the process, so after a restart
1123
1160
  * only invitations persisted as `FormationInvite` rows still hold the
1124
1161
  * window open (re-mint otherwise);
@@ -1129,8 +1166,8 @@ export declare class CadreNode implements SAppIdLookup {
1129
1166
  * When every check falls through: `'admit-provisionally'`, on every node,
1130
1167
  * relay server or not. The gate sees only a peer id, so it cannot tell an
1131
1168
  * outsider from a device whose invitation row this node has not received yet
1132
- * (it redeems here by seating the row from its bundle, after which check 6
1133
- * admits) or from a sibling whose membership row is still replicating (check 4
1169
+ * (it redeems here by seating the row from its bundle, after which check 7
1170
+ * admits) or from a sibling whose membership row is still replicating (check 5
1134
1171
  * admits once it lands; if it has not landed by the deadline, the connection
1135
1172
  * is closed and either side's next reconcile dial re-forms it, since outbound
1136
1173
  * dials are never gated). The protocol guard refuses all of them every
@@ -1138,12 +1175,12 @@ export declare class CadreNode implements SAppIdLookup {
1138
1175
  * {@link admitControlRelayReservation} disarms the deadline (see
1139
1176
  * `membership-connection-gater.ts` → "The relay-reservation seam").
1140
1177
  *
1141
- * NOTE: check 3/4 runs a control-DB read per inbound connection
1178
+ * NOTE: check 4/5 runs a control-DB read per inbound connection
1142
1179
  * (`listAuthorizedMembers`); connections are rare and cadres small, and this
1143
1180
  * layer is fail-open behind `ADMISSION_DECISION_TIMEOUT_MS`, so the live
1144
1181
  * read is safe here — unlike the per-stream gate, which must consult the
1145
1182
  * materialized {@link authorizedControlPeers} snapshot instead.
1146
- * NOTE: checks 5 and 6 add two more control reads (`hasOutstandingFormationInvite`,
1183
+ * NOTE: checks 6 and 7 add two more control reads (`hasOutstandingFormationInvite`,
1147
1184
  * `hasLiveCadreInvite`) for a stranger with no locally minted invitation in play, on
1148
1185
  * every node, and a provisional connection's deadline runs the whole decision once
1149
1186
  * more. If stranger connections ever arrive fast enough for those reads — on arrival
@@ -1312,9 +1349,9 @@ export declare class CadreNode implements SAppIdLookup {
1312
1349
  *
1313
1350
  * The trusted-owner anchor sync ({@link syncOwnerAnchor}) runs FIRST, so the snapshot
1314
1351
  * built here judges `CadrePeer` rows against the anchor as the `OwnerKey` table now
1315
- * has it. The invitation chain both reads may need, and the tombstone list the sync and
1316
- * the two end-of-refresh checks ({@link hangUpRevokedControlPeers}, then
1317
- * {@link noteOwnRemoval}) read, are each loaded at most once per refresh.
1352
+ * has it. The tombstone list the sync and the two end-of-refresh checks
1353
+ * ({@link hangUpRevokedControlPeers}, then {@link noteOwnRemoval}) read is loaded at most
1354
+ * once per refresh, as is the invitation chain the membership read may need.
1318
1355
  */
1319
1356
  private refreshAuthorizedControlPeers;
1320
1357
  /**
@@ -1381,25 +1418,31 @@ export declare class CadreNode implements SAppIdLookup {
1381
1418
  */
1382
1419
  private createPartyJoinedStrandLedger;
1383
1420
  /**
1384
- * This machine's owner signing key when it can make owner-gated control writes: its seed
1385
- * bootstrap holds an owner key (`SeedBootstrapService.canAuthorize`) and the party's
1386
- * `OwnerKey` table enrolls it. A phone that ran self-genesis after another machine
1387
- * founded the party passes the first test and not the second (`runOwnerGenesis` in the
1388
- * reference apps), and a write it signed would only be refused.
1421
+ * This machine's owner signer when it can make owner-gated control writes: one is wired
1422
+ * ({@link useOwnerSigner}) and the party's `OwnerKey` table enrolls its key. A phone that
1423
+ * ran self-genesis after another machine founded the party passes the first test and not
1424
+ * the second (`runOwnerGenesis` in the reference apps), and a write it signed would only
1425
+ * be refused.
1389
1426
  */
1390
- private enrolledOwnerSigningKey;
1427
+ private enrolledOwnerSigner;
1391
1428
  /**
1392
- * Whether this node can act as an owner of its cadre: its identity key is wired as its
1393
- * owner signing key ({@link initializeSeedBootstrap}) and enrolled in the live `OwnerKey`
1394
- * set ({@link enrolledOwnerSigningKey}). What the owner-only flows (`createCadreInvitation`,
1395
- * `claimNode`, `rentNode`) need; false before `start()`, and on a machine that joined as a
1396
- * member. The reference apps' "is this device an owner" check.
1429
+ * Whether this node can act as an owner of its cadre: an owner signer is wired
1430
+ * ({@link useOwnerSigner}) and its key is in the live `OwnerKey` set. What the owner-only
1431
+ * flows (`createCadreInvitation`, `claimNode`, `rentNode`) need; false before `start()`,
1432
+ * and on a machine that joined as a member. The reference apps' "is this device an owner"
1433
+ * check.
1397
1434
  */
1398
1435
  isOwner(): Promise<boolean>;
1399
- /** {@link enrolledOwnerSigningKey} judged against an owner set the caller has already read. */
1400
- private ownerSigningKeyEnrolledIn;
1401
- /** This machine's identity key as an owner signing key, when its seed bootstrap is wired to sign; enrollment not yet checked. */
1402
- private ownerSigningKeyCandidate;
1436
+ /** {@link enrolledOwnerSigner} judged against an owner set the caller has already read. */
1437
+ private ownerSignerEnrolledIn;
1438
+ /**
1439
+ * The owner signer {@link useOwnerSigner} wired, or null: every owner signature this node
1440
+ * makes goes through it. Enrollment is not checked ({@link isOwner} does). A caller that
1441
+ * makes several owner writes as one action opens one `approve` on it and hands the
1442
+ * resulting `sign` to the methods that take one (`publishStrand`, `ensureStrandPartyKey`,
1443
+ * `publishFormationInvite`), as `adoptStrandBundle` does.
1444
+ */
1445
+ getOwnerSigner(): OwnerSigner | null;
1403
1446
  /**
1404
1447
  * Schedule the node's initial self-record publish + ongoing refresh shortly
1405
1448
  * after start (non-blocking). {@link registerSelf} is idempotent and safely
@@ -1501,41 +1544,15 @@ export declare class CadreNode implements SAppIdLookup {
1501
1544
  */
1502
1545
  private getSelfSigningKey;
1503
1546
  /**
1504
- * Does the `Strand` row name THIS machine as its founder — i.e. is its
1505
- * `FounderOwnerKey` this node's own owner key (the key behind its PeerId)?
1506
- * A null/absent column (a consent-seated strand, or a hand-built row) and a
1507
- * node with no owner key both derive `false`: without a positive match this
1508
- * machine must attach, never bootstrap. Pure key derivation, no I/O — cheap
1509
- * enough for {@link launchStrand}'s tracked-instance early return.
1510
- *
1511
- * NOTE: reads "one owner key per machine" — the reference model, where the owner
1512
- * key IS the key behind the PeerId. Two machines running the SAME identity key
1513
- * would both derive `true` and each bootstrap on its own replica, which is the
1514
- * double-`Header` hazard this derivation exists to avoid. Unreachable today: that
1515
- * configuration also gives both machines one PeerId, which already breaks control
1516
- * networking well before any strand launches. Revisit if machines ever share an
1517
- * owner key while holding distinct transport identities — the derivation would
1518
- * then need a per-machine discriminator (the `CadrePeer` PeerId) on the row.
1547
+ * Does the `Strand` row name THIS machine as its founder — is its `FounderPeerId` this
1548
+ * node's peer id? A null column (a consent-seated strand, or a hand-built row) derives
1549
+ * `false`: without a positive match this machine must attach, never bootstrap. A peer id
1550
+ * rather than an owner key, so two machines holding one owner key never both bootstrap
1551
+ * (the double-`Header` hazard), and a node derives it before any owner signer is wired.
1552
+ * Pure comparison, no I/O — cheap enough for {@link launchStrand}'s tracked-instance
1553
+ * early return.
1519
1554
  */
1520
1555
  private isSelfFoundedRow;
1521
- /**
1522
- * The owner keypair (base64url Ed25519) derived from this node's resolved
1523
- * identity key. In the single-key reference model the owner signing key is
1524
- * *derived from* the node identity (see {@link ed25519KeyPairFromLibp2p}), so the
1525
- * same key material protected in a secure enclave backs both.
1526
- *
1527
- * Exposed so the hosting app retains control of owner genesis: cadre-core
1528
- * resolves + protects the identity, then the app sources this pair to drive
1529
- * `ensureOwnerKey(pub)` + `initializeSeedBootstrap(priv)` itself — cadre-core
1530
- * never silently runs genesis. A future separate-owner slot would return a
1531
- * distinct key here instead of the identity-derived one.
1532
- *
1533
- * @returns The base64url seed/public-key owner pair.
1534
- * @throws If called before {@link start} has resolved the identity, or when the
1535
- * node runs on an ephemeral libp2p key (no `keyStore`/`privateKey` configured),
1536
- * since that key is internal to libp2p and not exposed.
1537
- */
1538
- getIdentityOwnerKey(): Ed25519KeyPair;
1539
1556
  /**
1540
1557
  * Publish this node's own address record from the reconcile pass until its first publish of
1541
1558
  * this session lands. A node with no owner key — seeded (`addDrone`, `cadre enroll add`),
@@ -1847,8 +1864,8 @@ export declare class CadreNode implements SAppIdLookup {
1847
1864
  */
1848
1865
  private peerStoreAddrs;
1849
1866
  /**
1850
- * Retain a just-applied seed's owner-flagged peers as cold-start bootstrap
1851
- * dial targets (see {@link bootstrapPeerStore}).
1867
+ * Retain a just-applied seed's preferred peers (`SeedPeer.preferred`) as cold-start
1868
+ * bootstrap dial targets (see {@link bootstrapPeerStore}).
1852
1869
  *
1853
1870
  * Called for every seed this node accepts, on BOTH intake paths — the
1854
1871
  * {@link applySeed} wrapper (which may run on a throwaway service, so the
@@ -1856,7 +1873,7 @@ export declare class CadreNode implements SAppIdLookup {
1856
1873
  * `/sereus/seed/1.0.0` handler via `onSeedApplied`. Peers with no address are
1857
1874
  * skipped: there is nothing to dial.
1858
1875
  *
1859
- * `isOwner` is the seed's own claim about its peers, exactly as
1876
+ * `preferred` is the seed's own claim about its peers, exactly as
1860
1877
  * `SeedBootstrapService.applySeed` uses it to choose its dial targets. That is
1861
1878
  * sound here for the same reason it is sound there — the whole seed is
1862
1879
  * signature-checked against a trust-anchored signer before this runs, and the
@@ -1886,12 +1903,12 @@ export declare class CadreNode implements SAppIdLookup {
1886
1903
  private retainDialTarget;
1887
1904
  /**
1888
1905
  * Cold-start branch of the reconcile pass: re-dial every retained out-of-band
1889
- * dial target (see {@link bootstrapPeerStore}) — in practice the owner peers of
1890
- * the seeds this node applied — while the control database still has no
1906
+ * dial target (see {@link bootstrapPeerStore}) — in practice the preferred peers
1907
+ * of the seeds this node applied — while the control database still has no
1891
1908
  * siblings to dial.
1892
1909
  *
1893
- * `SeedBootstrapService.applySeed` dials those owners exactly ONCE, best-effort.
1894
- * When that single dial fails — owner momentarily down, relay reservation not
1910
+ * `SeedBootstrapService.applySeed` dials those peers exactly ONCE, best-effort.
1911
+ * When that single dial fails — peer momentarily down, relay reservation not
1895
1912
  * up yet, NAT traversal lost the race — the joining node has an empty
1896
1913
  * `CadrePeer` table and no connection, and the steady-state pass above cannot
1897
1914
  * help: it dials only siblings enumerated from that very table. Retrying here
@@ -1901,9 +1918,10 @@ export declare class CadreNode implements SAppIdLookup {
1901
1918
  * gated by "the control database has no siblings", so it stops the moment the
1902
1919
  * node is actually in the party, and a node that is stranded MUST keep trying —
1903
1920
  * a give-up rule would turn a transient outage into a permanent one. The steady
1904
- * cost is one dial per bootstrap peer per pass (seeds nominate one or a few
1905
- * owners), each failing fast against an unreachable address.
1906
- * NOTE: if seeds ever carry many owner peers, or the reconcile cadence tightens
1921
+ * cost is one dial per bootstrap peer per pass (a seed prefers its minter and the
1922
+ * party's always-on machines, a few at most), each failing fast against an
1923
+ * unreachable address.
1924
+ * NOTE: if seeds ever carry many preferred peers, or the reconcile cadence tightens
1907
1925
  * well below its 15 s default, add per-peer backoff here.
1908
1926
  *
1909
1927
  * Best-effort per peer, exactly like {@link dialControlSibling}: one dead
@@ -2322,7 +2340,7 @@ export declare class CadreNode implements SAppIdLookup {
2322
2340
  * `Strand` row. A joiner (the row arrived over the control network) wants exactly this; a
2323
2341
  * FOUNDER wants {@link foundStrand}, which publishes and attaches in one resumable call.
2324
2342
  * With no explicit `founder` flag, founder-ness is derived from the row's
2325
- * `FounderOwnerKey` (see {@link StrandConfig.founder}) — so attaching a row THIS machine
2343
+ * `FounderPeerId` (see {@link StrandConfig.founder}) — so attaching a row THIS machine
2326
2344
  * published (e.g. re-attaching its own orphan after a restart) founds it, and attaching
2327
2345
  * anyone else's row joins, without the caller needing to know which it is.
2328
2346
  *
@@ -2393,11 +2411,11 @@ export declare class CadreNode implements SAppIdLookup {
2393
2411
  * fresh publish also clears it for an OPEN strand, but is destructive for a closed one
2394
2412
  * (the key exists nowhere else). No app-data wipe is needed for either.
2395
2413
  *
2396
- * The insert is signed with the ed25519 key behind this node's PeerId — which
2397
- * {@link ed25519KeyPairFromLibp2p} also exposes as the node's owner keypair,
2398
- * so peer identity and owner key are one and the same. That key is also persisted on the
2399
- * row as `FounderOwnerKey` (the schema pins the column to the verified signer), which is
2400
- * what later lets any launch derive "this machine is the founder" from the row alone.
2414
+ * The insert is signed through this node's owner signer ({@link getOwnerSigner}), in one
2415
+ * approval with a closed strand's party-key mint. The signer's key is persisted on the row
2416
+ * as `FounderOwnerKey` (the schema pins the column to the verified signer), and this node's
2417
+ * peer id as `FounderPeerId` (bound into the signature), which is what later lets any
2418
+ * launch derive "this machine is the founder" from the row alone.
2401
2419
  * The key must be enrolled in `OwnerKey` (e.g. via {@link ControlDatabase.ensureOwnerKey}
2402
2420
  * at genesis) or the schema's `Strand.AuthorizedInsert` constraint rejects the write.
2403
2421
  * Failing loudly here is intentional: a silently-unpublished strand would run
@@ -2413,15 +2431,23 @@ export declare class CadreNode implements SAppIdLookup {
2413
2431
  * {@link addStrand}).
2414
2432
  * @param type - `'o'` for open (default) or `'c'` for closed.
2415
2433
  * @param memberPrivateKey - Optional shared membership (read) key for a closed strand.
2434
+ * @param sign - The `sign` of an owner approval the caller already holds
2435
+ * ({@link getOwnerSigner}), so a larger action asks once; omitted, this asks for its own.
2416
2436
  * @returns The live `Strand` row — the one just inserted, or the matching one already
2417
2437
  * there. A closed strand's caller should carry THIS row's `MemberPrivateKey` forward:
2418
2438
  * on a repeat it is the stored key, not the argument.
2419
- * @throws if the node is not started, exposes no owner signing key, the id is blank or
2439
+ * @throws if the node is not started, has no owner signer wired, the id is blank or
2420
2440
  * unusable as a storage scope key (`InvalidStrandIdError` — see `storage-scope.ts`), a
2421
2441
  * row with the same id holds different content, or the control DB rejects the
2422
2442
  * (unauthorized) insert.
2423
2443
  */
2424
- publishStrand(strandId: string, type?: 'o' | 'c', memberPrivateKey?: string): Promise<StrandRow>;
2444
+ publishStrand(strandId: string, type?: 'o' | 'c', memberPrivateKey?: string, sign?: OwnerSign): Promise<StrandRow>;
2445
+ /**
2446
+ * The write half of {@link publishStrand}: insert `desired`, adopting a matching row a
2447
+ * concurrent founder landed first, then mint a closed strand's party key, all under one
2448
+ * owner approval's `sign`.
2449
+ */
2450
+ private insertStrandRow;
2425
2451
  /**
2426
2452
  * Found a strand — publish its row cadre-wide AND start the local instance as its
2427
2453
  * founder — in one resumable call. The single entry point for creating a strand; a
@@ -2438,7 +2464,7 @@ export declare class CadreNode implements SAppIdLookup {
2438
2464
  * attempt (as the reference apps do) would otherwise present a key that does not match
2439
2465
  * the membership already seated in the strand.
2440
2466
  * - **instance already running** → the tracked instance is returned, and — because the
2441
- * `Strand` row records its publishing machine (`FounderOwnerKey`) — a founder request
2467
+ * `Strand` row records its founding machine (`FounderPeerId`) — a founder request
2442
2468
  * against an instance that was first launched as a joiner now runs the (idempotent,
2443
2469
  * insert-if-absent) founder bootstrap on it rather than silently skipping it
2444
2470
  * ({@link StrandInstanceManager.foundExistingStrand}). So a strand that something else
@@ -2447,7 +2473,7 @@ export declare class CadreNode implements SAppIdLookup {
2447
2473
  * `resolveCohortSeed` window — still ends up with its `Strand.Header` written.
2448
2474
  *
2449
2475
  * Founder-ness is resolved FROM THE ROW, not assumed: this machine founds iff the
2450
- * resolved row's `FounderOwnerKey` is its own owner key. A fresh publish records this
2476
+ * resolved row's `FounderPeerId` is its own peer id. A fresh publish records this
2451
2477
  * machine, so the common path founds; a machine that lost a concurrent founding race to
2452
2478
  * a sibling adopts the sibling's row and ATTACHES instead — deliberately, since two
2453
2479
  * machines bootstrapping the same strand on separate replicas is the double-`Header`
@@ -2456,12 +2482,41 @@ export declare class CadreNode implements SAppIdLookup {
2456
2482
  * `Type` is compared, not adopted: a stored row of the other type means the caller and
2457
2483
  * the control plane disagree about what this strand IS, so it throws.
2458
2484
  *
2485
+ * **Returning after the publish.** `foundStrand(config, { background: true })` resolves
2486
+ * once the row is published and continues the local start in
2487
+ * {@link FoundStrandBackgroundResult.ready}, for a caller that needs only the row (an
2488
+ * invitation reads just its id and membership key). Publish errors still reject the call
2489
+ * itself. A joiner that redeems an invitation to a closed strand before its start finishes
2490
+ * is refused with the retryable `host-strand-unavailable` and the token stays unspent:
2491
+ * {@link requestJoin} retries that on its backoff, while {@link formStrand} throws it to its
2492
+ * caller once every machine the invitation names has refused.
2493
+ *
2459
2494
  * @returns The instance AND the row the strand actually runs under — read the membership
2460
2495
  * key from the returned row, not from a freshly minted one ({@link FoundStrandResult}).
2461
2496
  * @throws if the node is not started, the id is blank, a published row of the same id has
2462
2497
  * a different `Type`, or either half rejects.
2463
2498
  */
2464
2499
  foundStrand(config: FoundStrandConfig): Promise<FoundStrandResult>;
2500
+ /**
2501
+ * Publish the strand's row as {@link foundStrand} does, and resolve once it is published;
2502
+ * the local start continues in {@link FoundStrandBackgroundResult.ready}.
2503
+ *
2504
+ * @throws as the default form does for the publish half; the start's own failure rejects
2505
+ * `ready` instead.
2506
+ */
2507
+ foundStrand(config: FoundStrandConfig, options: FoundStrandOptions): Promise<FoundStrandBackgroundResult>;
2508
+ /**
2509
+ * The publish half of {@link foundStrand}: adopt the stored row or publish a new one, and
2510
+ * derive whether this machine founds it. Every check that can refuse the founding is here,
2511
+ * so both forms of `foundStrand` reject on it before returning.
2512
+ */
2513
+ private publishFoundingRow;
2514
+ /**
2515
+ * The start half of {@link foundStrand}. Its timing lines (`addStrand: start`, then the
2516
+ * duration or `failed after`) are logged when the start settles, also in the background
2517
+ * form, so a start that hangs still shows as a start line with no end line.
2518
+ */
2519
+ private attachFoundingRow;
2465
2520
  /**
2466
2521
  * Resume onto an already-published `Strand` row: keep its stored content, reject a type
2467
2522
  * disagreement. The stored `MemberPrivateKey` is authoritative — see {@link foundStrand}
@@ -2521,7 +2576,7 @@ export declare class CadreNode implements SAppIdLookup {
2521
2576
  * step, not a widening of this method.
2522
2577
  *
2523
2578
  * @param strandId - The `Strand` row id to remove (as passed to {@link publishStrand}).
2524
- * @throws if the node is not started, exposes no owner signing key, the id is blank, or
2579
+ * @throws if the node is not started, has no owner signer wired, the id is blank, or
2525
2580
  * the signer is not an enrolled owner (the schema's `Strand.AuthorizedDelete` rejects
2526
2581
  * the write and the row survives). A rejection does NOT imply the row survived: the
2527
2582
  * local stop runs after the control-plane delete has already committed, so a failure
@@ -2552,15 +2607,17 @@ export declare class CadreNode implements SAppIdLookup {
2552
2607
  * `generateStrandMemberKey` mints). When a row already holds a DIFFERENT key this
2553
2608
  * throws — a party has one identity per strand; rotation is a deliberate
2554
2609
  * remove-then-insert, not a silent swap.
2610
+ * @param sign - The `sign` of an owner approval the caller already holds; omitted, a
2611
+ * mint asks for its own (a stored key asks for none).
2555
2612
  * @returns The live party key: the one just seated, or the stored one.
2556
2613
  */
2557
- ensureStrandPartyKey(strandId: string, partyMemberPrivateKey?: string): Promise<string>;
2614
+ ensureStrandPartyKey(strandId: string, partyMemberPrivateKey?: string, sign?: OwnerSign): Promise<string>;
2558
2615
  /**
2559
2616
  * Resolve the party membership key a closed strand's launch threads into the founder
2560
2617
  * bootstrap: the explicit attach-time key when given, else the party's persisted
2561
- * `StrandPartyKey` row, else — on the one machine whose owner key the row names as
2562
- * founder — a freshly minted-and-persisted key, which seats the identity for a publish
2563
- * that was interrupted before its mint. Everyone else resolves undefined: non-founding
2618
+ * `StrandPartyKey` row, else — on the one machine the row names as founder (`FounderPeerId`)
2619
+ * — a freshly minted-and-persisted key, which seats the identity for a publish that was
2620
+ * interrupted before its mint. Everyone else resolves undefined: non-founding
2564
2621
  * machines never mint (no mint race between a party's machines), and only a founder
2565
2622
  * bootstrap needs the key at all.
2566
2623
  *
@@ -2589,22 +2646,23 @@ export declare class CadreNode implements SAppIdLookup {
2589
2646
  * from this row. A host minting a closed-strand invite does both (mint +
2590
2647
  * publish), exactly as the integration harness's `createInvitation` does.
2591
2648
  *
2592
- * Signs with the same self-owner key as {@link publishStrand} (the ed25519
2593
- * key behind this node's PeerId, which must be an enrolled `OwnerKey`).
2594
- * Throws loudly if the node isn't started or exposes no signing key.
2649
+ * Signs through the same owner signer as {@link publishStrand}, whose key must be an
2650
+ * enrolled `OwnerKey`. Throws loudly if the node isn't started or has no owner signer wired.
2595
2651
  *
2596
2652
  * @param token - Invitation token (the `FormationInvite` primary key); use the
2597
2653
  * `token` of the {@link OpenInvitation} from {@link createOpenInvitation}.
2598
2654
  * @param sAppId - The sApp a redeemed strand will use.
2599
2655
  * @param options - Optional `expiresAtMs` (epoch ms), `totalUses`, `validationUrl`,
2600
2656
  * `strandId` (bind a closed/pre-existing host strand for provision-then-record).
2657
+ * @param sign - The `sign` of an owner approval the caller already holds; omitted, this
2658
+ * asks for its own.
2601
2659
  */
2602
2660
  publishFormationInvite(token: string, sAppId: string, options?: {
2603
2661
  expiresAtMs?: number;
2604
2662
  totalUses?: number;
2605
2663
  validationUrl?: string;
2606
2664
  strandId?: string;
2607
- }): Promise<void>;
2665
+ }, sign?: OwnerSign): Promise<void>;
2608
2666
  /**
2609
2667
  * Enroll an approver public key allowed to sign off on `ValidationUrl` redemptions.
2610
2668
  *
@@ -2613,10 +2671,10 @@ export declare class CadreNode implements SAppIdLookup {
2613
2671
  * — this is how a party says which outside approver it trusts. Without an enrollment,
2614
2672
  * every such invitation is unredeemable.
2615
2673
  *
2616
- * Signs with the same self-owner key as {@link publishStrand} (the ed25519 key behind
2617
- * this node's PeerId, which must be an enrolled `OwnerKey`). Throws loudly if the node
2618
- * isn't started, exposes no signing key, or the key is blank — a blank key would reach
2619
- * the database and fail the signature CHECK as an opaque constraint error.
2674
+ * Signs through the same owner signer as {@link publishStrand}, whose key must be an
2675
+ * enrolled `OwnerKey`. Throws loudly if the node isn't started, has no owner signer wired,
2676
+ * or the key is blank — a blank key would reach the database and fail the signature CHECK
2677
+ * as an opaque constraint error.
2620
2678
  *
2621
2679
  * Enrolling this node's OWN owner key is permitted and harmless: the domain/action tags
2622
2680
  * baked into each authorization digest mean an owner-key signature can never satisfy the
@@ -2650,22 +2708,22 @@ export declare class CadreNode implements SAppIdLookup {
2650
2708
  removeValidationKey(key: string): Promise<void>;
2651
2709
  /**
2652
2710
  * The approver keys currently enrolled, sorted. Read-only, so unlike
2653
- * {@link enrollValidationKey} / {@link removeValidationKey} it needs no owner signing
2654
- * key — only a started node.
2711
+ * {@link enrollValidationKey} / {@link removeValidationKey} it needs no owner signer —
2712
+ * only a started node.
2655
2713
  */
2656
2714
  listValidationKeys(): Promise<string[]>;
2657
2715
  /**
2658
- * Resolve the owner keypair every owner-signed control write needs — {@link publishStrand},
2659
- * {@link unpublishStrand}, {@link publishFormationInvite}, {@link enrollValidationKey},
2660
- * {@link removeValidationKey}
2661
- * — failing loudly in one two-part shape (not started / no signing key, naming owner
2662
- * genesis as the fix). Narrows `controlDatabase` for the caller: a non-null return means
2716
+ * The owner signer every owner-signed strand write needs — {@link publishStrand},
2717
+ * {@link unpublishStrand}, {@link ensureStrandPartyKey}, {@link publishFormationInvite},
2718
+ * {@link enrollValidationKey}, {@link removeValidationKey} — failing loudly in one two-part
2719
+ * shape (not started / no signer wired, naming the wiring call as the fix). Enrollment is the
2720
+ * schema's to judge. Narrows `controlDatabase` for the caller: a return means
2663
2721
  * `this.controlDatabase` is non-null too.
2664
2722
  *
2665
2723
  * @param action - Infinitive phrase naming the attempted write, e.g. `'enroll a
2666
2724
  * validation key'`; interpolated into both messages.
2667
2725
  */
2668
- private requireOwnerSigningKey;
2726
+ private requireOwnerSigner;
2669
2727
  /**
2670
2728
  * Shared strand launch path for both the explicit (`addStrand`) and the
2671
2729
  * control-discovered (`handleStrandAdded`) entry points. Resolves the cohort
@@ -2673,10 +2731,10 @@ export declare class CadreNode implements SAppIdLookup {
2673
2731
  * before emitting `strand:started`.
2674
2732
  *
2675
2733
  * Founder-ness: an explicit `founder` argument wins (the formation/responder flows pass
2676
- * one deliberately — their consent-seated rows carry a null `FounderOwnerKey`); when
2734
+ * one deliberately — their consent-seated rows carry a null `FounderPeerId`); when
2677
2735
  * unset it is DERIVED from the row, so the control-discovered path
2678
2736
  * (`handleStrandAdded`) and a restart's re-attach found this machine's own strands
2679
- * without the caller having to know. The derivation is pure key comparison — no I/O.
2737
+ * without the caller having to know. The derivation is a pure peer-id comparison — no I/O.
2680
2738
  *
2681
2739
  * Idempotent when the strand manager already tracks `strand.Id` — the watcher
2682
2740
  * rediscovers a row this node already started and calls this again via
@@ -2745,10 +2803,11 @@ export declare class CadreNode implements SAppIdLookup {
2745
2803
  * receiver records an admission grant BEFORE `libp2p.start()` (re-)dials the
2746
2804
  * relay reservation — the gate denial there is fatal, not degraded. A
2747
2805
  * party-member relay answers the RPC (its control node admits us as a
2748
- * member); a dedicated ops/ relay does not speak the protocol and the
2749
- * per-peer failure comes back `unreachable` — harmless, it has no membership gate and
2750
- * needs no grant. The relay's direct addr rides along as the dial fallback
2751
- * for a relay we are not yet connected to.
2806
+ * member); a dedicated ops/ relay does not speak the protocol, comes back
2807
+ * `unsupported`, and is left out of later passes
2808
+ * ({@link relaysRefusingStrandAddr}) — it has no membership gate and needs no
2809
+ * grant. The relay's direct addr rides along as the dial fallback for a relay
2810
+ * we are not yet connected to.
2752
2811
  */
2753
2812
  private resolveCohortSeed;
2754
2813
  /**
@@ -2811,20 +2870,25 @@ export declare class CadreNode implements SAppIdLookup {
2811
2870
  * `libp2p.start()`. Kept because it costs one spread and this is the one place
2812
2871
  * that would silently stop announcing if that rejection is ever relaxed.
2813
2872
  *
2873
+ * Less the relays remembered as refusing the strand-addr protocol
2874
+ * ({@link relaysRefusingStrandAddr}): both callers are announcement passes, and
2875
+ * those relays hold no grant.
2876
+ *
2814
2877
  * NOTE: a relay the STRAND node discovers on its own (autorelay — in neither
2815
2878
  * source) gets no announcement, and a membership-gated one will deny it; fine
2816
2879
  * now, every realistic topology feeds one of the two sources.
2817
2880
  */
2818
2881
  private circuitRelayTargets;
2819
2882
  /**
2820
- * Record the announce timestamps {@link refreshDelegateGrants} throttles on,
2821
- * for every relay a delegate-carrying announce pass dialed.
2883
+ * Record what a delegate-carrying announce pass learned about the relays it
2884
+ * dialed: the announce timestamps {@link refreshDelegateGrants} throttles on,
2885
+ * and from `outcomes` which relays refuse the strand-addr protocol
2886
+ * ({@link relaysRefusingStrandAddr}). Every announce site records through here.
2822
2887
  *
2823
- * Recorded OPTIMISTICALLY at announce time, whatever `collectStrandAddrs`
2824
- * reports per peer: a dedicated `ops/` relay never speaks the strand-addr
2825
- * protocol, so recording only on success would re-announce to it on every
2826
- * reconcile tick, one wasted protocol negotiation each. A failed INITIAL announce
2827
- * costs the strand supervisor its first attempt only (the relay denies the
2888
+ * Timestamps are recorded OPTIMISTICALLY, whatever `collectStrandAddrs` reports
2889
+ * per peer: recording only on success would re-announce to an unreachable relay
2890
+ * on every reconcile tick, one dial timeout each. A failed INITIAL announce costs
2891
+ * the strand supervisor its first attempt only (the relay denies the
2828
2892
  * reservation; every re-drive re-announces first through
2829
2893
  * {@link announceDelegateToRelay}); a failed REFRESH retries within
2830
2894
  * `DELEGATE_GRANT_TTL_MS / 2` (15 min), still inside the 30 min TTL.
@@ -2860,11 +2924,12 @@ export declare class CadreNode implements SAppIdLookup {
2860
2924
  * The throttle map is updated afterwards, so the periodic pass does not announce
2861
2925
  * again right away.
2862
2926
  *
2863
- * Against a dedicated ops relay (no strand-addr RPC) the request fails per-peer
2864
- * and `collectStrandAddrs` reports it `unreachable`: one wasted protocol negotiation per
2865
- * re-drive attempt, bounded by the supervisor's backoff. Never throws on that
2866
- * path; a relay addr that names no peer id is logged and skipped (the hook's
2867
- * caller runs the drive regardless).
2927
+ * Unlike the other announce passes, this asks a relay remembered in
2928
+ * {@link relaysRefusingStrandAddr} too, and its reply clears the entry — see
2929
+ * there for why. Against a dedicated ops relay (no strand-addr RPC) that costs
2930
+ * one refused protocol negotiation (`unsupported`) per re-drive attempt, bounded
2931
+ * by the supervisor's backoff. Never throws on that path; a relay addr that names
2932
+ * no peer id is logged and skipped (the hook's caller runs the drive regardless).
2868
2933
  *
2869
2934
  * NOTE: against a relay that is DOWN this hook costs up to two strand-addr
2870
2935
  * timeouts (dial by peer id, then by addr; 28.5 s each at the default declared link round trip)
@@ -3342,10 +3407,26 @@ export declare class CadreNode implements SAppIdLookup {
3342
3407
  */
3343
3408
  getSAppConfig(strandId: string): SAppConfig | undefined;
3344
3409
  /**
3345
- * Initialize the seed bootstrap service with an owner key.
3346
- * Must be called before using seed-related methods that require signing.
3410
+ * Make `signer` this node's owner signer (`owner-signer.ts`): every owner signature the node
3411
+ * produces from now on goes through it — seeds, vouches, removals, invitations, strand and
3412
+ * join rows, and the background re-issues. Builds the owner-capable seed bootstrap service
3413
+ * around it, so this must come before any of those. One signer per node; a node started
3414
+ * with `claim` is refused (its claimant owns it).
3347
3415
  *
3348
- * @param ownerPrivateKey - The owner's private key (base64url encoded)
3416
+ * Anchors the signer's key in the node-local trusted-owner store as `genesis`: wiring a key
3417
+ * declares it an authority of this party.
3418
+ *
3419
+ * @throws when the node is not started, was started with `claim`, or already holds the
3420
+ * seed protocol handler (a second wiring, or {@link enableSeedListener} first).
3421
+ */
3422
+ useOwnerSigner(signer: OwnerSigner): Promise<void>;
3423
+ /**
3424
+ * {@link useOwnerSigner} with a software signer over `ownerPrivateKey` (`softwareOwnerSigner`):
3425
+ * the owner key held in this process's memory. Kept as a convenience because most tests and
3426
+ * the embedders that hold the key themselves wire it this way; one that keeps the key
3427
+ * elsewhere wires its own signer.
3428
+ *
3429
+ * @param ownerPrivateKey - The owner key's 32-byte Ed25519 seed, base64url.
3349
3430
  */
3350
3431
  initializeSeedBootstrap(ownerPrivateKey: string): Promise<void>;
3351
3432
  /**
@@ -3392,15 +3473,12 @@ export declare class CadreNode implements SAppIdLookup {
3392
3473
  * never reach here: {@link ControlDatabase.queryCadrePeers} excludes them, so a
3393
3474
  * revoked peer is not addressable either.
3394
3475
  *
3395
- * NOTE: answers the whole `CadrePeerRow`, past the two fields declared; the integration
3476
+ * NOTE: answers the whole `CadrePeerRow`, past the fields declared; the integration
3396
3477
  * harness (`owner-cli-node.ts`, `OwnerCliMemberRow.sig`) reads `sig` off it through the admin
3397
3478
  * channel to tell a row another member seated from one the subject published itself. Narrow
3398
3479
  * this to the declared fields only together with that reader.
3399
3480
  */
3400
- listMembers(): Promise<Array<{
3401
- peerId: string;
3402
- multiaddr: string | null;
3403
- }>>;
3481
+ listMembers(): Promise<CohortPeerRow[]>;
3404
3482
  /**
3405
3483
  * Probe whether a given peer is a `CadrePeer` member.
3406
3484
  *
@@ -3524,11 +3602,9 @@ export declare class CadreNode implements SAppIdLookup {
3524
3602
  * NOTE: cost per membership refresh is one `OwnerKey` read and one `Revocation` read (the
3525
3603
  * tombstone list, whose `OwnerKey` stamps are handed to `queryOwnerKeyRows` so it does not
3526
3604
  * read the retired set again, and which the hang-up of removed peers shares through
3527
- * `loadTombstones`), plus the invitation chain when some owner row is
3528
- * invitation-admitted — shared with the membership read through `loadChain`; the memo
3529
- * suggestion on {@link loadInvitationChain} covers that part. Before the Revocation ledger
3530
- * marker is filed that one read is a cohort consult per refresh, which
3531
- * `control-founding-consult-budget.spec.ts` pins.
3605
+ * `loadTombstones`), plus the owner-grant chain when some owner row is grant-seated (the
3606
+ * NOTE in the body). Before the Revocation ledger marker is filed that one read is a cohort
3607
+ * consult per refresh, which `control-founding-consult-budget.spec.ts` pins.
3532
3608
  *
3533
3609
  * @param retry - forwarded to every read; `false` on the refresh path, for the reason on
3534
3610
  * {@link refreshAuthorizedControlPeers}.
@@ -3558,9 +3634,9 @@ export declare class CadreNode implements SAppIdLookup {
3558
3634
  /** A per-refresh {@link RevocationSource}: one `queryRevocations` read, shared by every caller in the pass. */
3559
3635
  private revocationSource;
3560
3636
  /**
3561
- * The usage and invitation rows {@link hasAnchoredProof} and {@link syncOwnerAnchor}
3562
- * resolve an invitation-admitted row through, keyed for lookup. Both reads honour `retry`
3563
- * for {@link listAuthorizedMembers}' reason.
3637
+ * The usage and invitation rows {@link hasAnchoredProof} resolves an invitation-admitted
3638
+ * `CadrePeer` row through, keyed for lookup. Both reads honour `retry` for
3639
+ * {@link listAuthorizedMembers}' reason.
3564
3640
  *
3565
3641
  * NOTE: once invitations are the only admission route (ticket
3566
3642
  * `cadre-invitations-redeemable-by-any-member`), every cadre with a member not yet
@@ -3571,6 +3647,12 @@ export declare class CadreNode implements SAppIdLookup {
3571
3647
  * when every admitted row is already memoised.
3572
3648
  */
3573
3649
  private loadInvitationChain;
3650
+ /**
3651
+ * The usage and grant rows {@link syncOwnerAnchor} resolves a grant-seated `OwnerKey` row
3652
+ * through, keyed for lookup: the owner-grant twin of {@link loadInvitationChain}, read only by
3653
+ * the anchor sync, since a grant admits no machine.
3654
+ */
3655
+ private loadOwnerGrantChain;
3574
3656
  /**
3575
3657
  * The owner-voucher half of {@link hasAnchoredProof}: complete voucher, `VouchOwner` in
3576
3658
  * the node-local anchor, signature valid over the row's (PeerId, StampId) voucher digest.
@@ -3635,11 +3717,11 @@ export declare class CadreNode implements SAppIdLookup {
3635
3717
  private installSeedBootstrapService;
3636
3718
  /**
3637
3719
  * The event callbacks every {@link SeedBootstrapService} this node owns is
3638
- * wired with — shared by the owner-capable service ({@link initializeSeedBootstrap})
3720
+ * wired with — shared by the owner-capable service ({@link useOwnerSigner})
3639
3721
  * and the listener-only one ({@link enableSeedListener}) so the two cannot drift.
3640
3722
  *
3641
3723
  * A seed applied by the INBOUND protocol handler writes no `CadrePeer` row of
3642
- * its own (it merges the libp2p peer store and dials owners), so the automatic
3724
+ * its own (it merges the libp2p peer store and dials the seed's preferred peers), so the automatic
3643
3725
  * write-driven refresh never fires for it — yet applying it can ANCHOR a new
3644
3726
  * owner key, which flips rows already present from unauthorized to authorized.
3645
3727
  * Hence the explicit refresh here; without it a peer the freshly anchored owner
@@ -3729,13 +3811,15 @@ export declare class CadreNode implements SAppIdLookup {
3729
3811
  * @param multiaddrs - Optional multiaddrs for the peer
3730
3812
  */
3731
3813
  authorizePeer(peerId: string, multiaddrs?: string[]): Promise<void>;
3814
+ /** {@link authorizePeer}'s body, signing inside the caller's owner approval when it passes `sign` ({@link claimNode}). */
3815
+ private vouchPeer;
3732
3816
  /**
3733
3817
  * Remove a previously-authorized peer from the cadre.
3734
3818
  * Signs the peer ID with the owner key and deletes the CadrePeer row.
3735
3819
  *
3736
- * Removes the `CadrePeer` row only. The call that removes a machine completely (its owner
3737
- * key, its push token and the invitations it could come back through, in one transaction)
3738
- * is {@link removeMachine}.
3820
+ * Removes the `CadrePeer` row only. The call that removes a machine completely (its push
3821
+ * token and the invitations it could come back through too, in one transaction) is
3822
+ * {@link removeMachine}.
3739
3823
  *
3740
3824
  * @param peerId - The peer ID to remove
3741
3825
  */
@@ -3745,55 +3829,121 @@ export declare class CadreNode implements SAppIdLookup {
3745
3829
  * `OwnerKey.Authorized`; the row stores the vouching pair). The write notifies the
3746
3830
  * membership hub, so the refresh it triggers has run by the time this resolves.
3747
3831
  *
3832
+ * Refused while alone ({@link ownerKeyWriteAlone}): an `OwnerKey` add has no re-issue path,
3833
+ * so one committed with no control connection would stay on this node.
3834
+ *
3748
3835
  * @param ownerPublicKeyB64 - the new owner's ed25519 public key (base64url).
3836
+ * @param options.label - the user's name for the key ("phone", "paper backup"), bound into
3837
+ * the signature; trimmed, at most 64 characters, blank meaning none.
3749
3838
  * @returns `true` when the row was seated, `false` when the key is already a live owner.
3839
+ * @throws OwnerKeyAddRefusedError `alone`, before any write.
3840
+ */
3841
+ addOwner(ownerPublicKeyB64: string, options?: {
3842
+ label?: string;
3843
+ }): Promise<boolean>;
3844
+ /**
3845
+ * Whether an `OwnerKey` add made now ({@link addOwner}, or the re-add of
3846
+ * {@link removeOwner}'s re-vouch) is refused `alone`: this node holds no control connection
3847
+ * while the party has other members. An `OwnerKey` row has no update to re-touch, so nothing
3848
+ * re-issues it on cohort growth, and a row written now would fork this node's history. A
3849
+ * party with no other members cannot fork, so a solo founder adding an owner key is not
3850
+ * refused.
3851
+ *
3852
+ * Waits out an in-flight membership refresh first: until `start`'s first one lands the
3853
+ * member snapshot is empty, which would read as a party with no other members.
3854
+ */
3855
+ private ownerKeyWriteAlone;
3856
+ /**
3857
+ * Every live owner key with its label, who vouched it, whether this node's owner signer
3858
+ * holds it and whether this node's trusted-owner anchor trusts it ({@link CadreOwnerKey}),
3859
+ * for an app's owner-key screen.
3860
+ */
3861
+ listOwnerKeys(): Promise<CadreOwnerKey[]>;
3862
+ /**
3863
+ * What {@link removeOwner} would do for `ownerPublicKeyB64`, read without writing, for an
3864
+ * app's confirmation step ({@link OwnerKeyRemovalPlan}): whether this node may remove it,
3865
+ * which rows the key vouched (so the app can offer to keep them by re-vouching), and how this
3866
+ * node's own rows depend on it (`planOwnerKeyRemoval` in `owner-keys.ts`).
3867
+ */
3868
+ planOwnerKeyRemoval(ownerPublicKeyB64: string): Promise<OwnerKeyRemovalPlan>;
3869
+ /**
3870
+ * Remove an owner key, signed by this node's owner key (which must be a different owner): the
3871
+ * `OwnerKey` row and its `Revocation` tombstone commit together, and the tombstone rides the
3872
+ * committed-delete seam ({@link noteGuardedDelete}), so one committed while alone is re-issued
3873
+ * on cohort growth like every other removal. The machines the key was used on stay members;
3874
+ * removing one is {@link removeMachine}. The owner grants the key issued stop being redeemable
3875
+ * (the plan's `grants` names the live ones) and are not withdrawn: a tombstone over a grant
3876
+ * nobody can redeem would add nothing.
3877
+ *
3878
+ * Steps: re-plan ({@link planOwnerKeyRemoval}) and throw {@link OwnerKeyRemovalRefusedError}
3879
+ * on a refusal; check `options.revouch` against the plan's dependents and throw before any
3880
+ * write when it names anything else; refuse `alone` ({@link ownerKeyWriteAlone}) when an
3881
+ * `OwnerKey` row is to be re-vouched, since that re-add has no re-issue path; **re-vouch
3882
+ * first**, under this node's key, the dependents the caller chose to keep and this node's own
3883
+ * row when the key vouched it (`ControlDatabase.revouchOwnerKey`, `reauthorizePeer`), so they
3884
+ * are never untrusted in between, and stop before the removal if one fails (re-vouches
3885
+ * already made stay, harmlessly); then the removal. The re-vouches and the removal are one owner approval. The
3886
+ * write notifies the membership hub, so the refresh it triggers has run by the time this
3887
+ * resolves.
3888
+ *
3889
+ * @param ownerPublicKeyB64 - the owner's ed25519 public key (base64url).
3890
+ * @returns what was written ({@link OwnerKeyRemovalResult}).
3891
+ * @throws {OwnerKeyRemovalRefusedError} `'not-an-owner'`, `'unknown'`, `'last-owner'`,
3892
+ * `'self'` or `'alone'`, before any write.
3750
3893
  */
3751
- addOwner(ownerPublicKeyB64: string): Promise<boolean>;
3894
+ removeOwner(ownerPublicKeyB64: string, options?: RemoveOwnerOptions): Promise<OwnerKeyRemovalResult>;
3895
+ /** {@link planOwnerKeyRemoval} with the rows it was judged from, which {@link removeOwner} re-vouches dependents by. */
3896
+ private planOwnerKeyRemovalWith;
3752
3897
  /**
3753
- * Remove an owner key, signed by this node's owner key (which must be a different owner):
3754
- * the delete and its `Revocation` tombstone commit together. Refused by name when the key
3755
- * is this node's own or the only live owner. The write notifies the membership hub; the
3756
- * tombstone rides the committed-delete seam ({@link noteGuardedDelete}), so one committed
3757
- * while alone is re-issued on cohort growth like every other removal.
3898
+ * The re-vouch step of {@link removeOwner}: every chosen dependent row is rebound to this
3899
+ * node's key BEFORE the removal, so no reader ever judges it against an owner that is gone.
3900
+ * Owners through `ControlDatabase.revouchOwnerKey` (a delete-and-re-add under a fresh stamp,
3901
+ * the only rebinding `OwnerKey.NoUpdate` allows); peers through the owner re-touch
3902
+ * (`reauthorizePeer`), which rewrites `VouchOwner`/`VouchSig` and clears `VouchUsage`.
3758
3903
  *
3759
- * Removes the `OwnerKey` row only. The call that removes a machine completely, its owner key
3760
- * included, is {@link removeMachine}.
3904
+ * The peer re-touch keeps the row's `UpdatedAt` where it is. The owner branch of
3905
+ * `CadrePeer.AuthorizedUpdate` does not require a bump, and bumping a self-published row
3906
+ * would leave its `Sig` unverifiable, so unresolvable and unlisted as self-published, until
3907
+ * the machine's next heartbeat (the NOTE on that constraint); the write-while-alone drain
3908
+ * bumps because it re-issues an otherwise unchanged row, whereas here the voucher itself
3909
+ * changes. A row never self-published carries no stamp to keep and gets the clock.
3761
3910
  *
3762
- * @param ownerPublicKeyB64 - the owner's ed25519 public key (base64url).
3763
- * @returns `true` when the row was removed, `false` when the key is not a live owner here.
3911
+ * A failure rethrows and stops the call before the removal; re-vouches already made stay
3912
+ * (the row is only re-signed by a current owner) and the caller retries. An owner re-vouch
3913
+ * is refused alone before this runs; one whose last connection drops while it runs is logged,
3914
+ * as {@link addOwner}'s add is.
3915
+ *
3916
+ * NOTE: a peer re-touch committed alone is not queued, and the drain skips self-published
3917
+ * rows anyway, so the other machines may get the removal's tombstone without the new voucher
3918
+ * (`tickets/backlog/bug-owner-key-removal-revouch-made-alone-is-not-reissued`).
3764
3919
  */
3765
- removeOwner(ownerPublicKeyB64: string): Promise<boolean>;
3920
+ private revouchDependents;
3766
3921
  /**
3767
3922
  * The cadre's machines as an app lists them ({@link CadreMachine}): one entry per live
3768
- * `CadrePeer` row, judged by this node's membership predicate, plus one per live `OwnerKey`
3769
- * whose key names a machine with no row (`describeMachines` in `machine-removal.ts`).
3923
+ * `CadrePeer` row, judged by this node's membership predicate (`describeMachines` in
3924
+ * `machine-removal.ts`). Owner keys are not machines; {@link listOwnerKeys} lists them.
3770
3925
  * Unlocked reads with the default retry.
3771
3926
  */
3772
3927
  listMachines(): Promise<CadreMachine[]>;
3773
3928
  /**
3774
3929
  * What {@link removeMachine} would do for `peerId`, read without writing, for an app's
3775
- * confirmation step ({@link MachineRemovalPlan}): whether this node may remove it, whether its
3776
- * owner key goes with it, which rows that key vouched (so the app can offer to keep them by
3777
- * re-vouching), which invitations it could come back through, and whether this node's own
3778
- * rows depend on it (`planMachineRemoval` in `machine-removal.ts`).
3930
+ * confirmation step ({@link MachineRemovalPlan}): whether this node may remove it and which
3931
+ * invitations it could come back through (`planMachineRemoval` in `machine-removal.ts`).
3779
3932
  */
3780
3933
  planMachineRemoval(peerId: string): Promise<MachineRemovalPlan>;
3781
3934
  /**
3782
3935
  * Remove a machine from the cadre completely, signed by this node's owner key: its
3783
- * `CadrePeer` row, its `OwnerKey` row when its key is an owner, its `DeviceToken` row (a
3784
- * removed phone must not be push-woken) and a withdrawal of every live invitation it could
3785
- * redeem to come back, in ONE transaction ({@link ControlDatabase.removeMachine}), with a
3786
- * `Revocation` tombstone per row so the removal holds on every machine
3787
- * ("Deletes made while alone" in docs/architecture.md).
3788
- *
3789
- * Steps: re-plan ({@link planMachineRemoval}) and throw {@link MachineRemovalRefusedError} on a
3790
- * refusal; check `options.revouch` against the plan's dependents and throw before any write
3791
- * when it names anything else; **re-vouch first**, under this node's key, the dependents the
3792
- * caller chose to keep (`ControlDatabase.revouchOwnerKey`, `reauthorizePeer`), so they are
3793
- * never untrusted in between, and stop before the removal if one fails (re-vouches already
3794
- * made stay, harmlessly); then the removal transaction. Afterwards this node forgets the
3795
- * machine as a dial target, and the refresh the write notifies hangs up its control
3796
- * connection ({@link hangUpRevokedControlPeers}) before this resolves.
3936
+ * `CadrePeer` row, its `DeviceToken` row (a removed phone must not be push-woken) and a
3937
+ * withdrawal of every live invitation it could redeem to come back, in ONE transaction
3938
+ * ({@link ControlDatabase.removeMachine}), with a `Revocation` tombstone per row so the
3939
+ * removal holds on every machine ("Deletes made while alone" in docs/architecture.md), under
3940
+ * one owner approval. Afterwards this node forgets the machine as a dial target, and the
3941
+ * refresh the write notifies hangs up its control connection ({@link hangUpRevokedControlPeers})
3942
+ * before this resolves.
3943
+ *
3944
+ * Owner keys stay: they are the person's, not the machine's. A lost phone that held one can
3945
+ * still sign, a fresh machine invitation it could redeem included, so removing that key
3946
+ * ({@link removeOwner}) is the step that locks it out.
3797
3947
  *
3798
3948
  * Not undone by this call: the machine keeps every row and strand block it already synced
3799
3949
  * and the party's strand member keys (`tickets/blocked/decide-removed-machine-strand-access`).
@@ -3801,37 +3951,14 @@ export declare class CadreNode implements SAppIdLookup {
3801
3951
  *
3802
3952
  * @returns what was written ({@link MachineRemovalResult}); all false when no live row names
3803
3953
  * the machine here (already removed elsewhere).
3804
- * @throws {MachineRemovalRefusedError} `'not-an-owner'`, `'self'` or `'last-owner'`.
3954
+ * @throws {MachineRemovalRefusedError} `'not-an-owner'` or `'self'`, before any write.
3805
3955
  */
3806
- removeMachine(peerId: string, options?: RemoveMachineOptions): Promise<MachineRemovalResult>;
3956
+ removeMachine(peerId: string): Promise<MachineRemovalResult>;
3807
3957
  /**
3808
- * The reads behind {@link listMachines}: the live `CadrePeer` and `OwnerKey` rows and the
3809
- * machines projected from them, with this node's membership predicate as the `trusted` test.
3958
+ * The read behind {@link listMachines}: the live `CadrePeer` rows projected as machines, with
3959
+ * this node's membership predicate as the `trusted` test.
3810
3960
  */
3811
3961
  private readMachines;
3812
- /** {@link planMachineRemoval} with the rows it was judged from, which {@link removeMachine} re-vouches dependents by. */
3813
- private planMachineRemovalWith;
3814
- /**
3815
- * The re-vouch step of {@link removeMachine}: every chosen dependent row is rebound to this
3816
- * node's key BEFORE the removal, so no reader ever judges it against an owner that is gone.
3817
- * Owners through `ControlDatabase.revouchOwnerKey` (a delete-and-re-add under a fresh stamp,
3818
- * the only rebinding `OwnerKey.NoUpdate` allows); peers through the owner re-touch
3819
- * (`reauthorizePeer`), which rewrites `VouchOwner`/`VouchSig` and clears `VouchUsage`.
3820
- *
3821
- * The peer re-touch keeps the row's `UpdatedAt` where it is. The owner branch of
3822
- * `CadrePeer.AuthorizedUpdate` does not require a bump, and bumping a self-published row
3823
- * would leave its `Sig` unverifiable, so unresolvable and unlisted as self-published, until
3824
- * the machine's next heartbeat (the NOTE on that constraint); the write-while-alone drain
3825
- * bumps because it re-issues an otherwise unchanged row, whereas here the voucher itself
3826
- * changes. A row never self-published carries no stamp to keep and gets the clock.
3827
- *
3828
- * A failure rethrows and stops the call before the removal; re-vouches already made stay
3829
- * (the row is only re-signed by a current owner) and the caller retries. An owner re-vouch
3830
- * committed alone is not re-issued on growth (the add half has no re-issue path,
3831
- * `tickets/backlog/debt-owner-key-add-while-alone-has-no-reissue`), so it is logged as
3832
- * {@link addOwner} logs it; the peer re-touches are re-issued by the existing drain.
3833
- */
3834
- private revouchDependents;
3835
3962
  /**
3836
3963
  * Create a seed from the current control network state.
3837
3964
  * The seed contains peer information and is signed by an owner.
@@ -3852,9 +3979,9 @@ export declare class CadreNode implements SAppIdLookup {
3852
3979
  trustPolicy?: SeedTrustPolicy;
3853
3980
  }): Promise<ApplySeedResult>;
3854
3981
  /**
3855
- * Post-process an {@link applySeed} outcome: retain the seed's owner peers as
3982
+ * Post-process an {@link applySeed} outcome: retain the seed's preferred peers as
3856
3983
  * cold-start bootstrap targets, and surface a seed that was accepted but whose
3857
- * every owner dial failed — the node is now seeded yet unconnected, and the
3984
+ * every dial failed — the node is now seeded yet unconnected, and the
3858
3985
  * cold-start reconcile branch is what gets it out of that state.
3859
3986
  */
3860
3987
  private noteAppliedSeed;
@@ -3910,9 +4037,10 @@ export declare class CadreNode implements SAppIdLookup {
3910
4037
  * vouch the node into this cadre and keep its addresses as a dial target. The owner
3911
4038
  * side of the claim-secret entry in the Seed Delivery Protocol (`docs/architecture.md`).
3912
4039
  *
3913
- * Requires the seed service with an owner key ({@link initializeSeedBootstrap}), as
3914
- * {@link addDrone} does. The order is the REVERSE of `addDrone`'s: seed first, row
3915
- * second. A refused claim or a failed delivery therefore leaves no `CadrePeer` row and
4040
+ * Requires the seed service with an owner key ({@link useOwnerSigner}), as
4041
+ * {@link addDrone} does, and like it is one owner approval (the seed and the vouch). The
4042
+ * order is the REVERSE of `addDrone`'s: seed first, row second. A refused claim or a
4043
+ * failed delivery therefore leaves no `CadrePeer` row and
3916
4044
  * no retained dial target behind on this node — a phone that typed the wrong secret,
3917
4045
  * or scanned a node someone else already owns, has nothing to clean up.
3918
4046
  *
@@ -3936,7 +4064,7 @@ export declare class CadreNode implements SAppIdLookup {
3936
4064
  * timed reconcile pass; a caller that wants to wait for the connection awaits
3937
4065
  * {@link reconcileControlCohort} itself, with `addDrone`'s caveat that a pass already
3938
4066
  * in flight is joined rather than restarted. The claimed node dials this one back too,
3939
- * from the seed's owner list, when this node has a listen or relay address; a phone
4067
+ * as the seed's minter, when this node has a listen or relay address; a phone
3940
4068
  * with neither is reached by its own reconcile pass alone.
3941
4069
  *
3942
4070
  * @throws {ClaimRefusedError} when the node was reached and refused the claim.
@@ -3969,13 +4097,13 @@ export declare class CadreNode implements SAppIdLookup {
3969
4097
  /**
3970
4098
  * Mint an owner-signed cadre invitation that a device can redeem at ANY member machine of
3971
4099
  * this party, the owner offline included ({@link redeemCadreInvitation}). Requires the
3972
- * owner key ({@link initializeSeedBootstrap}).
4100
+ * owner key ({@link useOwnerSigner}).
3973
4101
  *
3974
4102
  * The invitation is an ed25519 keypair: the public half is the `CadreInvite` row's key,
3975
- * the private half rides in the returned bundle as the proof of possession. Whoever holds
3976
- * the bundle can redeem it, so an UNTARGETED, OWNER-GRANTING invitation — a bearer
3977
- * credential for admin rights — defaults to a 15-minute lifetime; every other kind to 24
3978
- * hours. One use unless `uses` says otherwise. The bundle carries the signed row (so a
4103
+ * the private half rides in the returned bundle as the proof of possession. It admits a
4104
+ * machine and grants nothing else (ownership is {@link createOwnerGrant}'s), so it defaults
4105
+ * to a 24-hour lifetime ({@link CADRE_INVITATION_DEFAULT_TTL_MS}) and one use unless `uses`
4106
+ * says otherwise. The bundle carries the signed row (so a
3979
4107
  * member that has not received it by replication seats it from the bundle), this node's
3980
4108
  * anchored owner keys — every current owner this node has verified, the out-of-band ones
3981
4109
  * and those derived along verifiable chains ({@link syncOwnerAnchor}) — (the device pins
@@ -4000,7 +4128,15 @@ export declare class CadreNode implements SAppIdLookup {
4000
4128
  * @throws when no owner key is wired, the anchor is empty, `uses` is not a positive
4001
4129
  * integer, or neither this machine nor any other member has an address.
4002
4130
  */
4003
- createCadreInvitation(options: CreateCadreInvitationOptions): Promise<CreateCadreInvitationResult>;
4131
+ createCadreInvitation(options?: CreateCadreInvitationOptions): Promise<CreateCadreInvitationResult>;
4132
+ /**
4133
+ * What the two credentials an owner mints share ({@link createCadreInvitation},
4134
+ * {@link createOwnerGrant}), checked in this order: the owner-capable service; the anchor's
4135
+ * keys the holder pins (an empty anchor is refused rather than falling back to the table, since
4136
+ * a reply could not be checked against it); the use count; and the member addresses with their
4137
+ * reach, refused when neither this machine nor any other member has one.
4138
+ */
4139
+ private issuedBundleParts;
4004
4140
  /**
4005
4141
  * Every cadre invitation this node holds — minted here or replicated in — with its
4006
4142
  * standing ({@link CadreInviteStatus}: live, withdrawn, redemptions recorded).
@@ -4044,11 +4180,9 @@ export declare class CadreNode implements SAppIdLookup {
4044
4180
  * pass whose reply was lost answers the next as already a member. `options.onProgress`
4045
4181
  * reports each pass and pause.
4046
4182
  *
4047
- * When the invitation grants ownership, the member seats this node's key as an `OwnerKey`
4048
- * row by consent. The caller still has to wire that key for signing afterwards, with
4049
- * {@link wireOwnKeyIfOwner}; never `ensureOwnerKey`, the row is already there. Other
4050
- * machines derive this node's key into their anchors from the row's stored proof on their
4051
- * next membership refresh ({@link syncOwnerAnchor}).
4183
+ * The invitation admits this node as a machine and nothing else: ownership is an owner
4184
+ * grant ({@link redeemOwnerGrant}, {@link joinWithOwnerGrant}). A key of this node's that is
4185
+ * already an owner here is wired afterwards with {@link wireOwnKeyIfOwner}(signer).
4052
4186
  *
4053
4187
  * Limit: the reply check ({@link verifyRedeemReply}) does not authenticate the member. A
4054
4188
  * forged bundle, or a legitimate one whose addresses were swapped, dials a machine that
@@ -4064,24 +4198,173 @@ export declare class CadreNode implements SAppIdLookup {
4064
4198
  */
4065
4199
  redeemCadreInvitation(invitation: CadreInvitation, options?: RedeemCadreInvitationOptions): Promise<RedeemCadreInvitationResult>;
4066
4200
  /**
4067
- * If this node's identity key is a live `OwnerKey` here, wire it for signing
4068
- * ({@link initializeSeedBootstrap}) and return true; otherwise change nothing and return
4069
- * false. For a machine that joined someone else's cadre by invitation: after an
4070
- * owner-granting redemption, and on every later start. A redemption seats the request's
4071
- * `peerKey`, the key behind this node's peer id, so the identity key is the one to look for.
4201
+ * If `signer`'s key is a live `OwnerKey` here, wire it ({@link useOwnerSigner}) and return
4202
+ * true; otherwise change nothing and return false. For a machine that joined someone else's
4203
+ * cadre: after an owner grant seated its key, and on every later start.
4204
+ *
4205
+ * Without `signer` the candidate is the signer already wired, so the call answers whether
4206
+ * that key is still a live owner; with nothing wired it is false. The node's identity key is
4207
+ * never a candidate: a machine's network identity is never an owner key.
4072
4208
  *
4073
4209
  * Unlike `ensureOwnerKey` it never inserts an `OwnerKey` row, so a machine that joined a
4074
4210
  * cadre can never genesis-insert itself into it, and a non-owner never anchors its own key.
4075
4211
  * An owner whose key is already wired (an earlier call) is answered true with nothing redone.
4076
4212
  *
4077
4213
  * NOTE: a node running the listener-only seed service ({@link enableSeedListener}) cannot be
4078
- * wired this way: libp2p takes one seed handler, and {@link initializeSeedBootstrap} would
4079
- * register a second. Nothing that joins by invitation enables the listener today; if one
4080
- * does, replace the listener here rather than beside it.
4081
- *
4082
- * @throws when the node is not started.
4083
- */
4084
- wireOwnKeyIfOwner(): Promise<boolean>;
4214
+ * wired this way: libp2p takes one seed handler, and {@link useOwnerSigner} would register a
4215
+ * second. Nothing that joins by invitation enables the listener today; if one does, replace
4216
+ * the listener here rather than beside it.
4217
+ *
4218
+ * @throws when the node is not started, or when `signer` names a different key from the
4219
+ * signer already wired (one signer per node).
4220
+ */
4221
+ wireOwnKeyIfOwner(signer?: OwnerSigner): Promise<boolean>;
4222
+ /**
4223
+ * Mint an owner-signed owner grant: whoever holds the returned bundle can seat an owner key of
4224
+ * their own through ANY member machine of this party ({@link redeemOwnerGrant}), or join the
4225
+ * party as a machine and an owner at once ({@link joinWithOwnerGrant}), the owner that minted
4226
+ * it offline or not. Requires the owner key ({@link useOwnerSigner}); one owner approval.
4227
+ *
4228
+ * The grant is an ed25519 keypair: the public half is the `OwnerGrant` row's key, the private
4229
+ * half rides in the bundle as the proof of possession. It admits no machine and names no peer
4230
+ * id. Whoever holds the bundle can become an owner, so it defaults to a 15-minute lifetime
4231
+ * ({@link OWNER_GRANT_DEFAULT_TTL_MS}) and one use; `expiresInMs: null` mints a BACKUP grant
4232
+ * with no expiry, which must be asked for explicitly and kept as carefully as an owner key.
4233
+ * The label is the issuer's choice and goes on every key the grant seats ({@link addOwner}'s
4234
+ * rule: trimmed, at most 64 characters, blank meaning none). The bundle carries the signed
4235
+ * row, this node's anchored owner keys and at most six member addresses, chosen as
4236
+ * {@link createCadreInvitation} chooses them and for the same reasons; `reach` says where a
4237
+ * holder could redeem it once this machine is gone. A backup's addresses are those at mint
4238
+ * time, so it can go stale if every member's address changes.
4239
+ *
4240
+ * @throws when no owner key is wired, the anchor is empty, `uses` is not a positive integer,
4241
+ * the label is over 64 characters, or neither this machine nor any other member has an address.
4242
+ */
4243
+ createOwnerGrant(options?: CreateOwnerGrantOptions): Promise<CreateOwnerGrantResult>;
4244
+ /**
4245
+ * Every owner grant this node holds — minted here or replicated in — with its standing
4246
+ * ({@link OwnerGrantStatus}: live, withdrawn, redemptions recorded).
4247
+ */
4248
+ listOwnerGrants(): Promise<OwnerGrantStatus[]>;
4249
+ /**
4250
+ * Withdraw an owner grant: an owner-signed `Revocation` tombstone over its row, which stays as
4251
+ * the proof behind every key it seated; those keys stay owners (removing one is
4252
+ * {@link removeOwner}). No further redemption succeeds anywhere the tombstone reaches, and a
4253
+ * member holding it refuses the bundle's copy of the row. A withdrawal committed while alone is
4254
+ * re-issued on cohort growth like any other tombstone.
4255
+ *
4256
+ * @returns `true` when this call filed the tombstone, `false` when the row is not held here or
4257
+ * was already withdrawn.
4258
+ */
4259
+ withdrawOwnerGrant(key: string): Promise<boolean>;
4260
+ /**
4261
+ * Become an owner of this node's party with an owner grant: seat `signer`'s key as an
4262
+ * `OwnerKey` row by consent, with no owner online. `signer` holds the key to seat; its one
4263
+ * `approve` names the party and says the key becomes an owner of it, and signs the `'consent'`
4264
+ * digest. The grant's `'redeem'` signature is made with the bundle's private key in memory.
4265
+ * A bundle for another party is refused before anything is pinned, dialed or written.
4266
+ *
4267
+ * Two paths, chosen by whether this node is a live member of the party ({@link isLiveMemberSelf})
4268
+ * or an enrolled owner of it ({@link isOwner}):
4269
+ *
4270
+ * - **Local**, on a member machine: the grant row is seated from the bundle when absent (its
4271
+ * own transaction) and the redemption written in this node's own control database
4272
+ * (`ControlDatabase.redeemOwnerGrant`), replicating like any other write. Refused `alone`
4273
+ * under {@link addOwner}'s rule, before the approval: an `OwnerKey` row has no re-issue
4274
+ * path, so one committed with no control connection while the party has other members would
4275
+ * stay here; connect, then try again. A grant that is not live here (withdrawn, expired,
4276
+ * spent, its issuer no longer an owner) is refused before the approval too, unless the key
4277
+ * is already an owner. The bundle's `ownerKeys` are NOT pinned: this node judges the issuer
4278
+ * by its own `OwnerKey` table, and its anchor must not be widened by pasted text.
4279
+ * - **Protocol**, otherwise (a device with no machine in the cadre; a node started in the
4280
+ * party on an empty store): the bundle's `ownerKeys` are pinned into the node-local anchor
4281
+ * (`'invite'` provenance, the pin a credential the owner handed out carries, as
4282
+ * {@link redeemCadreInvitation} pins), then the members the bundle names are dialed over
4283
+ * `/sereus/owner-grant/1.0.0` (`redeemOwnerGrantInPasses`), one signed request for every
4284
+ * address and every pass as {@link redeemCadreInvitation} dials, so a retry after a lost
4285
+ * reply is answered `alreadyOwner`. The member that accepts pushes its control store here
4286
+ * and writes the row with this node counted in its write cohort, so a member alone with
4287
+ * this node still commits. `options` are the invitation's (`waitForMember`, `signal`,
4288
+ * `onProgress`).
4289
+ *
4290
+ * Neither path wires the signer: call {@link wireOwnKeyIfOwner}(signer) afterwards — at once
4291
+ * on the local path, and on the protocol path once the row has synced here. A key that is
4292
+ * already a live owner is answered `alreadyOwner` with no use spent, on both paths. To join
4293
+ * the party as a machine too, use {@link joinWithOwnerGrant}.
4294
+ *
4295
+ * @throws a plain `Error` for a node not started or a bundle naming another party;
4296
+ * {@link OwnerKeyAddRefusedError} `alone` on the local path, `OwnerGrantIssuerUnknownError`
4297
+ * when the issuer is not an owner here, `OwnerGrantRejectedError` `grant-spent` for a
4298
+ * grant no longer live here, and `OwnerGrantRejectedError` `conflict` (retryable) when the
4299
+ * cadre holds the grant row and this node has not synced it yet; on the protocol path
4300
+ * `OwnerGrantRejectedError` on a final refusal, `OwnerGrantReplyInvalidError` on an
4301
+ * acceptance that fails the reply check,
4302
+ * `OwnerGrantUnreachableError` when every address was tried (one pass only), and the
4303
+ * signal's reason on an abort.
4304
+ */
4305
+ redeemOwnerGrant(bundle: OwnerGrantBundle, signer: OwnerSigner, options?: RedeemOwnerGrantOptions): Promise<RedeemOwnerGrantResult>;
4306
+ /**
4307
+ * {@link redeemOwnerGrant}'s local path: the alone refusal, the seat, the liveness refusal,
4308
+ * then one approval around the consent signature and the write. Every refusal comes before
4309
+ * the approval, so a prompting signer never asks for a redemption this node would refuse.
4310
+ */
4311
+ private redeemOwnerGrantLocally;
4312
+ /**
4313
+ * {@link redeemOwnerGrant}'s protocol path, given the signed request: dial the bundle's members
4314
+ * in passes, then refresh the membership gate, since the rows the member pushed and wrote are
4315
+ * judged against the pins and the anchor derives this node's key from them once its row is here.
4316
+ */
4317
+ private redeemOwnerGrantAtMembers;
4318
+ /** The signed request for `bundle` seating `signer`'s key, under an open approval's `sign`; the nonce is minted here, by the holder. */
4319
+ private signOwnerGrantRequest;
4320
+ /**
4321
+ * Join this node's party as a machine AND an owner with one owner grant, for a node started in
4322
+ * the grant's party that is not a member: a fresh install recovering with a backup grant, an
4323
+ * app joining as an owner. Needs a started node with a stable identity, as
4324
+ * {@link redeemCadreInvitation} does, and a bundle for this party.
4325
+ *
4326
+ * Steps, the two signatures made under ONE approval of `signer` before any dial, so a
4327
+ * prompting signer asks once and its prompt does not span the network:
4328
+ *
4329
+ * 1. {@link redeemOwnerGrant}'s protocol path: the member that accepts seats `signer`'s key.
4330
+ * 2. A **self-invitation**: a `CadreInvite` row targeted at this node's peer id, one use, a
4331
+ * short expiry ({@link SELF_INVITATION_TTL_MS}), signed by `signer`
4332
+ * (`ControlDatabase.signCadreInvite`; never inserted here, since this store holds no
4333
+ * `OwnerKey` row for the issuer yet). Its bundle carries the grant's owner keys plus the
4334
+ * new key (the device's reply check wants the row's issuer among the pins) and the grant's
4335
+ * members with the member that accepted the grant first, since that member holds the new
4336
+ * key as a live owner and seats the row from the bundle; one the row has not reached yet
4337
+ * answers `issuer-unknown`, which is retryable.
4338
+ * 3. {@link redeemCadreInvitation} with it: the member admits this node.
4339
+ * 4. {@link wireOwnKeyIfOwner}(signer) once the `OwnerKey` row has synced here, asked every
4340
+ * {@link OWNER_ROW_SYNC_RECHECK_MS} up to {@link OWNER_ROW_SYNC_TIMEOUT_MS}; the result's
4341
+ * `ownerWired` says whether it did in time, and a later call wires it otherwise.
4342
+ *
4343
+ * @throws as {@link redeemOwnerGrant} (protocol path) and {@link redeemCadreInvitation} throw,
4344
+ * and a plain `Error` for an ephemeral identity.
4345
+ */
4346
+ joinWithOwnerGrant(bundle: OwnerGrantBundle, signer: OwnerSigner, options?: RedeemOwnerGrantOptions): Promise<JoinWithOwnerGrantResult>;
4347
+ /**
4348
+ * {@link wireOwnKeyIfOwner}(signer) once `signer`'s `OwnerKey` row has reached this node, asked
4349
+ * again every {@link OWNER_ROW_SYNC_RECHECK_MS} until it has, the node stops, or
4350
+ * {@link OWNER_ROW_SYNC_TIMEOUT_MS} passes: false then, and a later call wires it. Usually
4351
+ * immediate: the member pushed its whole store, the row included, before the admission write.
4352
+ */
4353
+ private wireOwnKeyOnceSynced;
4354
+ /**
4355
+ * The started node's handles for a grant call, the bundle's party checked first: a grant for
4356
+ * another party is refused before any pin, dial or write.
4357
+ */
4358
+ private requireStartedForGrant;
4359
+ /**
4360
+ * Is this node a live member of its party: its own `CadrePeer` row is held here and passes the
4361
+ * membership predicate ({@link hasAnchoredProof}, judged as {@link listAuthorizedMembers}
4362
+ * judges every other row; that listing leaves this node out)? What decides
4363
+ * {@link redeemOwnerGrant}'s path. A machine's own row is written by its enrollment (or its
4364
+ * first self-record, for an owner's machine), so a node on an empty store, or one holding only
4365
+ * a store a member pushed it, is not one.
4366
+ */
4367
+ private isLiveMemberSelf;
4085
4368
  /**
4086
4369
  * Replace this node's strand solicitation service — the formation responder every node
4087
4370
  * installs at {@link start}, and the initiator side of {@link formStrand}. Optional: call it
@@ -4191,8 +4474,8 @@ export declare class CadreNode implements SAppIdLookup {
4191
4474
  */
4192
4475
  dismissPendingJoin(id: string): Promise<boolean>;
4193
4476
  private requirePendingJoins;
4194
- /** This machine's owner key and signer for an owner-signed control write, or a throw carrying `refusal`. */
4195
- private requireOwnerSigner;
4477
+ /** This machine's enrolled owner signer ({@link enrolledOwnerSigner}), or a throw carrying `refusal`. */
4478
+ private requireEnrolledOwnerSigner;
4196
4479
  /**
4197
4480
  * Write a join request, or adopt the party's existing request for the same invitation: a
4198
4481
  * pending one as it is, a finished one rewritten as `requested` (the user asked again).