@serfab/cadre-core 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +2 -2
  2. package/dist/cadre-node.d.ts +101 -61
  3. package/dist/cadre-node.js +165 -124
  4. package/dist/cadre-node.js.map +1 -1
  5. package/dist/control-cohort.d.ts +15 -15
  6. package/dist/control-cohort.js +14 -14
  7. package/dist/control-cohort.js.map +1 -1
  8. package/dist/control-database.d.ts +51 -34
  9. package/dist/control-database.js +72 -56
  10. package/dist/control-database.js.map +1 -1
  11. package/dist/control-formation-recorder.d.ts +2 -2
  12. package/dist/control-formation-recorder.js +2 -2
  13. package/dist/control-schema.d.ts +1 -1
  14. package/dist/control-schema.js +57 -33
  15. package/dist/control-schema.js.map +1 -1
  16. package/dist/device-token.d.ts +1 -1
  17. package/dist/device-token.js +1 -1
  18. package/dist/{authority-key.d.ts → ed25519-key.d.ts} +11 -10
  19. package/dist/{authority-key.js → ed25519-key.js} +9 -10
  20. package/dist/ed25519-key.js.map +1 -0
  21. package/dist/index.d.ts +3 -3
  22. package/dist/index.js +8 -8
  23. package/dist/index.js.map +1 -1
  24. package/dist/key-store.d.ts +1 -1
  25. package/dist/key-store.js +1 -1
  26. package/dist/peer-authorization.d.ts +28 -6
  27. package/dist/peer-authorization.js +33 -7
  28. package/dist/peer-authorization.js.map +1 -1
  29. package/dist/peer-record.d.ts +3 -3
  30. package/dist/peer-record.js +3 -3
  31. package/dist/push-node.d.ts +38 -0
  32. package/dist/push-node.js +48 -0
  33. package/dist/push-node.js.map +1 -0
  34. package/dist/push-notifier-apns.d.ts +10 -2
  35. package/dist/push-notifier-apns.js +14 -10
  36. package/dist/push-notifier-apns.js.map +1 -1
  37. package/dist/push-notifier-fcm.d.ts +7 -1
  38. package/dist/push-notifier-fcm.js +9 -10
  39. package/dist/push-notifier-fcm.js.map +1 -1
  40. package/dist/push-notifier.d.ts +16 -35
  41. package/dist/push-notifier.js +16 -44
  42. package/dist/push-notifier.js.map +1 -1
  43. package/dist/seed-bootstrap.d.ts +70 -51
  44. package/dist/seed-bootstrap.js +168 -106
  45. package/dist/seed-bootstrap.js.map +1 -1
  46. package/dist/seed-trust-policy.d.ts +11 -11
  47. package/dist/seed-trust-policy.js +11 -11
  48. package/dist/seed-trust-policy.js.map +1 -1
  49. package/dist/strand-database.d.ts +5 -5
  50. package/dist/strand-database.js +4 -4
  51. package/dist/strand-database.js.map +1 -1
  52. package/dist/strand-formation-protocol.d.ts +1 -1
  53. package/dist/strand-formation-protocol.js +1 -1
  54. package/dist/strand-instance-manager.d.ts +1 -1
  55. package/dist/strand-instance-manager.js +1 -1
  56. package/dist/strand-instance-manager.js.map +1 -1
  57. package/dist/strand-member-key.d.ts +6 -6
  58. package/dist/strand-member-key.js +6 -6
  59. package/dist/strand-member-key.js.map +1 -1
  60. package/dist/strand-member-registry.d.ts +7 -7
  61. package/dist/strand-member-registry.js +5 -5
  62. package/dist/strand-member-registry.js.map +1 -1
  63. package/dist/strand-membership-writer.d.ts +84 -84
  64. package/dist/strand-membership-writer.js +90 -90
  65. package/dist/strand-membership-writer.js.map +1 -1
  66. package/dist/types.d.ts +52 -32
  67. package/dist/types.js.map +1 -1
  68. package/package.json +7 -3
  69. package/src/cadre-node.ts +170 -126
  70. package/src/control-cohort.ts +17 -17
  71. package/src/control-database.ts +76 -59
  72. package/src/control-formation-recorder.ts +2 -2
  73. package/src/control-schema.ts +57 -33
  74. package/src/device-token.ts +1 -1
  75. package/src/{authority-key.ts → ed25519-key.ts} +12 -11
  76. package/src/index.ts +21 -19
  77. package/src/key-store.ts +1 -1
  78. package/src/peer-authorization.ts +35 -7
  79. package/src/peer-record.ts +3 -3
  80. package/src/push-node.ts +64 -0
  81. package/src/push-notifier-apns.ts +16 -12
  82. package/src/push-notifier-fcm.ts +9 -10
  83. package/src/push-notifier.ts +16 -57
  84. package/src/seed-bootstrap.ts +179 -115
  85. package/src/seed-trust-policy.ts +18 -18
  86. package/src/strand-database.ts +6 -6
  87. package/src/strand-formation-protocol.ts +1 -1
  88. package/src/strand-instance-manager.ts +2 -2
  89. package/src/strand-member-key.ts +7 -7
  90. package/src/strand-member-registry.ts +10 -10
  91. package/src/strand-membership-writer.ts +127 -127
  92. package/src/types.ts +52 -32
  93. package/dist/authority-key.js.map +0 -1
