Mechanical Advantage and the Active Root: Authoritative Prefixes for Replayable AI Pipelines

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

Setting the Stage: Context for the Curious Book Reader

As our ongoing journey through automated publishing and local tooling unfolds, this entry captures an important lesson in the Age of AI: the distinction between mechanical sympathy and mechanical advantage. When collaborating with language models, it is tempting to demand total perfection from prompts alone. Yet real engineering begins when we stop treating models as magic oracles and start treating them as checkable components within a broader, replayable system. Here, an accidental workspace hop between checkouts exposes a subtle seam in our publishing pipeline—reminding us that while an AI model excels at linguistic synthesis like naming an article, deterministic code must remain the authoritative owner of filesystem locations, target prefixes, and routing receipts. By stepping through the problem with rigorous before-and-after probes, this essay demonstrates how system scars become our most durable guardrails.

TL;DR: A publishing pipeline was revised so that blog configuration—not AI-generated instructions—determines each new article’s URL prefix. The writer now derives the filename, permalink, canonical URL, preview link, and promotional link from one validated slug. All eight targeted identity checks passed after the change. A subsequent real publishing run remains unwitnessed. Pasted text(20261004-015124) Pasted text(20261004-015124) Pasted text(20261004-015124) Pasted text(20261004-015124)


Technical Journal Entry Begins

🔗 Verified Pipulate Commits:

MikeLev.in: What we’re doing here amounts to asserting mechanical advantage in the sense of using levers and pulleys once we achieved a sort of mechanical sympathy in the sense talked about by that racecar driver who said you don’t need to understand a car engine through and through but it does help. Help me pinpoint that quote please, ChatGPT. And so after we’ve achieved mechanical sympathy with these LLM-style AIs, understanding them as replayable Pinball Machines with different bumper-boards (models of weights) but not so different that the replay can’t produce equivalent output given enough pinned automated flipper-moves that have been tested against the…

Ugh! It’s exhausting even just to say that. You see my system ChatGPT 6 Pro. Say it better.

And while we’re on the topic of publishing articles, every time I do my publishing pipeline it tries to use the /futureproof/ folder for all my blogs even though my blogs.nix defines a specific subfolder per blog (if any at all). I know I compensate downstream for this but let’s do it right.

Pinpointing Mechanical Sympathy in Racing and Software

ChatGPT 6 Pro:

Mechanical sympathy is understanding the machine well enough to work with it. Mechanical advantage is turning that understanding into tools that multiply your effort.

That is the distinction your paragraph is reaching for—and the publishing bug is a concrete example of it.

The quotation: Jackie Stewart, via Martin Thompson

You are thinking of Sir Jackie Stewart. The wording reproduced in Caer Sanders’s Principles of Mechanical Sympathy, published on Martin Fowler’s site, is:

“You don’t need to be an engineer to be a racing driver, but you do need Mechanical Sympathy.” Martin Fowler

The software connection is Martin Thompson. His July 15, 2011 post, Why Mechanical Sympathy?, explicitly credits Stewart and explains the idea as understanding enough about a machine to work in harmony with it. Mechanical Sympathy

There is an attribution boundary worth preserving: I found that early software source and the familiar quoted wording, but not an original Stewart interview or recording establishing that exact sentence. For publication, “a saying attributed to Jackie Stewart” is the defensible wording.

Say it this way

Here is a replacement for your opening:

First comes mechanical sympathy: learning enough about a machine to stop fighting it. Then comes mechanical advantage: building tools that turn that understanding into leverage.

That is what I’m doing with AI. I don’t need to understand every weight inside a Model. I need a useful working understanding of its behavior, a way to give it the right evidence, and checks that distinguish a working result from a convincing answer.

Think of different Models as different pinball tables. I’m not trying to make every ball follow the same path. I’m preserving the setup, making the controls explicit, and judging each run against the same requirements. When a move works, I turn it into a reusable procedure instead of relying on another lucky bounce.

The objective is not identical wording from every Model. It is a portable method for obtaining and checking useful work. The Model handles the parts that need judgment; ordinary code handles the parts we already know how to specify.

Mechanical sympathy teaches me how to use the machine. Mechanical advantage lets me stop doing all the work by hand.

The important qualification is in “judging each run against the same requirements.” That states your engineering objective without claiming that enough prompting makes arbitrary Models interchangeable. Whether another Model meets those requirements is something the replay tests, not something the metaphor proves.

Separating Language Tasks from Routing Declarations

And it leads directly into your next issue:

Choosing an article’s title is a language task. Remembering which folder a blog uses is not.

The right boundary is:

The Model proposes the slug. The selected blog configuration owns the prefix. Python constructs the final permalink.

Your current code has already implemented part of that idea, but not the part that makes it binding.

The prompt already receives the configured pattern

The supplied articleizer.py calls lsa.permalink_prefix(target_config), builds a pattern with lsa.default_permalink("[slug]", prompt_prefix), and substitutes that into the editing prompt. The template then tells the Model to preserve that pattern. This is not simply a remaining hardcoded /futureproof/ example in the editing prompt. Pasted text(20261004-011650) Pasted text(20261004-011650)

The writer still lets the Model decide

At assembly time, however, create_jekyll_post takes yaml_updates["permalink"], adds a leading slash when necessary, and uses that value for the output. It does not receive the selected prefix. The filename’s slug is determined separately, later in the function. Pasted text(20261004-011650) Pasted text(20261004-011650) Pasted text(20261004-011650)

I executed that URL-assembly slice from the supplied source in the sandbox. Given /futureproof/example/, it retained /futureproof/example/ unchanged, both with an empty base URL and with a nonempty one. That is a test of the supplied assembly logic—not a live publishing run.

The configuration currently gives the Model advice; the writer does not enforce it.

That also explains why strengthening the prompt alone is insufficient. The --local path reads cached instructions without rebuilding the editing prompt, so enforcement needs to happen where all instruction sources converge: immediately before the post is assembled. Pasted text(20261004-011650)

The supplied configuration has a second important detail

In this compile, the declarations are:

Blog alias Declared permalink_prefix Required shape for a new post
article futureproof /futureproof/slug/
grim futureproof /futureproof/slug/
vault vault /vault/slug/
bot futureproof /futureproof/slug/

Both blogs.nix and its supplied JSON agree on these values. None of these four currently declares an empty prefix. The empty strings on vault and bot are their base_url values, not their directory prefixes. An empty hostname and an empty subfolder are separate settings. Pasted text Pasted text

Therefore, /futureproof/ is the configured answer for three of the four blogs in this snapshot. A new Vault article landing there would contradict the declaration; a Grimoire article landing there would not. A root-level blog should explicitly declare permalink_prefix = "", and the writer must preserve that empty value rather than treating it as permission to use a fallback.

I would not change those declarations to guessed destinations. The mechanical repair is to make whichever prefix you declare authoritative.

The change should happen before writing, not as a cleanup pass

The implementation should select and validate the slug once, combine it with the selected blog’s prefix through the existing URL helper, and use the resulting permalink for frontmatter, canonical URL, preview URL, and collision checks. The generated promotional link should agree with that same finalized URL. This is a proposed change to the writer’s contract, not a claim that the current code already does it.

It should cover API responses, browser responses, and cached JSON alike. It should not rewrite quoted URLs in the article body or migrate existing posts merely because a configuration value changes.

Preserving Downstream Routing and Navigation Integrity

That distinction matters downstream. generate_post_nav.py already documents that a post without an explicit permalink raises a different question: what Jekyll’s own configuration serves. Its fallback is not automatically the same thing as the prefix used when writing a new post. We should not “fix” that separate routing question by globally replacing /futureproof/. Pasted text(20261004-011650)

Two things must be resolved before the patch

scripts/articles/lsa.py is missing from this compile. It owns the existing prefix and permalink helpers and also resolves target aliases. Its file appears in the tree, but its source is not in the Manifest. Reimplementing its behavior in articleizer.py without reading it would create precisely the second owner we are trying to eliminate. Pasted text Pasted text Pasted text(20261004-011650)

The source path also changed between turns. The previous heading repair was supplied from /home/mike/qamyai; this compile supplies /home/mike/repos/pipulate. The latter currently contains the old heading-assembly line and the old introduction instruction. That establishes a difference between the supplied snapshots—not whether anything was reverted, pulled, or deliberately left on another checkout. The active root needs to be identified before another patch lands. Pasted text Pasted text(20261004-011650) Pasted text(20261004-011650)

The next step is consequently a small source-and-configuration check, not another speculative rewrite.

1. PROBES

The active configuration read is the cheapest falsifier for the assumption that the selected blog declares a different folder. It also exposes an environment override without printing credentials or unrelated configuration fields.

The other commands identify the working root, compare the latest publishing-code commits at the two observed paths, and locate the existing helper calls and shell entry points. They are read-only and do not invoke publication, clipboard access, a Model, or a rebuild. The rg result is capped per file; the next context carries the complete relevant source.

Run at (nix) pipulate $:

printf 'PWD=%s\nPIPULATE_ROOT=%s\nPIPULATE_BLOGS_CONFIG=%s\n' "$PWD" "${PIPULATE_ROOT:-}" "${PIPULATE_BLOGS_CONFIG:-}"; git rev-parse --show-toplevel HEAD
for repo in /home/mike/repos/pipulate /home/mike/qamyai; do printf '\nCHECKOUT=%s\n' "$repo"; git -C "$repo" log -1 --format='%h %s' -- scripts/articles/articleizer.py scripts/articles/editing_prompt.txt; done
.venv/bin/python -B -c 'import json,os; from pathlib import Path; p=Path(os.environ.get("PIPULATE_BLOGS_CONFIG",Path.home()/".config"/"pipulate"/"blogs.json")).expanduser(); print("blogs_config="+str(p.resolve())); d=json.loads(p.read_text()); print("target_count="+str(len(d))); [print(json.dumps({"target":k,"alias":v.get("alias"),"permalink_prefix":v.get("permalink_prefix"),"base_url":v.get("base_url"),"path":v.get("path")},sort_keys=True)) for k,v in sorted(d.items())[:12]]'
rg -n -m 20 'permalink_prefix|default_permalink|create_jekyll_post|write_post|alias (article|grim|vault|bot)=' scripts/articles/lsa.py scripts/articles/articleizer.py flake.nix

The supplied default configuration predicts the four rows above. A different active path or mapping is evidence to follow, not an error to overwrite.

2. NEXT CONTEXT

~/repos/nixos/blogs.nix
~/.config/pipulate/blogs.json
scripts/articles/common.py
scripts/articles/lsa.py
scripts/articles/articleizer.py
scripts/articles/editing_prompt.txt
flake.nix
apply.py
! printf 'PWD=%s\nPIPULATE_ROOT=%s\nPIPULATE_BLOGS_CONFIG=%s\n' "$PWD" "${PIPULATE_ROOT:-}" "${PIPULATE_BLOGS_CONFIG:-}"; git rev-parse --show-toplevel HEAD
! for repo in /home/mike/repos/pipulate /home/mike/qamyai; do printf '\nCHECKOUT=%s\n' "$repo"; git -C "$repo" log -1 --format='%h %s' -- scripts/articles/articleizer.py scripts/articles/editing_prompt.txt; done
! .venv/bin/python -B -c 'import json,os; from pathlib import Path; p=Path(os.environ.get("PIPULATE_BLOGS_CONFIG",Path.home()/".config"/"pipulate"/"blogs.json")).expanduser(); print("blogs_config="+str(p.resolve())); d=json.loads(p.read_text()); print("target_count="+str(len(d))); [print(json.dumps({"target":k,"alias":v.get("alias"),"permalink_prefix":v.get("permalink_prefix"),"base_url":v.get("base_url"),"path":v.get("path")},sort_keys=True)) for k,v in sorted(d.items())[:12]]'
! rg -n -m 20 'permalink_prefix|default_permalink|create_jekyll_post|write_post|alias (article|grim|vault|bot)=' scripts/articles/lsa.py scripts/articles/articleizer.py flake.nix

3. PATCHES

No repo patches issued this turn. The writer needs the ownership change described above, but the existing resolver and active launch path must be established first. No configuration values, historical URLs, or previously repaired files are being changed speculatively.

No ignition required.

4. PROMPT

Make the selected blog's permalink_prefix authoritative when writing a new article.

First read the live root/configuration receipts and the supplied flake.nix launchers. The preceding compile used /home/mike/repos/pipulate and carried the older heading code; the earlier repair used /home/mike/qamyai. Establish the active source before editing. Do not infer a revert or blindly reapply an earlier patch.

Read lsa.py's existing permalink_prefix, default_permalink, and target-resolution behavior. Reuse the existing helper rather than introducing a second URL policy.

The editing prompt already receives a configured permalink pattern, but create_jekyll_post trusts the returned JSON permalink. Move ownership to the writer: select and validate the slug once, combine it with the selected target's declared prefix, and make frontmatter, canonical URL, preview URL, collision checks, and the generated promotional link agree.

Cover API, web, and --local instruction paths. Test a nondefault prefix against deliberately wrong /futureproof/ input, an explicitly empty prefix, an empty base_url, and already-correct input. Keep missing/null prefix policy distinct from an explicitly empty prefix.

Do not guess new destinations for the existing blogs, migrate published URLs, globally replace paths in article prose, or change Jekyll's historical missing-permalink fallback.

Emit minimal exact-match patches against the active supplied source, bounded verification, and any required ignition.

5. EXTERNAL DELIVERABLES

The replacement opening above is manual-paste article prose, not a repository patch. Its governing idea also states the publishing repair:

Use the Model where judgment adds value. Use code where the answer has already been declared.

MikeLev.in: Oh whoops! I now see that I Prompt Fu’d out of the wrong repo. I’ve been moving around my Pipulate Prime home folder from ~/repos/pipulate/ to ~/qamyai/ as resiliency testing and I made my first during-article-writing mistakes of working out of the wrong folder as I typed my prompt, context and compile commands. I see that now. I guess that’s part of the mechanical sympathy too!

The Cost of Workspace Hopping Across Repositories

A particular repo root that you nix develop from becomes your primary “home” for the duration of that particular ride collaborating with the AI over that one puzzle-solving session. One must not hop between different “homes” that very well may (and maybe should) exist. You just don’t want to cause your own right-hand not knowing what the left-hand is doing problems when you don’t have to incur them.

THE AI-EDIT METHOD

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

1: BEFORE:

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) qamyai $ printf 'PWD=%s\nPIPULATE_ROOT=%s\nPIPULATE_BLOGS_CONFIG=%s\n' "$PWD" "${PIPULATE_ROOT:-}" "${PIPULATE_BLOGS_CONFIG:-}"; git rev-parse --show-toplevel HEAD
for repo in /home/mike/repos/pipulate /home/mike/qamyai; do printf '\nCHECKOUT=%s\n' "$repo"; git -C "$repo" log -1 --format='%h %s' -- scripts/articles/articleizer.py scripts/articles/editing_prompt.txt; done
.venv/bin/python -B -c 'import json,os; from pathlib import Path; p=Path(os.environ.get("PIPULATE_BLOGS_CONFIG",Path.home()/".config"/"pipulate"/"blogs.json")).expanduser(); print("blogs_config="+str(p.resolve())); d=json.loads(p.read_text()); print("target_count="+str(len(d))); [print(json.dumps({"target":k,"alias":v.get("alias"),"permalink_prefix":v.get("permalink_prefix"),"base_url":v.get("base_url"),"path":v.get("path")},sort_keys=True)) for k,v in sorted(d.items())[:12]]'
rg -n -m 20 'permalink_prefix|default_permalink|create_jekyll_post|write_post|alias (article|grim|vault|bot)=' scripts/articles/lsa.py scripts/articles/articleizer.py flake.nix
PWD=/home/mike/qamyai
PIPULATE_ROOT=/home/mike/qamyai
PIPULATE_BLOGS_CONFIG=
/home/mike/qamyai
a48c53ba4882e033efc98bda2fbbf79d34892d07

CHECKOUT=/home/mike/repos/pipulate
0db51620 chore: Update articleizer.py to handle web mode

CHECKOUT=/home/mike/qamyai
fdb622e chore: Refactor editing prompt for clarity and consistency
blogs_config=/home/mike/.config/pipulate/blogs.json
target_count=4
{"alias": "article", "base_url": "https://mikelev.in", "path": "/home/mike/repos/trimnoir/_posts", "permalink_prefix": "futureproof", "target": "1"}
{"alias": "grim", "base_url": "http://nixos.local:4002", "path": "/home/mike/repos/grimoire/_posts", "permalink_prefix": "futureproof", "target": "2"}
{"alias": "vault", "base_url": "", "path": "/home/mike/repos/vault/_posts", "permalink_prefix": "vault", "target": "3"}
{"alias": "bot", "base_url": "", "path": "/home/mike/repos/botifyml/_posts", "permalink_prefix": "futureproof", "target": "4"}
flake.nix
2558:              printf '%s\n' '{"1":{"name":"Mac Shadow - MikeLev.in","path":"~/.local/share/pipulate/shadow-publishing/article/_posts","lane":"public","alias":"article","base_url":"https://mikelev.in","permalink_prefix":"futureproof","preview_port":4001},"3":{"name":"Mac Shadow - Grimoire","path":"~/.local/share/pipulate/shadow-publishing/grim/_posts","lane":"private","alias":"grim","base_url":"http://nixos.local:4003","permalink_prefix":"futureproof","preview_port":4003},"4":{"name":"Mac Shadow - BotifyML","path":"~/.local/share/pipulate/shadow-publishing/bot/_posts","lane":"public","alias":"bot","base_url":"","permalink_prefix":"futureproof","preview_port":4004}}' > "$PIPULATE_BLOGS_CONFIG"
2563:            # aliases Prime uses, and write_post pins the shadow inside its own
2569:            write_post() {
2581:            alias article='write_post article'
2582:            alias grim='write_post grim'
2583:            alias vault='write_post vault'
2584:            alias bot='write_post bot'
2627:            # write_post: unified, data-driven article intake. The privacy lane
2634:            write_post() {
2640:            # THE WORD IS THE KEY (2026-09-27). These read write_post 1, 3
2649:            alias article='write_post article'
2650:            alias grim='write_post grim'
2651:            alias vault='write_post vault'
2652:            alias bot='write_post bot'

scripts/articles/articleizer.py
53:# default_permalink exists to avoid. That same function builds this string,
227:def create_jekyll_post(article_content, instructions, output_dir, preview_port, base_url=""):
570:        prompt_prefix = lsa.permalink_prefix(target_config)
574:        # logic further down, and permalink_prefix's None-vs-falsy rule.
580:            lsa.default_permalink("[slug]", prompt_prefix) + "/")
623:        # is matched against the ORIGINAL article in create_jekyll_post, which
829:        saved_path = create_jekyll_post(article_text, instructions, output_dir, preview_port, base_url)

scripts/articles/lsa.py
126:def permalink_prefix(target_config) -> str:
133:    raw = (target_config or {}).get('permalink_prefix')
139:def default_permalink(slug: str, prefix: str) -> str:
188:    # the Linux `grim` word, spelled `write_post 3`, wrote into the Vault).
699:                                 permalink_prefix(blog_cfg))
716:                    permalink = default_permalink(slug, prefix)
(nix) qamyai $ 

