[FIX] The link canary was blind to code spans, to wikilinks, and to its own class

The steward's 2026-08-09 to-do read "the gap is neither knowledge nor home but
the absence of an EXECUTABLE." The premise was false: classify_pointers has
existed since 19bddd5 (2026-08-08), wired to SessionStart, with controls. The
gap was that the executable was incomplete, and the incompleteness had already
produced a false positive.

Four defects, three named in the spec and one found by building it:

1. CODE SPANS. `](file.md)` inside backticks read as a pointer, so the single
   DEAD pointer reported on 2026-08-09 was the link pattern written inside
   MEMORY.md's own specification of this canary. An instrument that flags its
   own documentation flags it every wake forever, and the real signal drowns —
   the same "known canary bug" dismissal the 2026-07-28 block was written to
   end, arriving by a second route. Fences and inline spans are blanked with
   offsets preserved; inline spans may not cross a newline and an unterminated
   fence does not match, so a stray backtick can never blank the file and HIDE
   dead pointers.

2. WIKILINKS. reference-verification-ladder.md has specified this canary as
   covering "every `](file.md)` and `[[wikilink]]`" since 2026-07-06. Only the
   first half was ever built. 31 wikilinks now checked.

3. BREAKAGE AGE, derived from git rather than a stored prior run — a state file
   would make this the one cached section in a digest whose governing property
   is that it is computed. Where git cannot answer, it says so.

4. Found by running it: the first wikilink pass reported only UNWRITTEN, and
   both live hits were [[trust-prior-pass-frame]], whose file EXISTS as
   feedback-trust-prior-pass-frame.md. That is precisely the one-word alarm the
   comment ten lines above it was written to forbid. Wikilinks now report three
   outcomes and hand back the replacement slug. Both are repaired here.

The wake-up skill and the ladder now POINT AT the executable instead of
describing the check — the described-not-invoked gap is why it kept being
retyped by hand on 2026-08-08 and 2026-08-09.

Verify: python3 scripts/wake-digest.py --selftest   (61 checks, exit 0)
        python3 scripts/wake-digest.py | grep 'MEMORY POINTERS'
Induced red: blank_code reverted to a no-op (behaviour, not the symbol) →
        exit 2, five named failures, no traceback; direction controls held.

