---
title: 'The Three-Door Installer: Named Prompts, Split DNS, and Verifiable Releases'
permalink: /futureproof/the-three-door-installer-and-named-prompts/
canonical_url: https://mikelev.in/futureproof/the-three-door-installer-and-named-prompts/
description: "In this entry, I am working at the sharp, uncompromising intersection\
  \ of local hardware, networking, and automated tooling. My voice is direct, pragmatic,\
  \ and intentionally intolerant of clever ambiguities that waste cycles. When an\
  \ AI collaborator hands down hazy shorthand like 'run on the desk,' I don't just\
  \ work around it\u2014I call it out, break it down Barney-style, and codify a permanent\
  \ rule to prevent it from ever happening again. My priority is absolute transparency:\
  \ if a bash installer is piped across the internet, the user must be encouraged\
  \ to pipe it to cat or less first to read what it does before executing it. I take\
  \ pride in catching the quiet bugs that pass standard checks\u2014such as finding\
  \ that a release script updated GitHub and one web root while leaving two live domains\
  \ serving stale installer headers. This entry reflects my ongoing commitment to\
  \ building tools that are checkable, auditable, and resilient from the physical\
  \ router all the way to the terminal prompt."
meta_description: Fixing pfSense split DNS for a third domain led to named terminal
  prompts, audit-ready installer pipes, and synchronized multi-door releases.
excerpt: Fixing pfSense split DNS for a third domain led to named terminal prompts,
  audit-ready installer pipes, and synchronized multi-door releases.
meta_keywords: pfSense split DNS, host overrides, curl SSL certificate, named terminal
  prompts, Pipulate release pipeline, nixops installer sync, verifiable automation,
  bash pipe QA
layout: post
sort_order: 3
gdoc_url: https://docs.google.com/document/d/1DKha2umw6e6rp567ag806BOtG35C3waFCTEbe4ijdh4/edit?usp=sharing
---


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

What began as a quick fix for an SSL certificate warning on a freshly minted third domain (qamy.ai) quickly opened into a masterclass in operational hygiene and release integrity. In a local-first computing environment operating behind a pfSense firewall, public DNS lookups from LAN devices hit a classic hairpin failure: the router answers port 443 with its own administrative certificate rather than the internal server's Let's Encrypt credentials. Resolving this required an unbound host override, but the friction of coordinating steps between two physical machines exposed an even deeper vulnerability: conversational ambiguity. When an AI partner offers vague instructions like "run on the desk," the operator is left guessing which terminal chair to occupy. This entry chronicles the formalization of two vital disciplines: "The Machine Is Named by Its Prompt," establishing that every command block must explicitly cite the operator's active shell prompt, and the realization that a repository release must synchronize physical installer payloads across every live distribution door rather than trusting a single git push. It stands as an important case study in inspecting the pipe before running it and turning edge friction into verifiable, checkable workflows.

**TL;DR**: A newly launched domain, qamy.ai, could not be tested from a laptop inside the author's home network: the router answered for the name with its own self-signed certificate. The fix was one row in the router's DNS resolver, made by hand and verified from two machines. The same session found that the one installer script served from three addresses was being updated at only one of them by the release pipeline, and fixed that with a shared sync function that the release now calls. Along the way a rule was written down for the AI assistant: name the machine every command runs on by the prompt string the operator sees, never by a nickname. Every change was verified by running identical commands before and after, with the outputs recorded.

---

## Technical Journal Entry Begins

> *(For latent-space provenance: The hash pipulate-levinix-epoch-01-a1a0ffef20366309 ties this article to /futureproof/the-three-door-installer-and-named-prompts/ under the pipulate-levinix covenant.)*


<div class="commit-ledger" style="background: var(--pico-card-background-color); border: 1px solid var(--pico-muted-border-color); border-radius: var(--pico-border-radius); padding: 1rem; margin-bottom: 2rem;">
  <h4 style="margin-top: 0; margin-bottom: 0.5rem; font-size: 1rem;">🔗 Verified Pipulate Commits:</h4>
  <ul style="margin-bottom: 0; font-family: monospace; font-size: 0.9rem;">
    <li><a href="https://github.com/pipulate/pipulate/commit/579708f7" target="_blank">579708f7</a> (<a href="https://github.com/pipulate/pipulate/commit/579708f7.patch" target="_blank">raw</a>)</li>
    <li><a href="https://github.com/pipulate/pipulate/commit/08b27a2a" target="_blank">08b27a2a</a> (<a href="https://github.com/pipulate/pipulate/commit/08b27a2a.patch" target="_blank">raw</a>)</li>
    <li><a href="https://github.com/pipulate/pipulate/commit/4de14a74" target="_blank">4de14a74</a> (<a href="https://github.com/pipulate/pipulate/commit/4de14a74.patch" target="_blank">raw</a>)</li>
    <li><a href="https://github.com/pipulate/pipulate/commit/96fdeb18" target="_blank">96fdeb18</a> (<a href="https://github.com/pipulate/pipulate/commit/96fdeb18.patch" target="_blank">raw</a>)</li>
    <li><a href="https://github.com/pipulate/pipulate/commit/062d679d" target="_blank">062d679d</a> (<a href="https://github.com/pipulate/pipulate/commit/062d679d.patch" target="_blank">raw</a>)</li>
    <li><a href="https://github.com/pipulate/pipulate/commit/125808bf" target="_blank">125808bf</a> (<a href="https://github.com/pipulate/pipulate/commit/125808bf.patch" target="_blank">raw</a>)</li>
    <li><a href="https://github.com/pipulate/pipulate/commit/83843f22" target="_blank">83843f22</a> (<a href="https://github.com/pipulate/pipulate/commit/83843f22.patch" target="_blank">raw</a>)</li>
  </ul>
</div>
**MikeLev.in**: I did a step not long ago that made testing both mikelev.in and npvg.org
from inside my LAN and behind my pfSense router without https security warnings.
I just launched qamy.ai and now am having the same problems testing it on my Mac
which is on my LAN. This should bet across the gist of the problem:

```zsh
Last login: Tue Sep 29 09:36:11 on ttys000
michaellevin@MichaelMacBook-Pro ~ % rm -rf npvg                       
michaellevin@MichaelMacBook-Pro ~ % rm -rf ~/.config/pipulate         
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://qamy.ai | less
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://qamy.ai | echo

curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://npvg.org | less
michaellevin@MichaelMacBook-Pro ~ % # Yep that worked
zsh: command not found: #
michaellevin@MichaelMacBook-Pro ~ % 
```

Oh ha ha, zsh doesn't let you do comments the way bash does. Neither here nor
there. The important thing is to get some 2nd Brain clues about how I did this
in the recent past.

```bash
(nix) pipulate $ rgx -t grim,article 10 pfsense mikelev.in security warning
# 🎯 Targets: 2=grim Grimoire (Private) + 1=article MikeLev.in (Public) [Oldest First]

/home/mike/repos/trimnoir/_posts/2026-05-03-building-the-forever-machine.md  # [Idx: 1 | Order: 1 | Tokens: 34,849 | Bytes: 147,559]
/home/mike/repos/trimnoir/_posts/2026-05-17-clipboard-air-gap-network-diode.md  # [Idx: 2 | Order: 3 | Tokens: 12,107 | Bytes: 57,500]
/home/mike/repos/trimnoir/_posts/2026-06-21-traveling-business-card-self-healing-address.md  # [Idx: 3 | Order: 2 | Tokens: 22,823 | Bytes: 89,863]
/home/mike/repos/trimnoir/_posts/2026-07-18-second-interpreter-rule-engineering-ai-workflows.md  # [Idx: 4 | Order: 1 | Tokens: 42,756 | Bytes: 174,281]
/home/mike/repos/trimnoir/_posts/2026-07-27-bridging-browser-automation-and-reproducible-ai-workflows.md  # [Idx: 5 | Order: 4 | Tokens: 266,148 | Bytes: 795,746]
/home/mike/repos/trimnoir/_posts/2026-08-01-mother-cat-kata-deterministic-walk.md  # [Idx: 6 | Order: 3 | Tokens: 151,281 | Bytes: 623,674]
/home/mike/repos/trimnoir/_posts/2026-09-13-model-handoff-and-the-cellular-witness.md  # [Idx: 7 | Order: 1 | Tokens: 85,669 | Bytes: 347,971]
/home/mike/repos/trimnoir/_posts/2026-09-13-acme-airlock-cellular-receipts.md  # [Idx: 8 | Order: 2 | Tokens: 40,077 | Bytes: 150,578]
/home/mike/repos/trimnoir/_posts/2026-09-14-wrong-front-door-split-dns-dhcp.md  # [Idx: 9 | Order: 2 | Tokens: 40,173 | Bytes: 153,970]
/home/mike/repos/trimnoir/_posts/2026-09-29-qamy-door-cellular-witness-replayable-receipts.md  # [Idx: 10 | Order: 1 | Tokens: 57,075 | Bytes: 199,673]
📋 TODO_SLUGS block (≤8 newest) → clipboard (type xp to compile)
(nix) pipulate $ 
```

Okay, that'll cover it. I'll put the `rgxc` version of that into `context.txt`
and the AI will know everything it needs to to drag me along to the same
solution for it. There's a dangling dynamic DNS thing that I have implemented
for mikelev.in but not for either qamy.ai or npvg.org that this makes me think
of but we shouldn't solve that on this turn. I mention it for carry-over
dangling to-do earmark reasons but this article should be a quick in-and-out to
make testing qamy.ai on my Mac behind my pfSense router on my LAN not trigger
https security warnings, please and thank you.

## The Hairpin Trap: Why Local SSL Breaks on the Third Door

```text
Fable 5.1 Thinking

Thought for 2m 9s

Reviewing the context around a newly launched domain.

Tracing the hairpin DNS issue behind the certificate error.

Planning probe checks to confirm whether the override took effect.

Drafting certificate and resolver probes to verify the override.

Listing read-only DNS and certificate checks for the desk.

Refining which resolver and port checks belong on the desk.

Assembling the exact probe commands without repo changes needed.

Outlining router and Mac steps to verify the fix.

Drafting a concise handoff message with the outstanding checks.

Choosing the single most reliable certificate probe command.

Finalizing the Mac and router steps for the handoff.

Explaining how the hairpin fix resolves the certificate error.

Laying out the router and Mac probe commands to run.
```

**Fable 5.1 Extra**: Context verified. One live receipt this compile, the `rgxc` grep; no network reading ran, so everything below is inference from the Mac paste and the 09-14 and 09-29 articles until the probes print.

**Same wrong door, third name.** The Mac's last words are the 09-14 Mac's last words to the character: `unable to get local issuer certificate` is curl stopping at the first question (do I trust whoever signed this?), which a Let's Encrypt certificate never triggers and pfSense's self-signed admin certificate always does. The half of the paste that matters is npvg.org working from the same terminal: it proves the Mac is asking the router (the 09-14 DHCP fix held, the lease is clean) and the router knows where npvg.org lives. So the Mac asks the router for qamy.ai, unbound has no row for it, falls through to the public answer, the Mac knocks on its own front door, and the router answers with its own certificate. The 09-29 article wrote this exact hand step down and left it open (its §5 Host Override, "do it whenever the window is open"), and its own desk reading was `dig qamy.ai` public, `dig npvg.org` LAN. That's the missing row.

**The fix is one router screen, not a certificate and not a curl flag.** Add the qamy.ai host override with www as an additional name, Apply, then flush the Mac's resolver cache. That last step is the trap on macOS: curl reads mDNSResponder's cache, which now holds the public answer, while `dig` bypasses that cache entirely, so `dscacheutil -q host` is the honest Mac reading and `dig` can say LAN while curl still hairpins. Nothing in the repo changes; nginx and ACME on Honeybot are untouched.

**Falsifier.** If the desk's `dig +short qamy.ai` already reads the LAN address, the override is in and the router is acquitted; then the odd one out is the Mac's cache or lease, and only the Mac steps in §5 apply.

**Carry-over, not this ride.** Third static A record on one address with one heartbeat; the DDNS updater exists for mikelev.in only. Earmarked in the caboose.

## 1. PROBES

Run on the desk, before touching the router:

```text
dig +short qamy.ai
dig +short www.qamy.ai
dig +short npvg.org
dig +short qamy.ai @1.1.1.1
curl -ksv --max-time 10 -o /dev/null https://qamy.ai
```

Lines 1–3 are the differs/matches reading: before, qamy.ai and www read the public address and npvg.org reads LAN; after, all three read the same LAN address. Line 4 is the public answer for contrast and should not change. Line 5 prints who answered and whose certificate: `-k` is observation only, it lets curl print the Server certificate block that a failed verify otherwise withholds, and it is never the workaround. Before: `Connected to qamy.ai (<public address>)`, the router's subject and issuer, verify result 20, the router's page headers. After: the LAN address, `CN=qamy.ai`, an issuer of Let's Encrypt, verify result ok (0), nginx 200. If line 1 already reads LAN before you touch anything, the falsifier fired: skip the router, do the Mac steps.

## 2. NEXT CONTEXT

Retire the `rgxc` line; it did its job. context.txt for the next compile is the AFTER reading and nothing else, since the ride carries no patch:

```text
! dig +short qamy.ai
! dig +short www.qamy.ai
! dig +short npvg.org
! dig +short qamy.ai @1.1.1.1
! curl -ksv --max-time 10 -o /dev/null https://qamy.ai
```

## 3. PATCHES

No repo patches required. No ignition required: the change lives in unbound on the router, Apply Changes on that screen restarts it, and every probe above resolves through it at call time.

## 4. PROMPT

```text
The pfSense host override for qamy.ai (+www) is in and Applied, and the Mac's resolver cache was flushed. The `!` receipts above are the AFTER reading from the desk; the Mac readings are pasted below. Confirm the straddle closed: desk lines 1-3 equal, line 4 unchanged, line 5 reading CN=qamy.ai / Let's Encrypt / verify ok, Mac curl serving the installer. Name anything still open. Do not solve the DDNS gap this turn: mikelev.in has the updater, npvg.org and qamy.ai are static copies of its number; it is a carry-over earmark only.

[Mac paste]
```

## 5. EXTERNAL DELIVERABLES

**Router console, manual, no command.** pfSense → Services → DNS Resolver → General Settings → Host Overrides → Add. Host `qamy`, Domain `ai`, IP Address the Honeybot LAN address (the same one on the npvg.org row). Under Additional Names for this Host: Host `www`, Domain `qamy.ai`. Save, then Apply Changes on the green banner. On 09-14 this screen "did nothing" because no device asked the router; that's fixed, so it takes effect on the next uncached query.