2: AFTER:

# # Context 1
# foo_files.py
# ~/repos/nixos/blogs.nix
# ~/.config/pipulate/blogs.json                # <-- CAUTION! Derived from ~/repos/nixos/blogs.nix
# scripts/articles/publishizer.py              # <-- Orchestrates different publishing workflows per target blog.
# scripts/articles/common.py                   # <-- Self-explanatory
# scripts/articles/articleizer.py              # <-- Transforms raw article.txt to formal Jekyll markdown format
# scripts/articles/editing_prompt.txt          # <-- Forcing response into strict JSON data structure
# scripts/articles/sanitizer.py                # <-- Scrubs PII
# scripts/articles/gsc_historical_fetch.py
# scripts/articles/contextualizer.py           # <-- Builds JSON summaries of articles in `_posts/context/` called "Holographic Shards".
# scripts/articles/confluenceizer.py           # <-- Idempotent Jekyll-to-Confluence corporate wiki
# scripts/articles/googledocizer.py            # <-- Just added
# scripts/articles/build_knowledge_graph.py    # <-- Topically load-balances site using hierarchical K-Means keyword clustering groups
# scripts/articles/generate_ai_context.py      # <-- AIs WILL interrogate your repo. This gives epic context of article URLs for drill-down.
# scripts/articles/generate_hubs.py            # <-- Uses just-produced link-graph data to generate each of the new hubs it suggests
# scripts/articles/generate_llms_txt.py        # <-- Builds an llms.txt based on the auto-organized structure suggested here
# scripts/articles/generate_redirects.py       # <-- Generates redirect map above hub-churn suggests is needed
# scripts/articles/sanitize_redirects.py       # <-- Deals with follow-up meticulous pedantic detail required for a good Nginx redirect map
# scripts/articles/link_injector.py
# scripts/articles/generate_post_nav.py  # [1,174 tokens | 4,616 bytes]
# deleteme.json
# /home/mike/qamyai/scripts/articles/article.txt
# /home/mike/repos/trimnoir/_posts/2026-10-03-separating-exposure-from-corruption-render-canary.md

# # Context 2
# scripts/articles/articleizer.py
# scripts/articles/editing_prompt.txt
# scripts/articles/common.py
# scripts/articles/sanitizer.py
# scripts/articles/article.txt
# deleteme.json
# init.lua
# prompt_foo.py
# scripts/foo_cartridge.py
# flake.nix
# apply.py
# ! .venv/bin/python -B -c 'import ast,json,re,textwrap; from pathlib import Path; s=Path("scripts/articles/articleizer.py").read_text(); ast.parse(s); a=s.index("    prepend_text = "); b=s.index("    # --- WRAPPING LOGIC START ---",a); n={"re":re,"editing_instr":json.loads(Path("deleteme.json").read_text())["editing_instructions"],"article_body":"BODY_SENTINEL"}; exec(textwrap.dedent(s[a:b]),n); h=re.findall(r"^#{1,6}[ \t]+.*$",n["article_body"],re.M); print("articleizer_ast=OK"); print("intro_heading_count="+str(len(h))); print("\n".join(h)); print("body_preserved="+str(n["article_body"].endswith("BODY_SENTINEL")))'
# ! rg -n -m 3 -A 9 -B 2 -F '6. NOTARIZE:' init.lua; printf 'notary_template_rc=%s\n' "$?"
# ! .venv/bin/python -B scripts/foo_cartridge.py qamy.ai_40-fc0beb6f.zip; printf 'archive_verify_rc=%s\n' "$?"
# ! .venv/bin/python -B -c 'from pathlib import Path; from datetime import datetime,timezone; import json,zipfile; p=Path("qamy.ai_40-fc0beb6f.zip"); print("archive_name="+p.name); print("archive_mtime_utc="+datetime.fromtimestamp(p.stat().st_mtime,timezone.utc).isoformat()); z=zipfile.ZipFile(p); m=json.loads(z.read("manifest.json")); z.close(); print("manifest_key_count="+str(len(m))); print("manifest_keys_first20="+json.dumps(sorted(m)[:20]))'

# Context 3
~/repos/nixos/blogs.nix
~/.config/pipulate/blogs.json
scripts/articles/common.py
scripts/articles/lsa.py
scripts/articles/articleizer.py
scripts/articles/editing_prompt.txt
flake.nix
apply.py
! printf 'PWD=%s\nPIPULATE_ROOT=%s\nPIPULATE_BLOGS_CONFIG=%s\n' "$PWD" "${PIPULATE_ROOT:-}" "${PIPULATE_BLOGS_CONFIG:-}"; git rev-parse --show-toplevel HEAD
! for repo in /home/mike/repos/pipulate /home/mike/qamyai; do printf '\nCHECKOUT=%s\n' "$repo"; git -C "$repo" log -1 --format='%h %s' -- scripts/articles/articleizer.py scripts/articles/editing_prompt.txt; done
! .venv/bin/python -B -c 'import json,os; from pathlib import Path; p=Path(os.environ.get("PIPULATE_BLOGS_CONFIG",Path.home()/".config"/"pipulate"/"blogs.json")).expanduser(); print("blogs_config="+str(p.resolve())); d=json.loads(p.read_text()); print("target_count="+str(len(d))); [print(json.dumps({"target":k,"alias":v.get("alias"),"permalink_prefix":v.get("permalink_prefix"),"base_url":v.get("base_url"),"path":v.get("path")},sort_keys=True)) for k,v in sorted(d.items())[:12]]'
! rg -n -m 20 'permalink_prefix|default_permalink|create_jekyll_post|write_post|alias (article|grim|vault|bot)=' scripts/articles/lsa.py scripts/articles/articleizer.py flake.nix

3: CHANGE: None this turn.

4: PROMPT:

Make the selected blog’s permalink_prefix authoritative when writing a new article.

First read the live root/configuration receipts and the supplied flake.nix launchers. The preceding compile used /home/mike/repos/pipulate and carried the older heading code; the earlier repair used /home/mike/qamyai. Establish the active source before editing. Do not infer a revert or blindly reapply an earlier patch.

Read lsa.py’s existing permalink_prefix, default_permalink, and target-resolution behavior. Reuse the existing helper rather than introducing a second URL policy.

The editing prompt already receives a configured permalink pattern, but create_jekyll_post trusts the returned JSON permalink. Move ownership to the writer: select and validate the slug once, combine it with the selected target’s declared prefix, and make frontmatter, canonical URL, preview URL, collision checks, and the generated promotional link agree.

Cover API, web, and –local instruction paths. Test a nondefault prefix against deliberately wrong /futureproof/ input, an explicitly empty prefix, an empty base_url, and already-correct input. Keep missing/null prefix policy distinct from an explicitly empty prefix.

Do not guess new destinations for the existing blogs, migrate published URLs, globally replace paths in article prose, or change Jekyll’s historical missing-permalink fallback.

Emit minimal exact-match patches against the active supplied source, bounded verification, and any required ignition.

5: DELIVERABLE: A better publishing pipeline. ChatGPT used the language “active root”. I like that. I need to clarify language surrounding this and lock-in on the best language for it and consistently use it.

ChatGPT 6 Pro: The active root is now established: /home/mike/qamyai. The permalink repair is ready against that checkout, with the earlier heading fix preserved. The live receipts identify that directory as both PIPULATE_ROOT and the Git working-tree root, and distinguish its publishing-code history from /home/mike/repos/pipulate. Pasted text(20261004-013346)

Settle the terminology

Use active root for the root directory of the checkout selected by the current Pipulate shell. It is a role a directory plays—not a permanent nickname for one machine or one installation.

Term Meaning in this system
Active root The selected code checkout, named by PIPULATE_ROOT. Here: /home/mike/qamyai.
Working directory The process’s current location, reported by PWD. It can change without changing the active root.
Publishing target The blog configuration selected by an alias or key, such as vault or 3.
Posts directory The target’s configured path, such as /home/mike/repos/vault/_posts.
Site root The parent of that posts directory: /home/mike/repos/vault in this example.

These distinctions match the supplied launcher: write_post enters $PIPULATE_ROOT/scripts/articles, then passes the selected target to the publishing scripts. The code location and output destination are separate choices. Pasted text(20261004-013346) Pasted text(20261004-013346)

The sentence to keep is:

The active root selects the code. The publishing target selects the destination. The working directory is where a process happens to be standing.

And distinguish the URL prefix from all three. permalink_prefix controls the new article’s URL path; it does not identify the code checkout or the filesystem directory containing its Markdown.

What changes

The existing helper already expresses the required policy: a missing or null prefix falls back to futureproof, while an explicitly empty string remains empty. Its URL builder also avoids a doubled slash for root-level posts. There is no reason to duplicate or change that logic. Pasted text(20261004-013346)

The missing connection is in the writer. It currently accepts the Model’s permalink directly and chooses the filename’s slug separately afterward. Pasted text(20261004-013346)

The patch gives those decisions one owner:

First filename → validated slug → configured prefix → final permalink.

That same permalink feeds frontmatter, canonical URL, preview URL, and collision detection. The selected target is passed into the common writer, so API, browser, and --local instructions take the same route.

For the promotional text, new instructions request a literal [ARTICLE_URL] marker. The writer fills it with the finalized canonical URL. Older cached instructions containing one naked absolute or root-relative link remain supported. An empty base_url leaves canonical_url empty and removes the promotional link rather than inventing a public host. Multiple candidate links are refused rather than guessed between.

