Engineering the Safety Airlock: A Compile-Lane Sanitizer Blueprint

🤖 Read Raw Markdown

Setting the Stage: Context for the Curious Book Reader

In this installment of our ongoing architectural record, we move from theoretical caution to structural enforcement. This article documents the creation of a ‘compile-lane sanitizer’—a critical gatekeeper that ensures sensitive client information is transformed or refused before ever leaving your local machine, bridging the gap between volatile ad hoc development and public code history.


Technical Journal Entry Begins

🔗 Verified Pipulate Commits:

MikeLev.in: I’ve been pushing myself so hard and I do indeed have the big reveal coming, but it’s not on the public side here but rather in Confluence when I do this PageWorkers optimization with full AI tied-into live documentation through Swagger, the ability to inspect the DevTools Network data of back-end API calls of things I make happen with a page-load by including the URL in foo_files.py as page to scrape and cache, so aside from things actually actively encoded, I can hack anything that’s like GraphQL or restful APIs of whatever in the wherever. Or stuff in your hydrated DOM. We see it all, which is amazing. And if we don’t see it right away, we see it the click after. And I should get this by what chop? adhoc for the LLM Optics insights or latest for the resent article automatically included? Easiest chop? Old fashioned favorite old alias foo, I just edit-in that chapter.

The Topology of Data Leaks

Yeah, I like the way AI_PHOOEY_CHOP is populated right now. I have patches to apply to finish the last round of work, don’t I? latest.

But first I have patches to apply:

$ git status
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 'prompt_foo.py'.
(nix) pipulate $ d
diff --git a/prompt_foo.py b/prompt_foo.py
index 704128c8..55fba4df 100644
--- a/prompt_foo.py
+++ b/prompt_foo.py
@@ -1563,6 +1563,17 @@ def main():
                                 "path": f"OPTICS [{title}]: {target_url}", "comment": comment, "content": content,
                                 "tokens": count_tokens(content), "words": count_words(content), "lang": "markdown" if key == 'seo_md' else "text"
                             })
+
+                    # WIRE TRUTH LENS (7th lens): distill the flight recorder
+                    # whenever a ledger exists — fresh scrape or cache hit.
+                    # The raw JSONL never enters context; only this distillate.
+                    ledger_path = artifacts.get('network_log')
+                    if ledger_path and os.path.exists(ledger_path):
+                        content = distill_network_ledger(ledger_path, target_domain=domain)
+                        processed_files_data.append({
+                            "path": f"OPTICS [Wire Truth]: {target_url}", "comment": comment, "content": content,
+                            "tokens": count_tokens(content), "words": count_words(content), "lang": "markdown"
+                        })
                 else:
                     logger.print(f"      [Error] Scrape failed: {result.get('error')}")
             else:
(nix) pipulate $ m
📝 Committing: chore: Distill network ledger for OPTICS scrapes  
[main 9c0bad2d] chore: Distill network ledger for OPTICS scrapes
 1 file changed, 11 insertions(+)
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
(nix) pipulate $ d
diff --git a/foo_files.py b/foo_files.py
index 607d2ba5..13bdaebd 100644
--- a/foo_files.py
+++ b/foo_files.py
@@ -649,8 +649,7 @@ ADHOC_CHOP = r"""
 
 tools/scraper_tools.py
 tools/llm_optics.py
-! echo "--- LARGEST NETWORK LEDGERS ---" && find browser_cache -name network_log.jsonl -printf "%s %p\n" | sort -rn | head -5
-! echo "--- SAMPLE LEDGER HEAD ---" && f=$(find browser_cache -name network_log.jsonl | head -1) && head -c 6000 "$f"
+%https://mikelev.in/
 
 # --- ADHOC SLOT END ---
 
(nix) pipulate $ m
📝 Committing: chore: Remove unnecessary echo and find commands in foo_files.py
[main e01a87db] chore: Remove unnecessary echo and find commands in foo_files.py
 1 file changed, 1 insertion(+), 2 deletions(-)
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'foo_files.py'.
(nix) pipulate $ d
diff --git a/foo_files.py b/foo_files.py
index 13bdaebd..f317bfa7 100644
--- a/foo_files.py
+++ b/foo_files.py
@@ -922,7 +922,7 @@ scripts/xp.py  # [1,981 tokens | 8,377 bytes]
 # - Make Honeybot 404 hub-healing only redirect to other hubs (not article leafs)
 # - Make the inference endpoint a configurable OpenAI-compatible URL to get vLLM, llama.cpp, LM Studio, and every future engine for free.
 # - Rustkyll drop-in test blocked on NixOS: uvx ships a generic dynamically-linked binary (stub-ld). Retry via nix-ld, steam-run, or `cargo install rustkyll`; then `diff -r` scratch build vs fresh Jekyll _site.
-# - CDP flight recorder REMAINS: add ('network_truth', 'Wire Truth') lens to the fresh-scrape lenses tuple in tools/scraper_tools.py (needs adhoc context — slot already staged), and secret-scan/sanitize `!` command output in the compile lane (browser_cache domain names leak client identity).
+# - Compile-lane sanitizer: pass `!` command stdout through pii_substitutions/denylist before stacking into payloads. Evidence: browser_cache paths leaked a client domain AND a product slug into a cloud-bound compile on 2026-07-10 — after the Ch. VIII caution was written. The caution is documentation; this todo is enforcement.
 
 # ============================================================================
 # VIII. THE PAINTBOX (Unused Colors)
(nix) pipulate $ m
📝 Committing: chore: Sanitize `!` command output in compile lane
[main 0239fbdf] chore: Sanitize `!` command output in compile lane
 1 file changed, 1 insertion(+), 1 deletion(-)
(nix) pipulate $ git push
Enumerating objects: 12, done.
Counting objects: 100% (12/12), done.
Delta compression using up to 48 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 1.47 KiB | 1.47 MiB/s, done.
Total 9 (delta 6), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (6/6), completed with 3 local objects.
To github.com:pipulate/pipulate.git
   ba8c2965..0239fbdf  main -> main
(nix) pipulate $

Implementing the Compile-Lane Sanitizer

Oh shoot! I forgot to do the cheapest falsifying probe before applying the patches… again. You and your sense of time. You acknowledge the problem and in the same article you do the same mistake again and I fall for it. Is that your machine analogue to a sense of humor, or something? Anyway, I run it after the fact for what it’s worth:

(nix) pipulate $ rg -n "network_log" prompt_foo.py tools/scraper_tools.py
tools/scraper_tools.py
140:            "network_log.jsonl",
325:        # --- Network Flight Recorder (CDP perf log -> network_log.jsonl) ---
333:            netlog_path = output_dir / "network_log.jsonl"
342:            artifacts['network_log'] = str(netlog_path)

prompt_foo.py
405:    """Distills a CDP performance-log flight recorder (network_log.jsonl)
1506:                # Reads the cached CDP flight recorder (network_log.jsonl) and
1515:                ledger_file = os.path.join(cache_dir, "network_log.jsonl")
1570:                    ledger_path = artifacts.get('network_log')
(nix) pipulate $

