:orphan:

.. _hell-ll-infra-b45-path-case-drift-link-integrity-llog:

===========================================================================================================
LLog --- Path-case drift on the deploy server, and a consistent guard against silent link death
===========================================================================================================

| **Date:** 2026m08d03_15h42
| **Effort:** Max (from file ``.claude/effort-level``; set in-session via ``/model`` to Opus 5 max effort)
| **Mode:** EDEN (from file ``.claude/mode``; NOT yet confirmed by LLoL in-session --- reported at reply top)
| **Model:** Claude Opus 5 --- proposed VVN nick ``ClaOp5Max`` (PENDING LLoL confirmation; first Opus 5
  session in this repo; pattern follows ``ClaOp48Max`` / ``ClaFa5Max``)
| **Scope:** (1) Independent check of a prior agent's diagnosis and remediation brief concerning a
  git-index-vs-disk directory-name CASE MISMATCH that silently 404s pages on the live site;
  (2) measurement of the FULL class of silently-dying references of which that bug is one instance;
  (3) a consistent prevention plan so this class becomes visible instead of invisible.

.. note::

   Append-only audit trail. Driving prompt recorded verbatim below. **No source content files were
   modified in this session.** This is a check-and-plan session; all remediation is proposed, not
   applied. The only file written is this llog.


Verbatim prompt 1 (the task)
============================

