@serfab/cadre-core 0.10.0 → 0.11.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 (105) hide show
  1. package/README.md +255 -255
  2. package/dist/cadre-node.d.ts +203 -28
  3. package/dist/cadre-node.js +445 -84
  4. package/dist/cadre-node.js.map +1 -1
  5. package/dist/control-cohort.d.ts +26 -0
  6. package/dist/control-cohort.js +26 -0
  7. package/dist/control-cohort.js.map +1 -1
  8. package/dist/control-database.d.ts +82 -117
  9. package/dist/control-database.js +217 -295
  10. package/dist/control-database.js.map +1 -1
  11. package/dist/control-formation-recorder.d.ts +8 -7
  12. package/dist/control-formation-recorder.js +8 -7
  13. package/dist/control-formation-recorder.js.map +1 -1
  14. package/dist/control-schema.d.ts +1 -1
  15. package/dist/control-schema.js +789 -774
  16. package/dist/control-schema.js.map +1 -1
  17. package/dist/control-write-retry.d.ts +15 -5
  18. package/dist/control-write-retry.js +19 -18
  19. package/dist/control-write-retry.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/peer-addr-book.d.ts +92 -0
  24. package/dist/peer-addr-book.js +220 -0
  25. package/dist/peer-addr-book.js.map +1 -0
  26. package/dist/seed-bootstrap.d.ts +7 -0
  27. package/dist/seed-bootstrap.js +15 -13
  28. package/dist/seed-bootstrap.js.map +1 -1
  29. package/dist/strand-cohort.d.ts +4 -37
  30. package/dist/strand-cohort.js +4 -15
  31. package/dist/strand-cohort.js.map +1 -1
  32. package/dist/strand-database.d.ts +11 -16
  33. package/dist/strand-database.js +19 -7
  34. package/dist/strand-database.js.map +1 -1
  35. package/dist/strand-formation-manager.d.ts +8 -8
  36. package/dist/strand-formation-manager.js +15 -18
  37. package/dist/strand-formation-manager.js.map +1 -1
  38. package/dist/strand-instance-manager.d.ts +52 -21
  39. package/dist/strand-instance-manager.js +100 -34
  40. package/dist/strand-instance-manager.js.map +1 -1
  41. package/dist/strand-membership-writer.d.ts +6 -4
  42. package/dist/strand-membership-writer.js +38 -36
  43. package/dist/strand-membership-writer.js.map +1 -1
  44. package/dist/types.d.ts +39 -31
  45. package/dist/types.js.map +1 -1
  46. package/package.json +8 -7
  47. package/src/arachnode-stub.ts +159 -159
  48. package/src/bootstrap-peer-store-file.ts +44 -44
  49. package/src/bootstrap-peer-store.ts +172 -172
  50. package/src/cadre-node.ts +5158 -4771
  51. package/src/canonical-json.ts +30 -30
  52. package/src/control-authorization.ts +114 -114
  53. package/src/control-cohort.ts +130 -103
  54. package/src/control-database.ts +2406 -2506
  55. package/src/control-formation-recorder.ts +305 -304
  56. package/src/control-schema.ts +801 -786
  57. package/src/control-stream.ts +205 -205
  58. package/src/control-write-retry.ts +483 -473
  59. package/src/delegate-admission.ts +302 -302
  60. package/src/device-token.ts +94 -94
  61. package/src/diagnostics/connection-path.ts +277 -277
  62. package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
  63. package/src/ed25519-key.ts +134 -134
  64. package/src/enrollment.ts +199 -199
  65. package/src/file-durable-slot.ts +61 -61
  66. package/src/formation-approval.ts +604 -604
  67. package/src/fs-atomic.ts +107 -107
  68. package/src/hibernation-manager.ts +437 -437
  69. package/src/identity-key.ts +109 -109
  70. package/src/index.ts +1 -0
  71. package/src/key-store-file.ts +132 -132
  72. package/src/key-store.ts +99 -99
  73. package/src/membership-connection-gater.ts +204 -204
  74. package/src/node-local-snapshot.ts +260 -260
  75. package/src/peer-addr-book.ts +250 -0
  76. package/src/peer-authorization.ts +259 -259
  77. package/src/peer-record.ts +164 -164
  78. package/src/push-credentials.ts +84 -84
  79. package/src/push-node.ts +64 -64
  80. package/src/push-notifier-apns.ts +247 -247
  81. package/src/push-notifier-fcm.ts +220 -220
  82. package/src/push-notifier-shared.ts +28 -28
  83. package/src/push-notifier.ts +52 -52
  84. package/src/relay-addrs.ts +111 -111
  85. package/src/relay-reservation.ts +823 -823
  86. package/src/seed-bootstrap.ts +15 -15
  87. package/src/seed-trust-policy.ts +140 -140
  88. package/src/strand-addr-protocol.ts +441 -441
  89. package/src/strand-backfill.ts +366 -366
  90. package/src/strand-cohort.ts +38 -77
  91. package/src/strand-database.ts +202 -204
  92. package/src/strand-formation-manager.ts +491 -494
  93. package/src/strand-formation-protocol.ts +692 -692
  94. package/src/strand-instance-manager.ts +108 -50
  95. package/src/strand-member-key.ts +39 -39
  96. package/src/strand-member-registry.ts +182 -182
  97. package/src/strand-membership-writer.ts +1432 -1430
  98. package/src/strand-solicitation.ts +472 -472
  99. package/src/strand-transport-key.ts +58 -58
  100. package/src/strand-wake-payload.ts +32 -32
  101. package/src/strand-wake-protocol.ts +314 -314
  102. package/src/strand-watcher.ts +314 -314
  103. package/src/trusted-owner-store-file.ts +41 -41
  104. package/src/trusted-owner-store.ts +171 -171
  105. package/src/types.ts +1309 -1303