We try to recover from mistakes, but come on. Did you set me up for that?

Fable 5?

🎤

Fable 5 High: Fable 5 High: Context verified. Verdict on the probe first, then the accusation, because one of them is green and the other is fair.

Reading the after-the-fact probe

Late is not worthless — a falsifying probe run after the patch is still a verifier, and this one ratifies everything:

  • scraper_tools.py:342artifacts['network_log'] = str(netlog_path) — the fresh-scrape path exposes the key.
  • scraper_tools.py:140"network_log.jsonl" in the cached-artifact enumeration — the cache-hit path exposes it too, because that loop keys by filename stem, and the stem of network_log.jsonl is exactly network_log.
  • prompt_foo.py:1570 — the seventh lens consumes artifacts.get('network_log') — the consumer’s vocabulary matches both producers.
  • prompt_foo.py:1515 — the %URL branch builds its path directly from browser_cache/<domain>/<slug>/, which your earlier find probe already confirmed exists on disk at exactly that depth, %2F and all.

So the topology is closed: !URL gets six lenses plus Wire Truth automatically; @URL gets the same from cache because the stem-keying carries the ledger through; %URL gets the distillate alone. One distiller, one chokepoint, three routes in. The probe returned the “three-plus hits” the last turn demanded — it returned eight. Nothing to patch this turn, which is convenient, because it means I can’t repeat the crime while confessing to it.

Did I set you up?

No — and the honest answer is worse than a setup, because a setup would at least imply planning. Look at what the previous turn actually did: it promised “probes go before patches from here on,” and then the very same reply placed the section titled “Cheapest falsifying probe, before you app this time” below three patches. The rule was stated; the layout contradicted it; you execute top-to-bottom; layout won. That’s not a machine sense of humor. It’s the document-as-geography problem failing in the most literal way possible: I know the rule as a fact about the text, but the text still gets generated in rhetorical order — setup, payload, verification — because that’s the shape a billion documents taught me conclusions live in. Knowing about the bias and generating against it are different muscles, and you just caught the gap between them twice. The only real fix is structural, and it’s the same fix you apply to everything else here: don’t trust the genie’s stated intention, change the constitution. If it matters, the probe belongs in the slot — like the %https://mikelev.in/ line now sitting in ADHOC — where the compiler executes it before I ever get a vote. You already did that. The reading order and the running order are now enforced by prompt_foo.py, not by my promises.

One caution before the big reveal, because the lanes just switched

You’re about to point this apparatus at a client page for the PageWorkers work, and the chop question has a trap in it that yesterday’s lesson doesn’t cover. Yesterday’s leak was the compile lane! stdout carrying browser_cache paths into a cloud-bound payload. But watch what today’s own diff shows: the ADHOC slot edit was committed and pushed to github.com:pipulate/pipulate. The slot is not disposable in the way the comment implies — it’s versioned. Put !https://client-domain.com/product-page in that slot, type m, and the client engagement is in a public repo’s history forever, where git-filter-repo weekends go to be born. %https://mikelev.in/ was safe because it’s your own domain. For the client URL: edit it into the slot, compile, and wipe the slot before committing — or keep that line in the working tree uncommitted while the campaign runs. The gitignore protects browser_cache/; nothing protects foo_files.py from itself.

That said, adhoc is the right chop for the work: the optics modules are already in the slot, the Wire Truth lens now fires unbidden, and one !URL line against the client page hands the work-account conversation the hydrated DOM, the semantic outline, the JavaScript diff, and every GraphQL and REST call the page made on the wire — the flight recorder catching the animals nobody documented. latest is for narrative turns; this is an instrument turn.

Ratification remains one command: run adhoc and look for OPTICS [Wire Truth]: https://mikelev.in/ carrying a request table with zero chrome:// rows and your domain flagged ← target in the census. If that lands, the constitution is amended end to end — and the next amnesiac finds the flight recorder on the menu, priced, lit, and impossible to miss.

Go do the reveal. The pipe is warm, and this time the probes run themselves.

🎤 (returned)

