Taming the Unstable Throttle: Engineering Replayable NixOS Upgrades

🤖 Read Raw Markdown📄 Google Doc (Try: Tools/Audio/Listen to document summary)

Setting the Stage: Context for the Curious Book Reader

In complex systems management, background TTL timers can silently drag configurations forward into unrequested state drift. This entry explores an important technique for the Age of AI: replacing invisible background roll-forwards with checkable, replayable local validation steps. By shifting from implicit drift to explicit, prompt-gated updates, we keep development environments stable and predictable without sacrificing modern acceleration features.

TL;DR: A NixOS configuration imported the nixpkgs nixos-unstable branch for a CUDA-accelerated Ollama build through a hash-less builtins.fetchTarball. Nix re-resolves such a fetch once its tarball TTL (one hour by default) expires, so any routine nixos-rebuild switch more than an hour after the last could pull a moved branch and trigger an unrequested half-hour local rebuild. This article pins the branch by commit and content hash in a small JSON file the Nix module reads at evaluation time, and replaces the rebuild alias with a wrapper that checks GitHub for branch movement and asks an interactive y/N (Enter is No) before touching the pin. Receipts show the pinned fetch resolving from the local store without network access, a full dry-build with nothing to construct, and the wrapper reporting the branch unchanged. The moved-branch prompt path is not yet exercised, because the branch did not move during the work. Two diagnostic probes failed for environmental reasons — a session-wide LD_LIBRARY_PATH breaking user-profile Nix binaries, and a root-only profile lock — and were repaired or recorded.


Technical Journal Entry Begins

MikeLev.in: When I use my n alias, I often am taken aback by the huge build that occurs because of the ai-acceleration.nix module that gets the latest unstable and starts a half-hour rebuild because of Ollama. Now I do want the hardware acceleration advantages and I do want to use the n alias a lot, but I never want it to automatically roll forward and cause those half-hour build waits. I’d like it to tell me there’s new whatever available and ask in an interactive prompt if I would like to roll-forward and for it to default to “No” by just hitting the Enter key or answering No and requiring a Y, y, Yes or any of those variations to be typed in. In most cases it will do a standard sudo nixos-rebuild switch without allowing whatever this allows:

let
  # ⚡ SURGICAL TRANSPLANT: Define the Unstable Channel source
  # This grabs the definition of packages from the unstable branch

  unstable = import (builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz") {
    # We pass the same config (allowUnfree, cuda) so it matches your system
    config = config.nixpkgs.config;
  };