Not changed: the wrap_inside detector, which announced "PREVIOUS SESSION DID
NOT WRAP" for a session that wrapped at 19:48 and kept working until 21:54 —
a two-valued detector over a three-case state. Named in the ledger, not fixed.
This commit is contained in:
David F Glidden
2026-08-10 16:01:51 +02:00
parent ed9951d1d7
commit 32b0c9dfc6
5 changed files with 255 additions and 9 deletions
+2 -2
View File
@@ -25,7 +25,7 @@ metadata:
- [Session 2026-08-01 — the Fool, and the boundary I manufactured](session-2026-08-01-the-fool-and-the-manufactured-boundary.md) — closed the reset thread (PENDING-85 eyeballed → **Arcades verdict WRONG**, a ClearScan scan; PENDING-84 triaged via the **§VII quarantine lane**, *dispositioned not repaired*), then found the stuckness was largely mine: **three of the day's biggest closures were ALREADY authorized** — register split (166K→44K, 177 open readable), ladder Stroke 2 (21→**71 instruments**), classifier fix — and once I justified inaction by invoking **PENDING-88's own unratified rule**. Landed **spec v2.9.1** (0-of-17→0-of-14; the prior figure is **irreproducible**, recorded in the constitution). **REVIEWED-85 ruled, NOT placed.** ESCALATE doctrine package filed (*differently biased checkers*). **Derrida: the independent witness WORKS** — ocrmac recovered what olmOCR silently dropped (5,590 words / 152 spans, invisible to every wired gate). **PULLING THREAD: stabilize the Fool method** (Qwen 3.6 35B on the M4, 2 trials, protocol unstable) — and **v1 Chamber's paired GPT/Claude raw outputs survive in ARC**, a ready-made dataset. Detail in the session file.
- [MemPalace KG object 128-char cap](feedback-mempalace-kg-object-128-char-cap.md) — `kg_add` `object` hard-caps at 128 chars; write KG objects as short keyword phrases on the FIRST pass, detail goes in the drawer. Recurs at every /wrap-up §5 — stop re-deriving it. *(Relocation note: palace-memory wound down 2026-07-07; wrap §5 now appends JSONL — the cap now matters only for typography-palace kg_adds, which are rare.)*
- [MemPalace wing-filter broken](feedback-mempalace-wing-filter-broken.md) — wing-scoped search → 'Error finding id' (upstream #1665, open at HEAD); search UNSCOPED + post-filter by wing. Don't run `repair` (#1589). Affects wake-up §2.b.3. *(Relocation note: §2.b.3 was rewired to the files layer 2026-07-07; relevant only to the typography palace CLI now.)*
- [Verify each post type after changes](feedback-verify-each-post-type-after-changes.md) — ARC: after any shared CSS/template/composition change, check EVERY content type on BOTH desktop and phone, not a sample on one viewport. Lesson from §II.d gate verified phone-only → desktop appearance surfaced 2 sessions late (no regression; just unseen). Per-type×per-viewport extension of [[trust-prior-pass-frame]]; belongs in the SCSS verification ladder.
- [Verify each post type after changes](feedback-verify-each-post-type-after-changes.md) — ARC: after any shared CSS/template/composition change, check EVERY content type on BOTH desktop and phone, not a sample on one viewport. Lesson from §II.d gate verified phone-only → desktop appearance surfaced 2 sessions late (no regression; just unseen). Per-type×per-viewport extension of [[feedback-trust-prior-pass-frame]]; belongs in the SCSS verification ladder.
- [Reading Compass is SVG, not type](feedback-reading-compass-is-svg-not-type.md) — ARC Compass sigils are SVG `<symbol>` artwork, NOT Plex Sans / nav typeface / Pattern 3. Recurring conflation; steward corrected 2026-06-03. Plex-Sans nav *text* = breadcrumb + version-stamp + post-type only.
- [ARC chamber v1-legacy cluster](project-arc-chamber-v1-legacy-cluster.md) — ARC's `content/chamber/**` is intentional v1-Chamber legacy (not drift); deferred to-do = gather into a presentable cluster as a record of development. Out of §5 clause-1 audit scope. Confirmed 2026-05-29.
@@ -280,7 +280,7 @@ Split out of [MEMORY.md](MEMORY.md) on 2026-07-06 to keep the wake-loaded index
- [Session 2026-06-17 — the cul-de-lampe designed, rolled out & shipped; Levi base settled & paused](session-2026-06-17-cul-de-lampe-designed-rolled-out-shipped-levi-base-settled.md) — **A long ARC-dominated day, fully shipped.** Designed the **cul-de-lampe** (confronted EB Garamond hederae U+E001/E002, tips meeting, central heart; drawn from real glyph outlines via fontTools), **sized to the text-em** (`height:1em` — em-anchored NOT a constant; the steward's "is there a mathematically right proportion?" pushed to a doctrine-consistent answer per REVIEWED-42 silver-ratio retirement), **`var(--fg)` opacity 0.7** (NOT grey — steward caught it; grey retired Stage M; CSS mask, white-fill luminance-safe, hand-written -webkit-). **Close-only** ruling; **type-applicability**: Essay/Hommage/Tombeau carry it, **Fragment refused**, Photo-essay declined; placement (end of text proper, before Sources) + **publication mandate**. Caught + fixed my own **Sources-as-apparatus over-claim** (steward flagged). **Rolled out to 7 essays**; **Vespers quieted** (titles→§XII.b register-marks I–VIII; ornaments→silence; §VIII image-caption fixed) + **Mushi-Ken revised** (analysed against type-spec = it IS an essay; dropped the pre-typology appendix); **both reworks VERSIONED** (ADR-005 genesis snapshots + /v/ routes; steward: "respect the versioning"). Spec'd (§IV + Sub-table 6 reconciled). **Committed `d736df1` + `68001a4`, deployed live, pushed origin + github-backup.** Began on **Levi/Camus** Position-I conversion: **Camus DONE**; **Levi base settled = ocrmac fresh OCR** (4-pass; reading-order fidelity decides) BUT **drop-caps lost at OCR-source level** → source-verified restoration + ingest **PAUSED** (verbatim-trust > speed). Steward ruled **Levi = whole book**. **PULLING THREAD reverts to Levi → the pattern-finder's first TRUE pass.** Captured **B5** (versioning-links rethink) in the open-work register; A2 → DONE.
## Archived (2026-06-17 — cul-de-lampe shipped + Levi base settled)
- [Session 2026-06-16 (evening, post-/clear) — pattern-finder built + Station-I ingested + pass-1 contamination caught + ARC ornaments grounded](session-2026-06-16-evening-pattern-finder-build-station-i-contamination-arc-ornaments.md) — **The thread's first real build, and the engine catching its own error.** (1) **Station-I ingested** into studium-engine (5 authors/7 work-files; steward ruled FR-originals · Levi=Gray-Zone-only · both Weil; new manifest vocab `role:reading-source`+`station:I`; omnibus works sidecar-scoped; 15 sources/7282 drawers/verify HEALTHY). **[FIX] Mauss re-anchor** — the morning's own chamber-cleanup (`03de347`) broke the engine's pinned hash; gate caught it. (2) **Built `engine/pattern_finder.py`** — charter-L2 harness (three cognitions; generate-free/verify-tight; honest-silence; reasoner=Claude over FTS). (3) **PASS-1 CAUGHT ITS OWN FALSE GROUNDING** ([[feedback-tool-review-after-each-use]] PASS-BUT-FALSELY): "5/5 voices" partly false — **Camus la-chute span is mostly *Le Premier Homme*** (mislabelled mega-omnibus); **Musil/Arendt apparatus leak** (translator N.d.T. / Amos Elon intro / Bibliography-Index). The engine's thesis (§VII/§XV) *working*. Survivor: **P3 "complicity that depletes not damns"** (Weil↔Levi↔Arendt, 1947↔1986). **Return — frame-inheritance** ([[trust-prior-pass-frame]]): verified endpoints, not interior. (4) **Pass-2 fixes applied** (Musil/Arendt sidecars corrected, interior-verified; **Camus WITHDRAWN** pending clean re-source) — **NOT re-run/verified (Bash flaky); studium tree left inconsistent+uncommitted on purpose.** (5) **ARC ornaments grounded**: **cul-de-lampe** = confronted hederae, SVG from open EB Garamond, terminal-close, *ivy=lié=ligature binding dead-to-unborn-via-living* (steward's keystone); **triple point** = Penguin/Tschichold restraint citation at the opening threshold, "a touch more weight." Both → spec §IV + type-matrix (steward, tomorrow). **PULLING THREAD: the pattern-finder's first TRUE pass on Position I — gated now on a clean, complete source set the steward will assemble.** NEXT: re-source → re-run+verify pass-2 → clean grounding → marginalia → commit.
- [Session 2026-06-16 (evening, post-/clear) — pattern-finder built + Station-I ingested + pass-1 contamination caught + ARC ornaments grounded](session-2026-06-16-evening-pattern-finder-build-station-i-contamination-arc-ornaments.md) — **The thread's first real build, and the engine catching its own error.** (1) **Station-I ingested** into studium-engine (5 authors/7 work-files; steward ruled FR-originals · Levi=Gray-Zone-only · both Weil; new manifest vocab `role:reading-source`+`station:I`; omnibus works sidecar-scoped; 15 sources/7282 drawers/verify HEALTHY). **[FIX] Mauss re-anchor** — the morning's own chamber-cleanup (`03de347`) broke the engine's pinned hash; gate caught it. (2) **Built `engine/pattern_finder.py`** — charter-L2 harness (three cognitions; generate-free/verify-tight; honest-silence; reasoner=Claude over FTS). (3) **PASS-1 CAUGHT ITS OWN FALSE GROUNDING** ([[feedback-tool-review-after-each-use]] PASS-BUT-FALSELY): "5/5 voices" partly false — **Camus la-chute span is mostly *Le Premier Homme*** (mislabelled mega-omnibus); **Musil/Arendt apparatus leak** (translator N.d.T. / Amos Elon intro / Bibliography-Index). The engine's thesis (§VII/§XV) *working*. Survivor: **P3 "complicity that depletes not damns"** (Weil↔Levi↔Arendt, 1947↔1986). **Return — frame-inheritance** ([[feedback-trust-prior-pass-frame]]): verified endpoints, not interior. (4) **Pass-2 fixes applied** (Musil/Arendt sidecars corrected, interior-verified; **Camus WITHDRAWN** pending clean re-source) — **NOT re-run/verified (Bash flaky); studium tree left inconsistent+uncommitted on purpose.** (5) **ARC ornaments grounded**: **cul-de-lampe** = confronted hederae, SVG from open EB Garamond, terminal-close, *ivy=lié=ligature binding dead-to-unborn-via-living* (steward's keystone); **triple point** = Penguin/Tschichold restraint citation at the opening threshold, "a touch more weight." Both → spec §IV + type-matrix (steward, tomorrow). **PULLING THREAD: the pattern-finder's first TRUE pass on Position I — gated now on a clean, complete source set the steward will assemble.** NEXT: re-source → re-run+verify pass-2 → clean grounding → marginalia → commit.
## Archived (2026-06-16 morning — officina + conversion tooling trustworthy + Station-I corpus complete)
- [Session 2026-06-16 — officina established + conversion tooling made trustworthy + Station-I corpus complete](session-2026-06-16-officina-conversion-tooling-station-i.md) — **Long, two movements, almost entirely the prerequisite for the thread.** (1) **Officina** = ARC's in-repo writing workshop (genetic trail seeds/notes/fragments/drafts; never published; **ADR-008**; gates = Hakyll allowlist + both-remotes-private; sub-canonical sources → chamber `antechamber/`). Seeded by The Making (sketch + *Magnifica Humanitas* note [steward deeply affected] + reading list). (2) **The conversion-tooling marathon** (steward's deepest ask: *review every tool after each use until reliable* → [[feedback-tool-review-after-each-use]]): hardened **audit_cruft** (gate was passing falsely-clean → corpus never "1280 clean": 160→215 dirty), built **verify_conversion.py** (composite verifier **+ prose-safety `prose_delta`**), fixed **strip_cruft** + **repair_epub_headings**, wrote **`conversion-runbook.yaml`** (the single operational place) + **`tool-evolution-log.md`**. Prose-gated cleanup: **180 cleaned, 35 reconvert → honest 1245/35**. **Station I corpus-complete** (Camus *La Chute* + Musil ×2 converted/graduated; joins Weil/Levi/Arendt). **Return: the false alarm** — cried "destroyed prose," disproved it (cruft tokens, not prose; recalibration: content-aware compare, not token counts). All pushed (chamber `d9c6755`, ARC `7a0d4b6`). NEXT: ingest the five into the engine → build/run the pattern-finder's first pass.
@@ -23,7 +23,7 @@ Proven gates, each earned from a real catch. Reach for the one the claim's shape
- **Delayer-diff** (2026-06-08) — for an `@layer`/structural-wrap refactor where the compiled output gains uniform wrappers: strip the `@layer X{`/closing-`}` wrappers from the compiled CSS (brace-aware script), diff vs the true baseline; byte-identical proves nothing *moved, dropped, or changed* — only got wrapped. Proven 3× in W3R Stage 2a. **CAUTION — byte-identical ≠ rendered-identical under `@layer`:** `@layer` overrides specificity, so a structural-wrap byte-proof is *necessary but not sufficient*; render-review is the gate when the cascade *basis* changes (a 5-layer wrap was byte-true yet produced 3 render inversions the diff could not see — the steward's eye caught them). Rendered-identity is then argued *by construction* (only already-winning rules promoted) AND render-reviewed — not inferred from the byte-diff.
- **Hold or exclude volatile build-stamps** — a build-date/commit stamp (colophon `_build_info`) differs between baseline and post builds and falsely flags a byte-diff. Hold it constant or exclude it, and say which (2026-06-10, Stage-G close).
- **Two-hat commit separation** — name the hat each commit wears: a *refactor* commit's compiled output is byte-identical (or carries a pre-stated classified delta); a *feature* commit is where rendered values change. Makes the gate above applicable per-commit instead of per-session (2026-06-08, from Fowler).
- **Lossless-relocation gate** — restructuring an index or moving prose between files: line-range **slices, never retyping**, + md5-conservation per moved slice + a link-resolution canary over every `](file.md)` and `[[wikilink]]`. Proven on the MEMORY.md two-file split (2026-07-06) and again on the skill-harvest split (2026-08-01, archive byte-identical over 166,027 bytes).
- **Lossless-relocation gate** — restructuring an index or moving prose between files: line-range **slices, never retyping**, + md5-conservation per moved slice + a link-resolution canary over every `](file.md)` and `[[wikilink]]`. Proven on the MEMORY.md two-file split (2026-07-06) and again on the skill-harvest split (2026-08-01, archive byte-identical over 166,027 bytes). **The canary is an executable — `python3 ~/dotfiles/scripts/wake-digest.py` (both link forms, four outcomes each, breakage dated from git; `--selftest` proves it). Never hand-type it: the hand version cannot strip code spans, and on 2026-08-09 its one "finding" was the link pattern inside its own specification** (2026-08-10).
## Mechanical text transforms ("strip/replace token X across the file")
- **Token-strip hits prose** (2026-06-08) — a whole-file `sed s/…!important…//` (or any token strip) also removes the token from comments, code-refs, and docstrings, silently mangling prose. Scope the transform to declaration lines, OR compare raw-before vs clean-declaration counts and verify comment integrity after (caught 4 mangled comments via the 56-vs-70 count mismatch; restored). Same family as *Count first, then look* — the word lives in prose too.
@@ -0,0 +1,50 @@
---
name: session-ledger-2026-08-10
description: "Practice-of-return ledger maintained by /symmetria — returns, open horizons, recalibrations, authorization moves, sub-agent dialogues, bypasses."
metadata:
node_type: memory
type: feedback
originSessionId: bc984a6d-e8ce-47f5-99da-062703403e7d
modified: 2026-08-10T13:56:12.136Z
---
# Session Ledger — 2026-08-10
## Returns
- **09:xx — inherited premise checked before executing it (second consecutive wake).** The wrap's
steward-directed to-do asserted *"the gap is neither knowledge nor home but the absence of an
EXECUTABLE."* Substrate says otherwise: `~/dotfiles/scripts/wake-digest.py` already carries
`classify_pointers`/`sec_pointers` (built 2026-08-08, `19bddd5`), both indexes, four outcomes,
positive controls, wired to SessionStart. The task is therefore **completing an instrument**,
not building one. Named before starting work; the fix is scoped to the three real gaps.
- **Reached the ladder before deriving a gate.** `reference-verification-ladder.md` already
specifies the canary under *lossless-relocation gate* — "a link-resolution canary over every
`](file.md)` and `[[wikilink]]`". The wikilink half was specified on the ladder and absent from
the code; read, not re-derived.
## What held
- The wake's own instrument reported its lone "dead" pointer honestly enough to be diagnosable —
the four-outcome design (2026-07-28 provenance) did what it was built to do.
## Open horizons
- **`wrap_inside` false alarm.** The digest announced "PREVIOUS SESSION DID NOT WRAP" for a session
that wrapped at 19:48 and then kept working until 21:54. Same defect family as the pointer alarm:
a two-valued detector over a state that has three cases (wrapped · wrapped-then-continued ·
never-wrapped). Named, not fixed — outside today's scope.
- REVIEWED-114 stays OPEN on 4b (steward's marked answer key). No mechanism can cover it.
- Skill-harvest: 41 `S2` rows authorized 2026-07-19, still unexecuted.
## Confidence to recalibrate
## Authorization moves
- Canary completion treated as `[FIX]` — it resolves scoped bugs against an existing steward
specification (the 2026-08-09 to-do), on an instrument the steward directed be completed today.
No new architectural direction; no governance file touched.
## Sub-agent dialogues
## Bypasses
+1 -1
View File
@@ -63,7 +63,7 @@ Run these in parallel to minimize latency:
- Read `skill-harvest-register.md` directly — the canonical surface for open skill proposals (wrap §1.6 appends there); surface any awaiting steward authorization <!-- 2026-06-05: register wiring, authorized 2026-05-29, applied with wrap-up §1.6 counterpart -->
- Read `reference-verification-ladder.md` directly — the canonical surface for the named verification instruments; hold the one or two the session's work will actually need <!-- 2026-08-07: PENDING-112 → REVIEWED-95. THIS SENTENCE IS A PRE-REGISTERED TRIAL INTERVENTION, landed alone and deliberately parallel to the register line above. Baseline before it: the ladder was reached in 9 of 64 sessions (14%), while the register — identical in kind, differing only in being named here — sat at 77%. Prediction: >60% over the 20 sessions following. Graded automatically at 84 transcripts via the `ladder-ritual-trial` DEFERRED-DECISION trigger; the result is filed as a dated PENDING entry whichever way it falls. Do not add to, reword, or "improve" this line before the trial is graded — a second change confounds the only check standing behind PENDING-112's causal claim. -->
- Read `~/.claude/projects/-Users-davidglidden/memory/session-ledger-[previous-date].md` if it exists — **specifically read the "Returns" and "Confidence to recalibrate" sections** for mood signal
- **Link-resolution canary (seconds-cheap):** verify MEMORY.md's file pointers resolve — every `](file.md)` target exists in the memory dir. Flag dead pointers in the briefing, distinguishing pre-existing-broken from newly-broken (grep the previous git state if in doubt). A pointer to a missing file is the index lying about what memory holds. <!-- 2026-07-19 harvest review (steward-authorized): the wake-canary link-resolution half, proposed 2026-06-07, partially built 2026-07-06 (truncation half), now standing. -->
- **Link-resolution canary — READ THE DIGEST'S `MEMORY POINTERS` LINE; do not re-implement it.** `~/dotfiles/scripts/wake-digest.py` runs at SessionStart and reports both indexes over both link forms — path pointers `](file.md)` and `[[wikilinks]]` — in four outcomes each (OK · MIS-AUTHORED, with the replacement string handed back · DEAD/UNWRITTEN · NON-PORTABLE), and dates each break as pre-existing or newly-broken from git. Surface any non-OK outcome in the briefing; a pointer to a missing file is the index lying about what memory holds. If the digest is absent, run it: `python3 ~/dotfiles/scripts/wake-digest.py`. ⚠ **Never hand-type this check.** It was typed inline on 2026-08-08 and again on 2026-08-09, and the hand version's one "finding" was the link pattern inside its own specification — the executable strips code spans and the hand version cannot. Prove the instrument before trusting a clean line: `wake-digest.py --selftest` (61 checks, positive and negative controls). <!-- 2026-07-19 harvest review (steward-authorized): the wake-canary link-resolution half, proposed 2026-06-07, partially built 2026-07-06 (truncation half). 2026-08-08: built as classify_pointers (19bddd5). 2026-08-10 [FIX], steward-directed: code-span blindness closed, wikilinks added (the ladder had specified them since 2026-07-06), breakage dated from git, and this step turned from a description into a pointer at the executable — the described-not-invoked gap was the whole reason it kept being retyped. -->
- **Telos conditional:** if the pulling thread touches the studium engine / The Making / ARC-as-public-proof (the engine's reason-for-being), also read `project-studium-engine-telos-chamber-of-voices.md` and hold ONE line of the why in the briefing — the telos lane drawn first, never only the production lanes. Do NOT recite it on unrelated wakes (decorative). <!-- 2026-07-19 harvest review (steward-authorized): proposed 2026-06-18 after the steward had to re-disclose the chamber's origin; second evidence instance 2026-07-19 (map-drawn-from-production-lanes-not-telos, steward corrected twice). -->
**b. The files layer — primary memory** `[palace-memory MemPalace wound down 2026-07-07]`
+201 -5
View File
@@ -429,10 +429,52 @@ def brief_age_days():
# memory files' own prose (17 uses). Absolute still VALIDATES here — it is not
# wrong, only unportable — but it is counted and reported so it cannot re-enter
# silently.
#
# 2026-08-10 — three completions, all earned before this code existed.
#
# (1) CODE SPANS. The matcher read `](file.md)` inside backticks as a pointer,
# so on 2026-08-09 it reported exactly one DEAD pointer: the link pattern
# written inside MEMORY.md's own SPECIFICATION OF THIS CANARY. An
# instrument that flags its own documentation flags it every wake, forever,
# and the real signal drowns in a permanent known-false line — the same
# "known canary bug" dismissal the block above was written to end, arriving
# by a second route. Code spans and fenced blocks are blanked before
# matching, offsets preserved so every reported line number stays true.
# Inline spans may not cross a newline: a stray unmatched backtick must
# bound its damage to one line rather than blanking the rest of the file
# and HIDING dead pointers. An unterminated fence simply does not match,
# so the failure direction is over-reporting, never silence.
#
# (2) WIKILINKS. `reference-verification-ladder.md` has specified the canary as
# covering "every `](file.md)` and `[[wikilink]]`" since 2026-07-06; only
# the first half was ever built. Reported as UNWRITTEN, not DEAD, because
# ~/CLAUDE.md rules a wikilink with no file yet to be legitimate — it marks
# something worth writing. A permitted forward-reference and a broken index
# pointer are different findings and must not share a word.
#
# (3) PRE-EXISTING vs NEWLY BROKEN. The 2026-08-09 spec asked for it. Derived
# from git, NOT from a stored prior run: a state file would make this the
# one cached section in a digest whose governing property is that it is
# computed. Where git cannot answer — a target outside this repo — it says
# so rather than guessing.
NONPORTABLE_RE = re.compile(r"^/(Users|home)/")
MEM_INDEXES = ["MEMORY.md", "MEMORY-reference.md"]
POINTER_RE = re.compile(r"\]\(([^)\s]+\.md)\)")
WIKILINK_RE = re.compile(r"\[\[([^\[\]|#\n]+?)(?:#[^\[\]|\n]*)?(?:\|[^\[\]\n]*)?\]\]")
FENCE_RE = re.compile(r"^(?P<f>`{3,}|~{3,})[^\n]*\n.*?^(?P=f)[^\n]*$", re.M | re.S)
INLINE_CODE_RE = re.compile(r"`+[^`\n]*`+")
def blank_code(text):
"""Blank code-span and fenced-block CONTENT, preserving offsets and newlines.
Preserving offsets is the point: every line number this module reports is
computed from the blanked text, so it must still address the real line.
"""
def blank(m):
return "".join("\n" if c == "\n" else " " for c in m.group(0))
return INLINE_CODE_RE.sub(blank, FENCE_RE.sub(blank, text))
def find_basename(name):
@@ -458,6 +500,7 @@ def resolve(raw, base):
def classify_pointers(text, base):
"""(n_checked, mis_authored, dead, nonportable) — outcomes, never one word."""
n, mis, dead, nonport = 0, [], [], []
text = blank_code(text)
for m in POINTER_RE.finditer(text):
raw = m.group(1)
if raw.startswith(("http://", "https://", "mailto:")):
@@ -479,8 +522,93 @@ def classify_pointers(text, base):
return n, mis, dead, nonport
def near_slugs(slug, memdir):
"""Memory files this wikilink was plainly REACHING FOR. Prefix-family only.
`[[trust-prior-pass-frame]]` wants `feedback-trust-prior-pass-frame.md`; the
slug is the tail of the real stem after a hyphen. Matching on that boundary
(never on a bare substring) keeps `[[arc]]` from claiming every file with
"arc" inside a word.
"""
out = []
try:
stems = [f[:-3] for f in os.listdir(memdir) if f.endswith(".md")]
except OSError:
return out
for stem in stems:
if stem.endswith("-" + slug) or slug.endswith("-" + stem):
out.append(stem)
return sorted(out)
def classify_wikilinks(text, memdir):
"""(n_checked, mis_authored, unwritten) — three outcomes, never one word.
The same discipline as the path pointers above, and for the same reason: the
first build of this function reported only UNWRITTEN, and its two live hits
were both `[[trust-prior-pass-frame]]`, whose file EXISTS as
`feedback-trust-prior-pass-frame.md`. One word would have sent a fixable
typo to the wake every morning wearing the label "nothing to do here".
UNWRITTEN, never DEAD, for the genuine misses: ~/CLAUDE.md rules that a
`[[name]]` matching no file yet "is fine — it marks something worth writing
later, not an error." A permitted forward-reference is not a defect.
"""
n, mis, unwritten = 0, [], []
text = blank_code(text)
for m in WIKILINK_RE.finditer(text):
slug = m.group(1).strip()
if not slug:
continue
n += 1
stem = slug[:-3] if slug.endswith(".md") else slug
if os.path.exists(os.path.join(memdir, stem + ".md")):
continue
line = text.count("\n", 0, m.start()) + 1
near = near_slugs(stem, memdir)
if near:
mis.append((line, slug, " | ".join(near)))
else:
unwritten.append((line, slug))
return n, mis, unwritten
# Breakage age is DERIVED from git, never from a stored prior run — a cache is
# the one thing this digest's governing property forbids. Two questions, both
# answerable at HEAD: was the pointer already written, and did its target
# already exist? Their four combinations are the four honest verdicts.
def breakage(raw, head_text, target_at_head):
"""PRE-EXISTING vs NEWLY BROKEN. `None` inputs mean 'git cannot say'."""
if head_text is None:
return "age unknown — no committed version of this index to compare"
if raw not in head_text:
return "NEW — this pointer was added since HEAD"
if target_at_head is True:
return "NEWLY BROKEN — target existed at HEAD and is gone now"
if target_at_head is False:
return "pre-existing — already broken at HEAD"
return "pre-existing at HEAD — target lives outside this repo, break date unknown"
def head_text_of(relpath):
"""The index as committed. None => never committed, or git unavailable."""
return sh(["git", "-C", D, "show", f"HEAD:{relpath}"])
def target_at_head(raw, memrel):
"""Did the pointer's target exist at HEAD? None where git cannot know."""
base = os.path.basename(raw)
if os.path.normpath(resolve(raw, os.path.realpath(MEM))) != os.path.normpath(
os.path.join(os.path.realpath(MEM), base)):
return None # leaves the memory dir — outside git's reach here
return sh(["git", "-C", D, "cat-file", "-e", f"HEAD:{memrel}/{base}"]) is not None
def sec_pointers():
total, mis, dead, nonport = 0, [], [], []
wtotal, wmis, unwritten = 0, [], []
memdir = os.path.realpath(MEM)
memrel = os.path.relpath(memdir, D)
for name in MEM_INDEXES:
path = os.path.realpath(os.path.join(MEM, name))
text = read(path)
@@ -489,10 +617,16 @@ def sec_pointers():
continue
n, m_, d_, np_ = classify_pointers(text, os.path.dirname(path))
total += n
head = head_text_of(f"{memrel}/{name}")
mis += [(name,) + t for t in m_]
dead += [(name,) + t for t in d_]
dead += [(name, ln, raw, breakage(raw, head, target_at_head(raw, memrel)))
for ln, raw, _ in d_]
nonport += [(name,) + t for t in np_]
return total, mis, dead, nonport
wn, wm, wu = classify_wikilinks(text, memdir)
wtotal += wn
wmis += [(name,) + t for t in wm]
unwritten += [(name,) + t for t in wu]
return total, mis, dead, nonport, wtotal, wmis, unwritten
# ---------- stray maps ----------
@@ -609,6 +743,61 @@ def selftest():
classify_pointers("[x](https://a.md)", MEMDIR) == (0, [], [], []))
chk("classify_pointers returns clean on empty input",
classify_pointers("", MEMDIR) == (0, [], [], []))
NOWHERE = "no-such-file-anywhere-xyzzy-9931.md"
print("\ncode spans [2026-08-09: the canary flagged its own specification]:")
chk("a pointer INSIDE a code span is not a pointer",
classify_pointers(f"`[y]({NOWHERE})`", MEMDIR) == (0, [], [], []))
chk("...and the SAME pointer outside one still fires [direction control —"
" blanking must not swallow everything]",
classify_pointers(f"[y]({NOWHERE})", MEMDIR)[0] == 1)
chk("MEMORY.md's own canary spec is silent [the exact 2026-08-09 false positive]",
(lambda t: classify_pointers(t, MEMDIR) == (0, [], [], [])
and classify_wikilinks(t, MEMDIR) == (0, [], []))(
"→ `~/dotfiles/scripts/`, invoked not described; `](file.md)` +"
" `[[wikilink]]` over both memory indexes"))
chk("blanking preserves LINE NUMBERS [offsets kept, not deleted]",
classify_pointers(f"`](x.md)`\n[y]({NOWHERE})\n", MEMDIR)[2][0][0] == 2)
chk("a fenced block is blanked",
classify_pointers(f"```\n[y]({NOWHERE})\n```\n", MEMDIR) == (0, [], [], []))
chk("a STRAY unmatched backtick blanks nothing [damage bounded to one line;"
" a greedy matcher would HIDE dead pointers]",
classify_pointers(f"a ` b\n[y]({NOWHERE})\n", MEMDIR)[0] == 1)
chk("an UNTERMINATED fence blanks nothing [fails toward reporting, not silence]",
classify_pointers(f"```\n[y]({NOWHERE})\n", MEMDIR)[0] == 1)
print("\nwikilinks [the ladder specified them 2026-07-06; never built until now]:")
chk("a wikilink whose file exists resolves",
classify_wikilinks("see [[MEMORY]]", MEMDIR) == (1, [], []))
chk("a wikilink with no file is UNWRITTEN, not dead [~/CLAUDE.md rules it legitimate]",
(lambda r: r[0] == 1 and not r[1] and len(r[2]) == 1)(
classify_wikilinks("see [[no-such-memory-xyzzy-9931]]", MEMDIR)))
chk("a NEAR-MISS slug is MIS-AUTHORED with the real file handed back"
" [live: the two hits the one-word version mislabelled]",
(lambda r: len(r[1]) == 1 and not r[2]
and r[1][0][2] == "feedback-trust-prior-pass-frame")(
classify_wikilinks("[[trust-prior-pass-frame]]", MEMDIR)))
chk("...and a genuine miss does NOT collapse into mis-authored [direction control]",
not classify_wikilinks("[[no-such-memory-xyzzy-9931]]", MEMDIR)[1])
chk("near_slugs matches on the HYPHEN boundary, not bare substring"
" [else [[arc]] would claim every file with 'arc' in a word]",
all(s.endswith("-arc") or "arc".endswith("-" + s) for s in near_slugs("arc", MEMDIR)))
chk("an alias and a heading are stripped down to the slug",
classify_wikilinks("[[MEMORY#Index|the index]]", MEMDIR) == (1, [], []))
chk("a wikilink inside a code span is not a wikilink",
classify_wikilinks("`[[no-such-memory-xyzzy-9931]]`", MEMDIR) == (0, [], []))
chk("classify_wikilinks returns clean on empty input [positive control]",
classify_wikilinks("", MEMDIR) == (0, [], []))
print("\nbreakage age [derived from git — this digest may not cache]:")
chk("a pointer absent at HEAD is NEW",
breakage("x.md", "nothing here", False).startswith("NEW"))
chk("a pointer present at HEAD whose target was there too is NEWLY BROKEN",
breakage("x.md", "[a](x.md)", True).startswith("NEWLY BROKEN"))
chk("a pointer present at HEAD whose target was already gone is pre-existing",
breakage("x.md", "[a](x.md)", False).startswith("pre-existing"))
chk("an out-of-repo target says the break date is unknown [honest degradation]",
"unknown" in breakage("x.md", "[a](x.md)", None))
chk("no committed index reads as UNKNOWN, never as pre-existing"
" [negative control — absence of evidence]",
breakage("x.md", None, None).startswith("age unknown"))
chk("is_homed TRUE for a Desktop map symlinked into maps/ [live substrate]",
is_homed(os.path.join(DESKTOP, "corpus-index-2026-07-26.md"), MAPS_DIR))
chk("is_homed FALSE for a regular file [negative control — must not pass everything]",
@@ -676,16 +865,23 @@ def main():
o.append(f"\nGOVERNANCE DRIFT — ~/CLAUDE.md: {drift} substrate-contradicted claim(s)"
" (detection only; correction needs [ESCALATE])")
ptot, pmis, pdead, pnp = sec_pointers()
ptot, pmis, pdead, pnp, wtot, wmis, wun = sec_pointers()
o.append(f"\nMEMORY POINTERS — {ptot} checked · {len(pmis)} mis-authored · {len(pdead)} dead"
+ (f" · {len(pnp)} non-portable" if pnp else ""))
+ (f" · {len(pnp)} non-portable" if pnp else "")
+ f" | {wtot} wikilinks · {len(wmis)} mis-authored · {len(wun)} unwritten")
for f, ln, raw, fix in pmis:
o.append(f" MIS-AUTHORED {f}:{ln} {raw}")
o.append(f" → target exists; replace with: {fix}")
for f, ln, raw, _ in pdead:
for f, ln, raw, age in pdead:
o.append(f" DEAD {f}:{ln} {raw} (no file of that name in ~/_Dev or the memory dir)")
o.append(f" → {age}")
for f, ln, raw in pnp:
o.append(f" NON-PORTABLE {f}:{ln} {raw} (resolves, but hardcodes this machine)")
for f, ln, slug, near in wmis:
o.append(f" MIS-AUTHORED {f}:{ln} [[{slug}]]")
o.append(f" → the file exists; replace with: [[{near}]]")
for f, ln, slug in wun:
o.append(f" UNWRITTEN {f}:{ln} [[{slug}]] (permitted forward-reference, not an error)")
strays = stray_maps()
if strays: