@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
@@ -2,8 +2,8 @@ import { Database } from '@quereus/quereus';
2
2
  import type { SqlParameters } from '@quereus/quereus';
3
3
  import type { Libp2p } from '@libp2p/interface';
4
4
  import type { IRepo } from '@optimystic/db-core';
5
- import type { StrandRow, JoinRequestRow, JoinOutcome, PendingJoin, NodeProfile, PeerAddressRecord, CadrePeerRow, OwnerKeyRow, RevocationRow, RevocationLedgerOpenResult, DeviceTokenRecord, DeviceTokenRow, CadreInviteRow, CadreInviteUsageRow, CadreInviteRedemptionResult, CadreInviteStatus, SeedPeer, MachineRemovalResult } from './types.js';
6
- import type { ControlTable, RevocableTable, BoundControlDomain, ControlAction, CadreInviteSignedFields } from './control-authorization.js';
5
+ import type { StrandRow, JoinRequestRow, JoinOutcome, PendingJoin, NodeProfile, PeerAddressRecord, CadrePeerRow, OwnerKeyRow, RevocationRow, RevocationLedgerOpenResult, DeviceTokenRecord, DeviceTokenRow, CadreInviteRow, CadreInviteUsageRow, CadreInviteRedemptionResult, CadreInviteStatus, OwnerGrantRow, OwnerGrantUsageRow, OwnerGrantRedemptionResult, OwnerGrantStatus, SeedPeer, MachineRemovalResult } from './types.js';
6
+ import type { ControlTable, RevocableTable, BoundControlDomain, ControlAction, CadreInviteSignedFields, OwnerGrantSignedFields } from './control-authorization.js';
7
7
  import type { ControlRetryAbandonment } from './control-retry.js';
8
8
  export type { ControlTable, RevocableTable, ControlDomain, ControlAction } from './control-authorization.js';
9
9
  /**
@@ -52,13 +52,14 @@ export declare class FormationAbortedError extends Error {
52
52
  /**
53
53
  * The invitation's seat budget is already spent: the count of recorded usage rows for the
54
54
  * invitation has reached its `TotalUses`, so this redemption cannot be given a seat. `token`
55
- * is the `FormationInvite.Token` or the `CadreInvite.Key`, whichever table the caller
56
- * redeemed against.
55
+ * is the `FormationInvite.Token`, the `CadreInvite.Key` or the `OwnerGrant.Key`, whichever
56
+ * table the caller redeemed against.
57
57
  *
58
58
  * Raised off the committed-count check ({@link ControlDatabase.assertSeatRemains}) ahead of
59
59
  * the write, inside {@link ControlDatabase.redeemInvitation} /
60
60
  * {@link ControlDatabase.recordFormationUsage}, off the same check for a cadre invitation
61
- * ({@link ControlDatabase.redeemCadreInvite}), and off the unlocked pre-check
61
+ * ({@link ControlDatabase.redeemCadreInvite}) and an owner grant
62
+ * ({@link ControlDatabase.redeemOwnerGrant}), and off the unlocked pre-check
62
63
  * `ControlFormationUsageRecorder.authorizeUsage` runs before the manager issues a membership
63
64
  * pass — and off nothing else. It exists because
64
65
  * without it the refusal surfaces as a generic `CHECK constraint failed: Authorized` from the
@@ -89,6 +90,35 @@ export declare class CadreInviteIssuerUnknownError extends Error {
89
90
  cause?: unknown;
90
91
  });
91
92
  }
93
+ /**
94
+ * {@link ControlDatabase.seatOwnerGrant} was refused by `OwnerGrant.AuthorizedInsert`: the
95
+ * grant twin of {@link CadreInviteIssuerUnknownError}, for the same two causes (the issuer's
96
+ * `OwnerKey` row has not replicated here yet or has since been removed; or a tampered row).
97
+ * Its own class rather than a shared one so a caller that handles invitation and grant
98
+ * bundles can tell by `instanceof` which credential was refused.
99
+ */
100
+ export declare class OwnerGrantIssuerUnknownError extends Error {
101
+ readonly grantKey: string;
102
+ readonly issuerKey: string;
103
+ constructor(grantKey: string, issuerKey: string, options?: {
104
+ cause?: unknown;
105
+ });
106
+ }
107
+ /**
108
+ * An issued row (a `CadreInvite` or an `OwnerGrant`) the cadre holds that this node cannot read
109
+ * yet: {@link ControlDatabase.seatCadreInvite} / {@link ControlDatabase.seatOwnerGrant} had its
110
+ * insert refused by a cohort member already holding the row, and a refreshed read here still
111
+ * finds nothing; or the redemption that follows the seat finds no row. It clears once this
112
+ * node's store catches up, which is why the redemption paths answer it with the retryable
113
+ * `conflict` code.
114
+ */
115
+ export declare class IssuedRowNotHereYetError extends Error {
116
+ readonly table: IssuedTable;
117
+ readonly key: string;
118
+ constructor(table: IssuedTable, key: string, options?: {
119
+ cause?: unknown;
120
+ });
121
+ }
92
122
  /**
93
123
  * What a recorded `FormationUsage` row came out as.
94
124
  *
@@ -238,6 +268,73 @@ export declare function cadreInviteRedeemMessage(partyId: string, fields: CadreI
238
268
  * distinct action tag, so neither signature can stand in for the other.
239
269
  */
240
270
  export declare function cadreInviteConsentMessage(partyId: string, fields: CadreInviteRedemptionFields): Uint8Array;
271
+ /**
272
+ * The exact bytes an owner signs to seat an `OwnerGrant` row — the TS mirror of the `'add'`
273
+ * digest in `OwnerGrant.AuthorizedInsert`, over the whole row in the schema's order with
274
+ * nullable columns as `''` (`ownerGrantRowFields`, shared with the base64url verifier
275
+ * `peer-authorization.ts`'s `ownerGrantAddDigest`). `expiresAt` must already be the stored
276
+ * canonical `datetime` string, as {@link ControlDatabase.insertOwnerGrant} produces it, for
277
+ * the reason on {@link cadreInviteAddMessage}.
278
+ */
279
+ export declare function ownerGrantAddMessage(partyId: string, row: OwnerGrantSignedFields): Uint8Array;
280
+ /** The fields both owner-grant redemption signatures cover; see {@link ownerGrantRedeemMessage}. */
281
+ export interface OwnerGrantRedemptionFields {
282
+ grantKey: string;
283
+ /** The single-use nonce the holder minted for this redemption (`generateStampId`). */
284
+ usageStampId: string;
285
+ /** The owner key being seated (base64url ed25519). */
286
+ seatedKey: string;
287
+ }
288
+ /**
289
+ * The exact bytes the HOLDER of an owner grant signs, with the grant's private key, to redeem
290
+ * it once — the TS mirror of the `'redeem'` digest `OwnerGrantUsage.GrantPossessed` verifies
291
+ * against the grant's public key. Binding the seated key and the nonce means a captured
292
+ * redemption can seat no other key and spend no other use.
293
+ */
294
+ export declare function ownerGrantRedeemMessage(partyId: string, fields: OwnerGrantRedemptionFields): Uint8Array;
295
+ /**
296
+ * The exact bytes the SEATED key signs, proving the holder holds it — the TS mirror of the
297
+ * `'consent'` digest `OwnerGrantUsage.OwnerConsented` verifies against `SeatedKey`. Same
298
+ * fields as {@link ownerGrantRedeemMessage} under a distinct action tag, so neither
299
+ * signature can stand in for the other.
300
+ */
301
+ export declare function ownerGrantConsentMessage(partyId: string, fields: OwnerGrantRedemptionFields): Uint8Array;
302
+ /**
303
+ * A guarded table of owner-issued credentials: signed rows that travel inside a bundle, are
304
+ * seated verbatim by any member ({@link ControlDatabase.seatCadreInvite},
305
+ * {@link ControlDatabase.seatOwnerGrant}), and are never deleted (`NoDelete`) because the
306
+ * rows they admitted prove themselves through them. Withdrawing one is a tombstone alone
307
+ * ({@link ControlDatabase.withdrawCadreInvite}, {@link ControlDatabase.withdrawOwnerGrant}).
308
+ */
309
+ type IssuedTable = Extract<RevocableTable, 'CadreInvite' | 'OwnerGrant'>;
310
+ /**
311
+ * An owner key's label as its row stores it: trimmed, a blank one left out, and refused past
312
+ * {@link OWNER_KEY_MAX_LABEL} by name rather than by the schema's `LabelLength` check.
313
+ * Idempotent, so a caller that checks before opening an owner approval can pass the result on.
314
+ */
315
+ export declare function ownerKeyLabel(label: string | null | undefined): string | null;
316
+ /**
317
+ * Signs one owner digest's raw bytes (no pre-hash) and returns a base64url Ed25519 signature:
318
+ * the callback every owner-signed writer takes. An `OwnerSign` from an open
319
+ * `OwnerSigner.approve` fits it, and so does a synchronous signer.
320
+ *
321
+ * NOTE: writers that read the stamp they sign do so inside the write lock, so the lock is held
322
+ * across these calls, and a `lockedWithRetry` re-run signs again inside the same approval.
323
+ * Free for a software signer. If a prompting signer ever holds the lock long enough to stall
324
+ * other control writes, sign outside the lock and re-check the stamp inside it.
325
+ */
326
+ export type ControlSign = (message: Uint8Array) => string | Promise<string>;
327
+ /** What an owner asks {@link ControlDatabase.insertCadreInvite} (or {@link ControlDatabase.signCadreInvite}) to sign. */
328
+ export interface CadreInviteFields {
329
+ /** The invitation public key (base64url ed25519), the row's primary key. */
330
+ key: string;
331
+ /** The device the invitation is for, or null for whoever redeems first. */
332
+ peerId?: string | null;
333
+ /** Epoch ms after which it cannot be redeemed; omit for never. */
334
+ expiresAtMs?: number | null;
335
+ /** How many redemptions it allows; omit for unlimited. */
336
+ totalUses?: number | null;
337
+ }
241
338
  /** The control tables keyed by a strand id whose repeat insert a caller may treat as idempotent. */
