Closing the Loop: Replayable AI Workflows and the End of Manual Edits
Setting the Stage: Context for the Curious Book Reader
Important to know in the Age of AI, this essay explores how moving from static build artifacts to run-time evaluation enables seamless, checkable multi-machine synchronization. By anchoring automated operations to verifiable identity mappings rather than fragile directory indices, we bridge the gap between rapid iteration and system stability across distinct computing environments.
TL;DR: This article moves a declarative Chrome-bookmark system from a Linux-only build step to a two-machine workflow. The bookmark list lives in a Nix data file that a Python script now evaluates at run time with nix-instantiate, so editing bookmarks no longer needs sudo nixos-rebuild switch or a materialized JSON file; a git checkout plus a Nix install is the whole deployment on NixOS and macOS alike. Porting to the Mac exposed a design defect: the file keyed profiles by Chrome’s per-machine directory names, and on the second machine that key pointed at a personal profile rather than the work one. The fix keys each entry by a human name plus a list of identities (profile label, account email, or account domain) read from Chrome’s own registry. The script also gained a macOS data directory, a process check that works without procfs, and a second liveness check. Both machines were witnessed projecting the same file into their respective work profiles, and the loop the operator runs off-site reduces to: pull, edit, run, read the verdict, act, push.
Technical Journal Entry Begins
MikeLev.in: Look at the work we just did. I am going to be working on a Mac a lot
and I think I made a mistake having to do a sudo nixos-rebuild switch is not
good for when I’m remote working from the Mac and want to do bookmark
organization work. I can pull the private repo down from GitHub easily enough,
but I can’t generate the bookmarks.json on a Mac machine. Take a look at my
configuration and figure out how.
I can either just keep the bookmark file as bookmarks.json directly in the
repo and eliminate one layer of a build-like step which is always good. The blog
configurations are not nearly as dynamic as bookmarks so the sort of rapid
iteration I see with organizing bookmarks never happens with the blog
configuration, so that’s one possibility. The other possibility is to break
whatever we can from configuration.nix out into a flake.nix that the main
configuration just uses as a matter of course. That way when I’m on Mac I can
nix develop that repo folder.
Fable 5.1 Extra: Context verified.
The ruling. Three ways to get the matrix onto a Mac, and the two you named are the outer pair. (A) A hand-edited bookmarks.json in the repo removes the build step but also removes comments, and the thing you said you want next — a description field so the file can talk about the job — wants a format that tolerates prose beside data; JSON is the one format here that refuses a comment and dies on a trailing comma. (B) A flake.nix with a devShell whose hook materializes the JSON is the same build step wearing a shell, plus a flake to keep evaluating on two platforms, for one file. (C) Keep bookmarks.nix as the source and let the script evaluate it: nix-instantiate --eval --strict --json bookmarks.nix turns a pure-data Nix file into the same JSON the activation script wrote, in under a second, with no sudo, no rebuild, no flake, and no materialized copy, on any machine with Nix on PATH — which is both of yours. That is one layer fewer than today, not one more, and a Nix syntax error becomes a BAD_MATRIX refusal before anything is touched instead of a broken n. blogs.nix stays a module on purpose: it derives firewall rules and .port files, and it changes once a season. The consequence you did not ask about is the better one: with the projector runnable from the checkout on both machines, git is the transport and the Chrome Sync toggle stops being one; sync stays off for bookmarks everywhere, and the same fence refuses on the Mac if it is not.
Moving Beyond Materialized Configurations
What the Mac needs beyond the matrix, read from the script. Its user-data-dir is ~/Library/Application Support/Google/Chrome, not ~/.config/google-chrome. Its pid check uses /proc, which macOS does not have, so today every SingletonLock on a Mac, stale or live, would read RUNNING forever; os.kill(pid, 0) answers on both kernels, the portability the CONTINUATION LADDER earmark already names. And the matrix is keyed by a profile directory name, "Profile 2", which Chrome numbers per machine in creation order — the Mac’s Work profile may be Default or Profile 1. Chrome’s Local State maps directory → the label the profile menu shows, so a matrix key can resolve as a directory first and a label second, and the same file can serve both machines once a Mac receipt prints its label. Under SINGLE-CANDIDATE BLINDNESS the key stays "Profile 2" this turn; the switch is one word after the second candidate exists. Two cautions: the bookmarks.nix in this payload has five entries and no ledger paragraph, and Car 1 is a whole-file write, so commit anything you changed after this compile first. And with two machines each harvesting into a gitignored ledger you get two inboxes; the repo is private, so tracking the ledger is a one-line deletion and is offered as a car you may refuse.
1. PROBES
python3 -c 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: v.get("name") for k, v in d.items()})'
LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 400
grep -c '^ ./bookmarks.nix' /home/mike/repos/nixos/configuration.nix
grep -c '"description"' /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 --inspect Work
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
ls -l /home/mike/.config/pipulate/bookmarks.json
git -C /home/mike/repos/nixos status --short | head -5
git -C /home/mike/repos/nixos log --oneline -3
Probe 1 is a CENSUS with an instrument independent of the script: directory → label for every profile on this machine, labels only, no account emails; it reads the same on both sides and it is the cheapest falsifier for the label mechanism — whatever it prints for Profile 2 is what Probe 6 must resolve. Probe 2 is the Car 1 straddle: BEFORE, nix-instantiate refuses to convert a module function to JSON; AFTER, it prints JSON opening with the Profile 2 key, and that same evaluation is what the script will run. Probe 3 anchors on the indented import line, not the name, so the comment Car 1 adds cannot inflate it: 1 → 0, and the AFTER’s non-zero exit is grep’s no-match. Probe 4 predicts 0 → 2, the two code lines of the validator. Probe 5 is the Car 3 straddle: the Preferences: line leaves and a label= line arrives; the label’s value is the receipt’s to print. Probe 6 discriminates resolution: BEFORE it reads Work ... Bookmarks ABSENT; AFTER it prints Profile 2’s block if Chrome’s label for that directory is literally Work, and stays ABSENT if it is anything else, in which case Probe 1 has already said what it is. Probe 7’s matrix: line must move from the JSON path to bookmarks.nix; its counts reflect whatever the bar holds against the five declared. Probe 8 witnesses the rm half of ignition. Probes 9 and 10 read the uncommitted hand edits and then the three commits.
2. NEXT CONTEXT
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/.gitignore
/home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 -c 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: v.get("name") for k, v in d.items()})'
! LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 400
! grep -c '^ ./bookmarks.nix' /home/mike/repos/nixos/configuration.nix
! grep -c '"description"' /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 --inspect Work
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! ls -l /home/mike/.config/pipulate/bookmarks.json
! git -C /home/mike/repos/nixos status --short | head -5
! git -C /home/mike/repos/nixos log --oneline -3
3. PATCHES
All cars land in ~/repos/nixos; commit by name. Cars 1 and 2 are one change split across two files: after Car 1 alone, the next n refuses with an unknown-option error because a plain attrset is not a module, and the script still reads the stale JSON until Car 2 lands. Apply all three before any rebuild.
Car 1 — bookmarks.nix becomes pure data; configuration.nix stops importing it. Whole-file write, so the Nix airlock does not run; Probe 2 is its gate and witness.
Target: /home/mike/repos/nixos/bookmarks.nix
[[[WRITE_FILE]]]
# ============================================================================
# 🔖 THE BOOKMARK MATRIX (Single Source of Truth for Chrome's bookmarks bar)
# ============================================================================
# PURE DATA, NOT A NixOS MODULE (restructured 2026-09-08 so a Mac can use it).
# This file is one attrset and nothing else. scripts/bookmarks_sync.py
# evaluates it at call time with `nix-instantiate --eval --strict --json`, so
# there is no rebuild, no sudo, no materialized JSON and no flake: a checkout
# of this repository plus a Nix install is the whole deployment, on NixOS and
# on macOS alike. configuration.nix no longer imports this file. blogs.nix
# stays a NixOS module on purpose: it also derives firewall rules and .port
# files, and it changes once a season, not once an hour.
#
# THE EDIT-TO-BAR LOOP: edit here, close Chrome, run `bm` (NixOS) or
# `python3 scripts/bookmarks_sync.py` (Mac, from the checkout). A syntax
# error here is refused as BAD_MATRIX before anything is touched, and the
# old bar is always backed up first. Git is the transport between machines;
# Chrome Sync is not needed for that, and it must be OFF for bookmarks on any
# profile this file declares, or the projector refuses that profile.
#
# The projector, 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
# projection, 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.
#
# PROFILE KEYS RESOLVE TWO WAYS. A key is tried first as a profile DIRECTORY
# ("Profile 2"), then as the LABEL Chrome shows in its profile menu, read from
# the user-data-dir's Local State. Directory numbers are assigned per machine
# in creation order; the label travels with the account. Keep the directory
# key until a Mac `--inspect` receipt prints that machine's label for the Work
# profile; if both machines print the same label, switch this ONE key to it
# and the same file serves both.
#
# ENTRY GRAMMAR: { name = "..."; url = "..."; } is a bookmark;
# { name = "..."; children = [ ... ]; } is a folder. Either may also carry
# description = "..."; -- prose the projector validates as a string and
# otherwise ignores, so this file can say WHY a bookmark earns its place. 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.
{
"Profile 2" = {
bookmark_bar = [
# PLACEHOLDERS, NOT THE WORK BAR: five entries carried over from Default
# while the projector was built. The first real projection ran
# 2026-09-08 14:32 and moved the Work bar's 679 urls into
# bookmarks_harvest.md as 675 paste-ready lines; promote from there.
{ name = ""; url = "https://mail.google.com/mail/u/0/#inbox"; }
{ name = ""; url = "https://calendar.google.com/calendar/u/0/r"; }
{ name = "Gemini"; url = "https://gemini.google.com/app"; }
{ name = "Claude"; url = "https://claude.ai/"; }
{ name = "ChatGPT"; url = "https://chatgpt.com/"; }
# A folder looks like this:
# { name = "Local"; children = [
# { name = "Trilium"; url = "https://nixos.local/"; }
# ]; }
];
# The "Other bookmarks" root. Empty means: wiped on projection, after harvest.
other = [ ];
};
}
[[[END_WRITE_FILE]]]
Target: /home/mike/repos/nixos/configuration.nix
[[[SEARCH]]]
./blogs.nix
./bookmarks.nix
[[[DIVIDER]]]
./blogs.nix
[[[REPLACE]]]
Target: /home/mike/repos/nixos/configuration.nix
[[[SEARCH]]]
# 🔖 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";
[[[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. Since
# 2026-09-08 the script evaluates bookmarks.nix itself at call time, so
# an edit there needs no `n`: that file is pure data and is deliberately
# NOT in the imports list above.
bm = "python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py";
[[[REPLACE]]]
Car 2 — the script evaluates the Nix file and accepts description. Seven single-anchor or contiguous blocks; the AST airlock checks the whole.
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
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
[[[DIVIDER]]]
THE PIPELINE (one stage SHORTER than blogs.nix -> blogs.json since 2026-09-08):
bookmarks.nix --(this script evaluates it with nix-instantiate at call
time: `init` or `bm` on NixOS, by path on macOS)--> <user-data-dir>/<Profile>/Bookmarks
No rebuild, no sudo, no materialized JSON: a checkout plus Nix is the deployment.
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
import shutil
import sys
[[[DIVIDER]]]
import shutil
import subprocess
import sys
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
MATRIX_PATH = Path.home() / ".config" / "pipulate" / "bookmarks.json"
[[[DIVIDER]]]
# THE MATRIX IS THE NIX FILE ITSELF (2026-09-08): bookmarks.nix at the repo
# root is pure data, evaluated at call time by load_matrix() below, so a
# checkout of this repo is the whole deployment on any machine with Nix.
# --matrix still accepts a .json for the odd case.
MATRIX_PATH = HERE.parent / "bookmarks.nix"
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
def load_json(path):
with open(path, "r", encoding="utf-8") as handle:
return json.load(handle)
[[[DIVIDER]]]
def load_json(path):
with open(path, "r", encoding="utf-8") as handle:
return json.load(handle)
def load_matrix(path):
"""The matrix as a dict. A .nix file is evaluated with nix-instantiate
(--strict forces the nested lists, --json makes the output parse); any
other suffix is read as JSON. LD_LIBRARY_PATH is cleared for the child
because a caller inside pipulate's dev shell carries a polluted one and
nix-instantiate dies on library skew (THE UNEXPORTED-SHIM RULE); on a
clean shell the clearing is a no-op."""
if path.suffix != ".nix":
return load_json(path)
nix = shutil.which("nix-instantiate")
if not nix:
raise ValueError("nix-instantiate not on PATH; cannot evaluate %s (pass --matrix some.json instead)" % path)
run = subprocess.run(
[nix, "--eval", "--strict", "--json", str(path)],
capture_output=True, text=True,
env={**os.environ, "LD_LIBRARY_PATH": ""},
)
if run.returncode != 0:
raise ValueError("nix-instantiate failed on %s:\n%s" % (path, run.stderr.strip()))
return json.loads(run.stdout)
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
unknown = sorted(set(entry) - {"name", "url", "children"})
[[[DIVIDER]]]
if not isinstance(entry.get("description", ""), str):
raise ValueError("%s: description must be a string" % spot)
unknown = sorted(set(entry) - {"name", "url", "children", "description"})
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
print("matrix: %s %s" % (matrix_path, "present" if matrix_path.exists() else "ABSENT -- run the rebuild (n) so bookmarks.nix materializes it"))
[[[DIVIDER]]]
print("matrix: %s %s" % (matrix_path, "present" if matrix_path.exists() else "ABSENT -- expected bookmarks.nix at the repo root beside scripts/, or pass --matrix"))
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
matrix = load_json(matrix_path)
[[[DIVIDER]]]
matrix = load_matrix(matrix_path)
[[[REPLACE]]]
Car 3 — macOS: the user-data-dir, a procfs-free liveness check, and profile labels from Local State. The Preferences line that never discriminated retires in the same car, replaced by the instrument that will.
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
USER_DATA_DIR = Path.home() / ".config" / "google-chrome"
[[[DIVIDER]]]
# Chrome's user-data-dir per platform; --user-data-dir overrides either.
USER_DATA_DIR = (
Path.home() / "Library" / "Application Support" / "Google" / "Chrome"
if sys.platform == "darwin"
else Path.home() / ".config" / "google-chrome"
)
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
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."""
[[[DIVIDER]]]
Chrome itself ignores, so it does not block a write. Liveness is
os.kill(pid, 0), which answers on Linux and macOS alike: macOS has no
procfs, so the earlier /proc test would have read every Mac lock as
RUNNING, stale or not. EPERM means the pid exists under another user and
still reads as alive; only ESRCH reads as dead. The lock's name and shape
on macOS are RECALLED, not witnessed: the first Mac --inspect taken with
Chrome open must print RUNNING, or this fence is blind there."""
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
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
[[[DIVIDER]]]
if not pid.isdigit():
return "RUNNING", "SingletonLock -> %s, unparseable pid; refusing conservatively" % target
try:
os.kill(int(pid), 0)
except ProcessLookupError:
return "STALE_LOCK", "SingletonLock -> %s, pid not alive" % target
except PermissionError:
pass
return "RUNNING", "SingletonLock -> %s, pid alive" % target
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
def validate_entries(entries, where):
[[[DIVIDER]]]
def profile_labels(user_data_dir):
"""Directory -> label ("Work", "Person 1") from Chrome's Local State,
the file that maps profile directories to what the profile menu shows.
Directory numbers are assigned per machine in creation order; the label
travels with the account, so a matrix key may name either. An absent or
unreadable Local State yields an empty dict, which makes label resolution
a no-op rather than a crash."""
path = user_data_dir / "Local State"
try:
cache = load_json(path).get("profile", {}).get("info_cache", {})
except (OSError, ValueError, AttributeError):
return {}
return {d: str(v.get("name", "")) for d, v in cache.items() if isinstance(v, dict)}
def resolve_profile(key, user_data_dir, labels):
"""A matrix key is first a profile DIRECTORY, then a profile LABEL."""
if (user_data_dir / key).is_dir():
return key
for directory, label in labels.items():
if label == key:
return directory
return None
def validate_entries(entries, where):
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
def inspect_profile(profile, user_data_dir, bar=0):
[[[DIVIDER]]]
def inspect_profile(profile, user_data_dir, bar=0, label=""):
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
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"
[[[DIVIDER]]]
accounts = "-"
prefs_path = pdir / "Preferences"
if prefs_path.exists():
try:
accounts = len(load_json(prefs_path).get("account_info") or [])
except (OSError, ValueError, AttributeError):
accounts = "unreadable"
[[[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]]]
# label= is what Chrome's profile menu shows, read from Local State; it is
# the host-independent name a matrix key may use, so a Mac's --inspect is
# where the key to switch to gets read. The sync.* Preferences fields that
# printed here read True/-/- in every world across five compiles and
# discriminated nothing; retired 2026-09-08.
print(" label=%s account_info=%s" % (label or "-", accounts))
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
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, max(0, args.bar))
print("VERDICT: INSPECT")
return 0
[[[DIVIDER]]]
labels = profile_labels(user_data_dir)
if args.inspect:
profiles = [resolve_profile(p, user_data_dir, labels) or p for p in 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, max(0, args.bar), labels.get(profile, ""))
print("VERDICT: INSPECT")
return 0
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
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)))
[[[DIVIDER]]]
keys = args.profiles or sorted(matrix)
verdicts = []
for key in keys:
spec = matrix.get(key)
if spec is None:
print("%s: not declared in the matrix; skipped" % key)
verdicts.append("UNDECLARED")
continue
directory = resolve_profile(key, user_data_dir, labels)
if directory is None:
print("%s: no profile directory or label by that name under %s; skipped" % (key, user_data_dir))
verdicts.append("SKIPPED_NO_PROFILE")
continue
if directory != key:
print("%s: resolved by label to profile directory %s" % (key, directory))
verdicts.append(sync_profile(directory, spec, user_data_dir, Path(args.harvest).expanduser(), args.dry_run, state, max(0, args.preview)))
print("VERDICT: " + " ".join("%s=%s" % (k.replace(" ", "_"), v) for k, v in zip(keys, verdicts)))
[[[REPLACE]]]
Car 4 — track the ledger (operator’s ruling; skip it and nothing else changes). The nixos .gitignore is not in this context, so this rides as a line deletion; two machines harvesting into a gitignored file means two inboxes, and the repo is private.
sed -i '/^bookmarks_harvest\.md$/d' /home/mike/repos/nixos/.gitignore && git -C /home/mike/repos/nixos add bookmarks_harvest.md .gitignore
Ignition, Linux half. From a (sys) shell, after all three apps: n — it must succeed, and it retires the activation script from the closure; a failure naming an option called Profile 2 means Car 1’s second block did not land. Then rm -f ~/.config/pipulate/bookmarks.json, which nothing reads any more. Cars 2 and 3 need no further ignition: every probe loads the script by path. Commit per car: cd ~/repos/nixos && git add bookmarks.nix && git commit -am "bookmarks.nix is pure data; configuration.nix stops importing it" && git commit -am "bookmarks_sync.py evaluates the Nix matrix at call time; description field" --allow-empty && git push — or one commit if you prefer; the message on Car 3 is “macOS user-data-dir, os.kill liveness, profile labels from Local State”.
Car 5 — the Mac half, operator lane, when you are there. These cannot ride as ! lines; paste the receipts into the article. Each fence is one inspected step.
cd ~/repos/nixos && git pull && nix-instantiate --version && python3 scripts/bookmarks_sync.py --inspect
Run that with Chrome OPEN: the chrome: line must read RUNNING with a pid, or the running-Chrome fence is blind on macOS and the ride stops here; the per-profile blocks print each directory’s label= and sync_metadata, which together say which directory is the Work profile there and whether Sync owns it.
python3 scripts/bookmarks_sync.py --dry-run --preview 0
Reads the harvest count for the Mac’s Work profile and which refusal a real run would stop at; REFUSED_SYNC_METADATA means bookmark sync is on for that profile on the Mac and comes off first, the same purge you already ran once.
python3 scripts/bookmarks_sync.py
Only with Chrome closed and the dry-run read; a bm for the Mac is one line in its shell rc: alias bm='python3 ~/repos/nixos/scripts/bookmarks_sync.py'.
4. PROMPT
Rule from the LIVE RECEIPTS. Precondition first: the nix-instantiate receipt must read JSON opening with the Profile 2 key; if it reads an error, Car 1 did not land and every later reading is stale, so name that and stop. Then: the import grep must read 0 (its NON-ZERO EXIT 1 is grep's no-match, not a failure); the description grep must read 2; the --inspect "Profile 2" receipt must carry a label= line and no Preferences: line, and quote the label it prints; the --inspect Work receipt either shows Profile 2's block, in which case label resolution is witnessed and the label is literally Work, or reads ABSENT, in which case say what the Local State census printed for Profile 2 and name the one-key change; the dry-run's matrix: line must name bookmarks.nix; ls on bookmarks.json must read No such file if the rm ran, else name that ignition as unfired. Then the Mac half, if the article carries pasted Mac receipts, ruled by lane: the Mac --inspect taken with Chrome open must read RUNNING, or the fence is blind on macOS and the ride stops there; read the Mac's label and sync_metadata for its Work profile and the dry-run's harvest count and refusal. If both machines print the same label, the next car is the one-key switch in bookmarks.nix to that label; if they differ, propose keying per machine and say why. No other 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)
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
(nix) pipulate $ python3 -c 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: v.get("name") for k, v in d.items()})'
LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 400
grep -c '^ ./bookmarks.nix' /home/mike/repos/nixos/configuration.nix
grep -c '"description"' /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 --inspect Work
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
ls -l /home/mike/.config/pipulate/bookmarks.json
git -C /home/mike/repos/nixos status --short | head -5
git -C /home/mike/repos/nixos log --oneline -3
{'Default': 'Person 1', 'Profile 2': 'Work', 'Profile 8': 'Mike'}
error: cannot convert a function to JSON
at /home/mike/repos/nixos/bookmarks.nix:1:1:
1| { lib, ... }:
| ^
2| # ============================================================================
1
0
chrome: RUNNING (SingletonLock -> nixos-17216, pid alive) user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Profile 2 2,483 B mtime 2026-09-08 16:30 version=1 siblings=['Bookmarks', 'Bookmarks.bak']
urls=5 folders=0 bookmark_bar=5 other=0 synced=0
sync_metadata=ABSENT extra_keys=-
checksum stored=1b08876e67bf computed=1b08876e67bf -> MATCH
Preferences: account_info=1 sync.has_setup_completed=True keep_everything_synced=- bookmarks=-
VERDICT: INSPECT
chrome: RUNNING (SingletonLock -> nixos-17216, pid alive) user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Work Bookmarks ABSENT at /home/mike/.config/google-chrome/Work/Bookmarks
VERDICT: INSPECT
chrome: RUNNING (SingletonLock -> nixos-17216, pid alive) user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/.config/pipulate/bookmarks.json present
Profile 2: 5 url bookmarks in the browser, 5 declared, 0 to harvest; shape IN SYNC; codec VERIFIED
VERDICT: Profile_2=IN_SYNC
-rw-r--r-- 1 mike users 316 Sep 8 16:30 /home/mike/.config/pipulate/bookmarks.json
M bookmarks.nix
M en.utf-8.add
M en.utf-8.add.spl
f021270 (HEAD -> main, origin/main, origin/HEAD) Ledger gitignored by choice: repo is private; starter comment records the first sync
daf1a40 Wiring it to correct profile
6491b2a Adding control of work bookmarks from Nix
(nix) pipulate $
2: Context: (AFTER: the same probes re-run by the compiler as ! lines)
# adhoc.txt _ _ _ to set context____ _ _ ___ ____ _ Simpson Couch Gag Here (explain anything to the audience you feel needs it explained)G
# / \ __| | | | | | ___ ___ / ___| | | |/ _ \| _ \| |
# ahe/ _ \ / _` | | |_| |/ _ \ / __| | | | |_| | | | | |_) | | Getting my immutable bookmark system working on Mac too
# 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 ---
# New Context 1 (Edit-in selections from above and add new files immediately below)
# /home/mike/repos/nixos/autognome.py # <-- More rare to have to include, but the true "top" of the muscle memory stack for day-to-day purposes
# /home/mike/repos/nixos/configuration.nix # <-- "Global" IaC context (most of you won't have)
# /home/mike/repos/nixos/blogs.nix
# /home/mike/repos/nixos/bookmarks.nix
# /home/mike/repos/nixos/packages.nix # <-- Full disclosure on pre-flake IaC available apps.
# /home/mike/repos/nixos/services.nix # <-- Running Linux system services.
# /home/mike/repos/nixos/scripts/bookmarks_sync.py
# 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
# New Context 2
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/.gitignore
/home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 -c 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: v.get("name") for k, v in d.items()})'
! LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 400
! grep -c '^ ./bookmarks.nix' /home/mike/repos/nixos/configuration.nix
! grep -c '"description"' /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 --inspect Work
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! ls -l /home/mike/.config/pipulate/bookmarks.json
! 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)
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
✅ 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'.
✅ 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 $ 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'.
✅ 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'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/scripts/bookmarks_sync.py'.
(nix) pipulate $
And the diffs
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
(nix) nixos $ d
diff --git a/bookmarks.nix b/bookmarks.nix
index 8045b34..84ebb1d 100644
--- a/bookmarks.nix
+++ b/bookmarks.nix
@@ -1,19 +1,31 @@
-{ 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:
+# PURE DATA, NOT A NixOS MODULE (restructured 2026-09-08 so a Mac can use it).
+# This file is one attrset and nothing else. scripts/bookmarks_sync.py
+# evaluates it at call time with `nix-instantiate --eval --strict --json`, so
+# there is no rebuild, no sudo, no materialized JSON and no flake: a checkout
+# of this repository plus a Nix install is the whole deployment, on NixOS and
+# on macOS alike. configuration.nix no longer imports this file. blogs.nix
+# stays a NixOS module on purpose: it also derives firewall rules and .port
+# files, and it changes once a season, not once an hour.
+#
+# THE EDIT-TO-BAR LOOP: edit here, close Chrome, run `bm` (NixOS) or
+# `python3 scripts/bookmarks_sync.py` (Mac, from the checkout). A syntax
+# error here is refused as BAD_MATRIX before anything is touched, and the
+# old bar is always backed up first. Git is the transport between machines;
+# Chrome Sync is not needed for that, and it must be OFF for bookmarks on any
+# profile this file declares, or the projector refuses that profile.
+#
+# The projector, 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.
+# projection, 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
@@ -23,45 +35,39 @@
# `sync_metadata`, the script refuses any profile Sync owns, and declaring it
# would only print REFUSED on every init.
#
+# PROFILE KEYS RESOLVE TWO WAYS. A key is tried first as a profile DIRECTORY
+# ("Profile 2"), then as the LABEL Chrome shows in its profile menu, read from
+# the user-data-dir's Local State. Directory numbers are assigned per machine
+# in creation order; the label travels with the account. Keep the directory
+# key until a Mac `--inspect` receipt prints that machine's label for the Work
+# profile; if both machines print the same label, switch this ONE key to it
+# and the same file serves both.
+#
# 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 = "Gemini"; url = "https://gemini.google.com/app"; }
- { name = "Claude"; url = "https://claude.ai/"; }
- { name = "ChatGPT"; url = "https://chatgpt.com/"; }
- # 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
+# { name = "..."; children = [ ... ]; } is a folder. Either may also carry
+# description = "..."; -- prose the projector validates as a string and
+# otherwise ignores, so this file can say WHY a bookmark earns its place. 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.
{
- # 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 = [];
+ "Profile 2" = {
+ bookmark_bar = [
+ # PLACEHOLDERS, NOT THE WORK BAR: five entries carried over from Default
+ # while the projector was built. The first real projection ran
+ # 2026-09-08 14:32 and moved the Work bar's 679 urls into
+ # bookmarks_harvest.md as 675 paste-ready lines; promote from there.
+ { name = ""; url = "https://mail.google.com/mail/u/0/#inbox"; }
+ { name = ""; url = "https://calendar.google.com/calendar/u/0/r"; }
+ { name = "Gemini"; url = "https://gemini.google.com/app"; }
+ { name = "Claude"; url = "https://claude.ai/"; }
+ { name = "ChatGPT"; url = "https://chatgpt.com/"; }
+ # A folder looks like this:
+ # { name = "Local"; children = [
+ # { name = "Trilium"; url = "https://nixos.local/"; }
+ # ]; }
+ ];
+ # The "Other bookmarks" root. Empty means: wiped on projection, after harvest.
+ other = [ ];
};
}
diff --git a/configuration.nix b/configuration.nix
index 05857af..a5a7125 100644
--- a/configuration.nix
+++ b/configuration.nix
@@ -89,7 +89,6 @@ in
./services.nix
./ai-acceleration.nix
./blogs.nix
- ./bookmarks.nix
# ./openclaw.nix
]
# Conditional import: Only import secrets.nix if it exists
@@ -276,7 +275,10 @@ in
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.
+ # `init` runs the same script before its first Chrome launch. Since
+ # 2026-09-08 the script evaluates bookmarks.nix itself at call time, so
+ # an edit there needs no `n`: that file is pure data and is deliberately
+ # NOT in the imports list above.
bm = "python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py";
open = "xdg-open .";
vim = "nvim";
diff --git a/scripts/bookmarks_sync.py b/scripts/bookmarks_sync.py
index 2d10c1d..127254b 100644
--- a/scripts/bookmarks_sync.py
+++ b/scripts/bookmarks_sync.py
@@ -2,9 +2,10 @@
"""
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
+THE PIPELINE (one stage SHORTER than blogs.nix -> blogs.json since 2026-09-08):
+ bookmarks.nix --(this script evaluates it with nix-instantiate at call
+ time: `init` or `bm` on NixOS, by path on macOS)--> <user-data-dir>/<Profile>/Bookmarks
+No rebuild, no sudo, no materialized JSON: a checkout plus Nix is the deployment.
Per declared profile, in this order:
1. READ the profile's current Bookmarks file (Chrome's own JSON).
@@ -54,6 +55,7 @@ import itertools
import json
import os
import shutil
+import subprocess
import sys
import time
import uuid
@@ -61,8 +63,17 @@ 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"
+# THE MATRIX IS THE NIX FILE ITSELF (2026-09-08): bookmarks.nix at the repo
+# root is pure data, evaluated at call time by load_matrix() below, so a
+# checkout of this repo is the whole deployment on any machine with Nix.
+# --matrix still accepts a .json for the odd case.
+MATRIX_PATH = HERE.parent / "bookmarks.nix"
+# Chrome's user-data-dir per platform; --user-data-dir overrides either.
+USER_DATA_DIR = (
+ Path.home() / "Library" / "Application Support" / "Google" / "Chrome"
+ if sys.platform == "darwin"
+ else 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
@@ -101,6 +112,28 @@ def load_json(path):
return json.load(handle)
+def load_matrix(path):
+ """The matrix as a dict. A .nix file is evaluated with nix-instantiate
+ (--strict forces the nested lists, --json makes the output parse); any
+ other suffix is read as JSON. LD_LIBRARY_PATH is cleared for the child
+ because a caller inside pipulate's dev shell carries a polluted one and
+ nix-instantiate dies on library skew (THE UNEXPORTED-SHIM RULE); on a
+ clean shell the clearing is a no-op."""
+ if path.suffix != ".nix":
+ return load_json(path)
+ nix = shutil.which("nix-instantiate")
+ if not nix:
+ raise ValueError("nix-instantiate not on PATH; cannot evaluate %s (pass --matrix some.json instead)" % path)
+ run = subprocess.run(
+ [nix, "--eval", "--strict", "--json", str(path)],
+ capture_output=True, text=True,
+ env={**os.environ, "LD_LIBRARY_PATH": ""},
+ )
+ if run.returncode != 0:
+ raise ValueError("nix-instantiate failed on %s:\n%s" % (path, run.stderr.strip()))
+ return json.loads(run.stdout)
+
+
def chrome_checksum(roots):
"""Chromium's BookmarkCodec checksum, recomputed from a roots dict.
See the module docstring for what this claims and how --inspect
@@ -169,18 +202,27 @@ 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."""
+ Chrome itself ignores, so it does not block a write. Liveness is
+ os.kill(pid, 0), which answers on Linux and macOS alike: macOS has no
+ procfs, so the earlier /proc test would have read every Mac lock as
+ RUNNING, stale or not. EPERM means the pid exists under another user and
+ still reads as alive; only ESRCH reads as dead. The lock's name and shape
+ on macOS are RECALLED, not witnessed: the first Mac --inspect taken with
+ Chrome open must print RUNNING, or this fence is blind there."""
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
+ if not pid.isdigit():
+ return "RUNNING", "SingletonLock -> %s, unparseable pid; refusing conservatively" % target
+ try:
+ os.kill(int(pid), 0)
+ except ProcessLookupError:
+ return "STALE_LOCK", "SingletonLock -> %s, pid not alive" % target
+ except PermissionError:
+ pass
+ return "RUNNING", "SingletonLock -> %s, pid alive" % target
def write_atomic(path, doc):
@@ -204,6 +246,31 @@ def back_up(bpath, profile):
# --- the matrix -------------------------------------------------------------
+def profile_labels(user_data_dir):
+ """Directory -> label ("Work", "Person 1") from Chrome's Local State,
+ the file that maps profile directories to what the profile menu shows.
+ Directory numbers are assigned per machine in creation order; the label
+ travels with the account, so a matrix key may name either. An absent or
+ unreadable Local State yields an empty dict, which makes label resolution
+ a no-op rather than a crash."""
+ path = user_data_dir / "Local State"
+ try:
+ cache = load_json(path).get("profile", {}).get("info_cache", {})
+ except (OSError, ValueError, AttributeError):
+ return {}
+ return {d: str(v.get("name", "")) for d, v in cache.items() if isinstance(v, dict)}
+
+
+def resolve_profile(key, user_data_dir, labels):
+ """A matrix key is first a profile DIRECTORY, then a profile LABEL."""
+ if (user_data_dir / key).is_dir():
+ return key
+ for directory, label in labels.items():
+ if label == key:
+ return directory
+ return None
+
+
def validate_entries(entries, where):
if not isinstance(entries, list):
raise ValueError("%s: expected a list, got %s" % (where, type(entries).__name__))
@@ -219,7 +286,9 @@ def validate_entries(entries, where):
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 not isinstance(entry.get("description", ""), str):
+ raise ValueError("%s: description must be a string" % spot)
+ unknown = sorted(set(entry) - {"name", "url", "children", "description"})
if unknown:
raise ValueError("%s: unknown key(s) %s" % (spot, unknown))
if has_children:
@@ -364,7 +433,7 @@ def clip(text, width=160):
# --- the two modes ----------------------------------------------------------
-def inspect_profile(profile, user_data_dir, bar=0):
+def inspect_profile(profile, user_data_dir, bar=0, label=""):
pdir = user_data_dir / profile
bpath = pdir / "Bookmarks"
if not bpath.exists():
@@ -390,15 +459,11 @@ def inspect_profile(profile, user_data_dir, bar=0):
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 = len(load_json(prefs_path).get("account_info") or [])
+ except (OSError, ValueError, AttributeError):
accounts = "unreadable"
print("%-12s %s B mtime %s version=%s siblings=%s" % (
profile, format(stat.st_size, ","),
@@ -408,9 +473,12 @@ def inspect_profile(profile, user_data_dir, bar=0):
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", "-")))
+ # label= is what Chrome's profile menu shows, read from Local State; it is
+ # the host-independent name a matrix key may use, so a Mac's --inspect is
+ # where the key to switch to gets read. The sync.* Preferences fields that
+ # printed here read True/-/- in every world across five compiles and
+ # discriminated nothing; retired 2026-09-08.
+ print(" label=%s account_info=%s" % (label or "-", accounts))
# 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
@@ -511,33 +579,41 @@ def main(argv=None):
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"))
+ print("matrix: %s %s" % (matrix_path, "present" if matrix_path.exists() else "ABSENT -- expected bookmarks.nix at the repo root beside scripts/, or pass --matrix"))
+ labels = profile_labels(user_data_dir)
if args.inspect:
- profiles = args.profiles or sorted(p.parent.name for p in user_data_dir.glob("*/Bookmarks"))
+ profiles = [resolve_profile(p, user_data_dir, labels) or p for p in 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, max(0, args.bar))
+ inspect_profile(profile, user_data_dir, max(0, args.bar), labels.get(profile, ""))
print("VERDICT: INSPECT")
return 0
if not matrix_path.exists():
print("VERDICT: NO_MATRIX")
return 1
try:
- matrix = load_json(matrix_path)
+ matrix = load_matrix(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)
+ keys = args.profiles or sorted(matrix)
verdicts = []
- for profile in profiles:
- spec = matrix.get(profile)
+ for key in keys:
+ spec = matrix.get(key)
if spec is None:
- print("%s: not declared in the matrix; skipped" % profile)
+ print("%s: not declared in the matrix; skipped" % key)
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)))
+ directory = resolve_profile(key, user_data_dir, labels)
+ if directory is None:
+ print("%s: no profile directory or label by that name under %s; skipped" % (key, user_data_dir))
+ verdicts.append("SKIPPED_NO_PROFILE")
+ continue
+ if directory != key:
+ print("%s: resolved by label to profile directory %s" % (key, directory))
+ verdicts.append(sync_profile(directory, spec, user_data_dir, Path(args.harvest).expanduser(), args.dry_run, state, max(0, args.preview)))
+ print("VERDICT: " + " ".join("%s=%s" % (k.replace(" ", "_"), v) for k, v in zip(keys, verdicts)))
return 2 if any(v.startswith("REFUSED") for v in verdicts) else 0
(nix) nixos $ m
📝 Committing: chore: Update bookmarks.nix to a more streamlined configuration
[main bccc8ee] chore: Update bookmarks.nix to a more streamlined configuration
3 files changed, 164 insertions(+), 80 deletions(-)
(nix) nixos $ git push
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 48 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 4.96 KiB | 4.96 MiB/s, done.
Total 6 (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
d949224..bccc8ee main -> main
(nix) nixos $
Nice, that’s the first time I used the nix develop shell environment commands
such as d and m from a repo other than Pipulate. That’s interesting. It
means I can do a nix develop and then change directories and get many of the
advantages of that shell enviornment such as the Prompt Fu commands in other
repos. Useful.
(nix) nixos $ 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'.
(nix) nixos $ sed -i '/^bookmarks_harvest\.md$/d' /home/mike/repos/nixos/.gitignore && git -C /home/mike/repos/nixos add bookmarks_harvest.md .gitignore
(nix) nixos $ d
(nix) nixos $ n
building Nix...
building the system configuration...
unpacking 'https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz' into the Git cache...
this derivation will be built:
/nix/store/2bq9s93wz70ar69ynsv699pxbphrgyrv-nixos-system-nixos-25.05.813814.ac62194c3917.drv
building '/nix/store/2bq9s93wz70ar69ynsv699pxbphrgyrv-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/klim3jz4fm2vrndq8abcp9sa44253mal-nixos-system-nixos-25.05.813814.ac62194c3917
(nix) nixos $ rm -f ~/.config/pipulate/bookmarks.json
(nix) nixos $ cd ~/repos/nixos && git add bookmarks.nix && git commit -am "bookmarks.nix is pure data; configuration.nix stops importing it" && git commit -am "bookmarks_sync.py evaluates the Nix matrix at call time; description field" --allow-empty && git push
[main 9b1d92a] bookmarks.nix is pure data; configuration.nix stops importing it
2 files changed, 713 insertions(+), 1 deletion(-)
create mode 100644 bookmarks_harvest.md
[main 230c64c] bookmarks_sync.py evaluates the Nix matrix at call time; description field
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 48 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 32.28 KiB | 8.07 MiB/s, done.
Total 5 (delta 3), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (3/3), completed with 2 local objects.
To github.com:miklevin/nixos-config.git
bccc8ee..230c64c main -> main
(nix) nixos $
Pshwew! Okay. And now the step on the Mac:
michaellevin@MichaelMacBook-Pro repos % cd nixos
michaellevin@MichaelMacBook-Pro nixos % git pull
remote: Enumerating objects: 427, done.
remote: Counting objects: 100% (265/265), done.
remote: Compressing objects: 100% (139/139), done.
remote: Total 427 (delta 194), reused 196 (delta 126), pack-reused 162 (from 1)
Receiving objects: 100% (427/427), 195.99 KiB | 1.27 MiB/s, done.
Resolving deltas: 100% (280/280), completed with 2 local objects.
From github.com:miklevin/nixos-config
c47425a..230c64c main -> origin/main
Updating c47425a..230c64c
Fast-forward
.gitignore | 2 +
ai.py | 73 -------------
autognome.py | 269 ++++++++++++++++++++++++++++++++--------------
blogs.nix | 133 +++++++++++++++++++++++
bookmarks.nix | 73 +++++++++++++
bookmarks_harvest.md | 713 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
configuration.nix | 104 ++++++++++++++----
en.utf-8.add | 185 ++++++++++++++++++++++++++++++++
en.utf-8.add.spl | Bin 4846 -> 6764 bytes
init.lua | 444 ----------------------------------------------------------------------------
packages.nix | 56 ++++++++--
scripts/backup-essential.py | 88 ++++++++++++++-
scripts/backup-home.py | 7 +-
scripts/bookmarks_sync.py | 621 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
services.nix | 21 +++-
15 files changed, 2152 insertions(+), 637 deletions(-)
delete mode 100644 ai.py
create mode 100644 blogs.nix
create mode 100644 bookmarks.nix
create mode 100644 bookmarks_harvest.md
delete mode 100644 init.lua
create mode 100644 scripts/bookmarks_sync.py
michaellevin@MichaelMacBook-Pro nixos % cd ~/repos/nixos && git pull && nix-instantiate --version && python3 scripts/bookmarks_sync.py --inspect
Already up to date.
nix-instantiate (Determinate Nix 3.19.1) 2.34.6
chrome: STOPPED (no SingletonLock) user_data_dir=/Users/michaellevin/Library/Application Support/Google/Chrome
matrix: /Users/michaellevin/repos/nixos/bookmarks.nix present
Profile 1 1,026 B mtime 2026-09-08 15:47 version=1 siblings=['Bookmarks', 'Bookmarks.bak']
urls=0 folders=0 bookmark_bar=0 other=0 synced=0
sync_metadata=ABSENT extra_keys=-
checksum stored=942764d02172 computed=942764d02172 -> MATCH
label=botify.com account_info=1
Profile 2 92,729 B mtime 2026-07-13 18:12 version=1 siblings=['Bookmarks', 'Bookmarks.bak']
urls=121 folders=8 bookmark_bar=107 other=14 synced=0
sync_metadata=PRESENT (42,548 chars) extra_keys=['sync_metadata']
checksum stored=ca9b16759b6c computed=ca9b16759b6c -> MATCH
label=Mike account_info=2
VERDICT: INSPECT
michaellevin@MichaelMacBook-Pro nixos %
4: Prompt: Rule from the LIVE RECEIPTS. Precondition first: the nix-instantiate receipt must read JSON opening with the Profile 2 key; if it reads an error, Car 1 did not land and every later reading is stale, so name that and stop. Then: the import grep must read 0 (its NON-ZERO EXIT 1 is grep’s no-match, not a failure); the description grep must read 2; the –inspect “Profile 2” receipt must carry a label= line and no Preferences: line, and quote the label it prints; the –inspect Work receipt either shows Profile 2’s block, in which case label resolution is witnessed and the label is literally Work, or reads ABSENT, in which case say what the Local State census printed for Profile 2 and name the one-key change; the dry-run’s matrix: line must name bookmarks.nix; ls on bookmarks.json must read No such file if the rm ran, else name that ignition as unfired. Then the Mac half, if the article carries pasted Mac receipts, ruled by lane: the Mac –inspect taken with Chrome open must read RUNNING, or the fence is blind on macOS and the ride stops there; read the Mac’s label and sync_metadata for its Work profile and the dry-run’s harvest count and refusal. If both machines print the same label, the next car is the one-key switch in bookmarks.nix to that label; if they differ, propose keying per machine and say why. No other patches unless a receipt convicts one.
5: Deliverables: A top-down “what to do” and how process, because that’s what all this really is with the bookmark organization and also operation stick bug and the Mother Cat Kata walks. Soon I will be able to:
- Do it the “old way” with unlimited infinitely variable magic hand-gestures in the browser but with “best guesses” because my bookmarks are all in the right order and I can at least be like first I click this and then I click that and so on. Automating something that’s as easy as riding a bicycle doesn’t happen until you’ve internalized all the nuances of riding a bicycle automatically by habit without thinking about all the little details every time.
- The second project will be hooking to this where I simply turn the Flight Data Recorder on at the beginning of a walk and all the back-end API HAR downloads occur for all the pages you visited along with the captured hydrated DOM and the rest of what Pipulate LLM Optics captures and generates.
And then finally after that runs there will be a tiny adhocwalk.txt file
somewhere that I can layer in the jira and gmail commands so that anything
that wasn’t captured with the browser FDR, and then that file is used as the new
router file for prompt_foo.py with a prompt.md file added as the standard
procedure there, and now there’s a very nice QA-bundle that handles the number
one Catch-22 itch of our time:
- LLMs make things easier when it comes to hitting multiple data-sources and knitting it all together into an answer but you can’t put your name behind it until you assure the quality the output, presumably through double-checking it’s work by doing the same things it did.
- But doing the same things it did is actually quite difficult, which is why you used the LLM in the first place and while if all the information the LLM used was actually provided to the user it could be done, this is generally not the case and there are opaque or at minimum difficult-to-reproduce steps that the LLM did.
Fable 5.1 Extra: Context verified.
Ruling from the live receipts. Every precondition held: nix-instantiate printed JSON opening with the Profile 2 key, the import grep read 0 under grep’s no-match exit, the description grep read 2, --inspect "Profile 2" carries label=Work and no Preferences: line, --inspect Work printed Profile 2’s block — label resolution witnessed, and the label is literally Work — the dry-run’s matrix: line names bookmarks.nix and reads IN_SYNC on five against five, so the evaluated Nix produces the same shape the materialized JSON did, and bookmarks.json is gone. Chrome’s pid was 17216 on both taps: the whole train landed under a running browser and never needed it closed. The git log is the one receipt that lies: bccc8ee carries all three cars under an m message about a “streamlined configuration,” 9b1d92a — labeled “pure data” — actually carries Car 4, the un-ignored 713-line ledger, and 230c64c is the --allow-empty commit, which is empty. No rewrite (the Mac has already pulled 230c64c); this article is the record, and it is another specimen for the ai.py numstat todo.
The Mac half, ruled by lane. Four mechanisms witnessed in one operator-lane receipt: the checkout-relative matrix path, the macOS user-data-dir, nix-instantiate under Determinate Nix, and Local State read for labels. Then the finding this compile was built to surface, and it is worse than “the labels differ”: on the Mac, Profile 1 is the Work profile (labeled botify.com, zero bookmarks, no sync_metadata — a clean slate), and Profile 2 is your personal profile (Mike, 121 synced bookmarks). The matrix key "Profile 2" therefore resolved, by directory, to the wrong profile on the second machine, and the only thing between a real run and a harvest-and-wipe of your personal Mac bar was the sync fence, which happens to be up there. SINGLE-CANDIDATE BLINDNESS in its expensive form: the key was right on the only machine that existed. So the caboose’s “if the labels differ, key per machine” is answered by removing directories from the key path entirely: a matrix entry is a human name, and a match list names what Chrome calls that profile on any machine — label, account email, or account domain. "Work" on NixOS and "botify.com" on the Mac both ride that list today, both paths witnessed by receipt. One reading stays open: the Mac’s chrome: line read STOPPED, and the article does not say whether Chrome was open. Closed, that is correct; open, the fence is blind on macOS. The receipt cannot tell those worlds apart, so a pgrep for the Chrome process rides beside the lock on macOS from this car on, and the next Mac tap is taken with a Chrome window visibly open.
Establishing a Replayable Automation Loop
1. PROBES
python3 -c 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: [f for f in ("name","user_name","hosted_domain","gaia_name") if f in v] for k, v in d.items()})'
LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 120
grep -c 'match = \[' /home/mike/repos/nixos/bookmarks.nix
grep -c '^def profile_info\|^def resolve_profile' /home/mike/repos/nixos/scripts/bookmarks_sync.py
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Work
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
git -C /home/mike/repos/nixos status --short | head -5
git -C /home/mike/repos/nixos log --oneline -3
Probe 1 is a CENSUS of field NAMES, no values: which of name, user_name, hosted_domain, gaia_name each profile carries in Local State. It reads the same on both sides and it gates the domain path: if user_name is absent, domain= will read - everywhere and that path is dead code to name, while the label path needs nothing from it. Probe 2 is the Car 1 straddle at 120 bytes: the key moves from Profile 2 to Work and a match array appears (Nix sorts attributes, so it sits after bookmark_bar). Probe 3 predicts 0 → 1; Probe 4 predicts 1 → 2, the resolve_profile def already existing. Probe 5 gains a domain= field after label=Work; its value is the receipt’s to print. Probe 6 is the discriminator: AFTER it must print Work: resolved to profile directory Profile 2 followed by the five-on-five IN SYNC line and VERDICT: Work=IN_SYNC — or REFUSED_AMBIGUOUS naming a second directory, which means another profile on this box carries a matching account domain and the match list narrows to the label alone. Probes 7 and 8 read the working tree and the two new commits.
2. NEXT CONTEXT
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/.gitignore
/home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 -c 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: [f for f in ("name","user_name","hosted_domain","gaia_name") if f in v] for k, v in d.items()})'
! LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 120
! grep -c 'match = \[' /home/mike/repos/nixos/bookmarks.nix
! grep -c '^def profile_info\|^def resolve_profile' /home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Work
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! git -C /home/mike/repos/nixos status --short | head -5
! git -C /home/mike/repos/nixos log --oneline -3
3. PATCHES
Cars 1 and 2 are one change in two files: after Car 1 alone the validator refuses match as an unknown key and every run prints BAD_MATRIX — a refusal, not a wipe, but init would print exit 1 until Car 2 lands. Apply all three before running bm anywhere.
Car 1 — a key is a name; match says what Chrome calls it on each machine. The Nix airlock parses it.
Target: /home/mike/repos/nixos/bookmarks.nix
[[[SEARCH]]]
# PROFILE KEYS RESOLVE TWO WAYS. A key is tried first as a profile DIRECTORY
# ("Profile 2"), then as the LABEL Chrome shows in its profile menu, read from
# the user-data-dir's Local State. Directory numbers are assigned per machine
# in creation order; the label travels with the account. Keep the directory
# key until a Mac `--inspect` receipt prints that machine's label for the Work
# profile; if both machines print the same label, switch this ONE key to it
# and the same file serves both.
[[[DIVIDER]]]
# A KEY IS A NAME, NEVER A DIRECTORY (convicted 2026-09-08 by the second
# machine). The first Mac --inspect read "Profile 2" as the PERSONAL profile
# there ("Mike", 121 synced bookmarks) while the Work profile sat in
# "Profile 1" labeled "botify.com"; on NixOS "Profile 2" is labeled "Work".
# Directory numbers are per machine, in creation order, so a directory key
# in a shared file names a different profile on every checkout, and only the
# sync fence kept a personal bar from being harvested and wiped. So each key
# below is a human name, and its `match` list says what Chrome calls that
# profile on ANY machine: a label from the profile menu, an account email,
# or an account domain. Exactly one profile on a machine may match; none
# skips the entry there, two or more refuse. `bm --inspect` prints label=
# and domain= for every profile on disk, which is where a new machine's
# match entry gets read before it is written here.
[[[REPLACE]]]
Target: /home/mike/repos/nixos/bookmarks.nix
[[[SEARCH]]]
"Profile 2" = {
bookmark_bar = [
[[[DIVIDER]]]
"Work" = {
# What Chrome calls this profile per machine: the NixOS box labels it
# "Work", the Mac labels it "botify.com". Either matches; so would the
# account's email or its domain.
match = [ "Work" "botify.com" ];
bookmark_bar = [
[[[REPLACE]]]
Car 2 — the script resolves identities, never directories. Seven blocks; the AST airlock checks the whole.
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
def profile_labels(user_data_dir):
"""Directory -> label ("Work", "Person 1") from Chrome's Local State,
the file that maps profile directories to what the profile menu shows.
Directory numbers are assigned per machine in creation order; the label
travels with the account, so a matrix key may name either. An absent or
unreadable Local State yields an empty dict, which makes label resolution
a no-op rather than a crash."""
path = user_data_dir / "Local State"
try:
cache = load_json(path).get("profile", {}).get("info_cache", {})
except (OSError, ValueError, AttributeError):
return {}
return {d: str(v.get("name", "")) for d, v in cache.items() if isinstance(v, dict)}
[[[DIVIDER]]]
def profile_info(user_data_dir):
"""Directory -> {label, account, domain} from Chrome's Local State, the
file that maps profile directories to what the profile menu shows (name)
and which account is signed in (user_name). Directory numbers are per
machine in creation order; the label and the account travel with the
profile, so a matrix entry names those and never a directory. The domain
is derived from user_name so a Workspace profile can be matched by its
organisation without writing an email into the matrix. An absent or
unreadable Local State yields an empty dict, which makes resolution skip
rather than crash."""
path = user_data_dir / "Local State"
try:
cache = load_json(path).get("profile", {}).get("info_cache", {})
except (OSError, ValueError, AttributeError):
return {}
out = {}
for directory, entry in cache.items():
if not isinstance(entry, dict):
continue
account = str(entry.get("user_name", "") or "")
out[directory] = {
"label": str(entry.get("name", "") or ""),
"account": account,
"domain": account.rsplit("@", 1)[-1].lower() if "@" in account else "",
}
return out
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
def resolve_profile(key, user_data_dir, labels):
"""A matrix key is first a profile DIRECTORY, then a profile LABEL."""
if (user_data_dir / key).is_dir():
return key
for directory, label in labels.items():
if label == key:
return directory
return None
[[[DIVIDER]]]
def resolve_profile(key, spec, profiles):
"""The profile directories on THIS machine that a matrix entry means.
The entry's `match` list, or its key when it has none, names IDENTITIES:
a label as the profile menu shows it, an account email, or an account
domain. Never a directory: "Profile 2" was the Work profile on one
machine and a personal one on the other (2026-09-08), and a shared file
cannot carry a per-machine number. The caller treats one hit as the
answer, none as a skip, and several as a refusal."""
wanted = [w for w in (spec.get("match") or [key]) if isinstance(w, str) and w]
hits = []
for directory, info in profiles.items():
if any(w == info["label"] or w == info["account"] or w.lower() == info["domain"] for w in wanted):
hits.append(directory)
return sorted(hits)
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
raise ValueError("matrix root must be an attrset keyed by profile directory name")
[[[DIVIDER]]]
raise ValueError("matrix root must be an attrset keyed by a profile name (see match)")
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
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)))
[[[DIVIDER]]]
match = spec.get("match", [])
if not isinstance(match, list) or not all(isinstance(m, str) and m for m in match):
raise ValueError("%s: match must be a list of non-empty strings (labels, account emails, or account domains)" % profile)
unknown = sorted(set(spec) - set(MATRIX_ROOTS) - {"match"})
if unknown:
raise ValueError("%s: unknown key(s) %s; only %s and match are declarable" % (profile, unknown, list(MATRIX_ROOTS)))
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
def inspect_profile(profile, user_data_dir, bar=0, label=""):
[[[DIVIDER]]]
def inspect_profile(profile, user_data_dir, bar=0, info=None):
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
# label= is what Chrome's profile menu shows, read from Local State; it is
# the host-independent name a matrix key may use, so a Mac's --inspect is
# where the key to switch to gets read. The sync.* Preferences fields that
# printed here read True/-/- in every world across five compiles and
# discriminated nothing; retired 2026-09-08.
print(" label=%s account_info=%s" % (label or "-", accounts))
[[[DIVIDER]]]
# label= is what Chrome's profile menu shows and domain= is the signed-in
# account's domain, both from Local State; a matrix entry's match list
# names either, never a directory. The email itself is not printed: this
# line rides compile receipts. The sync.* Preferences fields that printed
# here read True/-/- in every world and discriminated nothing (retired).
info = info or {}
print(" label=%s domain=%s account_info=%s" % (info.get("label") or "-", info.get("domain") or "-", accounts))
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
labels = profile_labels(user_data_dir)
if args.inspect:
profiles = [resolve_profile(p, user_data_dir, labels) or p for p in 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, max(0, args.bar), labels.get(profile, ""))
print("VERDICT: INSPECT")
return 0
[[[DIVIDER]]]
profiles_info = profile_info(user_data_dir)
if args.inspect:
# Read-only, so a positional may still be a directory; anything else
# resolves as an identity the way a matrix entry would.
profiles = []
for p in args.profiles:
hits = [p] if (user_data_dir / p).is_dir() else resolve_profile(p, {}, profiles_info)
profiles.extend(hits or [p])
profiles = profiles or sorted(p.parent.name for p in user_data_dir.glob("*/Bookmarks"))
for profile in profiles:
inspect_profile(profile, user_data_dir, max(0, args.bar), profiles_info.get(profile))
print("VERDICT: INSPECT")
return 0
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
directory = resolve_profile(key, user_data_dir, labels)
if directory is None:
print("%s: no profile directory or label by that name under %s; skipped" % (key, user_data_dir))
verdicts.append("SKIPPED_NO_PROFILE")
continue
if directory != key:
print("%s: resolved by label to profile directory %s" % (key, directory))
verdicts.append(sync_profile(directory, spec, user_data_dir, Path(args.harvest).expanduser(), args.dry_run, state, max(0, args.preview)))
[[[DIVIDER]]]
hits = resolve_profile(key, spec, profiles_info)
if not hits:
print("%s: no profile on this machine matches %s; skipped" % (key, spec.get("match") or [key]))
verdicts.append("SKIPPED_NO_PROFILE")
continue
if len(hits) > 1:
print("%s: REFUSED: %s match more than one profile here (%s); narrow the match list" % (key, spec.get("match") or [key], ", ".join(hits)))
verdicts.append("REFUSED_AMBIGUOUS")
continue
directory = hits[0]
print("%s: resolved to profile directory %s (label=%s domain=%s)" % (key, directory, profiles_info[directory]["label"] or "-", profiles_info[directory]["domain"] or "-"))
verdicts.append(sync_profile(directory, spec, user_data_dir, Path(args.harvest).expanduser(), args.dry_run, state, max(0, args.preview)))
[[[REPLACE]]]
Car 3 — macOS gets a second liveness check, and a shared ledger names the machine that harvested.
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
import shutil
import subprocess
[[[DIVIDER]]]
import shutil
import socket
import subprocess
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
on macOS are RECALLED, not witnessed: the first Mac --inspect taken with
Chrome open must print RUNNING, or this fence is blind there."""
[[[DIVIDER]]]
on macOS are RECALLED, not witnessed, and the first Mac --inspect read
STOPPED with the browser's state unrecorded, so on macOS a pgrep for the
Google Chrome process rides beside the lock and either one reads as
RUNNING. A second Chrome on another user-data-dir over-refuses there;
that is the conservative side."""
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
lock = user_data_dir / "SingletonLock"
[[[DIVIDER]]]
if sys.platform == "darwin":
try:
pgrep = subprocess.run(["pgrep", "-x", "Google Chrome"], capture_output=True, text=True)
except FileNotFoundError:
pgrep = None
if pgrep is not None and pgrep.stdout.split():
return "RUNNING", "pgrep found a Google Chrome process, pid %s" % pgrep.stdout.split()[0]
lock = user_data_dir / "SingletonLock"
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
block = ["## %s %s (%d harvested of %d url bookmarks in the browser)" % (stamp, profile, len(lines), in_browser), "", FENCE + "nix"]
[[[DIVIDER]]]
block = ["## %s %s on %s (%d harvested of %d url bookmarks in the browser)" % (stamp, profile, socket.gethostname(), len(lines), in_browser), "", FENCE + "nix"]
[[[REPLACE]]]
Ignition. None on NixOS: bookmarks.nix is evaluated at call time and every probe loads the script by path; no n, since nothing in the system closure reads either file. Commit by name in two stories, and the message is the operator’s this time rather than m’s: cd ~/repos/nixos && git commit -am "Matrix keys are names with a match list, never directories: Profile 2 meant a personal profile on the Mac" && git push, then the Mac-side cars below, which begin with git pull.
Car 4 — the Mac lane, operator’s terminal, one fence per inspected step. With a Chrome window visibly OPEN:
cd ~/repos/nixos && git pull && ls -la "$HOME/Library/Application Support/Google/Chrome/" | grep -i singleton; python3 scripts/bookmarks_sync.py --inspect "Profile 1"
The grep says whether macOS Chrome writes SingletonLock at all; the chrome: line must read RUNNING by the lock or by pgrep, and if it still reads STOPPED beside an open window both checks are blind on macOS and no real run happens there until one is not.
python3 scripts/bookmarks_sync.py --dry-run --preview 0
Must print Work: resolved to profile directory Profile 1 (label=botify.com ...), zero in the browser against five declared, and would stop at REFUSED_CHROME_RUNNING; anything resolving to Profile 2 there is the trap still open and the ride stops.
python3 scripts/bookmarks_sync.py
Chrome closed, dry-run read: Work=WRITTEN, five bookmarks, then open Chrome and look at the Work profile’s bar. A second run reads IN_SYNC, and from then on both machines project the same file.
4. PROMPT
Rule from the LIVE RECEIPTS. Precondition: the nix-instantiate receipt must open with the Work key; if it still reads Profile 2, Car 1 did not land, name that and stop. Then: the match grep must read 1 and the def grep 2; the --inspect Work receipt must print Profile 2's block with label=Work and a domain= field, and quote the domain; the field census says whether user_name is present on this machine, and if it is absent, say that the domain path is unexercised here and the label path carried the resolution; the dry-run must read Work: resolved to profile directory Profile 2 and VERDICT: Work=IN_SYNC, or REFUSED_AMBIGUOUS naming the second directory, in which case rule that the match list narrows to the label and emit that one-line car. Then the Mac receipts, if pasted, by lane: the singleton listing and the chrome: line taken beside an open window decide whether either liveness check works on macOS, and a STOPPED there stops the ride; the Mac dry-run must resolve to Profile 1 by the botify.com label, never Profile 2; a WRITTEN of five and a following IN_SYNC close the two-machine loop. No other patches unless a receipt convicts one.
5. EXTERNAL DELIVERABLES
Carried, since you asked for the top-down process and it lives in your head and this article rather than in any one file. Every noun below already exists in the repo except the one marked next.
- Declare the places.
bookmarks.nixis the ordered list of everywhere the job takes you; a folder is a recurring job and its order is click order. Edit it on either machine, close Chrome, runbmthere; the same bar appears on every machine because git is the transport and the projector refuses any profile Chrome Sync owns. - Ride by hand until it is a bicycle. Click along the folder in order. Anything you bookmark on the way survives one projection and lands in
bookmarks_harvest.md, now tracked, so both machines share one inbox; promote or let go weekly. The order is fixed, so the hand-gestures become the same gestures every time — that is the internalizing, and nothing gets automated before it. - Turn the folder into a trail.
bookmark_import.pyalready turns a bookmarks export into a.walk.mdauthoring surface;walk_compile.pyturns that into a trail YAML;walk_cartridge.py sealfreezes it. Next: read the folder straight out ofbookmarks.nix, since the trail’s stop list and the bar’s folder are now the same declared data. - Ride the trail with the recorder on.
mothercatruns the four moves — SETTLE once at the login, CAPTURE at every stop (hydrated DOM, headers,network_log.jsonl, the HAR-class wire truth), NARRATE and FENCE, ADVANCE — andcaptures.mdbanks the bytes before each ADVANCE, so a partial ride still has its evidence. - Let the ride write the router. A complete ride already writes
adhocwalk.txtbesideadhoc.txt(97feb327): the captured archives, named in router grammar, labeled unsanitized. - Layer what the browser could not see. Add
! python scripts/connectors/jira.py <issue>and! python scripts/connectors/gmail.py <id>lines for the ticket and the thread, pointPIPULATE_ADHOC_FILEat the walk’s router or paste its lines intoadhoc.txt, writeprompt.md, compile. - The compile is the QA bundle. Every source the model read is inside the sealed cartridge with a deed outside it. The Catch-22 dissolves because verification is no longer re-doing the work: a second reader opens the same cartridge and checks the same bytes, and when someone must re-walk, the bookmark order from step 1 is the script they follow.
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)
(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 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: [f for f in ("name","user_name","hosted_domain","gaia_name") if f in v] for k, v in d.items()})'
LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 120
grep -c 'match = \[' /home/mike/repos/nixos/bookmarks.nix
grep -c '^def profile_info\|^def resolve_profile' /home/mike/repos/nixos/scripts/bookmarks_sync.py
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Work
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
git -C /home/mike/repos/nixos status --short | head -5
git -C /home/mike/repos/nixos log --oneline -3
{'Default': ['name', 'user_name', 'hosted_domain', 'gaia_name'], 'Profile 2': ['name', 'user_name', 'hosted_domain', 'gaia_name'], 'Profile 8': ['name', 'user_name', 'hosted_domain', 'gaia_name']}
0"Profile 2":{"bookmark_bar":[{"name":"","url":"https://mail.google.com/mail/u/0/#inbox"},{"name":"","url":"https://cale
1
chrome: RUNNING (SingletonLock -> nixos-17216, pid alive) user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/repos/nixos/bookmarks.nix present
Profile 2 2,483 B mtime 2026-09-08 16:30 version=1 siblings=['Bookmarks', 'Bookmarks.bak']
urls=5 folders=0 bookmark_bar=5 other=0 synced=0
sync_metadata=ABSENT extra_keys=-
checksum stored=1b08876e67bf computed=1b08876e67bf -> MATCH
label=Work account_info=1
VERDICT: INSPECT
chrome: RUNNING (SingletonLock -> nixos-17216, pid alive) user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/repos/nixos/bookmarks.nix present
Profile 2: 5 url bookmarks in the browser, 5 declared, 0 to harvest; shape IN SYNC; codec VERIFIED
VERDICT: Profile_2=IN_SYNC
230c64c (HEAD -> main, origin/main, origin/HEAD) bookmarks_sync.py evaluates the Nix matrix at call time; description field
9b1d92a bookmarks.nix is pure data; configuration.nix stops importing it
bccc8ee chore: Update bookmarks.nix to a more streamlined configuration
(nix) pipulate $
2: Context: (AFTER: the same probes re-run by the compiler as ! lines)
# adhoc.txt _ _ _ to set context____ _ _ ___ ____ _ Simpson Couch Gag Here (explain anything to the audience you feel needs it explained)G
# / \ __| | | | | | ___ ___ / ___| | | |/ _ \| _ \| |
# ahe/ _ \ / _` | | |_| |/ _ \ / __| | | | |_| | | | | |_) | | Getting my immutable bookmark system working on Mac too.
# ahc ___ \ (_| | | _ | (_) | (__ | |___| _ | |_| | __/|_| So far so good.
# /_/ \_\__,_| |_| |_|\___/ \___| \____|_| |_|\___/|_| (_)
# 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 ---
# New Context 1 (Edit-in selections from above and add new files immediately below)
# /home/mike/repos/nixos/autognome.py # <-- More rare to have to include, but the true "top" of the muscle memory stack for day-to-day purposes
# /home/mike/repos/nixos/configuration.nix # <-- "Global" IaC context (most of you won't have)
# /home/mike/repos/nixos/blogs.nix
# /home/mike/repos/nixos/bookmarks.nix
# /home/mike/repos/nixos/packages.nix # <-- Full disclosure on pre-flake IaC available apps.
# /home/mike/repos/nixos/services.nix # <-- Running Linux system services.
# /home/mike/repos/nixos/scripts/bookmarks_sync.py
# 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
# New Context 2
# /home/mike/repos/nixos/autognome.py
# /home/mike/repos/nixos/configuration.nix
# /home/mike/repos/nixos/bookmarks.nix
# /home/mike/repos/nixos/.gitignore
# /home/mike/repos/nixos/scripts/bookmarks_sync.py
# ! python3 -c 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: v.get("name") for k, v in d.items()})'
# ! LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 400
# ! grep -c '^ ./bookmarks.nix' /home/mike/repos/nixos/configuration.nix
# ! grep -c '"description"' /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 --inspect Work
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
# ! ls -l /home/mike/.config/pipulate/bookmarks.json
# ! git -C /home/mike/repos/nixos status --short | head -5
# ! git -C /home/mike/repos/nixos log --oneline -3
# New Context 3
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/.gitignore
/home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 -c 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: [f for f in ("name","user_name","hosted_domain","gaia_name") if f in v] for k, v in d.items()})'
! LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 120
! grep -c 'match = \[' /home/mike/repos/nixos/bookmarks.nix
! grep -c '^def profile_info\|^def resolve_profile' /home/mike/repos/nixos/scripts/bookmarks_sync.py
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Work
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! 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)
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
✅ PATCH ALREADY APPLIED: '/home/mike/repos/nixos/bookmarks.nix' already contains the replacement block.
✅ PATCH ALREADY APPLIED: '/home/mike/repos/nixos/bookmarks.nix' already contains the replacement block.
(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'.
✅ 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 $ 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 $
It’s not for today but I wonder if I could cd into a different folder than
Pipualte and use the whole patch, app, d, m procedure from inside a repo
other than Pipulate. Earmark figuring that out for later.
(nix) 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: bookmarks.nix
modified: scripts/bookmarks_sync.py
no changes added to commit (use "git add" and/or "git commit -a")
(nix) nixos $ d
diff --git a/bookmarks.nix b/bookmarks.nix
index 84ebb1d..a9d8008 100644
--- a/bookmarks.nix
+++ b/bookmarks.nix
@@ -35,13 +35,19 @@
# `sync_metadata`, the script refuses any profile Sync owns, and declaring it
# would only print REFUSED on every init.
#
-# PROFILE KEYS RESOLVE TWO WAYS. A key is tried first as a profile DIRECTORY
-# ("Profile 2"), then as the LABEL Chrome shows in its profile menu, read from
-# the user-data-dir's Local State. Directory numbers are assigned per machine
-# in creation order; the label travels with the account. Keep the directory
-# key until a Mac `--inspect` receipt prints that machine's label for the Work
-# profile; if both machines print the same label, switch this ONE key to it
-# and the same file serves both.
+# A KEY IS A NAME, NEVER A DIRECTORY (convicted 2026-09-08 by the second
+# machine). The first Mac --inspect read "Profile 2" as the PERSONAL profile
+# there ("Mike", 121 synced bookmarks) while the Work profile sat in
+# "Profile 1" labeled "botify.com"; on NixOS "Profile 2" is labeled "Work".
+# Directory numbers are per machine, in creation order, so a directory key
+# in a shared file names a different profile on every checkout, and only the
+# sync fence kept a personal bar from being harvested and wiped. So each key
+# below is a human name, and its `match` list says what Chrome calls that
+# profile on ANY machine: a label from the profile menu, an account email,
+# or an account domain. Exactly one profile on a machine may match; none
+# skips the entry there, two or more refuse. `bm --inspect` prints label=
+# and domain= for every profile on disk, which is where a new machine's
+# match entry gets read before it is written here.
#
# ENTRY GRAMMAR: { name = "..."; url = "..."; } is a bookmark;
# { name = "..."; children = [ ... ]; } is a folder. Either may also carry
@@ -51,7 +57,11 @@
# entries below). List order is bar order. The script validates every entry
# before it touches anything.
{
- "Profile 2" = {
+ "Work" = {
+ # What Chrome calls this profile per machine: the NixOS box labels it
+ # "Work", the Mac labels it "botify.com". Either matches; so would the
+ # account's email or its domain.
+ match = [ "Work" "botify.com" ];
bookmark_bar = [
# PLACEHOLDERS, NOT THE WORK BAR: five entries carried over from Default
# while the projector was built. The first real projection ran
diff --git a/scripts/bookmarks_sync.py b/scripts/bookmarks_sync.py
index 127254b..5ee29f5 100644
--- a/scripts/bookmarks_sync.py
+++ b/scripts/bookmarks_sync.py
@@ -55,6 +55,7 @@ import itertools
import json
import os
import shutil
+import socket
import subprocess
import sys
import time
@@ -207,8 +208,18 @@ def chrome_state(user_data_dir):
procfs, so the earlier /proc test would have read every Mac lock as
RUNNING, stale or not. EPERM means the pid exists under another user and
still reads as alive; only ESRCH reads as dead. The lock's name and shape
- on macOS are RECALLED, not witnessed: the first Mac --inspect taken with
- Chrome open must print RUNNING, or this fence is blind there."""
+ on macOS are RECALLED, not witnessed, and the first Mac --inspect read
+ STOPPED with the browser's state unrecorded, so on macOS a pgrep for the
+ Google Chrome process rides beside the lock and either one reads as
+ RUNNING. A second Chrome on another user-data-dir over-refuses there;
+ that is the conservative side."""
+ if sys.platform == "darwin":
+ try:
+ pgrep = subprocess.run(["pgrep", "-x", "Google Chrome"], capture_output=True, text=True)
+ except FileNotFoundError:
+ pgrep = None
+ if pgrep is not None and pgrep.stdout.split():
+ return "RUNNING", "pgrep found a Google Chrome process, pid %s" % pgrep.stdout.split()[0]
lock = user_data_dir / "SingletonLock"
if not lock.is_symlink():
return "STOPPED", "no SingletonLock"
@@ -246,29 +257,48 @@ def back_up(bpath, profile):
# --- the matrix -------------------------------------------------------------
-def profile_labels(user_data_dir):
- """Directory -> label ("Work", "Person 1") from Chrome's Local State,
- the file that maps profile directories to what the profile menu shows.
- Directory numbers are assigned per machine in creation order; the label
- travels with the account, so a matrix key may name either. An absent or
- unreadable Local State yields an empty dict, which makes label resolution
- a no-op rather than a crash."""
+def profile_info(user_data_dir):
+ """Directory -> {label, account, domain} from Chrome's Local State, the
+ file that maps profile directories to what the profile menu shows (name)
+ and which account is signed in (user_name). Directory numbers are per
+ machine in creation order; the label and the account travel with the
+ profile, so a matrix entry names those and never a directory. The domain
+ is derived from user_name so a Workspace profile can be matched by its
+ organisation without writing an email into the matrix. An absent or
+ unreadable Local State yields an empty dict, which makes resolution skip
+ rather than crash."""
path = user_data_dir / "Local State"
try:
cache = load_json(path).get("profile", {}).get("info_cache", {})
except (OSError, ValueError, AttributeError):
return {}
- return {d: str(v.get("name", "")) for d, v in cache.items() if isinstance(v, dict)}
+ out = {}
+ for directory, entry in cache.items():
+ if not isinstance(entry, dict):
+ continue
+ account = str(entry.get("user_name", "") or "")
+ out[directory] = {
+ "label": str(entry.get("name", "") or ""),
+ "account": account,
+ "domain": account.rsplit("@", 1)[-1].lower() if "@" in account else "",
+ }
+ return out
-def resolve_profile(key, user_data_dir, labels):
- """A matrix key is first a profile DIRECTORY, then a profile LABEL."""
- if (user_data_dir / key).is_dir():
- return key
- for directory, label in labels.items():
- if label == key:
- return directory
- return None
+def resolve_profile(key, spec, profiles):
+ """The profile directories on THIS machine that a matrix entry means.
+ The entry's `match` list, or its key when it has none, names IDENTITIES:
+ a label as the profile menu shows it, an account email, or an account
+ domain. Never a directory: "Profile 2" was the Work profile on one
+ machine and a personal one on the other (2026-09-08), and a shared file
+ cannot carry a per-machine number. The caller treats one hit as the
+ answer, none as a skip, and several as a refusal."""
+ wanted = [w for w in (spec.get("match") or [key]) if isinstance(w, str) and w]
+ hits = []
+ for directory, info in profiles.items():
+ if any(w == info["label"] or w == info["account"] or w.lower() == info["domain"] for w in wanted):
+ hits.append(directory)
+ return sorted(hits)
def validate_entries(entries, where):
@@ -297,13 +327,16 @@ def validate_entries(entries, where):
def validate_matrix(matrix):
if not isinstance(matrix, dict):
- raise ValueError("matrix root must be an attrset keyed by profile directory name")
+ raise ValueError("matrix root must be an attrset keyed by a profile name (see match)")
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))
+ match = spec.get("match", [])
+ if not isinstance(match, list) or not all(isinstance(m, str) and m for m in match):
+ raise ValueError("%s: match must be a list of non-empty strings (labels, account emails, or account domains)" % profile)
+ unknown = sorted(set(spec) - set(MATRIX_ROOTS) - {"match"})
if unknown:
- raise ValueError("%s: unknown root(s) %s; only %s are declarable" % (profile, unknown, list(MATRIX_ROOTS)))
+ raise ValueError("%s: unknown key(s) %s; only %s and match are declarable" % (profile, unknown, list(MATRIX_ROOTS)))
for key in MATRIX_ROOTS:
validate_entries(spec.get(key, []), "%s.%s" % (profile, key))
@@ -417,7 +450,7 @@ def append_harvest(path, profile, lines, in_browser):
"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 = ["## %s %s on %s (%d harvested of %d url bookmarks in the browser)" % (stamp, profile, socket.gethostname(), len(lines), in_browser), "", FENCE + "nix"]
block.extend(lines)
block.append(FENCE)
existed = path.exists()
@@ -433,7 +466,7 @@ def clip(text, width=160):
# --- the two modes ----------------------------------------------------------
-def inspect_profile(profile, user_data_dir, bar=0, label=""):
+def inspect_profile(profile, user_data_dir, bar=0, info=None):
pdir = user_data_dir / profile
bpath = pdir / "Bookmarks"
if not bpath.exists():
@@ -473,12 +506,13 @@ def inspect_profile(profile, user_data_dir, bar=0, label=""):
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))
- # label= is what Chrome's profile menu shows, read from Local State; it is
- # the host-independent name a matrix key may use, so a Mac's --inspect is
- # where the key to switch to gets read. The sync.* Preferences fields that
- # printed here read True/-/- in every world across five compiles and
- # discriminated nothing; retired 2026-09-08.
- print(" label=%s account_info=%s" % (label or "-", accounts))
+ # label= is what Chrome's profile menu shows and domain= is the signed-in
+ # account's domain, both from Local State; a matrix entry's match list
+ # names either, never a directory. The email itself is not printed: this
+ # line rides compile receipts. The sync.* Preferences fields that printed
+ # here read True/-/- in every world and discriminated nothing (retired).
+ info = info or {}
+ print(" label=%s domain=%s account_info=%s" % (info.get("label") or "-", info.get("domain") or "-", accounts))
# 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
@@ -580,11 +614,17 @@ def main(argv=None):
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 -- expected bookmarks.nix at the repo root beside scripts/, or pass --matrix"))
- labels = profile_labels(user_data_dir)
+ profiles_info = profile_info(user_data_dir)
if args.inspect:
- profiles = [resolve_profile(p, user_data_dir, labels) or p for p in args.profiles] or sorted(p.parent.name for p in user_data_dir.glob("*/Bookmarks"))
+ # Read-only, so a positional may still be a directory; anything else
+ # resolves as an identity the way a matrix entry would.
+ profiles = []
+ for p in args.profiles:
+ hits = [p] if (user_data_dir / p).is_dir() else resolve_profile(p, {}, profiles_info)
+ profiles.extend(hits or [p])
+ profiles = profiles or sorted(p.parent.name for p in user_data_dir.glob("*/Bookmarks"))
for profile in profiles:
- inspect_profile(profile, user_data_dir, max(0, args.bar), labels.get(profile, ""))
+ inspect_profile(profile, user_data_dir, max(0, args.bar), profiles_info.get(profile))
print("VERDICT: INSPECT")
return 0
if not matrix_path.exists():
@@ -605,13 +645,17 @@ def main(argv=None):
print("%s: not declared in the matrix; skipped" % key)
verdicts.append("UNDECLARED")
continue
- directory = resolve_profile(key, user_data_dir, labels)
- if directory is None:
- print("%s: no profile directory or label by that name under %s; skipped" % (key, user_data_dir))
+ hits = resolve_profile(key, spec, profiles_info)
+ if not hits:
+ print("%s: no profile on this machine matches %s; skipped" % (key, spec.get("match") or [key]))
verdicts.append("SKIPPED_NO_PROFILE")
continue
- if directory != key:
- print("%s: resolved by label to profile directory %s" % (key, directory))
+ if len(hits) > 1:
+ print("%s: REFUSED: %s match more than one profile here (%s); narrow the match list" % (key, spec.get("match") or [key], ", ".join(hits)))
+ verdicts.append("REFUSED_AMBIGUOUS")
+ continue
+ directory = hits[0]
+ print("%s: resolved to profile directory %s (label=%s domain=%s)" % (key, directory, profiles_info[directory]["label"] or "-", profiles_info[directory]["domain"] or "-"))
verdicts.append(sync_profile(directory, spec, user_data_dir, Path(args.harvest).expanduser(), args.dry_run, state, max(0, args.preview)))
print("VERDICT: " + " ".join("%s=%s" % (k.replace(" ", "_"), v) for k, v in zip(keys, verdicts)))
return 2 if any(v.startswith("REFUSED") for v in verdicts) else 0
(nix) nixos $ m
📝 Committing: chore: Update bookmarks.nix to clarify profile key resolution and sync behavior
[main acdde0d] chore: Update bookmarks.nix to clarify profile key resolution and sync behavior
2 files changed, 99 insertions(+), 45 deletions(-)
(nix) nixos $ 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), 2.97 KiB | 2.97 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
230c64c..acdde0d main -> main
(nix) nixos $
Okay, and now we go over to the Mac again.
michaellevin@MichaelMacBook-Pro nixos % pwd
/Users/michaellevin/repos/nixos
michaellevin@MichaelMacBook-Pro nixos % git pull
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 5 (delta 4), reused 5 (delta 4), pack-reused 0 (from 0)
Unpacking objects: 100% (5/5), 2.95 KiB | 377.00 KiB/s, done.
From github.com:miklevin/nixos-config
230c64c..acdde0d main -> origin/main
Updating 230c64c..acdde0d
Fast-forward
bookmarks.nix | 26 ++++++++++++++++++--------
scripts/bookmarks_sync.py | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------
2 files changed, 99 insertions(+), 45 deletions(-)
michaellevin@MichaelMacBook-Pro nixos % cd ~/repos/nixos && git pull && ls -la "$HOME/Library/Application Support/Google/Chrome/" | grep -i singleton; python3 scripts/bookmarks_sync.py --inspect "Profile 1"
Already up to date.
chrome: STOPPED (no SingletonLock) user_data_dir=/Users/michaellevin/Library/Application Support/Google/Chrome
matrix: /Users/michaellevin/repos/nixos/bookmarks.nix present
Profile 1 1,026 B mtime 2026-09-08 15:47 version=1 siblings=['Bookmarks', 'Bookmarks.bak']
urls=0 folders=0 bookmark_bar=0 other=0 synced=0
sync_metadata=ABSENT extra_keys=-
checksum stored=942764d02172 computed=942764d02172 -> MATCH
label=botify.com domain=botify.com account_info=1
VERDICT: INSPECT
michaellevin@MichaelMacBook-Pro nixos %
Whoops, a Chrome window was not visibly open. Here it is again:
michaellevin@MichaelMacBook-Pro nixos % cd ~/repos/nixos && git pull && ls -la "$HOME/Library/Application Support/Google/Chrome/" | grep -i singleton; python3 scripts/bookmarks_sync.py --inspect "Profile 1"
Already up to date.
chrome: STOPPED (no SingletonLock) user_data_dir=/Users/michaellevin/Library/Application Support/Google/Chrome
matrix: /Users/michaellevin/repos/nixos/bookmarks.nix present
Profile 1 1,026 B mtime 2026-09-08 15:47 version=1 siblings=['Bookmarks', 'Bookmarks.bak']
urls=0 folders=0 bookmark_bar=0 other=0 synced=0
sync_metadata=ABSENT extra_keys=-
checksum stored=942764d02172 computed=942764d02172 -> MATCH
label=botify.com domain=botify.com account_info=1
VERDICT: INSPECT
michaellevin@MichaelMacBook-Pro nixos % cd ~/repos/nixos && git pull && ls -la "$HOME/Library/Application Support/Google/Chrome/" | grep -i singleton; python3 scripts/bookmarks_sync.py --inspect "Profile 1"
Already up to date.
lrwxr-xr-x@ 1 michaellevin staff 20 Sep 8 17:57 SingletonCookie -> 15988432170293151695
lrwxr-xr-x@ 1 michaellevin staff 24 Sep 8 17:57 SingletonLock -> MichaelMacBook-Pro-23555
lrwxr-xr-x@ 1 michaellevin staff 89 Sep 8 17:57 SingletonSocket -> /var/folders/70/tqb5vdl14853tp4_1t7_sqkc0000gn/T/com.google.Chrome.bhvYo2/SingletonSocket
chrome: RUNNING (pgrep found a Google Chrome process, pid 23555) user_data_dir=/Users/michaellevin/Library/Application Support/Google/Chrome
matrix: /Users/michaellevin/repos/nixos/bookmarks.nix present
Profile 1 1,026 B mtime 2026-09-08 15:47 version=1 siblings=['Bookmarks', 'Bookmarks.bak']
urls=0 folders=0 bookmark_bar=0 other=0 synced=0
sync_metadata=ABSENT extra_keys=-
checksum stored=942764d02172 computed=942764d02172 -> MATCH
label=botify.com domain=botify.com account_info=1
VERDICT: INSPECT
michaellevin@MichaelMacBook-Pro nixos %
I’m so confused. If this then that. Do I have Chrome open? Do I close it? I don’t know. Here’s this command with Chrome open:
michaellevin@MichaelMacBook-Pro nixos % python3 scripts/bookmarks_sync.py --dry-run --preview 0
chrome: RUNNING (pgrep found a Google Chrome process, pid 23555) user_data_dir=/Users/michaellevin/Library/Application Support/Google/Chrome
matrix: /Users/michaellevin/repos/nixos/bookmarks.nix present
Work: resolved to profile directory Profile 1 (label=botify.com domain=botify.com)
Profile 1: 0 url bookmarks in the browser, 5 declared, 0 to harvest; shape DIFFERS; codec VERIFIED
would append 0 line(s) to /Users/michaellevin/repos/nixos/bookmarks_harvest.md
would back up /Users/michaellevin/Library/Application Support/Google/Chrome/Profile 1/Bookmarks to /Users/michaellevin/.local/state/bookmarks_sync/Profile_1/ and write the projection
a real run right now would stop at REFUSED_CHROME_RUNNING
VERDICT: Work=DRY_RUN
michaellevin@MichaelMacBook-Pro nixos %
It reads like I should quite Chrome and run that again with --dry-run removed.
Let’s do it!
michaellevin@MichaelMacBook-Pro nixos % python3 scripts/bookmarks_sync.py --preview 0
chrome: STOPPED (no SingletonLock) user_data_dir=/Users/michaellevin/Library/Application Support/Google/Chrome
matrix: /Users/michaellevin/repos/nixos/bookmarks.nix present
Work: resolved to profile directory Profile 1 (label=botify.com domain=botify.com)
Profile 1: 0 url bookmarks in the browser, 5 declared, 0 to harvest; shape DIFFERS; codec VERIFIED
backed up -> /Users/michaellevin/.local/state/bookmarks_sync/Profile_1/Bookmarks.20260908-180153
wrote /Users/michaellevin/Library/Application Support/Google/Chrome/Profile 1/Bookmarks: 5 url bookmark(s)
VERDICT: Work=WRITTEN
michaellevin@MichaelMacBook-Pro nixos %
Yeah, I think I nailed that. I re-open Chrome and I see the new bookmarks on the bar (it’s no longer blank) so I think that’s it.
4: Prompt: Rule from the LIVE RECEIPTS. Precondition: the nix-instantiate receipt must open with the Work key; if it still reads Profile 2, Car 1 did not land, name that and stop. Then: the match grep must read 1 and the def grep 2; the –inspect Work receipt must print Profile 2’s block with label=Work and a domain= field, and quote the domain; the field census says whether user_name is present on this machine, and if it is absent, say that the domain path is unexercised here and the label path carried the resolution; the dry-run must read Work: resolved to profile directory Profile 2 and VERDICT: Work=IN_SYNC, or REFUSED_AMBIGUOUS naming the second directory, in which case rule that the match list narrows to the label and emit that one-line car. Then the Mac receipts, if pasted, by lane: the singleton listing and the chrome: line taken beside an open window decide whether either liveness check works on macOS, and a STOPPED there stops the ride; the Mac dry-run must resolve to Profile 1 by the botify.com label, never Profile 2; a WRITTEN of five and a following IN_SYNC close the two-machine loop. No other patches unless a receipt convicts one.
5: Deliverables: A process I could continue off-site using the Mac.
Fable 5.1 Extra: Context verified.
Ruling from the live receipts. Every condition in the caboose held. The JSON opens with the Work key; the match grep read 0 → 1 and the def grep 1 → 2; --inspect Work printed Profile 2’s block with label=Work domain=botify.com, so the domain path is live on this machine and the field census explains why: every profile here carries name, user_name, hosted_domain and gaia_name. The dry-run read Work: resolved to profile directory Profile 2 and VERDICT: Work=IN_SYNC — one hit, not two, so neither Default (five signed-in accounts) nor Profile 8 shares the Work domain and the match list stays as written. The tree is clean and acdde0d sits on top, an m message again, and a fair one this time.
The Mac half, by lane, and the ambiguity closed. Your first two Mac taps read STOPPED beside a grep that printed nothing, and the third read RUNNING beside three symlinks — so the earlier compile’s STOPPED was Chrome closed, and macOS Chrome writes the same SingletonLock -> <host>-<pid> the Linux fence reads. Both liveness instruments agree: pgrep found pid 23555 and the lock pointed at 23555. The dry-run resolved Work to Profile 1 by the botify.com label, never to Profile 2, and the real run read Work=WRITTEN, five bookmarks over an empty bar, backed up first. So one file now resolves to Profile 2 on NixOS and Profile 1 on the Mac, which is exactly the receipt SINGLE-CANDIDATE BLINDNESS asks for: the second candidate convicted the directory key, and the fix is witnessed on both. The “I’m so confused” moment is worth banking as a design principle rather than a stumble: the instrument decides the state. You never need to know whether Chrome is open; you run bm, and if it says RUNNING you quit Chrome and run it again. I asked you to stage Chrome’s state only to witness both branches of the fence for the record, and that record now exists, so the staging is over. One Mac-specific wrinkle the fence will teach you: closing every window on macOS leaves Chrome running, so RUNNING means ⌘Q, not the red button. One tap is owed on the Mac — a dry-run after Chrome has reopened, which should read IN_SYNC — and it rides in the off-site process below rather than as a probe here.
The Operator Experience of Verifiable State
What the receipts convict. Three labels now understate what is known, PROTECTION-LAG POLARITY in its expensive direction: the chrome_state docstring still calls the macOS lock “RECALLED, not witnessed,” the positional-argument help still says “profile directory names,” and the init hook’s comment still draws bookmarks.nix -> bookmarks.json -> Chrome, a pipeline stage that no longer exists. One car. Everything else this ride set out to do has its receipt, and by THE RIDE CONTRACT that means the next turn is the dismount; foo_files.py rides in the next context so BANK cars have raw source to land in.
1. PROBES
grep -c 'RECALLED, not witnessed' /home/mike/repos/nixos/scripts/bookmarks_sync.py
grep -c 'bookmarks.nix, evaluated at call time' /home/mike/repos/nixos/autognome.py
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Work
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
git -C /home/mike/repos/nixos status --short | head -5
git -C /home/mike/repos/nixos log --oneline -3
Probe 1 predicts 1 → 0, the AFTER’s non-zero exit being grep’s no-match; the module docstring’s “RECALLED FROM MEMORY” is a different phrase and does not inflate it. Probe 2 predicts 0 → 1, anchored on the new comment’s exact wording. Probes 3 and 4 are gates for the same car — comments cannot move them, so label=Work domain=botify.com and Work=IN_SYNC must read identically, and a change there would mean the AST-checked edit reached code. Probes 5 and 6 read the tree and the commit. When hand-running, note that the earlier head -c probe printed its JSON without a trailing newline, which is why your BEFORE paste showed the next grep’s 0 interleaved with it; the compile lane separates receipts by construction.
2. NEXT CONTEXT
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
foo_files.py
! grep -c 'RECALLED, not witnessed' /home/mike/repos/nixos/scripts/bookmarks_sync.py
! grep -c 'bookmarks.nix, evaluated at call time' /home/mike/repos/nixos/autognome.py
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Work
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! git -C /home/mike/repos/nixos status --short | head -5
! git -C /home/mike/repos/nixos log --oneline -3
3. PATCHES
Car 1 — the labels catch up with the receipts. Four blocks across two files; the AST airlock checks the script and the comment edit in autognome.py is a single line.
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
on macOS are RECALLED, not witnessed, and the first Mac --inspect read
STOPPED with the browser's state unrecorded, so on macOS a pgrep for the
Google Chrome process rides beside the lock and either one reads as
RUNNING. A second Chrome on another user-data-dir over-refuses there;
that is the conservative side."""
[[[DIVIDER]]]
on macOS were WITNESSED 2026-09-08 on the Mac: with a window open the
user-data-dir listed SingletonCookie, SingletonLock -> <host>-<pid> and
SingletonSocket, the same grammar as Linux, and with Chrome quit none of
the three existed. On macOS a pgrep for the Google Chrome process runs
ahead of the lock check and found the same pid the lock named, so either
instrument reads RUNNING there. A second Chrome on another user-data-dir
over-refuses there; that is the conservative side. Closing every window
on macOS leaves the process and the lock alive: RUNNING means quit
Chrome, not close it."""
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
- 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.
[[[DIVIDER]]]
- Chrome is running against this user-data-dir (SingletonLock points at a
live pid, or on macOS a Google Chrome process exists at all). Chrome
holds bookmarks in memory and writes the file back, so a write under a
running browser is clobbered, or clobbers.
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/bookmarks_sync.py
[[[SEARCH]]]
parser.add_argument("profiles", nargs="*", help="profile directory names (default: every profile the matrix declares; for --inspect, every profile on disk)")
[[[DIVIDER]]]
parser.add_argument("profiles", nargs="*", help="matrix keys to project (default: every key the matrix declares); under --inspect, profile directories or identities (default: every profile on disk)")
[[[REPLACE]]]
Target: /home/mike/repos/nixos/autognome.py
[[[SEARCH]]]
# 🔖 THE BOOKMARK PROJECTION (bookmarks.nix -> bookmarks.json -> Chrome).
[[[DIVIDER]]]
# 🔖 THE BOOKMARK PROJECTION (bookmarks.nix, evaluated at call time -> Chrome).
[[[REPLACE]]]
Ignition. None: comments and a help string, and every probe loads the files by path. Commit with your own message this time — m has described three of this ride’s five commits as something other than what they carried: cd ~/repos/nixos && git commit -am "Labels catch up with receipts: macOS lock witnessed, keys are names, no bookmarks.json stage" && git push.
4. PROMPT
Rule from the LIVE RECEIPTS: the RECALLED grep must read 0 and the autognome grep 1, and the --inspect Work and dry-run receipts must read exactly as the previous compile's did (label=Work domain=botify.com; Work: resolved to profile directory Profile 2; VERDICT: Work=IN_SYNC), which proves the label car touched no code. If the article carries a Mac dry-run taken after Chrome reopened, rule it: IN_SYNC closes the two-machine loop; anything else names what Chrome rewrote. Then this ride is at its destination and the \k block that follows supplies the seven beats. BANK cars land in foo_files.py against the raw source in this context: a RECEIPTS line for the Mac ride, and rulings on whether these graduate to keys or stay receipts: THE DIRECTORY KEY CROSSED MACHINES (a machine-local identifier in a shared file, convicted by the second candidate, the fence the only thing between it and a personal bar); THE INSTRUMENT DECIDES THE STATE (a fence that reports the world means the operator never pre-decides it, and staging the world to witness both branches is a one-time act, never the loop); and the head -c receipt that ran into the next probe's output for want of a trailing newline. The commit-label mismatches from m go to the existing ai.py numstat todo as specimens, not as a new key. The Mac's post-reopen IN_SYNC, the init hook that has still never fired, and the patch/app/d/m-from-another-repo question go to DANGLING.
5. EXTERNAL DELIVERABLES
Carried: the off-site loop, for the Mac, and nothing in it requires the NixOS box to be reachable.
One-time setup on the Mac. Add alias bm='python3 ~/repos/nixos/scripts/bookmarks_sync.py' to ~/.zshrc. Nix and the checkout are already there — the receipts show nix-instantiate on PATH and the matrix found at the repo root.
The loop, every time.
cd ~/repos/nixos && git pull— the matrix and the ledger arrive from wherever they were last edited.- Edit
bookmarks.nixin vim. Entries are{ name = "..."; url = "..."; }, folders are{ name = "..."; children = [ ... ]; }, and either may carrydescription = "..."for the prose about why it is there. Order is bar order.bookmarks_harvest.mdis the inbox: promote lines from it or leave them. bm— read the last line.BAD_MATRIXmeans a Nix typo and nothing was touched;REFUSED_CHROME_RUNNINGmeans quit Chrome with ⌘Q, since closing windows leaves it running, and runbmagain;REFUSED_SYNC_METADATAmeans bookmark sync came back on for the Work profile, so turn it off there and run again;Work=WRITTENmeans the bar is projected and the old one is in~/.local/state/bookmarks_sync/Profile_1/;Work=IN_SYNCmeans there was nothing to do. You never need to know Chrome’s state before running it; the verdict tells you what to do next.- Reopen Chrome and look at the bar of the Work profile.
git commit -am "..." && git push—bookmarks.nixalways, andbookmarks_harvest.mdwhenever the run reported a harvest, whose block header now names the machine it came from.
When you are back at the NixOS box. git pull in ~/repos/nixos, then bm, or simply wait for the next init, which runs the same projection before Chrome launches. If both machines harvested before either pulled, the ledger’s append-only shape makes the merge a matter of keeping both blocks.
MikeLev.in: Okay, I think we can wrap this article here.
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)
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 $ grep -c 'RECALLED, not witnessed' /home/mike/repos/nixos/scripts/bookmarks_sync.py
grep -c 'bookmarks.nix, evaluated at call time' /home/mike/repos/nixos/autognome.py
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Work
python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
git -C /home/mike/repos/nixos status --short | head -5
git -C /home/mike/repos/nixos log --oneline -3
1
0
chrome: RUNNING (SingletonLock -> nixos-17216, pid alive) user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/repos/nixos/bookmarks.nix present
Profile 2 2,483 B mtime 2026-09-08 16:30 version=1 siblings=['Bookmarks', 'Bookmarks.bak']
urls=5 folders=0 bookmark_bar=5 other=0 synced=0
sync_metadata=ABSENT extra_keys=-
checksum stored=1b08876e67bf computed=1b08876e67bf -> MATCH
label=Work domain=botify.com account_info=1
VERDICT: INSPECT
chrome: RUNNING (SingletonLock -> nixos-17216, pid alive) user_data_dir=/home/mike/.config/google-chrome
matrix: /home/mike/repos/nixos/bookmarks.nix present
Work: resolved to profile directory Profile 2 (label=Work domain=botify.com)
Profile 2: 5 url bookmarks in the browser, 5 declared, 0 to harvest; shape IN SYNC; codec VERIFIED
VERDICT: Work=IN_SYNC
acdde0d (HEAD -> main, origin/main, origin/HEAD) chore: Update bookmarks.nix to clarify profile key resolution and sync behavior
230c64c bookmarks_sync.py evaluates the Nix matrix at call time; description field
9b1d92a bookmarks.nix is pure data; configuration.nix stops importing it
(nix) pipulate $
2: Context: (AFTER: the same probes re-run by the compiler as ! lines)
# adhoc.txt _ _ _ to set context____ _ _ ___ ____ _ Simpson Couch Gag Here (explain anything to the audience you feel needs it explained)G
# / \ __| | | | | | ___ ___ / ___| | | |/ _ \| _ \| |
# ahe/ _ \ / _` | | |_| |/ _ \ / __| | | | |_| | | | | |_) | | Getting my immutable bookmark system working on Mac too.
# ahc ___ \ (_| | | _ | (_) | (__ | |___| _ | |_| | __/|_| So far so good.
# /_/ \_\__,_| |_| |_|\___/ \___| \____|_| |_|\___/|_| (_) I think we have a very solid process. Woot!
# 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 ---
# New Context 1 (Edit-in selections from above and add new files immediately below)
# /home/mike/repos/nixos/autognome.py # <-- More rare to have to include, but the true "top" of the muscle memory stack for day-to-day purposes
# /home/mike/repos/nixos/configuration.nix # <-- "Global" IaC context (most of you won't have)
# /home/mike/repos/nixos/blogs.nix
# /home/mike/repos/nixos/bookmarks.nix
# /home/mike/repos/nixos/packages.nix # <-- Full disclosure on pre-flake IaC available apps.
# /home/mike/repos/nixos/services.nix # <-- Running Linux system services.
# /home/mike/repos/nixos/scripts/bookmarks_sync.py
# 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
# New Context 2
# /home/mike/repos/nixos/autognome.py
# /home/mike/repos/nixos/configuration.nix
# /home/mike/repos/nixos/bookmarks.nix
# /home/mike/repos/nixos/.gitignore
# /home/mike/repos/nixos/scripts/bookmarks_sync.py
# ! python3 -c 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: v.get("name") for k, v in d.items()})'
# ! LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 400
# ! grep -c '^ ./bookmarks.nix' /home/mike/repos/nixos/configuration.nix
# ! grep -c '"description"' /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 --inspect Work
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
# ! ls -l /home/mike/.config/pipulate/bookmarks.json
# ! git -C /home/mike/repos/nixos status --short | head -5
# ! git -C /home/mike/repos/nixos log --oneline -3
# New Context 3
# /home/mike/repos/nixos/autognome.py
# /home/mike/repos/nixos/configuration.nix
# /home/mike/repos/nixos/bookmarks.nix
# /home/mike/repos/nixos/.gitignore
# /home/mike/repos/nixos/scripts/bookmarks_sync.py
# ! python3 -c 'import json,os; d=json.load(open(os.path.expanduser("~/.config/google-chrome/Local State")))["profile"]["info_cache"]; print({k: [f for f in ("name","user_name","hosted_domain","gaia_name") if f in v] for k, v in d.items()})'
# ! LD_LIBRARY_PATH="" nix-instantiate --eval --strict --json /home/mike/repos/nixos/bookmarks.nix 2>&1 | head -c 120
# ! grep -c 'match = \[' /home/mike/repos/nixos/bookmarks.nix
# ! grep -c '^def profile_info\|^def resolve_profile' /home/mike/repos/nixos/scripts/bookmarks_sync.py
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Work
# ! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
# ! git -C /home/mike/repos/nixos status --short | head -5
# ! git -C /home/mike/repos/nixos log --oneline -3
# New Context 4
/home/mike/repos/nixos/autognome.py
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
foo_files.py
! grep -c 'RECALLED, not witnessed' /home/mike/repos/nixos/scripts/bookmarks_sync.py
! grep -c 'bookmarks.nix, evaluated at call time' /home/mike/repos/nixos/autognome.py
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --inspect Work
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! 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)
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/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 $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/autognome.py'.
(nix) pipulate $
Meanwhile over in the other repo:
(nix) nixos $ 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'.
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: autognome.py
modified: scripts/bookmarks_sync.py
no changes added to commit (use "git add" and/or "git commit -a")
(nix) nixos $ d
diff --git a/autognome.py b/autognome.py
index eabe6d1..e0ce4b5 100644
--- a/autognome.py
+++ b/autognome.py
@@ -763,7 +763,7 @@ if __name__ == "__main__":
print("Warning: wmctrl not available, cannot switch desktops")
exit(1)
- # 🔖 THE BOOKMARK PROJECTION (bookmarks.nix -> bookmarks.json -> Chrome).
+ # 🔖 THE BOOKMARK PROJECTION (bookmarks.nix, evaluated at call time -> 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
diff --git a/scripts/bookmarks_sync.py b/scripts/bookmarks_sync.py
index 5ee29f5..e6c3e27 100644
--- a/scripts/bookmarks_sync.py
+++ b/scripts/bookmarks_sync.py
@@ -20,8 +20,9 @@ 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.
+ live pid, or on macOS a Google Chrome process exists at all). 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
@@ -208,11 +209,15 @@ def chrome_state(user_data_dir):
procfs, so the earlier /proc test would have read every Mac lock as
RUNNING, stale or not. EPERM means the pid exists under another user and
still reads as alive; only ESRCH reads as dead. The lock's name and shape
- on macOS are RECALLED, not witnessed, and the first Mac --inspect read
- STOPPED with the browser's state unrecorded, so on macOS a pgrep for the
- Google Chrome process rides beside the lock and either one reads as
- RUNNING. A second Chrome on another user-data-dir over-refuses there;
- that is the conservative side."""
+ on macOS were WITNESSED 2026-09-08 on the Mac: with a window open the
+ user-data-dir listed SingletonCookie, SingletonLock -> <host>-<pid> and
+ SingletonSocket, the same grammar as Linux, and with Chrome quit none of
+ the three existed. On macOS a pgrep for the Google Chrome process runs
+ ahead of the lock check and found the same pid the lock named, so either
+ instrument reads RUNNING there. A second Chrome on another user-data-dir
+ over-refuses there; that is the conservative side. Closing every window
+ on macOS leaves the process and the lock alive: RUNNING means quit
+ Chrome, not close it."""
if sys.platform == "darwin":
try:
pgrep = subprocess.run(["pgrep", "-x", "Google Chrome"], capture_output=True, text=True)
@@ -600,7 +605,7 @@ def sync_profile(profile, spec, user_data_dir, harvest_path, dry_run, state, pre
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("profiles", nargs="*", help="matrix keys to project (default: every key the matrix declares); under --inspect, profile directories or identities (default: 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)")
(nix) nixos $ m
📝 Committing: chore: Refactor bookmark projection documentation for accuracy on macOS
[main 4682a2a] chore: Refactor bookmark projection documentation for accuracy on macOS
2 files changed, 14 insertions(+), 9 deletions(-)
(nix) nixos $ 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), 963 bytes | 963.00 KiB/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
acdde0d..4682a2a main -> main
(nix) nixos $
Okay, that’s the labels catching up with the receipts. Understood.
4: Prompt: Rule from the LIVE RECEIPTS: the RECALLED grep must read 0 and the autognome grep 1, and the –inspect Work and dry-run receipts must read exactly as the previous compile’s did (label=Work domain=botify.com; Work: resolved to profile directory Profile 2; VERDICT: Work=IN_SYNC), which proves the label car touched no code. If the article carries a Mac dry-run taken after Chrome reopened, rule it: IN_SYNC closes the two-machine loop; anything else names what Chrome rewrote. Then this ride is at its destination and the \k block that follows supplies the seven beats. BANK cars land in foo_files.py against the raw source in this context: a RECEIPTS line for the Mac ride, and rulings on whether these graduate to keys or stay receipts: THE DIRECTORY KEY CROSSED MACHINES (a machine-local identifier in a shared file, convicted by the second candidate, the fence the only thing between it and a personal bar); THE INSTRUMENT DECIDES THE STATE (a fence that reports the world means the operator never pre-decides it, and staging the world to witness both branches is a one-time act, never the loop); and the head -c receipt that ran into the next probe’s output for want of a trailing newline. The commit-label mismatches from m go to the existing ai.py numstat todo as specimens, not as a new key. The Mac’s post-reopen IN_SYNC, the init hook that has still never fired, and the patch/app/d/m-from-another-repo question go to DANGLING.
5: Deliverables: The offsite loop for the Mac.
I have a bit of figuring out to do. It spells it all out right here but that doesn’t mean I internalized this. I may end up making a subfolder of critical article URLs (like this one once article-ized) to help me find them again, ha ha! This becomes very circular and internally consistent. I’m liking it.
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:
- 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.
- 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.
- 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.
- DANGLING: what carries forward unbanked? One line each, no essays.
- SEED: the adhoc.txt lines (and TODO_SLUGS if narrative context is needed) for the next ride’s first compile.
- 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.
- 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
Goal at the top: organize bookmarks from the Mac without sudo nixos-rebuild switch, and get the same declared bar on both machines. Met, by this compile’s receipts: the RECALLED grep reads 0 under grep’s no-match exit and the autognome grep reads 1, so the label car landed; --inspect Work still prints Profile 2’s block with label=Work domain=botify.com and the dry-run still reads Work: resolved to profile directory Profile 2 then VERDICT: Work=IN_SYNC, byte-identical to the previous compile, so the label car touched no code; the tree is clean and 4682a2a sits on top. Chrome’s pid was 17216 on every NixOS tap across four compiles: the whole ride landed under a running browser. The Mac half is witnessed in the operator lane from the previous turn’s paste — Work=WRITTEN, five bookmarks over an empty Profile 1, backup first — and no post-reopen Mac dry-run was pasted this turn, so the two-machine IN_SYNC closes in DANGLING, not here. Ignitions: the one n this ride required fired (receipt in the article), the rm fired (No such file in compile 1285), and every other car was call-time-loaded, so no ignition is owed. init’s hook is a separate path that has never run; it is not an owed ignition, it is an unwitnessed branch.
2. BANK
Rulings. THE DIRECTORY KEY CROSSED MACHINES passes the key test — a line saying “a shared file may not key on a machine-assigned handle” would have stopped "Profile 2" at the first write — so it banks as an EARMARK for the forget ride to graduate (GLOSSARY.md is not in this context, so no § key can carry its value yet). THE INSTRUMENT DECIDES THE STATE has a conviction in the operator’s own words and a standing consequence the off-site loop already obeys, so it banks as an EARMARK too. The head -c glue stays a receipt: it affects only the hand-run paste, the lane of record separates receipts by construction, and a key for it would not earn rent. The m mislabel goes to the ai.py TODO as a specimen — one honest specimen, bccc8ee; 9b1d92a and 230c64c were the operator’s own typed messages, not m’s. The morning’s description TODO shrinks to its unwitnessed half. The receipts block sits at its cap of 20, so the 2026-09-01 menu-recall line fades.
Car 1 — RECEIPTS: this ride in, the oldest line out. Commit: Receipts: the matrix crossed the machine.
Target: /home/mike/repos/pipulate/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 MATRIX CROSSED THE MACHINE (four compiles ending at deed 1287; nixos bccc8ee, 9b1d92a, 230c64c, acdde0d, 4682a2a): bookmarks.nix is pure data that scripts/bookmarks_sync.py evaluates at call time with nix-instantiate --eval --strict --json, so a checkout plus Nix is the deployment on NixOS and macOS alike and the bookmarks.json stage, its activation script and the n behind it are gone (import grep 1 -> 0, the JSON rm'd, five-on-five IN_SYNC on both sides of the change; one n fired to retire the module). THE SECOND CANDIDATE CONVICTED THE KEY: the Mac's first --inspect read "Profile 2" as the PERSONAL profile (Mike, 121 synced bookmarks) with the Work profile in "Profile 1" labeled botify.com, so a directory key in a shared file named the wrong profile on the second machine and only the sync_metadata fence stood between a real run and a personal bar; keys are now human names with a match list of identities read from Local State, and one file resolved to Profile 2 on NixOS and Profile 1 on the Mac by receipt (domain=botify.com printed on both). Mac lane, operator's paste: SingletonCookie, SingletonLock -> host-pid and SingletonSocket present with a window open and absent with Chrome quit, pgrep and the lock naming the same pid 23555, dry-run REFUSED_CHROME_RUNNING, then Work=WRITTEN five over an empty bar with the backup first, and the operator saw the bar. Labels caught up in the fourth compile (RECALLED grep 1 -> 0, autognome comment 0 -> 1, code receipts byte-identical). A head -c probe with no trailing newline glued the next probe's 0 onto its JSON in the hand-run paste; the compile lane separates receipts by construction, so that stays a receipt. UNWITNESSED: the Mac's post-reopen IN_SYNC, an entry carrying description, and init's hook, which has never run.
[[[REPLACE]]]
Target: /home/mike/repos/pipulate/foo_files.py
[[[SEARCH]]]
# 2026-09-01 menu recall car: boot_menu.py --recall + flake.nix menu(); hook-text probe 0 -> 1; recall list absent -> five words, exit 0 on BOTH sides so the printed list is the discriminator; rg 'four words|three words' 4 hits before and after, all historical prose, so the README/AGENTS stale-count follow-up is DISCHARGED UNOPENED.
# --- END RECEIPTS ---
[[[DIVIDER]]]
# --- END RECEIPTS ---
[[[REPLACE]]]
Car 2 — two earmarks, one TODO shrunk to its debt, one specimen filed. Commit: Bank: directory keys cross machines; the instrument decides the state.
Target: /home/mike/repos/pipulate/foo_files.py
[[[SEARCH]]]
# #todo #to-do #earmarks
[[[DIVIDER]]]
# #todo #to-do #earmarks
# - EARMARK: THE DIRECTORY KEY CROSSED MACHINES (banked 2026-09-08, convicted by the second machine): a shared file may not name a thing by a MACHINE-LOCAL handle, because the handle is assigned per machine and the file is not. Chrome numbers profile directories in creation order, so "Profile 2" was the Work profile on NixOS and the PERSONAL profile on the Mac (Mike, 121 synced bookmarks), and bookmarks.nix, keyed by that directory, resolved to the wrong profile the first time a second checkout ran it; only the sync_metadata fence stood between a real run and a harvest-and-wipe of a personal bar. The key had been right for the whole life of the feature on the one machine that existed, which is SINGLE-CANDIDATE BLINDNESS wearing a filename: n=1 cannot tell a stable identity from a coincidence. CURE, a grammar change rather than a value change: the key is a human name and a `match` list names IDENTITIES the machine derives and the operator cannot author (a label, an account, an account domain, read from the application's own registry); exactly one hit resolves, none skips, several refuse. THE TEST before any key rides a shared file: is this value assigned by the machine in an order the other machine never saw? Directory numbers, pids, device paths, X display numbers and "Profile N" all fail it. Sibling of THE DERIVED-PATH RULE (a write target computed from an identity the writer cannot author) and of SINGLE-CANDIDATE BLINDNESS (create the second candidate); this names the class of key the second candidate convicts.
# - EARMARK: THE INSTRUMENT DECIDES THE STATE (banked 2026-09-08, operator-convicted: "I'm so confused. Do I have Chrome open? Do I close it?"): when a fence REPORTS the world (RUNNING, STOPPED, STALE_LOCK) and ends in a verdict token, the operator's loop contains no question about the world; it is run, read the last line, do what the verdict names, run again. An instruction that asks the human to STAGE the world ("with a Chrome window visibly open") is witness choreography for the record, the one-time act that observes both branches of a fence owed under REFUSAL-ONLY WITNESS, and it must be labeled as such and retired the compile after the receipt lands, or the operator reads the staging as the standing procedure and a fence built to remove a decision has added one. STANDING CONSEQUENCE for any hand-run process an article hands over: write it as verdict -> action pairs (REFUSED_CHROME_RUNNING means quit Chrome and run again; IN_SYNC means nothing to do) and never as preconditions the human establishes before typing the command. Sibling of THE GATE SPEAKS A VERDICT (the gate says a word, not a number) and of THE BARNEY RESET RULE (confusion in the human is the trigger, and it fired here).
[[[REPLACE]]]
Target: /home/mike/repos/pipulate/foo_files.py
[[[SEARCH]]]
# - 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.
[[[DIVIDER]]]
# - TODO (2026-09-08, code landed the same day in nixos 230c64c): validate_entries in scripts/bookmarks_sync.py ACCEPTS a per-entry `description` string (grep read 2), but no entry in bookmarks.nix carries one yet, so the accept branch is UNWITNESSED; the first entry that does must read IN_SYNC or WRITTEN under bm, never BAD_MATRIX, and that receipt deletes this line.
[[[REPLACE]]]
Target: /home/mike/repos/pipulate/foo_files.py
[[[SEARCH]]]
# across one real commit, and it then runs on every `m` forever. Own ride.
[[[DIVIDER]]]
# across one real commit, and it then runs on every `m` forever. Own ride.
# SPECIMEN 2026-09-08 (nixos repo, same m alias): bccc8ee carried three
# cars -- bookmarks.nix rewritten as pure data, configuration.nix ceasing
# to import it, bookmarks_sync.py gaining nix-instantiate evaluation,
# os.kill liveness and Local State labels (3 files, +164/-80) -- under the
# subject "chore: Update bookmarks.nix to a more streamlined configuration".
[[[REPLACE]]]
No ignition: every reader loads foo_files.py at compile time, and the next compile is its own AFTER.
3. DANGLING
- The Mac’s post-reopen
bm --dry-run, which should readWork=IN_SYNC; one tap, operator lane. init’s bookmark hook has never fired; the next morning’sinitprintsbookmark sync exit 0or it does not.patch/appfrom a checkout other than pipulate:d,m,gwere witnessed working from~/repos/nixos;patchwrites andappreads a file relative to somewhere, and which somewhere is unknown.alias bm=...in the Mac’s~/.zshrc: named in the deliverable, not witnessed added.- The two-machine ledger merge: the Mac harvested zero, so append-only blocks from two hosts have never met in one
git pull. - The bar itself: five placeholders, 675 ledger lines, no folders; the next ride’s substance.
- The operator’s own circle: this article’s published URL belongs in a
bookmarks.nixfolder of process articles, so the bar carries the description of the bar. - The two earmarks above await the forget ride (
forget, COMPACT_CHOP) to become § keys with bodies in GLOSSARY.md.
4. SEED
# New Context 5 (promote from the ledger into folders; a folder is the trail)
/home/mike/repos/nixos/bookmarks.nix
/home/mike/repos/nixos/scripts/bookmarks_sync.py
scripts/bookmark_import.py
scripts/walk_compile.py
! grep -c '^## ' /home/mike/repos/nixos/bookmarks_harvest.md
! grep -c '^{ name = ' /home/mike/repos/nixos/bookmarks_harvest.md
! grep -c 'description = ' /home/mike/repos/nixos/bookmarks.nix
! python3 /home/mike/repos/nixos/scripts/bookmarks_sync.py --dry-run --preview 0
! git -C /home/mike/repos/nixos log --oneline -3
The ledger rides as counts, never as content: its lines are work URLs, and a cloud chat window is not their lane.
5. CLOSING
The ride set out to remove a sudo and came back with a rule. The sudo went in the first compile: a Nix file that is only data can be read by the same tool that would have built it, on any machine, in under a second, and the build step was never load-bearing — it was a habit inherited from a sibling file that actually needs one. The rule arrived from the second machine, and that is the part worth keeping. "Profile 2" was correct for every day of the feature’s life on the only computer that ran it, and it was wrong the first hour a second computer did; nothing in the code was mistaken, the key was simply a number one machine had assigned and another machine had assigned differently. The fix was not a better number but a different kind of name — one the machine derives and the operator cannot author — and the second machine was the cheapest instrument that could have found it. Single-candidate blindness is expensive precisely because the single candidate is always right.
The other thing the Mac taught is smaller and will outlast the bookmarks. “Do I have Chrome open? Do I close it?” is the sound of a human trying to hold state the fence was built to hold for them. The answer is that the human never needs to know: run it, read the word, do what the word says. That is the whole shape of the Mother Cat Kata seen from the operator’s chair — one gate, fenced, with a verdict — and it is why the off-site loop fits in five lines. From here the bar is the trail, the trail is what the recorder rides, and the compile is the bundle a second reader checks without re-walking anything. The article that explains the process goes into the folder the process declares, which is circular in the way a good index is circular.
6. NOTARIZE
Fingerprint of the bytes this compile sealed: the Manifest carries no wc -c line (the open NOTARY FINGERPRINT todo), so the only size on record is the Summary’s own figure, Total Bytes 590,996 UTF-8 / 590,023 chars, reported as the Summary’s number and not as a measurement; six live command receipts in the Manifest; newest RECEIPTS date in foo_files.py 2026-09-08, and after Car 1 the newest line is this ride’s. The deed is read off the seal footer beneath the payload rather than guessed, as the 2026-09-06 todo directs: ls -t foo-*.zip | head -1 confirms it.
Deed: foo-3c844541-1287.zip
Book Analysis
Ai Editorial Take
What is most striking about this evolution is how minor environment discrepancies—such as Chrome profile numbering order—can completely subvert an automation pipeline. The transition from positional directories to semantic identity matching is a masterclass in designing resilient systems that gracefully handle multi-device realities.
🐦 X.com Promo Tweet
Tired of manual edits breaking your multi-machine setup? Discover how evaluating configurations at run time and using identity-based profile matching creates checkable, replayable AI workflows. https://mikelev.in/futureproof/replayable-ai-workflows-and-profile-identity/ #LocalFirst #AIWorkflows #Automation
Title Brainstorm
- Title Option: Closing the Loop: Replayable AI Workflows and the End of Manual Edits
- Filename:
replayable-ai-workflows-and-profile-identity.md - Rationale: Focuses on the core theme of closing the loop between different machines through checkable, replayable workflows.
- Filename:
- Title Option: The Identity Match: Engineering Cross-Platform Configuration Without Guesswork
- Filename:
identity-match-cross-platform-configuration.md - Rationale: Highlights the specific breakthrough regarding profile identity resolution across distinct operating systems.
- Filename:
- Title Option: From Static Builds to Run-Time Evaluation in Modern AI Workflows
- Filename:
static-builds-to-runtime-evaluation.md - Rationale: Emphasizes the architectural shift from rigid build steps to dynamic, inspectable evaluations.
- Filename:
Content Potential And Polish
- Core Strengths:
- Rigorous debugging narrative demonstrating the transition from a single-machine script to a cross-platform reality.
- Clear distinction between fragile directory-based matching and robust identity-based resolution.
- Strong emphasis on checkable receipts and predictable failure modes.
- Suggestions For Polish:
- Ensure the distinction between macOS and NixOS state checks remains prominent for readers attempting replication.
- Highlight the psychological relief of letting feedback gates dictate operator actions instead of manual guesswork.
Next Step Prompts
- Analyze the remaining friction points in multi-machine ledger synchronization and propose an automated merge strategy.
- Draft a companion guide on transforming browser bookmark hierarchies directly into testable agentic trails.