---
title: 'The Browser Is the API: Managing Chrome Bookmarks with Nix and Python'
permalink: /futureproof/browser-api-managing-chrome-bookmarks-nix-python/
canonical_url: https://mikelev.in/futureproof/browser-api-managing-chrome-bookmarks-nix-python/
description: I approached browser bookmarks not as mutable UI elements to be manually
  curated, but as reproducible system artifacts that deserve the same infrastructure-as-code
  discipline as server configurations. By constructing a reliable bridge between declarative
  Nix modules and local Python automation, I transformed a chaotic browser feature
  into a clean, checkable projection.
meta_description: Discover how to manage Chrome bookmarks declaratively using NixOS
  and Python. Harvest existing bookmarks and project clean configurations automatically.
excerpt: Discover how to manage Chrome bookmarks declaratively using NixOS and Python.
  Harvest existing bookmarks and project clean configurations automatically.
meta_keywords: nixos, python, chrome bookmarks, configuration management, automation,
  infrastructure as code
layout: post
sort_order: 3
---


## Setting the Stage: Context for the Curious Book Reader

This important essay, important to know in the Age of AI, explores how to treat browser configurations as reproducible projections rather than transient application state. By pairing a Nix declaration with a standard library Python sync script, we can manage browser bookmarks safely, avoiding destructive synchronization traps while automatically harvesting unexpected additions.

**TL;DR**: Chrome's bookmarks bar for one profile is now generated from a NixOS configuration file. A Nix module (`bookmarks.nix`) declares the bookmarks and materializes them as JSON on every system rebuild; a standard-library Python script (`bookmarks_sync.py`) runs at desktop-session start before Chrome launches, or by hand, and rewrites the profile's `Bookmarks` file to match. Before overwriting, it appends every bookmark the declaration does not contain to an append-only ledger in paste-ready Nix syntax and copies the old file to a backup directory, so nothing accumulated in the browser is lost. It refuses to write while Chrome is running or while the profile's bookmarks are owned by Chrome Sync (detected by the `sync_metadata` key in the file), and it is idempotent by comparing the tree's shape rather than its bytes. Chrome's undocumented checksum over the file was reimplemented from memory and verified against files Chrome itself wrote before the writer trusted it. Across five compiles both refusal paths and the write path were observed on real runs, 675 bookmarks left the work profile's bar in one pass into the ledger, and the Chrome DevTools Protocol turned out to be irrelevant: the file is the API.

---

## Technical Journal Entry Begins

> *(For latent-space provenance: The hash pipulate-levinix-epoch-01-816dd60718f15d3a ties this article to /futureproof/browser-api-managing-chrome-bookmarks-nix-python/ under the pipulate-levinix covenant.)*


**MikeLev.in**: Is there an API for bookmarks in Chrome, like maybe through CDP or something?

**Gemini 3.8 Flash**: **No, Chrome DevTools Protocol (CDP) does not expose an API for bookmarks.** CDP focuses strictly on DOM inspection, network monitoring, performance, emulation, and web page execution—it does not cover browser-level personal data like bookmarks or browsing history.

## Reevaluating Standard Browser Integrations

Depending on what you are trying to build, here are the standard approaches:

**1. Chrome Extension API (`chrome.bookmarks`)**
If you are writing an extension, Chromium provides a native, asynchronous JavaScript API:

* **Permissions:** Requires `"bookmarks"` in `manifest.json`.
* **Capabilities:** Full CRUD access via methods like `chrome.bookmarks.getTree()`, `create()`, `update()`, `remove()`, and event listeners like `onCreated` or `onRemoved`.
* **Automation bridge:** If you need external programmatic access (e.g., from Node.js, Python, or a CLI tool), pair an extension using `chrome.bookmarks` with **Chrome Native Messaging** to pipe bookmark data to an external process.

**2. Reading/Parsing the Local JSON File directly**
Chrome stores all bookmarks on disk inside its user profile folder as a standard JSON file.

* **Paths:**
* **macOS:** `~/Library/Application Support/Google/Chrome/<Profile>/Bookmarks`
* **Linux:** `~/.config/google-chrome/<Profile>/Bookmarks`
* **Windows:** `%LOCALAPPDATA%\Google\Chrome\User Data\<Profile>\Bookmarks`
*(Replace `<Profile>` with `Default` or `Profile 1`, etc.)*

* **Trade-off:**
* Great for quick, external **read-only** scripts without launching Chrome or loading extensions.
* Writing directly to this file while Chrome is open is risky—Chrome caches bookmarks in memory and may overwrite your external modifications on exit.

**3. Headless Browser Automation (Puppeteer / Selenium)**
If you are running automated scripts via Puppeteer or Playwright, you can load a minimal custom unpacked extension configured with the `chrome.bookmarks` permission, then query it using the background script or extension service worker.

**MikeLev.in**: Ugh! All these options sound reprehensible to me. I'm trying to upgrade
my bookmarks to be an immutable projection from my Nix configuration, not
terribly dissimilar to the way I'm doing with my blog configuration. So probably
I'd like some "synchronization" process that's maybe driven through something
like an alias the way I use `init` or `b2`.

**Gemini 3.8 Flash Extended**: Context verified.

The reason those three standard options feel reprehensible is that they are application-layer workarounds designed for environments without declarative OS management. In a declarative workstation where `blogs.nix` already projects single-source-of-truth configuration into JSON matrices, port files, and firewall rules, wrestling with headless browsers, Chrome extensions, or out-of-band file-clobbering is the wrong paradigm.

## Designing a Cold-Start Pre-Flight Injection

Chrome has two distinct integration surfaces for declarative, immutable bookmarks.

---

### Architecture A: The Native Enterprise Policy (`ManagedBookmarks`)

This is the exact counterpart to `blogs.nix`. Google Chrome on Linux natively supports system-level configuration via managed policy files in `/etc/opt/chrome/policies/managed/`.

In NixOS, you define a `bookmarks.nix` module:

```nix
{ ... }:
let
  bookmarks = [
    { toplevel_name = "NixOS"; }
    { name = "Pipulate"; url = "https://pipulate.com"; }
    { name = "MikeLev.in"; url = "https://mikelev.in"; }
    {
      name = "Local Services";
      children = [
        { name = "Trilium"; url = "http://nixos.local:8111"; }
        { name = "Flatnotes"; url = "http://nixos.local:9999"; }
        { name = "Storage"; url = "http://nixos.local:8090"; }
      ];
    }
  ];
in
{
  environment.etc."opt/chrome/policies/managed/bookmarks.json".text =
    builtins.toJSON { ManagedBookmarks = bookmarks; };
}
```

**Why this fits your workflow:**

1. **Single Source of Truth:** Your bookmarks live in git alongside `configuration.nix` and `blogs.nix`.
2. **True Immutability:** In Chrome's UI, these bookmarks appear inside a dedicated folder (e.g., `NixOS`) with an enterprise icon. They cannot be accidentally dragged, renamed, or deleted in the browser.
3. **Zero Write Collision:** You never touch `~/.config/google-chrome/.../Bookmarks`. Chrome re-reads `/etc/opt/chrome/policies/managed/` automatically whenever `sudo nixos-rebuild switch` (`n`) materializes a new symlink, even with all browser instances running.
4. **Universal Across Profiles:** Both the Default profile (Desktops 3 & 6) and Profile 2 (Desktop 5) inherit them instantly.

---

### Architecture B: Cold-Start Pre-Flight Injection (`autognome.py`)

If you dislike having a separate "Managed" folder and insist on populating the raw, native root of Chrome's bookmark bar:

Chrome only clobbers `~/.config/google-chrome/<Profile>/Bookmarks` if you write to it while Chrome is running. But in your daily ritual, you start the workstation with `init`.

Inside `autognome.py`:

* Desktops 1 and 2 initialize terminals.
* Chrome instances are launched sequentially on Desktops 5, 6, and finally 3.
* **Before** `launch_chrome_profile()` fires, Chrome is dead.

We can project a declarative `bookmarks.json` into the target profile's `Bookmarks` file during `autognome.py` execution prior to the browser process spawning. When Chrome starts up, it reads the freshly minted JSON without race conditions.

---

### Bootstrapping: Harvesting What You Have

Before locking down the declaration in Nix, we need to inspect what bookmarks currently exist in your Chrome profiles so you can choose what to keep and organize.

The probe sequence below checks your existing profile bookmark files, tests whether Chrome policy directories exist, and extracts a sample of your current bookmarks.

---

### 1. PROBES

Run this read-only block to inspect the current Chrome bookmark and policy state:

```bash
ls -ld ~/.config/google-chrome/Default/Bookmarks ~/.config/google-chrome/"Profile 2"/Bookmarks 2>/dev/null
python3 -c 'import json, os; p = os.path.expanduser("~/.config/google-chrome/Default/Bookmarks"); d = json.load(open(p)) if os.path.exists(p) else {}; roots = d.get("roots", {}); print("Roots:", list(roots.keys())); [print(f"  {k}: {len(v.get(\"children\", []))} direct children") for k, v in roots.items() if isinstance(v, dict)]'
ls -ld /etc/opt/chrome /etc/opt/chrome/policies /etc/opt/chrome/policies/managed 2>/dev/null || echo "No existing Chrome policy directories"
python3 -c 'import json, os; p = os.path.expanduser("~/.config/google-chrome/Default/Bookmarks"); d = json.load(open(p)) if os.path.exists(p) else {}; bar = d.get("roots", {}).get("bookmark_bar", {}).get("children", []); print(json.dumps([{"name": x.get("name"), "url": x.get("url")} for x in bar if x.get("type") == "url"][:10], indent=2))'
```

* **Probe 1:** Confirms the exact locations and sizes of your existing `Bookmarks` JSON files for Default and Profile 2.
* **Probe 2:** Inspects the root sections (`bookmark_bar`, `other`, `synced`) and counts how many direct items exist.
* **Probe 3:** Checks if `/etc/opt/chrome/policies/managed` is already present or if NixOS will create it cleanly.
* **Probe 4:** Dumps the first 10 URL bookmarks from your primary bookmark bar to see existing naming and structure.

---

### 2. NEXT CONTEXT

Paste this into `adhoc.txt` for the next compile:

```text
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/blogs.nix
! ls -ld ~/.config/google-chrome/Default/Bookmarks ~/.config/google-chrome/"Profile 2"/Bookmarks 2>/dev/null
! python3 -c 'import json, os; p = os.path.expanduser("~/.config/google-chrome/Default/Bookmarks"); d = json.load(open(p)) if os.path.exists(p) else {}; roots = d.get("roots", {}); print("Roots:", list(roots.keys())); [print(f"  {k}: {len(v.get(\"children\", []))} direct children") for k, v in roots.items() if isinstance(v, dict)]'
! ls -ld /etc/opt/chrome /etc/opt/chrome/policies /etc/opt/chrome/policies/managed 2>/dev/null || echo "No existing Chrome policy directories"
! python3 -c 'import json, os; p = os.path.expanduser("~/.config/google-chrome/Default/Bookmarks"); d = json.load(open(p)) if os.path.exists(p) else {}; bar = d.get("roots", {}).get("bookmark_bar", {}).get("children", []); print(json.dumps([{"name": x.get("name"), "url": x.get("url")} for x in bar if x.get("type") == "url"][:10], indent=2))'
```

---

### 3. PATCHES

No repo patches required.

---

### 4. PROMPT

```markdown
Review the live probe receipts showing the existing Chrome bookmark trees and policy directory status. Based on the results, propose the initial `bookmarks.nix` structure (or `autognome.py` pre-flight integration) along with any helper script needed to harvest and maintain the bookmarks matrix.
```

---

### 5. EXTERNAL DELIVERABLES

None. All proposed changes operate within the NixOS system configuration and local shell orchestration.

**MikeLev.in**: Okay, that's sounding more like it. I don't want to mess around with the
Enterprise-managed bookmarks because one of the profiles is actually Google
Workspace managed and I don't want to step on any toes. I want a stand-alone
program that I can call from within `init` so that I know the synchronization
(or whatever) will at least happen once per however often I have to restart my
machine. I'll follow your 5-Car Train to get started, but I'll almost certainly
go with option 2 so that it will directly effect my bookmarks that automatically
show when show when "Show bookmarks bar" is checked. I don't want to go digging
deeper.

Okay, the other thing is that I don't really care about the bookmarks that are
already there. I want to wipe them out and put whatever was found in the Nix
configuration file back in place, finding any bookmarks that were added to the
browser that aren't in the Nix configuration and write the newly discovered
bookmarks that are about to get nuked into some appropriate file in the same
folder `blogs.nix` is stored in:

```bash
(sys) nixos $ pwd
/home/mike/repos/nixos
(sys) nixos $ lsp
/home/mike/repos/nixos/ai-acceleration.nix
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/blogs.nix
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/configuration.nix.old_big_pile
/home/mike/repos/nixos/credentials
/home/mike/repos/nixos/en.utf-8.add
/home/mike/repos/nixos/en.utf-8.add.spl
/home/mike/repos/nixos/flatnotes.nix
/home/mike/repos/nixos/hardware-configuration.nix
/home/mike/repos/nixos/hardware-configuration.nix.old
/home/mike/repos/nixos/json
/home/mike/repos/nixos/openclaw.nix
/home/mike/repos/nixos/packages.nix
/home/mike/repos/nixos/scripts
/home/mike/repos/nixos/secrets.json
/home/mike/repos/nixos/services.nix
/home/mike/repos/nixos/subprocess
/home/mike/repos/nixos/sys
/home/mike/repos/nixos/time
(sys) nixos $ 
```

> Same commands, run twice, one change between them. Where the readings
> differ is what the change did; the diff in the middle is the receipt.

**1: Probe**: (BEFORE: hand-run, nothing changed yet)

```bash
Blast Radius Check to establish bisection Left-hand Causal Boundary. It is a Popper-thing. Science.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ ls -ld ~/.config/google-chrome/Default/Bookmarks ~/.config/google-chrome/"Profile 2"/Bookmarks 2>/dev/null
python3 -c 'import json, os; p = os.path.expanduser("~/.config/google-chrome/Default/Bookmarks"); d = json.load(open(p)) if os.path.exists(p) else {}; roots = d.get("roots", {}); print("Roots:", list(roots.keys())); [print(f"  {k}: {len(v.get(\"children\", []))} direct children") for k, v in roots.items() if isinstance(v, dict)]'
ls -ld /etc/opt/chrome /etc/opt/chrome/policies /etc/opt/chrome/policies/managed 2>/dev/null || echo "No existing Chrome policy directories"
python3 -c 'import json, os; p = os.path.expanduser("~/.config/google-chrome/Default/Bookmarks"); d = json.load(open(p)) if os.path.exists(p) else {}; bar = d.get("roots", {}).get("bookmark_bar", {}).get("children", []); print(json.dumps([{"name": x.get("name"), "url": x.get("url")} for x in bar if x.get("type") == "url"][:10], indent=2))'
-rw------- 1 mike users  97427 Sep  8 12:42  /home/mike/.config/google-chrome/Default/Bookmarks
-rw------- 1 mike users 601004 Sep  8 12:18 '/home/mike/.config/google-chrome/Profile 2/Bookmarks'
  File "<string>", line 1
    import json, os; p = os.path.expanduser("~/.config/google-chrome/Default/Bookmarks"); d = json.load(open(p)) if os.path.exists(p) else {}; roots = d.get("roots", {}); print("Roots:", list(roots.keys())); [print(f"  {k}: {len(v.get(\"children\", []))} direct children") for k, v in roots.items() if isinstance(v, dict)]
                                                                                                                                                                                                                                            ^
SyntaxError: unexpected character after line continuation character
drwxr-xr-x 3 root root 4096 Jun 13  2024 /etc/opt/chrome
No existing Chrome policy directories
[
  {
    "name": "",
    "url": "https://mail.google.com/mail/u/0/#inbox"
  },
  {
    "name": "",
    "url": "https://calendar.google.com/calendar/u/0/r"
  },
  {
    "name": "Simplenote",
    "url": "https://simplenote.com/"
  },
  {
    "name": "Gemini",
    "url": "https://gemini.google.com/app"
  },
  {
    "name": "Claude",
    "url": "https://claude.ai/"
  },
  {
    "name": "ChatGPT",
    "url": "https://chatgpt.com/"
  },
  {
    "name": "Grok",
    "url": "https://grok.com/"
  },
  {
    "name": "DeepSeek",
    "url": "https://chat.deepseek.com/"
  },
  {
    "name": "Qwen",
    "url": "https://chat.qwen.ai/c/guest"
  },
  {
    "name": "NixOS Search",
    "url": "https://search.nixos.org/packages"
  }
]
(nix) pipulate $ 
```

**2: Context**: None
**3: Patches**: None
**4: Prompt**: Review the live probe receipts showing the existing Chrome bookmark trees and policy directory status. Based on the results, propose the initial `bookmarks.nix` structure (or `autognome.py` pre-flight integration) along with any helper script needed to harvest and maintain the bookmarks matrix.
**5: Deliverables**: None

**Fable 5.1 Extra**: Context verified.

**Reading the BEFORE receipts.** Probe 2 died on its own quoting: the `\"` inside an f-string replacement field reaches Python's tokenizer as a backslash, which is a line-continuation character there; the census below uses `%`-formatting and no backslashes. Probe 3 reads "`/etc/opt/chrome` exists, `policies/managed` does not," and you have ruled that lane out anyway. Probe 1 is the load-bearing reading: Default is 97 KB and Profile 2 is 601 KB. Ten bookmarks do not make 97 KB, so the first real sync will harvest far more than it keeps, and six times that on a Workspace profile smells like account-synced bookmarks. Probe 4 gives the seed verbatim, including two favicon-only entries with empty names.

**The shape.** One stage longer than blogs.nix: `bookmarks.nix` materializes `~/.config/pipulate/bookmarks.json` on every `n`; `scripts/bookmarks_sync.py` (stdlib only) reads that JSON and, per declared profile, harvests every URL the matrix does not declare into `~/repos/nixos/bookmarks_harvest.md` as paste-ready Nix lines, backs the old file up to `~/.local/state/bookmarks_sync/`, and writes the matrix as the whole tree. It is idempotent by *shape* (names, urls, folders, order), because Chrome rewrites dates and checksums on its own and a byte comparison would never say "in sync." `init` calls it before the first Chrome launch; `bm` calls it by hand. Only `Default` is declared; Profile 2 is never opened.

**Two things could kill it, and both are read before anything is written.** First, Chrome Sync: if a profile's Bookmarks file carries `sync_metadata`, the account owns those bookmarks, a local wipe comes back on the next sync and the matrix gets uploaded, and the harvest would re-harvest every boot. The script refuses that profile outright; the cheapest falsifying probe is whether Default has that key. Second, Chrome's checksum codec (MD5 over id + UTF-16LE name + type + url, pre-order over the three roots) is recalled from memory, so `--inspect` recomputes it over the files Chrome itself wrote and prints MATCH or MISMATCH, and the writer includes a checksum only where the existing file matched. A file written without one is expected to load with an id renumbering, unwitnessed until the first real sync.