242
339
  type StrandIdTable = Extract<RevocableTable, 'Strand' | 'JoinedStrand'>;
243
340
  /**
@@ -256,6 +353,13 @@ type StrandIdTable = Extract<RevocableTable, 'Strand' | 'JoinedStrand'>;
256
353
  * reword reddens there.
257
354
  */
258
355
  export declare function isStrandIdConflict(error: unknown, table: StrandIdTable): boolean;
356
+ /**
357
+ * Did a seat of an issued row fail because a cohort member already holds a row under its key?
358
+ * {@link ControlDatabase.seatCadreInvite} / {@link ControlDatabase.seatOwnerGrant} reach this
359
+ * on a node whose store lags the cohort: its guard read missed the row, and the member that
360
+ * checked the commit refused the insert. Text-matched, failing closed, like {@link isStrandIdConflict}.
361
+ */
362
+ export declare function isIssuedRowKeyConflict(error: unknown, table: IssuedTable): boolean;
259
363
  /**
260
364
  * Did {@link ControlDatabase.insertJoinRequest} fail because the party already holds a
261
365
  * `JoinRequest` row for this invitation? The caller adopts the existing request instead.
@@ -299,22 +403,17 @@ export declare const REVOCATION_LEDGER_MARKER: {
299
403
  };
300
404
  /**
301
405
  * Project `CadrePeer` rows into the {@link SeedPeer} list a seed, or a cadre invitation
302
- * redemption reply, carries.
406
+ * redemption reply, carries. `minterPeerId` is the machine minting the seed or answering the
407
+ * redemption; it contributes an entry only through its own `CadrePeer` row.
303
408
  *
304
- * Owner identity is sourced from the `OwnerKey` table, not from the transport peer id. An
305
- * Ed25519 libp2p peer id embeds its public key (identity multihash), so each peer's ed25519
306
- * key is derivable from its `PeerId`; a peer is an owner iff that derived key is in
307
- * `ownerKeys`. This makes any owner node markable — not just the local one. A non-Ed25519
308
- * or unparsable id yields a non-owner rather than failing the whole projection.
309
- *
310
- * NOTE: this is the one owner lookup deliberately left on the REPLICATED table rather than
311
- * the node-local anchor. `SeedPeer.isOwner` is a dial hint — the receiver dials owner-flagged
312
- * peers first — not a trust decision, and the receiver re-derives real trust from its own
313
- * anchor. So a polluted table costs at most a wasted dial, while anchoring here would
314
- * silently drop legitimate co-owners this node never pinned. If `isOwner` ever gates
315
- * anything the receiver TRUSTS, move it to the anchor.
409
+ * A peer is `preferred` when it is the minter, the one machine certain to be reachable by the
410
+ * receiver at that moment, since it is delivering the seed or the reply, or an always-on
411
+ * machine (`Profile = 'storage'`), the ones that stay dialable. Preference is a dial hint and
412
+ * reads no `OwnerKey` row: owner keys are not machines, and a receiver derives trust from its
413
+ * own anchor, never from this list. A `Profile` is self-declared and not checked here, so a
414
+ * machine misdeclaring one costs the receiver a wasted dial at most.
316
415
  */
317
- export declare function projectSeedPeers(rows: readonly CadrePeerRow[], ownerKeys: ReadonlySet<string>): SeedPeer[];
416
+ export declare function projectSeedPeers(rows: readonly CadrePeerRow[], minterPeerId: string): SeedPeer[];
318
417
  /**
319
418
  * Guarded tables a node may reap locally once their tombstone has committed — the
320
419
  * tables whose `AuthorizedDelete` carries the REAP branch (see the constraint comment on
@@ -326,8 +425,8 @@ export declare function projectSeedPeers(rows: readonly CadrePeerRow[], ownerKey
326
425
  * hold, so a reaped row is recoverable by re-forming. `JoinRequest` is reapable for the same
327
426
  * reason: the inviting party and the user also hold its invitation. `OwnerKey` has no
328
427
  * production removal path and `MinOneOwner` makes an automated owner-key reap a
329
- * party-bricking hazard. `CadreInvite` is never deleted at all (its tombstone is a
330
- * withdrawal over a row that stays), so there is nothing to reap.
428
+ * party-bricking hazard. `CadreInvite` and `OwnerGrant` are never deleted at all (their
429
+ * tombstones are withdrawals over rows that stay), so there is nothing to reap.
331
430
  */
332
431
  export declare const REAPABLE_TABLES: readonly ["CadrePeer", "DeviceToken", "ValidationKey", "JoinedStrand", "JoinRequest"];
333
432
  export type ReapableTable = (typeof REAPABLE_TABLES)[number];
@@ -361,8 +460,6 @@ export type MembershipChangeListener = (reason: string) => Promise<void>;
361
460
  * the writer that files the row supplies it.
362
461
  */
363
462
  export type RevokedRowRef = Pick<RevocationRow, 'tableName' | 'rowKey' | 'stampId'>;
364
- /** What {@link ControlDatabase.removeMachine} wrote: `CadreNode.removeMachine`'s result, less the re-vouches it made first. */
365
- export type MachineRemovalWrites = Pick<MachineRemovalResult, 'removedPeer' | 'removedOwnerKey' | 'removedDeviceToken' | 'withdrawnInvitations'>;
366
463
  /**
367
464
  * Notified after a guarded-table delete — and the `CadreControl.Revocation`
368
465
  * tombstone it writes in the same transaction — has COMMITTED.
@@ -400,6 +497,8 @@ export interface ControlDatabaseConfig {
400
497
  * Optional path to the control schema file.
401
498
  * If not provided, uses the embedded schema for cross-platform compatibility.
402
499
  * Only use this if you need to override the default schema (e.g., for testing).
500
+ * The file has the shape of `schemas/control.qsql`: one `declare schema CadreControl`
501
+ * followed by one plain `apply schema CadreControl` ({@link controlSchemaFileDeclaration}).
403
502
  */
404
503
  schemaPath?: string;
405
504
  /** Libp2p node for the control network (injected) */
