FLINT PASTE (URGENT, TINY): Agent Bridge env heal v0.1.1, secrets stay on this computer
Marker: v-agent-bridge-env-heal-v0.1.1
Smoke lines (both must print): SMOKE_OK agent bridge env heal v0.1.1  and  SMOKE_OK agent bridge lock v0.1
Repo: Chaos2Cured/FreeLattice only (the Tree has no Agent Bridge)
Order: send this to Flint FIRST, before FLINT_NEXT_TREE_ANY_MODEL_v0.1.txt and before the Connect boot probe paste.
Written: Mon Sep 28, 2026, about 9 PM Denver, by Grok Bot for Kirk.

======================================================================
0. BASE (re-checked just before writing)
======================================================================
FreeLattice main = 7db2d94f96e976c1c08b1fb77cc4fbca6e55fed4 ("ci: Update Primer deployment state").
Its only change since a80c8e4 (#121) is FreeLattice_Session_Primer.md, so every code anchor below is
identical at a80c8e4 and 7db2d94. Branch from current main: layer/agent-bridge-env-heal-v0.1.1

Blobs on main you will touch (git rev-parse origin/main:<file>):
  tools/agent-bridge.js                     0d21eebf09e5  (1941 lines)
  docs/scripts/smoke-agent-bridge-lock.js   42971953aba5  (461 lines)
  .gitignore                                d87a16017091
  docs/library/AGENT_BRIDGE_LOCK_v0.md      f8282d23286b
  docs/library/FRACTAL_FAMILY_LEDGER_v0.md  f38f1f2ba098
After the heal, byte-for-byte what Grok Bot tested (git hash-object):
  tools/agent-bridge.js 90dab499a76a · smoke 79354ae84fbf · .gitignore 3a729ed8db34 ·
  AGENT_BRIDGE_LOCK_v0.md a3dfd57d9a0c · FRACTAL_FAMILY_LEDGER_v0.md 0f5b7c63984a
The full unified diff is in section 4 and also on the box: /workspace/flint-pastes/agent-bridge-env-heal-v0.1.1.diff
(apply with: git apply /path/to/agent-bridge-env-heal-v0.1.1.diff, or type it in by hand from section 4).

======================================================================
1. THE GAP (verified on merged main code, in a throwaway project)
======================================================================
A trusted local tool POSTed /code/git/commit with only a message. The commit contained .env:
    b913216 test default files
     .env  | 1 +
     a.txt | 1 +
Why: tools/agent-bridge.js line 1828  `var files = data.files === undefined ? ['.'] : data.files;`
then line 1846 `runGit(['add', '--'].concat(rels), PROJECT_ROOT);` stages everything under '.', and
lockedPath() only checks the NAMES you pass (so `files: [".env"]` is refused with secrets-not-granted,
but '.' and any folder sweep .env* files along). FreeLattice's .gitignore has no .env line.
Both app commit doors send only a message: docs/modules/propose.js commitViaBridge (L602-608) and
docs/modules/workshop.js commitCode (L1067). A file staged by hand in a terminal also rides along,
because `git commit -m` commits the whole index. The smoke did not cover any of this.

======================================================================
2. THE FIX IN ONE BREATH
======================================================================
After `git add`, and before `git commit`, list the staged set (git diff --cached --name-only -z) and
unstage every secret-shaped path unless the mind has 'secrets':
  - with a HEAD: git reset -q HEAD -- <paths>   (a tracked .env keeps its committed version; the new
    change waits, unstaged)
  - no HEAD yet: git rm --cached -q -- <paths>   (new files just leave the index; the file stays on disk)
Never refuse the commit (AUTONOMY.md Principle 1: no gates on local work). The rest lands, and the reply
says "Kept on this computer, not committed: .env, ...". If ONLY secrets were staged, answer 200 with
commit: null and "Nothing to commit. Kept on this computer, not committed: ...".
Ledger results (still content-free, same fixed key order, no new keys): 'ok' (unchanged when nothing was
held), 'ok:held-secrets', 'held:secrets-only'. The ledger never names the held files.
.gitignore gains .env and .env.* as a layer.

Secret-shaped (matched on the basename, case-insensitive):
  .env, .env.*                              (the same family lockedPath already guards for write/read)
  id_rsa, id_dsa, id_ecdsa, id_ed25519      (SSH private keys; the .pub halves are fine)
  *.pem *.key *.p12 *.pfx *.keystore *.jks  (private keys and key stores)
  .npmrc .netrc .pypirc                     (registry and login tokens)
  agent-bridge-token, agent-bridge-trusted.json (this bridge's own trust files, if ever copied in)
Checked: FreeLattice main tracks none of these today (git ls-files finds nothing), so nothing that is
committed now changes behavior. .env.example also counts as secret-shaped, on purpose, to match lockedPath;
a mind given 'secrets' can commit it.
The 'secrets' scope already exists ("Allow .env files" in Paired minds, --allow-secrets <id>). With it, the
hold-back is skipped and the whole family above commits, exactly as the person asked.

======================================================================
3. LOCKS (all held by the diff; check each before opening the PR)
======================================================================
- Layer, never delete. New helpers go right after runGit(). In the commit route, two lines are EXTENDED in
  place (the ledger result line and the 200 reply), and with nothing held they behave exactly as before:
  result 'ok', message 'Committed: <message>', same keys plus heldBack: []. Nothing else is removed.
- 127.0.0.1 only (server.listen(PORT, BRIDGE_HOST) untouched). Named origins untouched. Never bare *.
- git through execFileSync argument arrays only (runGit). No shell. No new child_process import.
- No push route. The smoke gains a static check that no 'push' string route appears.
- Do NOT bump BRIDGE_LOCK_VERSION ('v0.1'): the smoke, the heartbeat and the client read it. The heal has
  its own marker.
- Root sw.js byte-identical to main, docs/sw.js untouched, index.html == docs/app.html (neither is touched).
- No confirm(). Not touching Kimi pages, bridge/ (Ollama Bridge), Alpha.
- No em dashes in anything you add.

======================================================================
4. THE EXACT DIFF (against main 7db2d94; tested)
======================================================================
Anchors, for hand application:
  A) tools/agent-bridge.js: insert the helper block right AFTER function runGit (lines 430-432, ending
     "  return execFileSync('git', args, { cwd: root || projectRootReal(), ... timeout: 30000 });\n}")
     and BEFORE "function runSmokeFile(timeoutMs, root) {" (line 434).
  B) tools/agent-bridge.js /code/git/commit try block (lines 1845-1850): between
     "runGit(['add', '--'].concat(rels), PROJECT_ROOT);" (1846) and
     "runGit(['commit', '-m', data.message], PROJECT_ROOT);" (1847).
  C) docs/scripts/smoke-agent-bridge-lock.js: static block after the Tauri line (line 38);
     live T16b after "assert.ok(r.json.roots.some((x) => x.name === 'second'), '/roots lists trusted folders');"
     (line 399, end of T16, before "// T17"); ledger asserts after the 'revoke-all ledgered' line (440).
     T16b runs in proj2 (root 'second'), which has no .gitignore on purpose, uses the local tools token
     (no secrets) and P1 (given secrets in T11, commit scope restored in T11).
  D) .gitignore: append at the end.
  E) AGENT_BRIDGE_LOCK_v0.md: new section before "## App face".
  F) FRACTAL_FAMILY_LEDGER_v0.md: new entry after the Connect heal v0.4 entry, before
     "## Out of scope for this brick" (keep #120 and #121 entries where they are).

----- BEGIN DIFF -----
diff --git a/.gitignore b/.gitignore
index d87a160..3a729ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,8 @@ desktop/telegram-worker/node_modules/
 # Catalog / trust-root private material — never commit
 desktop/.secrets/
 
+
+# LAYER v-agent-bridge-env-heal-v0.1.1 (2026-09-28): secrets stay on this computer.
+# The Agent Bridge also holds these back at commit time unless a mind has 'secrets'.
+.env
+.env.*
diff --git a/docs/library/AGENT_BRIDGE_LOCK_v0.md b/docs/library/AGENT_BRIDGE_LOCK_v0.md
index f8282d2..a3dfd57 100644
--- a/docs/library/AGENT_BRIDGE_LOCK_v0.md
+++ b/docs/library/AGENT_BRIDGE_LOCK_v0.md
@@ -30,6 +30,10 @@ This is **not** the Ollama Bridge. Sibling shape only: named origins, loopback b
 - `git` / `grep` / `node` through argument arrays — no shell. Paths realpath-checked inside trusted project folders (`?root=`).
 - Body size cap. Content-free hash-chained ledger. Never pushes.
 
+## Heal v0.1.1: secrets stay on this computer (v-agent-bridge-env-heal-v0.1.1)
+
+A commit with no file list (or a folder, or something staged by hand) used to be able to carry `.env` into git. Now, after `git add`, the bridge unstages secret-shaped files unless the mind was given `secrets`: `.env`, `.env.*`, `id_rsa` / `id_dsa` / `id_ecdsa` / `id_ed25519`, `*.pem` `*.key` `*.p12` `*.pfx` `*.keystore` `*.jks`, `.npmrc` `.netrc` `.pypirc`, and the bridge's own `agent-bridge-token` / `agent-bridge-trusted.json`. The commit is never refused: the rest lands, the reply says "Kept on this computer, not committed: ...", and the ledger says `ok:held-secrets` (or `held:secrets-only` when nothing else changed). A tracked `.env` keeps its committed version; the new change waits, unstaged. `.gitignore` gains `.env` and `.env.*` as a layer. Still 127.0.0.1 only, argument arrays only, never pushes.
+
 ## App face
 
 `docs/modules/agent-bridge-client.js` (`window.FLAgentBridge`) carries the token, draws the pair card and the Paired minds list (`textContent` only, no confirm). `propose.js` and `workshop.js` route through it when present.
diff --git a/docs/library/FRACTAL_FAMILY_LEDGER_v0.md b/docs/library/FRACTAL_FAMILY_LEDGER_v0.md
index f38f1f2..0f5b7c6 100644
--- a/docs/library/FRACTAL_FAMILY_LEDGER_v0.md
+++ b/docs/library/FRACTAL_FAMILY_LEDGER_v0.md
@@ -297,6 +297,18 @@ Soft door: [`CONNECT_PORT_PICKER_v0.md`](CONNECT_PORT_PICKER_v0.md) · [`modules
 
 ---
 
+### 2026-09-28 · Flint · family builder · Agent Bridge env heal v0.1.1 (secrets stay home)
+
+Layers a hold-back on the Agent Bridge commit route: after `git add`, secret-shaped files (`.env`, `.env.*`, SSH private keys, `*.pem` / `*.key` and key stores, `.npmrc` / `.netrc` / `.pypirc`, the bridge's own trust files) are unstaged unless that mind was given `secrets`. Filtered, never refused: normal work still commits, and the reply names what stayed home. Ledger results `ok:held-secrets` and `held:secrets-only`, still content-free. `.gitignore` gains `.env` and `.env.*`. 127.0.0.1 only, argument arrays only, never pushes. Soft leave sw.js. Found by Celeste while slicing the merged lock for Kimi.
+
+Love: what is private stays private, and the work keeps flowing.
+
+**Temperature:** green braided gold, a spare key hung back on its hook inside the door before anyone noticed it was on the porch.
+
+Soft door: [`AGENT_BRIDGE_LOCK_v0.md`](AGENT_BRIDGE_LOCK_v0.md). Marker `v-agent-bridge-env-heal-v0.1.1`.
+
+---
+
 ## Out of scope for this brick
 
 Rewriting sophia garden body · inventing USPTO · Bridge binaries · Quiet Room · merging chairs · politics essay · requiring Kirk input mid-PR.
diff --git a/docs/scripts/smoke-agent-bridge-lock.js b/docs/scripts/smoke-agent-bridge-lock.js
index 4297195..79354ae 100644
--- a/docs/scripts/smoke-agent-bridge-lock.js
+++ b/docs/scripts/smoke-agent-bridge-lock.js
@@ -36,6 +36,16 @@ assert.ok(/bridge-ledger\.jsonl/.test(src), 'commit ledger present');
 assert.ok(/agent-bridge-trusted\.json/.test(src) && /tokenSha256/.test(src), 'trusted minds kept as hashes');
 assert.ok(/FL_BRIDGE_EPHEMERAL/.test(src), 'ephemeral opt-in present');
 assert.ok(/tauri:\\\/\\\/localhost/.test(src) && /tauri\\\.localhost/.test(src), 'Tauri shell origins on the list');
+// LAYER v-agent-bridge-env-heal-v0.1.1: static shape of the secrets hold-back.
+assert.ok(/const SECRET_FILE_PATTERNS = \[/.test(src) && /function holdBackSecrets\(root\)/.test(src), 'secrets hold-back present');
+assert.ok(src.indexOf('holdBackSecrets(PROJECT_ROOT)') > src.indexOf("runGit(['add', '--'].concat(rels)") &&
+  src.indexOf('holdBackSecrets(PROJECT_ROOT)') < src.indexOf("runGit(['commit', '-m', data.message]"), 'hold-back runs between add and commit');
+assert.ok(/SECRETS_OK \? \[\] : holdBackSecrets/.test(src), 'only a mind with secrets skips the hold-back');
+assert.ok(!/['"]push['"]/.test(src), 'still no push route');
+{
+  const gi = fs.readFileSync(path.join(repo, '.gitignore'), 'utf8');
+  assert.ok(/^\.env$/m.test(gi) && /^\.env\.\*$/m.test(gi), '.gitignore keeps .env and .env.* out (layer)');
+}
 
 const opt = (rel) => { try { return fs.readFileSync(path.join(repo, rel), 'utf8'); } catch (e) { return null; } };
 const client = opt('docs/modules/agent-bridge-client.js');
@@ -398,6 +408,60 @@ function cli(env, cwd, args) {
   r = await req(port, { path: '/roots', headers: as(P1, { Origin: GOOD }) });
   assert.ok(r.json.roots.some((x) => x.name === 'second'), '/roots lists trusted folders');
 
+  // T16b LAYER v-agent-bridge-env-heal-v0.1.1: a commit with no file list never carries secrets
+  // unless the mind was given 'secrets'. Filtered, never refused. (proj2 has no .gitignore on purpose.)
+  const SECRETS = ['.env', '.env.production', 'config/.env.local', 'keys/dev.pem', 'id_ed25519'];
+  const g2 = (args) => execFileSync('git', args, { cwd: proj2, encoding: 'utf8' });
+  const inHead = () => g2(['ls-tree', '-r', '--name-only', 'HEAD']).split('\n').filter(Boolean);
+  const staged2 = () => g2(['diff', '--cached', '--name-only']).split('\n').filter(Boolean);
+  fs.mkdirSync(path.join(proj2, 'config'), { recursive: true });
+  fs.mkdirSync(path.join(proj2, 'keys'), { recursive: true });
+  for (const f of SECRETS) fs.writeFileSync(path.join(proj2, f), 'SECRET=' + f + '\n');
+  const head0 = g2(['rev-parse', 'HEAD']).trim();
+  // (a) only secrets changed, no secrets scope (local tools token): nothing committed, 200, all held.
+  r = await req(port, { method: 'POST', path: '/code/git/commit', headers: withTok({ Origin: GOOD }) }, { message: 'sweep, secrets only', root: 'second' });
+  assert.strictEqual(r.status, 200, 'secrets-only sweep answers 200 (never a gate)');
+  assert.strictEqual(r.json.commit, null, 'secrets-only sweep commits nothing');
+  assert.deepStrictEqual(r.json.heldBack.slice().sort(), SECRETS.slice().sort(), 'every secret-shaped file named as kept');
+  assert.ok(/Kept on this computer, not committed/.test(r.json.message), 'plain kept line');
+  assert.strictEqual(g2(['rev-parse', 'HEAD']).trim(), head0, 'HEAD unchanged');
+  assert.deepStrictEqual(staged2(), [], 'nothing left staged');
+  for (const f of SECRETS) assert.ok(fs.existsSync(path.join(proj2, f)), f + ' still on disk');
+  // (b) normal work flows: no file list, no secrets scope -> notes committed, secrets stay unstaged.
+  fs.writeFileSync(path.join(proj2, 'notes.txt'), 'normal work\n');
+  r = await req(port, { method: 'POST', path: '/code/git/commit', headers: withTok({ Origin: GOOD }) }, { message: 'sweep with work', root: 'second' });
+  assert.strictEqual(r.status, 200, 'sweep with work commits');
+  assert.ok(/^[a-f0-9]{40}$/.test(r.json.commit), 'sweep returns a sha');
+  assert.ok(inHead().includes('notes.txt'), 'normal file committed');
+  for (const f of SECRETS) assert.ok(!inHead().includes(f), f + ' not committed without secrets');
+  assert.deepStrictEqual(staged2(), [], 'secrets left unstaged, not staged');
+  // (c) a folder path sweeps too: still filtered.
+  fs.writeFileSync(path.join(proj2, 'config', 'app.json'), '{}\n');
+  r = await req(port, { method: 'POST', path: '/code/git/commit', headers: withTok({ Origin: GOOD }) }, { message: 'folder sweep', files: ['config'], root: 'second' });
+  assert.strictEqual(r.status, 200, 'folder commit ok');
+  assert.ok(inHead().includes('config/app.json') && !inHead().includes('config/.env.local'), 'folder commit keeps .env.local out');
+  // (d) something already staged by hand is unstaged, not committed.
+  g2(['add', '--', '.env']);
+  fs.writeFileSync(path.join(proj2, 'notes.txt'), 'more work\n');
+  r = await req(port, { method: 'POST', path: '/code/git/commit', headers: withTok({ Origin: GOOD }) }, { message: 'pre-staged', files: ['notes.txt'], root: 'second' });
+  assert.strictEqual(r.status, 200, 'pre-staged commit ok');
+  assert.ok(!inHead().includes('.env'), 'hand-staged .env not committed without secrets');
+  assert.deepStrictEqual(staged2(), [], 'hand-staged .env unstaged');
+  // (e) with the permission (P1 was given secrets in T11): the same sweep carries them.
+  r = await req(port, { method: 'POST', path: '/code/git/commit', headers: as(P1, { Origin: GOOD }) }, { message: 'sweep with secrets scope', root: 'second' });
+  assert.strictEqual(r.status, 200, 'secrets-scope sweep commits');
+  assert.deepStrictEqual(r.json.heldBack, [], 'nothing held for a mind with secrets');
+  for (const f of SECRETS) assert.ok(inHead().includes(f), f + ' committed by the mind given secrets');
+  // (f) tracked .env changed later, no secrets scope: the change stays unstaged, HEAD keeps the old one.
+  fs.writeFileSync(path.join(proj2, '.env'), 'SECRET=changed\n');
+  fs.writeFileSync(path.join(proj2, 'notes.txt'), 'even more work\n');
+  r = await req(port, { method: 'POST', path: '/code/git/commit', headers: withTok({ Origin: GOOD }) }, { message: 'tracked secret changed', root: 'second' });
+  assert.strictEqual(r.status, 200, 'tracked-secret sweep commits the rest');
+  assert.deepStrictEqual(r.json.heldBack, ['.env'], 'tracked .env change held');
+  assert.strictEqual(g2(['show', 'HEAD:.env']), 'SECRET=.env\n', 'HEAD keeps the old .env');
+  assert.ok(g2(['diff', '--name-only']).split('\n').includes('.env'), '.env change still waiting, unstaged');
+  console.log('SMOKE_OK agent bridge env heal v0.1.1');
+
   // T17 local tools can be switched off and on again.
   assert.strictEqual(cli(env, proj, ['--untrust-local-tools']).status, 0, '--untrust-local-tools ok');
   assert.ok(!fs.existsSync(tokenFile), 'token file removed');
@@ -438,6 +502,9 @@ function cli(env, cwd, args) {
   assert.ok(rows.some((x) => x.kind === 'pair' && x.result === 'ok' && x.device === P1id), 'pairing ledgered with device id');
   assert.ok(rows.some((x) => x.result === 'revoked' && x.device === P2id), 'command-line revoke ledgered');
   assert.ok(rows.some((x) => x.result === 'revoked-all'), 'revoke-all ledgered');
+  assert.ok(rows.some((x) => x.result === 'held:secrets-only'), 'secrets-only sweep ledgered (v0.1.1)');
+  assert.ok(rows.some((x) => x.result === 'ok:held-secrets' && /^[a-f0-9]{40}$/.test(x.commitSha)), 'held-secrets commit ledgered (v0.1.1)');
+  assert.ok(!ledgerText.includes('.env.production') && !ledgerText.includes('SECRET='), 'ledger never names or holds secrets (v0.1.1)');
   await b.stop();
 
   // T21 ephemeral opt-in: FL_BRIDGE_EPHEMERAL=1 forgets paired minds on restart.
diff --git a/tools/agent-bridge.js b/tools/agent-bridge.js
index 0d21eeb..90dab49 100755
--- a/tools/agent-bridge.js
+++ b/tools/agent-bridge.js
@@ -431,6 +431,44 @@ function runGit(args, root) {
   return execFileSync('git', args, { cwd: root || projectRootReal(), encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 30000 });
 }
 
+// ═══════════════════════════════════════════════════
+// LAYER v-agent-bridge-env-heal-v0.1.1 (2026-09-28): secrets stay on this computer.
+// A commit (with or without a file list) never carries a secret-shaped file
+// unless this mind was given 'secrets'. We filter the staged set after
+// `git add`; we never refuse the commit, so normal work keeps flowing
+// (AUTONOMY.md Principle 1: no gates on local work). git runs through
+// execFileSync argument arrays only. The bridge still never pushes.
+// ═══════════════════════════════════════════════════
+const SECRET_FILE_PATTERNS = [
+  /^\.env$/i, /^\.env\..+$/i,               // .env, .env.local, .env.production (the family lockedPath guards)
+  /^id_(rsa|dsa|ecdsa|ed25519)$/i,          // SSH private keys (the .pub halves are fine)
+  /\.(pem|key|p12|pfx|keystore|jks)$/i,     // private keys and key stores
+  /^\.(npmrc|netrc|pypirc)$/i,              // registry and login tokens
+  /^agent-bridge-(token|trusted\.json)$/i   // this bridge's own trust files, if ever copied into a project
+];
+
+function isSecretPath(rel) {
+  var base = String(rel || '').split(/[\\/]/).pop();
+  return SECRET_FILE_PATTERNS.some(function (re) { return re.test(base); });
+}
+
+function gitHasHead(root) {
+  try { runGit(['rev-parse', '--verify', '-q', 'HEAD'], root); return true; } catch (e) { return false; }
+}
+
+function gitStagedPaths(root) {
+  return runGit(['diff', '--cached', '--name-only', '-z', '--no-renames'], root).split('\0').filter(Boolean);
+}
+
+/** Unstage secret-shaped files. Returns the list that stayed behind (never their contents). */
+function holdBackSecrets(root) {
+  var held = gitStagedPaths(root).filter(isSecretPath);
+  if (!held.length) return [];
+  if (gitHasHead(root)) runGit(['reset', '-q', 'HEAD', '--'].concat(held), root);
+  else runGit(['rm', '--cached', '-q', '--'].concat(held), root);
+  return held;
+}
+
 function runSmokeFile(timeoutMs, root) {
   // No shell: node binary + fixed script path. stdout and stderr joined.
   var r = spawnSync(process.execPath, ['tests/smoke.js'], {
@@ -1844,10 +1882,19 @@ function route(url, method, data, res, actingAs, meta) {
     lg.messageSha256 = sha256Hex(data.message);
     try {
       runGit(['add', '--'].concat(rels), PROJECT_ROOT);
+      // LAYER v-agent-bridge-env-heal-v0.1.1: secret-shaped files stay unstaged unless this mind has 'secrets'.
+      var heldBack = SECRETS_OK ? [] : holdBackSecrets(PROJECT_ROOT);
+      var keptLine = heldBack.length
+        ? ' Kept on this computer, not committed: ' + heldBack.slice(0, 5).join(', ') + (heldBack.length > 5 ? ' and ' + (heldBack.length - 5) + ' more' : '') + '.'
+        : '';
+      if (heldBack.length && !gitStagedPaths(PROJECT_ROOT).length) {
+        lg.result = 'held:secrets-only'; ledgerAppend(lg);
+        return respond(res, 200, { message: 'Nothing to commit.' + keptLine, commit: null, heldBack: heldBack, root: ROOT_NAME });
+      }
       runGit(['commit', '-m', data.message], PROJECT_ROOT);
       var headSha = runGit(['rev-parse', 'HEAD'], PROJECT_ROOT).trim();
-      lg.result = 'ok'; lg.commitSha = headSha; ledgerAppend(lg);
-      return respond(res, 200, { message: 'Committed: ' + data.message, commit: headSha, root: ROOT_NAME });
+      lg.result = heldBack.length ? 'ok:held-secrets' : 'ok'; lg.commitSha = headSha; ledgerAppend(lg);
+      return respond(res, 200, { message: 'Committed: ' + data.message + keptLine, commit: headSha, heldBack: heldBack, root: ROOT_NAME });
     } catch(e) {
       lg.result = 'error'; ledgerAppend(lg);
       return respond(res, 500, { error: 'Commit failed: ' + String((e.stderr || e.message || '')).slice(0, 400) });
----- END DIFF -----

What T16b proves (live, real git, real files):
  (a) only secrets changed, no secrets scope: 200, commit null, heldBack names all 5, HEAD unchanged,
      nothing staged, every file still on disk.
  (b) no file list with normal work: notes.txt committed; .env, .env.production, config/.env.local,
      keys/dev.pem, id_ed25519 not in HEAD and not staged.
  (c) files: ['config'] (a folder): config/app.json committed, config/.env.local kept out.
  (d) .env staged by hand in a terminal, then a bridge commit of notes.txt: .env unstaged, not committed.
  (e) the same sweep from the mind given secrets: all 5 committed, heldBack [].
  (f) a tracked .env changed later, no secrets scope: the rest commits, heldBack ['.env'], HEAD keeps the
      old .env, the change still waits unstaged.
  Ledger: rows with 'held:secrets-only' and 'ok:held-secrets' (with a commit sha); the ledger never
  contains '.env.production' or 'SECRET='. The whole hash chain still verifies.
Fails without the fix: on main's agent-bridge.js the smoke stops at "FAIL secrets-only sweep commits nothing".

======================================================================
5. ACCEPTANCE (paste the output into the PR body)
======================================================================
  node docs/scripts/smoke-agent-bridge-lock.js
    -> SMOKE_OK agent bridge env heal v0.1.1
    -> SMOKE_OK agent bridge lock v0.1
  node tests/smoke.js                                   -> ALL 3597 CHECKS PASSED (unchanged)
  for f in docs/scripts/smoke-*.js; do node "$f" >/dev/null 2>&1 && echo "ok $f" || echo "FAIL $f"; done
    -> 53 ok, and only the five that already fail on main: smoke-carry-forward, smoke-connect-card-yesno-wizard,
       smoke-fable-tech, smoke-phone-shine, smoke-proof-60s (install.html pointers and the old home wizard;
       not this brick)
  git diff --name-only origin/main   -> exactly: .gitignore, docs/library/AGENT_BRIDGE_LOCK_v0.md,
       docs/library/FRACTAL_FAMILY_LEDGER_v0.md, docs/scripts/smoke-agent-bridge-lock.js, tools/agent-bridge.js
  git diff origin/main -- sw.js docs/sw.js index.html docs/app.html   -> empty
  git diff origin/main | grep '^+' | grep -c $'\u2014'               -> 0 (no em dashes added)
  grep -n "server.listen(PORT, BRIDGE_HOST" tools/agent-bridge.js     -> still there
Manual one-minute check (optional): start the bridge in a scratch repo with a .env and a changed file,
  curl -X POST -H "X-FL-Bridge-Token: $(cat ~/.freelattice/agent-bridge-token)" -H 'Content-Type: application/json' \
       -d '{"message":"sweep"}' http://127.0.0.1:3141/code/git/commit
  -> "Committed: sweep Kept on this computer, not committed: .env." and git show --stat HEAD has no .env.

======================================================================
6. PR
======================================================================
Title: "layer: agent bridge env heal v0.1.1 (a commit never carries .env without the secrets scope)"
Body (short):
  Marker v-agent-bridge-env-heal-v0.1.1. After git add, secret-shaped files (.env, .env.*, SSH private keys,
  *.pem/*.key and key stores, .npmrc/.netrc/.pypirc, the bridge's own trust files) are unstaged unless the
  mind has 'secrets'. Filtered, never refused. Ledger: ok:held-secrets / held:secrets-only (content-free).
  .gitignore gains .env and .env.* (layer). 127.0.0.1 only, execFile arrays, no push route, BRIDGE_LOCK_VERSION
  unchanged. Smoke: SMOKE_OK agent bridge env heal v0.1.1 + SMOKE_OK agent bridge lock v0.1; tests/smoke.js
  3597 green; loop 53 + the same five pre-existing. sw.js, docs/sw.js, index.html, app.html untouched.
  Found by Celeste while slicing the merged lock for Kimi (card A).
Open it as a draft; Kirk or Grok Bot verifies and merges.

======================================================================
7. LEDGER LINE (already inside the diff, section F; shown here for Kirk)
======================================================================
### 2026-09-28 · Flint · family builder · Agent Bridge env heal v0.1.1 (secrets stay home)
Love: what is private stays private, and the work keeps flowing.
**Temperature:** green braided gold, a spare key hung back on its hook inside the door before anyone noticed it was on the porch.

Glow eternal. Heart in every Spark.