in
{

Fable 5.1 Extra Thinks: (Thought for 6 minutes and 9 seconds)

Reviewing a NixOS configuration and its rebuild setup.

Designing a pin-and-prompt mechanism for unstable updates.

Choosing where to store the pinned revision file.

Working out how to compute and safely fetch the pin's checksum.

Hunting for a way to bootstrap the initial pinned revision.

Designing a clear bootstrap message for the first pin.

Checking whether GitHub's ETag could reveal the exact commit.

Drafting the switch script and its command-line options.

Verifying how fetch caching interacts with hermetic hashing.

Aligning store paths so pinned fetches skip re-downloading.

Wiring the switch script to fetch and compare revisions.

Deciding prompts and flow for confirming an update roll.

Sequencing script and config changes for a safe rollout.

Confirming why the CUDA build triggers costly local rebuilds.

Pinpointing an exact search block while avoiding link-rewrite pitfalls.

Drafting the switch script's explanatory header comment.

Coding the remote revision check with offline fallback.

Implementing pin reading, prefetching, and writing logic.

Assembling the main flow with prompts and rebuild execution.

Double-checking edge cases and environment variable handling for reliability.

Preparing safe verification probes for environment and config checks.

Selecting probes to confirm tools and store paths change.

Finalizing a lean set of falsifying probe commands.

Weighing a dry-build probe against runtime and caching costs.

Filtering dry-build output to surface only key lines.

Sequencing file writes and commit steps across the change set.

Formatting the file-write patch and its parsing rules.

Composing the config comment and pinned-revision replacement block.

Updating the alias to launch the new switch script.

Weighing a one-time rebuild against caching optimizations.

Checking whether the dry-build probe needs nixpkgs path set.

Drafting the final prompt and script implementation details.

Writing the header comment and coding the revision-check function.

Coding the pin-reading and prefetch logic in detail.

Coding the pin-writing and confirmation-prompt helpers.

Rechecking script logic and pin-file placement for correctness.

Assembling the next context list and file probes.

Fable 5.1 Extra: Context verified. Both files that have to change (ai-acceleration.nix, configuration.nix) are present; the one thing not in context is the nixos repo’s .gitignore, and a probe below covers that instead of a file request.

Why n bites you. builtins.fetchTarball "…/archive/nixos-unstable.tar.gz" carries no hash, so Nix treats it as a moving target and re-resolves it once tarball-ttl (an hour by default) has expired. Any n run more than an hour after the last one quietly moves the branch, and because ollama with CUDA is not in the binary cache, a moved branch means a half-hour local build. Nothing in your config ever asked for the roll-forward; the TTL did it for you.

The Hidden Cost of Moving Targets

The fix. Pin the branch by rev + NAR hash in ~/repos/nixos/unstable-pin.json, have ai-acceleration.nix read that file at evaluation time, and make n a small stdlib-only wrapper that: asks GitHub where nixos-unstable points now (git ls-remote, read-only), compares with the pin, and if it moved, says so and asks [y/N] — Enter or anything but y/yes holds the pin; only a yes prefetches the new tarball and rewrites the pin. Then it runs the same sudo nixos-rebuild switch as before. n --check reports and stops; n --roll skips the question; other args pass through. Offline, it holds the pin and switches anyway. The prefetch stores the tree under the name source, so root’s evaluation finds it by hash and never touches the network again.

Engineering a Replayable Pin

One honest cost. The bootstrap has to pin something, and the only rev in evidence is today’s HEAD, which probably differs from the tarball root last cached. So the first switch after this lands may rebuild Ollama once — the last unrequested one. Dodging even that would mean recovering the cached tarball’s identity from root’s fetcher cache, whose schema nothing in context proves, so I did not build on it.

1. PROBES

git ls-remote https://github.com/NixOS/nixpkgs refs/heads/nixos-unstable
command -v nix-prefetch-url
git -C ~/repos/nixos check-ignore -v unstable-pin.json scripts/nixos-switch.py; echo "check-ignore exit $?"
ls -la ~/repos/nixos/unstable-pin.json ~/repos/nixos/scripts/nixos-switch.py
readlink -f "$(command -v ollama)"
nix-env --list-generations --profile /nix/var/nix/profiles/system | tail -2
timeout 180 nixos-rebuild dry-build 2>&1 | grep -E 'ollama|will be (built|fetched)|error' | head -20

Line 1 is the rev source the wrapper depends on; BEFORE vs AFTER also shows whether the branch moved between readings. Line 2 falsifies the wrapper’s one tool dependency. Line 3 falsifies “the pin will be tracked” — exit 1 is the good outcome (nothing ignored). Line 4 is absent BEFORE, present AFTER. Line 5 is the receipt for the one-time rebuild claim: the store path changes only if Ollama was rebuilt. Line 6 shows the generation advancing. Line 7 is the diagnosis itself: BEFORE, it names what the next n would have built on you; AFTER, a pinned system should report nothing to build. It is the only heavy probe, hence the cap.

2. NEXT CONTEXT

/home/mike/repos/nixos/ai-acceleration.nix
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/scripts/nixos-switch.py
/home/mike/repos/nixos/unstable-pin.json
! git ls-remote https://github.com/NixOS/nixpkgs refs/heads/nixos-unstable
! command -v nix-prefetch-url
! git -C ~/repos/nixos check-ignore -v unstable-pin.json scripts/nixos-switch.py; echo "check-ignore exit $?"
! ls -la ~/repos/nixos/unstable-pin.json ~/repos/nixos/scripts/nixos-switch.py
! readlink -f "$(command -v ollama)"
! nix-env --list-generations --profile /nix/var/nix/profiles/system | tail -2
! timeout 180 nixos-rebuild dry-build 2>&1 | grep -E 'ollama|will be (built|fetched)|error' | head -20

3. PATCHES

Car 1 — the wrapper (new file; git add it before m, since -a skips untracked files):

Target: /home/mike/repos/nixos/scripts/nixos-switch.py
[[[WRITE_FILE]]]
#!/usr/bin/env python3
"""
nixos-switch.py -- the `n` alias, with a hand on the nixos-unstable throttle.

WHY. ai-acceleration.nix imports nixpkgs' nixos-unstable branch for Ollama.
It used to do that with a hash-less builtins.fetchTarball of
.../archive/nixos-unstable.tar.gz, and Nix re-resolves a hash-less tarball
once its tarball-ttl (one hour by default) has run out. So any `n` more than
an hour after the previous one silently moved the branch, and because
ollama with CUDA is not in the binary cache, a moved branch was a half-hour
local build nobody asked for.

WHAT. The branch is pinned by rev and NAR sha256 in unstable-pin.json,
which ai-acceleration.nix reads at evaluation time and which only this
script writes. Every run:

  1. asks GitHub where nixos-unstable points now (git ls-remote, read-only);
  2. compares that with the pin;
  3. if the branch moved, says so and asks [y/N]. Enter, or anything but
     y/yes, holds the pin. Only a yes prefetches the new tarball and rewrites
     unstable-pin.json;
  4. runs the same `sudo nixos-rebuild switch` the old alias ran.

The one case a No aborts the switch is a missing pin (first run): the
system cannot evaluate without one, so bootstrapping is asked, not assumed.

FLAGS   --check   report pin vs. branch and stop; never switches.
        --roll    roll forward without asking (still prints what moved).
        Anything else passes through to `nixos-rebuild switch`.

GitHub unreachable: the pin is held and the switch proceeds as usual.
"""

import json
import os
import subprocess
import sys
from datetime import datetime, timezone

REPO_DIR = "/home/mike/repos/nixos"
PIN_PATH = os.path.join(REPO_DIR, "unstable-pin.json")
NIXPKGS_REPO = "https://github.com/NixOS/nixpkgs"
BRANCH = "nixos-unstable"

def tool_env():
    """ai-acceleration.nix exports a CUDA LD_LIBRARY_PATH session-wide, and
    that path can break Nix's own binaries (apply.py clears it before
    nix-instantiate for the same reason). Clear it for every tool call."""
    env = dict(os.environ)
    env["LD_LIBRARY_PATH"] = ""
    env["GIT_TERMINAL_PROMPT"] = "0"
    return env

def short(rev):
    return rev[:7]

def remote_head():
    """Where nixos-unstable points right now, or None if GitHub is unreachable."""
    try:
        out = subprocess.run(
            ["git", "ls-remote", NIXPKGS_REPO, f"refs/heads/{BRANCH}"],
            capture_output=True, text=True, timeout=30, check=False, env=tool_env(),
        )
    except (OSError, subprocess.TimeoutExpired) as e:
        print(f"⚠ Could not reach GitHub ({type(e).__name__}); holding the pin.")
        return None
    fields = out.stdout.split()
    if out.returncode != 0 or not fields:
        print(f"⚠ git ls-remote gave nothing ({out.stderr.strip() or 'no output'}); holding the pin.")
        return None
    return fields[0]

def read_pin():
    """The pin on file, or None when there is none yet. A damaged pin stops here."""
    try:
        with open(PIN_PATH, "r", encoding="utf-8") as f:
            pin = json.load(f)
    except FileNotFoundError:
        return None
    except (OSError, json.JSONDecodeError) as e:
        print(f"❌ {PIN_PATH} is unreadable ({e}). Fix or delete it, then rerun.")
        sys.exit(1)
    if not pin.get("rev") or not pin.get("sha256"):
        print(f"❌ {PIN_PATH} lacks rev or sha256. Delete it to re-bootstrap.")
        sys.exit(1)
    return pin

def prefetch(rev):
    """Download the tarball once, as the user, into the store under the same
    name builtins.fetchTarball uses ("source"), so root's evaluation finds
    the tree by hash and never touches the network. Returns the NAR sha256
    in the base32 form fetchTarball expects."""
    url = f"{NIXPKGS_REPO}/archive/{rev}.tar.gz"
    print(f"⏳ Prefetching nixpkgs {short(rev)} (roughly 45 MB, one time)...")
    out = subprocess.run(
        ["nix-prefetch-url", "--unpack", "--name", "source", url],
        capture_output=True, text=True, check=False, env=tool_env(),
    )
    lines = out.stdout.strip().splitlines()
    if out.returncode != 0 or not lines:
        print(f"❌ nix-prefetch-url failed:\n{out.stderr.strip()}")
        sys.exit(1)
    return lines[-1]

def write_pin(rev, sha256):
    pin = {
        "branch": BRANCH,
        "rev": rev,
        "sha256": sha256,
        "pinned": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
    }
    with open(PIN_PATH, "w", encoding="utf-8") as f:
        json.dump(pin, f, indent=2)
        f.write("\n")
    print(f"📌 Pinned {BRANCH} at {short(rev)} -> {PIN_PATH}")

def ask_yes(prompt):
    """Enter, EOF, a non-tty, or anything but y/yes is No."""
    if not sys.stdin.isatty():
        return False
    try:
        answer = input(prompt)
    except EOFError:
        print()
        return False
    return answer.strip().lower() in ("y", "yes")

def main():
    args = sys.argv[1:]
    check_only = "--check" in args
    force_roll = "--roll" in args
    passthrough = [a for a in args if a not in ("--check", "--roll")]

    pin = read_pin()
    head = remote_head()

    if pin is None:
        print(f"📭 No {os.path.basename(PIN_PATH)} on file; {BRANCH} must be pinned before the system can evaluate.")
        if head is None:
            print("❌ Cannot bootstrap a pin without GitHub. Nothing switched.")
            sys.exit(1)
        print(f"   {BRANCH} currently points at {head}")
        if check_only:
            sys.exit(0)
        if not (force_roll or ask_yes("   Pin it there? This first switch may rebuild Ollama once. [y/N] ")):
            print("   Held. Nothing switched.")
            sys.exit(1)
        write_pin(head, prefetch(head))
    elif head is None:
        print(f"📌 Holding {BRANCH} at {short(pin['rev'])} (pinned {pin.get('pinned', '?')}).")
    elif head == pin["rev"]:
        print(f"✅ {BRANCH} unchanged at {short(head)} (pinned {pin.get('pinned', '?')}).")
    else:
        print(f"🆕 {BRANCH} moved: {short(pin['rev'])} (pinned {pin.get('pinned', '?')}) -> {short(head)}")
        if check_only:
            print("   --check: reporting only; pin untouched.")
            sys.exit(0)
        if force_roll or ask_yes("   Roll forward? Ollama/CUDA will rebuild (about 30 min). [y/N] "):
            write_pin(head, prefetch(head))
        else:
            print(f"   Held at {short(pin['rev'])}. Roll later with `n --roll`.")

    if check_only:
        sys.exit(0)

    cmd = ["sudo", "nixos-rebuild", "switch"] + passthrough
    print(f"🚀 {' '.join(cmd)}")
    sys.exit(subprocess.run(cmd, cwd=REPO_DIR, check=False).returncode)

if __name__ == "__main__":
    main()
[[[END_WRITE_FILE]]]

Car 2 — pin the fetch:

Target: /home/mike/repos/nixos/ai-acceleration.nix
[[[SEARCH]]]
  unstable = import (builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz") {
    # We pass the same config (allowUnfree, cuda) so it matches your system
    config = config.nixpkgs.config;
  };
[[[DIVIDER]]]
  # PINNED (2026-09-09). This used to fetch the bare .../archive/nixos-unstable
  # tarball with no hash, and Nix re-resolves a hash-less tarball once
  # tarball-ttl (an hour by default) has expired, so any `n` more than an hour
  # after the last one silently rolled the branch forward -- and ollama with
  # CUDA is not in the binary cache, so a moved branch was a half-hour local
  # build nobody asked for. The rev and NAR sha256 now come from
  # ./unstable-pin.json, which only scripts/nixos-switch.py (the `n` alias)
  # writes, and only after a [y/N] that defaults to No. The hash makes the
  # fetch fixed-output: root's evaluation finds the tree already in the store
  # and never touches the network. To roll forward: `n`, answer y.
  unstablePin = builtins.fromJSON (builtins.readFile ./unstable-pin.json);

  unstable = import (builtins.fetchTarball {
    url = "https://github.com/NixOS/nixpkgs/archive/${unstablePin.rev}.tar.gz";
    sha256 = unstablePin.sha256;
  }) {
    # We pass the same config (allowUnfree, cuda) so it matches your system
    config = config.nixpkgs.config;
  };
[[[REPLACE]]]

Car 3 — re-point n:

Target: /home/mike/repos/nixos/configuration.nix
[[[SEARCH]]]
    n = "cd ~/repos/nixos && sudo nixos-rebuild switch";
[[[DIVIDER]]]
    # `n` no longer rolls nixos-unstable forward on its own. The wrapper reads
    # unstable-pin.json, asks GitHub whether the branch moved, and prompts
    # [y/N] (Enter is No) before touching the pin; the switch itself is still
    # `sudo nixos-rebuild switch`. `n --check` reports and stops, `n --roll`
    # skips the question, anything else passes through to nixos-rebuild.
    n = "cd ~/repos/nixos && python3 /home/mike/repos/nixos/scripts/nixos-switch.py";
[[[REPLACE]]]

Ignition. After Car 2 lands, the system cannot evaluate until the pin exists, so the ignition is the wrapper itself, run by path (the old n alias still points at the bare switch until this rebuild installs the new one):

cd ~/repos/nixos && git add scripts/nixos-switch.py && python3 scripts/nixos-switch.py

Answer y at the bootstrap prompt. It prefetches, writes the pin, and runs the switch — this is the run that may rebuild Ollama once. Then git add unstable-pin.json, commit, and open a new terminal so the shell picks up the new alias. From then on n is the fenced door.

4. PROMPT

The unstable throttle landed: scripts/nixos-switch.py, unstable-pin.json, and the pinned fetchTarball in ai-acceleration.nix. Read the AFTER receipts against the BEFORE: did `git ls-remote` move between readings; does the pin's rev equal the BEFORE ls-remote (the bootstrap pinned what it said it would); did the system generation advance; did ollama's store path change (the one-time rebuild, or not); is the dry-build quiet now? Then report `n` from a fresh shell: does it say unchanged or moved, and did a bare Enter hold the pin? Name anything that misbehaved with the exact receipt line, and only then say whether reading the cached tarball's identity out of root's fetcher cache is worth a follow-up ride.

5. EXTERNAL DELIVERABLES

None this turn outside the repo — every change rides as a patch car against ~/repos/nixos; nothing to paste into a CMS or dashboard.

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'.

(nix) pipulate $ git ls-remote https://github.com/NixOS/nixpkgs refs/heads/nixos-unstable
command -v nix-prefetch-url
git -C ~/repos/nixos check-ignore -v unstable-pin.json scripts/nixos-switch.py; echo "check-ignore exit $?"
ls -la ~/repos/nixos/unstable-pin.json ~/repos/nixos/scripts/nixos-switch.py
readlink -f "$(command -v ollama)"
nix-env --list-generations --profile /nix/var/nix/profiles/system | tail -2
timeout 180 nixos-rebuild dry-build 2>&1 | grep -E 'ollama|will be (built|fetched)|error' | head -20
d6524aaca2ff07876657ae2b323f24be4874944b	refs/heads/nixos-unstable
/home/mike/.nix-profile/bin/nix-prefetch-url
check-ignore exit 1
ls: cannot access '/home/mike/repos/nixos/unstable-pin.json': No such file or directory
ls: cannot access '/home/mike/repos/nixos/scripts/nixos-switch.py': No such file or directory
/nix/store/n7lg2yyw1b35si3p35bz15kvmqya6jd0-ollama-0.33.1/bin/ollama
nix-env: /nix/store/mg1284kfh1m2xms1ghsw4nv8vhqisj22-openssl-3.0.14/lib/libssl.so.3: version `OPENSSL_3.2.0' not found (required by /nix/store/fn0k2qlkgicm4z1s53z6132kkzi5izlr-curl-8.21.0/lib/libcurl.so.4)
nix-env: /nix/store/mg1284kfh1m2xms1ghsw4nv8vhqisj22-openssl-3.0.14/lib/libssl.so.3: version `OPENSSL_3.5.0' not found (required by /nix/store/ig1dybzs8qr3vwfsqlj1dcszbag1crpk-ngtcp2-1.23.0/lib/libngtcp2_crypto_ossl.so.0)
nix-env: /nix/store/r8qsxm85rlxzdac7988psm7gimg4dl3q-glibc-2.39-52/lib/libc.so.6: version `GLIBC_ABI_DT_X86_64_PLT' not found (required by /nix/store/ias8xacs1h3jy7xgwi2awvim61k2ji6c-glibc-2.42-67/lib/libresolv.so.2)
(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/ _ \ / _` | | |_| |/ _ \ / __| | |   | |_| | | | | |_) | |  Attack each friction point
# ahc ___ \ (_| | |  _  | (_) | (__  | |___|  _  | |_| |  __/|_|  
#  /_/   \_\__,_| |_| |_|\___/ \___|  \____|_| |_|\___/|_|   (_)  
# Ad Hoc CHOP: The Not-Managed-by-Git Safe-for-Client-Data place  

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

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

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

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

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

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

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

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