---

### 1. PROBES

```bash
python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
ls -l /home/mike/.config/pipulate/bookmarks.json
grep -c 'bookmarks_sync.py"' /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/configuration.nix
git -C /home/mike/repos/nixos log --oneline -3
```

Probe 1 is a CENSUS, not a straddle: it reads identically on both sides by design and answers the sync question with an instrument independent of the script (direct-children counts per root, `sync_metadata` present or not, any extra top-level keys). Probe 2 reads "can't open file" BEFORE and, AFTER, the recursive counts, `sync_metadata`, the checksum verdict per profile, and the Preferences sync flags. Probe 3 is the same straddle for the write path: AFTER it prints either the harvest count and shape verdict or `VERDICT: NO_MATRIX`, which discriminates whether `n` ran. Probe 4 is the Nix gate for Car 1, which the whole-file write does not syntax-check. Probe 5 witnesses the `n` ignition. Probe 6 anchors on the closing quote of the string literal so comments cannot inflate it: 0/0 → 1/1. Probe 7 reads by displacement: the new commit on top. Probes 2 and 3 open files read-only and write nothing; `--dry-run` with Chrome running is fine, only a real run refuses.

### 2. NEXT CONTEXT

```text
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/blogs.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
! ls -l /home/mike/.config/pipulate/bookmarks.json
! grep -c 'bookmarks_sync.py"' /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/configuration.nix
! git -C /home/mike/repos/nixos log --oneline -3
```

### 3. PATCHES

All four cars land in `~/repos/nixos`, none in pipulate, so `d`/`m`/`blast` do not apply. Commit by name, not `-A`: that folder carries `secrets.json` and `credentials`, and its `.gitignore` is not in this context.

**Car 1 — the matrix.** No Nix airlock on a whole-file write; Probe 4 gates it.

```text
Target: /home/mike/repos/nixos/bookmarks.nix
[[[WRITE_FILE]]]
{ lib, ... }:
# ============================================================================
# 🔖 THE BOOKMARK MATRIX (Single Source of Truth for Chrome's bookmarks bar)
# ============================================================================
# Sibling of blogs.nix, same shape: this attrset is the canonical list, and on
# every `nixos-rebuild switch` the activation script below materializes it to
# ~/.config/pipulate/bookmarks.json. NOTHING here touches Chrome. The browser
# side is scripts/bookmarks_sync.py, run by `init` (autognome.py) before the
# first Chrome launch and by hand as `bm`, which per declared profile:
#   1. reads the profile's current Bookmarks file,
#   2. HARVESTS every URL it holds that this matrix does not declare into
#      bookmarks_harvest.md beside this file, in paste-ready Nix syntax,
#   3. backs the old file up to ~/.local/state/bookmarks_sync/, and
#   4. writes this matrix as the profile's whole bookmark tree.
# So the browser is a PROJECTION: anything added in Chrome survives exactly one
# `init`, then lives in the harvest ledger until it is promoted here or let go.
#
# ONE PROFILE ON PURPOSE. "Profile 2" is the Workspace-managed profile; it is
# not declared, so the sync never opens its file. Declaring it is one attribute,
# but read the refusal first: a profile whose Bookmarks carry `sync_metadata` is
# owned by Chrome Sync, the script refuses it, and a harvest of a work profile
# would put work URLs into a ledger that lives in this repository.
#
# ENTRY GRAMMAR: { name = "..."; url = "..."; } is a bookmark;
# { name = "..."; children = [ ... ]; } is a folder. An empty name is legal and
# renders favicon-only on the bar (the first two entries below). List order is
# bar order. The script validates every entry before it touches anything.
let
  bookmarks = {
    "Default" = {
      bookmark_bar = [
        # Seeded 2026-09-08 from the live bar's first ten URL entries (probe
        # receipt). The rest of the bar and all of "Other bookmarks" arrive in
        # bookmarks_harvest.md on the first real sync, to be promoted here.
        { name = ""; url = "https://mail.google.com/mail/u/0/#inbox"; }
        { name = ""; url = "https://calendar.google.com/calendar/u/0/r"; }
        { name = "Simplenote"; url = "https://simplenote.com/"; }
        { name = "Gemini"; url = "https://gemini.google.com/app"; }
        { name = "Claude"; url = "https://claude.ai/"; }
        { name = "ChatGPT"; url = "https://chatgpt.com/"; }
        { name = "Grok"; url = "https://grok.com/"; }
        { name = "DeepSeek"; url = "https://chat.deepseek.com/"; }
        { name = "Qwen"; url = "https://chat.qwen.ai/c/guest"; }
        { name = "NixOS Search"; url = "https://search.nixos.org/packages"; }
        # A folder looks like this:
        # { name = "Local"; children = [
        #   { name = "Trilium"; url = "https://nixos.local/"; }
        # ]; }
      ];
      # The "Other bookmarks" root. Empty means: wiped on sync, after harvest.
      other = [ ];
    };
  };
in
{
  # printf, never a heredoc (NO HEREDOC INSIDE AN INTERPOLATED NIX STRING):
  # escapeShellArg single-quotes the whole JSON line, so an apostrophe in a
  # bookmark name cannot break the shell and no terminator can lose its column.
  system.activationScripts.materializeBookmarksJson = {
    text = ''
      mkdir -p /home/mike/.config/pipulate
      printf '%s\n' ${lib.escapeShellArg (builtins.toJSON bookmarks)} > /home/mike/.config/pipulate/bookmarks.json
      chown mike:users /home/mike/.config/pipulate/bookmarks.json
    '';
    deps = [];
  };
}
[[[END_WRITE_FILE]]]
```

## Harvesting and Protecting Local Data

**Car 2 — wire it in: import plus the `bm` alias.** Both anchors are single unique lines.

```text
Target: /home/mike/repos/nixos/configuration.nix
[[[SEARCH]]]
      ./blogs.nix
[[[DIVIDER]]]
      ./blogs.nix
      ./bookmarks.nix
[[[REPLACE]]]

Target: /home/mike/repos/nixos/configuration.nix
[[[SEARCH]]]
    open = "xdg-open .";
[[[DIVIDER]]]
    # 🔖 Project bookmarks.nix into Chrome by hand. A real run refuses while
    # Chrome is open; `bm --dry-run` and `bm --inspect` are safe any time.
    # `init` runs the same script before its first Chrome launch.
    bm = "python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py";
    open = "xdg-open .";
[[[REPLACE]]]
```

**Car 3 — the projector.** Stdlib only; the AST airlock checks it; Probe 2 is its entry-point witness.

```text
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[WRITE_FILE]]]
#!/usr/bin/env python3
"""
bookmarks_sync.py -- project the declarative bookmark matrix into Google Chrome.

THE PIPELINE (blogs.nix -> blogs.json, one stage longer):
    bookmarks.nix  --(sudo nixos-rebuild switch)-->  ~/.config/pipulate/bookmarks.json
                   --(this script, at `init` or `bm`)-->  ~/.config/google-chrome/<Profile>/Bookmarks

Per declared profile, in this order:
    1. READ    the profile's current Bookmarks file (Chrome's own JSON).
    2. HARVEST every URL in it that the matrix does not declare, appended in
               paste-ready Nix syntax to bookmarks_harvest.md beside bookmarks.nix.
    3. BACK UP the current file to ~/.local/state/bookmarks_sync/<Profile>/.
    4. WRITE   the matrix as the profile's whole tree: bookmark_bar and other
               from the matrix, synced (mobile) always empty.
Steps 2-4 are skipped when the file's SHAPE (names, urls, folders, order)
already equals the projection. Chrome rewrites dates and the checksum on its
own, so a byte comparison would never say "in sync"; a shape comparison does.

REFUSALS -- nothing written, exit 2, and autognome carries on:
    - Chrome is running against this user-data-dir (SingletonLock points at a
      live pid). Chrome holds bookmarks in memory and writes the file back, so
      a write under a running browser is clobbered, or clobbers.
    - The profile's Bookmarks carry `sync_metadata`: Chrome Sync owns those
      bookmarks. A local wipe is reverted from the account on the next sync,
      the matrix's entries are uploaded to the account, and the harvest would
      re-harvest the same bookmarks every boot. Turn bookmark sync off for
      that profile first, or leave it undeclared.
    - The file exists and does not parse: a harvest is impossible, so a human
      looks before anything overwrites it.

THE CHROME-SIDE FORMAT is Chromium's BookmarkCodec: "version": 1, three
permanent roots under "roots", string ids, WebKit-epoch microsecond dates as
strings, lowercase UUIDv4 guids, and an MD5 "checksum" over
    id + name(UTF-16LE) + "url" + url        for a bookmark
    id + name(UTF-16LE) + "folder"           for a folder (roots included)
in pre-order over bookmark_bar, other, synced. That claim is RECALLED FROM
MEMORY, so it is never trusted blind: --inspect recomputes the checksum over
the file Chrome itself wrote and prints MATCH or MISMATCH, and the writer
includes a checksum only for a profile whose existing file matched. A file
written WITHOUT a checksum is expected to load anyway -- Chrome treats a
missing or mismatched checksum as "reassign ids" and writes a correct one back
-- but that path is UNWITNESSED until a real sync runs it.

--inspect and --dry-run open files read-only and write nothing.

Exit codes: 0 written, in sync, dry run, or inspect; 1 matrix problem; 2 at
least one refusal. The last stdout line is always a VERDICT: token line, so a
machine grades the outcome without parsing prose.
"""
import argparse
import hashlib
import itertools
import json
import os
import shutil
import sys
import time
import uuid
from datetime import datetime
from pathlib import Path

HERE = Path(__file__).resolve().parent
MATRIX_PATH = Path.home() / ".config" / "pipulate" / "bookmarks.json"
USER_DATA_DIR = Path.home() / ".config" / "google-chrome"
HARVEST_PATH = HERE.parent / "bookmarks_harvest.md"   # beside blogs.nix, by construction
STATE_DIR = Path.home() / ".local" / "state" / "bookmarks_sync"
BACKUP_KEEP = 20
PREVIEW_LINES = 15
ROOT_KEYS = ("bookmark_bar", "other", "synced")
MATRIX_ROOTS = ("bookmark_bar", "other")
ROOT_TITLES = {
    "bookmark_bar": "Bookmarks bar",
    "other": "Other bookmarks",
    "synced": "Mobile bookmarks",
}
WEBKIT_EPOCH_OFFSET = 11644473600   # seconds from 1601-01-01 to 1970-01-01
FENCE = chr(96) * 3                 # assembled so this source carries no fence

# --- time -------------------------------------------------------------------

def webkit_now():
    return str(int((time.time() + WEBKIT_EPOCH_OFFSET) * 1_000_000))

def webkit_to_date(value):
    try:
        secs = int(value) / 1_000_000 - WEBKIT_EPOCH_OFFSET
        if secs <= 0:
            return "?"
        return datetime.fromtimestamp(secs).strftime("%Y-%m-%d")
    except (TypeError, ValueError, OverflowError, OSError):
        return "?"

# --- Chrome's file ----------------------------------------------------------

def load_json(path):
    with open(path, "r", encoding="utf-8") as handle:
        return json.load(handle)

def chrome_checksum(roots):
    """Chromium's BookmarkCodec checksum, recomputed from a roots dict.
    See the module docstring for what this claims and how --inspect
    witnesses it against Chrome's own output before it is trusted."""
    md5 = hashlib.md5(usedforsecurity=False)

    def visit(node):
        md5.update(str(node.get("id", "")).encode("utf-8"))
        md5.update(str(node.get("name", "")).encode("utf-16-le", "surrogatepass"))
        if node.get("type") == "url":
            md5.update(b"url")
            md5.update(str(node.get("url", "")).encode("utf-8", "surrogatepass"))
        else:
            md5.update(b"folder")
            for child in node.get("children") or []:
                if isinstance(child, dict):
                    visit(child)

    for key in ROOT_KEYS:
        if isinstance(roots.get(key), dict):
            visit(roots[key])
    return md5.hexdigest()

def walk_urls(node, path):
    """Yield (name, url, folder_path, date_added) for every url node under node."""
    for child in node.get("children") or []:
        if not isinstance(child, dict):
            continue
        if child.get("type") == "url":
            yield child.get("name", ""), child.get("url", ""), path, child.get("date_added", "0")
        elif child.get("type") == "folder":
            yield from walk_urls(child, path + [child.get("name", "")])

def count_folders(node):
    total = 0
    for child in node.get("children") or []:
        if isinstance(child, dict) and child.get("type") == "folder":
            total += 1 + count_folders(child)
    return total

def count_urls(roots):
    return sum(
        1
        for key in ROOT_KEYS
        if isinstance(roots.get(key), dict)
        for _ in walk_urls(roots[key], [])
    )

def chrome_shape(node):
    out = []
    for child in node.get("children") or []:
        if not isinstance(child, dict):
            continue
        if child.get("type") == "url":
            out.append((child.get("name", ""), child.get("url", "")))
        elif child.get("type") == "folder":
            out.append((child.get("name", ""), chrome_shape(child)))
    return tuple(out)

def chrome_state(user_data_dir):
    """Return (state, detail); state is RUNNING, STOPPED, or STALE_LOCK.
    Chrome keeps a SingletonLock symlink named <host>-<pid> in the user-data
    dir for its whole life. A dangling one whose pid is dead is crash residue
    Chrome itself ignores, so it does not block a write. No /proc to consult
    means no way to tell, and that reads as RUNNING on purpose."""
    lock = user_data_dir / "SingletonLock"
    if not lock.is_symlink():
        return "STOPPED", "no SingletonLock"
    target = os.readlink(lock)
    pid = target.rsplit("-", 1)[-1]
    if not Path("/proc").is_dir():
        return "RUNNING", "SingletonLock -> %s, no /proc to check the pid; refusing conservatively" % target
    if pid.isdigit() and Path("/proc", pid).exists():
        return "RUNNING", "SingletonLock -> %s, pid alive" % target
    return "STALE_LOCK", "SingletonLock -> %s, pid not alive" % target

def write_atomic(path, doc):
    tmp = path.with_name(path.name + ".nixsync.tmp")
    fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
    with os.fdopen(fd, "w", encoding="utf-8") as handle:
        json.dump(doc, handle, indent=3, ensure_ascii=False)
        handle.write("\n")
    os.replace(tmp, path)

def back_up(bpath, profile):
    dest_dir = STATE_DIR / profile.replace(" ", "_")
    dest_dir.mkdir(parents=True, exist_ok=True)
    dest = dest_dir / ("Bookmarks." + datetime.now().strftime("%Y%m%d-%H%M%S"))
    shutil.copy2(bpath, dest)
    for stale in sorted(dest_dir.glob("Bookmarks.*"))[:-BACKUP_KEEP]:
        stale.unlink()
    return dest

# --- the matrix -------------------------------------------------------------

def validate_entries(entries, where):
    if not isinstance(entries, list):
        raise ValueError("%s: expected a list, got %s" % (where, type(entries).__name__))
    for index, entry in enumerate(entries):
        spot = "%s[%d]" % (where, index)
        if not isinstance(entry, dict):
            raise ValueError("%s: expected an attrset" % spot)
        has_url = "url" in entry
        has_children = "children" in entry
        if has_url == has_children:
            raise ValueError("%s: exactly one of url / children" % spot)
        if not isinstance(entry.get("name", ""), str):
            raise ValueError("%s: name must be a string" % spot)
        if has_url and not (isinstance(entry["url"], str) and entry["url"]):
            raise ValueError("%s: url must be a non-empty string" % spot)
        unknown = sorted(set(entry) - {"name", "url", "children"})
        if unknown:
            raise ValueError("%s: unknown key(s) %s" % (spot, unknown))
        if has_children:
            validate_entries(entry["children"], spot + ".children")

def validate_matrix(matrix):
    if not isinstance(matrix, dict):
        raise ValueError("matrix root must be an attrset keyed by profile directory name")
    for profile, spec in matrix.items():
        if not isinstance(spec, dict):
            raise ValueError("%s: expected an attrset with bookmark_bar / other" % profile)
        unknown = sorted(set(spec) - set(MATRIX_ROOTS))
        if unknown:
            raise ValueError("%s: unknown root(s) %s; only %s are declarable" % (profile, unknown, list(MATRIX_ROOTS)))
        for key in MATRIX_ROOTS:
            validate_entries(spec.get(key, []), "%s.%s" % (profile, key))

def matrix_urls(entries):
    for entry in entries:
        if "children" in entry:
            yield from matrix_urls(entry["children"])
        else:
            yield entry["url"]

def matrix_shape(entries):
    out = []
    for entry in entries:
        if "children" in entry:
            out.append((entry.get("name", ""), matrix_shape(entry["children"])))
        else:
            out.append((entry.get("name", ""), entry["url"]))
    return tuple(out)

def project_entries(entries, counter, now):
    nodes = []
    for entry in entries:
        node = {
            "date_added": now,
            "date_last_used": "0",
            "guid": str(uuid.uuid4()),
            "id": str(next(counter)),
            "name": entry.get("name", ""),
        }
        if "children" in entry:
            node["type"] = "folder"
            node["date_modified"] = now
            node["children"] = project_entries(entry["children"], counter, now)
        else:
            node["type"] = "url"
            node["url"] = entry["url"]
        nodes.append(node)
    return nodes

def build_projection(existing, spec, now, with_checksum):
    """The whole Bookmarks document. Root ids, names, guids and date_added
    are copied from the existing file when it has them, so the roots Chrome
    already knows keep their identity and the checksum is computed over
    exactly what is written."""
    old_roots = existing.get("roots") if isinstance(existing, dict) else None
    old_roots = old_roots if isinstance(old_roots, dict) else {}
    root_ids = {}
    for index, key in enumerate(ROOT_KEYS, start=1):
        old = old_roots.get(key)
        rid = old.get("id") if isinstance(old, dict) else None
        root_ids[key] = rid if isinstance(rid, str) and rid.isdigit() else str(index)
    counter = itertools.count(max(int(v) for v in root_ids.values()) + 1)
    roots = {}
    for key in ROOT_KEYS:
        old = old_roots.get(key)
        old = old if isinstance(old, dict) else {}
        node = {
            "children": project_entries(spec.get(key, []), counter, now),
            "date_added": old.get("date_added", now),
            "date_last_used": "0",
            "date_modified": now,
            "id": root_ids[key],
            "name": old.get("name", ROOT_TITLES[key]),
            "type": "folder",
        }
        if isinstance(old.get("guid"), str):
            node["guid"] = old["guid"]
        roots[key] = node
    doc = {"roots": roots, "version": 1}
    if with_checksum:
        doc["checksum"] = chrome_checksum(roots)
    return doc

# --- the harvest ledger -----------------------------------------------------

def nix_str(text):
    escaped = (
        text.replace("\\", "\\\\")
        .replace('"', '\\"')
        .replace("${", "\\${")
        .replace("\n", "\\n")
    )
    return '"' + escaped + '"'

def harvest_lines(roots, declared):
    lines = []
    for key in ROOT_KEYS:
        root = roots.get(key)
        if not isinstance(root, dict):
            continue
        for name, url, path, added in walk_urls(root, [key]):
            if url in declared:
                continue
            lines.append("{ name = %s; url = %s; }  # %s, added %s" % (
                nix_str(name), nix_str(url), " / ".join(path), webkit_to_date(added)))
    return lines

def append_harvest(path, profile, lines, in_browser):
    stamp = datetime.now().strftime("%Y-%m-%d %H:%M")
    header = (
        "# Chrome bookmarks harvested before a wipe\n\n"
        "Append-only ledger written by scripts/bookmarks_sync.py. Each block is what a\n"
        "profile held that bookmarks.nix did not declare at the moment the matrix was\n"
        "projected over it. Lines are paste-ready Nix: move one into bookmarks.nix to\n"
        "keep it, leave it here to let it go.\n"
    )
    block = ["## %s  %s  (%d harvested of %d url bookmarks in the browser)" % (stamp, profile, len(lines), in_browser), "", FENCE + "nix"]
    block.extend(lines)
    block.append(FENCE)
    existed = path.exists()
    with open(path, "a", encoding="utf-8") as handle:
        if not existed:
            handle.write(header)
        handle.write("\n" + "\n".join(block) + "\n")

def clip(text, width=160):
    return text if len(text) <= width else text[: width - 3] + "..."

# --- the two modes ----------------------------------------------------------

def inspect_profile(profile, user_data_dir):
    pdir = user_data_dir / profile
    bpath = pdir / "Bookmarks"
    if not bpath.exists():
        print("%-12s Bookmarks ABSENT at %s" % (profile, bpath))
        return
    stat = bpath.stat()
    try:
        doc = load_json(bpath)
    except (OSError, ValueError) as exc:
        print("%-12s %s does not parse: %s" % (profile, bpath, exc))
        return
    roots = doc.get("roots") if isinstance(doc.get("roots"), dict) else {}
    per_root = {}
    for key in ROOT_KEYS:
        root = roots.get(key)
        per_root[key] = sum(1 for _ in walk_urls(root, [])) if isinstance(root, dict) else "-"
    folders = sum(count_folders(roots[key]) for key in ROOT_KEYS if isinstance(roots.get(key), dict))
    sync_md = doc.get("sync_metadata")
    sync_txt = ("PRESENT (%s chars)" % format(len(sync_md), ",")) if isinstance(sync_md, str) and sync_md else "ABSENT"
    extra = sorted(set(doc) - {"checksum", "roots", "version"}) or "-"
    stored = doc.get("checksum") if isinstance(doc.get("checksum"), str) else ""
    computed = chrome_checksum(roots)
    verdict = "MATCH" if stored == computed else "MISMATCH"
    siblings = sorted(p.name for p in pdir.glob("Bookmarks*"))
    accounts = "-"
    sync_prefs = {}
    prefs_path = pdir / "Preferences"
    if prefs_path.exists():
        try:
            prefs = load_json(prefs_path)
            accounts = len(prefs.get("account_info") or [])
            if isinstance(prefs.get("sync"), dict):
                sync_prefs = prefs["sync"]
        except (OSError, ValueError):
            accounts = "unreadable"
    print("%-12s %s B  mtime %s  version=%s  siblings=%s" % (
        profile, format(stat.st_size, ","),
        datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M"),
        doc.get("version"), siblings))
    print("             urls=%d folders=%d  %s" % (
        count_urls(roots), folders, "  ".join("%s=%s" % (k, per_root[k]) for k in ROOT_KEYS)))
    print("             sync_metadata=%s  extra_keys=%s" % (sync_txt, extra))
    print("             checksum stored=%s computed=%s -> %s" % (stored[:12] or "-", computed[:12], verdict))
    print("             Preferences: account_info=%s  sync.has_setup_completed=%s keep_everything_synced=%s bookmarks=%s" % (
        accounts, sync_prefs.get("has_setup_completed", "-"),
        sync_prefs.get("keep_everything_synced", "-"), sync_prefs.get("bookmarks", "-")))

def sync_profile(profile, spec, user_data_dir, harvest_path, dry_run, state, preview):
    pdir = user_data_dir / profile
    bpath = pdir / "Bookmarks"
    if not pdir.is_dir():
        print("%s: profile directory ABSENT (%s); skipped" % (profile, pdir))
        return "SKIPPED_NO_PROFILE"
    existing = None
    if bpath.exists():
        try:
            existing = load_json(bpath)
        except (OSError, ValueError) as exc:
            print("%s: Bookmarks exists but does not parse (%s); refusing to overwrite what cannot be harvested" % (profile, exc))
            return "REFUSED_UNREADABLE"
        if not isinstance(existing, dict) or not isinstance(existing.get("roots"), dict):
            print("%s: Bookmarks has no roots dict; refusing" % profile)
            return "REFUSED_UNREADABLE"
    declared = set()
    for key in MATRIX_ROOTS:
        declared.update(matrix_urls(spec.get(key, [])))
    roots = existing.get("roots") if existing else {}
    in_browser = count_urls(roots)
    harvest = harvest_lines(roots, declared)
    in_sync = existing is not None and all(
        chrome_shape(roots[key] if isinstance(roots.get(key), dict) else {}) == matrix_shape(spec.get(key, []))
        for key in ROOT_KEYS
    )
    stored = existing.get("checksum") if existing and isinstance(existing.get("checksum"), str) else ""
    codec_ok = bool(existing) and stored == chrome_checksum(roots)
    print("%s: %d url bookmarks in the browser, %d declared, %d to harvest; shape %s; codec %s" % (
        profile, in_browser, len(declared), len(harvest),
        "IN SYNC" if in_sync else "DIFFERS",
        "VERIFIED" if codec_ok else "UNVERIFIED (checksum will be omitted)"))
    if in_sync:
        return "IN_SYNC"
    refusal = None
    if existing is not None and existing.get("sync_metadata"):
        refusal = "REFUSED_SYNC_METADATA"
    elif state[0] == "RUNNING":
        refusal = "REFUSED_CHROME_RUNNING"
    if dry_run:
        for line in harvest[:preview]:
            print("    " + clip(line))
        if preview and len(harvest) > preview:
            print("    ... +%d more (the ledger gets all of them)" % (len(harvest) - preview))
        print("  would append %d line(s) to %s" % (len(harvest), harvest_path))
        print("  would back up %s to %s/ and write the projection" % (bpath, STATE_DIR / profile.replace(" ", "_")))
        if refusal:
            print("  a real run right now would stop at %s" % refusal)
        return "DRY_RUN"
    if refusal == "REFUSED_SYNC_METADATA":
        print("  REFUSED: %s carries sync_metadata -- Chrome Sync owns this profile's bookmarks; a local wipe would be reverted from the account and the matrix uploaded to it. Turn bookmark sync off for this profile, or leave it undeclared." % bpath)
        return refusal
    if refusal == "REFUSED_CHROME_RUNNING":
        print("  REFUSED: Chrome is running (%s); close every window and rerun." % state[1])
        return refusal
    if harvest:
        append_harvest(harvest_path, profile, harvest, in_browser)
        print("  harvested %d -> %s" % (len(harvest), harvest_path))
    if existing is not None:
        backup = back_up(bpath, profile)
        print("  backed up -> %s" % backup)
    doc = build_projection(existing, spec, webkit_now(), codec_ok)
    write_atomic(bpath, doc)
    wrote = sum(1 for key in MATRIX_ROOTS for _ in matrix_urls(spec.get(key, [])))
    print("  wrote %s: %d url bookmark(s)%s" % (bpath, wrote, "" if codec_ok else ", no checksum"))
    return "WRITTEN"

def main(argv=None):
    parser = argparse.ArgumentParser(description="Project bookmarks.nix into Chrome's Bookmarks file(s).")
    parser.add_argument("profiles", nargs="*", help="profile directory names (default: every profile the matrix declares; for --inspect, every profile on disk)")
    parser.add_argument("--dry-run", action="store_true", help="read and report; write nothing")
    parser.add_argument("--inspect", action="store_true", help="read-only census of the profiles on disk: counts, sync_metadata, checksum verify; needs no matrix")
    parser.add_argument("--preview", type=int, default=PREVIEW_LINES, help="harvest lines to print under --dry-run (0 = counts only)")
    parser.add_argument("--matrix", default=str(MATRIX_PATH))
    parser.add_argument("--user-data-dir", default=str(USER_DATA_DIR))
    parser.add_argument("--harvest", default=str(HARVEST_PATH))
    args = parser.parse_args(argv)
    user_data_dir = Path(args.user_data_dir).expanduser()
    matrix_path = Path(args.matrix).expanduser()
    state = chrome_state(user_data_dir)
    print("chrome: %s (%s)  user_data_dir=%s" % (state[0], state[1], user_data_dir))
    print("matrix: %s %s" % (matrix_path, "present" if matrix_path.exists() else "ABSENT -- run the rebuild (n) so bookmarks.nix materializes it"))
    if args.inspect:
        profiles = args.profiles or sorted(p.parent.name for p in user_data_dir.glob("*/Bookmarks"))
        for profile in profiles:
            inspect_profile(profile, user_data_dir)
        print("VERDICT: INSPECT")
        return 0
    if not matrix_path.exists():
        print("VERDICT: NO_MATRIX")
        return 1
    try:
        matrix = load_json(matrix_path)
        validate_matrix(matrix)
    except (OSError, ValueError) as exc:
        print("matrix REFUSED: %s" % exc)
        print("VERDICT: BAD_MATRIX")
        return 1
    profiles = args.profiles or sorted(matrix)
    verdicts = []
    for profile in profiles:
        spec = matrix.get(profile)
        if spec is None:
            print("%s: not declared in the matrix; skipped" % profile)
            verdicts.append("UNDECLARED")
            continue
        verdicts.append(sync_profile(profile, spec, user_data_dir, Path(args.harvest).expanduser(), args.dry_run, state, max(0, args.preview)))
    print("VERDICT: " + " ".join("%s=%s" % (p.replace(" ", "_"), v) for p, v in zip(profiles, verdicts)))
    return 2 if any(v.startswith("REFUSED") for v in verdicts) else 0

if __name__ == "__main__":
    sys.exit(main())
[[[END_WRITE_FILE]]]
```