@@ -49,43 +49,27 @@ export declare class FormationAbortedError extends Error {
49
49
  });
50
50
  }
51
51
  /**
52
- * A redemption could not be given a `UseNumber` within the invite's seat budget: the use
53
- * number the writer would have to take is past `FormationInvite.TotalUses`, so the
54
- * invitation is spent.
52
+ * The invitation's seat budget is already spent: the count of recorded `FormationUsage` rows
53
+ * for the token has reached `FormationInvite.TotalUses`, so this redemption cannot be given a
54
+ * seat.
55
55
  *
56
- * Raised inside {@link ControlDatabase.redeemInvitation} / {@link ControlDatabase.recordFormationUsage}
57
- * on EVERY attempt, including the first: two redemptions of one token racing on the SAME node
58
- * serialize behind the local write queue, so the loser's first attempt already takes a use
59
- * number past the seat budget — `StrandFormationManager.validateToken`'s `isTokenUsed` check
60
- * cannot catch this, since it ran before either redemption wrote anything. Without this the
61
- * write would fail as a generic `CHECK constraint failed: Authorized`, which the manager
62
- * reports as a retryable `Formation conflict, retry` — telling the joiner to retry something
63
- * that can never succeed.
56
+ * Raised off the committed-count check ({@link ControlDatabase.assertSeatRemains}) ahead of
57
+ * the write, inside {@link ControlDatabase.redeemInvitation} /
58
+ * {@link ControlDatabase.recordFormationUsage} — and off nothing else. It exists because
59
+ * without it the refusal surfaces as a generic `CHECK constraint failed: Authorized` from the
60
+ * schema's own count-based cap clause, which the manager reports as a retryable
61
+ * `Formation conflict, retry` — telling the joiner to retry something that can never succeed.
62
+ * Two redemptions of one token racing on the SAME node serialize behind the local write
63
+ * queue, so the loser reads the winner's committed row and is refused here by name —
64
+ * `StrandFormationManager.validateToken`'s `isTokenUsed` check cannot catch that, since it
65
+ * ran before either redemption wrote anything.
64
66
  */
65
67
  export declare class InvitationExhaustedError extends Error {
66
68
  readonly token: string;
67
- readonly useNumber: number;
69
+ readonly usesRecorded: number;
68
70
  readonly totalUses: number;
69
- constructor(token: string, useNumber: number, totalUses: number);
71
+ constructor(token: string, usesRecorded: number, totalUses: number);
70
72
  }