@@ -448,6 +547,8 @@ export declare class ControlDatabase {
448
547
  */
449
548
  initialize(): Promise<void>;
450
549
  private loadSchema;
550
+ /** Apply the declared control schema, retrying transient cohort failures. */
551
+ private applyControlSchema;
451
552
  /**
452
553
  * One UNRETRIED drain of a control read to rows — every read in this class bottoms out
453
554
  * here (almost all via {@link readRows}, which adds the transient-failure retry), and
@@ -592,7 +693,7 @@ export declare class ControlDatabase {
592
693
  /**
593
694
  * Every strand this party joined from another party (`CadreControl.JoinedStrand`), shaped
594
695
  * as a {@link StrandRow} so a reader can treat it like one of the party's own. A joiner
595
- * never founds, so `FounderOwnerKey` is always null. Read raw, like {@link queryStrands}:
696
+ * never founds, so `FounderOwnerKey` and `FounderPeerId` are always null. Read raw, like {@link queryStrands}:
596
697
  * no retired-stamp filter.
597
698
  */
598
699
  queryJoinedStrands(): Promise<StrandRow[]>;
@@ -640,8 +741,10 @@ export declare class ControlDatabase {
640
741
  * Idempotent genesis: insert `key` as the founding owner key only when
641
742
  * the party has none yet. Returns true if it inserted, false if an owner
642
743
  * key already existed (so a repeat `--owner` start is a no-op).
744
+ *
745
+ * @param label - the user's name for the key ({@link insertOwnerKey}).
643
746
  */
644
- ensureOwnerKey(key: string): Promise<boolean>;
747
+ ensureOwnerKey(key: string, label?: string): Promise<boolean>;
645
748
  /**
646
749
  * Collect every owner key (`CadreControl.OwnerKey.Key`) as a set.
647
750
  *
@@ -666,8 +769,9 @@ export declare class ControlDatabase {
666
769
  */
667
770
  getOwnerKeys(retry?: boolean): Promise<Set<string>>;
668
771
  /**
669
- * Every live `OwnerKey` row with its persisted proof ({@link OwnerKeyRow}) — what the
670
- * trusted-owner anchor sync derives from (`CadreNode.syncOwnerAnchor`). Rows whose
772
+ * Every live `OwnerKey` row with its label and persisted proof ({@link OwnerKeyRow}) — what
773
+ * the trusted-owner anchor sync derives from (`CadreNode.syncOwnerAnchor`) and the owner-key
774
+ * listing shows (`CadreNode.listOwnerKeys`). Rows whose
671
775
  * `StampId` is retired in `CadreControl.Revocation` are excluded here, as
672
776
  * {@link queryCadrePeers} excludes them and for the reason on {@link getOwnerKeys}, which
673
777
  * reads through this. A row with no stamp (the schema permits it; no writer produces one)
@@ -723,15 +827,15 @@ export declare class ControlDatabase {
723
827
  queryCadrePeers(retry?: boolean): Promise<CadrePeerRow[]>;
724
828
  /**
725
829
  * The cadre's peers as a seed or a redemption reply carries them: every live `CadrePeer`
726
- * row with its addresses, each flagged `isOwner` when the key behind its peer id is in
727
- * the replicated `OwnerKey` table ({@link projectSeedPeers}).
830
+ * row with its addresses, the minter and every always-on machine marked preferred
831
+ * ({@link projectSeedPeers}).
728
832
  *
729
833
  * Read through {@link queryCadrePeers}, never a raw `CadrePeer` select, so a removed
730
834
  * member's addresses are never packed into a seed or handed to a redeeming device as a
731
835
  * dial hint: a revoked peer is off the addressable surface everywhere, and this is one of
732
836
  * its exits.
733
837
  */
734
- querySeedPeers(): Promise<SeedPeer[]>;
838
+ querySeedPeers(minterPeerId: string): Promise<SeedPeer[]>;
735
839
  /**
736
840
  * Every approver public key currently enrolled in `CadreControl.ValidationKey` — the
737
841
  * set a `ValidationUrl` redemption's approval signature must be signed by (see
@@ -948,19 +1052,34 @@ export declare class ControlDatabase {
948
1052
  updateSelfDeviceToken(record: DeviceTokenRecord): Promise<void>;
949
1053
  /**
950
1054
  * Insert the initial owner key (bootstrap - no existing owners required)
1055
+ *
1056
+ * @param label - the user's name for the key, at most {@link OWNER_KEY_MAX_LABEL}
1057
+ * characters once trimmed; blank is stored as none.
951
1058
  */
952
- insertOwnerKey(key: string): Promise<void>;
1059
+ insertOwnerKey(key: string, label?: string): Promise<void>;
953
1060
  /**
954
1061
  * Owner-signed INSERT of a further `OwnerKey` row, notifying the membership listener once
955
1062
  * it has committed. The signed-add branch of `OwnerKey.Authorized`: the signer must be an
956
1063
  * owner in the pre-transaction snapshot, its signature covers the `'add'` digest over
957
- * (Key, StampId), and the stored `VouchOwner`/`VouchSig` are that same pair, so a reader
958
- * can later judge the add against its own anchor (`verifyOwnerKeyVoucher`).
1064
+ * (Key, Label, StampId), and the stored `VouchOwner`/`VouchSig` are that same pair, so a
1065
+ * reader can later judge the add against its own anchor (`verifyOwnerKeyVoucher`).
959
1066
  *
960
1067
  * Through {@link mutateCadrePeer}, the membership hub. The liveness read, the stamp and the
961
1068
  * signature all sit inside the locked body: a {@link lockedWithRetry} re-run starts over a
962
1069
  * rolled-back attempt with a fresh stamp, as {@link redeemCadreInvite} does.
963
1070
  *
1071
+ * @param label - the user's name for the key, as {@link insertOwnerKey}'s.
1072
+ * @returns `true` when this call seated the row, `false` when `key` is already a live owner
1073
+ * here (nothing written, whatever its label).
1074
+ * @throws when this node holds `key`'s row under a retired stamp
1075
+ * ({@link liveOwnerKeyStamp}), or when the schema refuses the signer.
1076
+ */
1077
+ insertOwnerKeyVouched(key: string, ownerKey: string, signMessage: ControlSign, label?: string | null): Promise<boolean>;
1078
+ /**
1079
+ * The stamp of `key`'s live `OwnerKey` row here, or null when this node holds none: the
1080
+ * insert-if-absent read of {@link insertOwnerKeyVouched} and {@link redeemOwnerGrant}. Inside
1081
+ * the locked body (both reads `retry: false`; see queryStampId's NOTE).
1082
+ *
964
1083
  * NOTE: re-adding a removed owner from a machine that still holds its retired physical row
965
1084
  * is refused here by name: `OwnerKey` is not reaped, so a machine that received the
966
1085
  * tombstone without the delete (see {@link getOwnerKeys}) keeps the row, and an insert there
@@ -968,12 +1087,15 @@ export declare class ControlDatabase {
968
1087
  * machine the delete reached; if re-adding removed owners becomes routine, the fix is an
969
1088
  * `OwnerKey` reap branch, gated so it can never take the table below `MinOneOwner`.
970
1089
  *
971
- * @returns `true` when this call seated the row, `false` when `key` is already a live owner
972
- * here (nothing written).
973
- * @throws when this node holds `key`'s row under a retired stamp (above), or when the
974
- * schema refuses the signer.
1090
+ * @throws when this node holds `key`'s row under a retired stamp (above).
975
1091
  */
976
- insertOwnerKeyVouched(key: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1092
+ private liveOwnerKeyStamp;
1093
+ /**
1094
+ * The signed-add statement of {@link insertOwnerKeyVouched} and {@link revouchOwnerKey}, the
1095
+ * stored voucher being the signing pair. Bare `exec`: callers hold the write lock, which is
1096
+ * NOT re-entrant.
1097
+ */
1098
+ private execOwnerKeyVouchedInsert;
977
1099
  /**
978
1100
  * Owner-signed removal of one `OwnerKey` row plus the `Revocation` tombstone retiring its
979
1101
  * stamp, notifying the membership listener once it has committed. The remove branch of
@@ -991,56 +1113,51 @@ export declare class ControlDatabase {
991
1113
  * (nothing written, no tombstone).
992
1114
  * @throws when `key` is `ownerKey`, or is the only live owner.
993
1115
  */
994
- deleteOwnerKey(key: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1116
+ deleteOwnerKey(key: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
995
1117
  /**
996
1118
  * Re-vouch a live `OwnerKey` row under this owner's signature: in ONE transaction, the
997
1119
  * current row's guarded delete plus tombstone ({@link deleteOwnerKey}'s body) and an insert
998
- * of the same key under a fresh stamp, signed by `ownerKey` ({@link insertOwnerKeyVouched}'s
999
- * body), as {@link rewritePendingJoin} re-seats a request. `OwnerKey` has `NoUpdate`, so
1120
+ * of the same key and label under a fresh stamp, signed by `ownerKey`
1121
+ * ({@link insertOwnerKeyVouched}'s body), as {@link rewritePendingJoin} re-seats a request. `OwnerKey` has `NoUpdate`, so
1000
1122
  * this is the only way to rebind a row's voucher; the anchor derivation lets the re-add
1001
1123
  * under a fresh stamp beat the older tombstone (`deriveOwnerAnchor`). For
1002
- * {@link removeMachine}: the owners a machine being removed had added keep their anchor
1003
- * place everywhere once their row stands on a remaining owner's signature.
1124
+ * `CadreNode.removeOwner`: the owners a key being removed had added keep their anchor place
1125
+ * everywhere once their row stands on a remaining owner's signature.
1004
1126
  *
1005
- * Through {@link mutateCadrePeer}, the membership hub. The stamp read runs inside the locked
1127
+ * Through {@link mutateCadrePeer}, the membership hub. The row read runs inside the locked
1006
1128
  * body; a row whose stamp is already retired (held without its delete) is reported as absent
1007
1129
  * rather than tombstoned twice.
1008
1130
  *
1009
1131
  * @returns `true` when the row was re-vouched, `false` when `key` is not a live owner here.
1010
1132
  * @throws when `key` is `ownerKey` (an owner cannot sign its own row's removal).
1011
1133
  */
1012
- revouchOwnerKey(key: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1134
+ revouchOwnerKey(key: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
1013
1135
  /**
1014
- * Remove one machine completely, in ONE transaction: its `CadrePeer` row, its `OwnerKey`
1015
- * row when `target.ownerKey` names one, its `DeviceToken` row when it has one, each with
1016
- * the `Revocation` tombstone retiring its stamp ({@link deleteGuardedRow}'s statement pair,
1017
- * composed bare as {@link deleteStrandAndPartyKey} composes it), plus a withdrawal tombstone
1018
- * for every `CadreInvite` in `target.inviteKeys` that is still live here
1019
- * ({@link withdrawCadreInvite}'s body). One transaction so no reader sees the machine half
1020
- * removed: a row gone but its push token resolvable, or its owner key gone but an invitation
1021
- * it could redeem again still open.
1136
+ * Remove one machine completely, in ONE transaction: its `CadrePeer` row and its
1137
+ * `DeviceToken` row when it has one, each with the `Revocation` tombstone retiring its stamp
1138
+ * ({@link deleteGuardedRow}'s statement pair, composed bare as {@link deleteStrandAndPartyKey}
1139
+ * composes it), plus a withdrawal tombstone for every `CadreInvite` in `target.inviteKeys`
1140
+ * that is still live here ({@link withdrawCadreInvite}'s body). One transaction so no reader
1141
+ * sees the machine half removed: a row gone but its push token resolvable, or an invitation
1142
+ * it could redeem again still open. Owner keys are never touched: they are the person's, and
1143
+ * removing one is {@link deleteOwnerKey}.
1022
1144
  *
1023
1145
  * Through {@link mutateCadrePeer}, the membership hub. Every stamp is read inside the locked
1024
1146
  * body, and a row whose stamp is already retired (held without its delete) is skipped rather
1025
- * than tombstoned twice. The by-name owner refusals of {@link deleteOwnerKey} apply to the
1026
- * owner-key arm. Nothing to remove writes nothing and returns all false.
1027
- *
1028
- * The caller decides WHAT to remove, which owner key is the machine's and which invitations
1029
- * it could use, from its plan (`CadreNode.planMachineRemoval`); this method only refuses to
1030
- * write an inconsistent set.
1147
+ * than tombstoned twice. Nothing to remove writes nothing and returns all false.
1031
1148
  *
1032
- * @throws when `target.ownerKey` is `ownerKey`, or is the only live owner here.
1149
+ * The caller decides which invitations the machine could use, from its plan
1150
+ * (`CadreNode.planMachineRemoval`).
1033
1151
  */
1034
1152
  removeMachine(target: {
1035
1153
  peerId: string;
1036
- ownerKey: string | null;
1037
1154
  inviteKeys: readonly string[];
1038
- }, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<MachineRemovalWrites>;
1155
+ }, ownerKey: string, signMessage: ControlSign): Promise<MachineRemovalResult>;
1039
1156
  /**
1040
1157
  * The guarded removals {@link removeMachine} runs, signed over each row's current stamp:
1041
- * the `CadrePeer` row, the `OwnerKey` row when named, the `DeviceToken` row when present.
1042
- * Inside the locked body (every read `retry: false`); one `Revocation` scan serves the
1043
- * retired-stamp check for all three tables and the last-owner refusal.
1158
+ * the `CadrePeer` row and the `DeviceToken` row, each when present. Inside the locked body
1159
+ * (every read `retry: false`); one `Revocation` scan serves the retired-stamp check for both
1160
+ * tables.
1044
1161
  */
1045
1162
  private signMachineRemovals;
1046
1163
  /**
@@ -1062,7 +1179,7 @@ export declare class ControlDatabase {
1062
1179
  * with the owner's private key, returning a base64url signature
1063
1180
  * @param memberPrivateKey - Optional private key for membership in closed strands
1064
1181
  */
1065
- insertStrand(strandId: string, type: 'o' | 'c', ownerKey: string, signMessage: (message: Uint8Array) => string, memberPrivateKey?: string): Promise<void>;
1182
+ insertStrand(strandId: string, type: 'o' | 'c', ownerKey: string, signMessage: ControlSign, memberPrivateKey?: string): Promise<void>;
1066
1183
  /**
1067
1184
  * Delete a strand from the control database using an owner signature.
1068
1185
  *
@@ -1090,7 +1207,7 @@ export declare class ControlDatabase {
1090
1207
  * A no-op (no throw, no tombstone) when the row does not exist — `false` then, `true`
1091
1208
  * when a row was actually removed.
1092
1209
  */
1093
- deleteStrand(strandId: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1210
+ deleteStrand(strandId: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
1094
1211
  /**
1095
1212
  * The locked body of {@link deleteStrand}: the `Strand` delete + tombstone, plus — when
1096
1213
  * one exists — the strand's `StrandPartyKey` delete + tombstone, all in ONE
@@ -1120,7 +1237,7 @@ export declare class ControlDatabase {
1120
1237
  * @param signMessage - Function that ed25519-signs the raw message bytes (no pre-hash)
1121
1238
  * with the owner's private key, returning a base64url signature.
1122
1239
  */
1123
- insertStrandPartyKey(strandId: string, privateKey: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<void>;
1240
+ insertStrandPartyKey(strandId: string, privateKey: string, ownerKey: string, signMessage: ControlSign): Promise<void>;
1124
1241
  /**
1125
1242
  * Owner-signed removal of one `StrandPartyKey` row — the party's own membership
1126
1243
  * identity for that strand, stored nowhere else, so this is as destructive as
@@ -1133,7 +1250,7 @@ export declare class ControlDatabase {
1133
1250
  * `Revocation` tombstone in the same transaction. A no-op (no throw, no tombstone)
1134
1251
  * when the row does not exist — `false` then, `true` when a row was actually removed.
1135
1252
  */
1136
- deleteStrandPartyKey(strandId: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1253
+ deleteStrandPartyKey(strandId: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
1137
1254
  /**
1138
1255
  * Record, party-wide, a strand this party joined from another party (`JoinedStrand` row),
1139
1256
  * using an owner signature.
@@ -1146,14 +1263,14 @@ export declare class ControlDatabase {
1146
1263
  * Fails with a `JoinedStrand.Id` uniqueness violation when the id is already recorded;
1147
1264
  * `isStrandIdConflict(error, 'JoinedStrand')` identifies that case.
1148
1265
  */
1149
- insertJoinedStrand(row: Pick<StrandRow, 'Id' | 'Type' | 'MemberPrivateKey'>, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<void>;
1266
+ insertJoinedStrand(row: Pick<StrandRow, 'Id' | 'Type' | 'MemberPrivateKey'>, ownerKey: string, signMessage: ControlSign): Promise<void>;
1150
1267
  /**
1151
1268
  * Owner-signed removal of one `JoinedStrand` row. Mirrors {@link deleteStrandPartyKey}:
1152
1269
  * `'remove'`-tagged digest over (Id, StampId), `Revocation` tombstone in the same
1153
1270
  * transaction. A no-op (no throw, no tombstone) when the row does not exist — `false`
1154
1271
  * then, `true` when a row was actually removed.
1155
1272
  */
1156
- deleteJoinedStrand(strandId: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1273
+ deleteJoinedStrand(strandId: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
1157
1274
  /**
1158
1275
  * Record, party-wide, a join this party asked for: a `JoinRequest` row under a fresh stamp,
1159
1276
  * owner-signed over every column. Returns the pending join as written.
@@ -1161,7 +1278,7 @@ export declare class ControlDatabase {
1161
1278
  * Fails with a `JoinRequest.Id` uniqueness violation when the party already holds a request
1162
1279
  * for this invitation; {@link isPendingJoinConflict} identifies that case.
1163
1280
  */
1164
- insertJoinRequest(request: JoinRequestFields, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<PendingJoin>;
1281
+ insertJoinRequest(request: JoinRequestFields, ownerKey: string, signMessage: ControlSign): Promise<PendingJoin>;
1165
1282
  /**
1166
1283
  * Record the outcome of the pending join `expected`: its owner-signed `JoinSuccess` /
1167
1284
  * `JoinFailure` row, keyed by the request's stamp. A join recorded over a failure deletes the
@@ -1175,7 +1292,7 @@ export declare class ControlDatabase {
1175
1292
  * transaction instead (its outcome row collides on the primary key, or its tombstone retires
1176
1293
  * the stamp), and the caller re-reads either way.
1177
1294
  */
1178
- recordJoinOutcome(expected: PendingJoin, outcome: JoinOutcome, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<PendingJoin>;
1295
+ recordJoinOutcome(expected: PendingJoin, outcome: JoinOutcome, ownerKey: string, signMessage: ControlSign): Promise<PendingJoin>;
1179
1296
  /**
1180
1297
  * Re-write the pending join `next` (request and outcome) under a fresh stamp, retiring the
1181
1298
  * live request incarnation `expectedStampId`: how a finished request is asked for again, and
@@ -1190,14 +1307,14 @@ export declare class ControlDatabase {
1190
1307
  * that committed but reported failure throws {@link PendingJoinChangedError} whose live join
1191
1308
  * carries the returned stamp, so a caller that re-reads finds its write.
1192
1309
  */
1193
- rewritePendingJoin(expectedStampId: string, next: PendingJoinFields, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<PendingJoin>;
1310
+ rewritePendingJoin(expectedStampId: string, next: PendingJoinFields, ownerKey: string, signMessage: ControlSign): Promise<PendingJoin>;
1194
1311
  /**
1195
1312
  * Owner-signed removal of one pending join, as {@link deleteJoinedStrand} removes its row:
1196
1313
  * `'remove'`-tagged digest over (Id, StampId), `Revocation` tombstone in the same transaction,
1197
1314
  * and the request's outcome row with it. A no-op (no throw, no tombstone) when there is no
1198
1315
  * request — `false` then, `true` when one was actually removed.
1199
1316
  */
1200
- deletePendingJoin(id: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1317
+ deletePendingJoin(id: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
1201
1318
  /**
1202
1319
  * Mint a fresh stamp for `request` and the owner signature `JoinRequest.AuthorizedInsert`
1203
1320
  * verifies, returning the row as it will be written and its insert.
@@ -1224,7 +1341,7 @@ export declare class ControlDatabase {
1224
1341
  * @param signMessage - Function that ed25519-signs the raw message bytes (no pre-hash)
1225
1342
  * with the owner's private key, returning a base64url signature
1226
1343
  */
1227
- insertValidationKey(key: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<void>;
1344
+ insertValidationKey(key: string, ownerKey: string, signMessage: ControlSign): Promise<void>;
1228
1345
  /**
1229
1346
  * Delete a validation key from the control database using an owner signature.
1230
1347
  *
@@ -1241,7 +1358,7 @@ export declare class ControlDatabase {
1241
1358
  * A no-op (no throw, no tombstone) when the row does not exist — `false` then, `true`
1242
1359
  * when a row was actually removed.
1243
1360
  */
1244
- deleteValidationKey(key: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1361
+ deleteValidationKey(key: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
1245
1362
  /**
1246
1363
  * Owner-vouched INSERT of one `CadrePeer` membership row, notifying the membership
1247
1364
  * listener once it has committed.
@@ -1286,7 +1403,7 @@ export declare class ControlDatabase {
1286
1403
  updatedAt: number;
1287
1404
  sig: string | null;
1288
1405
  profile: NodeProfile | null;
1289
- }, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1406
+ }, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
1290
1407
  /**
1291
1408
  * Owner "re-touch" of an existing `CadrePeer` membership row: bump `UpdatedAt` and
1292
1409
  * rewrite `VouchOwner`/`VouchSig` under the owner branch of `CadrePeer.AuthorizedUpdate`
@@ -1327,13 +1444,13 @@ export declare class ControlDatabase {
1327
1444
  *
1328
1445
  * NOTE: this rebinds VouchOwner to the CALLING owner's key, and the authorized-membership
1329
1446
  * predicate (`CadreNode.listAuthorizedMembers`) now judges rows by that column against each
1330
- * reader's node-local anchor. Benign today because the only caller — the write-while-alone
1331
- * drain — re-touches solely rows this node itself authored (`pendingPeerWrites`), so the
1332
- * voucher is rewritten to the key that already signed it. If a future path ever lets one
1333
- * owner re-touch a row a DIFFERENT owner vouched, the voucher silently flips: readers that
1334
- * anchor the original owner but not this one would drop a legitimate member. Such a path
1335
- * must re-vouch deliberately (or preserve the existing VouchOwner/VouchSig) rather than
1336
- * inherit this rebinding.
1447
+ * reader's node-local anchor. The two callers rebind on purpose: the write-while-alone drain
1448
+ * re-touches solely rows this node itself authored (`pendingPeerWrites`), so the voucher is
1449
+ * rewritten to the key that already signed it, and `CadreNode.removeOwner` moves rows off a
1450
+ * key it is about to remove. A new caller that re-touches a row a DIFFERENT owner vouched
1451
+ * flips the voucher silently: readers that anchor the original owner but not this one would
1452
+ * drop a legitimate member. It must re-vouch deliberately (or preserve the existing
1453
+ * VouchOwner/VouchSig) rather than inherit this rebinding.
1337
1454
  *
1338
1455
  * @param peerId - the membership row to re-touch.
1339
1456
  * @param updatedAt - the strictly-increasing freshness stamp to write.
@@ -1341,7 +1458,7 @@ export declare class ControlDatabase {
1341
1458
  * @param signMessage - ed25519-signs the raw message bytes with that owner's private key.
1342
1459
  * @returns `true` when the row was re-touched, `false` when no row exists (no notify).
1343
1460
  */
1344
- reauthorizeCadrePeer(peerId: string, updatedAt: number, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1461
+ reauthorizeCadrePeer(peerId: string, updatedAt: number, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
1345
1462
  /**
1346
1463
  * Owner-signed removal of one `CadrePeer` membership row, notifying the membership
1347
1464
  * listener once it has committed.
@@ -1362,7 +1479,7 @@ export declare class ControlDatabase {
1362
1479
  * A no-op (no throw, no tombstone) when the row does not exist — `false` then, `true`
1363
1480
  * when a row was actually removed.
1364
1481
  */
1365
- deleteCadrePeer(peerId: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1482
+ deleteCadrePeer(peerId: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
1366
1483
  /**
1367
1484
  * Owner-signed removal of one peer's `DeviceToken` row (logout / token invalidation).
1368
1485
  *
@@ -1376,7 +1493,7 @@ export declare class ControlDatabase {
1376
1493
  * A no-op (no throw, no tombstone) when the row does not exist — `false` then, `true`
1377
1494
  * when a row was actually removed.
1378
1495
  */
1379
- deleteDeviceToken(peerId: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
1496
+ deleteDeviceToken(peerId: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
1380
1497
  /**
1381
1498
  * Owner-signed delete of one guarded row plus the `Revocation` tombstone retiring its
1382
1499
  * stamp, in ONE transaction. The single body behind EVERY guarded delete —
@@ -1421,8 +1538,8 @@ export declare class ControlDatabase {
1421
1538
  private execGuardedRemoval;
1422
1539
  /**
1423
1540
  * File the owner-signed `Revocation` tombstone retiring one row incarnation — the second
1424
- * statement of every guarded removal ({@link execGuardedRemoval}) and the whole of a
1425
- * `CadreInvite` withdrawal ({@link withdrawCadreInvite}), whose row stays — or the ledger
1541
+ * statement of every guarded removal ({@link execGuardedRemoval}) and the whole of an
1542
+ * issued row's withdrawal ({@link withdrawIssuedRow}), whose row stays — or the ledger
1426
1543
  * marker ({@link openRevocationLedger}), which retires nothing. The caller supplies the
1427
1544
  * lock and, where one is needed, the transaction.
1428
1545
  *
@@ -1575,7 +1692,7 @@ export declare class ControlDatabase {
1575
1692
  *
1576
1693
  * Returns how many UPDATE statements ran (`rows.length` on success).
1577
1694
  */
1578
- reissueRevocations(rows: readonly RevocationRow[], reissuedAt: number, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<number>;
1695
+ reissueRevocations(rows: readonly RevocationRow[], reissuedAt: number, ownerKey: string, signMessage: ControlSign): Promise<number>;
1579
1696
  /**
1580
1697
  * Owner-signed filing of the singleton `Revocation` ledger marker
1581
1698
  * ({@link REVOCATION_LEDGER_MARKER}), the one row in that table that retires nothing.
@@ -1607,7 +1724,7 @@ export declare class ControlDatabase {
1607
1724
  * @returns `'opened'` when this call filed the marker, `'already-open'` when it was
1608
1725
  * already there.
1609
1726
  */
1610
- openRevocationLedger(ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<RevocationLedgerOpenResult>;
1727
+ openRevocationLedger(ownerKey: string, signMessage: ControlSign): Promise<RevocationLedgerOpenResult>;
1611
1728
  /**
1612
1729
  * Whether the ledger marker is present locally: a scan of the `'Revocation'` rows with the
1613
1730
  * stamp compared in TypeScript (see {@link openRevocationLedger} for why not a seek).
@@ -1672,8 +1789,9 @@ export declare class ControlDatabase {
1672
1789
  * Covers every write that can change who is a member or an owner: EVERY `CadrePeer`
1673
1790
  * writer, with one documented exception ({@link updateSelfPeerRecord}, which cannot
1674
1791
  * change the snapshot), the `OwnerKey` writers ({@link insertOwnerKeyVouched},
1675
- * {@link deleteOwnerKey}) and the invitation redemption ({@link redeemCadreInvite}),
1676
- * which seats both. That is what makes the party-membership snapshot refresh automatic
1792
+ * {@link deleteOwnerKey}), the invitation redemption ({@link redeemCadreInvite}), which
1793
+ * seats both, and the owner-grant redemption ({@link redeemOwnerGrant}). That is what
1794
+ * makes the party-membership snapshot refresh automatic
1677
1795
  * rather than a caller obligation. The name predates the `OwnerKey` writers. A
1678
1796
  * writer necessarily holds the target node's ControlDatabase (the `SeedBootstrapService`'s
1679
1797
  * event callbacks are NOT a viable seam: the temp service `CadreNode.applySeed`
@@ -1760,7 +1878,7 @@ export declare class ControlDatabase {
1760
1878
  * backoff never stalls the other local writers queued behind it. Safe because every
1761
1879
  * locked body is atomic and re-runnable (the contract on {@link withWriteLock}).
1762
1880
  *
1763
- * Also carries {@link loadSchema}'s distributed DDL, the one caller that runs BEFORE
1881
+ * Also carries {@link applyControlSchema}'s distributed DDL, the one caller that runs BEFORE
1764
1882
  * `initialized` is set — so it reaches this method directly rather than through
1765
1883
  * {@link execWrite}, whose {@link ensureInitialized} would reject it. The write queue is
1766
1884
  * necessarily empty at that point, so the lock is uncontended there; it is taken anyway
@@ -1863,7 +1981,7 @@ export declare class ControlDatabase {
1863
1981
  * @param options - Optional `expiresAtMs` (epoch ms), `totalUses`, `validationUrl`,
1864
1982
  * `strandId` (bind to a pre-existing host strand for provision-then-record)
1865
1983
  */
1866
- insertFormationInvite(token: string, sAppId: string, ownerKey: string, signMessage: (message: Uint8Array) => string, options?: {
1984
+ insertFormationInvite(token: string, sAppId: string, ownerKey: string, signMessage: ControlSign, options?: {
1867
1985
  expiresAtMs?: number;
1868
1986
  totalUses?: number;
1869
1987
  validationUrl?: string;
@@ -2091,31 +2209,53 @@ export declare class ControlDatabase {
2091
2209
  * private half to the invitee out of band. It is never stored here (`Strand.Invite` has the
2092
2210
  * same shape).
2093
2211
  *
2094
- * @param invite.key - the invitation public key (base64url ed25519), the row's primary key
2095
- * @param invite.peerId - the device the invitation is for, or null for whoever redeems first
2096
- * @param invite.grantsOwner - whether redemption also seats an `OwnerKey` row for the device
2097
- * @param invite.expiresAtMs - epoch ms after which it cannot be redeemed; omit for never
2098
- * @param invite.totalUses - how many redemptions it allows; omit for unlimited
2099
2212
  * @returns the row as stored, `expiresAt` in its canonical form
2100
2213
  */
2101
- insertCadreInvite(invite: {
2102
- key: string;
2103
- peerId?: string | null;
2104
- grantsOwner: boolean;
2105
- expiresAtMs?: number | null;
2106
- totalUses?: number | null;
2107
- }, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<CadreInviteRow>;
2214
+ insertCadreInvite(invite: CadreInviteFields, ownerKey: string, signMessage: ControlSign): Promise<CadreInviteRow>;
2215
+ /**
2216
+ * Build and owner-sign a `CadreInvite` row WITHOUT inserting it: the signing half of
2217
+ * {@link insertCadreInvite}, which `CadreNode.joinWithOwnerGrant` uses alone for the
2218
+ * invitation a node issues to itself, since a store that holds no `OwnerKey` row for the
2219
+ * issuer would refuse the local insert and the row is seated at a member from the bundle
2220
+ * instead. `ExpiresAt` is canonicalized by this database's engine, which is a format, not a
2221
+ * store, so the row reads the same wherever it is seated.
2222
+ */
2223
+ signCadreInvite(invite: CadreInviteFields, ownerKey: string, signMessage: ControlSign): Promise<CadreInviteRow>;
2108
2224
  /**
2109
2225
  * Seat an owner-signed `CadreInvite` row received inside an invitation bundle, under its own
2110
2226
  * stored `IssuerKey`/`IssuerSig` as the write context — insert-if-absent, so a member that
2111
- * already holds the row (by replication, or from an earlier redemption attempt) is a no-op.
2227
+ * already holds the row (by replication, or from an earlier redemption attempt) is a no-op,
2228
+ * and so is one whose insert the cohort refuses because a member there holds it.
2112
2229
  *
2113
2230
  * @returns `true` when this call inserted the row, `false` when it was already present.
2114
2231
  * @throws {CadreInviteIssuerUnknownError} when `CadreInvite.AuthorizedInsert` refuses it: the
2115
2232
  * issuer is not an owner on this node (not replicated yet, or removed), or the signature does
2116
- * not verify. Every other refusal (a withdrawn stamp, `NotRevoked`) propagates as is.
2233
+ * not verify. {@link IssuedRowNotHereYetError} when the cohort holds the row and this node
2234
+ * cannot read it yet. Every other refusal (a withdrawn stamp, `NotRevoked`) propagates as is.
2117
2235
  */
2118
2236
  seatCadreInvite(row: CadreInviteRow): Promise<boolean>;
2237
+ /**
2238
+ * Insert-if-absent of an issued row from a bundle's copy, under the write lock: the body of
2239
+ * {@link seatCadreInvite} and {@link seatOwnerGrant}. `exec` runs the bare insert under the
2240
+ * row's own stored issuer pair.
2241
+ *
2242
+ * @returns `true` when this call inserted the row, `false` when it was already present —
2243
+ * here, or at the cohort member that refused the insert for holding it
2244
+ * ({@link adoptCohortHeldRow}).
2245
+ * @throws `refuse(cause)` when the table's `AuthorizedInsert` refuses the row;
2246
+ * {@link IssuedRowNotHereYetError} when the cohort holds the row and this node still cannot
2247
+ * read it; every other refusal (a withdrawn stamp, `NotRevoked`) propagates as is.
2248
+ */
2249
+ private seatIssuedRow;
2250
+ /**
2251
+ * The seat's insert was refused because a cohort member already holds a row under `key`, which
2252
+ * this node's guard read missed because its store lags the cohort. A bundle's copy is verbatim
2253
+ * and every column is under the issuer's signature, so the held row is this one: it counts as
2254
+ * seated once a refreshing read (unlocked, so retried as any read) finds it here.
2255
+ *
2256
+ * @throws {IssuedRowNotHereYetError} when the read still does not find it.
2257
+ */
2258
+ private adoptCohortHeldRow;
2119
2259
  /** Bare `CadreInvite` insert shared by {@link insertCadreInvite} and {@link seatCadreInvite}; the caller holds the lock. */
2120
2260
  private execCadreInviteInsert;
2121
2261
  /**
@@ -2149,6 +2289,8 @@ export declare class ControlDatabase {
2149
2289
  * `retry: false` is passed by {@link assertCadreInviteSeatRemains}, inside the locked body.
2150
2290
  */
2151
2291
  countCadreInviteUsage(key: string, retry?: boolean): Promise<number>;
2292
+ /** The one-row count read behind {@link countCadreInviteUsage} and {@link countOwnerGrantUsage}. */
2293
+ private countUsageRows;
2152
2294
  /**
2153
2295
  * Is any cadre invitation still redeemable — not withdrawn, unexpired, with uses below its
2154
2296
  * `TotalUses`, and issued by a current owner? The conditions `CadreInviteUsage.Authorized`
@@ -2172,6 +2314,11 @@ export declare class ControlDatabase {
2172
2314
  * what enforce every condition.
2173
2315
  */
2174
2316
  isCadreInviteLive(invite: CadreInviteRow, nowMs?: number): Promise<boolean>;
2317
+ /**
2318
+ * The body of {@link isCadreInviteLive} and {@link isOwnerGrantLive}: is `row` (the one held,
2319
+ * or a verified bundle copy; null for neither) still open and under its use cap?
2320
+ */
2321
+ private issuedRowLive;
2175
2322
  /**
2176
2323
  * Every `CadreInvite` row this node holds with its standing: how many redemptions are
2177
2324
  * recorded, whether it is withdrawn, and whether it is still redeemable — the same
@@ -2180,6 +2327,11 @@ export declare class ControlDatabase {
2180
2327
  * occasional read, not a gate's.
2181
2328
  */
2182
2329
  listCadreInviteStatuses(nowMs?: number): Promise<CadreInviteStatus[]>;
2330
+ /**
2331
+ * The body of {@link listCadreInviteStatuses} and {@link listOwnerGrantStatuses}: each of
2332
+ * `rows` with its withdrawal, its recorded uses and whether it is still redeemable.
2333
+ */
2334
+ private issuedRowStandings;
2183
2335
  /**
2184
2336
  * Owner-signed WITHDRAWAL of a cadre invitation: file the `Revocation` tombstone retiring its
2185
2337
  * stamp, with the row kept. The row stays because every device the invitation admitted
@@ -2197,24 +2349,29 @@ export declare class ControlDatabase {
2197
2349
  * Fires the guarded-delete listener like a removal does, so a withdrawal committed while the
2198
2350
  * node was alone is queued for re-issue on cohort growth ({@link CadreNode.noteGuardedDelete}).
2199
2351
  */
2200
- withdrawCadreInvite(key: string, ownerKey: string, signMessage: (message: Uint8Array) => string): Promise<boolean>;
2352
+ withdrawCadreInvite(key: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
2201
2353
  /**
2202
- * The withdrawal tombstones for the `CadreInvite` rows among `keys` that are held here and
2203
- * not yet withdrawn, signed and ready for {@link execTombstone}: the body
2204
- * {@link withdrawCadreInvite} files one of and {@link removeMachine} files several of in one
2354
+ * The body of {@link withdrawCadreInvite} and {@link withdrawOwnerGrant}: under the write
2355
+ * lock, file the owner-signed tombstone retiring the row's stamp with the row kept, and fire
2356
+ * the guarded-delete listener for it (so a withdrawal made while alone is re-issued).
2357
+ */
2358
+ private withdrawIssuedRow;
2359
+ /**
2360
+ * The withdrawal tombstones for the `table` rows among `keys` that are held here and not
2361
+ * yet withdrawn, signed and ready for {@link execTombstone}: the body
2362
+ * {@link withdrawIssuedRow} files one of and {@link removeMachine} files several of in one
2205
2363
  * transaction. Inside the locked body (every read `retry: false`); an absent or already
2206
2364
  * withdrawn key is logged and left out.
2207
2365
  */
2208
- private signCadreInviteWithdrawals;
2366
+ private signWithdrawals;
2209
2367
  /**
2210
- * Redeem a seated `CadreInvite` on the owner's behalf: admit `peerId` as a cadre member and,
2211
- * when the invitation grants ownership, as an owner — with NO owner signature. One
2212
- * transaction writes the `CadrePeer` row (consent branch of `AuthorizedInsert`), the
2213
- * `OwnerKey` row when granted (consent branch of `Authorized`), and last the
2214
- * `CadreInviteUsage` row that authorizes both; the usage row's own checks (`PeerExists`,
2215
- * `OwnerExists`) read the rows back, so the three are mutually bound to this exact
2216
- * redemption and none can be held in reserve. The invitation must already be committed
2217
- * ({@link seatCadreInvite}): every consent branch reads `committed.CadreInvite`.
2368
+ * Redeem a seated `CadreInvite` on the owner's behalf: admit `peerId` as a cadre member —
2369
+ * with NO owner signature, and as a machine only (ownership is {@link redeemOwnerGrant}'s).
2370
+ * One transaction writes the `CadrePeer` row (consent branch of `AuthorizedInsert`) and then
2371
+ * the `CadreInviteUsage` row that authorizes it; the usage row's own check (`PeerExists`)
2372
+ * reads the peer row back, so the two are mutually bound to this exact redemption and
2373
+ * neither can be held in reserve. The invitation must already be committed
2374
+ * ({@link seatCadreInvite}): the consent branch reads `committed.CadreInvite`.
2218
2375
  *
2219
2376
  * The two signatures are the holder's and the device's, minted on the device over
2220
2377
  * {@link cadreInviteRedeemMessage} / {@link cadreInviteConsentMessage}; this machine only
@@ -2223,18 +2380,17 @@ export declare class ControlDatabase {
2223
2380
  * (`verifyInvitationAdmission`) — a mismatched pair is written and then never trusted.
2224
2381
  *
2225
2382
  * Idempotent for a retry after a dropped reply: when the device's row is already present
2226
- * (and its `OwnerKey` row too, when granted) nothing is written and no seat is spent —
2227
- * `alreadyMember: true`. A present row whose `OwnerKey` row is missing under an
2228
- * owner-granting invitation is completed: the usage row then names the existing peer stamp.
2229
- * The seat check ({@link assertCadreInviteSeatRemains}) runs after that, so the retry of a
2230
- * single-use redemption is answered `alreadyMember` rather than refused as exhausted.
2383
+ * nothing is written and no seat is spent — `alreadyMember: true`. The seat check
2384
+ * ({@link assertCadreInviteSeatRemains}) runs after that, so the retry of a single-use
2385
+ * redemption is answered `alreadyMember` rather than refused as exhausted.
2231
2386
  *
2232
2387
  * Through {@link mutateCadrePeer}, so the membership hub is notified; inside the locked body
2233
2388
  * every read passes `retry: false` and every stamp is minted per attempt, so a
2234
2389
  * {@link lockedWithRetry} re-run starts clean.
2235
2390
  *
2236
2391
  * @throws {InvitationExhaustedError} when the invitation's seats are all spent (by name,
2237
- * ahead of the schema's generic `Authorized` refusal — see {@link assertSeatRemains}).
2392
+ * ahead of the schema's generic `Authorized` refusal — see {@link assertSeatRemains});
2393
+ * {@link IssuedRowNotHereYetError} when no `CadreInvite` row is readable here.
2238
2394
  */
2239
2395
  redeemCadreInvite(params: {
2240
2396
  inviteKey: string;
@@ -2251,13 +2407,6 @@ export declare class ControlDatabase {
2251
2407
  peerSig: string;
2252
2408
  nowMs?: number;
2253
2409
  }): Promise<CadreInviteRedemptionResult>;
2254
- /**
2255
- * The rows a redemption would seat that are already present: the device's `CadrePeer`
2256
- * stamp, and — only when the invitation grants ownership — its `OwnerKey` stamp. Raw stamp
2257
- * reads ({@link queryStampId}), so a physically present row counts; inside the locked
2258
- * body, so never retried.
2259
- */
2260
- private readSeatedAdmission;
2261
2410
  /**
2262
2411
  * Refuse a redemption that would consume a seat the cadre invitation does not have, by
2263
2412
  * name ({@link InvitationExhaustedError}) — the cadre-invitation twin of
@@ -2266,6 +2415,137 @@ export declare class ControlDatabase {
2266
2415
  * committed count inside the locked body, so on a same-node race the loser is refused here.
2267
2416
  */
2268
2417
  private assertCadreInviteSeatRemains;
2418
+ /** The body of {@link assertCadreInviteSeatRemains} and {@link assertOwnerGrantSeatRemains}; `countUsage` reads inside the locked body. */
2419
+ private assertIssuedSeatRemains;
2420
+ /**
2421
+ * Mint and insert an owner-signed `OwnerGrant`: a grant of ownership to whoever holds the
2422
+ * grant's private half, redeemable through any member machine ({@link redeemOwnerGrant})
2423
+ * to seat an owner key the holder generates or already holds. No machine is admitted and no
2424
+ * peer id is named. The grant twin of {@link insertCadreInvite}: the owner signs the whole
2425
+ * row ({@link ownerGrantAddMessage}), `ExpiresAt` through {@link canonicalDatetime}, and
2426
+ * the returned row is what the grant bundle carries, re-inserted verbatim by
2427
+ * {@link seatOwnerGrant} on a member that has not received it by replication.
2428
+ *
2429
+ * @param grant.key - the grant's ed25519 PUBLIC key (base64url), the row's primary key; the
2430
+ * caller minted the pair and hands the private half to the holder out of band
2431
+ * @param grant.label - the label every key this grant seats will carry
2432
+ * ({@link ownerKeyLabel}'s rule: trimmed, at most 64 characters, blank meaning none); the
2433
+ * issuer's choice, which the holder cannot change
2434
+ * @param grant.expiresAtMs - epoch ms after which it cannot be redeemed; omit or null for
2435
+ * never. The short default lifetime is the node API's to apply, not this writer's.
2436
+ * @param grant.totalUses - how many redemptions it allows; omit for unlimited
2437
+ * @returns the row as stored, `expiresAt` in its canonical form
2438
+ */
2439
+ insertOwnerGrant(grant: {
2440
+ key: string;
2441
+ label?: string | null;
2442
+ expiresAtMs?: number | null;
2443
+ totalUses?: number | null;
2444
+ }, ownerKey: string, signMessage: ControlSign): Promise<OwnerGrantRow>;
2445
+ /**
2446
+ * Seat an owner-signed `OwnerGrant` row received inside a grant bundle, under its own stored
2447
+ * `IssuerKey`/`IssuerSig` as the write context — insert-if-absent, as {@link seatCadreInvite}.
2448
+ *
2449
+ * @returns `true` when this call inserted the row, `false` when it was already present.
2450
+ * @throws {OwnerGrantIssuerUnknownError} when `OwnerGrant.AuthorizedInsert` refuses it: the
2451
+ * issuer is not an owner on this node (not replicated yet, or removed), or the signature does
2452
+ * not verify. {@link IssuedRowNotHereYetError} when the cohort holds the row and this node
2453
+ * cannot read it yet. Every other refusal (a withdrawn stamp, `NotRevoked`) propagates as is.
2454
+ */
2455
+ seatOwnerGrant(row: OwnerGrantRow): Promise<boolean>;
2456
+ /** Bare `OwnerGrant` insert shared by {@link insertOwnerGrant} and {@link seatOwnerGrant}; the caller holds the lock. */
2457
+ private execOwnerGrantInsert;
2458
+ /**
2459
+ * Read one `OwnerGrant` row by key, or null when absent. Withdrawn rows are returned too,
2460
+ * as {@link queryCadreInvite} returns them: the row is the holder's proof and never goes
2461
+ * away. `retry: false` is passed by {@link redeemOwnerGrant}, inside its locked body.
2462
+ */
2463
+ queryOwnerGrant(key: string, retry?: boolean): Promise<OwnerGrantRow | null>;
2464
+ /**
2465
+ * Every `OwnerGrant` row this node holds, withdrawn and expired ones included. Read by the
2466
+ * anchor sync when at least one `OwnerKey` row is consent-seated (`CadreNode.loadOwnerGrantChain`);
2467
+ * `retry: false` is forwarded from the membership-gate refresh for {@link queryCadrePeers}' reason.
2468
+ */
2469
+ queryOwnerGrants(retry?: boolean): Promise<OwnerGrantRow[]>;
2470
+ /** Every `OwnerGrantUsage` row this node holds — the redemption records a grant-seated `OwnerKey` row is verified against. Same `retry` contract as {@link queryOwnerGrants}. */
2471
+ queryOwnerGrantUsages(retry?: boolean): Promise<OwnerGrantUsageRow[]>;
2472
+ /**
2473
+ * How many times an owner grant has been redeemed (its `OwnerGrantUsage` rows), served
2474
+ * through the `OwnerGrantUsageByGrant` index. A permissive PRE-check like
2475
+ * {@link countCadreInviteUsage}: the cap is `OwnerGrantUsage.Authorized`'s own committed count.
2476
+ */
2477
+ countOwnerGrantUsage(key: string, retry?: boolean): Promise<number>;
2478
+ /**
2479
+ * Is this one owner grant still redeemable here — not withdrawn, unexpired, uses left, issuer
2480
+ * still an owner — judged as {@link isCadreInviteLive} judges an invitation: by the row this
2481
+ * node holds under `grant.key`, else by `grant` itself (a bundle's copy, not seated yet)
2482
+ * when its issuer signature verifies. A permissive pre-check; {@link seatOwnerGrant} and the
2483
+ * redemption's own write enforce every condition.
2484
+ */
2485
+ isOwnerGrantLive(grant: OwnerGrantRow, nowMs?: number): Promise<boolean>;
2486
+ /**
2487
+ * Every `OwnerGrant` row this node holds with its standing — recorded redemptions, withdrawal,
2488
+ * and whether it is still redeemable — for an owner's listing, as {@link listCadreInviteStatuses}.
2489
+ */
2490
+ listOwnerGrantStatuses(nowMs?: number): Promise<OwnerGrantStatus[]>;
2491
+ /**
2492
+ * Owner-signed WITHDRAWAL of an owner grant: file the `Revocation` tombstone retiring its
2493
+ * stamp, with the row kept, exactly as {@link withdrawCadreInvite} withdraws an invitation
2494
+ * and for the same reason (every key the grant seated proves itself through the row;
2495
+ * `OwnerGrant.NoDelete`, `Revocation.RowIsGone`). Afterwards `OwnerGrantUsage.Authorized`
2496
+ * refuses every further redemption and `OwnerGrant.NotRevoked` refuses re-seating the
2497
+ * holder's copy on any node holding the tombstone. Keys already seated stay owners;
2498
+ * removing one is {@link deleteOwnerKey}.
2499
+ *
2500
+ * Idempotent: `false` (nothing written) when the row is absent here or already withdrawn,
2501
+ * `true` when this call filed the tombstone. Fires the guarded-delete listener, so a
2502
+ * withdrawal committed while alone is re-issued on cohort growth.
2503
+ */
2504
+ withdrawOwnerGrant(key: string, ownerKey: string, signMessage: ControlSign): Promise<boolean>;
2505
+ /**
2506
+ * Redeem a seated `OwnerGrant` on the issuer's behalf: seat `seatedKey` as an owner with NO
2507
+ * owner signature. One transaction writes the `OwnerKey` row (the grant branch of
2508
+ * `Authorized`: `VouchOwner` the issuer, `VouchSig` null, `VouchUsage` the usage stamp,
2509
+ * `Label` the grant's) and then the `OwnerGrantUsage` row that authorizes it; the usage
2510
+ * row's `SeatedExists` reads the owner row back, so the two are bound to this exact
2511
+ * redemption and neither can be held in reserve. The grant must already be committed
2512
+ * ({@link seatOwnerGrant}): both consent reads are of `committed.OwnerGrant`. No machine is
2513
+ * admitted and no peer id is named.
2514
+ *
2515
+ * The two signatures are minted where the private keys are (the grant's and the seated
2516
+ * key's), over {@link ownerGrantRedeemMessage} / {@link ownerGrantConsentMessage}; this
2517
+ * machine only verifies them, by writing the row.
2518
+ *
2519
+ * Idempotent, and a no-op for a key that is already an owner: when `seatedKey` has a live
2520
+ * `OwnerKey` row here nothing is written and no use is spent — `alreadyOwner: true` with
2521
+ * that row's stamp. The seat check ({@link assertOwnerGrantSeatRemains}) runs after that, so
2522
+ * the retry of a single-use redemption is answered `alreadyOwner` rather than refused as
2523
+ * exhausted. A key removed earlier is seated afresh under a new stamp (`NotRevoked` passes),
2524
+ * except on a machine that still holds its retired row, which refuses by name
2525
+ * ({@link liveOwnerKeyStamp}).
2526
+ *
2527
+ * Through {@link mutateCadrePeer}, so the membership hub is notified and the anchors follow;
2528
+ * inside the locked body every read passes `retry: false` and the stamp is minted per
2529
+ * attempt, so a {@link lockedWithRetry} re-run starts clean.
2530
+ *
2531
+ * @throws {InvitationExhaustedError} when the grant's uses are all spent (by name, ahead of
2532
+ * the schema's generic `Authorized` refusal); its `token` is the grant key.
2533
+ * {@link IssuedRowNotHereYetError} when no `OwnerGrant` row is readable here.
2534
+ */
2535
+ redeemOwnerGrant(params: {
2536
+ grantKey: string;
2537
+ /** The owner key to seat (base64url ed25519): the holder generated it, or already holds it. */
2538
+ seatedKey: string;
2539
+ /** Single-use nonce minted by the holder; both signatures cover it. */
2540
+ usageStampId: string;
2541
+ /** The holder's signature with the grant private key over the `'redeem'` digest. */
2542
+ grantSig: string;
2543
+ /** The seated key's signature over the `'consent'` digest. */
2544
+ ownerSig: string;
2545
+ nowMs?: number;
2546
+ }): Promise<OwnerGrantRedemptionResult>;
2547
+ /** {@link assertCadreInviteSeatRemains} for an owner grant: refuse an exhausted grant by name, ahead of the write. */
2548
+ private assertOwnerGrantSeatRemains;
2269
2549
  /**
2270
2550
  * Close the database and cleanup resources.
2271
2551
  *