There are no changes to blogs.nix, lsa.py, or flake.nix. Your current declarations remain futureproof for article, grim, and bot, and vault for vault; this repair enforces those declarations rather than choosing new ones. Existing posts are not migrated. Pasted text(20261004-013346)

The current heading-normalization guard and its prose-only prompt instruction are already present in this active root and remain untouched. Pasted text(20261004-013346) Pasted text(20261004-013346)

Verification completed here

All 25 sandbox regression tests passed. They exercised the complete writer in temporary output directories, plus the API, browser, and cached-instruction dispatch paths with synthetic replies. Clipboard operations and the last-published recorder were stubbed; no real publication or Model request occurred.

The tests covered wrong prefixes and hosts, explicit empty prefixes, missing/null prefixes, empty base URLs, already-correct input, filename/URL disagreement, invalid filenames, legacy promotional links, and collisions against the final permalink. They also checked that input instructions were not mutated, historical files stayed unchanged, and the heading fix remained intact.

All nine SEARCH/REPLACE blocks below applied successfully through the supplied apply.py in the sandbox.

1. PROBES

The first command is the cheapest bounded behavioral check: it parses the full Python file, executes only the writer’s pre-write identity logic in memory, and checks that the main call passes target_config. It performs no directory scan, file write, clipboard operation, or Model call.

Against the supplied source, its BEFORE reading is configured_writer_calls=0 and identity_cases=3/8, exit 1. With Car 1 applied, the forecast is configured_writer_calls=1 and identity_cases=8/8, exit 0.

The second command checks Car 2’s template contract: promo_marker_mentions=0 → 1, and slug_owner=False → True.

Run at (nix) qamyai $:

.venv/bin/python -B -c 'import ast,re,sys; from pathlib import Path; sys.path.insert(0,"scripts/articles"); import lsa; t=ast.parse(Path("scripts/articles/articleizer.py").read_text()); f=next(n for n in t.body if isinstance(n,ast.FunctionDef) and n.name=="create_jekyll_post"); bound=sum(isinstance(n,ast.Call) and isinstance(n.func,ast.Name) and n.func.id=="create_jekyll_post" and any(k.arg=="target_config" and isinstance(k.value,ast.Name) and k.value.id=="target_config" for k in n.keywords) for n in ast.walk(t)); names=lambda n: [x.id for x in n.targets if isinstance(x,ast.Name)] if isinstance(n,ast.Assign) else []; i=next(i for i,n in enumerate(f.body) if "editing_instr" in names(n)); j=next(i for i,n in enumerate(f.body) if "new_yaml_data" in names(n)); f.body=f.body[i:j]+ast.parse("return permalink, canonical_url, locals().get(\"promo_text\", analysis_content.get(\"promotional_tweet\", \"\"))").body; ns={"lsa":lsa,"re":re}; exec(compile(ast.fix_missing_locations(ast.Module(body=[f],type_ignores=[])),"<identity-probe>","exec"),ns); kw=any(x.arg=="target_config" for x in f.args.kwonlyargs); u="https://example.invalid"; old=u+"/futureproof/probe/"; cases=[("vault",{"permalink_prefix":"vault"},u,old,"/vault/probe/"),("root",{"permalink_prefix":""},u,old,"/probe/"),("private",{"permalink_prefix":"vault"},"",old,"/vault/probe/"),("correct",{"permalink_prefix":"futureproof"},u,old,"/futureproof/probe/"),("missing",{},u,old,"/futureproof/probe/"),("null",{"permalink_prefix":None},u,old,"/futureproof/probe/"),("marker",{"permalink_prefix":"vault"},u,"[ARTICLE_URL]","/vault/probe/"),("relative",{"permalink_prefix":"vault"},"","/futureproof/probe/","/vault/probe/")]; run=lambda cfg,base,link: ns["create_jekyll_post"]("",{"editing_instructions":{"yaml_updates":{"permalink":"/futureproof/probe/"}},"book_analysis_content":{"title_brainstorm":[{"filename":"probe.md"}],"promotional_tweet":"Read\n\n"+link}},"",0,base,**({"target_config":cfg} if kw else {})); checks=[(name,run(cfg,base,link)==(p,base+p if base else "",("Read\n\n"+(base+p if base else "")).strip())) for name,cfg,base,link,p in cases]; print("articleizer_ast=OK"); print("configured_writer_calls="+str(bound)); [print(name+"="+str(ok)) for name,ok in checks]; print("identity_cases="+str(sum(ok for _,ok in checks))+"/"+str(len(checks))); sys.exit(0 if bound==1 and all(ok for _,ok in checks) else 1)'
.venv/bin/python -B -c 'from pathlib import Path; s=Path("scripts/articles/editing_prompt.txt").read_text(); print("promo_marker_mentions="+str(s.count("[ARTICLE_URL]"))); print("slug_owner="+str("sole source of the published slug" in s))'

2. NEXT CONTEXT

The launch-path question is resolved, so the large flake.nix body need not ride the next verification compile.

~/repos/nixos/blogs.nix
~/.config/pipulate/blogs.json
scripts/articles/common.py
scripts/articles/lsa.py
scripts/articles/articleizer.py
scripts/articles/editing_prompt.txt
apply.py
! .venv/bin/python -B -c 'import ast,re,sys; from pathlib import Path; sys.path.insert(0,"scripts/articles"); import lsa; t=ast.parse(Path("scripts/articles/articleizer.py").read_text()); f=next(n for n in t.body if isinstance(n,ast.FunctionDef) and n.name=="create_jekyll_post"); bound=sum(isinstance(n,ast.Call) and isinstance(n.func,ast.Name) and n.func.id=="create_jekyll_post" and any(k.arg=="target_config" and isinstance(k.value,ast.Name) and k.value.id=="target_config" for k in n.keywords) for n in ast.walk(t)); names=lambda n: [x.id for x in n.targets if isinstance(x,ast.Name)] if isinstance(n,ast.Assign) else []; i=next(i for i,n in enumerate(f.body) if "editing_instr" in names(n)); j=next(i for i,n in enumerate(f.body) if "new_yaml_data" in names(n)); f.body=f.body[i:j]+ast.parse("return permalink, canonical_url, locals().get(\"promo_text\", analysis_content.get(\"promotional_tweet\", \"\"))").body; ns={"lsa":lsa,"re":re}; exec(compile(ast.fix_missing_locations(ast.Module(body=[f],type_ignores=[])),"<identity-probe>","exec"),ns); kw=any(x.arg=="target_config" for x in f.args.kwonlyargs); u="https://example.invalid"; old=u+"/futureproof/probe/"; cases=[("vault",{"permalink_prefix":"vault"},u,old,"/vault/probe/"),("root",{"permalink_prefix":""},u,old,"/probe/"),("private",{"permalink_prefix":"vault"},"",old,"/vault/probe/"),("correct",{"permalink_prefix":"futureproof"},u,old,"/futureproof/probe/"),("missing",{},u,old,"/futureproof/probe/"),("null",{"permalink_prefix":None},u,old,"/futureproof/probe/"),("marker",{"permalink_prefix":"vault"},u,"[ARTICLE_URL]","/vault/probe/"),("relative",{"permalink_prefix":"vault"},"","/futureproof/probe/","/vault/probe/")]; run=lambda cfg,base,link: ns["create_jekyll_post"]("",{"editing_instructions":{"yaml_updates":{"permalink":"/futureproof/probe/"}},"book_analysis_content":{"title_brainstorm":[{"filename":"probe.md"}],"promotional_tweet":"Read\n\n"+link}},"",0,base,**({"target_config":cfg} if kw else {})); checks=[(name,run(cfg,base,link)==(p,base+p if base else "",("Read\n\n"+(base+p if base else "")).strip())) for name,cfg,base,link,p in cases]; print("articleizer_ast=OK"); print("configured_writer_calls="+str(bound)); [print(name+"="+str(ok)) for name,ok in checks]; print("identity_cases="+str(sum(ok for _,ok in checks))+"/"+str(len(checks))); sys.exit(0 if bound==1 and all(ok for _,ok in checks) else 1)'
! .venv/bin/python -B -c 'from pathlib import Path; s=Path("scripts/articles/editing_prompt.txt").read_text(); print("promo_marker_mentions="+str(s.count("[ARTICLE_URL]"))); print("slug_owner="+str("sole source of the published slug" in s))'

3. PATCHES

Car 1 — Make the publishing target authoritative in the writer

This removes the fallback to a made-up untitled-article slug: missing or malformed filenames now refuse the write. A refused write also produces a nonzero command-line exit.

Target: scripts/articles/articleizer.py
[[[SEARCH]]]
def create_jekyll_post(article_content, instructions, output_dir, preview_port, base_url=""):
[[[DIVIDER]]]
def create_jekyll_post(article_content, instructions, output_dir, preview_port, base_url="",
                       *, target_config=None):
[[[REPLACE]]]

Target: scripts/articles/articleizer.py
[[[SEARCH]]]
    # --- NEW: Construct the absolute Canonical URL ---
    permalink = yaml_updates.get("permalink", "")
    # Ensure proper slash formatting
    if not permalink.startswith("/"):
        permalink = f"/{permalink}"
    base_url = (base_url or "").rstrip("/")
    canonical_url = f"{base_url}{permalink}" if base_url else ""
    # -----------------------------------------------
[[[DIVIDER]]]
    # One slug; the selected publishing target owns its URL prefix.
    try:
        filename = analysis_content["title_brainstorm"][0]["filename"]
    except (KeyError, IndexError, TypeError):
        filename = None
    slug = filename.removesuffix(".md") if isinstance(filename, str) else ""
    if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", slug):
        print("Refusing to write: title_brainstorm[0].filename must be a "
              "lowercase alphanumeric/hyphen slug, optionally ending in .md.")
        return None
    prefix = lsa.permalink_prefix(target_config)
    permalink = lsa.default_permalink(slug, prefix) + "/"
    base_url = (base_url or "").rstrip("/")
    canonical_url = f"{base_url}{permalink}" if base_url else ""
    # Only the generated promo is normalized, never links in the article body.
    # Legacy cached replies carry a URL; new replies carry [ARTICLE_URL].
    promo_text = analysis_content.get("promotional_tweet", "")
    if not isinstance(promo_text, str):
        print("Refusing to write: promotional_tweet must be a string.")
        return None
    promo_links = list(re.finditer(
        r"\[ARTICLE_URL\]|https?://\S+|(?<!\S)/\S+", promo_text))
    if len(promo_links) > 1:
        print("Refusing to write: promotional_tweet must contain at most "
              "one article link or [ARTICLE_URL] marker.")
        return None
    if promo_links:
        match = promo_links[0]
        promo_text = promo_text[:match.start()] + canonical_url + promo_text[match.end():]
    elif canonical_url and promo_text:
        promo_text = f"{promo_text}\n\n{canonical_url}"
    promo_text = promo_text.strip()
[[[REPLACE]]]

Target: scripts/articles/articleizer.py
[[[SEARCH]]]
        analysis_markdown += f"\n### 🐦 X.com Promo Tweet\n[triple-backtick]text\n{analysis_content['promotional_tweet']}\n[triple-backtick]\n"
[[[DIVIDER]]]
        analysis_markdown += f"\n### 🐦 X.com Promo Tweet\n[triple-backtick]text\n{promo_text}\n[triple-backtick]\n"
[[[REPLACE]]]

Target: scripts/articles/articleizer.py
[[[SEARCH]]]
    # 4. Generate Filename
    slug = "untitled-article"
    title_brainstorm = analysis_content.get("title_brainstorm", [])
    if title_brainstorm and title_brainstorm[0].get("filename"):
        slug = os.path.splitext(title_brainstorm[0]["filename"])[0]
[[[DIVIDER]]]
    # 4. Generate Filename from the same validated slug used in the URL.
[[[REPLACE]]]

Target: scripts/articles/articleizer.py
[[[SEARCH]]]
        print("   Pick a new slug/permalink (edit instructions.json, rerun with --local).")
[[[DIVIDER]]]
        print("   Pick a new title_brainstorm[0].filename in instructions.json; rerun with --local.")
[[[REPLACE]]]

Target: scripts/articles/articleizer.py
[[[SEARCH]]]
        saved_path = create_jekyll_post(article_text, instructions, output_dir, preview_port, base_url)
        if saved_path:
            common.record_last_published(args.target, saved_path, target_config.get("name"))
[[[DIVIDER]]]
        saved_path = create_jekyll_post(
            article_text, instructions, output_dir, preview_port, base_url,
            target_config=target_config)
        if saved_path:
            common.record_last_published(args.target, saved_path, target_config.get("name"))
        else:
            sys.exit(1)
[[[REPLACE]]]

Car 2 — Align the editing prompt with that ownership

The JSON shape stays unchanged. The Model still proposes titles, filenames, and promotional prose, but it no longer needs to construct the promotional address.

Target: scripts/articles/editing_prompt.txt
[[[SEARCH]]]
    * permalink: Generate the SEO-optimized filename slug (e.g., primary-keywords.md) and use it to construct the permalink string in the format [INSERT PERMALINK PATTERN] -- copy that shape verbatim, keeping its leading and trailing slashes, and put your slug where [slug] appears.  
[[[DIVIDER]]]
    * permalink: Use the same slug as title_brainstorm[0].filename, without its .md extension, in [INSERT PERMALINK PATTERN]. The selected publishing target owns the prefix; articleizer.py constructs the final permalink from that configuration and the first filename, not from this suggested URL.
[[[REPLACE]]]

Target: scripts/articles/editing_prompt.txt
[[[SEARCH]]]
  * promotional_tweet: Write a tweet for X.com where we can announce this article so that it will most effectively reach the target audience whom it will most help. Use the same reasoning you used for choosing the top-choice SEO title and URL and adapt it for a tweet. Include the naked URL (e.g., [INSERT BASE URL][INSERT PERMALINK PATTERN]) and NOT markdown format because of how X.com works. Include the 2 or 3 best hashtags, space allowing. Target the size of a traditional "non-verified" tweet (max 280 characters).
[[[DIVIDER]]]
  * promotional_tweet: Write promotional prose for X.com and put the literal [ARTICLE_URL] on its own line. Do not supply any other URL or Markdown link. articleizer.py replaces this marker with the post's canonical URL, or removes it when the target has no base URL. Include the 2 or 3 best hashtags, space allowing. Budget at most 280 characters INCLUDING the final URL, whose shape is [INSERT BASE URL][INSERT PERMALINK PATTERN].
[[[REPLACE]]]

Target: scripts/articles/editing_prompt.txt
[[[SEARCH]]]
  * title_brainstorm: Populate with the full list of your 3-5 title ideas, including rationales and filenames. Ensure the first entry is your preferred choice. Do not prepend the date on the filename (that happens automatically).  
[[[DIVIDER]]]
  * title_brainstorm: Populate with 3-5 title ideas, rationales and filenames. The first entry is the preferred choice and the sole source of the published slug. Each filename must be a lowercase alphanumeric/hyphen slug ending in .md, with no folder or date prefix; the writer adds the date.
[[[REPLACE]]]

No ignition required for the probes: they load the edited source at call time. No shell restart or Nix rebuild is needed. The next ordinary publishing invocation loads the changed writer and, unless using cached instructions, the changed template.

4. PROMPT

Verify the publishing-target permalink repair in the active root /home/mike/qamyai.

Read this compile's live receipts before making claims. Expected AFTER readings:
- articleizer_ast=OK
- configured_writer_calls=1
- identity_cases=8/8
- promo_marker_mentions=1
- slug_owner=True

Confirm that the writer selects one validated slug from title_brainstorm[0].filename, reuses lsa.permalink_prefix and lsa.default_permalink, and receives target_config from the common API/web/--local call site.

Keep the existing policy: missing/null prefix defaults through lsa; an explicitly empty prefix means root-level URLs; an empty base_url means no canonical or promotional public URL.

Confirm that the heading fix remains intact, collision checks use the finalized permalink, and no configuration declarations, historical posts, or article-body URLs were migrated.

Distinguish the live in-memory identity checks from any separately supplied real publishing witness. Do not call publication a read-only probe. If the readings pass and no concrete regression appears, close the implementation rather than extending its scope.

Terminology: active root selects the code; publishing target selects the destination; working directory is the process's current location.

5. EXTERNAL DELIVERABLES

None. Both changes belong to the active root; the publishing configuration and existing blog contents remain unchanged.

MikeLev.in: I’m actually sort of glad I made that current directory mistake. This is part of system battle-testing and hardening. A project’s durability can be measured by its scars. Most projects try to hide their scars and put forward a sort of face as if it’s a cooking show with the finished (and flawless) product pulled fully-baked out of the oven.

That is not as useful to the AIs as revealing the ugly scars accumulated along the way of making the recipe it took to make such a quality finished product. The future reproducible finished products come out better when the AI is aware of all the many pitfalls it may also try to reproduce using the scars as pinball board bumpers.

Tell ‘em, ChatGPT!

THE AI-EDIT METHOD

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

1: BEFORE:

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) qamyai $ .venv/bin/python -B -c 'import ast,re,sys; from pathlib import Path; sys.path.insert(0,"scripts/articles"); import lsa; t=ast.parse(Path("scripts/articles/articleizer.py").read_text()); f=next(n for n in t.body if isinstance(n,ast.FunctionDef) and n.name=="create_jekyll_post"); bound=sum(isinstance(n,ast.Call) and isinstance(n.func,ast.Name) and n.func.id=="create_jekyll_post" and any(k.arg=="target_config" and isinstance(k.value,ast.Name) and k.value.id=="target_config" for k in n.keywords) for n in ast.walk(t)); names=lambda n: [x.id for x in n.targets if isinstance(x,ast.Name)] if isinstance(n,ast.Assign) else []; i=next(i for i,n in enumerate(f.body) if "editing_instr" in names(n)); j=next(i for i,n in enumerate(f.body) if "new_yaml_data" in names(n)); f.body=f.body[i:j]+ast.parse("return permalink, canonical_url, locals().get(\"promo_text\", analysis_content.get(\"promotional_tweet\", \"\"))").body; ns={"lsa":lsa,"re":re}; exec(compile(ast.fix_missing_locations(ast.Module(body=[f],type_ignores=[])),"<identity-probe>","exec"),ns); kw=any(x.arg=="target_config" for x in f.args.kwonlyargs); u="https://example.invalid"; old=u+"/futureproof/probe/"; cases=[("vault",{"permalink_prefix":"vault"},u,old,"/vault/probe/"),("root",{"permalink_prefix":""},u,old,"/probe/"),("private",{"permalink_prefix":"vault"},"",old,"/vault/probe/"),("correct",{"permalink_prefix":"futureproof"},u,old,"/futureproof/probe/"),("missing",{},u,old,"/futureproof/probe/"),("null",{"permalink_prefix":None},u,old,"/futureproof/probe/"),("marker",{"permalink_prefix":"vault"},u,"[ARTICLE_URL]","/vault/probe/"),("relative",{"permalink_prefix":"vault"},"","/futureproof/probe/","/vault/probe/")]; run=lambda cfg,base,link: ns["create_jekyll_post"]("",{"editing_instructions":{"yaml_updates":{"permalink":"/futureproof/probe/"}},"book_analysis_content":{"title_brainstorm":[{"filename":"probe.md"}],"promotional_tweet":"Read\n\n"+link}},"",0,base,**({"target_config":cfg} if kw else {})); checks=[(name,run(cfg,base,link)==(p,base+p if base else "",("Read\n\n"+(base+p if base else "")).strip())) for name,cfg,base,link,p in cases]; print("articleizer_ast=OK"); print("configured_writer_calls="+str(bound)); [print(name+"="+str(ok)) for name,ok in checks]; print("identity_cases="+str(sum(ok for _,ok in checks))+"/"+str(len(checks))); sys.exit(0 if bound==1 and all(ok for _,ok in checks) else 1)'
.venv/bin/python -B -c 'from pathlib import Path; s=Path("scripts/articles/editing_prompt.txt").read_text(); print("promo_marker_mentions="+str(s.count("[ARTICLE_URL]"))); print("slug_owner="+str("sole source of the published slug" in s))'
articleizer_ast=OK
configured_writer_calls=0
vault=False
root=False
private=False
correct=True
missing=True
null=True
marker=False
relative=False
identity_cases=3/8
promo_marker_mentions=0
slug_owner=False
(nix) qamyai $ 

2: AFTER:

# # Context 1
# foo_files.py
# ~/repos/nixos/blogs.nix
# ~/.config/pipulate/blogs.json                # <-- CAUTION! Derived from ~/repos/nixos/blogs.nix
# scripts/articles/publishizer.py              # <-- Orchestrates different publishing workflows per target blog.
# scripts/articles/common.py                   # <-- Self-explanatory
# scripts/articles/articleizer.py              # <-- Transforms raw article.txt to formal Jekyll markdown format
# scripts/articles/editing_prompt.txt          # <-- Forcing response into strict JSON data structure
# scripts/articles/sanitizer.py                # <-- Scrubs PII
# scripts/articles/gsc_historical_fetch.py
# scripts/articles/contextualizer.py           # <-- Builds JSON summaries of articles in `_posts/context/` called "Holographic Shards".
# scripts/articles/confluenceizer.py           # <-- Idempotent Jekyll-to-Confluence corporate wiki
# scripts/articles/googledocizer.py            # <-- Just added
# scripts/articles/build_knowledge_graph.py    # <-- Topically load-balances site using hierarchical K-Means keyword clustering groups
# scripts/articles/generate_ai_context.py      # <-- AIs WILL interrogate your repo. This gives epic context of article URLs for drill-down.
# scripts/articles/generate_hubs.py            # <-- Uses just-produced link-graph data to generate each of the new hubs it suggests
# scripts/articles/generate_llms_txt.py        # <-- Builds an llms.txt based on the auto-organized structure suggested here
# scripts/articles/generate_redirects.py       # <-- Generates redirect map above hub-churn suggests is needed
# scripts/articles/sanitize_redirects.py       # <-- Deals with follow-up meticulous pedantic detail required for a good Nginx redirect map
# scripts/articles/link_injector.py
# scripts/articles/generate_post_nav.py  # [1,174 tokens | 4,616 bytes]
# deleteme.json
# /home/mike/qamyai/scripts/articles/article.txt
# /home/mike/repos/trimnoir/_posts/2026-10-03-separating-exposure-from-corruption-render-canary.md

# # Context 2
# scripts/articles/articleizer.py
# scripts/articles/editing_prompt.txt
# scripts/articles/common.py
# scripts/articles/sanitizer.py
# scripts/articles/article.txt
# deleteme.json
# init.lua
# prompt_foo.py
# scripts/foo_cartridge.py
# flake.nix
# apply.py
# ! .venv/bin/python -B -c 'import ast,json,re,textwrap; from pathlib import Path; s=Path("scripts/articles/articleizer.py").read_text(); ast.parse(s); a=s.index("    prepend_text = "); b=s.index("    # --- WRAPPING LOGIC START ---",a); n={"re":re,"editing_instr":json.loads(Path("deleteme.json").read_text())["editing_instructions"],"article_body":"BODY_SENTINEL"}; exec(textwrap.dedent(s[a:b]),n); h=re.findall(r"^#{1,6}[ \t]+.*$",n["article_body"],re.M); print("articleizer_ast=OK"); print("intro_heading_count="+str(len(h))); print("\n".join(h)); print("body_preserved="+str(n["article_body"].endswith("BODY_SENTINEL")))'
# ! rg -n -m 3 -A 9 -B 2 -F '6. NOTARIZE:' init.lua; printf 'notary_template_rc=%s\n' "$?"
# ! .venv/bin/python -B scripts/foo_cartridge.py qamy.ai_40-fc0beb6f.zip; printf 'archive_verify_rc=%s\n' "$?"
# ! .venv/bin/python -B -c 'from pathlib import Path; from datetime import datetime,timezone; import json,zipfile; p=Path("qamy.ai_40-fc0beb6f.zip"); print("archive_name="+p.name); print("archive_mtime_utc="+datetime.fromtimestamp(p.stat().st_mtime,timezone.utc).isoformat()); z=zipfile.ZipFile(p); m=json.loads(z.read("manifest.json")); z.close(); print("manifest_key_count="+str(len(m))); print("manifest_keys_first20="+json.dumps(sorted(m)[:20]))'

# # Context 3
# ~/repos/nixos/blogs.nix
# ~/.config/pipulate/blogs.json
# scripts/articles/common.py
# scripts/articles/lsa.py
# scripts/articles/articleizer.py
# scripts/articles/editing_prompt.txt
# flake.nix
# apply.py
# ! printf 'PWD=%s\nPIPULATE_ROOT=%s\nPIPULATE_BLOGS_CONFIG=%s\n' "$PWD" "${PIPULATE_ROOT:-}" "${PIPULATE_BLOGS_CONFIG:-}"; git rev-parse --show-toplevel HEAD
# ! for repo in /home/mike/repos/pipulate /home/mike/qamyai; do printf '\nCHECKOUT=%s\n' "$repo"; git -C "$repo" log -1 --format='%h %s' -- scripts/articles/articleizer.py scripts/articles/editing_prompt.txt; done
# ! .venv/bin/python -B -c 'import json,os; from pathlib import Path; p=Path(os.environ.get("PIPULATE_BLOGS_CONFIG",Path.home()/".config"/"pipulate"/"blogs.json")).expanduser(); print("blogs_config="+str(p.resolve())); d=json.loads(p.read_text()); print("target_count="+str(len(d))); [print(json.dumps({"target":k,"alias":v.get("alias"),"permalink_prefix":v.get("permalink_prefix"),"base_url":v.get("base_url"),"path":v.get("path")},sort_keys=True)) for k,v in sorted(d.items())[:12]]'
# ! rg -n -m 20 'permalink_prefix|default_permalink|create_jekyll_post|write_post|alias (article|grim|vault|bot)=' scripts/articles/lsa.py scripts/articles/articleizer.py flake.nix

# Context 4 NOW I understand why we're on Context 4 on the 3rd turn, ha ha ha!
~/repos/nixos/blogs.nix
~/.config/pipulate/blogs.json
scripts/articles/common.py
scripts/articles/lsa.py
scripts/articles/articleizer.py
scripts/articles/editing_prompt.txt
apply.py
! .venv/bin/python -B -c 'import ast,re,sys; from pathlib import Path; sys.path.insert(0,"scripts/articles"); import lsa; t=ast.parse(Path("scripts/articles/articleizer.py").read_text()); f=next(n for n in t.body if isinstance(n,ast.FunctionDef) and n.name=="create_jekyll_post"); bound=sum(isinstance(n,ast.Call) and isinstance(n.func,ast.Name) and n.func.id=="create_jekyll_post" and any(k.arg=="target_config" and isinstance(k.value,ast.Name) and k.value.id=="target_config" for k in n.keywords) for n in ast.walk(t)); names=lambda n: [x.id for x in n.targets if isinstance(x,ast.Name)] if isinstance(n,ast.Assign) else []; i=next(i for i,n in enumerate(f.body) if "editing_instr" in names(n)); j=next(i for i,n in enumerate(f.body) if "new_yaml_data" in names(n)); f.body=f.body[i:j]+ast.parse("return permalink, canonical_url, locals().get(\"promo_text\", analysis_content.get(\"promotional_tweet\", \"\"))").body; ns={"lsa":lsa,"re":re}; exec(compile(ast.fix_missing_locations(ast.Module(body=[f],type_ignores=[])),"<identity-probe>","exec"),ns); kw=any(x.arg=="target_config" for x in f.args.kwonlyargs); u="https://example.invalid"; old=u+"/futureproof/probe/"; cases=[("vault",{"permalink_prefix":"vault"},u,old,"/vault/probe/"),("root",{"permalink_prefix":""},u,old,"/probe/"),("private",{"permalink_prefix":"vault"},"",old,"/vault/probe/"),("correct",{"permalink_prefix":"futureproof"},u,old,"/futureproof/probe/"),("missing",{},u,old,"/futureproof/probe/"),("null",{"permalink_prefix":None},u,old,"/futureproof/probe/"),("marker",{"permalink_prefix":"vault"},u,"[ARTICLE_URL]","/vault/probe/"),("relative",{"permalink_prefix":"vault"},"","/futureproof/probe/","/vault/probe/")]; run=lambda cfg,base,link: ns["create_jekyll_post"]("",{"editing_instructions":{"yaml_updates":{"permalink":"/futureproof/probe/"}},"book_analysis_content":{"title_brainstorm":[{"filename":"probe.md"}],"promotional_tweet":"Read\n\n"+link}},"",0,base,**({"target_config":cfg} if kw else {})); checks=[(name,run(cfg,base,link)==(p,base+p if base else "",("Read\n\n"+(base+p if base else "")).strip())) for name,cfg,base,link,p in cases]; print("articleizer_ast=OK"); print("configured_writer_calls="+str(bound)); [print(name+"="+str(ok)) for name,ok in checks]; print("identity_cases="+str(sum(ok for _,ok in checks))+"/"+str(len(checks))); sys.exit(0 if bound==1 and all(ok for _,ok in checks) else 1)'
! .venv/bin/python -B -c 'from pathlib import Path; s=Path("scripts/articles/editing_prompt.txt").read_text(); print("promo_marker_mentions="+str(s.count("[ARTICLE_URL]"))); print("slug_owner="+str("sole source of the published slug" in s))'

3: CHANGE:

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) qamyai $ patch
(nix) qamyai $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
(nix) qamyai $ d
diff --git a/scripts/articles/articleizer.py b/scripts/articles/articleizer.py
index c11755d..f45e828 100644
--- a/scripts/articles/articleizer.py
+++ b/scripts/articles/articleizer.py
@@ -224,7 +224,8 @@ def build_book_spine(entries, full_detail_count=SPINE_FULL_DETAIL_COUNT):
     lines += [f"{e['date']} {e['slug']} | {e['title']}" for e in recent]
     return "\n".join(lines)
 
-def create_jekyll_post(article_content, instructions, output_dir, preview_port, base_url=""):
+def create_jekyll_post(article_content, instructions, output_dir, preview_port, base_url="",
+                       *, target_config=None):
     """
     Assembles and writes a Jekyll post file from the article content and
     structured AI-generated instructions.
@@ -278,14 +279,38 @@ def create_jekyll_post(article_content, instructions, output_dir, preview_port,
     analysis_content = instructions.get("book_analysis_content", {})
     yaml_updates = editing_instr.get("yaml_updates", {})
 
-    # --- NEW: Construct the absolute Canonical URL ---
-    permalink = yaml_updates.get("permalink", "")
-    # Ensure proper slash formatting
-    if not permalink.startswith("/"):
-        permalink = f"/{permalink}"
+    # One slug; the selected publishing target owns its URL prefix.
+    try:
+        filename = analysis_content["title_brainstorm"][0]["filename"]
+    except (KeyError, IndexError, TypeError):
+        filename = None
+    slug = filename.removesuffix(".md") if isinstance(filename, str) else ""
+    if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", slug):
+        print("Refusing to write: title_brainstorm[0].filename must be a "
+              "lowercase alphanumeric/hyphen slug, optionally ending in .md.")
+        return None
+    prefix = lsa.permalink_prefix(target_config)
+    permalink = lsa.default_permalink(slug, prefix) + "/"
     base_url = (base_url or "").rstrip("/")
     canonical_url = f"{base_url}{permalink}" if base_url else ""
-    # -----------------------------------------------
+    # Only the generated promo is normalized, never links in the article body.
+    # Legacy cached replies carry a URL; new replies carry [ARTICLE_URL].
+    promo_text = analysis_content.get("promotional_tweet", "")
+    if not isinstance(promo_text, str):
+        print("Refusing to write: promotional_tweet must be a string.")
+        return None
+    promo_links = list(re.finditer(
+        r"\[ARTICLE_URL\]|https?://\S+|(?<!\S)/\S+", promo_text))
+    if len(promo_links) > 1:
+        print("Refusing to write: promotional_tweet must contain at most "
+              "one article link or [ARTICLE_URL] marker.")
+        return None
+    if promo_links:
+        match = promo_links[0]
+        promo_text = promo_text[:match.start()] + canonical_url + promo_text[match.end():]
+    elif canonical_url and promo_text:
+        promo_text = f"{promo_text}\n\n{canonical_url}"
+    promo_text = promo_text.strip()
 
     new_yaml_data = {
         'title': yaml_updates.get("title"),
@@ -384,7 +409,7 @@ def create_jekyll_post(article_content, instructions, output_dir, preview_port,
     if 'ai_editorial_take' in analysis_content:
         analysis_markdown += f"\n### Ai Editorial Take\n{analysis_content['ai_editorial_take']}\n"
     if 'promotional_tweet' in analysis_content:
-        analysis_markdown += f"\n### 🐦 X.com Promo Tweet\n[triple-backtick]text\n{analysis_content['promotional_tweet']}\n[triple-backtick]\n"
+        analysis_markdown += f"\n### 🐦 X.com Promo Tweet\n[triple-backtick]text\n{promo_text}\n[triple-backtick]\n"
     for key, value in analysis_content.items():
         if key in ['authors_imprint', 'ai_editorial_take', 'promotional_tweet']:
             continue
@@ -411,11 +436,7 @@ def create_jekyll_post(article_content, instructions, output_dir, preview_port,
 
     final_content = f"{final_yaml_block}\n\n{article_body}\n\n---\n{analysis_markdown}"
 
-    # 4. Generate Filename
-    slug = "untitled-article"
-    title_brainstorm = analysis_content.get("title_brainstorm", [])
-    if title_brainstorm and title_brainstorm[0].get("filename"):
-        slug = os.path.splitext(title_brainstorm[0]["filename"])[0]
+    # 4. Generate Filename from the same validated slug used in the URL.
 
     output_filename = f"{current_date}-{slug}.md"
 
@@ -435,7 +456,7 @@ def create_jekyll_post(article_content, instructions, output_dir, preview_port,
         print("🛑 COLLISION GUARD: refusing to write new article.")
         for c in collisions:
             print(f"   - {c}")
-        print("   Pick a new slug/permalink (edit instructions.json, rerun with --local).")
+        print("   Pick a new title_brainstorm[0].filename in instructions.json; rerun with --local.")
         return None
 
     output_path = os.path.join(output_dir, output_filename)
@@ -826,9 +847,13 @@ def main():
         base_url = target_config.get("base_url")
         if base_url is None:
             base_url = target_config.get("url", "https://mikelev.in")
-        saved_path = create_jekyll_post(article_text, instructions, output_dir, preview_port, base_url)
+        saved_path = create_jekyll_post(
+            article_text, instructions, output_dir, preview_port, base_url,
+            target_config=target_config)
         if saved_path:
             common.record_last_published(args.target, saved_path, target_config.get("name"))
+        else:
+            sys.exit(1)
 
 
 if __name__ == '__main__':
(nix) qamyai $ m
📝 Committing: refactor: Add target_config to create_jekyll_post
[main 63d199e] refactor: Add target_config to create_jekyll_post
 1 file changed, 40 insertions(+), 15 deletions(-)
(nix) qamyai $ patch
(nix) qamyai $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/editing_prompt.txt'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/editing_prompt.txt'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/editing_prompt.txt'.
(nix) qamyai $ d
diff --git a/scripts/articles/editing_prompt.txt b/scripts/articles/editing_prompt.txt
index 6dcaf18..76fe99a 100644
--- a/scripts/articles/editing_prompt.txt
+++ b/scripts/articles/editing_prompt.txt
@@ -72,7 +72,7 @@ The JSON object must conform to the following schema:
   * **yaml_updates**:  
     * title: Populate with your **preferred title** from your analysis.  
     * description: Populate with the meta_description you craft. This should be 100-160 characters.  
-    * permalink: Generate the SEO-optimized filename slug (e.g., primary-keywords.md) and use it to construct the permalink string in the format [INSERT PERMALINK PATTERN] -- copy that shape verbatim, keeping its leading and trailing slashes, and put your slug where [slug] appears.  
+    * permalink: Use the same slug as title_brainstorm[0].filename, without its .md extension, in [INSERT PERMALINK PATTERN]. The selected publishing target owns the prefix; articleizer.py constructs the final permalink from that configuration and the first filename, not from this suggested URL.
     * keywords: Populate with the comma-separated meta_keywords string.  
   * **prepend_to_article_body**:  
     * Write only the introductory prose for the curious book reader. Do not include a heading, title, horizontal rule, or fenced wrapper. articleizer.py adds the single "Setting the Stage: Context for the Curious Book Reader" heading and the section separator.
@@ -85,8 +85,8 @@ The JSON object must conform to the following schema:
           * VALIDATION: Ensure the snippet is followed by a double line-break (blank line) in the source text. Do NOT select words from the middle of a sentence or paragraph.
 * **book_analysis_content**: This top-level key contains all the valuable analysis that doesn't get edited into the file directly but is used for the book-writing process.  
   * authors_imprint: Populate with the first-person summary.  
-  * promotional_tweet: Write a tweet for X.com where we can announce this article so that it will most effectively reach the target audience whom it will most help. Use the same reasoning you used for choosing the top-choice SEO title and URL and adapt it for a tweet. Include the naked URL (e.g., [INSERT BASE URL][INSERT PERMALINK PATTERN]) and NOT markdown format because of how X.com works. Include the 2 or 3 best hashtags, space allowing. Target the size of a traditional "non-verified" tweet (max 280 characters).
-  * title_brainstorm: Populate with the full list of your 3-5 title ideas, including rationales and filenames. Ensure the first entry is your preferred choice. Do not prepend the date on the filename (that happens automatically).  
+  * promotional_tweet: Write promotional prose for X.com and put the literal [ARTICLE_URL] on its own line. Do not supply any other URL or Markdown link. articleizer.py replaces this marker with the post's canonical URL, or removes it when the target has no base URL. Include the 2 or 3 best hashtags, space allowing. Budget at most 280 characters INCLUDING the final URL, whose shape is [INSERT BASE URL][INSERT PERMALINK PATTERN].
+  * title_brainstorm: Populate with 3-5 title ideas, rationales and filenames. The first entry is the preferred choice and the sole source of the published slug. Each filename must be a lowercase alphanumeric/hyphen slug ending in .md, with no folder or date prefix; the writer adds the date.
   * content_potential_and_polish: Structure the "Core Strengths" and "Constructive Polish" suggestions here.  
   * ai_editorial_take: Populate with your AI perspective on the entry's potential. What surprised you most? Don't use the exact same thing as the previous AI participating in the dialogue. Find some absolutely new angle or perspective that isn't mentioned yet in the article.
   * next_step_prompts: Populate with your 1-2 suggestions for subsequent AI tasks.
(nix) qamyai $ m
📝 Committing: refactor: improve editing prompt documentation and clarify prompt usage
[main fb0b86e] refactor: improve editing prompt documentation and clarify prompt usage
 1 file changed, 3 insertions(+), 3 deletions(-)
(nix) qamyai $ git push
Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 48 threads
Compressing objects: 100% (10/10), done.
Writing objects: 100% (10/10), 2.24 KiB | 2.24 MiB/s, done.
Total 10 (delta 8), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (8/8), completed with 5 local objects.
To github.com:pipulate/pipulate.git
   04648a3..fb0b86e  main -> main
(nix) qamyai $ 

Ignition: None until we articleize this article which I’m super-pumped to do right after I extract an article wrap-up from our friendly neighborhood Amnesiac Genie Pinball Machine maybe something inside so uppercase respect 🫡🖖

4: PROMPT:

Verify the publishing-target permalink repair in the active root /home/mike/qamyai.

Read this compile’s live receipts before making claims. Expected AFTER readings:

  • articleizer_ast=OK
  • configured_writer_calls=1
  • identity_cases=8/8
  • promo_marker_mentions=1
  • slug_owner=True

Confirm that the writer selects one validated slug from title_brainstorm[0].filename, reuses lsa.permalink_prefix and lsa.default_permalink, and receives target_config from the common API/web/–local call site.

Keep the existing policy: missing/null prefix defaults through lsa; an explicitly empty prefix means root-level URLs; an empty base_url means no canonical or promotional public URL.

Confirm that the heading fix remains intact, collision checks use the finalized permalink, and no configuration declarations, historical posts, or article-body URLs were migrated.

Distinguish the live in-memory identity checks from any separately supplied real publishing witness. Do not call publication a read-only probe. If the readings pass and no concrete regression appears, close the implementation rather than extending its scope.

Terminology: active root selects the code; publishing target selects the destination; working directory is the process’s current location.

5: DELIVERABLE: This article and a better process for making it, I do believe.

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

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

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

ChatGPT 6 Pro:

1. VERIFY

The permalink-ownership repair has landed and passes this compile’s checks. Your recorded BEFORE readings and this compile’s live AFTER readings match the forecast exactly:

Check Recorded BEFORE Live AFTER
Python syntax OK OK
Writer calls passing the selected configuration 0 1
Identity cases passing 3/8 8/8
Promotional URL marker in the template 0 1
First filename explicitly owns the slug False True

The eight passing cases cover a nondefault prefix, an empty prefix, an empty base URL, already-correct input, missing and null prefixes, the promotional marker, and a legacy relative promotional link. These are in-memory identity checks, not eight publication runs. Pasted text(20261004-015124) Pasted text(20261004-015124)

The current source confirms the intended ownership chain: title_brainstorm[0].filename supplies the validated slug; the existing lsa helpers supply the configured path; the shared writer call receives target_config. Frontmatter, preview links, and collision checks use that finalized identity. Missing/null prefixes retain the existing fallback, while an explicitly empty prefix remains empty. Pasted text(20261004-015124) Pasted text(20261004-015124) Pasted text(20261004-015124) Pasted text(20261004-015124)

The heading fix remains intact. The new URL normalization operates on generated promotional text, not URLs quoted in the article body. The shown changes touch the writer and editing prompt; they introduce no configuration rewrite or historical-post migration. Your log records commits 63d199e and fb0b86e, followed by the push. Pasted text(20261004-015124) Pasted text(20261004-015124) Pasted text(20261004-015124)

No ignition is missing for the measured checks: they loaded the edited source during this compile. The complete publishing workflow has not yet run after this repair; your note explicitly places articleization after this wrap-up. That is a remaining production observation, not a stale AFTER reading. Pasted text(20261004-015124) Pasted text(20261004-015124)

2. BANK

No additional BANK cars. No repo patches required.

The earned rule already lives in the implementation and its editing instructions:

The Model proposes the slug. The publishing target owns the prefix. The writer constructs the URL. Pasted text(20261004-015124) Pasted text(20261004-015124)

The terminology stays equally precise: the active root selects the code; the publishing target selects the destination; the working directory is the process’s current location. No additional glossary or router write is claimed in this dismount.

3. DANGLING

The first complete article-generation run with the repaired writer remains unwitnessed. No further permalink implementation change is indicated by the current checks.

The pasted notary instructions still contain the superseded “name is not in your context” wording; this compile does not establish whether that came from an older prompt or the currently loaded editor template. Pasted text(20261004-015124)

4. SEED

No additional verification turn is required to close this repair. No next implementation ride has been selected, so no new context.txt block or TODO_SLUGS is prescribed.

5. CLOSING

This began with mechanical sympathy: understanding a machine well enough to stop fighting it. The practical result was mechanical advantage: taking a decision that already had a declared answer and making the machinery carry it.

A Model can help name an article, explain its significance, and find a useful angle. It does not need to decide which configured folder owns that article. Giving it that responsibility created another opportunity for a convincing answer to disagree with the system.

The improvement was not to demand a more obedient Model. It was to remove an unnecessary decision from its workload. Configuration declares the destination, code constructs the address, and the checks expose disagreement before the result becomes another manual cleanup.

We did not make every pinball bounce identical. We stopped leaving the destination to a bounce.

Practical Leverage: Retaining Judgment and Delegating Facts

That is the leverage: use the Model where judgment adds value, and let code carry the facts already settled.

6. NOTARIZE

This compile’s input archive is qamy.ai_43-b746dec4.zip, explicitly identified by its final footer. The Manifest lists two live command receipts, and both return the expected results. Pasted text(20261004-015124) Pasted text Pasted text(20261004-015124)

The Summary reports 292,979 UTF-8 bytes. That is a compiler-reported size, not an independent wc -c measurement. Neither live receipt records an execution timestamp, and no independent ZIP-verification result for deed 43 is supplied here. The archive’s identity is known; independent verification is not claimed. Pasted text(20261004-015124) Pasted text(20261004-015124)

The permalink-repair implementation is closed. This archive records its input evidence—not this later response or the article that will subsequently be generated.

Deed: qamy.ai_43-b746dec4.zip


Book Analysis

Ai Editorial Take

What stands out most sharply in this exchange is the philosophical inversion of how AI mistakes are handled. Most software developers treat AI missteps as prompting failures to be solved by ‘better prompting.’ Here, the realization is fundamentally different: when an LLM fails at a task like folder selection, the error is an architectural smell indicating that the model was asked to handle state and configuration rather than linguistic synthesis. The true mechanical advantage comes from narrowing the model’s responsibility to creative judgment while forcing replayable, checkable code to manage identity.

🐦 X.com Promo Tweet

Mechanical sympathy teaches you to work with the tool; mechanical advantage turns that into leverage. We fixed URL prefix ownership and locked in the active root:

https://mikelev.in/futureproof/mechanical-advantage-and-the-active-root/

#Python #AI

Title Brainstorm

  • Title Option: Mechanical Advantage and the Active Root: Authoritative Prefixes for Replayable AI Pipelines
    • Filename: mechanical-advantage-and-the-active-root.md
    • Rationale: Directly bridges Jackie Stewart’s mechanical sympathy to the architectural leverage of authoritative prefixes and single-slug ownership across repositories.
  • Title Option: The Active Root and the Authoritative Prefix: Hardening Replayable Publishing Pipelines
    • Filename: the-active-root-and-the-authoritative-prefix.md
    • Rationale: Emphasizes the operational terminology established during the session, distinguishing the active codebase from target destinations.
  • Title Option: From Mechanical Sympathy to Mechanical Advantage: Enforcing Boundaries in AI Tooling
    • Filename: from-mechanical-sympathy-to-mechanical-advantage.md
    • Rationale: Focuses on the philosophical shift from merely understanding LLM mechanics to codifying mechanical leverage that eliminates manual intervention.
  • Title Option: The Single Slug Rule: Clarifying Identity Across Workspaces and Targets
    • Filename: the-single-slug-rule-and-workspace-identity.md
    • Rationale: Highlights the architectural fix of making title_brainstorm[0].filename the sole owner of identity, preventing prompt-model discrepancies.

Content Potential And Polish

  • Core Strengths:
    • Clear articulation of the difference between mechanical sympathy and mechanical advantage applied directly to software development.
    • Honest, transparent capture of a human operational error (repo hopping) that became an opportunity to harden the system.
    • Rigorous verification methodology demonstrating eight distinct identity test cases passing against live code.
  • Suggestions For Polish:
    • Consolidate the multiple raw shell transcripts and AST test snippets into high-level summaries so the conceptual narrative flows smoothly.
    • Provide a clear architectural diagram or flow table illustrating how active root, publishing target, and working directory interact.

Next Step Prompts

  • Draft an automated integration test script in scripts/articles/tests/ that executes an end-to-end dry-run publication for each of the four configured targets to verify prefix enforcement.
  • Create a pre-flight shell check for write_post that asserts PWD matches PIPULATE_ROOT before executing publishing commands, preventing cross-checkout confusion.