@serfab/cadre-core 0.6.0 → 0.8.1
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 +9 -8
- package/dist/authority-key.d.ts +41 -0
- package/dist/authority-key.js +52 -0
- package/dist/authority-key.js.map +1 -0
- package/dist/cadre-node.d.ts +777 -11
- package/dist/cadre-node.js +1912 -82
- package/dist/cadre-node.js.map +1 -1
- package/dist/canonical-datetime.d.ts +33 -0
- package/dist/canonical-datetime.js +38 -0
- package/dist/canonical-datetime.js.map +1 -0
- package/dist/canonical-json.d.ts +12 -0
- package/dist/canonical-json.js +33 -0
- package/dist/canonical-json.js.map +1 -0
- package/dist/control-cohort.d.ts +63 -0
- package/dist/control-cohort.js +80 -0
- package/dist/control-cohort.js.map +1 -0
- package/dist/control-database.d.ts +259 -5
- package/dist/control-database.js +541 -128
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +89 -0
- package/dist/control-formation-recorder.js +139 -0
- package/dist/control-formation-recorder.js.map +1 -0
- package/dist/control-schema.d.ts +12 -0
- package/dist/control-schema.js +199 -0
- package/dist/control-schema.js.map +1 -0
- package/dist/control-stream.d.ts +54 -0
- package/dist/control-stream.js +104 -0
- package/dist/control-stream.js.map +1 -0
- package/dist/device-token.d.ts +50 -0
- package/dist/device-token.js +66 -0
- package/dist/device-token.js.map +1 -0
- package/dist/diagnostics/connection-path.d.ts +118 -0
- package/dist/diagnostics/connection-path.js +204 -0
- package/dist/diagnostics/connection-path.js.map +1 -0
- package/dist/diagnostics/webrtc-turn-tracker.d.ts +50 -0
- package/dist/diagnostics/webrtc-turn-tracker.js +196 -0
- package/dist/diagnostics/webrtc-turn-tracker.js.map +1 -0
- package/dist/hibernation-manager.d.ts +79 -0
- package/dist/hibernation-manager.js +173 -23
- package/dist/hibernation-manager.js.map +1 -1
- package/dist/index.d.ts +25 -4
- package/dist/index.js +59 -4
- package/dist/index.js.map +1 -1
- package/dist/key-store-file.d.ts +52 -0
- package/dist/key-store-file.js +194 -0
- package/dist/key-store-file.js.map +1 -0
- package/dist/key-store.d.ts +75 -0
- package/dist/key-store.js +66 -0
- package/dist/key-store.js.map +1 -0
- package/dist/peer-authorization.d.ts +26 -0
- package/dist/peer-authorization.js +40 -0
- package/dist/peer-authorization.js.map +1 -0
- package/dist/peer-record.d.ts +89 -0
- package/dist/peer-record.js +117 -0
- package/dist/peer-record.js.map +1 -0
- package/dist/push-credentials.d.ts +34 -0
- package/dist/push-credentials.js +81 -0
- package/dist/push-credentials.js.map +1 -0
- package/dist/push-fanout.d.ts +137 -0
- package/dist/push-fanout.js +197 -0
- package/dist/push-fanout.js.map +1 -0
- package/dist/push-notifier-apns.d.ts +53 -0
- package/dist/push-notifier-apns.js +195 -0
- package/dist/push-notifier-apns.js.map +1 -0
- package/dist/push-notifier-fcm.d.ts +38 -0
- package/dist/push-notifier-fcm.js +183 -0
- package/dist/push-notifier-fcm.js.map +1 -0
- package/dist/push-notifier-shared.d.ts +18 -0
- package/dist/push-notifier-shared.js +25 -0
- package/dist/push-notifier-shared.js.map +1 -0
- package/dist/push-notifier.d.ts +71 -0
- package/dist/push-notifier.js +50 -0
- package/dist/push-notifier.js.map +1 -0
- package/dist/schema-verification.d.ts +11 -2
- package/dist/schema-verification.js +15 -4
- package/dist/schema-verification.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +210 -7
- package/dist/seed-bootstrap.js +429 -131
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.d.ts +63 -0
- package/dist/seed-trust-policy.js +56 -0
- package/dist/seed-trust-policy.js.map +1 -0
- package/dist/strand-addr-protocol.d.ts +132 -0
- package/dist/strand-addr-protocol.js +313 -0
- package/dist/strand-addr-protocol.js.map +1 -0
- package/dist/strand-cohort.d.ts +54 -0
- package/dist/strand-cohort.js +38 -0
- package/dist/strand-cohort.js.map +1 -0
- package/dist/strand-database.d.ts +54 -6
- package/dist/strand-database.js +88 -89
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +79 -19
- package/dist/strand-formation-manager.js +196 -104
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +162 -0
- package/dist/strand-formation-protocol.js +279 -0
- package/dist/strand-formation-protocol.js.map +1 -0
- package/dist/strand-instance-manager.d.ts +73 -4
- package/dist/strand-instance-manager.js +167 -41
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +29 -0
- package/dist/strand-member-key.js +38 -0
- package/dist/strand-member-key.js.map +1 -0
- package/dist/strand-member-registry.d.ts +98 -0
- package/dist/strand-member-registry.js +129 -0
- package/dist/strand-member-registry.js.map +1 -0
- package/dist/strand-membership-writer.d.ts +361 -0
- package/dist/strand-membership-writer.js +449 -0
- package/dist/strand-membership-writer.js.map +1 -0
- package/dist/strand-solicitation.d.ts +77 -2
- package/dist/strand-solicitation.js +25 -4
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-payload.d.ts +30 -0
- package/dist/strand-wake-payload.js +20 -0
- package/dist/strand-wake-payload.js.map +1 -0
- package/dist/strand-wake-protocol.d.ts +114 -0
- package/dist/strand-wake-protocol.js +257 -0
- package/dist/strand-wake-protocol.js.map +1 -0
- package/dist/strand-watcher.d.ts +6 -2
- package/dist/strand-watcher.js +57 -17
- package/dist/strand-watcher.js.map +1 -1
- package/dist/types.d.ts +413 -23
- package/dist/types.js +22 -5
- package/dist/types.js.map +1 -1
- package/package.json +14 -8
- package/src/authority-key.ts +68 -0
- package/src/cadre-node.ts +2937 -929
- package/src/canonical-datetime.ts +39 -0
- package/src/canonical-json.ts +30 -0
- package/src/control-cohort.ts +103 -0
- package/src/control-database.ts +931 -383
- package/src/control-formation-recorder.ts +151 -0
- package/src/control-schema.ts +198 -0
- package/src/control-stream.ts +128 -0
- package/src/device-token.ts +89 -0
- package/src/diagnostics/connection-path.ts +277 -0
- package/src/diagnostics/webrtc-turn-tracker.ts +222 -0
- package/src/hibernation-manager.ts +197 -28
- package/src/index.ts +273 -79
- package/src/key-store-file.ts +203 -0
- package/src/key-store.ts +99 -0
- package/src/peer-authorization.ts +53 -0
- package/src/peer-record.ts +146 -0
- package/src/push-credentials.ts +84 -0
- package/src/push-fanout.ts +272 -0
- package/src/push-notifier-apns.ts +243 -0
- package/src/push-notifier-fcm.ts +221 -0
- package/src/push-notifier-shared.ts +28 -0
- package/src/push-notifier.ts +93 -0
- package/src/schema-verification.ts +18 -4
- package/src/seed-bootstrap.ts +504 -171
- package/src/seed-trust-policy.ts +107 -0
- package/src/strand-addr-protocol.ts +410 -0
- package/src/strand-cohort.ts +77 -0
- package/src/strand-database.ts +113 -126
- package/src/strand-formation-manager.ts +233 -142
- package/src/strand-formation-protocol.ts +391 -0
- package/src/strand-instance-manager.ts +207 -56
- package/src/strand-member-key.ts +39 -0
- package/src/strand-member-registry.ts +177 -0
- package/src/strand-membership-writer.ts +640 -0
- package/src/strand-solicitation.ts +93 -4
- package/src/strand-wake-payload.ts +32 -0
- package/src/strand-wake-protocol.ts +331 -0
- package/src/strand-watcher.ts +61 -17
- package/src/types.ts +1012 -575
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import type { Database } from '@quereus/quereus';
|
|
2
|
+
import type { SAppConfig } from './types.js';
|
|
3
|
+
import type { AuthorityKeyPair } from './authority-key.js';
|
|
4
|
+
/**
|
|
5
|
+
* The engine (rules-logic system) assumed to manage every strand. Persisted in
|
|
6
|
+
* `Strand.Header.Engine`.
|
|
7
|
+
*
|
|
8
|
+
* PLACEHOLDER SEAM: there is no engine-selection mechanism yet — Quereus is the
|
|
9
|
+
* only engine. When a real engine-config seam lands, the founder bootstrap should
|
|
10
|
+
* read the strand's chosen engine from there instead of this constant. Documented
|
|
11
|
+
* deliberately so the column is non-null and self-describing in the meantime.
|
|
12
|
+
*/
|
|
13
|
+
export declare const STRAND_ENGINE = "quereus";
|
|
14
|
+
/**
|
|
15
|
+
* The pinned engine version recorded in `Strand.Header.EngineVersion`. A pinned
|
|
16
|
+
* string for the same reason as {@link STRAND_ENGINE}: it is a documented
|
|
17
|
+
* placeholder until an engine-selection seam supplies the real running version.
|
|
18
|
+
*/
|
|
19
|
+
export declare const STRAND_ENGINE_VERSION = "0.1.0";
|
|
20
|
+
/**
|
|
21
|
+
* The single-digest ed25519 signer the `Strand.*` membership/RBAC constraints
|
|
22
|
+
* verify against.
|
|
23
|
+
*
|
|
24
|
+
* `schemas/strand.qsql` signs a single SHA-256 digest over a `'|'`-joined payload
|
|
25
|
+
* (e.g. `Member.Authorized` verifies
|
|
26
|
+
* `verify(digest(new.Key), context.AuthoritySignature, A.MemberKey, 'ed25519')`).
|
|
27
|
+
* So the signer hashes the payload to raw bytes and ed25519-signs *those bytes*:
|
|
28
|
+
* SQL `digest(...)`'s default output is base64url and `verify(...)`'s default
|
|
29
|
+
* `inputEncoding` is base64url, so signer and verifier operate on identical bytes.
|
|
30
|
+
* This differs from the control-layer's multi-field `buildAuthorizationMessage`
|
|
31
|
+
* (a single digest over MANY fields) — the strand layer is a single digest over ONE
|
|
32
|
+
* joined payload field.
|
|
33
|
+
*
|
|
34
|
+
* All strand keys are ed25519, so the explicit `'ed25519'` curve arg is mandatory
|
|
35
|
+
* (the crypto plugin otherwise defaults to secp256k1). Mirrors the proven
|
|
36
|
+
* `signItem` helper in the `rbac-signed-write` integration scenario.
|
|
37
|
+
*
|
|
38
|
+
* @param payload - The `'|'`-joined payload the matching constraint hashes.
|
|
39
|
+
* @param privateKeyB64 - The signer's base64url ed25519 private seed.
|
|
40
|
+
* @returns The base64url ed25519 signature over the payload's SHA-256 digest.
|
|
41
|
+
*/
|
|
42
|
+
export declare function signStrandPayload(payload: string, privateKeyB64: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* The verifier counterpart to {@link signStrandPayload}.
|
|
45
|
+
*
|
|
46
|
+
* Mirrors what every `Strand.*` constraint computes:
|
|
47
|
+
* `verify(digest(payload), signature, publicKey, 'ed25519')`.
|
|
48
|
+
* SQL `digest`'s default output and `verify`'s default `inputEncoding`/`sigEncoding`/
|
|
49
|
+
* `keyEncoding` are all base64url, so the off-engine check operates on the exact
|
|
50
|
+
* same bytes the in-engine CHECK does. Used by the off-engine `MemberVerifier`
|
|
51
|
+
* (pre-flight registration checks) so the on-engine constraint is not the only
|
|
52
|
+
* place a member self-proof is validated.
|
|
53
|
+
*
|
|
54
|
+
* @param payload - The `'|'`-joined payload the matching constraint hashes.
|
|
55
|
+
* @param signatureB64 - The base64url ed25519 signature to check.
|
|
56
|
+
* @param publicKeyB64 - The base64url ed25519 public key to verify against.
|
|
57
|
+
* @returns `true` iff the signature is valid for the payload under that key.
|
|
58
|
+
*/
|
|
59
|
+
export declare function verifyStrandPayload(payload: string, signatureB64: string, publicKeyB64: string): boolean;
|
|
60
|
+
/** Parameters for {@link bootstrapFounderMembership}. */
|
|
61
|
+
export interface FounderBootstrapParams {
|
|
62
|
+
/** The strand id — written to `Header.Id`. */
|
|
63
|
+
strandId: string;
|
|
64
|
+
/** Strand type: `'o'` (open, Header only) or `'c'` (closed, Header+Member+Authority). */
|
|
65
|
+
type: 'o' | 'c';
|
|
66
|
+
/** The sApp config whose id/version/schema/signature populate the Header. */
|
|
67
|
+
sApp: SAppConfig;
|
|
68
|
+
/**
|
|
69
|
+
* The founder's derived strand keypair (from {@link strandMemberKeyPair}). Its
|
|
70
|
+
* `publicKeyB64` becomes the founding `Member.Key` and `Authority.MemberKey`.
|
|
71
|
+
* Required for a closed strand; ignored for an open strand.
|
|
72
|
+
*/
|
|
73
|
+
founderKeyPair?: AuthorityKeyPair;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Founder-only one-time bootstrap of a strand's `Strand.*` membership/RBAC rows.
|
|
77
|
+
*
|
|
78
|
+
* Runs once at bring-up on the strand's FOUNDER (the party that provisioned and
|
|
79
|
+
* published the strand). A joiner never calls this — it receives these rows via
|
|
80
|
+
* Optimystic sync. Every write is insert-if-absent (guarded by a row count) so a
|
|
81
|
+
* restart / founder re-`addStrand` is a no-op and never double-inserts.
|
|
82
|
+
*
|
|
83
|
+
* Behavior by strand type:
|
|
84
|
+
* - **Open (`'o'`)**: insert `Header` only. `Member`/`Authority`/`Invite` are
|
|
85
|
+
* `OnlyClosed` and would trip that constraint — they are skipped entirely.
|
|
86
|
+
* - **Closed (`'c'`)**: insert `Header(Type='c')`, then the founding `Member`
|
|
87
|
+
* (`Key = founderKeyPair.publicKeyB64`), then the founding `Authority`
|
|
88
|
+
* (`MemberKey = founderKeyPair.publicKeyB64`). Insert order matters: the
|
|
89
|
+
* Header must commit before the deferred `OnlyClosed` checks on Member/Authority
|
|
90
|
+
* evaluate at commit.
|
|
91
|
+
*
|
|
92
|
+
* A closed strand with no `founderKeyPair` throws: a closed strand with no founding
|
|
93
|
+
* Authority could never admit anyone, so failing loudly here (which propagates out
|
|
94
|
+
* of `StrandDatabase.initialize()` and triggers the runtime's rollback) is correct.
|
|
95
|
+
*
|
|
96
|
+
* @param db - The strand's Quereus database (schema already applied).
|
|
97
|
+
* @param params - Strand id/type, the sApp config for the Header, and (closed only)
|
|
98
|
+
* the derived founder keypair.
|
|
99
|
+
* @throws If `type === 'c'` and no `founderKeyPair` is supplied.
|
|
100
|
+
*/
|
|
101
|
+
export declare function bootstrapFounderMembership(db: Database, params: FounderBootstrapParams): Promise<void>;
|
|
102
|
+
/** Parameters for {@link issueInvite}. */
|
|
103
|
+
export interface IssueInviteParams {
|
|
104
|
+
/**
|
|
105
|
+
* The issuing authority's strand keypair. Its `publicKeyB64` must already be a
|
|
106
|
+
* `Strand.Authority` row (the `InviteValid` constraint rejects a non-authority).
|
|
107
|
+
*/
|
|
108
|
+
authorityKeyPair: AuthorityKeyPair;
|
|
109
|
+
/**
|
|
110
|
+
* Optional invite expiry as epoch milliseconds. When set, it is canonicalised
|
|
111
|
+
* via {@link canonicalDatetime} so the signed payload segment byte-matches the
|
|
112
|
+
* `datetime`-coerced `Invite.Expiration` the deferred CHECK sees. When omitted,
|
|
113
|
+
* the invite never expires and the signed segment is `''` (matching the schema's
|
|
114
|
+
* `coalesce(new.Expiration, '')`).
|
|
115
|
+
*/
|
|
116
|
+
expiration?: number;
|
|
117
|
+
}
|
|
118
|
+
/** The minted invite: a public key (the `Invite.Key`) plus its private seed. */
|
|
119
|
+
export interface IssuedInvite {
|
|
120
|
+
/** The invite ed25519 PUBLIC key (base64url) — the `Invite.Key` primary key. */
|
|
121
|
+
inviteKey: string;
|
|
122
|
+
/**
|
|
123
|
+
* The invite ed25519 PRIVATE seed (base64url). Handed out-of-band to the
|
|
124
|
+
* invitee; whoever holds it can {@link consumeInvite} exactly once. NEVER
|
|
125
|
+
* persisted in the strand — only the public key is.
|
|
126
|
+
*/
|
|
127
|
+
invitePrivateKey: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Issue a single-use invitation to join a closed strand.
|
|
131
|
+
*
|
|
132
|
+
* Generates a fresh invite ed25519 keypair (the public key becomes `Invite.Key`),
|
|
133
|
+
* builds the constraint's payload (`Key || '|' || coalesce(Expiration, '')`), and
|
|
134
|
+
* signs it TWICE: with the authority private key (→ `AuthoritySignature`, proving
|
|
135
|
+
* an authority issued it) and with the invite private key (→ `InviteSignature`,
|
|
136
|
+
* proving the issuer actually holds the invite secret). Both signatures plus the
|
|
137
|
+
* authority public key are bound as constraint context for the `Invite` insert.
|
|
138
|
+
*
|
|
139
|
+
* The returned `invitePrivateKey` is the only secret the invitee needs to redeem
|
|
140
|
+
* the invite — it is NOT stored in the strand (only `Invite.Key`, the public half,
|
|
141
|
+
* is). Single-use is enforced at consumption time: `ConsumedInvite`'s primary key
|
|
142
|
+
* is `InviteKey`, so a given invite can be consumed at most once.
|
|
143
|
+
*
|
|
144
|
+
* @param db - The closed strand's database (founder already bootstrapped).
|
|
145
|
+
* @param params - The issuing authority keypair and optional expiry.
|
|
146
|
+
* @returns The invite public key and the out-of-band private seed.
|
|
147
|
+
*/
|
|
148
|
+
export declare function issueInvite(db: Database, params: IssueInviteParams): Promise<IssuedInvite>;
|
|
149
|
+
/** Parameters for {@link consumeInvite}. */
|
|
150
|
+
export interface ConsumeInviteParams {
|
|
151
|
+
/** The `Invite.Key` (invite PUBLIC key, base64url) being redeemed. */
|
|
152
|
+
inviteKey: string;
|
|
153
|
+
/** The matching invite PRIVATE seed (base64url) received out-of-band. */
|
|
154
|
+
invitePrivateKey: string;
|
|
155
|
+
/** The joining member's ed25519 PUBLIC key (base64url) — the new `Member.Key`. */
|
|
156
|
+
memberKey: string;
|
|
157
|
+
/**
|
|
158
|
+
* The instant (epoch ms) to compare against the invite's `Expiration` for the
|
|
159
|
+
* `NotExpired` gate. Defaults to `Date.now()`; tests pin it so the comparison is
|
|
160
|
+
* deterministic. Mirrors the control-layer `redeemInvitation`'s `nowMs` convention.
|
|
161
|
+
*/
|
|
162
|
+
nowMs?: number;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Redeem an invite to admit a new `Member`, atomically.
|
|
166
|
+
*
|
|
167
|
+
* `Member.Authorized`'s invite branch needs a `ConsumedInvite` row, while
|
|
168
|
+
* `ConsumedInvite`'s `MemberExists`/`MemberValid` need the `Member` row — a
|
|
169
|
+
* circular dependency. Both are deferred (subquery-bearing) checks that evaluate
|
|
170
|
+
* at COMMIT, so inserting `Member` then `ConsumedInvite` inside one explicit
|
|
171
|
+
* transaction lets both rows exist when the deferred checks fire. This mirrors
|
|
172
|
+
* `ControlDatabase.redeemInvitation` (Strand + FormationUsage in one txn).
|
|
173
|
+
*
|
|
174
|
+
* The `ConsumedInvite` insert carries an `InviteSignature` over
|
|
175
|
+
* `InviteKey || '|' || MemberKey`, proving the consumer holds the invite private
|
|
176
|
+
* key (the on-engine `ValidUsage` gate). The `Member` insert needs no member
|
|
177
|
+
* signature — its admission is the existence of the matching `ConsumedInvite`.
|
|
178
|
+
*
|
|
179
|
+
* Single-use: `ConsumedInvite`'s primary key is `InviteKey`, so a second consume
|
|
180
|
+
* of the same invite is rejected by the PK (a distinct layer from the control
|
|
181
|
+
* network's `FormationUsage` single-use, which gates strand FORMATION, not the
|
|
182
|
+
* per-strand member join enforced here).
|
|
183
|
+
*
|
|
184
|
+
* Expiry: the `ConsumedInvite.NotExpired` deferred check rejects redeeming an invite
|
|
185
|
+
* whose `Expiration` is at or before `context.Now`. `Now` is supplied here as a
|
|
186
|
+
* canonical-datetime string from `canonicalDatetime(db, nowMs)` — the SAME transform
|
|
187
|
+
* `issueInvite` uses to store `Invite.Expiration` — so both sides of the schema's
|
|
188
|
+
* `I.Expiration > context.Now` comparison are byte-identical canonical strings and
|
|
189
|
+
* the lexical `>` orders chronologically at any granularity. (This intentionally
|
|
190
|
+
* diverges from the control layer, which passes `Now` as a JS ISO string; Quereus
|
|
191
|
+
* does not coerce context params, so an ISO `Now` would be compared lexically against
|
|
192
|
+
* the canonical, T-separated `Expiration` and could mis-order near-same-instant
|
|
193
|
+
* timestamps (due to a trailing `.000Z` suffix). The control tests only use far-future/far-past expiries, so that latent
|
|
194
|
+
* skew never bites there — the strand layer avoids it outright.) A null `Expiration`
|
|
195
|
+
* never expires. Like `ValidUsage`, `NotExpired` defers to commit, so an expired
|
|
196
|
+
* invite rolls back the whole txn — neither the `Member` nor the `ConsumedInvite`
|
|
197
|
+
* row survives.
|
|
198
|
+
*
|
|
199
|
+
* @param db - The closed strand's database.
|
|
200
|
+
* @param params - The invite key/secret, the joining member's public key, and an
|
|
201
|
+
* optional `nowMs` instant for the expiry comparison (default `Date.now()`).
|
|
202
|
+
* @throws If any constraint rejects (bad signature, missing invite, or an expired
|
|
203
|
+
* invite); the whole transaction rolls back (neither the `Member` nor the
|
|
204
|
+
* `ConsumedInvite` row survives).
|
|
205
|
+
*/
|
|
206
|
+
export declare function consumeInvite(db: Database, params: ConsumeInviteParams): Promise<void>;
|
|
207
|
+
/** Parameters for {@link addMemberByAuthority}. */
|
|
208
|
+
export interface AddMemberByAuthorityParams {
|
|
209
|
+
/**
|
|
210
|
+
* The admitting authority's strand keypair. Its `publicKeyB64` must be a
|
|
211
|
+
* `Strand.Authority` row; it signs the new member key directly.
|
|
212
|
+
*/
|
|
213
|
+
authorityKeyPair: AuthorityKeyPair;
|
|
214
|
+
/** The joining member's ed25519 PUBLIC key (base64url) — the new `Member.Key`. */
|
|
215
|
+
memberKey: string;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Admit a `Member` directly by authority signature — the sibling of the invite
|
|
219
|
+
* path on `Member.Authorized`'s direct-authority branch.
|
|
220
|
+
*
|
|
221
|
+
* The constraint verifies `digest(new.Key)` against an `Authority` row matching
|
|
222
|
+
* `context.AuthorityKey`, so the payload is just the member key (no `'|'` join).
|
|
223
|
+
* No `ConsumedInvite` is involved: this is the path an authority uses to seat a
|
|
224
|
+
* member it already trusts (e.g. an authority-side enrollment that admits a
|
|
225
|
+
* party already authorised out-of-band).
|
|
226
|
+
*
|
|
227
|
+
* @param db - The closed strand's database.
|
|
228
|
+
* @param params - The admitting authority keypair and the new member key.
|
|
229
|
+
* @throws If `Member.Authorized` rejects (e.g. a non-authority key); the insert
|
|
230
|
+
* rolls back, leaving no `Member` row.
|
|
231
|
+
*/
|
|
232
|
+
export declare function addMemberByAuthority(db: Database, params: AddMemberByAuthorityParams): Promise<void>;
|
|
233
|
+
/** Parameters for {@link registerMemberPeer}. */
|
|
234
|
+
export interface RegisterMemberPeerParams {
|
|
235
|
+
/**
|
|
236
|
+
* The member's OWN strand keypair (`{ privateKeyB64, publicKeyB64 }`). Its
|
|
237
|
+
* `publicKeyB64` becomes `MemberPeer.MemberKey` and it self-signs the binding —
|
|
238
|
+
* `MemberPeer.Authorized` verifies the signature against `MemberKey` itself, so a
|
|
239
|
+
* peer can only be registered by the very member it belongs to (no authority
|
|
240
|
+
* involved). The founder passes its `strandMemberKeyPair`; an invited member
|
|
241
|
+
* passes its own keypair. Typed as {@link AuthorityKeyPair} only for the shared
|
|
242
|
+
* base64url keypair shape — no authority privilege is implied.
|
|
243
|
+
*/
|
|
244
|
+
memberKeyPair: AuthorityKeyPair;
|
|
245
|
+
/** The peer/node id (libp2p peer id string) to associate with the member. */
|
|
246
|
+
peerId: string;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Register a network node (`PeerId`) as acting on behalf of a member.
|
|
250
|
+
*
|
|
251
|
+
* The member self-signs `MemberKey || '|' || PeerId` with its OWN key; the schema's
|
|
252
|
+
* `MemberPeer.Authorized` verifies that signature against `coalesce(new.MemberKey,
|
|
253
|
+
* old.MemberKey)` — i.e. the member key itself — so only the member that owns the
|
|
254
|
+
* key can register peers for it. A deferred `MemberExists` additionally requires the
|
|
255
|
+
* `Member` row to already exist, so a peer for a non-member is rejected at commit.
|
|
256
|
+
*
|
|
257
|
+
* Insert-if-absent on the composite PK `(MemberKey, PeerId)`: a re-register on
|
|
258
|
+
* restart (or a redundant call) is a no-op rather than relying on the platform's
|
|
259
|
+
* PK-uniqueness rejection (not enforced in bootstrap mode — see
|
|
260
|
+
* `optimystic-insert-pk-uniqueness-not-enforced`). A member may register multiple
|
|
261
|
+
* DISTINCT `PeerId`s (multi-device); each is its own row under the same `MemberKey`.
|
|
262
|
+
*
|
|
263
|
+
* Out of scope: peer DELETION. The schema's `MemberExists` reads `new.MemberKey`,
|
|
264
|
+
* which is null on delete, so a `MemberPeer` delete is currently rejected by the
|
|
265
|
+
* schema (noted in the `apply-strand-membership-schema` review). Removing a peer
|
|
266
|
+
* would need a schema tweak (e.g. `coalesce(new.MemberKey, old.MemberKey)` in
|
|
267
|
+
* `MemberExists`), so no `removeMemberPeer` is provided here.
|
|
268
|
+
*
|
|
269
|
+
* @param db - The closed strand's database (the member already exists).
|
|
270
|
+
* @param params - The member's own keypair and the peer id to bind.
|
|
271
|
+
* @throws If `MemberPeer.Authorized`/`MemberExists` rejects (wrong signer, or no
|
|
272
|
+
* matching `Member` row); the insert rolls back, leaving no `MemberPeer` row.
|
|
273
|
+
*/
|
|
274
|
+
export declare function registerMemberPeer(db: Database, params: RegisterMemberPeerParams): Promise<void>;
|
|
275
|
+
/** Parameters for {@link addAuthority}. */
|
|
276
|
+
export interface AddAuthorityParams {
|
|
277
|
+
/**
|
|
278
|
+
* An EXISTING authority's strand keypair. Its `publicKeyB64` must already be a
|
|
279
|
+
* `Strand.Authority` row (the existing-authority branch of `Authority.Authorized`
|
|
280
|
+
* rejects a non-authority once the founder authority exists); it signs the new
|
|
281
|
+
* authority key and is bound as `context.AuthorityKey`.
|
|
282
|
+
*/
|
|
283
|
+
byAuthorityKeyPair: AuthorityKeyPair;
|
|
284
|
+
/** The member key to promote — the new `Authority.MemberKey` row. */
|
|
285
|
+
newAuthorityKey: string;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Promote a member to `Authority` on the signature of an existing authority.
|
|
289
|
+
*
|
|
290
|
+
* The existing-authority branch of `Authority.Authorized` verifies
|
|
291
|
+
* `digest(coalesce(new.MemberKey, old.MemberKey))` — `new.MemberKey` =
|
|
292
|
+
* `newAuthorityKey` on insert — against an `Authority` row matching
|
|
293
|
+
* `context.AuthorityKey`. So the signer signs the new authority key directly (no
|
|
294
|
+
* `'|'` join) and binds itself as `context.AuthorityKey`.
|
|
295
|
+
*
|
|
296
|
+
* Once the founder authority exists, the schema's `(select count(1) from Authority)
|
|
297
|
+
* <= 1` bootstrap shortcut no longer applies to a second add (at commit the count
|
|
298
|
+
* includes the new row, so it is ≥ 2), so this genuinely exercises signature
|
|
299
|
+
* verification — a non-authority signer, or a signature over the wrong key, is
|
|
300
|
+
* rejected.
|
|
301
|
+
*
|
|
302
|
+
* @param db - The closed strand's database (founder authority already seated).
|
|
303
|
+
* @param params - The authorizing authority's keypair and the new authority key.
|
|
304
|
+
* @throws If `Authority.Authorized` rejects; the insert rolls back.
|
|
305
|
+
*/
|
|
306
|
+
export declare function addAuthority(db: Database, params: AddAuthorityParams): Promise<void>;
|
|
307
|
+
/** Parameters for {@link removeAuthority}. */
|
|
308
|
+
export interface RemoveAuthorityParams {
|
|
309
|
+
/**
|
|
310
|
+
* The keypair authorizing the removal, bound as `context.AuthorityKey` and
|
|
311
|
+
* signing `digest(targetAuthorityKey)`. For an ADMIN removal this is a DIFFERENT
|
|
312
|
+
* existing authority (satisfying the existing-authority branch); for a
|
|
313
|
+
* SELF-resignation it is the target's OWN keypair (`publicKeyB64 ===
|
|
314
|
+
* targetAuthorityKey`, satisfying the former-authority self branch). The same
|
|
315
|
+
* context construction satisfies whichever branch applies — see
|
|
316
|
+
* {@link removeAuthority}.
|
|
317
|
+
*/
|
|
318
|
+
byAuthorityKeyPair: AuthorityKeyPair;
|
|
319
|
+
/** The `Authority.MemberKey` row to delete. */
|
|
320
|
+
targetAuthorityKey: string;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Remove an `Authority` row — either an admin removing a different authority or an
|
|
324
|
+
* authority resigning itself.
|
|
325
|
+
*
|
|
326
|
+
* On a DELETE, `coalesce(new.MemberKey, old.MemberKey)` binds `old.MemberKey =
|
|
327
|
+
* targetAuthorityKey` (there is no `new` row), so the signed payload is the target
|
|
328
|
+
* key for BOTH accepting branches of `Authority.Authorized`, and a single context
|
|
329
|
+
* construction serves both:
|
|
330
|
+
* - **Admin removal**: `byAuthorityKeyPair` is a different existing authority; the
|
|
331
|
+
* existing-authority branch verifies the signature against `A.MemberKey =
|
|
332
|
+
* context.AuthorityKey`.
|
|
333
|
+
* - **Self-resignation**: `byAuthorityKeyPair` IS the target (its `publicKeyB64`
|
|
334
|
+
* equals `targetAuthorityKey`); the former-authority self branch verifies
|
|
335
|
+
* `old.MemberKey = context.AuthorityKey` and the self-signature over `old.MemberKey`.
|
|
336
|
+
*
|
|
337
|
+
* The caller selects the case purely by which keypair it passes — no branching here.
|
|
338
|
+
*
|
|
339
|
+
* KNOWN PLATFORM GAP (not fixable here): the optimystic bootstrap-mode transactor
|
|
340
|
+
* evaluates deferred (subquery-bearing) CHECK constraints only on INSERT, not on
|
|
341
|
+
* DELETE. `Authority.Authorized` is deferred, so the platform currently accepts ANY
|
|
342
|
+
* `Authority` delete regardless of signature — `removeAuthority`'s authorization is
|
|
343
|
+
* effectively unenforced at runtime until that gap is closed (filed as
|
|
344
|
+
* `optimystic-deferred-check-not-enforced-on-delete`). This writer still builds the
|
|
345
|
+
* correct, signed delete so it works unchanged once enforcement lands — exactly as
|
|
346
|
+
* the invite path issues correct inserts despite the open PK-uniqueness gap.
|
|
347
|
+
*
|
|
348
|
+
* KNOWN SCHEMA HAZARD (not guarded here): even with enforcement, `Authority.
|
|
349
|
+
* Authorized`'s `(select count(1) from Authority) <= 1` bootstrap branch is true at
|
|
350
|
+
* commit whenever a delete drops the count to ≤ 1, so removing the strand's LAST (or
|
|
351
|
+
* second-to-last) authority would be accepted regardless of signature — and removing
|
|
352
|
+
* the last one orphans the strand (no one can ever add another). The schema does not
|
|
353
|
+
* prevent this; a "min-one-authority" invariant is deferred to a future schema change
|
|
354
|
+
* rather than grown into this writer.
|
|
355
|
+
*
|
|
356
|
+
* @param db - The closed strand's database.
|
|
357
|
+
* @param params - The authorizing keypair and the target authority key.
|
|
358
|
+
* @throws If `Authority.Authorized` rejects (e.g. a non-authority admin removal
|
|
359
|
+
* while > 2 authorities remain); the delete rolls back.
|
|
360
|
+
*/
|
|
361
|
+
export declare function removeAuthority(db: Database, params: RemoveAuthorityParams): Promise<void>;
|