@muhkoo/connect 0.14.0-alpha.9 → 0.15.0-alpha.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/dist/connect.d.ts CHANGED
@@ -160,14 +160,14 @@ type Permission = 'read' | 'write' | 'delete' | 'admin' | 'share' | 'invite';
160
160
  /**
161
161
  * Role types
162
162
  */
163
- type Role = 'owner' | 'admin' | 'editor' | 'viewer' | 'member';
163
+ type Role$1 = 'owner' | 'admin' | 'editor' | 'viewer' | 'member';
164
164
  /**
165
165
  * ACL Entry - grants permissions to a user (identified by public key)
166
166
  */
167
167
  interface ACLEntry {
168
168
  subject: string;
169
169
  permissions: Permission[];
170
- role?: Role;
170
+ role?: Role$1;
171
171
  expiresAt?: number;
172
172
  conditions?: {
173
173
  ipAllowlist?: string[];
@@ -194,7 +194,7 @@ interface ACL {
194
194
  */
195
195
  interface Participant {
196
196
  publicKey: string;
197
- role: Role;
197
+ role: Role$1;
198
198
  addedAt: number;
199
199
  addedBy: string;
200
200
  customPermissions?: Permission[];
@@ -434,20 +434,51 @@ interface DehydratedKeys {
434
434
  ecdsaPub: string;
435
435
  ecdsaPriv: string;
436
436
  }
437
+ /** A member's own ECDH and ECDSA pairs, held outside the store until {@link KeyStore.replaceOwnKeyPair}. */
438
+ interface OwnKeyPair {
439
+ ecdh: KeyPair;
440
+ ecdsa: AuthKeyPair;
441
+ }
437
442
  declare class KeyStore {
438
443
  private static instance;
439
444
  private _keys;
440
445
  private _authKeys;
446
+ /** The exact pairs marked vault-backed, so a pair written over them later is never taken for them. */
447
+ private _vaultBacked;
441
448
  private constructor();
442
449
  static getInstance(): KeyStore;
443
450
  get keys(): Map<string, KeyPair>;
444
451
  get authKeys(): Map<string, AuthKeyPair>;
445
452
  generateOwnKeyPair(id: string): Promise<KeyPair>;
446
453
  storeRemotePublicKeys(id: string, ecdhPublicKey: CryptoKey, ecdsaPublicKey: CryptoKey): Promise<void>;
454
+ /** Generate an own pair without touching the store; {@link replaceOwnKeyPair} puts it in place. */
455
+ generateKeyPair(): Promise<OwnKeyPair>;
447
456
  hydrateKeyPair(id: string, dehydrated: DehydratedKeys): Promise<void>;
457
+ /** Import a dehydrated pair without touching the store; {@link replaceOwnKeyPair} puts it in place. */
458
+ importKeyPair(dehydrated: DehydratedKeys): Promise<OwnKeyPair>;
448
459
  getKeyPair(id: string): KeyPair | null;
449
460
  getAuthKeyPair(id: string): AuthKeyPair | null;
461
+ /** Record that the pair `id` holds now came from the seed-wrapped vault (ChatKeyVault). */
462
+ markVaultBacked(id: string): void;
463
+ /**
464
+ * Put `pair` in place as `id`'s own pair and mark it vault-backed, in one
465
+ * synchronous step: whatever `id` held before is dropped, so a pair minted
466
+ * while the vault pair was being fetched or generated is never kept and
467
+ * marked in its place (design §3.3).
468
+ */
469
+ replaceOwnKeyPair(id: string, pair: OwnKeyPair): void;
470
+ /**
471
+ * Whether `id`'s own pair was loaded from, or persisted to, the vault in this
472
+ * tab, and is still the very pair that was marked. A Space identity must be
473
+ * vault-backed (design §3.3): any other pair, such as one minted by a locked
474
+ * DM subscription, never takes part, even one that lands over the marked pair.
475
+ */
476
+ isVaultBacked(id: string): boolean;
477
+ /** Drop every key held for `id` (SpecA §6.4 `KeyStore.forget`). */
478
+ forget(id: string): void;
450
479
  dehydrateKeyPair(id: string): Promise<DehydratedKeys>;
480
+ /** Dehydrate a pair that is not (yet) in the store, in the same format as {@link dehydrateKeyPair}. */
481
+ exportKeyPair(pair: OwnKeyPair): Promise<DehydratedKeys>;
451
482
  getRawEcdsaPublicKey(id: string): Promise<Uint8Array | null>;
452
483
  packDehydratedKeys(id: string): Promise<string>;
453
484
  hydrateFromPacked(id: string, masterKey: string): Promise<void>;
@@ -522,7 +553,6 @@ declare class DoubleRatchet {
522
553
  private sessionType;
523
554
  private maxSkip;
524
555
  private overlapPeriod;
525
- private windowSize;
526
556
  constructor(senderId: string, recipientId: string, sessionType: 'global' | 'specific', isClient?: boolean);
527
557
  private hkdf;
528
558
  initializeSession(isClient?: boolean): Promise<void>;
@@ -885,6 +915,8 @@ interface WSTransportOptions {
885
915
  * auto-response. Default `"pong"`.
886
916
  */
887
917
  pongFrame?: string;
918
+ /** A close with one of these codes is terminal: no reconnect, `DISCONNECTED` carries `{code, reason}`; `resume()` restarts. */
919
+ terminalCloseCodes?: number[];
888
920
  }
889
921
  declare class WSTransport extends EventCore {
890
922
  /**
@@ -917,11 +949,14 @@ declare class WSTransport extends EventCore {
917
949
  private pongFrame;
918
950
  private heartbeatTimer;
919
951
  private pongTimer;
952
+ private readonly terminalCloseCodes;
920
953
  constructor(opts: WSTransportOptions);
921
954
  /** Open the socket. Resolves when CONNECTED fires. Rejects on first error. */
922
955
  connect(): Promise<void>;
923
956
  /** Close the socket and stop reconnecting. */
924
957
  disconnect(): void;
958
+ /** Restart reconnecting after a terminal close or `disconnect()`; the next URL comes from `urlProvider`. */
959
+ resume(): void;
925
960
  /**
926
961
  * Send a raw frame. If currently disconnected, the frame is queued and sent
927
962
  * after the next successful reconnect (up to `maxQueueSize`).
@@ -1035,6 +1070,8 @@ declare class BroadcastChannel {
1035
1070
  connect(): Promise<void>;
1036
1071
  /** Close the WebSocket and stop reconnecting. */
1037
1072
  disconnect(): void;
1073
+ /** Reconnect after a terminal close (see `terminalCloseCodes`) or `disconnect()`, on a fresh `urlProvider` URL. */
1074
+ resume(): void;
1038
1075
  /**
1039
1076
  * Send our `keyExchange` frame to the room. Idempotent — only sends on the
1040
1077
  * first call. Call this once your higher-level handshake (e.g. sending a
@@ -1214,6 +1251,68 @@ declare class DoubleRatchetManager {
1214
1251
  private loadState;
1215
1252
  }
1216
1253
 
1254
+ /**
1255
+ * OPRF (Oblivious PRF) — RFC 9497, ristretto255-SHA512, mode 0x00.
1256
+ *
1257
+ * Used to gate the password/recovery factors of the identity vault
1258
+ * ({@link ./vault}). The wrap key for a password factor is
1259
+ * `HKDF(OPRF(serverKey, scrypt(password)))`, so:
1260
+ * - the client cannot derive the wrap key offline (it needs the server's
1261
+ * secret-keyed evaluation), and
1262
+ * - the server learns nothing — it only ever sees a *blinded* group element,
1263
+ * never the password or the resulting key.
1264
+ *
1265
+ * Net effect: a stolen vault blob is NOT offline-crackable; every guess costs a
1266
+ * round-trip to a rate-limited server endpoint. The same OPRF backs the M2
1267
+ * Google/email factors.
1268
+ *
1269
+ * Thin wrapper over `@noble/curves`' vetted `ristretto255_oprf` so the protocol
1270
+ * (blind / blindEvaluate / finalize) lives in one named place. CLIENT uses
1271
+ * {@link oprfBlind} + {@link oprfFinalize}.
1272
+ *
1273
+ * The `oprfDeriveKey` / `oprfBlindEvaluate` helpers here are the SERVER half,
1274
+ * but note they are used only by this repo's tests. The accelerator does NOT
1275
+ * import them: it has an independent implementation in its own
1276
+ * `src/services/oprf.ts`, also built directly on `@noble/curves`. (An earlier
1277
+ * version of this comment claimed the accelerator's VaultDO imported them; it
1278
+ * does not — it imports `@muhkoo/connect` in exactly two files, both for
1279
+ * Groth16 verification.) Any protocol change therefore has to land in BOTH
1280
+ * repos in lockstep.
1281
+ *
1282
+ * KNOWN WEAKNESS — mode 0x00, tracked as audit finding C4 and NOT fixed here.
1283
+ * This uses RFC 9497 base mode: the server returns a bare evaluation with no
1284
+ * DLEQ proof, and {@link oprfFinalize} verifies nothing. A hostile server can
1285
+ * answer `{evaluated: blinded}` (i.e. k=1); the unblinded point is then just
1286
+ * `H(input)` and the wrap key collapses to a pure function of public strings,
1287
+ * destroying the offline-uncrackability guarantee stated above.
1288
+ *
1289
+ * The fix is `ristretto255_oprf.voprf` (mode 0x01) with the server's public key
1290
+ * pinned as a build-time constant and the proof verified on every finalize.
1291
+ * noble's `voprf` variant is already present and unused. It cannot be done
1292
+ * client-side alone: the wire response (`{evaluated}` / `{evaluated, evaluated2}`)
1293
+ * carries no proof field, and the pinned public keys must correspond to the
1294
+ * accelerator's actual K1/K2 secrets. Changing only this file would break login
1295
+ * against the live server.
1296
+ */
1297
+ /**
1298
+ * The RFC 9497 mode this client speaks, declared on every evaluation request.
1299
+ *
1300
+ * Declaring it is what lets the server refuse loudly instead of answering in a
1301
+ * mode we did not ask for — the silent substitution that IS audit finding C4.
1302
+ * A server too old to know the field drops it and answers in base mode, which is
1303
+ * what we asked for anyway, so sending it is safe in both directions and imposes
1304
+ * no deploy ordering.
1305
+ *
1306
+ * NOT to be confused with the vault's KEY VERSION, a different axis that also
1307
+ * uses small integers: there, `2` means "per-account key". Here `2` means POPRF.
1308
+ * The server half of these constants is `accelerator/src/services/oprf.ts` and
1309
+ * MUST hold the same numbers.
1310
+ */
1311
+ declare const OPRF_PROTOCOL_BASE = 0;
1312
+ /** RFC 9497 mode 0x02 — the fix for C4. Reserved; no deployment serves it yet. */
1313
+ declare const OPRF_PROTOCOL_POPRF = 2;
1314
+ type OprfProtocol = typeof OPRF_PROTOCOL_BASE | typeof OPRF_PROTOCOL_POPRF;
1315
+
1217
1316
  /**
1218
1317
  * HTTP client for the accelerator's `/api/auth/*` endpoints.
1219
1318
  *
@@ -1227,6 +1326,21 @@ declare class DoubleRatchetManager {
1227
1326
  * (with the worker's message inlined where available).
1228
1327
  */
1229
1328
 
1329
+ /**
1330
+ * A non-2xx response from `/api/auth/*`, carrying the HTTP status and the
1331
+ * worker's machine-readable `code` alongside its human `error` string.
1332
+ *
1333
+ * Exists because the status used to be discarded: every failure arrived as a
1334
+ * bare `Error` whose only distinguishing feature was its message, so classifying
1335
+ * one meant regex-matching a sentence the server is free to reword. See
1336
+ * `VaultUnavailableError`, which still does exactly that for rate limits.
1337
+ */
1338
+ declare class AuthHttpError extends Error {
1339
+ readonly label: string;
1340
+ readonly status: number;
1341
+ readonly code: string | undefined;
1342
+ constructor(label: string, status: number, code: string | undefined, detail: string);
1343
+ }
1230
1344
  interface ZkRegisterRequest {
1231
1345
  username: string;
1232
1346
  /** Decimal-string Poseidon commitment. */
@@ -1306,16 +1420,24 @@ declare class AuthClient {
1306
1420
  verify(token: string): Promise<{
1307
1421
  username: string;
1308
1422
  }>;
1309
- /** Blinded OPRF evaluation — the server-gated half of a password/recovery wrap key. */
1310
- oprfEvaluate(username: string, blinded: string): Promise<{
1423
+ /**
1424
+ * Blinded OPRF evaluation — the server-gated half of a password/recovery wrap key.
1425
+ *
1426
+ * `oprfProtocol` declares the RFC 9497 mode this client speaks so the server
1427
+ * can refuse rather than answer in a different one. A server too old to know
1428
+ * the field drops it and answers in base mode, so this is safe against any
1429
+ * deployment.
1430
+ */
1431
+ oprfEvaluate(username: string, blinded: string, oprfProtocol?: OprfProtocol): Promise<{
1311
1432
  evaluated: string;
1312
1433
  }>;
1313
1434
  /**
1314
- * Gated split-key OPRF evaluation (M2 — email/google factors). Requires a
1315
- * live `verifyToken` from {@link emailOtpVerify}; returns BOTH trust
1316
- * domains' evaluations. The recover token is consumed by this call.
1435
+ * Gated OPRF evaluation (M2 — email/google factors). Requires a live
1436
+ * `verifyToken` from {@link emailOtpVerify}; returns BOTH keys'
1437
+ * evaluations (K1 and K2, both operated by Muhkoo). The recover token is
1438
+ * consumed by this call.
1317
1439
  */
1318
- oprfEvaluateGated(username: string, blinded: string, factorType: "email" | "google", purpose: "enroll" | "recover", verifyToken: string): Promise<{
1440
+ oprfEvaluateGated(username: string, blinded: string, factorType: "email" | "google", purpose: "enroll" | "recover", verifyToken: string, oprfProtocol?: OprfProtocol): Promise<{
1319
1441
  evaluated: string;
1320
1442
  evaluated2: string;
1321
1443
  }>;
@@ -2896,6 +3018,11 @@ interface HostedAuthDeps {
2896
3018
  /** Sleep used by the poll loop. Injectable so tests don't burn wall-clock. */
2897
3019
  sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
2898
3020
  }
3021
+ /**
3022
+ * A same-origin path the app may be sent back to after sign-in. It starts with
3023
+ * "/", not "//", and has no backslash or control characters (design §4.4.9).
3024
+ */
3025
+ declare function isSameOriginPath(p: unknown): p is string;
2899
3026
  /** One in-flight pairing attempt. Hold it; pass it back into poll/wait/cancel. */
2900
3027
  interface DevicePairingSession {
2901
3028
  /** Opaque; carry it back into poll/wait/cancel. Never render it. */
@@ -3048,13 +3175,15 @@ declare class HostedAuth {
3048
3175
  * (App side) Begin hosted login: stash a PKCE verifier + state, then send
3049
3176
  * the browser to the hosted authorize page. Returns the URL it redirects to
3050
3177
  * (also useful for popup mode / tests). `redirectUri` must be registered for
3051
- * the app in the portal.
3178
+ * the app in the portal. `returnTo`, a same-origin path, rides the stash and
3179
+ * comes back from {@link handleCallback}; anything else is refused here.
3052
3180
  */
3053
3181
  login(opts: {
3054
3182
  appId: string;
3055
3183
  redirectUri: string;
3056
3184
  redirect?: boolean;
3057
3185
  prompt?: "login";
3186
+ returnTo?: string;
3058
3187
  }): Promise<string>;
3059
3188
  /**
3060
3189
  * (App side) Send the user to centralized account & security management on
@@ -3071,9 +3200,12 @@ declare class HostedAuth {
3071
3200
  /**
3072
3201
  * (App side) Complete login on the callback URL: verify state, exchange the
3073
3202
  * code (+ PKCE verifier) for the session, unseal the seed from the fragment,
3074
- * establish the session, and scrub the URL. Returns the signed-in user.
3203
+ * establish the session, and scrub the URL. Returns the signed-in user, with
3204
+ * the `returnTo` passed to {@link login} when it is still a same-origin path.
3075
3205
  */
3076
- handleCallback(): Promise<AuthUser>;
3206
+ handleCallback(): Promise<AuthUser & {
3207
+ returnTo?: string;
3208
+ }>;
3077
3209
  /**
3078
3210
  * (Hosted-page side) Finish an /authorize request after the user has
3079
3211
  * authenticated here: seal the in-memory seed, mint the grant, and return
@@ -3299,6 +3431,24 @@ declare function formatUserCode(canonical: string): string;
3299
3431
  declare class VaultUnavailableError extends Error {
3300
3432
  constructor(cause?: unknown);
3301
3433
  }
3434
+ /**
3435
+ * The vault refused the OPRF protocol this client speaks.
3436
+ *
3437
+ * Distinct from {@link VaultUnavailableError} because the remedy is different and
3438
+ * the two used to be conflated: "unavailable" means retry, and a version mismatch
3439
+ * never resolves by retrying — it resolves by updating the app (or the server).
3440
+ *
3441
+ * Critically, this must NOT be caught into the
3442
+ * `vaultSeed ?? deriveMasterSeedFromPassword(...)` fallback in `login`/`unlock`.
3443
+ * That fallback is correct for an account with no password factor, and wrong for
3444
+ * a refusal: it derives a different seed, and on a legacy-migrated account —
3445
+ * where the fallback seed happens to be the right one — it would let
3446
+ * `migrateLegacyPasswordFactor` rewrite the factor as a side effect of a failure
3447
+ * the user was never shown.
3448
+ */
3449
+ declare class VaultProtocolMismatchError extends Error {
3450
+ constructor(cause?: unknown);
3451
+ }
3302
3452
  /**
3303
3453
  * A stored device pairing that the account has since revoked.
3304
3454
  *
@@ -3428,15 +3578,16 @@ declare class ZkAuth {
3428
3578
  recoverWithPhrase(username: string, mnemonic: string, opts?: LoginOptions): Promise<AuthUser>;
3429
3579
  /**
3430
3580
  * Add an **email** as a recovery + login factor (M2 verification-gated:
3431
- * the server only releases the split-key OPRF eval after the address is
3581
+ * the server only releases the gated OPRF eval after the address is
3432
3582
  * OTP-verified). Sends the code, then returns a `confirm` continuation:
3433
3583
  *
3434
3584
  * const { confirm } = await client.auth.zk.enrollEmailFactor("me@x.com");
3435
3585
  * await confirm(codeFromInbox);
3436
3586
  *
3437
3587
  * Requires being signed in with the seed held (like the other enrolls).
3438
- * Custody note: gated factors trade some custody for recoverability — the
3439
- * trade-off should be disclosed in the enroll UI.
3588
+ * Custody note: Muhkoo holds the keys behind gated factors, so it can
3589
+ * recover the seed of any account with an email or Google factor. The
3590
+ * hosted sign-in UI discloses this; a custom enroll UI must too.
3440
3591
  */
3441
3592
  enrollEmailFactor(email: string): Promise<{
3442
3593
  email: string;
@@ -3445,7 +3596,7 @@ declare class ZkAuth {
3445
3596
  /**
3446
3597
  * Recover an account via its enrolled **email** factor. Sends a code to the
3447
3598
  * enrolled address (the response never reveals whether one exists), then
3448
- * `confirm(code)` completes: gated read + split-key eval → unwrap seed →
3599
+ * `confirm(code)` completes: gated read + gated OPRF eval → unwrap seed →
3449
3600
  * normal ZK login. After this, call {@link changePassword} to set a new
3450
3601
  * password.
3451
3602
  *
@@ -3458,11 +3609,14 @@ declare class ZkAuth {
3458
3609
  /**
3459
3610
  * Link a Google account as a recovery + login factor. Requires being signed
3460
3611
  * in with the seed held. `idToken` is a fresh Google ID token (GIS).
3612
+ * Custody note: as with {@link enrollEmailFactor}, Muhkoo holds the keys
3613
+ * behind this factor and can recover the seed of an account that has it.
3614
+ * The hosted sign-in UI discloses this; a custom enroll UI must too.
3461
3615
  */
3462
3616
  enrollGoogleFactor(idToken: string): Promise<void>;
3463
3617
  /**
3464
3618
  * Sign in with Google — verify the ID token, unlock the seed via the gated
3465
- * split-key eval, and establish a session. Same machinery serves "Sign in
3619
+ * OPRF eval, and establish a session. Same machinery serves "Sign in
3466
3620
  * with Google" and "recover with Google"; the account must have linked the
3467
3621
  * factor first.
3468
3622
  */
@@ -3471,7 +3625,8 @@ declare class ZkAuth {
3471
3625
  * Register a brand-new, **passwordless** account whose only factor is Google.
3472
3626
  * A random master seed is generated and wrapped under the Google-gated key;
3473
3627
  * the user can add a password / passkey / phrase later. The ZK registration
3474
- * is identical to a password signup.
3628
+ * is identical to a password signup. Custody note: see
3629
+ * {@link enrollGoogleFactor} — Muhkoo can recover this account's seed.
3475
3630
  */
3476
3631
  registerWithGoogle(username: string, idToken: string): Promise<AuthUser>;
3477
3632
  /**
@@ -3726,9 +3881,10 @@ interface HttpClientOptions {
3726
3881
  * token) or `false` if it couldn't (the `401` propagates as an
3727
3882
  * {@link HttpError}). Wired by {@link Client} to `auth.zk.recover()`.
3728
3883
  *
3729
- * Only consulted by the JSON helpers (`get`/`post`/`del`); the raw
3730
- * {@link fetch} passes 401s straight through so the auth wire calls that
3731
- * power recovery itself can't recurse.
3884
+ * Only consulted by the JSON helpers (`get`/`post`/`del`) and
3885
+ * {@link HttpClient.fetchWithRecovery}; the raw {@link fetch} passes 401s
3886
+ * straight through so the auth wire calls that power recovery itself
3887
+ * can't recurse.
3732
3888
  */
3733
3889
  onUnauthorized?: () => Promise<boolean>;
3734
3890
  }
@@ -3755,6 +3911,15 @@ declare class HttpClient {
3755
3911
  * up credentials without knowing about them.
3756
3912
  */
3757
3913
  readonly fetch: typeof fetch;
3914
+ /**
3915
+ * {@link fetch} with the JSON helpers' stale-session recovery: a 401 on a
3916
+ * request made while a session token is held runs `onUnauthorized` once
3917
+ * and, if that recovered, replays the request once with the fresh token.
3918
+ * The final response is returned whatever its status, never thrown. The
3919
+ * replay re-sends `init` as given, so its body must be re-sendable (a
3920
+ * string or bytes, not a stream).
3921
+ */
3922
+ readonly fetchWithRecovery: typeof fetch;
3758
3923
  /** True when `input` resolves to {@link baseOrigin} (relative URLs always do). */
3759
3924
  private isSameOrigin;
3760
3925
  get<T = unknown>(path: string): Promise<T>;
@@ -3919,8 +4084,9 @@ interface OfflineStore {
3919
4084
  * mutation by re-invoking the real namespace method (with an `isReplay` flag so
3920
4085
  * it doesn't re-enqueue). `drain()` replays entries in HLC order:
3921
4086
  * - success → remove the entry;
3922
- * - permanent failure (4xx) → drop it and surface a `SYNC_ERROR` (a write the
3923
- * server will never accept must not wedge the queue forever);
4087
+ * - permanent failure (4xx, or a Space whose member list refuses this user)
4088
+ * → drop it and surface a `SYNC_ERROR` (a write the server will never
4089
+ * accept must not wedge the queue forever);
3924
4090
  * - transient/network failure → stop draining and leave the rest for the next
3925
4091
  * reconnect.
3926
4092
  *
@@ -4500,6 +4666,140 @@ declare class StorageNamespace {
4500
4666
  private space;
4501
4667
  }
4502
4668
 
4669
+ /**
4670
+ * Wire types of the e2e-channels trust layer (spec 2026-09-27 §3.2, §3.5,
4671
+ * §4.2.4, §5.3.1). These shapes are signed and stored: never rename a field.
4672
+ *
4673
+ * Every string field holds the exact string that was transmitted; the
4674
+ * canonical encoders in `canonical.ts` sign those strings as they are.
4675
+ *
4676
+ * Ported from the accelerator's `src/trust/types.ts` (the server-only app
4677
+ * roster types are left out); the shared shapes must stay identical.
4678
+ */
4679
+ type Visibility = "private" | "public";
4680
+ type HistoryPolicy$1 = "static" | "rotate";
4681
+ type MemberRole = "viewer" | "editor";
4682
+ type SvcMode = "addressed" | "all";
4683
+ type MlogOp = "genesis" | "add" | "role" | "remove" | "leave" | "join" | "link" | "unlink" | "redeem" | "svc" | "retire";
4684
+ /** `legacyCommits` is `{}` for a new Space; values are b64url of 32 bytes, keys canonical decimal epochs. */
4685
+ interface GenesisBody {
4686
+ visibility: Visibility;
4687
+ policy: HistoryPolicy$1;
4688
+ appId: string;
4689
+ baseEpoch: number;
4690
+ legacyCommits: Record<string, string>;
4691
+ }
4692
+ interface AddBody {
4693
+ member: string;
4694
+ memberIk: string;
4695
+ role: MemberRole;
4696
+ }
4697
+ interface RoleBody {
4698
+ member: string;
4699
+ role: MemberRole;
4700
+ }
4701
+ interface RemoveBody {
4702
+ member: string;
4703
+ }
4704
+ type LeaveBody = Record<string, never>;
4705
+ /** `via` is a linkId, or "" for no link. */
4706
+ interface JoinBody {
4707
+ via: string;
4708
+ }
4709
+ interface LinkBody {
4710
+ linkId: string;
4711
+ role: MemberRole;
4712
+ standing: boolean;
4713
+ expiresAt: number;
4714
+ maxUses: number;
4715
+ linkEcdhPub: string;
4716
+ linkSignPub: string;
4717
+ keyCommits: Record<string, string>;
4718
+ }
4719
+ interface UnlinkBody {
4720
+ linkId: string;
4721
+ }
4722
+ interface RedeemBody {
4723
+ linkId: string;
4724
+ }
4725
+ interface SvcBody {
4726
+ member: string;
4727
+ action: "add" | "remove";
4728
+ ecdhPub: string;
4729
+ ecdsaPub: string;
4730
+ mode: SvcMode;
4731
+ rosterSeq: number;
4732
+ }
4733
+ interface RetireBody {
4734
+ member: string;
4735
+ reason: string;
4736
+ }
4737
+ interface MlogBodies {
4738
+ genesis: GenesisBody;
4739
+ add: AddBody;
4740
+ role: RoleBody;
4741
+ remove: RemoveBody;
4742
+ leave: LeaveBody;
4743
+ join: JoinBody;
4744
+ link: LinkBody;
4745
+ unlink: UnlinkBody;
4746
+ redeem: RedeemBody;
4747
+ svc: SvcBody;
4748
+ retire: RetireBody;
4749
+ }
4750
+ interface EntryBase {
4751
+ v: 1;
4752
+ spaceId: string;
4753
+ /** 0 = genesis; −1 only in an enable intent (§5.3.3), never stored. */
4754
+ seq: number;
4755
+ /** hash of entry seq−1; "" at genesis and in an intent. */
4756
+ prev: string;
4757
+ /** Signer member id; "" in a server-written `retire`. */
4758
+ by: string;
4759
+ /** Signer IK (b64 or hex raw P-256), exactly as sent; "" in a `retire`. */
4760
+ ik: string;
4761
+ /** Signer clock, informational. */
4762
+ at: number;
4763
+ /** b64 P1363 ECDSA-P256-SHA256 by `ik` over entryBytes; "" only in a `retire`. */
4764
+ sig: string;
4765
+ /** Link-sign signature over entryBytes (`link`, `redeem`, `join` with `via`). */
4766
+ lsig?: string;
4767
+ }
4768
+ type MembershipEntry<O extends MlogOp = MlogOp> = {
4769
+ [K in O]: EntryBase & {
4770
+ op: K;
4771
+ body: MlogBodies[K];
4772
+ };
4773
+ }[O];
4774
+ type UnsignedEntry<O extends MlogOp = MlogOp> = {
4775
+ [K in O]: Omit<MembershipEntry<K>, "sig" | "lsig">;
4776
+ }[O];
4777
+ interface ChatKeyCert {
4778
+ v: 1;
4779
+ ik: string;
4780
+ ecdh: string;
4781
+ ecdsa: string;
4782
+ sig: string;
4783
+ }
4784
+ interface EpochCommit {
4785
+ v: 1;
4786
+ spaceId: string;
4787
+ epoch: number;
4788
+ prevKeyed: number;
4789
+ keyCommit: string;
4790
+ headSeq: number;
4791
+ headHash: string;
4792
+ sole: boolean;
4793
+ by: string;
4794
+ ik: string;
4795
+ at: number;
4796
+ sig: string;
4797
+ }
4798
+ interface MlogHead {
4799
+ seq: number;
4800
+ hash: string;
4801
+ }
4802
+
4503
4803
  /**
4504
4804
  * Shared types for the Space fan-out group-encryption layer.
4505
4805
  *
@@ -4509,6 +4809,7 @@ declare class StorageNamespace {
4509
4809
  * which is what lets the server persist + replay messages as history while
4510
4810
  * staying blind to their contents.
4511
4811
  */
4812
+
4512
4813
  /**
4513
4814
  * How a space treats history across membership changes.
4514
4815
  * - `static`: one group key for all time (epoch 0). New members can read the
@@ -4556,6 +4857,8 @@ interface JoinRequest {
4556
4857
  /** Epoch the newcomer wants a key for (defaults to current). */
4557
4858
  desiredEpoch?: number;
4558
4859
  ts?: number;
4860
+ /** The account IK's certificate over the two identity strings above (design §3.2). Optional: a join never requires one. */
4861
+ keyCert?: ChatKeyCert;
4559
4862
  }
4560
4863
  /** A space member's published identity keys (the keyring directory). */
4561
4864
  interface RosterMember {
@@ -4710,6 +5013,18 @@ interface KeyringTransport {
4710
5013
  /** Add `username` to a private channel's membership allowlist. */
4711
5014
  invite(username: string): Promise<void>;
4712
5015
  fetchMetadata(): Promise<SpaceMetadata | null>;
5016
+ /** GET /keyring/log (design §3.6): entries with seq > since, ascending, ≤ limit; head null = no log. */
5017
+ fetchLog?(since?: number, limit?: number): Promise<{
5018
+ entries: MembershipEntry[];
5019
+ head: MlogHead | null;
5020
+ }>;
5021
+ /** POST /keyring/log {entry, ec?}. */
5022
+ postLogEntry?(entry: MembershipEntry, ec?: EpochCommit): Promise<{
5023
+ ok: true;
5024
+ head: MlogHead;
5025
+ }>;
5026
+ /** Status of GET /keyring/state, or "network" when the request fails. */
5027
+ probeState?(): Promise<number | "network">;
4713
5028
  }
4714
5029
  /** PersonalSpace-backed cache of this member's group keys (epoch → base64 key). */
4715
5030
  interface SpaceKeyCache {
@@ -4729,6 +5044,10 @@ interface SpaceKeyringDeps {
4729
5044
  transport: KeyringTransport;
4730
5045
  cache?: SpaceKeyCache;
4731
5046
  historyPolicy?: HistoryPolicy;
5047
+ /** Mints the IK certificate for this member's chat keys (design §3.2). Absent, or resolving undefined, means none. */
5048
+ keyCert?: () => Promise<ChatKeyCert | undefined>;
5049
+ /** Whether a key may be wrapped to `memberId`; absent means anyone. On a signed Space: log members and the keeper only. */
5050
+ canWrapTo?: (memberId: string) => boolean;
4732
5051
  }
4733
5052
  declare class SpaceKeyring implements EpochKeyProvider {
4734
5053
  private readonly deps;
@@ -4745,7 +5064,11 @@ declare class SpaceKeyring implements EpochKeyProvider {
4745
5064
  * this window won't re-fetch (the roster hasn't changed that fast); genuine
4746
5065
  * membership changes use `{ force: true }` (join requests / history loads). */
4747
5066
  private static readonly DIR_COOLDOWN_MS;
5067
+ private readonly keyListeners;
4748
5068
  constructor(deps: SpaceKeyringDeps);
5069
+ /** Notified whenever this keyring gains a key (bootstrap, cache hit, pull). Returns an unsubscribe. */
5070
+ onKeysChanged(h: () => void): () => void;
5071
+ private notifyKeys;
4749
5072
  /** The cached ECDSA verify key for a member, if known. */
4750
5073
  ecdsaKeyFor(memberId: string): CryptoKey | undefined;
4751
5074
  /**
@@ -4763,6 +5086,10 @@ declare class SpaceKeyring implements EpochKeyProvider {
4763
5086
  currentEpoch(): number;
4764
5087
  keyForEpoch(epoch: number): Uint8Array | undefined;
4765
5088
  hasAnyKey(): boolean;
5089
+ /** The raw 32-byte group key for `epoch`, for its key commitment (design §4.2.4). */
5090
+ rawKey(epoch: number): Uint8Array | undefined;
5091
+ /** Whether a key may be wrapped to `memberId` (design §4.5.1: on a signed Space, log members and the keeper only). */
5092
+ canWrapTo(memberId: string): boolean;
4766
5093
  /** Hydrate keys from the PersonalSpace cache, if available. Best-effort —
4767
5094
  * a cache miss/failure (e.g. not unlocked) must not block joining. */
4768
5095
  loadFromCache(): Promise<boolean>;
@@ -4772,7 +5099,8 @@ declare class SpaceKeyring implements EpochKeyProvider {
4772
5099
  /**
4773
5100
  * Post a join request so an online key-holder can wrap a key for us. Also
4774
5101
  * publishes our identity keys in the member directory (so others can verify
4775
- * our message signatures, and the keeper can wrap to us).
5102
+ * our message signatures, and the keeper can wrap to us). With a `keyCert`
5103
+ * dep it also carries the IK certificate over exactly those two strings.
4776
5104
  */
4777
5105
  requestKey(desiredEpoch?: number): Promise<void>;
4778
5106
  /**
@@ -4810,6 +5138,124 @@ declare class SpaceKeyring implements EpochKeyProvider {
4810
5138
  invite(username: string): Promise<void>;
4811
5139
  }
4812
5140
 
5141
+ type Role = "owner" | "editor" | "viewer";
5142
+ interface LogMember {
5143
+ id: string;
5144
+ ik: string;
5145
+ role: Role;
5146
+ since: number;
5147
+ }
5148
+
5149
+ /** What LogSync needs from core (kept structural so spaces/ never imports core/). */
5150
+ interface LogSyncDeps {
5151
+ spaceId: string;
5152
+ me: () => string;
5153
+ appId: () => Promise<string>;
5154
+ commitment: () => string | null;
5155
+ transport: {
5156
+ fetchLog(since?: number, limit?: number): Promise<{
5157
+ entries: MembershipEntry[];
5158
+ head: MlogHead | null;
5159
+ }>;
5160
+ postLogEntry(entry: MembershipEntry, ec?: EpochCommit): Promise<{
5161
+ head: MlogHead;
5162
+ }>;
5163
+ };
5164
+ signer: {
5165
+ sign(b: Uint8Array): Promise<string>;
5166
+ identityKeyRaw(): Promise<string>;
5167
+ };
5168
+ directory: {
5169
+ lookup(username: string): Promise<string>;
5170
+ };
5171
+ pins: {
5172
+ get(doc: "identity" | "spaces", key: string): Promise<string | undefined>;
5173
+ pin(doc: "identity" | "spaces", entries: Record<string, string>): Promise<{
5174
+ conflicts: string[];
5175
+ }>;
5176
+ };
5177
+ store: () => Promise<{
5178
+ get<T>(k: string): Promise<T | undefined>;
5179
+ put(k: string, v: unknown): Promise<void>;
5180
+ }>;
5181
+ }
5182
+ interface MemberView {
5183
+ id: string;
5184
+ role: Role;
5185
+ ik: string;
5186
+ fingerprint: string;
5187
+ identityChanged: boolean;
5188
+ }
5189
+ type IntegrityState = {
5190
+ broken: false;
5191
+ } | {
5192
+ broken: true;
5193
+ reason: string;
5194
+ };
5195
+ /**
5196
+ * One Space's membership log, as this client has verified it (design §3.6–§3.7).
5197
+ * Syncs the whole log from −1 on each call (slice logs are tiny, so no entry
5198
+ * cache), checks the head against the high-water mark kept in `muhkoo.trust`,
5199
+ * checks the genesis against the pins (TOFU against the directory the first
5200
+ * time), and pins every IK a verified `add` names (design §3.4).
5201
+ */
5202
+ declare class LogSync {
5203
+ private readonly deps;
5204
+ private log;
5205
+ private enabled;
5206
+ private integrityState;
5207
+ private changed;
5208
+ private fps;
5209
+ private listeners;
5210
+ private timer;
5211
+ private running;
5212
+ private followUp;
5213
+ constructor(deps: LogSyncDeps);
5214
+ get logEnabled(): boolean;
5215
+ get integrity(): IntegrityState;
5216
+ get signer(): LogSyncDeps["signer"];
5217
+ get directory(): LogSyncDeps["directory"];
5218
+ member(id: string): LogMember | undefined;
5219
+ isMember(id: string): boolean;
5220
+ members(): MemberView[];
5221
+ onChange(h: () => void): () => void;
5222
+ /**
5223
+ * An `mlog` frame marks the log stale; sync once a burst of frames settles.
5224
+ * A failure is logged with its code and reported through `onChange`, never
5225
+ * thrown; integrity is left as it was, and the next frame or open retries.
5226
+ */
5227
+ scheduleSync(): void;
5228
+ private hwKey;
5229
+ /** Whether this client holds a high-water mark for this Space, i.e. has verified its log before (design §3.7 item 3). */
5230
+ hasHighWater(): Promise<boolean>;
5231
+ /**
5232
+ * Fetch and verify the whole log. Single-flight: a call made while a sync
5233
+ * runs does not start a second one alongside it. It waits for exactly one
5234
+ * follow-up run, which starts once the running one ends, so it sees whatever
5235
+ * prompted the call; every call made in the meantime shares that follow-up.
5236
+ */
5237
+ sync(): Promise<void>;
5238
+ private startSync;
5239
+ private runSync;
5240
+ /** Genesis trust (design §3.7 item 2): the pinned genesis hash, else TOFU with the owner's IK checked against the directory. */
5241
+ private checkGenesis;
5242
+ /** "s:" + b64url(SHA-256(utf8(spaceId))), the spaces-v1 key; computed exactly as PinStore.spacePinKey, which this layer may not import. */
5243
+ private spacePinKey;
5244
+ /** Pin every IK a verified add names (design §3.4 row 2); a different pinned IK flags the member (identity-changed). */
5245
+ private pinMembers;
5246
+ /**
5247
+ * Sign and append one entry. On `409 Log moved`: re-sync, re-sign against the
5248
+ * new head and retry, at most 3 times with jitter (design §9.2 I9). Nothing is
5249
+ * signed while integrity is broken, including after a re-sync that breaks it.
5250
+ * Any other refusal → SpaceTrustError("log-refused") carrying the server's code and reason.
5251
+ * Once the server has stored the entry, its `{seq}` is returned even if the sync confirming it fails.
5252
+ */
5253
+ append(op: MembershipEntry["op"], body: Record<string, unknown>, ec?: (hash: string, entry: UnsignedEntry) => Promise<EpochCommit>): Promise<{
5254
+ seq: number;
5255
+ }>;
5256
+ private emit;
5257
+ }
5258
+
4813
5259
  /**
4814
5260
  * `Space` — a client handle for one shared space.
4815
5261
  *
@@ -4927,6 +5373,11 @@ interface EphemeralEvent {
4927
5373
  interface MessageDeletedEvent {
4928
5374
  handle: number;
4929
5375
  }
5376
+ type AdmissionState = "locked" | "requesting" | "keyed" | "not-member" | "identity-mismatch" | "integrity";
5377
+ /** WebSocket close code for a refused private-Space socket (design §3.6; accelerator NOT_MEMBER_CLOSE_CODE). */
5378
+ declare const NOT_MEMBER_CLOSE_CODE = 4403;
5379
+ /** At most one resume after a 4403 per Space per this interval (design §6.7). */
5380
+ declare const NOT_MEMBER_RECHECK_MS = 60000;
4930
5381
  interface SpaceDeps {
4931
5382
  /** Transport room id. For fan-out spaces this is the encoded space pubkey. */
4932
5383
  name: string;
@@ -4961,6 +5412,21 @@ interface SpaceDeps {
4961
5412
  deferShardUpload?: (hash: string) => Promise<void>;
4962
5413
  /** Offline message cache + send queue. Undefined ⇒ no offline behavior. */
4963
5414
  offline?: SpaceOfflineAdapter;
5415
+ /** Locked session (design §3.3): no keyring, never connects; the app re-opens after sign-in. */
5416
+ locked?: boolean;
5417
+ /** The Space's membership log (a signed private Space), when this client has trust wired. */
5418
+ logSync?: LogSync;
5419
+ /** Visibility the Space was created with; only a private Space gets a genesis (design §3.8). */
5420
+ visibility?: "private" | "public";
5421
+ /** This app's id, which a genesis names (design §3.5). */
5422
+ appId?: () => Promise<string>;
5423
+ /**
5424
+ * Status of GET /keyring/state after the client's silent 401 recovery, or "network": asked after a
5425
+ * 4403 close before believing it (design §6.9).
5426
+ */
5427
+ probeState?: () => Promise<number | "network">;
5428
+ /** True while the session holds a token, so a reconnect must never go ticketless. */
5429
+ hasSession?: () => boolean;
4964
5430
  }
4965
5431
  /** The subset of {@link BroadcastChannel} a {@link Space} drives. */
4966
5432
  interface SpaceChannelLike {
@@ -4973,13 +5439,22 @@ interface SpaceChannelLike {
4973
5439
  sendRaw(frame: unknown): void;
4974
5440
  peers?(): string[];
4975
5441
  isConnected?(): boolean;
5442
+ /** Reconnect after a terminal close, on a fresh URL. */
5443
+ resume?(): void;
4976
5444
  }
4977
5445
  declare class Space {
4978
5446
  private readonly deps;
4979
5447
  readonly name: string;
4980
5448
  readonly historyPolicy: HistoryPolicy;
4981
5449
  private channel;
4982
- private readonly pending;
5450
+ /**
5451
+ * The log turned this user away and closed `channel` (design §3.7). Its
5452
+ * single-use ticket is spent, so it is never dialled again: the next
5453
+ * `connect()` checks the log and opens a new channel on a fresh ticket.
5454
+ */
5455
+ private channelSpent;
5456
+ /** Every raw channel listener (`on`), attached to each channel this Space opens. */
5457
+ private readonly listeners;
4983
5458
  private readonly cipher;
4984
5459
  /** Per-instance event bus for fan-out events (isolated across spaces). */
4985
5460
  private readonly events;
@@ -4987,12 +5462,52 @@ declare class Space {
4987
5462
  private catchUpUnsub;
4988
5463
  /** Optional P2P swarm for this Space (block exchange among members). */
4989
5464
  private peerNet;
5465
+ private _admission;
5466
+ /** Whether admission follows the log's changes yet (subscribed once, on the first open the log admits). */
5467
+ private admissionWired;
5468
+ /** When the last resume after a 4403 ran (design §6.7: at most one per {@link NOT_MEMBER_RECHECK_MS}). */
5469
+ private lastResumeAt;
5470
+ private resumeTimer;
5471
+ /** Bumped by `disconnect()`: a /keyring/state answer that arrives after it is dropped. */
5472
+ private teardowns;
4990
5473
  constructor(deps: SpaceDeps);
4991
5474
  /** The encoded space id (same as the transport name for fan-out spaces). */
4992
5475
  get id(): string;
4993
5476
  /** The keyring backing this space's group keys, if any. */
4994
5477
  get keyring(): SpaceKeyring | undefined;
4995
- /** Open the space websocket (resolves the ticket + identity lazily). */
5478
+ /** Where this member stands in this Space (design §4.5.12, as the slice ships it). */
5479
+ get admission(): AdmissionState;
5480
+ /** Subscribe to admission changes. Returns an unsubscribe. */
5481
+ onAdmission(h: (s: AdmissionState) => void): () => void;
5482
+ protected setAdmission(s: AdmissionState): void;
5483
+ /** The log turned this user away; nothing reconnects until it admits them again. */
5484
+ private denied;
5485
+ /**
5486
+ * Sync the membership log and derive admission from it, before any ticket
5487
+ * (design §3.4, §3.7, §4.5.12). A 403 from the log route is not-member and a
5488
+ * 401 is locked. Any other failure is fatal only where this client has
5489
+ * verified the log before (it holds a high-water mark). Otherwise the Space
5490
+ * opens as unlogged this time, and the next open or `mlog` frame syncs again.
5491
+ */
5492
+ private admitByLog;
5493
+ /** Re-derive admission from the verified log (design §3.4 rows, §3.7, §4.5.12). */
5494
+ private evaluateAdmission;
5495
+ /**
5496
+ * A 4403 alone never shows not-member: ask /keyring/state first (design §6.9, AV12).
5497
+ * 403 is not-member and 401, which `probeState` returns only once a silent re-auth has failed, is
5498
+ * locked; either closes the channel for good, like a log refusal. Any
5499
+ * other answer (404 while step I has no state route, 2xx, 5xx, a network error) resumes, at most
5500
+ * once per {@link NOT_MEMBER_RECHECK_MS}.
5501
+ */
5502
+ private onNotMemberClose;
5503
+ /** Close the socket of a user the log turned away, and never dial that channel again (design §3.7). */
5504
+ private dropChannel;
5505
+ /**
5506
+ * Open the space websocket (resolves the ticket + identity lazily). On a
5507
+ * Space with a membership log, the log is checked first: a user it does not
5508
+ * list, or lists under another identity key, is refused with
5509
+ * {@link AdmissionDeniedError} before any ticket is fetched (design §4.5.12).
5510
+ */
4996
5511
  connect(): Promise<void>;
4997
5512
  disconnect(): void;
4998
5513
  /** Attach a P2P swarm so this Space's file reads/writes can use peers. */
@@ -5008,7 +5523,7 @@ declare class Space {
5008
5523
  gossipToPeers(data: Uint8Array): void;
5009
5524
  /** Subscribe to peer gossip ({@link gossipToPeers}). Returns an unsubscribe fn. */
5010
5525
  onPeerGossip(handler: (from: string, data: Uint8Array) => void): () => void;
5011
- /** Subscribe to a raw channel event. Buffered until `connect()` if early. */
5526
+ /** Subscribe to a raw channel event. Buffered until `connect()` if early, and kept across a new channel. */
5012
5527
  on(event: string, handler: Listener): void;
5013
5528
  off(event: string, handler: Listener): void;
5014
5529
  /** Advertise our keyExchange to the room (idempotent per connection). */
@@ -5092,7 +5607,7 @@ declare class Space {
5092
5607
  timeoutMs?: number;
5093
5608
  pollMs?: number;
5094
5609
  }): Promise<boolean>;
5095
- /** Creator path: mint the initial group key, then connect. */
5610
+ /** Creator path: mint epoch 0; for a private Space, sign the genesis + ec(0) BEFORE connecting (design §4.5.10). */
5096
5611
  create(): Promise<void>;
5097
5612
  /** Wrap the group key for a newcomer and post it (key-holder action). */
5098
5613
  admit(memberId: string, identityEcdhPub: string): Promise<void>;
@@ -5103,6 +5618,29 @@ declare class Space {
5103
5618
  memberId: string;
5104
5619
  identityEcdhPub: string;
5105
5620
  }>): Promise<number>;
5621
+ /** Whether this Space has a verified membership log. */
5622
+ get logEnabled(): boolean;
5623
+ /** The verified member list, or null when this Space has no log. */
5624
+ membership(): MemberView[] | null;
5625
+ /** Called with the member list (null when unlogged) each time the log is re-synced. Returns an unsubscribe. */
5626
+ onMembership(h: (m: MemberView[] | null) => void): () => void;
5627
+ private requireLog;
5628
+ /** Add by exact username (design §3.8; the owner, or an editor for viewers). Looks the IK up in the directory. */
5629
+ add(username: string, opts?: {
5630
+ role?: "viewer" | "editor";
5631
+ }): Promise<{
5632
+ seq: number;
5633
+ }>;
5634
+ /** Remove a member. In this build it cuts them off at the relay; it does not rotate the key. */
5635
+ remove(memberId: string): Promise<{
5636
+ seq: number;
5637
+ }>;
5638
+ setRole(memberId: string, role: "viewer" | "editor"): Promise<{
5639
+ seq: number;
5640
+ }>;
5641
+ leave(): Promise<{
5642
+ seq: number;
5643
+ }>;
5106
5644
  putFile(file: File | Blob | Uint8Array, metadata: SpaceFileMetadata, opts?: {
5107
5645
  onProgress?: (completed: number, total: number) => void;
5108
5646
  /**
@@ -5295,19 +5833,27 @@ declare class ChatKeyVault {
5295
5833
  private readonly store;
5296
5834
  constructor(store: ChatKeyStore);
5297
5835
  /**
5298
- * Register-time: ensure a keypair exists for `memberId`, seed-wrap the
5299
- * dehydrated form, and persist it to the personal space. Idempotent — reuses
5300
- * an existing in-tab keypair rather than minting a second one.
5836
+ * Register-time: ensure a vault-backed keypair exists for `memberId`,
5837
+ * seed-wrap it and persist it. Idempotent for a pair that is already
5838
+ * vault-backed. Otherwise a fresh pair is generated, persisted, and only
5839
+ * then swapped in, replacing whatever the tab held: a throwaway key is never
5840
+ * persisted as the stable one, and a failed persist leaves the tab's keys
5841
+ * as they were, with nothing marked.
5301
5842
  */
5302
5843
  provision(memberId: string, seedSecret: string): Promise<void>;
5303
5844
  /**
5304
- * Sign-in / unlock time: fetch the wrapped blob, unwrap with the seed, and
5305
- * hydrate the `KeyStore` — restoring the SAME keypair the member had before.
5306
- * Returns `true` when a keypair is now in hand (already present or hydrated),
5307
- * `false` when there's no blob yet (a brand-new member → caller provisions).
5308
- * Throws only on a genuine unwrap failure (wrong secret / tampered blob).
5845
+ * Sign-in / unlock time: fetch the wrapped blob, unwrap it with the seed, and
5846
+ * hydrate the `KeyStore`, restoring the SAME keypair the member had before.
5847
+ * Returns `true` when a vault-backed keypair is now in hand, and `false` when
5848
+ * there is no blob yet (a brand-new member; the caller provisions). A store
5849
+ * failure propagates as thrown; a blob that does not unwrap, parse or import
5850
+ * throws `ChatKeysUnreadableError`. Nothing in the `KeyStore` changes until
5851
+ * the saved pair is imported; it then replaces whatever pair the tab held,
5852
+ * in one step, so a pair minted meanwhile is never kept in its place.
5309
5853
  */
5310
5854
  rehydrate(memberId: string, seedSecret: string): Promise<boolean>;
5855
+ /** Unwrap, parse and import a saved pair, outside the `KeyStore`. */
5856
+ private unwrapPair;
5311
5857
  /**
5312
5858
  * Ensure a stable keypair is in hand: rehydrate from the vault if a blob
5313
5859
  * exists, otherwise mint one and persist it (so it's stable next session).
@@ -5316,6 +5862,101 @@ declare class ChatKeyVault {
5316
5862
  ensure(memberId: string, seedSecret: string): Promise<void>;
5317
5863
  }
5318
5864
 
5865
+ /**
5866
+ * POST /api/auth/identity (SpecA §4.1 as amended by CS-7). An unknown name
5867
+ * answers a deterministic decoy, so a lookup never says whether an account
5868
+ * exists. Responses are cached for the life of the Client. Any failure,
5869
+ * including a 429, fails closed (SpecA §4.3).
5870
+ */
5871
+ declare class IdentityDirectory {
5872
+ private readonly http;
5873
+ private readonly cache;
5874
+ constructor(http: Pick<HttpClient, "post">);
5875
+ lookup(username: string): Promise<string>;
5876
+ lookupMany(usernames: string[]): Promise<Record<string, string>>;
5877
+ }
5878
+
5879
+ /** Signs with the account identity key (IK). Never exposes the seed (SpecA §6.1). */
5880
+ interface IdentitySigner {
5881
+ /** b64 IEEE-P1363 ECDSA-P256-SHA256 over `bytes`. */
5882
+ sign(bytes: Uint8Array): Promise<string>;
5883
+ /** The IK as standard padded base64 of the raw 65-byte point. */
5884
+ identityKeyRaw(): Promise<string>;
5885
+ }
5886
+
5887
+ /**
5888
+ * IndexedDB `muhkoo.trust` (design §6.6): pin high-water marks and per-Space log
5889
+ * heads. When IndexedDB is absent, throws on open, or has not opened within 2 s,
5890
+ * it is an in-memory store for the life of the Client, and `persistent` is
5891
+ * false (KI1).
5892
+ */
5893
+ interface TrustStore {
5894
+ readonly persistent: boolean;
5895
+ get<T>(key: string): Promise<T | undefined>;
5896
+ put(key: string, value: unknown): Promise<void>;
5897
+ del(key: string): Promise<void>;
5898
+ }
5899
+
5900
+ type PinDocName = "identity" | "spaces";
5901
+ interface PinDoc {
5902
+ v: 1;
5903
+ ownerCommitment: string;
5904
+ counter: number;
5905
+ entries: Record<string, string>;
5906
+ sig: string;
5907
+ }
5908
+ interface PinStoreDeps {
5909
+ http: Pick<HttpClient, "post">;
5910
+ commitment: () => string | null;
5911
+ signer: IdentitySigner;
5912
+ store: () => Promise<TrustStore>;
5913
+ }
5914
+ declare class PinStore {
5915
+ private readonly deps;
5916
+ constructor(deps: PinStoreDeps);
5917
+ private commitment;
5918
+ private markKey;
5919
+ private kvPath;
5920
+ private fetchDoc;
5921
+ private verify;
5922
+ /** The current verified document; an empty bootstrap at counter 0 when none exists and no mark does either. */
5923
+ read(doc: PinDocName): Promise<PinDoc>;
5924
+ get(doc: PinDocName, key: string): Promise<string | undefined>;
5925
+ /** Pin new keys. A key already pinned to a different value is reported, never overwritten (design §3.4). */
5926
+ pin(doc: PinDocName, entries: Record<string, string>): Promise<{
5927
+ conflicts: string[];
5928
+ }>;
5929
+ unpin(doc: PinDocName, keys: string[]): Promise<void>;
5930
+ /** User-confirmed recovery (design §9.1 "Your saved trust records are missing."): forget the high-water mark. */
5931
+ resetMark(doc: PinDocName): Promise<void>;
5932
+ private mutate;
5933
+ }
5934
+
5935
+ /** One per Client: shared by every Space (and, in sub-project 4, DMs). */
5936
+ declare class TrustContext {
5937
+ private readonly deps;
5938
+ readonly directory: IdentityDirectory;
5939
+ readonly signer: IdentitySigner;
5940
+ readonly pins: PinStore;
5941
+ private storeP;
5942
+ private opened;
5943
+ private appIdP;
5944
+ constructor(deps: {
5945
+ http: HttpClient;
5946
+ session: SessionState;
5947
+ openStore?: () => Promise<TrustStore>;
5948
+ });
5949
+ /** Whether muhkoo.trust survives a restart (KI1): false until the open settles, and false for an in-memory store. */
5950
+ get persistent(): boolean;
5951
+ store(): Promise<TrustStore>;
5952
+ /** This app's id, from GET /api/app/trust (a genesis must name it, D4). Cached; a failure is retried next call. */
5953
+ appId(): Promise<string>;
5954
+ /** User-confirmed "Reset trust for this space" (design §9.1): drop its genesis pin and log high-water. */
5955
+ resetSpacePins(spaceId: string): Promise<void>;
5956
+ /** `client.trust.unpin(username)`: forget a member's pinned identity after comparing safety numbers (design §3.4). */
5957
+ unpinUser(username: string): Promise<void>;
5958
+ }
5959
+
5319
5960
  /**
5320
5961
  * `client.space` — the fan-out group-encryption surface.
5321
5962
  *
@@ -5354,6 +5995,9 @@ interface SpaceNamespaceDeps {
5354
5995
  /** SDK-owned ratchet-keypair vault — gives a STABLE member keypair across
5355
5996
  * reloads (rehydrate-or-provision) instead of a fresh one each session. */
5356
5997
  chatKeys?: ChatKeyVault;
5998
+ /** The Client's trust context. Present, every join request carries the IK
5999
+ * certificate over this member's chat keys (design §3.2). */
6000
+ trust?: TrustContext;
5357
6001
  /** P2P swarm config — enables peer block exchange among Space members. */
5358
6002
  p2p?: {
5359
6003
  enabled?: boolean;
@@ -5369,6 +6013,10 @@ declare class SpaceNamespace {
5369
6013
  private spaceCache?;
5370
6014
  /** Spaces built this session, by id — used to route offline send replays. */
5371
6015
  private readonly openSpaces;
6016
+ /** In-flight identity loads, by member id; each entry is deleted once it settles. */
6017
+ private readonly identityInFlight;
6018
+ /** Chat-key certificates, by member and the exact key strings; a failed mint is dropped so the next join retries. */
6019
+ private readonly certs;
5372
6020
  /** Unique id for THIS device/session — the P2P peer identity. A user's own
5373
6021
  * devices share a member id, so the mesh MUST distinguish them by this or a
5374
6022
  * device treats its siblings' signals as its own echo and never connects. */
@@ -5397,8 +6045,14 @@ declare class SpaceNamespace {
5397
6045
  joinSpace(spaceId: string, opts?: {
5398
6046
  timeoutMs?: number;
5399
6047
  }): Promise<Space>;
5400
- /** A handle to a space without auto-connecting (uses cached keys on demand). */
5401
- get(spaceId: string, historyPolicy?: HistoryPolicy): Promise<Space>;
6048
+ /**
6049
+ * A handle to a space without auto-connecting (uses cached keys on demand).
6050
+ * `messaging: false` opens a files-only space, as {@link createSpace} makes:
6051
+ * no keyring and no identity, so it opens whether or not the session is locked.
6052
+ */
6053
+ get(spaceId: string, historyPolicy?: HistoryPolicy, opts?: {
6054
+ messaging?: boolean;
6055
+ }): Promise<Space>;
5402
6056
  /** List this app's channels (name → space-id) for the key's environment. */
5403
6057
  listChannels(): Promise<Array<{
5404
6058
  name: string;
@@ -5457,8 +6111,22 @@ declare class SpaceNamespace {
5457
6111
  }>>;
5458
6112
  private keyringFor;
5459
6113
  private build;
5460
- /** Ensure the member has an identity keypair; return its public ECDH + ECDSA keys. */
6114
+ /**
6115
+ * The member's stable chat identity (design §3.3). A locked session (no
6116
+ * seed) throws `IdentityLockedError`. A vault failure throws
6117
+ * `IdentityUnavailableError`: `retryable` when the vault could not be
6118
+ * reached (§9.2 I8), and not when the saved keys do not unwrap under this
6119
+ * seed. A throwaway pair is never minted: doing so is what hid a reloaded
6120
+ * member's earlier messages from everyone else.
6121
+ *
6122
+ * Concurrent calls for one member share a single load, so two first opens
6123
+ * can never interleave their forget/generate steps and persist a second or
6124
+ * mixed pair.
6125
+ */
5461
6126
  private ensureIdentity;
6127
+ private loadIdentity;
6128
+ /** One certificate per (member, chat keys) for the life of the Client (design §3.2: global, not per Space). */
6129
+ private certFor;
5462
6130
  private fetchPolicy;
5463
6131
  private fetchTicket;
5464
6132
  private myId;
@@ -6089,6 +6757,8 @@ interface VfsContentStore {
6089
6757
  write(data: Uint8Array | Blob, meta: {
6090
6758
  name: string;
6091
6759
  type: string;
6760
+ }, opts?: {
6761
+ onProgress?: (done: number, total: number) => void;
6092
6762
  }): Promise<{
6093
6763
  manifest: FileManifest;
6094
6764
  size: number;
@@ -6149,6 +6819,18 @@ declare class VfsNamespace {
6149
6819
  * than by TTL — a stale directory listing shows files that are not there.
6150
6820
  */
6151
6821
  private cache;
6822
+ /**
6823
+ * The write of each directory record still in flight, by id.
6824
+ *
6825
+ * Two writes of one record must land in the order they were made. The
6826
+ * personal space has no compare-and-swap: of two writes of one key that
6827
+ * are in flight together it keeps whichever request finishes last, and
6828
+ * that is often the one sent first. Then the earlier write, sealed before
6829
+ * the later edit was made, undoes it. That is how a file deleted forever
6830
+ * from the Trash, while the move that put it there was still being
6831
+ * written, came back. So each write waits for the one before it.
6832
+ */
6833
+ private readonly writing;
6152
6834
  private readonly historyLimit;
6153
6835
  private readonly maxEntries;
6154
6836
  private readonly maxPathChars;
@@ -6307,6 +6989,7 @@ declare class VfsNamespace {
6307
6989
  */
6308
6990
  writeFile(path: string, data: Uint8Array | string | Blob, opts?: {
6309
6991
  type?: string;
6992
+ onProgress?: (done: number, total: number) => void;
6310
6993
  }): Promise<VfsStat>;
6311
6994
  /**
6312
6995
  * Delete a file, or a directory when `recursive`.
@@ -6420,6 +7103,14 @@ declare class VfsNamespace {
6420
7103
  private readRecord;
6421
7104
  /** Apply an edit to a directory record and persist it. */
6422
7105
  private mutate;
7106
+ /**
7107
+ * Seal a directory record as it stands NOW and put it in the store.
7108
+ *
7109
+ * Read at the moment of sending, not when the edit was made, so a write
7110
+ * that waited behind another carries every edit made to the record by
7111
+ * then, its own included.
7112
+ */
7113
+ private writeRecord;
6423
7114
  private createDir;
6424
7115
  /**
6425
7116
  * Every directory record in a subtree, each once, opened - and nothing
@@ -7009,6 +7700,12 @@ declare class Client {
7009
7700
  readonly message: MessageNamespace;
7010
7701
  /** Fan-out group spaces — `client.space.createSpace(...)`, etc. */
7011
7702
  readonly space: SpaceNamespace;
7703
+ /** Trust records shared by every Space: pins and per-Space log heads (design §3.4, §6.9). */
7704
+ readonly trust: {
7705
+ readonly persistent: boolean;
7706
+ unpin(username: string): Promise<void>;
7707
+ resetSpacePins(spaceId: string): Promise<void>;
7708
+ };
7012
7709
  /** Programmable Agents — `client.agents.create(appId, …)`, etc. */
7013
7710
  readonly agents: AgentsNamespace;
7014
7711
  /** Serverless functions — `client.functions.deploy(appId, …)`, etc. */
@@ -7945,14 +8642,29 @@ declare namespace protocol {
7945
8642
  * GET /api/spaces/:id/keyring/pending -> JoinRequest[]
7946
8643
  * GET /api/spaces/:id/keyring/roster -> { memberId, identityEcdhPub }[]
7947
8644
  * POST /api/spaces/:id/keyring/rotate { nextEpoch } -> { epoch }
8645
+ * GET /api/spaces/:id/keyring/log ?since&limit -> { entries, head }
8646
+ * POST /api/spaces/:id/keyring/log { entry, ec? } -> { ok, head }
8647
+ * GET /api/spaces/:id/keyring/state (status only)
7948
8648
  * GET /api/spaces/:id/metadata -> SpaceMetadata
7949
8649
  */
7950
8650
 
8651
+ /** A keyring route answered non-2xx; status and JSON body preserved (the existing methods keep their plain Errors). */
8652
+ declare class KeyringHttpError extends Error {
8653
+ readonly status: number;
8654
+ readonly body: Record<string, unknown> | null;
8655
+ constructor(status: number, body: Record<string, unknown> | null, route: string);
8656
+ }
7951
8657
  interface KeyringClientDeps {
7952
8658
  spaceId: string;
7953
8659
  httpBaseUrl: string;
7954
8660
  /** Header-injecting fetch from the client's HttpClient. */
7955
8661
  fetch: typeof fetch;
8662
+ /**
8663
+ * The same fetch with the HttpClient's silent 401 recovery (`HttpClient.fetchWithRecovery`).
8664
+ * `probeState` and the log routes use it, so a 401 reads as locked only once a re-auth has failed (design §6.9).
8665
+ * Defaults to {@link fetch}.
8666
+ */
8667
+ fetchWithRecovery?: typeof fetch;
7956
8668
  }
7957
8669
  declare class KeyringClient implements KeyringTransport {
7958
8670
  private readonly deps;
@@ -7960,6 +8672,27 @@ declare class KeyringClient implements KeyringTransport {
7960
8672
  private url;
7961
8673
  private post;
7962
8674
  private get;
8675
+ /**
8676
+ * The log routes. Sent through `fetchWithRecovery` when given, as `probeState` is, so a stale session is
8677
+ * re-established before a 401 reads as locked (design §6.9): a GET replays safely, and a POST body is a string.
8678
+ */
8679
+ private request;
8680
+ /** GET /keyring/log (design §3.6): entries with seq > since, ascending, ≤ limit; head null = no log. */
8681
+ fetchLog(since?: number, limit?: number): Promise<{
8682
+ entries: MembershipEntry[];
8683
+ head: MlogHead | null;
8684
+ }>;
8685
+ /** POST /keyring/log {entry, ec?} → {ok, head}; `409 {error:"Log moved", head}` (no code) on a compare-and-set miss. */
8686
+ postLogEntry(entry: MembershipEntry, ec?: EpochCommit): Promise<{
8687
+ ok: true;
8688
+ head: MlogHead;
8689
+ }>;
8690
+ /**
8691
+ * Status of GET /keyring/state, or "network" when the request fails: 403 is not-member, 401 is locked,
8692
+ * anything else resumes (design §6.9). Sent through `fetchWithRecovery` when given, so a stale session
8693
+ * is re-established before a 401 is believed.
8694
+ */
8695
+ probeState(): Promise<number | "network">;
7963
8696
  postJoinRequest(req: JoinRequest): Promise<void>;
7964
8697
  fetchBlobs(memberId: string): Promise<WrappedKey[]>;
7965
8698
  postWrappedKey(targetMemberId: string, wrapped: WrappedKey, fromMemberId: string): Promise<void>;
@@ -7994,6 +8727,43 @@ declare class KeyringClient implements KeyringTransport {
7994
8727
  fetchMetadata(): Promise<SpaceMetadata | null>;
7995
8728
  }
7996
8729
 
8730
+ /**
8731
+ * Trust-layer errors shared by Spaces and, later, DMs (design §3.3, §6.9;
8732
+ * SpecA §6.6). Class names are public API and are frozen at their first
8733
+ * release (design Q16).
8734
+ */
8735
+ /** The session holds no seed, so it has no stable identity and may not take part (design §3.3, T7). */
8736
+ declare class IdentityLockedError extends Error {
8737
+ readonly code: "identity-locked";
8738
+ constructor(message?: string);
8739
+ }
8740
+ /**
8741
+ * The stable chat keys could not be loaded (design §3.3). `retryable` says
8742
+ * whether the same call can succeed later. It is true when the vault could not
8743
+ * be reached or written (a 5xx or a network error on `chat-keys`), which the
8744
+ * next open retries (§9.2 I8). It is false when the saved keys were read but do
8745
+ * not unwrap under this session's seed, because they are damaged or were saved
8746
+ * under another seed, and when the client has no vault: retrying fails the same way.
8747
+ */
8748
+ declare class IdentityUnavailableError extends Error {
8749
+ readonly reason?: unknown | undefined;
8750
+ readonly retryable: boolean;
8751
+ readonly code: "identity-unavailable";
8752
+ constructor(message?: string, reason?: unknown | undefined, retryable?: boolean);
8753
+ }
8754
+ type SpaceTrustCode = "integrity" | "log-conflict" | "log-refused" | "lookup-failed" | "pins-missing" | "pins-rollback" | "pins-stale" | "pins-invalid" | "pins-write-failed";
8755
+ /** A membership-log or pin failure. `detail` carries server fields (`reason`, `head`, ...). */
8756
+ declare class SpaceTrustError extends Error {
8757
+ readonly code: SpaceTrustCode;
8758
+ readonly detail?: Record<string, unknown> | undefined;
8759
+ constructor(code: SpaceTrustCode, message?: string, detail?: Record<string, unknown> | undefined);
8760
+ }
8761
+ /** This user may not open this Space (design §4.5.12, V25). */
8762
+ declare class AdmissionDeniedError extends Error {
8763
+ readonly code: "not-member" | "identity-mismatch";
8764
+ constructor(code: "not-member" | "identity-mismatch", message?: string);
8765
+ }
8766
+
7997
8767
  declare class Logger {
7998
8768
  private prefix;
7999
8769
  private logLevel;
@@ -8228,7 +8998,7 @@ declare function getId(row: {
8228
8998
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
8229
8999
  * `package.json` `version`.
8230
9000
  */
8231
- declare const VERSION = "0.14.0-alpha.9";
9001
+ declare const VERSION = "0.15.0-alpha.1";
8232
9002
 
8233
9003
  /**
8234
9004
  * The SDK's process-global logger.
@@ -8260,5 +9030,5 @@ declare global {
8260
9030
  }
8261
9031
  declare const appLogger: InstanceType<typeof Logger>;
8262
9032
 
8263
- export { ACCESS_TOKEN_SCOPES, AES_GCM_IV_BYTES, AES_GCM_KEY_BYTES, AES_GCM_TAG_BYTES, AccessTokensNamespace, AgentsNamespace, AuthClient, AuthNamespace, AuthPublicInput, Authenticator, BlockEngine, BroadcastChannel, BroadcastChannelEvents, ChannelExistsError, ChannelNotFoundError, ChunkCipher, CircuitIntegrityError, Client, ConnectivityManager, DEFAULT_AUTH_BASE_URL, DEFAULT_BASE_URL, DEFAULT_BRANCH, DEFAULT_FN_HOST_SUFFIX, DEVICE_IDENTITY_STORAGE_KEY, DEVICE_KEY_DB_NAME, DbCache, DbNamespace, DbTable, DevicePairingError, DeviceRevokedError, DeviceStoreUnavailableError, DoubleRatchet, DoubleRatchetCipher, DoubleRatchetManager, EncryptedSession, EventCore, EventCoreEvents, Field, Field as FieldElement, FileStorage, FunctionsNamespace, HLC, HashKnowledge, HostedAuth, HttpClient, HttpError, IndexedDbStore, KEEPER_MEMBER_ID, KeyStore, KeyringClient, KvCache, KvNamespace, LocalEngineHost, LocalStorageSessionStore, Logger, MemorySessionStore, Message, Message as MessageDefault, MessageNamespace, MuhkooAgent, MuhkooDB, MuhkooFunction, MuhkooSpace, NoopStore, OfflineLockedError, OfflineManager, OutboundQueue, P2P_SUBJECT, PAIRING_CODE_ALPHABET, PAIRING_CODE_LENGTH, PBKDF2_DEFAULT_ITERATIONS, PINNED_CIRCUIT_DIGESTS, PREIMAGE_POK_VERIFICATION_KEY, Packet, PasskeyOriginError, PeerExchange, PeerNetwork, PersonalSpaceClient, Poseidon, PreimagePoK, ROOT_ID, Ready, ReauthRequiredError, ReedSolomonCodec, Repo, Retry, Space as Room, SerializeMessage, SessionState, ShardCache, ShardClient, SharedSpaceClient, SnapshotNamespace, Space, SpaceCache, SpaceKeyring, SpaceNamespace, SpacePacketCipher, SpaceSignaler, StorageNamespace, SyncEngine, VERSION, VFS_ROOT_INFO, VaultUnavailableError, VcsError, VcsNamespace, VfsConflictError, VfsCorruptRecordError, VfsLimitError, VfsLockedError, VfsNamespace, VfsNotFoundError, WSTransport, WebRtcTransport, WorkerEngineHost, ZERO_HLC, ZkAuth, _accountId, _formatBytes, _messageId, _objectId, _packetId, _socketId, _userId, appLogger, assertType, base58Decode, base58Encode, buildCommitment, canonicalUserCode, clearDeviceIdentity, compareHlc, compilePrograms, concatBytes, configureDeviceStore, createPasskeyWithPrf, decodeFromHex, decorators, decryptAesGcm, defaultCircuitUrls, defaultRpId, defaultSessionStore, deleteBeatsWrite, deleteRegister, deriveAesKeyFromPassphrase, deriveBitsHkdf, deriveIdentity, deriveIdentityFromSeed, deriveMasterSeedFromPassword, deserialize, deviceFingerprint, deviceIdentityIsEphemeral, deviceIdentityKey, dirKey, ejectAgentPrompt, ejectAgentTools, emptyMap, emptyRegister, encodeToHex, encryptAesGcm, evaluatePasskeyPrf, exportPublicKeyBase64, exportPublicKeyHex, formatUserCode, fromBase64, fromBase64Url, fromHex, generateAuthProof, generateChecksum, generateDevicePairingKeypair, generateId, generatePkce, getIPAddress, getId, getMuhkooAppDescriptor, getSubtle, hasDeviceIdentity, headKey, historyKey, initBn128Wasm, initRsWasm, initializeCircuits, isNewer, isNumber, isOfflineCapable, isP2pCapable, isProvisional, loadDeviceIdentity, log, manifestToStat, materializeRow, merge3, merge3Text, mergeKey, mergeMap, mergeMessage, mergeRegister, messageKey, objectKey, pack, padHandle, pairingVerificationCode, passkeyPrfCapable, passkeySupported, passkeyUsableFromOrigin, persistDeviceIdentity, poseidonHash, protocol, provisionalHandle, quickVerify, randomBytes, randomState, refKey, refsIndexKey, rpIdUsableForOrigin, sealSeed, sealSeedToDevice, serialize, shardHash, signMessage, stableStringify, toBase64, toBase64Url, toField, toHex, tombstoneRow, unpack, unsealSeed, unsealSeedFromDevice, unwrapWithPassphrase, utf8Decode, utf8Encode, verify, verifyChecksum, verifyGroth16, verifyHashKnowledge, verifyPreimagePoK, wrapWithPassphrase, writeBeatsTombstone, writeColumns, writeRegister };
8264
- export type { ACL, ACLEntry, AccessTokenInfo, AccessTokensNamespaceDeps, AgentConfig, AgentCreateInput, AgentDbToolMode, AgentProvisioned, AgentScopeOpts, AgentSkill, AgentToolsConfig, AgentTrigger, AgentTriggerType, AgentUpdateInput, AgentsNamespaceDeps, AttachedPeerNetwork, AuthClientOptions, AuthKeyPair, AuthSuccess, AuthToken, AuthType, AuthUser, BlockStore, Bn128WasmInstance, BroadcastChannelEvent, BroadcastChannelOptions, CachedSpaceMessage, Change, ChannelLike, ChunkKeyMaterial, ChunkManifest, CipherFrame, CipherMessage, CipherMessageHeader, CircuitBufferConfig, CircuitIntegrity, CircuitUrls, ClientOptions, Commit, Conflict, ConnectivityOptions, ConnectivityState, CreateAccessTokenInput, CreatedAccessToken, DbFilterOp, DbNamespaceDeps, DbQuery, DbQueryPage, DbQueryResult, DbWhereCondition, DehydratedKeypair, DehydratedKeys, DeviceBlobStore, DeviceKeyVault, DevicePairingErrorReason, DevicePairingKeypair, DevicePairingPoll, DevicePairingRequest, DevicePairingSession, DirEntry, DirNode, DoubleRatchetCipherDeps, ECDHCurve, EncodeResult, EngineHandle, EngineHost, Entry, EphemeralEvent, EpochKeyProvider, FileEntry, FileManifest, FileNamespaceDeps, FileStat, FileStorageOptions, FunctionCaps, FunctionConfig, FunctionDeployInput, FunctionInvokeOptions, FunctionScopeOpts, FunctionTrigger, FunctionTriggerType, FunctionTriggers, FunctionUpdateInput, FunctionsNamespaceDeps, Groth16Proof, Hasher, Head, HistoryRecord, HlcParts, HlcState, HostedAuthDeps, HttpClientOptions, IncomingFrame, KeyExchangeFrame, KeyPair, Keypair, KeyringClientDeps, KeyringTransport, KvEntry, KvNamespaceDeps, LogEntry, LoginOptions, LwwMap, LwwRegister, Merge3Result, MergeRegionConflict, MergeResult, MessageBody, MessageDeletedEvent, MessageEntry, MessageEvent, MessageHeaders, MessageNamespaceDeps, MessageOp, MessageOptions, MessageSubscription, MuhkooAgentMeta, MuhkooAgentToolsConfig, MuhkooAppDescriptor, MuhkooDBAccess, MuhkooDBMeta, MuhkooFunctionMeta, MuhkooKeystoreBridge, MuhkooMessageEvent, MuhkooSpaceMeta, OAuthProvider, OfflineEntry, OfflineManagerDeps, OfflineStore, OutSink, PacketCipher, PacketHeaders, PacketOptions, PairedDevice, Participant, PasskeyEnrollment, PeerBlockSource, PeerExchangeOptions, PeerId, PeerNetworkOptions, PeerTransport, Permission, PermissionCheckRequest, PermissionCheckResult, PersistedDeviceIdentity, PersonalSpaceClientOptions, Pkce, PublicKeyJWK, QueueEntry, RatchetState, ReceiveResult, RegisterParams, Replayer, Role, SpaceDeps as RoomDeps, SpaceFileMetadata as RoomFileMetadata, Scope, SealedHandoff, SealedHeaders, SealedRecord, Session, SessionOptions, SessionStore, SetOptions, ShardByteCache, ShardClientOptions, SharedSpaceClientOptions, SignalEnvelope, SignalKind, SignalingSpace, SnapshotNamespaceDeps, SnarkProof, SpaceChannelLike, SpaceCursor, SpaceDeps, SpaceFileMetadata, SpaceKeyCache, SpaceKeyringDeps, SpaceMessageEvent, SpaceNamespaceDeps, SpaceOfflineAdapter, StartDevicePairingOptions, StorageChangeEvent, StoreName, StoredSession, Subscription, Tree, TreeDir, TreeEntry, TreeFile, UserIdentity, VaultFactor, VcsNamespaceDeps, VcsNamespaceOptions, VerificationKey, VfsContentStore, VfsNamespaceDeps, VfsStat, VfsStore, WSMessage, WSMessageType, WSTransportOptions, WaitForDevicePairingOptions, WebRtcTransportOptions, WrappedPayload, WriteFileInput, WriteFileOptions, WriteToShardsInput, ZkAuthDeps, ZkAuthenticateRequest, ZkChallengeResponse, ZkCompiled, ZkIdentity, ZkRegisterRequest, ZkRegisterResponse };
9033
+ export { ACCESS_TOKEN_SCOPES, AES_GCM_IV_BYTES, AES_GCM_KEY_BYTES, AES_GCM_TAG_BYTES, AccessTokensNamespace, AdmissionDeniedError, AgentsNamespace, AuthClient, AuthHttpError, AuthNamespace, AuthPublicInput, Authenticator, BlockEngine, BroadcastChannel, BroadcastChannelEvents, ChannelExistsError, ChannelNotFoundError, ChunkCipher, CircuitIntegrityError, Client, ConnectivityManager, DEFAULT_AUTH_BASE_URL, DEFAULT_BASE_URL, DEFAULT_BRANCH, DEFAULT_FN_HOST_SUFFIX, DEVICE_IDENTITY_STORAGE_KEY, DEVICE_KEY_DB_NAME, DbCache, DbNamespace, DbTable, DevicePairingError, DeviceRevokedError, DeviceStoreUnavailableError, DoubleRatchet, DoubleRatchetCipher, DoubleRatchetManager, EncryptedSession, EventCore, EventCoreEvents, Field, Field as FieldElement, FileStorage, FunctionsNamespace, HLC, HashKnowledge, HostedAuth, HttpClient, HttpError, IdentityLockedError, IdentityUnavailableError, IndexedDbStore, KEEPER_MEMBER_ID, KeyStore, KeyringClient, KeyringHttpError, KvCache, KvNamespace, LocalEngineHost, LocalStorageSessionStore, Logger, MemorySessionStore, Message, Message as MessageDefault, MessageNamespace, MuhkooAgent, MuhkooDB, MuhkooFunction, MuhkooSpace, NOT_MEMBER_CLOSE_CODE, NOT_MEMBER_RECHECK_MS, NoopStore, OfflineLockedError, OfflineManager, OutboundQueue, P2P_SUBJECT, PAIRING_CODE_ALPHABET, PAIRING_CODE_LENGTH, PBKDF2_DEFAULT_ITERATIONS, PINNED_CIRCUIT_DIGESTS, PREIMAGE_POK_VERIFICATION_KEY, Packet, PasskeyOriginError, PeerExchange, PeerNetwork, PersonalSpaceClient, Poseidon, PreimagePoK, ROOT_ID, Ready, ReauthRequiredError, ReedSolomonCodec, Repo, Retry, Space as Room, SerializeMessage, SessionState, ShardCache, ShardClient, SharedSpaceClient, SnapshotNamespace, Space, SpaceCache, SpaceKeyring, SpaceNamespace, SpacePacketCipher, SpaceSignaler, SpaceTrustError, StorageNamespace, SyncEngine, VERSION, VFS_ROOT_INFO, VaultProtocolMismatchError, VaultUnavailableError, VcsError, VcsNamespace, VfsConflictError, VfsCorruptRecordError, VfsLimitError, VfsLockedError, VfsNamespace, VfsNotFoundError, WSTransport, WebRtcTransport, WorkerEngineHost, ZERO_HLC, ZkAuth, _accountId, _formatBytes, _messageId, _objectId, _packetId, _socketId, _userId, appLogger, assertType, base58Decode, base58Encode, buildCommitment, canonicalUserCode, clearDeviceIdentity, compareHlc, compilePrograms, concatBytes, configureDeviceStore, createPasskeyWithPrf, decodeFromHex, decorators, decryptAesGcm, defaultCircuitUrls, defaultRpId, defaultSessionStore, deleteBeatsWrite, deleteRegister, deriveAesKeyFromPassphrase, deriveBitsHkdf, deriveIdentity, deriveIdentityFromSeed, deriveMasterSeedFromPassword, deserialize, deviceFingerprint, deviceIdentityIsEphemeral, deviceIdentityKey, dirKey, ejectAgentPrompt, ejectAgentTools, emptyMap, emptyRegister, encodeToHex, encryptAesGcm, evaluatePasskeyPrf, exportPublicKeyBase64, exportPublicKeyHex, formatUserCode, fromBase64, fromBase64Url, fromHex, generateAuthProof, generateChecksum, generateDevicePairingKeypair, generateId, generatePkce, getIPAddress, getId, getMuhkooAppDescriptor, getSubtle, hasDeviceIdentity, headKey, historyKey, initBn128Wasm, initRsWasm, initializeCircuits, isNewer, isNumber, isOfflineCapable, isP2pCapable, isProvisional, isSameOriginPath, loadDeviceIdentity, log, manifestToStat, materializeRow, merge3, merge3Text, mergeKey, mergeMap, mergeMessage, mergeRegister, messageKey, objectKey, pack, padHandle, pairingVerificationCode, passkeyPrfCapable, passkeySupported, passkeyUsableFromOrigin, persistDeviceIdentity, poseidonHash, protocol, provisionalHandle, quickVerify, randomBytes, randomState, refKey, refsIndexKey, rpIdUsableForOrigin, sealSeed, sealSeedToDevice, serialize, shardHash, signMessage, stableStringify, toBase64, toBase64Url, toField, toHex, tombstoneRow, unpack, unsealSeed, unsealSeedFromDevice, unwrapWithPassphrase, utf8Decode, utf8Encode, verify, verifyChecksum, verifyGroth16, verifyHashKnowledge, verifyPreimagePoK, wrapWithPassphrase, writeBeatsTombstone, writeColumns, writeRegister };
9034
+ export type { ACL, ACLEntry, AccessTokenInfo, AccessTokensNamespaceDeps, AdmissionState, AgentConfig, AgentCreateInput, AgentDbToolMode, AgentProvisioned, AgentScopeOpts, AgentSkill, AgentToolsConfig, AgentTrigger, AgentTriggerType, AgentUpdateInput, AgentsNamespaceDeps, AttachedPeerNetwork, AuthClientOptions, AuthKeyPair, AuthSuccess, AuthToken, AuthType, AuthUser, BlockStore, Bn128WasmInstance, BroadcastChannelEvent, BroadcastChannelOptions, CachedSpaceMessage, Change, ChannelLike, ChatKeyCert, ChunkKeyMaterial, ChunkManifest, CipherFrame, CipherMessage, CipherMessageHeader, CircuitBufferConfig, CircuitIntegrity, CircuitUrls, ClientOptions, Commit, Conflict, ConnectivityOptions, ConnectivityState, CreateAccessTokenInput, CreatedAccessToken, DbFilterOp, DbNamespaceDeps, DbQuery, DbQueryPage, DbQueryResult, DbWhereCondition, DehydratedKeypair, DehydratedKeys, DeviceBlobStore, DeviceKeyVault, DevicePairingErrorReason, DevicePairingKeypair, DevicePairingPoll, DevicePairingRequest, DevicePairingSession, DirEntry, DirNode, DoubleRatchetCipherDeps, ECDHCurve, EncodeResult, EngineHandle, EngineHost, Entry, EphemeralEvent, EpochKeyProvider, FileEntry, FileManifest, FileNamespaceDeps, FileStat, FileStorageOptions, FunctionCaps, FunctionConfig, FunctionDeployInput, FunctionInvokeOptions, FunctionScopeOpts, FunctionTrigger, FunctionTriggerType, FunctionTriggers, FunctionUpdateInput, FunctionsNamespaceDeps, Groth16Proof, Hasher, Head, HistoryRecord, HlcParts, HlcState, HostedAuthDeps, HttpClientOptions, IncomingFrame, KeyExchangeFrame, KeyPair, Keypair, KeyringClientDeps, KeyringTransport, KvEntry, KvNamespaceDeps, LogEntry, LoginOptions, LwwMap, LwwRegister, MemberView, MembershipEntry, Merge3Result, MergeRegionConflict, MergeResult, MessageBody, MessageDeletedEvent, MessageEntry, MessageEvent, MessageHeaders, MessageNamespaceDeps, MessageOp, MessageOptions, MessageSubscription, MuhkooAgentMeta, MuhkooAgentToolsConfig, MuhkooAppDescriptor, MuhkooDBAccess, MuhkooDBMeta, MuhkooFunctionMeta, MuhkooKeystoreBridge, MuhkooMessageEvent, MuhkooSpaceMeta, OAuthProvider, OfflineEntry, OfflineManagerDeps, OfflineStore, OutSink, PacketCipher, PacketHeaders, PacketOptions, PairedDevice, Participant, PasskeyEnrollment, PeerBlockSource, PeerExchangeOptions, PeerId, PeerNetworkOptions, PeerTransport, Permission, PermissionCheckRequest, PermissionCheckResult, PersistedDeviceIdentity, PersonalSpaceClientOptions, Pkce, PublicKeyJWK, QueueEntry, RatchetState, ReceiveResult, RegisterParams, Replayer, Role$1 as Role, SpaceDeps as RoomDeps, SpaceFileMetadata as RoomFileMetadata, Scope, SealedHandoff, SealedHeaders, SealedRecord, Session, SessionOptions, SessionStore, SetOptions, ShardByteCache, ShardClientOptions, SharedSpaceClientOptions, SignalEnvelope, SignalKind, SignalingSpace, SnapshotNamespaceDeps, SnarkProof, SpaceChannelLike, SpaceCursor, SpaceDeps, SpaceFileMetadata, SpaceKeyCache, SpaceKeyringDeps, SpaceMessageEvent, SpaceNamespaceDeps, SpaceOfflineAdapter, SpaceTrustCode, StartDevicePairingOptions, StorageChangeEvent, StoreName, StoredSession, Subscription, Tree, TreeDir, TreeEntry, TreeFile, UserIdentity, VaultFactor, VcsNamespaceDeps, VcsNamespaceOptions, VerificationKey, VfsContentStore, VfsNamespaceDeps, VfsStat, VfsStore, WSMessage, WSMessageType, WSTransportOptions, WaitForDevicePairingOptions, WebRtcTransportOptions, WrappedPayload, WriteFileInput, WriteFileOptions, WriteToShardsInput, ZkAuthDeps, ZkAuthenticateRequest, ZkChallengeResponse, ZkCompiled, ZkIdentity, ZkRegisterRequest, ZkRegisterResponse };