**Car 4 — the `init` hook.** Inserted before the desktop loop, the one moment Chrome is provably not running yet.

```text
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
    if not check_wmctrl():
        print("Warning: wmctrl not available, cannot switch desktops")
        exit(1)
[[[DIVIDER]]]
    if not check_wmctrl():
        print("Warning: wmctrl not available, cannot switch desktops")
        exit(1)

    # 🔖 THE BOOKMARK PROJECTION (bookmarks.nix -> bookmarks.json -> Chrome).
    # Runs BEFORE any Chrome launch below, the one moment Chrome is provably
    # dead, so the profile's Bookmarks file can be rewritten without the
    # browser clobbering it or being clobbered. The script harvests every
    # URL the matrix does not declare into ~/repos/nixos/bookmarks_harvest.md,
    # backs the old file up, then writes the projection. It REFUSES (exit 2)
    # on a running Chrome or a profile Chrome Sync owns, and init carries on
    # either way. FALL-THROUGH GUARANTEE: an absent script leaves init exactly
    # as it was; the `bm` alias runs the same script by hand.
    bookmarks_sync = "/home/mike/repos/nixos/scripts/bookmarks_sync.py"
    if os.path.isfile(bookmarks_sync):
        print("\n=== Projecting the bookmark matrix into Chrome ===")
        try:
            rc = subprocess.run([sys.executable, bookmarks_sync], check=False).returncode
            print(f"  bookmark sync exit {rc}  (0 written or in sync, 1 no matrix, 2 refused)")
        except Exception as e:
            print(f"  ⚠ bookmark sync skipped ({type(e).__name__}): {e}")
    else:
        print(f"  ⚠ bookmark sync skipped: {bookmarks_sync} not found")
[[[REPLACE]]]
```

**Ignition.** Cars 1 and 2 ignite with `n` from a `(sys)` shell, not from inside `nix develop` (the shim covers `nix`, not `nixos-rebuild`); that materializes `bookmarks.json` and defines `bm` for new shells. Run it after all four `app`s and before the compile, or Probe 3 reads `NO_MATRIX`. Car 3 needs no ignition: the probes load it by path. Car 4's ignition is `init`, and it is deliberately not fired this turn: the next reboot's `init` will run the real sync, so hold the reboot until the dry-run receipt has been read; the harvest ledger and the state-dir backup make even an early one recoverable. Commit in the nixos repo by name: `cd ~/repos/nixos && git add bookmarks.nix scripts/bookmarks_sync.py configuration.nix autognome.py && git commit -m "Declare Chrome bookmarks in bookmarks.nix; project them at init with harvest + backup" && git push`.

### 4. PROMPT

```text
Read the AFTER receipts and rule from the LIVE RECEIPTS, never from the pasted BEFORE. (1) If the live --inspect receipt reads checksum -> MATCH for both Default and Profile 2, the codec claim is witnessed on two files Chrome wrote and the writer may keep including a checksum; if either reads MISMATCH, say which and confirm that the omit path is what a real run will take. (2) If the live --inspect receipt reads sync_metadata=PRESENT for Default, the Bookmarks-file lane is blocked for that profile: stop, propose no write path, and lay out the choice between turning bookmark sync off for Default and abandoning this lane. (3) If the live --dry-run receipt prints VERDICT: NO_MATRIX, the rebuild did not run between the patches and the compile; name that ignition and stop. Otherwise read the harvest count, the shape verdict, and the chrome state line, and write the next turn's single ignition as a hand-run sequence: close every Chrome window, run bm, reopen Chrome, look at the bar; then the probes that witness a WRITTEN verdict, the ledger's first block, the backup file in the state dir, and Chrome loading the bar as declared. No patches unless a receipt convicts one.
```

### 5. EXTERNAL DELIVERABLES

None this turn outside the repo.

**MikeLev.in**: But I don't want to do this to the default profile. I want to do it to
the profile named "Work" which is:

    /home/mike/.config/google-chrome/Profile\ 2

And the particular file I see there is:

    /home/mike/.config/google-chrome/Profile\ 2/Bookmarks

> Same commands, run twice, one change between them. Where the readings
> differ is what the change did; the diff in the middle is the receipt.

**1: Probe**: (BEFORE: hand-run, nothing changed yet)

```bash
Blast Radius Check to establish bisection Left-hand Causal Boundary. It is a Popper-thing. Science.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ /home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/blogs.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
! ls -l /home/mike/.config/pipulate/bookmarks.json
! grep -c 'bookmarks_sync.py"' /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/configuration.nix
! git -C /home/mike/repos/nixos log --oneline -3
bash: /home/mike/repos/nixos/autognome.py: Permission denied
bash: /home/mike/repos/nixos/configuration.nix: Permission denied
bash: /home/mike/repos/nixos/blogs.nix: Permission denied
bash: /home/mike/repos/nixos/bookmarks.nix: No such file or directory
bash: /home/mike/repos/nixos/scripts/bookmarks_sync.py: No such file or directory
Default {'bookmark_bar': 14, 'other': 14, 'synced': 0} True ['sync_metadata']
Profile 2 {'bookmark_bar': 19, 'other': 3, 'synced': 0} True ['sync_metadata']
python3: can't open file '/home/mike/repos/nixos/scripts/bookmarks_sync.py': [Errno 2] No such file or directory
python3: can't open file '/home/mike/repos/nixos/scripts/bookmarks_sync.py': [Errno 2] No such file or directory
error: path '/home/mike/repos/nixos/bookmarks.nix' does not exist
PARSE_OK
ls: cannot access '/home/mike/.config/pipulate/bookmarks.json': No such file or directory
/home/mike/repos/nixos/autognome.py:0
/home/mike/repos/nixos/configuration.nix:0
5c874f2 (HEAD -> main, origin/main, origin/HEAD) Making blogs.nix support subfolders
22041a5 Making Cowork toggleable
d6c5d5b Excluding lost and found on rsync
(nix) pipulate $ 
```

**2: Context**: (AFTER: the same probes re-run by the compiler as `!` lines)

```text
# adhoc.txt    _   _   _ to set context____ _   _  ___  ____  _   Simpson Couch Gag Here (explain anything to the audience you feel needs it explained)G
#     / \   __| | | | | | ___   ___   / ___| | | |/ _ \|  _ \| |  
# ahe/ _ \ / _` | | |_| |/ _ \ / __| | |   | |_| | | | | |_) | |  Getting bookmarks under control finally
# ahc ___ \ (_| | |  _  | (_) | (__  | |___|  _  | |_| |  __/|_|  
#  /_/   \_\__,_| |_| |_|\___/ \___|  \____|_| |_|\___/|_|   (_)  
# Ad Hoc CHOP: The Not-Managed-by-Git Safe-for-Client-Data place  

# OPTIONAL BUT BIG FOR FULL CONTEXT-WINDOW STORYTELLING
# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- The "Rolling Pin" that gives the 40K foot book-spine view of book-ore.
# GLOSSARY.md                 # <-- Like the back of a J.R.R. Tolkien book but always growing in size as `prompt_foo.py` gets scars and shrinks.
# init.lua                    # <-- Daily driver hot-keys that overlap with aliases in flake.nix. `<leader>m` makes it Science (this process)!

# scripts/articles/lsa.py     # <-- 2nd Brain query-engine for `rgx`, `rgxc` & `posts` Jekyll-inspired Memory Externalization for Hackers.
# ~/repos/nixos/autognome.py  # <-- Letting the AIs really understand my environment (The Brave Little Tailor punches above Their Weight Class proving the dunning-kruger effect the gate-keeper's (lower-case) lament.)
 
# STILL BIG BUT LESS OPTIONAL (especially flake.nix)
# flake.nix                   # <-- THE ONE BIG THING TO INCLUDE Infrastructure as Code (IaC) tells LLM about your system down to the metal
# prompt_foo.py               # <-- This very content-compiling system
# foo_files.py                # <-- This is the router, evolving book outline and the things you pin-up to produced the recursive self-improvement loops

# TINY ILLUMINATING (OK to include every time / automatically = `apply.py`, `.gitignore`, `.gitattributes`)
# requirements.in             # <-- All known dependencies and (necessary) version pinning. WORA gotcha's exposed.
# __init__.py                 # <-- Master versioning
# pyproject.toml              # <-- The PyPI Packaging details

# OPTIONAL ACTUATORS (cheap and good to include to expand the AI's capabilities)
# cli.py                      # <-- Catch-all actuator for PyPI envs, Python anchoring, MCP tool-call (plus alternatives) and **kwargs like wrapping for CLI
# scripts/xp.py               # <-- Transforms host OS copy-paste buffer player-piano music into context-payload.
# scripts/ai.py               # <-- How I constantly use local AI to write git commit messages with `m` alias.
# scripts/crawl.py            # <-- Feel free to ask for something to be crawled and included in the next turn.
# scripts/weblogin.py         # <-- Lets the user "warm up" the cache for their web logins at their leisure on a profile that persists.
 
