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
Exact section text · Full source · Source index
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