@serfab/cadre-core 1.13.0 → 1.15.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 (230) hide show
  1. package/README.md +293 -274
  2. package/dist/cadre-invite-protocol.d.ts +383 -0
  3. package/dist/cadre-invite-protocol.js +774 -0
  4. package/dist/cadre-invite-protocol.js.map +1 -0
  5. package/dist/cadre-join-request.d.ts +42 -0
  6. package/dist/cadre-join-request.js +144 -0
  7. package/dist/cadre-join-request.js.map +1 -0
  8. package/dist/cadre-node.d.ts +849 -210
  9. package/dist/cadre-node.js +1574 -374
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/claim-proof.d.ts +45 -0
  12. package/dist/claim-proof.js +132 -0
  13. package/dist/claim-proof.js.map +1 -0
  14. package/dist/control-authorization.d.ts +80 -19
  15. package/dist/control-authorization.js +55 -13
  16. package/dist/control-authorization.js.map +1 -1
  17. package/dist/control-cohort.js +1 -1
  18. package/dist/control-cohort.js.map +1 -1
  19. package/dist/control-database.d.ts +605 -108
  20. package/dist/control-database.js +1299 -280
  21. package/dist/control-database.js.map +1 -1
  22. package/dist/control-protocol-guard.d.ts +142 -0
  23. package/dist/control-protocol-guard.js +293 -0
  24. package/dist/control-protocol-guard.js.map +1 -0
  25. package/dist/control-schema.d.ts +1 -1
  26. package/dist/control-schema.js +817 -1067
  27. package/dist/control-schema.js.map +1 -1
  28. package/dist/control-stream.d.ts +4 -0
  29. package/dist/control-stream.js +22 -7
  30. package/dist/control-stream.js.map +1 -1
  31. package/dist/delegate-admission.d.ts +1 -1
  32. package/dist/delegate-admission.js +1 -1
  33. package/dist/device-token.d.ts +11 -9
  34. package/dist/device-token.js +14 -12
  35. package/dist/device-token.js.map +1 -1
  36. package/dist/ed25519-key.d.ts +22 -9
  37. package/dist/ed25519-key.js +47 -10
  38. package/dist/ed25519-key.js.map +1 -1
  39. package/dist/fetch-impl.d.ts +12 -0
  40. package/dist/fetch-impl.js +23 -0
  41. package/dist/fetch-impl.js.map +1 -0
  42. package/dist/file-durable-slot.js +2 -1
  43. package/dist/file-durable-slot.js.map +1 -1
  44. package/dist/file-safe-name.d.ts +25 -0
  45. package/dist/file-safe-name.js +29 -0
  46. package/dist/file-safe-name.js.map +1 -0
  47. package/dist/formation-approval.js +5 -11
  48. package/dist/formation-approval.js.map +1 -1
  49. package/dist/fs-atomic.d.ts +0 -19
  50. package/dist/fs-atomic.js +3 -24
  51. package/dist/fs-atomic.js.map +1 -1
  52. package/dist/index.d.ts +22 -10
  53. package/dist/index.js +54 -11
  54. package/dist/index.js.map +1 -1
  55. package/dist/invitation-bootstrap.d.ts +50 -12
  56. package/dist/invitation-bootstrap.js +76 -13
  57. package/dist/invitation-bootstrap.js.map +1 -1
  58. package/dist/joined-strand-store.d.ts +7 -0
  59. package/dist/joined-strand-store.js +9 -0
  60. package/dist/joined-strand-store.js.map +1 -1
  61. package/dist/key-store-file.js +2 -1
  62. package/dist/key-store-file.js.map +1 -1
  63. package/dist/link-budget.d.ts +25 -33
  64. package/dist/link-budget.js +25 -35
  65. package/dist/link-budget.js.map +1 -1
  66. package/dist/machine-removal.d.ts +78 -0
  67. package/dist/machine-removal.js +166 -0
  68. package/dist/machine-removal.js.map +1 -0
  69. package/dist/membership-connection-gater.d.ts +191 -151
  70. package/dist/membership-connection-gater.js +236 -173
  71. package/dist/membership-connection-gater.js.map +1 -1
  72. package/dist/node-claim-payload.d.ts +74 -0
  73. package/dist/node-claim-payload.js +224 -0
  74. package/dist/node-claim-payload.js.map +1 -0
  75. package/dist/node-default-transports.d.ts +27 -0
  76. package/dist/node-default-transports.js +35 -0
  77. package/dist/node-default-transports.js.map +1 -0
  78. package/dist/node-transports.d.ts +30 -0
  79. package/dist/node-transports.js +40 -0
  80. package/dist/node-transports.js.map +1 -0
  81. package/dist/owner-anchor-sync.d.ts +22 -0
  82. package/dist/owner-anchor-sync.js +143 -0
  83. package/dist/owner-anchor-sync.js.map +1 -0
  84. package/dist/peer-authorization.d.ts +113 -18
  85. package/dist/peer-authorization.js +225 -29
  86. package/dist/peer-authorization.js.map +1 -1
  87. package/dist/peer-dial.d.ts +143 -27
  88. package/dist/peer-dial.js +276 -31
  89. package/dist/peer-dial.js.map +1 -1
  90. package/dist/peer-join-backfill.d.ts +26 -2
  91. package/dist/peer-join-backfill.js +60 -12
  92. package/dist/peer-join-backfill.js.map +1 -1
  93. package/dist/peer-record.d.ts +17 -52
  94. package/dist/peer-record.js +20 -58
  95. package/dist/peer-record.js.map +1 -1
  96. package/dist/pending-join-runner.d.ts +66 -58
  97. package/dist/pending-join-runner.js +136 -145
  98. package/dist/pending-join-runner.js.map +1 -1
  99. package/dist/primary-lan-address.d.ts +8 -0
  100. package/dist/primary-lan-address.js +46 -0
  101. package/dist/primary-lan-address.js.map +1 -0
  102. package/dist/provider/client.d.ts +131 -0
  103. package/dist/provider/client.js +226 -0
  104. package/dist/provider/client.js.map +1 -0
  105. package/dist/provider/descriptor.d.ts +41 -0
  106. package/dist/provider/descriptor.js +103 -0
  107. package/dist/provider/descriptor.js.map +1 -0
  108. package/dist/provider/index.d.ts +9 -0
  109. package/dist/provider/index.js +10 -0
  110. package/dist/provider/index.js.map +1 -0
  111. package/dist/provider/oauth.d.ts +125 -0
  112. package/dist/provider/oauth.js +199 -0
  113. package/dist/provider/oauth.js.map +1 -0
  114. package/dist/provider/rent-node.d.ts +101 -0
  115. package/dist/provider/rent-node.js +220 -0
  116. package/dist/provider/rent-node.js.map +1 -0
  117. package/dist/push-fanout.d.ts +8 -2
  118. package/dist/push-fanout.js +8 -2
  119. package/dist/push-fanout.js.map +1 -1
  120. package/dist/relay-addrs.d.ts +9 -4
  121. package/dist/relay-addrs.js +9 -4
  122. package/dist/relay-addrs.js.map +1 -1
  123. package/dist/relay-reservation.d.ts +51 -16
  124. package/dist/relay-reservation.js +107 -24
  125. package/dist/relay-reservation.js.map +1 -1
  126. package/dist/seed-bootstrap.d.ts +165 -123
  127. package/dist/seed-bootstrap.js +313 -275
  128. package/dist/seed-bootstrap.js.map +1 -1
  129. package/dist/seed-trust-policy.d.ts +98 -37
  130. package/dist/seed-trust-policy.js +188 -2
  131. package/dist/seed-trust-policy.js.map +1 -1
  132. package/dist/strand-addr-protocol.d.ts +24 -26
  133. package/dist/strand-addr-protocol.js +19 -26
  134. package/dist/strand-addr-protocol.js.map +1 -1
  135. package/dist/strand-commit-durability.d.ts +54 -0
  136. package/dist/strand-commit-durability.js +74 -0
  137. package/dist/strand-commit-durability.js.map +1 -0
  138. package/dist/strand-database.d.ts +23 -8
  139. package/dist/strand-database.js +41 -13
  140. package/dist/strand-database.js.map +1 -1
  141. package/dist/strand-first-sync-gate.d.ts +7 -1
  142. package/dist/strand-first-sync-gate.js +7 -1
  143. package/dist/strand-first-sync-gate.js.map +1 -1
  144. package/dist/strand-formation-protocol.js +5 -2
  145. package/dist/strand-formation-protocol.js.map +1 -1
  146. package/dist/strand-fret-addrs.d.ts +19 -1
  147. package/dist/strand-fret-addrs.js +27 -9
  148. package/dist/strand-fret-addrs.js.map +1 -1
  149. package/dist/strand-instance-manager.d.ts +81 -19
  150. package/dist/strand-instance-manager.js +216 -105
  151. package/dist/strand-instance-manager.js.map +1 -1
  152. package/dist/strand-move.d.ts +124 -0
  153. package/dist/strand-move.js +438 -0
  154. package/dist/strand-move.js.map +1 -0
  155. package/dist/strand-network-config.js +7 -6
  156. package/dist/strand-network-config.js.map +1 -1
  157. package/dist/strand-peer-redial.d.ts +113 -0
  158. package/dist/strand-peer-redial.js +236 -0
  159. package/dist/strand-peer-redial.js.map +1 -0
  160. package/dist/strand-revocation-enforcer.d.ts +11 -11
  161. package/dist/strand-revocation-enforcer.js +11 -11
  162. package/dist/strand-wake-protocol.d.ts +13 -16
  163. package/dist/strand-wake-protocol.js +23 -27
  164. package/dist/strand-wake-protocol.js.map +1 -1
  165. package/dist/trusted-owner-store.d.ts +35 -5
  166. package/dist/trusted-owner-store.js +50 -8
  167. package/dist/trusted-owner-store.js.map +1 -1
  168. package/dist/types.d.ts +512 -89
  169. package/dist/types.js +10 -0
  170. package/dist/types.js.map +1 -1
  171. package/package.json +19 -10
  172. package/src/cadre-invite-protocol.ts +1052 -0
  173. package/src/cadre-join-request.ts +167 -0
  174. package/src/cadre-node.ts +1703 -386
  175. package/src/claim-proof.ts +147 -0
  176. package/src/control-authorization.ts +203 -117
  177. package/src/control-cohort.ts +1 -1
  178. package/src/control-database.ts +4663 -3453
  179. package/src/control-protocol-guard.ts +348 -0
  180. package/src/control-schema.ts +828 -1078
  181. package/src/control-stream.ts +26 -7
  182. package/src/delegate-admission.ts +1 -1
  183. package/src/device-token.ts +14 -10
  184. package/src/ed25519-key.ts +47 -10
  185. package/src/fetch-impl.ts +23 -0
  186. package/src/file-durable-slot.ts +64 -63
  187. package/src/file-safe-name.ts +32 -0
  188. package/src/formation-approval.ts +604 -610
  189. package/src/fs-atomic.ts +92 -117
  190. package/src/index.ts +789 -648
  191. package/src/invitation-bootstrap.ts +91 -13
  192. package/src/joined-strand-store.ts +508 -498
  193. package/src/key-store-file.ts +142 -141
  194. package/src/link-budget.ts +25 -36
  195. package/src/machine-removal.ts +216 -0
  196. package/src/membership-connection-gater.ts +259 -191
  197. package/src/node-claim-payload.ts +259 -0
  198. package/src/node-default-transports.ts +38 -0
  199. package/src/node-transports.ts +49 -0
  200. package/src/owner-anchor-sync.ts +171 -0
  201. package/src/peer-authorization.ts +258 -29
  202. package/src/peer-dial.ts +604 -315
  203. package/src/peer-join-backfill.ts +62 -11
  204. package/src/peer-record.ts +159 -197
  205. package/src/pending-join-runner.ts +729 -735
  206. package/src/primary-lan-address.ts +47 -0
  207. package/src/provider/client.ts +326 -0
  208. package/src/provider/descriptor.ts +129 -0
  209. package/src/provider/index.ts +59 -0
  210. package/src/provider/oauth.ts +285 -0
  211. package/src/provider/rent-node.ts +275 -0
  212. package/src/push-fanout.ts +8 -2
  213. package/src/relay-addrs.ts +9 -4
  214. package/src/relay-reservation.ts +1211 -1121
  215. package/src/seed-bootstrap.ts +1502 -1448
  216. package/src/seed-trust-policy.ts +353 -140
  217. package/src/strand-addr-protocol.ts +26 -35
  218. package/src/strand-commit-durability.ts +116 -0
  219. package/src/strand-database.ts +46 -14
  220. package/src/strand-first-sync-gate.ts +7 -1
  221. package/src/strand-formation-protocol.ts +1039 -1036
  222. package/src/strand-fret-addrs.ts +179 -145
  223. package/src/strand-instance-manager.ts +2026 -1883
  224. package/src/strand-move.ts +585 -0
  225. package/src/strand-network-config.ts +7 -6
  226. package/src/strand-peer-redial.ts +307 -0
  227. package/src/strand-revocation-enforcer.ts +11 -11
  228. package/src/strand-wake-protocol.ts +23 -30
  229. package/src/trusted-owner-store.ts +242 -171
  230. package/src/types.ts +2687 -2242
