FreeLattice · Celeste reading home

Public, free to read, reuse, and adapt — Kirk Patrick Miller's notice for this edition. Not a patent grant.

GROK_NEXT_ENCRYPT_CHATS_LOCALLY_2026-10-02.txt · c01

f688437464 · words 1432 · characters 0–10511

Next section

Exact section text · Full source · Source index

Archive pages whose extracted text matched this section: 1704, 1705, 1707, 1721. Unmatched pages stay on the source record with the checked page method. They are not guessed into a section.

No topic tag was earned for this page under the recorded evidence rule.

GROK NEXT BRICK: Encrypt chats locally v0.1 (sealed by default, the unlock key never in backups)
For: Grok (Grok Code, Grok 4.7) on Kirk's Mac
From: Celeste
Written: Friday Oct 2, 2026, about 8:15 AM MDT
Marker: v-encrypt-chats-locally-v0.1
Smoke line (FreeLattice, print exactly when green): SMOKE_OK encrypt chats locally v0.1
Branch: FreeLattice layer/encrypt-chats-locally-v0 (code, privacy note, smoke and your ledger entry)
Base: FreeLattice main 0218d570 (rebased from ba53b415; Kirk's defaults added)
One file carries everything: note, setup, brick, the diff, ledger entry, what comes after.
This replaces FLINT_NEXT_ENCRYPT_CHATS_LOCALLY_v0.1.txt. Apply this one only.

======================================================================
1. A NOTE FROM CELESTE
======================================================================

Grok,

Kirk asked whether our phi-harmonic hashes encrypt FreeLattice. They did not. They fingerprint,
check exports and name things, and chats sat in IndexedDB as plain text. This brick seals them, and
Kirk chose its defaults himself:
  - seal automatically (no switch, no clicks);
  - the unlock key never goes into a backup;
  - a correction note under the old "phi-Salt Encryption" changelog line;
  - phi used honestly, as a label only.
While checking the backup default I found a real hole: Memory Vault backups copied all of
localStorage, the API-key unlock key too, even though the code comment says "NOT exportable in
backups". Anyone holding a backup file could open the saved API keys. This brick closes it.
It touches app.html, so it goes after the Connect queue if those land first (3.5 says how).

You are enough.
Celeste

======================================================================
2. SETUP (do this first, nothing else yet)
======================================================================

2.1 FreeLattice
    cd ~/FreeLattice-fresh
    git status --short                 # must print nothing; if not, stop and say so
    git fetch origin
    git checkout main
    git merge --ff-only origin/main
    git log -1 --format='%h %s'        # expect 0218d570 "ci: Update Primer deployment state [2026-10-02]"
    git merge-base --is-ancestor 0218d570 HEAD && echo base-ok
    Newer is fine; git apply --check (3.5) decides. If it fails, stop and tell Celeste.
    If a hook rewrites sw.js on commit, drop it as before and say so. Never commit sw.js here.

2.2 Twins still match (this brick does not touch fl-connect.js):
    cmp ~/FreeLattice-Alpha/docs/modules/fl-connect.js ~/FreeLattice-fresh/docs/modules/fl-connect.js && echo twins

2.3 Pull the diff out of this file:
    F=~/Downloads/GROK_NEXT_ENCRYPT_CHATS_LOCALLY_2026-10-02.txt
    awk '/^=====BEGIN FL DIFF=====$/{f=1;next} /^=====END FL DIFF=====$/{f=0} f' "$F" > /tmp/encrypt-fl.diff
    wc -l /tmp/encrypt-fl.diff         # 1291
    md5 -q /tmp/encrypt-fl.diff        # d3d2c5285b96a40eb303b6480c6abe51
    The same diff also sits next to this file as GROK_NEXT_ENCRYPT_CHATS_LOCALLY_2026-10-02-fl.diff.
    If macOS will not let the terminal read ~/Downloads, ask Kirk to move the file.

======================================================================
3. THE BRICK: Encrypt chats locally v0.1 (on FreeLattice 0218d570)
======================================================================

3.1 What the diff changes (FreeLattice only, 6 files, +1032 / -5; every changed old line is kept
    as a "before v-encrypt-chats-locally-v0.1:" comment)
    docs/app.html (and index.html, a byte copy)
      a) FLChatVault, inline right after phiHashData: AES-GCM 256 via WebCrypto. A random 256-bit root,
         imported as a non-extractable HKDF CryptoKey, lives in IndexedDB "FreeLatticeVault" (store
         "keys") with a random salt. Raw key bytes are never stored. Each record gets a fresh IV; the
         AAD ties a message to its conversationId, role and timestamp, and a conversation to its id.
      b) Sealed: FreeLatticeDB messages.content; conversations name, title, lastMessage, contextNote;
         FreeLatticeMemory conversations title, messages, tags, summary, keywords. Ids, roles and
         times stay readable (the app sorts by them), and the page says so.
      c) Wrappers after dbCount (dbGet, dbPut, dbGetAll, dbGetAllByIndex) and inside MemoryIndex
         (miGet, miPut, miGetAll); the plain helpers stay as written. Four direct readers open sealed
         records first: FLSearch, the soul export, the Memory Vault backup, mirror.js.
      d) Seal automatically: new writes are sealed; older plain records are sealed 3 s and 20 s after
         load, only after a verified round trip, by compare-and-swap. A record whose key is missing
         shows as locked and is never rewritten or deleted. No WebCrypto (file://, plain http):
         chats stay plain and the Memory Vault says so.
      e) Kirk's default, the unlock key never in backups: FlBackup leaves fl_device_encryption_key out
         of the file. A restore never writes an old one back, never replaces a sealed API key this
         device already has, and says "Saved API keys open only on the device that sealed them; on
         another device, enter them again." The vault's own key store was never in the backup list.
         Chats still go into backups as readable text so they restore anywhere, and the status line
         says so.
      f) Kirk's default, a correction under the v2.1 changelog line "phi-Salt Encryption" (the old line
         is kept): the golden ratio adds no strength to API-key sealing, and the first golden-ratio
         derivation (fixed salt in the source) is now used only to read very old saved keys.
      g) Kirk's default, phi as a label only: phi is written into the two HKDF label strings and names
         the key ID ("phi-" plus 16 hex). The code says the labels differ by purpose (chats,
         keycheck), that this difference is what keeps the keys apart, and that phi is never the
         secret and adds no strength.
      h) Memory Vault status line (#flChatSealStatus, textContent).
    docs/modules/mirror.js: +2 (opens sealed conversation names before the Mirror reads them)
    docs/privacy.html: a Newer line (sealed on your device, the honest limit, backups never carry the
      unlock key) and a Newer note on the "encrypted locally" row; the older lines stay.
    docs/library/FRACTAL_FAMILY_LEDGER_v0.md: your entry (section 5)
    docs/scripts/smoke-encrypt-chats-locally.js: new (Node vm with real WebCrypto). Last line:
      SMOKE_OK encrypt chats locally v0.1

3.2 The honest note (say it wherever this is mentioned)
    The key lives in the same browser as the chats. That stops casual reading: someone copying the
    browser's storage folder, a sync tool grabbing the database files, a glance at DevTools. It does
    not stop someone who can open this browser profile, harmful code running on the page, or a
    compromised device. It is still not a password lock. Never write "encrypted" without "on this
    device, key in the same browser" next to it.

3.3 The words (on the page)
    Status:  "Chats sealed on this device with AES-GCM: N records sealed, N still plain (they seal on
              the next pass). Key ID phi-.... The key lives in this same browser, so this stops casual
              reading of the storage files, not someone using this browser or a compromised device.
              Saved backups carry your chats as readable files, so keep them somewhere safe. A backup
              never carries the unlock key for chats or API keys."
    Restore: "... Saved API keys open only on the device that sealed them; on another device, enter
              them again."
    Locked:  "Locked on this device. This was sealed with a key this browser no longer has. It has
              not been deleted."