.. container:: verbatim-prompt

   ::

      Another agent found a bug with the license naming and prepared some work best fixed by you.
      I want you to check that other work and make a plan for how to handle all similar errors in
      this project consistently, so I don't have to worry about them any more. ---    Here is the
      work to be done as described by that other agent:    Please fix a path-case bug that is
      silently 404-ing pages on the live site, then build out the /license/ section. Everything
      below was diagnosed by reading the repo and curl-ing the live site on 2026-08-03. Verify each
      claim yourself before acting — do not take it on trust. Where it says "ask me", stop and ask;
      do not guess.

      # THE BUG, IN ONE SENTENCE

      Git's index records two directory names in a different CASE than the working tree, so a
      checkout on the case-sensitive deploy server produces `source/FF/` and `source/license/JoLi/`
      while every reference in the repo points at `ff/` and `joli/` — and those references silently
      die.

      ## Evidence

          on disk (python os.listdir, authoritative):   source/ff        source/license/joli
          git ls-files says:                            source/FF        source/license/JoLi

      macOS is case-INsensitive, so git never noticed the directories were renamed to lowercase at
      some point and kept the old index entries. Everything resolves locally. On the deploy server
      it does not:

          https://balospe.com/en/license/joli/    404      https://balospe.com/en/license/JoLi/    200
          https://balospe.com/en/ff/              404      https://balospe.com/en/FF/              200
          https://balospe.com/en/ff/system/       404      https://balospe.com/en/FF/system/       200
          https://balospe.com/en/ff/contact/      404      https://balospe.com/en/FF/contact/      200
                                                           https://balospe.com/en/FF/archive/      200
                                                           https://balospe.com/en/FF/philosophy/   200

      Reference counts in `source/`:

          327 files contain "license/joli"   |   2 contain "license/JoLi"  (both are the 2026-07-30 patch, step 2)
           25 files reference "/ff/"         |   0 reference "/FF/index"

      Sphinx does NOT fail on an unresolvable `:doc:` — it renders the link TEXT with no href. The
      build is green, the pages look right locally, and the links are simply dead in production.
      That is why this survived. A full-repo scan (`git ls-files` vs `os.listdir`) found EXACTLY
      these two and no others.

      ## Why it matters more than it looks

      `/en/ff/` is FeedbackFlow — the site's entire participation mechanism, the thing "Audit the
      Math" asks people to use. Six FF pages are unreachable at the URL the whole site points to.
      Separately, the FF mailto footer on nearly every page tells contributors their submission is
      published "under the Jonah License (balospe.com/en/license/joli/)" — a 404, quoted in outgoing
      email as the licence term of their contribution. Outreach is in flight that asks people to
      review the work and links into the site, so this is live-fire, not housekeeping.

      # A CORRECTION TO MY OWN EARLIER FRAMING

      I said the Jonah License page was "buried deep inside the MMv3 folder." That was the symptom
      talking, not the layout.

      A full Jonah License page already exists at `source/license/joli/index.rst` — ~200 lines: the
      MMv1 terms, the Purpose section, and the "On the charge of moral blackmail" admonition. It is
      invisible for two compounding reasons:

      1. its toctree entry in `source/license/index.rst` never bound (the case mismatch), so it is
         absent from site navigation entirely; and
      2. `/license/index` itself sits in the LAST toctree of `source/index.rst` — captionless and
         `:hidden:`, grouped with `/legal/index` and `/sitemap/index` as a utility page.

      What IS buried in MMv3 is a different, smaller thing: the 2-page intro exhibit at
      `source/good-news-pack/vv/mmv3/open-letter/files-shared/jonah-license-intro.rst`, an
      AI-generated exhibit page wrapping the printable 2-page PDF handout.

      So the job is not "copy the JoLi out of MMv3." It is: fix the case so the real page binds,
      give `/license/` a proper section with JoLi and CyLi as subpages, and decide what to do with
      the exhibit page.

      # STEP 0 — BRANCH DISCIPLINE (first, do not skip)

      As of 2026-08-03 the repo is on `b16-revision-for-expert-review` with uncommitted changes:

          M  source/license/JoLi/index.rst        <- the 2026-07-30 symptom patch (step 2)
          M  source/license/index.rst             <- the 2026-07-30 symptom patch
          ?? source/_file/pdf/hell/mm/b/16/b16-riskymad-waiting-time_oov2_...pdf
          ?? source/_file/pdf/hell/mm/b/16/b16-riskymad-waiting-time_oov3_...pdf

      Deploy runs from `main` (`.github/workflows/deploy.yml` →
      `balosp/llol-build/.github/workflows/sphinx-deploy.yml@v3`, `on: push: branches: [main]`). A
      site-wide URL fix must NOT ride on a paper-revision branch.

      Ask me how I want the b16 work handled, then do this on a short branch off `main` (suggested
      `fix/path-case-and-license-section`). Leave the two untracked b16 PDFs alone.

      # STEP 1 — MAKE GIT AGREE WITH DISK

      Lowercase wins: it is already on disk and is what all 352 references expect. Renaming the
      directories to match git would mean editing 352 files; fixing the index fixes them all at once.

      `git mv` cannot do a case-only rename on a case-insensitive filesystem. Do NOT use the "move to
      a temp name and back" dance — nothing needs to move on disk, only the index is wrong:

          git rm -r --cached source/license/JoLi
          git add source/license/joli
          git rm -r --cached source/FF
          git add source/ff

      Confirm git reports pure renames and nothing else:

          git status
          git diff --cached --stat -M

      Expect `source/license/JoLi/index.rst -> source/license/joli/index.rst` plus five FF files
      renamed, with 0 insertions and 0 deletions. If any file shows content changes, STOP — the index
      fix has picked up something it should not have.

      # STEP 2 — REVERT THE 2026-07-30 SYMPTOM PATCH (it becomes wrong after step 1)

      On 2026-07-30 the refs in `source/license/index.rst` were changed from lowercase `joli` to
      capital `JoLi` as a one-page workaround. After step 1 that workaround BREAKS the page it was
      meant to fix. Revert to lowercase `joli/index` (match on text, not line number):

          :doc:`Jonah License <JoLi/index>`
          :doc:`full JoLi text <JoLi/index>`
          :doc:`The Jonah License page <JoLi/index>`
          toctree entry:  JoLi/index

      NAMING CONVENTION, confirmed: the prose `JoLi` as the licence's NAME stays capitalised
      everywhere; only PATH segments go lowercase. Same for the Cyrus License: **`CyLi` in prose,
      `cyli` in paths.**

      Also collapse the two long `.. TODO AA:` blocks at the top of `source/license/index.rst` and
      the CASE BUG item in `source/license/joli/index.rst` — they describe a bug that will no longer
      exist. Replace with a one-line note that the path case was fixed 2026-08-03 and `_redirects`
      carries the old capitalised URLs.

      # STEP 3 — REDIRECTS (StayVS: published URLs are not silently revoked)

      `/en/license/JoLi/` and the six `/en/FF/...` URLs are live and indexed today; after step 1 they
      404. Root `_redirects` (Cloudflare-Pages style, copied into the build by `Makefile:152`) already
      contains case-permutation rules for the audit-the-math campaign — follow that precedent:

          ###### Path-case fix 2026-08-03 — old capitalised paths kept alive (StayVS)
          /en/license/JoLi/*  /en/license/joli/:splat  301
          /en/FF/*            /en/ff/:splat            301

      Check the splat syntax against the rules already in the file and against the host before
      assuming `:splat` works; if it does not, enumerate the seven URLs explicitly. Test after deploy
      that BOTH cases resolve.

      # STEP 4 — "Cycrus" → "Cyrus"

      `Cycrus` is a typo — the licence is named after Cyrus the Great. Six lines, two files:

          source/license/index.rst:2    :description: ... the Cycrus License (CyLi) ...
          source/license/index.rst:3    :keywords: ... Cycrus License, CyLi, ...
          source/license/index.rst:72   The Cycrus License (CyLi) --- draft
          source/license/index.rst:75   The Cycrus License is designed to enable ...
          source/license/index.rst:95   **Why "Cycrus"?** The name honors Cyrus the Great, ...
          source/sitemap/index.rst:127  - Jonah License (JoLi) and Cycrus License (CyLi) overview.

      Line 95 currently reads *Why "Cycrus"? The name honors Cyrus the Great* — the typo sits in the
      same sentence as its own etymology. After the fix it reads correctly as *Why "Cyrus"?*.

      `CyLi` stays unchanged — the abbreviation is correct. Change only the word `Cycrus` → `Cyrus`.
      Also grep the whole repo (not just `source/`) and `locale/` in case translations picked the typo
      up, and make sure `build/` is regenerated rather than carrying stale HTML.

      # STEP 5 — BUILD A REAL /license/ SECTION (JoLi + CyLi as sibling subpages)

      Target structure:

          source/license/index.rst        Licenses — overview, links to both
          source/license/joli/index.rst   The Jonah License (exists; surfaced by step 1)
          source/license/cyli/index.rst   The Cyrus License (NEW — step 5a)

      ## 5a. Create `source/license/cyli/index.rst`

      A real page, honestly marked as not-yet-drafted, carrying a rough outline of what the licence
      will be about — not an empty placeholder, and not invented licence text.

      - Copy the page skeleton from `source/license/joli/index.rst`: the same `.. meta::` block shape
        (description / keywords / author / og:card:title / og:card:description), the `.. TODO AA:`
        page-maturity block, and the whole `----- FOOTER FORM OOv1r2p1 -----` footer with the FF
        mailto rewritten for `license-cyli`. Mirror how `license-joli` is encoded in the joli page's
        `raw:: html` block — the subject and body are URL-encoded, so edit carefully and check the
        rendered mailto.
      - Page status: use the lowest maturity marker the StayC scheme uses for an unreviewed stub. Copy
        the convention from an existing early-stage page rather than inventing a level.
      - Content: migrate the existing CyLi section out of `source/license/index.rst` (lines ~72-102 —
        the core principle, the three guarantees, the "Why Cyrus?" etymology, the status line) and
        expand it into an outline of what the licence will cover. Leave `source/license/index.rst`
        with a short summary plus a link, so there is one canonical home.
      - WRITE NO LICENCE TERMS. The drafting work is genuinely not done. The page states the principle
        (*what has been published cannot be revoked, but newer versions can be published*), its
        relationship to `:doc:`/stayvs/index``, the open questions, and that the formal spec is
        forthcoming. Anything resembling operative legal language would be invented authority — do not
        produce it.
      - Link it both ways with `/stayvs/index`, which is the mechanism CyLi is meant to make
        institutionally reliable.

      Ask me three things in one message before drafting, then offer an outline I can correct — do not
      block on a perfect answer: (i) intended scope — does CyLi cover only site content under StayVS
      versioning, or also third-party/institutional contributions? (ii) how it relates to JoLi —
      successor, complement, or a different layer for a different kind of material? (iii) is it meant
      to be reusable by other projects, or balospe-specific?

      ## 5b. Rework `source/license/index.rst` as a hub

      Two parallel sections, each a short summary plus a link to its subpage, and a real toctree:

          .. toctree::
             :maxdepth: 1

             joli/index
             cyli/index

      The existing toctree is `:hidden:` — part of why the JoLi page was invisible even setting the
      case bug aside. Given the whole point is discoverability, exposing it is the obvious default;
      confirm with me if you disagree.

      # STEP 6 — NAVIGATION VISIBILITY

      `/license/index` currently lives in the final toctree of `source/index.rst`:

          .. toctree::
             :hidden:

             /legal/index
             /sitemap/index
             /license/index

      Captionless and hidden — the utility bucket, alongside legal boilerplate and the sitemap. That
      is why the licence is hard to find even though the page exists.

      The theme is `sphinx_book_theme`; the visible left-nav groups are the captioned toctrees (*The
      Problem*, *The Solution*, *Get Involved*, *The Background*). These are genuinely different
      editorial choices — present them and let me pick:

      - **(a) Move `/license/index` into "The Background"** (alongside `./ff/index`). Cheapest, and
        puts it where a curious reader already goes for provenance. Recommended default.
      - **(b) Give the last toctree a caption** such as *Reference* or *Legal & Licensing*, surfacing
        legal + sitemap + license as a deliberate group.
      - **(c) Promote it into "Get Involved"**, on the argument that JoLi is a call to use and share
        the material, not a legal footnote. Strongest fit with the JoLi's actual purpose; also the
        most opinionated change to the site's information architecture.

      Whichever is chosen, verify in a built page that the entry actually renders in the sidebar — a
      `:hidden:` toctree elsewhere can still swallow it.

      # STEP 7 — THE 2-PAGE INTRO EXHIBIT (do not blind-copy)

      I originally asked for a copy of the JoLi intro into the `/license/` folder. Flagging the cost
      once, then it is my call:

      A literal copy creates the same content at two URLs — duplicate content for search engines, two
      files that drift apart, and under StayVS two independently versioned things that are meant to be
      one document. The exhibit page also belongs to the Good News Pack exhibit catalogue and is linked
      from its MMv3 siblings, so it is not free-floating.

      - **Recommended:** do NOT duplicate. `source/license/joli/index.rst` already embeds the same
        2-page PDF/WebP under its "Flyer" heading, and cross-links between the two pages were added on
        2026-07-30. Make `/license/joli/` the canonical home — give the PDF and its download link real
        prominence near the top rather than at the bottom — and leave the MMv3 page as the
        exhibit-catalogue entry that points at it. This gets the handout reachable from `/license/` and
        into the nav, with one copy.
      - **If I still want a page under `/license/`:** create `source/license/joli/intro-2page/index.rst`
        that `.. include::`s a shared fragment rather than copying the prose, so there is one source of
        truth, and add a `_redirects` note recording the relationship.

      Confirm with me before writing.

      # STEP 8 — PREVENTION (the real deliverable; without it this recurs invisibly)

      ## 8a. A guard against index-vs-disk case drift

      This is the exact scan that found the bug. It runs in about a second and is the only thing that
      makes this class of fault visible on macOS. Add as a script and wire into CI (and/or a pre-commit
      hook):

      ```python
      #!/usr/bin/env python3
      """Fail if git's index disagrees with the on-disk case of any tracked path.
      Invisible on macOS/case-insensitive filesystems; fatal on the deploy server."""
      import subprocess, sys, os
      paths = subprocess.run(["git", "ls-files", "-z"], capture_output=True, text=True).stdout.split("\0")
      bad, seen = [], set()
      for p in filter(None, paths):
          cur = ""
          for part in p.split("/"):
              try:
                  real = [n for n in os.listdir(cur or ".") if n.lower() == part.lower()]
              except FileNotFoundError:
                  real = []
              if real and real[0] != part:
                  g = f"{cur}/{part}" if cur else part
                  d = f"{cur}/{real[0]}" if cur else real[0]
                  if (g, d) not in seen:
                      seen.add((g, d)); bad.append((g, d))
                  break
              cur = f"{cur}/{part}" if cur else part
      for g, d in bad:
          print(f"CASE MISMATCH  git: {g}  disk: {d}", file=sys.stderr)
      sys.exit(1 if bad else 0)
      ```

      ## 8b. Make dead `:doc:` refs fail the build

      Sphinx rendering an unresolvable `:doc:` as href-less plain text is what let this ship green.
      Turn on nitpicky mode (`-n`), and consider `-W` in CI. Expect a backlog of pre-existing warnings
      on the first run — triage them rather than abandoning the flag. If `-W` is too aggressive today,
      at least fail on `toctree contains reference to nonexisting document` and `unknown document`.

      ## 8c. Do NOT use `git config core.ignorecase false`

      On a case-insensitive filesystem it makes git report phantom renames. The 8a guard is the safe
      equivalent.

      # STEP 9 — VERIFY (do not report done before this)

      1. `git diff --cached --stat -M` shows six pure renames, zero content churn.
      2. The 8a guard exits clean.
      3. Confirm the fix from git's own view, not the filesystem's:
             git ls-tree -r --name-only HEAD | grep -iE "^source/(ff|license/)"
         Every path lowercase. A local macOS build proves nothing about case; the index state is what
         matters.
      4. Build locally; confirm `build/html/en/license/joli/index.html`, `.../license/cyli/index.html`
         and `.../ff/index.html` all exist.
      5. After merge to `main` and deploy, curl and expect 200 on lowercase, 301 on capitalised:
             /en/ff/  /en/ff/system/  /en/ff/contact/  /en/ff/archive/  /en/ff/philosophy/
             /en/license/  /en/license/joli/  /en/license/cyli/
             /en/FF/  /en/license/JoLi/
      6. Load `/en/license/` and confirm "The Jonah License" is a real link WITH an href, not plain
         text; that JoLi and CyLi both appear in site navigation; and that "Cycrus" appears nowhere
         (`grep -ri cycrus build/`).
      7. Report what you actually checked and what you did not. If a step was skipped, say so plainly.

      # SEPARATE — DO NOT BUNDLE INTO THE SAME COMMIT

      `https://balospe.com/en/study/matheo/overview-christian/` is 404 live although committed in
      source — the deployed build predates it. Any deploy fixes it, and it is wanted for outreach going
      out now, so a deploy has value independent of this bug. Mention it when the fix is ready to
      merge; do not fold it into these commits.


Part 1 --- Independent check of the prior agent's claims
========================================================

Every claim was re-tested from scratch in this session. Reporting in BREACH-before-HELD order
per Language Rule 5 and Rule 7.

BREACH --- claims that did not withstand checking
-------------------------------------------------

**BREACH 1 --- Step 0 and Step 1 contradict each other.** Step 1 says: *"Expect ... 0 insertions
and 0 deletions. If any file shows content changes, STOP."* But Step 0 itself records that
``source/license/JoLi/index.rst`` and ``source/license/index.rst`` are **modified in the working
tree** and uncommitted. ``git rm -r --cached source/license/JoLi`` followed by
``git add source/license/joli`` stages the *working-tree* content, so the 2026m07d30 patch (+55/-3
lines) is swept into the same staging area. Anyone following the brief literally hits their own
STOP condition and cannot tell a benign cause from a real one.

*Correction:* deal with the uncommitted patch **before** touching the index --- it is being
reverted in Step 2 anyway. ``git checkout -- source/license/`` (or stash) first, then do the index
fix, then re-apply the intended Step 2 edits as a **separate** commit. This gives a genuinely pure
rename commit, which is what makes the ``-M`` check meaningful.

**BREACH 2 --- Step 8b names the wrong Sphinx lever.** ``-n`` (nitpicky) does not affect ``:doc:``.
Nitpicky mode escalates *missing domain cross-reference targets* (``py:``, ``c:``, ``cpp:``, and
similar). An unresolvable ``:doc:`` already emits ``WARNING: unknown document: ...`` at default
settings --- the build was never silent, it was **unread**, because ``SPHINXOPTS ?=`` is empty
(``Makefile:3``) and nothing turns warnings into failure. The correct lever is ``-W``
(warnings-as-errors, ideally with ``--keep-going``) or an explicit warning-count gate. Turning on
``-n`` would add a large volume of unrelated noise while still not gating the actual fault.

**BREACH 3 --- one reference count is off.** The brief says *"25 files reference ``/ff/``"*.
Measured: **32** files. (The ``license/joli`` = 327 and ``license/JoLi`` = 2 counts both HELD.)
Minor, but the ``/ff/`` figure is the one used to argue the blast radius, so it should be right.

**BREACH 4 --- the blast radius is understated, not overstated.** The brief describes the damage as
"six FF pages unreachable" plus a dead licence link. Resolving all 6027 ``:doc:`` roles in
``source/`` against **git's** docname view (i.e. the deploy server's view) gives **620 unresolved
occurrences, of which 563 are this one bug**:

.. list-table::
   :header-rows: 1
   :widths: 45 20

   * - Unresolved ``:doc:`` target (deploy-server view)
     - Occurrences
   * - ``/license/joli/index``
     - 521
   * - ``/ff/index``
     - 22
   * - ``/ff/system/index``
     - 9
   * - ``/ff/contact/index``
     - 6
   * - ``/ff/archive/index``
     - 4
   * - ``/ff/philosophy/index``
     - 1
   * - **subtotal --- this bug**
     - **563**

HELD --- claims that withstood checking
----------------------------------------

- **The core diagnosis HELD, exactly.** ``git ls-files`` reports ``source/FF/`` and
  ``source/license/JoLi/``; ``os.listdir`` reports ``source/ff`` and ``source/license/joli``.
- **The "exactly two, no others" claim HELD.** The full scan was reproduced independently:
  **2141 tracked paths scanned, 2 case mismatches**, both as described.
- **Every live-site status code HELD.** Re-curled 2026m08d03: ``/en/ff/`` 404, ``/en/FF/`` 200,
  ``/en/license/joli/`` 404, ``/en/license/JoLi/`` 200, ``/en/ff/system/`` 404,
  ``/en/ff/contact/`` 404, ``/en/FF/archive/`` 200, ``/en/license/`` 200.
- **The separate matheo 404 HELD:** ``/en/study/matheo/overview-christian/`` returns 404 live.
- **The "Cycrus" claim HELD precisely** --- 6 occurrences, 2 files, exactly the lines listed,
  and **0** in ``locale/`` (so no translation sweep is needed).
- **The mechanism explanation HELD.** Sphinx does not fail on an unresolvable ``:doc:``; it drops
  the href and renders bare text. Confirmed ``SPHINXOPTS ?=`` is empty and
  ``suppress_warnings = ["bibtex"]`` is the only warning setting in ``source/conf.py``.
- **The deploy path HELD.** ``.github/workflows/deploy.yml`` triggers on push to ``main`` only,
  delegating to ``balosp/llol-build/.github/workflows/sphinx-deploy.yml@v3``.
- **The branch state HELD.** ``b16-revision-for-expert-review`` is 3 commits ahead of ``main``;
  ``main`` does **not** contain the 2026m07d30 patch (0 occurrences of ``JoLi/index``).
- **The self-correction about the JoLi page HELD** and is credited: ``source/license/joli/index.rst``
  really is a full 202-line page, and the "buried in MMv3" framing really was the symptom talking.
- **Step 8c HELD.** ``core.ignorecase false`` on a case-insensitive filesystem is the wrong remedy.

Corrections to this session's own work
---------------------------------------

Two scans written in this session were wrong before they were right, recorded here because the
llog is an audit trail and not a highlight reel:

1. A first self-URL resolver never tried ``.rst`` / ``index.rst`` suffixes and reported **23**
   non-resolving URLs. False alarm.
2. A second version handled suffixes but mis-ordered ``rstrip('/')`` and ``rstrip('.')``, so a URL
   ending a sentence (``.../study/matheo/.``) kept its trailing slash and reported
   ``/study/matheo/`` as MISSING. ``source/study/matheo/index.rst`` exists.

The corrected figures are in Part 2.


Part 2 --- Measuring the FULL class, not just the instance
===========================================================

The prompt asked for consistency across *all similar errors*, so the class was measured rather
than assumed. Three sub-classes of silently-dying reference exist in this repo:

.. list-table::
   :header-rows: 1
   :widths: 26 12 12 50

   * - Sub-class
     - Surface
     - Broken
     - Who currently catches it
   * - **A. git index vs disk case drift**
     - 2141 paths
     - **2**
     - Nothing. Invisible on macOS, fatal on the server.
   * - **B. hardcoded** ``balospe.com/<lang>/...`` **self-URLs**
     - 596 occurrences / 53 distinct paths
     - **1 case + 5 stale**
     - Nothing. Sphinx never looks inside a URL string.
   * - **C. Sphinx** ``:doc:`` **roles**
     - 6027 refs
     - **620** (563 = sub-class A)
     - Sphinx warns; nothing reads the warning.

Sub-class B detail (corrected figures)
---------------------------------------

46 of 53 distinct self-URL paths resolve case-exactly. The exceptions:

- **1 CASE mismatch:** ``/license/JoLi`` in ``source/license/index.rst`` --- the 2026m07d30 patch.
- **5 MISSING**, of which **4 sit in frozen HELL MockupModel archives** (``hell/mm/b/14/mmv1..3``
  pointing at ``/matheology/e7day/``, ``/matheology/e7he/``, ``/matheology/jub/``,
  ``/matheology/h-star/``, ``/matheology/call-to-action/``) and **1 in a HELL llog**. These are
  append-only historical records; a link that was correct when written is arguably *correct as
  history*. **Recommendation: exempt HELL from the URL guard rather than "fix" the archive** ---
  editing them to point somewhere else would falsify the record.
- **0 broken self-URLs on live funnel pages.** This surface is in good shape.

Sub-class C detail
-------------------

After the case fix, the ``:doc:`` backlog drops from **620 to ~57 occurrences**, and of those only
about **14 are on live (non-HELL) pages**. That residue includes some noise from the measuring
regex (multi-line prose captured inside backticks), so the true live backlog is smaller still.
Named live candidates worth triage:

- ``gospels-apostles`` (2x) --- ``source/matheology/compiler/sisyf/synthesis-howto.rst``
- ``b3-trifecta-registry`` --- ``source/_POST/DD/b/12/b12-ladder-pedagogy-for-acronyms.rst``
  (note: **b3** does not exist; the registry is **b13** --- looks like a stale pre-renumbering ref)
- ``b11-trifecta-italics-no-commas``, ``b12-ladder-pedagogy-for-acronyms`` ---
  ``source/_POST/DD/b/13/b13-trifecta-registry.rst`` (relative refs that need ``/_POST/DD/b/NN/`` paths)
- ``/good-news-pack/research-theology/abraham/`` --- trailing slash invalid in ``:doc:``
- ``/good-news-pack/references/leonhard-2010-visions-of-apocalypse.rst`` --- ``.rst`` suffix
  invalid in ``:doc:``

**This is the load-bearing number for the whole plan.** A warnings-as-errors gate is unaffordable
today (620 hits) and affordable immediately after the case fix (~14). That is what determines the
order of work.

Other uppercase-cased tracked directories (case-fragility inventory)
---------------------------------------------------------------------

11 tracked directories have an uppercase component: ``AHA``, ``HH``, ``source/FF``,
``source/_POST``, ``source/_POST/AnyAims``, ``source/_POST/DD``, ``source/good-news-pack/AA``,
``source/license/JoLi``, ``source/matheology/hell/mm/b/14/OOv1|OOv2|OOv3``. Only the two known ones
are *drifted*; the other nine agree between git and disk. They remain the standing risk surface for
this class, which is why the guard must be permanent rather than a one-off sweep.


Part 3 --- EDEN analysis
=========================

Knife Edge #1 --- the order of operations is forced
-----------------------------------------------------

There is exactly one ordering that survives. **Gate first, fix second** fails: a ``-W`` build with
620 unresolved ``:doc:`` occurrences produces an unusable wall of failure, the gate gets reverted
within a day, and the repo ends up *believing* it is guarded while being less guarded than before
--- textbook OSCR (over-Complicate → abandon → over-Simplify back to nothing). **Fix first, gate
second** works: the backlog collapses by 91% and the gate becomes affordable on the same afternoon.

The canonical case is equally forced: lowercase already exists on disk and 563 references assume
it; making git agree costs 2 index operations, while making the references agree costs edits across
327 files. There is no third option that is not strictly worse.

Green Meadow #1 --- how to implement the guard (count = many; 3 examples)
--------------------------------------------------------------------------

Any of these stays in ZION; the choice is taste, not safety.

1. A single ``scripts/check-paths.py`` chained into the existing ``make lint`` target ---
   **recommended**, because ``lint`` already chains ``rstcheck`` + ``check-bib.py``, so this adds a
   member to an existing family rather than a new mechanism.
2. A git ``pre-commit`` hook --- faster feedback, but hooks are per-clone and silently absent on a
   fresh clone, so it cannot be the only line of defence.
3. A standalone CI step in the deploy workflow --- catches everything, but only after push, and the
   reusable workflow lives in an external repo (``balosp/llol-build``), so changing it is a
   cross-repo edit.

Grey Edge #1 --- HELL archive links
-------------------------------------

Whether a dead link inside a frozen MockupModel or an append-only llog is a *defect* or a *faithful
record* cannot be settled from the code. Under StayVS and the append-only llog rule, editing them
looks like falsifying history; leaving them looks like tolerating rot. **Proposal: exempt
``source/matheology/hell/`` from the link guards by default** (with an opt-in ``--include-hell``
flag for auditing), and record the exemption as a deliberate decision rather than an oversight.
Flagged as Grey Edge because a BABL reading is available in both directions.

BABL Danger --- the framing "so I don't have to worry about them any more"
----------------------------------------------------------------------------

**BABL Danger: over-Reach in the request framing.** No finite set of checks can close an open system
(Language Rule 4). Any plan that *promises* "you never have to worry about this class again" is
selling closure it cannot deliver, and the confidence it buys is exactly what let the current bug
run for months: the build was green, therefore it was assumed fine.

The ZION-shaped version of the goal is narrower and actually achievable: **make this class of
failure loud and cheap instead of silent and expensive.** Concretely --- a fault that today requires
someone to curl the live site becomes a one-second local check that names the file. That is a real
and large gain. It is not immunity. The residual risks that stay uncovered are named explicitly in
Part 4 rather than papered over, and the recommended guards therefore *report* rather than
*guarantee*.

BABL Danger --- scope bundling in the incoming brief
------------------------------------------------------

**BABL Danger: over-Reach by bundling.** The brief packs a live-fire URL fix (Steps 1--4: zero
editorial judgement, deployable in under an hour) together with a new-page drafting job, an
information-architecture change, and a duplicate-content decision (Steps 5--7: several blocking
questions to LLoL, no urgency). Bundled, the urgent fix waits on the editorial decisions. The
brief itself sets the right precedent by carving out the matheo 404 as separate --- that same
reasoning applies one step earlier. **Recommendation: split into two branches.** Steps 1--4 +
guards ship now; Steps 5--7 proceed at their own pace.

Not-BABL notes
---------------

Steps 5a and 7 of the brief are notably ZION-shaped and worth keeping verbatim: refusing to invent
licence text for CyLi (invented authority), and refusing to duplicate the JoLi intro across two
URLs (drift + StayVS violation). Both should survive into the final plan unchanged.


Part 4 --- The plan
====================

Tier 1 --- Stop the bleeding (no editorial decisions; ~1 hour)
---------------------------------------------------------------

Branch off ``main``, e.g. ``fix/path-case-and-link-guards``.

1. **Park the uncommitted patch first** (BREACH 1). ``git checkout -- source/license/`` --- the
   2026m07d30 workaround is reverted by Step 2 of the brief anyway. Leave the two untracked b16
   PDFs untouched.
2. **Fix the index** as the brief specifies (``git rm -r --cached`` + ``git add``). Commit alone,
   so ``git diff --cached --stat -M`` shows 6 pure renames, 0 insertions, 0 deletions --- now
   achievable.
3. **Re-apply the intended Step 2 edits** as a separate commit: lowercase ``joli/index`` refs in
   ``source/license/index.rst``, collapse the two ``TODO AA`` blocks there and the CASE BUG item in
   ``source/license/joli/index.rst``, replaced by a one-line dated note.
4. **``_redirects``** for the 7 published capitalised URLs (StayVS). Cloudflare Pages does support
   ``:splat``, but this file uses none today, so the low-risk choice is to **enumerate the 7 URLs
   explicitly** and skip an untested syntax on a live fix. Splat can follow later if wanted.
5. **``Cycrus`` → ``Cyrus``**, 6 lines, 2 files, ``locale/`` confirmed clean.
6. Merge to ``main`` → deploy. This also clears the separate
   ``/en/study/matheo/overview-christian/`` 404, as the brief predicted.

Tier 2 --- One guard family, wired into ``make lint`` (the real deliverable)
-----------------------------------------------------------------------------

A single ``scripts/check-paths.py`` with three checks, chained into the existing ``lint`` target
next to ``rstcheck`` and ``check-bib.py``:

- **Check A --- index/disk case drift.** The prior agent's 8a script, adopted essentially as
  written; it is correct and it found the bug.
- **Check B --- hardcoded self-URLs.** Every ``balospe.com/<lang>/...`` in ``source/`` must resolve
  **case-exactly** to a source file. Nothing checks this surface today (596 occurrences).
- **Check C --- ``:doc:`` resolved against git's docname view**, not the filesystem's. This is the
  check that makes the macOS blind spot visible *without* a build, and the only one that would have
  caught this bug locally on day one.

Default scope excludes ``source/matheology/hell/`` (Grey Edge #1) with an ``--include-hell`` flag
for audits. Documented by extending ``AHA/linting.md`` --- not a new AHA file.

Tier 3 --- Gate the build on warnings (only after Tier 1)
-----------------------------------------------------------

Correcting BREACH 2: the lever is ``-W``, not ``-n``. Staged so it cannot be abandoned:

1. After Tier 1, capture the real warning count from one full build.
2. Triage the ~14 live ``:doc:`` residue (list in Part 2).
3. Turn on ``-W --keep-going`` in **CI only** at first, leaving local ``make dev`` ungated so
   day-to-day writing is not slowed.

Tier 4 --- Build out ``/license/`` (separate branch, LLoL's decisions)
-----------------------------------------------------------------------

Steps 5--7 of the brief, unbundled. The brief's own recommendations on 5a (no invented licence
text) and 7 (do not duplicate) are endorsed and should stand.

What this plan does NOT cover (stated rather than hidden)
-----------------------------------------------------------

- **External link rot.** No guard proposed here checks off-site URLs; ``sphinx linkcheck`` exists
  but is slow and noisy. Left out deliberately.
- **``_redirects`` target validity.** 136 lines of redirect rules; nothing checks that their
  targets resolve. Candidate for a later Check D.
- **Case correctness inside ``locale/`` ``.po`` files.** Not scanned.
- **``:ref:`` label resolution.** Not measured this session; a separate surface from ``:doc:``.
- **Anything that only fails at runtime in a browser** (JS, redirects interacting with Cloudflare
  asset precedence). Requires post-deploy curling, which stays a manual step.


Part 5 --- Decisions needed from LLoL
======================================

1. **The b16 branch** --- how should ``b16-revision-for-expert-review`` be handled while a fix
   branch is cut from ``main``? (Leave it parked / merge it first / something else.)
2. **Split or bundle** --- ship Tier 1 + Tier 2 now and defer Tier 4, or do everything on one
   branch? (Recommendation: split.)
3. **HELL exemption** --- exempt ``source/matheology/hell/`` from the link guards by default?
   (Recommendation: yes, per Grey Edge #1.)
4. **Navigation** --- Step 6 options (a) The Background / (b) captioned Reference group /
   (c) Get Involved.
5. **CyLi scope** --- the brief's three questions (scope, relation to JoLi, reusability), needed
   only when Tier 4 starts.

Recorded as an AnyAims item pending LLoL's answers; nothing in Tiers 1--4 has been executed.


Summary of this session
========================

- The prior agent's **core diagnosis HELD in full** and was reproduced independently: 2 case
  mismatches out of 2141 tracked paths, all 8 live-site status codes as reported, the ``Cycrus``
  typo exactly as listed, the deploy path and branch state as described.
- **4 BREACHes** were found in the brief: (1) Step 0 and Step 1 contradict each other via the
  uncommitted patch; (2) Step 8b names ``-n`` where ``-W`` is the correct lever; (3) the ``/ff/``
  file count is 32, not 25; (4) the blast radius is **563 dead** ``:doc:`` **occurrences**, larger
  than the brief's own framing.
- The **full class was measured**: sub-class A = 2 broken, sub-class B = 1 case + 5 stale (4 of them
  legitimately historical), sub-class C = 620 → ~14 live after the case fix.
- **Knife Edge #1:** fix-then-gate is the only viable order; gating first guarantees abandonment.
- **BABL Danger** flagged twice: the "never worry again" framing over-Reaches (no finite check set
  closes an open system), and the brief over-Reaches by bundling an urgent URL fix with an
  unhurried editorial build-out.
- **Nothing was executed.** No source content file was modified. This llog is the only file written.
