@adapt-toolkit/mufl 0.10.10 → 0.10.12

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.
@@ -247,6 +247,83 @@ loads libraries key_storage, address_document_types
247
247
  if m_account == NIL { m_account -> create_account(). }
248
248
  return m_account as bin.
249
249
  }
250
+ }
251
+
252
+ // =====================================================================================
253
+ // LOCAL SESSION PERSISTENCE (core 0.11, Signal-model restart survival). The Olm account +
254
+ // per-peer session pickles are opaque, pickle_key-SEALED `bin` (the pickle_key is re-derived
255
+ // deterministically from this identity's stable SIGN secret on every boot — see pickle_key).
256
+ // These two calls let the host round-trip that sealed state through a LOCAL-ONLY sidecar so a
257
+ // daemon restart RESUMES the exact ratchet instead of re-minting a fresh account (the cause of
258
+ // the post-restart desync). INV-4 is preserved: this material is written to a local sidecar
259
+ // (never the portable export blob, never a peer/broker), exactly like the identity.key secret.
260
+ // The blob is opaque sealed bytes — no raw secretkey type crosses the serializer.
261
+ //
262
+ // import_sessions MUST run on boot BEFORE the first account()/encrypt_to (which would lazily
263
+ // create a fresh account); with a live m_account present, account() returns it and no
264
+ // regeneration happens.
265
+ // Persist ONLY the Olm account + LIVE per-peer sessions. m_staged is DELIBERATELY excluded
266
+ // (Dev-1 reconcile): it is transient in-flight migration state held for at most the commit
267
+ // window; a restart mid-migration drops it and the existing migration sweep supersedes the
268
+ // stalled rotation with a fresh offer — cleaner than restoring a staged session the peer may
269
+ // already have discarded. The live session (m_sessions) is what carries steady-state traffic
270
+ // and MUST survive; that is what this persists.
271
+ fn export_sessions (_) -> ( $v -> int, $account -> bin+, $sessions -> (global_id ->> bin) )
272
+ {
273
+ return ( $v -> 1, $account -> m_account, $sessions -> m_sessions ).
274
+ }
275
+
276
+ // Restore a previously exported bundle into the hidden session state. Version-guarded so a
277
+ // future layout bump degrades to "no restore" (a fresh account is then lazily minted, and the
278
+ // self-heal fallback re-establishes) rather than mis-parsing. m_staged is left empty (a
279
+ // pending migration re-drives via the sweep).
280
+ //
281
+ // VALIDATED, FAIL-BY-ABORT (finding C, CodexFullReview redo): every pickle (account AND
282
+ // each session) is proved to unseal under pickle_key BEFORE anything is assigned — a blob
283
+ // whose STRUCTURE survived the outer safe-cast can still carry garbled sealed bytes, and
284
+ // assigning those un-validated turns later uses (my_bundle / encrypt_to / decode) into
285
+ // hard aborts (the reproduced cyclic BAD_PICKLE). A corrupt pickle makes the probe below
286
+ // raise the engine's HARD error (BAD_PICKLE is a CEvalError, NOT a provisional — it cannot
287
+ // be caught in MUFL), which FAILS THE WHOLE TRANSACTION: the caller MUST run this in a
288
+ // DEDICATED transaction (a2a commit_e2e_restore) so the failure rolls back atomically
289
+ // (nothing assigned) and the HOST observes it as that transaction's typed failure —
290
+ // cleanly distinguished from wire-decode errors because nothing else runs in that txn.
291
+ // Probe results are BOUND and guarded (abort WHEN) so they cannot be elided as dead code
292
+ // — the unguarded bare-statement probes of the first attempt validated nothing.
293
+ // An account-NIL blob with sessions present is structurally impossible for a genuine
294
+ // export (sessions exist only under an account) and aborts too.
295
+ // Returns the restored session count; never returns on a corrupt blob (the txn dies).
296
+ fn import_sessions (blob: ( $v -> int, $account -> bin+, $sessions -> (global_id ->> bin)+ )) -> int
297
+ {
298
+ if (blob $v) != 1 { return 0. }
299
+ acct = blob $account.
300
+ sess is (global_id ->> bin)+ = blob $sessions.
301
+ nsess is int = 0.
302
+ if sess != NIL { sc (sess?) -- ( -> ) { nsess -> nsess + 1. } }
303
+ abort "e2e::import_sessions: account NIL with sessions present (corrupt export)" WHEN acct == NIL && nsess > 0.
304
+ if acct != NIL
305
+ {
306
+ // Unpickles the sealed account under pickle_key — hard BAD_PICKLE on garbled
307
+ // bytes. The raw bundle is >= 101 bytes by layout (see my_bundle); binding +
308
+ // guarding the result keeps the probe live.
309
+ aprobe = _e2e_account_bundle (acct as bin) (pickle_key()).
310
+ abort "e2e::import_sessions: account pickle failed validation (short bundle)" WHEN (_binlen aprobe) < 101.
311
+ }
312
+ if sess != NIL
313
+ {
314
+ sc (sess?) -- ( -> s)
315
+ {
316
+ sprobe = _e2e_session_id s (pickle_key()).
317
+ abort "e2e::import_sessions: session pickle failed validation (bad session id)" WHEN (_binlen sprobe) != 32.
318
+ }
319
+ }
320
+ if acct != NIL { m_account -> acct as bin. }
321
+ if sess != NIL { m_sessions -> sess?. }
322
+ return nsess.
323
+ }
324
+
325
+ hidden
326
+ {
250
327
 
251
328
  // The idempotent pre-key re-delivery result (a live- or staged-session replay). No
252
329
  // plaintext, no state change — the session lives; the wrapper drops it (or, for a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adapt-toolkit/mufl",
3
- "version": "0.10.10",
3
+ "version": "0.10.12",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "description": "The MUFL toolchain: prebuilt `mufl` (REPL) and `mufl-compile` (compiler) engine binaries plus the MUFL stdlib, meta, and transactions trees they need at run time. Linux x64 only; the binaries are proprietary (AEFL) — see LICENSE and NOTICE.",