@@ -1,7 +1,7 @@
1
1
  import type { Libp2p } from '@libp2p/interface';
2
2
  import { type PeerDialBudget } from './peer-dial.js';
3
- import type { ControlNetworkSeed, SeedAckMessage, AuthorizePeerOptions, ApplySeedResult, AddDroneOptions, AddPhoneOptions, DroneInitResult, InviteResult, CadreInvite, PeerAddressRecord, DeviceTokenRecord, RevocationRow, RevocationLedgerOpenResult } from './types.js';
4
- import type { ControlDatabase } from './control-database.js';
3
+ import type { ControlNetworkSeed, SeedPeer, SeedAckMessage, SeedDeliveryTarget, AuthorizePeerOptions, ApplySeedResult, AddDroneOptions, DroneInitResult, CadreInviteRow, PeerAddressRecord, DeviceTokenRecord, RevocationRow, RevocationLedgerOpenResult } from './types.js';
4
+ import type { ControlDatabase, MachineRemovalWrites } from './control-database.js';
5
5
  import { type SeedTrustPolicy } from './seed-trust-policy.js';
6
6
  import type { TrustedOwnerStore } from './trusted-owner-store.js';
7
7
  /** Protocol ID for seed delivery */
@@ -15,17 +15,6 @@ export declare const SEED_PROTOCOL = "/sereus/seed/1.0.0";
15
15
  * the body is handed straight to `TextDecoder`.
16
16
  */
17
17
  export declare function decodeLengthPrefixedFrame(data: Uint8Array, maxLength?: number): Uint8Array;
18
- /**
19
- * Derive the base64url ed25519 public key embedded in an Ed25519 libp2p PeerId.
20
- *
21
- * An Ed25519 PeerId is an identity multihash of the public key, so
22
- * `peerIdFromString(id).publicKey.raw` is the 32-byte ed25519 key whose
23
- * base64url form matches the `OwnerKey.Key` representation (and
24
- * `ed25519KeyPairFromLibp2p().publicKeyB64`). Returns null for a non-Ed25519
25
- * id, a missing embedded key, or any parse failure — callers treat null as
26
- * "not an owner" rather than throwing.
27
- */
28
- export declare function ed25519PublicKeyB64FromPeerId(peerId: string): string | null;
29
18
  /**
30
19
  * Canonical byte representation of the authenticated seed fields.
31
20
  *
@@ -36,6 +25,22 @@ export declare function ed25519PublicKeyB64FromPeerId(peerId: string): string |
36
25
  * the fields the producer actually emits.
37
26
  */
38
27
  export declare function canonicalSeedPayload(seed: Pick<ControlNetworkSeed, 'partyId' | 'peers'>): string;
28
+ /**
29
+ * The digest a seed's signature covers: sha256 over {@link canonicalSeedPayload},
30
+ * base64url. One function so the signer (`createSeed`), the verifier
31
+ * (`validateSeedSignature`) and the claim proof (`claim-proof.ts`, which binds a
32
+ * proof to one seed through this value) all name the same bytes.
33
+ */
34
+ export declare function seedDigest(seed: Pick<ControlNetworkSeed, 'partyId' | 'peers'>): string;
35
+ /**
36
+ * Merge a peer list's addresses into `node`'s peer store, so the node can dial them: the
37
+ * seed's peers after its signature and trust checks, the dial hints a member returns on
38
+ * a cadre invitation redemption (`cadre-invite-protocol.ts`), or the node a seed is being
39
+ * delivered to ({@link SeedBootstrapService.deliverSeed}). Best-effort per peer — one
40
+ * unparsable entry costs that peer, not the rest — and a peer with no address is skipped,
41
+ * since there is nothing to dial. Returns how many peers were merged.
42
+ */
43
+ export declare function mergeSeedPeers(node: Libp2p, peers: readonly SeedPeer[]): Promise<number>;
39
44
  /**
40
45
  * Configuration for the SeedBootstrapService
41
46
  */