71
- /**
72
- * Did this write fail purely because another writer took the `UseNumber` it picked — i.e. is
73
- * re-presenting the SAME approval under the next free use number the right response?
74
- *
75
- * Classifies by MESSAGE, walking the `cause` chain with Quereus' own {@link unwrapError}:
76
- * neither surface can be recognised by type. The deferred `Monotonic` failure is thrown as a
77
- * bare `QuereusError`, not a `ConstraintError` (see
78
- * `quereus/src/runtime/deferred-constraint-queue.ts`), so an `instanceof ConstraintError` gate
79
- * would silently drop the branch this retry exists for. Messages also carry an
80
- * ` (at line N, column M)` suffix and arrive wrapped when the write ran inside a transaction,
81
- * so the match is a substring test rather than an equality one.
82
- *
83
- * NOTE: this is the one piece of the retry that depends on engine error TEXT. Its own test
84
- * (`control-formation-use-number-retry.spec.ts`) produces all three messages from the real
85
- * engine rather than from string literals, so a reworded storage-layer error fails that spec
86
- * instead of silently disabling the retry.
87
- */
88
- export declare function isLostUseNumberRace(error: unknown): boolean;
89
73
  /**
90
74
  * What a recorded `FormationUsage` row came out as.
91
75
  *
@@ -94,9 +78,7 @@ export declare function isLostUseNumberRace(error: unknown): boolean;
94
78
  * `FormationUsage.Authorized` rejects the row (see {@link formationVouchMessage}).
95
79
  */
96
80
  export interface FormationUsageResult {
97
- /** The `UseNumber` assigned to this redemption (1-based per token). */
98
- useNumber: number;
99
- /** The single-use nonce written to `UsageStampId` — the caller's, or a freshly minted one. */
81
+ /** The single-use nonce written to `UsageStampId` (the row's primary key) — the caller's, or a freshly minted one. */
100
82
  usageStampId: string;
101
83
  }
102
84
  /**
@@ -314,6 +296,14 @@ export declare class ControlDatabase {
314
296
  * membership-gate refresh path; control tables are a handful of rows today, so this
315
297
  * is cheap — if they ever grow, fold the exclusion into one statement or cache the
316
298
  * retired-stamp set.
299
+ *
300
+ * NOTE: this filter is HAND-MIRRORED by the gate suites' fake control databases
301
+ * (`test/membership-gate-helpers.ts` and `test/cadre-node-authorized-surface.spec.ts`),
302
+ * which feed the node rows directly. Nothing enforces that the copies stay true, so a
303
+ * change to the filter's SHAPE here (what it keys on, which table it consults) must be
304
+ * carried into both, or those suites keep passing against a contract the database no
305
+ * longer honours. Fine while the filter is one `Set.has` on `StampId`; if it grows a
306
+ * second dimension, give the fakes a shared fixture or a contract test instead.
317
307
  */
318
308
  queryCadrePeers(): Promise<CadrePeerRow[]>;
319
309
  /**
@@ -937,10 +927,8 @@ export declare class ControlDatabase {
937
927
  *
938
928
  * The retry wraps the LOCK, it does not live inside it: each attempt takes and
939
929
  * releases the lock, and the backoff sleeps with NO lock held, so a write parked in
940
- * backoff never stalls the other local writers queued behind it. Same reason
941
- * {@link withUseNumberRetry} re-takes the lock per attempt — see the note on
942
- * `USE_NUMBER_ATTEMPTS`. Safe because every locked body is atomic and re-runnable
943
- * (the contract on {@link withWriteLock}).
930
+ * backoff never stalls the other local writers queued behind it. Safe because every
931
+ * locked body is atomic and re-runnable (the contract on {@link withWriteLock}).
944
932
  *
945
933
  * Also carries {@link loadSchema}'s distributed DDL, the one caller that runs BEFORE
946
934
  * `initialized` is set — so it reaches this method directly rather than through
@@ -949,6 +937,11 @@ export declare class ControlDatabase {
949
937
  * rather than carving out a lockless path. It is also the ONLY caller that passes a
950
938
  * `policy` — everything else takes the default, whose classifier and attempt count are
951
939
  * unchanged by that call site existing.
940
+ *
941
+ * `label` names the operation in this loop's debug lines and nothing else
942
+ * ({@link ControlWriteRetryOptions.label}). Every call site in this class supplies one:
943
+ * several writes retry CONCURRENTLY in a real party, so an unlabelled line cannot be
944
+ * attributed to a write — keep new call sites labelled.
952
945
  */
953
946
  private lockedWithRetry;
954
947
  /**
@@ -960,8 +953,11 @@ export declare class ControlDatabase {
960
953
  * Public so `SeedBootstrapService`'s direct `CadrePeer`/`DeviceToken` SQL writes go
961
954
  * through the same seam. Must NOT be called from inside an already-locked body (the
962
955
  * lock is not re-entrant); such a caller uses a bare `getDatabase().exec` instead.
956
+ *
957
+ * `label` names the write in the retry loop's debug lines (see {@link lockedWithRetry});
958
+ * optional only because it is log-only, but every caller in this repo passes one.
963
959
  */
