@serfab/cadre-core 1.16.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 +11 -2
  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 +652 -1052
  151. package/src/cadre-join-request.ts +24 -3
  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 +886 -789
  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
@@ -69,11 +69,11 @@ export interface JoinedStrandStore {
69
69
 
70
70
  /**
71
71
  * The {@link StrandRow} a record stands for — the shape `addStrand` and `strand:discovered`
72
- * carry. `FounderOwnerKey` is null because a joiner never founds; `CadreNode.launchStrand`
73
- * reads null as "not mine" and joins.
72
+ * carry. The founder columns are null because a joiner never founds; `CadreNode.launchStrand`
73
+ * reads a null `FounderPeerId` as "not mine" and joins.
74
74
  */
75
75
  export function joinedStrandRow(record: JoinedStrandRecord): StrandRow {
76
- return { Id: record.Id, Type: record.Type, MemberPrivateKey: record.MemberPrivateKey, FounderOwnerKey: null };
76
+ return { Id: record.Id, Type: record.Type, MemberPrivateKey: record.MemberPrivateKey, FounderOwnerKey: null, FounderPeerId: null };
77
77
  }
78
78
 
79
79
  /**
@@ -234,7 +234,7 @@ function parseRecord(strandId: string, bytes: Uint8Array): JoinedStrandRecord |
234
234
  * key; this module sees only the seam, so it stays free of the runtime.
235
235
  */
236
236
  export interface PartyJoinedStrandLedger {
237
- /** Every party-wide join, as offerable rows (`FounderOwnerKey: null`). */
237
+ /** Every party-wide join, as offerable rows (null founder columns). */
238
238
  list(): Promise<StrandRow[]>;
239
239
  /** Whether this party's control database names `strandId`, as its own strand or as a party-wide join. */
240
240
  names(strandId: string): Promise<boolean>;
package/src/key-store.ts CHANGED
@@ -2,10 +2,10 @@
2
2
  * Backend-agnostic store for raw private key material.
3
3
  *
4
4
  * Mobile cadre nodes hold sensitive key material at rest: the libp2p
5
- * peer/node identity key and (in the single-key reference model) the owner
6
- * signing key derived from it. This module defines the seam those keys flow
7
- * through so a platform-secure backend (iOS Keychain / Android Keystore) can be
8
- * plugged in without `@serfab/cadre-core` taking any platform dependency.
5
+ * peer/node identity key and, on a device the user made an owner, an owner key
6
+ * in a slot of its own (`owner-key-store.ts`). This module defines the seam those
7
+ * keys flow through so a platform-secure backend (iOS Keychain / Android Keystore)
8
+ * can be plugged in without `@serfab/cadre-core` taking any platform dependency.
9
9
  *
10
10
  * This file is dependency-free (no node:/RN imports) so it is safe in every
11
11
  * entry graph. The Node reference {@link FileKeyStore} lives in a separate
@@ -23,6 +23,12 @@ export type KeyId = string;
23
23
  */
24
24
  export const DEFAULT_IDENTITY_KEY_ID: KeyId = 'cadre/identity';
25
25
 
26
+ /**
27
+ * Default slot id for an owner key (`owner-key-store.ts`). Not party-scoped: one
28
+ * owner key may own several cadres, each owner signature binding its party id.
29
+ */
30
+ export const DEFAULT_OWNER_KEY_ID: KeyId = 'cadre/owner';
31
+
26
32
  /**
27
33
  * Backend-agnostic store for raw private key material. Implementations may be
28
34
  * file-based, OS-keyring, or platform secure-enclave backed (iOS Keychain /
@@ -1,17 +1,15 @@
1
1
  /**
2
- * Removing a machine from a cadre: the listing an app shows (`CadreNode.listMachines`), the
3
- * read-only plan behind its confirmation step (`CadreNode.planMachineRemoval`), and the
4
- * check that a requested re-vouch stays inside that plan. Pure projections over rows the
5
- * node has already read; the reads and writes themselves are `CadreNode.removeMachine`'s and
6
- * `ControlDatabase.removeMachine`'s.
2
+ * Removing a machine from a cadre: the listing an app shows (`CadreNode.listMachines`) and the
3
+ * read-only plan behind its confirmation step (`CadreNode.planMachineRemoval`). Pure
4
+ * projections over rows the node has already read; the reads and writes themselves are
5
+ * `CadreNode.removeMachine`'s and `ControlDatabase.removeMachine`'s.
7
6
  *
8
- * A machine's owner key is its own identity key: an owner-granting invitation seats
9
- * `OwnerKey.Key = CadrePeer.PublicKey`, and `addOwner` takes the key behind a peer id. So
10
- * "this machine is an owner" is "the ed25519 key behind its peer id is a live `OwnerKey`",
11
- * and an owner key with no machine row is still listed, as the machine it names.
7
+ * A machine is a `CadrePeer` row and nothing more: an owner key is the person's, so removing a
8
+ * machine leaves every owner key standing, the one the machine's user signed with included
9
+ * (removing one is `CadreNode.removeOwner`, `owner-keys.ts`).
12
10
  */
13
11
 
14
- import { ed25519PublicKeyB64FromPeerId, peerIdFromEd25519PublicKeyB64 } from './ed25519-key.js';
12
+ import { ed25519PublicKeyB64FromPeerId } from './ed25519-key.js';
15
13
  import { verifyPeerRecordSignature } from './peer-record.js';
16
14
  import type {
17
15
  CadreInviteStatus,
@@ -20,8 +18,6 @@ import type {
20
18
  CadrePeerRow,
21
19
  MachineRemovalPlan,
22
20
  MachineRemovalRefusal,
23
- OwnerKeyRow,
24
- RemoveMachineOptions,
25
21
  } from './types.js';
26
22
 
27
23
  /**
@@ -42,8 +38,7 @@ export class MachineRemovalRefusedError extends Error {
42
38
 
43
39
  const MACHINE_REMOVAL_REFUSAL_REASON: Record<MachineRemovalRefusal, string> = {
44
40
  'not-an-owner': 'this node holds no enrolled owner key',
45
- 'self': 'an owner cannot remove its own machine; ask another owner',
46
- 'last-owner': 'it holds the only live owner key',
41
+ 'self': 'it is this node; leaving a cadre is stopping the node and deleting its data',
47
42
  };
48
43
 
49
44
  /** What {@link describeMachines} projects from: one node's live rows and its own predicate. */
@@ -52,45 +47,21 @@ export interface MachineListingInput {
52
47
  selfPeerId: string | null;
53
48
  /** Live `CadrePeer` rows (`ControlDatabase.queryCadrePeers`). */
54
49
  rows: readonly CadrePeerRow[];
55
- /** Live `OwnerKey` rows (`ControlDatabase.queryOwnerKeyRows`). */
56
- owners: readonly OwnerKeyRow[];
57
50
  /** This node's authorized-membership predicate for one row (`CadreNode.listAuthorizedMembers`'s). */
58
51
  trusted: (row: CadrePeerRow) => boolean;
59
52
  }
60
53
 
61
54
  /**
62
- * One entry per live `CadrePeer` row, then one per live `OwnerKey` whose key derives a peer
63
- * id with no row. `profile` and `lastPublishedAt` are reported only when the row's
64
- * self-signature verifies under the key behind its peer id, as `CadreNode.resolvePeerAddrs`
65
- * judges the same record: no insert rule constrains `Profile`, and an owner re-vouch may
66
- * bump `UpdatedAt` without re-signing.
55
+ * One entry per live `CadrePeer` row. `profile` and `lastPublishedAt` are reported only when
56
+ * the row's self-signature verifies under the key behind its peer id, as
57
+ * `CadreNode.resolvePeerAddrs` judges the same record: no insert rule constrains `Profile`, and
58
+ * an owner re-vouch may bump `UpdatedAt` without re-signing.
67
59
  */
68
60
  export function describeMachines(input: MachineListingInput): CadreMachine[] {
69
- const ownerKeys = new Set(input.owners.map((owner) => owner.key));
70
- const machines = input.rows.map((row) => describeMemberRow(input, row, ownerKeys));
71
- const listed = new Set(machines.map((machine) => machine.peerId));
72
- for (const owner of input.owners) {
73
- const peerId = peerIdFromEd25519PublicKeyB64(owner.key);
74
- if (peerId === null || listed.has(peerId)) {
75
- continue;
76
- }
77
- listed.add(peerId);
78
- machines.push({
79
- peerId,
80
- publicKey: owner.key,
81
- isSelf: peerId === input.selfPeerId,
82
- isOwner: true,
83
- isMember: false,
84
- trusted: false,
85
- profile: null,
86
- lastPublishedAt: null,
87
- vouchedBy: owner.vouchOwner,
88
- });
89
- }
90
- return machines;
61
+ return input.rows.map((row) => describeMemberRow(input, row));
91
62
  }
92
63
 
93
- function describeMemberRow(input: MachineListingInput, row: CadrePeerRow, ownerKeys: ReadonlySet<string>): CadreMachine {
64
+ function describeMemberRow(input: MachineListingInput, row: CadrePeerRow): CadreMachine {
94
65
  const publicKey = ed25519PublicKeyB64FromPeerId(row.peerId);
95
66
  const selfPublished = publicKey !== null && row.publicKey === publicKey && row.sig !== null
96
67
  && verifyPeerRecordSignature(input.partyId, {
@@ -105,8 +76,6 @@ function describeMemberRow(input: MachineListingInput, row: CadrePeerRow, ownerK
105
76
  peerId: row.peerId,
106
77
  publicKey,
107
78
  isSelf: row.peerId === input.selfPeerId,
108
- isOwner: publicKey !== null && ownerKeys.has(publicKey),
109
- isMember: true,
110
79
  trusted: input.trusted(row),
111
80
  profile: selfPublished ? row.profile : null,
112
81
  lastPublishedAt: selfPublished ? row.updatedAt : null,
@@ -118,99 +87,45 @@ function describeMemberRow(input: MachineListingInput, row: CadrePeerRow, ownerK
118
87
  export interface MachineRemovalPlanInput {
119
88
  peerId: string;
120
89
  machines: readonly CadreMachine[];
121
- rows: readonly CadrePeerRow[];
122
- owners: readonly OwnerKeyRow[];
123
90
  selfPeerId: string | null;
124
- /** This node's enrolled owner key (`CadreNode.enrolledOwnerSigningKey`), or null when it cannot sign owner writes. */
125
- selfOwnerKey: string | null;
91
+ /** Whether this node's owner signer is an enrolled owner (`CadreNode.ownerSignerEnrolledIn`), so it can sign the removal. */
92
+ canSign: boolean;
126
93
  invites: readonly CadreInviteStatus[];
127
94
  usages: readonly CadreInviteUsageRow[];
128
95
  }
129
96
 
130
97
  /**
131
- * The plan for removing `peerId` from these rows. The refusals come first and by name: a
132
- * node that cannot sign, the node itself (an owner cannot sign its own `OwnerKey` removal;
133
- * refusing the whole self-removal keeps one rule for owners and non-owners), and a machine
134
- * holding the only live owner key. The last cannot coexist with this node being a different
135
- * owner, but the local count can be wrong under concurrency, so the name stays.
136
- *
137
- * `dependents` are the rows the machine's owner key vouched, which every reader judges
138
- * against its anchor through `VouchOwner`: once the key is removed they stop passing the
139
- * membership predicate on every machine unless re-vouched first. This node's own rows are
140
- * reported as `ownDependency` instead, since it cannot re-vouch them itself.
98
+ * The plan for removing `peerId` from these rows. The refusals come first and by name: a node
99
+ * that cannot sign, and the node itself (leaving a cadre is the embedder's flow, which stops
100
+ * the node and deletes its data; the removal notice a machine acts on is built around another
101
+ * machine removing it).
141
102
  *
142
103
  * `invitations` are the live ones the machine could redeem to come back: targeted at it,
143
104
  * or already redeemed by it and still redeemable (a multi-use invitation, or an open
144
- * owner invitation inside its lifetime). The schema's rule: keeping a device out is removing
105
+ * invitation inside its lifetime). The schema's rule: keeping a device out is removing
145
106
  * its row and withdrawing the invitation.
146
107
  */
147
108
  export function planMachineRemoval(input: MachineRemovalPlanInput): MachineRemovalPlan {
148
- const machine = input.machines.find((candidate) => candidate.peerId === input.peerId) ?? null;
149
- const key = machine?.isOwner ? machine.publicKey : null;
150
- const refusal = refusalFor(input, machine);
151
- const removesOwnerKey = key !== null;
152
109
  return {
153
- machine,
154
- refusal,
155
- removesOwnerKey,
156
- dependents: removesOwnerKey ? dependentsOf(input, key) : { owners: [], peers: [] },
110
+ machine: input.machines.find((candidate) => candidate.peerId === input.peerId) ?? null,
111
+ refusal: refusalFor(input),
157
112
  invitations: invitationsOpenTo(input),
158
- ownDependency: removesOwnerKey && isOwnDependency(input, key),
159
113
  };
160
114
  }
161
115
 
162
- function refusalFor(input: MachineRemovalPlanInput, machine: CadreMachine | null): MachineRemovalRefusal | null {
163
- if (input.selfOwnerKey === null) {
116
+ function refusalFor(input: MachineRemovalPlanInput): MachineRemovalRefusal | null {
117
+ if (!input.canSign) {
164
118
  return 'not-an-owner';
165
119
  }
166
120
  if (input.peerId === input.selfPeerId) {
167
121
  return 'self';
168
122
  }
169
- if (machine?.isOwner && input.owners.length <= 1) {
170
- return 'last-owner';
171
- }
172
123
  return null;
173
124
  }
174
125
 
175
- function dependentsOf(input: MachineRemovalPlanInput, key: string): MachineRemovalPlan['dependents'] {
176
- return {
177
- owners: input.owners
178
- .filter((owner) => owner.vouchOwner === key && owner.key !== key && owner.key !== input.selfOwnerKey)
179
- .map((owner) => owner.key),
180
- peers: input.rows
181
- .filter((row) => row.vouchOwner === key && row.peerId !== input.peerId && row.peerId !== input.selfPeerId)
182
- .map((row) => row.peerId),
183
- };
184
- }
185
-
186
- function isOwnDependency(input: MachineRemovalPlanInput, key: string): boolean {
187
- const ownRow = input.rows.find((row) => row.peerId === input.selfPeerId);
188
- const ownOwner = input.owners.find((owner) => owner.key === input.selfOwnerKey);
189
- return ownRow?.vouchOwner === key || ownOwner?.vouchOwner === key;
190
- }
191
-
192
126
  function invitationsOpenTo(input: MachineRemovalPlanInput): string[] {
193
127
  const redeemed = new Set(input.usages.filter((usage) => usage.peerId === input.peerId).map((usage) => usage.inviteKey));
194
128
  return input.invites
195
129
  .filter((status) => status.live && (status.invite.peerId === input.peerId || redeemed.has(status.invite.key)))
196
130
  .map((status) => status.invite.key);
197
131
  }
198
-
199
- /**
200
- * The re-vouch a caller asked for, checked against the plan: every key and peer must be in
201
- * `plan.dependents`, so a caller cannot re-vouch a row the removed owner never vouched (nor
202
- * the machine itself, which `dependents.peers` excludes). Throws naming the offenders, before
203
- * `CadreNode.removeMachine` writes anything. Duplicates are dropped.
204
- */
205
- export function revouchWithinPlan(plan: MachineRemovalPlan, revouch: RemoveMachineOptions['revouch']): MachineRemovalPlan['dependents'] {
206
- const owners = [...new Set(revouch?.owners ?? [])];
207
- const peers = [...new Set(revouch?.peers ?? [])];
208
- const strayOwners = owners.filter((key) => !plan.dependents.owners.includes(key));
209
- const strayPeers = peers.filter((peerId) => !plan.dependents.peers.includes(peerId));
210
- if (strayOwners.length > 0 || strayPeers.length > 0) {
211
- throw new Error(
212
- `removeMachine: re-vouch names rows the removed machine did not vouch: ${[...strayOwners, ...strayPeers].join(', ')}`,
213
- );
214
- }
215
- return { owners, peers };
216
- }