@serfab/cadre-core 1.16.0 → 1.18.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 +4 -3
- package/dist/bootstrap-peer-store-file.js +1 -1
- package/dist/bootstrap-peer-store.d.ts +1 -1
- package/dist/bootstrap-peer-store.js +4 -4
- package/dist/cadre-invite-protocol.d.ts +81 -142
- package/dist/cadre-invite-protocol.js +168 -458
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-join-request.d.ts +15 -2
- package/dist/cadre-join-request.js +22 -3
- package/dist/cadre-join-request.js.map +1 -1
- package/dist/cadre-node.d.ts +1089 -475
- package/dist/cadre-node.js +2170 -1037
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +36 -14
- package/dist/control-authorization.js +20 -3
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +88 -32
- package/dist/control-cohort.js +91 -33
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +856 -318
- package/dist/control-database.js +1422 -701
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +46 -43
- package/dist/control-formation-recorder.js +77 -78
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +11 -4
- package/dist/control-protocol-guard.js +20 -5
- package/dist/control-protocol-guard.js.map +1 -1
- package/dist/control-schema-version.d.ts +97 -0
- package/dist/control-schema-version.js +225 -0
- package/dist/control-schema-version.js.map +1 -0
- package/dist/control-schema.d.ts +45 -5
- package/dist/control-schema.js +262 -78
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +45 -10
- package/dist/control-write-retry.js +92 -22
- package/dist/control-write-retry.js.map +1 -1
- package/dist/delegate-admission.d.ts +13 -0
- package/dist/delegate-admission.js +27 -0
- package/dist/delegate-admission.js.map +1 -1
- package/dist/ed25519-key.d.ts +10 -7
- package/dist/ed25519-key.js +23 -20
- package/dist/ed25519-key.js.map +1 -1
- package/dist/edge-node/config.d.ts +122 -0
- package/dist/edge-node/config.js +199 -0
- package/dist/edge-node/config.js.map +1 -0
- package/dist/edge-node/dissolve.d.ts +95 -0
- package/dist/edge-node/dissolve.js +184 -0
- package/dist/edge-node/dissolve.js.map +1 -0
- package/dist/edge-node/edge-join.d.ts +44 -0
- package/dist/edge-node/edge-join.js +227 -0
- package/dist/edge-node/edge-join.js.map +1 -0
- package/dist/edge-node/edge-node.d.ts +295 -0
- package/dist/edge-node/edge-node.js +704 -0
- package/dist/edge-node/edge-node.js.map +1 -0
- package/dist/edge-node/index.d.ts +18 -0
- package/dist/edge-node/index.js +18 -0
- package/dist/edge-node/index.js.map +1 -0
- package/dist/edge-node/join-credential.d.ts +11 -0
- package/dist/edge-node/join-credential.js +11 -0
- package/dist/edge-node/join-credential.js.map +1 -0
- package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
- package/dist/edge-node/lazy-raw-storage.js +134 -0
- package/dist/edge-node/lazy-raw-storage.js.map +1 -0
- package/dist/edge-node/node-local.d.ts +60 -0
- package/dist/edge-node/node-local.js +58 -0
- package/dist/edge-node/node-local.js.map +1 -0
- package/dist/edge-node/options.d.ts +83 -0
- package/dist/edge-node/options.js +110 -0
- package/dist/edge-node/options.js.map +1 -0
- package/dist/edge-node/owner-grant.d.ts +41 -0
- package/dist/edge-node/owner-grant.js +38 -0
- package/dist/edge-node/owner-grant.js.map +1 -0
- package/dist/edge-node/platform.d.ts +140 -0
- package/dist/edge-node/platform.js +12 -0
- package/dist/edge-node/platform.js.map +1 -0
- package/dist/edge-node/strand-move.d.ts +115 -0
- package/dist/edge-node/strand-move.js +223 -0
- package/dist/edge-node/strand-move.js.map +1 -0
- package/dist/edge-node/strands.d.ts +28 -0
- package/dist/edge-node/strands.js +60 -0
- package/dist/edge-node/strands.js.map +1 -0
- package/dist/enrolled-machine-store.d.ts +3 -3
- package/dist/enrolled-machine-store.js +3 -3
- package/dist/formation-token.d.ts +48 -0
- package/dist/formation-token.js +26 -0
- package/dist/formation-token.js.map +1 -0
- package/dist/index.d.ts +27 -17
- package/dist/index.js +39 -17
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +29 -12
- package/dist/joined-strand-store.js +66 -27
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/key-store.d.ts +9 -4
- package/dist/key-store.js +9 -4
- package/dist/key-store.js.map +1 -1
- package/dist/link-budget.d.ts +9 -7
- package/dist/link-budget.js +9 -7
- package/dist/link-budget.js.map +1 -1
- package/dist/machine-removal.d.ts +19 -39
- package/dist/machine-removal.js +24 -98
- package/dist/machine-removal.js.map +1 -1
- package/dist/member-redemption.d.ts +256 -0
- package/dist/member-redemption.js +490 -0
- package/dist/member-redemption.js.map +1 -0
- package/dist/membership-view.d.ts +91 -0
- package/dist/membership-view.js +110 -0
- package/dist/membership-view.js.map +1 -0
- package/dist/owner-anchor-sync.d.ts +4 -4
- package/dist/owner-anchor-sync.js +16 -15
- package/dist/owner-anchor-sync.js.map +1 -1
- package/dist/owner-grant-protocol.d.ts +251 -0
- package/dist/owner-grant-protocol.js +344 -0
- package/dist/owner-grant-protocol.js.map +1 -0
- package/dist/owner-key-export.d.ts +70 -0
- package/dist/owner-key-export.js +279 -0
- package/dist/owner-key-export.js.map +1 -0
- package/dist/owner-key-flows.d.ts +55 -0
- package/dist/owner-key-flows.js +60 -0
- package/dist/owner-key-flows.js.map +1 -0
- package/dist/owner-key-store.d.ts +31 -0
- package/dist/owner-key-store.js +81 -0
- package/dist/owner-key-store.js.map +1 -0
- package/dist/owner-keys.d.ts +78 -0
- package/dist/owner-keys.js +133 -0
- package/dist/owner-keys.js.map +1 -0
- package/dist/owner-signer.d.ts +54 -0
- package/dist/owner-signer.js +51 -0
- package/dist/owner-signer.js.map +1 -0
- package/dist/peer-addr-book.d.ts +10 -1
- package/dist/peer-addr-book.js +4 -3
- package/dist/peer-addr-book.js.map +1 -1
- package/dist/peer-authorization.d.ts +83 -18
- package/dist/peer-authorization.js +123 -53
- package/dist/peer-authorization.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +89 -20
- package/dist/peer-join-backfill.js +265 -108
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/pending-join-runner.d.ts +48 -18
- package/dist/pending-join-runner.js +74 -56
- package/dist/pending-join-runner.js.map +1 -1
- package/dist/provider/rent-node.js +1 -1
- package/dist/provider/rent-node.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +162 -133
- package/dist/seed-bootstrap.js +300 -283
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.js +4 -4
- package/dist/strand-addr-protocol.d.ts +4 -1
- package/dist/strand-addr-protocol.js +8 -2
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-cohort.d.ts +3 -0
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +24 -2
- package/dist/strand-database.js +29 -6
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +176 -88
- package/dist/strand-first-sync-gate.js +288 -121
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-deadlines.d.ts +9 -9
- package/dist/strand-formation-deadlines.js +9 -9
- package/dist/strand-formation-manager.d.ts +23 -17
- package/dist/strand-formation-manager.js +46 -44
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +5 -8
- package/dist/strand-formation-protocol.js +101 -16
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +27 -18
- package/dist/strand-instance-manager.js +119 -50
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +16 -13
- package/dist/strand-membership-reconciler.js +30 -22
- package/dist/strand-membership-reconciler.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +45 -22
- package/dist/strand-membership-writer.js +226 -131
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-move.d.ts +6 -5
- package/dist/strand-move.js +36 -32
- package/dist/strand-move.js.map +1 -1
- package/dist/strand-peer-redial.js +2 -3
- package/dist/strand-peer-redial.js.map +1 -1
- package/dist/strand-revocation-enforcer.d.ts +134 -60
- package/dist/strand-revocation-enforcer.js +144 -71
- package/dist/strand-revocation-enforcer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +50 -39
- package/dist/strand-solicitation.js +10 -3
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-watcher.d.ts +80 -13
- package/dist/strand-watcher.js +152 -48
- package/dist/strand-watcher.js.map +1 -1
- package/dist/table-change-watch.d.ts +63 -0
- package/dist/table-change-watch.js +209 -0
- package/dist/table-change-watch.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +4 -3
- package/dist/timeout-scheduler.js +4 -3
- package/dist/timeout-scheduler.js.map +1 -1
- package/dist/types.d.ts +374 -150
- package/dist/types.js +4 -4
- package/dist/types.js.map +1 -1
- package/package.json +17 -8
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +214 -214
- package/src/cadre-invite-protocol.ts +740 -1052
- package/src/cadre-join-request.ts +24 -3
- package/src/cadre-node.ts +2402 -1079
- package/src/control-authorization.ts +47 -14
- package/src/control-cohort.ts +204 -128
- package/src/control-database.ts +1749 -820
- package/src/control-formation-recorder.ts +359 -353
- package/src/control-protocol-guard.ts +23 -6
- package/src/control-schema-version.ts +301 -0
- package/src/control-schema.ts +273 -78
- package/src/control-write-retry.ts +641 -567
- package/src/delegate-admission.ts +351 -314
- package/src/ed25519-key.ts +174 -171
- package/src/edge-node/config.ts +277 -0
- package/src/edge-node/dissolve.ts +251 -0
- package/src/edge-node/edge-join.ts +319 -0
- package/src/edge-node/edge-node.ts +1045 -0
- package/src/edge-node/index.ts +59 -0
- package/src/edge-node/join-credential.ts +16 -0
- package/src/edge-node/lazy-raw-storage.ts +178 -0
- package/src/edge-node/node-local.ts +84 -0
- package/src/edge-node/options.ts +179 -0
- package/src/edge-node/owner-grant.ts +63 -0
- package/src/edge-node/platform.ts +151 -0
- package/src/edge-node/strand-move.ts +270 -0
- package/src/edge-node/strands.ts +72 -0
- package/src/enrolled-machine-store.ts +3 -3
- package/src/formation-token.ts +63 -0
- package/src/index.ts +899 -789
- package/src/joined-strand-store.ts +73 -28
- package/src/key-store.ts +10 -4
- package/src/link-budget.ts +9 -7
- package/src/machine-removal.ts +26 -111
- package/src/member-redemption.ts +649 -0
- package/src/membership-view.ts +144 -0
- package/src/owner-anchor-sync.ts +19 -18
- package/src/owner-grant-protocol.ts +547 -0
- package/src/owner-key-export.ts +348 -0
- package/src/owner-key-flows.ts +109 -0
- package/src/owner-key-store.ts +85 -0
- package/src/owner-keys.ts +178 -0
- package/src/owner-signer.ts +94 -0
- package/src/peer-addr-book.ts +4 -3
- package/src/peer-authorization.ts +558 -488
- package/src/peer-join-backfill.ts +971 -761
- package/src/pending-join-runner.ts +76 -53
- package/src/provider/rent-node.ts +1 -1
- package/src/seed-bootstrap.ts +343 -349
- package/src/seed-trust-policy.ts +4 -4
- package/src/strand-addr-protocol.ts +547 -538
- package/src/strand-cohort.ts +4 -0
- package/src/strand-database.ts +352 -319
- package/src/strand-first-sync-gate.ts +334 -133
- package/src/strand-formation-deadlines.ts +227 -227
- package/src/strand-formation-manager.ts +731 -725
- package/src/strand-formation-protocol.ts +122 -24
- package/src/strand-instance-manager.ts +2092 -2026
- package/src/strand-membership-reconciler.ts +33 -25
- package/src/strand-membership-writer.ts +2093 -1974
- package/src/strand-move.ts +50 -38
- package/src/strand-peer-redial.ts +2 -3
- package/src/strand-revocation-enforcer.ts +740 -654
- package/src/strand-solicitation.ts +61 -34
- package/src/strand-watcher.ts +175 -56
- package/src/table-change-watch.ts +285 -0
- package/src/timeout-scheduler.ts +29 -28
- package/src/types.ts +391 -152
|
@@ -2,15 +2,14 @@ import debug from 'debug';
|
|
|
2
2
|
import { FormationAbortedError, InvitationExhaustedError } from './control-database.js';
|
|
3
3
|
import { mintStrandId } from './strand-id.js';
|
|
4
4
|
import { createHttpFormationApprover, verifyFormationApproval, FormationApprovalError } from './formation-approval.js';
|
|
5
|
+
import { isInviteExpired } from './formation-token.js';
|
|
5
6
|
const log = debug('sereus:cadre:formation-recorder');
|
|
6
|
-
/** Far-future sentinel for an invitation that never expires (max valid JS Date). */
|
|
7
|
-
const NEVER_EXPIRES = new Date(8640000000000000);
|
|
8
7
|
/**
|
|
9
8
|
* {@link FormationUsageRecorder} backed by the real `CadreControl` tables.
|
|
10
9
|
*
|
|
11
|
-
* It reads
|
|
12
|
-
*
|
|
13
|
-
* provisioning shapes are supported, keyed on whether the invite binds a host strand:
|
|
10
|
+
* It reads a session's token once ({@link readToken}: the `FormationInvite` row and its
|
|
11
|
+
* `FormationUsage` count), and writes the consent row that records a redemption from that
|
|
12
|
+
* read. Two provisioning shapes are supported, keyed on whether the invite binds a host strand:
|
|
14
13
|
*
|
|
15
14
|
* - **Bound (provision-then-record):** the host strand already exists (owner-signed
|
|
16
15
|
* up front and named by the invite's `StrandId`), so {@link resolveStrand} reports it
|
|
@@ -48,38 +47,21 @@ export class ControlFormationUsageRecorder {
|
|
|
48
47
|
this.controlDatabase = controlDatabase;
|
|
49
48
|
this.approver = options?.approver ?? createHttpFormationApprover();
|
|
50
49
|
}
|
|
51
|
-
/** A token is valid when a matching, unexpired `FormationInvite` exists. */
|
|
52
|
-
async isTokenValid(token) {
|
|
53
|
-
const invite = await this.controlDatabase.queryFormationInvite(token);
|
|
54
|
-
if (!invite) {
|
|
55
|
-
return { valid: false, reason: 'unknown' };
|
|
56
|
-
}
|
|
57
|
-
if (invite.expiresAtMs !== null && invite.expiresAtMs <= Date.now()) {
|
|
58
|
-
log('Token expired: %s', token);
|
|
59
|
-
return { valid: false, reason: 'expired' };
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
valid: true,
|
|
63
|
-
invitation: {
|
|
64
|
-
token: invite.token,
|
|
65
|
-
sAppId: invite.sAppId,
|
|
66
|
-
expiration: invite.expiresAtMs !== null ? new Date(invite.expiresAtMs) : NEVER_EXPIRES,
|
|
67
|
-
bootstrap: [],
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
50
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
51
|
+
* One `FormationInvite` read, then one `FormationUsage` count only when the invite is
|
|
52
|
+
* metered (`TotalUses` set) and unexpired: an unknown, expired or unlimited token has no
|
|
53
|
+
* seat to count. This early count refuses a spent token before anything costly (the outside
|
|
54
|
+
* approval call, the strand-database write that issues the membership invite); the count
|
|
55
|
+
* inside the write lock decides.
|
|
75
56
|
*/
|
|
76
|
-
async
|
|
57
|
+
async readToken(token) {
|
|
77
58
|
const invite = await this.controlDatabase.queryFormationInvite(token);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
59
|
+
const metered = invite !== null && invite.totalUses !== null && !isInviteExpired(invite, Date.now());
|
|
60
|
+
return {
|
|
61
|
+
token,
|
|
62
|
+
invite,
|
|
63
|
+
usesRecorded: metered ? await this.controlDatabase.countFormationUsage(token) : null,
|
|
64
|
+
};
|
|
83
65
|
}
|
|
84
66
|
/**
|
|
85
67
|
* Any `FormationInvite` row still unexpired and not fully consumed — the
|
|
@@ -157,66 +139,79 @@ export class ControlFormationUsageRecorder {
|
|
|
157
139
|
* spent on AND provably the joiner that agreed. Use
|
|
158
140
|
* {@link ControlDatabase.redeemInvitation} for the consent-creates-strand path instead.
|
|
159
141
|
*
|
|
160
|
-
*
|
|
161
|
-
* in
|
|
142
|
+
* Reads the invite for itself (it is handed no session read) and the strand's stamp inside
|
|
143
|
+
* `recordFormationUsage`; otherwise authorizes as {@link authorizeUsage} does and writes in
|
|
144
|
+
* one step, for callers with nothing to do in between.
|
|
162
145
|
*/
|
|
163
146
|
async recordUsage(params) {
|
|
164
|
-
|
|
147
|
+
if (params.signal?.aborted) {
|
|
148
|
+
throw new FormationAbortedError(params.token, 'usage recording');
|
|
149
|
+
}
|
|
150
|
+
const invite = await this.controlDatabase.queryFormationInvite(params.token);
|
|
151
|
+
await (await this.authorize(params, invite, undefined)).record();
|
|
165
152
|
}
|
|
166
153
|
/**
|
|
167
154
|
* Everything {@link recordUsage} does before its write, so the manager can issue the
|
|
168
155
|
* joiner's membership pass into the host strand only once the redemption is cleared, and
|
|
169
|
-
* spend the token only after that.
|
|
170
|
-
*
|
|
171
|
-
* Reads the invite first (one extra read per redemption) to learn its `ValidationUrl` and
|
|
172
|
-
* seat budget, obtains the approval through {@link obtainApproval} when one is demanded,
|
|
173
|
-
* re-checks the abort (the approval call may have been long), and pre-checks the seat
|
|
174
|
-
* budget ({@link assertSeatFree}). The returned `record()` closes over the captured fields
|
|
175
|
-
* and approval, so the nonce the approver signed is the nonce inserted without the caller
|
|
176
|
-
* ever passing it back.
|
|
156
|
+
* spend the token only after that. Takes the invite's `ValidationUrl` and seat budget from
|
|
157
|
+
* the session's `read`, and the host strand's stamp from `host`, so it reads neither again.
|
|
177
158
|
*/
|
|
178
|
-
async authorizeUsage(params) {
|
|
159
|
+
async authorizeUsage(params, read, host) {
|
|
160
|
+
return await this.authorize(params, read.invite, host.strandStampId);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Obtain the approval through {@link obtainApproval} when `invite` demands one, re-check the
|
|
164
|
+
* abort (the approval call may have been long), and after an obtained approval re-check the
|
|
165
|
+
* seat budget ({@link assertSeatFree}). The returned `record()` closes over the captured
|
|
166
|
+
* fields and approval, so the nonce the approver signed is the nonce inserted without the
|
|
167
|
+
* caller ever passing it back. `strandStampId` undefined makes `recordFormationUsage` read it.
|
|
168
|
+
*/
|
|
169
|
+
async authorize(params, invite, strandStampId) {
|
|
179
170
|
const { token, peerKey, peerSignature, usageStampId, strandId, disclosure, signal } = params;
|
|
180
171
|
if (signal?.aborted) {
|
|
181
172
|
throw new FormationAbortedError(token, 'usage recording');
|
|
182
173
|
}
|
|
183
|
-
const invite = await this.controlDatabase.queryFormationInvite(token);
|
|
184
174
|
// NOTE: a missing invite row reads as "no approval required" here and in
|
|
185
|
-
// `provisionAndRecord`, so an invite that
|
|
186
|
-
// would be written unapproved. Safe only because the database's
|
|
187
|
-
// CHECK demands a matching `FormationInvite` and rolls the write
|
|
188
|
-
// CHECK ever loosens, reject on `invite === null` instead of
|
|
175
|
+
// `provisionAndRecord`, so an invite that was absent from the session's read (or vanished
|
|
176
|
+
// before `recordUsage`'s) would be written unapproved. Safe only because the database's
|
|
177
|
+
// `FormationUsage.Authorized` CHECK demands a matching `FormationInvite` and rolls the write
|
|
178
|
+
// back regardless; if that CHECK ever loosens, reject on `invite === null` instead of
|
|
179
|
+
// defaulting to null.
|
|
180
|
+
const validationUrl = invite?.validationUrl ?? null;
|
|
189
181
|
const approval = await this.obtainApproval({
|
|
190
|
-
token, usageStampId, strandId, peerKey, disclosure,
|
|
191
|
-
validationUrl: invite?.validationUrl ?? null,
|
|
182
|
+
token, usageStampId, strandId, peerKey, disclosure, validationUrl,
|
|
192
183
|
}, signal);
|
|
193
184
|
if (signal?.aborted) {
|
|
194
185
|
throw new FormationAbortedError(token, 'usage recording');
|
|
195
186
|
}
|
|
196
187
|
const totalUses = invite?.totalUses ?? null;
|
|
197
|
-
|
|
188
|
+
if (validationUrl !== null) {
|
|
189
|
+
await this.assertSeatFree(token, totalUses);
|
|
190
|
+
}
|
|
198
191
|
return {
|
|
199
192
|
record: async () => {
|
|
200
193
|
await this.controlDatabase.recordFormationUsage({
|
|
201
194
|
token, strandId, peerKey, peerSignature, usageStampId, disclosure, signal,
|
|
202
|
-
totalUses, ...approval,
|
|
195
|
+
totalUses, strandStampId, ...approval,
|
|
203
196
|
});
|
|
204
197
|
log('Recorded formation usage: token=%s strand=%s', token, strandId);
|
|
205
198
|
}
|
|
206
199
|
};
|
|
207
200
|
}
|
|
208
201
|
/**
|
|
209
|
-
* Refuse a redemption whose invite
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
202
|
+
* Refuse a redemption whose invite lost its last seat while this redemption waited on its
|
|
203
|
+
* outside approval, BEFORE anything is written — the authoritative check is the one
|
|
204
|
+
* `recordFormationUsage` repeats inside its write lock. Runs only after an obtained approval:
|
|
205
|
+
* that call is the one long wait between the session's early count (`readToken`) and the
|
|
206
|
+
* membership pass, in which a same-node rival could take the seat unseen.
|
|
213
207
|
*
|
|
214
|
-
* NOTE: two redemptions that both pass
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
208
|
+
* NOTE: two redemptions that both pass the last count before issuance (this one after an
|
|
209
|
+
* approval, `readToken`'s without one) within the time one of them takes to issue its
|
|
210
|
+
* membership pass still both issue one, and the loser's pass is orphaned (expiry-bounded,
|
|
211
|
+
* never handed out). Holding the control write lock across issuance would close it, but
|
|
212
|
+
* `lockedWithRetry` re-runs locked bodies on transient failures (a re-run would double-issue)
|
|
213
|
+
* and every control write would stall behind a strand-database write. If orphaned passes from
|
|
214
|
+
* same-node races ever matter, reserve the seat before issuance.
|
|
220
215
|
*/
|
|
221
216
|
async assertSeatFree(token, totalUses) {
|
|
222
217
|
if (totalUses === null) {
|
|
@@ -228,29 +223,32 @@ export class ControlFormationUsageRecorder {
|
|
|
228
223
|
}
|
|
229
224
|
}
|
|
230
225
|
/**
|
|
231
|
-
* Classify the host strand
|
|
226
|
+
* Classify the host strand the read invite binds to (see {@link ResolvedHostStrand}), with
|
|
227
|
+
* one `Strand` read and no invite read:
|
|
232
228
|
*
|
|
233
229
|
* - no invite / no `StrandId` → `unbound` (responder-provisions path).
|
|
234
|
-
* - `StrandId` set AND the strand row is present → `bound`, carrying its
|
|
235
|
-
* `
|
|
236
|
-
* validated invitee.
|
|
230
|
+
* - `StrandId` set AND the strand row is present → `bound`, carrying its type, its
|
|
231
|
+
* `StampId` (which the consent row names) and its `MemberPrivateKey` (the closed-strand
|
|
232
|
+
* read-gating secret) for delivery to a validated invitee.
|
|
237
233
|
* - `StrandId` set but the strand row is absent → `missing` (the host strand has not
|
|
238
234
|
* converged on this responder yet); the manager rejects cleanly instead of recording
|
|
239
235
|
* usage against a non-existent strand, which would fail the deferred `StrandExists`
|
|
240
236
|
* CHECK at commit and drop the result frame.
|
|
241
237
|
*/
|
|
242
|
-
async resolveStrand(
|
|
243
|
-
const
|
|
244
|
-
if (!
|
|
238
|
+
async resolveStrand(read) {
|
|
239
|
+
const strandId = read.invite?.strandId;
|
|
240
|
+
if (!strandId) {
|
|
245
241
|
return { kind: 'unbound' };
|
|
246
242
|
}
|
|
247
|
-
const strand = await this.controlDatabase.
|
|
243
|
+
const strand = await this.controlDatabase.queryStrandWithStamp(strandId);
|
|
248
244
|
if (!strand) {
|
|
249
|
-
return { kind: 'missing', strandId
|
|
245
|
+
return { kind: 'missing', strandId };
|
|
250
246
|
}
|
|
251
247
|
return {
|
|
252
248
|
kind: 'bound',
|
|
253
|
-
strandId
|
|
249
|
+
strandId,
|
|
250
|
+
strandType: strand.Type,
|
|
251
|
+
strandStampId: strand.StampId,
|
|
254
252
|
memberPrivateKey: strand.MemberPrivateKey ?? null,
|
|
255
253
|
};
|
|
256
254
|
}
|
|
@@ -269,7 +267,8 @@ export class ControlFormationUsageRecorder {
|
|
|
269
267
|
*
|
|
270
268
|
* The strand is open (`'o'`) — an unbound responder-provisioned strand has no membership
|
|
271
269
|
* key — so the returned `memberPrivateKey` is null. `sAppId` is accepted for parity/future
|
|
272
|
-
* use; `redeemInvitation` does not currently thread it into the `Strand` row.
|
|
270
|
+
* use; `redeemInvitation` does not currently thread it into the `Strand` row. The invite's
|
|
271
|
+
* `ValidationUrl` and seat budget come from the session's `read`.
|
|
273
272
|
*
|
|
274
273
|
* A concurrent redemption of the same single-use invite still THROWS for the loser — the
|
|
275
274
|
* manager maps that to a clean protocol rejection (it never lets the dropped insert close
|
|
@@ -282,13 +281,13 @@ export class ControlFormationUsageRecorder {
|
|
|
282
281
|
* removal), chosen over a fought-over shared key whose failure mode was the silent loss of
|
|
283
282
|
* a consented join.
|
|
284
283
|
*/
|
|
285
|
-
async provisionAndRecord(params) {
|
|
284
|
+
async provisionAndRecord(params, read) {
|
|
286
285
|
const { token, peerKey, peerSignature, usageStampId, disclosure, signal } = params;
|
|
287
286
|
if (signal?.aborted) {
|
|
288
287
|
throw new FormationAbortedError(token, 'redemption');
|
|
289
288
|
}
|
|
290
289
|
const strandId = mintStrandId();
|
|
291
|
-
const invite =
|
|
290
|
+
const invite = read.invite;
|
|
292
291
|
const approval = await this.obtainApproval({
|
|
293
292
|
token, usageStampId, strandId, peerKey, disclosure,
|
|
294
293
|
validationUrl: invite?.validationUrl ?? null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-formation-recorder.js","sourceRoot":"","sources":["../src/control-formation-recorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAwB,MAAM,uBAAuB,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,sBAAsB,EAIvB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"control-formation-recorder.js","sourceRoot":"","sources":["../src/control-formation-recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
*
|
|
15
15
|
* ## The classes
|
|
16
16
|
*
|
|
17
|
-
* - `stranger-open` — seed delivery, strand formation, cadre-invitation redemption
|
|
18
|
-
* each handler makes its own trust decision in-protocol (a seed
|
|
19
|
-
* anchored trust policy, a formation token, an invitation's
|
|
17
|
+
* - `stranger-open` — seed delivery, strand formation, cadre-invitation redemption, owner-grant
|
|
18
|
+
* redemption. Any peer; each handler makes its own trust decision in-protocol (a seed
|
|
19
|
+
* signature against the anchored trust policy, a formation token, an invitation's or a
|
|
20
|
+
* grant's possession proof).
|
|
20
21
|
* - `transport` — libp2p plumbing (identify, ping, hole punching, AutoNAT, the circuit-relay
|
|
21
22
|
* stop and hop protocols, WebRTC signaling). Every connection runs these, the redeeming
|
|
22
23
|
* device's own included: refusing identify would make that device treat the member as
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
import type { Libp2p } from '@libp2p/interface';
|
|
79
80
|
/** What a peer must be to open a stream on a control-node protocol (see the module doc). */
|
|
80
81
|
export type ControlProtocolClass =
|
|
81
|
-
/** Any peer. The handler makes its own trust decision in-protocol (seed, formation, cadre-invite). */
|
|
82
|
+
/** Any peer. The handler makes its own trust decision in-protocol (seed, formation, cadre-invite, owner-grant). */
|
|
82
83
|
'stranger-open'
|
|
83
84
|
/** libp2p plumbing that a stranger's connection needs or that carries no party data. */
|
|
84
85
|
| 'transport'
|
|
@@ -111,6 +112,12 @@ export declare class ControlStreamRefusedError extends Error {
|
|
|
111
112
|
* storage scope key already does.
|
|
112
113
|
*/
|
|
113
114
|
export declare function controlNetworkName(partyId: string): string;
|
|
115
|
+
/**
|
|
116
|
+
* Whether a peer whose protocol list is `protocols` serves the control database. Optimystic
|
|
117
|
+
* counts a peer into a control block's cohort by the same test (`servesThisNetwork` in db-p2p's
|
|
118
|
+
* `libp2p-key-network.ts`), before its membership check.
|
|
119
|
+
*/
|
|
120
|
+
export declare function servesControlDatabase(partyId: string, protocols: readonly string[]): boolean;
|
|
114
121
|
/**
|
|
115
122
|
* The class of every protocol a control node serves, by exact protocol id: the single
|
|
116
123
|
* declaration the guard enforces. See the module doc for what each class admits and why each
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
*
|
|
15
15
|
* ## The classes
|
|
16
16
|
*
|
|
17
|
-
* - `stranger-open` — seed delivery, strand formation, cadre-invitation redemption
|
|
18
|
-
* each handler makes its own trust decision in-protocol (a seed
|
|
19
|
-
* anchored trust policy, a formation token, an invitation's
|
|
17
|
+
* - `stranger-open` — seed delivery, strand formation, cadre-invitation redemption, owner-grant
|
|
18
|
+
* redemption. Any peer; each handler makes its own trust decision in-protocol (a seed
|
|
19
|
+
* signature against the anchored trust policy, a formation token, an invitation's or a
|
|
20
|
+
* grant's possession proof).
|
|
20
21
|
* - `transport` — libp2p plumbing (identify, ping, hole punching, AutoNAT, the circuit-relay
|
|
21
22
|
* stop and hop protocols, WebRTC signaling). Every connection runs these, the redeeming
|
|
22
23
|
* device's own included: refusing identify would make that device treat the member as
|
|
@@ -79,6 +80,7 @@ import debug from 'debug';
|
|
|
79
80
|
import { SEED_PROTOCOL } from './seed-bootstrap.js';
|
|
80
81
|
import { FORMATION_PROTOCOL } from './strand-formation-protocol.js';
|
|
81
82
|
import { CADRE_INVITE_PROTOCOL } from './cadre-invite-protocol.js';
|
|
83
|
+
import { OWNER_GRANT_PROTOCOL } from './owner-grant-protocol.js';
|
|
82
84
|
import { WAKE_PROTOCOL } from './strand-wake-protocol.js';
|
|
83
85
|
import { STRAND_ADDR_PROTOCOL } from './strand-addr-protocol.js';
|
|
84
86
|
import { withTimeout } from './control-stream.js';
|
|
@@ -113,6 +115,19 @@ export class ControlStreamRefusedError extends Error {
|
|
|
113
115
|
export function controlNetworkName(partyId) {
|
|
114
116
|
return `control-${partyId}`;
|
|
115
117
|
}
|
|
118
|
+
/** The control database's two storage protocols, `repo` and `cluster`. */
|
|
119
|
+
function controlDatabaseProtocols(partyId) {
|
|
120
|
+
const prefix = `/optimystic/${controlNetworkName(partyId)}`;
|
|
121
|
+
return [`${prefix}/repo/1.0.0`, `${prefix}/cluster/1.0.0`];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Whether a peer whose protocol list is `protocols` serves the control database. Optimystic
|
|
125
|
+
* counts a peer into a control block's cohort by the same test (`servesThisNetwork` in db-p2p's
|
|
126
|
+
* `libp2p-key-network.ts`), before its membership check.
|
|
127
|
+
*/
|
|
128
|
+
export function servesControlDatabase(partyId, protocols) {
|
|
129
|
+
return controlDatabaseProtocols(partyId).some((protocol) => protocols.includes(protocol));
|
|
130
|
+
}
|
|
116
131
|
/**
|
|
117
132
|
* The class of every protocol a control node serves, by exact protocol id: the single
|
|
118
133
|
* declaration the guard enforces. See the module doc for what each class admits and why each
|
|
@@ -125,10 +140,10 @@ export function controlProtocolClasses(partyId) {
|
|
|
125
140
|
[SEED_PROTOCOL, 'stranger-open'],
|
|
126
141
|
[FORMATION_PROTOCOL, 'stranger-open'],
|
|
127
142
|
[CADRE_INVITE_PROTOCOL, 'stranger-open'],
|
|
143
|
+
[OWNER_GRANT_PROTOCOL, 'stranger-open'],
|
|
128
144
|
[WAKE_PROTOCOL, 'members'],
|
|
129
145
|
[STRAND_ADDR_PROTOCOL, 'members'],
|
|
130
|
-
|
|
131
|
-
[`${prefix}/cluster/1.0.0`, 'members-snapshot'],
|
|
146
|
+
...controlDatabaseProtocols(partyId).map((protocol) => [protocol, 'members-snapshot']),
|
|
132
147
|
[`${prefix}/db-p2p/sync/1.0.0`, 'members-snapshot'],
|
|
133
148
|
[`${prefix}/db-p2p/block-transfer/1.0.0`, 'members-snapshot'],
|
|
134
149
|
[`${fret}/neighbors`, 'members-snapshot'],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-protocol-guard.js","sourceRoot":"","sources":["../src/control-protocol-guard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"control-protocol-guard.js","sourceRoot":"","sources":["../src/control-protocol-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* control-schema-version.ts — the check `ControlDatabase.loadSchema` runs on a warm control
|
|
3
|
+
* store after declaring the control schema and before applying it.
|
|
4
|
+
*
|
|
5
|
+
* `apply schema CadreControl` runs whatever DDL makes the hydrated catalog equal the declared
|
|
6
|
+
* schema, and that apply is how a store written under an older control schema version is
|
|
7
|
+
* migrated in place: between consecutive versions the difference is limited to statements the
|
|
8
|
+
* optimystic module applies to a table's catalog record without rewriting a row, so running the
|
|
9
|
+
* diff from a store at version K to this build's declaration runs each version's changes from
|
|
10
|
+
* K+1 up to the build's, in order. This module decides, from the stored version and the pending
|
|
11
|
+
* DDL and before any of it runs, whether the apply may go ahead, and throws one typed error
|
|
12
|
+
* naming the reason when it may not.
|
|
13
|
+
*
|
|
14
|
+
* The same change classifier ({@link classifyControlSchemaChange}) is what
|
|
15
|
+
* `control-schema-versions.spec.ts` runs over each pair of consecutive control schema versions,
|
|
16
|
+
* so a schema edit that no store could be migrated across fails when it is made. Where the
|
|
17
|
+
* version lives, when it changes and what a new version may change: docs/architecture.md →
|
|
18
|
+
* "Control schema versions and migrations".
|
|
19
|
+
*
|
|
20
|
+
* The stored tag is the version of the last start that applied the schema to the store: the
|
|
21
|
+
* apply's `alter table OwnerKey set tags (…)` persists since Optimystic 1.16.0, so after a
|
|
22
|
+
* migrating start the store records this build's version. A store additively upgraded by a build
|
|
23
|
+
* from before that still reads the version it was created under, which is harmless: the diff is
|
|
24
|
+
* computed from the catalog, not from the number, and the number only has to tell a store that
|
|
25
|
+
* a newer release migrated from one this build can migrate.
|
|
26
|
+
*/
|
|
27
|
+
import { type Database } from '@quereus/quereus';
|
|
28
|
+
/**
|
|
29
|
+
* Why a warm control store is refused, in the order the check decides them:
|
|
30
|
+
* - `newer-release`: the store records a version above this build's, so a newer release has
|
|
31
|
+
* migrated it (this machine before a downgrade, or another machine sharing its catalog
|
|
32
|
+
* lineage); this machine is the one to update.
|
|
33
|
+
* - `before-migrations`: the store records a version below {@link CONTROL_SCHEMA_BASELINE_VERSION}
|
|
34
|
+
* (written by 1.16.0 or earlier); it cannot be migrated and must be recreated.
|
|
35
|
+
* - `not-migratable`: a pending statement would rewrite or discard rows. Reachable only from a
|
|
36
|
+
* development or hand-made store, since the edit-time check refuses such a version.
|
|
37
|
+
*/
|
|
38
|
+
export type ControlStoreIncompatibility = 'newer-release' | 'before-migrations' | 'not-migratable';
|
|
39
|
+
/** A warm control store this build cannot apply its control schema to. */
|
|
40
|
+
export declare class ControlStoreIncompatibleError extends Error {
|
|
41
|
+
readonly reason: ControlStoreIncompatibility;
|
|
42
|
+
readonly partyId: string;
|
|
43
|
+
/** The version the store records: the one its last migrating start declared; 0 = before versions were recorded. */
|
|
44
|
+
readonly storeVersion: number;
|
|
45
|
+
/** The version this build declares. */
|
|
46
|
+
readonly buildVersion: number;
|
|
47
|
+
/** The DDL the apply would have run, from `diff schema`; for diagnostics, not for parsing. */
|
|
48
|
+
readonly pendingDdl: readonly string[];
|
|
49
|
+
/** `controlStorageScope(partyId)`: the storage the embedder deletes to recreate (docs/architecture.md). */
|
|
50
|
+
readonly storageScope: string;
|
|
51
|
+
constructor(reason: ControlStoreIncompatibility, partyId: string, storeVersion: number, buildVersion: number, pendingDdl: readonly string[]);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Throw {@link ControlStoreIncompatibleError} when the declared control schema must not be applied
|
|
55
|
+
* to the hydrated store; return having run nothing otherwise, and the apply then migrates the
|
|
56
|
+
* store. Needs `declare schema CadreControl` to have run, and runs only `diff schema`, which
|
|
57
|
+
* executes no DDL.
|
|
58
|
+
*
|
|
59
|
+
* A cold store (the hydrated catalog lists no control table) passes without a diff: the apply
|
|
60
|
+
* creates everything. A warm store is refused for the first of these that holds
|
|
61
|
+
* ({@link ControlStoreIncompatibility}):
|
|
62
|
+
* - its version is above the build's: even a diff that only adds (the newer release no longer
|
|
63
|
+
* declares something this build does) would put back what that release removed, and the newer
|
|
64
|
+
* release would then refuse its own store;
|
|
65
|
+
* - its version is below the baseline, whatever the diff: a store written by 1.16.0 or earlier,
|
|
66
|
+
* which the baseline paragraph of docs/architecture.md explains cannot be migrated;
|
|
67
|
+
* - any pending statement is `unmigratable` ({@link classifyControlSchemaChange}).
|
|
68
|
+
*
|
|
69
|
+
* Every `additive` and `catalog-only` change passes, so a store from an older migratable
|
|
70
|
+
* release warm-starts and the apply brings it to this build's version. A store that is both
|
|
71
|
+
* older than the baseline and not migratable reports `before-migrations`, whose message gives the
|
|
72
|
+
* recreate advice and points at the explanation.
|
|
73
|
+
*/
|
|
74
|
+
export declare function refuseIncompatibleControlStore(db: Database, partyId: string): Promise<void>;
|
|
75
|
+
/** The version the declared control schema carries on its version table; 0 when it carries none. */
|
|
76
|
+
export declare function declaredSchemaVersion(db: Database): number;
|
|
77
|
+
/**
|
|
78
|
+
* How applying one `diff schema` statement to a warm store treats its rows, from most to least
|
|
79
|
+
* migratable (docs/architecture.md → "Control schema versions and migrations"):
|
|
80
|
+
* - `additive`: a new table, a new index, or a table tag change, which the optimystic module
|
|
81
|
+
* applies without touching a row;
|
|
82
|
+
* - `catalog-only`: a change the optimystic module applies to the table's catalog record without
|
|
83
|
+
* rewriting a row (since Optimystic 1.16.0);
|
|
84
|
+
* - `unmigratable`: everything else, which rewrites or discards rows.
|
|
85
|
+
*/
|
|
86
|
+
export type ControlSchemaChangeKind = 'additive' | 'catalog-only' | 'unmigratable';
|
|
87
|
+
/** One row of `diff schema CadreControl`, classified. */
|
|
88
|
+
export interface PendingControlSchemaChange {
|
|
89
|
+
readonly ddl: string;
|
|
90
|
+
/** The least migratable kind among the row's statements. */
|
|
91
|
+
readonly kind: ControlSchemaChangeKind;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The DDL `apply schema CadreControl` would run now, one entry per `diff schema` row, each
|
|
95
|
+
* classified against the catalog `db` holds. Needs `declare schema CadreControl` to have run.
|
|
96
|
+
*/
|
|
97
|
+
export declare function pendingControlSchemaChanges(db: Database): Promise<PendingControlSchemaChange[]>;
|