@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.
- package/README.md +255 -255
- package/dist/cadre-node.d.ts +203 -28
- package/dist/cadre-node.js +445 -84
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-cohort.d.ts +26 -0
- package/dist/control-cohort.js +26 -0
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +82 -117
- package/dist/control-database.js +217 -295
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +8 -7
- package/dist/control-formation-recorder.js +8 -7
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +789 -774
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +15 -5
- package/dist/control-write-retry.js +19 -18
- package/dist/control-write-retry.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/peer-addr-book.d.ts +92 -0
- package/dist/peer-addr-book.js +220 -0
- package/dist/peer-addr-book.js.map +1 -0
- package/dist/seed-bootstrap.d.ts +7 -0
- package/dist/seed-bootstrap.js +15 -13
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/strand-cohort.d.ts +4 -37
- package/dist/strand-cohort.js +4 -15
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +11 -16
- package/dist/strand-database.js +19 -7
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +8 -8
- package/dist/strand-formation-manager.js +15 -18
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +52 -21
- package/dist/strand-instance-manager.js +100 -34
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +6 -4
- package/dist/strand-membership-writer.js +38 -36
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/types.d.ts +39 -31
- package/dist/types.js.map +1 -1
- package/package.json +8 -7
- package/src/arachnode-stub.ts +159 -159
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +172 -172
- package/src/cadre-node.ts +5158 -4771
- package/src/canonical-json.ts +30 -30
- package/src/control-authorization.ts +114 -114
- package/src/control-cohort.ts +130 -103
- package/src/control-database.ts +2406 -2506
- package/src/control-formation-recorder.ts +305 -304
- package/src/control-schema.ts +801 -786
- package/src/control-stream.ts +205 -205
- package/src/control-write-retry.ts +483 -473
- package/src/delegate-admission.ts +302 -302
- package/src/device-token.ts +94 -94
- package/src/diagnostics/connection-path.ts +277 -277
- package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
- package/src/ed25519-key.ts +134 -134
- package/src/enrollment.ts +199 -199
- package/src/file-durable-slot.ts +61 -61
- package/src/formation-approval.ts +604 -604
- package/src/fs-atomic.ts +107 -107
- package/src/hibernation-manager.ts +437 -437
- package/src/identity-key.ts +109 -109
- package/src/index.ts +1 -0
- package/src/key-store-file.ts +132 -132
- package/src/key-store.ts +99 -99
- package/src/membership-connection-gater.ts +204 -204
- package/src/node-local-snapshot.ts +260 -260
- package/src/peer-addr-book.ts +250 -0
- package/src/peer-authorization.ts +259 -259
- package/src/peer-record.ts +164 -164
- package/src/push-credentials.ts +84 -84
- package/src/push-node.ts +64 -64
- package/src/push-notifier-apns.ts +247 -247
- package/src/push-notifier-fcm.ts +220 -220
- package/src/push-notifier-shared.ts +28 -28
- package/src/push-notifier.ts +52 -52
- package/src/relay-addrs.ts +111 -111
- package/src/relay-reservation.ts +823 -823
- package/src/seed-bootstrap.ts +15 -15
- package/src/seed-trust-policy.ts +140 -140
- package/src/strand-addr-protocol.ts +441 -441
- package/src/strand-backfill.ts +366 -366
- package/src/strand-cohort.ts +38 -77
- package/src/strand-database.ts +202 -204
- package/src/strand-formation-manager.ts +491 -494
- package/src/strand-formation-protocol.ts +692 -692
- package/src/strand-instance-manager.ts +108 -50
- package/src/strand-member-key.ts +39 -39
- package/src/strand-member-registry.ts +182 -182
- package/src/strand-membership-writer.ts +1432 -1430
- package/src/strand-solicitation.ts +472 -472
- package/src/strand-transport-key.ts +58 -58
- package/src/strand-wake-payload.ts +32 -32
- package/src/strand-wake-protocol.ts +314 -314
- package/src/strand-watcher.ts +314 -314
- package/src/trusted-owner-store-file.ts +41 -41
- package/src/trusted-owner-store.ts +171 -171
- package/src/types.ts +1309 -1303
|
@@ -49,43 +49,27 @@ export declare class FormationAbortedError extends Error {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
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
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
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
|
|
69
|
+
readonly usesRecorded: number;
|
|
68
70
|
readonly totalUses: number;
|
|
69
|
-
constructor(token: string,
|
|
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 `
|
|
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.
|
|
941
|
-
*
|
|
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
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
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
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
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.
|
|
1106
|
-
* `
|
|
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
|
-
*
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
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
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
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
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
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
|
-
*
|
|
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`
|
|
1194
|
-
*
|
|
1195
|
-
*
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
1198
|
-
*
|
|
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
|
|
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
|
*
|