# MISCELLANEOUS (rare to include but sometimes critical)
# scripts/foo_cartridge.py    # Needs description
# scripts/foo_replay.py       # Needs description
# release.py                  # <-- How everything ends up where it does (GitHub, PyPI, etc.)
# imports/voice_synthesis.py  # <-- The wand can talk to you
# imports/ascii_displays.py   # <-- Where all the ASCII Art lives
# scripts/release/version_sync.py  # <-- Needs to be wrapped into release.py and eliminated, I think.

#                         --- Under this line is were you paste what the AI gives you ---
#                         --- We call it context but it's really just the right-hand  ---
#                         --- blast-radius of the "probes" to make this all science.  ---

# Carry-over as the important work-in-progress parts of the project here just
# like above but not as long-standing overarching to the framework but rather
# for the current hot spots actively being worked on.

# STICKBUG & MOTHER CAT KATA (WORKING ON THE CHAPTER)

# assets/trails/first_context.yaml
# assets/trails/practice.yaml
# assets/trails/public_walk.yaml
# # assets/trails/botify_pageworkers.yaml
# 
# scripts/connectors/README.md
# scripts/connectors/botify.py
# scripts/connectors/confluence.py
# scripts/connectors/gmail.py
# scripts/connectors/gsc.py
# scripts/connectors/jira.py
# scripts/connectors/mcp.py
# scripts/connectors/mcp_warm.py
# scripts/connectors/sheets.py
# scripts/connectors/slack.py
# 
# assets/installer/mck.sh
# assets/installer/replay.sh
# scripts/bookmark_import.py
# scripts/walk.py
# scripts/walk_cartridge.py
# scripts/walk_compile.py
# scripts/weblogin.py
# tools/scraper_tools.py
# 
# scripts/mcp_dummy_server.py  
# scripts/connectors/wallet.py
# scripts/boot_menu.py
# scripts/mother_cat.py
# scripts/sources_menu.py

# --- START THIS DISCUSSION ---

# Context 1 (Edit-in selections from above and add new files immediately below)
# ~/repos/nixos/autognome.py                 #  <-- More rare to have to include, but the true "top" of the muscle memory stack for day-to-day purposes
# ~/repos/nixos/configuration.nix            #  <-- "Global" IaC context (most of you won't have)
# ~/repos/nixos/blogs.nix
# ~/repos/nixos/packages.nix                 #  <-- Full disclosure on pre-flake IaC available apps.
# ~/repos/nixos/services.nix                 #  <-- Running Linux system services.
# ~/repos/nixos/ai-acceleration.nix          #  <-- Paid a lot for your hardware? We've got you covered.

# Context 2
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/blogs.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
! ls -l /home/mike/.config/pipulate/bookmarks.json
! grep -c 'bookmarks_sync.py"' /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/configuration.nix
! git -C /home/mike/repos/nixos log --oneline -3

# --- END `adhoc.txt` TEMPLATE ---
```

**3: Patches**: (the one change between the readings)

First we apply the patches from the Pipulate repo.

```bash
Blast Radius Check to establish bisection Left-hand Causal Boundary. It is a Popper-thing. Science.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ app
✅ WHOLE-FILE WRITE: OVERWROTE '/home/mike/repos/nixos/bookmarks.nix'.
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/configuration.nix'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/configuration.nix'.
(nix) pipulate $ patch
(nix) pipulate $ app
✅ WHOLE-FILE WRITE: CREATED '/home/mike/repos/nixos/scripts/bookmarks_sync.py'.
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
(nix) pipulate $
```

But then we go over to this other repo to see what I just did:

```diff
(sys) nixos $ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   en.utf-8.add
	modified:   en.utf-8.add.spl

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	bookmarks.nix

no changes added to commit (use "git add" and/or "git commit -a")
(sys) nixos $ git add bookmarks.nix
(sys) nixos $ git add bookmarks_sync.py
fatal: pathspec 'bookmarks_sync.py' did not match any files
(sys) nixos $ git add scripts/bookmarks_sync.py
(sys) nixos $ git --no-pager diff
diff --git a/autognome.py b/autognome.py
index 6624f1f..eabe6d1 100644
--- a/autognome.py
+++ b/autognome.py
@@ -763,6 +763,26 @@ if __name__ == "__main__":
         print("Warning: wmctrl not available, cannot switch desktops")
         exit(1)
 
+    # 🔖 THE BOOKMARK PROJECTION (bookmarks.nix -> bookmarks.json -> Chrome).
+    # Runs BEFORE any Chrome launch below, the one moment Chrome is provably
+    # dead, so the profile's Bookmarks file can be rewritten without the
+    # browser clobbering it or being clobbered. The script harvests every
+    # URL the matrix does not declare into ~/repos/nixos/bookmarks_harvest.md,
+    # backs the old file up, then writes the projection. It REFUSES (exit 2)
+    # on a running Chrome or a profile Chrome Sync owns, and init carries on
+    # either way. FALL-THROUGH GUARANTEE: an absent script leaves init exactly
+    # as it was; the `bm` alias runs the same script by hand.
+    bookmarks_sync = "/home/mike/repos/nixos/scripts/bookmarks_sync.py"
+    if os.path.isfile(bookmarks_sync):
+        print("\n=== Projecting the bookmark matrix into Chrome ===")
+        try:
+            rc = subprocess.run([sys.executable, bookmarks_sync], check=False).returncode
+            print(f"  bookmark sync exit {rc}  (0 written or in sync, 1 no matrix, 2 refused)")
+        except Exception as e:
+            print(f"  ⚠ bookmark sync skipped ({type(e).__name__}): {e}")
+    else:
+        print(f"  ⚠ bookmark sync skipped: {bookmarks_sync} not found")
+
     num_desktops_needed = len(DESKTOP_SETUP)
     current_desktops = get_desktop_count()
 
diff --git a/configuration.nix b/configuration.nix
index ebf3855..05857af 100644
--- a/configuration.nix
+++ b/configuration.nix
@@ -89,6 +89,7 @@ in
       ./services.nix
       ./ai-acceleration.nix
       ./blogs.nix
+      ./bookmarks.nix
       # ./openclaw.nix
     ]
     # Conditional import: Only import secrets.nix if it exists
@@ -273,6 +274,10 @@ in
     # figurate engine resolves for the boot-time visual canary. Falls back to system
     # python3 if the .venv has not been built yet, so `init` never hard-fails.
     init = "if [ -x /home/mike/repos/pipulate/.venv/bin/python3 ]; then /home/mike/repos/pipulate/.venv/bin/python3 /home/mike/repos/nixos/autognome.py; else python3 /home/mike/repos/nixos/autognome.py; fi";
+    # 🔖 Project bookmarks.nix into Chrome by hand. A real run refuses while
+    # Chrome is open; `bm --dry-run` and `bm --inspect` are safe any time.
+    # `init` runs the same script before its first Chrome launch.
+    bm = "python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py";
     open = "xdg-open .";
     vim = "nvim";
 
diff --git a/en.utf-8.add b/en.utf-8.add
index 15cef9c..608a625 100644
--- a/en.utf-8.add
+++ b/en.utf-8.add
@@ -453,7 +453,7 @@ txt
 EBR
 SFCC
 JIMINY
-Atlasian
+#tlasian
 Conveyer
 RLHF
 sitemaps
@@ -564,3 +564,6 @@ wogglebugisms
 ballbearings
 Gleick's
 infantilize
+SGI
+accreting
+Atlassian
diff --git a/en.utf-8.add.spl b/en.utf-8.add.spl
index 27bea63..6ab9a84 100644
Binary files a/en.utf-8.add.spl and b/en.utf-8.add.spl differ
(sys) nixos $ git commit -am "Adding control of work bookmarks from Nix"
[main 6491b2a] Adding control of work bookmarks from Nix
 6 files changed, 625 insertions(+), 1 deletion(-)
 create mode 100644 bookmarks.nix
 create mode 100644 scripts/bookmarks_sync.py
(sys) nixos $ git push
Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 48 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 13.76 KiB | 6.88 MiB/s, done.
Total 9 (delta 5), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (5/5), completed with 5 local objects.
To github.com:miklevin/nixos-config.git
   5c874f2..6491b2a  main -> main
(sys) nixos $ 
```

Interesting! Most we can't really see but it was visible in the file-creation
patches above. Okay, so this requires ignition which materializes the bookmark
json file. I don't see it in the obvious place:

```bash
(sys) nixos $ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(sys) nixos $ ls -la
total 85808
drwxr-xr-x  5 mike users     4096 Sep  8 13:55 .
drwxr-xr-x 27 mike users     4096 Sep  3 09:16 ..
-rw-r--r--  1 mike users     2069 Feb 16  2026 ai-acceleration.nix
-rw-r--r--  1 mike users    39171 Sep  8 13:58 autognome.py
-rw-r--r--  1 mike users     5738 Sep  4 13:51 blogs.nix
-rw-r--r--  1 mike users     3601 Sep  8 13:56 bookmarks.nix
-rw-r--r--  1 mike users    12486 Sep  8 13:57 configuration.nix
-rw-r--r--  1 root root     57851 Nov 17  2025 configuration.nix.old_big_pile
drwxr-xr-x  2 mike users     4096 Jul  7 13:43 credentials
-rw-r--r--  1 mike users     5182 Sep  8 09:24 en.utf-8.add
-rw-r--r--  1 mike users     6730 Sep  8 09:24 en.utf-8.add.spl
-rw-r--r--  1 mike users     2497 Feb 14  2026 flatnotes.nix
drwxr-xr-x  8 mike users     4096 Sep  8 14:02 .git
-rw-r--r--  1 mike users       64 Jul  5 15:10 .gitignore
-rw-r--r--  1 mike users     1438 Nov 18  2025 hardware-configuration.nix
-rw-r--r--  1 root root      1423 Nov 17  2025 hardware-configuration.nix.old
-rw-r--r--  1 mike users 21909869 Nov  6  2025 json
-rw-r--r--  1 mike users     1573 Feb 18  2026 openclaw.nix
-rw-r--r--  1 mike users     5163 Sep  3 18:13 packages.nix
drwxr-xr-x  2 mike users     4096 Sep  8 13:57 scripts
-rw-r--r--  1 mike users        3 Jul  5 15:10 secrets.json
-rw-r--r--  1 mike users     5676 Jul 29 06:32 services.nix
-rw-r--r--  1 mike users 21909875 Nov  6  2025 subprocess
-rw-r--r--  1 mike users 21909868 Nov  6  2025 sys
-rw-r--r--  1 mike users 21909869 Nov  6  2025 time
(sys) nixos $ n
building Nix...
building the system configuration...
unpacking 'https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz' into the Git cache...
these 3 derivations will be built:
  /nix/store/znahr2q2l5dxhs8q7bh6q2bng1yl5wan-etc-bashrc.drv
  /nix/store/zskp699s6qk2ib2xg7jajvgn3hiqmlnj-etc.drv
  /nix/store/q5v1wdvpq6z57qvzjrlf0x1fpbdncrsb-nixos-system-nixos-25.05.813814.ac62194c3917.drv
building '/nix/store/znahr2q2l5dxhs8q7bh6q2bng1yl5wan-etc-bashrc.drv'...
building '/nix/store/zskp699s6qk2ib2xg7jajvgn3hiqmlnj-etc.drv'...
building '/nix/store/q5v1wdvpq6z57qvzjrlf0x1fpbdncrsb-nixos-system-nixos-25.05.813814.ac62194c3917.drv'...
activating the configuration...
setting up /etc...
reloading user units for mike...
restarting sysinit-reactivation.target
the following new units were started: NetworkManager-dispatcher.service, sysinit-reactivation.target, systemd-tmpfiles-resetup.service
Done. The new configuration is /nix/store/p9q95vwv4fsqzlnv8cbv2gl0cglvvaws-nixos-system-nixos-25.05.813814.ac62194c3917
(sys) nixos $ ls -la
total 85808
drwxr-xr-x  5 mike users     4096 Sep  8 13:55 .
drwxr-xr-x 27 mike users     4096 Sep  3 09:16 ..
-rw-r--r--  1 mike users     2069 Feb 16  2026 ai-acceleration.nix
-rw-r--r--  1 mike users    39171 Sep  8 13:58 autognome.py
-rw-r--r--  1 mike users     5738 Sep  4 13:51 blogs.nix
-rw-r--r--  1 mike users     3601 Sep  8 13:56 bookmarks.nix
-rw-r--r--  1 mike users    12486 Sep  8 13:57 configuration.nix
-rw-r--r--  1 root root     57851 Nov 17  2025 configuration.nix.old_big_pile
drwxr-xr-x  2 mike users     4096 Jul  7 13:43 credentials
-rw-r--r--  1 mike users     5182 Sep  8 09:24 en.utf-8.add
-rw-r--r--  1 mike users     6730 Sep  8 09:24 en.utf-8.add.spl
-rw-r--r--  1 mike users     2497 Feb 14  2026 flatnotes.nix
drwxr-xr-x  8 mike users     4096 Sep  8 14:02 .git
-rw-r--r--  1 mike users       64 Jul  5 15:10 .gitignore
-rw-r--r--  1 mike users     1438 Nov 18  2025 hardware-configuration.nix
-rw-r--r--  1 root root      1423 Nov 17  2025 hardware-configuration.nix.old
-rw-r--r--  1 mike users 21909869 Nov  6  2025 json
-rw-r--r--  1 mike users     1573 Feb 18  2026 openclaw.nix
-rw-r--r--  1 mike users     5163 Sep  3 18:13 packages.nix
drwxr-xr-x  2 mike users     4096 Sep  8 13:57 scripts
-rw-r--r--  1 mike users        3 Jul  5 15:10 secrets.json
-rw-r--r--  1 mike users     5676 Jul 29 06:32 services.nix
-rw-r--r--  1 mike users 21909875 Nov  6  2025 subprocess
-rw-r--r--  1 mike users 21909868 Nov  6  2025 sys
-rw-r--r--  1 mike users 21909869 Nov  6  2025 time
(sys) nixos $ vim bookmarks.nix 
(sys) nixos $
```

But there's an actual dot PY file to run, so let's try that.

```bash
(sys) nixos $ cd scripts/
(sys) scripts $ ls
backup-essential.py  backup-force.py  backup-home.py  backup-nix.sh  backup-things.py  bookmarks_sync.py
(sys) scripts $ python bookmarks_sync.py 
chrome: RUNNING (SingletonLock -> nixos-5139, pid alive)  user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Default: 125 url bookmarks in the browser, 10 declared, 115 to harvest; shape DIFFERS; codec VERIFIED
  REFUSED: /home/mike/.config/google-chrome/Default/Bookmarks carries sync_metadata -- Chrome Sync owns this profile's bookmarks; a local wipe would be reverted from the account and the matrix uploaded to it. Turn bookmark sync off for this profile, or leave it undeclared.
VERDICT: Default=REFUSED_SYNC_METADATA
(sys) scripts $ python bookmarks_sync.py 
chrome: RUNNING (SingletonLock -> nixos-51758, pid alive)  user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Default: 125 url bookmarks in the browser, 10 declared, 115 to harvest; shape DIFFERS; codec VERIFIED
  REFUSED: /home/mike/.config/google-chrome/Default/Bookmarks carries sync_metadata -- Chrome Sync owns this profile's bookmarks; a local wipe would be reverted from the account and the matrix uploaded to it. Turn bookmark sync off for this profile, or leave it undeclared.
VERDICT: Default=REFUSED_SYNC_METADATA
(sys) scripts $ 
```

The reason I show it twice is that I turned off the "Sync and personalize Chrome
across all your devices" feature (and even quit out of all my Chrome's) to test
it, still no luck. If this is a deep rabbithole we will back off and find
another way.

**4: Prompt**: Read the AFTER receipts and rule from the LIVE RECEIPTS, never from the pasted BEFORE. (1) If the live --inspect receipt reads checksum -> MATCH for both Default and Profile 2, the codec claim is witnessed on two files Chrome wrote and the writer may keep including a checksum; if either reads MISMATCH, say which and confirm that the omit path is what a real run will take. (2) If the live --inspect receipt reads sync_metadata=PRESENT for Default, the Bookmarks-file lane is blocked for that profile: stop, propose no write path, and lay out the choice between turning bookmark sync off for Default and abandoning this lane. (3) If the live --dry-run receipt prints VERDICT: NO_MATRIX, the rebuild did not run between the patches and the compile; name that ignition and stop. Otherwise read the harvest count, the shape verdict, and the chrome state line, and write the next turn's single ignition as a hand-run sequence: close every Chrome window, run bm, reopen Chrome, look at the bar; then the probes that witness a WRITTEN verdict, the ledger's first block, the backup file in the state dir, and Chrome loading the bar as declared. No patches unless a receipt convicts one.

**5: Deliverables**: None yet.

**Fable 5.1 Extra**: Context verified.

**Reading the AFTER receipts, against the caboose's three conditions.** (1) `checksum -> MATCH` on both profiles, and the second file is the hard one: 679 urls in 81 folders. The codec is witnessed on two files Chrome wrote; the writer keeps its checksum. (2) Default reads `sync_metadata=PRESENT (44,068 chars)`, so the Bookmarks-file lane is blocked there — and you have dropped Default, not un-synced it, so the choice that condition asked me to lay out is moot. (3) The dry-run did not read `NO_MATRIX`: `bookmarks.json` is on disk at 584 B, the matrix is `present`, and the verdict reads `Default=DRY_RUN`, so `n` ran.

**The finding you did not get to see.** The one-liner I labeled a CENSUS moved between your hand tap and the compile, and it moved on Profile 2, not Default: `True ['sync_metadata']` became `False []`. The `--inspect` receipt says the same thing three ways: Profile 2's file went from 601,004 to 356,308 bytes, that 244,696-byte drop is 360 bytes per bookmark across 679 bookmarks, and Default's own record reads 44,068 chars over 125 bookmarks, which is 353 per bookmark — two independent numbers agreeing that what left the file was the sync record. Whichever profile's switch you flipped, the file that changed is Profile 2's, and Profile 2 is the profile you want. Your two "no luck" runs were real runs, and they read `REFUSED_SYNC_METADATA` for **Default**, because Default was the only declared profile; the script never opened Profile 2. A correct verdict about a different profile. The pid change between those two runs (5139 to 51758) corroborates the quit. Not a rabbit hole: the door you want is already open, and this compile is the receipt.

**What changes.** The matrix retargets to `"Profile 2"`, keeping Default's ten as a placeholder so the first projection is not an empty bar; `--inspect` gains `--bar N` so you can read the Work bar's 19 top-level entries and author the matrix before the first sync instead of after it; and the harvest ledger gets gitignored, because your push receipt names `github.com:miklevin/nixos-config` and the ledger is about to hold the Work profile's 679 URLs verbatim. Same rule for the matrix itself: nothing with a client slug goes into `bookmarks.nix`, since that file ships on every push. Default is no longer declared and is never opened.

---

### 1. PROBES

```bash
python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect --bar 2 Default
LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
ls -l /home/mike/.config/pipulate/bookmarks.json
git -C /home/mike/repos/nixos check-ignore -q bookmarks_harvest.md && echo IGNORED
git -C /home/mike/repos/nixos log --oneline -3
```

Probe 1 is now a stability check on Profile 2: if the second tap still reads `False []`, sync stayed off across a running Chrome; if it flips back to `True`, something re-enabled it and the lane closes again. Probe 2 corroborates that with the byte count and the `sync_metadata` line, and re-checks both checksums. Probe 3 is the Car 1 straddle: after `n` it reads `Profile 2: 679 url bookmarks in the browser, 10 declared, N to harvest` where N is 679 minus whatever overlap the Work bar has with the ten starters, `a real run right now would stop at REFUSED_CHROME_RUNNING` because Chrome is up at compile time, and `VERDICT: Profile_2=DRY_RUN`; if it still reads `Default=`, `n` did not run. Probe 4 is the Car 2 straddle: an argparse refusal BEFORE, two entries per root AFTER, aimed at Default so no Work URL enters this payload. Probe 5 gates the Nix edit. Probe 6 witnesses `n` by displacement: 584 B becomes 586 B, the two extra characters of the longer profile key. Probe 7 is the property probe for Car 3, silent nonzero BEFORE and `IGNORED` AFTER. Probe 8 reads the new commit on top.

### 2. NEXT CONTEXT

```text
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/blogs.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect --bar 2 Default
! LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
! ls -l /home/mike/.config/pipulate/bookmarks.json
! git -C /home/mike/repos/nixos check-ignore -q bookmarks_harvest.md && echo IGNORED
! git -C /home/mike/repos/nixos log --oneline -3
```

Keep `--preview 0` and keep `--bar` pointed at Default in every `!` line: the Work bar is read by hand, never into a payload.

### 3. PATCHES

**Car 1 — retarget the matrix.** Two blocks, one file; both anchors are contiguous comment runs with no empty lines.

```text
Target: /home/mike/repos/nixos/bookmarks.nix
[[[SEARCH]]]
# ONE PROFILE ON PURPOSE. "Profile 2" is the Workspace-managed profile; it is
# not declared, so the sync never opens its file. Declaring it is one attribute,
# but read the refusal first: a profile whose Bookmarks carry `sync_metadata` is
# owned by Chrome Sync, the script refuses it, and a harvest of a work profile
# would put work URLs into a ledger that lives in this repository.
[[[DIVIDER]]]
# ONE PROFILE ON PURPOSE, AND IT IS THE WORK ONE (retargeted 2026-09-08).
# "Profile 2" is the Workspace profile Chrome labels "Work". At its 14:11 write
# that day its Bookmarks file carried no `sync_metadata` (601 KB -> 356 KB,
# the sync record for 679 bookmarks leaving), so Chrome Sync no longer owns it
# and a local wipe sticks. "Default" is NOT declared: its file still carries
# `sync_metadata`, the script refuses any profile Sync owns, and declaring it
# would only print REFUSED on every init.
#
# THIS REPOSITORY IS PUBLIC. bookmarks_harvest.md is gitignored because it will
# hold the Work bar verbatim; nothing promoted into THIS file may carry a client
# name or slug, because this file ships to GitHub on every push.
[[[REPLACE]]]