@@ -2,7 +2,7 @@ import debug from 'debug';
2
2
  import type { Database } from '@quereus/quereus';
3
3
  import { digest, sign, verify, generatePrivateKey, getPublicKey } from '@optimystic/quereus-plugin-crypto';
4
4
  import type { SAppConfig } from './types.js';
5
- import type { AuthorityKeyPair } from './authority-key.js';
5
+ import type { Ed25519KeyPair } from './ed25519-key.js';
6
6
  import { canonicalDatetime } from './canonical-datetime.js';
7
7
 
8
8
  const log = debug('sereus:cadre:strand-membership');
@@ -31,7 +31,7 @@ export const STRAND_ENGINE_VERSION = '0.1.0';
31
31
  *
32
32
  * `schemas/strand.qsql` signs a single SHA-256 digest over a `'|'`-joined payload
33
33
  * (e.g. `Member.Authorized` verifies
34
- * `verify(digest(new.Key), context.AuthoritySignature, A.MemberKey, 'ed25519')`).
34
+ * `verify(digest(new.Key), context.ManagerSignature, A.MemberKey, 'ed25519')`).
35
35
  * So the signer hashes the payload to raw bytes and ed25519-signs *those bytes*:
36
36
  * SQL `digest(...)`'s default output is base64url and `verify(...)`'s default
37
37
  * `inputEncoding` is base64url, so signer and verifier operate on identical bytes.
