[FIX] A home for steward-facing maps; memory pointers made portable and self-diagnosing
Two loose ends closed before the Harrison re-gate, both surfaced by the steward. Memory pointers. Six links in MEMORY.md / MEMORY-reference.md pointed at files that all existed, via hand-counted relative depths that resolved from neither of the memory dir's two addresses (it is ~/dotfiles/claude/memory, symlinked from ~/.claude/projects/…). The wake canary detected this correctly FOUR times over two days and the banked remedy was to change the canary's path resolution — i.e. to silence a true positive. The defect was never the pointers: the alarm emitted one undifferentiated word, MISSING, so every firing had to be re-diagnosed by hand and the cheapest re-diagnosis is always "known bug". wake-digest.py now reports four outcomes (ok / mis-authored / dead / non-portable), hands back the exact replacement, and carries a regression control replaying this bug's shape. Pointers are home-anchored (~/…), not absolute — steward's correction; absolute hardcodes this machine into the repo whose purpose is surviving a machine change. Maps. With the noise gone, one genuine dead pointer surfaced: arc-current-state-2026-05-07.md, a live ARC dashboard the steward read to orient. It lived only on the Desktop and went with a tidy-up. A census found four more in the same condition, zero copies anywhere — including the Making-Sequence architecture and reading list, load-bearing for current corpus work. The cause is structural: code, session records and memories are durable; the one artifact class addressed to the steward had no home. All five now live in maps/ and are symlinked back to their exact Desktop paths (Desktop view unchanged), moved under a checksum gate with a positive control. wake-digest.py reports stray Desktop maps; it never moves them — the Desktop is the steward's. How to verify: python3 scripts/wake-digest.py --selftest # 28 controls, PASS python3 scripts/wake-digest.py | grep -A3 'MEMORY POINTERS' cd ~/Desktop && shasum -a 256 *.md # reads through the symlinks What was not changed: ~/CLAUDE.md and REVIEWED.md untouched (Constraint #1). No Desktop file was deleted or renamed. MEMORY-reference.md's May entry is marked superseded, not rewritten. Known limitation: maps/ has no successor for the ARC map's FUNCTION — the open-work register carries the content, but nothing exists that the steward can open and orient by. Named in the entry rather than quietly closed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WuMjg3ipEVa3n8CoSzoyvc
This commit is contained in:
co-authored by
Claude Opus 5
parent
9339abf412
commit
19bddd5ecb
@@ -294,6 +294,134 @@ def brief_age_days():
|
||||
return (time.time() - time.mktime((y, mo, d, 12, 0, 0, 0, 0, -1))) / 86400
|
||||
|
||||
|
||||
# ---------- memory pointers ----------
|
||||
#
|
||||
# Provenance: 2026-07-28. Earned expensively. Six pointers in MEMORY.md /
|
||||
# MEMORY-reference.md pointed at files that all EXIST via paths that never
|
||||
# resolved (two at depth 4, four at depth 5 — hand-counted, never checked).
|
||||
# The wake canary detected this and FIRED FOUR TIMES over two days. Each firing
|
||||
# was re-diagnosed by hand and dismissed as "known canary bug", and the banked
|
||||
# remedy was to change the canary's path resolution — i.e. to make the
|
||||
# instrument agree with the broken data and stop reporting a TRUE POSITIVE.
|
||||
#
|
||||
# The defect was never the pointers; it was that the alarm said only MISSING.
|
||||
# An undifferentiated alarm has to be re-diagnosed on every firing, and the
|
||||
# cheapest re-diagnosis is always "known bug". So this reports three outcomes,
|
||||
# never one, and hands back the exact replacement string:
|
||||
#
|
||||
# OK resolves from the file's real location
|
||||
# MIS-AUTHORED target exists, path as written does not reach it -> fix given
|
||||
# DEAD no file of that name anywhere -> a decision
|
||||
#
|
||||
# Relative pointers are resolved against the file's REAL directory, because the
|
||||
# memory dir has two live addresses (~/.claude/projects/…/memory is a symlink to
|
||||
# ~/dotfiles/claude/memory) and a relative escape is only ever valid from one of
|
||||
# them. That ambiguity is structural: any pointer LEAVING the directory must be
|
||||
# home-anchored (`~/…`) to be correct via both routes.
|
||||
#
|
||||
# Home-anchored, NOT absolute — steward's correction, same session. `/Users/
|
||||
# davidglidden/…` resolves today and hardcodes this machine into a dotfiles repo
|
||||
# whose whole purpose is surviving a machine change. `expanduser` is already the
|
||||
# idiom three lines into this file, and `~/_Dev/…` is already the idiom in the
|
||||
# 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.
|
||||
NONPORTABLE_RE = re.compile(r"^/(Users|home)/")
|
||||
|
||||
MEM_INDEXES = ["MEMORY.md", "MEMORY-reference.md"]
|
||||
POINTER_RE = re.compile(r"\]\(([^)\s]+\.md)\)")
|
||||
|
||||
|
||||
def find_basename(name):
|
||||
"""Bounded search for a file of this basename. None => genuinely dead."""
|
||||
local = os.path.join(os.path.realpath(MEM), name)
|
||||
if os.path.exists(local):
|
||||
return local
|
||||
hit = sh(["find", os.path.join(HOME, "_Dev"), "-maxdepth", "6",
|
||||
"-name", name, "-not", "-path", "*/node_modules/*",
|
||||
"-not", "-path", "*/.git/*"], timeout=8)
|
||||
return hit.splitlines()[0] if hit else None
|
||||
|
||||
|
||||
def resolve(raw, base):
|
||||
"""Home-anchored, absolute, or relative-to-the-file's-real-directory."""
|
||||
if raw.startswith("~"):
|
||||
return os.path.expanduser(raw)
|
||||
if os.path.isabs(raw):
|
||||
return raw
|
||||
return os.path.join(base, raw)
|
||||
|
||||
|
||||
def classify_pointers(text, base):
|
||||
"""(n_checked, mis_authored, dead, nonportable) — outcomes, never one word."""
|
||||
n, mis, dead, nonport = 0, [], [], []
|
||||
for m in POINTER_RE.finditer(text):
|
||||
raw = m.group(1)
|
||||
if raw.startswith(("http://", "https://", "mailto:")):
|
||||
continue
|
||||
n += 1
|
||||
line = text.count("\n", 0, m.start()) + 1
|
||||
if NONPORTABLE_RE.match(raw):
|
||||
nonport.append((line, raw))
|
||||
if os.path.exists(resolve(raw, base)):
|
||||
continue
|
||||
found = find_basename(os.path.basename(raw))
|
||||
if found:
|
||||
home = os.path.realpath(HOME)
|
||||
real = os.path.realpath(found)
|
||||
fix = "~" + real[len(home):] if real.startswith(home + os.sep) else real
|
||||
mis.append((line, raw, fix))
|
||||
else:
|
||||
dead.append((line, raw, None))
|
||||
return n, mis, dead, nonport
|
||||
|
||||
|
||||
def sec_pointers():
|
||||
total, mis, dead, nonport = 0, [], [], []
|
||||
for name in MEM_INDEXES:
|
||||
path = os.path.realpath(os.path.join(MEM, name))
|
||||
text = read(path)
|
||||
if text is None:
|
||||
warn.append(f"memory pointers: {name} unreadable")
|
||||
continue
|
||||
n, m_, d_, np_ = classify_pointers(text, os.path.dirname(path))
|
||||
total += n
|
||||
mis += [(name,) + t for t in m_]
|
||||
dead += [(name,) + t for t in d_]
|
||||
nonport += [(name,) + t for t in np_]
|
||||
return total, mis, dead, nonport
|
||||
|
||||
|
||||
# ---------- stray maps ----------
|
||||
#
|
||||
# A "map" is an artifact the steward reads directly to orient (dashboard, corpus
|
||||
# index, reading list). Provenance 2026-07-28: arc-current-state-2026-05-07.md
|
||||
# was exactly that, lived only on the Desktop, and vanished with a tidy-up; a
|
||||
# census then found four more in the same condition, zero copies anywhere. Maps
|
||||
# now live in ~/dotfiles/maps and are symlinked back to the Desktop. This reports
|
||||
# any Desktop .md that is NOT such a symlink — it never moves anything, because
|
||||
# the Desktop is the steward's. See maps/README.md.
|
||||
|
||||
DESKTOP = os.path.join(HOME, "Desktop")
|
||||
MAPS_DIR = os.path.join(D, "maps")
|
||||
|
||||
|
||||
def is_homed(path, maps_root):
|
||||
"""True iff path is a symlink resolving inside maps_root."""
|
||||
if not os.path.islink(path):
|
||||
return False
|
||||
return os.path.realpath(path).startswith(os.path.realpath(maps_root) + os.sep)
|
||||
|
||||
|
||||
def stray_maps():
|
||||
try:
|
||||
return [n for n in sorted(os.listdir(DESKTOP))
|
||||
if n.endswith(".md") and not is_homed(os.path.join(DESKTOP, n), MAPS_DIR)]
|
||||
except Exception:
|
||||
warn.append("stray-map check: Desktop unreadable")
|
||||
return []
|
||||
|
||||
|
||||
# ---------- self-test ----------
|
||||
|
||||
def selftest():
|
||||
@@ -341,6 +469,39 @@ def selftest():
|
||||
extract_anchor("**A:** " + "w " * 200, "A", limit=50).endswith("…[truncated]"))
|
||||
chk("extract_anchor returns None when the anchor is absent",
|
||||
extract_anchor("no anchor here", "PULLING THREAD") is None)
|
||||
MEMDIR = os.path.realpath(MEM)
|
||||
TOUCH = "~/_Dev/studium-engine/docs/the-chamber-touchstone.md"
|
||||
chk("classify_pointers accepts an in-directory relative pointer",
|
||||
classify_pointers("see [x](MEMORY.md)", MEMDIR) == (1, [], [], []))
|
||||
chk("classify_pointers accepts a HOME-ANCHORED pointer [the chosen form]",
|
||||
classify_pointers(f"[t]({TOUCH})", MEMDIR) == (1, [], [], []))
|
||||
chk("classify_pointers calls a real-target/bad-path pointer MIS-AUTHORED"
|
||||
" [the 4-firing bug, as a regression control]",
|
||||
(lambda r: r[0] == 1 and len(r[1]) == 1 and not r[2])(
|
||||
classify_pointers(
|
||||
"[t](../../../../_Dev/studium-engine/docs/the-chamber-touchstone.md)",
|
||||
MEMDIR)))
|
||||
chk("...and the fix it hands back is home-anchored, not machine-absolute",
|
||||
classify_pointers("[t](../../../../_Dev/studium-engine/docs/"
|
||||
"the-chamber-touchstone.md)", MEMDIR)[1][0][2] == TOUCH)
|
||||
chk("classify_pointers calls a nowhere-target pointer DEAD [must NOT collapse"
|
||||
" into mis-authored]",
|
||||
(lambda r: r[0] == 1 and not r[1] and len(r[2]) == 1)(
|
||||
classify_pointers("[x](no-such-file-anywhere-xyzzy-9931.md)", MEMDIR)))
|
||||
chk("classify_pointers flags a machine-absolute pointer NON-PORTABLE even though"
|
||||
" it resolves [prevents silent re-entry]",
|
||||
(lambda r: len(r[3]) == 1 and not r[1] and not r[2])(
|
||||
classify_pointers(f"[t]({os.path.expanduser(TOUCH)})", MEMDIR)))
|
||||
chk("classify_pointers ignores http links",
|
||||
classify_pointers("[x](https://a.md)", MEMDIR) == (0, [], [], []))
|
||||
chk("classify_pointers returns clean on empty input",
|
||||
classify_pointers("", MEMDIR) == (0, [], [], []))
|
||||
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]",
|
||||
not is_homed(os.path.join(MAPS_DIR, "README.md"), MAPS_DIR))
|
||||
chk("is_homed FALSE for a symlink pointing OUTSIDE maps/",
|
||||
not is_homed(os.path.join(HOME, ".claude"), MAPS_DIR))
|
||||
print("\nlive substrate:")
|
||||
chk("PENDING.md readable", read(PENDING) is not None)
|
||||
chk("REVIEWED.md readable", read(REVIEWED) is not None)
|
||||
@@ -377,6 +538,24 @@ 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()
|
||||
o.append(f"\nMEMORY POINTERS — {ptot} checked · {len(pmis)} mis-authored · {len(pdead)} dead"
|
||||
+ (f" · {len(pnp)} non-portable" if pnp else ""))
|
||||
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:
|
||||
o.append(f" DEAD {f}:{ln} {raw} (no file of that name in ~/_Dev or the memory dir)")
|
||||
for f, ln, raw in pnp:
|
||||
o.append(f" NON-PORTABLE {f}:{ln} {raw} (resolves, but hardcodes this machine)")
|
||||
|
||||
strays = stray_maps()
|
||||
if strays:
|
||||
o.append(f"\nSTRAY MAPS — {len(strays)} Desktop .md with no tracked copy in"
|
||||
" ~/dotfiles/maps/ (one tidy-up from gone; see maps/README.md)")
|
||||
for s in strays:
|
||||
o.append(f" {s}")
|
||||
|
||||
age = brief_age_days()
|
||||
if age is None:
|
||||
o.append("\n.APP BRIEF — never generated. The jurist's §Standing Context cannot be"
|
||||
|
||||
Reference in New Issue
Block a user