964
- execWrite(sql: string, params?: SqlParameters): Promise<void>;
960
+ execWrite(sql: string, params?: SqlParameters, label?: string): Promise<void>;
965
961
  /**
966
962
  * Throw unless the database sits at a committed boundary — `getAutocommit()` is false
967
963
  * exactly while a transaction is open.
@@ -1062,13 +1058,12 @@ export declare class ControlDatabase {
1062
1058
  * takes an owner re-seat ({@link insertStrand}) plus a bound invite, never another
1063
1059
  * redemption.
1064
1060
  *
1065
- * `UseNumber` is computed as `max(UseNumber)+1` for the token (the `Monotonic`
1066
- * constraint), inside the write lock and re-read per attempt by
1067
- * {@link withUseNumberRetry} — so concurrent redemptions of one token no longer need
1068
- * the caller to serialise them, and a use number lost to another writer is retried here
1069
- * with the caller's parameters passed through UNCHANGED. That is what makes the retry
1070
- * safe: the approval is bound to the nonce, not to the use number, so re-presenting it
1071
- * cannot drift, and no second trip through the approval hook is possible from in here.
1061
+ * The usage row is keyed by the joiner's own nonce (`UsageStampId` is the primary key), so
1062
+ * concurrent redemptions of one token never contend for a shared row key — there is no
1063
+ * lost race to retry and no second trip through the approval hook. The invite's seat
1064
+ * budget is checked by COUNT ({@link assertSeatRemains}) inside the write lock, ahead of
1065
+ * the write, so a spent invite is refused by name (`InvitationExhaustedError`) instead of
1066
+ * as the schema cap clause's generic `Authorized` CHECK failure.
1072
1067
  */
1073
1068
  redeemInvitation(params: {
1074
1069
  token: string;
@@ -1091,9 +1086,9 @@ export declare class ControlDatabase {
1091
1086
  signal?: AbortSignal;
1092
1087
  /**
1093
1088
  * The invite's seat budget, when the caller already has it in hand (e.g. it already read
1094
- * the `FormationInvite` row to get here). Passed through to {@link withUseNumberRetry} so
1095
- * every attempt can check the seat budget without an extra read. `undefined` falls back to
1096
- * a fresh {@link queryFormationInvite} read; `null` means "no invite row" / unlimited.
1089
+ * the `FormationInvite` row to get here). Passed through to {@link assertSeatRemains} so
1090
+ * the seat check needs no extra read. `undefined` falls back to a fresh
1091
+ * {@link queryFormationInvite} read; `null` means "no invite row" / unlimited.
1097
1092
  */
1098
1093
  totalUses?: number | null;
1099
1094
  }): Promise<FormationUsageResult>;
@@ -1102,8 +1097,8 @@ export declare class ControlDatabase {
1102
1097
  * insert). This is the redemption path when the strand was provisioned
1103
1098
  * separately (e.g. owner-signed) and the consent record is added after the
1104
1099
  * fact: the single insert auto-commits, and the deferred `StrandExists` CHECK
1105
- * is satisfied by the pre-existing committed strand row. Returns the assigned
1106
- * `UseNumber`.
1100
+ * is satisfied by the pre-existing committed strand row. Echoes back the
1101
+ * redemption's `usageStampId` (the row's primary key).
1107
1102
  *
1108
1103
  * Use {@link redeemInvitation} instead when the strand must be created by
1109
1104
  * consent atomically with the usage.
@@ -1117,12 +1112,12 @@ export declare class ControlDatabase {
1117
1112
  * by {@link ControlFormationUsageRecorder.resolveStrand}, so an absent row at this
1118
1113
  * point is a genuine race and deserves a named error, not a silent rollback.
1119
1114
  *
1120
- * `UseNumber` is read inside the write lock and re-read per attempt by
1121
- * {@link withUseNumberRetry}: this is the path production actually races on (a bound
1122
- * invite published by `cadre-web` / `cadre-phone` is always record-only), and a use number
1123
- * lost to another writer is retried here with the caller's parameters — nonce, approver
1124
- * sign-off, strand, peer key, disclosure — passed through UNCHANGED, so a granted approval
1125
- * is never thrown away for a second trip through the approval hook.
1115
+ * The row is keyed by the joiner's own nonce (`UsageStampId` is the primary key), so this
1116
+ * path — the one production actually races on (a bound invite published by `cadre-web` /
1117
+ * `cadre-phone` is always record-only) — never contends with another redemption for a
1118
+ * shared row key: there is no lost race to retry and no second trip through the approval
1119
+ * hook. The invite's seat budget is checked by COUNT ({@link assertSeatRemains}) inside
1120
+ * the write lock, ahead of the write.
1126
1121
  */
1127
1122
  recordFormationUsage(params: {
1128
1123
  token: string;
@@ -1138,66 +1133,28 @@ export declare class ControlDatabase {
1138
1133
  validationKey?: string;
1139
1134
  validationSignature?: string;
1140
1135
  /**
1141
- * Aborted when the caller has given up. Checked once PER ATTEMPT, inside the write lock,
1142
- * at the top of the attempt — before it reads a use number and before its insert is
1143
- * issued — throwing {@link FormationAbortedError} with the invite unspent. Never checked
1144
- * once an insert has been issued.
1136
+ * Aborted when the caller has given up. Checked inside the write lock, before the insert
1137
+ * is issued, throwing {@link FormationAbortedError} with the invite unspent. Never
1138
+ * checked once the insert has been issued.
1145
1139
  */
1146
1140
  signal?: AbortSignal;
1147
1141
  /**
1148
1142
  * The invite's seat budget, when the caller already has it in hand (e.g. it already read
1149
- * the `FormationInvite` row to get here). Passed through to {@link withUseNumberRetry} so
1150
- * every attempt can check the seat budget without an extra read. `undefined` falls back to
1151
- * a fresh {@link queryFormationInvite} read; `null` means "no invite row" / unlimited.
1143
+ * the `FormationInvite` row to get here). Passed through to {@link assertSeatRemains} so
1144
+ * the seat check needs no extra read. `undefined` falls back to a fresh
1145
+ * {@link queryFormationInvite} read; `null` means "no invite row" / unlimited.
1152
1146
  */
1153
1147
  totalUses?: number | null;
1154
1148
  }): Promise<FormationUsageResult>;
1155
1149
  /**
1156
- * Assign a `UseNumber` and run `write` under it, re-picking and retrying when another
1157
- * writer took that number first.
1158
- *
1159
- * Each attempt takes {@link withWriteLock} and, inside it, reads {@link nextUseNumber}
1160
- * afresh. Reading under the lock is the whole fix for the common case: two redemptions of
1161
- * one token on the SAME node are serialized by the local write queue, so each computes a
1162
- * number the other has already committed and neither collides. The retry covers the writers
1163
- * this lock does not reach — another node of the cadre, another `Database` handle over the
1164
- * same store — which can still take the number between our read and our commit.
1165
- *
1166
- * `write` receives ONLY the use number; every other parameter of the redemption (the nonce,
1167
- * the approver's signature, the strand, the joining peer key, the disclosure) is closed over
1168
- * by the caller and passed through untouched. So by construction a retry re-presents the
1169
- * IDENTICAL approval and the four non-nonce fields inside the approver's signed digest
1170
- * cannot drift. No second call to the approval hook is reachable from in here either:
1171
- * `obtainApproval` lives a layer up in `ControlFormationUsageRecorder` and is never
1172
- * re-entered.
1173
- *
1174
- * `write` must be a BARE private body ({@link inTransaction} /
1175
- * {@link execFormationUsageInsert}) — {@link withWriteLock} is not re-entrant and a `write`
1176
- * that called another locked public method would hang silently and strand the write queue.
1177
- *
1178
- * A failed attempt has written nothing (a bare insert never landed; a transactional one
1179
- * rolled back), so the abort check at the top of each attempt stays inside
1180
- * {@link FormationAbortedError}'s contract — it is only ever thrown before this attempt's
1181
- * write is issued.
1182
- *
1183
- * @param operation - `'redemption'` | `'usage recording'`, for the abort error's text.
1184
- * @param knownTotalUses - The invite's seat budget when the caller already has it in hand
1185
- * (`undefined` to fall back to a fresh {@link queryFormationInvite} read; `null` for "no
1186
- * invite row" / unlimited). Passed through to {@link assertSeatRemains} on every attempt.
1187
- * @returns The `UseNumber` the successful attempt wrote under.
1188
- */
1189
- private withUseNumberRetry;
1190
- /**
1191
- * Refuse an attempt that would consume a seat the invite does not have.
1150
+ * Refuse a redemption that would consume a seat the invite does not have.
1192
1151
  *
1193
- * `FormationUsage.Authorized` already enforces `FI.TotalUses is null or FI.TotalUses >=
1194
- * new.UseNumber`, so an over-limit write fails at the database anyway — but it fails as a
1195
- * generic `CHECK constraint failed: Authorized`, which the manager reports as a retryable
1196
- * conflict. Catching it here as {@link InvitationExhaustedError} lets the joiner be told the
1197
- * invitation is spent instead of being sent around a loop that can never close. Run on EVERY
1198
- * attempt, including the first: two redemptions racing on the SAME node serialize behind the
1199
- * local write queue, so the loser's first attempt is already over budget, with no retry
1200
- * involved.
1152
+ * The schema's count-based cap clause (`FormationUsage.Authorized`) refuses an over-limit
1153
+ * write at the database anyway — but it fails as a generic `CHECK constraint failed:
1154
+ * Authorized`, which the manager reports as a retryable conflict. Catching it here as
1155
+ * {@link InvitationExhaustedError} lets the joiner be told the invitation is spent instead
1156
+ * of being sent around a loop that can never close. Runs inside the write lock, so on a
1157
+ * same-node race the loser reads the winner's committed row and is refused by name.
1201
1158
  *
1202
1159
  * `knownTotalUses` lets a caller that already read the `FormationInvite` row (both
1203
1160
  * production callers in `ControlFormationUsageRecorder` do) skip a second read on the common,
@@ -1207,7 +1164,7 @@ export declare class ControlDatabase {
1207
1164
  * invite, before an outbound approval call). Safe today because `FormationInvite` is
1208
1165
  * insert/delete only (its `Immutable` constraint), so the only way to stale it is an owner
1209
1166
  * revoking the token and re-issuing it with MORE seats mid-redemption; if invites ever gain
1210
- * an update path, drop the parameter and read here on every attempt instead.
1167
+ * an update path, drop the parameter and read here instead.
1211
1168
  */
1212
1169
  private assertSeatRemains;
1213
1170
  /** Parameterised `FormationUsage` insert shared by redeem + record paths. */
@@ -1227,6 +1184,22 @@ export declare class ControlDatabase {
1227
1184
  } | null>;
1228
1185
  /**
1229
1186
  * Count `FormationUsage` rows recorded against a token (uses consumed so far).
1187
+ *
1188
+ * NOTE: `Token` left the primary key in `formation-unique-token-redesign` and gained the
1189
+ * `FormationUsageByToken` secondary index, so this filter is now served by an index SEEK
1190
+ * (a descent through the index collection) where it used to be a leading-key partial match
1191
+ * the optimystic vtab declined and the engine served by scan. That matters because the seat
1192
+ * cap is this count: a descent that under-reports admits a seat the invite does not have,
1193
+ * where a scan could not. Descent misses have been observed on a NETWORKED store (tracked
1194
+ * by `debt-composite-pk-point-lookup-unreliable-untracked`, for full-primary-key lookups);
1195
+ * no miss has been observed on this index on a SINGLE node. Across nodes the index is
1196
+ * currently blind: a sibling never learns index entries for rows written elsewhere (the
1197
+ * distributed commit drops the index update — measured 2026-08-12, tracked by
1198
+ * `blocked/secondary-index-seek-blind-to-sibling-rows`), so on a multi-machine party this
1199
+ * count under-reports and the cap over-admits until the upstream fix lands. If
1200
+ * over-admission ever shows up on a converged single node — a token with more usage rows
1201
+ * than its `TotalUses` and no concurrent redeemers to explain it — suspect this seek first
1202
+ * and drop the index to fall back to the scan.
1230
1203
  */
1231
1204
  countFormationUsage(token: string): Promise<number>;
1232
1205
  /**
@@ -1247,14 +1220,6 @@ export declare class ControlDatabase {
1247
1220
  * unlimited-use invite anywhere in the scan short-circuits all of them.
1248
1221
  */
1249
1222
  hasOutstandingFormationInvite(nowMs?: number): Promise<boolean>;
1250
- /**
1251
- * Next `UseNumber` for a token = max(existing)+1, per the `Monotonic` constraint.
1252
- *
1253
- * Called only from inside {@link withUseNumberRetry}'s locked body — a read takes no
1254
- * transaction of its own, so it is safe there, and reading it under the lock is what stops
1255
- * two local redemptions of one token from picking the same number.
1256
- */
1257
- private nextUseNumber;
1258
1223
  /**
1259
1224
  * Close the database and cleanup resources.
1260
1225
  *