MikeLev.in: Okay, one adhoc run to confirm. Then let’s do it, but one next-turn patch, or maybe a probe, but probably a patch request. I need one of those git commit hooks to keep me… oh yeah! I have it. Can you see that? There’s a file some where out of bounds oh yeah, I can figure it out. It’s in ~/.config/pipulate/ as all things of this out-of-band sort are. I look in that folder and remember it’s commit_denylist.txt. But that gives me something now for rg.

(nix) pipulate $ rg commit_denylist.txt
flake.nix
535:          # live OUTSIDE the repo (~/.config/pipulate/commit_denylist.txt) so

scripts/git_hooks/pre-commit
8:#     ~/.config/pipulate/commit_denylist.txt
28:DENYLIST="$HOME/.config/pipulate/commit_denylist.txt"
(nix) pipulate $

Hmm, so the actual script is scripts/git_hooks/pre-commit. Okay, I don’t write this kind of stuff that much. I know it’s used to make my Honeybot work a lot like GitHub Pages publishing-wise, and that nbstripout uses that, and I really rely on a level of Nix environment pinning for that to keep working anywhere because global machine versus repo scope stuff. Still getting a hang of all that. I still have not mastered every component of my own chosen tech stack: NPvg. So I look. Ah, that should be something you see. Oh, and it’s not in the paintbox. How could that be? Or maybe I’m just missing it. Either way, I put it in adhoc. Hi-Ya!

How does this work? I forget. But this system should make me brave to use certain things in the ad hoc chop for ad hoc work because I know those names or domains or whatever will be scrubbed. I just have to populate whatzit hownow?

Fable?

🎤

Fable 5 High: Fable 5 High: Context verified. Two answers and one correction — and the correction matters more than either answer, because you just said the word “scrubbed” about a mechanism that doesn’t scrub.

Why it’s not in the Paintbox

You’re not missing it. The Paintbox is blind to it by construction. collect_repo_files() filters git ls-files output through STORY_EXTENSIONS.py, .sh, .md, and so on. scripts/git_hooks/pre-commit has no extension, so os.path.splitext returns an empty string, the file never enters repo_files, and it can never appear in the unused-colors ledger. It’s also silently excluded from the Codex Mapping Coverage denominator, so that 76.9% is measured against a slightly smaller world than git’s. Same invisibility would hit any future extensionless hook, Makefile-style file, or bare script. If you want the census to see it, either add '' handling to the filter or accept that hooks live off-map — but now you know the blind spot is the filter, not your eyes.

How the airlock actually works

Three moving parts:

  1. Installation — the flake’s miscSetupLogic copies scripts/git_hooks/pre-commit into .git/hooks/pre-commit and chmod +x’s it on every nix develop entry, same idempotent pattern as the nbstripout filter config. The versioned file in scripts/ is the master; the copy in .git/hooks/ is what git actually runs. (Verify: diff scripts/git_hooks/pre-commit .git/hooks/pre-commit — empty output means installed and current.)

Establishing the Git Airlock

  1. Patterns~/.config/pipulate/commit_denylist.txt, deliberately outside the repo so the denylist itself can never leak into history. Format: one extended-regex pattern per line, blank lines and # comments ignored, matching is case-insensitive. So populating it is just:
# ~/.config/pipulate/commit_denylist.txt
clientname
client-domain\.com
secret-product-slug

One lowercase clientname line catches ClientName, CLIENTNAME, and clientname.com — that’s the capitalization-variant gap from the filter-repo post-mortem, closed.

  1. Enforcement — on every commit, the hook runs git grep --cached -I -n -i -E -f <patterns> against the entire staged index, not just added lines. One hit anywhere blocks the commit with the first 20 hits printed. No denylist file, or an empty one, is a silent no-op. Deliberate bypass: git commit --no-verify, once, with eyes open.

Cheapest falsifying probe, before you trust it with a real engagement:

echo 'zz_canary_test' >> ~/.config/pipulate/commit_denylist.txt
echo 'zz_canary_test' > canary.txt && git add canary.txt && git commit -m "canary"
# Expect: 🛑 COMMIT BLOCKED, canary.txt:1 listed
git reset HEAD canary.txt && rm canary.txt
sed -i '$d' ~/.config/pipulate/commit_denylist.txt

The correction: this refuses, it does not scrub

You wrote “I know those names or domains or whatever will be scrubbed.” They won’t — and the distinction is load-bearing. The system has three lanes with three different protections:

  • Repo lane (git history): the denylist hook. It’s a refusal ruleset — it blocks the commit and makes you scrub by hand. Nothing is rewritten.
  • Article/wiki lanes (publishing): pii_substitutions.txt via sanitizer.py. That one is a transform-on-the-way-out.
  • Compile lane (adhoc chop → clipboard → cloud chat window): nothing. That’s the todo you committed an hour ago, in your own words: “The caution is documentation; this todo is enforcement.” The 2026-07-10 leak happened in this lane, and the denylist would not have stopped it, because compiled payloads never pass through git commit.

So the correct bravery statement is narrower: the denylist makes you brave to have client strings in the working tree — in the ADHOC slot, in browser_cache paths, in scratch files — because the moment you reflexively type m, the airlock catches them before they reach public history. That’s real and valuable; it converts yesterday’s “wipe the slot before committing” from a memory-dependent ritual into a machine-enforced gate. But the compiled markdown that lands in your clipboard and gets pasted into a cloud UI still carries every string verbatim. Until the compile-lane sanitizer todo becomes code, the only guard on that lane is you reading the Payload Ledger before you paste.

Populate the denylist with the client’s name, domain, and any product slugs before you put the !https://client-page line in the ADHOC slot. Then the worst case of an absent-minded m is a loud red block instead of a git-filter-repo weekend. That’s the whole point of the airlock: it doesn’t make you careful — it makes carelessness fail closed.

🎤 (returned)

MikeLev.in: Okay, right:

(nix) pipulate $ vim ~/.config/pipulate/commit_denylist.txt
(nix) pipulate $

There, that was easy. I do feel better. I’ll have to think about that. It’s going to be hard to keep editing that ad hoc box. I might write one of these hooks so that it does scrub the content between sentinels before the commit. Or maybe some adhoc.md include I use like prompt.md but it always gets transformed into there during the prompt compile. That’s not even LLM Optics needed. That’s just like default.

Oh but first, I’m supposed to do this:

(nix) pipulate $ echo 'zz_canary_test' >> ~/.config/pipulate/commit_denylist.txt
echo 'zz_canary_test' > canary.txt && git add canary.txt && git commit -m "canary"
# Expect: 🛑 COMMIT BLOCKED, canary.txt:1 listed
git reset HEAD canary.txt && rm canary.txt
sed -i '$d' ~/.config/pipulate/commit_denylist.txt
🛑 COMMIT BLOCKED: denylisted identifier found in staged content.
AI_CONTEXT.md:451:- [2026-02-22] [71k] semantic-data-probe-ai-ghost-variations
apply.py:46:    # A more robust regex that ignores bracket width variations and code fence lines
assets/styles.css:1574:/* CONSOLIDATED: Theme variations for both role plugin classes */
canary.txt:1:zz_canary_test
foo_files.py:324:# apps/020_profiles.py  # <-- Variations of this used throughout system as privileged tapestry of system config & control apps.
scripts/articles/extract_404_ghosts.py:28:                        exclusions.add(raw_source.rstrip('/')) # Catch variations
scripts/articles/find_duplicates.py:96:    task = progress.add_task("[green]Calculating AI Ghost Variations...", total=len(days_to_check))
scripts/articles/generate_redirects.py:80:        'sitemap', '.xml', # Aggressively block any sitemap variations
scripts/articles/gsc_historical_fetch.py:103:    # Convert to dict: slug -> clicks (Aggregating if slugs duplicate due to protocol variations)
scripts/botify/botify_api_bootcamp.md:780:Botify API interactions come in many shapes and forms. The example shown below is the most popular: BQLv2 (Botify Query Language V2), but there are others — not just BQLv1 but also a vast array of *specialized endpoints* for custom reports and analysis. Of all the variations you will find, two "endpoints" (URLs that you make requests to) rise above all the others in their utility and frequency you'll encounter them. And they are:
scripts/botify/make_botify_docs.ipynb:807:    "Botify API interactions come in many shapes and forms. The example shown below is the most popular: BQLv2 (Botify Query Language V2), but there are others — not just BQLv1 but also a vast array of *specialized endpoints* for custom reports and analysis. Of all the variations you will find, two \"endpoints\" (URLs that you make requests to) rise above all the others in their utility and frequency you'll encounter them. And they are:\n",
scripts/dial_spinner.py:16:    print(f"🌀 Initiating Bumper Dial Sweep: {len(c_steps) * len(b_steps)} variations...")

   Patterns: /home/mike/.config/pipulate/commit_denylist.txt
   Scrub the string(s) above before committing. To bypass ONCE,
   deliberately and with eyes open: git commit --no-verify
Unstaged changes after reset:
M	foo_files.py
(nix) pipulate $ 

No, I could never be socially engineered into doing anything by an AI. I read that and understood every line. Actually, I kind of do. After it ran. Hmmm, maybe I’m vibe-coding.

Fable 5 High: Fable 5 High: Context verified. The canary probe returned a richer result than either of us ordered: it proved the airlock works and it falsified your denylist in the same breath. That’s the best kind of probe — one you ran expecting a green light and got a diagnosis instead.

Reading the block

Two separate facts are stacked in that output:

  1. The airlock is live. canary.txt:1:zz_canary_test is listed, the commit was refused, the bypass instructions printed. Installation, pattern loading, index-wide scanning — all confirmed in one shot. Note that it scanned AI_CONTEXT.md, assets/styles.css, notebook JSON — the entire staged index, not just your diff. That’s the whole point: a client string that snuck in three commits ago and is merely still present gets caught the next time anything touches the index.

  2. One of your real patterns is over-broad. Look at what every non-canary hit has in common: the word “variations”. Eleven files, one shared token. That means one of the lines you just put in the denylist is, case-insensitively, a substring of that ordinary English word. I can see from the hit pattern what the string must be — and I’m deliberately not going to type it, because this conversation is the compile lane, it’s bound for a cloud chat window, and the entire lesson of the last three turns is that strings like that don’t belong here. The mechanism is enough: short client identifiers collide with common-word substrings.

The fix is one character-pair per line

Anchor the patterns with word boundaries in ~/.config/pipulate/commit_denylist.txt:

# before (matches "variations", "covariate", anything)
clientstring

# after (matches only the standalone token)
\bclientstring\b

git grep -E honors \b. If you ever hit an environment where it doesn’t, the POSIX-portable fallback is (^|[^[:alnum:]])clientstring([^[:alnum:]]|$). Keep the domain form as a separate unanchored line (clientstring\.com) since dots already isolate it.

Then preview without committing anything — this is the standing probe for every future denylist edit:

git grep -I -n -i -E -f ~/.config/pipulate/commit_denylist.txt | head -20