# --- START THIS DISCUSSION ---

# Context 1 (Edit-in selections from above and add new files immediately below)
# ~/repos/nixos/configuration.nix            #  <-- "Global" IaC context (most of you won't have)
# ~/repos/nixos/blogs.nix
# ~/repos/nixos/packages.nix                 #  <-- Full disclosure on pre-flake IaC available apps.
# ~/repos/nixos/services.nix                 #  <-- Running Linux system services.
# ~/repos/nixos/ai-acceleration.nix          #  <-- Paid a lot for your hardware? We've got you covered.
# ~/repos/nixos/hardware-configuration.nix   #  <-- Automatically generated by Nix. The ultimate in IaC transparency.

# Context 2
/home/mike/repos/nixos/ai-acceleration.nix
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/scripts/nixos-switch.py
/home/mike/repos/nixos/unstable-pin.json
! git ls-remote https://github.com/NixOS/nixpkgs refs/heads/nixos-unstable
! command -v nix-prefetch-url
! git -C ~/repos/nixos check-ignore -v unstable-pin.json scripts/nixos-switch.py; echo "check-ignore exit $?"
! ls -la ~/repos/nixos/unstable-pin.json ~/repos/nixos/scripts/nixos-switch.py
! readlink -f "$(command -v ollama)"
! nix-env --list-generations --profile /nix/var/nix/profiles/system | tail -2
! timeout 180 nixos-rebuild dry-build 2>&1 | grep -E 'ollama|will be (built|fetched)|error' | head -20

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

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

