R-734: the update test ignores an app's listed marker files, each with its reason
`09` §3 decision 145. MARKER_IGNORE in upgrade-test.py: per app, the files the
files_may_change mark does not count — first immich's six 13-byte
{encoded-video,library,backups,profile,thumbs,upload}/.immich folder markers,
rewritten at every start (bench measurement 2026-09-30). A listed file is
ignored only when changed/added and still <= 64 bytes; a removed or grown
marker, any unlisted file, and a moved tree the file walk cannot name still
mark the step. The verdict records files_ignored with the reasons.
HARNESS_VERSION 5. Tests: test_upgrade_bench.py MarkerIgnore (red-proved).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
+66
-3
@@ -51,8 +51,9 @@ import importlib.util, json, os, re, shutil, subprocess, sys, time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
HARNESS_VERSION = 4 # 2: the memory watch (R-635); 3: box fixtures on the bench + files_may_change (2026-09-23 night);
|
||||
HARNESS_VERSION = 5 # 2: the memory watch (R-635); 3: box fixtures on the bench + files_may_change (2026-09-23 night);
|
||||
# 4: a PostgreSQL major is CONVERTED on the bench, and the ladder carries the mark (`09` §6.4 part 10)
|
||||
# 5: files_may_change no longer counts an app's listed MARKER files (MARKER_IGNORE, R-734)
|
||||
ROOT = Path("/opt/upg")
|
||||
TEMPLATES = ROOT / "templates"
|
||||
EVIDENCE = ROOT / "evidence"
|
||||
@@ -628,6 +629,62 @@ def bind_tree_files(project: str, workdir: Path) -> dict:
|
||||
return out
|
||||
|
||||
|
||||
# R-734 (`09` §3 decision 145): app-owned MARKER files the `files_may_change` mark does not count. Each entry is
|
||||
# (bind-source suffix, path inside that bind, largest size in bytes it may have, the reason). An entry ignores a file
|
||||
# only when it was CHANGED or ADDED and is still that small — a removed marker, a grown one, or any file not listed
|
||||
# here still counts, so the list can only take away what was measured to be noise. Pinned by
|
||||
# scripts/test_upgrade_bench.py (MarkerIgnore: an unlisted changed file still marks the step).
|
||||
_IMMICH_MARKER_REASON = ("immich rewrites this 13-byte folder marker at every start (its storage check writes a new "
|
||||
"random token); measured on the bench 2026-09-30, v3.2.2 -> v3.2.4 and the 0b82 re-proof: "
|
||||
"the only changed files were these six markers, no household file (R-734)")
|
||||
MARKER_IGNORE = {
|
||||
"immich": [("appdata/immich", "%s/.immich" % d, 64, _IMMICH_MARKER_REASON)
|
||||
for d in ("encoded-video", "library", "backups", "profile", "thumbs", "upload")],
|
||||
}
|
||||
|
||||
|
||||
def _size_of(entry) -> int:
|
||||
"""The size from a bind_tree_files value ("<size>:<digest>"); -1 when it cannot be read."""
|
||||
try:
|
||||
return int(str(entry).split(":", 1)[0])
|
||||
except ValueError:
|
||||
return -1
|
||||
|
||||
|
||||
def classify_changes(app: str, before: dict, after: dict):
|
||||
"""(counted, ignored) between two bind_tree_files results. `counted` is changed_files' list without the
|
||||
files MARKER_IGNORE lists for `app`; `ignored` is [{"file", "reason"}] for the ones it took away."""
|
||||
rules = MARKER_IGNORE.get(app) or []
|
||||
counted, ignored = [], []
|
||||
for src in sorted(set(before) | set(after)):
|
||||
b, a = before.get(src) or {}, after.get(src) or {}
|
||||
for rel in sorted(set(b) | set(a)):
|
||||
if b.get(rel) == a.get(rel):
|
||||
continue
|
||||
kind = "added" if rel not in b else "removed" if rel not in a else "changed"
|
||||
line = f"{src}/{rel} ({kind})"
|
||||
hit = None
|
||||
if kind != "removed":
|
||||
for suffix, path, max_size, reason in rules:
|
||||
if (src.rstrip("/") == suffix or src.rstrip("/").endswith("/" + suffix)) and rel == path \
|
||||
and 0 <= _size_of(a.get(rel)) <= max_size:
|
||||
hit = reason
|
||||
break
|
||||
if hit:
|
||||
ignored.append({"file": line, "reason": hit})
|
||||
else:
|
||||
counted.append(line)
|
||||
return counted, ignored
|
||||
|
||||
|
||||
def files_may_change(tree_changed: list, counted: list, ignored: list) -> bool:
|
||||
"""The mark. The tree hash moved and at least one changed file is NOT on the app's ignore list — or the tree
|
||||
hash moved and the per-file walk could not name any file (the two walks disagree: keep the honest mark)."""
|
||||
if not tree_changed:
|
||||
return False
|
||||
return bool(counted) or not ignored
|
||||
|
||||
|
||||
def changed_files(before: dict, after: dict) -> list:
|
||||
"""["<src>/<relpath> (added|removed|changed)"] between two bind_tree_files results."""
|
||||
out = []
|
||||
@@ -927,10 +984,16 @@ def run_edge(edge_id: str) -> dict:
|
||||
detail_after = bind_tree_files(project, workdir)
|
||||
(ev / "files-after-detail.json").write_text(json.dumps(detail_after, indent=2))
|
||||
rec["files_changed_detail"] = changed_files(detail_before, detail_after)[:200]
|
||||
if rec["files_changed"]:
|
||||
counted, ignored = classify_changes(app, detail_before, detail_after)
|
||||
rec["files_ignored"] = ignored[:200] # R-734: app-owned markers taken away, each with its reason
|
||||
if ignored:
|
||||
say(f"files ignored as app markers (MARKER_IGNORE, R-734): {[i['file'] for i in ignored][:20]}")
|
||||
if files_may_change(rec["files_changed"], counted, ignored):
|
||||
rec["marks"] = sorted(set(rec["marks"]) | {"files_may_change"})
|
||||
say(f"files_may_change — the files: {rec['files_changed_detail'][:20]}")
|
||||
say(f"files_may_change — the files: {(counted or rec['files_changed_detail'])[:20]}")
|
||||
say(f"files_may_change: the bind-mounted tree changed under {rec['files_changed']}")
|
||||
elif rec["files_changed"]:
|
||||
say("files_may_change NOT set: every changed file is an app marker on the ignore list")
|
||||
|
||||
# --- 5b. the MEMORY WATCH — only for an edge that just read back; a failed one is decided ---
|
||||
if rec["verdict"] == "proven" and SOAK_SECONDS > 0:
|
||||
|
||||
Reference in New Issue
Block a user