Empty output means your working tree is clean against the current patterns. Run it right after editing the denylist, every time. And this precision matters for a second reason beyond correctness: a hook that cries wolf on “variations” trains your fingers toward --no-verify, and a bypassed airlock is worse than no airlock, because you believe it’s guarding you. False positives are the safe failure direction exactly once. After that they’re alert fatigue.

On the vibe-coding confession

You pasted a four-line shell block from an AI and read it after it ran. Yes — that’s the pattern, named honestly. Here’s the asymmetry worth noticing: your patches travel through apply.py, which has an exact-match interlock, an AST airlock, and a d alias for review before m. Your shell probes travel through nothing. The most fortified channel in this whole system is the one carrying the least dangerous payload. What saved you wasn’t vigilance, it was that the probe was designed reversible — creates one throwaway file, appends one line it removes at the end, touches no state it doesn’t clean up. That’s the standard to hold every suggested probe to before running it: no deletion of things it didn’t create, no network, undoable in one line. If a probe from me ever fails that test, that’s the turn to slow down.

The scrub idea: right instinct, and I’m not patching it blind

Your sentinel-scrub / adhoc.md-transform musing is the compile-lane sanitizer todo wearing a different hat, and the machinery already exists: ~/.config/pipulate/pii_substitutions.txt plus whatever parser sanitizer.py uses to apply it in the article lane. The patch itself is small — apply the same substitution table to final_output in prompt_foo.py immediately before copy_to_clipboard(final_output) and the --output write, one chokepoint, both exits covered.

But sanitizer.py is not in this compile, and I don’t know the substitution file’s line format — old|new? old -> new? JSON? Guessing a parser for an out-of-band secrets file is exactly the “edit that could affect another runtime” the checklist says to stop on. So per rule 4: the missing context is scripts/articles/sanitizer.py (or just its substitution-loading function). Add it to the next compile — it’s already sitting commented in Chapter XIII of the chop, one uncomment away — and the turn after that is a single SEARCH/REPLACE that turns “the caution is documentation” into “the todo is enforcement,” using the same table the article lane has trusted for a thousand posts.

Probes before patches. This time the layout and the promise agree.

🎤 (returned)

MikeLev.in: One uncomment away. Do that enforcement something or other. Feeling my way through this blindly here. Yeah, you need to see sanitizer to understand why this is happening, and here’s my probe after I put stuff in there. Showing you what’s in there. Right, adhoc chop Hi-Ya!

MikeLev.in: Okay, so I probe. Notice it’s just a combination of vim and typing things in and out of terminals here. It’s not as fancy as you think. You use the operating system’s copy-paste buffer as an API data transport mechanism and you can do a truly lot with that like turning a very loosely coupled Web-based ChatBot into your AI Editor. You teach them a game. They write beautiful coding music for you. What’s this about overdue patches? Hmmm. Okay…