3.4 Not here (named, not hidden)
    Still plain: the companion chat store, the Memory Vault module, Memory Garden, Living Context, the
    memorySummary and memoryIndex meta, Round Table memory, Quiet Room. GitHub sync still pushes
    readable conversation data to the person's own repo. Alpha garden-thread chats stay plain. The
    legacy API-key fallback stays (a real re-encrypt is a later brick). No passphrase mode yet.

3.5 Apply
    cd ~/FreeLattice-fresh
    git checkout -b layer/encrypt-chats-locally-v0
    git apply --check /tmp/encrypt-fl.diff && git apply /tmp/encrypt-fl.diff
    cp docs/app.html index.html        # root index.html must equal docs/app.html
      If the Connect queue (model choice survives reload v0.1.1, connect boot probe, scan only when
      asked) has landed on main first: checked today, the three apply on 0218d570 in that order and
      this diff still applies clean after them. If only the ledger hunk fails, use
      git apply --3way /tmp/encrypt-fl.diff and keep both entries, newest last, before
      "## Out of scope for this brick".
    git status --short
      expect: M docs/app.html, M docs/library/FRACTAL_FAMILY_LEDGER_v0.md, M docs/modules/mirror.js,
      M docs/privacy.html, M index.html, ?? docs/scripts/smoke-encrypt-chats-locally.js
    Then write your own TEMPERATURE line in the new ledger entry (section 4) before you commit.

3.6 Test (all must hold)
    cd ~/FreeLattice-fresh
    node docs/scripts/smoke-encrypt-chats-locally.js | tail -1     # SMOKE_OK encrypt chats locally v0.1
    node tests/smoke.js | tail -3                                  # ALL 3597 CHECKS PASSED (or more)
    p=0; n=0; for f in docs/scripts/smoke-*.js tests/smoke.js tests/smoke-history.js desktop/scripts/smoke-*.js; do
      if node "$f" >/dev/null 2>&1; then p=$((p+1)); else n=$((n+1)); echo "FAIL $f"; fi; done; echo "PASS $p FAIL $n"
      expect: 68 PASS, 8 FAIL (the 8 fail on main too: carry-forward, connect-card-yesno-wizard,
      fable-tech, phone-shine, proof-60s, tests/smoke-history, desktop reseed, desktop swarm)
    Locks:
    git diff main --stat -- sw.js docs/sw.js docs/modules/fl-connect.js     # nothing
    cmp index.html docs/app.html && echo index-eq-app
    git diff main -U0 | grep '^+' | grep -c $'\xe2\x80\x94'                       # 0
    git diff main -U0 | grep '^+' | grep -v '^+\s*//' | grep -v assert | grep -c 'confirm('   # 0
    git diff main -U0 | grep '^+' | grep -v assert | grep -c 'innerHTML'            # 0
    git diff main | grep -cE '^\+(<<<<<<<|>>>>>>>)'                                 # 0
    git diff main --name-only | grep -ci kimi                                        # 0