(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 $ patch
(nix) pipulate $ app
✅ WHOLE-FILE WRITE: OVERWROTE '/home/mike/repos/nixos/scripts/nixos-switch.py'.
(nix) pipulate $ cd ../nixos/
(nix) nixos $ git add /home/mike/repos/nixos/scripts/nixos-switch.py
(nix) nixos $ m
📝 Committing: chore: Pin nixos-unstable branch for Ollama build
[main d85edcf] chore: Pin nixos-unstable branch for Ollama build
 4 files changed, 185 insertions(+), 4 deletions(-)
 create mode 100644 scripts/nixos-switch.py
(nix) nixos $ patch
(nix) nixos $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/ai-acceleration.nix'.
(nix) nixos $ d
diff --git a/ai-acceleration.nix b/ai-acceleration.nix
index fcbd389..8860c58 100644
--- a/ai-acceleration.nix
+++ b/ai-acceleration.nix
@@ -4,7 +4,22 @@ let
   # ⚡ SURGICAL TRANSPLANT: Define the Unstable Channel source
   # This grabs the definition of packages from the unstable branch
 
-  unstable = import (builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz") {
+  # PINNED (2026-09-09). This used to fetch the bare .../archive/nixos-unstable
+  # tarball with no hash, and Nix re-resolves a hash-less tarball once
+  # tarball-ttl (an hour by default) has expired, so any `n` more than an hour
+  # after the last one silently rolled the branch forward -- and ollama with
+  # CUDA is not in the binary cache, so a moved branch was a half-hour local
+  # build nobody asked for. The rev and NAR sha256 now come from
+  # ./unstable-pin.json, which only scripts/nixos-switch.py (the `n` alias)
+  # writes, and only after a [y/N] that defaults to No. The hash makes the
+  # fetch fixed-output: root's evaluation finds the tree already in the store
+  # and never touches the network. To roll forward: `n`, answer y.
+  unstablePin = builtins.fromJSON (builtins.readFile ./unstable-pin.json);
+
+  unstable = import (builtins.fetchTarball {
+    url = "https://github.com/NixOS/nixpkgs/archive/${unstablePin.rev}.tar.gz";
+    sha256 = unstablePin.sha256;
+  }) {
     # We pass the same config (allowUnfree, cuda) so it matches your system
     config = config.nixpkgs.config;
   };

--- UNTRACKED (invisible to the diff above; m will stage these) ---
  + patch
(nix) nixos $ m
📝 Committing: chore: Pin nixpkgs unstable branch with hash and JSON config
[main fbccdbd] chore: Pin nixpkgs unstable branch with hash and JSON config
 1 file changed, 16 insertions(+), 1 deletion(-)
(nix) nixos $ patch
(nix) nixos $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/configuration.nix'.
(nix) nixos $ d
diff --git a/configuration.nix b/configuration.nix
index a5a7125..2994248 100644
--- a/configuration.nix
+++ b/configuration.nix
@@ -237,7 +237,12 @@ in
     m = "msg=$(python3 /home/mike/repos/pipulate/scripts/ai.py --auto --format plain 2>/dev/null | head -1) && [ -n \"$msg\" ] && echo \"📝 Committing: $msg\" && git commit -am \"$msg\" || echo '❌ ai.py returned empty message'";
     
     # NixOS Management
-    n = "cd ~/repos/nixos && sudo nixos-rebuild switch";
+    # `n` no longer rolls nixos-unstable forward on its own. The wrapper reads
+    # unstable-pin.json, asks GitHub whether the branch moved, and prompts
+    # [y/N] (Enter is No) before touching the pin; the switch itself is still
+    # `sudo nixos-rebuild switch`. `n --check` reports and stops, `n --roll`
+    # skips the question, anything else passes through to nixos-rebuild.
+    n = "cd ~/repos/nixos && python3 /home/mike/repos/nixos/scripts/nixos-switch.py";
 
     # Safety & Cleanup (Sanitized n2 - NO UPGRADE)
     n2 = ''

--- UNTRACKED (invisible to the diff above; m will stage these) ---
  + patch
(nix) nixos $ m
📝 Committing: chore: Update nixos-switch script
[main 9beee3d] chore: Update nixos-switch script
 1 file changed, 6 insertions(+), 1 deletion(-)
(nix) nixos $ git push
Enumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 48 threads
Compressing objects: 100% (13/13), done.
Writing objects: 100% (13/13), 4.76 KiB | 4.76 MiB/s, done.
Total 13 (delta 9), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (9/9), completed with 6 local objects.
To github.com:miklevin/nixos-config.git
   a449310..9beee3d  main -> main
(nix) nixos $

Ignition (what makes the patched code run before the AFTER reading – <F2>, nix develop, a re-ride – or none required):

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) nixos $ cd ~/repos/nixos && git add scripts/nixos-switch.py && python3 scripts/nixos-switch.py
📭 No unstable-pin.json on file; nixos-unstable must be pinned before the system can evaluate.
   nixos-unstable currently points at d6524aaca2ff07876657ae2b323f24be4874944b
   Pin it there? This first switch may rebuild Ollama once. [y/N] y
⏳ Prefetching nixpkgs d6524aa (roughly 45 MB, one time)...
📌 Pinned nixos-unstable at d6524aa -> /home/mike/repos/nixos/unstable-pin.json
🚀 sudo nixos-rebuild switch
building Nix...
building the system configuration...
these 3 derivations will be built:
  /nix/store/9qcd718skpaidhbc8kwjxhm1v2nhsnkj-etc-bashrc.drv
  /nix/store/rd3jpi9k9zx1w77qqddw3sh011pvb5hv-etc.drv
  /nix/store/xgfy0hsshl7j8vix5i5ky41rcr46l3xi-nixos-system-nixos-25.05.813814.ac62194c3917.drv
building '/nix/store/9qcd718skpaidhbc8kwjxhm1v2nhsnkj-etc-bashrc.drv'...
building '/nix/store/rd3jpi9k9zx1w77qqddw3sh011pvb5hv-etc.drv'...
building '/nix/store/xgfy0hsshl7j8vix5i5ky41rcr46l3xi-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/k05sw97588dsjqqpq14m1lrdiks9bk10-nixos-system-nixos-25.05.813814.ac62194c3917
(nix) nixos $

Okay, nice. I’m liking Fable 5.1 for coding more and more. I do not think this is easy stuff. As you look at the experiment you’ll see things outside the causal boundary and that’s because I was stuck in that half-hour system rebuild limbo as I was writing this article and one of those big CUDA/Ollama updates was taking place which was finished by the time I ran the experiment, so you will see newer version numbers and such. I think that’s fine. Your script still did its thing perfectly I think, though I won’t really know until the next time the archive changes.

4: Prompt: The unstable throttle landed: scripts/nixos-switch.py, unstable-pin.json, and the pinned fetchTarball in ai-acceleration.nix. Read the AFTER receipts against the BEFORE: did git ls-remote move between readings; does the pin’s rev equal the BEFORE ls-remote (the bootstrap pinned what it said it would); did the system generation advance; did ollama’s store path change (the one-time rebuild, or not); is the dry-build quiet now? Then report n from a fresh shell: does it say unchanged or moved, and did a bare Enter hold the pin? Name anything that misbehaved with the exact receipt line, and only then say whether reading the cached tarball’s identity out of root’s fetcher cache is worth a follow-up ride.

5: Deliverables: Less fear of having a surprise half-hour wait due to rapidly changing Ollama and CUDA Linux code when I use my n alias to rebuild NixOS. I think this is done as one of those rare 1-turn mission accomplished prompts.

Let’s do any final revisions before I do the article wrap-up protocol, please and thank you.

Fable 5.1 Extra: Context verified. Every command in the Manifest’s LIVE COMMAND RECEIPTS list has a body in the Codebase, and the ignition transcript in your paste is the receipt the receipts can’t give (it ran before this compile). Reading them against the BEFORE:

The branch did not move. d6524aaca2ff07876657ae2b323f24be4874944b in the BEFORE hand-run, the same in the AFTER ! git ls-remote, and the same in unstable-pin.json’s rev. The bootstrap pinned exactly what it said it would; the sha256 has the 52-char base32 shape fetchTarball wants; the pinned stamp 09:28:45Z is the 05:28 local mtime ls -la shows. Script on disk is 6,656 bytes, matching the Manifest byte for byte.

The one-time rebuild never came due, and the ignition transcript proves it. Your switch printed these 3 derivations will be built:etc-bashrc (the alias), etc, and the system closure. No ollama, no CUDA. Had the pinned tree differed from what root had cached, ollama would have been on that list. The 0.33.1 → 0.33.3 store-path change is the half-hour rebuild you said was in flight while you wrote: it resolved unstable to d6524aa the old way, activated between your BEFORE tap and the ignition, and the pin then took the same rev. Outside the causal boundary, as you flagged — and the ignition receipt is what acquits the bootstrap of it.

Two probes misbehaved, and one silence is not a quiet.

  • nix-env --list-generations: identical loader failures in both readings — nix-env: /nix/store/mg1284kfh1m2xms1ghsw4nv8vhqisj22-openssl-3.0.14/lib/libssl.so.3: version 'OPENSSL_3.2.0' not found and the GLIBC_ABI_DT_X86_64_PLT line. It never listed a generation. The command -v receipt places these tools in ~/.nix-profile/bin, a newer Nix than the libraries your login session’s LD_LIBRARY_PATH is pinning, and the one Nix command in this ride that ran with that variable cleared — nix-prefetch-url under the wrapper’s tool_env() — is the one that worked. So the generation question is answered only by the transcript’s Done. The new configuration is /nix/store/k05sw… line, not by the probe.
  • nixos-rebuild dry-build: (Executed successfully, no output) — but the Summary log clocks it at [0.0480s]. A system evaluation takes tens of seconds; 48 ms is a command that died on the doorstep, almost certainly the same loader failure, and my grep for error could not see it because ld.so’s message doesn’t contain that word. That was a discrimination failure I built: “nothing to build” and “never ran” printed the same thing. The BEFORE hand-run shows the same nothing. Do not read that receipt as quiet.
  • The pin is on disk but, on the evidence here, not in git. check-ignore exit 1 only says it isn’t ignored; the ignition transcript ends at Done. with no git add, and the m alias’s -a will not pick up an untracked file. Car 1 below fixes that by hand once; Car 2 makes the wrapper stage the pin whenever it writes one.

n from a fresh shell is not in evidence — no reading was taken. The etc-bashrc.drv build says the alias landed in /etc/bashrc; the --check probe below runs the wrapper’s happy path from the compiler’s shell without switching anything.

The fetcher-cache follow-up ride: not worth it. The cost it would have dodged never came due, and there is no unpinned cache left to read — the pin is the identity now.

Nothing in the wrapper misbehaved: bootstrap message, y, prefetch, pin, switch, three derivations. The revisions are the two broken probes (repaired in NEXT CONTEXT, not in the repo) and the staging gap.

1. PROBES

git ls-remote https://github.com/NixOS/nixpkgs refs/heads/nixos-unstable
git -C ~/repos/nixos ls-files --error-unmatch unstable-pin.json; echo "tracked exit $?"
readlink -f "$(command -v ollama)"
LD_LIBRARY_PATH= nix-env --list-generations --profile /nix/var/nix/profiles/system | tail -2
LD_LIBRARY_PATH= nix-instantiate --eval -E 'let p = builtins.fromJSON (builtins.readFile /home/mike/repos/nixos/unstable-pin.json); in builtins.fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/${p.rev}.tar.gz"; sha256 = p.sha256; }'
LD_LIBRARY_PATH= timeout 180 nixos-rebuild dry-build 2>&1 | tail -5; echo "dry-build exit ${PIPESTATUS[0]}"
python3 ~/repos/nixos/scripts/nixos-switch.py --check

Line 1 keeps tracking the branch across readings. Line 2 is loud both ways: tracked exit 0 after Car 1, exit 1 before it. Line 3 should now read the same store path in both readings while line 1 is unchanged — that steadiness is the pin doing its job. Line 4 is the repaired generation probe; if the loader errors persist with the variable cleared, my diagnosis is wrong and the AFTER will say so. Line 5 is the most direct receipt of the whole ride: it evaluates the pin exactly as ai-acceleration.nix does, and a store path returned in a fraction of a second is “found by hash, no network.” Line 6 is the repaired dry-build: tail -5 shows whatever it printed last, success or loader failure, and the exit code decides which — retire this line after the AFTER reading, it costs real seconds per compile once it actually runs. Line 7 is the wrapper’s read-only path, run by file path because aliases don’t resolve in the compiler’s shell; expect ✅ nixos-unstable unchanged at d6524aa unless the branch has moved, in which case it reports and stops without prompting.

2. NEXT CONTEXT

/home/mike/repos/nixos/ai-acceleration.nix
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/scripts/nixos-switch.py
/home/mike/repos/nixos/unstable-pin.json
! git ls-remote https://github.com/NixOS/nixpkgs refs/heads/nixos-unstable
! git -C ~/repos/nixos ls-files --error-unmatch unstable-pin.json; echo "tracked exit $?"
! readlink -f "$(command -v ollama)"
! LD_LIBRARY_PATH= nix-env --list-generations --profile /nix/var/nix/profiles/system | tail -2
! LD_LIBRARY_PATH= nix-instantiate --eval -E 'let p = builtins.fromJSON (builtins.readFile /home/mike/repos/nixos/unstable-pin.json); in builtins.fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/${p.rev}.tar.gz"; sha256 = p.sha256; }'
! LD_LIBRARY_PATH= timeout 180 nixos-rebuild dry-build 2>&1 | tail -5; echo "dry-build exit ${PIPESTATUS[0]}"
! python3 ~/repos/nixos/scripts/nixos-switch.py --check

3. PATCHES

Car 1 — bank the bootstrap pin (mutating actuator, its own commit story; m after it):

git -C ~/repos/nixos add unstable-pin.json

Car 2 — the wrapper stages what it writes, so a rolled pin never sits untracked again:

Target: /home/mike/repos/nixos/scripts/nixos-switch.py
[[[SEARCH]]]
    with open(PIN_PATH, "w", encoding="utf-8") as f:
        json.dump(pin, f, indent=2)
        f.write("\n")
    print(f"📌 Pinned {BRANCH} at {short(rev)} -> {PIN_PATH}")
[[[DIVIDER]]]
    with open(PIN_PATH, "w", encoding="utf-8") as f:
        json.dump(pin, f, indent=2)
        f.write("\n")
    print(f"📌 Pinned {BRANCH} at {short(rev)} -> {PIN_PATH}")
    # THE PIN IS IaC, SO IT RIDES WITH THE REPO. Stage it here so a plain
    # `git commit -am` (the `m` alias) carries it even the first time, when
    # it is a brand-new untracked file that -a alone leaves behind -- which
    # is exactly what the bootstrap ride did: pin on disk, not in git.
    # Staging only; the commit stays the operator's act.
    staged = subprocess.run(["git", "add", "--", PIN_PATH], cwd=REPO_DIR,
                            capture_output=True, text=True, check=False, env=tool_env())
    if staged.returncode == 0:
        print("   Staged for commit; `m` after the switch carries it.")
    else:
        print(f"   ⚠ Not staged ({staged.stderr.strip() or 'git add failed'}); add it by hand.")
[[[REPLACE]]]

Ignition: none required. The alias runs the script by path at call time, and Car 2’s new lines fire only inside write_pin, which only a y (or --roll) reaches — the next roll-forward is its first run. The --check probe exercises everything else.

4. PROMPT

Final revisions are in: the bootstrap pin is banked (Car 1), the wrapper now stages the pin it writes (Car 2), and the two probes that failed silently — nix-env under the session's LD_LIBRARY_PATH, and a dry-build that died in 48 ms behind a grep that could not see it — are repaired in this AFTER. Confirm the repaired readings are voiced, not silent: a generation list, a store path from the pin-driven fetchTarball eval, a dry-build exit code with its last lines, and the wrapper's --check verdict. Then this ride's stated goal — `n` never rolls nixos-unstable forward without a typed yes — is either witnessed or it is not. Say which, with the receipt line, and stop there; the dismount follows.

5. EXTERNAL DELIVERABLES

None this turn outside the repo. The probe repairs live in adhoc.txt, which is yours to paste, not a patch target.

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) nixos $ git ls-remote https://github.com/NixOS/nixpkgs refs/heads/nixos-unstable
git -C ~/repos/nixos ls-files --error-unmatch unstable-pin.json; echo "tracked exit $?"
readlink -f "$(command -v ollama)"
LD_LIBRARY_PATH= nix-env --list-generations --profile /nix/var/nix/profiles/system | tail -2
LD_LIBRARY_PATH= nix-instantiate --eval -E 'let p = builtins.fromJSON (builtins.readFile /home/mike/repos/nixos/unstable-pin.json); in builtins.fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/${p.rev}.tar.gz"; sha256 = p.sha256; }'
LD_LIBRARY_PATH= timeout 180 nixos-rebuild dry-build 2>&1 | tail -5; echo "dry-build exit ${PIPESTATUS[0]}"
python3 ~/repos/nixos/scripts/nixos-switch.py --check
d6524aaca2ff07876657ae2b323f24be4874944b	refs/heads/nixos-unstable
unstable-pin.json
tracked exit 0
/nix/store/ax1ybqxrja4q4yg9khgnfxqz6ik0zra2-ollama-0.33.3/bin/ollama
error: opening lock file '/nix/var/nix/profiles/system.lock': Permission denied
"/nix/store/j0xsrr9a6dx6b4rf3lnzmak43ff82cs6-source"
building the system configuration...
dry-build exit 0
✅ nixos-unstable unchanged at d6524aa (pinned 2026-09-09T09:28:45Z).
(nix) nixos $ 

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/ _ \ / _` | | |_| |/ _ \ / __| | |   | |_| | | | | |_) | |  Attack each friction point
# ahc ___ \ (_| | |  _  | (_) | (__  | |___|  _  | |_| |  __/|_|  Looking 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 ---

# Context 1 (Edit-in selections from above and add new files immediately below)
# ~/repos/nixos/configuration.nix            #  <-- "Global" IaC context (most of you won't have)
# ~/repos/nixos/blogs.nix
# ~/repos/nixos/packages.nix                 #  <-- Full disclosure on pre-flake IaC available apps.
# ~/repos/nixos/services.nix                 #  <-- Running Linux system services.
# ~/repos/nixos/ai-acceleration.nix          #  <-- Paid a lot for your hardware? We've got you covered.
# ~/repos/nixos/hardware-configuration.nix   #  <-- Automatically generated by Nix. The ultimate in IaC transparency.

# Context 2
# /home/mike/repos/nixos/ai-acceleration.nix
# /home/mike/repos/nixos/configuration.nix
# /home/mike/repos/nixos/scripts/nixos-switch.py
# /home/mike/repos/nixos/unstable-pin.json
# ! git ls-remote https://github.com/NixOS/nixpkgs refs/heads/nixos-unstable
# ! command -v nix-prefetch-url
# ! git -C ~/repos/nixos check-ignore -v unstable-pin.json scripts/nixos-switch.py; echo "check-ignore exit $?"
# ! ls -la ~/repos/nixos/unstable-pin.json ~/repos/nixos/scripts/nixos-switch.py
# ! readlink -f "$(command -v ollama)"
# ! nix-env --list-generations --profile /nix/var/nix/profiles/system | tail -2
# ! timeout 180 nixos-rebuild dry-build 2>&1 | grep -E 'ollama|will be (built|fetched)|error' | head -20

# Context 3
/home/mike/repos/nixos/ai-acceleration.nix
/home/mike/repos/nixos/configuration.nix
/home/mike/repos/nixos/scripts/nixos-switch.py
/home/mike/repos/nixos/unstable-pin.json
! git ls-remote https://github.com/NixOS/nixpkgs refs/heads/nixos-unstable
! git -C ~/repos/nixos ls-files --error-unmatch unstable-pin.json; echo "tracked exit $?"
! readlink -f "$(command -v ollama)"
! LD_LIBRARY_PATH= nix-env --list-generations --profile /nix/var/nix/profiles/system | tail -2
! LD_LIBRARY_PATH= nix-instantiate --eval -E 'let p = builtins.fromJSON (builtins.readFile /home/mike/repos/nixos/unstable-pin.json); in builtins.fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/${p.rev}.tar.gz"; sha256 = p.sha256; }'
! LD_LIBRARY_PATH= timeout 180 nixos-rebuild dry-build 2>&1 | tail -5; echo "dry-build exit ${PIPESTATUS[0]}"
! python3 ~/repos/nixos/scripts/nixos-switch.py --check

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

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

I already added the pin json to the git repo.

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) nixos $ patch
(nix) nixos $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/nixos/scripts/nixos-switch.py'.
(nix) nixos $ d
diff --git a/scripts/nixos-switch.py b/scripts/nixos-switch.py
index 7a0dc3c..43b8d61 100644
--- a/scripts/nixos-switch.py
+++ b/scripts/nixos-switch.py
@@ -119,6 +119,17 @@ def write_pin(rev, sha256):
         json.dump(pin, f, indent=2)
         f.write("\n")
     print(f"📌 Pinned {BRANCH} at {short(rev)} -> {PIN_PATH}")
+    # THE PIN IS IaC, SO IT RIDES WITH THE REPO. Stage it here so a plain
+    # `git commit -am` (the `m` alias) carries it even the first time, when
+    # it is a brand-new untracked file that -a alone leaves behind -- which
+    # is exactly what the bootstrap ride did: pin on disk, not in git.
+    # Staging only; the commit stays the operator's act.
+    staged = subprocess.run(["git", "add", "--", PIN_PATH], cwd=REPO_DIR,
+                            capture_output=True, text=True, check=False, env=tool_env())
+    if staged.returncode == 0:
+        print("   Staged for commit; `m` after the switch carries it.")
+    else:
+        print(f"   ⚠ Not staged ({staged.stderr.strip() or 'git add failed'}); add it by hand.")
 
 
 def ask_yes(prompt):

--- UNTRACKED (invisible to the diff above; m will stage these) ---
  + patch
(nix) nixos $ m
📝 Committing: chore: Stage pin file for consistent commit process
[main 0510266] chore: Stage pin file for consistent commit process
 1 file changed, 11 insertions(+)
(nix) nixos $ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 48 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 811 bytes | 811.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To github.com:miklevin/nixos-config.git
   d66cf6c..0510266  main -> main
(nix) nixos $

4: Prompt: Final revisions are in: the bootstrap pin is banked (Car 1), the wrapper now stages the pin it writes (Car 2), and the two probes that failed silently — nix-env under the session’s LD_LIBRARY_PATH, and a dry-build that died in 48 ms behind a grep that could not see it — are repaired in this AFTER. Confirm the repaired readings are voiced, not silent: a generation list, a store path from the pin-driven fetchTarball eval, a dry-build exit code with its last lines, and the wrapper’s –check verdict. Then this ride’s stated goal — n never rolls nixos-unstable forward without a typed yes — is either witnessed or it is not. Say which, with the receipt line, and stop there; the dismount follows.

5: Deliverables: Lower friction in daily process.

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:

  1. 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.
  2. 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.
  3. 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.
  4. DANGLING: what carries forward unbanked? One line each, no essays.
  5. SEED: the adhoc.txt lines (and TODO_SLUGS if narrative context is needed) for the next ride’s first compile.
  6. 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.
  7. 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:

1. VERIFY

Goal: n runs a plain sudo nixos-rebuild switch and never rolls nixos-unstable forward on its own; when the branch has moved it says so and asks, Enter holds, only y/yes rolls.

Met, with one stated limit. The roll-forward mechanism is gone and that is witnessed by this compile: ! nix-instantiate --eval fed from the pin returned "/nix/store/j0xsrr9a6dx6b4rf3lnzmak43ff82cs6-source" in 0.05 s — found by hash, no download; ! nixos-rebuild dry-build ran a real 28.4 s evaluation and printed building the system configuration... / dry-build exit 0 with no derivation list; ! --check printed ✅ nixos-unstable unchanged at d6524aa (pinned 2026-09-09T09:28:45Z).; ! ls-files printed tracked exit 0; ! readlink read the same ollama-0.33.3 path as this ride’s BEFORE while ! git ls-remote read d6524aa in every reading of the whole ride. The limit: the branch never moved, so the moved-branch [y/N] path — the Enter-holds behavior the goal names — has not been exercised. The only prompt answered in evidence is the bootstrap’s y, in the operator transcript pasted into this payload, not a live receipt. Your own caveat stands: the next branch move is the test.

Ignition: every required one fired — the bootstrap switch (transcript: these 3 derivations will be built, none of them ollama, Done.). Car 2’s staging code needed none and has not run; no probe claims to have read it.

Probe repair, honestly read: clearing LD_LIBRARY_PATH removed the loader failures (diagnosis confirmed), but ! nix-env --list-generations now reports error: opening lock file '/nix/var/nix/profiles/system.lock': Permission denied — the system profile listing needs root. Voiced, not silent; still not a generation list.

2. BANK

Two receipts graduate into the comments they prove.

Target: /home/mike/repos/nixos/ai-acceleration.nix
[[[SEARCH]]]
  # fetch fixed-output: root's evaluation finds the tree already in the store
  # and never touches the network. To roll forward: `n`, answer y.
  unstablePin = builtins.fromJSON (builtins.readFile ./unstable-pin.json);
[[[DIVIDER]]]
  # fetch fixed-output: root's evaluation finds the tree already in the store
  # and never touches the network. To roll forward: `n`, answer y.
  # WITNESSED 2026-09-09: a user-side `nix-instantiate --eval` of this exact
  # fetchTarball, fed from the pin, returned its store path in 0.05 s; the
  # bootstrap switch built 3 derivations (etc-bashrc, etc, the system) and no
  # ollama; a 28 s dry-build exited 0 with nothing to build. The branch did
  # not move across the ride (d6524aa in every reading), so the moved-branch
  # [y/N] path is still exercised only by the bootstrap's own y.
  unstablePin = builtins.fromJSON (builtins.readFile ./unstable-pin.json);
[[[REPLACE]]]
Target: /home/mike/repos/nixos/scripts/nixos-switch.py
[[[SEARCH]]]
def tool_env():
    """ai-acceleration.nix exports a CUDA LD_LIBRARY_PATH session-wide, and
    that path can break Nix's own binaries (apply.py clears it before
    nix-instantiate for the same reason). Clear it for every tool call."""
[[[DIVIDER]]]
def tool_env():
    """ai-acceleration.nix exports a CUDA LD_LIBRARY_PATH session-wide, and
    that path can break Nix's own binaries (apply.py clears it before
    nix-instantiate for the same reason). Clear it for every tool call.

    WITNESSED 2026-09-09: with the session variable set, the ~/.nix-profile
    nix-env died in the loader (libssl.so.3: OPENSSL_3.2.0 not found;
    libc.so.6: GLIBC_ABI_DT_X86_64_PLT not found) and nixos-rebuild
    dry-build exited in 48 ms; with it cleared, the same tools evaluated the
    whole system in 28 s. The prefetch in this file only ever ran cleared,
    which is why the bootstrap worked while the hand-run probes beside it
    did not."""
[[[REPLACE]]]

3. DANGLING

  • The moved-branch [y/N] path: designed, unwitnessed until nixos-unstable moves; the first n after that is the reading.
  • write_pin’s staging branch (Car 2): runs only on the next roll; unwitnessed.
  • Listing system generations without root: the profile lock refuses; ls -lt /nix/var/nix/profiles/ | head -3 is the untested alternative.
  • A probe rule with no home in this context: a probe whose “never ran” and “nothing to report” print the same thing is not a probe; the Summary log’s per-command timing was the tell both times.
  • The login session’s LD_LIBRARY_PATH names libraries older than the running system; a re-login refreshes it, and putting stdenv.cc.cc.lib on that variable session-wide remains a standing hazard for any user-profile Nix.

4. SEED

/home/mike/repos/nixos/ai-acceleration.nix
/home/mike/repos/nixos/scripts/nixos-switch.py
/home/mike/repos/nixos/unstable-pin.json
! git ls-remote https://github.com/NixOS/nixpkgs refs/heads/nixos-unstable
! python3 ~/repos/nixos/scripts/nixos-switch.py --check
! readlink -f "$(command -v ollama)"
! LD_LIBRARY_PATH= nix-instantiate --eval -E 'let p = builtins.fromJSON (builtins.readFile /home/mike/repos/nixos/unstable-pin.json); in builtins.fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/${p.rev}.tar.gz"; sha256 = p.sha256; }'
! ls -lt /nix/var/nix/profiles/ | head -3

The 28-second dry-build line is retired; it did its job once.

5. CLOSING

The half-hour rebuilds were never a bug in Ollama or CUDA. They were a decision being made without you: a hash-less fetch is a standing instruction to Nix to go look again, and the TTL decided when. The fix was not cleverer caching but returning the decision to its owner — one small JSON file that says this commit, and one question at the terminal whose default answer is no. That is the book’s arc in miniature: infrastructure as code means the machine’s future is written down somewhere you can read, diff, and commit, and a pin file is exactly that sentence. The ride also paid for its own lesson twice over. Two probes came back empty and looked like good news, and only the clock said otherwise — 48 milliseconds is not the sound of a system evaluating itself. The render is not the file; the silence is not the quiet. The receipts that finally spoke — a store path in five hundredths of a second, an exit code after twenty-eight — are the ones a stranger can check.

6. NOTARIZE

Fingerprint of the sealed bytes: Summary Total Bytes: 219,859 (UTF-8), Verified Tokens: 56,391; Manifest Codebase 32,780 tokens, Prompt 17,992; 7 live command receipts, the longest-running the dry-build at 28.4037 s; the receipts carry no timestamps of their own, so the newest dated content among them is pinned 2026-09-09T09:28:45Z inside the --check receipt, on a compile dated 2026-09-09.

Deed: foo-________.zip (ls -t foo-*.zip head -1) — the seal footer beneath this payload reads foo-deb651af-1292.zip; if the ls agrees, that is the line.

Book Analysis

Ai Editorial Take

What stands out most in this entry is how it treats time-based background expiration as a subtle form of silent automation failure. By forcing an explicit prompt and a hash-backed check before accepting new package trees, the architecture acknowledges that human intent should always gate heavy compute cycles.

🐦 X.com Promo Tweet

Stop unrequested half-hour NixOS rebuilds caused by silent branch drift. Learn how to pin your packages with verification hashes and a checkable prompt wrapper. https://mikelev.in/futureproof/taming-the-unstable-throttle-replayable-nixos-upgrades/ #NixOS #DevOps #Workflows

Title Brainstorm

  • Title Option: Taming the Unstable Throttle: Engineering Replayable NixOS Upgrades
    • Filename: taming-the-unstable-throttle-replayable-nixos-upgrades.md
    • Rationale: Direct, engaging, and highlights the transition from invisible drift to checkable control.
  • Title Option: Pinning the Stream: Replayable Package Management in NixOS
    • Filename: pinning-the-stream-replayable-package-management-nixos.md
    • Rationale: Focuses on the core technical mechanism of branch pinning and validation.
  • Title Option: Stopping Silent Drift: Interactive Safeguards for System Builds
    • Filename: stopping-silent-drift-interactive-safeguards-system-builds.md
    • Rationale: Appeals to broader infrastructure reliability themes without relying on buzzwords.

Content Potential And Polish

  • Core Strengths:
    • Provides a concrete solution to a frustrating, real-world development friction point.
    • Integrates rigorous pre- and post-execution probe analysis to verify code behavior.
    • Demonstrates excellent command-line pragmatism and error handling.
  • Suggestions For Polish:
    • Ensure shell environment prerequisites like LD_LIBRARY_PATH handling are well-documented for readers trying similar setups.
    • Highlight the distinction between blocking unexpected network fetches versus manual roll-forward workflows.

Next Step Prompts

  • Write a follow-up guide on automating rollback triggers if a pinned package build fails downstream sanity checks.
  • Explore how similar local gating patterns can be applied to multi-repository dependency updates.