@@ -46,13 +51,6 @@ export interface SeedBootstrapConfig {
46
51
  ownerPrivateKey?: string;
47
52
  /** Owner public key (base64url) - derived from private key if not provided */
48
53
  ownerPublicKey?: string;
49
- /**
50
- * Optional async resolver returning the multiaddrs to embed in invites.
51
- * When unset, `libp2pNode.getMultiaddrs()` is used. Hosts behind NAT supply
52
- * this (via `@serfab/cadre-host`'s NatService) to substitute the host's
53
- * DDNS hostname and externally-mapped port.
54
- */
55
- inviteAddressResolver?: () => Promise<string[]>;
56
54
  /**
57
55
  * Trust anchor for incoming seeds. Decides whether a signature-verified
58
56
  * `signerKey` should be trusted, against the receiver's anchored owner
@@ -96,18 +94,20 @@ export interface SeedBootstrapConfig {
96
94
  */
97
95
  linkRoundTripMs?: number;
98
96
  /**
99
- * Time {@link SeedBootstrapService.deliverSeed} waits for the whole exchange —
100
- * dial, write, ack read — before aborting (ms). Bounds the SENDER against a seed
101
- * target that accepts the stream and then never replies; the target is a
102
- * not-yet-trusted node during onboarding, so this is the more exposed
97
+ * Time {@link SeedBootstrapService.deliverSeed} waits for the request — opening the
98
+ * stream, write, ack read — before aborting (ms). For a multiaddr string, or a peer id
99
+ * with no addresses, opening the stream includes the dial; for a peer id with addresses
100
+ * the connection is formed first, outside this limit and within {@link dialBudget}.
101
+ * Bounds the SENDER against a seed target that accepts the stream and then never replies;
102
+ * the target is a not-yet-trusted node during onboarding, so this is the more exposed
103
103
  * direction than the receiver knobs above.
104
104
  *
105
105
  * Defaults to `relayedRequestBudgetMs(linkRoundTripMs)` (`link-budget.ts`; 28.5 s at the
106
- * default declaration): one dial that may need a relay, then one request and its answer.
107
- * Delivery does not set `runOnLimitedConnection`, so it does not use a limited relayed
108
- * connection today; the relayed-dial count is the upper bound on the dial it can use, the
109
- * same choice `CadreNode.controlDialBudget` makes for every address. It holds only link work
110
- * because the receiver acks before its owner dials.
106
+ * default declaration): one dial that may need a relay, then one request and its answer —
107
+ * sized for the forms that dial inside it. Delivery does not set `runOnLimitedConnection`,
108
+ * so it does not use a limited relayed connection today; the relayed-dial count is the upper
109
+ * bound on the dial it can use, the same choice `CadreNode.controlDialBudget` makes for every
110
+ * address. It holds only link work because the receiver acks before its owner dials.
111
111
  *
112
112
  * NOTE: no transfer allowance — a seed is a peer list of a few KB, and `MAX_SEED_SIZE` (1 MiB)
113
113
  * is a defensive cap. If seeds ever grow toward that cap, add an allowance the way
@@ -117,8 +117,8 @@ export interface SeedBootstrapConfig {
117
117
  /**
118
118
  * Time limits for each peer this service dials from a list of addresses —
119
119
  * {@link SeedBootstrapService.applySeed}'s owner dials and
120
- * {@link SeedBootstrapService.dialInvite} — per address and per peer (see
121
- * `peer-dial.ts`). Defaults to {@link DEFAULT_PEER_DIAL_BUDGET}; a `CadreNode`
120
+ * {@link SeedBootstrapService.deliverSeed}'s dial of a peer id with addresses — per
121
+ * address and per peer (see `peer-dial.ts`). Defaults to {@link DEFAULT_PEER_DIAL_BUDGET}; a `CadreNode`
122
122
  * passes its `network.controlCohort` limits.
123
123
  */
124
124
  dialBudget?: PeerDialBudget;
@@ -182,10 +182,10 @@ export declare class SeedBootstrapService {
182
182
  * `registerHandler` (default true) gates registration of the shared inbound
183
183
  * `/sereus/seed/1.0.0` handler on `libp2pNode`. Persistent services
184
184
  * (`initializeSeedBootstrap`, `enableSeedListener`) own that handler and leave
185
- * it on. The throwaway temp services CadreNode builds in `applySeed` /
186
- * `dialInvite` pass `false`: they only need the stored `libp2pNode` /
187
- * `controlDatabase` for dialing and known-key lookup, and must NOT bind a
188
- * discarded closure to the shared node (a handler leak, and a second
185
+ * it on. The throwaway temp service CadreNode builds in `applySeed` passes
186
+ * `false`: it only needs the stored `libp2pNode` / `controlDatabase` for
187
+ * dialing and known-key lookup, and must NOT bind a discarded closure to the
188
+ * shared node (a handler leak, and a second
189
189
  * `handle()` of the same protocol throws `DuplicateProtocolHandlerError`).
190
190
  *
191
191
  * Rejects when libp2p refuses the registration; the node and database are
@@ -319,7 +319,7 @@ export declare class SeedBootstrapService {
319
319
  *
320
320
  * The `CadrePeer.AuthorizedDelete` (`check on delete`) constraint validates a
321
321
  * signature over the DISTINCT 'remove'-tagged digest
322
- * `digest('CadreControl.CadrePeer', 'remove', old.PeerId, old.StampId)` by an owner
322
+ * `digest('CadreControl.CadrePeer', 'remove', party_id(), old.PeerId, old.StampId)` by an owner
323
323
  * key — deliberately NOT the insert voucher digest, so the row's stored `VouchSig` can
324
324
  * never be replayed to delete.
325
325
  *
@@ -339,6 +339,42 @@ export declare class SeedBootstrapService {
339
339
  * owner-key precondition and the absent-row gate the notify depends on (below).
340
340
  */
341
341
  removePeer(peerId: string): Promise<void>;
342
+ /**
343
+ * Seat `ownerPublicKey` as a further owner, signed by this service's owner key. The
344
+ * liveness check, the stamp, the signature and the membership notify are
345
+ * {@link ControlDatabase.insertOwnerKeyVouched}'s; what stays here is the owner-key
346
+ * precondition, checked before any read (see {@link removePeer}).
347
+ *
348
+ * @returns `true` when the row was seated, `false` when the key is already a live owner.
349
+ */
350
+ addOwner(ownerPublicKey: string): Promise<boolean>;
351
+ /**
352
+ * Remove `ownerPublicKey` from the owners, signed by this service's owner key: the delete
353
+ * and its `Revocation` tombstone in one transaction. The by-name refusals (self-removal,
354
+ * the last owner), the digests and the membership notify are
355
+ * {@link ControlDatabase.deleteOwnerKey}'s; what stays here is the owner-key precondition,
356
+ * checked before any read (see {@link removePeer}).
357
+ *
358
+ * @returns `true` when the row was removed, `false` when the key is not a live owner here.
359
+ */
360
+ removeOwner(ownerPublicKey: string): Promise<boolean>;
361
+ /**
362
+ * Re-vouch another owner's row under this service's owner key, so it stands on a remaining
363
+ * owner's signature before the owner that added it is removed
364
+ * ({@link ControlDatabase.revouchOwnerKey}; for `CadreNode.removeMachine` only). What stays
365
+ * here is the owner-key precondition, checked before any read (see {@link removePeer}).
366
+ *
367
+ * @returns `true` when the row was re-vouched, `false` when the key is not a live owner here.
368
+ */
369
+ revouchOwner(ownerPublicKey: string): Promise<boolean>;
370
+ /**
371
+ * Remove a machine completely, signed by this service's owner key: its `CadrePeer` row, its
372
+ * `OwnerKey` row when `target.ownerKey` names one, its `DeviceToken` row, and the
373
+ * withdrawal of every invitation in `target.inviteKeys`, all in one transaction
374
+ * ({@link ControlDatabase.removeMachine}). What stays here is the owner-key precondition,
375
+ * checked before any read (see {@link removePeer}).
376
+ */
377
+ removeMachine(target: Parameters<ControlDatabase['removeMachine']>[0]): Promise<MachineRemovalWrites>;
342
378
  /**
343
379
  * Owner "re-touch" of an existing `CadrePeer` membership row: bump `UpdatedAt` and
344
380
  * re-vouch the row so it is re-emitted as a fresh, broadcasting transaction. This is
@@ -387,6 +423,27 @@ export declare class SeedBootstrapService {
387
423
  * initialized.
388
424
  */
389
425
  openRevocationLedger(): Promise<RevocationLedgerOpenResult>;
426
+ /**
427
+ * Owner-signed INSERT of a `CadreInvite` row — an invitation any member machine can redeem
428
+ * on this owner's behalf ({@link ControlDatabase.insertCadreInvite}). The keypair mint, the
429
+ * expiry and use-count defaults, and the bundle the holder receives are
430
+ * `CadreNode.createCadreInvitation`'s; what stays here is the owner-key precondition and
431
+ * the signature, as {@link insertSelfDeviceToken} keeps them for device tokens.
432
+ *
433
+ * @returns the row as stored, which the invitation bundle carries verbatim.
434
+ * @throws if no owner private key is configured or the control database is not initialized.
435
+ */
436
+ insertCadreInvite(invite: Parameters<ControlDatabase['insertCadreInvite']>[0]): Promise<CadreInviteRow>;
437
+ /**
438
+ * Owner-signed WITHDRAWAL of a `CadreInvite`: the `Revocation` tombstone over its stamp, with
439
+ * the row kept ({@link ControlDatabase.withdrawCadreInvite}). What stays here is the
440
+ * owner-key precondition.
441
+ *
442
+ * @returns `true` when this call filed the tombstone, `false` when the row is absent here or
443
+ * already withdrawn.
444
+ * @throws if no owner private key is configured or the control database is not initialized.
445
+ */
446
+ withdrawCadreInvite(key: string): Promise<boolean>;
390
447
  /**
391
448
  * Create a seed from the current control network state.
392
449
  * The seed contains peer information and is signed by an owner.
@@ -405,11 +462,11 @@ export declare class SeedBootstrapService {
405
462
  *
406
463
  * A key accepted via a pin/TOFU is persisted into the anchor (the policy says
407
464
  * so via `SeedTrustDecision.anchorAs`), so the next seed from that owner is
408
- * anchored without re-supplying the invite.
465
+ * anchored without re-supplying the pin.
409
466
  *
410
467
  * @param seed - The seed to apply (already transport-decoded).
411
468
  * @param options.trustPolicy - Per-call policy override (e.g. a
412
- * `pinnedKeyTrustPolicy` derived from a `CadreInvite`) used instead of the
469
+ * `pinnedKeyTrustPolicy` built from an operator pin) used instead of the
413
470
  * service-configured default for this seed only.
414
471
  */
415
472
  applySeed(seed: ControlNetworkSeed, options?: {
@@ -436,7 +493,7 @@ export declare class SeedBootstrapService {
436
493
  private dialSeedOwners;
437
494
  /**
438
495
  * Persist a signer that a pin/TOFU accepted into the node-local anchor, so a
439
- * later seed from the same owner is anchored without re-supplying the invite
496
+ * later seed from the same owner is anchored without re-supplying the pin
440
497
  * or re-prompting. Only the policy decides this happens (`anchorAs` is unset
441
498
  * when the key was already anchored, so a plain re-apply writes nothing) and
442
499
  * `trust()` is idempotent, keeping the original provenance for a known key.
@@ -444,14 +501,19 @@ export declare class SeedBootstrapService {
444
501
  * Failure to PERSIST does not fail the seed: `trust()` reflects the key in the
445
502
  * in-memory anchor synchronously, so this seed and the rest of the session are
446
503
  * unaffected — only durability across a restart is lost, and that is logged.
504
+ * That holds for pins and TOFU, which are re-supplied at the next start; a claim
505
+ * is the opposite case, so `claimSecretTrustPolicy` anchors its signer itself,
506
+ * awaits durability, and never sets `anchorAs` (the type excludes `'claim'`).
447
507
  *
448
508
  * NOTE: anchoring a key can flip `CadrePeer` rows ALREADY present from
449
509
  * unauthorized to authorized, which the write-driven membership-gate refresh
450
- * (`ControlDatabase.mutateCadrePeer`) cannot see — no row was written. Every
451
- * anchor mutation today rides seed application, and both seed paths refresh the
452
- * gate explicitly afterwards (`CadreNode.applySeed`, `onSeedApplied`). If some
453
- * future path anchors an owner OUTSIDE seed application, it owes the same
454
- * `CadreNode.refreshMembershipGate()` — or the anchor needs its own hub.
510
+ * (`ControlDatabase.mutateCadrePeer`) cannot see — no row was written. The anchor
511
+ * is mutated from two places: here, on seed application, where both seed paths
512
+ * refresh the gate explicitly afterwards (`CadreNode.applySeed`, `onSeedApplied`);
513
+ * and by the anchor sync (`CadreNode.syncOwnerAnchor`), which runs INSIDE the
514
+ * membership refresh, ahead of the membership read, so its changes are judged by
515
+ * the same pass. If some future path anchors an owner outside both, it owes the
516
+ * same `CadreNode.refreshMembershipGate()` — or the anchor needs its own hub.
455
517
  */
456
518
  private anchorAcceptedSigner;
457
519
  /**
@@ -465,26 +527,59 @@ export declare class SeedBootstrapService {
465
527
  /**
466
528
  * Deliver a seed directly to a peer via the /sereus/seed/1.0.0 protocol.
467
529
  *
468
- * Sender hardening: the whole exchange — dial, write, ack read — is bounded by
469
- * {@link seedDeliverTimeoutMs}, and the ack is capped at {@link MAX_SEED_SIZE}.
470
- * The target is a NOT-YET-TRUSTED node the instigator chose to dial during
471
- * onboarding, so an unbounded read here is strictly more exposed than the
472
- * membership-gated receiver paths: without the bound a target that accepts the
473
- * stream and never replies parks this call forever, and one that streams
474
- * arbitrary bytes as a fake ack exhausts memory.
530
+ * `target` is a multiaddr string, dialed as given, or a peer id with its addresses
531
+ * ({@link SeedDeliveryTarget}). For the second, the connection is formed first and on
532
+ * its own limits ({@link connectForDelivery}): an open one is reused, otherwise each
533
+ * address is dialed in turn, so one that never answers cannot use up the time a later
534
+ * one needed. Its addresses are also merged into the peer store, so identify and later
535
+ * dials see them. A peer id with no addresses is dialed by id, which reuses an open
536
+ * connection or tries the peer store's addresses. `options.claimProof` rides beside
537
+ * the seed in the message when the target is a brand-new node being claimed
538
+ * (`claim-proof.ts`).
539
+ *
540
+ * Sender hardening: the request — opening the stream (with its dial, for the forms
541
+ * that dial by `dialProtocol`), write, ack read — is bounded by {@link seedDeliverTimeoutMs},
542
+ * and the ack is capped at {@link MAX_SEED_SIZE}. The target is a NOT-YET-TRUSTED node the
543
+ * instigator chose to dial during onboarding, so an unbounded read here is strictly more
544
+ * exposed than the membership-gated receiver paths: without the bound a target that accepts
545
+ * the stream and never replies parks this call forever, and one that streams arbitrary bytes
546
+ * as a fake ack exhausts memory. A peer id with addresses can therefore take up to
547
+ * `dialBudget.totalMs` plus {@link seedDeliverTimeoutMs} (114.5 s at the default declared
548
+ * link) when none of its addresses answers.
549
+ *
550
+ * @throws {PeerUnreachableError} for a peer id with addresses, when no connection to it
551
+ * formed; nothing was sent. Anything else thrown after the connection formed means the
552
+ * node was reached and the exchange failed.
475
553
  */
476
- deliverSeed(targetMultiaddr: string, seed: ControlNetworkSeed): Promise<SeedAckMessage>;
554
+ deliverSeed(target: SeedDeliveryTarget, seed: ControlNetworkSeed, options?: {
555
+ claimProof?: string;
556
+ }): Promise<SeedAckMessage>;
557
+ /**
558
+ * The part of {@link deliverSeed} that runs before its deadline: how the delivery's stream
559
+ * will be opened, with any connection it needs already formed.
560
+ */
561
+ private seedStreamOpener;
562
+ /**
563
+ * A connection to `peerId` for {@link deliverSeed}, from {@link dialPeerAddrs}: one already
564
+ * open without relay limits, or one dialed from `addrs` — each address on
565
+ * `dialBudget.perAddressMs`, the whole peer within `dialBudget.totalMs`, direct addresses
566
+ * before relayed ones and the given order otherwise.
567
+ *
568
+ * @throws {PeerUnreachableError} when no connection formed, with the dial's error as `cause`.
569
+ */
570
+ private connectForDelivery;
477
571
  /**
478
572
  * Open one stream to the target, send the seed frame, half-close, and read the ack.
479
573
  *
480
- * `signal` is the deadline from {@link deliverSeed}: it goes to `dialProtocol` so
481
- * a timeout during connect aborts the dial, and into {@link exchangeFrame} so a
482
- * timeout after the stream is open resets it — releasing the otherwise unbounded
574
+ * `signal` is the deadline from {@link deliverSeed}: it goes to `openStream` so a
575
+ * timeout while dialing or opening the stream aborts it, and into {@link exchangeFrame}
576
+ * so a timeout after the stream is open resets it — releasing the otherwise unbounded
483
577
  * ack-read.
484
578
  *
485
579
  * Deliberately NOT `runOnLimitedConnection`: a wake sets it because a wake is a
486
- * tiny frame over a relay, whereas a seed is up to 1MB and this delivery path
487
- * does not dial relay addresses today. Changing that is a separate decision.
580
+ * tiny frame over a relay, whereas a seed is up to 1MB, so a relayed address that
581
+ * forms only a limited connection fails here when the stream is opened. Changing
582
+ * that is a separate decision.
488
583
  */
489
584
  private sendSeed;
490
585
  /**
@@ -502,32 +597,16 @@ export declare class SeedBootstrapService {
502
597
  getRelayAddress(): Promise<string | null>;
503
598
  /**
504
599
  * Validate a seed's signature.
600
+ *
601
+ * @param digestB64 - The seed's {@link seedDigest}, when the caller already computed it
602
+ * (`verifyAndMergeSeed` shares one digest between this check and the trust context).
505
603
  */
506
- validateSeedSignature(seed: ControlNetworkSeed): boolean;
604
+ validateSeedSignature(seed: ControlNetworkSeed, digestB64?: string): boolean;
507
605
  /**
508
- * Query peers from the control database.
509
- *
510
- * Owner identity is sourced from the `OwnerKey` table, not from the
511
- * transport peer ID. An Ed25519 libp2p PeerId embeds its public key (identity
512
- * multihash), so each peer's ed25519 key is derivable from its `PeerId`; a
513
- * peer is an owner iff that derived key is in the `OwnerKey` set.
514
- * This makes any owner node markable — not just the local one — and ties
515
- * `isOwner` to the control table rather than to `peerId === self`.
516
- *
517
- * Read through {@link ControlDatabase.queryCadrePeers}, not a raw `CadrePeer`
518
- * select: that reader drops any row whose `StampId` is retired in
519
- * `CadreControl.Revocation`, so a removed member's addresses are never packed
520
- * into a seed and pushed into a joiner's peerstore (`applySeed` adds every
521
- * seed peer's addrs and dials the owner-flagged ones). A revoked peer is off
522
- * the addressable surface everywhere, and this is one of its exits.
523
- *
524
- * NOTE: this is the one owner lookup deliberately left on the REPLICATED
525
- * table rather than the node-local anchor. `SeedPeer.isOwner` is a dial hint
526
- * — the receiver dials owner-flagged peers first — not a trust decision, and
527
- * the receiver re-derives real trust from its own anchor. So a polluted table
528
- * costs at most a wasted dial, while anchoring here would silently drop
529
- * legitimate co-owners this node never pinned. If `isOwner` ever gates
530
- * anything the receiver TRUSTS, move it to the anchor.
606
+ * The peers a seed carries: the control database's own projection
607
+ * ({@link ControlDatabase.querySeedPeers}, shared with the cadre invitation redemption
608
+ * reply), which reads through the revocation filter and flags owners from the replicated
609
+ * `OwnerKey` table — a dial hint, not a trust decision (the NOTE at `projectSeedPeers`).
531
610
  */
532
611
  private queryPeers;
533
612
  /**
@@ -559,7 +638,11 @@ export declare class SeedBootstrapService {
559
638
  * the exchange.
560
639
  */
561
640
  private handleSeedStream;
562
- /** Read the inbound seed frame to EOF (bounded and size-capped) and decode it. */
641
+ /**
642
+ * Read the inbound seed frame to EOF (bounded and size-capped) and decode it. The
643
+ * claim proof comes back beside the seed: it is not part of what the owner signed, so
644
+ * `ControlNetworkSeed` does not carry it.
645
+ */
563
646
  private readSeedFrame;
564
647
  /**
565
648
  * Shutdown the service.
@@ -585,31 +668,6 @@ export declare class SeedBootstrapService {
585
668
  * @returns Seed and encoded seed for drone initialization
586
669
  */
587
670
  addDrone(options: AddDroneOptions): Promise<DroneInitResult>;
588
- /**
589
- * Create an invite for a phone to join the cadre.
590
- *
591
- * Use this when a server (public IP) wants to invite a phone (NAT'd).
592
- * The invite is shared out-of-band (QR code, link, etc.) and contains
593
- * the server's address so the phone can dial in.
594
- *
595
- * @param token - Optional invite token for validation
596
- * @param expiresIn - Optional expiration time in milliseconds
597
- * @returns Invite and encoded invite for sharing
598
- */
599
- createInvite(token?: string, expiresIn?: number): Promise<InviteResult>;
600
- /**
601
- * Accept a phone connection using an invite.
602
- *
603
- * Use this when a phone dials in with an invite token. This method:
604
- * 1. Validates the token if provided
605
- * 2. Authorizes the phone peer
606
- *
607
- * After this, the phone can sync the control database normally.
608
- *
609
- * @param options - Phone peer info and invite token
610
- * @param issuedInvite - The original invite for validation
611
- */
612
- acceptPhone(options: AddPhoneOptions, issuedInvite?: CadreInvite): Promise<void>;
613
671
  /**
614
672
  * Add a phone to the cadre with relay support.
615
673
  *
@@ -621,20 +679,4 @@ export declare class SeedBootstrapService {
621
679
  * @returns Seed with relay addresses for out-of-band delivery
622
680
  */
623
681
  addPhoneWithRelay(phonePeerId: string): Promise<DroneInitResult>;
624
- /**
625
- * Encode an invite for out-of-band delivery.
626
- */
627
- encodeInvite(invite: CadreInvite): string;
628
- /**
629
- * Decode an invite from base64url encoding.
630
- */
631
- decodeInvite(encoded: string): CadreInvite;
632
- /**
633
- * Dial an owner from an invite.
634
- * Use this on a phone after receiving an invite to connect to the owner.
635
- *
636
- * @param invite - The invite received out-of-band
637
- * @returns Connection to the owner
638
- */
639
- dialInvite(invite: CadreInvite): Promise<void>;
640
682
  }