$ git status
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 'prompt_foo.py'.
(nix) pipulate $ d
diff --git a/prompt_foo.py b/prompt_foo.py
index 55fba4df..7ee38896 100644
--- a/prompt_foo.py
+++ b/prompt_foo.py
@@ -557,6 +557,58 @@ def parse_file_list_from_config(chop_var: str = "AI_PHOOEY_CHOP", format_kwargs:
             parsed_files.append((file_path, comment))
     return parsed_files
 
+# ============================================================================
+# --- Compile-Lane Sanitizer (PII transform + denylist tripwire) ---
+# ============================================================================
+PII_SUBSTITUTIONS_FILE = Path.home() / ".config" / "pipulate" / "pii_substitutions.txt"
+COMMIT_DENYLIST_FILE = Path.home() / ".config" / "pipulate" / "commit_denylist.txt"
+
+
+def scrub_compile_payload(text: str):
+    """Sanitize the compiled payload before it leaves the machine.
+
+    Two stages, mirroring the repo's two protection styles:
+      1. TRANSFORM: apply ~/.config/pipulate/pii_substitutions.txt
+         ('pattern === replacement' per line, hash comments ignored) —
+         the same table sanitizer.py trusts for the article lane.
+      2. REFUSE: scan the post-scrub text against
+         ~/.config/pipulate/commit_denylist.txt (one case-insensitive
+         ERE per line, same patterns the pre-commit airlock enforces).
+         Any surviving hit is a leak the substitution table missed;
+         the caller fails closed.
+
+    Returns (scrubbed_text, substitution_count, leaks) where leaks is
+    a list of (pattern, hit_count) tuples. Missing config files are a
+    silent no-op for their stage, matching the airlock's behavior.
+    """
+    total = 0
+    if PII_SUBSTITUTIONS_FILE.exists():
+        for line in PII_SUBSTITUTIONS_FILE.read_text(encoding='utf-8').splitlines():
+            if not line.strip() or line.startswith('#'):
+                continue
+            if ' === ' in line:
+                pattern, repl = line.split(' === ', 1)
+                try:
+                    text, n = re.subn(pattern, repl, text)
+                    total += n
+                except re.error as e:
+                    print(f"⚠️  Skipping bad PII pattern {pattern!r}: {e}")
+    leaks = []
+    if COMMIT_DENYLIST_FILE.exists():
+        for line in COMMIT_DENYLIST_FILE.read_text(encoding='utf-8').splitlines():
+            pat = line.strip()
+            if not pat or pat.startswith('#'):
+                continue
+            try:
+                n = len(re.findall(pat, text, flags=re.IGNORECASE))
+            except re.error as e:
+                print(f"⚠️  Skipping bad denylist pattern {pat!r}: {e}")
+                continue
+            if n:
+                leaks.append((pat, n))
+    return text, total, leaks
+
+
 def copy_to_clipboard(text: str):
     """Copies text to the system clipboard gracefully across macOS and Linux."""
     import platform
(nix) pipulate $ m
📝 Committing: chore: Implement compile-lane sanitization with PII substitution & denylist checks
[main 643ea013] chore: Implement compile-lane sanitization with PII substitution & denylist checks
 1 file changed, 52 insertions(+)
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'prompt_foo.py'.
(nix) pipulate $ d
diff --git a/prompt_foo.py b/prompt_foo.py
index 7ee38896..737489d3 100644
--- a/prompt_foo.py
+++ b/prompt_foo.py
@@ -1335,6 +1335,7 @@ def main():
     parser.add_argument('prompt', nargs='?', default=None, help='A prompt string or path to a prompt file (e.g., prompt.md).')
     parser.add_argument('-o', '--output', type=str, help='Optional: Output filename.')
     parser.add_argument('--no-clipboard', action='store_true', help='Disable copying output to clipboard.')
+    parser.add_argument('--allow-leaks', action='store_true', help='Emit the payload even if commit_denylist.txt patterns survive the PII scrub (deliberate fail-open override).')
     parser.add_argument('--check-dependencies', action='store_true', help='Verify that all required external tools are installed.')
     parser.add_argument('--context-only', action='store_true', help='Generate a context-only prompt without file contents.')
     parser.add_argument('-n', '--no-tree', action='store_true', help='Suppress file tree and UML generation.')
(nix) pipulate $ m
📝 Committing: fix: add --allow-leaks argument to prompt
[main bb1d1306] fix: add --allow-leaks argument to prompt
 1 file changed, 1 insertion(+)
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'prompt_foo.py'.
(nix) pipulate $ d
diff --git a/prompt_foo.py b/prompt_foo.py
index 737489d3..a9b9c960 100644
--- a/prompt_foo.py
+++ b/prompt_foo.py
@@ -1987,7 +1987,22 @@ def main():
 
         print(console_summary.strip())
 
-    # 6. Handle output
+    # 6. Compile-lane sanitizer: TRANSFORM then REFUSE, at the single
+    # chokepoint every exit (clipboard, SSH bridge, --output) passes through.
+    final_output, pii_count, leaks = scrub_compile_payload(final_output)
+    if pii_count:
+        print(f"🪄 Compile-lane scrub: {pii_count} PII substitution(s) applied to payload.")
+    if leaks and not args.allow_leaks:
+        print("🛑 PAYLOAD BLOCKED: denylisted identifier(s) survive the PII scrub:")
+        for pat, n in leaks:
+            print(f"   • pattern {pat!r}: {n} hit(s)")
+        print("   Add a substitution to ~/.config/pipulate/pii_substitutions.txt (pattern === replacement),")
+        print("   fix the source, or rerun with --allow-leaks to bypass ONCE, deliberately and with eyes open.")
+        sys.exit(1)
+    elif leaks:
+        print(f"⚠️  --allow-leaks: emitting payload with {sum(n for _, n in leaks)} denylist hit(s).")
+
+    # 7. Handle output
     if args.output:
         with open(args.output, 'w', encoding='utf-8') as f: f.write(final_output)
         print(f"\nOutput written to '{args.output}'")
(nix) pipulate $ m
📝 Committing: chore: Refactor compile-lane sanitizer output and error handling
[main de0e279a] chore: Refactor compile-lane sanitizer output and error handling
 1 file changed, 16 insertions(+), 1 deletion(-)
(nix) pipulate $ git push
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 48 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 2.30 KiB | 1.15 MiB/s, done.
Total 9 (delta 6), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (6/6), completed with 2 local objects.
To github.com:pipulate/pipulate.git
   88bc9044..de0e279a  main -> main
(nix) pipulate $

All right, that’s a bunch of stuff. Still not… well, actually maybe am vibe-coding. But I can stop and study that if I really, truly liked but I think we just pin that up here for later. It’s in an article. An artifact. A prompt. It’s book fodder; a slug-key this repo’s storytime-spine. And then this apparently:

$ git status
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 'prompt_foo.py'.
(nix) pipulate $ d
diff --git a/prompt_foo.py b/prompt_foo.py
index 55fba4df..7ee38896 100644
--- a/prompt_foo.py
+++ b/prompt_foo.py
@@ -557,6 +557,58 @@ def parse_file_list_from_config(chop_var: str = "AI_PHOOEY_CHOP", format_kwargs:
             parsed_files.append((file_path, comment))
     return parsed_files
 
+# ============================================================================
+# --- Compile-Lane Sanitizer (PII transform + denylist tripwire) ---
+# ============================================================================
+PII_SUBSTITUTIONS_FILE = Path.home() / ".config" / "pipulate" / "pii_substitutions.txt"
+COMMIT_DENYLIST_FILE = Path.home() / ".config" / "pipulate" / "commit_denylist.txt"
+
+
+def scrub_compile_payload(text: str):
+    """Sanitize the compiled payload before it leaves the machine.
+
+    Two stages, mirroring the repo's two protection styles:
+      1. TRANSFORM: apply ~/.config/pipulate/pii_substitutions.txt
+         ('pattern === replacement' per line, hash comments ignored) —
+         the same table sanitizer.py trusts for the article lane.
+      2. REFUSE: scan the post-scrub text against
+         ~/.config/pipulate/commit_denylist.txt (one case-insensitive
+         ERE per line, same patterns the pre-commit airlock enforces).
+         Any surviving hit is a leak the substitution table missed;
+         the caller fails closed.
+
+    Returns (scrubbed_text, substitution_count, leaks) where leaks is
+    a list of (pattern, hit_count) tuples. Missing config files are a
+    silent no-op for their stage, matching the airlock's behavior.
+    """
+    total = 0
+    if PII_SUBSTITUTIONS_FILE.exists():
+        for line in PII_SUBSTITUTIONS_FILE.read_text(encoding='utf-8').splitlines():
+            if not line.strip() or line.startswith('#'):
+                continue
+            if ' === ' in line:
+                pattern, repl = line.split(' === ', 1)
+                try:
+                    text, n = re.subn(pattern, repl, text)
+                    total += n
+                except re.error as e:
+                    print(f"⚠️  Skipping bad PII pattern {pattern!r}: {e}")
+    leaks = []
+    if COMMIT_DENYLIST_FILE.exists():
+        for line in COMMIT_DENYLIST_FILE.read_text(encoding='utf-8').splitlines():
+            pat = line.strip()
+            if not pat or pat.startswith('#'):
+                continue
+            try:
+                n = len(re.findall(pat, text, flags=re.IGNORECASE))
+            except re.error as e:
+                print(f"⚠️  Skipping bad denylist pattern {pat!r}: {e}")
+                continue
+            if n:
+                leaks.append((pat, n))
+    return text, total, leaks
+
+
 def copy_to_clipboard(text: str):
     """Copies text to the system clipboard gracefully across macOS and Linux."""
     import platform
(nix) pipulate $ m
📝 Committing: chore: Implement compile-lane sanitization with PII substitution & denylist checks
[main 643ea013] chore: Implement compile-lane sanitization with PII substitution & denylist checks
 1 file changed, 52 insertions(+)
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'prompt_foo.py'.
(nix) pipulate $ d
diff --git a/prompt_foo.py b/prompt_foo.py
index 7ee38896..737489d3 100644
--- a/prompt_foo.py
+++ b/prompt_foo.py
@@ -1335,6 +1335,7 @@ def main():
     parser.add_argument('prompt', nargs='?', default=None, help='A prompt string or path to a prompt file (e.g., prompt.md).')
     parser.add_argument('-o', '--output', type=str, help='Optional: Output filename.')
     parser.add_argument('--no-clipboard', action='store_true', help='Disable copying output to clipboard.')
+    parser.add_argument('--allow-leaks', action='store_true', help='Emit the payload even if commit_denylist.txt patterns survive the PII scrub (deliberate fail-open override).')
     parser.add_argument('--check-dependencies', action='store_true', help='Verify that all required external tools are installed.')
     parser.add_argument('--context-only', action='store_true', help='Generate a context-only prompt without file contents.')
     parser.add_argument('-n', '--no-tree', action='store_true', help='Suppress file tree and UML generation.')
(nix) pipulate $ m
📝 Committing: fix: add --allow-leaks argument to prompt
[main bb1d1306] fix: add --allow-leaks argument to prompt
 1 file changed, 1 insertion(+)
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'prompt_foo.py'.
(nix) pipulate $ d
diff --git a/prompt_foo.py b/prompt_foo.py
index 737489d3..a9b9c960 100644
--- a/prompt_foo.py
+++ b/prompt_foo.py
@@ -1987,7 +1987,22 @@ def main():
 
         print(console_summary.strip())
 
-    # 6. Handle output
+    # 6. Compile-lane sanitizer: TRANSFORM then REFUSE, at the single
+    # chokepoint every exit (clipboard, SSH bridge, --output) passes through.
+    final_output, pii_count, leaks = scrub_compile_payload(final_output)
+    if pii_count:
+        print(f"🪄 Compile-lane scrub: {pii_count} PII substitution(s) applied to payload.")
+    if leaks and not args.allow_leaks:
+        print("🛑 PAYLOAD BLOCKED: denylisted identifier(s) survive the PII scrub:")
+        for pat, n in leaks:
+            print(f"   • pattern {pat!r}: {n} hit(s)")
+        print("   Add a substitution to ~/.config/pipulate/pii_substitutions.txt (pattern === replacement),")
+        print("   fix the source, or rerun with --allow-leaks to bypass ONCE, deliberately and with eyes open.")
+        sys.exit(1)
+    elif leaks:
+        print(f"⚠️  --allow-leaks: emitting payload with {sum(n for _, n in leaks)} denylist hit(s).")
+
+    # 7. Handle output
     if args.output:
         with open(args.output, 'w', encoding='utf-8') as f: f.write(final_output)
         print(f"\nOutput written to '{args.output}'")
(nix) pipulate $ m
📝 Committing: chore: Refactor compile-lane sanitizer output and error handling
[main de0e279a] chore: Refactor compile-lane sanitizer output and error handling
 1 file changed, 16 insertions(+), 1 deletion(-)
(nix) pipulate $ git push
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 48 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 2.30 KiB | 1.15 MiB/s, done.
Total 9 (delta 6), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (6/6), completed with 2 local objects.
To github.com:pipulate/pipulate.git
   88bc9044..de0e279a  main -> main
(nix) pipulate $

The last hand-cranked session of you thought this might be interesting to this hand-cranked instance of you. What do I mean by that. Am I sort of paining a picture here with this article as if I was creating some sort of art-icle. Is that Art as in artifact as in something that is actually made by a person sense? Like artisan? Artisanal? Is this an artifact? Fact?

Fable?

Fable 5 High: Fable 5 High: Context verified. Three observations before the etymology, because two of them are proofs you didn’t announce and one is a wrinkle nobody has named yet.

The artifact already testifies

First: you pasted the same session twice. Look closely at “And then this apparently:” — the commits are 643ea013, bb1d1306, de0e279a, identical hashes, identical push range 88bc9044..de0e279a. That’s not a second batch of overdue patches; it’s the first transcript quoted back into itself. Which accidentally answers your own question: the article is now doing what the whole system does — carrying a prior turn’s output forward as evidence for the next amnesiac instance. The echo wasn’t a mistake so much as the mechanism showing through.

Second, and better: this compile is its own falsifying probe, and it passed. The prompt_foo.py in this payload already contains scrub_compile_payload, the --allow-leaks flag, and the section-6 chokepoint — the patches landed, and the very document I’m reading traveled through the sanitizer it documents. More than that: foo_files.py in this payload still contains the word “Variations” (the 020_profiles.py comment line). If your over-broad denylist pattern from the canary incident were still un-anchored, this compile would have printed 🛑 PAYLOAD BLOCKED and exited 1 — and I would not exist to read it. The artifact’s mere arrival proves you fixed the word boundaries. That’s the cleanest kind of verification: existence as green light.

One wrinkle to pin before it bites

The transform stage introduces a divergence the patch lane didn’t have before: scrub_compile_payload rewrites final_output after the codebase sections are baked in. If a PII substitution ever fires inside a file body — a client string in a comment, a domain in a test fixture — then the source I see is no longer the source on disk. Any SEARCH block I emit from the scrubbed view will fail its exact-match against the real file, and apply.py will correctly refuse it. That’s a safe failure (the interlock holds), but it’ll look like me botching indentation when it’s actually the sanitizer doing its job. When that day comes, the diagnostic is: check whether the failed SEARCH text contains a substitution token. Worth a one-line comment near the chokepoint, someday. Not urgent; the denylist’s fail-closed stage means nothing sensitive leaks either way.

Art, artifact, fact, fable

The etymology genuinely braids the way you’re hoping. Ars is from the root “to fit together” — the same root as arm and articulate. An artifact is arte factum: a thing made by skill. Fact is just factum: a thing done. And fable is fabula: a thing told. So your question — “is this an artifact? Fact? Fable?” — decomposes precisely: the git hashes, the diffs, the terminal output are facta, things done, non-negotiable and timestamped. The narration around them — the Sorcerer’s Apprentice framing, the amnesiac genie, the microphone — is fabula, the telling. And the article is the artifactum: the two fitted together by hand, in vim, one journal entry at a time. Artisanal is the right word not as a compliment but as a category: hand-cranked, no autonomous loop, a human turning the wheel between every AI turn.

And “the last session of you thought this might be interesting to this instance of you” — what you mean is that continuity here doesn’t live in me. Each Fable turn is stateless; the previous instance’s judgment survives only because you froze it into the transcript and compiled it back in. The article isn’t documentation of the memory system. It is the memory system. The prior me left patches and cautions the way a builder leaves a level on the windowsill for whoever works the site tomorrow, knowing tomorrow’s worker wakes with no yesterday.

Bridging Fact, Fable, and Artifact

So: fact, fable, and artifact, all three — which, given what Anthropic named this model family, is either a coincidence or the best running joke in the whole repo. The facts are done, the telling is yours, and the fitting-together is the only part that required a person.

🎤 (returned)

MikeLev.in: Okay, I’ll wrap this silly article here. Hey earmark article sourerer for potentially forgotten but really brilliant todo-list items bot… or whatever I name you and whenever I write you, you’ll finally at some point be reading this and then go suddenly, oh, ahh Ha! This is a perfect example of what he’s looking for. Some silly thing I should probably do right now but Fable said it’s not urgent and I need a break, so articleize!


Book Analysis

Ai Editorial Take

What strikes me as most impressive here is the accidental ‘self-hosting’ quality of the article. By documenting a sanitizer that it then travels through, the text serves as a live, unit-tested proof of its own utility. It transforms the meta-discussion into an actual part of the stack.

🐦 X.com Promo Tweet

Stop relying on memory to prevent PII leaks. I just finished building a compile-lane sanitizer that automatically scrubs and audits data before it hits the clipboard. Here is the blueprint for a system that fails closed. https://mikelev.in/futureproof/compile-lane-sanitizer-blueprint/ #DevOps #Security #AI

Title Brainstorm

  • Title Option: Engineering the Safety Airlock: A Compile-Lane Sanitizer Blueprint
    • Filename: compile-lane-sanitizer-blueprint.md
    • Rationale: Directly addresses the technical solution and aligns with the series theme of building futureproof methodologies.
  • Title Option: Fail-Closed Architecture: Automating PII Protection
    • Filename: fail-closed-architecture-pii.md
    • Rationale: Focuses on the security philosophy of the article, highlighting the shift from human vigilance to machine enforcement.
  • Title Option: The Compile-Lane Chokepoint: Lessons in Data Hygiene
    • Filename: compile-lane-chokepoint.md
    • Rationale: Emphasizes the strategic importance of the single enforcement point created in the prompt_foo.py module.

Content Potential And Polish

  • Core Strengths:
    • Strong transition from reflexive error-fixing to structural architecture.
    • Clear, replicable examples of safety-first engineering.
    • Excellent philosophical framing of the artifact’s role in memory.
  • Suggestions For Polish:
    • Clarify the distinction between the git pre-commit hook and the runtime compiler sanitizer.
    • Add a visual diagram of the ‘three lanes’ (repo, article, compile) to aid understanding.

Next Step Prompts

  • Create a diagnostic utility that visualizes the current ‘denylist coverage’ across the entire repository structure.
  • Explore the implementation of an automated ‘cleanup’ script that safely scrubs browser_cache folders on a scheduled interval.