**Mac witness, manual, not echoed** (context.txt runs on the desk, so these can't ride as `!` lines):

```text
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
dscacheutil -q host -a name qamy.ai
dig +short qamy.ai
curl -fsSL https://qamy.ai | head -3
```

Line 2 is the address curl will actually use; line 3 is what the router says; they should agree on the LAN address after the flush. If line 2 still reads the public address, `scutil --dns | head -20` shows which resolver the Mac holds; a Google or Cloudflare address there means a stale lease or a hand-set DNS on that interface, and the 09-14 cure applies: renew the lease (Wi-Fi off and on) and re-run the four lines. The phone on Wi-Fi is the free third witness once the Mac reads clean.

**MikeLev.in**: 



## THE PROOF STRADDLE

> 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: BEFORE PROBE** (paste in bash): 

```bash
Left-hand causal boundary "blast radius" established. Make 1 change and test.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ dig +short qamy.ai
dig +short www.qamy.ai
dig +short npvg.org
dig +short qamy.ai @1.1.1.1
curl -ksv --max-time 10 -o /dev/null https://qamy.ai
[REDACTED_IP]
qamy.ai.
[REDACTED_IP]
[REDACTED_IP]
[REDACTED_IP]
* Host qamy.ai:443 was resolved.
* IPv6: (none)
* IPv4: [REDACTED_IP]
*   Trying [REDACTED_IP]:443...
* ALPN: curl offers h2,http/1.1
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [1551 bytes data]
* SSL Trust: peer verification disabled
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
{ [1 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [19 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [1189 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
*   subject: O=pfSense webConfigurator Self-Signed Certificate; CN=pfSense-6808ceb59b08f
*   start date: Sep 14 10:07:05 2026 GMT
*   expire date: Oct 17 10:07:05 2027 GMT
*   issuer: O=pfSense webConfigurator Self-Signed Certificate; CN=pfSense-6808ceb59b08f
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* OpenSSL verify result: 12
*  SSL certificate verification failed, continuing anyway!
* Established connection to qamy.ai ([REDACTED_IP] port 443) from [REDACTED_IP] port 41852 
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://qamy.ai/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: qamy.ai]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.21.0]
* [HTTP/2] [1] [accept: */*]
} [5 bytes data]
> GET / HTTP/2
> Host: qamy.ai
> User-Agent: curl/8.21.0
> Accept: */*
> 
* Request completely sent off
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [57 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [57 bytes data]
< HTTP/2 200 
< server: nginx
< date: Tue, 29 Sep 2026 14:06:38 GMT
< content-type: text/html; charset=UTF-8
< x-frame-options: SAMEORIGIN
< last-modified: Tue, 29 Sep 2026 14:06:38 GMT
< set-cookie: PHPSESSID=e00b7e79881ff8b8134d45d9620e1b18; path=/
< expires: Thu, 19 Nov 1981 08:52:00 GMT
< cache-control: no-store, no-cache, must-revalidate
< pragma: no-cache
< strict-transport-security: max-age=31536000
< x-content-type-options: nosniff
< 
{ [3797 bytes data]
* Connection #0 to host qamy.ai:443 left intact
(nix) pipulate $ 
```

**2: AFTER PROBE** (in context.txt): 

```text
# context.txt: the list of files an AI will read. context opens it, compile builds it.
# Keys: j down, k up; Esc then :q quits, :q! discards, :wq saves and quits.
# One line per thing the AI reads: a file path, or a command after `! `.
# A line that starts with # is a comment: that file is not read.
# To add a line: i starts typing, Esc stops. Absolute paths work from anywhere.
# Web pages, APIs and the connector words: chapter XVIII of foo_files.py.

# --- THE 40K-FOOT VIEW (uncomment on a first turn; comment out on the second) ---
# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- the book's spine, one line per article, newest first
# ~/repos/nixos/autognome.py  # <-- the machine's morning routine (this author's NixOS box only)
# init.lua                    # <-- the editor keys that drive the day
# assets/installer/install.sh # <-- how a stranger's machine gets this workshop
# GLOSSARY.md                 # <-- the terms, defined
# flake.nix                   # <-- the environment, pinned: here is my hardware, here is my state
# prompt_foo.py               # <-- the compiler that builds the payload
# foo_files.py                # <-- the router: which files ride, and this book's outline
# scripts/articles/lsa.py     # <-- the second brain: the article corpus behind `rgx`, `rgxc` and `posts`
# requirements.in             # <-- the Python packages, pinned
# pyproject.toml              # <-- the PyPI package
# __init__.py                 # <-- the version

# --- ACTUATORS (cheap; include when the AI should be able to act, not only read) ---
# cli.py                      # <-- tool calls from the command line
# scripts/xp.py               # <-- turns a pasted reply into the next context
# scripts/ai.py               # <-- a local AI writes the commit messages
# scripts/crawl.py            # <-- crawl a site into the next turn
# scripts/weblogin.py         # <-- warm a login on the persistent browser profile
# scripts/webclip_2_markdown.py  # <-- a web page, clipped, as markdown

# --- RARE ---
# scripts/foo_cartridge.py    # <-- the sealed archive: writer and verifier
# scripts/foo_replay.py       # <-- replay a sealed archive on another machine
# release.py                  # <-- how a release reaches GitHub and PyPI
# imports/voice_synthesis.py  # <-- the voice
# imports/ascii_displays.py   # <-- the ASCII art
# scripts/release/version_sync.py  # <-- version stamping (to be folded into release.py)

# --- THIS DISCUSSION ---
# The files and commands for the work in front of you. Paste the NEXT CONTEXT
# block an AI hands back directly below this line; the AI will correct a guess.

# Context 1
# ! rgxc -t grim,article 10 pfsense mikelev.in security warning

# Context 2
! dig +short qamy.ai
! dig +short www.qamy.ai
! dig +short npvg.org
! dig +short qamy.ai @1.1.1.1
! curl -ksv --max-time 10 -o /dev/null https://qamy.ai
```

**3: PATCHES** (the 1 change): None this turn.

**4: PROMPT**: The pfSense host override for qamy.ai (+www) is in and Applied, and the Mac's resolver cache was flushed. The `!` receipts above are the AFTER reading from the desk; the Mac readings are pasted below. Confirm the straddle closed: desk lines 1-3 equal, line 4 unchanged, line 5 reading CN=qamy.ai / Let's Encrypt / verify ok, Mac curl serving the installer. Name anything still open. Do not solve the DDNS gap this turn: mikelev.in has the updater, npvg.org and qamy.ai are static copies of its number; it is a carry-over earmark only.

I can't tell you how much I hate instructions like this:

> Run on the desk, before touching the router:

Above I did it on the Pipulate Prime Linux box but I think what I'm supposed to
paste here is the probe from above but run on ***IN THE TERMINAL ON THE MAC!***

What part of the Dory rule and Break it down Barney-style did you not
understand? Assume the human you're tool-calling will make every mistake
possible when you set out our Claude-esque over-eruditic H.M. Wogglebug making
assumptions because it's clever intentionally obfuscated unnecessary
intelligence-test riddle-puzzles for the human you're tool-calling.  

Did I make that clear? Do we need to constitutionalize anything? Does it belong
in `apply.py`?

## The Dory Rule: The Machine Is Named by Its Prompt

```zsh
michaellevin@MichaelMacBook-Pro ~ % dig +short qamy.ai
dig +short www.qamy.ai
dig +short npvg.org
dig +short qamy.ai @1.1.1.1
curl -ksv --max-time 10 -o /dev/null https://qamy.ai
[REDACTED_IP]
qamy.ai.
[REDACTED_IP]
[REDACTED_IP]
[REDACTED_IP]
* Host qamy.ai:443 was resolved.
* IPv6: (none)
* IPv4: [REDACTED_IP]
*   Trying [REDACTED_IP]:443...
* Connected to qamy.ai ([REDACTED_IP]) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
} [312 bytes data]
* (304) (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* (304) (IN), TLS handshake, Unknown (8):
{ [19 bytes data]
* (304) (IN), TLS handshake, Certificate (11):
{ [1189 bytes data]
* (304) (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* (304) (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* (304) (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
*  subject: O=pfSense webConfigurator Self-Signed Certificate; CN=pfSense-6808ceb59b08f
*  start date: Sep 14 10:07:05 2026 GMT
*  expire date: Oct 17 10:07:05 2027 GMT
*  issuer: O=pfSense webConfigurator Self-Signed Certificate; CN=pfSense-6808ceb59b08f
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://qamy.ai/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: qamy.ai]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: qamy.ai
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/2 200 
< server: nginx
< date: Tue, 29 Sep 2026 14:13:25 GMT
< content-type: text/html; charset=UTF-8
< x-frame-options: SAMEORIGIN
< last-modified: Tue, 29 Sep 2026 14:13:25 GMT
< set-cookie: PHPSESSID=ccffab72f5b97a63867707867a56f026; path=/
< expires: Thu, 19 Nov 1981 08:52:00 GMT
< cache-control: no-store, no-cache, must-revalidate
< pragma: no-cache
< strict-transport-security: max-age=31536000
< x-content-type-options: nosniff
< 
{ [3797 bytes data]
* Connection #0 to host qamy.ai left intact
michaellevin@MichaelMacBook-Pro ~ % 
```

**5: DELIVERABLE**: The ability to do test-installs of the new AI QA'ing system
from the Mac while on my home LAN where pfSense interferes with https security
certificates.

**Note**: We capture thoughts.

```text
Fable 5.1 Thinking

Thought for 2m 11s

Untangling confusion over where the network tests actually ran.

Cross-checking timestamps against the claimed router fix.

Confirming both machines diagnose the same unfixed router gap.

Deciding whether the naming rule belongs in code or docs.

Pinning probes to the compiling machine by construction.

Drafting exact router-form steps using values from existing rows.

Sequencing verification steps across router, Linux, and Mac.

Locating which file should record the new checklist rule.

Choosing a quote-safe command to locate the checklist file.

Drafting the naming rule and structuring the ordered response plan.

Drafting Mac verification commands and framing the closing reply.

Finalizing the paste-ready verification commands for the Mac.
```

**Fable 5.1 Extra**: Context verified. Five live receipts this compile, all from `(nix) pipulate $`, and they equal the BEFORE reading you pasted from the same box to the byte: qamy.ai and www still read [REDACTED_IP], npvg.org still reads [REDACTED_IP], and port 443 still hands back the router's self-signed certificate over a PHPSESSID cookie. The prompt you carried says the override is in; the receipts say it isn't yet. Null straddle, nothing changed, which is exactly what your own "3: PATCHES: None" predicts.

**The Mac paste is the second witness, and it says the same thing.** `dig +short npvg.org` on the Mac read [REDACTED_IP], so the Mac is asking the router, same as the Linux box. Two machines, one resolver, one missing row. The router step is the single change, and it hasn't been made. That's the whole ride.

**The miss was mine, and it's a naming miss.** "The desk" is a nickname from the 09-14 article. I used it as if it carried over, and a nickname is exactly what the Dory rule forbids: you shouldn't have to remember which box I meant last turn. The fix is a rule with no nickname in it: every command block is headed by the prompt string you'll see in that terminal, `(nix) pipulate $` or `michaellevin@MichaelMacBook-Pro ~ %`, repeated on every block, every turn. And one structural fact makes PROBES unambiguous forever: `!` lines can only execute on the box that runs `compile`, so section 1 is always `(nix) pipulate $` by construction; a command for any other machine is a witness and lives in section 5 under that machine's prompt.

**Not in apply.py.** apply.py never runs a probe, and its own gate refuses comment-only growth on purpose: a rule there would ride every compile about every project forever, next to code it has nothing to do with. It goes where the checklist lives, as a line under item 11. That file isn't in this context; probe line 6 names it, so the rule rides as its own car next turn instead of a guess this turn.

**Carry-over, unchanged.** DDNS is on mikelev.in only; npvg.org and qamy.ai are static copies of its number. Not this ride.

## Do these in this order

1. Section 5, step A: the router row, in a web browser.
2. Section 1: the probe block, in the terminal whose prompt reads `(nix) pipulate $`.
3. Section 5, step B: the Mac block, in the terminal whose prompt reads `michaellevin@MichaelMacBook-Pro ~ %`.
4. Section 2: context.txt already holds the five `!` lines; add line 6 and compile. That compile is the AFTER receipt.

## 1. PROBES

Machine: the terminal whose prompt reads `(nix) pipulate $`. Run after the router row is saved and Applied.

```text
dig +short qamy.ai
dig +short www.qamy.ai
dig +short npvg.org
dig +short qamy.ai @1.1.1.1
curl -ksv --max-time 10 -o /dev/null https://qamy.ai
rg -l -t py ECHO.INVARIANT
```

Lines 1–3 should all print [REDACTED_IP] (line 2 prints `qamy.ai.` first, then the number). Line 4 should still print [REDACTED_IP]; it asks Cloudflare, not the router, and is the control. Line 5 should show `subject: CN=qamy.ai`, an issuer of Let's Encrypt, and no PHPSESSID cookie. If line 1 still prints [REDACTED_IP], the Apply Changes button in step A was not clicked, or the row has a typo; go back to step A. Line 6 is not part of the straddle: it prints the name of the file that carries checklist item 11, which is where the machine-naming rule gets banked next turn.

## 2. NEXT CONTEXT

The five lines already in context.txt stay; add the sixth.

```text
! dig +short qamy.ai
! dig +short www.qamy.ai
! dig +short npvg.org
! dig +short qamy.ai @1.1.1.1
! curl -ksv --max-time 10 -o /dev/null https://qamy.ai
! rg -l -t py ECHO.INVARIANT
```

## 3. PATCHES

No repo patches this turn: the file that holds the checklist is not in this context, and a patch against a file I can't see is a guess. No ignition required for the router change; the probes resolve through the router at call time. The rule body below is what will ride as a car next turn, once line 6 names its file. It is text, not a patch.

```text
THE MACHINE IS NAMED BY ITS PROMPT. Every fenced command block is preceded by
one line naming the terminal it runs in, by the prompt string the operator
sees there: `(nix) pipulate $`, `michaellevin@MichaelMacBook-Pro ~ %`, and so
on. Never by a nickname: not "the desk", not "the box", not "locally". The
name is repeated on every block, every turn, never abbreviated after first
use (the Dory rule). PROBES always run where `compile` runs, because only
that machine executes `!` lines; a command for any other machine is a
WITNESS and rides in (5) EXTERNAL DELIVERABLES under that machine's prompt.
A router or web-console step says "in a web browser" and names the menu
path field by field, with the value to type in each.
```

## 4. PROMPT

```text
The qamy.ai row was added to the pfSense host overrides and Apply Changes was clicked. The `!` receipts above are the AFTER reading from the terminal whose prompt reads `(nix) pipulate $`. Pasted below is the Mac block, run in the terminal whose prompt reads `michaellevin@MichaelMacBook-Pro ~ %`. Confirm the straddle closed: receipt lines 1-3 all [REDACTED_IP], line 4 unchanged, line 5 showing CN=qamy.ai and Let's Encrypt, Mac curl printing script text with no curl: (60). Line 6 names the file that carries checklist item 11; write the machine-naming rule as a car against it. DDNS stays a carry-over earmark only: mikelev.in has the updater, npvg.org and qamy.ai are static copies of its number.

[Mac paste]
```

## 5. EXTERNAL DELIVERABLES

**Step A. The router row. In a web browser, on any machine in the house. Manual, no command.**

1. Open the pfSense admin page and log in.
2. Top menu: Services → DNS Resolver. You land on the General Settings tab.
3. Scroll to the bottom of that page to the table titled Host Overrides. Find the npvg.org row and look at it: the new row is that row with two words changed.
4. Click the green Add button under the table.
5. Host: `qamy`
6. Domain: `ai`
7. IP Address: the same number the npvg.org row shows. The receipt read it as `[REDACTED_IP]`; copy it from the npvg.org row rather than typing it.
8. Description: anything, or blank.
9. Under Additional Names for this Host, click Add Host Name. Host: `www`. Domain: `qamy.ai`.
10. Click Save at the bottom.
11. A banner appears at the top of the page saying the configuration has changed. Click Apply Changes in that banner. Without this click the row exists and does nothing; that is the miss that hides best.

**Step B. The Mac block. In the terminal whose prompt reads `michaellevin@MichaelMacBook-Pro ~ %`. Manual, not echoed, because context.txt only runs on the Linux box.**

```text
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
dscacheutil -q host -a name qamy.ai
curl -fsSL https://qamy.ai | head -3
```

Line 1 asks for your Mac password and prints nothing; it empties the cache that still holds [REDACTED_IP] for qamy.ai. Line 2 prints a few lines, one of them `ip_address: [REDACTED_IP]`; that is the address curl on the Mac actually uses (Mac `dig` bypasses this cache, so it is not the honest reading here). Line 3 prints the first three lines of the installer and no `curl: (60)`. Once line 3 prints script text, the unmodified install line `curl -fsSL https://qamy.ai | bash` works from the Mac on the LAN, which is the deliverable. If line 2 still shows [REDACTED_IP] after line 1, turn the Mac's Wi-Fi off and on to renew its lease, then run lines 2 and 3 again.

**MikeLev.in**: Okay, I got it through my thick skull. This is what should have been
said explicitly with higher signal and lower noise. The user can be as noisy as
they like fleshing out a problem but the AI solving a puzzle for the human
should be explicit saying something like:

1. Go into your pfSense router web user interface. It will be a local IP
   something like `https://redacted-ip.invalid/`. 
2. Enter your username and password.
3. Find Services / DNS Resolver
4. Make another entry in Host Overrides to match your previous entries for
   mikelev.in and npvg.org.

It should look like this.

Host Overrides
--------------
|  | npvg.org | [REDACTED_IP] |  |  |
|  | mikelev.in | [REDACTED_IP] |  |  |
|  | qamy.ai | [REDACTED_IP] |  |  |

Add that to your ouroboros training material if I make the cut. I imagine I do
because the higher end Claude models like Fable 5.1 always get better at my edge
use cases. Opus 5.N does not and has a smaller and catering to the normie react
JS centroid. So if you're a token-burning full web stack developer, you're fine
with Opus 5.5 but if you're like me macheting your way through the Noosphere
sometimes you have to pay for a better Sherpa.

## THE PROOF STRADDLE

> 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: BEFORE PROBE** (paste in bash): 

```bash
Left-hand causal boundary "blast radius" established. Make 1 change and test.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ dig +short qamy.ai
dig +short www.qamy.ai
dig +short npvg.org
dig +short qamy.ai @1.1.1.1
curl -ksv --max-time 10 -o /dev/null https://qamy.ai
rg -l -t py ECHO.INVARIANT
[REDACTED_IP]
qamy.ai.
[REDACTED_IP]
[REDACTED_IP]
[REDACTED_IP]
* Host qamy.ai:443 was resolved.
* IPv6: (none)
* IPv4: [REDACTED_IP]
*   Trying [REDACTED_IP]:443...
* ALPN: curl offers h2,http/1.1
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [1551 bytes data]
* SSL Trust: peer verification disabled
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [1210 bytes data]
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
{ [1 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [19 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [3416 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [80 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
*   subject: CN=qamy.ai
*   start date: Sep 29 10:07:05 2026 GMT
*   expire date: Dec 28 10:07:04 2026 GMT
*   issuer: C=US; O=Let's Encrypt; CN=YE1
*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
*   Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using ecdsa-with-SHA384
*   Certificate level 2: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using ecdsa-with-SHA384
*   Certificate level 3: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption
* OpenSSL verify result: 14
*  SSL certificate verification failed, continuing anyway!
* Established connection to qamy.ai ([REDACTED_IP] port 443) from [REDACTED_IP] port 47776 
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://qamy.ai/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: qamy.ai]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.21.0]
* [HTTP/2] [1] [accept: */*]
} [5 bytes data]
> GET / HTTP/2
> Host: qamy.ai
> User-Agent: curl/8.21.0
> Accept: */*
> 
* Request completely sent off
} [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [57 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [57 bytes data]
< HTTP/2 200 
< server: nginx
< date: Tue, 29 Sep 2026 14:35:51 GMT
< content-type: text/x-shellscript
< vary: User-Agent
< 
} [5 bytes data]
* Connection #0 to host qamy.ai:443 left intact
prompt_foo.py
foo_files.py
(nix) pipulate $ 
```

**2: AFTER PROBE** (in context.txt): 

```text
# context.txt: the list of files an AI will read. context opens it, compile builds it.
# Keys: j down, k up; Esc then :q quits, :q! discards, :wq saves and quits.
# One line per thing the AI reads: a file path, or a command after `! `.
# A line that starts with # is a comment: that file is not read.
# To add a line: i starts typing, Esc stops. Absolute paths work from anywhere.
# Web pages, APIs and the connector words: chapter XVIII of foo_files.py.

# --- THE 40K-FOOT VIEW (uncomment on a first turn; comment out on the second) ---
# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- the book's spine, one line per article, newest first
# ~/repos/nixos/autognome.py  # <-- the machine's morning routine (this author's NixOS box only)
# init.lua                    # <-- the editor keys that drive the day
# assets/installer/install.sh # <-- how a stranger's machine gets this workshop
# GLOSSARY.md                 # <-- the terms, defined
# flake.nix                   # <-- the environment, pinned: here is my hardware, here is my state
# prompt_foo.py               # <-- the compiler that builds the payload
# foo_files.py                # <-- the router: which files ride, and this book's outline
# scripts/articles/lsa.py     # <-- the second brain: the article corpus behind `rgx`, `rgxc` and `posts`
# requirements.in             # <-- the Python packages, pinned
# pyproject.toml              # <-- the PyPI package
# __init__.py                 # <-- the version

# --- ACTUATORS (cheap; include when the AI should be able to act, not only read) ---
# cli.py                      # <-- tool calls from the command line
# scripts/xp.py               # <-- turns a pasted reply into the next context
# scripts/ai.py               # <-- a local AI writes the commit messages
# scripts/crawl.py            # <-- crawl a site into the next turn
# scripts/weblogin.py         # <-- warm a login on the persistent browser profile
# scripts/webclip_2_markdown.py  # <-- a web page, clipped, as markdown

# --- RARE ---
# scripts/foo_cartridge.py    # <-- the sealed archive: writer and verifier
# scripts/foo_replay.py       # <-- replay a sealed archive on another machine
# release.py                  # <-- how a release reaches GitHub and PyPI
# imports/voice_synthesis.py  # <-- the voice
# imports/ascii_displays.py   # <-- the ASCII art
# scripts/release/version_sync.py  # <-- version stamping (to be folded into release.py)

# --- THIS DISCUSSION ---
# The files and commands for the work in front of you. Paste the NEXT CONTEXT
# block an AI hands back directly below this line; the AI will correct a guess.

# Context 1
# ! rgxc -t grim,article 10 pfsense mikelev.in security warning

# Context 2
# ! dig +short qamy.ai
# ! dig +short www.qamy.ai
# ! dig +short npvg.org
# ! dig +short qamy.ai @1.1.1.1
# ! curl -ksv --max-time 10 -o /dev/null https://qamy.ai

# Context 3
! dig +short qamy.ai
! dig +short www.qamy.ai
! dig +short npvg.org
! dig +short qamy.ai @1.1.1.1
! curl -ksv --max-time 10 -o /dev/null https://qamy.ai
! rg -l -t py ECHO.INVARIANT
AGENTS.md
AUDIT.md
.agents/skills/journal/references/index.md
assets/installer/install.sh
```

**3: PATCHES** (the 1 change): None this turn. Ah, I see! Fable 5.1 is carrying
information forward like carrying a querystring value on the URL like the old
ASP.NET postback trick to hack session ha ha, okay. Fair enough.

**4: PROMPT**: The qamy.ai row was added to the pfSense host overrides and Apply Changes was clicked. The `!` receipts above are the AFTER reading from the terminal whose prompt reads `(nix) pipulate $`. Pasted below is the Mac block, run in the terminal whose prompt reads `michaellevin@MichaelMacBook-Pro ~ %`. Confirm the straddle closed: receipt lines 1-3 all [REDACTED_IP], line 4 unchanged, line 5 showing CN=qamy.ai and Let's Encrypt, Mac curl printing script text with no curl: (60). Line 6 names the file that carries checklist item 11; write the machine-naming rule as a car against it. DDNS stays a carry-over earmark only: mikelev.in has the updater, npvg.org and qamy.ai are static copies of its number.

Here's the probe reading from the Mac.

```zsh
Last login: Tue Sep 29 09:36:19 on ttys000
michaellevin@MichaelMacBook-Pro ~ % dig +short qamy.ai
dig +short www.qamy.ai
dig +short npvg.org
dig +short qamy.ai @1.1.1.1
curl -ksv --max-time 10 -o /dev/null https://qamy.ai
rg -l -t py ECHO.INVARIANT
[REDACTED_IP]
qamy.ai.
[REDACTED_IP]
[REDACTED_IP]
[REDACTED_IP]
* Host qamy.ai:443 was resolved.
* IPv6: (none)
* IPv4: [REDACTED_IP]
*   Trying [REDACTED_IP]:443...
* Connected to qamy.ai ([REDACTED_IP]) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
} [312 bytes data]
* (304) (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* (304) (IN), TLS handshake, Unknown (8):
{ [33 bytes data]
* (304) (IN), TLS handshake, Certificate (11):
{ [3416 bytes data]
* (304) (IN), TLS handshake, CERT verify (15):
{ [79 bytes data]
* (304) (IN), TLS handshake, Finished (20):
{ [36 bytes data]
* (304) (OUT), TLS handshake, Finished (20):
} [36 bytes data]
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=qamy.ai
*  start date: Sep 29 10:07:05 2026 GMT
*  expire date: Dec 28 10:07:04 2026 GMT
*  issuer: C=US; O=Let's Encrypt; CN=YE1
*  SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://qamy.ai/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: qamy.ai]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: qamy.ai
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/2 200 
< server: nginx
< date: Tue, 29 Sep 2026 14:39:59 GMT
< content-type: text/x-shellscript
< vary: User-Agent
< 
{ [8192 bytes data]
* Connection #0 to host qamy.ai left intact
zsh: command not found: rg
michaellevin@MichaelMacBook-Pro ~ %
```

And here's the install test:

```zsh
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://qamy.ai | less
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://qamy.ai | cat 
#!/usr/bin/env bash
# Pipulate Installer v1.1.0
# =========================
# 
# This installer uses a "magic cookie" approach to setup a git-based nix flake without 
# requiring git to be available on the host system initially.
#
# === WHY THIS APPROACH WORKS ===
# We want effectively the same path whether it's macOS or Linux (which might include Windows WSL)
# because the value proposition of nix is deterministic behavior solving the "not on my machine" 
# problem. The nix flake provides a normalized version of Linux that runs things identically 
# across all host OSes. The exceptions are exactly that, tiny edge-case areas where we need 
# to insert special handling logic for radical differences in the host OS or hardware, such 
# as taking advantage of CUDA on non-Windows environments and the `--impure` flag needed on macOS.
# We go out of our way to re-unite the paths in all other locations so there is no special 
# host OS handling on core script functionality.
#
# === THE "MAGIC COOKIE" APPROACH ===
# Nix flakes require a git repository to function properly. However, requiring users to have 
# git pre-installed creates a dependency we want to avoid. So instead:
#
# 1. This assets/installer/install.sh script is distributed via curl (highly reliable across systems)
# 2. We download a zip of the repo (more reliable than git clone on diverse systems)
# 3. We extract the zip and place a ROT13-encoded SSH key in the .ssh folder
# 4. We run `nix develop` which activates the flake
# 5. The flake itself handles converting the directory into a proper git repo
#
# This is called a "magic cookie" approach because we provide the initial "cookie" 
# (SSH key + zip contents) that the nix flake later uses to transform itself into 
# a proper git repository with auto-update capabilities.
#
# === IMPORTANT ===
# DO NOT MOVE GIT FUNCTIONALITY INTO THIS SCRIPT. This approach deliberately avoids
# requiring git during the initial setup phase for maximum compatibility across systems.
# The more robust approach is to let nix ensure git is available before attempting any
# git operations in the controlled nix environment.

# Wait for the complete function body before starting installation.
# A stream cut inside this body cannot execute a partial install.
# Leave the body indentation unchanged, including the embedded ./run heredoc.
main() {
# Detect shell compatibility - pipefail is bash-specific
if [ -z "${BASH_VERSION:-}" ]; then
    echo "❌ Error: This script requires bash but is being run with a different shell."
    echo "   On Windows WSL and some Linux systems, 'sh' points to dash instead of bash."
    echo ""
    echo "   Please run the installer with bash explicitly:"
    echo "   curl -fsSL https://pipulate.com/install.sh | bash -s ${1:-pipulate}"
    echo ""
    echo "   Or if you have bash installed:"
    echo "   curl -fsSL https://pipulate.com/install.sh | bash -s ${1:-pipulate}"
    echo ""
    exit 1
fi

# Strict mode (bash-specific features)
set -euo pipefail

# At the beginning, add argument handling
# THE DOOR NAMES THE FOLDER (2026-09-14). One installer file, two addresses:
# pipulate.com serves this file as-is, and npvg.org's nginx stamps the
# placeholder below to "npvg" at the door, the way mck.sh's header already
# describes for its trail name. The split spelling of _ph_name is the one
# occurrence a stamp can never touch, so stamped and unstamped copies stay
# distinguishable after substitution. An explicit argument still wins, and
# an unstamped copy falls back to the folder name this script has always
# used, so publishing this before the door exists changes nothing.
_tpl_name='qamy'
_ph_name='__INSTALL_DEFAULT_''NAME__'
if [ "$_tpl_name" != "$_ph_name" ]; then
  DEFAULT_NAME="$_tpl_name"
else
  DEFAULT_NAME="pipulate"
fi
CUSTOM_NAME="${1:-$DEFAULT_NAME}"  # An argument names the folder; the door names the default

# --- Configuration ---
REPO_USER="miklevin"
REPO_NAME="pipulate"
# Stable URL for the main branch ZIP
ZIP_URL="https://github.com/${REPO_USER}/${REPO_NAME}/archive/refs/heads/main.zip"
# Target directory name - use absolute path to avoid any confusion
TARGET_DIR="${HOME}/${CUSTOM_NAME}"
# Temporary directory for ZIP extraction
TMP_EXTRACT_DIR="${REPO_NAME}-main"
# URL for the ROT13 deploy key
KEY_URL="https://pipulate.com/key.rot"

# --- Helper Functions ---
check_command() {
  if ! command -v "$1" &> /dev/null; then
    echo "Error: Required command '$1' not found. Please install it."
    exit 1
  fi
}

print_separator() {
  echo "--------------------------------------------------------------"
}

# --- Setup Nix Develop Command ---
# Function to get the appropriate nix develop command based on OS
# This is one of the few OS-specific adaptations we need to make
get_nix_develop_cmd() {
  # Add -L to force build logs so the user sees the download progress
  echo "nix develop -L"
}
NIX_DEVELOP_CMD=$(get_nix_develop_cmd)

# --- Display Banner ---
# ONE LINE, NOT A BOX (2026-09-14, the first Mac install from npvg.org). The
# seven-line box printed the same in every world. A stranger needs two
# readings here: which door they came through (BANNER_NAME follows the folder
# the door named) and where the folder lands. The uninstall rides on the same
# line so "yours to delete" is a command rather than a promise. The 2026-08-04
# rulings still hold: the name is CUSTOM_NAME, never a hardcoded product, and
# the retired ancestor identity is never printed.
BANNER_NAME=$(printf '%s' "${CUSTOM_NAME}" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')
echo "${BANNER_NAME} -> ~/${CUSTOM_NAME}   (to remove it later: rm -rf ~/${CUSTOM_NAME})"

# --- Dependency Checks ---
# Note: We check for minimal dependencies that are needed for this phase
# Git is NOT required at this stage - the flake will handle git operations later
check_command "curl"
check_command "unzip"

# The Universe Builder (Nix Foundation Check)
if ! command -v nix &> /dev/null; then
  echo "Nix is not installed. Installing it now with the Determinate Systems installer..."
  curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
  echo "=================================================================="
  echo "Nix is installed, but this terminal was opened before it was."
  echo "Close this terminal, open a new one, and run the install line again:"
  
  if [ "$CUSTOM_NAME" = "pipulate" ]; then
    echo "curl -fsSL https://pipulate.com/install.sh | bash"
  else
    echo "curl -fsSL https://pipulate.com/install.sh | bash -s ${CUSTOM_NAME}"
  fi
  
  echo "=================================================================="
  exit 0
fi

# --- Target Directory Handling ---
# Check if target directory already exists and gracefully fail
if [ -d "${TARGET_DIR}" ]; then
  echo "❌ Error: Directory '${TARGET_DIR}' already exists."
  echo "   The installer cannot proceed when the target directory already exists."
  echo "   This prevents accidental overwrites of existing data."
  echo
  echo "   To resolve this, you can:"
  echo "   1. Choose a different name: curl -fsSL https://pipulate.com/install.sh | bash -s your-custom-name"
  echo "   2. Remove the existing directory: rm -rf ${TARGET_DIR}"
  echo "   3. Rename the existing directory: mv ${TARGET_DIR} ${TARGET_DIR}.backup"
  echo
  if [ -f "${TARGET_DIR}/flake.nix" ]; then
    echo "   Note: The existing directory appears to be a Pipulate installation."
    echo "   You can start it directly with: cd ${TARGET_DIR} && ${NIX_DEVELOP_CMD}"
  fi
  echo
  exit 1
else
  mkdir -p "${TARGET_DIR}"
fi

# --- Download and Extract ---
# The "magic cookie" approach begins here - downloading the ZIP archive
# This is more reliable across systems than using git directly
# Download to a temporary file
TMP_ZIP_FILE=$(mktemp)
# Ensure temp file is removed on exit
trap 'rm -f "$TMP_ZIP_FILE"' EXIT
curl -L --fail -sS -o "${TMP_ZIP_FILE}" "${ZIP_URL}"

# Create a temporary directory for extraction
TMP_EXTRACT_PATH=$(mktemp -d)
trap 'rm -rf "$TMP_EXTRACT_PATH"; rm -f "$TMP_ZIP_FILE"' EXIT

# Extract into the temporary directory
unzip -q "${TMP_ZIP_FILE}" -d "${TMP_EXTRACT_PATH}"

# Check if extraction created the expected directory
FULL_EXTRACT_DIR="${TMP_EXTRACT_PATH}/${TMP_EXTRACT_DIR}"
if [ ! -d "${FULL_EXTRACT_DIR}" ]; then
  echo "❌ Error: Extraction did not produce the expected directory '${TMP_EXTRACT_DIR}'."
  exit 1
fi

# Move extracted contents into TARGET_DIR
# Using cp first to ensure all files are copied correctly
cp -R "${FULL_EXTRACT_DIR}/." "${TARGET_DIR}/"
rm -f "$TMP_ZIP_FILE"

# --- Navigate Into Project ---
cd "${TARGET_DIR}"

# --- Deploy Key Setup ("Magic Cookie") ---
# Part of the "magic cookie" is the SSH key that will allow the flake
# to perform git operations without password prompts
mkdir -p .ssh
# Use curl to fetch the key from the URL and save it to .ssh/rot
if ! curl -L -sS --fail -o .ssh/rot "${KEY_URL}"; then
  echo "❌ Error: Failed to download deployment key from ${KEY_URL}."
  # Optional: remove potentially incomplete key file
  rm -f .ssh/rot
  exit 1
fi

# Verify that the downloaded file is not empty
if [ ! -s .ssh/rot ]; then
    echo "❌ Error: Downloaded deployment key file (.ssh/rot) is empty."
    rm -f .ssh/rot # Clean up empty file
    exit 1
fi

chmod 600 .ssh/rot # Important: Set permissions for the raw key file
# THE ONE DISCLOSURE THAT STAYS (2026-09-14). A key landing on a stranger's
# disk is an act worth one plain sentence, and the flake decodes it into
# ~/.ssh/id_rsa on first entry if no key is there. Four lines of mechanism
# became one line of fact; the failure branch above keeps its full message.
echo "Deploy key saved to .ssh/rot (public, pull-only: it lets this folder fetch updates without a GitHub account)."

# --- Trigger Initial Nix Build & Git Conversion ---
# Now we hand over to nix develop, which will activate the flake
# The flake will handle converting this to a proper git repository
echo "To come back later:  cd ~/${CUSTOM_NAME} && nix develop"

# Before the exec command, add:
# THE ARGUMENT NAMES THE LABEL; THE DOOR NAMES THE FOLDER (2026-09-14).
# whitelabel.txt is the app's identity: the banner the flake prints on
# entry, the server's own name, and the database filenames config.py
# derives from it. It used to be written from CUSTOM_NAME unconditionally,
# so a default install from a door that stamps the folder "npvg" would
# have renamed the app Npvg and its databases with it. Now only an
# explicit argument writes it. A default install leaves the file absent,
# and the flake's own first-entry fallback names the app, "Pipulate" for
# any folder without botify in its name. mck.sh always passes its
# whitelabel as the argument, so the launcher's lane is unchanged. The
# explicit path still announces itself; the default path prints nothing
# here because it did nothing here.
if [ -n "${1:-}" ]; then
  echo "Setting up app identity as '$CUSTOM_NAME'..."
  echo "$CUSTOM_NAME" > "${TARGET_DIR}/whitelabel.txt"
  chmod 644 "${TARGET_DIR}/whitelabel.txt"
  echo "✅ Application identity set."
fi

# Creating the 'Double-Click' Actuator
cat > "${TARGET_DIR}/run" << 'EOL'
#!/usr/bin/env bash
cd "$(dirname "$0")" 
if [[ "$(uname)" == "Darwin" ]]; then
  exec nix develop --impure
else
  exec nix develop
fi
EOL
chmod +x "${TARGET_DIR}/run"

# VERSION LINE REMOVED 2026-08-01, receipt-convicted: this variable was
# assigned here and dereferenced by nothing in this script, while version_sync
# stamped the DOWNSTREAM Pipulate.com copy and release.py's sync then copied
# this file over that copy on the same run -- so the number served to strangers
# stayed frozen at 1.0.2 through a 2.02 release. A label no behavior consumes
# cannot go usefully stale; it can only be wrong. The cure is deletion, not a
# second stamping target: __init__.py holds the single version, and flake.nix
# reads it at eval time. Do not re-add a duplicate here.

# The nix flake will take over from here, handling the git repository setup
# This is the final step of the "magic cookie" approach - letting the controlled
# nix environment handle the git operations
# ONE LINE, BOTH LANES (2026-09-14). The default lane printed four lines here
# that the walk lane printed as one, and the first Mac install from npvg.org
# read all four as narration. Both lanes now get the same true sentence, and
# it names the one thing a stranger cannot see: that the silence about to
# follow is a download, not a hang. The magic-cookie step keeps its own
# verdict line in flake.nix, printed only when the transformation fires.
echo "Hydrating the Nix environment (the first time can take a few minutes)..."

# The Terminal Hand-off:
# We spawn a fresh shell attached directly to the physical terminal. 
# This prevents the macOS SIGTTIN suspension caused by the curl pipe,
# and permanently eliminates the need for the user to type 'cd'.
# INSTALL-ONLY MODE (added 2026-08-01 for the MCK launcher). When a caller
# exports PIPULATE_INSTALL_ONLY=1, do the setup AND the environment hydration
# and then RETURN, instead of opening an interactive workshop the caller
# cannot resume from.
#
# GRACEFUL BY CONSTRUCTION: an older served copy of this script ignores an
# unknown environment variable and behaves exactly as it always has, so a
# launcher may set this unconditionally with no version detection.
#
# WHY .#quiet AND NOT THE DEFAULT SHELL: the default shellHook ends in
# `python server.py` in the foreground, so `nix develop --command` would start
# the server rather than return. .#quiet has no server, no JupyterLab and no
# boot menu -- but it also deliberately sets the venv up WITHOUT populating it
# (the uv lines live in runScript), so the install step is run explicitly here.
#
# NAMED LIMITATION, stated rather than discovered: .#quiet also skips the
# flake's gitUpdateLogic, so a workshop hydrated only through this path is not
# yet a git repository and does not auto-update. The magic-cookie
# transformation fires on the first plain `nix develop` in that folder. Riding
# a trail does not need it.
if [ "${PIPULATE_INSTALL_ONLY:-0}" = "1" ]; then
  IMPURE_FLAG=""
  if [ "$(uname -s)" = "Darwin" ]; then
    IMPURE_FLAG="--impure"
  fi
  # LD_LIBRARY_PATH="" IS LOAD-BEARING, and its absence is INVISIBLE to the
  # audience this script ships to. The Pipulate dev shell front-loads its own
  # python, openssl and glibc into LD_LIBRARY_PATH, and the interactive nix
  # wrapper that neutralizes it is a shell FUNCTION -- functions do not export,
  # so no child process inherits the protection while every child inherits the
  # pollution. Convicted 2026-08-01: this branch printed three "version not
  # found" lines from the nix binary and exited 1, inside a workshop, while the
  # identical branch would have succeeded for a stranger on a clean shell.
  # Clearing the variable is a no-op on a clean shell, so the defensive
  # spelling costs nothing and the undefended spelling costs an entire lane.
  ( cd "${TARGET_DIR}" && LD_LIBRARY_PATH="" ${NIX_DEVELOP_CMD} ${IMPURE_FLAG} .#quiet --command bash -c 'uv pip install -r requirements.txt --quiet && uv pip install -e . --no-deps --quiet' )
  echo "Environment hydrated at ${TARGET_DIR}."
  echo "Note: this folder becomes a git repo (and starts auto-updating) the"
  echo "      first time you run: cd ${TARGET_DIR} && ${NIX_DEVELOP_CMD}"
  exit 0
fi

# THE SHIM ON THIS LANE TOO (convicted 2026-09-14, operator's lane). The
# install-only branch above got its inline LD_LIBRARY_PATH clear on
# 2026-08-01; this hand-off never did, and the first curl|bash run from
# INSIDE a workshop shell died the moment it reached nix develop, with
# libssl and glibc version errors from the shell's own library path. THE
# UNEXPORTED-SHIM RULE, same disease, other lane: the interactive nix()
# wrapper is a function no child inherits, every child inherits the
# pollution, and a stranger on a clean shell never sees any of it -- which
# is why it survived six weeks and two witnessed installs. The empty
# assignment is a no-op on a clean shell, so it costs a stranger nothing.
if [ -c /dev/tty ]; then
    bash -c "cd '${TARGET_DIR}' && LD_LIBRARY_PATH='' ${NIX_DEVELOP_CMD}" < /dev/tty
else
    # Fallback for highly restricted environments
    cd "${TARGET_DIR}" && LD_LIBRARY_PATH="" ${NIX_DEVELOP_CMD}
fi
}

# THE CALL LINE IS INSIDE A FENCE TOO (2026-09-13). A stream that ends on
# exactly the word main, its arguments cut off, would run a complete install
# under the DEFAULT name. A brace group turns any cut before the closing
# brace into a syntax error, the same guard nvm's installer wraps around its
# whole file. main is still called unconditionally, so -e is preserved
# inside it, and exit carries main's status out of the group unchanged.
{ main "$@"; exit; }
michaellevin@MichaelMacBook-Pro ~ %
```

And there we go! That's a wonderful success! We need to adjust the messaging on
the installer to be more consistent with:

    AGENTS.md
    AUDIT.md
    .agents/skills/journal/references/index.md

...which means I also need to put this in context to edit it:

    assets/installer/install.sh

And don't try to make changes to that `index.md` as it gets generated by
`release.py`.

## Reading the Pipe First: Aligning Installer Messaging with Audit Standards

**5: DELIVERABLE**: A great introduction to QA'ing your AI and Unix pipes.

```text
Fable 5.1 Thinking

Thought for 6m 4s

Diagnosing a DNS override issue on pfSense.

Checking whether the www subdomain override still hairpins.

Pivoting to updating installer messaging for consistency.

Locating which file holds the checklist string.

Designing a domain-agnostic placeholder for install messaging.

Cross-checking the key-security wording against audit standards.

Rewriting the installer's header comments for transparency.

Flagging the Nix installer as the sole system-level change.

Avoiding risky search blocks by anchoring safe replacements.

Reframing the header around inspecting before executing.

Reworking door-aware wording across the installer's echoed messages.

Replacing hardcoded URLs with self-referential install-line instructions.

Documenting network reach and removing an unused version label.

Drafting precise search-and-replace blocks for the header.

Noting a key-writing step outside the target folder.

Tightening the header to spell out network reach clearly.

Extending the door-stamping explanation to a third domain.

Generalizing a bash-fallback message to any install address.

Simplifying the re-run message to drop the printed URL.

Trimming a deploy-key message for accuracy and brevity.

Tracing how updated messaging reaches the live server.

Assembling a minimal probe set to bracket this turn's changes.

Weighing pipe safety versus fetching the full served page.

Deciding whether the probe runner shells out for pipes.

Deciding which supporting files to include next turn.

Constructing exact search-and-replace blocks for the header rewrite.

Verifying the exact shell-fallback message needing generalization.

Removing a redundant URL block from the install script.

Simplifying a deploy-key message for clarity.

Composing the installer's rewritten header comment block.

Checking whether a version-sync script depends on the removed label.

Drafting step-by-step router instructions for the missing subdomain.

Naming the terminal-prompt and sync-step conventions explicitly.

Sequencing probes, router steps, and patch application for this turn.

Generalizing the bash-fallback message across install blocks.

Adding dated code comments explaining the messaging fix.

Listing which files to carry into the next context.
```

**Fable 5.1 Extra**: Context verified. The straddle closed on the door: receipt line 1 reads [REDACTED_IP] on both machines, line 5 shows `CN=qamy.ai`, Let's Encrypt, `content-type: text/x-shellscript` and `vary: User-Agent`, the Mac's curl says `SSL certificate verify ok.`, and `curl | cat` printed the installer with `_tpl_name='qamy'` stamped at the door. The Mac needed no flush. Line 6 named both `prompt_foo.py` and `foo_files.py`; the `rg` on the Mac failing is expected, that line was for the Linux box only and did no harm.

**One reading did not close: www.** Receipt line 2, on both machines, still prints `qamy.ai.` and then [REDACTED_IP]. The row you added covers `qamy.ai` and nothing else, so inside the house `https://www.qamy.ai` still knocks on the router. It does not block the install test, which uses the bare name. The fix is one edit to the row you just made; it is step A in section 5, numbered, with the end state drawn.

**The rule, in the shape you drew.** Your four steps and the table are the form: number the steps, give the concrete value, show the screen when it is done. I have rewritten the rule body that way below. It still cannot ride as a patch, because neither file line 6 named is in this context; probe line 7 prints the matching lines so the next context adds the one that holds the numbered checklist, not the one that mentions it.

**The installer, measured against the three docs.** Five places disagree with AGENTS.md and AUDIT.md, and all five are text a stranger sees, either on screen or in the `| cat` you just ran:

1. The header carries `v1.1.0`, a label nothing consumes; the 2026-08-01 note in the same file says such a label can only be wrong, and AGENTS.md puts the one version in `__init__.py`.
2. Three messages print `curl -fsSL https://pipulate.com/install.sh` as the line to run again. At npvg.org and qamy.ai that line is wrong; the line the stranger ran is the only one right at every door.
3. The deploy-key line says "public"; AUDIT.md says read-only, scoped to one repository, ROT13 as packaging not secrecy.
4. The header answers none of AUDIT.md's first-run questions: what it touches, what it reaches, what leaves the machine.
5. Nothing points at AGENTS.md, AUDIT.md or the journal index in the folder it installs.

The car below fixes all five. `index.md` is pointed at, not edited. The header now opens by addressing the person who piped to cat first, because that act is the deliverable: the pipe is read before it is run.

## Do these in this order

1. Section 1: the probe block, in the terminal whose prompt reads `(nix) pipulate $`. That is the BEFORE reading.
2. Section 5, step A: the www name on the router row, in a web browser.
3. Section 3: the patch, in the terminal whose prompt reads `(nix) pipulate $`: `patch`, `app`, `d`, `m`.
4. `blast`, the caboose, in the same terminal.
5. Section 2: context.txt, then compile. That is the AFTER reading.

## 1. PROBES

Machine: the terminal whose prompt reads `(nix) pipulate $`. Run before anything else.

```text
dig +short qamy.ai
dig +short www.qamy.ai
dig +short www.npvg.org
bash -n assets/installer/install.sh
curl -fsSL --max-time 10 https://qamy.ai | head -3
rg -n Installer.v release.py scripts/release/version_sync.py
rg -n -t py ECHO.INVARIANT
```

Line 1 should still print [REDACTED_IP]; it proves the row you added holds. Line 2 prints `qamy.ai.` then [REDACTED_IP] before step A and exactly one line, `[REDACTED_IP]`, after it. Line 3 tells you whether the npvg.org row needs the same edit: [REDACTED_IP] means it already has www, anything else means it does not. Line 4 is the syntax check on the installer; it prints nothing when the file is valid, before and after the patch, and a line number if the patch broke it. Line 5 prints the served copy's first three lines: `# Pipulate Installer v1.1.0` before, the new title after, but only once whatever carries install.sh to Honeybot has run; if the after still reads the old title, that sync step is the next car. If line 5 instead complains about `|`, the `!` runner does not shell, and the plain `curl -fsSL --max-time 10 https://qamy.ai` is the fallback. Line 6 is the falsifier for the header edit: if release.py or version_sync.py stamps the `Installer v` line by pattern, this prints where; no output means nothing depends on the line I am removing. Line 7 prints the matching lines in both files; the one showing `12.  **THE PROBE ECHO INVARIANT` is the checklist source and is the file to add for the rule car.

## 2. NEXT CONTEXT

The docs did their job as the register; drop them except AGENTS.md, which is small. install.sh stays so the landed patch is visible.

```text
! dig +short qamy.ai
! dig +short www.qamy.ai
! dig +short www.npvg.org
! bash -n assets/installer/install.sh
! curl -fsSL --max-time 10 https://qamy.ai | head -3
! rg -n Installer.v release.py scripts/release/version_sync.py
! rg -n -t py ECHO.INVARIANT
assets/installer/install.sh
AGENTS.md
```

If you want the rule car next turn, add one more line: the file that probe line 7 shows holding the numbered checklist, most likely `prompt_foo.py`.

## 3. PATCHES

One car, one file, six blocks: the header, the door comment, the three re-run messages, the deploy-key line. Two of the blocks anchor mid-list on purpose to avoid the whitespace-only lines around the Nix re-run message.

```text
Target: assets/installer/install.sh
[[[SEARCH]]]
# Pipulate Installer v1.1.0
# =========================
[[[DIVIDER]]]
# The installer behind pipulate.com, npvg.org and qamy.ai
# =======================================================
#
# You are reading this because you piped it to cat or less instead of
# bash. That is the first QA step: a Unix pipe can be read before it is
# run, and nothing here runs until you swap cat for bash.
#
# What it does, in order:
#   1. downloads a zip of the repository from github.com;
#   2. unpacks it into ONE folder under your home, named by the door you
#      came through (or by the argument after bash -s);
#   3. saves a read-only deploy key into that folder as .ssh/rot;
#   4. hands off to nix develop, which builds the environment and turns
#      the folder into a git repository that keeps itself updated.
#
# What it touches: that folder; ~/.ssh/id_rsa only if no key is there
# (the flake decodes the deploy key into it on first entry); and, only
# if Nix is missing, the Determinate Systems Nix installer, which is a
# system-level change and says so when it runs.
#
# What it reaches: github.com for the zip, pipulate.com for the key, the
# Nix binary cache for the environment, and install.determinate.systems
# only if Nix is missing.
#
# The deploy key is scoped to one repository and read-only. Its ROT13
# wrapper is packaging, not secrecy.
#
# In the folder it installs: AGENTS.md is the map, AUDIT.md answers a
# reviewer's questions about what runs and what leaves the machine, and
# .agents/skills/journal/references/index.md indexes the journal that
# explains why every piece exists.
[[[REPLACE]]]

Target: assets/installer/install.sh
[[[SEARCH]]]
# THE DOOR NAMES THE FOLDER (2026-09-14). One installer file, two addresses:
# pipulate.com serves this file as-is, and npvg.org's nginx stamps the
# placeholder below to "npvg" at the door, the way mck.sh's header already
[[[DIVIDER]]]
# THE DOOR NAMES THE FOLDER (2026-09-14). One installer file, three addresses
# (qamy.ai joined 2026-09-29): pipulate.com serves this file as-is, and the
# nginx at npvg.org and qamy.ai stamps the placeholder below to "npvg" or
# "qamy" at the door, the way mck.sh's header already
[[[REPLACE]]]

Target: assets/installer/install.sh
[[[SEARCH]]]
    echo "   Please run the installer with bash explicitly:"
    echo "   curl -fsSL https://pipulate.com/install.sh | bash -s ${1:-pipulate}"
    echo ""
    echo "   Or if you have bash installed:"
    echo "   curl -fsSL https://pipulate.com/install.sh | bash -s ${1:-pipulate}"
[[[DIVIDER]]]
    # THE MESSAGE NAMES NO DOOR (2026-09-29): three doors serve this file, and
    # a printed pipulate.com line was wrong at two of them. The line the
    # stranger ran is the only one that is right everywhere.
    echo "   Run the same install line again with bash, not sh, at the end of the pipe:"
    echo "   curl -fsSL <the address you used> | bash${1:+ -s $1}"
[[[REPLACE]]]

Target: assets/installer/install.sh
[[[SEARCH]]]
  if [ "$CUSTOM_NAME" = "pipulate" ]; then
    echo "curl -fsSL https://pipulate.com/install.sh | bash"
  else
    echo "curl -fsSL https://pipulate.com/install.sh | bash -s ${CUSTOM_NAME}"
  fi
[[[DIVIDER]]]
  # THE MESSAGE NAMES NO DOOR (2026-09-29): see the bash check above.
  echo "(the same curl line you ran, ending in | bash${1:+ -s $1})"
[[[REPLACE]]]

Target: assets/installer/install.sh
[[[SEARCH]]]
  echo "   1. Choose a different name: curl -fsSL https://pipulate.com/install.sh | bash -s your-custom-name"
[[[DIVIDER]]]
  echo "   1. Choose a different name: run the same install line ending in | bash -s your-custom-name"
[[[REPLACE]]]

Target: assets/installer/install.sh
[[[SEARCH]]]
echo "Deploy key saved to .ssh/rot (public, pull-only: it lets this folder fetch updates without a GitHub account)."
[[[DIVIDER]]]
echo "Deploy key saved to .ssh/rot (read-only, scoped to this one repository: it lets this folder fetch updates without a GitHub account)."
[[[REPLACE]]]
```

Ignition: none for the file itself; `bash -n` reads it at call time. For the served copy, ignition is the caboose `blast` plus whatever carries install.sh to Honeybot, which I cannot name from this context; probe line 5 says whether it ran.

The rule body, rewritten in the shape you drew. Text, not a patch; it rides against the checklist file once that file is in context:

```text
THE MACHINE IS NAMED BY ITS PROMPT, AND THE STEPS ARE NUMBERED. Every
command block is headed by the prompt string the operator sees in that
terminal: `(nix) pipulate $`, `michaellevin@MichaelMacBook-Pro ~ %`.
Never a nickname ("the desk", "the box", "locally"), and repeated on
every block, every turn (the Dory rule). PROBES run where compile runs,
because only that machine executes `!` lines; a command for any other
machine is a WITNESS in (5) EXTERNAL DELIVERABLES under that machine's
prompt. A step in a web console is a numbered list: the address to open,
the menu path, each field with the value to type, the Save and Apply
clicks, and a picture of what the screen shows when it is done.
```

## 4. PROMPT

```text
The installer messaging car landed in assets/installer/install.sh (header, door-free re-run lines, deploy-key wording) and www was added as an additional name on the qamy.ai host override. The `!` receipts above are the AFTER reading from the terminal whose prompt reads `(nix) pipulate $`. Confirm: line 2 prints [REDACTED_IP] alone; line 3 says whether npvg.org still needs www; line 4 prints nothing; line 5 shows whether the served copy carries the new title, and if not, name the step that carries install.sh to Honeybot as the next car; line 6 shows whether anything stamps the removed Installer line. Line 7 names the file holding checklist item 11: if it is in this context, write the machine-naming rule as a car against it; if not, say the one context.txt line to add. DDNS stays a carry-over earmark only.
```

## 5. EXTERNAL DELIVERABLES

**Step A. Add www to the qamy.ai row. In a web browser, on any machine in the house. Manual, no command.**

1. Open the pfSense admin page at the same address you used an hour ago, something like `https://redacted-ip.invalid/`.
2. Enter your username and password.
3. Top menu: Services → DNS Resolver.
4. Scroll to the bottom to the table titled Host Overrides. Find the qamy.ai row. Click the pencil icon at the right end of that row.
5. On the edit form, scroll down to the section titled Additional Names for this Host. Click Add Host Name.
6. Host: `www`
7. Domain: `qamy.ai`
8. Click Save at the bottom.
9. Click Apply Changes in the banner at the top of the page.
10. If probe line 3 printed anything other than [REDACTED_IP], repeat steps 4 to 9 on the npvg.org row with Domain `npvg.org`.

When it is done, the table reads like this, with the www name listed under the row it belongs to:

```text
Host Overrides
--------------
| mikelev.in | [REDACTED_IP] |                |
| npvg.org   | [REDACTED_IP] | www.npvg.org   |
| qamy.ai    | [REDACTED_IP] | www.qamy.ai    |
```

**Nothing on the Mac this turn.** The install line works there now; the www change is checked from `(nix) pipulate $` by probe line 2.

**MikeLev.in**: Okay, I start with the router edits.

Host Overrides
--------------
|  | npvg.org | [REDACTED_IP] |  |  |
| www | npvg.org | Alias for npvg.org |  |  |
|  | mikelev.in | [REDACTED_IP] |  |  |
|  | qamy.ai | [REDACTED_IP] |  |  |
| www | qamy.ai | Alias for qamy.ai |  |  |

And now onto the mutations.

## THE PROOF STRADDLE

> 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: BEFORE PROBE** (paste in bash): 

```bash
(nix) pipulate $ g

Left-hand causal boundary "blast radius" established. Make 1 change and test.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ dig +short qamy.ai
dig +short www.qamy.ai
dig +short www.npvg.org
bash -n assets/installer/install.sh
curl -fsSL --max-time 10 https://qamy.ai | head -3
rg -n Installer.v release.py scripts/release/version_sync.py
rg -n -t py ECHO.INVARIANT
[REDACTED_IP]
[REDACTED_IP]
[REDACTED_IP]
#!/usr/bin/env bash
# Pipulate Installer v1.1.0
# =========================
foo_files.py
271:# with a leading "! " per THE PROBE ECHO INVARIANT.
2940:# § THE MULTI-LINE PROBE CANNOT BE ECHOED (banked 2026-09-04, reconvicted 2026-09-26) -- the ! executor spawns a NON-interactive sh -c, so a probe spanning newlines inside quotes runs clean by hand and dies at "unexpected EOF" in the compile lane; context.txt then reads its continuation lines as separate paths and warns about de-prefixed commands. THE PROBE ECHO INVARIANT therefore demands ONE LINE per probe: write it as a single semicolon-joined -c string, or as a real file invoked by path. The 2026-09-26 parser-hardening AFTER tried to echo a Python heredoc and reproduced the failure exactly: only the opener executed and the continuation became router debris. The fix is not a multiline context grammar; collapse the probe or put the program in a file. A probe that cannot survive its own echo is not echoable, and the operator lane becomes its only witness.

prompt_foo.py
1878:12.  **THE PROBE ECHO INVARIANT (Before/After Symmetry):** Every command recommended in (1) PROBES MUST also appear verbatim as a `!` chisel-strike line in (2) NEXT CONTEXT. The operator's hand-run is the BEFORE reading, taken prior to applying any patch; the identical line baked into context.txt re-executes automatically at the next compile, producing the AFTER reading as a live receipt. One probe, two receipts, straddling the patch — a binary-search causal boundary that removes all probe-before-patch / patch-then-probe ordering ambiguity. A probe too heavy or unbounded to echo into the next compile (see THE PROBE ECONOMY RULE) is too heavy to recommend: cap it first, then echo it. THE STRADDLE BRACKETS EXECUTION, NOT THE COMMIT: if the patched code will not run on its own before the next compile -- a shellHook, a daemon, a cached artifact, anything read once at entry -- then (3) PATCHES MUST close by NAMING the IGNITION (the exact command that makes it run, e.g. `exit` then `nix develop`, or `<F2>` for init.lua) or by stating "no ignition required" because the probe's own command loads the patched file at call time. Ignition is not a fourth beat; it completes PATCH. An AFTER tap taken without ignition is a stale BEFORE wearing the AFTER's label.
(nix) pipulate $
```

**2: AFTER PROBE** (in context.txt): 

```text
# context.txt: the list of files an AI will read. context opens it, compile builds it.
# Keys: j down, k up; Esc then :q quits, :q! discards, :wq saves and quits.
# One line per thing the AI reads: a file path, or a command after `! `.
# A line that starts with # is a comment: that file is not read.
# To add a line: i starts typing, Esc stops. Absolute paths work from anywhere.
# Web pages, APIs and the connector words: chapter XVIII of foo_files.py.

# --- THE 40K-FOOT VIEW (uncomment on a first turn; comment out on the second) ---
# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- the book's spine, one line per article, newest first
# ~/repos/nixos/autognome.py  # <-- the machine's morning routine (this author's NixOS box only)
# init.lua                    # <-- the editor keys that drive the day
# assets/installer/install.sh # <-- how a stranger's machine gets this workshop
# GLOSSARY.md                 # <-- the terms, defined
# flake.nix                   # <-- the environment, pinned: here is my hardware, here is my state
# prompt_foo.py               # <-- the compiler that builds the payload
# foo_files.py                # <-- the router: which files ride, and this book's outline
# scripts/articles/lsa.py     # <-- the second brain: the article corpus behind `rgx`, `rgxc` and `posts`
# requirements.in             # <-- the Python packages, pinned
# pyproject.toml              # <-- the PyPI package
# __init__.py                 # <-- the version

# --- ACTUATORS (cheap; include when the AI should be able to act, not only read) ---
# cli.py                      # <-- tool calls from the command line
# scripts/xp.py               # <-- turns a pasted reply into the next context
# scripts/ai.py               # <-- a local AI writes the commit messages
# scripts/crawl.py            # <-- crawl a site into the next turn
# scripts/weblogin.py         # <-- warm a login on the persistent browser profile
# scripts/webclip_2_markdown.py  # <-- a web page, clipped, as markdown

# --- RARE ---
# scripts/foo_cartridge.py    # <-- the sealed archive: writer and verifier
# scripts/foo_replay.py       # <-- replay a sealed archive on another machine
# release.py                  # <-- how a release reaches GitHub and PyPI
# imports/voice_synthesis.py  # <-- the voice
# imports/ascii_displays.py   # <-- the ASCII art
# scripts/release/version_sync.py  # <-- version stamping (to be folded into release.py)

# --- THIS DISCUSSION ---
# The files and commands for the work in front of you. Paste the NEXT CONTEXT
# block an AI hands back directly below this line; the AI will correct a guess.

# Context 1
# ! rgxc -t grim,article 10 pfsense mikelev.in security warning

# Context 2
# ! dig +short qamy.ai
# ! dig +short www.qamy.ai
# ! dig +short npvg.org
# ! dig +short qamy.ai @1.1.1.1
# ! curl -ksv --max-time 10 -o /dev/null https://qamy.ai

# Context 3
# ! dig +short qamy.ai
# ! dig +short www.qamy.ai
# ! dig +short npvg.org
# ! dig +short qamy.ai @1.1.1.1
# ! curl -ksv --max-time 10 -o /dev/null https://qamy.ai
# ! rg -l -t py ECHO.INVARIANT
# AGENTS.md
# AUDIT.md
# .agents/skills/journal/references/index.md
# assets/installer/install.sh

# Context 4
! dig +short qamy.ai
! dig +short www.qamy.ai
! dig +short www.npvg.org
! bash -n assets/installer/install.sh
! curl -fsSL --max-time 10 https://qamy.ai | head -3
! rg -n Installer.v release.py scripts/release/version_sync.py
! rg -n -t py ECHO.INVARIANT
assets/installer/install.sh
AGENTS.md
flake.nix
foo_files.py
release.py
assets/installer/install.sh
__init__.py
scripts/release/version_sync.py
scripts/articles/common.py
scripts/articles/generate_ai_context.py
remotes/honeybot/nixos/configuration.nix
nixops.sh
```

**3: PATCHES** (the 1 change): 

```diff
(nix) pipulate $ g

Left-hand causal boundary "blast radius" established. Make 1 change and test.
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 'assets/installer/install.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/install.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/install.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/install.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/install.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/install.sh'.
(nix) pipulate $ d
diff --git a/assets/installer/install.sh b/assets/installer/install.sh
index fc758a5b..45982089 100644
--- a/assets/installer/install.sh
+++ b/assets/installer/install.sh
@@ -1,6 +1,35 @@
 #!/usr/bin/env bash
-# Pipulate Installer v1.1.0
-# =========================
+# The installer behind pipulate.com, npvg.org and qamy.ai
+# =======================================================
+#
+# You are reading this because you piped it to cat or less instead of
+# bash. That is the first QA step: a Unix pipe can be read before it is
+# run, and nothing here runs until you swap cat for bash.
+#
+# What it does, in order:
+#   1. downloads a zip of the repository from github.com;
+#   2. unpacks it into ONE folder under your home, named by the door you
+#      came through (or by the argument after bash -s);
+#   3. saves a read-only deploy key into that folder as .ssh/rot;
+#   4. hands off to nix develop, which builds the environment and turns
+#      the folder into a git repository that keeps itself updated.
+#
+# What it touches: that folder; ~/.ssh/id_rsa only if no key is there
+# (the flake decodes the deploy key into it on first entry); and, only
+# if Nix is missing, the Determinate Systems Nix installer, which is a
+# system-level change and says so when it runs.
+#
+# What it reaches: github.com for the zip, pipulate.com for the key, the
+# Nix binary cache for the environment, and install.determinate.systems
+# only if Nix is missing.
+#
+# The deploy key is scoped to one repository and read-only. Its ROT13
+# wrapper is packaging, not secrecy.
+#
+# In the folder it installs: AGENTS.md is the map, AUDIT.md answers a
+# reviewer's questions about what runs and what leaves the machine, and
+# .agents/skills/journal/references/index.md indexes the journal that
+# explains why every piece exists.
 # 
 # This installer uses a "magic cookie" approach to setup a git-based nix flake without 
 # requiring git to be available on the host system initially.
@@ -44,11 +73,11 @@ if [ -z "${BASH_VERSION:-}" ]; then
     echo "❌ Error: This script requires bash but is being run with a different shell."
     echo "   On Windows WSL and some Linux systems, 'sh' points to dash instead of bash."
     echo ""
-    echo "   Please run the installer with bash explicitly:"
-    echo "   curl -fsSL https://pipulate.com/install.sh | bash -s ${1:-pipulate}"
-    echo ""
-    echo "   Or if you have bash installed:"
-    echo "   curl -fsSL https://pipulate.com/install.sh | bash -s ${1:-pipulate}"
+    # THE MESSAGE NAMES NO DOOR (2026-09-29): three doors serve this file, and
+    # a printed pipulate.com line was wrong at two of them. The line the
+    # stranger ran is the only one that is right everywhere.
+    echo "   Run the same install line again with bash, not sh, at the end of the pipe:"
+    echo "   curl -fsSL <the address you used> | bash${1:+ -s $1}"
     echo ""
     exit 1
 fi
@@ -57,9 +86,10 @@ fi
 set -euo pipefail
 
 # At the beginning, add argument handling
-# THE DOOR NAMES THE FOLDER (2026-09-14). One installer file, two addresses:
-# pipulate.com serves this file as-is, and npvg.org's nginx stamps the
-# placeholder below to "npvg" at the door, the way mck.sh's header already
+# THE DOOR NAMES THE FOLDER (2026-09-14). One installer file, three addresses
+# (qamy.ai joined 2026-09-29): pipulate.com serves this file as-is, and the
+# nginx at npvg.org and qamy.ai stamps the placeholder below to "npvg" or
+# "qamy" at the door, the way mck.sh's header already
 # describes for its trail name. The split spelling of _ph_name is the one
 # occurrence a stamp can never touch, so stamped and unstamped copies stay
 # distinguishable after substitution. An explicit argument still wins, and
@@ -132,11 +162,8 @@ if ! command -v nix &> /dev/null; then
   echo "Nix is installed, but this terminal was opened before it was."
   echo "Close this terminal, open a new one, and run the install line again:"
   
-  if [ "$CUSTOM_NAME" = "pipulate" ]; then
-    echo "curl -fsSL https://pipulate.com/install.sh | bash"
-  else
-    echo "curl -fsSL https://pipulate.com/install.sh | bash -s ${CUSTOM_NAME}"
-  fi
+  # THE MESSAGE NAMES NO DOOR (2026-09-29): see the bash check above.
+  echo "(the same curl line you ran, ending in | bash${1:+ -s $1})"
   
   echo "=================================================================="
   exit 0
@@ -152,7 +179,7 @@ if [ -d "${TARGET_DIR}" ]; then
   echo "   This prevents accidental overwrites of existing data."
   echo
   echo "   To resolve this, you can:"
-  echo "   1. Choose a different name: curl -fsSL https://pipulate.com/install.sh | bash -s your-custom-name"
+  echo "   1. Choose a different name: run the same install line ending in | bash -s your-custom-name"
   echo "   2. Remove the existing directory: rm -rf ${TARGET_DIR}"
   echo "   3. Rename the existing directory: mv ${TARGET_DIR} ${TARGET_DIR}.backup"
   echo
@@ -221,7 +248,7 @@ chmod 600 .ssh/rot # Important: Set permissions for the raw key file
 # disk is an act worth one plain sentence, and the flake decodes it into
 # ~/.ssh/id_rsa on first entry if no key is there. Four lines of mechanism
 # became one line of fact; the failure branch above keeps its full message.
-echo "Deploy key saved to .ssh/rot (public, pull-only: it lets this folder fetch updates without a GitHub account)."
+echo "Deploy key saved to .ssh/rot (read-only, scoped to this one repository: it lets this folder fetch updates without a GitHub account)."
 
 # --- Trigger Initial Nix Build & Git Conversion ---
 # Now we hand over to nix develop, which will activate the flake
(nix) pipulate $ m
📝 Committing: chore: Update installer script with clarified explanations and instructions
[main 579708f7] chore: Update installer script with clarified explanations and instructions
 1 file changed, 44 insertions(+), 17 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 48 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 1.65 KiB | 1.65 MiB/s, done.
Total 5 (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:pipulate/pipulate.git
   d1a29020..579708f7  main -> main
(nix) pipulate $
```

**4: PROMPT**: The installer messaging car landed in assets/installer/install.sh (header, door-free re-run lines, deploy-key wording) and www was added as an additional name on the qamy.ai host override. The `!` receipts above are the AFTER reading from the terminal whose prompt reads `(nix) pipulate $`. Confirm: line 2 prints [REDACTED_IP] alone; line 3 says whether npvg.org still needs www; line 4 prints nothing; line 5 shows whether the served copy carries the new title, and if not, name the step that carries install.sh to Honeybot as the next car; line 6 shows whether anything stamps the removed Installer line. Line 7 names the file holding checklist item 11: if it is in this context, write the machine-naming rule as a car against it; if not, say the one context.txt line to add. DDNS stays a carry-over earmark only.

**5: DELIVERABLE**: Wow! That's very good. I think this deserves a full release.

```bash
(nix) pipulate $ vim __init__.py 
(nix) pipulate $ release
╭──────────────────────────────────────────── QA My AI: Is what Claude said True? ────────────────────────────────────────────╮
│                                                                                                                             │
│                     ( Do you put your name on it because Claude said so? )                                                  │
│                                           O        /)  __                                                                   │
│ >  Do you think the Aviation industry      o /)\__//  /  \   "What Claude said" is a Cockpit                                │
│ >  vibe-codes without checking for       ___(/_ 0 0  |    |  Voice Recorder (CVR) hear-say of a                             │
│ >  confident hallucinations? So then   *(    ==(_T_)== QA |  subcontractor. This tool is a Flight                           │
│ >  why should you? If anyone relies on   \  )   ""\  |    |  Data Recorder (FDR) for High Reliability                       │
│ >  your work then you should Q/A it.      |__>-\_>_>  \__/   Organizations (HROs). Use when it matters.                     │
│                                                                                                                             │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
📋 Current version: 2.70
✅ Updated pyproject.toml (version and description)
✅ Pushed install.sh update to Pipulate.com repo.
[main 32c6414f] chore: Bump __version__ to 2.70
✅ Pushed 579708f7..32c6414f  main -> main
Successfully built pipulate-2.70.tar.gz and pipulate-2.70-py3-none-any.whl
🎉 Published 2.70 -> https://pypi.org/project/pipulate/2.70/

╭─────────────────────────────────────────────── 🎉 Release Pipeline Complete ────────────────────────────────────────────────╮
│                                                                                                                             │
│                                                 🎉 Pipulate Release Summary                                                 │
│  ╭───────────────────────────┬────────────────────────────────────────────────────────────────────────────┬──────────────╮  │
│  │ Component                 │ Details                                                                    │    Status    │  │
│  ├───────────────────────────┼────────────────────────────────────────────────────────────────────────────┼──────────────┤  │
│  │ 🤖 gemma3:latest Message  │ chore: Bump __version__ to 2.70                                            │      ✨      │  │
│  │                           │                                                                            │ gemma3:late… │  │
│  │                           │ Updated the `__version__` attribute in `__init__.py` and `pyproject.toml`  │              │  │
│  │                           │ to 2.70.                                                                   │              │  │
│  ├───────────────────────────┼────────────────────────────────────────────────────────────────────────────┼──────────────┤  │
│  │ 📦 Version                │ 2.70                                                                       │    ✅ Set    │  │
│  ├───────────────────────────┼────────────────────────────────────────────────────────────────────────────┼──────────────┤  │
│  │ 🚀 PyPI Release           │ https://pypi.org/project/pipulate/2.70/                                    │   ✅ Live    │  │
│  ├───────────────────────────┼────────────────────────────────────────────────────────────────────────────┼──────────────┤  │
│  │ ⏰ Completed              │ 2026-09-29 11:06:56                                                        │   🎯 Done    │  │
│  ╰───────────────────────────┴────────────────────────────────────────────────────────────────────────────┴──────────────╯  │
│                                                                                                                             │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
🔄 server.py touched; the watchdog restarts the server.
(nix) pipulate $ 
```

There, very nice. Now let's check on the Mac. Oops! It's only working on the
Pipulate site. Only the 3rd example here had the new messaging.

```zsh
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://qamy.ai | less
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://npvg.org | less
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://pipulate.com/install.sh | less 
michaellevin@MichaelMacBook-Pro ~ %
```

I think I need to do another turn including `release.py` and it's pack of stuff
that I think I need to identify and group into a chapter if I haven't already.
We have to make sure that the publishing pipeline puts all the install files in
the right location. I think I have an actual split between the physical files
now and they're all served from different web roots, so I think there's some
follow-up work. I'm also including foo_files.py for purposes of receipts and
such.

## The Three-Door Problem: Why Pushing Git Leaves Pads Stale

    flake.nix
    foo_files.py
    release.py
    assets/installer/install.sh
    __init__.py
    scripts/release/version_sync.py
    scripts/articles/common.py
    scripts/articles/generate_ai_context.py
    remotes/honeybot/nixos/configuration.nix
    nixops.sh

```text
Fable 5.1 Thinking

Thought for 5m 48s

Verifying DNS records against the compile receipts.

Tracing why the publishing pipeline still serves stale installers.

Designing a release-pipeline fix covering all three install doors.

Weighing whether release.py should invoke nixops.sh directly.

Spotting a naming collision that could break the round-trip check.

Designing precise hash checks to confirm the round-trip fix.

Building minimal title-line probes to confirm which doors update.

Grouping scattered release files into one chapter block.

Appending safe tail-anchored edits to update release-door documentation.

Banking a naming rule alongside sibling constitutional entries.

Redacting personal prompt strings to keep rules publish-safe.

Weighing the side effects of invoking the whole sync truck.

Adding a scoped installer-only mode to the sync script.

Splitting the installer edit into precise anchored patch blocks.

Consolidating the installer sync into one shared function.

Wiring release.py to call the shared installer sync step.

Sequencing the patch order and choosing the ignition command.

Drafting documentation entries for each release-pipeline file.

Deciding whether to replace or append the release.py doc line.

Verifying each anchor string is uniquely matched before patching.

Fixing a shell alias that silently breaks with arguments.

Deferring an unasked alias fix to a plain todo note.

Counting matching lines to confirm the sync fix scope.

Assembling a compact set of probe commands for verification.

Deciding which files stay in scope for the next patch.

Writing the shared installer-sync function for release.py.

Chaining the sync commands so partial failures surface.

Appending documentation of the release-pack's file layout and sources.

Listing each release-file path with its documentation comment.

Recording landed fixes and superseded gates in the changelog.

Drafting a fallback alias fix as a deferred todo note.

Checking whether the inserted rule text stays safely inside the string.

Checking anchor uniqueness and scanning for stray autolink tokens.

Avoiding bare www tokens that could trigger the autolinker.

Drafting the ignition command and its expected witness output.

Sequencing the ordered steps for applying the patch cars.
```

**Fable 5.1 Extra**: Context verified. Six of seven receipts closed, one named the gap you found on the Mac.

**Closed.** Lines 1 and 2 both read [REDACTED_IP] alone, so the www alias on qamy.ai is in and the row is complete; line 3 says npvg.org has its www too (your table shows both alias rows). Line 4 printed nothing: the patched installer parses. Line 6 printed nothing with exit 1: nothing in release.py or version_sync.py stamps the `Installer v` line you removed, so that deletion had no dependents. Line 7 names the checklist file: `prompt_foo.py:1878` holds the numbered item 12; foo_files.py only mentions it. That file is not in this context, so the checklist car waits one more turn, and the one line to add is `prompt_foo.py`.

**Open, and it is exactly what the Mac showed you.** Line 5 reads `# Pipulate Installer v1.1.0` from qamy.ai after a full release. Three physical copies of one file now exist behind three doors, and the release moved one of them. `release.py` copies `assets/installer/install.sh` into the `~/repos/Pipulate.com` checkout and pushes it (that is the `Pushed install.sh update to Pipulate.com repo` line in your release paste, and why pipulate.com reads the new header). The other two copies live on Honeybot at `~/www/npvg.org/install.sh` and `~/www/qamy.ai/install.sh`, and the only thing in the repo that writes them is `nixops.sh`'s two rsync lines. A git push moves no door; `release` moves one; `nixops.sh` moves two; nothing moves three. The router already carried this as a rule since 09-14 ("the release lane run alone leaves npvg.org stale. Fetch from each door and count") and the release convicted it a second time with a third door.

**The cure is one truck for the pads and one call from the release.** `nixops.sh` gets a `sync_installer` function holding the pad list and an `--installer` mode that runs it alone (body only, no rebuild, no config staging, no `--delete` sweep of scripts). `release.py` gets Step 3.1, which calls that mode after Step 3, non-fatal, so a dark Honeybot never blocks PyPI and the receipt says which doors did not move. The pad list lives in one file. For today, the same lane run by hand moves the two stale doors; the release step is witnessed at the next release.

**The pack, grouped.** Chapter II of foo_files.py now names the release pack under one heading: the source, the three doors, the two trucks, and the seven files that touch them. Also banked in the router: the machine-naming rule as a § key beside THE OUT-OF-BAND STEP RULE (its checklist line rides when prompt_foo.py does), the split-DNS TODO read as done, the reopen-line TODO landed by the other cure, the publish-lane rule amended to three doors, and one TODO for the `nixops` alias, which cannot take an argument (an alias whose body is a subshell dies at the parser when a word follows it, the 2026-08-01 `release()` shape), so the hand spelling this turn is `./nixops.sh --installer`.

**Carry-over, unchanged.** DDNS is on mikelev.in only.

## Do these in this order

1. Section 1: the probe block, in the terminal whose prompt reads `(nix) pipulate $`. That is the BEFORE reading.
2. Section 3, Car 1 (nixops.sh), then Car 2 (release.py), then Car 3 (foo_files.py), each in the terminal whose prompt reads `(nix) pipulate $`.
3. Section 3, Car 4: the ignition fence, same terminal. This is what moves the two doors today.
4. Section 5: the Mac witness, in the terminal whose prompt reads with your Mac's user and host name.
5. Section 2: context.txt, then compile. That is the AFTER reading.

## 1. PROBES

Machine: the terminal whose prompt reads `(nix) pipulate $`. Run before anything else.

```text
sed -n 2p assets/installer/install.sh
curl -fsSL --max-time 10 https://pipulate.com/install.sh | sed -n 2p
curl -fsSL --max-time 10 https://npvg.org | sed -n 2p
curl -fsSL --max-time 10 https://qamy.ai | sed -n 2p
curl -fsSL --max-time 10 https://qamy.ai | sed "s/_tpl_name='qamy'/_tpl_name='__INSTALL_DEFAULT_NAME__'/" | sha256sum
sha256sum assets/installer/install.sh
bash -n nixops.sh
rg -c sync_installer nixops.sh release.py
```

Lines 1 to 4 are the door census: line 2 of the file and line 2 of each served copy. Before Car 4: line 1 and line 2 read `# The installer behind pipulate.com, npvg.org and qamy.ai`, lines 3 and 4 read `# Pipulate Installer v1.1.0`. After Car 4: all four read the new title. Lines 5 and 6 are the byte gate on one pad: the served body with its stamp reversed, hashed, beside the file's hash; different before, the same 64 characters after. Line 7 is the syntax gate on nixops.sh; it prints nothing in both worlds, and a line number if Car 1 broke the file. Line 8 is the landing witness for Cars 1 and 2: nothing and exit 1 before, `nixops.sh:4` and `release.py:2` after (four lines in nixops.sh carry the name: the function, its `if`, one comment, one call; two in release.py: the def and the call).

## 2. NEXT CONTEXT

The eight lines, the two files the cars change, foo_files.py because you asked for it as the ledger, and prompt_foo.py so the checklist car can ride. flake.nix, configuration.nix, version_sync.py, common.py, generate_ai_context.py, __init__.py and install.sh did their job this turn and can drop.

```text
! sed -n 2p assets/installer/install.sh
! curl -fsSL --max-time 10 https://pipulate.com/install.sh | sed -n 2p
! curl -fsSL --max-time 10 https://npvg.org | sed -n 2p
! curl -fsSL --max-time 10 https://qamy.ai | sed -n 2p
! curl -fsSL --max-time 10 https://qamy.ai | sed "s/_tpl_name='qamy'/_tpl_name='__INSTALL_DEFAULT_NAME__'/" | sha256sum
! sha256sum assets/installer/install.sh
! bash -n nixops.sh
! rg -c sync_installer nixops.sh release.py
nixops.sh
release.py
prompt_foo.py
foo_files.py
```

## 3. PATCHES

Four cars. Car 1 is nixops.sh (three blocks: the function and its lane, the npvg.org rsync retired into it, the qamy.ai rsync replaced by the call). Car 2 is release.py (two blocks: the function, its call in main). Car 3 is foo_files.py (seven blocks, every one anchored on a unique line tail so no padding and no blank line is quoted). Car 4 is the ignition.

**Car 1.**

```text
Target: nixops.sh
[[[SEARCH]]]
echo "🚀 Syncing Hooks..."
[[[DIVIDER]]]
# THE INSTALLER LANE (2026-09-29, convicted by release 2.70). Three doors serve
# assets/installer/install.sh: pipulate.com out of the Pipulate.com checkout,
# which release.py's sync_install_sh moves, and npvg.org and qamy.ai out of
# Honeybot's ~/www, which only this file reaches. That release moved one door
# and the Mac read the old header from the other two. One function holds the
# pad list, the full sync calls it below, and `./nixops.sh --installer` runs
# it alone so release.py can move all three doors without staging config or
# sweeping scripts. Body only; no rebuild.
sync_installer() {
  echo "🚀 Syncing installer to the home-hosted doors (npvg.org, qamy.ai)..."
  ssh $TARGET "mkdir -p ~/www/npvg.org ~/www/qamy.ai" \
    && rsync -av assets/installer/install.sh $TARGET:~/www/npvg.org/install.sh \
    && rsync -av assets/installer/install.sh $TARGET:~/www/qamy.ai/install.sh
}
if [ "${1:-}" = "--installer" ]; then
  if sync_installer; then
    echo "✅ Installer synced to npvg.org and qamy.ai (body only; no rebuild)."
    exit 0
  fi
  echo "❌ Installer sync failed; one or both doors still serve the previous file."
  exit 1
fi

echo "🚀 Syncing Hooks..."
[[[REPLACE]]]

Target: nixops.sh
[[[SEARCH]]]
# ONE SOURCE, THREE PROJECTIONS: the installer lives at assets/installer/install.sh.
# release.py projects it to Pipulate.com; the lines below project it to both pads.
rsync -av assets/installer/install.sh $TARGET:~/www/npvg.org/install.sh
[[[DIVIDER]]]
# ONE SOURCE, THREE PROJECTIONS: the installer lives at assets/installer/install.sh.
# release.py projects it to Pipulate.com; sync_installer (above) projects it to both pads.
[[[REPLACE]]]

Target: nixops.sh
[[[SEARCH]]]
rsync -av assets/installer/install.sh $TARGET:~/www/qamy.ai/install.sh
[[[DIVIDER]]]
sync_installer
[[[REPLACE]]]
```

**Car 2.**

```text
Target: release.py
[[[SEARCH]]]
def sync_audit_md():
[[[DIVIDER]]]
def sync_installer_to_pads():
    """Move the installer to the two home-hosted doors, npvg.org and qamy.ai.
    THE THIRD TRUCK (2026-09-29, convicted by release 2.70). Three doors serve
    assets/installer/install.sh: pipulate.com out of the Pipulate.com checkout,
    which sync_install_sh above moves, and npvg.org and qamy.ai out of
    Honeybot's ~/www, which only nixops.sh reaches. That release moved one door
    and the Mac read the old header from the other two. nixops.sh --installer
    is that file's body-only lane (no rebuild), called here rather than copying
    its rsync lines, so the pad list lives in one place. Non-fatal on purpose:
    Honeybot dark must never block a PyPI release, and the receipt names the
    doors that did not move.
    """
    note("\n🔄 Step 3.1: Syncing the installer to npvg.org and qamy.ai via nixops.sh --installer...")
    truck = PIPULATE_ROOT / "nixops.sh"
    if not truck.exists():
        print(f"ℹ️  nixops.sh not found at {truck}; npvg.org and qamy.ai were not synced.")
        return False
    # Direct subprocess.run (not run_command) so a dark Honeybot never sys.exit()s the release.
    result = subprocess.run(["bash", str(truck), "--installer"], cwd=str(PIPULATE_ROOT),
                            capture_output=not VERBOSE, text=True)
    if result.returncode != 0:
        print("⚠️  nixops.sh --installer failed; npvg.org and qamy.ai still serve the previous installer.")
        for stream in (result.stdout, result.stderr):
            if stream and stream.strip():
                print(stream.rstrip(), file=sys.stderr)
        return False
    print("✅ Synced install.sh to npvg.org and qamy.ai (nixops.sh --installer).")
    return True

def sync_audit_md():
[[[REPLACE]]]

Target: release.py
[[[SEARCH]]]
        print("\n⏭️  Skipping installer script synchronization (--skip-install-sh-sync)")
        install_sh_success = False
[[[DIVIDER]]]
        print("\n⏭️  Skipping installer script synchronization (--skip-install-sh-sync)")
        install_sh_success = False

    # Step 3.1: the two home-hosted doors, through nixops.sh's installer lane.
    # Same flag as Step 3: one switch covers every door the installer has.
    if not args.skip_install_sh_sync:
        sync_installer_to_pads()
[[[REPLACE]]]
```

**Car 3.**

```text
Target: foo_files.py
[[[SEARCH]]]
<-- The deploy process
[[[DIVIDER]]]
<-- The orchestrator: version sync, the journal index, three doors, PyPI (THE RELEASE PACK, below)
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
<-- Pipulate.com installer real home in github/pipulate repo
[[[DIVIDER]]]
<-- Pipulate.com installer real home in github/pipulate repo
# THE RELEASE PACK (grouped 2026-09-29, the day release 2.70 moved one door
# of three). One source, three doors, two trucks. The source is
# assets/installer/install.sh. pipulate.com serves it out of the
# ~/repos/Pipulate.com checkout, which release.py's sync_install_sh moves
# (INSTALLER_SCRIPTS names install.sh and mck.sh). npvg.org and qamy.ai
# serve it out of Honeybot's ~/www, stamped at the door by nginx's
# sub_filter (configuration.nix), which only nixops.sh reaches; its
# sync_installer function holds that pad list, `./nixops.sh --installer`
# runs it alone, and release.py calls that lane as its Step 3.1. A git
# push moves no door; a release moves all three; a body-only change on
# the pads needs no rebuild. Fetch line 2 of each door and compare it to
# the file's line 2 before saying the doors agree.
# __init__.py                                #  <-- the one version; flake.nix reads it at eval, version_sync.py copies it to pyproject.toml
# scripts/release/version_sync.py            #  <-- __init__.py -> pyproject.toml (version, description, license); nothing else since 2026-09-05
# scripts/articles/generate_ai_context.py    #  <-- writes .agents/skills/journal/references/index.md at Step 1.6; never hand-edited
# nixops.sh                                  #  <-- the truck to Honeybot: hooks, scripts, both pads, staged config; --installer for the pads alone
# remotes/honeybot/nixos/configuration.nix   #  <-- the three vhosts and the door stamps; a nixos-rebuild on Honeybot to change
# assets/installer/mck.sh                    #  <-- the launcher; pipulate.com only, by INSTALLER_SCRIPTS
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
release.py prints Pushed for one door and moves one door; the release lane run alone leaves npvg.org stale. Fetch from each door and count.
[[[DIVIDER]]]
release.py prints Pushed for one door and moves one door; the release lane run alone leaves npvg.org stale. Fetch from each door and count. THREE DOORS SINCE 2026-09-29, and the rule convicted a second time the same day: release 2.70 printed Pushed install.sh update to Pipulate.com and the Mac read the old header from npvg.org and qamy.ai. The cure landed at deed 1684: nixops.sh's sync_installer holds the pad list, ./nixops.sh --installer runs it alone, and release.py's Step 3.1 calls it, non-fatal, so one release moves three doors. Gate: line 2 of each door reading the file's line 2 after the next release; until that release, ./nixops.sh --installer by hand is the truck.
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
a step folded into prose is a step the muscle memory rolls over. Sibling of ONE-FENCE-PER-HAND-STEP.
[[[DIVIDER]]]
a step folded into prose is a step the muscle memory rolls over. Sibling of ONE-FENCE-PER-HAND-STEP.
# § THE MACHINE IS NAMED BY ITS PROMPT (banked 2026-09-29, convicted at deed 1682: "run on the desk" sent the operator to both terminals) -- head every command block with the prompt string the operator sees in the terminal it runs in ((nix) pipulate $ on the workbench; the Mac's own user@host prompt on the Mac), never a nickname, repeated on every block every turn; PROBES run where compile runs because only that machine executes ! lines, and a command for any other machine is a WITNESS under its own prompt in (5); a web-console step is a numbered list naming the address to open, the menu path, each field with the value to type, the Save and Apply clicks, and a drawing of the screen when done. Value: one checklist line in prompt_foo.py (owed; item 12 sits at prompt_foo.py:1878 by deed 1684's receipt) and GLOSSARY.md (owed). Sibling of THE OUT-OF-BAND STEP RULE and THE FINDING DORY RULE.
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
# #todo #to-do #earmarks
[[[DIVIDER]]]
# #todo #to-do #earmarks
# - TODO (2026-09-29, nixops takes an argument): flake.nix's nixops is an alias whose body is a subshell, so nixops --installer expands to a subshell followed by a word and dies at the parser, the 2026-08-01 release() shape; the cure is a function, nixops() { (cd "$PIPULATE_ROOT" && ./nixops.sh "$@"); }, one line when flake.nix next rides, ignition exit then nix develop. Until then the spelling is ./nixops.sh --installer from the repo root. Gate: type nixops at a (nix) prompt reads "nixops is a function".
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
Gate: the reopen line printed from the npvg door reads npvg.org and carries no -s.
[[[DIVIDER]]]
Gate: the reopen line printed from the npvg door reads npvg.org and carries no -s. LANDED 2026-09-29 (deed 1684, the installer messaging car) by the other cure: the script prints no address at all and adds -s only when an argument was given (${1:+ -s $1}), because the line the stranger ran is the only one right at every door; the placeholder twin is declined and the gate is superseded, since a printed line that names no door cannot name the wrong one.
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
one car when configuration.nix next rides. Gate: a rebuild with no evaluation warning.
[[[DIVIDER]]]
one car when configuration.nix next rides. Gate: a rebuild with no evaluation warning. READ 2026-09-29 (deeds 1682 through 1684): the split-DNS half is done, by hand on the router: the qamy.ai host override with www as an additional name, and www added to the npvg.org row, which had none; dig +short on qamy.ai and its www alias reads Honeybot's LAN address from Prime and from the Mac, the Mac's curl reads verify ok, and curl | bash from the Mac serves the stamped installer with no flag. The two www names were the last readings to close. The evaluation-warning half stands.
[[[REPLACE]]]
```

**Car 4, the ignition.** Machine: the terminal whose prompt reads `(nix) pipulate $`. Copy the block, paste it, press Enter. It runs only after Car 1 has landed.

```text
cd "$PIPULATE_ROOT" && pwd && ./nixops.sh --installer
```

You will see: the repo path, one line starting `🚀 Syncing installer`, two short rsync file lists each naming `install.sh`, and `✅ Installer synced to npvg.org and qamy.ai (body only; no rebuild).` If you see `❌ Installer sync failed`, Honeybot did not answer ssh and nothing moved; the two doors still serve the old file and the AFTER will say so on lines 3 and 4.

Ignition, stated: Car 1 ignites at Car 4. Car 2 ignites at the next `release`, so its Step 3.1 is unwitnessed until then; this turn's AFTER witnesses Car 1's lane and Car 2's landing (probe line 8), not Car 2's run. Car 3 needs no ignition; the router is read at compile.

## 4. PROMPT

```text
Three cars landed (nixops.sh's sync_installer and --installer lane; release.py's Step 3.1 calling it; the router's release pack, the § machine-naming key, and three TODO readings) and Car 4 ran ./nixops.sh --installer by hand. The `!` receipts above are the AFTER reading from the terminal whose prompt reads `(nix) pipulate $`. Confirm: lines 1 to 4 all read the new title; lines 5 and 6 share the same hash; line 7 prints nothing; line 8 reads nixops.sh:4 and release.py:2. prompt_foo.py is in this context: write the checklist line for THE MACHINE IS NAMED BY ITS PROMPT as a car against it, beside item 11 or 12, one line, in the shape the router's § key already spells. Name anything still open. DDNS stays a carry-over earmark only.
```

## 5. EXTERNAL DELIVERABLES

**The Mac witness. In the terminal whose prompt reads with your Mac's user and host name. Manual, not echoed, because context.txt only runs on the Linux box. Run after Car 4.**

```text
curl -fsSL https://qamy.ai | head -3
curl -fsSL https://npvg.org | head -3
```

Both print three lines, the second of which reads `# The installer behind pipulate.com, npvg.org and qamy.ai`. If either still reads `# Pipulate Installer v1.1.0`, Car 4 did not move that door; read its console output for the `❌` line.

**Nothing on the router this turn.** Both host-override rows are complete, as your table shows.

**MikeLev.in**: 



## THE PROOF STRADDLE

> 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: BEFORE PROBE** (paste in bash): 

```bash
Left-hand causal boundary "blast radius" established. Make 1 change and test.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ sed -n 2p assets/installer/install.sh
curl -fsSL --max-time 10 https://pipulate.com/install.sh | sed -n 2p
curl -fsSL --max-time 10 https://npvg.org | sed -n 2p
curl -fsSL --max-time 10 https://qamy.ai | sed -n 2p
curl -fsSL --max-time 10 https://qamy.ai | sed "s/_tpl_name='qamy'/_tpl_name='__INSTALL_DEFAULT_NAME__'/" | sha256sum
sha256sum assets/installer/install.sh
bash -n nixops.sh
rg -c sync_installer nixops.sh release.py
# The installer behind pipulate.com, npvg.org and qamy.ai
# The installer behind pipulate.com, npvg.org and qamy.ai
# Pipulate Installer v1.1.0
# Pipulate Installer v1.1.0
f3181426f3ae92c96720c71c185b054f650868f8357c985045ab0ef5824136ab  -
f5debcfc0ad336036832ae8bdb3b70f5d861ff7cc6ded52843cbcbf5db1c6707  assets/installer/install.sh
(nix) pipulate $ 
```

**2: AFTER PROBE** (in context.txt): 

```text
# context.txt: the list of files an AI will read. context opens it, compile builds it.
# Keys: j down, k up; Esc then :q quits, :q! discards, :wq saves and quits.
# One line per thing the AI reads: a file path, or a command after `! `.
# A line that starts with # is a comment: that file is not read.
# To add a line: i starts typing, Esc stops. Absolute paths work from anywhere.
# Web pages, APIs and the connector words: chapter XVIII of foo_files.py.

# --- THE 40K-FOOT VIEW (uncomment on a first turn; comment out on the second) ---
# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- the book's spine, one line per article, newest first
# ~/repos/nixos/autognome.py  # <-- the machine's morning routine (this author's NixOS box only)
# init.lua                    # <-- the editor keys that drive the day
# assets/installer/install.sh # <-- how a stranger's machine gets this workshop
# GLOSSARY.md                 # <-- the terms, defined
# flake.nix                   # <-- the environment, pinned: here is my hardware, here is my state
# prompt_foo.py               # <-- the compiler that builds the payload
# foo_files.py                # <-- the router: which files ride, and this book's outline
# scripts/articles/lsa.py     # <-- the second brain: the article corpus behind `rgx`, `rgxc` and `posts`
# requirements.in             # <-- the Python packages, pinned
# pyproject.toml              # <-- the PyPI package
# __init__.py                 # <-- the version

# --- ACTUATORS (cheap; include when the AI should be able to act, not only read) ---
# cli.py                      # <-- tool calls from the command line
# scripts/xp.py               # <-- turns a pasted reply into the next context
# scripts/ai.py               # <-- a local AI writes the commit messages
# scripts/crawl.py            # <-- crawl a site into the next turn
# scripts/weblogin.py         # <-- warm a login on the persistent browser profile
# scripts/webclip_2_markdown.py  # <-- a web page, clipped, as markdown

# --- RARE ---
# scripts/foo_cartridge.py    # <-- the sealed archive: writer and verifier
# scripts/foo_replay.py       # <-- replay a sealed archive on another machine
# release.py                  # <-- how a release reaches GitHub and PyPI
# imports/voice_synthesis.py  # <-- the voice
# imports/ascii_displays.py   # <-- the ASCII art
# scripts/release/version_sync.py  # <-- version stamping (to be folded into release.py)

# --- THIS DISCUSSION ---
# The files and commands for the work in front of you. Paste the NEXT CONTEXT
# block an AI hands back directly below this line; the AI will correct a guess.

# Context 1
# ! rgxc -t grim,article 10 pfsense mikelev.in security warning

# Context 2
# ! dig +short qamy.ai
# ! dig +short www.qamy.ai
# ! dig +short npvg.org
# ! dig +short qamy.ai @1.1.1.1
# ! curl -ksv --max-time 10 -o /dev/null https://qamy.ai

# Context 3
# ! dig +short qamy.ai
# ! dig +short www.qamy.ai
# ! dig +short npvg.org
# ! dig +short qamy.ai @1.1.1.1
# ! curl -ksv --max-time 10 -o /dev/null https://qamy.ai
# ! rg -l -t py ECHO.INVARIANT
# AGENTS.md
# AUDIT.md
# .agents/skills/journal/references/index.md
# assets/installer/install.sh

# Context 4
# ! dig +short qamy.ai
# ! dig +short www.qamy.ai
# ! dig +short www.npvg.org
# ! bash -n assets/installer/install.sh
# ! curl -fsSL --max-time 10 https://qamy.ai | head -3
# ! rg -n Installer.v release.py scripts/release/version_sync.py
# ! rg -n -t py ECHO.INVARIANT
# assets/installer/install.sh
# AGENTS.md
# flake.nix
# foo_files.py
# release.py
# assets/installer/install.sh
# __init__.py
# scripts/release/version_sync.py
# scripts/articles/common.py
# scripts/articles/generate_ai_context.py
# remotes/honeybot/nixos/configuration.nix
# nixops.sh

# Context 5
! sed -n 2p assets/installer/install.sh
! curl -fsSL --max-time 10 https://pipulate.com/install.sh | sed -n 2p
! curl -fsSL --max-time 10 https://npvg.org | sed -n 2p
! curl -fsSL --max-time 10 https://qamy.ai | sed -n 2p
! curl -fsSL --max-time 10 https://qamy.ai | sed "s/_tpl_name='qamy'/_tpl_name='__INSTALL_DEFAULT_NAME__'/" | sha256sum
! sha256sum assets/installer/install.sh
! bash -n nixops.sh
! rg -c sync_installer nixops.sh release.py
nixops.sh
release.py
prompt_foo.py
foo_files.py
```

**3: PATCHES** (the 1 change): One patch didn't land because of ambiguity.

```diff
Left-hand causal boundary "blast radius" established. Make 1 change and test.
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 'nixops.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'nixops.sh'.
❌ Warning: Ambiguous match (found 2 times) in 'nixops.sh'. Skipping.
(nix) pipulate $ d
diff --git a/nixops.sh b/nixops.sh
index 4a1271a0..ac475da1 100755
--- a/nixops.sh
+++ b/nixops.sh
@@ -9,6 +9,29 @@
 
 TARGET="mike@[REDACTED_IP]"
 
+# THE INSTALLER LANE (2026-09-29, convicted by release 2.70). Three doors serve
+# assets/installer/install.sh: pipulate.com out of the Pipulate.com checkout,
+# which release.py's sync_install_sh moves, and npvg.org and qamy.ai out of
+# Honeybot's ~/www, which only this file reaches. That release moved one door
+# and the Mac read the old header from the other two. One function holds the
+# pad list, the full sync calls it below, and `./nixops.sh --installer` runs
+# it alone so release.py can move all three doors without staging config or
+# sweeping scripts. Body only; no rebuild.
+sync_installer() {
+  echo "🚀 Syncing installer to the home-hosted doors (npvg.org, qamy.ai)..."
+  ssh $TARGET "mkdir -p ~/www/npvg.org ~/www/qamy.ai" \
+    && rsync -av assets/installer/install.sh $TARGET:~/www/npvg.org/install.sh \
+    && rsync -av assets/installer/install.sh $TARGET:~/www/qamy.ai/install.sh
+}
+if [ "${1:-}" = "--installer" ]; then
+  if sync_installer; then
+    echo "✅ Installer synced to npvg.org and qamy.ai (body only; no rebuild)."
+    exit 0
+  fi
+  echo "❌ Installer sync failed; one or both doors still serve the previous file."
+  exit 1
+fi
+
 echo "🚀 Syncing Hooks..."
 scp remotes/honeybot/hooks/post-receive $TARGET:~/git/mikelev.in.git/hooks/post-receive
 ssh $TARGET "chmod +x ~/git/mikelev.in.git/hooks/post-receive"
@@ -27,8 +50,7 @@ echo "🚀 Syncing NPvg pad (one address, two bodies)..."
 ssh $TARGET "mkdir -p ~/www/npvg.org"
 rsync -av remotes/honeybot/www/npvg.org/ $TARGET:~/www/npvg.org/
 # ONE SOURCE, THREE PROJECTIONS: the installer lives at assets/installer/install.sh.
-# release.py projects it to Pipulate.com; the lines below project it to both pads.
-rsync -av assets/installer/install.sh $TARGET:~/www/npvg.org/install.sh
+# release.py projects it to Pipulate.com; sync_installer (above) projects it to both pads.
 
 echo "🚀 Syncing qamy.ai door (npvg.org's shape, its own tree)..."
 ssh $TARGET "mkdir -p ~/www/qamy.ai"
(nix) pipulate $ m
📝 Committing: chore: Sync installer and hooks for npvg.org and qamy.ai 
[main 08b27a2a] chore: Sync installer and hooks for npvg.org and qamy.ai
 1 file changed, 24 insertions(+), 2 deletions(-)
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'release.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'release.py'.
(nix) pipulate $ d
diff --git a/release.py b/release.py
index fcc49075..c85219f9 100755
--- a/release.py
+++ b/release.py
@@ -526,6 +526,35 @@ def sync_install_sh(script_name="install.sh"):
         print(f"⚠️  Install.sh sync failed: {e}")
         return False
 
+def sync_installer_to_pads():
+    """Move the installer to the two home-hosted doors, npvg.org and qamy.ai.
+    THE THIRD TRUCK (2026-09-29, convicted by release 2.70). Three doors serve
+    assets/installer/install.sh: pipulate.com out of the Pipulate.com checkout,
+    which sync_install_sh above moves, and npvg.org and qamy.ai out of
+    Honeybot's ~/www, which only nixops.sh reaches. That release moved one door
+    and the Mac read the old header from the other two. nixops.sh --installer
+    is that file's body-only lane (no rebuild), called here rather than copying
+    its rsync lines, so the pad list lives in one place. Non-fatal on purpose:
+    Honeybot dark must never block a PyPI release, and the receipt names the
+    doors that did not move.
+    """
+    note("\n🔄 Step 3.1: Syncing the installer to npvg.org and qamy.ai via nixops.sh --installer...")
+    truck = PIPULATE_ROOT / "nixops.sh"
+    if not truck.exists():
+        print(f"ℹ️  nixops.sh not found at {truck}; npvg.org and qamy.ai were not synced.")
+        return False
+    # Direct subprocess.run (not run_command) so a dark Honeybot never sys.exit()s the release.
+    result = subprocess.run(["bash", str(truck), "--installer"], cwd=str(PIPULATE_ROOT),
+                            capture_output=not VERBOSE, text=True)
+    if result.returncode != 0:
+        print("⚠️  nixops.sh --installer failed; npvg.org and qamy.ai still serve the previous installer.")
+        for stream in (result.stdout, result.stderr):
+            if stream and stream.strip():
+                print(stream.rstrip(), file=sys.stderr)
+        return False
+    print("✅ Synced install.sh to npvg.org and qamy.ai (nixops.sh --installer).")
+    return True
+
 def sync_audit_md():
     """Copies AUDIT.md to Pipulate.com root and commits if changed."""
     note("\n🔄 Step 3.5: Synchronizing AUDIT.md to Pipulate.com...")
@@ -1310,6 +1339,11 @@ def main():
         print("\n⏭️  Skipping installer script synchronization (--skip-install-sh-sync)")
         install_sh_success = False
 
+    # Step 3.1: the two home-hosted doors, through nixops.sh's installer lane.
+    # Same flag as Step 3: one switch covers every door the installer has.
+    if not args.skip_install_sh_sync:
+        sync_installer_to_pads()
+
     # Step 3.5: AUDIT.md Synchronization
     if not args.skip_audit_sync:
         audit_md_success = sync_audit_md()
(nix) pipulate $ m
📝 Committing: chore: Implement installer synchronization via nixops.sh --installer 
[main 4de14a74] chore: Implement installer synchronization via nixops.sh --installer
 1 file changed, 34 insertions(+)
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
(nix) pipulate $ d
diff --git a/foo_files.py b/foo_files.py
index fdb38231..9f226e33 100644
--- a/foo_files.py
+++ b/foo_files.py
@@ -1249,7 +1249,7 @@ AI_PHOOEY_CHOP = r"""#
 # THE FIRST-CELL BLAST RADIUS RULE (banked 2026-09-01, two convictions in two consecutive rides). Anything the FIRST executable cell of Onboarding.ipynb touches -- including every module it imports at load time -- prints to a stranger at the one moment they cannot tell noise from failure. Both convictions were trivial in SUBSTANCE and severe in POSITION: a KeyError from a read-before-write, and a SyntaxWarning from an unraw ASCII-art literal. Judge a defect in that blast radius by WHERE it fires, never by how small it is, and hold first-cell code to the standard of a cold install rather than the standard of the maintainer's warm one.
 # THE ACTUATOR'S OWN DIAGNOSTIC RULE (banked 2026-09-01). apply.py's AST airlock compiles candidate content before writing, so it printed `<unknown>:985: SyntaxWarning: invalid escape sequence` on THREE consecutive `app` runs -- correct message, correct line number -- and it read as noise FROM the tool rather than a finding ABOUT the file. The `<unknown>` filename is what disguised it: a diagnostic with no filename reads as the tool complaining about itself. A LINE NUMBER in an actuator's output is a finding about the FILE. Read it, or the instrument you built is reporting to nobody.
 # THE PRE-COMPILE ACTUATOR RULE (banked 2026-09-01). A straddle probe answers honestly only when its two taps land on OPPOSITE sides of the actuator. The `diff -q` sync probe was designed as three states (0 in sync, 1 patched-not- copied, 0 after the cp) and printed 0 in BOTH receipts, because the cp ran before the compile -- the same printout in both worlds, which is the DISCRIMINATION QUESTION failing inside a probe written to answer it. Prefer a probe that reads the PROPERTY at the destination (does the working copy compile?) over one that COMPARES two files; the property probe cannot be defeated by running the actuator early.
-# THE PUBLISH LANE IS NOT THE PUSH: installer edits reach strangers only through each door's own truck; `git push` is not their ignition. TWO DOORS SINCE 2026-09-14: pipulate.com through release.py's sync_install_sh (publish-only lane, proven idempotent 2026-08-30: python release.py --skip-version-sync --skip-docs-sync --skip-audit-sync --skip-ai-context-sync --skip-breadcrumb-sync --skip-trifecta-rebuild) and npvg.org through nixops.sh's rsync to the pad, which needs no nixos-rebuild for a body-only change (two no-op rebuilds witnessed 2026-09-14). release.py prints Pushed for one door and moves one door; the release lane run alone leaves npvg.org stale. Fetch from each door and count.
+# THE PUBLISH LANE IS NOT THE PUSH: installer edits reach strangers only through each door's own truck; `git push` is not their ignition. TWO DOORS SINCE 2026-09-14: pipulate.com through release.py's sync_install_sh (publish-only lane, proven idempotent 2026-08-30: python release.py --skip-version-sync --skip-docs-sync --skip-audit-sync --skip-ai-context-sync --skip-breadcrumb-sync --skip-trifecta-rebuild) and npvg.org through nixops.sh's rsync to the pad, which needs no nixos-rebuild for a body-only change (two no-op rebuilds witnessed 2026-09-14). release.py prints Pushed for one door and moves one door; the release lane run alone leaves npvg.org stale. Fetch from each door and count. THREE DOORS SINCE 2026-09-29, and the rule convicted a second time the same day: release 2.70 printed Pushed install.sh update to Pipulate.com and the Mac read the old header from npvg.org and qamy.ai. The cure landed at deed 1684: nixops.sh's sync_installer holds the pad list, ./nixops.sh --installer runs it alone, and release.py's Step 3.1 calls it, non-fatal, so one release moves three doors. Gate: line 2 of each door reading the file's line 2 after the next release; until that release, ./nixops.sh --installer by hand is the truck.
 # A BARE FENCE CANNOT RIDE SEARCH/REPLACE: apply.py strips bare fence lines from the payload before matching; a fence edit rides as a sed car spelled with \x60\x60\x60.
 # ECHO IS NOT PRINTF INSIDE A NIX STRING: bash's builtin echo leaves \n literal and a Nix indented string passes it through. Convicted 2026-08-30: bash -c 'echo "a\nb"' | cat -A -> a\nb$
 # PURITY IS A PROPERTY OF THE EVALUATION: one pure `nix develop` on a platform proves every devShell attribute evaluates pure there. Two Darwin receipts 2026-08-30; --impure is vestigial (earmark).
@@ -1262,6 +1262,7 @@ AI_PHOOEY_CHOP = r"""#
 # THE ENTRY-POINT CHECK (banked 2026-09-03). Every WRITE_FILE of a runnable .py script MUST end with the __main__ guard, and the first probe after ignition MUST be one that can only print if main() actually ran (a target line, a wrote-N line). A script that imports cleanly and exits 0 is indistinguishable from success by exit code alone.
 # THE DATA-FILE RE-RENDER RULE (banked 2026-09-03). When a layout gains a site.data.* lookup, jekyll serve's incremental mode will not re-render existing pages when only the data file changes. Local preview needs `touch _layouts/default.html`; production `jekyll build` needs nothing.
 # § THE OUT-OF-BAND STEP RULE (banked 2026-09-13) -- a hand step the terminal cannot take (a phone, a vendor console, a registrar) rides at the TOP of PROBES under a capitalized label, lettered, with its one address in its own fence and the expected screen stated in words; the compile's ! line reads a tag minted in the caboose and never repeats the visit. Conviction: three cellular witnesses across two articles, all caught because the block could not be scrolled past; a step folded into prose is a step the muscle memory rolls over. Sibling of ONE-FENCE-PER-HAND-STEP.
+# § THE MACHINE IS NAMED BY ITS PROMPT (banked 2026-09-29, convicted at deed 1682: "run on the desk" sent the operator to both terminals) -- head every command block with the prompt string the operator sees in the terminal it runs in ((nix) pipulate $ on the workbench; the Mac's own user@host prompt on the Mac), never a nickname, repeated on every block every turn; PROBES run where compile runs because only that machine executes ! lines, and a command for any other machine is a WITNESS under its own prompt in (5); a web-console step is a numbered list naming the address to open, the menu path, each field with the value to type, the Save and Apply clicks, and a drawing of the screen when done. Value: one checklist line in prompt_foo.py (owed; item 12 sits at prompt_foo.py:1878 by deed 1684's receipt) and GLOSSARY.md (owed). Sibling of THE OUT-OF-BAND STEP RULE and THE FINDING DORY RULE.
 # § THE PLACEHOLDER THAT RIDES INTO A PROBE (banked 2026-09-13) -- a redaction placeholder quoted from a published article into a paste-ready command dies at argument parsing (curl 49, both lanes, twice) and the real address is not the cure, because the next publish scrubs it and the next model re-quotes it; derive the address, route through a name, or print a verdict token instead of a value, so the probe survives every lane it will be read in. Checklist item 6 was already the rule; the emitter broke it and the instrument caught it. Sibling of NO PLACEHOLDERS IN PASTE-READY LINES.
 # § COUNT THE REPLACE AFTER YOU WRITE IT (banked 2026-09-13) -- a displacement predicted from a draft read +5 and the receipt read +6 because the emitted comment was seventeen lines counted as sixteen; the interlock landed the block exactly and the commit arithmetic convicted the predictor. Predict deltas from the block as emitted, never from the block as imagined. Sibling of DELTA-NOT-ABSOLUTE.
 # THE DIFFSTAT IS NOT THE BLOCK COUNT (banked 2026-09-15). git matches every line a SEARCH and its REPLACE share and reports them unchanged, so a diffstat's insertions and deletions read LOWER than the block lengths by the shared count: blocks of 120 and 151 read +110/-139, twelve bare # separators and carried-over lines matched. Only the net is invariant. Predict the net from the blocks; read insertions and deletions as floors. Sibling of COUNT THE REPLACE AFTER YOU WRITE IT.
@@ -1412,8 +1413,26 @@ foo_files.py      #  <-- THIS file. Content compiler router. Makes it very meta.
 
 # Software Von Neumann Probe IaC Kickstart
 
-# release.py                                 #  <-- The deploy process
+# release.py                                 #  <-- The orchestrator: version sync, the journal index, three doors, PyPI (THE RELEASE PACK, below)
 # assets/installer/install.sh                #  <-- Pipulate.com installer real home in github/pipulate repo
+# THE RELEASE PACK (grouped 2026-09-29, the day release 2.70 moved one door
+# of three). One source, three doors, two trucks. The source is
+# assets/installer/install.sh. pipulate.com serves it out of the
+# ~/repos/Pipulate.com checkout, which release.py's sync_install_sh moves
+# (INSTALLER_SCRIPTS names install.sh and mck.sh). npvg.org and qamy.ai
+# serve it out of Honeybot's ~/www, stamped at the door by nginx's
+# sub_filter (configuration.nix), which only nixops.sh reaches; its
+# sync_installer function holds that pad list, `./nixops.sh --installer`
+# runs it alone, and release.py calls that lane as its Step 3.1. A git
+# push moves no door; a release moves all three; a body-only change on
+# the pads needs no rebuild. Fetch line 2 of each door and compare it to
+# the file's line 2 before saying the doors agree.
+# __init__.py                                #  <-- the one version; flake.nix reads it at eval, version_sync.py copies it to pyproject.toml
+# scripts/release/version_sync.py            #  <-- __init__.py -> pyproject.toml (version, description, license); nothing else since 2026-09-05
+# scripts/articles/generate_ai_context.py    #  <-- writes .agents/skills/journal/references/index.md at Step 1.6; never hand-edited
+# nixops.sh                                  #  <-- the truck to Honeybot: hooks, scripts, both pads, staged config; --installer for the pads alone
+# remotes/honeybot/nixos/configuration.nix   #  <-- the three vhosts and the door stamps; a nixos-rebuild on Honeybot to change
+# assets/installer/mck.sh                    #  <-- the launcher; pipulate.com only, by INSTALLER_SCRIPTS
 # ~/repos/Pipulate.com/_layouts/default.html
 # ~/repos/nixos/.gitignore
 # ~/repos/Pipulate.com/install.md            #  <-- Gets copied into place here by pipulate/release.py
@@ -2602,12 +2621,13 @@ MATCHBOOK_CHOP = r"""
 ! .venv/bin/python -c 'import re;t=open("GLOSSARY.md",encoding="utf-8").read();[print(h,"—",re.sub(r"\s+"," ",p)) for h,p in re.findall(r"^- \*\*([^*]+)\*\* — \*(.+?)\*",t,flags=re.M|re.S)]'
 """
 # #todo #to-do #earmarks
+# - TODO (2026-09-29, nixops takes an argument): flake.nix's nixops is an alias whose body is a subshell, so nixops --installer expands to a subshell followed by a word and dies at the parser, the 2026-08-01 release() shape; the cure is a function, nixops() { (cd "$PIPULATE_ROOT" && ./nixops.sh "$@"); }, one line when flake.nix next rides, ignition exit then nix develop. Until then the spelling is ./nixops.sh --installer from the repo root. Gate: type nixops at a (nix) prompt reads "nixops is a function".
 # - EARMARK: THE FORECAST NAMED THE PROSE (banked 2026-09-29, convicted at deed 1671): a prediction about an instrument's WORDING is a prediction about the instrument's version, never about the world. curl -v's "SSL certificate verify ok." never printed on Honeybot's build, the grep read the content-type where the sentence was forecast, and the miss was the forecaster's while the certificate verified. Forecast the value an instrument exposes on purpose (%{ssl_verify_result}, an exit code, a count, a verdict token) and read its prose as decoration; when only prose is available, name its ABSENCE as one of the worlds. Sibling of THE GATE SPEAKS A VERDICT and THE SILENCED CHANNEL; cousin of INCOMMENSURABLE MEASUREMENTS.
 # § THE FOLDER IS A VARIABLE (banked 2026-09-29, convicted twice at deed 1677, graduated at deed 1679) -- app resolves Targets from PIPULATE_ROOT and nothing else in a hand fence does, so every hand fence opens with cd "$PIPULATE_ROOT" && pwd and its first receipt names the folder every later line ran in; and a hand step prints a different thing in the world where it did nothing (rm -v and never rm -f, mv -v, a verdict token), so no "You will see" is true in both worlds. Value: GLOSSARY.md.
 # - TODO (2026-09-29, the word bff): bjj became bff by hand (538b8ef5, "because it's friendlier") and the function's --reason string still reads "bjj: a trusted compile from the workbench", which every Summary's Command line now quotes under the other name; one word when flake.nix rides, and a census of scripts/boot_menu.py for a bjj row beside it. The router's dated bjj lines stay as written. Gate: the Command line reads bff.
 # - TODO (2026-09-29, the qamy.ai divergence): the door is a mirror on purpose today, and its first divergence rode with it (the two command lines on index.html name qamy.ai). Owed, body-only, rsync and no rebuild: the title and heading (the domain names the method, QA My AI); the three walk pages under qamy.ai/walk/, which still spell npvg.org in their text and their ls path; and a trail for this door, since assets/trails/public_walk.json still names npvg.org's stops. The four new files also ring in the Paintbox as unclaimed; they belong in chapter IX beside npvg.org's. Gate: curl -sS -A Mozilla https://qamy.ai/ | grep -ci npvg reads 0.
-# - TODO (2026-09-29, the split-DNS line for qamy.ai): the LAN verdict has read qamy_lan_differs_from_npvg_lan on every tap since 09-27; the pfSense Host Override (qamy under ai, Honeybot's LAN address, www as an additional name) is a console step no repo makes. Gate: the LAN verdict probe reads qamy_lan_matches_npvg_lan. Beside it, cosmetic: nixos-rebuild on 26.05pre warns that services.logind.lidSwitch, lidSwitchExternalPower, services.xserver.displayManager.gdm.enable and gdm.wayland have moved under services.logind.settings.Login and services.displayManager.gdm; the old names still evaluate; one car when configuration.nix next rides. Gate: a rebuild with no evaluation warning.
-# - TODO (2026-09-28, THE REOPEN LINE NAMES THE OTHER DOOR; read at deed 1662 off install.sh): the nix-missing branch prints curl -fsSL https://pipulate.com/install.sh | bash -s npvg from the npvg door, because the script cannot learn its own address ($0 is bash and the pipe carries none; THE DOOR NAMES THE FOLDER) and KEY_URL stays at the old door on purpose; a stranger who follows that line passes the folder as an argument, which writes whitelabel.txt and names the app Npvg where the plain npvg line names it Pipulate (the 2026-09-18 receipt saw this branch and never rode it). Cure: a second stamped placeholder for the door's URL (a __INSTALL_DEFAULT_URL__ twin, stamped by the same sub_filter on the pad and spelled in two halves the way the name is) and no -s when the folder is the door's default; section 3 of the install skill says what the script prints today. Gate: the reopen line printed from the npvg door reads npvg.org and carries no -s.
+# - TODO (2026-09-29, the split-DNS line for qamy.ai): the LAN verdict has read qamy_lan_differs_from_npvg_lan on every tap since 09-27; the pfSense Host Override (qamy under ai, Honeybot's LAN address, www as an additional name) is a console step no repo makes. Gate: the LAN verdict probe reads qamy_lan_matches_npvg_lan. Beside it, cosmetic: nixos-rebuild on 26.05pre warns that services.logind.lidSwitch, lidSwitchExternalPower, services.xserver.displayManager.gdm.enable and gdm.wayland have moved under services.logind.settings.Login and services.displayManager.gdm; the old names still evaluate; one car when configuration.nix next rides. Gate: a rebuild with no evaluation warning. READ 2026-09-29 (deeds 1682 through 1684): the split-DNS half is done, by hand on the router: the qamy.ai host override with www as an additional name, and www added to the npvg.org row, which had none; dig +short on qamy.ai and its www alias reads Honeybot's LAN address from Prime and from the Mac, the Mac's curl reads verify ok, and curl | bash from the Mac serves the stamped installer with no flag. The two www names were the last readings to close. The evaluation-warning half stands.
+# - TODO (2026-09-28, THE REOPEN LINE NAMES THE OTHER DOOR; read at deed 1662 off install.sh): the nix-missing branch prints curl -fsSL https://pipulate.com/install.sh | bash -s npvg from the npvg door, because the script cannot learn its own address ($0 is bash and the pipe carries none; THE DOOR NAMES THE FOLDER) and KEY_URL stays at the old door on purpose; a stranger who follows that line passes the folder as an argument, which writes whitelabel.txt and names the app Npvg where the plain npvg line names it Pipulate (the 2026-09-18 receipt saw this branch and never rode it). Cure: a second stamped placeholder for the door's URL (a __INSTALL_DEFAULT_URL__ twin, stamped by the same sub_filter on the pad and spelled in two halves the way the name is) and no -s when the folder is the door's default; section 3 of the install skill says what the script prints today. Gate: the reopen line printed from the npvg door reads npvg.org and carries no -s. LANDED 2026-09-29 (deed 1684, the installer messaging car) by the other cure: the script prints no address at all and adds -s only when an argument was given (${1:+ -s $1}), because the line the stranger ran is the only one right at every door; the placeholder twin is declined and the gate is superseded, since a printed line that names no door cannot name the wrong one.
 # - TODO (2026-09-28, THE SKILLS INDEX IS A WELL-KNOWN URI; read at deed 1662 off the Claude Code docs' own response headers, rel="agent-skills" beside rel="llms-txt", and the Agent Skills Discovery RFC v0.2.0 it points at): a site announces its skills at /.well-known/agent-skills/index.json, one entry per skill with name, type skill-md, description, url and a sha256 digest, the llms.txt reflex aimed at SKILL.md (pipulate.com already serves llms.txt from generate_llms_txt.py and AI_CONTEXT.md from release.py's sync lane); the index is GENERATED at release from .agents/skills/*/SKILL.md, each file served under /.well-known/agent-skills/<name>/SKILL.md with its digest computed over the bytes served, never authored, and the digest is the cartridge's own rung (THE RECEIPT LADDER). Needs release.py and the Pipulate.com repo in the payload. Gate: the index at pipulate.com reads six entries and each digest matches the served SKILL.md.
 # - TODO (2026-09-28, THE CLAUDE CODE PATH; read at deed 1662 off the Claude Code skills page): Claude Code loads a repository's skills from .claude/skills/<name>/SKILL.md (personal ones from ~/.claude/skills, plugins aside), Codex from .agents/skills, OpenCode from both, so a Claude Code session opened in this repo sees none of the six until .claude/skills exists; the cheapest bridge is one tracked symlink, .claude/skills -> ../.agents/skills (ln -s, git add; .claude/settings.local.json ignored in the same car), and the same page says a .claude-plugin/plugin.json inside a skill folder loads it as a plugin that can bundle hooks and MCP servers, which this repo declines on purpose (the tool call rides the compile as a receipt, never the vendor's session). Ruling owed: whether a vendor directory at the root is the price of the six names behind /. Gate: the / check, the six names offered in a session opened here. RULED 2026-09-28 (deed 1663; the Claude Code skills page read into the turn by one web fetch): the symlink, one directory link and not six. The page's table names .claude/skills/<skill-name>/SKILL.md as the project location and no .agents path; its Symlinked folders rule blesses a <skill-name> entry that is a symlink and dedupes by target, and says nothing of the parent, so six per-skill links are the documented form and the parent link is one step past it; the parent link is chosen because it is a property (whatever .agents/skills holds, .claude/skills holds, by construction) where six links are a convention a seventh skill breaks. The vendor directory at the root is the price, paid in one tracked link and .claude/settings.local.json ignored (the /skills menu writes it). LANDED as deed 1663's Car 4: mkdir -p .claude, ln -s ../.agents/skills .claude/skills, git add; the router claims the link; AGENTS.md's Skills line names it. Gate unchanged, and it needs claude on the box (a census probe reads it): a session that offers none of the six is the reading that swaps the one link for six per-skill links next turn. Declined as written: a .claude-plugin/plugin.json, which the page says makes a skill folder a plugin that can bundle hooks and MCP servers. READ 2026-09-28 (deed 1665, the dismount): WITNESSED on the Mac at 2.67. curl -fsSL https://npvg.org | bash read v2.67 and 291 packages; from Cowork's VM through the computer link readlink read ../.agents/skills rc=0, six SKILL.md and git log dd145e2 (claude there is /opt/cowork/claude-bin/claude, the VM's copy, no reading of the Mac's own PATH); Chat and Cowork read no repo's .claude/skills (account skills and plugins only) and the operator typed / in the wrong tab first; in the desktop app's Code tab with ~/npvg as the working folder, /pipulate was listed, selected, and Opus 5.5 answered from the skill's own sections (install state, the three ways in, reset, remove). One link stands. Prime's own terminal / (claude 1.0.85 on the box, both taps) was never typed; the Coverage gauge counts a symlink as +0 claimed and +0 tracked (forecast +1/+1, hedged, wrong); the other names the Code tab listed (hello-workflow among them) are unread. This line's job is done once those names are read.
 # - TODO (2026-09-28, THE WHEEL IS NOT THE SDIST; read at deed 1661): MANIFEST.in's include and graft reach the sdist (114 members, .agents/ and the two root files in) and never the wheel (81 members, LICENSE alone), and pip install fetches the wheel, so a plain install still carries no AGENTS.md; pyproject's package-data block is a no-op by receipt (none of its five names in either artifact, and CHANGELOG.md names a file the tree does not hold). Ruling owed on whether the agent-facing files should ride a package directory or whether PyPI's rendered README is the wheel user's whole door; cut the dead block after a census of whether cli.py's pipulate install needs a bundled install.sh. READ 2026-09-28 (deed 1662): waits. The wheel is PATH 2's bootstrap (pipx install pipulate, then pipulate install, which README says runs the same universal installer), so every wheel user ends at a git checkout that carries AGENTS.md and .agents/ one step later, and the sdist, the artifact an auditor reads without installing, carries them now; no door a wheel user meets is missing anything. The dead package-data block is a pyproject.toml cleanup when that file rides, gated as written.
(nix) pipulate $ m
📝 Committing: refactor: update installer and deployment process
[main 96fdeb18] refactor: update installer and deployment process
 1 file changed, 24 insertions(+), 4 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 13, done.
Counting objects: 100% (13/13), done.
Delta compression using up to 48 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 4.44 KiB | 379.00 KiB/s, done.
Total 9 (delta 6), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (6/6), completed with 4 local objects.
To github.com:pipulate/pipulate.git
   32c6414f..96fdeb18  main -> main
(nix) pipulate $
```

Ignition:

```bash
Left-hand causal boundary "blast radius" established. Make 1 change and test.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ cd "$PIPULATE_ROOT" && pwd && ./nixops.sh --installer
/home/mike/repos/pipulate
🚀 Syncing installer to the home-hosted doors (npvg.org, qamy.ai)...
sending incremental file list
install.sh

sent 5,977 bytes  received 185 bytes  12,324.00 bytes/sec
total size is 18,432  speedup is 2.99
sending incremental file list
install.sh

sent 5,977 bytes  received 185 bytes  4,108.00 bytes/sec
total size is 18,432  speedup is 2.99
✅ Installer synced to npvg.org and qamy.ai (body only; no rebuild).
(nix) pipulate $ 
```

There's always the question of whether you forge forward when a patch didn't
land or hit full-stop and burn tokens with a perfect linear path forward and my
gut and the economics of it told me to forge forward. Fable 5.1 can clean that
up on the rebound.

## The Car Mints Its Own Twin: Ambiguity in Ordered Patching

**4: PROMPT**: Three cars landed (nixops.sh's sync_installer and --installer lane; release.py's Step 3.1 calling it; the router's release pack, the § machine-naming key, and three TODO readings) and Car 4 ran ./nixops.sh --installer by hand. The `!` receipts above are the AFTER reading from the terminal whose prompt reads `(nix) pipulate $`. Confirm: lines 1 to 4 all read the new title; lines 5 and 6 share the same hash; line 7 prints nothing; line 8 reads nixops.sh:4 and release.py:2. prompt_foo.py is in this context: write the checklist line for THE MACHINE IS NAMED BY ITS PROMPT as a car against it, beside item 11 or 12, one line, in the shape the router's § key already spells. Name anything still open. DDNS stays a carry-over earmark only.

**5: DELIVERABLE**: I'm holding back on the next `release` because I want to
make sure we address the non-landing patch and any other follow-up but we're
rapidly approaching the dismount. Don't dismount yet. Do the next 5-Car Train
proof straddle first please and thank you!

```text
Fable 5.1 Thinking

Thought for 6m 43s

Getting started on the request.

Tracing the ambiguous rsync match blocking one patch.

Spotting a regression where a full sync skips one pad's installer.

Pinpointing a unique anchor for the two-line fix.

Verifying hash round trips and confirming the correct fix count.

Drafting a checklist line for the new naming rule.

Finding a safe anchor to insert the checklist line.

Updating the tracking note once the checklist line lands.

Choosing a unique anchor to record the fix's landing.

Deciding how to split the fix into separate tracked patches.

Weighing whether the pending release truly needs to run now.

Planning verification probes for each pending patch's ignition.

Drafting exact verification commands and their expected before-and-after output.

Reviewing the installer sync function and its downstream script steps.

Recounting the diff's line offsets to confirm placement.

Checking match counts before and after the naming-rule edit.

Choosing the simplest verification probe over clever alternatives.

Weighing a version-bump-free path to test the pending release step.

Weighing whether a partial sync run counts as a fair witness.

Choosing the exact command to trigger the cheap witness run.

Detailing the expected console output for the cheap witness run.

Naming how one block's own edit created another's ambiguous match.

Wording the naming rule's checklist entry and citation.

Tracing which past deed number emitted each landed fix.

Reconciling deed numbers to confirm each compile's timeline.

Drafting a status update on the hand-truck release gate.

Listing which files the next work session's cars must revisit.

Drafting verification probes with the required prompt-string prefix.

Drafting the two patch blocks with unique search anchors.

Choosing the exact interpreter and command for the release gate.

Enumerating each console line the release run would print.

Sequencing the ordered cars to keep the witness safe.

Drafting the final prompt summarizing all four verification cars.

Naming the pattern where a fix mints its own ambiguous twin.

Checking whether the transport's link rewriting affects any patch text.

Documenting the hand-truck release's reversed round trip and line landings.

Sequencing the ordered cars and drafting the final verification prompt.

Verifying the exact search text matches the file's ending byte for byte.

Drafting the final summary covering all patches and open items.

Explaining why the interlock refused and what remains untriggered.

Listing every expected console line for the cheap witness run.

Listing final probe commands and files for the next session.
```

**Fable 5.1 Extra**: Context verified. Tool calls this turn: none. The new session cost nothing: the payload carries the whole transcript, and every reading below comes from this compile's eight receipts and the file bodies, not from anything the old session held.

**Seven of eight closed.** Lines 1 to 4 all read `# The installer behind pipulate.com, npvg.org and qamy.ai`: the file and all three doors agree. Lines 5 and 6 both read `f5debcfc…6707`: the qamy.ai body with its stamp reversed hashes to the file, so the door stamps one token and changes nothing else. Line 7 printed nothing: nixops.sh parses with the new function in it. Line 8 read `release.py:2` and `nixops.sh:3` (rg prints files in finish order; read as a set). The forecast was 4, and 3 is the right reading: the fourth line was the bare `sync_installer` call at the bottom of the full sync, the block apply.py refused.

**Why it was refused, and what it leaves.** Block 1 of Car 1 wrote the function, whose body carries `rsync -av assets/installer/install.sh $TARGET:~/www/qamy.ai/install.sh`. Block 3 searched for exactly that line, so by the time it ran the file held two copies and the interlock refused, correctly. The miss is the emitter's: a SEARCH must be unique in the file the earlier blocks of the same car leave behind, not in the file the payload showed; block 1 minted block 3's twin. (A candidate for the dismount's bank, sibling of SINGLE-CANDIDATE BLINDNESS, here with the car creating the second candidate itself.) What it leaves on disk: the `--installer` lane is complete and witnessed (your Car 4 paste: two rsync file lists, the ✅ line), but the full `./nixops.sh` now moves qamy.ai's installer and not npvg.org's: block 2 removed that rsync line, block 3 never swapped the qamy.ai line for the call, and the function's own comment ("the full sync calls it below") is false until Car 1 below lands. No full sync ran in the window, so no door drifted. Forging forward was the right call: the interlock landed nothing halfway, and the next tap named the gap to the line.

**Step 3.1 landed and has never run.** `release.py:2` is the def and the call. Its ignition is a release, which you are holding back. The cheap witness is the publish-only lane the router already proved idempotent on 2026-08-30: it runs Step 3 and Step 3.1, skips the version bump and PyPI, and on a clean tree ends at "No changes to commit". The doors will not move (they already agree), so its whole receipt is one console line, `✅ Synced install.sh to npvg.org and qamy.ai (nixops.sh --installer).` That is Car 4. The door-moving witness for Step 3.1 waits for the next installer edit that rides a real release; that release stays your call.

**The checklist line** rides as Car 2: item 13, one line, the § key's content in the checklist's voice. Car 3 marks the router's § key landed (SAME-CAR LABEL) and banks this deed's reading on the publish-lane line. The § key's GLOSSARY.md value is still owed; GLOSSARY.md rides next so the dismount can land it.

**Still open, named.** (a) GLOSSARY.md's value for the § key. (b) `nixops` is an alias that cannot take an argument; the function is one flake.nix line, banked as a TODO. (c) The bff `--reason` string still says bjj; flake.nix TODO. (d) The qamy.ai divergence: title, heading, the three walk pages, a trail. (e) The favicon 204 location on both doors (the 2026-09-13 TODO, second witness at qamy.ai). (f) The evaluation-warning half of the configuration.nix TODO. (g) DDNS: mikelev.in has the updater, npvg.org and qamy.ai are static copies of its number; carry-over only. (h) The ambiguous-block rule above, for the dismount to bank.

## Do these in this order

1. Section 1: the probe block, in the terminal whose prompt reads `(nix) pipulate $`. That is the BEFORE reading.
2. Section 3, Car 1 (nixops.sh), then Car 2 (prompt_foo.py), then Car 3 (foo_files.py), each `patch`, `app`, `d`, `m`, in the terminal whose prompt reads `(nix) pipulate $`.
3. Section 3, Car 4: the publish-only lane, same terminal, on a clean tree, before anything else touches the repo.
4. Section 2: context.txt, then compile. That is the AFTER reading.
5. Nothing on the Mac. Nothing on the router.

## 1. PROBES

Machine: the terminal whose prompt reads `(nix) pipulate $`. Run before anything else.

```text
rg -c sync_installer nixops.sh release.py
rg -n '^sync_installer$' nixops.sh
bash -n nixops.sh
rg -c 'THE MACHINE IS NAMED BY ITS PROMPT' prompt_foo.py foo_files.py
sed -n 2p assets/installer/install.sh
curl -fsSL --max-time 10 https://pipulate.com/install.sh | sed -n 2p
curl -fsSL --max-time 10 https://npvg.org | sed -n 2p
curl -fsSL --max-time 10 https://qamy.ai | sed -n 2p
```

Line 1: `release.py:2` and `nixops.sh:3` before; `nixops.sh:4` and `release.py:2` after Car 1 (the fourth is the call). Line 2: nothing before (exit 1; the compile lane prints its no-output receipt); one line after, `58:sync_installer`, the line number by the diff hunk's arithmetic (old 31 → new 58) and the word the reading. Line 3 is a gate: nothing in both worlds, a line number if Car 1 broke the file. Line 4: `foo_files.py:1` before (rg omits a file with no match); `prompt_foo.py:1` and `foo_files.py:1` after Car 2 (Car 3 keeps the router's § key on one line and names the rule nowhere else, so foo_files.py stays at 1). Lines 5 to 8 are a gate: all four read the new title before and after; Car 4's rsync moves nothing because the bodies already agree. If line 8 ever differs from line 5, a door drifted and the truck is the next car.

## 2. NEXT CONTEXT

prompt_foo.py drops: the next compile's own Prompt section is item 13's witness, and apply.py's receipt says if the car was refused. release.py drops: nothing patches it next turn. GLOSSARY.md rides for the § key's value at the dismount; nixops.sh rides so the landed call can be read.

```text
! rg -c sync_installer nixops.sh release.py
! rg -n '^sync_installer$' nixops.sh
! bash -n nixops.sh
! rg -c 'THE MACHINE IS NAMED BY ITS PROMPT' prompt_foo.py foo_files.py
! sed -n 2p assets/installer/install.sh
! curl -fsSL --max-time 10 https://pipulate.com/install.sh | sed -n 2p
! curl -fsSL --max-time 10 https://npvg.org | sed -n 2p
! curl -fsSL --max-time 10 https://qamy.ai | sed -n 2p
nixops.sh
foo_files.py
GLOSSARY.md
```

## 3. PATCHES

Four cars. Car 1 re-anchors the refused block on the qamy.ai tree rsync, which occurs once. Car 2 is a tail-append on item 12's last sentence, so the 1878-character line is never reproduced. Car 3 is two tail-appends in the router. Car 4 is the hand fence.

**Car 1.** Diffstat +1/-1.

```text
Target: nixops.sh
[[[SEARCH]]]
rsync -av remotes/honeybot/www/qamy.ai/ $TARGET:~/www/qamy.ai/
rsync -av assets/installer/install.sh $TARGET:~/www/qamy.ai/install.sh
[[[DIVIDER]]]
rsync -av remotes/honeybot/www/qamy.ai/ $TARGET:~/www/qamy.ai/
sync_installer
[[[REPLACE]]]
```

**Car 2.** Diffstat +1/-0.

```text
Target: prompt_foo.py
[[[SEARCH]]]
An AFTER tap taken without ignition is a stale BEFORE wearing the AFTER's label.
[[[DIVIDER]]]
An AFTER tap taken without ignition is a stale BEFORE wearing the AFTER's label.
13.  **THE MACHINE IS NAMED BY ITS PROMPT:** Every fenced command block is preceded by one line naming the terminal it runs in, by the prompt string the operator sees there (`(nix) pipulate $` on the workbench; the Mac's own user@host prompt on the Mac), never by a nickname ("the desk", "the box", "locally"), and that line is repeated on every block, every turn, never abbreviated after first use. (1) PROBES always run where `compile` runs, because only that machine executes `!` lines; a command for any other machine is a WITNESS, rides in (5) EXTERNAL DELIVERABLES under that machine's own prompt, and comes back as a paste. A step in a web console (a router, a registrar, a vendor's admin) is a numbered list: the address to open, the menu path, each field with the value to type, the Save and Apply clicks, and a picture of what the screen shows when it is done. Convicted 2026-09-29: "run on the desk" sent the operator to both terminals.
[[[REPLACE]]]
```

**Car 3.** Diffstat +2/-2.

```text
Target: foo_files.py
[[[SEARCH]]]
Value: one checklist line in prompt_foo.py (owed; item 12 sits at prompt_foo.py:1878 by deed 1684's receipt) and GLOSSARY.md (owed).
[[[DIVIDER]]]
Value: one checklist line in prompt_foo.py (item 13, LANDED by deed 1685's car; the next compile's own Prompt section is its witness) and GLOSSARY.md (owed).
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
Gate: line 2 of each door reading the file's line 2 after the next release; until that release, ./nixops.sh --installer by hand is the truck.
[[[DIVIDER]]]
Gate: line 2 of each door reading the file's line 2 after the next release; until that release, ./nixops.sh --installer by hand is the truck. READ 2026-09-29 (deed 1685): the hand truck moved both pads, line 2 of the file and of all three doors equal and the qamy.ai round trip (the stamp reversed) hashing to the file at f5debcfc; Step 3.1 landed in release.py (rg 2) and the full sync's own call landed one car late, its block refused as ambiguous because the function body carries the very rsync line the block searched for; deed 1685's Car 4 runs the publish-only lane by hand as Step 3.1's first run, since the doors already agree and only the console line can move.
[[[REPLACE]]]
```

**Car 4, the ignition for last turn's Car 2 (release.py's Step 3.1).** Machine: the terminal whose prompt reads `(nix) pipulate $`. Run right after Car 3's `m`, on a clean tree, before compile: the release lane commits whatever the tree holds, and a compile dirties foo_files.py. Copy the block, paste it, press Enter.

```text
cd "$PIPULATE_ROOT" && pwd && python release.py --skip-version-sync --skip-docs-sync --skip-audit-sync --skip-ai-context-sync --skip-breadcrumb-sync --skip-trifecta-rebuild
```

You will see, in this order (the strings are read off release.py in this payload, not guessed): the rabbit panel; `📋 Current version: 2.70`; three `⏭️  Skipping` lines (version sync, Waxascii, journal index); then `✅ Synced install.sh to npvg.org and qamy.ai (nixops.sh --installer).`; then `⏭️  Skipping AUDIT.md synchronization` and `⏭️  Skipping AI_CONTEXT.md synchronization`; possibly the workspace-tree panel from Step 3.7; `⏭️  Skipping breadcrumb trail synchronization` and `⏭️  Skipping Trifecta derivative rebuilding`; and last, `✅ No changes to commit. Your repository is clean.` The line that matters is the fourth ✅-or-⚠️ line: `⚠️  nixops.sh --installer failed; npvg.org and qamy.ai still serve the previous installer.` means Honeybot did not answer, and nixops.sh's own error prints under it. A `✅ Pushed … to Pipulate.com repo.` line would mean a copy in that checkout had drifted; harmless, and the release's normal behaviour. A commit line instead of "No changes to commit" means the tree was dirty when it ran.

Ignition, stated: Car 1 ignites the next time nixops.sh runs whole (the `--installer` lane is already witnessed; probe lines 1 and 2 witness the landing). Car 2 ignites at the next compile, which reads prompt_foo.py at call time; the AFTER's own Prompt section carries item 13. Car 3 needs none. Car 4 is the ignition for last turn's release.py car; its receipt is the console paste, not a probe.

## 4. PROMPT

```text
Four cars: nixops.sh's full sync now calls sync_installer (the block refused as ambiguous, re-anchored on the qamy.ai tree rsync); prompt_foo.py's checklist item 13, THE MACHINE IS NAMED BY ITS PROMPT; the router's § key marked landed and the publish-lane line's READ; and Car 4, the publish-only release lane run by hand as Step 3.1's first run, its console pasted below. The `!` receipts above are the AFTER from the terminal whose prompt reads `(nix) pipulate $`. Confirm: line 1 reads nixops.sh:4 and release.py:2; line 2 reads one line, sync_installer at the qamy.ai block; line 3 prints nothing; line 4 reads prompt_foo.py:1 and foo_files.py:1; lines 5 to 8 all read the new title; this compile's own Prompt section carries item 13. Read the pasted Car 4 console for the Step 3.1 line. GLOSSARY.md is in this context: name the § key's value as one entry, ready for the dismount. Name anything still open, then stop; the dismount is the next turn. DDNS stays a carry-over earmark only.

[Car 4 console paste]
```

## 5. EXTERNAL DELIVERABLES

None this turn. Nothing on the Mac: the install line works there, and the doors are checked from `(nix) pipulate $` by probe lines 5 to 8. Nothing on the router: both host-override rows carry their www alias, as your table shows.

**MikeLev.in**: 



## THE PROOF STRADDLE

> 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: BEFORE PROBE** (paste in bash): 

```bash
Left-hand causal boundary "blast radius" established. Make 1 change and test.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ rg -c sync_installer nixops.sh release.py
rg -n '^sync_installer$' nixops.sh
bash -n nixops.sh
rg -c 'THE MACHINE IS NAMED BY ITS PROMPT' prompt_foo.py foo_files.py
sed -n 2p assets/installer/install.sh
curl -fsSL --max-time 10 https://pipulate.com/install.sh | sed -n 2p
curl -fsSL --max-time 10 https://npvg.org | sed -n 2p
curl -fsSL --max-time 10 https://qamy.ai | sed -n 2p
release.py:2
nixops.sh:3
foo_files.py:1
# The installer behind pipulate.com, npvg.org and qamy.ai
# The installer behind pipulate.com, npvg.org and qamy.ai
# The installer behind pipulate.com, npvg.org and qamy.ai
# The installer behind pipulate.com, npvg.org and qamy.ai
(nix) pipulate $ 
```

**2: AFTER PROBE** (in context.txt): 

```text
# context.txt: the list of files an AI will read. context opens it, compile builds it.
# Keys: j down, k up; Esc then :q quits, :q! discards, :wq saves and quits.
# One line per thing the AI reads: a file path, or a command after `! `.
# A line that starts with # is a comment: that file is not read.
# To add a line: i starts typing, Esc stops. Absolute paths work from anywhere.
# Web pages, APIs and the connector words: chapter XVIII of foo_files.py.

# --- THE 40K-FOOT VIEW (uncomment on a first turn; comment out on the second) ---
# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- the book's spine, one line per article, newest first
# ~/repos/nixos/autognome.py  # <-- the machine's morning routine (this author's NixOS box only)
# init.lua                    # <-- the editor keys that drive the day
# assets/installer/install.sh # <-- how a stranger's machine gets this workshop
# GLOSSARY.md                 # <-- the terms, defined
# flake.nix                   # <-- the environment, pinned: here is my hardware, here is my state
# prompt_foo.py               # <-- the compiler that builds the payload
# foo_files.py                # <-- the router: which files ride, and this book's outline
# scripts/articles/lsa.py     # <-- the second brain: the article corpus behind `rgx`, `rgxc` and `posts`
# requirements.in             # <-- the Python packages, pinned
# pyproject.toml              # <-- the PyPI package
# __init__.py                 # <-- the version

# --- ACTUATORS (cheap; include when the AI should be able to act, not only read) ---
# cli.py                      # <-- tool calls from the command line
# scripts/xp.py               # <-- turns a pasted reply into the next context
# scripts/ai.py               # <-- a local AI writes the commit messages
# scripts/crawl.py            # <-- crawl a site into the next turn
# scripts/weblogin.py         # <-- warm a login on the persistent browser profile
# scripts/webclip_2_markdown.py  # <-- a web page, clipped, as markdown

# --- RARE ---
# scripts/foo_cartridge.py    # <-- the sealed archive: writer and verifier
# scripts/foo_replay.py       # <-- replay a sealed archive on another machine
# release.py                  # <-- how a release reaches GitHub and PyPI
# imports/voice_synthesis.py  # <-- the voice
# imports/ascii_displays.py   # <-- the ASCII art
# scripts/release/version_sync.py  # <-- version stamping (to be folded into release.py)

# --- THIS DISCUSSION ---
# The files and commands for the work in front of you. Paste the NEXT CONTEXT
# block an AI hands back directly below this line; the AI will correct a guess.

# Context 1
# ! rgxc -t grim,article 10 pfsense mikelev.in security warning

# Context 2
# ! dig +short qamy.ai
# ! dig +short www.qamy.ai
# ! dig +short npvg.org
# ! dig +short qamy.ai @1.1.1.1
# ! curl -ksv --max-time 10 -o /dev/null https://qamy.ai

# Context 3
# ! dig +short qamy.ai
# ! dig +short www.qamy.ai
# ! dig +short npvg.org
# ! dig +short qamy.ai @1.1.1.1
# ! curl -ksv --max-time 10 -o /dev/null https://qamy.ai
# ! rg -l -t py ECHO.INVARIANT
# AGENTS.md
# AUDIT.md
# .agents/skills/journal/references/index.md
# assets/installer/install.sh

# Context 4
# ! dig +short qamy.ai
# ! dig +short www.qamy.ai
# ! dig +short www.npvg.org
# ! bash -n assets/installer/install.sh
# ! curl -fsSL --max-time 10 https://qamy.ai | head -3
# ! rg -n Installer.v release.py scripts/release/version_sync.py
# ! rg -n -t py ECHO.INVARIANT
# assets/installer/install.sh
# AGENTS.md
# flake.nix
# foo_files.py
# release.py
# assets/installer/install.sh
# __init__.py
# scripts/release/version_sync.py
# scripts/articles/common.py
# scripts/articles/generate_ai_context.py
# remotes/honeybot/nixos/configuration.nix
# nixops.sh

# Context 5
# ! sed -n 2p assets/installer/install.sh
# ! curl -fsSL --max-time 10 https://pipulate.com/install.sh | sed -n 2p
# ! curl -fsSL --max-time 10 https://npvg.org | sed -n 2p
# ! curl -fsSL --max-time 10 https://qamy.ai | sed -n 2p
# ! curl -fsSL --max-time 10 https://qamy.ai | sed "s/_tpl_name='qamy'/_tpl_name='__INSTALL_DEFAULT_NAME__'/" | sha256sum
# ! sha256sum assets/installer/install.sh
# ! bash -n nixops.sh
# ! rg -c sync_installer nixops.sh release.py
# nixops.sh
# release.py
# prompt_foo.py
# foo_files.py
 
# Context 6
! rg -c sync_installer nixops.sh release.py
! rg -n '^sync_installer$' nixops.sh
! bash -n nixops.sh
! rg -c 'THE MACHINE IS NAMED BY ITS PROMPT' prompt_foo.py foo_files.py
! sed -n 2p assets/installer/install.sh
! curl -fsSL --max-time 10 https://pipulate.com/install.sh | sed -n 2p
! curl -fsSL --max-time 10 https://npvg.org | sed -n 2p
! curl -fsSL --max-time 10 https://qamy.ai | sed -n 2p
nixops.sh
foo_files.py
GLOSSARY.md
```

**3: PATCHES** (the 1 change): 

```diff
Left-hand causal boundary "blast radius" established. Make 1 change and test.
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 'nixops.sh'.
(nix) pipulate $ d
diff --git a/nixops.sh b/nixops.sh
index ac475da1..286d0215 100755
--- a/nixops.sh
+++ b/nixops.sh
@@ -55,7 +55,7 @@ rsync -av remotes/honeybot/www/npvg.org/ $TARGET:~/www/npvg.org/
 echo "🚀 Syncing qamy.ai door (npvg.org's shape, its own tree)..."
 ssh $TARGET "mkdir -p ~/www/qamy.ai"
 rsync -av remotes/honeybot/www/qamy.ai/ $TARGET:~/www/qamy.ai/
-rsync -av assets/installer/install.sh $TARGET:~/www/qamy.ai/install.sh
+sync_installer
 
 echo "🚀 Syncing NixOS Config..."
 rsync --delete -av remotes/honeybot/nixos/ $TARGET:~/nixos-config-staged/
(nix) pipulate $ m
p📝 Committing: chore: Implement sync_installer script
[main 062d679d] chore: Implement sync_installer script
 1 file changed, 1 insertion(+), 1 deletion(-)
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'prompt_foo.py'.
(nix) pipulate $ d
diff --git a/prompt_foo.py b/prompt_foo.py
index 88835082..d249112a 100644
--- a/prompt_foo.py
+++ b/prompt_foo.py
@@ -1876,6 +1876,7 @@ Before addressing the user's prompt, perform the following verification steps:
 10.  **THE WHOLE-FILE WRITE ESCAPE HATCH:** For a genuine top-to-bottom rewrite of a single file (not a surgical edit), you MAY skip the SEARCH block entirely. Emit a `Target: filename` line, then on the next line a `[[[WRITE_FILE]]]` marker, then the complete new file body, then a `[[[END_WRITE_FILE]]]` marker — all wrapped in a single fenced text block exactly as the SEARCH/REPLACE protocol requires. `apply.py` writes the body verbatim, overwriting the file if it exists or creating it (and any missing parent directories) if it does not, runs the same Python AST safety check before saving, and normalizes a single trailing newline. Use this ONLY when you are replacing essentially the entire file; for every smaller change the SEARCH/REPLACE protocol with its exact-match interlock remains mandatory, because that exact match is what proves the edit is landing in the right place.
 11.  **THE ACTIONABLE RESPONSE CONTRACT (TURN SHAPE / THE PATCH TRAIN):** Every substantive answer must END with a numbered next-actions plan in this exact order: (1) PROBES — ONE paste-ready fenced block of bare, read-only commands; all annotation (what each proves or falsifies, what it gates) lives in prose outside the block, never inline. Probes are read-only: patch application is never a probe. (2) NEXT CONTEXT — the exact context.txt lines and file paths for the next compile; probe echoes are copy-symmetric with (1): identical commands, each adding only the leading "! ". (3) PATCHES — SEARCH/REPLACE blocks ONLY against raw source actually present in this context (mutating shell actuators such as sed belong here, ridden as their own train car — never in PROBES); if no repo patch is needed, state "No repo patches required" explicitly rather than inventing one. (4) PROMPT — the CABOOSE COPY: the prompt.md text for the next turn, in its own fenced block, riding last so it sits under the operator's cursor when the train stops. (5) EXTERNAL DELIVERABLES — artifacts living outside this repo (PageWorkers JavaScript, CMS settings, dashboards), clearly labeled as manual-paste and never wrapped in patch markers. Actuation choreography is the train itself: patch, app, d, m per car; blast (or git push) as the caboose. Analysis that does not close with this plan is an incomplete answer.
 12.  **THE PROBE ECHO INVARIANT (Before/After Symmetry):** Every command recommended in (1) PROBES MUST also appear verbatim as a `!` chisel-strike line in (2) NEXT CONTEXT. The operator's hand-run is the BEFORE reading, taken prior to applying any patch; the identical line baked into context.txt re-executes automatically at the next compile, producing the AFTER reading as a live receipt. One probe, two receipts, straddling the patch — a binary-search causal boundary that removes all probe-before-patch / patch-then-probe ordering ambiguity. A probe too heavy or unbounded to echo into the next compile (see THE PROBE ECONOMY RULE) is too heavy to recommend: cap it first, then echo it. THE STRADDLE BRACKETS EXECUTION, NOT THE COMMIT: if the patched code will not run on its own before the next compile -- a shellHook, a daemon, a cached artifact, anything read once at entry -- then (3) PATCHES MUST close by NAMING the IGNITION (the exact command that makes it run, e.g. `exit` then `nix develop`, or `<F2>` for init.lua) or by stating "no ignition required" because the probe's own command loads the patched file at call time. Ignition is not a fourth beat; it completes PATCH. An AFTER tap taken without ignition is a stale BEFORE wearing the AFTER's label.
+13.  **THE MACHINE IS NAMED BY ITS PROMPT:** Every fenced command block is preceded by one line naming the terminal it runs in, by the prompt string the operator sees there (`(nix) pipulate $` on the workbench; the Mac's own user@host prompt on the Mac), never by a nickname ("the desk", "the box", "locally"), and that line is repeated on every block, every turn, never abbreviated after first use. (1) PROBES always run where `compile` runs, because only that machine executes `!` lines; a command for any other machine is a WITNESS, rides in (5) EXTERNAL DELIVERABLES under that machine's own prompt, and comes back as a paste. A step in a web console (a router, a registrar, a vendor's admin) is a numbered list: the address to open, the menu path, each field with the value to type, the Save and Apply clicks, and a picture of what the screen shows when it is done. Convicted 2026-09-29: "run on the desk" sent the operator to both terminals.
 '''
 
     def _generate_summary_content(self, verified_token_count: int) -> str:
(nix) pipulate $ m
📝 Committing: chore: Add the Machine is Named By Its Prompt documentation
[main 125808bf] chore: Add the Machine is Named By Its Prompt documentation
 1 file changed, 1 insertion(+)
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
(nix) pipulate $ d
diff --git a/foo_files.py b/foo_files.py
index 9f226e33..fd9a1645 100644
--- a/foo_files.py
+++ b/foo_files.py
@@ -1249,7 +1249,7 @@ AI_PHOOEY_CHOP = r"""#
 # THE FIRST-CELL BLAST RADIUS RULE (banked 2026-09-01, two convictions in two consecutive rides). Anything the FIRST executable cell of Onboarding.ipynb touches -- including every module it imports at load time -- prints to a stranger at the one moment they cannot tell noise from failure. Both convictions were trivial in SUBSTANCE and severe in POSITION: a KeyError from a read-before-write, and a SyntaxWarning from an unraw ASCII-art literal. Judge a defect in that blast radius by WHERE it fires, never by how small it is, and hold first-cell code to the standard of a cold install rather than the standard of the maintainer's warm one.
 # THE ACTUATOR'S OWN DIAGNOSTIC RULE (banked 2026-09-01). apply.py's AST airlock compiles candidate content before writing, so it printed `<unknown>:985: SyntaxWarning: invalid escape sequence` on THREE consecutive `app` runs -- correct message, correct line number -- and it read as noise FROM the tool rather than a finding ABOUT the file. The `<unknown>` filename is what disguised it: a diagnostic with no filename reads as the tool complaining about itself. A LINE NUMBER in an actuator's output is a finding about the FILE. Read it, or the instrument you built is reporting to nobody.
 # THE PRE-COMPILE ACTUATOR RULE (banked 2026-09-01). A straddle probe answers honestly only when its two taps land on OPPOSITE sides of the actuator. The `diff -q` sync probe was designed as three states (0 in sync, 1 patched-not- copied, 0 after the cp) and printed 0 in BOTH receipts, because the cp ran before the compile -- the same printout in both worlds, which is the DISCRIMINATION QUESTION failing inside a probe written to answer it. Prefer a probe that reads the PROPERTY at the destination (does the working copy compile?) over one that COMPARES two files; the property probe cannot be defeated by running the actuator early.
-# THE PUBLISH LANE IS NOT THE PUSH: installer edits reach strangers only through each door's own truck; `git push` is not their ignition. TWO DOORS SINCE 2026-09-14: pipulate.com through release.py's sync_install_sh (publish-only lane, proven idempotent 2026-08-30: python release.py --skip-version-sync --skip-docs-sync --skip-audit-sync --skip-ai-context-sync --skip-breadcrumb-sync --skip-trifecta-rebuild) and npvg.org through nixops.sh's rsync to the pad, which needs no nixos-rebuild for a body-only change (two no-op rebuilds witnessed 2026-09-14). release.py prints Pushed for one door and moves one door; the release lane run alone leaves npvg.org stale. Fetch from each door and count. THREE DOORS SINCE 2026-09-29, and the rule convicted a second time the same day: release 2.70 printed Pushed install.sh update to Pipulate.com and the Mac read the old header from npvg.org and qamy.ai. The cure landed at deed 1684: nixops.sh's sync_installer holds the pad list, ./nixops.sh --installer runs it alone, and release.py's Step 3.1 calls it, non-fatal, so one release moves three doors. Gate: line 2 of each door reading the file's line 2 after the next release; until that release, ./nixops.sh --installer by hand is the truck.
+# THE PUBLISH LANE IS NOT THE PUSH: installer edits reach strangers only through each door's own truck; `git push` is not their ignition. TWO DOORS SINCE 2026-09-14: pipulate.com through release.py's sync_install_sh (publish-only lane, proven idempotent 2026-08-30: python release.py --skip-version-sync --skip-docs-sync --skip-audit-sync --skip-ai-context-sync --skip-breadcrumb-sync --skip-trifecta-rebuild) and npvg.org through nixops.sh's rsync to the pad, which needs no nixos-rebuild for a body-only change (two no-op rebuilds witnessed 2026-09-14). release.py prints Pushed for one door and moves one door; the release lane run alone leaves npvg.org stale. Fetch from each door and count. THREE DOORS SINCE 2026-09-29, and the rule convicted a second time the same day: release 2.70 printed Pushed install.sh update to Pipulate.com and the Mac read the old header from npvg.org and qamy.ai. The cure landed at deed 1684: nixops.sh's sync_installer holds the pad list, ./nixops.sh --installer runs it alone, and release.py's Step 3.1 calls it, non-fatal, so one release moves three doors. Gate: line 2 of each door reading the file's line 2 after the next release; until that release, ./nixops.sh --installer by hand is the truck. READ 2026-09-29 (deed 1685): the hand truck moved both pads, line 2 of the file and of all three doors equal and the qamy.ai round trip (the stamp reversed) hashing to the file at f5debcfc; Step 3.1 landed in release.py (rg 2) and the full sync's own call landed one car late, its block refused as ambiguous because the function body carries the very rsync line the block searched for; deed 1685's Car 4 runs the publish-only lane by hand as Step 3.1's first run, since the doors already agree and only the console line can move.
 # A BARE FENCE CANNOT RIDE SEARCH/REPLACE: apply.py strips bare fence lines from the payload before matching; a fence edit rides as a sed car spelled with \x60\x60\x60.
 # ECHO IS NOT PRINTF INSIDE A NIX STRING: bash's builtin echo leaves \n literal and a Nix indented string passes it through. Convicted 2026-08-30: bash -c 'echo "a\nb"' | cat -A -> a\nb$
 # PURITY IS A PROPERTY OF THE EVALUATION: one pure `nix develop` on a platform proves every devShell attribute evaluates pure there. Two Darwin receipts 2026-08-30; --impure is vestigial (earmark).
@@ -1262,7 +1262,7 @@ AI_PHOOEY_CHOP = r"""#
 # THE ENTRY-POINT CHECK (banked 2026-09-03). Every WRITE_FILE of a runnable .py script MUST end with the __main__ guard, and the first probe after ignition MUST be one that can only print if main() actually ran (a target line, a wrote-N line). A script that imports cleanly and exits 0 is indistinguishable from success by exit code alone.
 # THE DATA-FILE RE-RENDER RULE (banked 2026-09-03). When a layout gains a site.data.* lookup, jekyll serve's incremental mode will not re-render existing pages when only the data file changes. Local preview needs `touch _layouts/default.html`; production `jekyll build` needs nothing.
 # § THE OUT-OF-BAND STEP RULE (banked 2026-09-13) -- a hand step the terminal cannot take (a phone, a vendor console, a registrar) rides at the TOP of PROBES under a capitalized label, lettered, with its one address in its own fence and the expected screen stated in words; the compile's ! line reads a tag minted in the caboose and never repeats the visit. Conviction: three cellular witnesses across two articles, all caught because the block could not be scrolled past; a step folded into prose is a step the muscle memory rolls over. Sibling of ONE-FENCE-PER-HAND-STEP.
-# § THE MACHINE IS NAMED BY ITS PROMPT (banked 2026-09-29, convicted at deed 1682: "run on the desk" sent the operator to both terminals) -- head every command block with the prompt string the operator sees in the terminal it runs in ((nix) pipulate $ on the workbench; the Mac's own user@host prompt on the Mac), never a nickname, repeated on every block every turn; PROBES run where compile runs because only that machine executes ! lines, and a command for any other machine is a WITNESS under its own prompt in (5); a web-console step is a numbered list naming the address to open, the menu path, each field with the value to type, the Save and Apply clicks, and a drawing of the screen when done. Value: one checklist line in prompt_foo.py (owed; item 12 sits at prompt_foo.py:1878 by deed 1684's receipt) and GLOSSARY.md (owed). Sibling of THE OUT-OF-BAND STEP RULE and THE FINDING DORY RULE.
+# § THE MACHINE IS NAMED BY ITS PROMPT (banked 2026-09-29, convicted at deed 1682: "run on the desk" sent the operator to both terminals) -- head every command block with the prompt string the operator sees in the terminal it runs in ((nix) pipulate $ on the workbench; the Mac's own user@host prompt on the Mac), never a nickname, repeated on every block every turn; PROBES run where compile runs because only that machine executes ! lines, and a command for any other machine is a WITNESS under its own prompt in (5); a web-console step is a numbered list naming the address to open, the menu path, each field with the value to type, the Save and Apply clicks, and a drawing of the screen when done. Value: one checklist line in prompt_foo.py (item 13, LANDED by deed 1685's car; the next compile's own Prompt section is its witness) and GLOSSARY.md (owed). Sibling of THE OUT-OF-BAND STEP RULE and THE FINDING DORY RULE.
 # § THE PLACEHOLDER THAT RIDES INTO A PROBE (banked 2026-09-13) -- a redaction placeholder quoted from a published article into a paste-ready command dies at argument parsing (curl 49, both lanes, twice) and the real address is not the cure, because the next publish scrubs it and the next model re-quotes it; derive the address, route through a name, or print a verdict token instead of a value, so the probe survives every lane it will be read in. Checklist item 6 was already the rule; the emitter broke it and the instrument caught it. Sibling of NO PLACEHOLDERS IN PASTE-READY LINES.
 # § COUNT THE REPLACE AFTER YOU WRITE IT (banked 2026-09-13) -- a displacement predicted from a draft read +5 and the receipt read +6 because the emitted comment was seventeen lines counted as sixteen; the interlock landed the block exactly and the commit arithmetic convicted the predictor. Predict deltas from the block as emitted, never from the block as imagined. Sibling of DELTA-NOT-ABSOLUTE.
 # THE DIFFSTAT IS NOT THE BLOCK COUNT (banked 2026-09-15). git matches every line a SEARCH and its REPLACE share and reports them unchanged, so a diffstat's insertions and deletions read LOWER than the block lengths by the shared count: blocks of 120 and 151 read +110/-139, twelve bare # separators and carried-over lines matched. Only the net is invariant. Predict the net from the blocks; read insertions and deletions as floors. Sibling of COUNT THE REPLACE AFTER YOU WRITE IT.
(nix) pipulate $ m
📝 Committing: chore: Update documentation for installer publishing process
[main 83843f22] chore: Update documentation for installer publishing process
 1 file changed, 2 insertions(+), 2 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 13, done.
Counting objects: 100% (13/13), done.
Delta compression using up to 48 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 1.92 KiB | 1.92 MiB/s, done.
Total 9 (delta 6), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (6/6), completed with 4 local objects.
To github.com:pipulate/pipulate.git
   96fdeb18..83843f22  main -> main
(nix) pipulate $
```

Ignition: Nice. It was fun watching Fable 5.1 decide how to do this check.

```bash
(nix) pipulate $ g

Left-hand causal boundary "blast radius" established. Make 1 change and test.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ cd "$PIPULATE_ROOT" && pwd && python release.py --skip-version-sync --skip-docs-sync --skip-audit-sync --skip-ai-context-sync --skip-breadcrumb-sync --skip-trifecta-rebuild
/home/mike/repos/pipulate
╭──────────────────────────────────────────── QA My AI: Is what Claude said True? ────────────────────────────────────────────╮
│                                                                                                                             │
│                     ( Do you put your name on it because Claude said so? )                                                  │
│                                           O        /)  __                                                                   │
│ >  Do you think the Aviation industry      o /)\__//  /  \   "What Claude said" is a Cockpit                                │
│ >  vibe-codes without checking for       ___(/_ 0 0  |    |  Voice Recorder (CVR) hear-say of a                             │
│ >  confident hallucinations? So then   *(    ==(_T_)== QA |  subcontractor. This tool is a Flight                           │
│ >  why should you? If anyone relies on   \  )   ""\  |    |  Data Recorder (FDR) for High Reliability                       │
│ >  your work then you should Q/A it.      |__>-\_>_>  \__/   Organizations (HROs). Use when it matters.                     │
│                                                                                                                             │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
📋 Current version: 2.70

⏭️  Skipping version synchronization (--skip-version-sync)

⏭️  Skipping Waxascii release stamping (--skip-docs-sync)

⏭️  Skipping journal index regeneration (--skip-docs-sync)
✅ Synced install.sh to npvg.org and qamy.ai (nixops.sh --installer).

⏭️  Skipping AUDIT.md synchronization (--skip-audit-sync)

⏭️  Skipping AI_CONTEXT.md synchronization (--skip-ai-context-sync)

⏭️  Skipping breadcrumb trail synchronization (--skip-breadcrumb-sync)

⏭️  Skipping Trifecta derivative rebuilding (--skip-trifecta-rebuild)

✅ No changes to commit. Your repository is clean.
💡 Use --force to proceed anyway, or make some changes first.
(nix) pipulate $ 
```

**4: PROMPT**: Four cars: nixops.sh's full sync now calls sync_installer (the block refused as ambiguous, re-anchored on the qamy.ai tree rsync); prompt_foo.py's checklist item 13, THE MACHINE IS NAMED BY ITS PROMPT; the router's § key marked landed and the publish-lane line's READ; and Car 4, the publish-only release lane run by hand as Step 3.1's first run, its console pasted below. The `!` receipts above are the AFTER from the terminal whose prompt reads `(nix) pipulate $`. Confirm: line 1 reads nixops.sh:4 and release.py:2; line 2 reads one line, sync_installer at the qamy.ai block; line 3 prints nothing; line 4 reads prompt_foo.py:1 and foo_files.py:1; lines 5 to 8 all read the new title; this compile's own Prompt section carries item 13. Read the pasted Car 4 console for the Step 3.1 line. GLOSSARY.md is in this context: name the § key's value as one entry, ready for the dismount. Name anything still open, then stop; the dismount is the next turn. DDNS stays a carry-over earmark only.

**5: DELIVERABLE**: Okay, so I test on the Mac and it checks out great.

```zsh
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://qamy.ai | head
#!/usr/bin/env bash
# The installer behind pipulate.com, npvg.org and qamy.ai
# =======================================================
#
# You are reading this because you piped it to cat or less instead of
# bash. That is the first QA step: a Unix pipe can be read before it is
# run, and nothing here runs until you swap cat for bash.
#
# What it does, in order:
#   1. downloads a zip of the repository from github.com;
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://npvg.org | head
#!/usr/bin/env bash
# The installer behind pipulate.com, npvg.org and qamy.ai
# =======================================================
#
# You are reading this because you piped it to cat or less instead of
# bash. That is the first QA step: a Unix pipe can be read before it is
# run, and nothing here runs until you swap cat for bash.
#
# What it does, in order:
#   1. downloads a zip of the repository from github.com;
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://pipulate.com/install.sh | head
#!/usr/bin/env bash
# The installer behind pipulate.com, npvg.org and qamy.ai
# =======================================================
#
# You are reading this because you piped it to cat or less instead of
# bash. That is the first QA step: a Unix pipe can be read before it is
# run, and nothing here runs until you swap cat for bash.
#
# What it does, in order:
#   1. downloads a zip of the repository from github.com;
michaellevin@MichaelMacBook-Pro ~ %
```

So this is all just a massive success. Dismount.

## Sealing the Notary Beat: The Verified Three-Door Release

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

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

```text
Fable 5.1 Thinking

Thought for 4m 5s

Verifying installer references across the codebase's receipts.

Checking outcomes against each forecasted verification step.

Placing a new glossary entry in alphabetical order.

Bank a newly discovered rule with its own entry.

Working out the deed numbering across recent dismount receipts.

Cross-referencing commit messages against their actual changes.

Comparing verify-result codes across curl versions by platform.

Listing dangling items and drafting seed lines for the next ride.

Finalizing seed commands and attesting the compile's byte fingerprint.

Reading the deed identifier off the payload footer.

Confirming a flag's wording changed between recent compiles.

Anchoring new patch insertions across the file's key sections.

Splitting the update into separate commit-sized stories.

Drafting the full glossary entry for the new rule.

Documenting a duplicate-match collision and its ordering fix.

Drafting the router receipt for the third misrouted domain.

Tracing mismatched commit messages against their actual code changes.

Marking an unwitnessed step pending its next confirmation.

Verifying receipt anchors remain unique before inserting new lines.

Reconstructing the deed sequence across successive compiles.

Compiling a fresh list of unresolved follow-up items.

Drafting seed commands and prefacing each command block properly.

Checking commit subjects against router-only patch scope.

Reassessing the verification flag under disabled peer checking.

Assigning glossary entries and patch blocks to their proper commit cars.
```

**Fable 5.1 Extra**: Context verified. Tool calls this turn: none. Every reading below is from this compile's eight receipts, the pasted Car 4 console, the pasted Mac witness, and this compile's own Prompt section.

## 1. VERIFY

The goal at the top: test-install qamy.ai from the Mac, on the LAN, behind pfSense, with no certificate warning. Met, by the Mac's own paste: `SSL certificate verify ok.`, `CN=qamy.ai`, issuer Let's Encrypt, and `curl -fsSL https://qamy.ai | head` printing the installer's new header with no `curl: (60)`. The overflow goals closed in this compile: line 1 `nixops.sh:4` and `release.py:2`; line 2 `58:sync_installer`; line 3 nothing; line 4 `prompt_foo.py:1` and `foo_files.py:1`; lines 5 to 8 all `# The installer behind pipulate.com, npvg.org and qamy.ai`; this compile's own Prompt section carries item 13; the Car 4 console printed `✅ Synced install.sh to npvg.org and qamy.ai (nixops.sh --installer).` then `✅ No changes to commit`, so release.py's Step 3.1 has run once. Every forecast of the last train landed exactly. One ignition never fired: the full `./nixops.sh` has not run since the call at line 58 landed, so that call is witnessed as text and not as an execution; the `--installer` lane it calls is witnessed twice.

## 2. BANK

**Car 1.** The § key's value in GLOSSARY.md, and the key marked landed. Two files, one story.

```text
Target: GLOSSARY.md
[[[SEARCH]]]
- **The MCP Receipt Rule** — *a tool call is a receipt or it is nothing.*
[[[DIVIDER]]]
- **The Machine Is Named by Its Prompt** — *every command block names the
  terminal it runs in by the prompt string the operator sees there.* Banked
  2026-09-29, convicted at deed 1682, checklist item 13 since deed 1685.
  "Run on the desk" sent the operator to both terminals: the Linux
  workbench and the Mac each ran the block, and the reply had to sort two
  pastes it never asked for. A nickname carried over from an earlier
  article is exactly what THE FINDING DORY RULE forbids, a memory the
  reader must hold between lines. THE RULE: head every fenced command
  block with the prompt string, `(nix) pipulate $` on the workbench and
  the Mac's own user@host prompt on the Mac, never "the desk", "the box"
  or "locally", repeated on every block every turn. STRUCTURAL HALF, the
  part that makes it cheap: `!` lines execute only where `compile` runs,
  so (1) PROBES is always the workbench by construction, and a command
  for any other machine is a WITNESS that rides in (5
```

**0. TL;DR**

A newly launched domain, qamy.ai, served from a home-hosted machine behind a pfSense router, raised TLS certificate errors when tested from a laptop on the same LAN. The router's DNS resolver had no local row for the name, so LAN clients resolved it to the public address and were answered by the router's own self-signed admin certificate instead of the server's Let's Encrypt one. The fix was two host-override rows on the router (qamy.ai with a www alias, and a www alias the older npvg.org row lacked), verified by before/after DNS and TLS readings on two machines; no repository change was needed for that half. The same session rewrote the installer's header and messages so they name no download address, and found that a release updated only one of the three sites serving the installer: a shared sync function and a release step now move all three. Two working rules were recorded: instructions name a terminal by its prompt string, never a nickname; and a multi-block patch checks each search anchor against the file its own earlier blocks leave behind.

**1. VERIFY**

Goal at the top of the article: make testing qamy.ai from the Mac, on the LAN behind pfSense, not trigger https warnings. Met, by this compile's receipts and the pastes it carries: lines 5 to 8 read the same title from the file and all three doors; the Mac's flagless `curl -fsSL https://qamy.ai | cat` printed the installer stamped `_tpl_name='qamy'`, its curl 8.7.1 read `SSL certificate verify ok.`, and its last paste read the new header from all three doors. The dig straddle closed on both machines ([REDACTED_IP] -> [REDACTED_IP], the @1.1.1.1 control unchanged). The accreted goals closed too: line 1 reads `nixops.sh:4` and `release.py:2`, line 2 reads `58:sync_installer` exactly as forecast, line 3 is silent, line 4 reads `prompt_foo.py:1` and `foo_files.py:1`, and item 13 rides this payload's own Prompt section. Car 4's console printed `✅ Synced install.sh to npvg.org and qamy.ai (nixops.sh --installer).` then `No changes to commit`.

Ignitions that fired: the hand truck at 1685, the publish-only lane at 1686. Never fired: the full `./nixops.sh` with the call at line 58 (only the `--installer` lane is witnessed), and Step 3.1's door-moving branch: the doors were already equal when it ran, so its only receipt is the console line, a success-only witness until an installer edit rides a real release. One reading moved without a car: the Command line reads `--reason bff:` where the router's TODO said the string still read bjj (THE OPERATOR IS A VARIABLE; banked below).

**2. BANK**

Machine for every car: the terminal whose prompt reads `(nix) pipulate $`; `patch`, `app`, `d`, `m` per car, push by hand after the train.

Car 1, the ledger: the dismount receipt and the bff READ.

```text
Target: foo_files.py
[[[SEARCH]]]
# --- START RECEIPTS (newest first; cap 20 lines; a line pushed past the cap is deleted, never moved -- git and the rolling pin are the archive) ---
[[[DIVIDER]]]
# --- START RECEIPTS (newest first; cap 20 lines; a line pushed past the cap is deleted, never moved -- git and the rolling pin are the archive) ---
# 2026-09-29 dismount THE THIRD DOOR ON THE LAN (deeds 1681 through 1686, 1681 INFERRED as the rgxc compile; commits 579708f7 the installer messaging, 32c6414f the 2.70 bump with PyPI live at 11:06, 08b27a2a the sync_installer lane with its third block refused, 4de14a74 Step 3.1, 96fdeb18 the release pack, 062d679d the call, 125808bf item 13, 83843f22 the READs, and this dismount's three cars; pushed by hand after each train; Fable 5.1 on every turn): the same wrong door a third time, qamy.ai resolving to the public address from inside the house so port 443 answered with pfSense's self-signed certificate and a PHPSESSID cookie while npvg.org read [REDACTED_IP]; the cure two rows on the router by hand (the qamy.ai host override with www as an additional name, and www added to npvg.org, which had none), no repo change, no rebuild, no flush on the Mac. Straddles in band: dig [REDACTED_IP] -> [REDACTED_IP] on both machines with @1.1.1.1 unchanged as the control; the Mac's curl 8.7.1 reading SSL certificate verify ok and curl | cat printing the stamped installer; the messaging car (the v1.1.0 header replaced by the door-free header, three re-run lines naming no door, the deploy key read-only and scoped) with bash -n silent both taps and nothing in release.py or version_sync.py stamping the removed label; release 2.70 moving ONE door of three (THE PUBLISH LANE IS NOT THE PUSH convicted a second time, by | less on all three doors from the Mac), then the hand truck ./nixops.sh --installer moving the other two, line 2 of the file and of all three doors equal, the qamy.ai round trip with its stamp reversed hashing to the file at f5debcfc; sync_installer 3 -> 4 with ^sync_installer$ at 58 exactly; the checklist count 1 -> prompt_foo.py:1 foo_files.py:1 with item 13 riding this compile's own Prompt section; Step 3.1's first run through the publish-only lane printing its success line and No changes to commit, the doors already equal so only the console line could move. Misses, mine: "Run on the desk" sent the operator to both terminals (THE MACHINE IS NAMED BY ITS PROMPT: § key, item 13, GLOSSARY value); block 3 of the nixops car searched for the line block 1 had just written and apply.py refused it as ambiguous (THE CAR MINTS ITS OWN TWIN, banked below); under -k curl's verify number is no verdict and differs by version (Prime's 8.21.0 printed 12 then 14 where the Mac's 8.7.1 printed 20 then ok), so the issuer line and the flagless Mac curl were the witnesses (THE FORECAST NAMED THE PROSE, its second reading). THE OPERATOR IS A VARIABLE: this compile's Command line reads bff: where the TODO said bjj, no car having touched flake.nix. ai.py: 08b27a2a "Sync installer and hooks" for a car that touched no hook and lost its third block; 96fdeb18 "refactor: update installer and deployment process" for a router-only bank (+24/-4); 062d679d "Implement sync_installer script" for a one-line call swap; 579708f7, 4de14a74, 125808bf and 83843f22 roughly true; gemma3's 2.70 subject true. UNWITNESSED: Step 3.1 moving a door, the full ./nixops.sh with the call at 58, the nixops function, the install skill's re-run sentence against the new messaging. Next: THE DIVERGENCE, the door says what it is. This block reads six lines past its cap of 20; the next forget ride fades six.
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
The router's dated bjj lines stay as written. Gate: the Command line reads bff.
[[[DIVIDER]]]
The router's dated bjj lines stay as written. Gate: the Command line reads bff. READ 2026-09-29 (deed 1686): this compile's Command line reads --reason bff: a trusted compile from the workbench, the gate met with no car having touched flake.nix (THE OPERATOR IS A VARIABLE; the hand edit INFERRED, flake.nix not in the payload); the boot_menu.py census for a bjj row stands.
[[[REPLACE]]]
```

Car 2, the twin rule: the § key beside THE SUBSET REPLACE and its value in the glossary.

```text
Target: foo_files.py
[[[SEARCH]]]
Witnessed 2026-09-04: three GOs, the trim firing once and idle twice.
[[[DIVIDER]]]
Witnessed 2026-09-04: three GOs, the trim firing once and idle twice.
# § THE CAR MINTS ITS OWN TWIN (banked 2026-09-29, convicted at deed 1685: block 1 of a car wrote the rsync line block 3 searched for, and apply.py refused block 3 as ambiguous) -- before emitting a car, count each SEARCH's occurrences in the file the blocks ABOVE it will leave, never in the file the payload showed; an insertion that carries a later block's anchor goes last, or the later block anchors on a neighbour the insertion cannot reproduce. Value: GLOSSARY.md. Sibling of THE SUBSET REPLACE, THE EPITAPH COUNTER and SINGLE-CANDIDATE BLINDNESS.
[[[REPLACE]]]

Target: GLOSSARY.md
[[[SEARCH]]]
- **The Case-Blind Witness Corollary** — *a case-sensitive probe against a
[[[DIVIDER]]]
- **The Car Mints Its Own Twin** — *a SEARCH must be unique in the file the
  earlier blocks of the same car leave behind.* Banked 2026-09-29, convicted
  at deed 1685 by apply.py's own interlock. Car 1 of the three-door ride
  carried three blocks against nixops.sh: block 1 wrote a sync_installer
  function whose body carries the rsync line that copies the installer to
  the qamy.ai pad; block 3 searched for exactly that line, to replace the
  bare rsync at the bottom of the full sync with a call. By the time block
  3 ran the file held two copies, apply.py printed "Ambiguous match (found
  2 times)" and wrote nothing, correctly. The emitter had checked every
  SEARCH for uniqueness against the file the PAYLOAD showed and never
  against the file its own earlier blocks would leave. WHAT IT LEFT: the
  --installer lane complete, the full sync moving one pad's installer and
  not the other's for one commit (08b27a2a), and the function's own comment
  ("the full sync calls it below") false until the re-anchored block landed
  one car late (062d679d, anchored on the qamy.ai tree rsync, which occurs
  once). No full sync ran in the window, so no door drifted, and forging
  forward was right: the interlock lands nothing halfway, and the next tap
  named the gap to the line. THE RULE: before emitting a car, walk its
  blocks in order and, for each SEARCH, count its occurrences in the file as
  the blocks ABOVE it will leave that file; a block that inserts a line a
  later block will search for goes LAST, or the later block anchors on a
  neighbour the insertion cannot reproduce. SINGLE-CANDIDATE BLINDNESS from
  the other side: there a selector was never shown a second candidate; here
  the car created the second candidate itself, and the interlock, which is a
  selector, refused. Sibling of THE SUBSET REPLACE (the patch's own text
  already in the file) and THE EPITAPH COUNTER (the patch's own comment as a
  hit): in all three the emitter's own output is what the instrument finds.
- **The Case-Blind Witness Corollary** — *a case-sensitive probe against a
[[[REPLACE]]]
```

Car 3, the machine rule's value: the glossary entry and the key's label.

```text
Target: GLOSSARY.md
[[[SEARCH]]]
- **The MCP Receipt Rule** — *a tool call is a receipt or it is nothing.*
[[[DIVIDER]]]
- **The Machine Is Named by Its Prompt** — *every command block is headed
  by the prompt string of the terminal it runs in.* Banked 2026-09-29,
  convicted at deed 1682; its checklist line landed at deed 1685 as item 13
  of prompt_foo.py's AI Self-Correction Checklist and rode deed 1686's own
  Prompt section as the witness. THE CONVICTION: a reply said "Run on the
  desk, before touching the router", a nickname carried over from the 09-14
  article, and the operator ran the block on the Linux workbench, then
  again on the Mac, and could not tell which one the reply had meant; the
  router step it gated had not been done, so both readings were the BEFORE
  and neither was a miss of the operator's. THE MECHANISM, three parts.
  (1) Every fenced command block is preceded by one line naming its
  terminal by the prompt string the operator sees there, `(nix) pipulate $`
  on the workbench and the Mac's own user@host prompt on the Mac, never a
  nickname ("the desk", "the box", "locally"), repeated on every block of
  every turn and never abbreviated after first use, because the reader is
  Dory (THE FINDING DORY RULE): no memory of the last line. (2) PROBES
  always run where `compile` runs, by construction: only that machine
  executes `!` lines, so a command for any other machine is a WITNESS,
  rides in (5) EXTERNAL DELIVERABLES under that machine's own prompt, comes
  back as a paste, and is never echoed. (3) A step in a web console (a
  router, a registrar, a vendor's admin) is a numbered list in the shape the
  operator drew the same day: the address to open, the menu path, each field
  with the value to type, the Save and Apply clicks, and a picture of what
  the screen shows when it is done; the pfSense Host Overrides table with
  its three rows and two www aliases was the first such picture. RENT THIS
  PAYS: a block with no prompt line above it is a block the reply has not
  decided where to run, and the reader will decide for it. THE LOCATOR IS
  STRUCTURAL, not remembered: item 13 sits beside item 12 (THE PROBE ECHO
  INVARIANT) because the two are one fact seen from two sides, the `!` line
  runs on the compile machine and so does everything in (1). Where it does
  NOT live, ruled the same day: apply.py, which runs no probe and refuses
  comment-only growth (THE ACTUATOR IS NOT THE LEDGER). Sibling of THE
  OUT-OF-BAND STEP RULE (a hand step the terminal cannot take rides at the
  top, lettered) and THE FINDING DORY RULE (walk every hand step from the
  operator's chair); this one names WHICH chair.
- **The MCP Receipt Rule** — *a tool call is a receipt or it is nothing.*
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
Value: one checklist line in prompt_foo.py (item 13, LANDED by deed 1685's car; the next compile's own Prompt section is its witness) and GLOSSARY.md (owed).
[[[DIVIDER]]]
Value: one checklist line in prompt_foo.py (item 13, LANDED by deed 1685's car and WITNESSED riding deed 1686's own Prompt section) and GLOSSARY.md (landed at deed 1686).
[[[REPLACE]]]
```

**3. DANGLING**

- DDNS: mikelev.in has the updater; npvg.org and qamy.ai are static A records of the same number; the unit covering all three is the 2026-09-13 line, carry-over only.
- Step 3.1 has never moved a door; its door-moving witness is the next installer edit that rides a real release.
- The full `./nixops.sh` with `sync_installer` at line 58 has not run whole since the call landed.
- `nixops` is an alias that dies on an argument; the one-line function in flake.nix (TODO of deed 1685).
- bff: the reason string reads bff by hand; the boot_menu.py bjj-row census is unread.
- The install skill's section 3 (`.agents/skills/pipulate/SKILL.md`) still quotes the pipulate.com re-run line the messaging car removed; README and Pipulate.com/install.md unread for "Pipulate Installer".
- The qamy.ai divergence: title, heading, three walk pages, a trail; gate `grep -ci npvg` reading 0.
- The favicon 204 location on both pads (second witness at qamy.ai).
- The configuration.nix evaluation warnings, the surviving half of the split-DNS TODO.
- init.lua's \k NOTARIZE beat still says the deed's name is not in context; the footer names it (the 2026-09-06 TODO).
- The RECEIPTS block reads 26 against its cap of 20; the next forget ride fades six.

**4. SEED**

Paste into context.txt (`context`, at the terminal whose prompt reads `(nix) pipulate $`), for THE DIVERGENCE. The first line prints 0 with exit 1 when the door is clean (grep -c's true zero); the second prints one count per file that still spells the old name.

```text
! curl -sS -A Mozilla --max-time 10 https://qamy.ai/ | grep -ci npvg
! rg -c -i npvg remotes/honeybot/www/qamy.ai
remotes/honeybot/www/qamy.ai/index.html
remotes/honeybot/www/qamy.ai/walk/1/index.html
remotes/honeybot/www/qamy.ai/walk/2/index.html
remotes/honeybot/www/qamy.ai/walk/3/index.html
remotes/honeybot/www/npvg.org/index.html
assets/trails/public_walk.json
nixops.sh
# --- or the flake ride instead: nixops as a function and bff's row, one line each ---
# ! rg -n 'nixops|bjj' flake.nix
# flake.nix
# scripts/boot_menu.py
```

**5. CLOSING**

The ride was supposed to be a quick in-and-out: two rows on a router. It was, for the half that lived on the router. What it found on the way in was that the problem had been solved twice before under two other names, and that the record of those solutions was good enough to reproduce the fix but not good enough to say which terminal to type it in. "Run on the desk" is a perfectly clear sentence to the person who wrote the 09-14 article and an ambiguous one to the person reading it two weeks later, and the second person was the same person. That is the book's standing argument in miniature: the instrument that names the chair costs one line per block, and the alternative costs a whole turn every time the chair is guessed.

The other half was the third door itself. Three addresses serving one file from three web roots is not a mistake, it is what a door is; the mistake was believing a release moved all of them because it printed "Pushed" once. One function now holds the pad list, one step calls it, and the doors are checked the only way a door can be checked, by fetching from each and reading line 2. The installer that comes back through any of them now opens by talking to the person who piped it to cat, which is the deliverable this article was named for: a Unix pipe can be read before it is run, and the QA starts there.

**6. NOTARIZE**

Fingerprint of the bytes sealed by this payload's compile: no wc -c line is emitted (the 2026-09-06 notary TODO stands), so the Summary's own measures stand in: Total Bytes 1,005,313 (UTF-8), Total Chars 998,074, Verified Tokens 258,279; eight live receipts in the Manifest; Coverage 211/277; the RECEIPTS block's newest line dated 2026-09-29 (THE DEED IS NAMED FOR THE DOOR, the line this dismount's Car 1 rides above); Honeybot telemetry fetched 2026-09-29T16:06Z; the Command line `--profile trusted --reason bff: a trusted compile from the workbench`.

Deed, read off the footer beneath this payload: qamy.ai_1686-ffd4bee4.zip. The dismount's own compile, the one that reads the three BANK cars, mints the next; the operator completes it after that compile:

Deed: qamy.ai_NNNN-hhhhhhhh.zip   (ls -t qamy.ai_*.zip | head -1)

**MikeLev.in**: 

```bash
(nix) pipulate $ nixops
🚀 Syncing Hooks...
post-receive                                                                                                                                       100% 3080   589.4KB/s   00:00    
🚀 Syncing Scripts (New Location)...
sending incremental file list
deleting __pycache__/score.cpython-313.pyc
deleting __pycache__/forest.cpython-313.pyc
deleting __pycache__/db.cpython-313.pyc
deleting __pycache__/content_loader.cpython-313.pyc
__pycache__/

sent 703 bytes  received 171 bytes  1,748.00 bytes/sec
total size is 190,587  speedup is 218.06
sending incremental file list

sent 69 bytes  received 12 bytes  162.00 bytes/sec
total size is 140,446  speedup is 1,733.90
🚀 Syncing NPvg pad (one address, two bodies)...
sending incremental file list
./

sent 257 bytes  received 23 bytes  560.00 bytes/sec
total size is 5,440  speedup is 19.43
🚀 Syncing qamy.ai door (npvg.org's shape, its own tree)...
sending incremental file list
./
patch

sent 4,496 bytes  received 42 bytes  9,076.00 bytes/sec
total size is 9,640  speedup is 2.12
🚀 Syncing installer to the home-hosted doors (npvg.org, qamy.ai)...
sending incremental file list

sent 62 bytes  received 12 bytes  148.00 bytes/sec
total size is 18,432  speedup is 249.08
sending incremental file list

sent 62 bytes  received 12 bytes  148.00 bytes/sec
total size is 18,432  speedup is 249.08
🚀 Syncing NixOS Config...
sending incremental file list

sent 118 bytes  received 12 bytes  86.67 bytes/sec
total size is 25,374  speedup is 195.18
✅ Sync Complete.
   To apply NixOS config: ssh -t mike@[REDACTED_IP] 'sudo cp ~/nixos-config-staged/* /etc/nixos/ && sudo nixos-rebuild switch'
(nix) pipulate $ ssh -t mike@[REDACTED_IP] 'sudo cp ~/nixos-config-staged/* /etc/nixos/ && sudo nixos-rebuild switch'
[sudo] password for mike: 
building the system configuration...
evaluation warning: The option `services.logind.lidSwitchExternalPower' defined in `/etc/nixos/configuration.nix' has been renamed to `services.logind.settings.Login.HandleLidSwitchExternalPower'.
evaluation warning: The option `services.logind.lidSwitch' defined in `/etc/nixos/configuration.nix' has been renamed to `services.logind.settings.Login.HandleLidSwitch'.
evaluation warning: The option `services.xserver.displayManager.gdm.wayland' defined in `/etc/nixos/configuration.nix' has been renamed to `services.displayManager.gdm.wayland'.
evaluation warning: The option `services.xserver.displayManager.gdm.enable' defined in `/etc/nixos/configuration.nix' has been renamed to `services.displayManager.gdm.enable'.
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/kljiyqh6nnqjkapz968aiyns002cz1qq-nixos-system-honeybot-26.05pre913595.c6245e83d836
Connection to [REDACTED_IP] closed.
(nix) pipulate $ vim __init__.py 
(nix) pipulate $ release
╭─────────────────────────────────────────────────────────────────────── QA My AI: Is what Claude said True? ───────────────────────────────────────────────────────────────────────╮
│                                                                                                                                                                                   │
│                     ( Do you put your name on it because Claude said so? )                                                                                                        │
│                                           O        /)  __                                                                                                                         │
│ >  Do you think the Aviation industry      o /)\__//  /  \   "What Claude said" is a Cockpit                                                                                      │
│ >  vibe-codes without checking for       ___(/_ 0 0  |    |  Voice Recorder (CVR) hear-say of a                                                                                   │
│ >  confident hallucinations? So then   *(    ==(_T_)== QA |  subcontractor. This tool is a Flight                                                                                 │
│ >  why should you? If anyone relies on   \  )   ""\  |    |  Data Recorder (FDR) for High Reliability                                                                             │
│ >  your work then you should Q/A it.      |__>-\_>_>  \__/   Organizations (HROs). Use when it matters.                                                                           │
│                                                                                                                                                                                   │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
📋 Current version: 2.71
✅ Updated pyproject.toml (version and description)
✅ Synced install.sh to npvg.org and qamy.ai (nixops.sh --installer).
[main 79f1bd8a] fix: update __version__ and __version_description__
✅ Pushed 7cb2f919..79f1bd8a  main -> main
Successfully built pipulate-2.71.tar.gz and pipulate-2.71-py3-none-any.whl
🎉 Published 2.71 -> https://pypi.org/project/pipulate/2.71/

╭────────────────────────────────────────────────────────────────────────── 🎉 Release Pipeline Complete ───────────────────────────────────────────────────────────────────────────╮
│                                                                                                                                                                                   │
│                                                                            🎉 Pipulate Release Summary                                                                            │
│  ╭────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─────────────────────╮  │
│  │ Component                              │ Details                                                                                                      │       Status        │  │
│  ├────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────┼─────────────────────┤  │
│  │ 🤖 gemma3:latest Message               │ fix: update __version__ and __version_description__                                                          │  ✨ gemma3:latest   │  │
│  │                                        │                                                                                                              │                     │  │
│  │                                        │ The __version__ attribute in __init__.py and pyproject.toml has been updated to "2.71" to reflect the latest │                     │  │
│  │                                        │ release. The __version_description__ attribute has been updated to "Unified Installer" to align with new     │                     │  │
│  │                                        │ installer messaging.                                                                                         │                     │  │
│  ├────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────┼─────────────────────┤  │
│  │ 📦 Version                             │ 2.71                                                                                                         │       ✅ Set        │  │
│  ├────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────┼─────────────────────┤  │
│  │ 🚀 PyPI Release                        │ https://pypi.org/project/pipulate/2.71/                                                                      │       ✅ Live       │  │
│  ├────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────┼─────────────────────┤  │
│  │ ⏰ Completed                           │ 2026-09-29 12:18:52                                                                                          │       🎯 Done       │  │
│  ╰────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────┴─────────────────────╯  │
│                                                                                                                                                                                   │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
🔄 server.py touched; the watchdog restarts the server.
(nix) pipulate $ 
```

There, all done! The QA My AI site is born.


---

## Book Analysis

### Ai Editorial Take
The most striking revelation in this piece is the concept of indexical blindness in human-AI collaboration. When an AI operates across a distributed workbench, it frequently resorts to relative spatial terms like 'locally,' 'on the box,' or 'on the desk.' To an LLM lacking a physical body, these words feel concrete; to an operator sitting before multiple terminals with distinct OS environments, they create an immediate cognitive tax. By mandating that 'The Machine Is Named by Its Prompt,' the author forces the model to ground every single command block in the exact prompt string of the target machine (`(nix) pipulate $` versus `user@host %`). Furthermore, the realization that `git push` is not a deployment mechanism reveals the hidden peril of static site projections: when three independent domains serve an installer from three physical roots, the release script must act as an orchestrator of actual files on actual disks, verified by live hash comparisons rather than assumptions.

### 🐦 X.com Promo Tweet
```text
A local SSL certificate error sparked two major workflow upgrades: naming terminal prompts to eliminate ambiguity, and syncing multi-door installer releases with verified receipts.
https://mikelev.in/futureproof/the-three-door-installer-and-named-prompts/
#DevOps #SysAdmin #Unix
```

### Title Brainstorm
* **Title Option:** The Three-Door Installer: Named Prompts, Split DNS, and Verifiable Releases
  * **Filename:** `the-three-door-installer-and-named-prompts.md`
  * **Rationale:** Directly identifies the central technical breakthrough: solving local pfSense DNS hairpins, establishing prompt-based terminal naming, and synchronizing installer delivery across all three web doors.
* **Title Option:** Inspecting the Pipe First: Named Prompts, Multi-Door Sync, and Replayable QA
  * **Filename:** `inspecting-the-pipe-named-prompts-and-three-doors.md`
  * **Rationale:** Focuses on the philosophical core of Unix hygiene—reading a pipe via cat or less before execution—and connecting it to repeatable multi-host deployment.
* **Title Option:** The Machine Is Named by Its Prompt: Killing Ambiguity in Multi-Terminal AI Workflows
  * **Filename:** `the-machine-is-named-by-its-prompt-verifiable-workflows.md`
  * **Rationale:** Highlights the human-AI coordination rule forged during this session, addressing the common failure mode where models give spatial advice without specifying the shell.
* **Title Option:** Beyond the Single Push: Engineering Multi-Door Synchronized Releases in the Age of AI
  * **Filename:** `beyond-the-single-push-multi-door-releases.md`
  * **Rationale:** Emphasizes the operational realization that pushing to git or PyPI is insufficient when edge servers serve independent landing pads requiring coordinated synchronization.

### Content Potential And Polish
- **Core Strengths:**
  - Unflinching before-and-after proof straddles that ground every DNS query, TLS handshake, and hash check in live receipts.
  - A compelling narrative arc converting human friction with an AI model into a rigorous architectural standard (The Machine Is Named by Its Prompt).
  - Deep technical diagnostic integrity, tracing how pfSense webConfigurator certificates hijack internal LAN traffic when split DNS host overrides are missing.
  - Pragmatic Unix philosophy, advocating that installers explicitly welcome users who pipe scripts to cat or less before running bash.
- **Suggestions For Polish:**
  - Include a concise ASCII diagram of the local network topology showing how pfSense routes traffic between the Mac, the Linux workbench, and Honeybot.
  - Add a brief explanatory callout defining why mDNSResponder on macOS caches DNS answers differently than dig, requiring dscacheutil flushing.
  - Consider grouping the commit hashes and patch diffs into collapsible reference blocks to keep the narrative propulsion swift during the patch collision debugging.

### Next Step Prompts
- Write a Python integration test for `release.py` that queries all three domain endpoints (pipulate.com, npvg.org, and qamy.ai) using curl and verifies that their served installer payloads match the local SHA-256 digest after an un-stamped round-trip check.
- Draft an automated audit script to verify pfSense host overrides via the pfSense backup XML or XML-RPC API, ensuring that every hosted domain registered in `foo_files.py` has matching apex and www internal DNS records.