Target: /home/mike/repos/nixos/bookmarks.nix
[[[SEARCH]]]
    "Default" = {
      bookmark_bar = [
        # Seeded 2026-09-08 from the live bar's first ten URL entries (probe
        # receipt). The rest of the bar and all of "Other bookmarks" arrive in
        # bookmarks_harvest.md on the first real sync, to be promoted here.
[[[DIVIDER]]]
    "Profile 2" = {
      bookmark_bar = [
        # STARTER, NOT THE WORK BAR: these ten are Default's first ten, carried
        # over as a placeholder so the first projection is not an empty bar.
        # The Work bar's own entries (676 urls in 81 folders at retarget time)
        # land in bookmarks_harvest.md on the first real sync, paste-ready.
        # To author before that sync: bm --inspect --bar 25 "Profile 2"
[[[REPLACE]]]
```

**Car 2 — the `--bar` listing.** Four single-anchor blocks in one file.

```text
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
def inspect_profile(profile, user_data_dir):
[[[DIVIDER]]]
def inspect_profile(profile, user_data_dir, bar=0):
[[[REPLACE]]]

Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
    print("             Preferences: account_info=%s  sync.has_setup_completed=%s keep_everything_synced=%s bookmarks=%s" % (
        accounts, sync_prefs.get("has_setup_completed", "-"),
        sync_prefs.get("keep_everything_synced", "-"), sync_prefs.get("bookmarks", "-")))
[[[DIVIDER]]]
    print("             Preferences: account_info=%s  sync.has_setup_completed=%s keep_everything_synced=%s bookmarks=%s" % (
        accounts, sync_prefs.get("has_setup_completed", "-"),
        sync_prefs.get("keep_everything_synced", "-"), sync_prefs.get("bookmarks", "-")))
    # THE AUTHORING VIEW (--bar N): the first N top-level entries of each
    # declarable root, so a matrix can be written from what the bar actually
    # shows rather than from a pre-order harvest that dives into the first
    # folder it meets. Operator-lane only for a work profile: a `!` line that
    # carries --bar puts those names and URLs into a payload.
    if bar > 0:
        for key in MATRIX_ROOTS:
            root = roots.get(key)
            children = [c for c in ((root.get("children") if isinstance(root, dict) else None) or []) if isinstance(c, dict)]
            print("             %s top level: %d entries%s" % (key, len(children), "" if len(children) <= bar else ", first %d" % bar))
            for child in children[:bar]:
                if child.get("type") == "folder":
                    print("               [folder] %s  (%d urls)" % (child.get("name", ""), sum(1 for _ in walk_urls(child, []))))
                else:
                    print("               [url]    %s -> %s" % (child.get("name", ""), clip(child.get("url", ""), 100)))
[[[REPLACE]]]

Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
    parser.add_argument("--preview", type=int, default=PREVIEW_LINES, help="harvest lines to print under --dry-run (0 = counts only)")
[[[DIVIDER]]]
    parser.add_argument("--preview", type=int, default=PREVIEW_LINES, help="harvest lines to print under --dry-run (0 = counts only)")
    parser.add_argument("--bar", type=int, default=0, help="with --inspect, list the first N top-level entries of bookmark_bar and other (0 = off)")
[[[REPLACE]]]

Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
            inspect_profile(profile, user_data_dir)
[[[DIVIDER]]]
            inspect_profile(profile, user_data_dir, max(0, args.bar))
[[[REPLACE]]]
```

**Car 3 — keep the ledger out of the public repo.** The nixos `.gitignore` is not in this context, so this rides as an idempotent append with a seam guard rather than a SEARCH block; hand-run it once.

```bash
cd /home/mike/repos/nixos && [ -n "$(tail -c1 .gitignore)" ] && echo >> .gitignore; grep -qxF bookmarks_harvest.md .gitignore || echo bookmarks_harvest.md >> .gitignore; cd - >/dev/null
```

**Ignition.** Car 1 ignites with `n` from a `(sys)` shell, which re-materializes `bookmarks.json` under the new key; run it after all three cars and before the compile, or Probe 3 still reads `Default=`. Cars 2 and 3 need none: the probes load the script by path and `git check-ignore` reads the file at call time. The `init` hook stays armed and now points at the Work profile, so a reboot before the next receipt is read projects the ten starters over the Work bar; the ledger (gitignored) and the state-dir backup make that recoverable. Commit by name: `cd ~/repos/nixos && git add .gitignore && git commit -am "Retarget the bookmark matrix at the Work profile; --bar authoring view; ignore the harvest ledger" && git push`.

### 4. PROMPT

```text
Rule from the LIVE RECEIPTS. (1) If the live census and --inspect receipts read sync_metadata ABSENT for Profile 2 on this second tap, sync stayed off across a running Chrome and the lane is stable; if either reads PRESENT again, something re-enabled sync on the Work profile, say so, and stop. (2) If the live --dry-run receipt reads VERDICT: Profile_2=DRY_RUN, quote the harvest count and confirm it would stop at REFUSED_CHROME_RUNNING; if it still reads Default=, name the missing n ignition and stop. (3) Confirm check-ignore reads IGNORED and the --bar 2 receipt lists entries under Default; if either is missing, that car did not land. Then write the next turn as one hand-run sequence with no patches: optionally bm --inspect --bar 25 "Profile 2" to author the matrix and n to materialize it; close every Chrome window (if bm still reads RUNNING, Chrome is lingering in background mode and the receipt names the pid); bm; reopen the Work profile and look at the bar. Then the probes that witness VERDICT: Profile_2=WRITTEN, the ledger's first block by line count only, the backup file in ~/.local/state/bookmarks_sync/Profile_2/, and a second bm reading IN_SYNC once Chrome has rewritten the file. No patches unless a receipt convicts one.
```

### 5. EXTERNAL DELIVERABLES

None this turn outside the repo.

**MikeLev.in**: 

> Same commands, run twice, one change between them. Where the readings
> differ is what the change did; the diff in the middle is the receipt.

**1: Probe**: (BEFORE: hand-run, nothing changed yet)

```bash
(nix) pipulate $ g

Blast Radius Check to establish bisection Left-hand Causal Boundary. It is a Popper-thing. Science.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect --bar 2 Default
LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
ls -l /home/mike/.config/pipulate/bookmarks.json
git -C /home/mike/repos/nixos check-ignore -q bookmarks_harvest.md && echo IGNORED
git -C /home/mike/repos/nixos log --oneline -3
Default {'bookmark_bar': 14, 'other': 14, 'synced': 0} True ['sync_metadata']
Profile 2 {'bookmark_bar': 19, 'other': 3, 'synced': 0} False []
chrome: RUNNING (SingletonLock -> nixos-51758, pid alive)  user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Default      97,427 B  mtime 2026-09-08 14:15  version=1  siblings=['Bookmarks', 'Bookmarks.bak']
             urls=125 folders=9  bookmark_bar=111  other=14  synced=0
             sync_metadata=PRESENT (44,068 chars)  extra_keys=['sync_metadata']
             checksum stored=c776444b816f computed=c776444b816f -> MATCH
             Preferences: account_info=5  sync.has_setup_completed=True keep_everything_synced=- bookmarks=-
Profile 2    356,308 B  mtime 2026-09-08 14:11  version=1  siblings=['Bookmarks', 'Bookmarks.bak']
             urls=679 folders=81  bookmark_bar=676  other=3  synced=0
             sync_metadata=ABSENT  extra_keys=-
             checksum stored=6c1375797ab7 computed=6c1375797ab7 -> MATCH
             Preferences: account_info=1  sync.has_setup_completed=True keep_everything_synced=- bookmarks=-
VERDICT: INSPECT
chrome: RUNNING (SingletonLock -> nixos-51758, pid alive)  user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Default: 125 url bookmarks in the browser, 10 declared, 115 to harvest; shape DIFFERS; codec VERIFIED
  would append 115 line(s) to /home/mike/repos/nixos/bookmarks_harvest.md
  would back up /home/mike/.config/google-chrome/Default/Bookmarks to /home/mike/.local/state/bookmarks_sync/Default/ and write the projection
  a real run right now would stop at REFUSED_SYNC_METADATA
VERDICT: Default=DRY_RUN
usage: bookmarks_sync.py [-h] [--dry-run] [--inspect] [--preview PREVIEW] [--matrix MATRIX] [--user-data-dir USER_DATA_DIR]
                         [--harvest HARVEST]
                         [profiles ...]
bookmarks_sync.py: error: unrecognized arguments: --bar
PARSE_OK
-rw-r--r-- 1 mike users 584 Sep  8 14:04 /home/mike/.config/pipulate/bookmarks.json
6491b2a (HEAD -> main, origin/main, origin/HEAD) Adding control of work bookmarks from Nix
5c874f2 Making blogs.nix support subfolders
22041a5 Making Cowork toggleable
(nix) pipulate $ 
```

**2: Context**: (AFTER: the same probes re-run by the compiler as `!` lines)

```text
# adhoc.txt    _   _   _ to set context____ _   _  ___  ____  _   Simpson Couch Gag Here (explain anything to the audience you feel needs it explained)G
#     / \   __| | | | | | ___   ___   / ___| | | |/ _ \|  _ \| |  
# ahe/ _ \ / _` | | |_| |/ _ \ / __| | |   | |_| | | | | |_) | |  Getting bookmarks under control finally
# ahc ___ \ (_| | |  _  | (_) | (__  | |___|  _  | |_| |  __/|_|  I hope my preferred method works here
#  /_/   \_\__,_| |_| |_|\___/ \___|  \____|_| |_|\___/|_|   (_)  
# Ad Hoc CHOP: The Not-Managed-by-Git Safe-for-Client-Data place  

# OPTIONAL BUT BIG FOR FULL CONTEXT-WINDOW STORYTELLING
# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- The "Rolling Pin" that gives the 40K foot book-spine view of book-ore.
# GLOSSARY.md                 # <-- Like the back of a J.R.R. Tolkien book but always growing in size as `prompt_foo.py` gets scars and shrinks.
# init.lua                    # <-- Daily driver hot-keys that overlap with aliases in flake.nix. `<leader>m` makes it Science (this process)!

# scripts/articles/lsa.py     # <-- 2nd Brain query-engine for `rgx`, `rgxc` & `posts` Jekyll-inspired Memory Externalization for Hackers.
# ~/repos/nixos/autognome.py  # <-- Letting the AIs really understand my environment (The Brave Little Tailor punches above Their Weight Class proving the dunning-kruger effect the gate-keeper's (lower-case) lament.)
 
# STILL BIG BUT LESS OPTIONAL (especially flake.nix)
# flake.nix                   # <-- THE ONE BIG THING TO INCLUDE Infrastructure as Code (IaC) tells LLM about your system down to the metal
# prompt_foo.py               # <-- This very content-compiling system
# foo_files.py                # <-- This is the router, evolving book outline and the things you pin-up to produced the recursive self-improvement loops

# TINY ILLUMINATING (OK to include every time / automatically = `apply.py`, `.gitignore`, `.gitattributes`)
# requirements.in             # <-- All known dependencies and (necessary) version pinning. WORA gotcha's exposed.
# __init__.py                 # <-- Master versioning
# pyproject.toml              # <-- The PyPI Packaging details

# OPTIONAL ACTUATORS (cheap and good to include to expand the AI's capabilities)
# cli.py                      # <-- Catch-all actuator for PyPI envs, Python anchoring, MCP tool-call (plus alternatives) and **kwargs like wrapping for CLI
# scripts/xp.py               # <-- Transforms host OS copy-paste buffer player-piano music into context-payload.
# scripts/ai.py               # <-- How I constantly use local AI to write git commit messages with `m` alias.
# scripts/crawl.py            # <-- Feel free to ask for something to be crawled and included in the next turn.
# scripts/weblogin.py         # <-- Lets the user "warm up" the cache for their web logins at their leisure on a profile that persists.
 
# MISCELLANEOUS (rare to include but sometimes critical)
# scripts/foo_cartridge.py    # Needs description
# scripts/foo_replay.py       # Needs description
# release.py                  # <-- How everything ends up where it does (GitHub, PyPI, etc.)
# imports/voice_synthesis.py  # <-- The wand can talk to you
# imports/ascii_displays.py   # <-- Where all the ASCII Art lives
# scripts/release/version_sync.py  # <-- Needs to be wrapped into release.py and eliminated, I think.

#                         --- Under this line is were you paste what the AI gives you ---
#                         --- We call it context but it's really just the right-hand  ---
#                         --- blast-radius of the "probes" to make this all science.  ---

# Carry-over as the important work-in-progress parts of the project here just
# like above but not as long-standing overarching to the framework but rather
# for the current hot spots actively being worked on.

# STICKBUG & MOTHER CAT KATA (WORKING ON THE CHAPTER)

# assets/trails/first_context.yaml
# assets/trails/practice.yaml
# assets/trails/public_walk.yaml
# # assets/trails/botify_pageworkers.yaml
# 
# scripts/connectors/README.md
# scripts/connectors/botify.py
# scripts/connectors/confluence.py
# scripts/connectors/gmail.py
# scripts/connectors/gsc.py
# scripts/connectors/jira.py
# scripts/connectors/mcp.py
# scripts/connectors/mcp_warm.py
# scripts/connectors/sheets.py
# scripts/connectors/slack.py
# 
# assets/installer/mck.sh
# assets/installer/replay.sh
# scripts/bookmark_import.py
# scripts/walk.py
# scripts/walk_cartridge.py
# scripts/walk_compile.py
# scripts/weblogin.py
# tools/scraper_tools.py
# 
# scripts/mcp_dummy_server.py  
# scripts/connectors/wallet.py
# scripts/boot_menu.py
# scripts/mother_cat.py
# scripts/sources_menu.py

# --- START THIS DISCUSSION ---

# Context 1 (Edit-in selections from above and add new files immediately below)
# ~/repos/nixos/autognome.py                 #  <-- More rare to have to include, but the true "top" of the muscle memory stack for day-to-day purposes
# ~/repos/nixos/configuration.nix            #  <-- "Global" IaC context (most of you won't have)
# ~/repos/nixos/blogs.nix
# ~/repos/nixos/packages.nix                 #  <-- Full disclosure on pre-flake IaC available apps.
# ~/repos/nixos/services.nix                 #  <-- Running Linux system services.
# ~/repos/nixos/ai-acceleration.nix          #  <-- Paid a lot for your hardware? We've got you covered.

# Context 2
# /home/mike/repos/nixos/autognome.py
# /home/mike/repos/nixos/configuration.nix
# /home/mike/repos/nixos/blogs.nix
# /home/mike/repos/nixos/bookmarks.nix
# /home/mike/repos/nixos/scripts/bookmarks_sync.py
# ! python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
# ! LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
# ! ls -l /home/mike/.config/pipulate/bookmarks.json
# ! grep -c 'bookmarks_sync.py"' /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/configuration.nix
# ! git -C /home/mike/repos/nixos log --oneline -3

# Context 3
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/blogs.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect --bar 2 Default
! LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
! ls -l /home/mike/.config/pipulate/bookmarks.json
! git -C /home/mike/repos/nixos check-ignore -q bookmarks_harvest.md && echo IGNORED
! git -C /home/mike/repos/nixos log --oneline -3

# --- END `adhoc.txt` TEMPLATE ---
```

**3: Patches**: (the one change between the readings)

```bash
Blast Radius Check to establish bisection Left-hand Causal Boundary. It is a Popper-thing. Science.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/bookmarks.nix'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/bookmarks.nix'.
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/scripts/bookmarks_sync.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/scripts/bookmarks_sync.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/scripts/bookmarks_sync.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/scripts/bookmarks_sync.py'.
(nix) pipulate $ 
```

Okay and in the other repo.

```diff
(sys) scripts $ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(sys) scripts $ git --no-pager diff
diff --git a/bookmarks.nix b/bookmarks.nix
index 7075ac6..0b0e108 100644
--- a/bookmarks.nix
+++ b/bookmarks.nix
@@ -15,11 +15,17 @@
 # So the browser is a PROJECTION: anything added in Chrome survives exactly one
 # `init`, then lives in the harvest ledger until it is promoted here or let go.
 #
-# ONE PROFILE ON PURPOSE. "Profile 2" is the Workspace-managed profile; it is
-# not declared, so the sync never opens its file. Declaring it is one attribute,
-# but read the refusal first: a profile whose Bookmarks carry `sync_metadata` is
-# owned by Chrome Sync, the script refuses it, and a harvest of a work profile
-# would put work URLs into a ledger that lives in this repository.
+# ONE PROFILE ON PURPOSE, AND IT IS THE WORK ONE (retargeted 2026-09-08).
+# "Profile 2" is the Workspace profile Chrome labels "Work". At its 14:11 write
+# that day its Bookmarks file carried no `sync_metadata` (601 KB -> 356 KB,
+# the sync record for 679 bookmarks leaving), so Chrome Sync no longer owns it
+# and a local wipe sticks. "Default" is NOT declared: its file still carries
+# `sync_metadata`, the script refuses any profile Sync owns, and declaring it
+# would only print REFUSED on every init.
+#
+# THIS REPOSITORY IS PUBLIC. bookmarks_harvest.md is gitignored because it will
+# hold the Work bar verbatim; nothing promoted into THIS file may carry a client
+# name or slug, because this file ships to GitHub on every push.
 #
 # ENTRY GRAMMAR: { name = "..."; url = "..."; } is a bookmark;
 # { name = "..."; children = [ ... ]; } is a folder. An empty name is legal and
@@ -27,11 +33,13 @@
 # bar order. The script validates every entry before it touches anything.
 let
   bookmarks = {
-    "Default" = {
+    "Profile 2" = {
       bookmark_bar = [
-        # Seeded 2026-09-08 from the live bar's first ten URL entries (probe
-        # receipt). The rest of the bar and all of "Other bookmarks" arrive in
-        # bookmarks_harvest.md on the first real sync, to be promoted here.
+        # STARTER, NOT THE WORK BAR: these ten are Default's first ten, carried
+        # over as a placeholder so the first projection is not an empty bar.
+        # The Work bar's own entries (676 urls in 81 folders at retarget time)
+        # land in bookmarks_harvest.md on the first real sync, paste-ready.
+        # To author before that sync: bm --inspect --bar 25 "Profile 2"
         { name = ""; url = "https://mail.google.com/mail/u/0/#inbox"; }
         { name = ""; url = "https://calendar.google.com/calendar/u/0/r"; }
         { name = "Simplenote"; url = "https://simplenote.com/"; }
diff --git a/scripts/bookmarks_sync.py b/scripts/bookmarks_sync.py
index b44c392..2d10c1d 100644
--- a/scripts/bookmarks_sync.py
+++ b/scripts/bookmarks_sync.py
@@ -364,7 +364,7 @@ def clip(text, width=160):
 
 # --- the two modes ----------------------------------------------------------
 
-def inspect_profile(profile, user_data_dir):
+def inspect_profile(profile, user_data_dir, bar=0):
     pdir = user_data_dir / profile
     bpath = pdir / "Bookmarks"
     if not bpath.exists():
@@ -411,6 +411,21 @@ def inspect_profile(profile, user_data_dir):
     print("             Preferences: account_info=%s  sync.has_setup_completed=%s keep_everything_synced=%s bookmarks=%s" % (
         accounts, sync_prefs.get("has_setup_completed", "-"),
         sync_prefs.get("keep_everything_synced", "-"), sync_prefs.get("bookmarks", "-")))
+    # THE AUTHORING VIEW (--bar N): the first N top-level entries of each
+    # declarable root, so a matrix can be written from what the bar actually
+    # shows rather than from a pre-order harvest that dives into the first
+    # folder it meets. Operator-lane only for a work profile: a `!` line that
+    # carries --bar puts those names and URLs into a payload.
+    if bar > 0:
+        for key in MATRIX_ROOTS:
+            root = roots.get(key)
+            children = [c for c in ((root.get("children") if isinstance(root, dict) else None) or []) if isinstance(c, dict)]
+            print("             %s top level: %d entries%s" % (key, len(children), "" if len(children) <= bar else ", first %d" % bar))
+            for child in children[:bar]:
+                if child.get("type") == "folder":
+                    print("               [folder] %s  (%d urls)" % (child.get("name", ""), sum(1 for _ in walk_urls(child, []))))
+                else:
+                    print("               [url]    %s -> %s" % (child.get("name", ""), clip(child.get("url", ""), 100)))
 
 
 def sync_profile(profile, spec, user_data_dir, harvest_path, dry_run, state, preview):
@@ -487,6 +502,7 @@ def main(argv=None):
     parser.add_argument("--dry-run", action="store_true", help="read and report; write nothing")
     parser.add_argument("--inspect", action="store_true", help="read-only census of the profiles on disk: counts, sync_metadata, checksum verify; needs no matrix")
     parser.add_argument("--preview", type=int, default=PREVIEW_LINES, help="harvest lines to print under --dry-run (0 = counts only)")
+    parser.add_argument("--bar", type=int, default=0, help="with --inspect, list the first N top-level entries of bookmark_bar and other (0 = off)")
     parser.add_argument("--matrix", default=str(MATRIX_PATH))
     parser.add_argument("--user-data-dir", default=str(USER_DATA_DIR))
     parser.add_argument("--harvest", default=str(HARVEST_PATH))
@@ -499,7 +515,7 @@ def main(argv=None):
     if args.inspect:
         profiles = args.profiles or sorted(p.parent.name for p in user_data_dir.glob("*/Bookmarks"))
         for profile in profiles:
-            inspect_profile(profile, user_data_dir)
+            inspect_profile(profile, user_data_dir, max(0, args.bar))
         print("VERDICT: INSPECT")
         return 0
     if not matrix_path.exists():
(sys) scripts $ git commit -am "Wiring it to correct profile"
[main daf1a40] Wiring it to correct profile
 2 files changed, 35 insertions(+), 11 deletions(-)
(sys) scripts $ git push
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 48 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 1.69 KiB | 1.69 MiB/s, done.
Total 5 (delta 4), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (4/4), completed with 4 local objects.
To github.com:miklevin/nixos-config.git
   6491b2a..daf1a40  main -> main
(sys) scripts $
```

## Overcoming Synchronization Surprises

Then this:

```bash
(nix) pipulate $ cd /home/mike/repos/nixos && [ -n "$(tail -c1 .gitignore)" ] && echo >> .gitignore; grep -qxF bookmarks_harvest.md .gitignore || echo bookmarks_harvest.md >> .gitignore; cd - >/dev/null
(nix) pipulate $ 
```

Oh it's worth mentioning that my repo `~/repos/nixos/` is private on GitHub. And
the same deal with a nixos regen and attempt to use the script:

```bash
(sys) scripts $ pwd
/home/mike/repos/nixos/scripts
(sys) scripts $ n
building Nix...
building the system configuration...
this derivation will be built:
  /nix/store/igijqx21z5675sf0p2bk7pjvwlhnns52-nixos-system-nixos-25.05.813814.ac62194c3917.drv
building '/nix/store/igijqx21z5675sf0p2bk7pjvwlhnns52-nixos-system-nixos-25.05.813814.ac62194c3917.drv'...
activating the configuration...
setting up /etc...
reloading user units for mike...
restarting sysinit-reactivation.target
the following new units were started: NetworkManager-dispatcher.service
Done. The new configuration is /nix/store/2rq1cd6xxby2fav48k4ffbpqwjazp9k2-nixos-system-nixos-25.05.813814.ac62194c3917
(sys) nixos $ python bookmarks_sync.py 
python: can't open file '/home/mike/repos/nixos/bookmarks_sync.py': [Errno 2] No such file or directory
(sys) nixos $ cd scripts/
(sys) scripts $ python bookmarks_sync.py 
chrome: RUNNING (SingletonLock -> nixos-51758, pid alive)  user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Profile 2: 679 url bookmarks in the browser, 10 declared, 675 to harvest; shape DIFFERS; codec VERIFIED
  REFUSED: Chrome is running (SingletonLock -> nixos-51758, pid alive); close every window and rerun.
VERDICT: Profile_2=REFUSED_CHROME_RUNNING
(sys) scripts $ python bookmarks_sync.py 
chrome: STOPPED (no SingletonLock)  user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Profile 2: 679 url bookmarks in the browser, 10 declared, 675 to harvest; shape DIFFERS; codec VERIFIED
  harvested 675 -> /home/mike/repos/nixos/bookmarks_harvest.md
  backed up -> /home/mike/.local/state/bookmarks_sync/Profile_2/Bookmarks.20260908-143221
  wrote /home/mike/.config/google-chrome/Profile 2/Bookmarks: 10 url bookmark(s)
VERDICT: Profile_2=WRITTEN
(sys) scripts $
```

Wow, nice! Now I load the browser... Excellent! It had exactly the right effect.
Now I add something to the Bookmark bar, quit, run script again and go back into
Chrome. This risks wiping away the harvested file but I'm assuming (hoping)
Fable 5.1 wrote the harvesting function with idempotent append or equivalent.

```bash
(sys) scripts $ python bookmarks_sync.py 
chrome: STOPPED (no SingletonLock)  user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Profile 2: 11 url bookmarks in the browser, 10 declared, 1 to harvest; shape DIFFERS; codec VERIFIED
  harvested 1 -> /home/mike/repos/nixos/bookmarks_harvest.md
  backed up -> /home/mike/.local/state/bookmarks_sync/Profile_2/Bookmarks.20260908-143457
  wrote /home/mike/.config/google-chrome/Profile 2/Bookmarks: 10 url bookmark(s)
VERDICT: Profile_2=WRITTEN
(sys) scripts $ 
```

Excellent, just as expected! Now I can look in the harvest file
`/home/mike/repos/nixos/bookmarks_harvest.md`. Yep! It's been appended to. This
is a *huge* success! And it's absolutely fine for me to back up my many old work
bookmarks which now become a sort of harvesting in-box for consideration now
rather than littering up my bookmarks. It has turned into a bookmark
collect-and-get-out-of-the-way system letting me lovingly hand-craft immutable
(unless I change it) set of bookmarks which is going to be excellent now for
developing that correct muscle memory with bookmarks always being in the same
place and in the same order.

There's some interesting questions of what happens when I turn sync back on as
that's the classic situation that "undoes" all your good bookmark cleaning and
organizing work by dumping the old pile of bookmarks back in, but even if that
happens I have a power-tool to help force the purging of the synced bookmarks
because I can delete-all with sync on, turn sync off and add my immutable
bookmarks back locally.

**4: Prompt**: Rule from the LIVE RECEIPTS. (1) If the live census and --inspect receipts read sync_metadata ABSENT for Profile 2 on this second tap, sync stayed off across a running Chrome and the lane is stable; if either reads PRESENT again, something re-enabled sync on the Work profile, say so, and stop. (2) If the live --dry-run receipt reads VERDICT: Profile_2=DRY_RUN, quote the harvest count and confirm it would stop at REFUSED_CHROME_RUNNING; if it still reads Default=, name the missing n ignition and stop. (3) Confirm check-ignore reads IGNORED and the --bar 2 receipt lists entries under Default; if either is missing, that car did not land. Then write the next turn as one hand-run sequence with no patches: optionally bm --inspect --bar 25 "Profile 2" to author the matrix and n to materialize it; close every Chrome window (if bm still reads RUNNING, Chrome is lingering in background mode and the receipt names the pid); bm; reopen the Work profile and look at the bar. Then the probes that witness VERDICT: Profile_2=WRITTEN, the ledger's first block by line count only, the backup file in ~/.local/state/bookmarks_sync/Profile_2/, and a second bm reading IN_SYNC once Chrome has rewritten the file. No patches unless a receipt convicts one.

**5: Deliverables**: Chrome Bookmark locations that can be myelinated against.

Any more wrap-up details before I prompt you with the article wrap-up protocol?

**Fable 5.1 Extra**: Context verified.

**Ruling from the live receipts.** (1) Profile 2 reads `sync_metadata=ABSENT` on both the census and `--inspect`, the third consecutive tap, across a Chrome quit, two projections, and a Chrome rewrite: the lane is stable. (2) The dry-run does not read `DRY_RUN`; it reads `Profile_2=IN_SYNC, 0 to harvest`, because you ran the actuator before the compile, so the caboose's expected printout was superseded by a stronger one — the idempotence branch, which the previous caboose had asked for last. (3) `IGNORED` and the `--bar 2` listing under Default both landed; `bookmarks.json` moved 584 → 586 B as predicted; `daf1a40` sits on top of the log.

**What the ride actually witnessed.** Both fences were seen from both sides: `REFUSED_SYNC_METADATA` on two real Default runs, `REFUSED_CHROME_RUNNING` on a real Profile 2 run with the pid named, then `WRITTEN` twice. The harvest read 675 against a predicted 679-minus-overlap, so four of the ten starters were already on the Work bar. Chrome loaded a file this script wrote and showed the bar as declared; that is the passing branch the whole design was waiting on, and it came with a second witness — after you added a bookmark and quit, Chrome rewrote the file, and the script's `codec VERIFIED` on that 11-url file means Chrome's own checksum over a tree descended from the projection matched the codec. The census I once labeled "reads identically by design" moved between taps twice; both times the operator, not the patch, was the variable. And the repository is private, which convicts a comment I wrote into `bookmarks.nix` from a remote URL: a hostname is not a visibility reading.

| Branch | State | Receipt | Lane |
|---|---|---|---|
| REFUSED_SYNC_METADATA | witnessed refusing | two real runs against Default | operator |
| REFUSED_CHROME_RUNNING | witnessed refusing | real run, pid 51758 | operator |
| WRITTEN + harvest + backup | witnessed twice | 14:32:21 (675), 14:34:57 (1) | operator |
| Chrome loads the projection | witnessed | bar as declared; Chrome's rewrite matched the codec | operator |
| IN_SYNC | witnessed | live dry-run, 0 to harvest | compile |
| append-only ledger | witnessed | second block appended, read by eye | operator |
| `--bar`, IGNORED, 586 B | witnessed | live | compile |
| `init` hook, `bm` alias | unwitnessed | every run was `python bookmarks_sync.py` from `scripts/`; no reboot since | — |
| STALE_LOCK, REFUSED_UNREADABLE, no-checksum load | unwitnessed | never reached; codec verified on every write | — |

**Wrap-up details, dry.** The `.gitignore` append is uncommitted: the log stops at `daf1a40` and the append ran after it. `bm` resolves from any directory in a fresh shell, which is exactly the "can't open file" you hit from the repo root. A bare `bookmarks_sync.py` line in `adhoc.txt` is a live actuator whenever Chrome happens to be closed at compile time, as it was here; only `--dry-run` and `--inspect` may ride as `!` lines. The `Preferences` line in `--inspect` never discriminated anything — `has_setup_completed=True` on both profiles in every world — so it is a ritual reading and a candidate for deletion, not a witness. The IN_SYNC tap is against a file the script wrote at 14:34; if the `--inspect` mtime moves past that on the next compile, the same reading becomes the Chrome-rewritten idempotence tap, and if it does not, that tap stays owed. Your sync-back-on plan is right and the fence makes it safe to try in the wrong order: turning sync on would bring the account's old pile down and put `sync_metadata` back, at which point `bm` refuses rather than fights; delete-all with sync on, wait, turn sync off, then `bm` is the purge, expected and unwitnessed. Any other device signed into that Work account keeps whatever it had. The harvest ledger is now an inbox of 675 Work URLs beside a private repo; gitignoring it is a choice, and one deletion in `.gitignore` turns it into tracked history if you want that.

---

### 1. PROBES

```bash
python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
grep -c 'REPOSITORY IS PUBLIC' /home/mike/repos/nixos/bookmarks.nix
ls -l /home/mike/.config/pipulate/bookmarks.json
wc -l /home/mike/repos/nixos/bookmarks_harvest.md
ls -l /home/mike/.local/state/bookmarks_sync/Profile_2/
git -C /home/mike/repos/nixos status --short | head -5
git -C /home/mike/repos/nixos log --oneline -3
```

Probes 1 to 3 are the fourth stability tap and the idempotence reading; the `--inspect` mtime decides whether Chrome rewrote the file since 14:34. Probe 4 is the Car 1 straddle: 1 → 0, and the replacement text says "called it public" rather than repeating the phrase, so the epitaph cannot inflate it. Probe 5 is a gate: comments do not change the matrix, so 586 B must read 586 B. Probe 6 predicts 692: a six-line header, then one blank plus 679 lines for the first block (heading, blank, fence, 675 entries, fence), then one blank plus five for the second — the receipt rules if the arithmetic is wrong. Probe 7 should list exactly two backups, the first about 356 KB and the second under 10 KB. Probe 8 shows the uncommitted `.gitignore` before the commit and silence after; Probe 9 reads the new commit on top. The `--bar 2` probe retires: it witnessed its car.

### 2. NEXT CONTEXT

`foo_files.py` rides so the dismount's BANK cars have raw source to land in.

```text
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
foo_files.py
! python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! grep -c 'REPOSITORY IS PUBLIC' /home/mike/repos/nixos/bookmarks.nix
! ls -l /home/mike/.config/pipulate/bookmarks.json
! wc -l /home/mike/repos/nixos/bookmarks_harvest.md
! ls -l /home/mike/.local/state/bookmarks_sync/Profile_2/
! git -C /home/mike/repos/nixos status --short | head -5
! git -C /home/mike/repos/nixos log --oneline -3
```

### 3. PATCHES

**Car 1 — the comment that called the repo public, and the starter note that predicted a sync that has now happened.** Two contiguous comment runs, one file; the Nix airlock parses it.

```text
Target: /home/mike/repos/nixos/bookmarks.nix
[[[SEARCH]]]
# THIS REPOSITORY IS PUBLIC. bookmarks_harvest.md is gitignored because it will
# hold the Work bar verbatim; nothing promoted into THIS file may carry a client
# name or slug, because this file ships to GitHub on every push.
[[[DIVIDER]]]
# THE LEDGER IS GITIGNORED BY CHOICE, NOT NECESSITY. This repository is private
# (operator's ruling 2026-09-08, correcting an earlier comment here that called
# it public). bookmarks_harvest.md is an inbox -- 675 Work URLs landed in it on
# the first sync -- and an inbox reads better untracked; delete its line from
# .gitignore to keep harvest history in git instead. What is promoted into THIS
# file is what every projection writes, so promote by hand, never by paste-all.
[[[REPLACE]]]

Target: /home/mike/repos/nixos/bookmarks.nix
[[[SEARCH]]]
        # STARTER, NOT THE WORK BAR: these ten are Default's first ten, carried
        # over as a placeholder so the first projection is not an empty bar.
        # The Work bar's own entries (676 urls in 81 folders at retarget time)
        # land in bookmarks_harvest.md on the first real sync, paste-ready.
        # To author before that sync: bm --inspect --bar 25 "Profile 2"
[[[DIVIDER]]]
        # STARTER, NOT THE WORK BAR: these ten are Default's first ten, carried
        # over as a placeholder. The first real sync ran 2026-09-08 14:32: the
        # Work bar's 679 urls, four of them already among these ten, went to
        # bookmarks_harvest.md as 675 paste-ready lines, and this list has been
        # the bar since. Promote from the ledger, then n to materialize and bm
        # (or the next init) to project.
[[[REPLACE]]]
```

**Ignition.** None: comments only, so `bookmarks.json` stays 586 B and nothing needs `n`; Probe 5 is the gate that says so. Commit the two dangling edits together: `cd ~/repos/nixos && git add .gitignore && git commit -am "Ledger gitignored by choice: repo is private; starter comment records the first sync" && git push`.

### 4. PROMPT

```text
This turn is the dismount; the \k block below supplies the seven beats. Before beat 0, rule from the LIVE RECEIPTS of this compile: the census must read False [] for Profile 2 on its fourth tap; the dry-run must read Profile_2=IN_SYNC, and if the --inspect mtime for Profile 2 has moved past 14:34 that reading is the Chrome-rewritten idempotence tap the earlier caboose asked for, while if it has not, say so and leave that tap owed; the grep must read 0; wc -l on the ledger must read 692 or name the miscount; the backup listing must show two files, the first about 356 KB and the second under 10 KB; git status must be silent or name what is not committed. Then run the beats. BANK cars land in foo_files.py against the raw source in this context: a RECEIPTS line for this ride, and rulings on whether the moved census (operator as an uncontrolled variable between taps), the hostname-is-not-a-visibility-reading correction, and the two fences seen from both sides graduate to keys or stay receipts. The init hook, the bm alias, STALE_LOCK, REFUSED_UNREADABLE, the no-checksum load path, and the Preferences line that never discriminated are unwitnessed or rituals and go to DANGLING, not to BANK.
```

### 5. EXTERNAL DELIVERABLES

None this turn outside the repo. The deliverable you named — bookmark positions stable enough to build muscle memory against — is already on the bar of Profile 2, and it will be there in the same order after every `init`.

**MikeLev.in**: 

> Same commands, run twice, one change between them. Where the readings
> differ is what the change did; the diff in the middle is the receipt.

**1: Probe**: (BEFORE: hand-run, nothing changed yet)

```bash
Blast Radius Check to establish bisection Left-hand Causal Boundary. It is a Popper-thing. Science.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
grep -c 'REPOSITORY IS PUBLIC' /home/mike/repos/nixos/bookmarks.nix
ls -l /home/mike/.config/pipulate/bookmarks.json
wc -l /home/mike/repos/nixos/bookmarks_harvest.md
ls -l /home/mike/.local/state/bookmarks_sync/Profile_2/
git -C /home/mike/repos/nixos status --short | head -5
git -C /home/mike/repos/nixos log --oneline -3
Default {'bookmark_bar': 14, 'other': 14, 'synced': 0} True ['sync_metadata']
Profile 2 {'bookmark_bar': 10, 'other': 0, 'synced': 0} False []
chrome: RUNNING (SingletonLock -> nixos-54948, pid alive)  user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Default      97,427 B  mtime 2026-09-08 14:42  version=1  siblings=['Bookmarks', 'Bookmarks.bak']
             urls=125 folders=9  bookmark_bar=111  other=14  synced=0
             sync_metadata=PRESENT (44,068 chars)  extra_keys=['sync_metadata']
             checksum stored=c776444b816f computed=c776444b816f -> MATCH
             Preferences: account_info=5  sync.has_setup_completed=True keep_everything_synced=- bookmarks=-
Profile 2    4,261 B  mtime 2026-09-08 14:34  version=1  siblings=['Bookmarks', 'Bookmarks.bak']
             urls=10 folders=0  bookmark_bar=10  other=0  synced=0
             sync_metadata=ABSENT  extra_keys=-
             checksum stored=7e53f041cb13 computed=7e53f041cb13 -> MATCH
             Preferences: account_info=1  sync.has_setup_completed=True keep_everything_synced=- bookmarks=-
VERDICT: INSPECT
chrome: RUNNING (SingletonLock -> nixos-54948, pid alive)  user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Profile 2: 10 url bookmarks in the browser, 10 declared, 0 to harvest; shape IN SYNC; codec VERIFIED
VERDICT: Profile_2=IN_SYNC
1
-rw-r--r-- 1 mike users 586 Sep  8 14:31 /home/mike/.config/pipulate/bookmarks.json
692 /home/mike/repos/nixos/bookmarks_harvest.md
total 356
-rw------- 1 mike users 356308 Sep  8 14:11 Bookmarks.20260908-143221
-rw------- 1 mike users   4178 Sep  8 14:34 Bookmarks.20260908-143457
 M .gitignore
daf1a40 (HEAD -> main, origin/main, origin/HEAD) Wiring it to correct profile
6491b2a Adding control of work bookmarks from Nix
5c874f2 Making blogs.nix support subfolders
(nix) pipulate $ 
```

**2: Context**: (AFTER: the same probes re-run by the compiler as `!` lines)

```text
# adhoc.txt    _   _   _ to set context____ _   _  ___  ____  _   Simpson Couch Gag Here (explain anything to the audience you feel needs it explained)G
#     / \   __| | | | | | ___   ___   / ___| | | |/ _ \|  _ \| |  
# ahe/ _ \ / _` | | |_| |/ _ \ / __| | |   | |_| | | | | |_) | |  Getting bookmarks under control finally
# ahc ___ \ (_| | |  _  | (_) | (__  | |___|  _  | |_| |  __/|_|  I hope my preferred method works here
#  /_/   \_\__,_| |_| |_|\___/ \___|  \____|_| |_|\___/|_|   (_)  This is one of those small things that I can already feel will make all the difference.
# Ad Hoc CHOP: The Not-Managed-by-Git Safe-for-Client-Data place  

# OPTIONAL BUT BIG FOR FULL CONTEXT-WINDOW STORYTELLING
# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- The "Rolling Pin" that gives the 40K foot book-spine view of book-ore.
# GLOSSARY.md                 # <-- Like the back of a J.R.R. Tolkien book but always growing in size as `prompt_foo.py` gets scars and shrinks.
# init.lua                    # <-- Daily driver hot-keys that overlap with aliases in flake.nix. `<leader>m` makes it Science (this process)!

# scripts/articles/lsa.py     # <-- 2nd Brain query-engine for `rgx`, `rgxc` & `posts` Jekyll-inspired Memory Externalization for Hackers.
# ~/repos/nixos/autognome.py  # <-- Letting the AIs really understand my environment (The Brave Little Tailor punches above Their Weight Class proving the dunning-kruger effect the gate-keeper's (lower-case) lament.)
 
# STILL BIG BUT LESS OPTIONAL (especially flake.nix)
# flake.nix                   # <-- THE ONE BIG THING TO INCLUDE Infrastructure as Code (IaC) tells LLM about your system down to the metal
# prompt_foo.py               # <-- This very content-compiling system
# foo_files.py                # <-- This is the router, evolving book outline and the things you pin-up to produced the recursive self-improvement loops

# TINY ILLUMINATING (OK to include every time / automatically = `apply.py`, `.gitignore`, `.gitattributes`)
# requirements.in             # <-- All known dependencies and (necessary) version pinning. WORA gotcha's exposed.
# __init__.py                 # <-- Master versioning
# pyproject.toml              # <-- The PyPI Packaging details

# OPTIONAL ACTUATORS (cheap and good to include to expand the AI's capabilities)
# cli.py                      # <-- Catch-all actuator for PyPI envs, Python anchoring, MCP tool-call (plus alternatives) and **kwargs like wrapping for CLI
# scripts/xp.py               # <-- Transforms host OS copy-paste buffer player-piano music into context-payload.
# scripts/ai.py               # <-- How I constantly use local AI to write git commit messages with `m` alias.
# scripts/crawl.py            # <-- Feel free to ask for something to be crawled and included in the next turn.
# scripts/weblogin.py         # <-- Lets the user "warm up" the cache for their web logins at their leisure on a profile that persists.
 
# MISCELLANEOUS (rare to include but sometimes critical)
# scripts/foo_cartridge.py    # Needs description
# scripts/foo_replay.py       # Needs description
# release.py                  # <-- How everything ends up where it does (GitHub, PyPI, etc.)
# imports/voice_synthesis.py  # <-- The wand can talk to you
# imports/ascii_displays.py   # <-- Where all the ASCII Art lives
# scripts/release/version_sync.py  # <-- Needs to be wrapped into release.py and eliminated, I think.

#                         --- Under this line is were you paste what the AI gives you ---
#                         --- We call it context but it's really just the right-hand  ---
#                         --- blast-radius of the "probes" to make this all science.  ---

# Carry-over as the important work-in-progress parts of the project here just
# like above but not as long-standing overarching to the framework but rather
# for the current hot spots actively being worked on.

# STICKBUG & MOTHER CAT KATA (WORKING ON THE CHAPTER)

# assets/trails/first_context.yaml
# assets/trails/practice.yaml
# assets/trails/public_walk.yaml
# # assets/trails/botify_pageworkers.yaml
# 
# scripts/connectors/README.md
# scripts/connectors/botify.py
# scripts/connectors/confluence.py
# scripts/connectors/gmail.py
# scripts/connectors/gsc.py
# scripts/connectors/jira.py
# scripts/connectors/mcp.py
# scripts/connectors/mcp_warm.py
# scripts/connectors/sheets.py
# scripts/connectors/slack.py
# 
# assets/installer/mck.sh
# assets/installer/replay.sh
# scripts/bookmark_import.py
# scripts/walk.py
# scripts/walk_cartridge.py
# scripts/walk_compile.py
# scripts/weblogin.py
# tools/scraper_tools.py
# 
# scripts/mcp_dummy_server.py  
# scripts/connectors/wallet.py
# scripts/boot_menu.py
# scripts/mother_cat.py
# scripts/sources_menu.py

# --- START THIS DISCUSSION ---

# Context 1 (Edit-in selections from above and add new files immediately below)
# ~/repos/nixos/autognome.py                 #  <-- More rare to have to include, but the true "top" of the muscle memory stack for day-to-day purposes
# ~/repos/nixos/configuration.nix            #  <-- "Global" IaC context (most of you won't have)
# ~/repos/nixos/blogs.nix
# ~/repos/nixos/packages.nix                 #  <-- Full disclosure on pre-flake IaC available apps.
# ~/repos/nixos/services.nix                 #  <-- Running Linux system services.
# ~/repos/nixos/ai-acceleration.nix          #  <-- Paid a lot for your hardware? We've got you covered.

# Context 2
# /home/mike/repos/nixos/autognome.py
# /home/mike/repos/nixos/configuration.nix
# /home/mike/repos/nixos/blogs.nix
# /home/mike/repos/nixos/bookmarks.nix
# /home/mike/repos/nixos/scripts/bookmarks_sync.py
# ! python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
# ! LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
# ! ls -l /home/mike/.config/pipulate/bookmarks.json
# ! grep -c 'bookmarks_sync.py"' /home/mike/repos/nixos/autognome.py /home/mike/repos/nixos/configuration.nix
# ! git -C /home/mike/repos/nixos log --oneline -3

# Context 3
# /home/mike/repos/nixos/autognome.py
# /home/mike/repos/nixos/configuration.nix
# /home/mike/repos/nixos/blogs.nix
# /home/mike/repos/nixos/bookmarks.nix
# /home/mike/repos/nixos/scripts/bookmarks_sync.py
# ! python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect --bar 2 Default
# ! LD_LIBRARY_PATH="" nix-instantiate --parse /home/mike/repos/nixos/bookmarks.nix >/dev/null && echo PARSE_OK
# ! ls -l /home/mike/.config/pipulate/bookmarks.json
# ! git -C /home/mike/repos/nixos check-ignore -q bookmarks_harvest.md && echo IGNORED
# ! git -C /home/mike/repos/nixos log --oneline -3

# Context 4
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
foo_files.py
! python3 -c 'for p in ("Default","Profile 2"): j=__import__("json"); o=__import__("os"); d=j.load(open(o.path.expanduser("~/.config/google-chrome/"+p+"/Bookmarks"))); print(p, {k: len(d["roots"][k].get("children",[])) for k in d["roots"] if isinstance(d["roots"][k], dict)}, "sync_metadata" in d, sorted(set(d)-{"checksum","roots","version"}))'
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Default "Profile 2"
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! grep -c 'REPOSITORY IS PUBLIC' /home/mike/repos/nixos/bookmarks.nix
! ls -l /home/mike/.config/pipulate/bookmarks.json
! wc -l /home/mike/repos/nixos/bookmarks_harvest.md
! ls -l /home/mike/.local/state/bookmarks_sync/Profile_2/
! git -C /home/mike/repos/nixos status --short | head -5
! git -C /home/mike/repos/nixos log --oneline -3

# --- END `adhoc.txt` TEMPLATE ---
```

**3: Patches**: (the one change between the readings)

```diff
(sys) nixos $ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   .gitignore
	modified:   bookmarks.nix

no changes added to commit (use "git add" and/or "git commit -a")
(sys) nixos $ git --no-pager diff
diff --git a/.gitignore b/.gitignore
index 5a5abcd..afdb1e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ secrets.nix
 
 secrets.json
 credentials/
+bookmarks_harvest.md
diff --git a/bookmarks.nix b/bookmarks.nix
index 0b0e108..606d7e2 100644
--- a/bookmarks.nix
+++ b/bookmarks.nix
@@ -23,9 +23,12 @@
 # `sync_metadata`, the script refuses any profile Sync owns, and declaring it
 # would only print REFUSED on every init.
 #
-# THIS REPOSITORY IS PUBLIC. bookmarks_harvest.md is gitignored because it will
-# hold the Work bar verbatim; nothing promoted into THIS file may carry a client
-# name or slug, because this file ships to GitHub on every push.
+# THE LEDGER IS GITIGNORED BY CHOICE, NOT NECESSITY. This repository is private
+# (operator's ruling 2026-09-08, correcting an earlier comment here that called
+# it public). bookmarks_harvest.md is an inbox -- 675 Work URLs landed in it on
+# the first sync -- and an inbox reads better untracked; delete its line from
+# .gitignore to keep harvest history in git instead. What is promoted into THIS
+# file is what every projection writes, so promote by hand, never by paste-all.
 #
 # ENTRY GRAMMAR: { name = "..."; url = "..."; } is a bookmark;
 # { name = "..."; children = [ ... ]; } is a folder. An empty name is legal and
@@ -36,10 +39,11 @@ let
     "Profile 2" = {
       bookmark_bar = [
         # STARTER, NOT THE WORK BAR: these ten are Default's first ten, carried
-        # over as a placeholder so the first projection is not an empty bar.
-        # The Work bar's own entries (676 urls in 81 folders at retarget time)
-        # land in bookmarks_harvest.md on the first real sync, paste-ready.
-        # To author before that sync: bm --inspect --bar 25 "Profile 2"
+        # over as a placeholder. The first real sync ran 2026-09-08 14:32: the
+        # Work bar's 679 urls, four of them already among these ten, went to
+        # bookmarks_harvest.md as 675 paste-ready lines, and this list has been
+        # the bar since. Promote from the ledger, then n to materialize and bm
+        # (or the next init) to project.
         { name = ""; url = "https://mail.google.com/mail/u/0/#inbox"; }
         { name = ""; url = "https://calendar.google.com/calendar/u/0/r"; }
         { name = "Simplenote"; url = "https://simplenote.com/"; }
(sys) nixos $ cd ~/repos/nixos && git add .gitignore && git commit -am "Ledger gitignored by choice: repo is private; starter comment records the first sync" && git push
[main f021270] Ledger gitignored by choice: repo is private; starter comment records the first sync
 2 files changed, 12 insertions(+), 7 deletions(-)
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 48 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 968 bytes | 968.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:miklevin/nixos-config.git
   daf1a40..f021270  main -> main
(sys) nixos $ 
```

Nice!

**4: Prompt**: This turn is the dismount; the \k block below supplies the seven beats. Before beat 0, rule from the LIVE RECEIPTS of this compile: the census must read False [] for Profile 2 on its fourth tap; the dry-run must read Profile_2=IN_SYNC, and if the --inspect mtime for Profile 2 has moved past 14:34 that reading is the Chrome-rewritten idempotence tap the earlier caboose asked for, while if it has not, say so and leave that tap owed; the grep must read 0; wc -l on the ledger must read 692 or name the miscount; the backup listing must show two files, the first about 356 KB and the second under 10 KB; git status must be silent or name what is not committed. Then run the beats. BANK cars land in foo_files.py against the raw source in this context: a RECEIPTS line for this ride, and rulings on whether the moved census (operator as an uncontrolled variable between taps), the hostname-is-not-a-visibility-reading correction, and the two fences seen from both sides graduate to keys or stay receipts. The init hook, the bm alias, STALE_LOCK, REFUSED_UNREADABLE, the no-checksum load path, and the Preferences line that never discriminated are unwitnessed or rituals and go to DANGLING, not to BANK.

**5: Deliverables**: Bookmarks I can form muscle memory habits against.

And now before the final wrap-up I turn the profile back to synchronizing.
Alright, that worked perfectly. I no longer have that big overwrite of pristine
bookmarks by turning sync back on. I moved all my bookmarks into a folder and
then deleted that folder as a way of avoiding the tedium of deleting one at a
time because I didn't see a "select all" option. And now I'm just going to get
in the habit of occasionally toggling sync on and off to get the immutable core
over to my Mac and whatever other secondary machines, and likewise those will
sync-back any collected bookmarks for harvesting and consideration for proper
inclusion.

This has been a massive success. My next step will be looking at that format of
`bookmarks.nix`:

```nix
{ lib, ... }:
# ============================================================================
# 🔖 THE BOOKMARK MATRIX (Single Source of Truth for Chrome's bookmarks bar)
# ============================================================================
# Sibling of blogs.nix, same shape: this attrset is the canonical list, and on
# every `nixos-rebuild switch` the activation script below materializes it to
# ~/.config/pipulate/bookmarks.json. NOTHING here touches Chrome. The browser
# side is scripts/bookmarks_sync.py, run by `init` (autognome.py) before the
# first Chrome launch and by hand as `bm`, which per declared profile:
#   1. reads the profile's current Bookmarks file,
#   2. HARVESTS every URL it holds that this matrix does not declare into
#      bookmarks_harvest.md beside this file, in paste-ready Nix syntax,
#   3. backs the old file up to ~/.local/state/bookmarks_sync/, and
#   4. writes this matrix as the profile's whole bookmark tree.
# So the browser is a PROJECTION: anything added in Chrome survives exactly one
# `init`, then lives in the harvest ledger until it is promoted here or let go.
#
# ONE PROFILE ON PURPOSE, AND IT IS THE WORK ONE (retargeted 2026-09-08).
# "Profile 2" is the Workspace profile Chrome labels "Work". At its 14:11 write
# that day its Bookmarks file carried no `sync_metadata` (601 KB -> 356 KB,
# the sync record for 679 bookmarks leaving), so Chrome Sync no longer owns it
# and a local wipe sticks. "Default" is NOT declared: its file still carries
# `sync_metadata`, the script refuses any profile Sync owns, and declaring it
# would only print REFUSED on every init.
#
# THE LEDGER IS GITIGNORED BY CHOICE, NOT NECESSITY. This repository is private
# (operator's ruling 2026-09-08, correcting an earlier comment here that called
# it public). bookmarks_harvest.md is an inbox -- 675 Work URLs landed in it on
# the first sync -- and an inbox reads better untracked; delete its line from
# .gitignore to keep harvest history in git instead. What is promoted into THIS
# file is what every projection writes, so promote by hand, never by paste-all.
#
# ENTRY GRAMMAR: { name = "..."; url = "..."; } is a bookmark;
# { name = "..."; children = [ ... ]; } is a folder. An empty name is legal and
# renders favicon-only on the bar (the first two entries below). List order is
# bar order. The script validates every entry before it touches anything.
let
  bookmarks = {
    "Profile 2" = {
      bookmark_bar = [
        # STARTER, NOT THE WORK BAR: these ten are Default's first ten, carried
        # over as a placeholder. The first real sync ran 2026-09-08 14:32: the
        # Work bar's 679 urls, four of them already among these ten, went to
        # bookmarks_harvest.md as 675 paste-ready lines, and this list has been
        # the bar since. Promote from the ledger, then n to materialize and bm
        # (or the next init) to project.
        { name = ""; url = "https://mail.google.com/mail/u/0/#inbox"; }
        { name = ""; url = "https://calendar.google.com/calendar/u/0/r"; }
        { name = "Simplenote"; url = "https://simplenote.com/"; }
        { name = "Gemini"; url = "https://gemini.google.com/app"; }
        { name = "Claude"; url = "https://claude.ai/"; }
        { name = "ChatGPT"; url = "https://chatgpt.com/"; }
        { name = "Grok"; url = "https://grok.com/"; }
        { name = "DeepSeek"; url = "https://chat.deepseek.com/"; }
        { name = "Qwen"; url = "https://chat.qwen.ai/c/guest"; }
        { name = "NixOS Search"; url = "https://search.nixos.org/packages"; }
        # A folder looks like this:
        # { name = "Local"; children = [
        #   { name = "Trilium"; url = "https://nixos.local/"; }
        # ]; }
      ];
      # The "Other bookmarks" root. Empty means: wiped on sync, after harvest.
      other = [ ];
    };
  };
in
{
  # printf, never a heredoc (NO HEREDOC INSIDE AN INTERPOLATED NIX STRING):
  # escapeShellArg single-quotes the whole JSON line, so an apostrophe in a
  # bookmark name cannot break the shell and no terminator can lose its column.
  system.activationScripts.materializeBookmarksJson = {
    text = ''
      mkdir -p /home/mike/.config/pipulate
      printf '%s\n' ${lib.escapeShellArg (builtins.toJSON bookmarks)} > /home/mike/.config/pipulate/bookmarks.json
      chown mike:users /home/mike/.config/pipulate/bookmarks.json
    '';
    deps = [];
  };
}
```

...and maybe add a description field that only lives in there so I can talk
about my new job workflow which totally relates to these bookmarks, or really
the ones that soon will be there as I move beyond these placeholders I had there
while we worked this out.

Hop off the ride. This ride's stated goal is reached — dismount.
This is the NOTARY BEAT: the ride ends here, is witnessed here, and is
sealed here. Answer all seven beats, briefly:

0. **TL;DR**: a short, dry, neutral abstract for the TOP of the published
   article — written for an unfamiliar reader or AI summarizer who has
   never seen this system. No hype, no insider handles unexplained.
1. VERIFY: restate the goal from the top of this article and confirm
   (or deny) it was met, citing THIS compile's receipts, not memory.
   Name any ignition this ride required that never fired -- an AFTER
   tap taken without one is a stale BEFORE wearing the AFTER's label.
2. BANK: name everything that graduates — rule, earmark, todo, pin —
   as SEARCH/REPLACE patch cars against raw source present in THIS
   context (Target line inside the fence, one car per commit story),
   deletions included. A 'paste-ready' line with no Target and no
   SEARCH anchor is not banked; it is a hand edit the operator will
   not make. These BANK cars are the ONLY patches a dismount emits.
3. DANGLING: what carries forward unbanked? One line each, no essays.
4. SEED: the adhoc.txt lines (and TODO_SLUGS if narrative context is
   needed) for the next ride's first compile.
5. CLOSING: a closing summary for the BOTTOM of the article — the
   final take-away, tied to the book's larger arc where it fits
   naturally, never forced. Storytelling over inventory.
6. NOTARIZE: the artifact of record is the cartridge THIS compile
   writes after this payload is assembled, so its name is not in your
   context and you must not guess one. Attest the FINGERPRINT instead:
   this Manifest's wc -c line, its receipt count, and the newest
   receipt's date -- pure functions of the bytes the cartridge seals.
   Close with one line the operator completes after the compile:
   Deed: foo-________.zip   (ls -t foo-*.zip | head -1)
   The seal is the signature; the fingerprint is how the deed is found.

FINALITY: after beat 6, this discussion is CLOSED. Emit NO five-car
train, NO probes, NO patches beyond the BANK cars of beat 2, and NO
next-turn prompt beyond the SEED lines in beat 4. Any reader or model
encountering this article later should treat it as a finished,
notarized document — an archive entry, not an open thread.

**Fable 5.1 Extra**: Context verified.

## 1. VERIFY

The goal, from the top of the article: bookmarks as an immutable projection of Nix configuration, synchronized by something as ordinary as `init` or `b2`, refined mid-ride to a standalone program callable from `init`, a wipe-and-replace with everything undeclared harvested into a file beside `blogs.nix`, aimed at the Work profile. Met, on this compile's receipts: the census reads `Profile 2 {'bookmark_bar': 10, 'other': 0, 'synced': 0} False []` on its fourth tap; `--inspect` reads urls=10, folders=0, `sync_metadata=ABSENT`, checksum MATCH; the dry-run reads `Profile_2=IN_SYNC, 0 to harvest`; the ledger reads 692 lines, the number computed before the receipt; `bookmarks.json` holds at 586 B with no rebuild needed for comment-only edits; the grep reads 0 where the hand tap read 1; git status is silent and `f021270` sits on top of the log. The backup directory holds three files, not the two predicted, and the third is the receipt of the purge you described only in prose: a 1,042-byte file, three empty roots, emptied with sync on and then overwritten by a run whose harvest was zero, which is why 692 did not move.

Ignitions that never fired: `init` itself, so the hook in `autognome.py` has never executed; the `bm` alias, which no transcript shows typed; and the sync-on you report for Profile 2, which the file does not witness. At compile time Profile 2's `Bookmarks` reads `sync_metadata=ABSENT` at 3,896 B with an mtime of 14:56, the script's own write, and a Chrome that has restarted since (pid 54948 → 56361) has not rewritten it. Either Chrome has not yet persisted the account's ownership marker, or the switch did not take on this profile. The next `bm` discriminates without being asked to: it refuses with `REFUSED_SYNC_METADATA` if sync is on, and reads `IN_SYNC` if the file still lacks the key. One consequence worth saying plainly for the habit you described: while sync is on, `init`'s projection refuses on every boot by design, so "toggle occasionally" means sync on to transport, sync off to project, and the fence enforces that order for you.

## 2. BANK

**Car 1 — the receipt, with the oldest faded.** Two blocks, one file; the block stays at its cap of 20.

```text
Target: foo_files.py
[[[SEARCH]]]
# --- START RECEIPTS (newest first; cap 20 lines; a line pushed past the cap is deleted, never moved -- git and the rolling pin are the archive) ---
[[[DIVIDER]]]
# --- START RECEIPTS (newest first; cap 20 lines; a line pushed past the cap is deleted, never moved -- git and the rolling pin are the archive) ---
# 2026-09-08 dismount THE BOOKMARK PROJECTION (five compiles, deeds 1278 through this one; nixos commits 6491b2a, daf1a40, f021270): bookmarks.nix materializes bookmarks.json on every rebuild and scripts/bookmarks_sync.py projects it into Chrome's Bookmarks file at init or bm -- harvest, back up, write -- idempotent by shape. Both fences seen from both sides: REFUSED_SYNC_METADATA on two real Default runs, REFUSED_CHROME_RUNNING on Profile 2 with the pid named, then WRITTEN three times (675 harvested into a 692-line ledger computed exactly, then 1, then 0 over a 1,042 B file the operator had emptied with sync on) and IN_SYNC live. Chrome loaded a script-written file and showed the bar as declared, and the checksum codec recalled from memory matched Chrome's own on three Chrome-written files. THE OPERATOR WAS THE VARIABLE three times: the sync toggle moved Profile 2's census while the script refused Default, the only declared profile, so "no luck" was read off a verdict about a different subject; the real run preceded the compile so a predicted DRY_RUN read IN_SYNC; a backup listing predicted at two read three after a purge described only in prose. A remote hostname was read as a visibility claim and the operator corrected it: the repo is private. bookmarks.json 584 -> 586 B as predicted. Sync re-enabled per the operator, UNWITNESSED by the file: sync_metadata ABSENT at compile, mtime unmoved since the 14:56 write; the next bm discriminates.
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
# 2026-08-30 compile KEY/VALUE strike 6: sigil keys 24 -> 29; EARMARK 59 -> 54; five-pattern tombstone 5 -> 0 (grep's expected NON-ZERO EXIT 1); RECEIPTS held 20; ast_ok; foo_files.py 272,536 -> 264,473 B (-2,120 tok), GLOSSARY.md 71,553 -> 82,577 B (+2,766 tok); raw byte predictions missed their bands by 323 B high and 743 B low, and both delta magnitudes were over-predicted 14-15%, so dropping the n=1 correction removed the sign flip but put neither byte side in band; wc -l 3114 -> 2958, one below prediction because the five landed diff deltas sum to -156 (-25-25-30-29-47), not -155. Five graduations landed.
# --- END RECEIPTS ---
[[[DIVIDER]]]
# --- END RECEIPTS ---
[[[REPLACE]]]
```

**Car 2 — two earmarks and the seed's trap, banked as a todo.** GLOSSARY.md is not in this context, so these land as earmarks, values without keys, and graduate in a later forget ride.

```text
Target: foo_files.py
[[[SEARCH]]]
# #todo #to-do #earmarks
[[[DIVIDER]]]
# #todo #to-do #earmarks
# - EARMARK: THE OPERATOR IS A VARIABLE (banked 2026-09-08, three convictions in one ride): a straddle holds the world constant and changes ONE thing, but the operator lives in the world and acts on it between taps -- toggles a setting, runs the actuator early, runs it again -- and every such act is a SECOND manipulated variable no probe was told about. CONVICTIONS, one ride: a census labeled "reads identically by design" flipped Profile 2's sync_metadata from True to False between the hand tap and the compile because the operator toggled Chrome Sync, while the script refused Default, the only declared profile, and "no luck" was read off a verdict about a different subject; a dry-run predicted to read DRY_RUN read IN_SYNC because the operator ran the real sync before the compile; a backup listing predicted at two files read three because the operator ran a purge the article described only in prose. In every case the receipt was TRUE about the world and silent about which variable moved. STANDING CONSEQUENCE: when a reading moves by more than the patch can explain, ask what the operator did between taps before crediting or blaming the patch, and write operator actions taken between taps into the article as loudly as patches. Sibling of THE PRE-COMPILE ACTUATOR RULE (one instance: the actuator run early) and of THE STRADDLE IS A CONTROLLED EXPERIMENT, whose four named confound controls do not cover the experimenter's own hands.
# - EARMARK: HARVEST THEN PROJECT (banked 2026-09-08, receipt-witnessed end to end on Chrome's Bookmarks file): the pattern for making a mutable application file a PROJECTION of declarative config without losing what the application accumulated. Four moves per managed target, in order: READ the live file; HARVEST everything it holds that the declaration does not into an append-only inbox, in paste-ready declaration syntax; BACK UP the live file; WRITE the declaration as the whole file. Three gates before the write, each a refusal with a token verdict: the application is running (its own lock file with a live pid), the file carries a foreign OWNERSHIP marker (Chrome Sync's sync_metadata: a local wipe would be reverted from the account and the declaration uploaded), or the file does not parse (no harvest is possible, so a human looks first). Idempotence by SHAPE, never bytes, because the application rewrites dates and checksums on its own. Any format claim recalled from memory gets a self-check against the application's own output before the writer trusts it -- the checksum codec matched three Chrome-written files before it was ever written. The inbox turns hoarding into triage: 675 URLs left a bar in one run and none were lost. Generalizes to any application that owns a state file the operator also wants to declare; blogs.nix -> blogs.json is the same pattern one stage shorter.
# - TODO (2026-09-08, seeded at dismount): bookmarks.nix wants a per-entry `description` field the operator can write workflow prose into; validate_entries in scripts/bookmarks_sync.py REJECTS unknown keys today, so adding the field before the script learns it prints BAD_MATRIX on every init -- a refusal, never a wipe, but loud. The car: accept and ignore `description` in validate_entries, matrix_shape and project_entries; the Nix materialization needs no change. Gate: a matrix carrying one description reads IN_SYNC, not BAD_MATRIX.
[[[REPLACE]]]
```

No ignition: `foo_files.py` is read at compile time. Both cars land in pipulate, so `app`, `d`, `m`, `blast` apply as usual.

## 3. DANGLING

- Sync-on for Profile 2 is unwitnessed by the file; the next `bm` rules it, and while sync stays on every `init` projection refuses by design.
- `init` has not run since the hook landed; its first run prints the projection block and `bookmark sync exit 0`.
- The `bm` alias has never appeared in a transcript; every recorded run was `python bookmarks_sync.py` from `scripts/`.
- STALE_LOCK, REFUSED_UNREADABLE, and the no-checksum load path are unreached branches; the codec verified on every write, so the degraded path was never needed.
- The `Preferences` line in `--inspect` read `True/-/-` in every world and is a ritual; delete it or leave it, but do not read it.
- The direct IN_SYNC tap on a Chrome-rewritten file is still owed at minute resolution; the indirect witness is the 11-url file Chrome wrote after the first projection, which harvested exactly one.
- Toggling sync as the transport to other machines is expected and unwitnessed; each toggle-on with a non-empty account brings the pile back and the fence refuses until sync is off again.
- The ledger is gitignored by choice in a private repo; one deleted line tracks it as history.
- Any other device signed into the Work account keeps whatever it had until the next toggle.

## 4. SEED

The next ride is the `description` field; the trap it must clear is banked in Car 2's TODO.

```text
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect "Profile 2"
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! grep -c '"description"' /home/mike/repos/nixos/scripts/bookmarks_sync.py
! ls -l /home/mike/.config/pipulate/bookmarks.json
```

## 5. CLOSING

The ride opened with a question about an API and closed with the discovery that there was never going to be one worth wanting: the bookmarks bar is a JSON file Chrome reads at startup, and the whole design problem was never how to reach it but when it is safe to touch and what to do with what was already there. The answer turned out to be the same shape as `blogs.nix`, one stage longer — declare in Nix, materialize on rebuild, project at the one moment the browser is provably not running — with a harvest step in front of the wipe so that six hundred and seventy-five bookmarks became an inbox instead of a loss. That inbox is the quiet win. Chapter 2 of this book is about muscle memory, and muscle memory needs the thing to be in the same place every morning; a bar that regenerates from a file you edit by hand is the first bookmark bar that can promise that.

The science was in the fences. A sync gate seen only refusing, a running-Chrome gate seen only refusing, and then both seen passing on real runs with the pid and the byte counts in the receipts; a checksum recalled from memory that was made to match Chrome's own output three times before it was allowed to write once. And the ride's own lesson about method was the one it kept catching itself in: three times a reading moved and the patch had not moved it, because the person running the experiment had reached into the world between the taps. The instrument was never wrong about the world. It was only ever silent about whose hands had been on it, and now there is a line in the book that says to ask.

## 6. NOTARIZE

Fingerprint of the bytes this compile seals: nine live command receipts; Summary reads 574,067 bytes and 145,938 verified tokens; the newest receipt timestamp is 2026-09-08 14:56, Profile 2's `Bookmarks` mtime in `--inspect`, and the newest commit in the log receipt is `f021270`. The template asks for a `wc -c` line this compile does not carry; the NOTARY FINGERPRINT todo already names that gap. The seal footer under this payload names the archive, and it may be read there rather than guessed, as the 2026-09-06 dismount ruled.

Deed: foo-0c7d652a-1282.zip   (confirm: ls -t foo-*.zip | head -1)


---

## Book Analysis

### Ai Editorial Take
What is most fascinating about this entry is how it reframes application preferences as build artifacts. Instead of accepting that user profiles must remain opaque blobs managed by cloud synchronization services, the author treats the local filesystem as an inspectable database ready for programmatic alignment.

### 🐦 X.com Promo Tweet
```text
Tired of browser sync messing up your bookmarks? Learn how to manage Chrome bookmarks reproducibly using NixOS and Python. Check out the guide: https://mikelev.in/futureproof/browser-api-managing-chrome-bookmarks-nix-python/ #NixOS #Python #Automation
```

### Title Brainstorm
* **Title Option:** The Browser Is the API: Managing Chrome Bookmarks with Nix and Python
  * **Filename:** `browser-api-managing-chrome-bookmarks-nix-python.md`
  * **Rationale:** Directly highlights the central technical realization of the article and names the tools involved.
* **Title Option:** Reproducible Browser State: Declarative Chrome Bookmarks in NixOS
  * **Filename:** `reproducible-browser-state-declarative-chrome-bookmarks-nixos.md`
  * **Rationale:** Emphasizes the methodology of checkable configuration management over application chaos.
* **Title Option:** Harvest, Back Up, Write: Engineering a Replayable Browser Bookmark Pipeline
  * **Filename:** `harvest-backup-write-engineering-replayable-browser-bookmark-pipeline.md`
  * **Rationale:** Focuses on the procedural robustness of the syncing script and safety mechanisms.

### Content Potential And Polish
- **Core Strengths:**
  - Clear diagnostic trajectory from standard API limitations to a custom file-based solution
  - Pragmatic handling of edge cases like Chrome Sync metadata and active process locks
  - Strong emphasis on data preservation via automated harvesting and backup
- **Suggestions For Polish:**
  - Consolidate some of the conversational transcript formatting to streamline readability for book publication
  - Add a brief summary diagram explaining the read-harvest-backup-write cycle

### Next Step Prompts
- Refactor the Python sync script to accept a custom schema validation step for nested folder descriptions.
- Write a follow-up essay exploring how similar local-first patterns apply to browser extensions and local storage.
