@serfab/cadre-core 1.14.0 → 1.16.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 +293 -274
- package/dist/cadre-invite-protocol.d.ts +64 -29
- package/dist/cadre-invite-protocol.js +207 -38
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-join-request.d.ts +42 -0
- package/dist/cadre-join-request.js +144 -0
- package/dist/cadre-join-request.js.map +1 -0
- package/dist/cadre-node.d.ts +375 -79
- package/dist/cadre-node.js +758 -186
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-database.d.ts +92 -12
- package/dist/control-database.js +323 -142
- package/dist/control-database.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +19 -3
- package/dist/control-schema.js.map +1 -1
- package/dist/ed25519-key.d.ts +7 -9
- package/dist/ed25519-key.js +20 -10
- package/dist/ed25519-key.js.map +1 -1
- package/dist/fetch-impl.d.ts +12 -0
- package/dist/fetch-impl.js +23 -0
- package/dist/fetch-impl.js.map +1 -0
- package/dist/file-durable-slot.js +2 -1
- package/dist/file-durable-slot.js.map +1 -1
- package/dist/file-safe-name.d.ts +25 -0
- package/dist/file-safe-name.js +29 -0
- package/dist/file-safe-name.js.map +1 -0
- package/dist/formation-approval.js +5 -11
- package/dist/formation-approval.js.map +1 -1
- package/dist/fs-atomic.d.ts +0 -19
- package/dist/fs-atomic.js +3 -24
- package/dist/fs-atomic.js.map +1 -1
- package/dist/index.d.ts +12 -5
- package/dist/index.js +31 -6
- package/dist/index.js.map +1 -1
- package/dist/invitation-bootstrap.d.ts +50 -12
- package/dist/invitation-bootstrap.js +76 -13
- package/dist/invitation-bootstrap.js.map +1 -1
- package/dist/joined-strand-store.d.ts +7 -0
- package/dist/joined-strand-store.js +9 -0
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/key-store-file.js +2 -1
- package/dist/key-store-file.js.map +1 -1
- package/dist/link-budget.d.ts +9 -4
- package/dist/link-budget.js +9 -4
- package/dist/link-budget.js.map +1 -1
- package/dist/machine-removal.d.ts +78 -0
- package/dist/machine-removal.js +166 -0
- package/dist/machine-removal.js.map +1 -0
- package/dist/membership-connection-gater.d.ts +6 -5
- package/dist/membership-connection-gater.js +6 -5
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/node-claim-payload.d.ts +55 -0
- package/dist/node-claim-payload.js +108 -4
- package/dist/node-claim-payload.js.map +1 -1
- package/dist/node-default-transports.d.ts +27 -0
- package/dist/node-default-transports.js +35 -0
- package/dist/node-default-transports.js.map +1 -0
- package/dist/node-transports.d.ts +30 -0
- package/dist/node-transports.js +40 -0
- package/dist/node-transports.js.map +1 -0
- package/dist/peer-dial.d.ts +124 -27
- package/dist/peer-dial.js +254 -30
- package/dist/peer-dial.js.map +1 -1
- package/dist/peer-join-backfill.js +6 -0
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/peer-record.d.ts +6 -45
- package/dist/peer-record.js +8 -50
- package/dist/peer-record.js.map +1 -1
- package/dist/primary-lan-address.d.ts +8 -0
- package/dist/primary-lan-address.js +46 -0
- package/dist/primary-lan-address.js.map +1 -0
- package/dist/provider/client.d.ts +131 -0
- package/dist/provider/client.js +226 -0
- package/dist/provider/client.js.map +1 -0
- package/dist/provider/descriptor.d.ts +41 -0
- package/dist/provider/descriptor.js +103 -0
- package/dist/provider/descriptor.js.map +1 -0
- package/dist/provider/index.d.ts +9 -0
- package/dist/provider/index.js +10 -0
- package/dist/provider/index.js.map +1 -0
- package/dist/provider/oauth.d.ts +125 -0
- package/dist/provider/oauth.js +199 -0
- package/dist/provider/oauth.js.map +1 -0
- package/dist/provider/rent-node.d.ts +101 -0
- package/dist/provider/rent-node.js +220 -0
- package/dist/provider/rent-node.js.map +1 -0
- package/dist/relay-addrs.d.ts +9 -4
- package/dist/relay-addrs.js +9 -4
- package/dist/relay-addrs.js.map +1 -1
- package/dist/relay-reservation.d.ts +51 -16
- package/dist/relay-reservation.js +107 -24
- package/dist/relay-reservation.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +22 -5
- package/dist/seed-bootstrap.js +48 -25
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/strand-commit-durability.d.ts +54 -0
- package/dist/strand-commit-durability.js +74 -0
- package/dist/strand-commit-durability.js.map +1 -0
- package/dist/strand-database.d.ts +23 -8
- package/dist/strand-database.js +41 -13
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +7 -1
- package/dist/strand-first-sync-gate.js +7 -1
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-protocol.js +4 -1
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-fret-addrs.d.ts +19 -1
- package/dist/strand-fret-addrs.js +27 -9
- package/dist/strand-fret-addrs.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +81 -19
- package/dist/strand-instance-manager.js +216 -105
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-move.d.ts +124 -0
- package/dist/strand-move.js +438 -0
- package/dist/strand-move.js.map +1 -0
- package/dist/strand-network-config.js +7 -6
- package/dist/strand-network-config.js.map +1 -1
- package/dist/strand-peer-redial.d.ts +113 -0
- package/dist/strand-peer-redial.js +236 -0
- package/dist/strand-peer-redial.js.map +1 -0
- package/dist/strand-wake-protocol.js +10 -7
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/types.d.ts +212 -4
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/package.json +18 -10
- package/src/cadre-invite-protocol.ts +1052 -864
- package/src/cadre-join-request.ts +167 -0
- package/src/cadre-node.ts +10003 -9381
- package/src/control-database.ts +4663 -4451
- package/src/control-schema.ts +19 -3
- package/src/ed25519-key.ts +20 -10
- package/src/fetch-impl.ts +23 -0
- package/src/file-durable-slot.ts +64 -63
- package/src/file-safe-name.ts +32 -0
- package/src/formation-approval.ts +604 -610
- package/src/fs-atomic.ts +92 -117
- package/src/index.ts +71 -4
- package/src/invitation-bootstrap.ts +91 -13
- package/src/joined-strand-store.ts +508 -498
- package/src/key-store-file.ts +142 -141
- package/src/link-budget.ts +9 -4
- package/src/machine-removal.ts +216 -0
- package/src/membership-connection-gater.ts +638 -637
- package/src/node-claim-payload.ts +138 -4
- package/src/node-default-transports.ts +38 -0
- package/src/node-transports.ts +49 -0
- package/src/peer-dial.ts +604 -336
- package/src/peer-join-backfill.ts +6 -0
- package/src/peer-record.ts +159 -202
- package/src/primary-lan-address.ts +47 -0
- package/src/provider/client.ts +326 -0
- package/src/provider/descriptor.ts +129 -0
- package/src/provider/index.ts +59 -0
- package/src/provider/oauth.ts +285 -0
- package/src/provider/rent-node.ts +275 -0
- package/src/relay-addrs.ts +9 -4
- package/src/relay-reservation.ts +1211 -1121
- package/src/seed-bootstrap.ts +1502 -1471
- package/src/strand-commit-durability.ts +116 -0
- package/src/strand-database.ts +46 -14
- package/src/strand-first-sync-gate.ts +7 -1
- package/src/strand-formation-protocol.ts +1039 -1036
- package/src/strand-fret-addrs.ts +179 -145
- package/src/strand-instance-manager.ts +2026 -1883
- package/src/strand-move.ts +585 -0
- package/src/strand-network-config.ts +7 -6
- package/src/strand-peer-redial.ts +307 -0
- package/src/strand-wake-protocol.ts +10 -7
- package/src/types.ts +220 -4
package/dist/control-database.js
CHANGED
|
@@ -140,7 +140,7 @@ function parseStoredDatetimeMs(value) {
|
|
|
140
140
|
* and "unparseable" to null — i.e. "no expiry". Shared by every `ExpiresAt`
|
|
141
141
|
* reader so the NaN guard cannot drift between them.
|
|
142
142
|
*/
|
|
143
|
-
function parseNullableStoredDatetimeMs(value) {
|
|
143
|
+
export function parseNullableStoredDatetimeMs(value) {
|
|
144
144
|
if (value === null || value === undefined)
|
|
145
145
|
return null;
|
|
146
146
|
const parsed = parseStoredDatetimeMs(value);
|
|
@@ -530,32 +530,43 @@ function joinedStrandRow(row) {
|
|
|
530
530
|
FounderOwnerKey: null,
|
|
531
531
|
};
|
|
532
532
|
}
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
533
|
+
const FORMATION_INVITE_SELECT_SQL = 'select Token, sAppId, ExpiresAt, TotalUses, ValidationUrl, StrandId from CadreControl.FormationInvite';
|
|
534
|
+
function formationInviteRowOf(row) {
|
|
535
|
+
return {
|
|
536
|
+
token: row.Token,
|
|
537
|
+
sAppId: row.sAppId,
|
|
538
|
+
expiresAtMs: parseNullableStoredDatetimeMs(row.ExpiresAt),
|
|
539
|
+
totalUses: row.TotalUses ?? null,
|
|
540
|
+
validationUrl: row.ValidationUrl ?? null,
|
|
541
|
+
strandId: row.StrandId ?? null,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
const JOIN_REQUEST_INSERT_SQL = `
|
|
545
|
+
insert into CadreControl.JoinRequest (Id, Invitation, Disclosure, RequestedAt, ExpiresAt, StampId)
|
|
546
|
+
with context OwnerKey = ?, Signature = ?
|
|
547
|
+
values (?, ?, ?, ?, ?, ?)
|
|
537
548
|
`;
|
|
538
|
-
const JOIN_SUCCESS_INSERT_SQL = `
|
|
539
|
-
insert into CadreControl.JoinSuccess (RequestStampId, RecordedAt, StrandId, MembershipInvite)
|
|
540
|
-
with context OwnerKey = ?, Signature = ?
|
|
541
|
-
values (?, ?, ?, ?)
|
|
549
|
+
const JOIN_SUCCESS_INSERT_SQL = `
|
|
550
|
+
insert into CadreControl.JoinSuccess (RequestStampId, RecordedAt, StrandId, MembershipInvite)
|
|
551
|
+
with context OwnerKey = ?, Signature = ?
|
|
552
|
+
values (?, ?, ?, ?)
|
|
542
553
|
`;
|
|
543
|
-
const JOIN_FAILURE_INSERT_SQL = `
|
|
544
|
-
insert into CadreControl.JoinFailure (RequestStampId, RecordedAt, Code, Reason)
|
|
545
|
-
with context OwnerKey = ?, Signature = ?
|
|
546
|
-
values (?, ?, ?, ?)
|
|
554
|
+
const JOIN_FAILURE_INSERT_SQL = `
|
|
555
|
+
insert into CadreControl.JoinFailure (RequestStampId, RecordedAt, Code, Reason)
|
|
556
|
+
with context OwnerKey = ?, Signature = ?
|
|
557
|
+
values (?, ?, ?, ?)
|
|
547
558
|
`;
|
|
548
559
|
/**
|
|
549
560
|
* A request with its outcome in one statement: the outcome tables are keyed by the request's
|
|
550
561
|
* stamp, so the join finds exactly this incarnation's outcome.
|
|
551
562
|
*/
|
|
552
|
-
const PENDING_JOIN_SELECT_SQL = `
|
|
553
|
-
select R.Id, R.Invitation, R.Disclosure, R.RequestedAt, R.ExpiresAt, R.StampId,
|
|
554
|
-
S.RecordedAt as JoinedAt, S.StrandId, S.MembershipInvite,
|
|
555
|
-
F.RecordedAt as FailedAt, F.Code, F.Reason
|
|
556
|
-
from CadreControl.JoinRequest R
|
|
557
|
-
left join CadreControl.JoinSuccess S on S.RequestStampId = R.StampId
|
|
558
|
-
left join CadreControl.JoinFailure F on F.RequestStampId = R.StampId
|
|
563
|
+
const PENDING_JOIN_SELECT_SQL = `
|
|
564
|
+
select R.Id, R.Invitation, R.Disclosure, R.RequestedAt, R.ExpiresAt, R.StampId,
|
|
565
|
+
S.RecordedAt as JoinedAt, S.StrandId, S.MembershipInvite,
|
|
566
|
+
F.RecordedAt as FailedAt, F.Code, F.Reason
|
|
567
|
+
from CadreControl.JoinRequest R
|
|
568
|
+
left join CadreControl.JoinSuccess S on S.RequestStampId = R.StampId
|
|
569
|
+
left join CadreControl.JoinFailure F on F.RequestStampId = R.StampId
|
|
559
570
|
`;
|
|
560
571
|
/** A {@link PENDING_JOIN_SELECT_SQL} row as read. */
|
|
561
572
|
function pendingJoinOf(row) {
|
|
@@ -1220,14 +1231,22 @@ export class ControlDatabase {
|
|
|
1220
1231
|
this.ensureInitialized();
|
|
1221
1232
|
const revoked = await this.queryRevokedStamps('CadrePeer', retry);
|
|
1222
1233
|
const rows = [];
|
|
1223
|
-
|
|
1234
|
+
// The proof columns lead and the record columns follow, so this scan keeps a prefix no
|
|
1235
|
+
// other `CadrePeer` statement shares (`queryPeerRecord` starts `PeerId, PublicKey`): the
|
|
1236
|
+
// read-retry spec rigs the membership scan by that prefix.
|
|
1237
|
+
const sql = 'select PeerId, Multiaddr, StampId, VouchOwner, VouchSig, VouchUsage, PublicKey, UpdatedAt, Profile, Sig from CadreControl.CadrePeer';
|
|
1238
|
+
for (const row of await this.readRows(sql, undefined, 'cadre-peers', retry)) {
|
|
1224
1239
|
const stampId = row.StampId ?? null;
|
|
1225
1240
|
if (stampId !== null && revoked.has(stampId)) {
|
|
1226
1241
|
continue;
|
|
1227
1242
|
}
|
|
1228
1243
|
rows.push({
|
|
1229
1244
|
peerId: row.PeerId,
|
|
1245
|
+
publicKey: row.PublicKey ?? null,
|
|
1230
1246
|
multiaddr: row.Multiaddr ?? null,
|
|
1247
|
+
updatedAt: row.UpdatedAt ?? 0,
|
|
1248
|
+
profile: row.Profile ?? null,
|
|
1249
|
+
sig: row.Sig ?? null,
|
|
1231
1250
|
stampId,
|
|
1232
1251
|
vouchOwner: row.VouchOwner ?? null,
|
|
1233
1252
|
vouchSig: row.VouchSig ?? null,
|
|
@@ -1483,7 +1502,7 @@ export class ControlDatabase {
|
|
|
1483
1502
|
async queryPeerRecord(peerId) {
|
|
1484
1503
|
this.ensureInitialized();
|
|
1485
1504
|
const revoked = await this.queryRevokedStamps('CadrePeer');
|
|
1486
|
-
for (const row of await this.readRows('select PeerId, PublicKey, Multiaddr, UpdatedAt, Sig, StampId from CadreControl.CadrePeer where PeerId = ?', [peerId], 'peer-record')) {
|
|
1505
|
+
for (const row of await this.readRows('select PeerId, PublicKey, Multiaddr, UpdatedAt, Profile, Sig, StampId from CadreControl.CadrePeer where PeerId = ?', [peerId], 'peer-record')) {
|
|
1487
1506
|
const stampId = row.StampId ?? null;
|
|
1488
1507
|
if (stampId !== null && revoked.has(stampId)) {
|
|
1489
1508
|
return null;
|
|
@@ -1494,6 +1513,7 @@ export class ControlDatabase {
|
|
|
1494
1513
|
publicKey: row.PublicKey ?? '',
|
|
1495
1514
|
addrs: multiaddr.length > 0 ? multiaddr.split(',') : [],
|
|
1496
1515
|
updatedAt: row.UpdatedAt ?? 0,
|
|
1516
|
+
profile: row.Profile ?? null,
|
|
1497
1517
|
sig: row.Sig ?? '',
|
|
1498
1518
|
};
|
|
1499
1519
|
}
|
|
@@ -1525,12 +1545,12 @@ export class ControlDatabase {
|
|
|
1525
1545
|
async updateSelfPeerRecord(record) {
|
|
1526
1546
|
this.ensureInitialized();
|
|
1527
1547
|
const multiaddr = record.addrs.join(',');
|
|
1528
|
-
await this.execWrite(`
|
|
1529
|
-
update CadreControl.CadrePeer
|
|
1530
|
-
with context OwnerKey = null, Signature = ?
|
|
1531
|
-
set Multiaddr = ?, UpdatedAt = ?, Sig = ?
|
|
1532
|
-
where PeerId = ?
|
|
1533
|
-
`, [record.sig, multiaddr, record.updatedAt, record.sig, record.peerId], 'self-record-update');
|
|
1548
|
+
await this.execWrite(`
|
|
1549
|
+
update CadreControl.CadrePeer
|
|
1550
|
+
with context OwnerKey = null, Signature = ?
|
|
1551
|
+
set Multiaddr = ?, UpdatedAt = ?, Profile = ?, Sig = ?
|
|
1552
|
+
where PeerId = ?
|
|
1553
|
+
`, [record.sig, multiaddr, record.updatedAt, record.profile, record.sig, record.peerId], 'self-record-update');
|
|
1534
1554
|
log('Self peer record updated: %s (updatedAt=%d)', record.peerId, record.updatedAt);
|
|
1535
1555
|
}
|
|
1536
1556
|
/**
|
|
@@ -1573,11 +1593,11 @@ export class ControlDatabase {
|
|
|
1573
1593
|
*/
|
|
1574
1594
|
async updateSelfDeviceToken(record) {
|
|
1575
1595
|
this.ensureInitialized();
|
|
1576
|
-
await this.execWrite(`
|
|
1577
|
-
update CadreControl.DeviceToken
|
|
1578
|
-
with context OwnerKey = null, Signature = ?
|
|
1579
|
-
set Platform = ?, Token = ?, UpdatedAt = ?, Sig = ?
|
|
1580
|
-
where PeerId = ?
|
|
1596
|
+
await this.execWrite(`
|
|
1597
|
+
update CadreControl.DeviceToken
|
|
1598
|
+
with context OwnerKey = null, Signature = ?
|
|
1599
|
+
set Platform = ?, Token = ?, UpdatedAt = ?, Sig = ?
|
|
1600
|
+
where PeerId = ?
|
|
1581
1601
|
`, [record.sig, record.platform, record.token, record.updatedAt, record.sig, record.peerId], 'device-token-update');
|
|
1582
1602
|
log('Self device token updated: %s (platform=%s, updatedAt=%d)', record.peerId, record.platform, record.updatedAt);
|
|
1583
1603
|
}
|
|
@@ -1596,10 +1616,10 @@ export class ControlDatabase {
|
|
|
1596
1616
|
// not-null/unique anti-replay constraint — the StampId is a real column value, not the
|
|
1597
1617
|
// optimystic `StampId()` SQL function.
|
|
1598
1618
|
const stampId = generateStampId(this.config.libp2pNode.peerId.toString());
|
|
1599
|
-
await this.execWrite(`
|
|
1600
|
-
insert into CadreControl.OwnerKey (Key, StampId)
|
|
1601
|
-
with context OwnerKey = null, Signature = null
|
|
1602
|
-
values (?, ?)
|
|
1619
|
+
await this.execWrite(`
|
|
1620
|
+
insert into CadreControl.OwnerKey (Key, StampId)
|
|
1621
|
+
with context OwnerKey = null, Signature = null
|
|
1622
|
+
values (?, ?)
|
|
1603
1623
|
`, [key, stampId], 'owner-key-insert');
|
|
1604
1624
|
log('Owner key inserted');
|
|
1605
1625
|
}
|
|
@@ -1642,10 +1662,10 @@ export class ControlDatabase {
|
|
|
1642
1662
|
const stampId = generateStampId(this.config.libp2pNode.peerId.toString());
|
|
1643
1663
|
const signature = signMessage(ownerKeyAddMessage(this.config.partyId, key, stampId));
|
|
1644
1664
|
// Bare `exec`: already inside the write lock, which is NOT re-entrant.
|
|
1645
|
-
await this.db.exec(`
|
|
1646
|
-
insert into CadreControl.OwnerKey (Key, StampId, VouchOwner, VouchSig)
|
|
1647
|
-
with context OwnerKey = ?, Signature = ?
|
|
1648
|
-
values (?, ?, ?, ?)
|
|
1665
|
+
await this.db.exec(`
|
|
1666
|
+
insert into CadreControl.OwnerKey (Key, StampId, VouchOwner, VouchSig)
|
|
1667
|
+
with context OwnerKey = ?, Signature = ?
|
|
1668
|
+
values (?, ?, ?, ?)
|
|
1649
1669
|
`, [ownerKey, signature, key, stampId, ownerKey, signature]);
|
|
1650
1670
|
log('Owner key %s added (vouched by %s)', key, ownerKey);
|
|
1651
1671
|
return true;
|
|
@@ -1685,6 +1705,145 @@ export class ControlDatabase {
|
|
|
1685
1705
|
return await this.deleteGuardedRow('OwnerKey', key, ownerKey, signMessage);
|
|
1686
1706
|
});
|
|
1687
1707
|
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Re-vouch a live `OwnerKey` row under this owner's signature: in ONE transaction, the
|
|
1710
|
+
* current row's guarded delete plus tombstone ({@link deleteOwnerKey}'s body) and an insert
|
|
1711
|
+
* of the same key under a fresh stamp, signed by `ownerKey` ({@link insertOwnerKeyVouched}'s
|
|
1712
|
+
* body), as {@link rewritePendingJoin} re-seats a request. `OwnerKey` has `NoUpdate`, so
|
|
1713
|
+
* this is the only way to rebind a row's voucher; the anchor derivation lets the re-add
|
|
1714
|
+
* under a fresh stamp beat the older tombstone (`deriveOwnerAnchor`). For
|
|
1715
|
+
* {@link removeMachine}: the owners a machine being removed had added keep their anchor
|
|
1716
|
+
* place everywhere once their row stands on a remaining owner's signature.
|
|
1717
|
+
*
|
|
1718
|
+
* Through {@link mutateCadrePeer}, the membership hub. The stamp read runs inside the locked
|
|
1719
|
+
* body; a row whose stamp is already retired (held without its delete) is reported as absent
|
|
1720
|
+
* rather than tombstoned twice.
|
|
1721
|
+
*
|
|
1722
|
+
* @returns `true` when the row was re-vouched, `false` when `key` is not a live owner here.
|
|
1723
|
+
* @throws when `key` is `ownerKey` (an owner cannot sign its own row's removal).
|
|
1724
|
+
*/
|
|
1725
|
+
async revouchOwnerKey(key, ownerKey, signMessage) {
|
|
1726
|
+
if (key === ownerKey) {
|
|
1727
|
+
throw new Error(`Cannot re-vouch owner key ${key}: an owner cannot re-vouch itself`);
|
|
1728
|
+
}
|
|
1729
|
+
return await this.mutateCadrePeer('owner-key-revouch', async () => {
|
|
1730
|
+
// retry: false on both reads — inside the locked write body (see queryStampId's NOTE).
|
|
1731
|
+
const stampId = await this.queryStampId('OwnerKey', key, false);
|
|
1732
|
+
if (stampId === null || (await this.queryRevokedStamps('OwnerKey', false)).has(stampId)) {
|
|
1733
|
+
log('revouchOwnerKey: %s is not a live owner here (nothing to re-vouch)', key);
|
|
1734
|
+
return false;
|
|
1735
|
+
}
|
|
1736
|
+
const removal = signGuardedRemoval(this.config.partyId, 'OwnerKey', key, stampId, signMessage);
|
|
1737
|
+
const freshStampId = generateStampId(this.config.libp2pNode.peerId.toString());
|
|
1738
|
+
const signature = signMessage(ownerKeyAddMessage(this.config.partyId, key, freshStampId));
|
|
1739
|
+
await this.inTransaction('revouch OwnerKey', async () => {
|
|
1740
|
+
await this.execGuardedRemoval(removal, ownerKey);
|
|
1741
|
+
await this.db.exec(`
|
|
1742
|
+
insert into CadreControl.OwnerKey (Key, StampId, VouchOwner, VouchSig)
|
|
1743
|
+
with context OwnerKey = ?, Signature = ?
|
|
1744
|
+
values (?, ?, ?, ?)
|
|
1745
|
+
`, [ownerKey, signature, key, freshStampId, ownerKey, signature]);
|
|
1746
|
+
});
|
|
1747
|
+
log('Owner key %s re-vouched by %s (stamp %s retired)', key, ownerKey, stampId);
|
|
1748
|
+
this.notifyGuardedDelete(removal.ref);
|
|
1749
|
+
return true;
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Remove one machine completely, in ONE transaction: its `CadrePeer` row, its `OwnerKey`
|
|
1754
|
+
* row when `target.ownerKey` names one, its `DeviceToken` row when it has one, each with
|
|
1755
|
+
* the `Revocation` tombstone retiring its stamp ({@link deleteGuardedRow}'s statement pair,
|
|
1756
|
+
* composed bare as {@link deleteStrandAndPartyKey} composes it), plus a withdrawal tombstone
|
|
1757
|
+
* for every `CadreInvite` in `target.inviteKeys` that is still live here
|
|
1758
|
+
* ({@link withdrawCadreInvite}'s body). One transaction so no reader sees the machine half
|
|
1759
|
+
* removed: a row gone but its push token resolvable, or its owner key gone but an invitation
|
|
1760
|
+
* it could redeem again still open.
|
|
1761
|
+
*
|
|
1762
|
+
* Through {@link mutateCadrePeer}, the membership hub. Every stamp is read inside the locked
|
|
1763
|
+
* body, and a row whose stamp is already retired (held without its delete) is skipped rather
|
|
1764
|
+
* than tombstoned twice. The by-name owner refusals of {@link deleteOwnerKey} apply to the
|
|
1765
|
+
* owner-key arm. Nothing to remove writes nothing and returns all false.
|
|
1766
|
+
*
|
|
1767
|
+
* The caller decides WHAT to remove, which owner key is the machine's and which invitations
|
|
1768
|
+
* it could use, from its plan (`CadreNode.planMachineRemoval`); this method only refuses to
|
|
1769
|
+
* write an inconsistent set.
|
|
1770
|
+
*
|
|
1771
|
+
* @throws when `target.ownerKey` is `ownerKey`, or is the only live owner here.
|
|
1772
|
+
*/
|
|
1773
|
+
async removeMachine(target, ownerKey, signMessage) {
|
|
1774
|
+
if (target.ownerKey === ownerKey) {
|
|
1775
|
+
throw new Error(`Cannot remove owner key ${ownerKey}: an owner cannot remove itself`);
|
|
1776
|
+
}
|
|
1777
|
+
return await this.mutateCadrePeer('machine-remove', async () => {
|
|
1778
|
+
const removals = await this.signMachineRemovals(target, signMessage);
|
|
1779
|
+
const withdrawals = await this.signCadreInviteWithdrawals(target.inviteKeys, signMessage);
|
|
1780
|
+
if (removals.length === 0 && withdrawals.length === 0) {
|
|
1781
|
+
log('removeMachine: nothing live names %s here (already removed)', target.peerId);
|
|
1782
|
+
return { removedPeer: false, removedOwnerKey: false, removedDeviceToken: false, withdrawnInvitations: [] };
|
|
1783
|
+
}
|
|
1784
|
+
await this.inTransaction('remove machine', async () => {
|
|
1785
|
+
for (const removal of removals) {
|
|
1786
|
+
await this.execGuardedRemoval(removal, ownerKey);
|
|
1787
|
+
}
|
|
1788
|
+
for (const withdrawal of withdrawals) {
|
|
1789
|
+
await this.execTombstone(withdrawal.ref, withdrawal.signature, ownerKey);
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
1792
|
+
const removed = new Set(removals.map((removal) => removal.ref.tableName));
|
|
1793
|
+
log('Machine %s removed: %s; %d invitation(s) withdrawn', target.peerId, [...removed].join(', ') || 'no row', withdrawals.length);
|
|
1794
|
+
for (const removal of removals) {
|
|
1795
|
+
this.notifyGuardedDelete(removal.ref);
|
|
1796
|
+
}
|
|
1797
|
+
for (const withdrawal of withdrawals) {
|
|
1798
|
+
this.notifyGuardedDelete(withdrawal.ref);
|
|
1799
|
+
}
|
|
1800
|
+
return {
|
|
1801
|
+
removedPeer: removed.has('CadrePeer'),
|
|
1802
|
+
removedOwnerKey: removed.has('OwnerKey'),
|
|
1803
|
+
removedDeviceToken: removed.has('DeviceToken'),
|
|
1804
|
+
withdrawnInvitations: withdrawals.map((withdrawal) => withdrawal.ref.rowKey),
|
|
1805
|
+
};
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* The guarded removals {@link removeMachine} runs, signed over each row's current stamp:
|
|
1810
|
+
* the `CadrePeer` row, the `OwnerKey` row when named, the `DeviceToken` row when present.
|
|
1811
|
+
* Inside the locked body (every read `retry: false`); one `Revocation` scan serves the
|
|
1812
|
+
* retired-stamp check for all three tables and the last-owner refusal.
|
|
1813
|
+
*/
|
|
1814
|
+
async signMachineRemovals(target, signMessage) {
|
|
1815
|
+
const retiredByTable = new Map();
|
|
1816
|
+
for (const tombstone of await this.queryRevocations(false)) {
|
|
1817
|
+
const stamps = retiredByTable.get(tombstone.tableName) ?? new Set();
|
|
1818
|
+
stamps.add(tombstone.stampId);
|
|
1819
|
+
retiredByTable.set(tombstone.tableName, stamps);
|
|
1820
|
+
}
|
|
1821
|
+
const retired = (table) => retiredByTable.get(table) ?? new Set();
|
|
1822
|
+
const liveStamp = async (table, keyValue) => {
|
|
1823
|
+
const stampId = await this.queryStampId(table, keyValue, false);
|
|
1824
|
+
return stampId !== null && !retired(table).has(stampId) ? stampId : null;
|
|
1825
|
+
};
|
|
1826
|
+
const removals = [];
|
|
1827
|
+
const peerStamp = await liveStamp('CadrePeer', target.peerId);
|
|
1828
|
+
if (peerStamp !== null) {
|
|
1829
|
+
removals.push(signGuardedRemoval(this.config.partyId, 'CadrePeer', target.peerId, peerStamp, signMessage));
|
|
1830
|
+
}
|
|
1831
|
+
if (target.ownerKey !== null) {
|
|
1832
|
+
const ownerStamp = await liveStamp('OwnerKey', target.ownerKey);
|
|
1833
|
+
if (ownerStamp !== null) {
|
|
1834
|
+
const owners = await this.queryOwnerKeyRows(false, retired('OwnerKey'));
|
|
1835
|
+
if (owners.length <= 1) {
|
|
1836
|
+
throw new Error(`Cannot remove owner key ${target.ownerKey}: cannot remove the last owner`);
|
|
1837
|
+
}
|
|
1838
|
+
removals.push(signGuardedRemoval(this.config.partyId, 'OwnerKey', target.ownerKey, ownerStamp, signMessage));
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
const tokenStamp = await liveStamp('DeviceToken', target.peerId);
|
|
1842
|
+
if (tokenStamp !== null) {
|
|
1843
|
+
removals.push(signGuardedRemoval(this.config.partyId, 'DeviceToken', target.peerId, tokenStamp, signMessage));
|
|
1844
|
+
}
|
|
1845
|
+
return removals;
|
|
1846
|
+
}
|
|
1688
1847
|
/**
|
|
1689
1848
|
* Insert a strand into the control database using an owner signature.
|
|
1690
1849
|
*
|
|
@@ -1718,10 +1877,10 @@ export class ControlDatabase {
|
|
|
1718
1877
|
const signature = signMessage(message);
|
|
1719
1878
|
// StampId is a real, unique column (single-use anti-replay), no longer a context value.
|
|
1720
1879
|
// FounderOwnerKey must equal the signing owner or the constraint rejects the row.
|
|
1721
|
-
await this.execWrite(`
|
|
1722
|
-
insert into CadreControl.Strand (Id, Type, MemberPrivateKey, StampId, FounderOwnerKey)
|
|
1723
|
-
with context OwnerKey = ?, Signature = ?
|
|
1724
|
-
values (?, ?, ?, ?, ?)
|
|
1880
|
+
await this.execWrite(`
|
|
1881
|
+
insert into CadreControl.Strand (Id, Type, MemberPrivateKey, StampId, FounderOwnerKey)
|
|
1882
|
+
with context OwnerKey = ?, Signature = ?
|
|
1883
|
+
values (?, ?, ?, ?, ?)
|
|
1725
1884
|
`, [ownerKey, signature, strandId, type, memberPrivateKey ?? null, stampId, ownerKey], 'strand-insert');
|
|
1726
1885
|
log('Strand inserted: %s', strandId);
|
|
1727
1886
|
}
|
|
@@ -1818,10 +1977,10 @@ export class ControlDatabase {
|
|
|
1818
1977
|
// Id, PrivateKey, StampId.
|
|
1819
1978
|
const message = buildAuthorizationMessage('CadreControl.StrandPartyKey', 'add', this.config.partyId, [strandId, privateKey, stampId]);
|
|
1820
1979
|
const signature = signMessage(message);
|
|
1821
|
-
await this.execWrite(`
|
|
1822
|
-
insert into CadreControl.StrandPartyKey (Id, PrivateKey, StampId)
|
|
1823
|
-
with context OwnerKey = ?, Signature = ?
|
|
1824
|
-
values (?, ?, ?)
|
|
1980
|
+
await this.execWrite(`
|
|
1981
|
+
insert into CadreControl.StrandPartyKey (Id, PrivateKey, StampId)
|
|
1982
|
+
with context OwnerKey = ?, Signature = ?
|
|
1983
|
+
values (?, ?, ?)
|
|
1825
1984
|
`, [ownerKey, signature, strandId, privateKey, stampId], 'strand-party-key-insert');
|
|
1826
1985
|
log('Strand party key inserted for strand: %s', strandId);
|
|
1827
1986
|
}
|
|
@@ -1860,10 +2019,10 @@ export class ControlDatabase {
|
|
|
1860
2019
|
// Id, Type, MemberPrivateKey ('' when null), StampId.
|
|
1861
2020
|
const message = buildAuthorizationMessage('CadreControl.JoinedStrand', 'add', this.config.partyId, [row.Id, row.Type, row.MemberPrivateKey ?? '', stampId]);
|
|
1862
2021
|
const signature = signMessage(message);
|
|
1863
|
-
await this.execWrite(`
|
|
1864
|
-
insert into CadreControl.JoinedStrand (Id, Type, MemberPrivateKey, StampId)
|
|
1865
|
-
with context OwnerKey = ?, Signature = ?
|
|
1866
|
-
values (?, ?, ?, ?)
|
|
2022
|
+
await this.execWrite(`
|
|
2023
|
+
insert into CadreControl.JoinedStrand (Id, Type, MemberPrivateKey, StampId)
|
|
2024
|
+
with context OwnerKey = ?, Signature = ?
|
|
2025
|
+
values (?, ?, ?, ?)
|
|
1867
2026
|
`, [ownerKey, signature, row.Id, row.Type, row.MemberPrivateKey, stampId], 'joined-strand-insert');
|
|
1868
2027
|
log('Joined strand inserted: %s', row.Id);
|
|
1869
2028
|
}
|
|
@@ -1990,10 +2149,10 @@ export class ControlDatabase {
|
|
|
1990
2149
|
}
|
|
1991
2150
|
/** One unsigned outcome delete (see {@link deleteJoinOutcomes}; a failure's is also authorized by the join superseding it). */
|
|
1992
2151
|
async deleteJoinOutcome(table, requestStampId) {
|
|
1993
|
-
await this.db.exec(`
|
|
1994
|
-
delete from CadreControl.${table}
|
|
1995
|
-
with context OwnerKey = null, Signature = null
|
|
1996
|
-
where RequestStampId = ?
|
|
2152
|
+
await this.db.exec(`
|
|
2153
|
+
delete from CadreControl.${table}
|
|
2154
|
+
with context OwnerKey = null, Signature = null
|
|
2155
|
+
where RequestStampId = ?
|
|
1997
2156
|
`, [requestStampId]);
|
|
1998
2157
|
}
|
|
1999
2158
|
/**
|
|
@@ -2017,10 +2176,10 @@ export class ControlDatabase {
|
|
|
2017
2176
|
// Field order MUST match the schema's ValidationKey `AuthorizedInsert` verify: Key, StampId.
|
|
2018
2177
|
const message = buildAuthorizationMessage('CadreControl.ValidationKey', 'add', this.config.partyId, [key, stampId]);
|
|
2019
2178
|
const signature = signMessage(message);
|
|
2020
|
-
await this.execWrite(`
|
|
2021
|
-
insert into CadreControl.ValidationKey (Key, StampId)
|
|
2022
|
-
with context OwnerKey = ?, Signature = ?
|
|
2023
|
-
values (?, ?)
|
|
2179
|
+
await this.execWrite(`
|
|
2180
|
+
insert into CadreControl.ValidationKey (Key, StampId)
|
|
2181
|
+
with context OwnerKey = ?, Signature = ?
|
|
2182
|
+
values (?, ?)
|
|
2024
2183
|
`, [ownerKey, signature, key, stampId], 'validation-key-insert');
|
|
2025
2184
|
log('Validation key inserted: %s', key);
|
|
2026
2185
|
}
|
|
@@ -2098,11 +2257,11 @@ export class ControlDatabase {
|
|
|
2098
2257
|
// Persist the vouching (owner, signature) onto the row (VouchOwner/VouchSig) —
|
|
2099
2258
|
// identical to the context pair, which the AuthorizedInsert constraint binds — so a
|
|
2100
2259
|
// reader can later re-check the voucher against its node-local trusted-owner anchor.
|
|
2101
|
-
await this.db.exec(`
|
|
2102
|
-
insert into CadreControl.CadrePeer (PeerId, PublicKey, Multiaddr, UpdatedAt, Sig, StampId, VouchOwner, VouchSig)
|
|
2103
|
-
with context OwnerKey = ?, Signature = ?
|
|
2104
|
-
values (?, ?, ?, ?, ?, ?, ?, ?)
|
|
2105
|
-
`, [ownerKey, signature, row.peerId, row.publicKey, row.multiaddr, row.updatedAt, row.sig, stampId, ownerKey, signature]);
|
|
2260
|
+
await this.db.exec(`
|
|
2261
|
+
insert into CadreControl.CadrePeer (PeerId, PublicKey, Multiaddr, UpdatedAt, Sig, Profile, StampId, VouchOwner, VouchSig)
|
|
2262
|
+
with context OwnerKey = ?, Signature = ?
|
|
2263
|
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2264
|
+
`, [ownerKey, signature, row.peerId, row.publicKey, row.multiaddr, row.updatedAt, row.sig, row.profile, stampId, ownerKey, signature]);
|
|
2106
2265
|
return true;
|
|
2107
2266
|
});
|
|
2108
2267
|
}
|
|
@@ -2116,7 +2275,8 @@ export class ControlDatabase {
|
|
|
2116
2275
|
* local-only (its block's cluster ≤1 at insert) is pushed to the cohort once it grows, by
|
|
2117
2276
|
* re-issuing this monotonic bump. It is an UPDATE (not the original INSERT) because the row
|
|
2118
2277
|
* already exists locally; a re-INSERT would hit the `PeerId` PK. Only the freshness stamp
|
|
2119
|
-
* and voucher change — `PublicKey` / `Multiaddr` / `Sig` are left intact
|
|
2278
|
+
* and voucher change — `PublicKey` / `Multiaddr` / `Sig` / `Profile` are left intact (the
|
|
2279
|
+
* owner branch requires them kept: they are the machine's own claim) — so it is safe
|
|
2120
2280
|
* over a row whose peer has not self-published (`Sig` null); the caller must skip a row
|
|
2121
2281
|
* that already carries a self-`Sig` (that row is the owning peer's to refresh, and bumping
|
|
2122
2282
|
* `UpdatedAt` without re-signing would invalidate its self-signature).
|
|
@@ -2169,11 +2329,11 @@ export class ControlDatabase {
|
|
|
2169
2329
|
const signature = signMessage(buildAuthorizationMessage('CadreControl.CadrePeer', 'vouch', this.config.partyId, [peerId, stampId]));
|
|
2170
2330
|
await this.mutateCadrePeer('peer-reauthorize', async () => {
|
|
2171
2331
|
// Bare `exec` — inside the non-re-entrant write lock (see insertCadrePeer).
|
|
2172
|
-
await this.db.exec(`
|
|
2173
|
-
update CadreControl.CadrePeer
|
|
2174
|
-
with context OwnerKey = ?, Signature = ?
|
|
2175
|
-
set UpdatedAt = ?, VouchOwner = ?, VouchSig = ?, VouchUsage = null
|
|
2176
|
-
where PeerId = ?
|
|
2332
|
+
await this.db.exec(`
|
|
2333
|
+
update CadreControl.CadrePeer
|
|
2334
|
+
with context OwnerKey = ?, Signature = ?
|
|
2335
|
+
set UpdatedAt = ?, VouchOwner = ?, VouchSig = ?, VouchUsage = null
|
|
2336
|
+
where PeerId = ?
|
|
2177
2337
|
`, [ownerKey, signature, updatedAt, ownerKey, signature, peerId]);
|
|
2178
2338
|
});
|
|
2179
2339
|
return true;
|
|
@@ -2276,10 +2436,10 @@ export class ControlDatabase {
|
|
|
2276
2436
|
*/
|
|
2277
2437
|
async execGuardedRemoval(removal, ownerKey) {
|
|
2278
2438
|
const { tableName, rowKey, stampId } = removal.ref;
|
|
2279
|
-
await this.db.exec(`
|
|
2280
|
-
delete from CadreControl.${tableName}
|
|
2281
|
-
with context OwnerKey = ?, Signature = ?
|
|
2282
|
-
where ${GUARDED_KEY_COLUMN[tableName]} = ? and StampId = ?
|
|
2439
|
+
await this.db.exec(`
|
|
2440
|
+
delete from CadreControl.${tableName}
|
|
2441
|
+
with context OwnerKey = ?, Signature = ?
|
|
2442
|
+
where ${GUARDED_KEY_COLUMN[tableName]} = ? and StampId = ?
|
|
2283
2443
|
`, [ownerKey, removal.signature, rowKey, stampId]);
|
|
2284
2444
|
await this.execTombstone(removal.ref, removal.revocationSignature, ownerKey);
|
|
2285
2445
|
}
|
|
@@ -2298,10 +2458,10 @@ export class ControlDatabase {
|
|
|
2298
2458
|
* equal), so a node that receives the row by replication can tell which owner filed it.
|
|
2299
2459
|
*/
|
|
2300
2460
|
async execTombstone(ref, revocationSignature, ownerKey) {
|
|
2301
|
-
await this.db.exec(`
|
|
2302
|
-
insert into CadreControl.Revocation (TableName, RowKey, StampId, ReissuedAt, SignerKey, SignerSig)
|
|
2303
|
-
with context OwnerKey = ?, Signature = ?
|
|
2304
|
-
values (?, ?, ?, 0, ?, ?)
|
|
2461
|
+
await this.db.exec(`
|
|
2462
|
+
insert into CadreControl.Revocation (TableName, RowKey, StampId, ReissuedAt, SignerKey, SignerSig)
|
|
2463
|
+
with context OwnerKey = ?, Signature = ?
|
|
2464
|
+
values (?, ?, ?, 0, ?, ?)
|
|
2305
2465
|
`, [ownerKey, revocationSignature, ref.tableName, ref.rowKey, ref.stampId, ownerKey, revocationSignature]);
|
|
2306
2466
|
}
|
|
2307
2467
|
/**
|
|
@@ -2384,10 +2544,10 @@ export class ControlDatabase {
|
|
|
2384
2544
|
log('reap %s: %s holds a fresh incarnation (live stamp differs from the tombstoned one); leaving it', table, rowKey);
|
|
2385
2545
|
return false;
|
|
2386
2546
|
}
|
|
2387
|
-
const sql = `
|
|
2388
|
-
delete from CadreControl.${table}
|
|
2389
|
-
with context OwnerKey = null, Signature = null
|
|
2390
|
-
where ${GUARDED_KEY_COLUMN[table]} = ? and StampId = ?
|
|
2547
|
+
const sql = `
|
|
2548
|
+
delete from CadreControl.${table}
|
|
2549
|
+
with context OwnerKey = null, Signature = null
|
|
2550
|
+
where ${GUARDED_KEY_COLUMN[table]} = ? and StampId = ?
|
|
2391
2551
|
`;
|
|
2392
2552
|
if (table === 'CadrePeer') {
|
|
2393
2553
|
// Through mutateCadrePeer so the "EVERY CadrePeer writer goes through here"
|
|
@@ -2449,8 +2609,8 @@ export class ControlDatabase {
|
|
|
2449
2609
|
* a retired stamp, and which a revoked drone cannot produce at all. The trade is
|
|
2450
2610
|
* recurring failure noise every cycle against removing the one stale row nobody else
|
|
2451
2611
|
* reads from this node (every other node already filters it by tombstone). A revoked
|
|
2452
|
-
* node therefore keeps its own copy of its own row;
|
|
2453
|
-
*
|
|
2612
|
+
* node therefore keeps its own copy of its own row; learning that it was removed is a
|
|
2613
|
+
* separate behaviour (`CadreNode.removedFromCadre`, the `cadre:removed` event).
|
|
2454
2614
|
* `ValidationKey`, `JoinedStrand` and `JoinRequest` have no self notion and are not
|
|
2455
2615
|
* special-cased.
|
|
2456
2616
|
*
|
|
@@ -2560,11 +2720,11 @@ export class ControlDatabase {
|
|
|
2560
2720
|
// output minted per row incarnation (generateStampId), so it identifies one
|
|
2561
2721
|
// row across all TableName values. Do NOT "fix" this by adding TableName to
|
|
2562
2722
|
// the where clause.
|
|
2563
|
-
await this.db.exec(`
|
|
2564
|
-
update CadreControl.Revocation
|
|
2565
|
-
with context OwnerKey = ?, Signature = ?
|
|
2566
|
-
set ReissuedAt = ?
|
|
2567
|
-
where StampId = ?
|
|
2723
|
+
await this.db.exec(`
|
|
2724
|
+
update CadreControl.Revocation
|
|
2725
|
+
with context OwnerKey = ?, Signature = ?
|
|
2726
|
+
set ReissuedAt = ?
|
|
2727
|
+
where StampId = ?
|
|
2568
2728
|
`, [ownerKey, signature, reissuedAt, row.stampId]);
|
|
2569
2729
|
executed++;
|
|
2570
2730
|
}
|
|
@@ -2988,10 +3148,10 @@ export class ControlDatabase {
|
|
|
2988
3148
|
// Persist the canonical ExpiresAt string (datetime parse is idempotent on it) so the
|
|
2989
3149
|
// signed source-of-truth and the stored value are produced once. StampId is a real,
|
|
2990
3150
|
// unique column (single-use anti-replay), no longer a context value.
|
|
2991
|
-
await this.execWrite(`
|
|
2992
|
-
insert into CadreControl.FormationInvite (Token, sAppId, ExpiresAt, TotalUses, ValidationUrl, StrandId, StampId)
|
|
2993
|
-
with context OwnerKey = ?, Signature = ?
|
|
2994
|
-
values (?, ?, ?, ?, ?, ?, ?)
|
|
3151
|
+
await this.execWrite(`
|
|
3152
|
+
insert into CadreControl.FormationInvite (Token, sAppId, ExpiresAt, TotalUses, ValidationUrl, StrandId, StampId)
|
|
3153
|
+
with context OwnerKey = ?, Signature = ?
|
|
3154
|
+
values (?, ?, ?, ?, ?, ?, ?)
|
|
2995
3155
|
`, [
|
|
2996
3156
|
ownerKey, signature,
|
|
2997
3157
|
token, sAppId,
|
|
@@ -3060,10 +3220,10 @@ export class ControlDatabase {
|
|
|
3060
3220
|
// Hard-coded open + keyless + explicit null FounderOwnerKey: the
|
|
3061
3221
|
// consent branch admits no other shape (no signature means no
|
|
3062
3222
|
// trustworthy founder machine to record — see the schema's NOTE).
|
|
3063
|
-
await this.db.exec(`
|
|
3064
|
-
insert into CadreControl.Strand (Id, Type, MemberPrivateKey, StampId, FounderOwnerKey)
|
|
3065
|
-
with context OwnerKey = null, Signature = null
|
|
3066
|
-
values (?, 'o', null, ?, null)
|
|
3223
|
+
await this.db.exec(`
|
|
3224
|
+
insert into CadreControl.Strand (Id, Type, MemberPrivateKey, StampId, FounderOwnerKey)
|
|
3225
|
+
with context OwnerKey = null, Signature = null
|
|
3226
|
+
values (?, 'o', null, ?, null)
|
|
3067
3227
|
`, [strandId, strandStampId]);
|
|
3068
3228
|
// 2. FormationUsage row — authorised by the matching FormationInvite, and
|
|
3069
3229
|
// carrying the strand's stamp so it authorizes THIS row and no other.
|
|
@@ -3183,10 +3343,10 @@ export class ControlDatabase {
|
|
|
3183
3343
|
// robustness/consistency change, matching the strand layer's `consumeInvite`,
|
|
3184
3344
|
// not a fix for an observable mis-ordering.
|
|
3185
3345
|
const nowCanonical = await canonicalDatetime(this.db, opts.nowMs);
|
|
3186
|
-
await this.db.exec(`
|
|
3187
|
-
insert into CadreControl.FormationUsage (Token, UsageStampId, PeerKey, PeerSig, Disclosure, StrandId, StrandStampId)
|
|
3188
|
-
with context Now = ?, ValidationKey = ?, ValidationSignature = ?
|
|
3189
|
-
values (?, ?, ?, ?, ?, ?, ?)
|
|
3346
|
+
await this.db.exec(`
|
|
3347
|
+
insert into CadreControl.FormationUsage (Token, UsageStampId, PeerKey, PeerSig, Disclosure, StrandId, StrandStampId)
|
|
3348
|
+
with context Now = ?, ValidationKey = ?, ValidationSignature = ?
|
|
3349
|
+
values (?, ?, ?, ?, ?, ?, ?)
|
|
3190
3350
|
`, [
|
|
3191
3351
|
nowCanonical, opts.validationKey, opts.validationSignature,
|
|
3192
3352
|
opts.token, opts.usageStampId, opts.peerKey,
|
|
@@ -3204,18 +3364,18 @@ export class ControlDatabase {
|
|
|
3204
3364
|
*/
|
|
3205
3365
|
async queryFormationInvite(token, retry = true) {
|
|
3206
3366
|
this.ensureInitialized();
|
|
3207
|
-
const
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
return
|
|
3367
|
+
const rows = await this.readRows(`${FORMATION_INVITE_SELECT_SQL} where Token = ?`, [token], 'formation-invite', retry);
|
|
3368
|
+
return rows.length === 0 ? null : formationInviteRowOf(rows[0]);
|
|
3369
|
+
}
|
|
3370
|
+
/**
|
|
3371
|
+
* Every `FormationInvite` row, expired and spent ones included, in the shape
|
|
3372
|
+
* {@link queryFormationInvite} returns one. Read by a strand move
|
|
3373
|
+
* (`exportStrandBundle`), which decides per row what is still redeemable.
|
|
3374
|
+
*/
|
|
3375
|
+
async queryFormationInvites() {
|
|
3376
|
+
this.ensureInitialized();
|
|
3377
|
+
const rows = await this.readRows(FORMATION_INVITE_SELECT_SQL, undefined, 'formation-invites');
|
|
3378
|
+
return rows.map(formationInviteRowOf);
|
|
3219
3379
|
}
|
|
3220
3380
|
/**
|
|
3221
3381
|
* Count `FormationUsage` rows recorded against a token (uses consumed so far).
|
|
@@ -3392,10 +3552,10 @@ export class ControlDatabase {
|
|
|
3392
3552
|
}
|
|
3393
3553
|
/** Bare `CadreInvite` insert shared by {@link insertCadreInvite} and {@link seatCadreInvite}; the caller holds the lock. */
|
|
3394
3554
|
async execCadreInviteInsert(row) {
|
|
3395
|
-
await this.db.exec(`
|
|
3396
|
-
insert into CadreControl.CadreInvite (Key, PeerId, GrantsOwner, ExpiresAt, TotalUses, IssuerKey, IssuerSig, StampId)
|
|
3397
|
-
with context OwnerKey = ?, Signature = ?
|
|
3398
|
-
values (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3555
|
+
await this.db.exec(`
|
|
3556
|
+
insert into CadreControl.CadreInvite (Key, PeerId, GrantsOwner, ExpiresAt, TotalUses, IssuerKey, IssuerSig, StampId)
|
|
3557
|
+
with context OwnerKey = ?, Signature = ?
|
|
3558
|
+
values (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3399
3559
|
`, [
|
|
3400
3560
|
row.issuerKey, row.issuerSig,
|
|
3401
3561
|
row.key, row.peerId, row.grantsOwner ? 1 : 0, row.expiresAt, row.totalUses,
|
|
@@ -3571,19 +3731,12 @@ export class ControlDatabase {
|
|
|
3571
3731
|
async withdrawCadreInvite(key, ownerKey, signMessage) {
|
|
3572
3732
|
this.ensureInitialized();
|
|
3573
3733
|
const filed = await this.lockedWithRetry(async () => {
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
if (stampId === null) {
|
|
3577
|
-
log('withdrawCadreInvite: no CadreInvite row for %s (nothing to withdraw)', key);
|
|
3734
|
+
const [withdrawal] = await this.signCadreInviteWithdrawals([key], signMessage);
|
|
3735
|
+
if (withdrawal === undefined) {
|
|
3578
3736
|
return null;
|
|
3579
3737
|
}
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
return null;
|
|
3583
|
-
}
|
|
3584
|
-
const ref = { tableName: 'CadreInvite', rowKey: key, stampId };
|
|
3585
|
-
await this.execTombstone(ref, signMessage(revocationTombstoneMessage(this.config.partyId, ref)), ownerKey);
|
|
3586
|
-
return ref;
|
|
3738
|
+
await this.execTombstone(withdrawal.ref, withdrawal.signature, ownerKey);
|
|
3739
|
+
return withdrawal.ref;
|
|
3587
3740
|
}, {}, 'cadre-invite-withdraw');
|
|
3588
3741
|
if (filed === null) {
|
|
3589
3742
|
return false;
|
|
@@ -3592,6 +3745,34 @@ export class ControlDatabase {
|
|
|
3592
3745
|
this.notifyGuardedDelete(filed);
|
|
3593
3746
|
return true;
|
|
3594
3747
|
}
|
|
3748
|
+
/**
|
|
3749
|
+
* The withdrawal tombstones for the `CadreInvite` rows among `keys` that are held here and
|
|
3750
|
+
* not yet withdrawn, signed and ready for {@link execTombstone}: the body
|
|
3751
|
+
* {@link withdrawCadreInvite} files one of and {@link removeMachine} files several of in one
|
|
3752
|
+
* transaction. Inside the locked body (every read `retry: false`); an absent or already
|
|
3753
|
+
* withdrawn key is logged and left out.
|
|
3754
|
+
*/
|
|
3755
|
+
async signCadreInviteWithdrawals(keys, signMessage) {
|
|
3756
|
+
if (keys.length === 0) {
|
|
3757
|
+
return [];
|
|
3758
|
+
}
|
|
3759
|
+
const withdrawn = await this.queryRevokedStamps('CadreInvite', false);
|
|
3760
|
+
const withdrawals = [];
|
|
3761
|
+
for (const key of keys) {
|
|
3762
|
+
const stampId = await this.queryStampId('CadreInvite', key, false);
|
|
3763
|
+
if (stampId === null) {
|
|
3764
|
+
log('withdrawCadreInvite: no CadreInvite row for %s (nothing to withdraw)', key);
|
|
3765
|
+
continue;
|
|
3766
|
+
}
|
|
3767
|
+
if (withdrawn.has(stampId)) {
|
|
3768
|
+
log('withdrawCadreInvite: %s already withdrawn (stamp %s retired)', key, stampId);
|
|
3769
|
+
continue;
|
|
3770
|
+
}
|
|
3771
|
+
const ref = { tableName: 'CadreInvite', rowKey: key, stampId };
|
|
3772
|
+
withdrawals.push({ ref, signature: signMessage(revocationTombstoneMessage(this.config.partyId, ref)) });
|
|
3773
|
+
}
|
|
3774
|
+
return withdrawals;
|
|
3775
|
+
}
|
|
3595
3776
|
/**
|
|
3596
3777
|
* Redeem a seated `CadreInvite` on the owner's behalf: admit `peerId` as a cadre member and,
|
|
3597
3778
|
* when the invitation grants ownership, as an owner — with NO owner signature. One
|
|
@@ -3647,23 +3828,23 @@ export class ControlDatabase {
|
|
|
3647
3828
|
const nowCanonical = await canonicalDatetime(this.db, nowMs);
|
|
3648
3829
|
await this.inTransaction('cadre-invite-redeem', async () => {
|
|
3649
3830
|
if (seated.peerStampId === null) {
|
|
3650
|
-
await this.db.exec(`
|
|
3651
|
-
insert into CadreControl.CadrePeer (PeerId, PublicKey, Multiaddr, UpdatedAt, Sig, StampId, VouchOwner, VouchSig, VouchUsage)
|
|
3652
|
-
with context OwnerKey = null, Signature = null
|
|
3653
|
-
values (?, ?, ?, ?, null, ?, ?, null, ?)
|
|
3831
|
+
await this.db.exec(`
|
|
3832
|
+
insert into CadreControl.CadrePeer (PeerId, PublicKey, Multiaddr, UpdatedAt, Sig, StampId, VouchOwner, VouchSig, VouchUsage)
|
|
3833
|
+
with context OwnerKey = null, Signature = null
|
|
3834
|
+
values (?, ?, ?, ?, null, ?, ?, null, ?)
|
|
3654
3835
|
`, [peerId, peerKey, multiaddr, nowMs, peerStampId, invite.issuerKey, usageStampId]);
|
|
3655
3836
|
}
|
|
3656
3837
|
if (ownerStampId !== null && seated.ownerStampId === null) {
|
|
3657
|
-
await this.db.exec(`
|
|
3658
|
-
insert into CadreControl.OwnerKey (Key, StampId, VouchOwner, VouchSig, VouchUsage)
|
|
3659
|
-
with context OwnerKey = null, Signature = null
|
|
3660
|
-
values (?, ?, ?, null, ?)
|
|
3838
|
+
await this.db.exec(`
|
|
3839
|
+
insert into CadreControl.OwnerKey (Key, StampId, VouchOwner, VouchSig, VouchUsage)
|
|
3840
|
+
with context OwnerKey = null, Signature = null
|
|
3841
|
+
values (?, ?, ?, null, ?)
|
|
3661
3842
|
`, [peerKey, ownerStampId, invite.issuerKey, usageStampId]);
|
|
3662
3843
|
}
|
|
3663
|
-
await this.db.exec(`
|
|
3664
|
-
insert into CadreControl.CadreInviteUsage (UsageStampId, InviteKey, PeerId, PeerKey, PeerStampId, OwnerStampId, InviteSig, PeerSig)
|
|
3665
|
-
with context Now = ?
|
|
3666
|
-
values (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3844
|
+
await this.db.exec(`
|
|
3845
|
+
insert into CadreControl.CadreInviteUsage (UsageStampId, InviteKey, PeerId, PeerKey, PeerStampId, OwnerStampId, InviteSig, PeerSig)
|
|
3846
|
+
with context Now = ?
|
|
3847
|
+
values (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3667
3848
|
`, [nowCanonical, usageStampId, inviteKey, peerId, peerKey, peerStampId, ownerStampId, inviteSig, peerSig]);
|
|
3668
3849
|
});
|
|
3669
3850
|
log('Redeemed cadre invitation %s: %s admitted (owner=%s, usage %s)', inviteKey, peerId, ownerStampId !== null, usageStampId);
|