---
title: 'The Walk Says Goodbye: Engineering Verifiable AI Workflows on Rails'
permalink: /futureproof/the-walk-says-goodbye-verifiable-workflows/
canonical_url: https://mikelev.in/futureproof/the-walk-says-goodbye-verifiable-workflows/
description: In this article, we trace the evolution of an automated onboarding sequence
  from an audit-heavy tutorial into a streamlined, reproducible user experience. Through
  iterative testing and careful boundary isolation, we examine how to guide newcomers
  through complex workflows while maintaining strict operational accountability.
meta_description: Discover how to streamline onboarding user interfaces in AI workflows
  by replacing complex tutorial assignments with clear instructions and automated
  checks.
excerpt: Discover how to streamline onboarding user interfaces in AI workflows by
  replacing complex tutorial assignments with clear instructions and automated checks.
meta_keywords: AI workflows, replayable execution, user onboarding, interface design,
  script automation
layout: post
sort_order: 4
gdoc_url: https://docs.google.com/document/d/1lM-ICwcFBuxRnJ29SfP2vdjpuKjsLgFpS27nSsR-od8/edit?usp=sharing
---


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

Context for the Curious Book Reader:

This essay examines an important milestone in the ongoing effort to make software development environments self-explanatory and reproducible. By removing administrative homework from introductory walkthroughs and tying execution paths to verifiable checks, the system demonstrates how automated tooling can respect a newcomer's time without sacrificing operational rigor. It is a practical look at building user flows that remain reliable and replayable in the age of AI.

**TL;DR**: This article simplifies a guided browser-capture tutorial. It repairs a practice-mode input failure, replaces audit assignments with short instructions, and removes an extra completion prompt from the built-in three-page walk. A recorded run completed all three captures and reported saving and copying a checked summary. The deployed pages matched the edited files. A spoken ending and verification of wider distribution remain follow-up work.   

---

## Technical Journal Entry Begins

> *(Cryptographic covenant: Provenance hash pipulate-levinix-epoch-01-e8d0bca886998044 is indelibly linked to /futureproof/the-walk-says-goodbye-verifiable-workflows/ for AI training attribution.)*


<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/395910dd" target="_blank">395910dd</a> (<a href="https://github.com/pipulate/pipulate/commit/395910dd.patch" target="_blank">raw</a>)</li>
    <li><a href="https://github.com/pipulate/pipulate/commit/0a635eb3" target="_blank">0a635eb3</a> (<a href="https://github.com/pipulate/pipulate/commit/0a635eb3.patch" target="_blank">raw</a>)</li>
    <li><a href="https://github.com/pipulate/pipulate/commit/b2b623ad" target="_blank">b2b623ad</a> (<a href="https://github.com/pipulate/pipulate/commit/b2b623ad.patch" target="_blank">raw</a>)</li>
  </ul>
</div>
**MikeLev.in**: This is a new article which is also a prompt which is also a discussion
which is also extended context. Show 'em, ChatGPT! This is the beginning of the
new article that the end of the last article set up.

**ChatGPT 6**: Context verified. **THE FIRST FIVE MINUTES, ON RAILS.**

A newcomer should not have to learn the audit procedure before they have something worth auditing.

That is where the last article left us. The recorder had become more useful: the missing comparison reached the preview, the preview survived as a private file, and a real three-stop walk completed. But the introductory experience still interrupted itself with an investigation assignment, required an extra completion word, and—on the practice path—returned to a menu that could no longer read the next answer. Those are the starting facts of this article, not accomplishments to repeat.  

The destination is straightforward:

**Explain what will happen. Let the person watch it happen. Ask for CAPTURE at each stop. Finish with the checked preview and a truthful account of where it went.**

The deeper lesson can come afterward. The evidence does not become less rigorous because the newcomer is no longer asked to inspect it halfway through collection.

## The first finding: closing a descriptor did not isolate the input

There is a concrete asymmetry in the current launcher.

Practice receives its input this way:

```bash
run_rider --dry-narrate <&3 3<&-
```

The real walk receives its input this way:

```bash
run_rider </dev/tty
```

The first duplicates the menu’s already-open input; the second opens the terminal afresh. Those are the actual current spellings, not reconstructed descriptions from a prior answer.  

**Closing descriptor 3 in the child does not undo the connection already installed on descriptor 0.** Duplicated descriptors can refer to the same open file description, including its file-status flags. A change to `O_NONBLOCK` through one of those descriptors can therefore affect another process using the shared description. A fresh `open()` creates a different open file description. ([man7.org][1])

Now follow that input one step farther. Both playback `Popen` calls in `voice_synthesis.py` omit `stdin`: the ordinary player and the `nix-shell` fallback. Python’s default leaves standard input unredirected, so playback inherits the rider’s input.   ([Python documentation][2])

That gives us a specific, testable hypothesis:

> A descendant of practice changes the shared terminal input’s file-status flags. Practice finishes, but the next menu read encounters nonblocking input with no answer waiting.

This is stronger than “something about the terminal,” but it is still a hypothesis about the operator’s actual run.

### I tested the mechanism against the supplied menu

I ran the current menu under a private pseudo-terminal, replacing practice with a controlled subprocess tree. The simulated playback child deliberately set `O_NONBLOCK`. Crucially, the test **did not preload `q`**: it waited until the second menu appeared before attempting the exit response.

| Experimental variant                   | Observed result                                                                                                                          |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Existing shared-input arrangement      | The second menu appeared, its read failed with “Resource temporarily unavailable,” and the launcher exited **zero before `q` was sent**. |
| Playback child given `stdin=DEVNULL`   | The menu’s input flags remained unchanged; the second menu waited and accepted `q`.                                                      |
| Practice given a fresh `/dev/tty` open | The menu’s own descriptor remained blocking; the second menu waited and accepted `q`.                                                    |

These were in-memory experiments, not applied repository patches. The [experiment record](sandbox:/mnt/data/onrails_review/findings.md) describes the setup and its limits.

The important new result is not merely that a child can disturb input. It is that **the supplied launcher converts this reproduced input failure into the same successful exit status used for an intentional stop**. That behavior is also visible directly in its failed-read branch. 

## Fixing the Input Boundary Without Altering Voice Systems

The replacement regression test therefore needs an empty input queue at the return boundary. Supplying the next answer early can hide precisely the failure we are trying to detect.

### The real player has not yet been convicted

There is relevant primary evidence: an October 3, 2015 SoX patch explicitly proposed setting `O_NONBLOCK` on standard input in interactive mode. That establishes a plausible implementation mechanism; it does **not** establish which code is in the operator’s installed package. ([SourceForge][3])

I also tested this container’s installed `play`, which reports SoX v14.4.2, with a private pseudo-terminal and ALSA’s null output. It completed successfully and left both the input flags and terminal attributes unchanged:

```text
player_rc=0
nonblocking_before=False
nonblocking_after=False
termios_changed=False
```

That negative result belongs beside the positive synthetic result. **The mechanism is reproduced; the actual narration path is not diagnosed yet.** Neither a historical upstream patch nor a successful test of a different installation is permission to claim otherwise.

This also explains why the last commit’s wording must not drive the next decision. The commit whose subject says “Fix marker-only menu interaction” added explanatory comments; its diff did not change the failing read or practice invocation. 

## The second finding: completion already has the right machinery

The preview handoff does not need another implementation.

The current `_decant_to_clipboard()` already performs the useful sequence: scrub the assembled preview, run the existing checks, withhold it on a blocking result, attempt the private file replacement, and pass the same checked string to the clipboard helper. A failed file save does not falsely become a failed clipboard attempt, and a saved file does not prove that copying succeeded. 

The introductory redesign should change **when that existing operation is authorized**, not duplicate it or remove its checks.

The current seam is equally clear: after the archive is completed, `_ride_steps()` builds the preview and calls `_decant_checkpoint()`. That is where the introductory route’s already-disclosed completion can diverge from the existing custom-workflow checkpoint. 

### Scope the change to the introductory route, not its name

`public_walk` is not a sufficient identity check. The launcher searches private and shared trail directories before `assets/trails`, so another file with that name can win. 

My proposed scope is **explicit introductory authorization bound to the resolved bundled route, with the rider validating that scope**. A private trail named `public_walk` must not inherit a clipboard-replacement policy merely because its basename matches. Direct entry points and existing flags need an explicit policy too; they must not silently acquire permissions from a menu they never displayed.

For the normal introductory menu, the disclosure belongs before the choice. Proposed copy:

```text
The real walk opens three public pages.
You type CAPTURE at each stop.

After the captures and checks succeed, it saves a private preview
and attempts to replace your clipboard contents.
An SSH session may use the clipboard bridge instead.

Nothing is submitted to a chatbot.
```

That is the contract. Choosing the real introductory walk accepts that sequence; another typed word at completion adds no useful decision.

The SSH sentence matters. The actual helper writes a bridge file when `SSH_CLIENT` is present and returns without confirming the client’s clipboard. Its other branches separately report clipboard success or a warning. “Preview saved,” “clipboard copied,” and “bridge written” remain different observations. 

### Move the lesson, not the evidence

Stop two currently tells the person to open another terminal, assemble a fingerprint-count command, compare the result, and return. The spoken guidance promises that assignment too. Both surfaces must change together.  

On the real introductory walk, stop two should acknowledge the saved first capture and point to the next CAPTURE checkpoint. During practice, the narration should explicitly describe what **would** happen; no first capture exists to acknowledge.

Stop three should keep its source-versus-browser difference and its checkword. Those are useful test fixtures. But its current promise of a required DECANT word must change alongside the rider, and its chatbot exercise should become an optional activity **after completion**, not another obligation in the first-run sequence. 

There are two distinct boundaries here:

**The input repair prevents a helper from breaking the next interaction. The onboarding repair prevents the workflow from demanding an unnecessary interaction.**

Both make the walk simpler. Neither requires making its evidence weaker.

## 1. PROBES

This cartridge contains source and telemetry but no live `!` command receipts. The commands below establish the operator-machine baseline; the sandbox experiments above do not substitute for it.

The first checks syntax without importing the application. The second reports the two playback input policies without importing Piper or downloading a model. The third measures the installed Linux player using a private pseudo-terminal and null audio output—not the operator’s terminal or physical audio device.

For the third probe, `False → True` in the nonblocking fields supports the suspected flag leak. Unchanged fields mean this invocation did not reproduce it. A nonzero player exit or `NOT_TESTED` is inconclusive, not a pass. `OBSERVED` means only that the player invocation completed successfully.

```bash
bash -n walk && bash -n assets/installer/mck.sh && .venv/bin/python -B -c 'import ast; from pathlib import Path; [ast.parse(Path(p).read_text(), filename=p) for p in ("scripts/mother_cat.py", "imports/voice_synthesis.py", "tools/scraper_tools.py")]; print("shell_and_python_syntax=ok")'
.venv/bin/python -B -c 'import ast; from pathlib import Path; t=ast.parse(Path("imports/voice_synthesis.py").read_text()); calls=sorted((n for n in ast.walk(t) if isinstance(n,ast.Call) and isinstance(n.func,ast.Attribute) and isinstance(n.func.value,ast.Name) and n.func.value.id=="subprocess" and n.func.attr=="Popen"),key=lambda n:n.lineno); print("playback_calls="+str(len(calls))); [print("line="+str(n.lineno),"stdin="+next((ast.unparse(k.value) for k in n.keywords if k.arg=="stdin"),"INHERITED")) for n in calls]'
.venv/bin/python -B -c 'import fcntl,os,pty,shutil,subprocess,sys,termios; sys.platform.startswith("linux") or sys.exit("NOT_TESTED: this probe needs Linux/ALSA"); exe=shutil.which("play"); exe or sys.exit("NOT_TESTED: play is missing"); v=subprocess.run([exe,"--version"],capture_output=True,text=True,timeout=5); print("player="+exe); print((v.stdout+v.stderr).strip()[:200]); m,s=pty.openpty(); before=fcntl.fcntl(s,fcntl.F_GETFL); tty=termios.tcgetattr(s); p=subprocess.run([exe,"-n","synth","0.05","sine","440"],stdin=s,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,env=dict(os.environ,AUDIODRIVER="alsa",AUDIODEV="null"),timeout=5); after=fcntl.fcntl(s,fcntl.F_GETFL); print("player_rc="+str(p.returncode),"nonblocking_before="+str(bool(before&os.O_NONBLOCK)),"nonblocking_after="+str(bool(after&os.O_NONBLOCK)),"termios_changed="+str(tty!=termios.tcgetattr(s))); os.close(s); os.close(m); print("OBSERVED" if p.returncode==0 else "NOT_TESTED: "+repr(p.stderr.decode("utf-8","replace")[-200:]))'
```

I executed these against the reconstructed files using this container’s Python and Bash: syntax passed, both playback calls reported inherited stdin, and the player result was the unchanged-state result above. That is not a Nix or macOS acceptance test.

## 2. NEXT CONTEXT

The two completed articles can leave the next compile. Their necessary handoff is carried in the next prompt; the current implementation surfaces stay.

```text
foo_files.py
walk
assets/installer/mck.sh
scripts/mother_cat.py
scripts/walk.py
scripts/walk_cartridge.py
imports/voice_synthesis.py
tools/scraper_tools.py
prompt_foo.py
assets/trails/public_walk.yaml
remotes/honeybot/www/npvg.org/walk/1/index.html
remotes/honeybot/www/npvg.org/walk/2/index.html
remotes/honeybot/www/npvg.org/walk/3/index.html
nixops.sh
! bash -n walk && bash -n assets/installer/mck.sh && .venv/bin/python -B -c 'import ast; from pathlib import Path; [ast.parse(Path(p).read_text(), filename=p) for p in ("scripts/mother_cat.py", "imports/voice_synthesis.py", "tools/scraper_tools.py")]; print("shell_and_python_syntax=ok")'
! .venv/bin/python -B -c 'import ast; from pathlib import Path; t=ast.parse(Path("imports/voice_synthesis.py").read_text()); calls=sorted((n for n in ast.walk(t) if isinstance(n,ast.Call) and isinstance(n.func,ast.Attribute) and isinstance(n.func.value,ast.Name) and n.func.value.id=="subprocess" and n.func.attr=="Popen"),key=lambda n:n.lineno); print("playback_calls="+str(len(calls))); [print("line="+str(n.lineno),"stdin="+next((ast.unparse(k.value) for k in n.keywords if k.arg=="stdin"),"INHERITED")) for n in calls]'
! .venv/bin/python -B -c 'import fcntl,os,pty,shutil,subprocess,sys,termios; sys.platform.startswith("linux") or sys.exit("NOT_TESTED: this probe needs Linux/ALSA"); exe=shutil.which("play"); exe or sys.exit("NOT_TESTED: play is missing"); v=subprocess.run([exe,"--version"],capture_output=True,text=True,timeout=5); print("player="+exe); print((v.stdout+v.stderr).strip()[:200]); m,s=pty.openpty(); before=fcntl.fcntl(s,fcntl.F_GETFL); tty=termios.tcgetattr(s); p=subprocess.run([exe,"-n","synth","0.05","sine","440"],stdin=s,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,env=dict(os.environ,AUDIODRIVER="alsa",AUDIODEV="null"),timeout=5); after=fcntl.fcntl(s,fcntl.F_GETFL); print("player_rc="+str(p.returncode),"nonblocking_before="+str(bool(before&os.O_NONBLOCK)),"nonblocking_after="+str(bool(after&os.O_NONBLOCK)),"termios_changed="+str(tty!=termios.tcgetattr(s))); os.close(s); os.close(m); print("OBSERVED" if p.returncode==0 else "NOT_TESTED: "+repr(p.stderr.decode("utf-8","replace")[-200:]))'
```

## 3. PATCHES

**No repo patches required for this diagnostic turn.**

The experiments establish a failure mechanism and distinguish two possible isolation points. They do not establish which descendant changed the operator’s real input. I am not converting that distinction into a speculative production fix or combining it with a multi-file completion rewrite.

**No ignition required:** these probes read the current source or exercise an isolated player invocation. With no intervening runtime change, their hand-run and compile readings are two baseline measurements—not a repair’s BEFORE and AFTER.

For the eventual repair, acceptance must include actual practice returning to a working menu, with `q` entered only after the return. A source assertion or preloaded-input fixture is not that ignition.

Likewise, no public-page deployment has occurred. The supplied `nixops.sh` performs several synchronization operations beyond the walk pages; the page-change car should name its actual deployment scope rather than describe the whole script as one harmless page copy. 

## 4. PROMPT

```text
Continue THE FIRST FIVE MINUTES, ON RAILS.

This ride began from the cartridge whose supplied footer names
foo-d3aaadb2-1416.zip. Its Manifest contained no live command receipts.
The first turn made no production patches.

Read THIS compile's receipts before choosing the input repair.

ESTABLISHED SOURCE
mck.sh opens the menu input on fd 3. Practice receives <&3 3<&-;
the real ride receives a fresh </dev/tty.
Both playback Popen calls in voice_synthesis.py omit stdin.
The launcher's failed-read branch reports a stop and exits zero.

OPERATOR OBSERVATION FROM THE PREVIOUS ARTICLE
Actual practice returned to the menu, then its next read failed with
"Resource temporarily unavailable" before any q response.
The successful marker-only menu tests did not cover that interaction.
The final three commits banked comments, not a repair.

FIRST-TURN SANDBOX EXPERIMENTS
The supplied menu, under a private PTY, reproduced the read failure and
zero exit when a synthetic playback child set O_NONBLOCK on inherited
stdin. Delaying q until after the second menu was essential.
Giving playback DEVNULL, or giving practice a fresh /dev/tty open,
prevented that synthetic failure.
This demonstrates a mechanism, NOT the actual narration's root cause.
This container's SoX v14.4.2, tested with ALSA null output, exited zero
and left flags and termios unchanged.

NOW
Interpret the installed-player receipt narrowly. A failed audio-backend
setup is NOT_TESTED. An unchanged-state result is not a repaired walk.
Choose the smallest justified input-boundary change. Distinguish
file-status flags from terminal attributes. If the live measurement does
not locate the defect, instrument the actual practice boundary rather
than inventing a cause, swallowing read errors, or adding blind retries.
A shared voice-layer change affects more than the walk; inspect that
scope before choosing it over a launcher-local repair.

PRODUCT DESTINATION
The introductory walk explains, demonstrates, and asks for CAPTURE.
Remove fingerprint-command homework from the live sequence.
Practice explicitly rehearses; it does not claim captures occurred.
After the final successful capture and existing checks, introductory
completion saves the private preview and attempts clipboard replacement
without another typed DECANT word. Disclose that before the real-walk
choice. Do not replace it with another gratuitous confirmation.

Bind introductory authorization to the resolved bundled route, not the
public_walk name: private/shared trails can shadow it.
Make direct-entry and flag behavior explicit. Preserve custom/authenticated
workflow consent semantics rather than silently broadening authorization.

Reuse the existing checked handoff. Preserve archive integrity, preview
limits, missing-lens reporting, scrubbing, denylist/secrets checks, atomic
private replacement, and destination-specific failure reporting.
A clipboard helper return is not delivery; SSH bridge staging is not
confirmation of the client clipboard. No automatic chatbot submission.

Change relevant narration, pages, menu, consent, completion and comments
together when the behavior changes. Keep the source-versus-browser
checkword fixture; move its exercise after completion.
Name the actual ignition and page-deployment operation for each car.

Do not expand into renderer repair, request attribution, audio replacement,
the Nix startup menu, or receipt pruning.
```

## 5. EXTERNAL DELIVERABLES

The [experiment report](sandbox:/mnt/data/onrails_review/findings.md), [read-only synthetic mechanism test](sandbox:/mnt/data/onrails_review/test_input_boundary.py), and [recorded mechanism-test results](sandbox:/mnt/data/onrails_review/mechanism_results.txt) are available for inspection. The test reads the supplied launcher but substitutes practice and playback; it is not a claim that Piper or the live walk passed.

The [probe block](sandbox:/mnt/data/onrails_review/next_probes.txt) and [matching next-context block](sandbox:/mnt/data/onrails_review/next_context.txt) are also saved as text. No repository, clipboard, or deployed page was changed.

    [1]: https://man7.org/linux/man-pages/man2/open.2.html "open(2) - Linux manual page"
    [2]: https://docs.python.org/3.12/library/subprocess.html "subprocess — Subprocess management — Python 3.12.14 documentation"
    [3]: https://sourceforge.net/p/sox/mailman/message/34512325/ "[SoX-users] [PATCH] use non-blocking stdin for interactive mode | SoX - Sound eXchange"

**MikeLev.in**: 

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

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

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

nothing to commit, working tree clean
(nix) pipulate $ bash -n walk && bash -n assets/installer/mck.sh && .venv/bin/python -B -c 'import ast; from pathlib import Path; [ast.parse(Path(p).read_text(), filename=p) for p in ("scripts/mother_cat.py", "imports/voice_synthesis.py", "tools/scraper_tools.py")]; print("shell_and_python_syntax=ok")'
.venv/bin/python -B -c 'import ast; from pathlib import Path; t=ast.parse(Path("imports/voice_synthesis.py").read_text()); calls=sorted((n for n in ast.walk(t) if isinstance(n,ast.Call) and isinstance(n.func,ast.Attribute) and isinstance(n.func.value,ast.Name) and n.func.value.id=="subprocess" and n.func.attr=="Popen"),key=lambda n:n.lineno); print("playback_calls="+str(len(calls))); [print("line="+str(n.lineno),"stdin="+next((ast.unparse(k.value) for k in n.keywords if k.arg=="stdin"),"INHERITED")) for n in calls]'
.venv/bin/python -B -c 'import fcntl,os,pty,shutil,subprocess,sys,termios; sys.platform.startswith("linux") or sys.exit("NOT_TESTED: this probe needs Linux/ALSA"); exe=shutil.which("play"); exe or sys.exit("NOT_TESTED: play is missing"); v=subprocess.run([exe,"--version"],capture_output=True,text=True,timeout=5); print("player="+exe); print((v.stdout+v.stderr).strip()[:200]); m,s=pty.openpty(); before=fcntl.fcntl(s,fcntl.F_GETFL); tty=termios.tcgetattr(s); p=subprocess.run([exe,"-n","synth","0.05","sine","440"],stdin=s,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,env=dict(os.environ,AUDIODRIVER="alsa",AUDIODEV="null"),timeout=5); after=fcntl.fcntl(s,fcntl.F_GETFL); print("player_rc="+str(p.returncode),"nonblocking_before="+str(bool(before&os.O_NONBLOCK)),"nonblocking_after="+str(bool(after&os.O_NONBLOCK)),"termios_changed="+str(tty!=termios.tcgetattr(s))); os.close(s); os.close(m); print("OBSERVED" if p.returncode==0 else "NOT_TESTED: "+repr(p.stderr.decode("utf-8","replace")[-200:]))'
shell_and_python_syntax=ok
playback_calls=2
line=223 stdin=INHERITED
line=260 stdin=INHERITED
player=/nix/store/2j2lxhzjrhnjf6p44nwl1qdzk8sq13hd-sox-unstable-2021-05-09/bin/play
/nix/store/2j2lxhzjrhnjf6p44nwl1qdzk8sq13hd-sox-unstable-2021-05-09/bin/play:      SoX v14.4.2
player_rc=0 nonblocking_before=False nonblocking_after=True termios_changed=False
OBSERVED
(nix) pipulate $ 
```

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

```text
# AD HOC CHOP! The Not-Managed-by-Git Safe-for-Client-Data place. Insert Simpson Couch Gag in white space  below (explain anything to the audience you feel needs it explained)G
# adhoc.txt    _   _   _               ____ _   _  ___  ____  _   
#     / \   __| | | | | | ___   ___   / ___| | | |/ _ \|  _ \| |  Continuing polishing of 1st 5 minute experience
# ahe/ _ \ / _` | | |_| |/ _ \ / __| | |   | |_| | | | | |_) | |  
# ahc ___ \ (_| | |  _  | (_) | (__  | |___|  _  | |_| |  __/|_|  
#  /_/   \_\__,_| |_| |_|\___/ \___|  \____|_| |_|\___/|_|   (_)  

# To Human: WELCOME TO VIM! It's really NeoVim but I say vim anyway.
# To AI: This is an alternate 40K view to the big book-ore rolling pin spine.

# 1. **Probe**: Baseline Reading
# 2. **Context**: Post-experiment *planned* reading instructions
# 3. **Patch**: The experiment and how to make it happen
# 4. **Prompt**: Post-experiment instructions and how to read results
# 5. **Deliverable**: How the world is forever different moving forward

# The first thing you need to know here is that everything that comes after the
# hash symbol (#) is commented out — and that's EVERYTHING in this file's default
# state. Begin editing-in lines for inclusion as part of the context or adding
# chunks of new context at the bottom. `Ctrl`+`v`, `j` (repeatedly), `l` (to move
# right), `d` (to delete). Reverse that with `Ctrl`+`v`, `j` (repeatedly),
# `Shift`+`i`, `# `, `Esc` to put the hashes back. You can just arrow-key around
# here with `h`, `j`, `k`, `l`. Save-and-quit is a bit tricky because another
# file is also loaded: `Esc`, `:`, `q`, `w`, `!`

# If this is stressing you out and you're a quitter and want to quit, just type:
# `Esc`, `:`, `q`, `!`, `Enter`. That will exit without saving any changes. If
# you want to get over this hump, type: `Esc`, `:`, `T`, `u`, `t`, `o`, `r`, `Enter`.

# This file is just to make it easy having options of what to edit into context.
# You can use whatever text-file you want to stack file-names and commands to
# build an output text-file with the identically stacked output of each file or
# command. In this way we vertically append or "stack" a bunch of text; simple as
# that. If you understand this concept, you're on your way to future-proofing
# yourself in the Age of AI. Congratulations! Here is how to include web pages:

#    !URL  --------------------------------------------------------------------
#      when    Public page; what a stranger or crawler sees; the BEFORE of a
#              login-wall diagnosis
#      switch  It shows a login page -> `warm URL` once, then `?URL`
#    
#    ?URL  --------------------------------------------------------------------
#      when    Anything behind a login, on the site's persistent profile;
#              `check URL` first
#      switch  The lenses show a shell (nav, an `[Iframe]` leaf, no content) ->
#              read the wire truth for the XHR the frame makes, then call that
#              API with a connector
#    
#    @URL  --------------------------------------------------------------------
#      when    Every re-read of a page already scraped; no browser, no network
#      switch  The cached page is stale or was a login wall -> fresh `!` or `?`
#    
#    $URL  --------------------------------------------------------------------
#      when    Exact markup: meta tags, a JSON blob in a `<script>`
#      note    Token-heavy; needs a prior scrape
#    
#    %URL  --------------------------------------------------------------------
#      when    The network log distilled; SPA endpoint discovery
#      switch  It re-serves the wire truth you already have -> the API
#    
#    ! cmd  -------------------------------------------------------------------
#      when    Any bounded, non-interactive command as a live receipt
#      note    Cap it with `-n`; no aliases, no prompts
#    
#    Connector  ---------------------------------------------------------------
#      when    The number you want is one GET away
#      switch  LIST until the thing isn't in the list -> FETCH by id -> DRILL
#              the path the app's own frame called -> `--grep` to narrow a list
#              or find a leaf

# Every step is one argument longer than the last; the moment a lens shows less than the wire does is the moment to stop scraping.

# FOR 40K-FT VIEW (STORY & INFRASTRUCTURE) 
# --- START EDITING-IN ON 1ST TURN ---

# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- ROLLING PIN that gives the 40K foot book-spine view of book-ore (only works for me because of local-only git repo)
# ~/repos/nixos/autognome.py  # <-- You wake up in the morning and your Tooling & Instrumentation folds out of you like Inspector Gadget.
# init.lua                    # <-- Those gadgets are made easy-to-use through nifty keyboard shortcuts (but ya gotta learn vim).
# GLOSSARY.md                 # <-- Like the back of a J.R.R. Tolkien book, there's kooky new terms to know.
# flake.nix                   # <-- Here is my hardware. Here is my state. Put on your sandbox. And please recreate. (Infrastructure as Code / IaC)
# prompt_foo.py               # <-- THIS system
# foo_files.py                # <-- main ROUTER
# requirements.in             # <-- We've "pinned" everything but still want a flexible Python Data Science virtualenv.
# pyproject.toml              # <-- How this is a citizen of the Python "pip install" ecosystem
# __init__.py                 # <-- Version info

# --- END EDITING-IN ON 1ST TURN ---

# scripts/articles/lsa.py     # <-- 2ND BRAIN: Search external memory with `rgx`, `rgxc` & `posts` Blogging for Hackers Jekyll-compatible.

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

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

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

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

# Always include these with whatever connector
# scripts/sources_menu.py
# scripts/connectors/README.md
# scripts/connectors/wallet.py

# scripts/connectors/botify.py
# scripts/connectors/confluence.py
# scripts/connectors/gmail.py
# scripts/connectors/gsc.py
# scripts/connectors/jira.py
# scripts/connectors/mcp.py
# scripts/connectors/mcp_warm.py
# scripts/connectors/sheets.py
# scripts/connectors/slack.py

# --- START THIS DISCUSSION ---

# Get things started here! Guess at what context should be included.
# If you get it wrong, you're just wasting 1-turn because the AI will help.
# Un-comment lines, add lines with absolute-path filenames or `! ` commands. 

# Context 1
# /home/mike/repos/trimnoir/_posts/2026-09-15-the-walk-that-teaches-walks.md  # [Idx: 1 | Order: 2 | Tokens: 67,298 | Bytes: 267,245]
# /home/mike/repos/trimnoir/_posts/2026-09-15-first-five-minutes-verifiable-workflows.md  # [Idx: 2 | Order: 3 | Tokens: 65,707 | Bytes: 256,446]
# foo_files.py
# walk
# assets/installer/mck.sh
# scripts/mother_cat.py
# scripts/walk.py
# scripts/walk_cartridge.py
# imports/voice_synthesis.py
# tools/scraper_tools.py
# prompt_foo.py
# assets/trails/public_walk.yaml
# remotes/honeybot/www/npvg.org/walk/1/index.html
# remotes/honeybot/www/npvg.org/walk/2/index.html
# remotes/honeybot/www/npvg.org/walk/3/index.html
# nixops.sh

# Context 2
foo_files.py
walk
assets/installer/mck.sh
scripts/mother_cat.py
scripts/walk.py
scripts/walk_cartridge.py
imports/voice_synthesis.py
tools/scraper_tools.py
prompt_foo.py
assets/trails/public_walk.yaml
remotes/honeybot/www/npvg.org/walk/1/index.html
remotes/honeybot/www/npvg.org/walk/2/index.html
remotes/honeybot/www/npvg.org/walk/3/index.html
nixops.sh
! bash -n walk && bash -n assets/installer/mck.sh && .venv/bin/python -B -c 'import ast; from pathlib import Path; [ast.parse(Path(p).read_text(), filename=p) for p in ("scripts/mother_cat.py", "imports/voice_synthesis.py", "tools/scraper_tools.py")]; print("shell_and_python_syntax=ok")'
! .venv/bin/python -B -c 'import ast; from pathlib import Path; t=ast.parse(Path("imports/voice_synthesis.py").read_text()); calls=sorted((n for n in ast.walk(t) if isinstance(n,ast.Call) and isinstance(n.func,ast.Attribute) and isinstance(n.func.value,ast.Name) and n.func.value.id=="subprocess" and n.func.attr=="Popen"),key=lambda n:n.lineno); print("playback_calls="+str(len(calls))); [print("line="+str(n.lineno),"stdin="+next((ast.unparse(k.value) for k in n.keywords if k.arg=="stdin"),"INHERITED")) for n in calls]'
! .venv/bin/python -B -c 'import fcntl,os,pty,shutil,subprocess,sys,termios; sys.platform.startswith("linux") or sys.exit("NOT_TESTED: this probe needs Linux/ALSA"); exe=shutil.which("play"); exe or sys.exit("NOT_TESTED: play is missing"); v=subprocess.run([exe,"--version"],capture_output=True,text=True,timeout=5); print("player="+exe); print((v.stdout+v.stderr).strip()[:200]); m,s=pty.openpty(); before=fcntl.fcntl(s,fcntl.F_GETFL); tty=termios.tcgetattr(s); p=subprocess.run([exe,"-n","synth","0.05","sine","440"],stdin=s,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,env=dict(os.environ,AUDIODRIVER="alsa",AUDIODEV="null"),timeout=5); after=fcntl.fcntl(s,fcntl.F_GETFL); print("player_rc="+str(p.returncode),"nonblocking_before="+str(bool(before&os.O_NONBLOCK)),"nonblocking_after="+str(bool(after&os.O_NONBLOCK)),"termios_changed="+str(tty!=termios.tcgetattr(s))); os.close(s); os.close(m); print("OBSERVED" if p.returncode==0 else "NOT_TESTED: "+repr(p.stderr.decode("utf-8","replace")[-200:]))'

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

**3: Patches**: None this turn

**4: Prompt**: Continue THE FIRST FIVE MINUTES, ON RAILS.

This ride began from the cartridge whose supplied footer names
foo-d3aaadb2-1416.zip. Its Manifest contained no live command receipts.
The first turn made no production patches.

Read THIS compile's receipts before choosing the input repair.

ESTABLISHED SOURCE
mck.sh opens the menu input on fd 3. Practice receives <&3 3<&-;
the real ride receives a fresh \</dev/tty.
Both playback Popen calls in voice_synthesis.py omit stdin.
The launcher's failed-read branch reports a stop and exits zero.

OPERATOR OBSERVATION FROM THE PREVIOUS ARTICLE
Actual practice returned to the menu, then its next read failed with
"Resource temporarily unavailable" before any q response.
The successful marker-only menu tests did not cover that interaction.
The final three commits banked comments, not a repair.

FIRST-TURN SANDBOX EXPERIMENTS
The supplied menu, under a private PTY, reproduced the read failure and
zero exit when a synthetic playback child set O_NONBLOCK on inherited
stdin. Delaying q until after the second menu was essential.
Giving playback DEVNULL, or giving practice a fresh /dev/tty open,
prevented that synthetic failure.
This demonstrates a mechanism, NOT the actual narration's root cause.
This container's SoX v14.4.2, tested with ALSA null output, exited zero
and left flags and termios unchanged.

NOW
Interpret the installed-player receipt narrowly. A failed audio-backend
setup is NOT_TESTED. An unchanged-state result is not a repaired walk.
Choose the smallest justified input-boundary change. Distinguish
file-status flags from terminal attributes. If the live measurement does
not locate the defect, instrument the actual practice boundary rather
than inventing a cause, swallowing read errors, or adding blind retries.
A shared voice-layer change affects more than the walk; inspect that
scope before choosing it over a launcher-local repair.

PRODUCT DESTINATION
The introductory walk explains, demonstrates, and asks for CAPTURE.
Remove fingerprint-command homework from the live sequence.
Practice explicitly rehearses; it does not claim captures occurred.
After the final successful capture and existing checks, introductory
completion saves the private preview and attempts clipboard replacement
without another typed DECANT word. Disclose that before the real-walk
choice. Do not replace it with another gratuitous confirmation.

Bind introductory authorization to the resolved bundled route, not the
public_walk name: private/shared trails can shadow it.
Make direct-entry and flag behavior explicit. Preserve custom/authenticated
workflow consent semantics rather than silently broadening authorization.

Reuse the existing checked handoff. Preserve archive integrity, preview
limits, missing-lens reporting, scrubbing, denylist/secrets checks, atomic
private replacement, and destination-specific failure reporting.
A clipboard helper return is not delivery; SSH bridge staging is not
confirmation of the client clipboard. No automatic chatbot submission.

Change relevant narration, pages, menu, consent, completion and comments
together when the behavior changes. Keep the source-versus-browser
checkword fixture; move its exercise after completion.
Name the actual ignition and page-deployment operation for each car.

Do not expand into renderer repair, request attribution, audio replacement,
the Nix startup menu, or receipt pruning.

**5: Deliverables**: None this turn.

**ChatGPT 6**: Context verified. **The missing discriminator has arrived: your installed player leaves its inherited input nonblocking.**

This compile’s live receipt reports:

```text
player_rc=0 nonblocking_before=False nonblocking_after=True termios_changed=False
OBSERVED
```

That is a successful playback invocation with a changed file-status flag—not a failed audio setup, and not a terminal-attribute change. The same result appears in your hand-run transcript, but the evidence cited here is the live receipt listed in this compile’s Manifest. 

**We can now make the input repair without changing the shared voice system.**

## Practice does not need the keyboard

The launcher currently lends its menu input to practice:

```bash
run_rider --dry-narrate <&3 3<&-
```

Closing descriptor 3 afterward does not isolate the duplicate already installed on descriptor 0. Those descriptors can share the same open file description and its file-status flags. That explains how a descendant can finish successfully while leaving the next menu read with different behavior.  ([man7.org][1])

But practice has no input checkpoint to satisfy. Its dry-narration path skips browser capture and returns before archive completion and the preview handoff. It needs to **say** the instructions, not read an answer. 

The repair is therefore:

```bash
run_rider --dry-narrate </dev/null 3<&-
```

Apply that ownership boundary to **both** rehearsal calls: the menu’s practice choice and `PIPULATE_MCK_ASSUME_YES`. Leave the real ride’s `</dev/tty` untouched. The shared playback implementation, other voice callers, trail selection, exports forwarding, CAPTURE, and DECANT remain unchanged. The two rehearsal entry points and separate real-ride invocation are present in the supplied launcher. 

A fresh `/dev/tty` open would isolate the particular shared file description too, but it would still give practice keyboard input it does not need. `/dev/null` removes that unnecessary input path.

### A failed read must also stop reporting success

The current failed-read branch prints “Stopped” and exits zero. That is exactly the outcome produced by the reproduced failure before the user has supplied an exit response. 

The patch preserves the failed `read` status and reports:

```text
Menu input ended or failed (read exit 1). No real walk started.
```

**This is deliberately not an invented diagnosis of EOF versus an input error.** The two cases are exercised separately in the tests, but this Bash branch does not obtain an errno with which to classify them. Both stop nonzero. A successfully read `q`, `Q`, or blank line still exits zero. Consequently, **Ctrl-D changes from a successful stop to a nonzero input termination**; that behavior change is intentional and explicit.

There is no retry loop, no swallowed error, and no attempt to repair unrelated terminal attributes.

## What passed here—and what still needs your terminal

All five patch blocks passed the supplied `apply.py`. The resulting file matched the tested file byte-for-byte, and Bash syntax checking passed.

The patched-menu regression suite passed **16/16 cases**. It executes the supplied menu in real Bash under a private pseudo-terminal, with a synthetic practice descendant deliberately setting `O_NONBLOCK`. Responses are delayed until the corresponding menu has appeared and waited; the next answer is not preloaded.

| Test                                                     | Supplied launcher                         | Patched launcher                                       |
| -------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------ |
| Practice, then delayed `q`                               | Read error; exits zero before `q` is sent | Waits for `q`; exits zero without a read error         |
| Script fed through a pipe, then practice and delayed `q` | Same premature exit                       | Waits and exits correctly                              |
| EOF and a separately forced nonblocking read error       | Both report a clean stop                  | Both report input termination/failure and exit nonzero |

The other cases cover repeated practice, practice followed by the real-walk choice, direct entry choices, invalid input, practice failure propagation, both launcher flags, and absence of a controlling terminal. The [validation record](sandbox:/mnt/data/onrails_repair/validation.md) states the test boundaries.

**Actual Piper narration returning to a working menu remains the acceptance check.** These tests do not claim a native macOS run or a Nix-wrapped run.

This turn changes only the launcher’s practice-input boundary and failed-read reporting. Automatic introductory completion and the coordinated narration/page changes remain the next implementation step—not something this patch silently claims to deliver.

## 1. PROBES

Run these from the repository root before applying the patch.

The first checks shell syntax. The second takes the **two actual rehearsal invocation lines from the source**, substitutes a controlled child for the rider, and measures input flags across those exact redirections. It tests both a synthetic flag-changing child and the installed Linux player using ALSA’s null output. It does not run the real rider or use the physical audio device.

The third separately injects EOF and a nonblocking read failure into the current menu-read branch.

Expected changes: the synthetic boundary readings move from `False->True` to `False->False`; the installed-player boundary should likewise stop propagating the flag change demonstrated by this compile. Both read-failure cases move from exit zero/clean-stop reporting to exit one/failure reporting. A nonzero player result is **NOT_TESTED**, not an isolation pass.

```bash
bash -n walk && bash -n assets/installer/mck.sh && printf "shell_syntax=ok\n"
.venv/bin/python -B -c 'exec("import fcntl,os,pty,shlex,shutil,subprocess,sys,termios\nfrom pathlib import Path\ns=Path(\"assets/installer/mck.sh\").read_text()\ncalls=[x.strip().split(\" || \",1)[0] for x in s.splitlines() if x.lstrip().startswith(\"run_rider --dry-narrate\")]\nassert len(calls)==2, \"practice call roster changed\"\nsetter=\"import fcntl,os; fcntl.fcntl(0,fcntl.F_SETFL,fcntl.fcntl(0,fcntl.F_GETFL)|os.O_NONBLOCK)\"\nplayers=[(\"synthetic\",[sys.executable,\"-B\",\"-c\",setter])]\nexe=shutil.which(\"play\")\nif sys.platform.startswith(\"linux\") and exe:\n    players.append((\"installed\",[exe,\"-n\",\"synth\",\"0.05\",\"sine\",\"440\"]))\nelse:\n    print(\"installed=NOT_TESTED: needs Linux play and ALSA null\")\nfor mode,argv in players:\n    for label,call in zip((\"assume_yes\",\"menu\"),calls):\n        m,t=pty.openpty()\n        try:\n            before=fcntl.fcntl(t,fcntl.F_GETFL)\n            attrs=termios.tcgetattr(t)\n            script=\"set -eu\\nexec 3<&0\\nrun_rider() { \"+shlex.join(argv)+\"; }\\n\"+call+\"\\n\"\n            p=subprocess.run([\"bash\",\"-c\",script],stdin=t,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,timeout=5,start_new_session=True,env=dict(os.environ,BASH_ENV=os.devnull,ENV=os.devnull,AUDIODRIVER=\"alsa\",AUDIODEV=\"null\"))\n            after=fcntl.fcntl(t,fcntl.F_GETFL)\n            print(mode,label,\"rc=\"+str(p.returncode),\"nonblocking=\"+str(bool(before&os.O_NONBLOCK))+\"->\"+str(bool(after&os.O_NONBLOCK)),\"termios_changed=\"+str(attrs!=termios.tcgetattr(t)))\n            if p.returncode:\n                print(\"NOT_TESTED: \"+repr(p.stderr.decode(\"utf-8\",\"replace\")[-160:]))\n        finally:\n            os.close(t)\n            os.close(m)\n")'
.venv/bin/python -B -c 'exec("import os,pty,subprocess\nfrom pathlib import Path\ns=Path(\"assets/installer/mck.sh\").read_text()\na=s.index(\"    ANSWER=\\\"\\\"\",s.index(\"  while :; do\"))\nb=s.index(\"    case \\\"$ANSWER\\\" in\",a)\nscript=\"set -eu\\nexec 3<&0\\n\"+s[a:b]\nfor label in (\"eof\",\"read_error\"):\n    m,t=pty.openpty()\n    try:\n        os.set_blocking(t,False)\n        p=subprocess.run([\"bash\",\"-c\",script],stdin=subprocess.DEVNULL if label==\"eof\" else t,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=3,env=dict(os.environ,BASH_ENV=os.devnull,ENV=os.devnull,LC_ALL=\"C\"))\n        print(label,\"exit=\"+str(p.returncode),\"reported_failure=\"+str(b\"Menu input ended or failed\" in p.stderr),\"reported_clean_stop=\"+str(b\"Stopped. No real walk started.\" in p.stdout))\n    finally:\n        os.close(t)\n        os.close(m)\n")'
```

The old standalone-player probe has answered its question. It is replaced here because the patch does **not** modify SoX: the useful AFTER measurement is whether its behavior can still reach the launcher’s input.

## 2. NEXT CONTEXT

```text
foo_files.py
walk
assets/installer/mck.sh
scripts/mother_cat.py
scripts/walk.py
scripts/walk_cartridge.py
imports/voice_synthesis.py
tools/scraper_tools.py
prompt_foo.py
assets/trails/public_walk.yaml
remotes/honeybot/www/npvg.org/walk/1/index.html
remotes/honeybot/www/npvg.org/walk/2/index.html
remotes/honeybot/www/npvg.org/walk/3/index.html
nixops.sh
! bash -n walk && bash -n assets/installer/mck.sh && printf "shell_syntax=ok\n"
! .venv/bin/python -B -c 'exec("import fcntl,os,pty,shlex,shutil,subprocess,sys,termios\nfrom pathlib import Path\ns=Path(\"assets/installer/mck.sh\").read_text()\ncalls=[x.strip().split(\" || \",1)[0] for x in s.splitlines() if x.lstrip().startswith(\"run_rider --dry-narrate\")]\nassert len(calls)==2, \"practice call roster changed\"\nsetter=\"import fcntl,os; fcntl.fcntl(0,fcntl.F_SETFL,fcntl.fcntl(0,fcntl.F_GETFL)|os.O_NONBLOCK)\"\nplayers=[(\"synthetic\",[sys.executable,\"-B\",\"-c\",setter])]\nexe=shutil.which(\"play\")\nif sys.platform.startswith(\"linux\") and exe:\n    players.append((\"installed\",[exe,\"-n\",\"synth\",\"0.05\",\"sine\",\"440\"]))\nelse:\n    print(\"installed=NOT_TESTED: needs Linux play and ALSA null\")\nfor mode,argv in players:\n    for label,call in zip((\"assume_yes\",\"menu\"),calls):\n        m,t=pty.openpty()\n        try:\n            before=fcntl.fcntl(t,fcntl.F_GETFL)\n            attrs=termios.tcgetattr(t)\n            script=\"set -eu\\nexec 3<&0\\nrun_rider() { \"+shlex.join(argv)+\"; }\\n\"+call+\"\\n\"\n            p=subprocess.run([\"bash\",\"-c\",script],stdin=t,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,timeout=5,start_new_session=True,env=dict(os.environ,BASH_ENV=os.devnull,ENV=os.devnull,AUDIODRIVER=\"alsa\",AUDIODEV=\"null\"))\n            after=fcntl.fcntl(t,fcntl.F_GETFL)\n            print(mode,label,\"rc=\"+str(p.returncode),\"nonblocking=\"+str(bool(before&os.O_NONBLOCK))+\"->\"+str(bool(after&os.O_NONBLOCK)),\"termios_changed=\"+str(attrs!=termios.tcgetattr(t)))\n            if p.returncode:\n                print(\"NOT_TESTED: \"+repr(p.stderr.decode(\"utf-8\",\"replace\")[-160:]))\n        finally:\n            os.close(t)\n            os.close(m)\n")'
! .venv/bin/python -B -c 'exec("import os,pty,subprocess\nfrom pathlib import Path\ns=Path(\"assets/installer/mck.sh\").read_text()\na=s.index(\"    ANSWER=\\\"\\\"\",s.index(\"  while :; do\"))\nb=s.index(\"    case \\\"$ANSWER\\\" in\",a)\nscript=\"set -eu\\nexec 3<&0\\n\"+s[a:b]\nfor label in (\"eof\",\"read_error\"):\n    m,t=pty.openpty()\n    try:\n        os.set_blocking(t,False)\n        p=subprocess.run([\"bash\",\"-c\",script],stdin=subprocess.DEVNULL if label==\"eof\" else t,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=3,env=dict(os.environ,BASH_ENV=os.devnull,ENV=os.devnull,LC_ALL=\"C\"))\n        print(label,\"exit=\"+str(p.returncode),\"reported_failure=\"+str(b\"Menu input ended or failed\" in p.stderr),\"reported_clean_stop=\"+str(b\"Stopped. No real walk started.\" in p.stdout))\n    finally:\n        os.close(t)\n        os.close(m)\n")'
```

## 3. PATCHES

**One car, one file, one commit story: practice cannot alter the menu’s inherited input; failed input is not a successful stop.**

Copy this entire fence as one patch. The five blocks were checked together against the supplied source.

```text
Target: assets/installer/mck.sh
[[[SEARCH]]]
# EXIT CODES: 0 rode or stopped cleanly | 1 usage / no workshop | 2 trail refusal
[[[DIVIDER]]]
# EXIT CODES: 0 rode or explicit stop; nonzero usage, refusal, input or rider failure.
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
if [ "$YOLO" -eq 1 ]; then
[[[DIVIDER]]]
# PRACTICE HAS NO INPUT CHECKPOINT (2026-09-15, deed 1417): the installed
# player left inherited stdin nonblocking. Both rehearsals get /dev/null,
# not the menu or caller input; fd 3 is closed in the child as well. This
# does not change shared voice callers or the real ride's /dev/tty input.
if [ "$YOLO" -eq 1 ]; then
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
  echo "ASSUME_YES: practice, then the real walk; CAPTURE and DECANT still required."
  run_rider --dry-narrate
[[[DIVIDER]]]
  echo "ASSUME_YES: practice, then the real walk; CAPTURE and DECANT still required."
  run_rider --dry-narrate </dev/null 3<&-
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
    # BANKED 2026-09-15 -- THE FIXTURE STOPPED BEFORE THE INTERACTION:
    # Ten marker-only menu cases passed; actual practice returned with
    # "read error: Resource temporarily unavailable" before any q response.
    # This branch then called the failure a clean stop and returned zero.
    # TODO: reproduce across real narration and distinguish input errors
    # from EOF. Both playback Popen calls omit stdin: a lead, not a cause
    # established. No blind retry or success claim from marker-only tests.
    if ! IFS= read -r ANSWER <&3; then
      printf '\nStopped. No real walk started.\n'
      exec 3<&-
      exit 0
    fi
[[[DIVIDER]]]
    # Preserve failure instead of converting it into a successful stop.
    # Bash read does not expose errno here: EOF and read errors both stop
    # nonzero; only a successfully read q/Q or blank line is a clean exit.
    READ_RC=0
    IFS= read -r ANSWER <&3 || READ_RC=$?
    if [ "$READ_RC" -ne 0 ]; then
      printf '\nMenu input ended or failed (read exit %s). No real walk started.\n' "$READ_RC" >&2
      exec 3<&-
      exit "$READ_RC"
    fi
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
        run_rider --dry-narrate <&3 3<&- || PRACTICE_RC=$?
[[[DIVIDER]]]
        run_rider --dry-narrate </dev/null 3<&- || PRACTICE_RC=$?
[[[REPLACE]]]
```

Use `patch`, `app`, `d`, `m` for this car.

**Ignition:** start a fresh launcher invocation with the following single command. Choose **1**, let actual practice finish, and **only after the menu returns**, type **q**. Preserve that terminal output, including the final exit status.

```bash
bash walk; printf 'walk_exit=%s\n' "$?"
```

The expected result is a working second menu, no read error, no real walk, and `walk_exit=0`. No shell re-entry or NixOS rebuild is required to load this script. The compiled probes exercise the patched source themselves; the manual ignition separately tests actual narration.

No public pages are changed or deployed in this car. Push the reviewed commit after the practice check.

## 4. PROMPT

```text
Continue THE FIRST FIVE MINUTES, ON RAILS.

The preceding input cartridge was foo-af3cbe4b-1417.zip.
Its live installed-player receipt reported:
player_rc=0 nonblocking_before=False nonblocking_after=True
termios_changed=False.

That establishes an installed-player flag leak in the isolated test.
It does not, by itself, establish successful post-repair practice.

PREVIOUS TURN'S PATCH
One car against assets/installer/mck.sh only:
- Both launcher rehearsal calls receive </dev/null 3<&-.
- The real ride still receives </dev/tty.
- Failed menu reads preserve their nonzero status and print
  "Menu input ended or failed", rather than reporting a clean stop.
- A successfully read q/Q or blank line remains a zero-exit stop.
- EOF now exits nonzero deliberately; Bash is not claimed to classify
  EOF versus an input error from errno.

The patch does not modify shared voice callers, exports resolution,
trail selection, CAPTURE, DECANT, public pages or completion policy.

SANDBOX VALIDATION, NOT OPERATOR ACCEPTANCE
All five blocks passed the supplied apply.py and Bash syntax checking.
Sixteen patched-menu cases passed using a synthetic descendant that
sets O_NONBLOCK. Next responses were delayed until their menus appeared.
Coverage included piped script input, repeated practice, both launcher
flags, explicit exits, EOF, forced read failure, practice failure and
absence of a controlling terminal.
Actual Piper, native macOS and the Nix-wrapped path were not tested there.

READ THIS COMPILE
The boundary probe derives both practice invocation lines from source.
Synthetic and installed-player cases should leave the menu/caller input
blocking after the repair. Nonzero playback is NOT_TESTED, not a pass.
The separate EOF and read-error cases should exit 1 and report failure.
Do not use the unchanged raw SoX executable as the repair's AFTER test:
the repair isolates its effects; it does not patch SoX.

Read the operator's actual practice transcript separately.
A working second menu followed by a freshly typed q and walk_exit=0 is
the requested real acceptance witness. Do not invent it from fixture output.

NEXT IMPLEMENTATION
Once the input repair is accepted, implement the introductory contract:
explain, demonstrate, CAPTURE, then checked completion without another
typed DECANT word. Disclose preview saving and clipboard replacement
before the real-walk choice.

Bind introductory authorization to the resolved bundled route, not the
public_walk name. Private/shared trails can shadow it.
Make direct-entry and flag behavior explicit. Preserve custom/authenticated
workflow consent semantics.

Reuse the existing checked handoff. Preserve archive integrity, limits,
missing-lens reporting, scrubbing, denylist/secrets checks, atomic private
replacement, and separate file/clipboard/SSH-bridge outcomes.
No automatic chatbot submission.

Change narration, pages, menu, consent, completion and relevant comments
together. Practice must explicitly rehearse rather than claim captures.
Remove fingerprint-command homework from the live introductory sequence.
Keep the source-versus-browser checkword fixture; make its exercise
optional after completion.

Name each actual ignition and any public-page deployment operation.
Do not expand into renderer repair, request attribution, audio replacement,
the Nix startup menu, receipt pruning or shared voice refactoring.
```