@@ -77,16 +77,16 @@ export function verifyStrandPayload(payload: string, signatureB64: string, publi
77
77
  export interface FounderBootstrapParams {
78
78
  /** The strand id — written to `Header.Id`. */
79
79
  strandId: string;
80
- /** Strand type: `'o'` (open, Header only) or `'c'` (closed, Header+Member+Authority). */
80
+ /** Strand type: `'o'` (open, Header only) or `'c'` (closed, Header+Member+Manager). */
81
81
  type: 'o' | 'c';
82
82
  /** The sApp config whose id/version/schema/signature populate the Header. */
83
83
  sApp: SAppConfig;
84
84
  /**
85
85
  * The founder's derived strand keypair (from {@link strandMemberKeyPair}). Its
86
- * `publicKeyB64` becomes the founding `Member.Key` and `Authority.MemberKey`.
86
+ * `publicKeyB64` becomes the founding `Member.Key` and `Manager.MemberKey`.
87
87
  * Required for a closed strand; ignored for an open strand.
88
88
  */
89
- founderKeyPair?: AuthorityKeyPair;
89
+ founderKeyPair?: Ed25519KeyPair;
90
90
  }
91
91
 
92
92
  /**
@@ -96,7 +96,7 @@ export interface FounderBootstrapParams {
96
96
  * so the interpolation is not an injection surface — it just keeps the singleton
97
97
  * insert-if-absent guards terse.
98
98
  */
99
- async function strandTableCount(db: Database, table: 'Header' | 'Member' | 'Authority'): Promise<number> {
99
+ async function strandTableCount(db: Database, table: 'Header' | 'Member' | 'Manager'): Promise<number> {
100
100
  for await (const row of db.eval(`select count(1) as Count from Strand.${table}`)) {
101
101
  return (row.Count as number) ?? 0;
102
102
  }
@@ -139,7 +139,7 @@ async function insertHeaderIfAbsent(db: Database, params: FounderBootstrapParams
139
139
  * Insert the founding `Strand.Member` if no member exists yet.
140
140
  *
141
141
  * The empty-table state satisfies the `count(1) from Member <= 1` bootstrap branch
142
- * of `Member.Authorized`, so no authority signature is needed — the context fields
142
+ * of `Member.Authorized`, so no manager signature is needed — the context fields
143
143
  * are explicit nulls. Guarding on the count makes this idempotent: a re-run (the
144
144
  * founder re-`addStrand`/`resumeStrand`) finds the member present and skips.
145
145
  */
@@ -150,7 +150,7 @@ async function insertFounderMemberIfAbsent(db: Database, memberKey: string, stra
150
150
  }
151
151
  await db.exec(
152
152
  `insert into Strand.Member (Key)
153
- with context AuthorityKey = null, AuthoritySignature = null
153
+ with context ManagerKey = null, ManagerSignature = null
154
154
  values (?)`,
155
155
  [memberKey],
156
156
  );
@@ -158,26 +158,26 @@ async function insertFounderMemberIfAbsent(db: Database, memberKey: string, stra
158
158
  }
159
159
 
160
160
  /**
161
- * Insert the founding `Strand.Authority` if no authority exists yet.
161
+ * Insert the founding `Strand.Manager` if no manager exists yet.
162
162
  *
163
- * The empty-table state satisfies the `count(1) from Authority <= 1` bootstrap
164
- * branch of `Authority.Authorized`, so no signature is needed. Idempotent via the
165
- * same count guard. Must run AFTER the Header insert: `Authority.OnlyClosed` is a
163
+ * The empty-table state satisfies the `count(1) from Manager <= 1` bootstrap
164
+ * branch of `Manager.Authorized`, so no signature is needed. Idempotent via the
165
+ * same count guard. Must run AFTER the Header insert: `Manager.OnlyClosed` is a
166
166
  * deferred (subquery) check evaluated at commit, and the sequential auto-commit
167
- * Header→Member→Authority order ensures the closed `Header` is committed first.
167
+ * Header→Member→Manager order ensures the closed `Header` is committed first.
168
168
  */
169
- async function insertFounderAuthorityIfAbsent(db: Database, memberKey: string, strandId: string): Promise<void> {
170
- if (await strandTableCount(db, 'Authority') > 0) {
171
- log('Authority already present for strand %s; skipping founder Authority', strandId);
169
+ async function insertFounderManagerIfAbsent(db: Database, memberKey: string, strandId: string): Promise<void> {
170
+ if (await strandTableCount(db, 'Manager') > 0) {
171
+ log('Manager already present for strand %s; skipping founder Manager', strandId);
172
172
  return;
173
173
  }
174
174
  await db.exec(
175
- `insert into Strand.Authority (MemberKey)
176
- with context AuthorityKey = null, Signature = null
175
+ `insert into Strand.Manager (MemberKey)
176
+ with context ManagerKey = null, Signature = null
177
177
  values (?)`,
178
178
  [memberKey],
179
179
  );
180
- log('Inserted founding Authority for strand %s', strandId);
180
+ log('Inserted founding Manager for strand %s', strandId);
181
181
  }
182
182
 
183
183
  /**
@@ -189,16 +189,16 @@ async function insertFounderAuthorityIfAbsent(db: Database, memberKey: string, s
189
189
  * restart / founder re-`addStrand` is a no-op and never double-inserts.
190
190
  *
191
191
  * Behavior by strand type:
192
- * - **Open (`'o'`)**: insert `Header` only. `Member`/`Authority`/`Invite` are
192
+ * - **Open (`'o'`)**: insert `Header` only. `Member`/`Manager`/`Invite` are
193
193
  * `OnlyClosed` and would trip that constraint — they are skipped entirely.
194
194
  * - **Closed (`'c'`)**: insert `Header(Type='c')`, then the founding `Member`
195
- * (`Key = founderKeyPair.publicKeyB64`), then the founding `Authority`
195
+ * (`Key = founderKeyPair.publicKeyB64`), then the founding `Manager`
196
196
  * (`MemberKey = founderKeyPair.publicKeyB64`). Insert order matters: the
197
- * Header must commit before the deferred `OnlyClosed` checks on Member/Authority
197
+ * Header must commit before the deferred `OnlyClosed` checks on Member/Manager
198
198
  * evaluate at commit.
199
199
  *
200
200
  * A closed strand with no `founderKeyPair` throws: a closed strand with no founding
201
- * Authority could never admit anyone, so failing loudly here (which propagates out
201
+ * Manager could never admit anyone, so failing loudly here (which propagates out
202
202
  * of `StrandDatabase.initialize()` and triggers the runtime's rollback) is correct.
203
203
  *
204
204
  * @param db - The strand's Quereus database (schema already applied).
@@ -211,26 +211,26 @@ export async function bootstrapFounderMembership(db: Database, params: FounderBo
211
211
  log('Founder bootstrap for strand %s (type %s)', strandId, type);
212
212
 
213
213
  // Validate BEFORE writing anything: a closed strand with no founder key must
214
- // fail without leaving a closed `Header` that has no founding Member/Authority
214
+ // fail without leaving a closed `Header` that has no founding Member/Manager
215
215
  // (such a strand could never admit anyone).
216
216
  if (type === 'c' && !founderKeyPair) {
217
217
  throw new Error(
218
218
  `Cannot bootstrap closed strand ${strandId}: no founder key pair derived from MemberPrivateKey. ` +
219
- 'A closed strand needs a founding Member + Authority or it can never admit members.',
219
+ 'A closed strand needs a founding Member + Manager or it can never admit members.',
220
220
  );
221
221
  }
222
222
 
223
- // Header is written for every strand; Member/Authority are closed-only.
223
+ // Header is written for every strand; Member/Manager are closed-only.
224
224
  await insertHeaderIfAbsent(db, params);
225
225
 
226
226
  if (type === 'o') {
227
- // Open strand: Member/Authority/Invite are OnlyClosed — nothing else to write.
227
+ // Open strand: Member/Manager/Invite are OnlyClosed — nothing else to write.
228
228
  return;
229
229
  }
230
230
 
231
231
  const memberKey = founderKeyPair!.publicKeyB64;
232
232
  await insertFounderMemberIfAbsent(db, memberKey, strandId);
233
- await insertFounderAuthorityIfAbsent(db, memberKey, strandId);
233
+ await insertFounderManagerIfAbsent(db, memberKey, strandId);
234
234
  log('Founder bootstrap complete for closed strand %s', strandId);
235
235
  }
236
236
 
@@ -239,10 +239,10 @@ export async function bootstrapFounderMembership(db: Database, params: FounderBo
239
239
  /** Parameters for {@link issueInvite}. */
240
240
  export interface IssueInviteParams {
241
241
  /**
242
- * The issuing authority's strand keypair. Its `publicKeyB64` must already be a
243
- * `Strand.Authority` row (the `InviteValid` constraint rejects a non-authority).
242
+ * The issuing manager's strand keypair. Its `publicKeyB64` must already be a
243
+ * `Strand.Manager` row (the `InviteValid` constraint rejects a non-manager).
244
244
  */
245
- authorityKeyPair: AuthorityKeyPair;
245
+ managerKeyPair: Ed25519KeyPair;
246
246
  /**
247
247
  * Optional invite expiry as epoch milliseconds. When set, it is canonicalised
248
248
  * via {@link canonicalDatetime} so the signed payload segment byte-matches the
@@ -270,10 +270,10 @@ export interface IssuedInvite {
270
270
  *
271
271
  * Generates a fresh invite ed25519 keypair (the public key becomes `Invite.Key`),
272
272
  * builds the constraint's payload (`Key || '|' || coalesce(Expiration, '')`), and
273
- * signs it TWICE: with the authority private key (→ `AuthoritySignature`, proving
274
- * an authority issued it) and with the invite private key (→ `InviteSignature`,
273
+ * signs it TWICE: with the manager private key (→ `ManagerSignature`, proving
274
+ * a manager issued it) and with the invite private key (→ `InviteSignature`,
275
275
  * proving the issuer actually holds the invite secret). Both signatures plus the
276
- * authority public key are bound as constraint context for the `Invite` insert.
276
+ * manager public key are bound as constraint context for the `Invite` insert.
277
277
  *
278
278
  * The returned `invitePrivateKey` is the only secret the invitee needs to redeem
279
279
  * the invite — it is NOT stored in the strand (only `Invite.Key`, the public half,
@@ -281,11 +281,11 @@ export interface IssuedInvite {
281
281
  * is `InviteKey`, so a given invite can be consumed at most once.
282
282
  *
283
283
  * @param db - The closed strand's database (founder already bootstrapped).
284
- * @param params - The issuing authority keypair and optional expiry.
284
+ * @param params - The issuing manager keypair and optional expiry.
285
285
  * @returns The invite public key and the out-of-band private seed.
286
286
  */
287
287
  export async function issueInvite(db: Database, params: IssueInviteParams): Promise<IssuedInvite> {
288
- const { authorityKeyPair, expiration } = params;
288
+ const { managerKeyPair, expiration } = params;
289
289
 
290
290
  const invitePrivateKey = generatePrivateKey('ed25519', 'base64url') as string;
291
291
  const inviteKey = getPublicKey(invitePrivateKey, 'ed25519', 'base64url', 'base64url') as string;
@@ -296,14 +296,14 @@ export async function issueInvite(db: Database, params: IssueInviteParams): Prom
296
296
  const expirationColumn = expiration == null ? null : expirationSegment;
297
297
 
298
298
  const payload = `${inviteKey}|${expirationSegment}`;
299
- const authoritySignature = signStrandPayload(payload, authorityKeyPair.privateKeyB64);
299
+ const managerSignature = signStrandPayload(payload, managerKeyPair.privateKeyB64);
300
300
  const inviteSignature = signStrandPayload(payload, invitePrivateKey);
301
301
 
302
302
  await db.exec(
303
303
  `insert into Strand.Invite (Key, Expiration)
304
- with context AuthorityKey = ?, AuthoritySignature = ?, InviteSignature = ?
304
+ with context ManagerKey = ?, ManagerSignature = ?, InviteSignature = ?
305
305
  values (?, ?)`,
306
- [authorityKeyPair.publicKeyB64, authoritySignature, inviteSignature, inviteKey, expirationColumn],
306
+ [managerKeyPair.publicKeyB64, managerSignature, inviteSignature, inviteKey, expirationColumn],
307
307
  );
308
308
  log('Issued invite %s (expires=%s)', inviteKey, expirationColumn ?? 'never');
309
309
 
@@ -382,10 +382,10 @@ export async function consumeInvite(db: Database, params: ConsumeInviteParams):
382
382
  await db.beginTransaction();
383
383
  try {
384
384
  // 1. Member — admitted by the deferred invite branch (the matching
385
- // ConsumedInvite below), so no authority signature is supplied.
385
+ // ConsumedInvite below), so no manager signature is supplied.
386
386
  await db.exec(
387
387
  `insert into Strand.Member (Key)
388
- with context AuthorityKey = null, AuthoritySignature = null
388
+ with context ManagerKey = null, ManagerSignature = null
389
389
  values (?)`,
390
390
  [memberKey],
391
391
  );
@@ -413,42 +413,42 @@ export async function consumeInvite(db: Database, params: ConsumeInviteParams):
413
413
  }
414
414
  }
415
415
 
416
- /** Parameters for {@link addMemberByAuthority}. */
417
- export interface AddMemberByAuthorityParams {
416
+ /** Parameters for {@link addMemberByManager}. */
417
+ export interface AddMemberByManagerParams {
418
418
  /**
419
- * The admitting authority's strand keypair. Its `publicKeyB64` must be a
420
- * `Strand.Authority` row; it signs the new member key directly.
419
+ * The admitting manager's strand keypair. Its `publicKeyB64` must be a
420
+ * `Strand.Manager` row; it signs the new member key directly.
421
421
  */
422
- authorityKeyPair: AuthorityKeyPair;
422
+ managerKeyPair: Ed25519KeyPair;
423
423
  /** The joining member's ed25519 PUBLIC key (base64url) — the new `Member.Key`. */
424
424
  memberKey: string;
425
425
  }
426
426
 
427
427
  /**
428
- * Admit a `Member` directly by authority signature — the sibling of the invite
429
- * path on `Member.Authorized`'s direct-authority branch.
428
+ * Admit a `Member` directly by manager signature — the sibling of the invite
429
+ * path on `Member.Authorized`'s direct-manager branch.
430
430
  *
431
- * The constraint verifies `digest(new.Key)` against an `Authority` row matching
432
- * `context.AuthorityKey`, so the payload is just the member key (no `'|'` join).
433
- * No `ConsumedInvite` is involved: this is the path an authority uses to seat a
434
- * member it already trusts (e.g. an authority-side enrollment that admits a
431
+ * The constraint verifies `digest(new.Key)` against a `Manager` row matching
432
+ * `context.ManagerKey`, so the payload is just the member key (no `'|'` join).
433
+ * No `ConsumedInvite` is involved: this is the path a manager uses to seat a
434
+ * member it already trusts (e.g. a manager-side enrollment that admits a
435
435
  * party already authorised out-of-band).
436
436
  *
437
437
  * @param db - The closed strand's database.
438
- * @param params - The admitting authority keypair and the new member key.
439
- * @throws If `Member.Authorized` rejects (e.g. a non-authority key); the insert
438
+ * @param params - The admitting manager keypair and the new member key.
439
+ * @throws If `Member.Authorized` rejects (e.g. a non-manager key); the insert
440
440
  * rolls back, leaving no `Member` row.
441
441
  */
442
- export async function addMemberByAuthority(db: Database, params: AddMemberByAuthorityParams): Promise<void> {
443
- const { authorityKeyPair, memberKey } = params;
444
- const signature = signStrandPayload(memberKey, authorityKeyPair.privateKeyB64);
442
+ export async function addMemberByManager(db: Database, params: AddMemberByManagerParams): Promise<void> {
443
+ const { managerKeyPair, memberKey } = params;
444
+ const signature = signStrandPayload(memberKey, managerKeyPair.privateKeyB64);
445
445
  await db.exec(
446
446
  `insert into Strand.Member (Key)
447
- with context AuthorityKey = ?, AuthoritySignature = ?
447
+ with context ManagerKey = ?, ManagerSignature = ?
448
448
  values (?)`,
449
- [authorityKeyPair.publicKeyB64, signature, memberKey],
449
+ [managerKeyPair.publicKeyB64, signature, memberKey],
450
450
  );
451
- log('Admitted member %s by authority %s', memberKey, authorityKeyPair.publicKeyB64);
451
+ log('Admitted member %s by manager %s', memberKey, managerKeyPair.publicKeyB64);
452
452
  }
453
453
 
454
454
  // ── MemberPeer registration (a member binds its own network nodes) ─────────────
@@ -459,12 +459,12 @@ export interface RegisterMemberPeerParams {
459
459
  * The member's OWN strand keypair (`{ privateKeyB64, publicKeyB64 }`). Its
460
460
  * `publicKeyB64` becomes `MemberPeer.MemberKey` and it self-signs the binding —
461
461
  * `MemberPeer.Authorized` verifies the signature against `MemberKey` itself, so a
462
- * peer can only be registered by the very member it belongs to (no authority
462
+ * peer can only be registered by the very member it belongs to (no manager
463
463
  * involved). The founder passes its `strandMemberKeyPair`; an invited member
464
- * passes its own keypair. Typed as {@link AuthorityKeyPair} only for the shared
465
- * base64url keypair shape — no authority privilege is implied.
464
+ * passes its own keypair. Typed as {@link Ed25519KeyPair} only for the shared
465
+ * base64url keypair shape — no manager privilege is implied.
466
466
  */
467
- memberKeyPair: AuthorityKeyPair;
467
+ memberKeyPair: Ed25519KeyPair;
468
468
  /** The peer/node id (libp2p peer id string) to associate with the member. */
469
469
  peerId: string;
470
470
  }
@@ -526,115 +526,115 @@ async function memberPeerExists(db: Database, memberKey: string, peerId: string)
526
526
  return false;
527
527
  }
528
528
 
529
- // ── Authority rotation (add / remove RBAC admins) ─────────────────────────────
529
+ // ── Manager rotation (add / remove RBAC admins) ───────────────────────────────
530
530
 
531
- /** Parameters for {@link addAuthority}. */
532
- export interface AddAuthorityParams {
531
+ /** Parameters for {@link addManager}. */
532
+ export interface AddManagerParams {
533
533
  /**
534
- * An EXISTING authority's strand keypair. Its `publicKeyB64` must already be a
535
- * `Strand.Authority` row (the existing-authority branch of `Authority.Authorized`
536
- * rejects a non-authority once the founder authority exists); it signs the new
537
- * authority key and is bound as `context.AuthorityKey`.
534
+ * An EXISTING manager's strand keypair. Its `publicKeyB64` must already be a
535
+ * `Strand.Manager` row (the existing-manager branch of `Manager.Authorized`
536
+ * rejects a non-manager once the founder manager exists); it signs the new
537
+ * manager key and is bound as `context.ManagerKey`.
538
538
  */
539
- byAuthorityKeyPair: AuthorityKeyPair;
540
- /** The member key to promote — the new `Authority.MemberKey` row. */
541
- newAuthorityKey: string;
539
+ byManagerKeyPair: Ed25519KeyPair;
540
+ /** The member key to promote — the new `Manager.MemberKey` row. */
541
+ newManagerKey: string;
542
542
  }
543
543
 
544
544
  /**
545
- * Promote a member to `Authority` on the signature of an existing authority.
545
+ * Promote a member to `Manager` on the signature of an existing manager.
546
546
  *
547
- * The existing-authority branch of `Authority.Authorized` verifies
547
+ * The existing-manager branch of `Manager.Authorized` verifies
548
548
  * `digest(coalesce(new.MemberKey, old.MemberKey))` — `new.MemberKey` =
549
- * `newAuthorityKey` on insert — against an `Authority` row matching
550
- * `context.AuthorityKey`. So the signer signs the new authority key directly (no
551
- * `'|'` join) and binds itself as `context.AuthorityKey`.
549
+ * `newManagerKey` on insert — against a `Manager` row matching
550
+ * `context.ManagerKey`. So the signer signs the new manager key directly (no
551
+ * `'|'` join) and binds itself as `context.ManagerKey`.
552
552
  *
553
- * Once the founder authority exists, the schema's `(select count(1) from Authority)
553
+ * Once the founder manager exists, the schema's `(select count(1) from Manager)
554
554
  * <= 1` bootstrap shortcut no longer applies to a second add (at commit the count
555
555
  * includes the new row, so it is ≥ 2), so this genuinely exercises signature
556
- * verification — a non-authority signer, or a signature over the wrong key, is
556
+ * verification — a non-manager signer, or a signature over the wrong key, is
557
557
  * rejected.
558
558
  *
559
- * @param db - The closed strand's database (founder authority already seated).
560
- * @param params - The authorizing authority's keypair and the new authority key.
561
- * @throws If `Authority.Authorized` rejects; the insert rolls back.
559
+ * @param db - The closed strand's database (founder manager already seated).
560
+ * @param params - The authorizing manager's keypair and the new manager key.
561
+ * @throws If `Manager.Authorized` rejects; the insert rolls back.
562
562
  */
563
- export async function addAuthority(db: Database, params: AddAuthorityParams): Promise<void> {
564
- const { byAuthorityKeyPair, newAuthorityKey } = params;
565
- const signature = signStrandPayload(newAuthorityKey, byAuthorityKeyPair.privateKeyB64);
563
+ export async function addManager(db: Database, params: AddManagerParams): Promise<void> {
564
+ const { byManagerKeyPair, newManagerKey } = params;
565
+ const signature = signStrandPayload(newManagerKey, byManagerKeyPair.privateKeyB64);
566
566
  await db.exec(
567
- `insert into Strand.Authority (MemberKey)
568
- with context AuthorityKey = ?, Signature = ?
567
+ `insert into Strand.Manager (MemberKey)
568
+ with context ManagerKey = ?, Signature = ?
569
569
  values (?)`,
570
- [byAuthorityKeyPair.publicKeyB64, signature, newAuthorityKey],
570
+ [byManagerKeyPair.publicKeyB64, signature, newManagerKey],
571
571
  );
572
- log('Added authority %s by %s', newAuthorityKey, byAuthorityKeyPair.publicKeyB64);
572
+ log('Added manager %s by %s', newManagerKey, byManagerKeyPair.publicKeyB64);
573
573
  }
574
574
 
575
- /** Parameters for {@link removeAuthority}. */
576
- export interface RemoveAuthorityParams {
575
+ /** Parameters for {@link removeManager}. */
576
+ export interface RemoveManagerParams {
577
577
  /**
578
- * The keypair authorizing the removal, bound as `context.AuthorityKey` and
579
- * signing `digest(targetAuthorityKey)`. For an ADMIN removal this is a DIFFERENT
580
- * existing authority (satisfying the existing-authority branch); for a
578
+ * The keypair authorizing the removal, bound as `context.ManagerKey` and
579
+ * signing `digest(targetManagerKey)`. For an ADMIN removal this is a DIFFERENT
580
+ * existing manager (satisfying the existing-manager branch); for a
581
581
  * SELF-resignation it is the target's OWN keypair (`publicKeyB64 ===
582
- * targetAuthorityKey`, satisfying the former-authority self branch). The same
582
+ * targetManagerKey`, satisfying the former-manager self branch). The same
583
583
  * context construction satisfies whichever branch applies — see
584
- * {@link removeAuthority}.
584
+ * {@link removeManager}.
585
585
  */
586
- byAuthorityKeyPair: AuthorityKeyPair;
587
- /** The `Authority.MemberKey` row to delete. */
588
- targetAuthorityKey: string;
586
+ byManagerKeyPair: Ed25519KeyPair;
587
+ /** The `Manager.MemberKey` row to delete. */
588
+ targetManagerKey: string;
589
589
  }
590
590
 
591
591
  /**
592
- * Remove an `Authority` row — either an admin removing a different authority or an
593
- * authority resigning itself.
592
+ * Remove a `Manager` row — either an admin removing a different manager or a
593
+ * manager resigning itself.
594
594
  *
595
595
  * On a DELETE, `coalesce(new.MemberKey, old.MemberKey)` binds `old.MemberKey =
596
- * targetAuthorityKey` (there is no `new` row), so the signed payload is the target
597
- * key for BOTH accepting branches of `Authority.Authorized`, and a single context
596
+ * targetManagerKey` (there is no `new` row), so the signed payload is the target
597
+ * key for BOTH accepting branches of `Manager.Authorized`, and a single context
598
598
  * construction serves both:
599
- * - **Admin removal**: `byAuthorityKeyPair` is a different existing authority; the
600
- * existing-authority branch verifies the signature against `A.MemberKey =
601
- * context.AuthorityKey`.
602
- * - **Self-resignation**: `byAuthorityKeyPair` IS the target (its `publicKeyB64`
603
- * equals `targetAuthorityKey`); the former-authority self branch verifies
604
- * `old.MemberKey = context.AuthorityKey` and the self-signature over `old.MemberKey`.
599
+ * - **Admin removal**: `byManagerKeyPair` is a different existing manager; the
600
+ * existing-manager branch verifies the signature against `A.MemberKey =
601
+ * context.ManagerKey`.
602
+ * - **Self-resignation**: `byManagerKeyPair` IS the target (its `publicKeyB64`
603
+ * equals `targetManagerKey`); the former-manager self branch verifies
604
+ * `old.MemberKey = context.ManagerKey` and the self-signature over `old.MemberKey`.
605
605
  *
606
606
  * The caller selects the case purely by which keypair it passes — no branching here.
607
607
  *
608
608
  * KNOWN PLATFORM GAP (not fixable here): the optimystic bootstrap-mode transactor
609
609
  * evaluates deferred (subquery-bearing) CHECK constraints only on INSERT, not on
610
- * DELETE. `Authority.Authorized` is deferred, so the platform currently accepts ANY
611
- * `Authority` delete regardless of signature — `removeAuthority`'s authorization is
610
+ * DELETE. `Manager.Authorized` is deferred, so the platform currently accepts ANY
611
+ * `Manager` delete regardless of signature — `removeManager`'s authorization is
612
612
  * effectively unenforced at runtime until that gap is closed (filed as
613
613
  * `optimystic-deferred-check-not-enforced-on-delete`). This writer still builds the
614
614
  * correct, signed delete so it works unchanged once enforcement lands — exactly as
615
615
  * the invite path issues correct inserts despite the open PK-uniqueness gap.
616
616
  *
617
- * KNOWN SCHEMA HAZARD (not guarded here): even with enforcement, `Authority.
618
- * Authorized`'s `(select count(1) from Authority) <= 1` bootstrap branch is true at
617
+ * KNOWN SCHEMA HAZARD (not guarded here): even with enforcement, `Manager.
618
+ * Authorized`'s `(select count(1) from Manager) <= 1` bootstrap branch is true at
619
619
  * commit whenever a delete drops the count to ≤ 1, so removing the strand's LAST (or
620
- * second-to-last) authority would be accepted regardless of signature — and removing
620
+ * second-to-last) manager would be accepted regardless of signature — and removing
621
621
  * the last one orphans the strand (no one can ever add another). The schema does not
622
- * prevent this; a "min-one-authority" invariant is deferred to a future schema change
622
+ * prevent this; a "min-one-manager" invariant is deferred to a future schema change
623
623
  * rather than grown into this writer.
624
624
  *
625
625
  * @param db - The closed strand's database.
626
- * @param params - The authorizing keypair and the target authority key.
627
- * @throws If `Authority.Authorized` rejects (e.g. a non-authority admin removal
628
- * while > 2 authorities remain); the delete rolls back.
626
+ * @param params - The authorizing keypair and the target manager key.
627
+ * @throws If `Manager.Authorized` rejects (e.g. a non-manager admin removal
628
+ * while > 2 managers remain); the delete rolls back.
629
629
  */
630
- export async function removeAuthority(db: Database, params: RemoveAuthorityParams): Promise<void> {
631
- const { byAuthorityKeyPair, targetAuthorityKey } = params;
632
- const signature = signStrandPayload(targetAuthorityKey, byAuthorityKeyPair.privateKeyB64);
630
+ export async function removeManager(db: Database, params: RemoveManagerParams): Promise<void> {
631
+ const { byManagerKeyPair, targetManagerKey } = params;
632
+ const signature = signStrandPayload(targetManagerKey, byManagerKeyPair.privateKeyB64);
633
633
  await db.exec(
634
- `delete from Strand.Authority
635
- with context AuthorityKey = ?, Signature = ?
634
+ `delete from Strand.Manager
635
+ with context ManagerKey = ?, Signature = ?
636
636
  where MemberKey = ?`,
637
- [byAuthorityKeyPair.publicKeyB64, signature, targetAuthorityKey],
637
+ [byManagerKeyPair.publicKeyB64, signature, targetManagerKey],
638
638
  );
639
- log('Removed authority %s by %s', targetAuthorityKey, byAuthorityKeyPair.publicKeyB64);
639
+ log('Removed manager %s by %s', targetManagerKey, byManagerKeyPair.publicKeyB64);
640
640
  }