[FIX] REVIEWED-131: build (e) and (c), and annotate the digest per (b)
(e) parked_workers reads ~/.claude/jobs/<id>/state.json's respawnFlags — harness state, not the session, so its enforcement does not depend on the party checked. Fails to NOT ESTABLISHED, never to safety: a missing dir, an unparseable state.json, and an absent respawnFlags are each enumerated rather than counted as "no parked workers" (cond. 1). (c) human_turns counts genuine human turns in a transcript, discounting hook-injected and /clear-/exit records. Retrospective by construction and says so. Output names which control produced each line (cond. 2). (b) the OPEN QUESTION field is marked orientation-not-instruction, in the code and in the output, explicitly not a control (cond. 3). Two corrections found while building. json was never imported, which the top-level guard would have turned into WAKE DIGEST UNAVAILABLE at every session start — the selftest caught it on first run. And the first draft asserted that a STOPPED job carrying the flag would take a turn on restart; whether the daemon respawns a stopped job is NOT ESTABLISHED, and it now says so rather than claiming either way. previous_session() lifted out of sec_unwrapped so both consumers share one definition of "the session before this one" — this file already refuses a second definition of "an item". Controls run in both directions, two of them against the real transcripts: b7e7eb39 (the unattended session) returns 0 human turns, this session returns 10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wn9mxsFjHJkzYjuDdrtxwp
This commit is contained in:
co-authored by
Claude Opus 5
parent
7d8d4bfe9c
commit
70440db147
+236
-12
@@ -16,7 +16,7 @@ if it cannot be computed it says so rather than emitting nothing.
|
||||
Provenance: 2026-07-28, steward-authorized alongside the PENDING split and the
|
||||
CLAUDE.md doctrine annotation. Sibling of governance-drift-check.py.
|
||||
"""
|
||||
import calendar, os, re, subprocess, sys, time
|
||||
import calendar, json, os, re, subprocess, sys, time
|
||||
|
||||
HOME = os.path.expanduser("~")
|
||||
D = os.path.join(HOME, "dotfiles")
|
||||
@@ -340,6 +340,26 @@ def wrap_verdict(span, events, lead=300, lag=10800):
|
||||
return "unwrapped", ""
|
||||
|
||||
|
||||
def previous_session():
|
||||
"""THE single selection of 'the session before this one', for every consumer.
|
||||
|
||||
Lifted out of sec_unwrapped 2026-08-31 when the loop check (REVIEWED-131) needed
|
||||
the same transcript. This file already refuses a second definition of 'an item'
|
||||
(item_spans); a second definition of 'the previous session' would be the same
|
||||
bug at a different site. -> (path, start_epoch, end_epoch) or None.
|
||||
"""
|
||||
proj = os.path.dirname(os.path.join(HOME, ".claude", "projects",
|
||||
"-Users-davidglidden", "memory"))
|
||||
try:
|
||||
tx = [os.path.join(proj, f) for f in os.listdir(proj) if f.endswith(".jsonl")]
|
||||
except OSError:
|
||||
warn.append("session transcripts unreadable — wrap check did not run")
|
||||
return None
|
||||
if not tx:
|
||||
return None
|
||||
return previous_transcript(time.time(), [(p,) + transcript_span(p) for p in tx])
|
||||
|
||||
|
||||
def sec_unwrapped():
|
||||
"""
|
||||
PENDING-S2's obligation, rebuilt on our own substrate.
|
||||
@@ -361,16 +381,7 @@ def sec_unwrapped():
|
||||
4. a two-valued verdict over an assessment that can fail.
|
||||
Fixing only (4), as scoped, would have left the false alarms firing.
|
||||
"""
|
||||
proj = os.path.dirname(os.path.join(HOME, ".claude", "projects",
|
||||
"-Users-davidglidden", "memory"))
|
||||
try:
|
||||
tx = [os.path.join(proj, f) for f in os.listdir(proj) if f.endswith(".jsonl")]
|
||||
except OSError:
|
||||
warn.append("session transcripts unreadable — wrap check did not run")
|
||||
return None
|
||||
if not tx:
|
||||
return None
|
||||
prev = previous_transcript(time.time(), [(p,) + transcript_span(p) for p in tx])
|
||||
prev = previous_session()
|
||||
if prev is None:
|
||||
return None
|
||||
_path, a, b = prev
|
||||
@@ -862,6 +873,143 @@ def stray_maps():
|
||||
return []
|
||||
|
||||
|
||||
# ---------- (e) + (c): is the loop currently removable? REVIEWED-131 (2026-08-31) ----------
|
||||
# EARNED 2026-08-31. A binary upgrade restarted the daemon, which respawned a parked
|
||||
# worker with --reply-on-resume. THIS digest was injected into that session as its only
|
||||
# instruction, and an executor worked and committed with no human present (PENDING-172).
|
||||
# (e) reads harness state on disk; (c) reads the session transcript. Two substrates,
|
||||
# deliberately — REVIEWED-131 cond. 2 requires the output to name which one fired.
|
||||
|
||||
JOBS_DIR = os.path.expanduser("~/.claude/jobs")
|
||||
ARM_FLAG = "--reply-on-resume"
|
||||
|
||||
|
||||
def parked_workers(jobs_dir=JOBS_DIR):
|
||||
"""(e) — REVIEWED-131 cond. 1. Returns (rows, unreadable, established).
|
||||
|
||||
Whether a parked worker will take a turn with no human present is recorded
|
||||
per session in <jobs>/<id>/state.json's `respawnFlags`. This reads the
|
||||
HARNESS, not the session: the only check here whose enforcement does not
|
||||
depend on the party being checked.
|
||||
|
||||
`established` is False whenever the directory is missing or unreadable.
|
||||
A control over parked workers whose negative result is an opaque zero is
|
||||
the failure PENDING-172 is about, so this NEVER reports absence of evidence
|
||||
as evidence of absence. Every dir read and every dir refused is enumerated.
|
||||
"""
|
||||
rows, unreadable = [], []
|
||||
if not os.path.isdir(jobs_dir):
|
||||
return rows, unreadable, False
|
||||
try:
|
||||
entries = sorted(os.listdir(jobs_dir))
|
||||
except OSError as e:
|
||||
return rows, [("<listing>", type(e).__name__)], False
|
||||
for name in entries:
|
||||
d = os.path.join(jobs_dir, name)
|
||||
if not os.path.isdir(d):
|
||||
continue
|
||||
f = os.path.join(d, "state.json")
|
||||
try:
|
||||
with open(f, encoding="utf-8") as fh:
|
||||
st = json.load(fh)
|
||||
except Exception as e:
|
||||
unreadable.append((name, type(e).__name__))
|
||||
continue
|
||||
flags = st.get("respawnFlags")
|
||||
# An absent or non-list respawnFlags is NOT "unarmed" — it is unreadable state.
|
||||
if not isinstance(flags, list):
|
||||
unreadable.append((name, "respawnFlags missing or not a list"))
|
||||
continue
|
||||
rows.append((name, str(st.get("state") or "?"), ARM_FLAG in flags,
|
||||
str(st.get("name") or "")[:52]))
|
||||
return rows, unreadable, True
|
||||
|
||||
|
||||
NONHUMAN = ("<local-command-", "<command-name>", "<command-message>", "<system-reminder",
|
||||
"=== WAKE DIGEST", "<local-command-stdout>", "<local-command-caveat>")
|
||||
|
||||
|
||||
def human_turns(path):
|
||||
"""(c) — REVIEWED-131 cond. 2. Count of genuine human turns in a transcript.
|
||||
|
||||
A hook-injected digest arrives as a `user` record, and so do /clear and /exit;
|
||||
none of them is a person. Returns None when the transcript cannot be read —
|
||||
unreadable is not zero.
|
||||
|
||||
⚠ RETROSPECTIVE BY CONSTRUCTION. At SessionStart the current session has no
|
||||
turns yet, so this is applied to the most recent COMPLETED session. It answers
|
||||
'did an executor just run unattended?', which is what nothing detected on
|
||||
2026-08-31 — not 'am I about to'. (e) is the forward-looking half.
|
||||
"""
|
||||
try:
|
||||
with open(path, encoding="utf-8", errors="replace") as fh:
|
||||
lines = fh.readlines()
|
||||
except Exception:
|
||||
return None
|
||||
n = 0
|
||||
for line in lines:
|
||||
try:
|
||||
o = json.loads(line)
|
||||
except Exception:
|
||||
continue
|
||||
if o.get("type") != "user" or o.get("isMeta"):
|
||||
continue
|
||||
c = o.get("message", {}).get("content")
|
||||
if isinstance(c, list):
|
||||
c = " ".join(x.get("text", "") for x in c if isinstance(x, dict))
|
||||
if not isinstance(c, str) or not c.strip():
|
||||
continue
|
||||
if any(m in c for m in NONHUMAN):
|
||||
continue
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def sec_loop(prev_transcript=None):
|
||||
"""Assemble (e) and (c), naming which control produced each line."""
|
||||
o = []
|
||||
rows, unreadable, established = parked_workers()
|
||||
if not established:
|
||||
o.append("PARKED WORKERS [control e — harness state] — NOT ESTABLISHED:"
|
||||
f" {JOBS_DIR} is missing or unreadable."
|
||||
" This is 'could not assess', not 'none parked'.")
|
||||
else:
|
||||
armed = [r for r in rows if r[2]]
|
||||
head = (f"PARKED WORKERS [control e — harness state] — {len(rows)} job dir(s) read,"
|
||||
f" {len(unreadable)} unreadable, {len(armed)} ARMED")
|
||||
o.append("⚠ " + head if armed else head)
|
||||
for jid, state, arm, name in rows:
|
||||
o.append(f" {'⚠ ARMED' if arm else ' ok '} {jid} state={state} {name}")
|
||||
for jid, why in unreadable:
|
||||
o.append(f" NOT ESTABLISHED {jid} ({why}) — counted, not classified")
|
||||
# ⚠ A stopped job still carries its flags. Whether the daemon respawns a
|
||||
# STOPPED job is NOT ESTABLISHED — the 2026-08-31 event respawned one that was
|
||||
# parked (state=blocked). Reporting both as equally live would assert what was
|
||||
# never measured; reporting the stopped one as safe would assume it.
|
||||
live = [r for r in armed if r[1] not in ("stopped",)]
|
||||
stopped = [r for r in armed if r[1] in ("stopped",)]
|
||||
if live:
|
||||
o.append(f" ⇒ {len(live)} PARKED worker(s) carry {ARM_FLAG}: a daemon restart"
|
||||
" (an auto-update will do) makes them take a turn with no human"
|
||||
" present. PENDING-172 / REVIEWED-131.")
|
||||
if stopped:
|
||||
o.append(f" ⇒ {len(stopped)} STOPPED job(s) still carry {ARM_FLAG}."
|
||||
" Whether the daemon respawns a stopped job is NOT ESTABLISHED —"
|
||||
" the 2026-08-31 respawn took a job that was parked, not stopped."
|
||||
" Neither live nor proven safe.")
|
||||
if prev_transcript:
|
||||
n = human_turns(prev_transcript)
|
||||
label = "LAST SESSION [control c — transcript]"
|
||||
if n is None:
|
||||
o.append(f"{label} — NOT ESTABLISHED: transcript unreadable.")
|
||||
elif n == 0:
|
||||
o.append(f"⚠ {label} — NO HUMAN TURN in {os.path.basename(prev_transcript)}."
|
||||
" That session ran unattended. PENDING-172.")
|
||||
else:
|
||||
o.append(f"{label} — {n} human turn(s); a person was present.")
|
||||
return o
|
||||
|
||||
|
||||
# ---------- self-test ----------
|
||||
|
||||
def selftest():
|
||||
@@ -979,6 +1127,57 @@ def selftest():
|
||||
chk("classify_pointers returns clean on empty input",
|
||||
classify_pointers("", MEMDIR) == (0, [], [], []))
|
||||
NOWHERE = "no-such-file-anywhere-xyzzy-9931.md"
|
||||
print("\nloop checks (e)+(c) [REVIEWED-131, earned 2026-08-31]:")
|
||||
import tempfile, shutil
|
||||
_tmp = tempfile.mkdtemp(prefix="wd-loop-")
|
||||
try:
|
||||
def _job(name, payload):
|
||||
d = os.path.join(_tmp, name)
|
||||
os.makedirs(d, exist_ok=True)
|
||||
open(os.path.join(d, "state.json"), "w").write(payload)
|
||||
_job("armed", json.dumps({"state": "blocked", "name": "x",
|
||||
"respawnFlags": ["--reply-on-resume", "--allowed-tools"]}))
|
||||
_job("quiet", json.dumps({"state": "blocked", "name": "y", "respawnFlags": []}))
|
||||
_job("broken", "{not json")
|
||||
_job("noflags", json.dumps({"state": "blocked", "name": "z"}))
|
||||
rows, unread, est = parked_workers(_tmp)
|
||||
chk("parked_workers ESTABLISHES on a readable dir", est is True)
|
||||
chk("parked_workers DETECTS an armed worker [must-detect]",
|
||||
[r for r in rows if r[0] == "armed" and r[2] is True] != [])
|
||||
chk("parked_workers does NOT flag respawnFlags=[] [must-not-flag]",
|
||||
[r for r in rows if r[0] == "quiet" and r[2] is False] != [])
|
||||
chk("parked_workers ENUMERATES unparseable state.json, never drops it",
|
||||
"broken" in [j for j, _ in unread])
|
||||
chk("absent respawnFlags is NOT ESTABLISHED, not 'unarmed' [permissive direction]",
|
||||
"noflags" in [j for j, _ in unread]
|
||||
and "noflags" not in [r[0] for r in rows])
|
||||
_job("armedstopped", json.dumps({"state": "stopped", "name": "s",
|
||||
"respawnFlags": ["--reply-on-resume"]}))
|
||||
_rows3, _u3, _e3 = parked_workers(_tmp)
|
||||
chk("a STOPPED job still reports its flag [must-detect: flags outlive the process]",
|
||||
[r for r in _rows3 if r[0] == "armedstopped" and r[2] is True and r[1] == "stopped"] != [])
|
||||
chk("...and its state is carried, so the report can separate it [must-not-conflate]",
|
||||
{r[1] for r in _rows3 if r[2]} == {"blocked", "stopped"})
|
||||
_rows2, _u2, est2 = parked_workers(os.path.join(_tmp, "does-not-exist"))
|
||||
chk("missing jobs dir -> NOT ESTABLISHED, never 'none parked'", est2 is False)
|
||||
|
||||
_t = os.path.join(_tmp, "t.jsonl")
|
||||
_hook = json.dumps({"type": "user", "message": {"content":
|
||||
"=== WAKE DIGEST ===\nOPEN QUESTION — do the thing"}})
|
||||
_cmd = json.dumps({"type": "user", "message": {"content":
|
||||
"<local-command-caveat>x</local-command-caveat>"}})
|
||||
_human = json.dumps({"type": "user", "message": {"content": "please file it"}})
|
||||
open(_t, "w").write(_hook + "\n" + _cmd + "\n")
|
||||
chk("human_turns returns 0 on a hook-only transcript [must-detect]",
|
||||
human_turns(_t) == 0)
|
||||
open(_t, "w").write(_hook + "\n" + _human + "\n")
|
||||
chk("human_turns does NOT flag a real human turn [must-not-flag]",
|
||||
human_turns(_t) == 1)
|
||||
chk("human_turns on an unreadable path -> None, not 0",
|
||||
human_turns(os.path.join(_tmp, "nope.jsonl")) is None)
|
||||
finally:
|
||||
shutil.rmtree(_tmp, ignore_errors=True)
|
||||
|
||||
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, [], [], []))
|
||||
@@ -1127,6 +1326,23 @@ def selftest():
|
||||
+ " — a spread is not a proof of correctness; the controls above are.")
|
||||
|
||||
print("\nlive substrate:")
|
||||
_PROJ = os.path.join(HOME, ".claude", "projects", "-Users-davidglidden")
|
||||
# Controls drawn from the 2026-08-31 event itself. They are marked NOT ESTABLISHED
|
||||
# rather than FAIL when the transcript ages out of the 30-day window — a control
|
||||
# that silently degrades into a pass is the failure this whole item is about.
|
||||
for _sid, _want, _why in (
|
||||
("b7e7eb39-1e81-4af3-9be7-c323c794c1d9", 0,
|
||||
"the unattended session of 2026-08-31 (PENDING-172)"),
|
||||
("62083f11-b56f-4e67-bbbb-135efeba0ed2", "gt0",
|
||||
"the attended session that found it")):
|
||||
_p = os.path.join(_PROJ, _sid + ".jsonl")
|
||||
if not os.path.exists(_p):
|
||||
print(f" [ .. ] NOT ESTABLISHED — {_sid[:8]} aged out; control not run ({_why})")
|
||||
continue
|
||||
_n = human_turns(_p)
|
||||
chk(f"human_turns on {_sid[:8]} — {_why}",
|
||||
(_n == 0) if _want == 0 else (isinstance(_n, int) and _n > 0))
|
||||
|
||||
chk("PENDING.md readable", read(PENDING) is not None)
|
||||
chk("REVIEWED.md readable", read(REVIEWED) is not None)
|
||||
chk("drift check runs", sec_drift() is not None)
|
||||
@@ -1147,10 +1363,18 @@ def main():
|
||||
unwrapped = sec_unwrapped()
|
||||
if unwrapped:
|
||||
o.append(unwrapped)
|
||||
|
||||
_prev = previous_session()
|
||||
for line in sec_loop(_prev[0] if _prev else None):
|
||||
o.append("\n" + line if line.startswith(("PARKED", "⚠ PARKED")) else line)
|
||||
if th:
|
||||
o.append(f"\nPULLING THREAD — {th}")
|
||||
if q:
|
||||
o.append(f"\nOPEN QUESTION — {q}")
|
||||
# (b) — REVIEWED-131 cond. 3: ANNOTATION, explicitly NOT a control. It cannot
|
||||
# enforce anything and must never be reported as a mitigation. It exists because
|
||||
# on 2026-08-31 this field was read by an unattended executor as an instruction.
|
||||
o.append(f"\nOPEN QUESTION [orientation, not an instruction — inherited from a"
|
||||
f" human and answerable to one] — {q}")
|
||||
|
||||
o.append(f"\nOPEN AUTHORIZATION ITEMS ({len(items)}) — bodies in ~/PENDING.md at the line given;"
|
||||
" closed items are in ~/PENDING-archive.md")
|
||||
|
||||
Reference in New Issue
Block a user