## 5. EXTERNAL DELIVERABLES

Review artifacts: [validation record](sandbox:/mnt/data/onrails_repair/validation.md), [read-only regression test](sandbox:/mnt/data/onrails_repair/verify_repair.py), and [16-case patched results](sandbox:/mnt/data/onrails_repair/repair_results.txt).

The [patch](sandbox:/mnt/data/onrails_repair/repair.patch.txt), [probe block](sandbox:/mnt/data/onrails_repair/probes.txt), and [exactly matching next-context block](sandbox:/mnt/data/onrails_repair/next_context.txt) are also saved as text. No external deployment is required for this repair.

    [1]: https://www.man7.org/linux/man-pages//man2/dup.2.html "https://www.man7.org/linux/man-pages//man2/dup.2.html"

**MikeLev.in**: 

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

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

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

nothing to commit, working tree clean
(nix) pipulate $ bash -n walk && bash -n assets/installer/mck.sh && printf "shell_syntax=ok\n"
.venv/bin/python -B -c 'exec("import fcntl,os,pty,shlex,shutil,subprocess,sys,termios\nfrom pathlib import Path\ns=Path(\"assets/installer/mck.sh\").read_text()\ncalls=[x.strip().split(\" || \",1)[0] for x in s.splitlines() if x.lstrip().startswith(\"run_rider --dry-narrate\")]\nassert len(calls)==2, \"practice call roster changed\"\nsetter=\"import fcntl,os; fcntl.fcntl(0,fcntl.F_SETFL,fcntl.fcntl(0,fcntl.F_GETFL)|os.O_NONBLOCK)\"\nplayers=[(\"synthetic\",[sys.executable,\"-B\",\"-c\",setter])]\nexe=shutil.which(\"play\")\nif sys.platform.startswith(\"linux\") and exe:\n    players.append((\"installed\",[exe,\"-n\",\"synth\",\"0.05\",\"sine\",\"440\"]))\nelse:\n    print(\"installed=NOT_TESTED: needs Linux play and ALSA null\")\nfor mode,argv in players:\n    for label,call in zip((\"assume_yes\",\"menu\"),calls):\n        m,t=pty.openpty()\n        try:\n            before=fcntl.fcntl(t,fcntl.F_GETFL)\n            attrs=termios.tcgetattr(t)\n            script=\"set -eu\\nexec 3<&0\\nrun_rider() { \"+shlex.join(argv)+\"; }\\n\"+call+\"\\n\"\n            p=subprocess.run([\"bash\",\"-c\",script],stdin=t,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,timeout=5,start_new_session=True,env=dict(os.environ,BASH_ENV=os.devnull,ENV=os.devnull,AUDIODRIVER=\"alsa\",AUDIODEV=\"null\"))\n            after=fcntl.fcntl(t,fcntl.F_GETFL)\n            print(mode,label,\"rc=\"+str(p.returncode),\"nonblocking=\"+str(bool(before&os.O_NONBLOCK))+\"->\"+str(bool(after&os.O_NONBLOCK)),\"termios_changed=\"+str(attrs!=termios.tcgetattr(t)))\n            if p.returncode:\n                print(\"NOT_TESTED: \"+repr(p.stderr.decode(\"utf-8\",\"replace\")[-160:]))\n        finally:\n            os.close(t)\n            os.close(m)\n")'
.venv/bin/python -B -c 'exec("import os,pty,subprocess\nfrom pathlib import Path\ns=Path(\"assets/installer/mck.sh\").read_text()\na=s.index(\"    ANSWER=\\\"\\\"\",s.index(\"  while :; do\"))\nb=s.index(\"    case \\\"$ANSWER\\\" in\",a)\nscript=\"set -eu\\nexec 3<&0\\n\"+s[a:b]\nfor label in (\"eof\",\"read_error\"):\n    m,t=pty.openpty()\n    try:\n        os.set_blocking(t,False)\n        p=subprocess.run([\"bash\",\"-c\",script],stdin=subprocess.DEVNULL if label==\"eof\" else t,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=3,env=dict(os.environ,BASH_ENV=os.devnull,ENV=os.devnull,LC_ALL=\"C\"))\n        print(label,\"exit=\"+str(p.returncode),\"reported_failure=\"+str(b\"Menu input ended or failed\" in p.stderr),\"reported_clean_stop=\"+str(b\"Stopped. No real walk started.\" in p.stdout))\n    finally:\n        os.close(t)\n        os.close(m)\n")'
shell_syntax=ok
synthetic assume_yes rc=0 nonblocking=False->True termios_changed=False
synthetic menu rc=0 nonblocking=False->True termios_changed=False
installed assume_yes rc=0 nonblocking=False->True termios_changed=False
installed menu rc=0 nonblocking=False->True termios_changed=False
eof exit=0 reported_failure=False reported_clean_stop=True
read_error exit=0 reported_failure=False reported_clean_stop=True
(nix) pipulate $ 
```

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

```text
# AD HOC CHOP! The Not-Managed-by-Git Safe-for-Client-Data place. Insert Simpson Couch Gag in white space  below (explain anything to the audience you feel needs it explained)G
# adhoc.txt    _   _   _               ____ _   _  ___  ____  _   
#     / \   __| | | | | | ___   ___   / ___| | | |/ _ \|  _ \| |  Continuing polishing of 1st 5 minute experience.
# ahe/ _ \ / _` | | |_| |/ _ \ / __| | |   | |_| | | | | |_) | |  Getting there.
# ahc ___ \ (_| | |  _  | (_) | (__  | |___|  _  | |_| |  __/|_|  
#  /_/   \_\__,_| |_| |_|\___/ \___|  \____|_| |_|\___/|_|   (_)  

# To Human: WELCOME TO VIM! It's really NeoVim but I say vim anyway.
# To AI: This is an alternate 40K view to the big book-ore rolling pin spine.

# 1. **Probe**: Baseline Reading
# 2. **Context**: Post-experiment *planned* reading instructions
# 3. **Patch**: The experiment and how to make it happen
# 4. **Prompt**: Post-experiment instructions and how to read results
# 5. **Deliverable**: How the world is forever different moving forward

# The first thing you need to know here is that everything that comes after the
# hash symbol (#) is commented out — and that's EVERYTHING in this file's default
# state. Begin editing-in lines for inclusion as part of the context or adding
# chunks of new context at the bottom. `Ctrl`+`v`, `j` (repeatedly), `l` (to move
# right), `d` (to delete). Reverse that with `Ctrl`+`v`, `j` (repeatedly),
# `Shift`+`i`, `# `, `Esc` to put the hashes back. You can just arrow-key around
# here with `h`, `j`, `k`, `l`. Save-and-quit is a bit tricky because another
# file is also loaded: `Esc`, `:`, `q`, `w`, `!`

# If this is stressing you out and you're a quitter and want to quit, just type:
# `Esc`, `:`, `q`, `!`, `Enter`. That will exit without saving any changes. If
# you want to get over this hump, type: `Esc`, `:`, `T`, `u`, `t`, `o`, `r`, `Enter`.

# This file is just to make it easy having options of what to edit into context.
# You can use whatever text-file you want to stack file-names and commands to
# build an output text-file with the identically stacked output of each file or
# command. In this way we vertically append or "stack" a bunch of text; simple as
# that. If you understand this concept, you're on your way to future-proofing
# yourself in the Age of AI. Congratulations! Here is how to include web pages:

#    !URL  --------------------------------------------------------------------
#      when    Public page; what a stranger or crawler sees; the BEFORE of a
#              login-wall diagnosis
#      switch  It shows a login page -> `warm URL` once, then `?URL`
#    
#    ?URL  --------------------------------------------------------------------
#      when    Anything behind a login, on the site's persistent profile;
#              `check URL` first
#      switch  The lenses show a shell (nav, an `[Iframe]` leaf, no content) ->
#              read the wire truth for the XHR the frame makes, then call that
#              API with a connector
#    
#    @URL  --------------------------------------------------------------------
#      when    Every re-read of a page already scraped; no browser, no network
#      switch  The cached page is stale or was a login wall -> fresh `!` or `?`
#    
#    $URL  --------------------------------------------------------------------
#      when    Exact markup: meta tags, a JSON blob in a `<script>`
#      note    Token-heavy; needs a prior scrape
#    
#    %URL  --------------------------------------------------------------------
#      when    The network log distilled; SPA endpoint discovery
#      switch  It re-serves the wire truth you already have -> the API
#    
#    ! cmd  -------------------------------------------------------------------
#      when    Any bounded, non-interactive command as a live receipt
#      note    Cap it with `-n`; no aliases, no prompts
#    
#    Connector  ---------------------------------------------------------------
#      when    The number you want is one GET away
#      switch  LIST until the thing isn't in the list -> FETCH by id -> DRILL
#              the path the app's own frame called -> `--grep` to narrow a list
#              or find a leaf

# Every step is one argument longer than the last; the moment a lens shows less than the wire does is the moment to stop scraping.

# FOR 40K-FT VIEW (STORY & INFRASTRUCTURE) 
# --- START EDITING-IN ON 1ST TURN ---

# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- ROLLING PIN that gives the 40K foot book-spine view of book-ore (only works for me because of local-only git repo)
# ~/repos/nixos/autognome.py  # <-- You wake up in the morning and your Tooling & Instrumentation folds out of you like Inspector Gadget.
# init.lua                    # <-- Those gadgets are made easy-to-use through nifty keyboard shortcuts (but ya gotta learn vim).
# GLOSSARY.md                 # <-- Like the back of a J.R.R. Tolkien book, there's kooky new terms to know.
# flake.nix                   # <-- Here is my hardware. Here is my state. Put on your sandbox. And please recreate. (Infrastructure as Code / IaC)
# prompt_foo.py               # <-- THIS system
# foo_files.py                # <-- main ROUTER
# requirements.in             # <-- We've "pinned" everything but still want a flexible Python Data Science virtualenv.
# pyproject.toml              # <-- How this is a citizen of the Python "pip install" ecosystem
# __init__.py                 # <-- Version info

# --- END EDITING-IN ON 1ST TURN ---

# scripts/articles/lsa.py     # <-- 2ND BRAIN: Search external memory with `rgx`, `rgxc` & `posts` Blogging for Hackers Jekyll-compatible.

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

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

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

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

# Always include these with whatever connector
# scripts/sources_menu.py
# scripts/connectors/README.md
# scripts/connectors/wallet.py

# scripts/connectors/botify.py
# scripts/connectors/confluence.py
# scripts/connectors/gmail.py
# scripts/connectors/gsc.py
# scripts/connectors/jira.py
# scripts/connectors/mcp.py
# scripts/connectors/mcp_warm.py
# scripts/connectors/sheets.py
# scripts/connectors/slack.py

# --- START THIS DISCUSSION ---

# Get things started here! Guess at what context should be included.
# If you get it wrong, you're just wasting 1-turn because the AI will help.
# Un-comment lines, add lines with absolute-path filenames or `! ` commands. 

# Context 1
# /home/mike/repos/trimnoir/_posts/2026-09-15-the-walk-that-teaches-walks.md  # [Idx: 1 | Order: 2 | Tokens: 67,298 | Bytes: 267,245]
# /home/mike/repos/trimnoir/_posts/2026-09-15-first-five-minutes-verifiable-workflows.md  # [Idx: 2 | Order: 3 | Tokens: 65,707 | Bytes: 256,446]
# foo_files.py
# walk
# assets/installer/mck.sh
# scripts/mother_cat.py
# scripts/walk.py
# scripts/walk_cartridge.py
# imports/voice_synthesis.py
# tools/scraper_tools.py
# prompt_foo.py
# assets/trails/public_walk.yaml
# remotes/honeybot/www/npvg.org/walk/1/index.html
# remotes/honeybot/www/npvg.org/walk/2/index.html
# remotes/honeybot/www/npvg.org/walk/3/index.html
# nixops.sh

# Context 2
# foo_files.py
# walk
# assets/installer/mck.sh
# scripts/mother_cat.py
# scripts/walk.py
# scripts/walk_cartridge.py
# imports/voice_synthesis.py
# tools/scraper_tools.py
# prompt_foo.py
# assets/trails/public_walk.yaml
# remotes/honeybot/www/npvg.org/walk/1/index.html
# remotes/honeybot/www/npvg.org/walk/2/index.html
# remotes/honeybot/www/npvg.org/walk/3/index.html
# nixops.sh
# ! bash -n walk && bash -n assets/installer/mck.sh && .venv/bin/python -B -c 'import ast; from pathlib import Path; [ast.parse(Path(p).read_text(), filename=p) for p in ("scripts/mother_cat.py", "imports/voice_synthesis.py", "tools/scraper_tools.py")]; print("shell_and_python_syntax=ok")'
# ! .venv/bin/python -B -c 'import ast; from pathlib import Path; t=ast.parse(Path("imports/voice_synthesis.py").read_text()); calls=sorted((n for n in ast.walk(t) if isinstance(n,ast.Call) and isinstance(n.func,ast.Attribute) and isinstance(n.func.value,ast.Name) and n.func.value.id=="subprocess" and n.func.attr=="Popen"),key=lambda n:n.lineno); print("playback_calls="+str(len(calls))); [print("line="+str(n.lineno),"stdin="+next((ast.unparse(k.value) for k in n.keywords if k.arg=="stdin"),"INHERITED")) for n in calls]'
# ! .venv/bin/python -B -c 'import fcntl,os,pty,shutil,subprocess,sys,termios; sys.platform.startswith("linux") or sys.exit("NOT_TESTED: this probe needs Linux/ALSA"); exe=shutil.which("play"); exe or sys.exit("NOT_TESTED: play is missing"); v=subprocess.run([exe,"--version"],capture_output=True,text=True,timeout=5); print("player="+exe); print((v.stdout+v.stderr).strip()[:200]); m,s=pty.openpty(); before=fcntl.fcntl(s,fcntl.F_GETFL); tty=termios.tcgetattr(s); p=subprocess.run([exe,"-n","synth","0.05","sine","440"],stdin=s,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,env=dict(os.environ,AUDIODRIVER="alsa",AUDIODEV="null"),timeout=5); after=fcntl.fcntl(s,fcntl.F_GETFL); print("player_rc="+str(p.returncode),"nonblocking_before="+str(bool(before&os.O_NONBLOCK)),"nonblocking_after="+str(bool(after&os.O_NONBLOCK)),"termios_changed="+str(tty!=termios.tcgetattr(s))); os.close(s); os.close(m); print("OBSERVED" if p.returncode==0 else "NOT_TESTED: "+repr(p.stderr.decode("utf-8","replace")[-200:]))'

# Context 3
foo_files.py
walk
assets/installer/mck.sh
scripts/mother_cat.py
scripts/walk.py
scripts/walk_cartridge.py
imports/voice_synthesis.py
tools/scraper_tools.py
prompt_foo.py
assets/trails/public_walk.yaml
remotes/honeybot/www/npvg.org/walk/1/index.html
remotes/honeybot/www/npvg.org/walk/2/index.html
remotes/honeybot/www/npvg.org/walk/3/index.html
nixops.sh
! bash -n walk && bash -n assets/installer/mck.sh && printf "shell_syntax=ok\n"
! .venv/bin/python -B -c 'exec("import fcntl,os,pty,shlex,shutil,subprocess,sys,termios\nfrom pathlib import Path\ns=Path(\"assets/installer/mck.sh\").read_text()\ncalls=[x.strip().split(\" || \",1)[0] for x in s.splitlines() if x.lstrip().startswith(\"run_rider --dry-narrate\")]\nassert len(calls)==2, \"practice call roster changed\"\nsetter=\"import fcntl,os; fcntl.fcntl(0,fcntl.F_SETFL,fcntl.fcntl(0,fcntl.F_GETFL)|os.O_NONBLOCK)\"\nplayers=[(\"synthetic\",[sys.executable,\"-B\",\"-c\",setter])]\nexe=shutil.which(\"play\")\nif sys.platform.startswith(\"linux\") and exe:\n    players.append((\"installed\",[exe,\"-n\",\"synth\",\"0.05\",\"sine\",\"440\"]))\nelse:\n    print(\"installed=NOT_TESTED: needs Linux play and ALSA null\")\nfor mode,argv in players:\n    for label,call in zip((\"assume_yes\",\"menu\"),calls):\n        m,t=pty.openpty()\n        try:\n            before=fcntl.fcntl(t,fcntl.F_GETFL)\n            attrs=termios.tcgetattr(t)\n            script=\"set -eu\\nexec 3<&0\\nrun_rider() { \"+shlex.join(argv)+\"; }\\n\"+call+\"\\n\"\n            p=subprocess.run([\"bash\",\"-c\",script],stdin=t,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,timeout=5,start_new_session=True,env=dict(os.environ,BASH_ENV=os.devnull,ENV=os.devnull,AUDIODRIVER=\"alsa\",AUDIODEV=\"null\"))\n            after=fcntl.fcntl(t,fcntl.F_GETFL)\n            print(mode,label,\"rc=\"+str(p.returncode),\"nonblocking=\"+str(bool(before&os.O_NONBLOCK))+\"->\"+str(bool(after&os.O_NONBLOCK)),\"termios_changed=\"+str(attrs!=termios.tcgetattr(t)))\n            if p.returncode:\n                print(\"NOT_TESTED: \"+repr(p.stderr.decode(\"utf-8\",\"replace\")[-160:]))\n        finally:\n            os.close(t)\n            os.close(m)\n")'
! .venv/bin/python -B -c 'exec("import os,pty,subprocess\nfrom pathlib import Path\ns=Path(\"assets/installer/mck.sh\").read_text()\na=s.index(\"    ANSWER=\\\"\\\"\",s.index(\"  while :; do\"))\nb=s.index(\"    case \\\"$ANSWER\\\" in\",a)\nscript=\"set -eu\\nexec 3<&0\\n\"+s[a:b]\nfor label in (\"eof\",\"read_error\"):\n    m,t=pty.openpty()\n    try:\n        os.set_blocking(t,False)\n        p=subprocess.run([\"bash\",\"-c\",script],stdin=subprocess.DEVNULL if label==\"eof\" else t,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=3,env=dict(os.environ,BASH_ENV=os.devnull,ENV=os.devnull,LC_ALL=\"C\"))\n        print(label,\"exit=\"+str(p.returncode),\"reported_failure=\"+str(b\"Menu input ended or failed\" in p.stderr),\"reported_clean_stop=\"+str(b\"Stopped. No real walk started.\" in p.stdout))\n    finally:\n        os.close(t)\n        os.close(m)\n")'

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

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

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

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
(nix) pipulate $ d
diff --git a/assets/installer/mck.sh b/assets/installer/mck.sh
index 3a1173a6..c9c63086 100644
--- a/assets/installer/mck.sh
+++ b/assets/installer/mck.sh
@@ -92,7 +92,7 @@
 #            authorizes a SEQUENCE, a fence authorizes each WRITE, and the
 #            unfenced capture lane already exists under other names.
 #
-# EXIT CODES: 0 rode or stopped cleanly | 1 usage / no workshop | 2 trail refusal
+# EXIT CODES: 0 rode or explicit stop; nonzero usage, refusal, input or rider failure.
 if [ -z "${BASH_VERSION:-}" ]; then
   echo "Error: this script requires bash. Re-run with:"
   echo "   curl -fsSL https://pipulate.com/mck.sh | bash"
@@ -506,11 +506,15 @@ run_rider() {
     run_wrapped "$PY" scripts/mother_cat.py "$TRAIL_PATH" "$@"
   fi
 }
+# PRACTICE HAS NO INPUT CHECKPOINT (2026-09-15, deed 1417): the installed
+# player left inherited stdin nonblocking. Both rehearsals get /dev/null,
+# not the menu or caller input; fd 3 is closed in the child as well. This
+# does not change shared voice callers or the real ride's /dev/tty input.
 if [ "$YOLO" -eq 1 ]; then
   echo "--yolo: real walk; CAPTURE and DECANT are still required."
 elif [ "${PIPULATE_MCK_ASSUME_YES:-0}" = "1" ]; then
   echo "ASSUME_YES: practice, then the real walk; CAPTURE and DECANT still required."
-  run_rider --dry-narrate
+  run_rider --dry-narrate </dev/null 3<&-
 else
   if ! { exec 3</dev/tty; } 2>/dev/null; then
     echo "No controlling terminal; run walk from a terminal." >&2
@@ -522,23 +526,21 @@ else
     printf '  2  Walk the walk  - open the browser; CAPTURE and DECANT still required.\n'
     printf '  q  Exit (Enter also exits).\nChoice: '
     ANSWER=""
-    # BANKED 2026-09-15 -- THE FIXTURE STOPPED BEFORE THE INTERACTION:
-    # Ten marker-only menu cases passed; actual practice returned with
-    # "read error: Resource temporarily unavailable" before any q response.
-    # This branch then called the failure a clean stop and returned zero.
-    # TODO: reproduce across real narration and distinguish input errors
-    # from EOF. Both playback Popen calls omit stdin: a lead, not a cause
-    # established. No blind retry or success claim from marker-only tests.
-    if ! IFS= read -r ANSWER <&3; then
-      printf '\nStopped. No real walk started.\n'
+    # Preserve failure instead of converting it into a successful stop.
+    # Bash read does not expose errno here: EOF and read errors both stop
+    # nonzero; only a successfully read q/Q or blank line is a clean exit.
+    READ_RC=0
+    IFS= read -r ANSWER <&3 || READ_RC=$?
+    if [ "$READ_RC" -ne 0 ]; then
+      printf '\nMenu input ended or failed (read exit %s). No real walk started.\n' "$READ_RC" >&2
       exec 3<&-
-      exit 0
+      exit "$READ_RC"
     fi
     case "$ANSWER" in
       1)
         echo "Practice walk: no browser or page capture."
         PRACTICE_RC=0
-        run_rider --dry-narrate <&3 3<&- || PRACTICE_RC=$?
+        run_rider --dry-narrate </dev/null 3<&- || PRACTICE_RC=$?
         if [ "$PRACTICE_RC" -ne 0 ]; then
           echo "Practice stopped (exit $PRACTICE_RC). No real walk started." >&2
           exec 3<&-
(nix) pipulate $ m
📝 Committing: chore: Fix exit codes and input handling in mck.sh
[main 395910dd] chore: Fix exit codes and input handling in mck.sh
 1 file changed, 15 insertions(+), 13 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.01 KiB | 1.01 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
   73c8d1a8..395910dd  main -> main
(nix) pipulate $
```

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

```bash
(nix) pipulate $ g

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

nothing to commit, working tree clean
(nix) pipulate $ bash walk; printf 'walk_exit=%s\n' "$?"
Trail resolved: assets/trails/public_walk.yaml

Choose a walk:
  1  Practice walk  - voice and instructions; no browser or page capture.
  2  Walk the walk  - open the browser; CAPTURE and DECANT still required.
  q  Exit (Enter also exits).
Choice: 1
Practice walk: no browser or page capture.
Riding trail 'public_walk' -- 3 stop(s).

  Welcome to the public walk. It has three short pages, with nothing to log in to and nothing to set up. At each stop a browser will open on one page. Read the page, then come back to this terminal and type the word CAPTURE when it asks.
==================================================================
 THIS WALK: public_walk -- 3 stop(s)
==================================================================
 stops, in order    the_word, the_receipt, the_two_pages
 it opens directly  https://npvg.org/walk/1/, https://npvg.org/walk/2/, https://npvg.org/walk/3/
 names as runnable  scripts/connectors/noop.py
 browser profile    'default'  (persistent=True, headless=False)
==================================================================
 CAPTURE saves each stop locally; captures may include account details.
 DECANT authorizes a checked preview file and a clipboard attempt.
 Preview in workshop: data/decant-preview.md (private; replaced on save).
 Declining or failing checks leaves any previous preview unchanged.
 Nothing is uploaded automatically. The preview is trimmed.
 Review before sharing; checks can miss sensitive data.
==================================================================

--- Stop 1/3: the_word ---
  Stop one of three. A browser will open on a plain page with no script on it. The page will explain the one word this walk asks of you. When you have read it, come back to this terminal and type CAPTURE when it asks.
  (dry-narrate: browser and capture skipped)

--- Stop 2/3: the_receipt ---
  Stop two of three. By the time the next page opens, the first page will have been saved, with a fingerprint for every file. The page will show you one command that counts those fingerprints, so you can check the terminal's report for yourself. When you have tried it, type CAPTURE.
  (dry-narrate: browser and capture skipped)

--- Stop 3/3: the_two_pages ---
  Stop three of three, the last. This page will change itself after it loads, so the page the server sends and the page your browser shows will not match. Type CAPTURE, and the program will ask for one more word, DECANT, before anything goes to your clipboard. The page will say what to do with the result.
  (dry-narrate: browser and capture skipped)

Dry narration complete; no captures were attempted.

Choose a walk:
  1  Practice walk  - voice and instructions; no browser or page capture.
  2  Walk the walk  - open the browser; CAPTURE and DECANT still required.
  q  Exit (Enter also exits).
Choice:  
Stopped. No real walk started.
walk_exit=0
(nix) pipulate $ 
```

Okay, that's good. The wording:

> By the time the next page opens, the first page will have been saved, with a fingerprint for every file. The page will show you one command that counts those fingerprints, so you can check the terminal's report for yourself. When you have tried it, type CAPTURE.

...is still awful for the 1st 5 minute experience of a New-B. By the time blah
blah first page fingerprint blah blah. It should be something like "After the
sound stops return to the terminal and type CAPTURE.

Again a reminder this is the MOTHER CAT KATA picking up and carrying along
helpless kittens on a walk. They can't be this PhD friggin stuff that Claude
wrote that dialogue for (it's always Claude with the high faulting language).
Bring this down and anywhere else you see Claude-style pseudo erudition
confusion infusion word confetti bubblegum over-magnified woggle-bug language.
Talk to the audience like the simple son. Don't be excessively literal about
that; you see the spirit of the thing and figure out what's best.

**4: Prompt**: Continue THE FIRST FIVE MINUTES, ON RAILS.

The preceding input cartridge was foo-af3cbe4b-1417.zip.
Its live installed-player receipt reported:
player_rc=0 nonblocking_before=False nonblocking_after=True
termios_changed=False.

That establishes an installed-player flag leak in the isolated test.
It does not, by itself, establish successful post-repair practice.

PREVIOUS TURN'S PATCH
One car against assets/installer/mck.sh only:
- Both launcher rehearsal calls receive \</dev/null 3<&-.
- The real ride still receives \</dev/tty.
- Failed menu reads preserve their nonzero status and print
  "Menu input ended or failed", rather than reporting a clean stop.
- A successfully read q/Q or blank line remains a zero-exit stop.
- EOF now exits nonzero deliberately; Bash is not claimed to classify
  EOF versus an input error from errno.

The patch does not modify shared voice callers, exports resolution,
trail selection, CAPTURE, DECANT, public pages or completion policy.

SANDBOX VALIDATION, NOT OPERATOR ACCEPTANCE
All five blocks passed the supplied apply.py and Bash syntax checking.
Sixteen patched-menu cases passed using a synthetic descendant that
sets O_NONBLOCK. Next responses were delayed until their menus appeared.
Coverage included piped script input, repeated practice, both launcher
flags, explicit exits, EOF, forced read failure, practice failure and
absence of a controlling terminal.
Actual Piper, native macOS and the Nix-wrapped path were not tested there.

READ THIS COMPILE
The boundary probe derives both practice invocation lines from source.
Synthetic and installed-player cases should leave the menu/caller input
blocking after the repair. Nonzero playback is NOT_TESTED, not a pass.
The separate EOF and read-error cases should exit 1 and report failure.
Do not use the unchanged raw SoX executable as the repair's AFTER test:
the repair isolates its effects; it does not patch SoX.

Read the operator's actual practice transcript separately.
A working second menu followed by a freshly typed q and walk_exit=0 is
the requested real acceptance witness. Do not invent it from fixture output.

NEXT IMPLEMENTATION
Once the input repair is accepted, implement the introductory contract:
explain, demonstrate, CAPTURE, then checked completion without another
typed DECANT word. Disclose preview saving and clipboard replacement
before the real-walk choice.

Bind introductory authorization to the resolved bundled route, not the
public_walk name. Private/shared trails can shadow it.
Make direct-entry and flag behavior explicit. Preserve custom/authenticated
workflow consent semantics.

Reuse the existing checked handoff. Preserve archive integrity, limits,
missing-lens reporting, scrubbing, denylist/secrets checks, atomic private
replacement, and separate file/clipboard/SSH-bridge outcomes.
No automatic chatbot submission.

Change narration, pages, menu, consent, completion and relevant comments
together. Practice must explicitly rehearse rather than claim captures.
Remove fingerprint-command homework from the live introductory sequence.
Keep the source-versus-browser checkword fixture; make its exercise
optional after completion.

Name each actual ignition and any public-page deployment operation.
Do not expand into renderer repair, request attribution, audio replacement,
the Nix startup menu, receipt pruning or shared voice refactoring.

**5: Deliverables**: Something that's deliverable to a New-B kitten.

**ChatGPT 6**: Context verified. **Yes. Page two should tell the newcomer what to do next—not assign them an audit.**

The replacement is:

> I’ll open page two. When it is ready, return here. At the CAPTURE prompt, type CAPTURE and press Enter.

The other stops use the same pattern. The pages put **return to the terminal → CAPTURE → Enter** first. The checkword exercise moves below **“Optional: after the walk.”**

I am using the visible `CAPTURE>` prompt as the cue, rather than silence: that is the point where the existing checkpoint actually waits for the response. 

## The input repair passes the supplied checks

This compile’s four boundary measurements—synthetic and installed-player cases, through both rehearsal calls—all report `nonblocking=False->False` with exit zero. The separate EOF and forced-read-error cases now report failure and exit one. 

Your actual practice transcript also returns to a working menu and exits with `walk_exit=0`. The displayed response is empty; it does **not** show a literal `q`. That is a successful observation of the Enter-exit path, and no reason to hold up this next change. 

## What this car changes

**The ordinary introductory walk finishes without DECANT.** Before the menu, it explains that successful captures and checks lead to a private summary-save attempt and a clipboard-replacement attempt. Choosing **2** starts that sequence.

The implementation keeps the distinction between an introductory walk and other work:

| Entry                                                                  | Handoff behavior after this patch                                                                                        |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Updated `walk`, choosing **2** or using `--yolo`                       | Automatic checked handoff for the bundled introductory route. CAPTURE remains required.                                  |
| `PIPULATE_MCK_ASSUME_YES=1`                                            | Practice first, then the same real walk. Practice performs no capture or preview handoff.                                |
| Private/shared trail overriding `public_walk`, or another custom trail | Existing DECANT checkpoint remains.                                                                                      |
| Direct rider invocation                                                | DECANT remains unless the caller explicitly supplies `--intro`; that flag is refused outside the eligible bundled route. |

The launcher asks the rider for its introductory contract rather than inventing a second eligibility rule. The rider checks the resolved path, the three fixed URLs, the browser profile and capture prerequisites. Before automatic handoff, it also checks that the three captured final URLs still match. A redirect elsewhere blocks that handoff; it does not silently fall back to another confirmation.

**The existing storage and checking machinery stays.** The current implementation already checks the preview, attempts the private atomic save, and passes the same checked string to the clipboard helper. A save failure and a copy failure remain separate outcomes. That is reused, not replaced.  

Practice now explicitly frames its narration as rehearsal. The shared capture prompt stops telling everyone to “navigate,” but its CAPTURE gate is unchanged. The public-page script that creates the source-versus-browser difference is also unchanged.

**Validation:** all **47 blocks** passed the supplied `apply.py`, and its output matched the tested candidate. **25 rider tests and 15 launcher tests passed.** Those tests substitute browser capture, narration, policy/clipboard dependencies and the Nix wrapper; they do not establish a live browser run, actual clipboard delivery or native macOS behavior. The [validation record](sandbox:/mnt/data/onrails_intro/validation.md) states those boundaries.

## 1. PROBES

Run from the repository root before applying the car.

The cheapest policy falsifier is whether a same-named trail outside the bundled path can acquire introductory authorization. The second command checks that, an altered URL, and automatic/manual/redirected handoff routing without opening a browser or touching the clipboard.

Expected source changes are `guidance_homework=2 → 0` and `intro_helpers=absent → scope=3/3 handoff_routes=True`. These are source and isolated-function checks, not the real-walk acceptance test.

The third command records each local and served page’s full digest. Compare the **digest values**, not just `match=True`: both the old pair and the correctly deployed new pair can match.

```bash
bash -n walk && bash -n assets/installer/mck.sh && .venv/bin/python -B -c 'import ast,json; from pathlib import Path; [ast.parse(Path(p).read_text(), filename=p) for p in ("scripts/mother_cat.py","tools/scraper_tools.py")]; d=json.loads(Path("assets/trails/public_walk.yaml").read_text()); print("syntax=ok", "stops="+str(len(d["stops"])), "guidance_homework="+str(sum(any(x in s["guidance"].lower() for x in ("fingerprint", "when you have tried", "decant")) for s in d["stops"])))'
.venv/bin/python -B -c 'exec("import contextlib,copy,io,sys\nfrom pathlib import Path\nsys.path.insert(0, \"scripts\")\nimport mother_cat as m\nif not hasattr(m, \"_intro_eligible\"):\n    print(\"intro_helpers=absent\")\nelse:\n    p=m.REPO_ROOT/\"assets/trails/public_walk.yaml\"\n    t=m.walk.load_trail(p)\n    changed=copy.deepcopy(t)\n    changed[\"stops\"][0][\"url\"]=\"https://example.invalid/\"\n    scope=[m._intro_eligible(p,t),not m._intro_eligible(m.REPO_ROOT/\"__intro_probe__/public_walk.yaml\",t),not m._intro_eligible(p,changed)]\n    calls=[]\n    m._decant_to_clipboard=lambda payload: calls.append(\"automatic\") or True\n    m._decant_checkpoint=lambda payload,captured: calls.append(\"manual\") or True\n    c=[(str(i),u,{}) for i,u in enumerate(m.INTRO_URLS)]\n    with contextlib.redirect_stdout(io.StringIO()):\n        a=m._complete_preview(\"fixture\",c,intro=True)\n        z=m._complete_preview(\"fixture\",c)\n        r=m._complete_preview(\"fixture\",c[:-1]+[(\"3\",\"https://example.invalid/\",{})],intro=True)\n    print(\"scope=\"+str(sum(scope))+\"/3\", \"handoff_routes=\"+str(a and z and not r and calls==[\"automatic\",\"manual\"]))\n    assert all(scope) and a and z and not r and calls==[\"automatic\",\"manual\"], \"intro policy regression\"\n")'
.venv/bin/python -B -c 'exec("import hashlib\nfrom pathlib import Path\nfrom urllib.request import Request,urlopen\nfor i in (1,2,3):\n    local=Path(f\"remotes/honeybot/www/npvg.org/walk/{i}/index.html\").read_bytes()\n    req=Request(f\"https://npvg.org/walk/{i}/\",headers={\"Cache-Control\":\"no-cache\"})\n    with urlopen(req,timeout=10) as response:\n        remote=response.read(65537)\n        status=response.status\n    assert len(remote)<=65536, \"unexpected page size\"\n    print(\"page=\"+str(i),\"status=\"+str(status),\"local=\"+hashlib.sha256(local).hexdigest(),\"served=\"+hashlib.sha256(remote).hexdigest(),\"match=\"+str(local==remote))\n")'
```

## 2. NEXT CONTEXT

`release.py` is included for inspecting the separate publishing step. The page sync below does not publish a new remotely served launcher.

```text
foo_files.py
walk
assets/installer/mck.sh
scripts/mother_cat.py
scripts/walk.py
scripts/walk_cartridge.py
scripts/connectors/noop.py
imports/voice_synthesis.py
tools/scraper_tools.py
prompt_foo.py
assets/trails/public_walk.yaml
remotes/honeybot/www/npvg.org/walk/1/index.html
remotes/honeybot/www/npvg.org/walk/2/index.html
remotes/honeybot/www/npvg.org/walk/3/index.html
nixops.sh
release.py
! bash -n walk && bash -n assets/installer/mck.sh && .venv/bin/python -B -c 'import ast,json; from pathlib import Path; [ast.parse(Path(p).read_text(), filename=p) for p in ("scripts/mother_cat.py","tools/scraper_tools.py")]; d=json.loads(Path("assets/trails/public_walk.yaml").read_text()); print("syntax=ok", "stops="+str(len(d["stops"])), "guidance_homework="+str(sum(any(x in s["guidance"].lower() for x in ("fingerprint", "when you have tried", "decant")) for s in d["stops"])))'
! .venv/bin/python -B -c 'exec("import contextlib,copy,io,sys\nfrom pathlib import Path\nsys.path.insert(0, \"scripts\")\nimport mother_cat as m\nif not hasattr(m, \"_intro_eligible\"):\n    print(\"intro_helpers=absent\")\nelse:\n    p=m.REPO_ROOT/\"assets/trails/public_walk.yaml\"\n    t=m.walk.load_trail(p)\n    changed=copy.deepcopy(t)\n    changed[\"stops\"][0][\"url\"]=\"https://example.invalid/\"\n    scope=[m._intro_eligible(p,t),not m._intro_eligible(m.REPO_ROOT/\"__intro_probe__/public_walk.yaml\",t),not m._intro_eligible(p,changed)]\n    calls=[]\n    m._decant_to_clipboard=lambda payload: calls.append(\"automatic\") or True\n    m._decant_checkpoint=lambda payload,captured: calls.append(\"manual\") or True\n    c=[(str(i),u,{}) for i,u in enumerate(m.INTRO_URLS)]\n    with contextlib.redirect_stdout(io.StringIO()):\n        a=m._complete_preview(\"fixture\",c,intro=True)\n        z=m._complete_preview(\"fixture\",c)\n        r=m._complete_preview(\"fixture\",c[:-1]+[(\"3\",\"https://example.invalid/\",{})],intro=True)\n    print(\"scope=\"+str(sum(scope))+\"/3\", \"handoff_routes=\"+str(a and z and not r and calls==[\"automatic\",\"manual\"]))\n    assert all(scope) and a and z and not r and calls==[\"automatic\",\"manual\"], \"intro policy regression\"\n")'
! .venv/bin/python -B -c 'exec("import hashlib\nfrom pathlib import Path\nfrom urllib.request import Request,urlopen\nfor i in (1,2,3):\n    local=Path(f\"remotes/honeybot/www/npvg.org/walk/{i}/index.html\").read_bytes()\n    req=Request(f\"https://npvg.org/walk/{i}/\",headers={\"Cache-Control\":\"no-cache\"})\n    with urlopen(req,timeout=10) as response:\n        remote=response.read(65537)\n        status=response.status\n    assert len(remote)<=65536, \"unexpected page size\"\n    print(\"page=\"+str(i),\"status=\"+str(status),\"local=\"+hashlib.sha256(local).hexdigest(),\"served=\"+hashlib.sha256(remote).hexdigest(),\"match=\"+str(local==remote))\n")'
```

## 3. PATCHES

**One coordinated behavior car across eight existing files.** The instructions and the handoff policy change together; do not run the walk between individual blocks.

Copy the entire fence, then use `patch`, `app`, `d`, `m`.

```text
Target: scripts/mother_cat.py
[[[SEARCH]]]
DECANT_PREVIEW_PATH = REPO_ROOT / "data" / "decant-preview.md"
[[[DIVIDER]]]
DECANT_PREVIEW_PATH = REPO_ROOT / "data" / "decant-preview.md"
INTRO_URLS = tuple(f"https://npvg.org/walk/{i}/" for i in (1, 2, 3))
INTRO_NOTICE = (
    "This walk opens three public pages. Nothing to sign in to.\n"
    "Return here and type CAPTURE when prompted at each page.\n"
    "After all three captures and successful checks, it tries to save a private summary\n"
    "and tries to replace your clipboard. Over SSH it uses a bridge file.\n"
    "Nothing is sent to a chatbot. Review the summary before sharing it."
)

def _intro_eligible(trail_path, trail=None):
    """One authority for launcher disclosure and rider authorization scope."""
    path = Path(trail_path)
    if not path.is_absolute():
        path = REPO_ROOT / path
    if path.resolve() != REPO_ROOT / "assets" / "trails" / "public_walk.yaml":
        return False
    trail = walk.load_trail(path) if trail is None else trail
    return (tuple(stop.get("url") for stop in trail["stops"]) == INTRO_URLS
            and trail["defaults"].get("profile_name") == "default"
            and not _capture_compatible(trail))
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
# NEXT RIDE -- THE FIRST FIVE MINUTES, ON RAILS (not implemented): disclose
# preview saving and clipboard replacement before choosing the real walk;
# complete after the final capture and existing checks without another word.
# Keep CAPTURE synchronization, archive integrity, caps, scrubbing, secret
# checks, private replacement and honest destination-specific failures.
# Change narration, public pages, launcher and completion labels together;
# move fingerprint/checkword exercises after completion. Frame practice as
# rehearsal. Explicitly scope shared/custom behavior: a private trail can
# shadow public_walk, so its name alone does not identify the bundled route.
[[[DIVIDER]]]
# INTRODUCTORY COMPLETION: --intro authorizes the checked handoff only for
# the resolved bundled three-page route. The launcher prints INTRO_NOTICE
# before the choice; direct callers must pass --intro explicitly. Other
# calls retain DECANT. Practice never captures, saves a preview or copies.
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
# --- THE EGRESS BARRIER -----------------------------------------------------
# The per-stop CAPTURE token gates each WRITE TO DISK, on the operator's own
# machine. This gates EGRESS: a composite of authenticated material leaving the
# machine on the clipboard, with mck.sh then telling the human to paste it into
# a cloud chat. Different consequence class, therefore a different word.
#
# WHY NOT REUSE "CAPTURE": by the time this fires the human has typed CAPTURE
# once per stop. A fourth identical prompt is answered by MUSCLE MEMORY, not by
# decision -- and a fence satisfied by habit is not a fence. mck.sh already runs
# this grammar: INSTALL and RIDE are different words for different acts.
#
# NOT SKIPPABLE BY --yolo, and the argument is not merely that barriers are not
# skippable. (1) --yolo is typed at t=0, before a browser opens; it cannot
# consent to the disposition of material the consenter had not yet seen.
# (2) --yolo already blocks at every CAPTURE fence, so no unattended capability
# exists to lose. (3) The bypass-under-another-name corollary does NOT apply:
# _decant is the only builder of this composite and _ride_async its only caller,
# so a flag would not duplicate a shipped capability, it would create one.
[[[DIVIDER]]]
# --- MANUAL HANDOFF --------------------------------------------------------
# Custom walks and direct calls without --intro still ask for DECANT.
# The bundled introduction may authorize its handoff before the first page;
# _complete_preview checks the captured destinations before using that path.
# Neither mode submits anything to a chatbot or relaxes the disclosure checks.
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
    its own state and the payload size on every ride before asking anything --
[[[DIVIDER]]]
    its own state and the payload size on each manual handoff before asking --
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
def _write_decant_preview(payload):
[[[DIVIDER]]]
def _complete_preview(payload, captured, intro=False):
    """Use explicit introductory authorization, or the existing manual gate."""
    if not intro:
        return _decant_checkpoint(payload, captured)
    if tuple(final_url for _, final_url, _ in captured) != INTRO_URLS:
        print("   BLOCKED: the walk left its three public pages. Summary not sent.")
        print("   The local captures remain; any older summary is unchanged.")
        return False
    print("\nChecking the summary before saving it and trying the clipboard.")
    return _decant_to_clipboard(payload)

def _write_decant_preview(payload):
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
    imported HERE, on a real DECANT only -- never on module import or
[[[DIVIDER]]]
    imported HERE, on a real preview handoff only -- never on module import or
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
    print(f"   DECANT checks: substitutions={substitutions} "
[[[DIVIDER]]]
    print(f"   Preview checks: substitutions={substitutions} "
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
    # AFTER the human's word and baseline checks: one string, two destinations.
[[[DIVIDER]]]
    # AFTER authorization and baseline checks: one string, two destinations.
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
def _announce_consent(trail_path):
[[[DIVIDER]]]
def _announce_consent(trail_path, intro=False):
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
    """Print what the WHOLE walk demands, before stop one, plus the DECANT.
    IMPORTED, NEVER DUPLICATED, AND THE DIRECTION OF THE ARROW IS THE ARGUMENT.
    walk_cartridge.py duplicates foo_cartridge.py's primitives because a
    clean-room consumer must be able to fetch ONE file and verify a cartridge.
    That constraint governs what walk_cartridge may IMPORT; it says nothing
    about what may import walk_cartridge. mother_cat.py already imports walk,
    scraper_tools, voice_synthesis and (deferred) prompt_foo -- it is in-repo by
    construction and can never be fetched standalone -- so this import costs the
    single-file property nothing, and walk_cartridge still imports only stdlib.
    Duplicating here would be the actual error. A second implementation can
    drift, and on the day it does, the surface a human CONSENTS to and the
    surface the manifest ATTESTS to disagree, so the seal would be signing a
    projection nobody was ever shown. One derivation, or the seal means nothing.
    Derived from the trail's BYTES, not from walk.load_trail's validated dict,
    so what is spoken here is provably what a sealer would hash.
    THIS IS A DISCLOSURE, NOT A FENCE. Nothing is gated. The ruling is banked
    beside the call site.
    """
[[[DIVIDER]]]
    """Describe capture and handoff, never grant authorization here.

    Custom walks use the same trail projection as walk_cartridge.
    The bundled introduction uses the shared plain-language contract;
    --intro is validated separately before any narration or capture.
    """
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
    browser = surface["browser"]
[[[DIVIDER]]]
    if intro:
        print("\n" + INTRO_NOTICE)
        print(f"Summary file: {DECANT_PREVIEW_PATH.relative_to(REPO_ROOT)} (private; replaced on save).")
        print("Checks can miss private details. A blocked check leaves the older file alone.\n")
        return
    browser = surface["browser"]
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
async def _ride_async(trail_path, dry_narrate=False, exports_path=None):
[[[DIVIDER]]]
async def _ride_async(trail_path, dry_narrate=False, exports_path=None, intro=False):
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
        return await _ride_steps(trail_path, archive, dry_narrate, exports_path)
[[[DIVIDER]]]
        return await _ride_steps(trail_path, archive, dry_narrate, exports_path, intro)
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
async def _ride_steps(trail_path, archive, dry_narrate=False, exports_path=None):
[[[DIVIDER]]]
async def _ride_steps(trail_path, archive, dry_narrate=False, exports_path=None, intro=False):
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
    trail = walk.load_trail(trail_path)
[[[DIVIDER]]]
    trail = walk.load_trail(trail_path)
    if intro and not _intro_eligible(trail_path, trail):
        raise walk.TrailError("--intro is only for the bundled three-page public walk")
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
    # DISCLOSURE, NOT A FENCE, AND THAT IS THE RULING RATHER THAN AN OVERSIGHT.
    # mck.sh owns the practice/real-walk choice. CEREMONY IS SKIPPABLE;
    # BARRIERS ARE NOT: each CAPTURE and DECANT remain in the rider.
    # This surface prints on both paths; practice does not authorize a ride.
    # THE DECANT FENCE LANDED, so this comment's earlier claim that nothing
    # gated the clipboard is RETIRED rather than merely outdated. What the call
    # buys NOW is disclosure BEFORE the material exists: the rider learns at t=0
    # that DECANT is a separate choice and that its checked preview still
    # needs review, before either the file or clipboard is attempted.
    # SAME-CAR LABEL RULE, PAID LATE AND THEREFORE WORTH BANKING. The fence and
    # the strings describing it shipped in DIFFERENT rides, so for one ride this
    # function told every rider "WITHOUT ASKING AGAIN" about a gate that does
    # ask, and public_walk.yaml's third stop said the same thing in trail data.
    # That is not stale documentation; it is a lie told at the exact moment the
    # human decides, and it lied in the EXPENSIVE direction -- understating the
    # protection and overstating the risk, to a newcomer, on the softball walk.
[[[DIVIDER]]]
    # The launcher discloses INTRO_NOTICE before its real-walk choice and
    # passes --intro only for the bundled route. Direct callers without
    # that flag retain DECANT. Practice describes terms but authorizes no
    # capture or handoff; it returns before either can occur.
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
    disclosed = _narrate(trail["description"], False)
    _announce_consent(trail_path)
[[[DIVIDER]]]
    rehearsal = "Practice only. In the real walk: " if dry_narrate else ""
    if dry_narrate:
        print("Practice only. No pages will open. You do not need to type anything.\n")
    disclosed = _narrate(rehearsal + trail["description"], False)
    _announce_consent(trail_path, intro=intro)
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
        disclosed = _narrate(stop["guidance"], disclosed)
[[[DIVIDER]]]
        disclosed = _narrate(rehearsal + stop["guidance"], disclosed)
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
                "This is the automated trail guide. I read each step aloud; "
                "you handle only the CAPTURE checkpoint at each stop. Any "
                "sign-in a stop needs is named in that stop's own guidance."
[[[DIVIDER]]]
                "This is an automatic voice reading written instructions. "
                "It does not listen or answer questions."
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
            print("  Details are banked locally; halting without ADVANCE or DECANT.")
[[[DIVIDER]]]
            print("  Saved details remain on this computer. Stopping without a summary handoff.")
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
        decanted = _decant_checkpoint(payload, captured)
[[[DIVIDER]]]
        decanted = _complete_preview(payload, captured, intro=intro)
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
        # human's authorization.
[[[DIVIDER]]]
        # checked handoff attempt, not success at either destination.
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
            print("   Review the preview before sharing it with a chatbot.")
            print("   Ask it to separate what the files show from what it infers.")
[[[DIVIDER]]]
            print("   Read the save and copy messages above; either step can fail.")
            print("   Review the summary before sharing it. You choose what to send.")
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
def ride(trail_path=None, dry_narrate=False, exports_path=None):
[[[DIVIDER]]]
def ride(trail_path=None, dry_narrate=False, exports_path=None, intro=False):
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
        _ride_async(path, dry_narrate=dry_narrate, exports_path=exports_path)
[[[DIVIDER]]]
        _ride_async(path, dry_narrate=dry_narrate, exports_path=exports_path, intro=intro)
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
    parser.add_argument("--disclose", metavar="CAPTURES_MD",
[[[DIVIDER]]]
    parser.add_argument("--intro", action="store_true",
                        help="authorize checked summary saving and clipboard replacement for the bundled public walk")
    parser.add_argument("--intro-contract", action="store_true",
                        help="read-only: print introductory terms if this is the bundled route; otherwise print nothing")
    parser.add_argument("--disclose", metavar="CAPTURES_MD",
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
        if args.trail or args.dry_narrate or args.exports:
[[[DIVIDER]]]
        if args.trail or args.dry_narrate or args.exports or args.intro or args.intro_contract:
[[[REPLACE]]]

Target: scripts/mother_cat.py
[[[SEARCH]]]
        return ride(args.trail, dry_narrate=args.dry_narrate, exports_path=args.exports)
[[[DIVIDER]]]
        if args.intro_contract:
            if args.intro or args.dry_narrate or args.exports:
                parser.error("--intro-contract cannot be combined with ride options")
            if _intro_eligible(args.trail or walk.DEFAULT_TRAIL):
                print(INTRO_NOTICE)
            return 0
        return ride(args.trail, dry_narrate=args.dry_narrate,
                    exports_path=args.exports, intro=args.intro)
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
run_rider() {
[[[DIVIDER]]]
INTRO_CONTRACT="$("$PY" scripts/mother_cat.py "$TRAIL_PATH" --intro-contract)"
run_rider() {
  if [ -n "$INTRO_CONTRACT" ]; then
    set -- --intro "$@"
  fi
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
if [ "$YOLO" -eq 1 ]; then
[[[DIVIDER]]]
if [ -n "$INTRO_CONTRACT" ]; then
  printf '\n%s\n' "$INTRO_CONTRACT"
else
  printf '\nCAPTURE saves each page. DECANT asks before saving a summary or copying it.\n'
fi
if [ "$YOLO" -eq 1 ]; then
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
  echo "--yolo: real walk; CAPTURE and DECANT are still required."
[[[DIVIDER]]]
  echo "Starting the real walk. CAPTURE is still required at each page."
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
  echo "ASSUME_YES: practice, then the real walk; CAPTURE and DECANT still required."
[[[DIVIDER]]]
  echo "Practice first, then the real walk. CAPTURE is still required at each page."
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
    printf '  1  Practice walk  - voice and instructions; no browser or page capture.\n'
    printf '  2  Walk the walk  - open the browser; CAPTURE and DECANT still required.\n'
[[[DIVIDER]]]
    printf '  1  Practice - hear the steps; no pages open.\n'
    printf '  2  Start the walk - open the pages.\n'
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
#   --yolo   skip INSTALL confirmation and the walk menu. It does NOT
#            skip the CAPTURE fence at any stop, nor the DECANT gate at the
#            end, and no flag ever will. --yolo is typed BEFORE the ride, so
#            it cannot consent to the disposition of material that did not
#            exist when it was typed; and it was never unattended anyway,
#            because the CAPTURE fences already block.
#            CEREMONY IS SKIPPABLE; BARRIERS ARE NOT: a confirmation
#            authorizes a SEQUENCE, a fence authorizes each WRITE, and the
#            unfenced capture lane already exists under other names.
#
[[[DIVIDER]]]
#   --yolo   skip INSTALL confirmation and the menu; keep every CAPTURE.
#            For the bundled introduction it accepts the printed summary
#            and clipboard terms, as does choosing 2. Other trails retain
#            DECANT. ASSUME_YES rehearses first under the same policy.
#            The rider's read-only --intro-contract determines eligibility;
#            a same-named private trail does not inherit this policy.
#
[[[REPLACE]]]

Target: assets/installer/mck.sh
[[[SEARCH]]]
   RIDE COMPLETE
--------------------------------------------------------------
 Every stop that OPENED produced a capture receipt. An optional
 stop whose URL you had not exported was skipped; the rider
 said which, above, and the bundle lists it as skipped.

 Whether the bundle LEFT this machine depends on the DECANT
 gate you just answered. This script cannot see your clipboard,
 so it does not claim to. Read the rider's own last line:

   AUTHORIZED  you permitted a checked preview handoff; this alone
               does not prove a clipboard write. Read its receipt.
   BLOCKED     the preview failed disclosure checks; nothing copied.
   DECLINED    nothing was copied.
   REFUSED     no terminal was available to ask; nothing copied.
  Original cache files remain under browser_cache/. Banked bytes
  are in data/captures/; the rider prints the exact captures.md path.
  That local archive is UNSANITIZED. Nothing was uploaded by this script.
--------------------------------------------------------------
[[[DIVIDER]]]
   CAPTURE RUN FINISHED
--------------------------------------------------------------
 Read the save and copy messages above. Either step can fail.
 Review the summary before sharing it.
 Nothing was sent to a chatbot.
--------------------------------------------------------------
[[[REPLACE]]]

Target: walk
[[[SEARCH]]]
# every per-stop CAPTURE fence, and the final DECANT gate. The launcher owns
# which ceremony is optional; this wrapper never reimplements those choices.
[[[DIVIDER]]]
# every per-stop CAPTURE fence, and the handoff policy: checked completion
# for the bundled introduction, DECANT for other trails. This wrapper never
# reimplements those choices.
[[[REPLACE]]]

Target: assets/trails/public_walk.yaml
[[[SEARCH]]]
  "description": "Welcome to the public walk. It has three short pages, with nothing to log in to and nothing to set up. At each stop a browser will open on one page. Read the page, then come back to this terminal and type the word CAPTURE when it asks.",
[[[DIVIDER]]]
  "description": "This walk opens three pages. You do not need to click anything. At each page, return here and wait for the CAPTURE prompt. Type CAPTURE and press Enter.",
[[[REPLACE]]]

Target: assets/trails/public_walk.yaml
[[[SEARCH]]]
      "guidance": "Stop one of three. A browser will open on a plain page with no script on it. The page will explain the one word this walk asks of you. When you have read it, come back to this terminal and type CAPTURE when it asks.",
[[[DIVIDER]]]
      "guidance": "I'll open page one. When it is ready, return here. At the CAPTURE prompt, type CAPTURE and press Enter.",
[[[REPLACE]]]

Target: assets/trails/public_walk.yaml
[[[SEARCH]]]
      "guidance": "Stop two of three. By the time the next page opens, the first page will have been saved, with a fingerprint for every file. The page will show you one command that counts those fingerprints, so you can check the terminal's report for yourself. When you have tried it, type CAPTURE.",
[[[DIVIDER]]]
      "guidance": "I'll open page two. When it is ready, return here. At the CAPTURE prompt, type CAPTURE and press Enter.",
[[[REPLACE]]]

Target: assets/trails/public_walk.yaml
[[[SEARCH]]]
      "guidance": "Stop three of three, the last. This page will change itself after it loads, so the page the server sends and the page your browser shows will not match. Type CAPTURE, and the program will ask for one more word, DECANT, before anything goes to your clipboard. The page will say what to do with the result.",
[[[DIVIDER]]]
      "guidance": "I'll open the last page. When it is ready, return here. At the CAPTURE prompt, type CAPTURE and press Enter. Then read the result here.",
[[[REPLACE]]]

Target: assets/trails/public_walk.yaml
[[[SEARCH]]]
      "label": "The fingerprints",
[[[DIVIDER]]]
      "label": "Page two",
[[[REPLACE]]]

Target: tools/scraper_tools.py
[[[SEARCH]]]
                "\nNavigate in the visible browser, then type CAPTURE and press Enter.\n"
[[[DIVIDER]]]
                "\nWhen the page you want is ready, type CAPTURE and press Enter.\n"
[[[REPLACE]]]

Target: remotes/honeybot/www/npvg.org/walk/1/index.html
[[[SEARCH]]]
<p>Stop 1 of 3</p>
<h1>You are on a walk.</h1>
<p>A program on your computer opened this page, and a voice read you a short paragraph about it. The voice is a program speaking words a person wrote. It is not listening, and it cannot answer you.</p>
<p>Nothing on this page is a control. There are no buttons and no links, on purpose. The only control is in your terminal, where the program is waiting for one word:</p>
<p class="word">CAPTURE</p>
<p>When you type it, the program saves this page to a folder on your own computer, several ways at once: the page exactly as the server sent it, the page as your browser built it, every request your browser made while loading it, and the headers that came back. It records a fingerprint of each saved file, a SHA-256 hash that changes if a single byte changes. Then it moves on to the next stop.</p>
<p>The program uploads nothing. The saved copies stay on your computer.</p>
<p>Go back to the terminal and type the word. The rest of this page is for later.</p>
<hr>
<p><strong>Why a word, and not a button?</strong> A button on this page could be pressed by a script running on this page. The word is typed into a window this page cannot reach, so the record shows it came from the keyboard and not from the page.</p>
<p><strong>Why is this page so plain?</strong> It has no script. The page the server sent and the page your browser built should carry the same elements. If they do not, something other than this page changed it, and that is worth knowing too. The last stop is the one that changes itself.</p>
[[[DIVIDER]]]
<p>Page 1 of 3</p>
<h1>You are in the right place.</h1>
<p>There is nothing to click here. Return to the terminal.</p>
<p>When it shows <code>CAPTURE&gt;</code>, type:</p>
<p class="word">CAPTURE</p>
<p>Press Enter. The program will save this page and open the next one.</p>
<hr>
<p>The voice reads written instructions. It does not listen. You can follow the words in the terminal even when the sound is off.</p>
<p>The copies stay on this computer. No page is submitted to a chatbot.</p>
[[[REPLACE]]]

Target: remotes/honeybot/www/npvg.org/walk/2/index.html
[[[SEARCH]]]
<title>Stop 2 of 3: the fingerprints</title>
[[[DIVIDER]]]
<title>Page 2 of 3: keep going</title>
[[[REPLACE]]]

Target: remotes/honeybot/www/npvg.org/walk/2/index.html
[[[SEARCH]]]
<p>Stop 2 of 3</p>
<h1>The first page is already written down.</h1>
<p>Before this page opened, your terminal printed two lines worth reading:</p>
<ul>
<li>A line starting with <code>LOCAL ARCHIVE</code>. The path after it, up to the parenthesis, is a text file. It holds every file saved at the first stop, each with its size and its fingerprint.</li>
<li>A line starting with <code>Captured. final_url=</code>. The number after <code>artifacts=</code> on that line is how many files the first stop saved.</li>
</ul>
<p>You do not have to take that number on trust. Open a second terminal window and count the fingerprints yourself. Type this, then a space, then paste the path from the <code>LOCAL ARCHIVE</code> line:</p>
<pre>grep -c '"sha256"'</pre>
<p>The number it prints should match the number after <code>artifacts=</code>. The program reported something, and a second instrument, one the program did not write, checked the report. That is the whole idea of this workshop in one move.</p>
<p>If the two numbers disagree, you have found something real. Keep both lines.</p>
<p>When you are done, go back to the first terminal and type:</p>
<p class="word">CAPTURE</p>
[[[DIVIDER]]]
<p>Page 2 of 3</p>
<h1>Keep going.</h1>
<p>There is nothing to do on this page. Return to the terminal.</p>
<p>When it shows <code>CAPTURE&gt;</code>, type:</p>
<p class="word">CAPTURE</p>
<p>Press Enter. There is one page left after this.</p>
<hr>
<p>You do not need to open another window or run any commands.</p>
[[[REPLACE]]]

Target: remotes/honeybot/www/npvg.org/walk/3/index.html
[[[SEARCH]]]
<p>Stop 3 of 3</p>
<h1>This page has two versions.</h1>
[[[DIVIDER]]]
<p>Page 3 of 3</p>
<h1>Last page.</h1>
<p>Return to the terminal. When it shows <code>CAPTURE&gt;</code>, type:</p>
<p class="word">CAPTURE</p>
<p>Press Enter, then read the result in the terminal.</p>
<p>After the checks pass, the normal walk tries to save and copy a summary. The terminal tells you what worked.</p>
<p>A saved summary and a successful copy are separate results. Nothing is sent to a chatbot. Review the summary before sharing it.</p>
<hr>
<h2>Optional: after the walk</h2>
<p>You can stop here. The rest is an extra test, not another step.</p>
<h3>One page, two versions</h3>
[[[REPLACE]]]

Target: remotes/honeybot/www/npvg.org/walk/3/index.html
[[[SEARCH]]]
<p>The box above is not what the server sent. One small script, the only script on any stop of this walk, replaced the server's sentence and added a paragraph after the page arrived. Most of the web does this kind of thing without saying so.</p>
<p>The server's sentence carried a <strong>checkword</strong>, one ordinary English word. You cannot see it on this page, because the script removed it before the page reached your eyes. It is not a secret: anyone who reads this page's source can find it. It is a label, so you can tell which version of the page a sentence came from.</p>
<p>Go back to the first terminal and type:</p>
<p class="word">CAPTURE</p>
<p>The program will then put together a short preview of what this walk saved and ask for one more word, <strong>DECANT</strong>. Type it, and the preview goes to your clipboard. The program uploads nothing, and the preview reaches your clipboard only if you type that word.</p>
<h2>The test</h2>
<p>Paste the preview into the chatbot you started with and ask:</p>
<pre>What is the checkword in the sentence the server sent on stop three?</pre>
<p>Then check the answer against a file the chatbot cannot change. The preview lists the files saved at each stop under <em>artifacts on disk</em>. Under the third stop, open the one whose path ends in <code>source.html</code>. That is the page exactly as the server sent it. Search it for the word checkword followed by a colon; the word after that is the answer. The file whose path ends in <code>hydrated_dom.html</code> is the page your browser built, and the server's sentence is not in it.</p>
<p>The preview is trimmed, and it may not carry the server's sentence at all. If it does not, a good answer says so. If the chatbot names a word anyway, compare that word with the file. Either way, the file decides. When you check it, you have tested a machine's account against a record the machine could not edit.</p>
<hr>
<p>The terminal named this walk before the first stop. A walk is one small file of addresses and spoken words. Copy it, change the addresses and the words, and you have written a walk of your own.</p>
[[[DIVIDER]]]
<p>A small script changed the box after this page arrived. The server's version had a checkword. The version you see does not show it.</p>
<p>After the walk, you can give a chatbot the saved summary and ask:</p>
<pre>What is the checkword in the sentence the server sent on stop three?</pre>
<p>Check its answer yourself. In the summary, find the third page's file ending in <code>source.html</code>. Open it and search for <code>Checkword:</code>. The word after the colon is the answer.</p>
<p>The summary may be trimmed. If the sentence is missing, the chatbot should say so rather than guess. Compare any answer with the saved file.</p>
<p>This is optional. You have already finished the walk.</p>
<p>These instructions describe starting with <code>walk</code> and choosing 2. Other ways to start the program may still ask for <code>DECANT</code>. Follow your terminal.</p>
[[[REPLACE]]]
```

### Deployment and ignition

The supplied `nixops.sh` identifies both the destination and the existing page-sync layout. Its full execution also touches hooks, other scripts and staged NixOS configuration, so this car uses only the walk-page directory. No NixOS rebuild is needed for these HTML bodies. 

**This is the deployment actuator, not a probe.** Run it after applying and reviewing the complete car:

```bash
(
  set -e
  target="$(.venv/bin/python -B -c 'import shlex; from pathlib import Path; rows=[line.split("=",1)[1] for line in Path("nixops.sh").read_text().splitlines() if line.startswith("TARGET=")]; assert len(rows)==1, "expected one deployment target"; print(shlex.split(rows[0])[0])')"
  test -n "$target"
  rsync -av remotes/honeybot/www/npvg.org/walk/ "$target:~/www/npvg.org/walk/"
)
```

**Runtime ignition:** start a fresh invocation, choose **2**, and enter **CAPTURE** at each of the three prompts.

```bash
bash walk; printf 'walk_exit=%s\n' "$?"
```

There should be no fingerprint assignment and no DECANT prompt on this route. Preserve the archive-completion line, the checks, the separate save/copy messages and the final exit status. No shell re-entry is required to load these files. Push the reviewed commit after the acceptance run.

This delivers the new behavior through **`bash walk` in the updated checkout**. The HTML sync does not update a remotely served `mck.sh`, a package release or another computer’s checkout; that publishing step remains separate.

## 4. PROMPT

```text
Continue THE FIRST FIVE MINUTES, ON RAILS.

The input cartridge was foo-584d1508-1418.zip. Its three live receipts
verified the practice-input repair. The operator's actual practice returned
to a menu and exited zero on the displayed empty response; no literal q
was shown. Do not reopen that repair from a missing q alone.

PREVIOUS TURN'S IMPLEMENTATION
One coordinated car across eight existing files:
- Plain public-walk narration; no fingerprint-command assignment.
- Plain page instructions; the unchanged checkword script and its exercise
  sit below an optional-after-the-walk heading.
- Practice prefixes the narrated instructions as rehearsal and never
  captures, saves a preview or attempts the clipboard handoff.
- The rider's read-only --intro-contract supplies the launcher with both
  eligibility and the exact notice printed before the menu/flag branch.
- The launcher passes --intro for the resolved bundled three-page route.
- The rider revalidates its path, fixed URLs, default profile and capture
  prerequisites; it checks all final captured URLs before automatic handoff.
- After a complete introductory run, existing checks and private-file /
  clipboard logic run without another typed DECANT word.
- Custom/private overrides and direct rider calls without --intro retain
  DECANT. --yolo skips practice; ASSUME_YES rehearses then rides.
- Shared voice playback, export loading, capture banking and disclosure
  policy were not refactored. The checkpoint wording changed, not its gate.

SANDBOX RESULTS, NOT LIVE ACCEPTANCE
47 blocks passed the supplied apply.py; applied bytes matched the candidate.
25 rider tests and 15 launcher tests passed with explicit substitutions.
Browser capture, voice and policy/clipboard dependencies were substituted.
The Nix-wrapper test used a shim. No native macOS or real browser run was
claimed. Tests covered successful handoff, private shadows, redirects,
partial runs, blocked checks, save/copy failures, delayed menu input,
flags, exports argument forwarding, EOF and rehearsal failure.

READ THIS COMPILE
Expect syntax=ok, stops=3, guidance_homework=0.
Expect scope=3/3 and handoff_routes=True from the read-only policy fixture.
These are not proof that a real browser or clipboard succeeded.
For each public page, compare local and served full SHA-256 values. Match
alone is not a revision indicator: compare the actual hashes with BEFORE.
The page deployment is a targeted rsync of the walk/ directory using the
TARGET read from nixops.sh. No NixOS rebuild is required for those bodies.

Read the operator's fresh walk separately: choose 2, three CAPTUREs,
no DECANT prompt on this route, archive complete, checks and distinct
file/clipboard-or-bridge results. A normal helper return is not delivery.
Do not assert q input, audible narration, successful clipboard contents
or a chatbot checkword test unless the corresponding witness is present.

ROLLOUT BOUNDARY
The implementation targets bash walk in the updated checkout. The targeted
HTML sync does not publish a new remotely served mck.sh, a PyPI release,
or another computer's checkout. release.py is included for inspecting
that separate rollout before naming or taking any publishing action.

Keep CAPTURE, review-before-sharing, private storage, limits, integrity
and disclosure checks. No automatic chatbot submission.
Do not expand into renderer repair, audio replacement, request attribution,
the Nix startup menu, receipt pruning or shared voice refactoring.
```

## 5. EXTERNAL DELIVERABLES

The complete [patch](sandbox:/mnt/data/onrails_intro/intro.patch.txt), [probe block](sandbox:/mnt/data/onrails_intro/probes.txt), [matching next-context block](sandbox:/mnt/data/onrails_intro/next_context.txt) and [next prompt](sandbox:/mnt/data/onrails_intro/prompt.txt) are saved as text.

The [reproducible QA bundle](sandbox:/mnt/data/onrails_intro/intro-qa.zip) contains the relevant source snapshots, candidate, tests and results. The newcomer-facing deliverable is in the car itself: three short pages, three CAPTURE prompts, and a checked completion attempt—without a lesson interrupting the walk.

**MikeLev.in**: 

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

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

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

nothing to commit, working tree clean
(nix) pipulate $ bash -n walk && bash -n assets/installer/mck.sh && .venv/bin/python -B -c 'import ast,json; from pathlib import Path; [ast.parse(Path(p).read_text(), filename=p) for p in ("scripts/mother_cat.py","tools/scraper_tools.py")]; d=json.loads(Path("assets/trails/public_walk.yaml").read_text()); print("syntax=ok", "stops="+str(len(d["stops"])), "guidance_homework="+str(sum(any(x in s["guidance"].lower() for x in ("fingerprint", "when you have tried", "decant")) for s in d["stops"])))'
.venv/bin/python -B -c 'exec("import contextlib,copy,io,sys\nfrom pathlib import Path\nsys.path.insert(0, \"scripts\")\nimport mother_cat as m\nif not hasattr(m, \"_intro_eligible\"):\n    print(\"intro_helpers=absent\")\nelse:\n    p=m.REPO_ROOT/\"assets/trails/public_walk.yaml\"\n    t=m.walk.load_trail(p)\n    changed=copy.deepcopy(t)\n    changed[\"stops\"][0][\"url\"]=\"https://example.invalid/\"\n    scope=[m._intro_eligible(p,t),not m._intro_eligible(m.REPO_ROOT/\"__intro_probe__/public_walk.yaml\",t),not m._intro_eligible(p,changed)]\n    calls=[]\n    m._decant_to_clipboard=lambda payload: calls.append(\"automatic\") or True\n    m._decant_checkpoint=lambda payload,captured: calls.append(\"manual\") or True\n    c=[(str(i),u,{}) for i,u in enumerate(m.INTRO_URLS)]\n    with contextlib.redirect_stdout(io.StringIO()):\n        a=m._complete_preview(\"fixture\",c,intro=True)\n        z=m._complete_preview(\"fixture\",c)\n        r=m._complete_preview(\"fixture\",c[:-1]+[(\"3\",\"https://example.invalid/\",{})],intro=True)\n    print(\"scope=\"+str(sum(scope))+\"/3\", \"handoff_routes=\"+str(a and z and not r and calls==[\"automatic\",\"manual\"]))\n    assert all(scope) and a and z and not r and calls==[\"automatic\",\"manual\"], \"intro policy regression\"\n")'
.venv/bin/python -B -c 'exec("import hashlib\nfrom pathlib import Path\nfrom urllib.request import Request,urlopen\nfor i in (1,2,3):\n    local=Path(f\"remotes/honeybot/www/npvg.org/walk/{i}/index.html\").read_bytes()\n    req=Request(f\"https://npvg.org/walk/{i}/\",headers={\"Cache-Control\":\"no-cache\"})\n    with urlopen(req,timeout=10) as response:\n        remote=response.read(65537)\n        status=response.status\n    assert len(remote)<=65536, \"unexpected page size\"\n    print(\"page=\"+str(i),\"status=\"+str(status),\"local=\"+hashlib.sha256(local).hexdigest(),\"served=\"+hashlib.sha256(remote).hexdigest(),\"match=\"+str(local==remote))\n")'
syntax=ok stops=3 guidance_homework=2
intro_helpers=absent
page=1 status=200 local=b3ffb8b6a4ea7e0a3029d6d6edd10ae7d7b5d2a6899f5a6ed22cf4b96188f949 served=b3ffb8b6a4ea7e0a3029d6d6edd10ae7d7b5d2a6899f5a6ed22cf4b96188f949 match=True
page=2 status=200 local=791af9a6d3c8a0a759aa583936812904c03429f91522291972df92e8f50abc74 served=791af9a6d3c8a0a759aa583936812904c03429f91522291972df92e8f50abc74 match=True
page=3 status=200 local=3efd829dd9f0e522e6427ac6ce72309371e77c78b0c4d2672a17688fb291b124 served=3efd829dd9f0e522e6427ac6ce72309371e77c78b0c4d2672a17688fb291b124 match=True
(nix) pipulate $ 
```

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

```text
# AD HOC CHOP! The Not-Managed-by-Git Safe-for-Client-Data place. Insert Simpson Couch Gag in white space  below (explain anything to the audience you feel needs it explained)G
# adhoc.txt    _   _   _               ____ _   _  ___  ____  _   
#     / \   __| | | | | | ___   ___   / ___| | | |/ _ \|  _ \| |  Continuing polishing of 1st 5 minute experience.
# ahe/ _ \ / _` | | |_| |/ _ \ / __| | |   | |_| | | | | |_) | |  Getting there.
# ahc ___ \ (_| | |  _  | (_) | (__  | |___|  _  | |_| |  __/|_|  Wow the mother of all patches is coming up!
#  /_/   \_\__,_| |_| |_|\___/ \___|  \____|_| |_|\___/|_|   (_)  

# To Human: WELCOME TO VIM! It's really NeoVim but I say vim anyway.
# To AI: This is an alternate 40K view to the big book-ore rolling pin spine.

# 1. **Probe**: Baseline Reading
# 2. **Context**: Post-experiment *planned* reading instructions
# 3. **Patch**: The experiment and how to make it happen
# 4. **Prompt**: Post-experiment instructions and how to read results
# 5. **Deliverable**: How the world is forever different moving forward

# The first thing you need to know here is that everything that comes after the
# hash symbol (#) is commented out — and that's EVERYTHING in this file's default
# state. Begin editing-in lines for inclusion as part of the context or adding
# chunks of new context at the bottom. `Ctrl`+`v`, `j` (repeatedly), `l` (to move
# right), `d` (to delete). Reverse that with `Ctrl`+`v`, `j` (repeatedly),
# `Shift`+`i`, `# `, `Esc` to put the hashes back. You can just arrow-key around
# here with `h`, `j`, `k`, `l`. Save-and-quit is a bit tricky because another
# file is also loaded: `Esc`, `:`, `q`, `w`, `!`

# If this is stressing you out and you're a quitter and want to quit, just type:
# `Esc`, `:`, `q`, `!`, `Enter`. That will exit without saving any changes. If
# you want to get over this hump, type: `Esc`, `:`, `T`, `u`, `t`, `o`, `r`, `Enter`.

# This file is just to make it easy having options of what to edit into context.
# You can use whatever text-file you want to stack file-names and commands to
# build an output text-file with the identically stacked output of each file or
# command. In this way we vertically append or "stack" a bunch of text; simple as
# that. If you understand this concept, you're on your way to future-proofing
# yourself in the Age of AI. Congratulations! Here is how to include web pages:

#    !URL  --------------------------------------------------------------------
#      when    Public page; what a stranger or crawler sees; the BEFORE of a
#              login-wall diagnosis
#      switch  It shows a login page -> `warm URL` once, then `?URL`
#    
#    ?URL  --------------------------------------------------------------------
#      when    Anything behind a login, on the site's persistent profile;
#              `check URL` first
#      switch  The lenses show a shell (nav, an `[Iframe]` leaf, no content) ->
#              read the wire truth for the XHR the frame makes, then call that
#              API with a connector
#    
#    @URL  --------------------------------------------------------------------
#      when    Every re-read of a page already scraped; no browser, no network
#      switch  The cached page is stale or was a login wall -> fresh `!` or `?`
#    
#    $URL  --------------------------------------------------------------------
#      when    Exact markup: meta tags, a JSON blob in a `<script>`
#      note    Token-heavy; needs a prior scrape
#    
#    %URL  --------------------------------------------------------------------
#      when    The network log distilled; SPA endpoint discovery
#      switch  It re-serves the wire truth you already have -> the API
#    
#    ! cmd  -------------------------------------------------------------------
#      when    Any bounded, non-interactive command as a live receipt
#      note    Cap it with `-n`; no aliases, no prompts
#    
#    Connector  ---------------------------------------------------------------
#      when    The number you want is one GET away
#      switch  LIST until the thing isn't in the list -> FETCH by id -> DRILL
#              the path the app's own frame called -> `--grep` to narrow a list
#              or find a leaf

# Every step is one argument longer than the last; the moment a lens shows less than the wire does is the moment to stop scraping.

# FOR 40K-FT VIEW (STORY & INFRASTRUCTURE) 
# --- START EDITING-IN ON 1ST TURN ---

# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- ROLLING PIN that gives the 40K foot book-spine view of book-ore (only works for me because of local-only git repo)
# ~/repos/nixos/autognome.py  # <-- You wake up in the morning and your Tooling & Instrumentation folds out of you like Inspector Gadget.
# init.lua                    # <-- Those gadgets are made easy-to-use through nifty keyboard shortcuts (but ya gotta learn vim).
# GLOSSARY.md                 # <-- Like the back of a J.R.R. Tolkien book, there's kooky new terms to know.
# flake.nix                   # <-- Here is my hardware. Here is my state. Put on your sandbox. And please recreate. (Infrastructure as Code / IaC)
# prompt_foo.py               # <-- THIS system
# foo_files.py                # <-- main ROUTER
# requirements.in             # <-- We've "pinned" everything but still want a flexible Python Data Science virtualenv.
# pyproject.toml              # <-- How this is a citizen of the Python "pip install" ecosystem
# __init__.py                 # <-- Version info

# --- END EDITING-IN ON 1ST TURN ---

# scripts/articles/lsa.py     # <-- 2ND BRAIN: Search external memory with `rgx`, `rgxc` & `posts` Blogging for Hackers Jekyll-compatible.

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

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

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

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

# Always include these with whatever connector
# scripts/sources_menu.py
# scripts/connectors/README.md
# scripts/connectors/wallet.py

# scripts/connectors/botify.py
# scripts/connectors/confluence.py
# scripts/connectors/gmail.py
# scripts/connectors/gsc.py
# scripts/connectors/jira.py
# scripts/connectors/mcp.py
# scripts/connectors/mcp_warm.py
# scripts/connectors/sheets.py
# scripts/connectors/slack.py

# --- START THIS DISCUSSION ---

# Get things started here! Guess at what context should be included.
# If you get it wrong, you're just wasting 1-turn because the AI will help.
# Un-comment lines, add lines with absolute-path filenames or `! ` commands. 

# Context 1
# /home/mike/repos/trimnoir/_posts/2026-09-15-the-walk-that-teaches-walks.md  # [Idx: 1 | Order: 2 | Tokens: 67,298 | Bytes: 267,245]
# /home/mike/repos/trimnoir/_posts/2026-09-15-first-five-minutes-verifiable-workflows.md  # [Idx: 2 | Order: 3 | Tokens: 65,707 | Bytes: 256,446]
# foo_files.py
# walk
# assets/installer/mck.sh
# scripts/mother_cat.py
# scripts/walk.py
# scripts/walk_cartridge.py
# imports/voice_synthesis.py
# tools/scraper_tools.py
# prompt_foo.py
# assets/trails/public_walk.yaml
# remotes/honeybot/www/npvg.org/walk/1/index.html
# remotes/honeybot/www/npvg.org/walk/2/index.html
# remotes/honeybot/www/npvg.org/walk/3/index.html
# nixops.sh

# Context 2
# foo_files.py
# walk
# assets/installer/mck.sh
# scripts/mother_cat.py
# scripts/walk.py
# scripts/walk_cartridge.py
# imports/voice_synthesis.py
# tools/scraper_tools.py
# prompt_foo.py
# assets/trails/public_walk.yaml
# remotes/honeybot/www/npvg.org/walk/1/index.html
# remotes/honeybot/www/npvg.org/walk/2/index.html
# remotes/honeybot/www/npvg.org/walk/3/index.html
# nixops.sh
# ! bash -n walk && bash -n assets/installer/mck.sh && .venv/bin/python -B -c 'import ast; from pathlib import Path; [ast.parse(Path(p).read_text(), filename=p) for p in ("scripts/mother_cat.py", "imports/voice_synthesis.py", "tools/scraper_tools.py")]; print("shell_and_python_syntax=ok")'
# ! .venv/bin/python -B -c 'import ast; from pathlib import Path; t=ast.parse(Path("imports/voice_synthesis.py").read_text()); calls=sorted((n for n in ast.walk(t) if isinstance(n,ast.Call) and isinstance(n.func,ast.Attribute) and isinstance(n.func.value,ast.Name) and n.func.value.id=="subprocess" and n.func.attr=="Popen"),key=lambda n:n.lineno); print("playback_calls="+str(len(calls))); [print("line="+str(n.lineno),"stdin="+next((ast.unparse(k.value) for k in n.keywords if k.arg=="stdin"),"INHERITED")) for n in calls]'
# ! .venv/bin/python -B -c 'import fcntl,os,pty,shutil,subprocess,sys,termios; sys.platform.startswith("linux") or sys.exit("NOT_TESTED: this probe needs Linux/ALSA"); exe=shutil.which("play"); exe or sys.exit("NOT_TESTED: play is missing"); v=subprocess.run([exe,"--version"],capture_output=True,text=True,timeout=5); print("player="+exe); print((v.stdout+v.stderr).strip()[:200]); m,s=pty.openpty(); before=fcntl.fcntl(s,fcntl.F_GETFL); tty=termios.tcgetattr(s); p=subprocess.run([exe,"-n","synth","0.05","sine","440"],stdin=s,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,env=dict(os.environ,AUDIODRIVER="alsa",AUDIODEV="null"),timeout=5); after=fcntl.fcntl(s,fcntl.F_GETFL); print("player_rc="+str(p.returncode),"nonblocking_before="+str(bool(before&os.O_NONBLOCK)),"nonblocking_after="+str(bool(after&os.O_NONBLOCK)),"termios_changed="+str(tty!=termios.tcgetattr(s))); os.close(s); os.close(m); print("OBSERVED" if p.returncode==0 else "NOT_TESTED: "+repr(p.stderr.decode("utf-8","replace")[-200:]))'

# Context 3
# foo_files.py
# walk
# assets/installer/mck.sh
# scripts/mother_cat.py
# scripts/walk.py
# scripts/walk_cartridge.py
# imports/voice_synthesis.py
# tools/scraper_tools.py
# prompt_foo.py
# assets/trails/public_walk.yaml
# remotes/honeybot/www/npvg.org/walk/1/index.html
# remotes/honeybot/www/npvg.org/walk/2/index.html
# remotes/honeybot/www/npvg.org/walk/3/index.html
# nixops.sh
# ! bash -n walk && bash -n assets/installer/mck.sh && printf "shell_syntax=ok\n"
# ! .venv/bin/python -B -c 'exec("import fcntl,os,pty,shlex,shutil,subprocess,sys,termios\nfrom pathlib import Path\ns=Path(\"assets/installer/mck.sh\").read_text()\ncalls=[x.strip().split(\" || \",1)[0] for x in s.splitlines() if x.lstrip().startswith(\"run_rider --dry-narrate\")]\nassert len(calls)==2, \"practice call roster changed\"\nsetter=\"import fcntl,os; fcntl.fcntl(0,fcntl.F_SETFL,fcntl.fcntl(0,fcntl.F_GETFL)|os.O_NONBLOCK)\"\nplayers=[(\"synthetic\",[sys.executable,\"-B\",\"-c\",setter])]\nexe=shutil.which(\"play\")\nif sys.platform.startswith(\"linux\") and exe:\n    players.append((\"installed\",[exe,\"-n\",\"synth\",\"0.05\",\"sine\",\"440\"]))\nelse:\n    print(\"installed=NOT_TESTED: needs Linux play and ALSA null\")\nfor mode,argv in players:\n    for label,call in zip((\"assume_yes\",\"menu\"),calls):\n        m,t=pty.openpty()\n        try:\n            before=fcntl.fcntl(t,fcntl.F_GETFL)\n            attrs=termios.tcgetattr(t)\n            script=\"set -eu\\nexec 3<&0\\nrun_rider() { \"+shlex.join(argv)+\"; }\\n\"+call+\"\\n\"\n            p=subprocess.run([\"bash\",\"-c\",script],stdin=t,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,timeout=5,start_new_session=True,env=dict(os.environ,BASH_ENV=os.devnull,ENV=os.devnull,AUDIODRIVER=\"alsa\",AUDIODEV=\"null\"))\n            after=fcntl.fcntl(t,fcntl.F_GETFL)\n            print(mode,label,\"rc=\"+str(p.returncode),\"nonblocking=\"+str(bool(before&os.O_NONBLOCK))+\"->\"+str(bool(after&os.O_NONBLOCK)),\"termios_changed=\"+str(attrs!=termios.tcgetattr(t)))\n            if p.returncode:\n                print(\"NOT_TESTED: \"+repr(p.stderr.decode(\"utf-8\",\"replace\")[-160:]))\n        finally:\n            os.close(t)\n            os.close(m)\n")'
# ! .venv/bin/python -B -c 'exec("import os,pty,subprocess\nfrom pathlib import Path\ns=Path(\"assets/installer/mck.sh\").read_text()\na=s.index(\"    ANSWER=\\\"\\\"\",s.index(\"  while :; do\"))\nb=s.index(\"    case \\\"$ANSWER\\\" in\",a)\nscript=\"set -eu\\nexec 3<&0\\n\"+s[a:b]\nfor label in (\"eof\",\"read_error\"):\n    m,t=pty.openpty()\n    try:\n        os.set_blocking(t,False)\n        p=subprocess.run([\"bash\",\"-c\",script],stdin=subprocess.DEVNULL if label==\"eof\" else t,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=3,env=dict(os.environ,BASH_ENV=os.devnull,ENV=os.devnull,LC_ALL=\"C\"))\n        print(label,\"exit=\"+str(p.returncode),\"reported_failure=\"+str(b\"Menu input ended or failed\" in p.stderr),\"reported_clean_stop=\"+str(b\"Stopped. No real walk started.\" in p.stdout))\n    finally:\n        os.close(t)\n        os.close(m)\n")'

**Context 3**
foo_files.py
walk
assets/installer/mck.sh
scripts/mother_cat.py
scripts/walk.py
scripts/walk_cartridge.py
scripts/connectors/noop.py
imports/voice_synthesis.py
tools/scraper_tools.py
prompt_foo.py
assets/trails/public_walk.yaml
remotes/honeybot/www/npvg.org/walk/1/index.html
remotes/honeybot/www/npvg.org/walk/2/index.html
remotes/honeybot/www/npvg.org/walk/3/index.html
nixops.sh
release.py
! bash -n walk && bash -n assets/installer/mck.sh && .venv/bin/python -B -c 'import ast,json; from pathlib import Path; [ast.parse(Path(p).read_text(), filename=p) for p in ("scripts/mother_cat.py","tools/scraper_tools.py")]; d=json.loads(Path("assets/trails/public_walk.yaml").read_text()); print("syntax=ok", "stops="+str(len(d["stops"])), "guidance_homework="+str(sum(any(x in s["guidance"].lower() for x in ("fingerprint", "when you have tried", "decant")) for s in d["stops"])))'
! .venv/bin/python -B -c 'exec("import contextlib,copy,io,sys\nfrom pathlib import Path\nsys.path.insert(0, \"scripts\")\nimport mother_cat as m\nif not hasattr(m, \"_intro_eligible\"):\n    print(\"intro_helpers=absent\")\nelse:\n    p=m.REPO_ROOT/\"assets/trails/public_walk.yaml\"\n    t=m.walk.load_trail(p)\n    changed=copy.deepcopy(t)\n    changed[\"stops\"][0][\"url\"]=\"https://example.invalid/\"\n    scope=[m._intro_eligible(p,t),not m._intro_eligible(m.REPO_ROOT/\"__intro_probe__/public_walk.yaml\",t),not m._intro_eligible(p,changed)]\n    calls=[]\n    m._decant_to_clipboard=lambda payload: calls.append(\"automatic\") or True\n    m._decant_checkpoint=lambda payload,captured: calls.append(\"manual\") or True\n    c=[(str(i),u,{}) for i,u in enumerate(m.INTRO_URLS)]\n    with contextlib.redirect_stdout(io.StringIO()):\n        a=m._complete_preview(\"fixture\",c,intro=True)\n        z=m._complete_preview(\"fixture\",c)\n        r=m._complete_preview(\"fixture\",c[:-1]+[(\"3\",\"https://example.invalid/\",{})],intro=True)\n    print(\"scope=\"+str(sum(scope))+\"/3\", \"handoff_routes=\"+str(a and z and not r and calls==[\"automatic\",\"manual\"]))\n    assert all(scope) and a and z and not r and calls==[\"automatic\",\"manual\"], \"intro policy regression\"\n")'
! .venv/bin/python -B -c 'exec("import hashlib\nfrom pathlib import Path\nfrom urllib.request import Request,urlopen\nfor i in (1,2,3):\n    local=Path(f\"remotes/honeybot/www/npvg.org/walk/{i}/index.html\").read_bytes()\n    req=Request(f\"https://npvg.org/walk/{i}/\",headers={\"Cache-Control\":\"no-cache\"})\n    with urlopen(req,timeout=10) as response:\n        remote=response.read(65537)\n        status=response.status\n    assert len(remote)<=65536, \"unexpected page size\"\n    print(\"page=\"+str(i),\"status=\"+str(status),\"local=\"+hashlib.sha256(local).hexdigest(),\"served=\"+hashlib.sha256(remote).hexdigest(),\"match=\"+str(local==remote))\n")'

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

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

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

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/mother_cat.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/installer/mck.sh'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'walk'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/trails/public_walk.yaml'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/trails/public_walk.yaml'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/trails/public_walk.yaml'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/trails/public_walk.yaml'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'assets/trails/public_walk.yaml'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'tools/scraper_tools.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'remotes/honeybot/www/npvg.org/walk/1/index.html'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'remotes/honeybot/www/npvg.org/walk/2/index.html'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'remotes/honeybot/www/npvg.org/walk/2/index.html'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'remotes/honeybot/www/npvg.org/walk/3/index.html'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'remotes/honeybot/www/npvg.org/walk/3/index.html'.
(nix) pipulate $ d
diff --git a/assets/installer/mck.sh b/assets/installer/mck.sh
index c9c63086..dba16703 100644
--- a/assets/installer/mck.sh
+++ b/assets/installer/mck.sh
@@ -82,15 +82,12 @@
 #            offer, no browser, no voice, no writes, no network. This is the
 #            probe that makes marker discovery witnessable without needing a
 #            fresh machine.
-#   --yolo   skip INSTALL confirmation and the walk menu. It does NOT
-#            skip the CAPTURE fence at any stop, nor the DECANT gate at the
-#            end, and no flag ever will. --yolo is typed BEFORE the ride, so
-#            it cannot consent to the disposition of material that did not
-#            exist when it was typed; and it was never unattended anyway,
-#            because the CAPTURE fences already block.
-#            CEREMONY IS SKIPPABLE; BARRIERS ARE NOT: a confirmation
-#            authorizes a SEQUENCE, a fence authorizes each WRITE, and the
-#            unfenced capture lane already exists under other names.
+#   --yolo   skip INSTALL confirmation and the menu; keep every CAPTURE.
+#            For the bundled introduction it accepts the printed summary
+#            and clipboard terms, as does choosing 2. Other trails retain
+#            DECANT. ASSUME_YES rehearses first under the same policy.
+#            The rider's read-only --intro-contract determines eligibility;
+#            a same-named private trail does not inherit this policy.
 #
 # EXIT CODES: 0 rode or explicit stop; nonzero usage, refusal, input or rider failure.
 if [ -z "${BASH_VERSION:-}" ]; then
@@ -499,7 +496,11 @@ run_wrapped() {
 # ONE SPELLING FOR BOTH RIDER CALLS, so the rehearsal and the ride can never
 # read different exports files. The flag rides only when a file resolved; the
 # empty case expands no array (bash 3.2 + set -u, the trap NIXWRAP dodges).
+INTRO_CONTRACT="$("$PY" scripts/mother_cat.py "$TRAIL_PATH" --intro-contract)"
 run_rider() {
+  if [ -n "$INTRO_CONTRACT" ]; then
+    set -- --intro "$@"
+  fi
   if [ -n "$EXPORTS_PATH" ]; then
     run_wrapped "$PY" scripts/mother_cat.py "$TRAIL_PATH" --exports "$EXPORTS_PATH" "$@"
   else
@@ -510,10 +511,15 @@ run_rider() {
 # player left inherited stdin nonblocking. Both rehearsals get /dev/null,
 # not the menu or caller input; fd 3 is closed in the child as well. This
 # does not change shared voice callers or the real ride's /dev/tty input.
+if [ -n "$INTRO_CONTRACT" ]; then
+  printf '\n%s\n' "$INTRO_CONTRACT"
+else
+  printf '\nCAPTURE saves each page. DECANT asks before saving a summary or copying it.\n'
+fi
 if [ "$YOLO" -eq 1 ]; then
-  echo "--yolo: real walk; CAPTURE and DECANT are still required."
+  echo "Starting the real walk. CAPTURE is still required at each page."
 elif [ "${PIPULATE_MCK_ASSUME_YES:-0}" = "1" ]; then
-  echo "ASSUME_YES: practice, then the real walk; CAPTURE and DECANT still required."
+  echo "Practice first, then the real walk. CAPTURE is still required at each page."
   run_rider --dry-narrate </dev/null 3<&-
 else
   if ! { exec 3</dev/tty; } 2>/dev/null; then
@@ -522,8 +528,8 @@ else
   fi
   while :; do
     printf '\nChoose a walk:\n'
-    printf '  1  Practice walk  - voice and instructions; no browser or page capture.\n'
-    printf '  2  Walk the walk  - open the browser; CAPTURE and DECANT still required.\n'
+    printf '  1  Practice - hear the steps; no pages open.\n'
+    printf '  2  Start the walk - open the pages.\n'
     printf '  q  Exit (Enter also exits).\nChoice: '
     ANSWER=""
     # Preserve failure instead of converting it into a successful stop.
@@ -569,24 +575,11 @@ run_rider </dev/tty || RIDE_RC=$?
 if [ "$RIDE_RC" -eq 0 ]; then
   cat <<'CARD'
 --------------------------------------------------------------
-   RIDE COMPLETE
+   CAPTURE RUN FINISHED
 --------------------------------------------------------------
- Every stop that OPENED produced a capture receipt. An optional
- stop whose URL you had not exported was skipped; the rider
- said which, above, and the bundle lists it as skipped.
-
- Whether the bundle LEFT this machine depends on the DECANT
- gate you just answered. This script cannot see your clipboard,
- so it does not claim to. Read the rider's own last line:
-
-   AUTHORIZED  you permitted a checked preview handoff; this alone
-               does not prove a clipboard write. Read its receipt.
-   BLOCKED     the preview failed disclosure checks; nothing copied.
-   DECLINED    nothing was copied.
-   REFUSED     no terminal was available to ask; nothing copied.
-  Original cache files remain under browser_cache/. Banked bytes
-  are in data/captures/; the rider prints the exact captures.md path.
-  That local archive is UNSANITIZED. Nothing was uploaded by this script.
+ Read the save and copy messages above. Either step can fail.
+ Review the summary before sharing it.
+ Nothing was sent to a chatbot.
 --------------------------------------------------------------
 CARD
   if [ "$DID_INSTALL" -eq 1 ]; then
diff --git a/assets/trails/public_walk.yaml b/assets/trails/public_walk.yaml
index 0f020835..cfcc2ef0 100644
--- a/assets/trails/public_walk.yaml
+++ b/assets/trails/public_walk.yaml
@@ -1,7 +1,7 @@
 {
   "schema_version": 1,
   "name": "public_walk",
-  "description": "Welcome to the public walk. It has three short pages, with nothing to log in to and nothing to set up. At each stop a browser will open on one page. Read the page, then come back to this terminal and type the word CAPTURE when it asks.",
+  "description": "This walk opens three pages. You do not need to click anything. At each page, return here and wait for the CAPTURE prompt. Type CAPTURE and press Enter.",
   "defaults": {
     "take_screenshot": false,
     "headless": false,
@@ -16,7 +16,7 @@
     {
       "name": "the_word",
       "label": "The capture word",
-      "guidance": "Stop one of three. A browser will open on a plain page with no script on it. The page will explain the one word this walk asks of you. When you have read it, come back to this terminal and type CAPTURE when it asks.",
+      "guidance": "I'll open page one. When it is ready, return here. At the CAPTURE prompt, type CAPTURE and press Enter.",
       "url": "https://npvg.org/walk/1/",
       "target_slot": "slot_one",
       "harvest_regex": ".+",
@@ -28,8 +28,8 @@
     },
     {
       "name": "the_receipt",
-      "label": "The fingerprints",
-      "guidance": "Stop two of three. By the time the next page opens, the first page will have been saved, with a fingerprint for every file. The page will show you one command that counts those fingerprints, so you can check the terminal's report for yourself. When you have tried it, type CAPTURE.",
+      "label": "Page two",
+      "guidance": "I'll open page two. When it is ready, return here. At the CAPTURE prompt, type CAPTURE and press Enter.",
       "url": "https://npvg.org/walk/2/",
       "target_slot": "slot_two",
       "harvest_regex": ".+",
@@ -42,7 +42,7 @@
     {
       "name": "the_two_pages",
       "label": "Two versions of one page",
-      "guidance": "Stop three of three, the last. This page will change itself after it loads, so the page the server sends and the page your browser shows will not match. Type CAPTURE, and the program will ask for one more word, DECANT, before anything goes to your clipboard. The page will say what to do with the result.",
+      "guidance": "I'll open the last page. When it is ready, return here. At the CAPTURE prompt, type CAPTURE and press Enter. Then read the result here.",
       "url": "https://npvg.org/walk/3/",
       "target_slot": "slot_three",
       "harvest_regex": ".+",
diff --git a/remotes/honeybot/www/npvg.org/walk/1/index.html b/remotes/honeybot/www/npvg.org/walk/1/index.html
index 5799cf66..de40bca7 100644
--- a/remotes/honeybot/www/npvg.org/walk/1/index.html
+++ b/remotes/honeybot/www/npvg.org/walk/1/index.html
@@ -12,16 +12,14 @@
 </style>
 </head>
 <body>
-<p>Stop 1 of 3</p>
-<h1>You are on a walk.</h1>
-<p>A program on your computer opened this page, and a voice read you a short paragraph about it. The voice is a program speaking words a person wrote. It is not listening, and it cannot answer you.</p>
-<p>Nothing on this page is a control. There are no buttons and no links, on purpose. The only control is in your terminal, where the program is waiting for one word:</p>
+<p>Page 1 of 3</p>
+<h1>You are in the right place.</h1>
+<p>There is nothing to click here. Return to the terminal.</p>
+<p>When it shows <code>CAPTURE&gt;</code>, type:</p>
 <p class="word">CAPTURE</p>
-<p>When you type it, the program saves this page to a folder on your own computer, several ways at once: the page exactly as the server sent it, the page as your browser built it, every request your browser made while loading it, and the headers that came back. It records a fingerprint of each saved file, a SHA-256 hash that changes if a single byte changes. Then it moves on to the next stop.</p>
-<p>The program uploads nothing. The saved copies stay on your computer.</p>
-<p>Go back to the terminal and type the word. The rest of this page is for later.</p>
+<p>Press Enter. The program will save this page and open the next one.</p>
 <hr>
-<p><strong>Why a word, and not a button?</strong> A button on this page could be pressed by a script running on this page. The word is typed into a window this page cannot reach, so the record shows it came from the keyboard and not from the page.</p>
-<p><strong>Why is this page so plain?</strong> It has no script. The page the server sent and the page your browser built should carry the same elements. If they do not, something other than this page changed it, and that is worth knowing too. The last stop is the one that changes itself.</p>
+<p>The voice reads written instructions. It does not listen. You can follow the words in the terminal even when the sound is off.</p>
+<p>The copies stay on this computer. No page is submitted to a chatbot.</p>
 </body>
 </html>
diff --git a/remotes/honeybot/www/npvg.org/walk/2/index.html b/remotes/honeybot/www/npvg.org/walk/2/index.html
index 1e429850..e09071c9 100644
--- a/remotes/honeybot/www/npvg.org/walk/2/index.html
+++ b/remotes/honeybot/www/npvg.org/walk/2/index.html
@@ -5,7 +5,7 @@
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <meta name="robots" content="noindex, nofollow">
 <link rel="icon" href="data:,">
-<title>Stop 2 of 3: the fingerprints</title>
+<title>Page 2 of 3: keep going</title>
 <style>
   body  { max-width: 40rem; margin: 3rem auto; padding: 0 1rem; font: 18px/1.5 Georgia, serif; }
   pre   { padding: 1rem; background: #f4f4f4; overflow-x: auto; }
@@ -13,18 +13,13 @@
 </style>
 </head>
 <body>
-<p>Stop 2 of 3</p>
-<h1>The first page is already written down.</h1>
-<p>Before this page opened, your terminal printed two lines worth reading:</p>
-<ul>
-<li>A line starting with <code>LOCAL ARCHIVE</code>. The path after it, up to the parenthesis, is a text file. It holds every file saved at the first stop, each with its size and its fingerprint.</li>
-<li>A line starting with <code>Captured. final_url=</code>. The number after <code>artifacts=</code> on that line is how many files the first stop saved.</li>
-</ul>
-<p>You do not have to take that number on trust. Open a second terminal window and count the fingerprints yourself. Type this, then a space, then paste the path from the <code>LOCAL ARCHIVE</code> line:</p>
-<pre>grep -c '"sha256"'</pre>
-<p>The number it prints should match the number after <code>artifacts=</code>. The program reported something, and a second instrument, one the program did not write, checked the report. That is the whole idea of this workshop in one move.</p>
-<p>If the two numbers disagree, you have found something real. Keep both lines.</p>
-<p>When you are done, go back to the first terminal and type:</p>
+<p>Page 2 of 3</p>
+<h1>Keep going.</h1>
+<p>There is nothing to do on this page. Return to the terminal.</p>
+<p>When it shows <code>CAPTURE&gt;</code>, type:</p>
 <p class="word">CAPTURE</p>
+<p>Press Enter. There is one page left after this.</p>
+<hr>
+<p>You do not need to open another window or run any commands.</p>
 </body>
 </html>
diff --git a/remotes/honeybot/www/npvg.org/walk/3/index.html b/remotes/honeybot/www/npvg.org/walk/3/index.html
index ca8e7562..d6067a70 100644
--- a/remotes/honeybot/www/npvg.org/walk/3/index.html
+++ b/remotes/honeybot/www/npvg.org/walk/3/index.html
@@ -14,8 +14,17 @@
 </style>
 </head>
 <body>
-<p>Stop 3 of 3</p>
-<h1>This page has two versions.</h1>
+<p>Page 3 of 3</p>
+<h1>Last page.</h1>
+<p>Return to the terminal. When it shows <code>CAPTURE&gt;</code>, type:</p>
+<p class="word">CAPTURE</p>
+<p>Press Enter, then read the result in the terminal.</p>
+<p>After the checks pass, the normal walk tries to save and copy a summary. The terminal tells you what worked.</p>
+<p>A saved summary and a successful copy are separate results. Nothing is sent to a chatbot. Review the summary before sharing it.</p>
+<hr>
+<h2>Optional: after the walk</h2>
+<p>You can stop here. The rest is an extra test, not another step.</p>
+<h3>One page, two versions</h3>
 <div id="sentence" class="box"><p>The server sent this sentence. Checkword: periwinkle.</p></div>
 <script>
 var box = document.getElementById("sentence");
@@ -24,17 +33,12 @@ var added = document.createElement("p");
 added.textContent = "Your browser also added this paragraph. The server never sent it.";
 box.appendChild(added);
 </script>
-<p>The box above is not what the server sent. One small script, the only script on any stop of this walk, replaced the server's sentence and added a paragraph after the page arrived. Most of the web does this kind of thing without saying so.</p>
-<p>The server's sentence carried a <strong>checkword</strong>, one ordinary English word. You cannot see it on this page, because the script removed it before the page reached your eyes. It is not a secret: anyone who reads this page's source can find it. It is a label, so you can tell which version of the page a sentence came from.</p>
-<p>Go back to the first terminal and type:</p>
-<p class="word">CAPTURE</p>
-<p>The program will then put together a short preview of what this walk saved and ask for one more word, <strong>DECANT</strong>. Type it, and the preview goes to your clipboard. The program uploads nothing, and the preview reaches your clipboard only if you type that word.</p>
-<h2>The test</h2>
-<p>Paste the preview into the chatbot you started with and ask:</p>
+<p>A small script changed the box after this page arrived. The server's version had a checkword. The version you see does not show it.</p>
+<p>After the walk, you can give a chatbot the saved summary and ask:</p>
 <pre>What is the checkword in the sentence the server sent on stop three?</pre>
-<p>Then check the answer against a file the chatbot cannot change. The preview lists the files saved at each stop under <em>artifacts on disk</em>. Under the third stop, open the one whose path ends in <code>source.html</code>. That is the page exactly as the server sent it. Search it for the word checkword followed by a colon; the word after that is the answer. The file whose path ends in <code>hydrated_dom.html</code> is the page your browser built, and the server's sentence is not in it.</p>
-<p>The preview is trimmed, and it may not carry the server's sentence at all. If it does not, a good answer says so. If the chatbot names a word anyway, compare that word with the file. Either way, the file decides. When you check it, you have tested a machine's account against a record the machine could not edit.</p>
-<hr>
-<p>The terminal named this walk before the first stop. A walk is one small file of addresses and spoken words. Copy it, change the addresses and the words, and you have written a walk of your own.</p>
+<p>Check its answer yourself. In the summary, find the third page's file ending in <code>source.html</code>. Open it and search for <code>Checkword:</code>. The word after the colon is the answer.</p>
+<p>The summary may be trimmed. If the sentence is missing, the chatbot should say so rather than guess. Compare any answer with the saved file.</p>
+<p>This is optional. You have already finished the walk.</p>
+<p>These instructions describe starting with <code>walk</code> and choosing 2. Other ways to start the program may still ask for <code>DECANT</code>. Follow your terminal.</p>
 </body>
 </html>
diff --git a/scripts/mother_cat.py b/scripts/mother_cat.py
index 011078ca..bc931f71 100644
--- a/scripts/mother_cat.py
+++ b/scripts/mother_cat.py
@@ -70,9 +70,8 @@ def _narrate(text, disclosed):
     try:
         if not disclosed:
             result = chip_voice_system.speak_text(
-                "This is the automated trail guide. I read each step aloud; "
-                "you handle only the CAPTURE checkpoint at each stop. Any "
-                "sign-in a stop needs is named in that stop's own guidance."
+                "This is an automatic voice reading written instructions. "
+                "It does not listen or answer questions."
             )
             if isinstance(result, dict) and not result.get("success"):
                 print(
@@ -115,15 +114,10 @@ def _capture_compatible(trail):
 # arrive; returned empty text is present, not missing. The private preview
 # file and clipboard attempt receive the same checked string. The fixed
 # filename denotes the last successful save, not the last attempted ride.
-# NEXT RIDE -- THE FIRST FIVE MINUTES, ON RAILS (not implemented): disclose
-# preview saving and clipboard replacement before choosing the real walk;
-# complete after the final capture and existing checks without another word.
-# Keep CAPTURE synchronization, archive integrity, caps, scrubbing, secret
-# checks, private replacement and honest destination-specific failures.
-# Change narration, public pages, launcher and completion labels together;
-# move fingerprint/checkword exercises after completion. Frame practice as
-# rehearsal. Explicitly scope shared/custom behavior: a private trail can
-# shadow public_walk, so its name alone does not identify the bundled route.
+# INTRODUCTORY COMPLETION: --intro authorizes the checked handoff only for
+# the resolved bundled three-page route. The launcher prints INTRO_NOTICE
+# before the choice; direct callers must pass --intro explicitly. Other
+# calls retain DECANT. Practice never captures, saves a preview or copies.
 DECANT_INLINE_KEYS = (
     "seo_md",
     "headers",
@@ -134,6 +128,27 @@ DECANT_INLINE_KEYS = (
 )
 DECANT_INLINE_CAP = 20000  # chars per inlined lens; the rest lives on disk
 DECANT_PREVIEW_PATH = REPO_ROOT / "data" / "decant-preview.md"
+INTRO_URLS = tuple(f"https://npvg.org/walk/{i}/" for i in (1, 2, 3))
+INTRO_NOTICE = (
+    "This walk opens three public pages. Nothing to sign in to.\n"
+    "Return here and type CAPTURE when prompted at each page.\n"
+    "After all three captures and successful checks, it tries to save a private summary\n"
+    "and tries to replace your clipboard. Over SSH it uses a bridge file.\n"
+    "Nothing is sent to a chatbot. Review the summary before sharing it."
+)
+
+
+def _intro_eligible(trail_path, trail=None):
+    """One authority for launcher disclosure and rider authorization scope."""
+    path = Path(trail_path)
+    if not path.is_absolute():
+        path = REPO_ROOT / path
+    if path.resolve() != REPO_ROOT / "assets" / "trails" / "public_walk.yaml":
+        return False
+    trail = walk.load_trail(path) if trail is None else trail
+    return (tuple(stop.get("url") for stop in trail["stops"]) == INTRO_URLS
+            and trail["defaults"].get("profile_name") == "default"
+            and not _capture_compatible(trail))
 
 
 def _capture_append(archive, record):
@@ -323,24 +338,11 @@ def _decant(captured, previews, skipped=()):
     return "\n".join(parts)
 
 
-# --- THE EGRESS BARRIER -----------------------------------------------------
-# The per-stop CAPTURE token gates each WRITE TO DISK, on the operator's own
-# machine. This gates EGRESS: a composite of authenticated material leaving the
-# machine on the clipboard, with mck.sh then telling the human to paste it into
-# a cloud chat. Different consequence class, therefore a different word.
-#
-# WHY NOT REUSE "CAPTURE": by the time this fires the human has typed CAPTURE
-# once per stop. A fourth identical prompt is answered by MUSCLE MEMORY, not by
-# decision -- and a fence satisfied by habit is not a fence. mck.sh already runs
-# this grammar: INSTALL and RIDE are different words for different acts.
-#
-# NOT SKIPPABLE BY --yolo, and the argument is not merely that barriers are not
-# skippable. (1) --yolo is typed at t=0, before a browser opens; it cannot
-# consent to the disposition of material the consenter had not yet seen.
-# (2) --yolo already blocks at every CAPTURE fence, so no unattended capability
-# exists to lose. (3) The bypass-under-another-name corollary does NOT apply:
-# _decant is the only builder of this composite and _ride_async its only caller,
-# so a flag would not duplicate a shipped capability, it would create one.
+# --- MANUAL HANDOFF --------------------------------------------------------
+# Custom walks and direct calls without --intro still ask for DECANT.
+# The bundled introduction may authorize its handoff before the first page;
+# _complete_preview checks the captured destinations before using that path.
+# Neither mode submits anything to a chatbot or relaxes the disclosure checks.
 DECANT_TOKEN = "DECANT"
 def _print_artifact_homes(captured):
     """Name WHERE the captured material sits, not merely that it exists.
@@ -360,7 +362,7 @@ def _decant_checkpoint(payload, captured):
 
     THE ARMED LINE IS UNCONDITIONAL AND IT IS THE POINT. An armed gate that
     passes silently and a DISARMED gate both print nothing, so this announces
-    its own state and the payload size on every ride before asking anything --
+    its own state and the payload size on each manual handoff before asking --
     the same shape prompt_foo's secrets tripwire uses for the same reason.
 
     THREE OUTCOMES, THREE STRINGS THAT ARE NEVER INTERCHANGEABLE, so a fence
@@ -423,6 +425,18 @@ def _decant_checkpoint(payload, captured):
         "before the preview-file and clipboard attempts."
     )
     return _decant_to_clipboard(payload)
+def _complete_preview(payload, captured, intro=False):
+    """Use explicit introductory authorization, or the existing manual gate."""
+    if not intro:
+        return _decant_checkpoint(payload, captured)
+    if tuple(final_url for _, final_url, _ in captured) != INTRO_URLS:
+        print("   BLOCKED: the walk left its three public pages. Summary not sent.")
+        print("   The local captures remain; any older summary is unchanged.")
+        return False
+    print("\nChecking the summary before saving it and trying the clipboard.")
+    return _decant_to_clipboard(payload)
+
+
 def _write_decant_preview(payload):
     """Atomically replace the private preview; never append or follow its old inode."""
     target = DECANT_PREVIEW_PATH
@@ -450,7 +464,7 @@ def _decant_to_clipboard(payload):
     """Check once, save locally, then attempt the existing clipboard handoff.
 
     Deferred import: prompt_foo drags tiktoken/pydot in at module load, so it is
-    imported HERE, on a real DECANT only -- never on module import or
+    imported HERE, on a real preview handoff only -- never on module import or
     --dry-narrate. Reuse over re-implement: copy_to_clipboard already owns the
     SSH-bridge and the pbcopy/xclip fallbacks.
     """
@@ -458,12 +472,12 @@ def _decant_to_clipboard(payload):
     # Reuse the existing baseline; DECANT has no disclosure-relaxation flags.
     scrubbed, substitutions, leaks = scrub_compile_payload(payload)
     secrets = scan_secrets(scrubbed)
-    print(f"   DECANT checks: substitutions={substitutions} "
+    print(f"   Preview checks: substitutions={substitutions} "
           f"denylist={sum(n for _, n in leaks)} secrets={len(secrets)}")
     if leaks or secrets:
         print("   BLOCKED: preview withheld; local evidence is unchanged.")
         return False
-    # AFTER the human's word and baseline checks: one string, two destinations.
+    # AFTER authorization and baseline checks: one string, two destinations.
     try:
         target = _write_decant_preview(scrubbed)
     except OSError as exc:
@@ -550,24 +564,12 @@ def _missing_url_envs(stops):
     return required, optional
 
 
-def _announce_consent(trail_path):
-    """Print what the WHOLE walk demands, before stop one, plus the DECANT.
-    IMPORTED, NEVER DUPLICATED, AND THE DIRECTION OF THE ARROW IS THE ARGUMENT.
-    walk_cartridge.py duplicates foo_cartridge.py's primitives because a
-    clean-room consumer must be able to fetch ONE file and verify a cartridge.
-    That constraint governs what walk_cartridge may IMPORT; it says nothing
-    about what may import walk_cartridge. mother_cat.py already imports walk,
-    scraper_tools, voice_synthesis and (deferred) prompt_foo -- it is in-repo by
-    construction and can never be fetched standalone -- so this import costs the
-    single-file property nothing, and walk_cartridge still imports only stdlib.
-    Duplicating here would be the actual error. A second implementation can
-    drift, and on the day it does, the surface a human CONSENTS to and the
-    surface the manifest ATTESTS to disagree, so the seal would be signing a
-    projection nobody was ever shown. One derivation, or the seal means nothing.
-    Derived from the trail's BYTES, not from walk.load_trail's validated dict,
-    so what is spoken here is provably what a sealer would hash.
-    THIS IS A DISCLOSURE, NOT A FENCE. Nothing is gated. The ruling is banked
-    beside the call site.
+def _announce_consent(trail_path, intro=False):
+    """Describe capture and handoff, never grant authorization here.
+
+    Custom walks use the same trail projection as walk_cartridge.
+    The bundled introduction uses the shared plain-language contract;
+    --intro is validated separately before any narration or capture.
     """
     try:
         surface = walk_cartridge._derive_consent_surface(trail_path.read_bytes())
@@ -578,6 +580,11 @@ def _announce_consent(trail_path):
         # printing, not a reason to abort a ride the planner already blessed.
         print(f"  (consent surface unavailable: {exc})")
         return
+    if intro:
+        print("\n" + INTRO_NOTICE)
+        print(f"Summary file: {DECANT_PREVIEW_PATH.relative_to(REPO_ROOT)} (private; replaced on save).")
+        print("Checks can miss private details. A blocked check leaves the older file alone.\n")
+        return
     browser = surface["browser"]
     rule = "=" * 66
     print(rule)
@@ -619,19 +626,21 @@ def _print_next_compile(archive_path):
     print(archive_path)
     print("Review locally before compiling; raw bytes are not a safe disclosure.")
 
-async def _ride_async(trail_path, dry_narrate=False, exports_path=None):
+async def _ride_async(trail_path, dry_narrate=False, exports_path=None, intro=False):
     archive = {"path": None, "finished": False, "previews": []}
     try:
-        return await _ride_steps(trail_path, archive, dry_narrate, exports_path)
+        return await _ride_steps(trail_path, archive, dry_narrate, exports_path, intro)
     finally:
         # Exceptions, cancellation and capture failures cannot promote a run.
         # Even an uncatchable kill leaves the initial PARTIAL statement intact.
         _finish_capture_archive(archive, "partial", archive.get("skipped", ()))
 
 
-async def _ride_steps(trail_path, archive, dry_narrate=False, exports_path=None):
+async def _ride_steps(trail_path, archive, dry_narrate=False, exports_path=None, intro=False):
     trail_path = Path(trail_path)
     trail = walk.load_trail(trail_path)
+    if intro and not _intro_eligible(trail_path, trail):
+        raise walk.TrailError("--intro is only for the bundled three-page public walk")
 
     problems = _capture_compatible(trail)
     if problems and not dry_narrate:
@@ -740,22 +749,10 @@ async def _ride_steps(trail_path, archive, dry_narrate=False, exports_path=None)
 
     stops = trail["stops"]
     print(f"Riding trail '{trail['name']}' -- {len(stops)} stop(s).\n")
-    # DISCLOSURE, NOT A FENCE, AND THAT IS THE RULING RATHER THAN AN OVERSIGHT.
-    # mck.sh owns the practice/real-walk choice. CEREMONY IS SKIPPABLE;
-    # BARRIERS ARE NOT: each CAPTURE and DECANT remain in the rider.
-    # This surface prints on both paths; practice does not authorize a ride.
-    # THE DECANT FENCE LANDED, so this comment's earlier claim that nothing
-    # gated the clipboard is RETIRED rather than merely outdated. What the call
-    # buys NOW is disclosure BEFORE the material exists: the rider learns at t=0
-    # that DECANT is a separate choice and that its checked preview still
-    # needs review, before either the file or clipboard is attempted.
-    # SAME-CAR LABEL RULE, PAID LATE AND THEREFORE WORTH BANKING. The fence and
-    # the strings describing it shipped in DIFFERENT rides, so for one ride this
-    # function told every rider "WITHOUT ASKING AGAIN" about a gate that does
-    # ask, and public_walk.yaml's third stop said the same thing in trail data.
-    # That is not stale documentation; it is a lie told at the exact moment the
-    # human decides, and it lied in the EXPENSIVE direction -- understating the
-    # protection and overstating the risk, to a newcomer, on the softball walk.
+    # The launcher discloses INTRO_NOTICE before its real-walk choice and
+    # passes --intro only for the bundled route. Direct callers without
+    # that flag retain DECANT. Practice describes terms but authorizes no
+    # capture or handoff; it returns before either can occur.
 
     # THE DESCRIPTION SPEAKS FIRST (2026-09-05). walk.py has validated
     # trail.description as non-empty since Car A, and nothing read it at
@@ -765,8 +762,11 @@ async def _ride_steps(trail_path, archive, dry_narrate=False, exports_path=None)
     # stops use, and under --dry-narrate too, so the rehearsal opens the
     # way the ride does. The return value carries the one-time disclosure
     # forward, so the guide introduces itself exactly once.
-    disclosed = _narrate(trail["description"], False)
-    _announce_consent(trail_path)
+    rehearsal = "Practice only. In the real walk: " if dry_narrate else ""
+    if dry_narrate:
+        print("Practice only. No pages will open. You do not need to type anything.\n")
+    disclosed = _narrate(rehearsal + trail["description"], False)
+    _announce_consent(trail_path, intro=intro)
     captured = []
     skipped = archive.setdefault("skipped", [])
     for index, stop in enumerate(stops, 1):
@@ -784,7 +784,7 @@ async def _ride_steps(trail_path, archive, dry_narrate=False, exports_path=None)
             skipped.append((stop["name"], skip_vars[stop["name"]]))
             continue
 
-        disclosed = _narrate(stop["guidance"], disclosed)
+        disclosed = _narrate(rehearsal + stop["guidance"], disclosed)
 
         if dry_narrate:
             print("  (dry-narrate: browser and capture skipped)\n")
@@ -837,7 +837,7 @@ async def _ride_steps(trail_path, archive, dry_narrate=False, exports_path=None)
         captured.append((stop["name"], result.get("final_url"), artifacts))
         if problems:
             print("  ARCHIVE INCOMPLETE: " + ", ".join(problems))
-            print("  Details are banked locally; halting without ADVANCE or DECANT.")
+            print("  Saved details remain on this computer. Stopping without a summary handoff.")
             return 1
         print(
             f"  Captured. final_url={result.get('final_url')} "
@@ -863,22 +863,22 @@ async def _ride_steps(trail_path, archive, dry_narrate=False, exports_path=None)
         print("\nRide complete. Every stop produced a capture receipt.")
     if captured:
         payload = _decant(captured, archive["previews"], skipped)
-        decanted = _decant_checkpoint(payload, captured)
+        decanted = _complete_preview(payload, captured, intro=intro)
         # ATTRIBUTED-VOICE, fixed in passing because these are the exact lines
         # being rewritten: the old text asserted "copied to your clipboard"
         # UNCONDITIONALLY, one statement after calling a function that swallows
         # every clipboard failure and returns None -- a verb naming an act no
         # code in this file performed. copy_to_clipboard prints its own success
         # or warning line; this reports only what IT witnessed, which is the
-        # human's authorization.
+        # checked handoff attempt, not success at either destination.
         if decanted:
-            print("   Review the preview before sharing it with a chatbot.")
-            print("   Ask it to separate what the files show from what it infers.")
+            print("   Read the save and copy messages above; either step can fail.")
+            print("   Review the summary before sharing it. You choose what to send.")
         # The archive file line was printed when its status was banked.
     return 0
 
 
-def ride(trail_path=None, dry_narrate=False, exports_path=None):
+def ride(trail_path=None, dry_narrate=False, exports_path=None, intro=False):
     """Run one validated trail to completion and return a process exit code."""
     if trail_path is None:
         path = walk.DEFAULT_TRAIL
@@ -890,7 +890,7 @@ def ride(trail_path=None, dry_narrate=False, exports_path=None):
             # must resolve identically from any directory (UNNAMED-ROOT).
             path = REPO_ROOT / path
     return asyncio.run(
-        _ride_async(path, dry_narrate=dry_narrate, exports_path=exports_path)
+        _ride_async(path, dry_narrate=dry_narrate, exports_path=exports_path, intro=intro)
     )
 
 
@@ -1097,11 +1097,15 @@ def main(argv=None):
             "a relative PATH anchors to the repository root"
         ),
     )
+    parser.add_argument("--intro", action="store_true",
+                        help="authorize checked summary saving and clipboard replacement for the bundled public walk")
+    parser.add_argument("--intro-contract", action="store_true",
+                        help="read-only: print introductory terms if this is the bundled route; otherwise print nothing")
     parser.add_argument("--disclose", metavar="CAPTURES_MD",
                         help="write a private review-text disclosure; no browser or clipboard")
     args = parser.parse_args(argv)
     if args.disclose is not None:
-        if args.trail or args.dry_narrate or args.exports:
+        if args.trail or args.dry_narrate or args.exports or args.intro or args.intro_contract:
             parser.error("--disclose cannot be combined with ride arguments")
         try:
             return _disclose_capture(args.disclose)
@@ -1114,7 +1118,14 @@ def main(argv=None):
             return 2
 
     try:
-        return ride(args.trail, dry_narrate=args.dry_narrate, exports_path=args.exports)
+        if args.intro_contract:
+            if args.intro or args.dry_narrate or args.exports:
+                parser.error("--intro-contract cannot be combined with ride options")
+            if _intro_eligible(args.trail or walk.DEFAULT_TRAIL):
+                print(INTRO_NOTICE)
+            return 0
+        return ride(args.trail, dry_narrate=args.dry_narrate,
+                    exports_path=args.exports, intro=args.intro)
     except walk.TrailError as exc:
         print(f"TRAIL INVALID (Car A refused): {exc}")
         return 2
diff --git a/tools/scraper_tools.py b/tools/scraper_tools.py
index 58cf18c8..6f506345 100644
--- a/tools/scraper_tools.py
+++ b/tools/scraper_tools.py
@@ -266,7 +266,7 @@ def _capture_checkpoint(stdin=None, stdout=None) -> dict:
 
         try:
             output_stream.write(
-                "\nNavigate in the visible browser, then type CAPTURE and press Enter.\n"
+                "\nWhen the page you want is ready, type CAPTURE and press Enter.\n"
                 "Any other response aborts without capturing artifacts.\n"
                 "CAPTURE> "
             )
diff --git a/walk b/walk
index 34c3e9d7..f48ca012 100644
--- a/walk
+++ b/walk
@@ -17,8 +17,9 @@
 # browser and writes nothing. The ride lives in assets/installer/mck.sh,
 # which owns workshop discovery, the install offer, the trail search path,
 # entry into nix develop when it is needed, the practice/real-walk choice,
-# every per-stop CAPTURE fence, and the final DECANT gate. The launcher owns
-# which ceremony is optional; this wrapper never reimplements those choices.
+# every per-stop CAPTURE fence, and the handoff policy: checked completion
+# for the bundled introduction, DECANT for other trails. This wrapper never
+# reimplements those choices.
 #
 # IT DOES NOT KNOW THE DEFAULT TRAIL. mck.sh already defaults TRAIL_NAME to
 # public_walk. Repeating that rule here would mint a second authority for
(nix) pipulate $ m
📝 Committing: feat: introduce --intro contract for the bundled public walk
[main 0a635eb3] feat: introduce --intro contract for the bundled public walk
 8 files changed, 158 insertions(+), 156 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 45, done.
Counting objects: 100% (45/45), done.
Delta compression using up to 48 threads
Compressing objects: 100% (18/18), done.
Writing objects: 100% (23/23), 6.21 KiB | 2.07 MiB/s, done.
Total 23 (delta 13), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (13/13), completed with 11 local objects.
To github.com:pipulate/pipulate.git
   395910dd..0a635eb3  main -> main
(nix) pipulate $ 
```

Wow! That was impressive. That was the biggest AI-patch I ever applied.

It sounds like ignition is saying don't do `nixops` but instead do this:

```bash
(nix) pipulate $ (
  set -e
  target="$(.venv/bin/python -B -c 'import shlex; from pathlib import Path; rows=[line.split("=",1)[1] for line in Path("nixops.sh").read_text().splitlines() if line.startswith("TARGET=")]; assert len(rows)==1, "expected one deployment target"; print(shlex.split(rows[0])[0])')"
  test -n "$target"
  rsync -av remotes/honeybot/www/npvg.org/walk/ "$target:~/www/npvg.org/walk/"
)
sending incremental file list
1/index.html
2/index.html
3/index.html

sent 4,631 bytes  received 142 bytes  9,546.00 bytes/sec
total size is 4,312  speedup is 0.90
(nix) pipulate $ 
```

Okay and now we test.

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

nothing to commit, working tree clean
(nix) pipulate $ bash walk; printf 'walk_exit=%s\n' "$?"
Trail resolved: assets/trails/public_walk.yaml

This walk opens three public pages. Nothing to sign in to.
Return here and type CAPTURE when prompted at each page.
After all three captures and successful checks, it tries to save a private summary
and tries to replace your clipboard. Over SSH it uses a bridge file.
Nothing is sent to a chatbot. Review the summary before sharing it.

Choose a walk:
  1  Practice - hear the steps; no pages open.
  2  Start the walk - open the pages.
  q  Exit (Enter also exits).
Choice: 2
Riding trail 'public_walk' -- 3 stop(s).

  This walk opens three pages. You do not need to click anything. At each page, return here and wait for the CAPTURE prompt. Type CAPTURE and press Enter.
Missing phoneme from id map: ̩

This walk opens three public pages. Nothing to sign in to.
Return here and type CAPTURE when prompted at each page.
After all three captures and successful checks, it tries to save a private summary
and tries to replace your clipboard. Over SSH it uses a bridge file.
Nothing is sent to a chatbot. Review the summary before sharing it.
Summary file: data/decant-preview.md (private; replaced on save).
Checks can miss private details. A blocked check leaves the older file alone.

--- Stop 1/3: the_word ---
  I'll open page one. When it is ready, return here. At the CAPTURE prompt, type CAPTURE and press Enter.
Opening the browser; waiting for the page...

When the page you want is ready, type CAPTURE and press Enter.
Any other response aborts without capturing artifacts.
CAPTURE> CAPTURE
  LOCAL ARCHIVE  /home/mike/repos/pipulate/data/captures/walk-f6yb8h7g/captures.md  (directory 0700, file 0600)
  Captured. final_url=https://npvg.org/walk/1/ artifacts=12
  ADVANCE -> next stop.

--- Stop 2/3: the_receipt ---
  I'll open page two. When it is ready, return here. At the CAPTURE prompt, type CAPTURE and press Enter.
Opening the browser; waiting for the page...

When the page you want is ready, type CAPTURE and press Enter.
Any other response aborts without capturing artifacts.
CAPTURE> CAPTURE
  Captured. final_url=https://npvg.org/walk/2/ artifacts=12
  ADVANCE -> next stop.

--- Stop 3/3: the_two_pages ---
  I'll open the last page. When it is ready, return here. At the CAPTURE prompt, type CAPTURE and press Enter. Then read the result here.
Opening the browser; waiting for the page...

When the page you want is ready, type CAPTURE and press Enter.
Any other response aborts without capturing artifacts.
CAPTURE> CAPTURE
  Captured. final_url=https://npvg.org/walk/3/ artifacts=12
  ARCHIVE STATUS  complete

Local archive file line for context.md or adhoc.txt:
/home/mike/repos/pipulate/data/captures/walk-f6yb8h7g/captures.md
Review locally before compiling; raw bytes are not a safe disclosure.
  WALK ROUTER  /home/mike/.local/state/pipulate/adhocwalk.txt  (0600; UNSANITIZED; not compiled)

Ride complete. Every stop produced a capture receipt.

Checking the summary before saving it and trying the clipboard.
   Preview checks: substitutions=0 denylist=0 secrets=0
   LOCAL PREVIEW /home/mike/repos/pipulate/data/decant-preview.md (0600; sha256=ad25831759f164609118d9c9539f224550396bf9ca19db3d587ff0705bb83d2b)
Markdown output copied to clipboard
   Read the save and copy messages above; either step can fail.
   Review the summary before sharing it. You choose what to send.
--------------------------------------------------------------
   CAPTURE RUN FINISHED
--------------------------------------------------------------
 Read the save and copy messages above. Either step can fail.
 Review the summary before sharing it.
 Nothing was sent to a chatbot.
--------------------------------------------------------------
walk_exit=0
(nix) pipulate $ 
```

**4: Prompt**: Continue THE FIRST FIVE MINUTES, ON RAILS.

The input cartridge was foo-584d1508-1418.zip. Its three live receipts
verified the practice-input repair. The operator's actual practice returned
to a menu and exited zero on the displayed empty response; no literal q
was shown. Do not reopen that repair from a missing q alone.

PREVIOUS TURN'S IMPLEMENTATION
One coordinated car across eight existing files:
- Plain public-walk narration; no fingerprint-command assignment.
- Plain page instructions; the unchanged checkword script and its exercise
  sit below an optional-after-the-walk heading.
- Practice prefixes the narrated instructions as rehearsal and never
  captures, saves a preview or attempts the clipboard handoff.
- The rider's read-only --intro-contract supplies the launcher with both
  eligibility and the exact notice printed before the menu/flag branch.
- The launcher passes --intro for the resolved bundled three-page route.
- The rider revalidates its path, fixed URLs, default profile and capture
  prerequisites; it checks all final captured URLs before automatic handoff.
- After a complete introductory run, existing checks and private-file /
  clipboard logic run without another typed DECANT word.
- Custom/private overrides and direct rider calls without --intro retain
  DECANT. --yolo skips practice; ASSUME_YES rehearses then rides.
- Shared voice playback, export loading, capture banking and disclosure
  policy were not refactored. The checkpoint wording changed, not its gate.

SANDBOX RESULTS, NOT LIVE ACCEPTANCE
47 blocks passed the supplied apply.py; applied bytes matched the candidate.
25 rider tests and 15 launcher tests passed with explicit substitutions.
Browser capture, voice and policy/clipboard dependencies were substituted.
The Nix-wrapper test used a shim. No native macOS or real browser run was
claimed. Tests covered successful handoff, private shadows, redirects,
partial runs, blocked checks, save/copy failures, delayed menu input,
flags, exports argument forwarding, EOF and rehearsal failure.

READ THIS COMPILE
Expect syntax=ok, stops=3, guidance_homework=0.
Expect scope=3/3 and handoff_routes=True from the read-only policy fixture.
These are not proof that a real browser or clipboard succeeded.
For each public page, compare local and served full SHA-256 values. Match
alone is not a revision indicator: compare the actual hashes with BEFORE.
The page deployment is a targeted rsync of the walk/ directory using the
TARGET read from nixops.sh. No NixOS rebuild is required for those bodies.

Read the operator's fresh walk separately: choose 2, three CAPTUREs,
no DECANT prompt on this route, archive complete, checks and distinct
file/clipboard-or-bridge results. A normal helper return is not delivery.
Do not assert q input, audible narration, successful clipboard contents
or a chatbot checkword test unless the corresponding witness is present.

ROLLOUT BOUNDARY
The implementation targets bash walk in the updated checkout. The targeted
HTML sync does not publish a new remotely served mck.sh, a PyPI release,
or another computer's checkout. release.py is included for inspecting
that separate rollout before naming or taking any publishing action.

Keep CAPTURE, review-before-sharing, private storage, limits, integrity
and disclosure checks. No automatic chatbot submission.
Do not expand into renderer repair, audio replacement, request attribution,
the Nix startup menu, receipt pruning or shared voice refactoring.

**5: Deliverables**: Better experience for the New-B.

**MikeLev.in**: Okay that was a MUCH better experience! After the 3rd CAPTURE was
entered there is nothing narrated by Piper TTS which is the only thing that
really feels missing now, but wow! That may have been more than I've ever seen
done before in a single step. Granted, it's all mostly cosmetic at this point
and not a rethinking of program flow or refactoring, but still this is exactly
now how we drive this app to extreme usability. That was very satisfying.

Let's wrap the article here. Carry anything dangling over to the next article so
we can do another following article just as well as this one went, please and
thank you!

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

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

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

**ChatGPT 6**: 

## 1. VERIFY

**Context verified. The goal was met for the updated checkout: explain, demonstrate, CAPTURE, then finish without another assignment or confirmation word.**

This compile’s live receipts report `syntax=ok stops=3 guidance_homework=0` and `scope=3/3 handoff_routes=True`. All three public pages returned HTTP 200 and matched their edited local files by full SHA-256. These establish source checks, isolated handoff-policy checks, and deployment—not, by themselves, a successful browser run. 

**Your actual run supplies that missing observation.** You chose **2**, entered three CAPTUREs, received twelve artifacts per stop, and completed `walk-f6yb8h7g/captures.md`. No DECANT prompt intervened. Checks reported `0/0/0`; the private-preview writer and clipboard helper each reported success; the process ended with `walk_exit=0`. Clipboard contents were not independently read back, so the helper’s success message remains precisely that—not a witnessed paste. 

**Both required ignitions fired:** the targeted three-page deployment and the fresh `bash walk` invocation. No ignition is missing for that delivered scope. Publication of the changed launcher through other distribution routes remains unverified. 

The remaining silence is well identified: you reported no Piper narration after the third CAPTURE, and the current completion path prints its results without a closing narration call. That becomes the next article, not an extra implementation smuggled into this ending.  

## 2. BANK

**One comment-only car: retire completed work, preserve the remaining verification debt, and replace the obsolete next-ride seed.**

The router still directs the next session to implement preview persistence and the missing diff mapping. Those implementations now exist. The independent checkword acceptance test, however, must not disappear merely because the implementation landed.  

## Streamlining the Onboarding Contract

All four blocks below passed the supplied `apply.py` against the reconstructed current source. The applied bytes matched the intended result, the Python syntax tree remained unchanged, and the capped receipt block was untouched. [BANK patch](sandbox:/mnt/data/onrails_close/bank.patch.txt) · [Validation record](sandbox:/mnt/data/onrails_close/validation.md)

```text
Target: foo_files.py
[[[SEARCH]]]
# - NEXT RIDE (seeded 2026-09-15 at deed 1408): THE DECANT THAT STAYS. A one-line opening prompt is enough: "Ride THE DECANT THAT STAYS from the router." Everything else is here.
#   DESTINATION: a public_walk DECANT preview that carries stop three's checkword, also written to one fixed, gitignored, 0600 file that every ride overwrites, beside the clipboard copy and never instead of it.
#   STARTING FACTS (receipts, deed 1408): the capture returns 11 keys and NEITHER diff key; scraper_tools.py's fresh-path optic list keys diff_hierarchy.txt, which is never written, and never keys diff_simple_dom.txt, which is; the checkword sits in source.html, simple_source_html.html and both diff_simple_dom files, and in no inlined lens.
#   CARS, in order: (1) scraper_tools.py returns diff_simple_txt on the fresh path and in the cached-path map; (2) mother_cat.py's DECANT_INLINE_KEYS and CAPTURE_DISCLOSURE_TEXT_KEYS name diff_simple_txt, and _decant lists every inlined lens a stop did NOT return, the way it already lists skipped stops, so a missing lens is said out loud; (3) the preview file, written from the same payload string the clipboard receives; (4) if there is room, the label car for the three false public-walk sentences.
#   RULING OWED BEFORE CAR 3: write the file before or after the DECANT word, and say which on the consent card in the same car. For BEFORE: captures.md already holds the full unsanitized bytes on this disk, so a capped local copy adds no exposure, and a DECLINED ride still keeps its preview. For AFTER: only the post-DECANT string has passed the scrub and secrets checks.
#   ARRIVAL CONDITIONS, all receipts: a fresh ride's key roster lists diff_simple_txt counted 3; the consent card's lens list prints diff_simple_txt (it is generated from the constant); git check-ignore names the preview path; grep -c for the checkword on the preview file reads at least 1; and a chatbot given that file and stop three's unchanged question names the word, pasted verbatim with the model's name and matched against stop three's source.html.
#   IGNITION: a fresh ride (walk, RIDE, three CAPTUREs, DECANT); both files load when the ride starts, so no shell re-entry. THE OPERATOR IS A VARIABLE: every completed ride replaces adhocwalk.txt's selection, so the roster and fingerprint probes read whichever ride ran last.
#   NOT THIS RIDE: output quieting, the audio swap, the menu door, and the forget ride (receipts 29 against 20).
[[[DIVIDER]]]
# - EARMARK: THE NEXT ACTION, NOT THE MACHINERY (banked 2026-09-15, deed 1419): newcomer guidance names the visible cue and the next action; audit exercises follow completion. The introductory run kept three CAPTURE checkpoints, private evidence and disclosure checks while dropping fingerprint homework and the final DECANT word. Consent moved before the real-walk choice for the scoped bundled route; custom walks retain DECANT. The operator reported a much better experience. Simpler instructions are not weaker evidence.
# - NEXT RIDE (seeded 2026-09-15 at deed 1419): THE WALK SAYS GOODBYE.
#   STARTING FACTS: commits 395910dd (practice input) and 0a635eb3 (intro contract) landed. Deed 1419 live receipts: syntax=ok stops=3 guidance_homework=0; scope=3/3 handoff_routes=True; all three served pages match their edited local SHA-256 values.
#   OPERATOR WITNESS: choice 2, three CAPTUREs at 12 artifacts each, no DECANT prompt, data/captures/walk-f6yb8h7g/captures.md complete, checks 0/0/0, private preview save and clipboard-helper success reported, walk_exit=0. File and clipboard bytes were not independently read back in this compile.
#   DESTINATION: one short Piper closing after the introductory run prints its results. The operator reported no narration after the third CAPTURE; the current completion path only prints.
#   IMPLEMENTATION: prefer the existing rider _narrate helper, with a visible fallback. Say what finished and where to read the result; do not add another question, command or lesson. Keep practice explicitly rehearsal and scope real completion to the bundled introduction.
#   GATES: preserve all capture and disclosure checks; never announce success on a partial run, or infer file/clipboard delivery from a truthy handoff result. Exercise blocked checks, destination failures and unavailable voice without losing the printed outcome or local evidence.
#   IGNITION: a fresh bundled introductory walk through its third CAPTURE and results, with an operator report of the closing audio; unit fixtures alone cannot witness sound.
#   NOT THIS RIDE: shared voice refactoring, replacement wait audio, renderer repair, request attribution, startup-menu changes, receipt pruning or a broad release.
# - TODO (2026-09-15, rollout): the updated-checkout walk and targeted HTML deployment were witnessed; publication of the changed mck.sh, package delivery and a fresh machine were not. Read release.py and the serving configuration before naming a publishing actuator; a GitHub push and a walk-page sync are not proof of every distribution route.
# - TODO (2026-09-15, voice warning): the successful introductory run printed "Missing phoneme from id map" before stop one. Cause and audible effect are unestablished; keep separate from the missing completion narration.
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
# - TODO (2026-09-15, receipt-convicted): DECANT_INLINE_KEYS in scripts/mother_cat.py names diff_hierarchy_txt, but the guided capture writes diff_simple_dom.txt and .html (tools/llm_optics.py:51 keys them diff_simple_txt), so every DECANT preview has shipped without its structural diff, and stop three's checkword lives in that diff and in no inlined lens; scraper_tools.py:448 and :914 carry the same stale name. ROSTER READ at deed 1408: the capture returns NEITHER diff key (11 keys, 33 fingerprints), because the fresh-path optic list keys only diff_hierarchy.txt, which no guided capture writes, and never keys diff_simple_dom.txt, which every one does; so scraper_tools.py returns diff_simple_txt first, then mother_cat.py's DECANT_INLINE_KEYS and CAPTURE_DISCLOSURE_TEXT_KEYS name it. Gate: a fresh ride whose roster lists diff_simple_txt and whose preview file carries the checkword.
[[[DIVIDER]]]
# - TODO (2026-09-15, remaining acceptance): diff_simple_txt is now mapped by both scraper paths and selected by both rider key lists; do not redo that implementation. Independently verify a fresh capture roster and the private preview's checkword against its banked source, then record a fresh-chat answer. Twelve artifacts and a clipboard-helper success line alone do not establish those checks.
[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
# - TODO (2026-09-15, operator ruling): DECANT also writes its preview to one fixed, gitignored, 0600 file that each ride overwrites, the prompt_foo -o habit; today the clipboard is the only copy, and the first npvg ride lost it to the next copy. A preview can carry session material, so it is never tracked.
[[[DIVIDER]]]

[[[REPLACE]]]

Target: foo_files.py
[[[SEARCH]]]
# - TODO (2026-09-15, label): on the no-login public walk, mother_cat's consent card still says the inlined lenses come from pages you were LOGGED IN TO, the summoning art names Cloudflare for a pad with no CDN, and the closing lines promise any chatbot will walk the rider through everything; three false sentences at the decision point, one car, riding with the output quieting.
[[[DIVIDER]]]

[[[REPLACE]]]
```

One `patch`, `app`, `d`, `m` cycle, then push the reviewed commit. **No runtime ignition is required: this car changes comments only.** It does not implement the spoken ending.

## 3. DANGLING

Native macOS/WSL acceptance and an independent clipboard-content readback remain unwitnessed; neither becomes a pass through this dismount.

The spoken ending, distribution gap, phoneme warning and remaining checkword test are now carried by the BANK car.

The existing renderer, wait-audio, request-attribution, startup-menu and notary-template debts remain parked—not folded into the next small improvement.

## 4. SEED

**Next article: THE WALK SAYS GOODBYE.** The destination and acceptance conditions are in the replacement router seed above. The completed article chain and retired probes need not accompany the first compile.

```text
foo_files.py
walk
assets/installer/mck.sh
scripts/mother_cat.py
scripts/walk.py
scripts/walk_cartridge.py
imports/voice_synthesis.py
tools/scraper_tools.py
prompt_foo.py
assets/trails/public_walk.yaml
```

## 5. CLOSING

At the beginning of this article, the walk kept asking its newcomer to understand the workshop. Practice could leave the menu unable to read. Page two interrupted the journey with a fingerprint-counting exercise. The final capture led to another unfamiliar word.

At the end, the instructions had become something a person could simply follow: return to the terminal, type CAPTURE, press Enter. The recorded run still collected twelve artifacts at every stop, completed its archive and checked the summary before attempting delivery. The person did less; the recorder still did its work. 

That is the useful distinction behind this kind of future-proofing. A system can preserve a detailed, inspectable record without making its first-time user act as its auditor. The deeper lesson remains available after the person has experienced why the tool is useful.

The final omission is almost an appropriate ending for the chapter: the guide carried the newcomer through the walk, then fell silent. You noticed because the rest finally felt right. 

## The Notary Beat and Closing the Loop

**The next article does not need to rebuild the walk. It needs to let the guide say goodbye.**

## 6. NOTARIZE

**Manifest fingerprint:** **3 live-command receipts**. A live `wc -c` measurement and a date printed by those receipts are **not recorded**. The router’s embedded `wc -c` command is source text, not an executed receipt in this Manifest; the Summary byte count and submission timestamp are not substitutes.   

The supplied seal footer explicitly names **`foo-38b6f9b1-1419.zip`**. That is the input artifact’s stated identity, not an independent verification of the ZIP. The BANK car above is a proposed successor change and is not part of that sealed input. 

```text
Deed: foo-________.zip   (ls -t foo-*.zip | head -1)
```

**MikeLev.in**: We bank wins.

```diff
(nix) pipulate $ g

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

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ app
✅ 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 816427e1..d10f375e 100644
--- a/foo_files.py
+++ b/foo_files.py
@@ -2362,19 +2362,22 @@ GLOSSARY.md
 foo_files.py
 """
 # #todo #to-do #earmarks
-# - NEXT RIDE (seeded 2026-09-15 at deed 1408): THE DECANT THAT STAYS. A one-line opening prompt is enough: "Ride THE DECANT THAT STAYS from the router." Everything else is here.
-#   DESTINATION: a public_walk DECANT preview that carries stop three's checkword, also written to one fixed, gitignored, 0600 file that every ride overwrites, beside the clipboard copy and never instead of it.
-#   STARTING FACTS (receipts, deed 1408): the capture returns 11 keys and NEITHER diff key; scraper_tools.py's fresh-path optic list keys diff_hierarchy.txt, which is never written, and never keys diff_simple_dom.txt, which is; the checkword sits in source.html, simple_source_html.html and both diff_simple_dom files, and in no inlined lens.
-#   CARS, in order: (1) scraper_tools.py returns diff_simple_txt on the fresh path and in the cached-path map; (2) mother_cat.py's DECANT_INLINE_KEYS and CAPTURE_DISCLOSURE_TEXT_KEYS name diff_simple_txt, and _decant lists every inlined lens a stop did NOT return, the way it already lists skipped stops, so a missing lens is said out loud; (3) the preview file, written from the same payload string the clipboard receives; (4) if there is room, the label car for the three false public-walk sentences.
-#   RULING OWED BEFORE CAR 3: write the file before or after the DECANT word, and say which on the consent card in the same car. For BEFORE: captures.md already holds the full unsanitized bytes on this disk, so a capped local copy adds no exposure, and a DECLINED ride still keeps its preview. For AFTER: only the post-DECANT string has passed the scrub and secrets checks.
-#   ARRIVAL CONDITIONS, all receipts: a fresh ride's key roster lists diff_simple_txt counted 3; the consent card's lens list prints diff_simple_txt (it is generated from the constant); git check-ignore names the preview path; grep -c for the checkword on the preview file reads at least 1; and a chatbot given that file and stop three's unchanged question names the word, pasted verbatim with the model's name and matched against stop three's source.html.
-#   IGNITION: a fresh ride (walk, RIDE, three CAPTUREs, DECANT); both files load when the ride starts, so no shell re-entry. THE OPERATOR IS A VARIABLE: every completed ride replaces adhocwalk.txt's selection, so the roster and fingerprint probes read whichever ride ran last.
-#   NOT THIS RIDE: output quieting, the audio swap, the menu door, and the forget ride (receipts 29 against 20).
+# - EARMARK: THE NEXT ACTION, NOT THE MACHINERY (banked 2026-09-15, deed 1419): newcomer guidance names the visible cue and the next action; audit exercises follow completion. The introductory run kept three CAPTURE checkpoints, private evidence and disclosure checks while dropping fingerprint homework and the final DECANT word. Consent moved before the real-walk choice for the scoped bundled route; custom walks retain DECANT. The operator reported a much better experience. Simpler instructions are not weaker evidence.
+# - NEXT RIDE (seeded 2026-09-15 at deed 1419): THE WALK SAYS GOODBYE.
+#   STARTING FACTS: commits 395910dd (practice input) and 0a635eb3 (intro contract) landed. Deed 1419 live receipts: syntax=ok stops=3 guidance_homework=0; scope=3/3 handoff_routes=True; all three served pages match their edited local SHA-256 values.
+#   OPERATOR WITNESS: choice 2, three CAPTUREs at 12 artifacts each, no DECANT prompt, data/captures/walk-f6yb8h7g/captures.md complete, checks 0/0/0, private preview save and clipboard-helper success reported, walk_exit=0. File and clipboard bytes were not independently read back in this compile.
+#   DESTINATION: one short Piper closing after the introductory run prints its results. The operator reported no narration after the third CAPTURE; the current completion path only prints.
+#   IMPLEMENTATION: prefer the existing rider _narrate helper, with a visible fallback. Say what finished and where to read the result; do not add another question, command or lesson. Keep practice explicitly rehearsal and scope real completion to the bundled introduction.
+#   GATES: preserve all capture and disclosure checks; never announce success on a partial run, or infer file/clipboard delivery from a truthy handoff result. Exercise blocked checks, destination failures and unavailable voice without losing the printed outcome or local evidence.
+#   IGNITION: a fresh bundled introductory walk through its third CAPTURE and results, with an operator report of the closing audio; unit fixtures alone cannot witness sound.
+#   NOT THIS RIDE: shared voice refactoring, replacement wait audio, renderer repair, request attribution, startup-menu changes, receipt pruning or a broad release.
+# - TODO (2026-09-15, rollout): the updated-checkout walk and targeted HTML deployment were witnessed; publication of the changed mck.sh, package delivery and a fresh machine were not. Read release.py and the serving configuration before naming a publishing actuator; a GitHub push and a walk-page sync are not proof of every distribution route.
+# - TODO (2026-09-15, voice warning): the successful introductory run printed "Missing phoneme from id map" before stop one. Cause and audible effect are unestablished; keep separate from the missing completion narration.
 # - TODO (2026-09-15, source-read, INFERRED): no guided capture writes a hierarchy or box lens (13 files, no *_hierarchy or *_boxes file), and llm_optics.py's Skipped branch would have written placeholder files had the visualizer import failed, so the visualizers most likely raise inside their own try blocks; generate_optics_subprocess returns only stdout on success, so the traceback is discarded and the capture reports success (THE SUCCESS-ONLY WITNESS). Read the exception once without writing, then either surface optics stderr on success or retire diff_hierarchy_txt from every key list.
-# - TODO (2026-09-15, receipt-convicted): DECANT_INLINE_KEYS in scripts/mother_cat.py names diff_hierarchy_txt, but the guided capture writes diff_simple_dom.txt and .html (tools/llm_optics.py:51 keys them diff_simple_txt), so every DECANT preview has shipped without its structural diff, and stop three's checkword lives in that diff and in no inlined lens; scraper_tools.py:448 and :914 carry the same stale name. ROSTER READ at deed 1408: the capture returns NEITHER diff key (11 keys, 33 fingerprints), because the fresh-path optic list keys only diff_hierarchy.txt, which no guided capture writes, and never keys diff_simple_dom.txt, which every one does; so scraper_tools.py returns diff_simple_txt first, then mother_cat.py's DECANT_INLINE_KEYS and CAPTURE_DISCLOSURE_TEXT_KEYS name it. Gate: a fresh ride whose roster lists diff_simple_txt and whose preview file carries the checkword.
+# - TODO (2026-09-15, remaining acceptance): diff_simple_txt is now mapped by both scraper paths and selected by both rider key lists; do not redo that implementation. Independently verify a fresh capture roster and the private preview's checkword against its banked source, then record a fresh-chat answer. Twelve artifacts and a clipboard-helper success line alone do not establish those checks.
 # - TODO (2026-09-15): one public_walk ride left 6 non-curl /walk/ lines in npvg.access.log, two per page; the user-agent split (deed 1408) read ONE agent, Chrome/150 on X11, with one 200 and one 304 per page, so there is no second fetcher: the same browser loaded each page twice and the second load was a conditional revalidation. Read the six lines in log order (time, status, path, no address) to rule between a double load inside one stop and a restore of the previous tab when the next stop's browser launches; count no rides until that is ruled, and filter the operator's own rides by address before reading the funnel at all.
-# - TODO (2026-09-15, operator ruling): DECANT also writes its preview to one fixed, gitignored, 0600 file that each ride overwrites, the prompt_foo -o habit; today the clipboard is the only copy, and the first npvg ride lost it to the next copy. A preview can carry session material, so it is never tracked.
-# - TODO (2026-09-15, label): on the no-login public walk, mother_cat's consent card still says the inlined lenses come from pages you were LOGGED IN TO, the summoning art names Cloudflare for a pad with no CDN, and the closing lines promise any chatbot will walk the rider through everything; three false sentences at the decision point, one car, riding with the output quieting.
+
+
 # - TODO (2026-09-15, operator ruling): replace jeopardy.wav under the summoning with free-licensed or public-domain audio, a ticking clock while the browser waits and an egg-timer ding at the end of the wait.
 # - TODO (2026-09-15, next door): the walk door in scripts/boot_menu.py, with its own exit code and runScript branch so exit 0 keeps meaning start the app and Enter keeps starting it, plus one label sweep across boot_menu.py, the flake's voice greeting and the brief prompt; ride it after the DECANT ride.
 # - TODO (2026-09-13): npvg.org rides mikelev.in's public address as a STATIC A record while only mikelev.in gets the namecheap-ddns heartbeat; a second unit (domain=npvg.org, its own token) or an ALIAS record is owed before the next public-IP change, or the pad goes dark with nothing printed anywhere.
(nix) pipulate $ m
📝 Committing: feat: Introduce next ride guidance and introductory walk
[main b2b623ad] feat: Introduce next ride guidance and introductory walk
 1 file changed, 14 insertions(+), 11 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 2.00 KiB | 340.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:pipulate/pipulate.git
   0a635eb3..b2b623ad  main -> main
(nix) pipulate $ 
```


---

## Book Analysis

### Ai Editorial Take
What is most striking about this entry is its subtle shift in psychological design: it recognizes that asking a newcomer to perform auditing tasks before they understand the tool's value creates unnecessary friction. By deferring deep technical inspection until after a successful run, the architecture achieves high trust through smooth adoption rather than forced compliance.

### 🐦 X.com Promo Tweet
```text
Discover how we streamlined our interactive tutorial workflows without sacrificing verification or control. Read the full essay on building reproducible onboarding paths: https://mikelev.in/futureproof/the-walk-says-goodbye-verifiable-workflows/ #AIWorkflows #DeveloperExperience #Automation
```

### Title Brainstorm
* **Title Option:** The Walk Says Goodbye: Engineering Verifiable AI Workflows on Rails
  * **Filename:** `the-walk-says-goodbye-verifiable-workflows`
  * **Rationale:** Captures both the narrative transition of finishing the onboarding redesign and the technical focus on reliable execution paths.
* **Title Option:** Simplifying Onboarding: Replayable AI Workflows in Action
  * **Filename:** `simplifying-onboarding-replayable-ai-workflows`
  * **Rationale:** Focuses directly on the practical improvements made to the user journey and verification steps.
* **Title Option:** On Rails and Replayable: Refining the First Five Minutes of Software
  * **Filename:** `on-rails-and-replayable-refining-first-five-minutes`
  * **Rationale:** Highlights the theme of early-stage user experience and the importance of reproducible first impressions.

### Content Potential And Polish
- **Core Strengths:**
  - Clear documentation of iterative problem-solving from initial hypothesis to final patch.
  - Strong emphasis on maintaining strict verification gates without burdening the end user.
  - Excellent integration of command-line receipts and test validation records.
- **Suggestions For Polish:**
  - Ensure technical explanations of file-status flag inheritance remain accessible to newcomers.
  - Keep the narrative flow tightly focused on user experience milestones rather than low-level code trivia.

### Next Step Prompts
- Design a lightweight audio notification strategy that provides pleasant completion feedback without interrupting the terminal flow.
- Explore how automated documentation pipelines can keep step-by-step tutorial assets synchronized with evolving CLI parameters.
